diff --git a/renderer/kdenlive_render.cpp b/renderer/kdenlive_render.cpp index 753c4f65c..cf4740278 100644 --- a/renderer/kdenlive_render.cpp +++ b/renderer/kdenlive_render.cpp @@ -1,160 +1,159 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "renderjob.h" #include #include #include #include #include #include #include int main(int argc, char **argv) { QCoreApplication app(argc, argv); QStringList args = app.arguments(); QStringList preargs; QString locale; if (args.count() >= 7) { int pid = 0; int in = -1; int out = -1; // Remove program name args.removeFirst(); bool erase = false; if (args.at(0) == QLatin1String("-erase")) { erase = true; args.removeFirst(); } bool usekuiserver = false; if (args.at(0) == QLatin1String("-kuiserver")) { usekuiserver = true; args.removeFirst(); } if (args.at(0).startsWith(QLatin1String("-pid:"))) { pid = args.at(0).section(QLatin1Char(':'), 1).toInt(); args.removeFirst(); } if (args.at(0).startsWith(QLatin1String("-locale:"))) { locale = args.at(0).section(QLatin1Char(':'), 1); args.removeFirst(); } if (args.at(0).startsWith(QLatin1String("in="))) { in = args.takeFirst().section(QLatin1Char('='), -1).toInt(); } if (args.at(0).startsWith(QLatin1String("out="))) { out = args.takeFirst().section(QLatin1Char('='), -1).toInt(); } if (args.at(0).startsWith(QLatin1String("preargs="))) { preargs = args.takeFirst().section(QLatin1Char('='), 1).split(QLatin1Char(' '), QString::SkipEmptyParts); } QString render = args.takeFirst(); QString profile = args.takeFirst(); QString rendermodule = args.takeFirst(); QString player = args.takeFirst(); QString srcString = args.takeFirst(); QUrl srcurl; if (srcString.startsWith(QLatin1String("consumer:"))) { srcurl = QUrl::fromEncoded(srcString.section(QLatin1Char(':'), 1).toUtf8().constData()); } else { srcurl = QUrl::fromEncoded(srcString.toUtf8().constData()); } QString src = srcurl.toLocalFile(); // The QUrl path() strips the consumer: protocol, so re-add it if necessary if (srcString.startsWith(QStringLiteral("consumer:"))) { src.prepend(QLatin1String("consumer:")); } QString dest = QFileInfo(QUrl::fromEncoded(args.takeFirst().toUtf8()).toLocalFile()).absoluteFilePath(); bool dualpass = false; bool doerase; QString vpre; int vprepos = args.indexOf(QRegExp(QLatin1String("vpre=.*"))); if (vprepos >= 0) { vpre = args.at(vprepos); } QStringList vprelist = vpre.remove(QStringLiteral("vpre=")).split(QLatin1Char(',')); if (!vprelist.isEmpty()) { args.replaceInStrings(QRegExp(QLatin1String("^vpre=.*")), QStringLiteral("vpre=%1").arg(vprelist.at(0))); } if (args.contains(QStringLiteral("pass=2"))) { // dual pass encoding dualpass = true; doerase = false; args.replace(args.indexOf(QStringLiteral("pass=2")), QStringLiteral("pass=1")); if (args.contains(QStringLiteral("vcodec=libx264"))) { args << QStringLiteral("passlogfile=%1").arg(dest + QStringLiteral(".log")); } } else { args.removeAll(QStringLiteral("pass=1")); doerase = erase; } // Decode metadata for (int i = 0; i < args.count(); ++i) { if (args.at(i).startsWith(QLatin1String("meta.attr"))) { QString data = args.at(i); - args.replace(i, - data.section(QLatin1Char('='), 0, 0) + QStringLiteral("=\"") + - QUrl::fromPercentEncoding(data.section(QLatin1Char('='), 1).toUtf8()) + QLatin1Char('\"')); + args.replace(i, data.section(QLatin1Char('='), 0, 0) + QStringLiteral("=\"") + + QUrl::fromPercentEncoding(data.section(QLatin1Char('='), 1).toUtf8()) + QLatin1Char('\"')); } } qDebug() << "//STARTING RENDERING: " << erase << ',' << usekuiserver << ',' << render << ',' << profile << ',' << rendermodule << ',' << player << ',' << src << ',' << dest << ',' << preargs << ',' << args << ',' << in << ',' << out; auto *job = new RenderJob(doerase, usekuiserver, pid, render, profile, rendermodule, player, src, dest, preargs, args, in, out); if (!locale.isEmpty()) { job->setLocale(locale); } job->start(); RenderJob *dualjob = nullptr; if (dualpass) { if (vprelist.size() > 1) { args.replaceInStrings(QRegExp(QLatin1String("^vpre=.*")), QStringLiteral("vpre=%1").arg(vprelist.at(1))); } args.replace(args.indexOf(QStringLiteral("pass=1")), QStringLiteral("pass=2")); dualjob = new RenderJob(erase, usekuiserver, pid, render, profile, rendermodule, player, src, dest, preargs, args, in, out); QObject::connect(job, &RenderJob::renderingFinished, dualjob, &RenderJob::start); } app.exec(); delete dualjob; } else { fprintf(stderr, "Kdenlive video renderer for MLT.\nUsage: " "kdenlive_render [-erase] [-kuiserver] [-locale:LOCALE] [in=pos] [out=pos] [render] [profile] [rendermodule] [player] [src] [dest] [[arg1] " "[arg2] ...]\n" " -erase: if that parameter is present, src file will be erased at the end\n" " -kuiserver: if that parameter is present, use KDE job tracker\n" " -locale:LOCALE : set a locale for rendering. For example, -locale:fr_FR.UTF-8 will use a french locale (comma as numeric separator)\n" " in=pos: start rendering at frame pos\n" " out=pos: end rendering at frame pos\n" " render: path to MLT melt renderer\n" " profile: the MLT video profile\n" " rendermodule: the MLT consumer used for rendering, usually it is avformat\n" " player: path to video player to play when rendering is over, use '-' to disable playing\n" " src: source file (usually MLT XML)\n" " dest: destination file\n" " args: space separated libavformat arguments\n"); } } diff --git a/src/assets/abstractassetsrepository.ipp b/src/assets/abstractassetsrepository.ipp index b1006b546..f82ba20ce 100644 --- a/src/assets/abstractassetsrepository.ipp +++ b/src/assets/abstractassetsrepository.ipp @@ -1,241 +1,239 @@ /*************************************************************************** * 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 "xml/xml.hpp" #include #include -#include #include +#include #include #include #ifdef Q_OS_MAC #include #endif -template AbstractAssetsRepository::AbstractAssetsRepository() -{ -} +template AbstractAssetsRepository::AbstractAssetsRepository() {} template void AbstractAssetsRepository::init() { // Warning: Mlt::Factory::init() resets the locale to the default system value, make sure we keep correct locale #ifndef Q_OS_MAC setlocale(LC_NUMERIC, nullptr); #else setlocale(LC_NUMERIC_MASK, nullptr); #endif // Parse effects blacklist parseBlackList(assetBlackListPath()); // Retrieve the list of MLT's available assets. QScopedPointer assets(retrieveListFromMlt()); int max = assets->count(); QString sox = QStringLiteral("sox."); for (int i = 0; i < max; ++i) { Info info; QString name = assets->get_name(i); info.id = name; if (name.startsWith(sox)) { // sox effects are not usage directly (parameters not available) continue; } if (!m_blacklist.contains(name) && parseInfoFromMlt(name, info)) { m_assets[name] = info; } else { if (m_blacklist.contains(name)) { qDebug() << name << "is blacklisted"; } else { qDebug() << "WARNING : Fails to parse " << name; } } } // We now parse custom effect xml // Set the directories to look into for effects. QStringList asset_dirs = assetDirs(); /* Parsing of custom xml works as follows: we parse all custom files. Each of them contains a tag, which is the corresponding mlt asset, and an id that is the name of the asset. Note that several custom files can correspond to the same tag, and in that case they must have different ids. We do the parsing in a map from ids to parse info, and then we add them to the asset list, while discarding the bare version of each tag (the one with no file associated) */ std::unordered_map customAssets; for (const auto &dir : asset_dirs) { QDir current_dir(dir); QStringList filter; filter << QStringLiteral("*.xml"); QStringList fileList = current_dir.entryList(filter, QDir::Files); for (const auto &file : fileList) { QString path = current_dir.absoluteFilePath(file); parseCustomAssetFile(path, customAssets); } } // We add the custom assets for (const auto &custom : customAssets) { // Custom assets should override default ones m_assets[custom.first] = custom.second; /*if (m_assets.count(custom.second.mltId) > 0) { m_assets.erase(custom.second.mltId); } if (m_assets.count(custom.first) == 0) { m_assets[custom.first] = custom.second; } else { qDebug() << "Error: conflicting asset name " << custom.first; }*/ } } template void AbstractAssetsRepository::parseBlackList(const QString &path) { QFile blacklist_file(path); if (blacklist_file.open(QIODevice::ReadOnly)) { QTextStream stream(&blacklist_file); QString line; while (stream.readLineInto(&line)) { line = line.simplified(); if (!line.isEmpty() && !line.startsWith('#')) { m_blacklist.insert(line); } } blacklist_file.close(); } } template bool AbstractAssetsRepository::parseInfoFromMlt(const QString &assetId, Info &res) { QScopedPointer metadata(getMetadata(assetId)); if (metadata && metadata->is_valid()) { if (metadata->get("title") && metadata->get("identifier") && strlen(metadata->get("title")) > 0) { res.name = metadata->get("title"); res.name[0] = res.name[0].toUpper(); res.description = metadata->get("description"); res.author = metadata->get("creator"); res.version_str = metadata->get("version"); res.version = metadata->get_double("version"); res.id = res.mltId = assetId; parseType(metadata, res); return true; } } return false; } template bool AbstractAssetsRepository::exists(const QString &assetId) const { return m_assets.count(assetId) > 0; } template QVector> AbstractAssetsRepository::getNames() const { QVector> res; res.reserve((int)m_assets.size()); for (const auto &asset : m_assets) { res.push_back({asset.first, asset.second.name}); } std::sort(res.begin(), res.end(), [](const QPair &a, const QPair &b) { return a.second < b.second; }); return res; } template AssetType AbstractAssetsRepository::getType(const QString &assetId) const { Q_ASSERT(m_assets.count(assetId) > 0); return m_assets.at(assetId).type; } template QString AbstractAssetsRepository::getName(const QString &assetId) const { Q_ASSERT(m_assets.count(assetId) > 0); return m_assets.at(assetId).name; } template QString AbstractAssetsRepository::getDescription(const QString &assetId) const { Q_ASSERT(m_assets.count(assetId) > 0); return m_assets.at(assetId).description; } template bool AbstractAssetsRepository::isFavorite(const QString & /*assetId*/) const { // TODO return true; } template bool AbstractAssetsRepository::parseInfoFromXml(const QDomElement ¤tAsset, Info &res) const { QLocale locale; // We first deal with locale if (currentAsset.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // set a locale for that effect locale = QLocale(currentAsset.attribute(QStringLiteral("LC_NUMERIC"))); } locale.setNumberOptions(QLocale::OmitGroupSeparator); QString tag = currentAsset.attribute(QStringLiteral("tag"), QString()); QString id = currentAsset.attribute(QStringLiteral("id"), QString()); if (id.isEmpty()) { id = tag; } if (!exists(tag)) { qDebug() << "++++++ Unknown asset : " << tag; return false; } // Check if there is a maximal version set if (currentAsset.hasAttribute(QStringLiteral("version"))) { // a specific version of the filter is required if (locale.toDouble(currentAsset.attribute(QStringLiteral("version"))) > m_assets.at(tag).version) { return false; } } res = m_assets.at(tag); res.id = id; res.mltId = tag; // Update description if the xml provide one QString description = Xml::getSubTagContent(currentAsset, QStringLiteral("description")); if (!description.isEmpty()) { res.description = description; } // Update name if the xml provide one QString name = Xml::getSubTagContent(currentAsset, QStringLiteral("name")); if (!name.isEmpty()) { res.name = name; } return true; } template QDomElement AbstractAssetsRepository::getXml(const QString &assetId) const { if (m_assets.count(assetId) == 0) { qDebug() << "Error : Requesting info on unknown transition " << assetId; return QDomElement(); } return m_assets.at(assetId).xml; } diff --git a/src/assets/assetlist/model/assetfilter.cpp b/src/assets/assetlist/model/assetfilter.cpp index 61a8027bb..1c9c23c54 100644 --- a/src/assets/assetlist/model/assetfilter.cpp +++ b/src/assets/assetlist/model/assetfilter.cpp @@ -1,177 +1,176 @@ /*************************************************************************** * 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 "assetfilter.hpp" #include "abstractmodel/abstracttreemodel.hpp" #include "abstractmodel/treeitem.hpp" #include "assettreemodel.hpp" #include AssetFilter::AssetFilter(QObject *parent) : QSortFilterProxyModel(parent) , m_name_enabled(false) { setFilterRole(Qt::DisplayRole); setSortRole(Qt::DisplayRole); setDynamicSortFilter(false); } void AssetFilter::setFilterName(bool enabled, const QString &pattern) { m_name_enabled = enabled; m_name_value = pattern; invalidateFilter(); if (rowCount() > 1) { sort(0); } } bool AssetFilter::filterName(const std::shared_ptr &item) const { if (!m_name_enabled) { return true; } QString itemText = item->dataColumn(AssetTreeModel::nameCol).toString(); itemText = itemText.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]"))); QString patt = m_name_value.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]"))); return itemText.contains(patt, Qt::CaseInsensitive); } bool AssetFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex row = sourceModel()->index(sourceRow, 0, sourceParent); auto *model = static_cast(sourceModel()); std::shared_ptr item = model->getItemById((int)row.internalId()); if (item->dataColumn(AssetTreeModel::idCol) == QStringLiteral("root")) { // In that case, we have a category. We hide it if it does not have children. QModelIndex category = sourceModel()->index(sourceRow, 0, sourceParent); if (!category.isValid()) { return false; } bool accepted = false; for (int i = 0; i < sourceModel()->rowCount(category) && !accepted; ++i) { accepted = filterAcceptsRow(i, category); } return accepted; } return applyAll(item); } bool AssetFilter::isVisible(const QModelIndex &sourceIndex) { auto parent = sourceModel()->parent(sourceIndex); return filterAcceptsRow(sourceIndex.row(), parent); } bool AssetFilter::applyAll(std::shared_ptr item) const { return filterName(std::move(item)); } QModelIndex AssetFilter::getNextChild(const QModelIndex ¤t) { QModelIndex nextItem = current.sibling(current.row() + 1, current.column()); if (!nextItem.isValid()) { QModelIndex folder = index(current.parent().row() + 1, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = folder.sibling(folder.row() + 1, folder.column()); } if (folder.isValid() && rowCount(folder) > 0) { return index(0, current.column(), folder); } nextItem = current; } return nextItem; } QModelIndex AssetFilter::getPreviousChild(const QModelIndex ¤t) { QModelIndex nextItem = current.sibling(current.row() - 1, current.column()); if (!nextItem.isValid()) { QModelIndex folder = index(current.parent().row() - 1, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = folder.sibling(folder.row() - 1, folder.column()); } if (folder.isValid() && rowCount(folder) > 0) { return index(rowCount(folder) - 1, current.column(), folder); } nextItem = current; } return nextItem; } QModelIndex AssetFilter::firstVisibleItem(const QModelIndex ¤t) { if (current.isValid() && isVisible(mapToSource(current))) { return current; } QModelIndex folder = index(0, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = index(folder.row() + 1, 0, QModelIndex()); } if (rowCount(folder) > 0) { return index(0, 0, folder); } return current; } QModelIndex AssetFilter::getCategory(int catRow) { QModelIndex cat = index(catRow, 0, QModelIndex()); return cat; } QVariantList AssetFilter::getCategories() { QVariantList list; for (int i = 0; i < sourceModel()->rowCount(); i++) { QModelIndex cat = getCategory(i); if (cat.isValid()) { list << cat; } } return list; } QModelIndex AssetFilter::getModelIndex(QModelIndex current) { QModelIndex sourceIndex = mapToSource(current); - return sourceIndex;//this returns an integer + return sourceIndex; // this returns an integer } QModelIndex AssetFilter::getProxyIndex(QModelIndex current) { QModelIndex sourceIndex = mapFromSource(current); - return sourceIndex;//this returns an integer + return sourceIndex; // this returns an integer } - diff --git a/src/assets/assetpanel.cpp b/src/assets/assetpanel.cpp index e7cb89d91..bd6045c75 100644 --- a/src/assets/assetpanel.cpp +++ b/src/assets/assetpanel.cpp @@ -1,296 +1,295 @@ /*************************************************************************** * 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 "assetpanel.hpp" #include "core.cpp" #include "definitions.h" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "effects/effectstack/view/effectstackview.hpp" #include "kdenlivesettings.h" #include "model/assetparametermodel.hpp" #include "transitions/transitionsrepository.hpp" #include "transitions/view/transitionstackview.hpp" #include "utils/KoIconUtils.h" #include "view/assetparameterview.hpp" #include #include #include #include #include #include #include #include #include #include AssetPanel::AssetPanel(QWidget *parent) : QWidget(parent) , m_lay(new QVBoxLayout(this)) , m_assetTitle(new KSqueezedTextLabel(this)) , m_container(new QWidget(this)) , m_transitionWidget(new TransitionStackView(this)) , m_effectStackWidget(new EffectStackView(this)) { QHBoxLayout *tLayout = new QHBoxLayout; tLayout->addWidget(m_assetTitle); m_switchBuiltStack = new QToolButton(this); m_switchBuiltStack->setIcon(KoIconUtils::themedIcon(QStringLiteral("adjustlevels"))); m_switchBuiltStack->setToolTip(i18n("Adjust clip")); m_switchBuiltStack->setCheckable(true); m_switchBuiltStack->setChecked(KdenliveSettings::showbuiltstack()); m_switchBuiltStack->setVisible(false); - //connect(m_switchBuiltStack, &QToolButton::toggled, m_effectStackWidget, &EffectStackView::switchBuiltStack); + // connect(m_switchBuiltStack, &QToolButton::toggled, m_effectStackWidget, &EffectStackView::switchBuiltStack); tLayout->addWidget(m_switchBuiltStack); m_splitButton = new QToolButton(this); m_splitButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-split-left-right"))); m_splitButton->setToolTip(i18n("Compare effect")); m_splitButton->setCheckable(true); m_splitButton->setVisible(false); connect(m_splitButton, &QToolButton::toggled, this, &AssetPanel::processSplitEffect); tLayout->addWidget(m_splitButton); m_timelineButton = new QToolButton(this); m_timelineButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("adjustlevels"))); m_timelineButton->setToolTip(i18n("Display keyframes in timeline")); m_timelineButton->setCheckable(true); m_timelineButton->setVisible(false); connect(m_timelineButton, &QToolButton::toggled, this, &AssetPanel::showKeyframes); tLayout->addWidget(m_timelineButton); m_lay->addLayout(tLayout); m_lay->setContentsMargins(0, 0, 0, 0); QVBoxLayout *lay = new QVBoxLayout(m_container); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget(m_transitionWidget); lay->addWidget(m_effectStackWidget); QScrollArea *sc = new QScrollArea; sc->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); sc->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); sc->setFrameStyle(QFrame::NoFrame); sc->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding)); m_container->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding)); sc->setWidgetResizable(true); m_lay->addWidget(sc); sc->setWidget(m_container); m_transitionWidget->setVisible(false); m_effectStackWidget->setVisible(false); updatePalette(); connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos); connect(m_transitionWidget, &TransitionStackView::seekToTransPos, this, &AssetPanel::seekToPos); } void AssetPanel::showTransition(int tid, std::shared_ptr transitionModel) { clear(); QString transitionId = transitionModel->getAssetId(); QString transitionName = TransitionsRepository::get()->getName(transitionId); m_assetTitle->setText(i18n("%1 properties", transitionName)); m_transitionWidget->setVisible(true); m_timelineButton->setVisible(true); m_transitionWidget->setModel(transitionModel, QSize(), true); } -void AssetPanel::showEffectStack(const QString &itemName, std::shared_ptr effectsModel, QSize frameSize, - bool showKeyframes) +void AssetPanel::showEffectStack(const QString &itemName, std::shared_ptr effectsModel, QSize frameSize, bool showKeyframes) { if (effectsModel == nullptr) { // Item is not ready clear(); return; } ObjectId id = effectsModel->getOwnerId(); if (m_effectStackWidget->stackOwner() == id) { // already on this effect stack, do nothing return; } clear(); QString title; bool showSplit = false; bool enableKeyframes = false; switch (id.first) { case ObjectType::TimelineClip: title = i18n("%1 effects", itemName); showSplit = true; enableKeyframes = true; break; case ObjectType::TimelineComposition: title = i18n("%1 parameters", itemName); enableKeyframes = true; break; case ObjectType::TimelineTrack: title = i18n("Track %1 effects", itemName); // TODO: track keyframes // enableKeyframes = true; break; case ObjectType::BinClip: title = i18n("Bin %1 effects", itemName); showSplit = true; break; default: title = itemName; break; } m_assetTitle->setText(title); m_splitButton->setVisible(showSplit); m_timelineButton->setVisible(enableKeyframes); m_timelineButton->setChecked(showKeyframes); // Disable built stack until properly implemented // m_switchBuiltStack->setVisible(true); m_effectStackWidget->setVisible(true); m_effectStackWidget->setModel(effectsModel, frameSize); } void AssetPanel::clearAssetPanel(int itemId) { ObjectId id = m_effectStackWidget->stackOwner(); if (id.first == ObjectType::TimelineClip && id.second == itemId) { clear(); } else { id = m_transitionWidget->stackOwner(); if (id.first == ObjectType::TimelineComposition && id.second == itemId) { clear(); } } } void AssetPanel::clear() { m_transitionWidget->setVisible(false); m_transitionWidget->unsetModel(); m_effectStackWidget->setVisible(false); m_splitButton->setVisible(false); m_timelineButton->setVisible(false); m_switchBuiltStack->setVisible(false); m_effectStackWidget->unsetModel(); m_assetTitle->setText(QString()); } void AssetPanel::updatePalette() { QString styleSheet = getStyleSheet(); setStyleSheet(styleSheet); m_transitionWidget->setStyleSheet(styleSheet); m_effectStackWidget->setStyleSheet(styleSheet); } // static const QString AssetPanel::getStyleSheet() { KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color(); QColor hgh = KColorUtils::mix(QApplication::palette().window().color(), selected_bg, 0.2); QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color(); QColor light_bg = scheme.shade(KColorScheme::LightShade); QColor alt_bg = scheme.background(KColorScheme::NormalBackground).color(); QString stylesheet; // effect background stylesheet.append(QStringLiteral("QFrame#decoframe {border-bottom:2px solid " "palette(mid);background: transparent} QFrame#decoframe[active=\"true\"] {background: %1;}") .arg(hgh.name())); // effect in group background stylesheet.append( QStringLiteral("QFrame#decoframesub {border-top:1px solid palette(light);} QFrame#decoframesub[active=\"true\"] {background: %1;}").arg(hgh.name())); // group background stylesheet.append(QStringLiteral("QFrame#decoframegroup {border:2px solid palette(dark);margin:0px;margin-top:2px;} ")); // effect title bar stylesheet.append(QStringLiteral("QFrame#frame {margin-bottom:2px;} QFrame#frame[target=\"true\"] " "{background: palette(highlight);}")); // group effect title bar stylesheet.append(QStringLiteral("QFrame#framegroup {background: palette(dark);} " "QFrame#framegroup[target=\"true\"] {background: palette(highlight);} ")); // draggable effect bar content stylesheet.append(QStringLiteral("QProgressBar::chunk:horizontal {background: palette(button);border-top-left-radius: 4px;border-bottom-left-radius: 4px;} " "QProgressBar::chunk:horizontal#dragOnly {background: %1;border-top-left-radius: 4px;border-bottom-left-radius: 4px;} " "QProgressBar::chunk:horizontal:hover {background: %2;}") .arg(alt_bg.name(), selected_bg.name())); // draggable effect bar stylesheet.append(QStringLiteral("QProgressBar:horizontal {border: 1px solid palette(dark);border-top-left-radius: 4px;border-bottom-left-radius: " "4px;border-right:0px;background:%3;padding: 0px;text-align:left center} QProgressBar:horizontal:disabled {border: 1px " "solid palette(button)} QProgressBar:horizontal#dragOnly {background: %3} QProgressBar:horizontal[inTimeline=\"true\"] { " "border: 1px solid %1;border-right: 0px;background: %2;padding: 0px;text-align:left center } " "QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %1;}") .arg(hover_bg.name(), light_bg.name(), alt_bg.name())); // spin box for draggable widget stylesheet.append( QStringLiteral("QAbstractSpinBox#dragBox {border: 1px solid palette(dark);border-top-right-radius: 4px;border-bottom-right-radius: " "4px;padding-right:0px;} QAbstractSpinBox::down-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox:disabled#dragBox {border: 1px " "solid palette(button);} QAbstractSpinBox::up-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox[inTimeline=\"true\"]#dragBox { " "border: 1px solid %1;} QAbstractSpinBox:hover#dragBox {border: 1px solid %2;} ") .arg(hover_bg.name(), selected_bg.name())); // group editable labels stylesheet.append(QStringLiteral("MyEditableLabel { background-color: transparent; color: palette(bright-text); border-radius: 2px;border: 1px solid " "transparent;} MyEditableLabel:hover {border: 1px solid palette(highlight);} ")); // transparent qcombobox stylesheet.append(QStringLiteral("QComboBox { background-color: transparent;} ")); return stylesheet; } void AssetPanel::processSplitEffect(bool enable) { ObjectType id = m_effectStackWidget->stackOwner().first; if (id == ObjectType::TimelineClip) { emit doSplitEffect(enable); } else if (id == ObjectType::BinClip) { emit doSplitBinEffect(enable); } } void AssetPanel::showKeyframes(bool enable) { if (m_transitionWidget->isVisible()) { pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable); } else { pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable); } } ObjectId AssetPanel::effectStackOwner() { if (m_transitionWidget->isVisible()) { return m_transitionWidget->stackOwner(); } if (!m_effectStackWidget->isVisible()) { return ObjectId(ObjectType::NoItem, -1); } return m_effectStackWidget->stackOwner(); } void AssetPanel::parameterChanged(QString name, int value) { Q_UNUSED(name) emit changeSpeed(value); } diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp index 73e1bd619..f681175ad 100644 --- a/src/assets/keyframes/model/keyframemodel.cpp +++ b/src/assets/keyframes/model/keyframemodel.cpp @@ -1,804 +1,804 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "keyframemodel.hpp" #include "core.h" #include "doc/docundostack.hpp" #include "macros.hpp" #include #include KeyframeModel::KeyframeModel(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent) : QAbstractListModel(parent) , m_model(std::move(model)) , m_undoStack(std::move(undo_stack)) , m_index(index) , m_lastData() , m_lock(QReadWriteLock::Recursive) { qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired(); if (auto ptr = m_model.lock()) { m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value(); } setup(); refresh(); } void KeyframeModel::setup() { // We connect the signals of the abstractitemmodel to a more generic one. connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::dataChanged, this, &KeyframeModel::modelChanged); connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification); } bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo) { qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify; QWriteLocker locker(&m_lock); Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; if (m_keyframeList.count(pos) > 0) { qDebug() << "already there"; if (std::pair({type, value}) == m_keyframeList.at(pos)) { qDebug() << "nothing to do"; return true; // nothing to do } // In this case we simply change the type and value KeyframeType oldType = m_keyframeList[pos].first; QVariant oldValue = m_keyframeList[pos].second; local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify); local_redo = updateKeyframe_lambda(pos, type, value, notify); } else { qDebug() << "True addittion"; local_redo = addKeyframe_lambda(pos, type, value, notify); local_undo = deleteKeyframe_lambda(pos, notify); } if (local_redo()) { UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } return false; } bool KeyframeModel::addKeyframe(int frame, double normalizedValue) { if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble(); double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble(); double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble(); double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble(); int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt(); double realValue; if (logRole == -1) { // Logarythmic scale for lower than norm values if (normalizedValue >= 0.5) { realValue = norm + (2 * (normalizedValue - 0.5) * (max / factor - norm)); } else { - realValue = norm - pow(2 * (0.5 - normalizedValue), 10.0/6) * (norm - min / factor); + realValue = norm - pow(2 * (0.5 - normalizedValue), 10.0 / 6) * (norm - min / factor); } } else { realValue = (normalizedValue * (max - min) + min) / factor; } // TODO: Use default configurable kf type return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, realValue); } return false; } bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value) { QWriteLocker locker(&m_lock); Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool update = (m_keyframeList.count(pos) > 0); bool res = addKeyframe(pos, type, value, true, undo, redo); if (res) { PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe")); } return res; } bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo) { qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()); qDebug() << "before" << getAnimProperty(); QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType oldType = m_keyframeList[pos].first; QVariant oldValue = m_keyframeList[pos].second; Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, true); Fun local_redo = deleteKeyframe_lambda(pos, true); qDebug() << "before2" << getAnimProperty(); if (local_redo()) { UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } return false; } bool KeyframeModel::removeKeyframe(int frame) { GenTime pos(frame, pCore->getCurrentFps()); return removeKeyframe(pos); } bool KeyframeModel::removeKeyframe(GenTime pos) { QWriteLocker locker(&m_lock); Fun undo = []() { return true; }; Fun redo = []() { return true; }; if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) { return false; // initial point must stay } bool res = removeKeyframe(pos, undo, redo); if (res) { PUSH_UNDO(undo, redo, i18n("Delete keyframe")); } return res; } bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, double newVal, Fun &undo, Fun &redo) { qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps()); QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(oldPos) > 0); KeyframeType oldType = m_keyframeList[oldPos].first; QVariant oldValue = m_keyframeList[oldPos].second; if (oldPos == pos) return true; if (hasKeyframe(pos)) return false; Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; qDebug() << getAnimProperty(); bool res = removeKeyframe(oldPos, local_undo, local_redo); qDebug() << "Move keyframe finished deletion:" << res; qDebug() << getAnimProperty(); if (res) { if (newVal > -1) { if (auto ptr = m_model.lock()) { double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble(); double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble(); double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble(); double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble(); int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt(); double realValue; if (logRole == -1) { // Logarythmic scale for lower than norm values if (newVal >= 0.5) { realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm)); } else { - realValue = norm - pow(2 * (0.5 - newVal), 10.0/6) * (norm - min / factor); + realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor); } } else { realValue = (newVal * (max - min) + min) / factor; } res = addKeyframe(pos, oldType, realValue, true, local_undo, local_redo); } } else { res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo); } qDebug() << "Move keyframe finished insertion:" << res; qDebug() << getAnimProperty(); } if (res) { UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); } else { bool undone = local_undo(); Q_ASSERT(undone); } return res; } bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo) { GenTime oPos(oldPos, pCore->getCurrentFps()); GenTime nPos(pos, pCore->getCurrentFps()); return moveKeyframe(oPos, nPos, -1, logUndo); } bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo) { if (oldPos == pos) return true; GenTime oldFrame(oldPos, pCore->getCurrentFps()); Q_ASSERT(m_keyframeList.count(oldFrame) > 0); GenTime diff(pos - oldPos, pCore->getCurrentFps()); QWriteLocker locker(&m_lock); Fun undo = []() { return true; }; Fun redo = []() { return true; }; - QList times; + QList times; for (const auto &m : m_keyframeList) { if (m.first < oldFrame) continue; times << m.first; } bool res; for (const auto &t : times) { res = moveKeyframe(t, t + diff, -1, undo, redo); } if (res && logUndo) { PUSH_UNDO(undo, redo, i18n("Move keyframes")); } return res; } bool KeyframeModel::moveKeyframe(int oldPos, int pos, double newVal) { GenTime oPos(oldPos, pCore->getCurrentFps()); GenTime nPos(pos, pCore->getCurrentFps()); return moveKeyframe(oPos, nPos, newVal, true); } bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, double newVal, bool logUndo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(oldPos) > 0); if (oldPos == pos) return true; Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = moveKeyframe(oldPos, pos, newVal, undo, redo); if (res && logUndo) { PUSH_UNDO(undo, redo, i18n("Move keyframe")); } return res; } bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType type = m_keyframeList[pos].first; QVariant oldValue = m_keyframeList[pos].second; // Chek if keyframe is different if (m_paramType == ParamType::KeyframeParam) { if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true; } auto operation = updateKeyframe_lambda(pos, type, value, true); auto reverse = updateKeyframe_lambda(pos, type, oldValue, true); bool res = operation(); if (res) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return res; } bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = updateKeyframe(pos, value, undo, redo); if (res) { PUSH_UNDO(undo, redo, i18n("Update keyframe")); } return res; } KeyframeType convertFromMltType(mlt_keyframe_type type) { switch (type) { case mlt_keyframe_linear: return KeyframeType::Linear; case mlt_keyframe_discrete: return KeyframeType::Discrete; case mlt_keyframe_smooth: return KeyframeType::Curve; } return KeyframeType::Linear; } bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType oldType = m_keyframeList[pos].first; KeyframeType newType = convertFromMltType((mlt_keyframe_type)type); QVariant value = m_keyframeList[pos].second; // Check if keyframe is different if (m_paramType == ParamType::KeyframeParam) { if (oldType == newType) return true; } auto operation = updateKeyframe_lambda(pos, newType, value, true); auto reverse = updateKeyframe_lambda(pos, oldType, value, true); bool res = operation(); if (res) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return res; } Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify) { QWriteLocker locker(&m_lock); return [this, pos, type, value, notify]() { qDebug() << "udpate lambda" << pos.frames(pCore->getCurrentFps()) << value << notify; Q_ASSERT(m_keyframeList.count(pos) > 0); int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos))); m_keyframeList[pos].first = type; m_keyframeList[pos].second = value; if (notify) emit dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole}); return true; }; } Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify) { QWriteLocker locker(&m_lock); return [this, notify, pos, type, value]() { qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify; Q_ASSERT(m_keyframeList.count(pos) == 0); // We determine the row of the newly added marker auto insertionIt = m_keyframeList.lower_bound(pos); int insertionRow = static_cast(m_keyframeList.size()); if (insertionIt != m_keyframeList.end()) { insertionRow = static_cast(std::distance(m_keyframeList.begin(), insertionIt)); } if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow); m_keyframeList[pos].first = type; m_keyframeList[pos].second = value; if (notify) endInsertRows(); return true; }; } Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify) { QWriteLocker locker(&m_lock); return [this, pos, notify]() { qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify; qDebug() << "before" << getAnimProperty(); Q_ASSERT(m_keyframeList.count(pos) > 0); Q_ASSERT(pos != GenTime()); // cannot delete initial point int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos))); if (notify) beginRemoveRows(QModelIndex(), row, row); m_keyframeList.erase(pos); if (notify) endRemoveRows(); qDebug() << "after" << getAnimProperty(); return true; }; } QHash KeyframeModel::roleNames() const { QHash roles; roles[PosRole] = "position"; roles[FrameRole] = "frame"; roles[TypeRole] = "type"; roles[ValueRole] = "value"; roles[NormalizedValueRole] = "normalizedValue"; return roles; } QVariant KeyframeModel::data(const QModelIndex &index, int role) const { READ_LOCK(); if (index.row() < 0 || index.row() >= static_cast(m_keyframeList.size()) || !index.isValid()) { return QVariant(); } auto it = m_keyframeList.begin(); std::advance(it, index.row()); switch (role) { case Qt::DisplayRole: case Qt::EditRole: case ValueRole: return it->second.second; case NormalizedValueRole: { if (m_paramType == ParamType::AnimatedRect) { const QString &data = it->second.second.toString(); QLocale locale; return locale.toDouble(data.section(QLatin1Char(' '), -1)); } double val = it->second.second.toDouble(); if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble(); double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble(); double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble(); double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble(); int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt(); double linear = val * factor; if (logRole == -1) { // Logarythmic scale for lower than norm values if (linear >= norm) { return 0.5 + (linear - norm) / (max * factor - norm) * 0.5; } // transform current value to 0..1 scale double scaled = (linear - norm) / (min * factor - norm); // Log scale return 0.5 - pow(scaled, 0.6) * 0.5; } return (linear - min) / (max - min); } else { qDebug() << "// CANNOT LOCK effect MODEL"; } return 1; } case PosRole: return it->first.seconds(); case FrameRole: case Qt::UserRole: return it->first.frames(pCore->getCurrentFps()); case TypeRole: return QVariant::fromValue(it->second.first); } return QVariant(); } int KeyframeModel::rowCount(const QModelIndex &parent) const { READ_LOCK(); if (parent.isValid()) return 0; return static_cast(m_keyframeList.size()); } bool KeyframeModel::singleKeyframe() const { READ_LOCK(); return m_keyframeList.size() <= 1; } Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const { READ_LOCK(); if (m_keyframeList.count(pos) <= 0) { // return empty marker *ok = false; return {GenTime(), KeyframeType::Linear}; } *ok = true; return {pos, m_keyframeList.at(pos).first}; } Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const { auto it = m_keyframeList.upper_bound(pos); if (it == m_keyframeList.end()) { // return empty marker *ok = false; return {GenTime(), KeyframeType::Linear}; } *ok = true; return {(*it).first, (*it).second.first}; } Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const { auto it = m_keyframeList.lower_bound(pos); if (it == m_keyframeList.begin()) { // return empty marker *ok = false; return {GenTime(), KeyframeType::Linear}; } --it; *ok = true; return {(*it).first, (*it).second.first}; } Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const { if (m_keyframeList.count(pos) > 0) { return getKeyframe(pos, ok); } bool ok1, ok2; auto next = getNextKeyframe(pos, &ok1); auto prev = getPrevKeyframe(pos, &ok2); *ok = ok1 || ok2; if (ok1 && ok2) { double fps = pCore->getCurrentFps(); if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) { return next; } return prev; } else if (ok1) { return next; } else if (ok2) { return prev; } // return empty marker return {GenTime(), KeyframeType::Linear}; } bool KeyframeModel::hasKeyframe(int frame) const { return hasKeyframe(GenTime(frame, pCore->getCurrentFps())); } bool KeyframeModel::hasKeyframe(const GenTime &pos) const { READ_LOCK(); return m_keyframeList.count(pos) > 0; } bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); std::vector all_pos; Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; for (const auto &m : m_keyframeList) { all_pos.push_back(m.first); } bool res = true; bool first = true; for (const auto &p : all_pos) { if (first) { // skip first point first = false; continue; } res = removeKeyframe(p, local_undo, local_redo); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } bool KeyframeModel::removeAllKeyframes() { QWriteLocker locker(&m_lock); Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = removeAllKeyframes(undo, redo); if (res) { PUSH_UNDO(undo, redo, i18n("Delete all keyframes")); } return res; } QString KeyframeModel::getAnimProperty() const { QString prop; bool first = true; QLocale locale; for (const auto keyframe : m_keyframeList) { if (first) { first = false; } else { prop += QStringLiteral(";"); } prop += QString::number(keyframe.first.frames(pCore->getCurrentFps())); switch (keyframe.second.first) { case KeyframeType::Linear: prop += QStringLiteral("="); break; case KeyframeType::Discrete: prop += QStringLiteral("|="); break; case KeyframeType::Curve: prop += QStringLiteral("~="); break; } switch (m_paramType) { case ParamType::AnimatedRect: prop += keyframe.second.second.toString(); break; default: prop += locale.toString(keyframe.second.second.toDouble()); break; } } return prop; } mlt_keyframe_type convertToMltType(KeyframeType type) { switch (type) { case KeyframeType::Linear: return mlt_keyframe_linear; case KeyframeType::Discrete: return mlt_keyframe_discrete; case KeyframeType::Curve: return mlt_keyframe_smooth; } return mlt_keyframe_linear; } void KeyframeModel::parseAnimProperty(const QString &prop) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; Mlt::Properties mlt_prop; mlt_prop.set("key", prop.toUtf8().constData()); // This is a fake query to force the animation to be parsed (void)mlt_prop.anim_get_int("key", 0, 0); Mlt::Animation *anim = mlt_prop.get_anim("key"); qDebug() << "Found" << anim->key_count() << "animation properties"; for (int i = 0; i < anim->key_count(); ++i) { int frame; mlt_keyframe_type type; anim->key_get(i, frame, type); if (!prop.contains(QLatin1Char('='))) { // TODO: use a default user defined type type = mlt_keyframe_linear; } QVariant value; switch (m_paramType) { - case ParamType::AnimatedRect: { - mlt_rect rect = mlt_prop.anim_get_rect("key", frame); - value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(rect.o)); - break; - } - default: - value = QVariant(mlt_prop.anim_get_double("key", frame)); - break; + case ParamType::AnimatedRect: { + mlt_rect rect = mlt_prop.anim_get_rect("key", frame); + value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(rect.o)); + break; + } + default: + value = QVariant(mlt_prop.anim_get_double("key", frame)); + break; } addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo); } delete anim; /* std::vector > separators({QStringLiteral("="), QStringLiteral("|="), QStringLiteral("~=")}); QStringList list = prop.split(';', QString::SkipEmptyParts); for (const auto& k : list) { bool found = false; KeyframeType type; QStringList values; for (const auto &sep : separators) { if (k.contains(sep.first)) { found = true; type = sep.second; values = k.split(sep.first); break; } } if (!found || values.size() != 2) { qDebug() << "ERROR while parsing value of keyframe"<getCurrentFps()); return getInterpolatedValue(pos); } QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const { int p = pos.frames(pCore->getCurrentFps()); if (m_keyframeList.count(pos) > 0) { return m_keyframeList.at(pos).second; } auto next = m_keyframeList.upper_bound(pos); if (next == m_keyframeList.cbegin()) { return (m_keyframeList.cbegin())->second.second; } else if (next == m_keyframeList.cend()) { auto it = m_keyframeList.cend(); --it; return it->second.second; } auto prev = next; --prev; // We now have surrounding keyframes, we use mlt to compute the value Mlt::Properties prop; QLocale locale; if (m_paramType == ParamType::KeyframeParam) { prop.anim_set("keyframe", prev->second.second.toDouble(), prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()), convertToMltType(prev->second.first)); prop.anim_set("keyframe", next->second.second.toDouble(), next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()), convertToMltType(next->second.first)); return QVariant(prop.anim_get_double("keyframe", p)); } else if (m_paramType == ParamType::AnimatedRect) { mlt_rect rect; QStringList vals = prev->second.second.toString().split(QLatin1Char(' ')); if (vals.count() >= 4) { rect.x = vals.at(0).toInt(); rect.y = vals.at(1).toInt(); rect.w = vals.at(2).toInt(); rect.h = vals.at(3).toInt(); if (vals.count() > 4) { rect.o = locale.toDouble(vals.at(4)); } else { rect.o = 1; } } prop.anim_set("keyframe", rect, prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()), convertToMltType(prev->second.first)); vals = next->second.second.toString().split(QLatin1Char(' ')); if (vals.count() >= 4) { rect.x = vals.at(0).toInt(); rect.y = vals.at(1).toInt(); rect.w = vals.at(2).toInt(); rect.h = vals.at(3).toInt(); if (vals.count() > 4) { rect.o = locale.toDouble(vals.at(4)); } else { rect.o = 1; } } prop.anim_set("keyframe", rect, next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()), convertToMltType(next->second.first)); rect = prop.anim_get_rect("keyframe", p); const QString res = QStringLiteral("%1 %2 %3 %4 %5").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h).arg(rect.o); return QVariant(res); } return QVariant(); } void KeyframeModel::sendModification() { if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString(); QString data; if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) { data = getAnimProperty(); ptr->setParameter(name, data); } else { Q_ASSERT(false); // Not implemented, TODO } m_lastData = data; ptr->dataChanged(m_index, m_index); } } void KeyframeModel::refresh() { if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); QString data = ptr->data(m_index, AssetParameterModel::ValueRole).toString(); if (data == m_lastData) { // nothing to do return; } if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) { qDebug() << "parsing keyframe" << data; parseAnimProperty(data); } else { // first, try to convert to double bool ok = false; double value = data.toDouble(&ok); if (ok) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo); qDebug() << "KEYFRAME ADDED" << value; } else { Q_ASSERT(false); // Not implemented, TODO } } } else { qDebug() << "WARNING : unable to access keyframe's model"; } } diff --git a/src/assets/keyframes/model/keyframemodel.hpp b/src/assets/keyframes/model/keyframemodel.hpp index 518711f6b..02b601686 100644 --- a/src/assets/keyframes/model/keyframemodel.hpp +++ b/src/assets/keyframes/model/keyframemodel.hpp @@ -1,200 +1,200 @@ /*************************************************************************** * 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 KEYFRAMELISTMODEL_H #define KEYFRAMELISTMODEL_H #include "assets/model/assetparametermodel.hpp" #include "definitions.h" #include "gentime.h" #include "undohelper.hpp" #include #include #include #include class AssetParameterModel; class DocUndoStack; class EffectItemModel; /* @brief This class is the model for a list of keyframes. A keyframe is defined by a time, a type and a value We store them in a sorted fashion using a std::map */ enum class KeyframeType { Linear = 0, Discrete, Curve }; Q_DECLARE_METATYPE(KeyframeType) using Keyframe = std::pair; class KeyframeModel : public QAbstractListModel { Q_OBJECT public: /* @brief Construct a keyframe list bound to the given effect @param init_value is the value taken by the param at time 0. @param model is the asset this parameter belong to @param index is the index of this parameter in its model */ explicit KeyframeModel(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent = nullptr); enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole, NormalizedValueRole }; friend class KeyframeModelList; /* @brief Adds a keyframe at the given position. If there is already one then we update it. @param pos defines the position of the keyframe, relative to the clip @param type is the type of the keyframe. */ bool addKeyframe(GenTime pos, KeyframeType type, QVariant value); Q_INVOKABLE bool addKeyframe(int frame, double normalizedValue); protected: /* @brief Same function but accumulates undo/redo @param notify: if true, send a signal to model */ bool addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo); public: /* @brief Removes the keyframe at the given position. */ Q_INVOKABLE bool removeKeyframe(int frame); Q_INVOKABLE bool moveKeyframe(int oldPos, int pos, double newVal); bool removeKeyframe(GenTime pos); /* @brief Delete all the keyframes of the model */ bool removeAllKeyframes(); bool removeAllKeyframes(Fun &undo, Fun &redo); protected: /* @brief Same function but accumulates undo/redo */ bool removeKeyframe(GenTime pos, Fun &undo, Fun &redo); public: /* @brief moves a keyframe @param oldPos is the old position of the keyframe @param pos defines the new position of the keyframe, relative to the clip @param logUndo if true, then an undo object is created */ Q_INVOKABLE bool moveKeyframe(int oldPos, int pos, bool logUndo); Q_INVOKABLE bool offsetKeyframes(int oldPos, int pos, bool logUndo); bool moveKeyframe(GenTime oldPos, GenTime pos, double newVal, bool logUndo); bool moveKeyframe(GenTime oldPos, GenTime pos, double newVal, Fun &undo, Fun &redo); /* @brief updates the value of a keyframe @param old is the position of the keyframe @param value is the new value of the param */ bool updateKeyframe(GenTime pos, QVariant value); bool updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo); bool updateKeyframe(GenTime pos, QVariant value, Fun &undo, Fun &redo); /* @brief Returns a keyframe data at given pos ok is a return parameter, set to true if everything went good */ Keyframe getKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns true if we only have 1 keyframe - */ + */ bool singleKeyframe() const; /* @brief Returns the keyframe located after given position. If there is a keyframe at given position it is ignored. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getNextKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns the keyframe located before given position. If there is a keyframe at given position it is ignored. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getPrevKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns the closest keyframe from given position. @param ok is a return parameter to tell if a keyframe was found. */ Keyframe getClosestKeyframe(const GenTime &pos, bool *ok) const; /* @brief Returns true if a keyframe exists at given pos Notice that add/remove queries are done in real time (gentime), but this request is made in frame */ Q_INVOKABLE bool hasKeyframe(int frame) const; Q_INVOKABLE bool hasKeyframe(const GenTime &pos) const; /* @brief Read the value from the model and update itself accordingly */ void refresh(); /* @brief Return the interpolated value at given pos */ QVariant getInterpolatedValue(int pos) const; QVariant getInterpolatedValue(const GenTime &pos) const; // Mandatory overloads Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: /** @brief Helper function that generate a lambda to change type / value of given keyframe */ Fun updateKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify); /** @brief Helper function that generate a lambda to add given keyframe */ Fun addKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify); /** @brief Helper function that generate a lambda to remove given keyframe */ Fun deleteKeyframe_lambda(GenTime pos, bool notify); /* @brief Connects the signals of this object */ void setup(); /* @brief Commit the modification to the model */ void sendModification(); /** @brief returns the keyframes as a Mlt Anim Property string. It is defined as pairs of frame and value, separated by ; Example : "0|=50; 50|=100; 100=200; 200~=60;" Spaces are ignored by Mlt. |= represents a discrete keyframe, = a linear one and ~= a Catmull-Rom spline */ QString getAnimProperty() const; /* @brief this function does the opposite: given a MLT representation of an animation, build the corresponding model */ void parseAnimProperty(const QString &prop); private: std::weak_ptr m_model; std::weak_ptr m_undoStack; QPersistentModelIndex m_index; QString m_lastData; ParamType m_paramType; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access std::map> m_keyframeList; signals: void modelChanged(); public: // this is to enable for range loops auto begin() -> decltype(m_keyframeList.begin()) { return m_keyframeList.begin(); } auto end() -> decltype(m_keyframeList.end()) { return m_keyframeList.end(); } }; // Q_DECLARE_METATYPE(KeyframeModel *) #endif diff --git a/src/assets/keyframes/view/keyframeview.cpp b/src/assets/keyframes/view/keyframeview.cpp index f2eb34165..ff2f2e8fb 100644 --- a/src/assets/keyframes/view/keyframeview.cpp +++ b/src/assets/keyframes/view/keyframeview.cpp @@ -1,314 +1,314 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Kdenlive is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Kdenlive. If not, see . * ***************************************************************************/ #include "keyframeview.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "core.h" #include "kdenlivesettings.h" #include #include #include #include KeyframeView::KeyframeView(std::shared_ptr model, QWidget *parent) : QWidget(parent) , m_model(model) , m_duration(1) , m_position(0) , m_currentKeyframe(-1) , m_currentKeyframeOriginal(-1) , m_hoverKeyframe(-1) , m_scale(1) , m_currentType(KeyframeType::Linear) { setMouseTracking(true); setMinimumSize(QSize(150, 20)); setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum)); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QPalette p = palette(); KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); m_colSelected = palette().highlight().color(); m_colKeyframe = scheme.foreground(KColorScheme::NormalText).color(); m_size = QFontInfo(font()).pixelSize() * 1.8; m_lineHeight = m_size / 2; setMinimumHeight(m_size); setMaximumHeight(m_size); connect(m_model.get(), &KeyframeModelList::modelChanged, this, &KeyframeView::slotModelChanged); } void KeyframeView::slotModelChanged() { emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe()); emit modified(); update(); } void KeyframeView::slotSetPosition(int pos, bool isInRange) { if (!isInRange) { m_position = -1; update(); return; } if (pos != m_position) { m_position = pos; emit atKeyframe(m_model->hasKeyframe(pos), m_model->singleKeyframe()); update(); } } void KeyframeView::initKeyframePos() { emit atKeyframe(m_model->hasKeyframe(m_position), m_model->singleKeyframe()); } void KeyframeView::slotAddKeyframe(int pos) { if (pos < 0) { pos = m_position; } m_model->addKeyframe(GenTime(pos, pCore->getCurrentFps()), m_currentType); } void KeyframeView::slotAddRemove() { if (m_model->hasKeyframe(m_position)) { slotRemoveKeyframe(m_position); } else { slotAddKeyframe(m_position); } } void KeyframeView::slotEditType(int type, const QPersistentModelIndex &index) { if (m_model->hasKeyframe(m_position)) { m_model->updateKeyframeType(GenTime(m_position, pCore->getCurrentFps()), type, index); } } void KeyframeView::slotRemoveKeyframe(int pos) { if (pos < 0) { pos = m_position; } m_model->removeKeyframe(GenTime(pos, pCore->getCurrentFps())); } void KeyframeView::setDuration(int dur) { m_duration = dur; } void KeyframeView::slotGoToNext() { if (m_position == m_duration) { return; } bool ok; auto next = m_model->getNextKeyframe(GenTime(m_position, pCore->getCurrentFps()), &ok); if (ok) { emit seekToPos(next.first.frames(pCore->getCurrentFps())); } else { // no keyframe after current position emit seekToPos(m_duration - 1); } } void KeyframeView::slotGoToPrev() { if (m_position == 0) { return; } bool ok; auto prev = m_model->getPrevKeyframe(GenTime(m_position, pCore->getCurrentFps()), &ok); if (ok) { emit seekToPos(prev.first.frames(pCore->getCurrentFps())); } else { // no keyframe after current position emit seekToPos(m_duration); } } void KeyframeView::mousePressEvent(QMouseEvent *event) { int pos = event->x() / m_scale; if (event->y() < m_lineHeight && event->button() == Qt::LeftButton) { bool ok; GenTime position(pos, pCore->getCurrentFps()); auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_currentKeyframeOriginal = keyframe.first.frames(pCore->getCurrentFps()); // Select and seek to keyframe m_currentKeyframe = m_currentKeyframeOriginal; emit seekToPos(m_currentKeyframeOriginal); return; } } // no keyframe next to mouse m_currentKeyframe = m_currentKeyframeOriginal = -1; emit seekToPos(pos); update(); } void KeyframeView::mouseMoveEvent(QMouseEvent *event) { int pos = qBound(0, (int)(event->x() / m_scale), m_duration); GenTime position(pos, pCore->getCurrentFps()); if ((event->buttons() & Qt::LeftButton) != 0u) { if (m_currentKeyframe == pos) { return; } if (m_currentKeyframe > 0) { if (!m_model->hasKeyframe(pos)) { GenTime currentPos(m_currentKeyframe, pCore->getCurrentFps()); if (m_model->moveKeyframe(currentPos, position, false)) { m_currentKeyframe = pos; } } } emit seekToPos(pos); return; } if (event->y() < m_lineHeight) { bool ok; auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_hoverKeyframe = keyframe.first.frames(pCore->getCurrentFps()); setCursor(Qt::PointingHandCursor); update(); return; } } if (m_hoverKeyframe != -1) { m_hoverKeyframe = -1; setCursor(Qt::ArrowCursor); update(); } } void KeyframeView::mouseReleaseEvent(QMouseEvent *event) { Q_UNUSED(event) if (m_currentKeyframe >= 0) { GenTime initPos(m_currentKeyframeOriginal, pCore->getCurrentFps()); GenTime targetPos(m_currentKeyframe, pCore->getCurrentFps()); bool ok1 = m_model->moveKeyframe(targetPos, initPos, false); bool ok2 = m_model->moveKeyframe(initPos, targetPos, true); qDebug() << "RELEASING keyframe move" << ok1 << ok2 << initPos.frames(pCore->getCurrentFps()) << targetPos.frames(pCore->getCurrentFps()); } } void KeyframeView::mouseDoubleClickEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && event->y() < m_lineHeight) { int pos = qBound(0, (int)(event->x() / m_scale), m_duration); GenTime position(pos, pCore->getCurrentFps()); bool ok; auto keyframe = m_model->getClosestKeyframe(position, &ok); if (ok && qAbs(keyframe.first.frames(pCore->getCurrentFps()) - pos) < 5) { m_model->removeKeyframe(keyframe.first); if (keyframe.first.frames(pCore->getCurrentFps()) == m_currentKeyframe) { m_currentKeyframe = m_currentKeyframeOriginal = -1; } if (keyframe.first.frames(pCore->getCurrentFps()) == m_position) { emit atKeyframe(false, m_model->singleKeyframe()); } return; } // add new keyframe m_model->addKeyframe(position, m_currentType); } else { QWidget::mouseDoubleClickEvent(event); } } void KeyframeView::wheelEvent(QWheelEvent *event) { int change = event->delta() < 0 ? -1 : 1; int pos = qBound(0, m_position + change, m_duration); emit seekToPos(pos); } void KeyframeView::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QStylePainter p(this); m_scale = width() / (double)(m_duration); // p.translate(0, m_lineHeight); int headOffset = m_lineHeight / 1.5; /* * keyframes */ for (const auto &keyframe : *m_model.get()) { int pos = keyframe.first.frames(pCore->getCurrentFps()); if (pos == m_currentKeyframe || pos == m_hoverKeyframe) { p.setBrush(m_colSelected); } else { p.setBrush(m_colKeyframe); } double scaledPos = pos * m_scale; p.drawLine(QPointF(scaledPos, headOffset), QPointF(scaledPos, m_lineHeight + headOffset / 2.0)); switch (keyframe.second.first) { case KeyframeType::Linear: { QPolygonF position = QPolygonF() << QPointF(-headOffset / 2.0, headOffset / 2.0) << QPointF(0, 0) << QPointF(headOffset / 2.0, headOffset / 2.0) << QPointF(0, headOffset); position.translate(scaledPos, 0); p.drawPolygon(position); break; } case KeyframeType::Discrete: p.drawRect(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset)); break; default: p.drawEllipse(QRectF(scaledPos - headOffset / 2.0, 0, headOffset, headOffset)); break; } } p.setPen(palette().dark().color()); /* * Time-"line" */ p.setPen(m_colKeyframe); - p.drawLine(0, m_lineHeight + (headOffset / 2), (m_duration -1) * m_scale, m_lineHeight + (headOffset / 2)); + p.drawLine(0, m_lineHeight + (headOffset / 2), (m_duration - 1) * m_scale, m_lineHeight + (headOffset / 2)); /* * current position */ if (m_position >= 0) { QPolygon pa(3); int cursorwidth = (m_size - (m_lineHeight + headOffset / 2)) / 2 + 1; QPolygonF position = QPolygonF() << QPointF(-cursorwidth, m_size) << QPointF(cursorwidth, m_size) << QPointF(0, m_lineHeight + (headOffset / 2) + 1); position.translate(m_position * m_scale, 0); p.setBrush(m_colKeyframe); p.drawPolygon(position); } } diff --git a/src/assets/model/assetcommand.cpp b/src/assets/model/assetcommand.cpp index 5c5849692..76e87f761 100644 --- a/src/assets/model/assetcommand.cpp +++ b/src/assets/model/assetcommand.cpp @@ -1,77 +1,77 @@ /*************************************************************************** * Copyright (C) 2017 by 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 "assetcommand.hpp" #include "effects/effectsrepository.hpp" #include "transitions/transitionsrepository.hpp" #include AssetCommand::AssetCommand(std::shared_ptr model, const QModelIndex &index, const QString &value, QUndoCommand *parent) : QUndoCommand(parent) , m_model(model) , m_index(index) , m_value(value) , m_updateView(false) , m_stamp(QTime::currentTime()) { QLocale locale; m_name = m_model->data(index, AssetParameterModel::NameRole).toString(); const QString id = model->getAssetId(); if (EffectsRepository::get()->exists(id)) { setText(i18n("Edit %1", EffectsRepository::get()->getName(id))); } else if (TransitionsRepository::get()->exists(id)) { setText(i18n("Edit %1", TransitionsRepository::get()->getName(id))); } QVariant previousVal = m_model->data(index, AssetParameterModel::ValueRole); m_oldValue = previousVal.type() == QMetaType::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString(); } void AssetCommand::undo() { m_model->setParameter(m_name, m_oldValue, true, m_index); - //m_model->dataChanged(m_index, m_index, QVector()); + // m_model->dataChanged(m_index, m_index, QVector()); } // virtual void AssetCommand::redo() { m_model->setParameter(m_name, m_value, m_updateView, m_index); /*if (m_updateView) { m_model->dataChanged(m_index, m_index, QVector()); }*/ m_updateView = true; } // virtual int AssetCommand::id() const { return 1; } // virtual bool AssetCommand::mergeWith(const QUndoCommand *other) { if (other->id() != id() || static_cast(other)->m_index != m_index || m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) { return false; } m_value = static_cast(other)->m_value; m_stamp = static_cast(other)->m_stamp; return true; } diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp index ea286bdff..ffdb34a61 100644 --- a/src/assets/model/assetparametermodel.cpp +++ b/src/assets/model/assetparametermodel.cpp @@ -1,505 +1,504 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "assetparametermodel.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "core.h" #include "kdenlivesettings.h" #include "klocalizedstring.h" #include "profiles/profilemodel.hpp" #include #include #include AssetParameterModel::AssetParameterModel(Mlt::Properties *asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId, QObject *parent) : QAbstractListModel(parent) , monitorId(ownerId.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor) , m_xml(assetXml) , m_assetId(assetId) , m_ownerId(ownerId) , m_asset(asset) { Q_ASSERT(asset->is_valid()); QDomNodeList nodeList = m_xml.elementsByTagName(QStringLiteral("parameter")); bool needsLocaleConversion = false; QChar separator, oldSeparator; // Check locale if (m_xml.hasAttribute(QStringLiteral("LC_NUMERIC"))) { QLocale locale = QLocale(m_xml.attribute(QStringLiteral("LC_NUMERIC"))); if (locale.decimalPoint() != QLocale().decimalPoint()) { needsLocaleConversion = true; separator = QLocale().decimalPoint(); oldSeparator = locale.decimalPoint(); } } qDebug() << "XML parsing of " << assetId << ". found : " << nodeList.count(); for (int i = 0; i < nodeList.count(); ++i) { QDomElement currentParameter = nodeList.item(i).toElement(); // Convert parameters if we need to if (needsLocaleConversion) { QDomNamedNodeMap attrs = currentParameter.attributes(); for (int k = 0; k < attrs.count(); ++k) { QString nodeName = attrs.item(k).nodeName(); if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) { QString val = attrs.item(k).nodeValue(); if (val.contains(oldSeparator)) { QString newVal = val.replace(oldSeparator, separator); attrs.item(k).setNodeValue(newVal); } } } } // Parse the basic attributes of the parameter QString name = currentParameter.attribute(QStringLiteral("name")); QString type = currentParameter.attribute(QStringLiteral("type")); QString value = currentParameter.attribute(QStringLiteral("value")); QLocale locale; if (value.isNull()) { QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter); value = defaultValue.type() == QMetaType::Double ? locale.toString(defaultValue.toDouble()) : defaultValue.toString(); } bool isFixed = (type == QLatin1String("fixed")); if (isFixed) { m_fixedParams[name] = value; - } - else if (type == QLatin1String("position")) { + } else if (type == QLatin1String("position")) { int val = value.toInt(); if (val < 0) { int in = pCore->getItemIn(m_ownerId); int out = in + pCore->getItemDuration(m_ownerId); val += out; value = QString::number(val); } } if (!name.isEmpty()) { setParameter(name, value, false); // Keep track of param order m_paramOrder.push_back(name); } if (isFixed) { // fixed parameters are not displayed so we don't store them. continue; } ParamRow currentRow; currentRow.type = paramTypeFromStr(type); currentRow.xml = currentParameter; currentRow.value = value; QString title = currentParameter.firstChildElement(QStringLiteral("name")).text(); currentRow.name = title.isEmpty() ? name : title; m_params[name] = currentRow; m_rows.push_back(name); } if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Sox effects need to have a special "Effect" value set QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)}; for (const QString &pName : m_paramOrder) { effectParam << m_asset->get(pName.toUtf8().constData()); } m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData()); } qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size(); emit modelChanged(); } void AssetParameterModel::prepareKeyframes() { if (m_keyframes) return; int ix = 0; for (const auto &name : m_rows) { if (m_params[name].type == ParamType::KeyframeParam || m_params[name].type == ParamType::AnimatedRect) { addKeyframeParam(index(ix, 0)); } ix++; } } void AssetParameterModel::setParameter(const QString &name, const 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 (update) { if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Warning, SOX effect, need unplug/replug - qDebug()<<"// 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()); } else { emit modelChanged(); emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {}); } // Update timeline view if necessary pCore->updateItemModel(m_ownerId, m_assetId); pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } } void AssetParameterModel::setParameter(const QString &name, const QString &value, bool update, const QModelIndex ¶mIndex) { Q_ASSERT(m_asset->is_valid()); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); bool conversionSuccess; double doubleValue = locale.toDouble(value, &conversionSuccess); if (conversionSuccess) { m_asset->set(name.toLatin1().constData(), doubleValue); if (m_fixedParams.count(name) == 0) { m_params[name].value = doubleValue; } else { m_fixedParams[name] = doubleValue; } } else { m_asset->set(name.toLatin1().constData(), value.toUtf8().constData()); if (m_fixedParams.count(name) == 0) { m_params[name].value = value; } else { m_fixedParams[name] = value; } } if (update) { if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Warning, SOX effect, need unplug/replug - qDebug()<<"// 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()); } else { emit modelChanged(); if (paramIndex.isValid()) { emit dataChanged(paramIndex, paramIndex, {}); } else { emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {}); } } } // Update timeline view if necessary pCore->updateItemModel(m_ownerId, m_assetId); pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } void AssetParameterModel::setParameter(const QString &name, double &value) { Q_ASSERT(m_asset->is_valid()); m_asset->set(name.toLatin1().constData(), value); if (m_fixedParams.count(name) == 0) { m_params[name].value = value; } else { m_fixedParams[name] = value; } if (m_assetId.startsWith(QStringLiteral("sox_"))) { // Warning, SOX effect, need unplug/replug - qDebug()<<"// 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()); - } else { + // these effects don't understand param change and need to be rebuild + emit replugEffect(shared_from_this()); + } else { emit modelChanged(); } pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } AssetParameterModel::~AssetParameterModel() = default; QVariant AssetParameterModel::data(const QModelIndex &index, int role) const { if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) { return QVariant(); } QString paramName = m_rows[index.row()]; Q_ASSERT(m_params.count(paramName) > 0); const QDomElement &element = m_params.at(paramName).xml; switch (role) { case Qt::DisplayRole: case Qt::EditRole: return m_params.at(paramName).name; case NameRole: return paramName; case TypeRole: return QVariant::fromValue(m_params.at(paramName).type); case CommentRole: { QDomElement commentElem = element.firstChildElement(QStringLiteral("comment")); QString comment; if (!commentElem.isNull()) { comment = i18n(commentElem.text().toUtf8().data()); } return comment; } case InRole: return m_asset->get_int("in"); case OutRole: return m_asset->get_int("out"); case ParentInRole: return pCore->getItemIn(m_ownerId); case ParentDurationRole: return pCore->getItemDuration(m_ownerId); case MinRole: return parseAttribute(m_ownerId, QStringLiteral("min"), element); case MaxRole: return parseAttribute(m_ownerId, QStringLiteral("max"), element); case FactorRole: return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1); case ScaleRole: return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0); case DecimalsRole: return parseAttribute(m_ownerId, QStringLiteral("decimals"), element); case DefaultRole: return parseAttribute(m_ownerId, QStringLiteral("default"), element); case FilterRole: return parseAttribute(m_ownerId, QStringLiteral("filter"), element); case 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 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(',')); } } 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; } qDebug() << "WARNING: Unknown type :" << type; return ParamType::Double; } // static QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly) { QString keyframes = QString::number(start); if (linearOnly) { keyframes.append(QLatin1Char('=')); } else { switch (KdenliveSettings::defaultkeyframeinterp()) { case mlt_keyframe_discrete: keyframes.append(QStringLiteral("|=")); break; case mlt_keyframe_smooth: keyframes.append(QStringLiteral("~=")); break; default: keyframes.append(QLatin1Char('=')); break; } } keyframes.append(defaultValue); return keyframes; } // static QVariant AssetParameterModel::parseAttribute(const ObjectId owner, const QString &attribute, const QDomElement &element, QVariant defaultValue) { if (!element.hasAttribute(attribute) && !defaultValue.isNull()) { return defaultValue; } ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type"))); QString content = element.attribute(attribute); if (content.contains(QLatin1Char('%'))) { std::unique_ptr &profile = pCore->getCurrentProfile(); int width = profile->width(); int height = profile->height(); int in = pCore->getItemIn(owner); int out = in + pCore->getItemDuration(owner); // replace symbols in the double parameter content.replace(QLatin1String("%maxWidth"), QString::number(width)) .replace(QLatin1String("%maxHeight"), QString::number(height)) .replace(QLatin1String("%width"), QString::number(width)) .replace(QLatin1String("%height"), QString::number(height)) .replace(QLatin1String("%out"), QString::number(out)); if (type == ParamType::Double) { // Use a Mlt::Properties to parse mathematical operators Mlt::Properties p; p.set("eval", content.toLatin1().constData()); return p.get_double("eval"); } } else if (type == ParamType::Double) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); return locale.toDouble(content); } if (attribute == QLatin1String("default")) { if (type == ParamType::RestrictedAnim) { content = getDefaultKeyframes(0, content, true); } else { if (element.hasAttribute(QStringLiteral("factor"))) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); return QVariant(locale.toDouble(content) / locale.toDouble(element.attribute(QStringLiteral("factor")))); } } } return content; } QString AssetParameterModel::getAssetId() const { return m_assetId; } QVector> AssetParameterModel::getAllParameters() const { QVector> res; res.reserve((int)m_fixedParams.size() + (int)m_params.size()); for (const auto &fixed : m_fixedParams) { res.push_back(QPair(fixed.first, fixed.second)); } for (const auto ¶m : m_params) { res.push_back(QPair(param.first, param.second.value)); } return res; } void AssetParameterModel::setParameters(const QVector> ¶ms) { QLocale locale; for (const auto ¶m : params) { if (param.second.type() == QMetaType::Double) { setParameter(param.first, locale.toString(param.second.toDouble())); } else { setParameter(param.first, param.second.toString()); } } } ObjectId AssetParameterModel::getOwnerId() const { return m_ownerId; } void AssetParameterModel::addKeyframeParam(const QModelIndex index) { if (m_keyframes) { m_keyframes->addParameter(index); } else { m_keyframes.reset(new KeyframeModelList(shared_from_this(), index, pCore->undoStack())); } } std::shared_ptr AssetParameterModel::getKeyframeModel() { return m_keyframes; } void AssetParameterModel::resetAsset(Mlt::Properties *asset) { m_asset.reset(asset); } diff --git a/src/assets/model/assetparametermodel.hpp b/src/assets/model/assetparametermodel.hpp index 9abbe4d83..11e9b1631 100644 --- a/src/assets/model/assetparametermodel.hpp +++ b/src/assets/model/assetparametermodel.hpp @@ -1,180 +1,180 @@ /*************************************************************************** * 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 }; Q_DECLARE_METATYPE(ParamType) class AssetParameterModel : public QAbstractListModel, public enable_shared_from_this_virtual { Q_OBJECT public: explicit AssetParameterModel(Mlt::Properties *asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId, QObject *parent = nullptr); virtual ~AssetParameterModel(); enum { NameRole = Qt::UserRole + 1, TypeRole, CommentRole, MinRole, MaxRole, DefaultRole, SuffixRole, DecimalsRole, ValueRole, AlphaRole, ListValuesRole, ListNamesRole, FactorRole, FilterRole, ScaleRole, OpacityRole, RelativePosRole, InRole, OutRole, ParentInRole, ParentDurationRole }; /* @brief Returns the id of the asset represented by this object */ QString getAssetId() const; /* @brief Set the parameter with given name to the given value */ Q_INVOKABLE void setParameter(const QString &name, const QString &value, bool update = true, const QModelIndex ¶mIndex = QModelIndex()); void setParameter(const QString &name, const int value, bool update = true); Q_INVOKABLE void setParameter(const QString &name, double &value); /* @brief Return all the parameters as pairs (parameter name, parameter value) */ QVector> getAllParameters() const; /* @brief Sets the value of a list of parameters @param params contains the pairs (parameter name, parameter value) */ void setParameters(const QVector> ¶ms); /* Which monitor is attached to this asset (clip/project) - */ + */ Kdenlive::MonitorId monitorId; QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; /* @brief Returns the id of the actual object associated with this asset */ ObjectId getOwnerId() const; /* @brief Returns the keyframe model associated with this asset Return empty ptr if there is no keyframable parameter in the asset or if prepareKeyframes was not called */ Q_INVOKABLE std::shared_ptr getKeyframeModel(); /* @brief Must be called before using the keyframes of this model */ void prepareKeyframes(); void resetAsset(Mlt::Properties *asset); 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()); /* @brief Helper function to register one more parameter that is keyframable. @param index is the index corresponding to this parameter */ void addKeyframeParam(const QModelIndex index); struct ParamRow { ParamType type; QDomElement xml; QVariant value; QString name; }; QDomElement m_xml; QString m_assetId; ObjectId m_ownerId; std::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; signals: void modelChanged(); void compositionTrackChanged(); void replugEffect(std::shared_ptr asset); void rebuildEffect(std::shared_ptr asset); }; #endif diff --git a/src/assets/view/assetparameterview.cpp b/src/assets/view/assetparameterview.cpp index 846081faf..875264d79 100644 --- a/src/assets/view/assetparameterview.cpp +++ b/src/assets/view/assetparameterview.cpp @@ -1,220 +1,220 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "assetparameterview.hpp" #include "assets/model/assetcommand.hpp" #include "assets/model/assetparametermodel.hpp" #include "assets/view/widgets/abstractparamwidget.hpp" #include "assets/view/widgets/keyframewidget.hpp" #include "core.h" #include #include #include #include #include AssetParameterView::AssetParameterView(QWidget *parent) : QWidget(parent) , m_mainKeyframeWidget(nullptr) { m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(2, 2, 2, 2); m_lay->setSpacing(2); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); } void AssetParameterView::setModel(const std::shared_ptr &model, QSize frameSize, bool addSpacer) { unsetModel(); QMutexLocker lock(&m_lock); m_model = model; m_model->prepareKeyframes(); const QString paramTag = model->getAssetId(); connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh); if (paramTag == QStringLiteral("lift_gamma_gain")) { // Special case, the colorwheel widget manages several parameters QModelIndex index = model->index(0, 0); auto w = AbstractParamWidget::construct(model, index, frameSize, this); connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges); m_lay->addWidget(w); m_widgets.push_back(w); } else { for (int i = 0; i < model->rowCount(); ++i) { QModelIndex index = model->index(i, 0); auto type = model->data(index, AssetParameterModel::TypeRole).value(); if (m_mainKeyframeWidget && - (type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::KeyframeParam)) { + (type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::KeyframeParam)) { // Keyframe widget can have some extra params that should'nt build a new widget qDebug() << "// FOUND ADDED PARAM"; m_mainKeyframeWidget->addParameter(index); } else { auto w = AbstractParamWidget::construct(model, index, frameSize, this); connect(this, &AssetParameterView::initKeyframeView, w, &AbstractParamWidget::slotInitMonitor); - if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect ) { + if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect) { m_mainKeyframeWidget = static_cast(w); } connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges); connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos); m_lay->addWidget(w); m_widgets.push_back(w); } } } if (addSpacer) { m_lay->addStretch(); } } void AssetParameterView::resetValues() { auto type = m_model->data(m_model->index(0, 0), AssetParameterModel::TypeRole).value(); for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); QString name = m_model->data(index, AssetParameterModel::NameRole).toString(); QString defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toString(); m_model->setParameter(name, defaultValue); if (m_mainKeyframeWidget) { // Handles additionnal params like rotation so only refresh initial param at the end - } - else if (type == ParamType::ColorWheel) { + } else if (type == ParamType::ColorWheel) { if (i == m_model->rowCount() - 1) { // Special case, the ColorWheel widget handles several params, so only refresh once when all parameters were set. QModelIndex firstIndex = m_model->index(0, 0); refresh(firstIndex, firstIndex, QVector()); } } else { refresh(index, index, QVector()); } } if (m_mainKeyframeWidget) { m_mainKeyframeWidget->slotRefresh(); } } void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo) { // Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own AssetCommand *command = new AssetCommand(m_model, index, value); if (storeUndo) { pCore->pushUndo(command); } else { command->redo(); delete command; } } void AssetParameterView::unsetModel() { QMutexLocker lock(&m_lock); if (m_model) { // if a model is already there, we have to disconnect signals first disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh); } m_mainKeyframeWidget = nullptr; // clear layout m_widgets.clear(); QLayoutItem *child; while ((child = m_lay->takeAt(0)) != nullptr) { if (child->layout()) { QLayoutItem *subchild; while ((subchild = child->layout()->takeAt(0)) != nullptr) { delete subchild->widget(); delete subchild->spacerItem(); } } delete child->widget(); delete child->spacerItem(); } // Release ownership of smart pointer m_model.reset(); } void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QMutexLocker lock(&m_lock); if (m_widgets.size() == 0) { // no visible param for this asset, abort - qDebug()<<"/// ASKING REFRESH... EMPTY WIDGET"; + qDebug() << "/// ASKING REFRESH... EMPTY WIDGET"; return; } Q_UNUSED(roles); // We are expecting indexes that are children of the root index, which is "invalid" Q_ASSERT(!topLeft.parent().isValid()); // We make sure the range is valid if (m_mainKeyframeWidget) { m_mainKeyframeWidget->slotRefresh(); } else { auto type = m_model->data(m_model->index(bottomRight.row(), 0), AssetParameterModel::TypeRole).value(); if (type == ParamType::ColorWheel) { - // Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets. Should be better managed + // Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets. + // Should be better managed m_widgets[0]->slotRefresh(); return; } Q_ASSERT(bottomRight.row() < (int)m_widgets.size()); for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { m_widgets[(uint)i]->slotRefresh(); } } } int AssetParameterView::contentHeight() const { return m_lay->sizeHint().height(); } MonitorSceneType AssetParameterView::needsMonitorEffectScene() const { if (m_mainKeyframeWidget) { return MonitorSceneGeometry; } for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); if (type == ParamType::Geometry) { return MonitorSceneGeometry; } } return MonitorSceneDefault; } /*void AssetParameterView::initKeyframeView() { if (m_mainKeyframeWidget) { m_mainKeyframeWidget->initMonitor(); } else { for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); if (type == ParamType::Geometry) { return MonitorSceneGeometry; } } } }*/ void AssetParameterView::slotRefresh() { refresh(m_model->index(0, 0), m_model->index(m_model->rowCount() - 1, 0), {}); } diff --git a/src/assets/view/widgets/abstractparamwidget.cpp b/src/assets/view/widgets/abstractparamwidget.cpp index 3068955ea..68645f738 100644 --- a/src/assets/view/widgets/abstractparamwidget.cpp +++ b/src/assets/view/widgets/abstractparamwidget.cpp @@ -1,129 +1,129 @@ /*************************************************************************** * Copyright (C) 2016 by Nicolas Carion * * * * 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 "abstractparamwidget.hpp" #include "assets/model/assetparametermodel.hpp" #include "boolparamwidget.hpp" -#include "switchparamwidget.hpp" -#include "lumaliftgainparam.hpp" +#include "coloreditwidget.hpp" #include "doubleparamwidget.hpp" #include "geometryeditwidget.hpp" #include "keyframewidget.hpp" #include "listparamwidget.h" +#include "lumaliftgainparam.hpp" #include "positioneditwidget.hpp" -#include "coloreditwidget.hpp" #include "slidewidget.hpp" +#include "switchparamwidget.hpp" #include "urlparamwidget.hpp" #include #include #include // temporary place holder for parameters that don't currently have a display class class Unsupported : public AbstractParamWidget { public: Unsupported(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { auto *lay = new QVBoxLayout(this); lay->setContentsMargins(4, 0, 4, 0); m_label = new QLabel(this); lay->addWidget(m_label); } void setText(const QString &str) { m_label->setText(str); } void slotRefresh() override {} protected: QLabel *m_label; }; AbstractParamWidget::AbstractParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : QWidget(parent) , m_model(std::move(model)) , m_index(index) { } AbstractParamWidget *AbstractParamWidget::construct(const std::shared_ptr &model, QModelIndex index, QSize frameSize, QWidget *parent) { // We retrieve the parameter type auto type = model->data(index, AssetParameterModel::TypeRole).value(); QString name = model->data(index, AssetParameterModel::NameRole).toString(); AbstractParamWidget *widget; switch (type) { case ParamType::Double: widget = new DoubleParamWidget(model, index, parent); break; case ParamType::List: widget = new ListParamWidget(model, index, parent); break; case ParamType::Bool: widget = new BoolParamWidget(model, index, parent); break; case ParamType::KeyframeParam: case ParamType::AnimatedRect: widget = new KeyframeWidget(model, index, parent); break; case ParamType::Geometry: widget = new GeometryEditWidget(model, index, frameSize, parent); break; case ParamType::Position: widget = new PositionEditWidget(model, index, parent); break; case ParamType::Color: widget = new ColorEditWidget(model, index, parent); break; case ParamType::ColorWheel: widget = new LumaLiftGainParam(model, index, parent); break; case ParamType::Wipe: widget = new SlideWidget(model, index, parent); break; case ParamType::Switch: widget = new SwitchParamWidget(model, index, parent); break; case ParamType::Url: widget = new UrlParamWidget(model, index, parent); break; case ParamType::Animated: case ParamType::RestrictedAnim: // widget = new AnimationWidget(model, index, range, parent); // break; // case ParamType::KeyframeParam: // widget = new KeyframeEdit(model, index, parent); // break; case ParamType::Addedgeometry: case ParamType::Curve: case ParamType::Bezier_spline: case ParamType::Roto_spline: case ParamType::Keywords: case ParamType::Fontfamily: case ParamType::Filterjob: case ParamType::Readonly: // not reimplemented widget = new Unsupported(model, index, parent); static_cast(widget)->setText(name); } return widget; } diff --git a/src/assets/view/widgets/boolparamwidget.cpp b/src/assets/view/widgets/boolparamwidget.cpp index e612f0c94..03f242dcf 100644 --- a/src/assets/view/widgets/boolparamwidget.cpp +++ b/src/assets/view/widgets/boolparamwidget.cpp @@ -1,65 +1,63 @@ /*************************************************************************** * 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 "boolparamwidget.hpp" #include "assets/model/assetparametermodel.hpp" BoolParamWidget::BoolParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { setupUi(this); // setup the comment QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); setToolTip(comment); m_labelComment->setText(comment); m_widgetComment->setHidden(true); // setup the name m_labelName->setText(m_model->data(m_index, Qt::DisplayRole).toString()); // set check state slotRefresh(); // emit the signal of the base class when appropriate - connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int) { - emit valueChanged(m_index, QString::number(m_checkBox->isChecked()), true); - }); + connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int) { emit valueChanged(m_index, QString::number(m_checkBox->isChecked()), true); }); } void BoolParamWidget::slotShowComment(bool show) { if (!m_labelComment->text().isEmpty()) { m_widgetComment->setVisible(show); } } void BoolParamWidget::slotRefresh() { bool checked = m_model->data(m_index, AssetParameterModel::ValueRole).toInt(); m_checkBox->setChecked(checked); } bool BoolParamWidget::getValue() { return m_checkBox->isChecked(); } diff --git a/src/assets/view/widgets/boolparamwidget.hpp b/src/assets/view/widgets/boolparamwidget.hpp index 7b803f1d9..48a2efe02 100644 --- a/src/assets/view/widgets/boolparamwidget.hpp +++ b/src/assets/view/widgets/boolparamwidget.hpp @@ -1,59 +1,58 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef BOOLPARAMWIDGET_H #define BOOLPARAMWIDGET_H #include "abstractparamwidget.hpp" #include "ui_boolparamwidget_ui.h" #include /** @brief This class represents a parameter that requires the user to choose tick a checkbox */ class BoolParamWidget : public AbstractParamWidget, public Ui::BoolParamWidget_UI { Q_OBJECT public: /** @brief Constructor for the widgetComment @param name String containing the name of the parameter @param comment Optional string containing the comment associated to the parameter @param checked Boolean indicating wether the checkbox should initially be checked @param parent Parent widget */ BoolParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); /** @brief Returns the current value of the parameter */ bool getValue(); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - }; #endif diff --git a/src/assets/view/widgets/coloreditwidget.cpp b/src/assets/view/widgets/coloreditwidget.cpp index 2b9b6d2db..ba7dc13e3 100644 --- a/src/assets/view/widgets/coloreditwidget.cpp +++ b/src/assets/view/widgets/coloreditwidget.cpp @@ -1,158 +1,156 @@ /*************************************************************************** * 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 "coloreditwidget.hpp" -#include "widgets/colorpickerwidget.h" #include "assets/model/assetparametermodel.hpp" +#include "widgets/colorpickerwidget.h" #include #include #include #include static QColor stringToColor(QString strColor) { bool ok = false; QColor color("black"); uint intval = 0; if (strColor.startsWith(QLatin1String("0x"))) { if (strColor.length() == 10) { // 0xRRGGBBAA intval = strColor.toUInt(&ok, 16); color.setRgb((int)((intval >> 24) & 0xff), // r - (intval >> 16) & 0xff, // g - (intval >> 8) & 0xff, // b - intval & 0xff); // a + (intval >> 16) & 0xff, // g + (intval >> 8) & 0xff, // b + intval & 0xff); // a } else { // 0xRRGGBB, 0xRGB color.setNamedColor(strColor.replace(0, 2, QLatin1Char('#'))); } } else { if (strColor.length() == 9) { // #AARRGGBB strColor = strColor.replace('#', QLatin1String("0x")); intval = strColor.toUInt(&ok, 16); - color.setRgb((intval >> 16) & 0xff, // r - (intval >> 8) & 0xff, // g - intval&0xff, // b + color.setRgb((intval >> 16) & 0xff, // r + (intval >> 8) & 0xff, // g + intval & 0xff, // b (int)((intval >> 24) & 0xff)); // a } else if (strColor.length() == 8) { // 0xRRGGBB strColor = strColor.replace('#', QLatin1String("0x")); color.setNamedColor(strColor); } else { // #RRGGBB, #RGB color.setNamedColor(strColor); } } return color; } static QString colorToString(const QColor &color, bool alpha) { QString colorStr; QTextStream stream(&colorStr); stream << "0x"; stream.setIntegerBase(16); stream.setFieldWidth(2); stream.setFieldAlignment(QTextStream::AlignRight); stream.setPadChar('0'); stream << color.red() << color.green() << color.blue(); if (alpha) { stream << color.alpha(); } else { // MLT always wants 0xRRGGBBAA format stream << "ff"; } return colorStr; } ColorEditWidget::ColorEditWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { // setup the comment QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); bool alphaEnabled = m_model->data(m_index, AssetParameterModel::AlphaRole).toBool(); QString color = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); setToolTip(comment); auto *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); QLabel *label = new QLabel(name, this); QWidget *rightSide = new QWidget(this); auto *rightSideLayout = new QHBoxLayout(rightSide); rightSideLayout->setContentsMargins(0, 0, 0, 0); rightSideLayout->setSpacing(0); m_button = new KColorButton(stringToColor(color), rightSide); if (alphaEnabled) { m_button->setAlphaChannelEnabled(alphaEnabled); } // m_button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); auto *picker = new ColorPickerWidget(rightSide); layout->addWidget(label, 1); layout->addWidget(rightSide, 1); rightSideLayout->addStretch(); rightSideLayout->addWidget(m_button, 2); rightSideLayout->addWidget(picker); connect(picker, &ColorPickerWidget::colorPicked, this, &ColorEditWidget::setColor); connect(picker, &ColorPickerWidget::disableCurrentFilter, this, &ColorEditWidget::disableCurrentFilter); connect(m_button, &KColorButton::changed, this, &ColorEditWidget::modified); // emit the signal of the base class when appropriate connect(this, &ColorEditWidget::modified, [this](const QColor &) { emit valueChanged(m_index, getColor(), true); }); // setup comment setToolTip(comment); } -void ColorEditWidget::slotShowComment(bool ) -{ -} +void ColorEditWidget::slotShowComment(bool) {} void ColorEditWidget::slotRefresh() { - QString color = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); + QString color = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); m_button->setColor(stringToColor(color)); } QString ColorEditWidget::getColor() const { return colorToString(m_button->color(), m_button->isAlphaChannelEnabled()); } void ColorEditWidget::setColor(const QColor &color) { m_button->setColor(color); } void ColorEditWidget::slotColorModified(const QColor &color) { blockSignals(true); m_button->setColor(color); blockSignals(false); } diff --git a/src/assets/view/widgets/coloreditwidget.hpp b/src/assets/view/widgets/coloreditwidget.hpp index 205b67073..57b368b8c 100644 --- a/src/assets/view/widgets/coloreditwidget.hpp +++ b/src/assets/view/widgets/coloreditwidget.hpp @@ -1,71 +1,71 @@ /*************************************************************************** * 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 COLOREDITWIDGET_H #define COLOREDITWIDGET_H #include "abstractparamwidget.hpp" #include class KColorButton; /** * @class ColorEditWidget * @brief Provides options to choose a color. Two mechanisms are provided: color-picking directly on the screen and choosing from a list * @author Till Theato */ class ColorEditWidget : public AbstractParamWidget { Q_OBJECT public: /** @brief Sets up the widget. - */ + */ explicit ColorEditWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); /** @brief Gets the chosen color. */ QString getColor() const; private: KColorButton *m_button; public slots: void slotColorModified(const QColor &color); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; /** @brief Updates the different color choosing options to have all selected @param color. */ void setColor(const QColor &color); signals: /** @brief Emitted whenever a different color was chosen. */ void modified(QColor = QColor()); void disableCurrentFilter(bool); }; #endif diff --git a/src/assets/view/widgets/doubleparamwidget.cpp b/src/assets/view/widgets/doubleparamwidget.cpp index 247cf3e2a..158a299c5 100644 --- a/src/assets/view/widgets/doubleparamwidget.cpp +++ b/src/assets/view/widgets/doubleparamwidget.cpp @@ -1,75 +1,74 @@ /*************************************************************************** * 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(4, 0, 4, 0); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); // Construct object slotRefresh(); } void DoubleParamWidget::slotRefresh() { // A double paramwidget is not too expansive to create, we can afford to recreate it from scratch delete m_lay; delete m_doubleWidget; QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(4, 0, 4, 0); // 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, defaultValue, comment, -1, suffix, decimals, this); m_doubleWidget->factor = factor; m_lay->addWidget(m_doubleWidget); // Connect signal connect(m_doubleWidget, &DoubleWidget::valueChanged, [this, locale](double val) { emit valueChanged(m_index, locale.toString(val / m_doubleWidget->factor), true); }); } void DoubleParamWidget::slotShowComment(bool show) { m_doubleWidget->slotShowComment(show); } - diff --git a/src/assets/view/widgets/geometryeditwidget.cpp b/src/assets/view/widgets/geometryeditwidget.cpp index 1316e8282..2fe915182 100644 --- a/src/assets/view/widgets/geometryeditwidget.cpp +++ b/src/assets/view/widgets/geometryeditwidget.cpp @@ -1,111 +1,109 @@ /*************************************************************************** * 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 "geometryeditwidget.hpp" -#include "kdenlivesettings.h" -#include "timecodedisplay.h" +#include "assets/model/assetparametermodel.hpp" #include "core.h" +#include "kdenlivesettings.h" #include "monitor/monitor.h" -#include "widgets/geometrywidget.h" #include "monitor/monitormanager.h" -#include "assets/model/assetparametermodel.hpp" +#include "timecodedisplay.h" +#include "widgets/geometrywidget.h" #include #include #include #include GeometryEditWidget::GeometryEditWidget(std::shared_ptr model, QModelIndex index, QSize frameSize, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { auto *layout = new QVBoxLayout(this); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); const QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString().simplified(); int start = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); int end = start + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); Mlt::Geometry geometry(value.toUtf8().data(), end, frameSize.width(), frameSize.height()); Mlt::GeometryItem item; QRect rect; if (geometry.fetch(&item, 0) == 0) { rect = QRect(item.x(), item.y(), item.w(), item.h()); } else { // Cannot read value, use random default rect = QRect(50, 50, 200, 200); } Monitor *monitor = pCore->getMonitor(m_model->monitorId); - m_geom = new GeometryWidget(monitor, QPair(start, end), rect, frameSize, false, m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), this); + m_geom = new GeometryWidget(monitor, QPair(start, end), rect, frameSize, false, m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), + this); m_geom->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); layout->addWidget(m_geom); // emit the signal of the base class when appropriate - connect(this->m_geom, &GeometryWidget::valueChanged, [this](const QString val) { - emit valueChanged(m_index, val, true); }); + connect(this->m_geom, &GeometryWidget::valueChanged, [this](const QString val) { emit valueChanged(m_index, val, true); }); setToolTip(comment); } -GeometryEditWidget::~GeometryEditWidget() -{ -} +GeometryEditWidget::~GeometryEditWidget() {} void GeometryEditWidget::slotRefresh() { const QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString().simplified(); QRect rect; QStringList vals = value.split(QLatin1Char(' ')); int start = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); int end = start + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); - m_geom->slotSetRange(QPair (start, end)); + m_geom->slotSetRange(QPair(start, end)); if (vals.count() >= 4) { rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt()); m_geom->setValue(rect); } } void GeometryEditWidget::slotShowComment(bool show) { Q_UNUSED(show); } void GeometryEditWidget::monitorSeek(int pos) { // Update monitor scene for geometry params int start = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); int end = start + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); if (pos >= start && pos < end) { m_geom->connectMonitor(true); pCore->getMonitor(m_model->monitorId)->setEffectKeyframe(true); } else { m_geom->connectMonitor(false); } } void GeometryEditWidget::slotInitMonitor(bool active) { m_geom->connectMonitor(active); - Monitor * monitor = pCore->getMonitor(m_model->monitorId); + Monitor *monitor = pCore->getMonitor(m_model->monitorId); if (active) { monitor->setEffectKeyframe(true); connect(monitor, &Monitor::seekPosition, this, &GeometryEditWidget::monitorSeek, Qt::UniqueConnection); } else { disconnect(monitor, &Monitor::seekPosition, this, &GeometryEditWidget::monitorSeek); } } diff --git a/src/assets/view/widgets/keyframeedit.h b/src/assets/view/widgets/keyframeedit.h index 9234d5c2c..e4c7c11a3 100644 --- a/src/assets/view/widgets/keyframeedit.h +++ b/src/assets/view/widgets/keyframeedit.h @@ -1,167 +1,167 @@ /*************************************************************************** keyframeedit.h - description ------------------- begin : 22 Jun 2009 copyright : (C) 2008 by Jean-Baptiste Mardelle email : jb@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) any later version. * * * ***************************************************************************/ #ifndef KEYFRAMEEDIT_H #define KEYFRAMEEDIT_H #include "assets/view/widgets/abstractparamwidget.hpp" #include "definitions.h" #include "ui_keyframeeditor_ui.h" #include #include #include #include #include class PositionWidget; class KeyItemDelegate : public QItemDelegate { Q_OBJECT public: KeyItemDelegate(int min, int max, QAbstractItemView *parent = nullptr) : QItemDelegate(parent) , m_min(min) , m_max(max) { } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (index.column() == 1) { QSpinBox *spin = new QSpinBox(parent); connect(spin, static_cast(&QSpinBox::valueChanged), this, &KeyItemDelegate::commitEditorData); connect(spin, &QAbstractSpinBox::editingFinished, this, &KeyItemDelegate::commitAndCloseEditor); return spin; } else { return QItemDelegate::createEditor(parent, option, index); } } void setEditorData(QWidget *editor, const QModelIndex &index) const override { if (index.column() == 1) { QSpinBox *spin = qobject_cast(editor); spin->setRange(m_min, m_max); spin->setValue(index.model()->data(index).toInt()); } else { QItemDelegate::setEditorData(editor, index); } } private slots: void commitAndCloseEditor() { QSpinBox *spin = qobject_cast(sender()); emit closeEditor(spin); } void commitEditorData() { QSpinBox *spin = qobject_cast(sender()); emit commitData(spin); } private: int m_min; int m_max; }; class KeyframeEdit : public AbstractParamWidget, public Ui::KeyframeEditor_UI { Q_OBJECT public: explicit KeyframeEdit(std::shared_ptr model, QModelIndex index, QWidget *parent); virtual ~KeyframeEdit(); virtual void addParameter(QModelIndex index, int activeKeyframe = -1); const QString getValue(const QString &name); /** @brief Updates the timecode display according to settings (frame number or hh:mm:ss:ff) */ void updateTimecodeFormat(); /** @brief Returns true if the parameter @param name should be shown on the clip in timeline. */ bool isVisibleParam(const QString &name); /** @brief Makes the first parameter visible in timeline if no parameter is selected. */ void checkVisibleParam(); /** @brief Returns attribute name for returned keyframes. */ const QString getTag() const; public slots: void slotUpdateRange(int inPoint, int outPoint); void slotAddKeyframe(int pos = -1); /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; protected: /** @brief Gets the position of a keyframe from the table. * @param row Row of the keyframe in the table */ int getPos(int row); /** @brief Converts a frame value to timecode considering the frames vs. HH:MM:SS:FF setting. * @return timecode */ QString getPosString(int pos); void generateAllParams(); QList m_paramIndexes; int m_min; int m_max; int m_offset; protected slots: void slotAdjustKeyframeInfo(bool seek = true); private: QList m_params; QGridLayout *m_slidersLayout; PositionWidget *m_position; bool m_keyframesTag; private slots: void slotDeleteKeyframe(); void slotGenerateParams(int row, int column); void slotAdjustKeyframePos(int value); void slotAdjustKeyframeValue(double value); /** @brief Turns the seek to keyframe position setting on/off. - * @param seek true = seeking on */ + * @param seek true = seeking on */ void slotSetSeeking(bool seek); /** @brief Shows the keyframe table and adds a second keyframe. */ void slotKeyframeMode(); /** @brief Resets all parameters of the selected keyframe to their default values. */ void slotResetKeyframe(); /** @brief Makes the parameter at column @param id the visible (in timeline) one. */ void slotUpdateVisibleParameter(int id, bool update = true); /** @brief A row was clicked, adjust parameters. */ void rowClicked(int newRow, int, int oldRow, int); signals: void seekToPos(int); void showComments(bool show); }; #endif diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp index d74f82062..574d932ac 100644 --- a/src/assets/view/widgets/keyframewidget.cpp +++ b/src/assets/view/widgets/keyframewidget.cpp @@ -1,299 +1,300 @@ /*************************************************************************** * 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/keyframemodellist.hpp" #include "assets/keyframes/view/keyframeview.hpp" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "monitor/monitor.h" #include "timecode.h" #include "timecodedisplay.h" #include "utils/KoIconUtils.h" #include "widgets/doublewidget.h" #include "widgets/geometrywidget.h" #include #include #include #include KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(model, index, parent) { m_keyframes = model->getKeyframeModel(); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); m_lay = new QVBoxLayout(this); bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); m_keyframeview = new KeyframeView(m_keyframes, this); m_keyframeview->setDuration(duration); m_buttonAddDelete = new QToolButton(this); m_buttonAddDelete->setAutoRaise(true); m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); m_buttonPrevious = new QToolButton(this); m_buttonPrevious->setAutoRaise(true); m_buttonPrevious->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-skip-backward"))); m_buttonPrevious->setToolTip(i18n("Go to previous keyframe")); m_buttonNext = new QToolButton(this); m_buttonNext->setAutoRaise(true); m_buttonNext->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-skip-forward"))); m_buttonNext->setToolTip(i18n("Go to next keyframe")); // Keyframe type widget m_selectType = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this); QAction *linear = new QAction(KoIconUtils::themedIcon(QStringLiteral("linear")), i18n("Linear"), this); linear->setData((int)mlt_keyframe_linear); linear->setCheckable(true); m_selectType->addAction(linear); QAction *discrete = new QAction(KoIconUtils::themedIcon(QStringLiteral("discrete")), i18n("Discrete"), this); discrete->setData((int)mlt_keyframe_discrete); discrete->setCheckable(true); m_selectType->addAction(discrete); QAction *curve = new QAction(KoIconUtils::themedIcon(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); + connect(m_selectType, static_cast(&KSelectAction::triggered), this, &KeyframeWidget::slotEditKeyframeType); m_selectType->setToolBarMode(KSelectAction::ComboBoxMode); QToolBar *toolbar = new QToolBar(this); Monitor *monitor = pCore->getMonitor(m_model->monitorId); m_time = new TimecodeDisplay(monitor->timecode(), this); m_time->setRange(0, duration - 1); toolbar->addWidget(m_buttonPrevious); toolbar->addWidget(m_buttonAddDelete); toolbar->addWidget(m_buttonNext); toolbar->addAction(m_selectType); toolbar->addWidget(m_time); m_lay->addWidget(m_keyframeview); m_lay->addWidget(toolbar); // slotSetPosition(0, false); monitorSeek(monitor->position()); connect(m_time, &TimecodeDisplay::timeCodeEditingFinished, [&]() { slotSetPosition(-1, true); }); connect(m_keyframeview, &KeyframeView::seekToPos, [&](int p) { slotSetPosition(p, true); }); connect(m_keyframeview, &KeyframeView::atKeyframe, this, &KeyframeWidget::slotAtKeyframe); connect(m_keyframeview, &KeyframeView::modified, this, &KeyframeWidget::slotRefreshParams); connect(m_buttonAddDelete, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotAddRemove); connect(m_buttonPrevious, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToPrev); connect(m_buttonNext, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToNext); addParameter(index); } 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); connectMonitor(isInRange); int framePos = qBound(in, pos, out) - in; m_keyframeview->slotSetPosition(framePos, isInRange); m_time->setValue(framePos); } 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())); m_selectType->setCurrentItem((int)keyType); for (const auto &w : m_parameters) { ParamType 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.0; 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); } } } 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); } slotRefreshParams(); if (update) { emit seekToPos(pos); } } int KeyframeWidget::getPosition() const { return m_time->getValue(); } void KeyframeWidget::addKeyframe(int pos) { blockSignals(true); m_keyframeview->slotAddKeyframe(pos); blockSignals(false); setEnabled(true); } void KeyframeWidget::updateTimecodeFormat() { m_time->slotUpdateTimeCodeFormat(); } void KeyframeWidget::slotAtKeyframe(bool atKeyframe, bool singleKeyframe) { if (atKeyframe) { m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-remove"))); m_buttonAddDelete->setToolTip(i18n("Delete keyframe")); } else { m_buttonAddDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); m_buttonAddDelete->setToolTip(i18n("Add keyframe")); } 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_keyframeview->setDuration(duration); m_time->setRange(0, duration - 1); // refresh keyframes m_keyframes->refresh(); 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(); ParamType type = m_model->data(index, AssetParameterModel::TypeRole).value(); // Construct object QWidget *paramWidget = nullptr; if (type == ParamType::AnimatedRect) { int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt(); QPair range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt()); QSize frameSize = pCore->getCurrentFrameSize(); const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString(); QRect rect; 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()); } - GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, frameSize, false, m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), this); + GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, frameSize, false, + m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), this); connect(geomWidget, &GeometryWidget::valueChanged, [this, index](const QString v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); }); paramWidget = geomWidget; } else { QLocale locale; 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 = factor == 0 ? 1 : factor; auto doubleWidget = new DoubleWidget(name, value * factor, min, max, defaultValue, comment, -1, suffix, decimals, this); doubleWidget->factor = factor; connect(doubleWidget, &DoubleWidget::valueChanged, [this, index, factor](double v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); }); paramWidget = doubleWidget; } if (paramWidget) { m_parameters[index] = paramWidget; m_lay->addWidget(paramWidget); } } void KeyframeWidget::slotInitMonitor(bool active) { if (m_keyframeview) { m_keyframeview->initKeyframePos(); } Monitor *monitor = pCore->getMonitor(m_model->monitorId); 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) { for (const auto &w : m_parameters) { ParamType type = m_model->data(w.first, AssetParameterModel::TypeRole).value(); if (type == ParamType::AnimatedRect) { ((GeometryWidget *)w.second)->connectMonitor(active); break; } } } diff --git a/src/assets/view/widgets/keyframewidget.hpp b/src/assets/view/widgets/keyframewidget.hpp index 8a365a42a..e01a253ec 100644 --- a/src/assets/view/widgets/keyframewidget.hpp +++ b/src/assets/view/widgets/keyframewidget.hpp @@ -1,85 +1,84 @@ /*************************************************************************** * Copyright (C) 2011 by Till Theato (root@ttill.de) * * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive (www.kdenlive.org). * * * * Kdenlive is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 2 of the License, or * * (at your option) any later version. * * * * Kdenlive is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with Kdenlive. If not, see . * ***************************************************************************/ #ifndef KEYFRAMEWIDGET_H #define KEYFRAMEWIDGET_H #include "abstractparamwidget.hpp" #include "definitions.h" #include #include #include class AssetParameterModel; class DoubleWidget; class KeyframeView; class KeyframeModelList; class QVBoxLayout; class QToolButton; class TimecodeDisplay; class KSelectAction; class KeyframeWidget : public AbstractParamWidget { Q_OBJECT public: explicit KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent = nullptr); ~KeyframeWidget(); /* @brief Add a new parameter to be managed using the same keyframe viewer */ void addParameter(const QPersistentModelIndex &index); int getPosition() const; void addKeyframe(int pos = -1); void updateTimecodeFormat(); public slots: void slotRefresh() override; /** @brief intialize qml overlay */ void slotInitMonitor(bool active) override; public slots: void slotSetPosition(int pos = -1, bool update = true); private slots: /* brief Update the value of the widgets to reflect keyframe change */ void slotRefreshParams(); void slotAtKeyframe(bool atKeyframe, bool singleKeyframe); void monitorSeek(int pos); void slotEditKeyframeType(QAction *action); private: QVBoxLayout *m_lay; std::shared_ptr m_keyframes; KeyframeView *m_keyframeview; QToolButton *m_buttonAddDelete; QToolButton *m_buttonPrevious; QToolButton *m_buttonNext; KSelectAction *m_selectType; TimecodeDisplay *m_time; void connectMonitor(bool active); std::unordered_map m_parameters; - }; #endif diff --git a/src/assets/view/widgets/listparamwidget.h b/src/assets/view/widgets/listparamwidget.h index 479a58d64..d435317d4 100644 --- a/src/assets/view/widgets/listparamwidget.h +++ b/src/assets/view/widgets/listparamwidget.h @@ -1,88 +1,87 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef LISTPARAMETERWIDGET_H #define LISTPARAMETERWIDGET_H #include "assets/view/widgets/abstractparamwidget.hpp" #include "ui_listparamwidget_ui.h" #include #include class AssetParameterModel; /** @brief This class represents a parameter that requires the user to choose a value from a list */ class ListParamWidget : public AbstractParamWidget, public Ui::ListParamWidget_UI { Q_OBJECT public: /** @brief Constructor for the widgetComment @param name String containing the name of the parameter @param comment Optional string containing the comment associated to the parameter @param parent Parent widget */ ListParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); /** @brief Set the index of the current displayed element @param index Integer holding the index of the target element (0-indexed) */ void setCurrentIndex(int index); /** @brief Set the text currently displayed on the list @param text String containing the text of the element to show */ void setCurrentText(const QString &text); /** @brief Add an item to the list. @param text String to be displayed in the list @param value Underlying value corresponding to the text */ void addItem(const QString &text, const QVariant &value = QVariant()); /** @brief Set the icon of a given element @param index Integer holding the index of the target element (0-indexed) @param icon The corresponding icon */ void setItemIcon(int index, const QIcon &icon); /** @brief Set the size of the icons shown in the list @param size Target size of the icon */ void setIconSize(const QSize &size); /** @brief Returns the current value of the parameter */ QString getValue(); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - }; #endif diff --git a/src/assets/view/widgets/lumaliftgainparam.cpp b/src/assets/view/widgets/lumaliftgainparam.cpp index 510d42875..cc0298815 100644 --- a/src/assets/view/widgets/lumaliftgainparam.cpp +++ b/src/assets/view/widgets/lumaliftgainparam.cpp @@ -1,138 +1,133 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * Some code was borrowed from shotcut * * * * 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 "lumaliftgainparam.hpp" #include "assets/model/assetparametermodel.hpp" #include "colorwheel.h" #include "utils/flowlayout.h" #include static const double GAMMA_FACTOR = 2.0; static const double GAIN_FACTOR = 4.0; LumaLiftGainParam::LumaLiftGainParam(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { auto *flowLayout = new FlowLayout(this, 2, 2, 2); /*QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); */ m_locale.setNumberOptions(QLocale::OmitGroupSeparator); m_lift = new ColorWheel(QStringLiteral("lift"), i18n("Lift"), QColor(), this); connect(m_lift, &ColorWheel::colorChange, this, &LumaLiftGainParam::liftChanged); m_gamma = new ColorWheel(QStringLiteral("gamma"), i18n("Gamma"), QColor(), this); connect(m_gamma, &ColorWheel::colorChange, this, &LumaLiftGainParam::gammaChanged); m_gain = new ColorWheel(QStringLiteral("gain"), i18n("Gain"), QColor(), this); connect(m_gain, &ColorWheel::colorChange, this, &LumaLiftGainParam::gainChanged); QMap indexes; for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex local_index = m_model->index(i, 0); QString name = m_model->data(local_index, AssetParameterModel::NameRole).toString(); indexes.insert(name, local_index); } flowLayout->addWidget(m_lift); flowLayout->addWidget(m_gamma); flowLayout->addWidget(m_gain); setLayout(flowLayout); slotRefresh(); - connect(this, &LumaLiftGainParam::liftChanged, - [this, indexes]() { - QColor liftColor = m_lift->color(); - emit valueChanged(indexes.value(QStringLiteral("lift_r")), m_locale.toString(liftColor.redF()), true); - emit valueChanged(indexes.value(QStringLiteral("lift_g")), m_locale.toString(liftColor.greenF()), true); - emit valueChanged(indexes.value(QStringLiteral("lift_b")), m_locale.toString(liftColor.blueF()), true); - }); - connect(this, &LumaLiftGainParam::gammaChanged, - [this, indexes]() { - QColor gammaColor = m_gamma->color(); - emit valueChanged(indexes.value(QStringLiteral("gamma_r")), m_locale.toString(gammaColor.redF() * GAMMA_FACTOR), true); - emit valueChanged(indexes.value(QStringLiteral("gamma_g")), m_locale.toString(gammaColor.greenF() * GAMMA_FACTOR), true); - emit valueChanged(indexes.value(QStringLiteral("gamma_b")), m_locale.toString(gammaColor.blueF() * GAMMA_FACTOR), true); - }); - connect(this, &LumaLiftGainParam::gainChanged, - [this, indexes]() { - QColor gainColor = m_gain->color(); - emit valueChanged(indexes.value(QStringLiteral("gain_r")), m_locale.toString(gainColor.redF()* GAIN_FACTOR), true); - emit valueChanged(indexes.value(QStringLiteral("gain_g")), m_locale.toString(gainColor.greenF()* GAIN_FACTOR), true); - emit valueChanged(indexes.value(QStringLiteral("gain_b")), m_locale.toString(gainColor.blueF()* GAIN_FACTOR), true); - }); + connect(this, &LumaLiftGainParam::liftChanged, [this, indexes]() { + QColor liftColor = m_lift->color(); + emit valueChanged(indexes.value(QStringLiteral("lift_r")), m_locale.toString(liftColor.redF()), true); + emit valueChanged(indexes.value(QStringLiteral("lift_g")), m_locale.toString(liftColor.greenF()), true); + emit valueChanged(indexes.value(QStringLiteral("lift_b")), m_locale.toString(liftColor.blueF()), true); + }); + connect(this, &LumaLiftGainParam::gammaChanged, [this, indexes]() { + QColor gammaColor = m_gamma->color(); + emit valueChanged(indexes.value(QStringLiteral("gamma_r")), m_locale.toString(gammaColor.redF() * GAMMA_FACTOR), true); + emit valueChanged(indexes.value(QStringLiteral("gamma_g")), m_locale.toString(gammaColor.greenF() * GAMMA_FACTOR), true); + emit valueChanged(indexes.value(QStringLiteral("gamma_b")), m_locale.toString(gammaColor.blueF() * GAMMA_FACTOR), true); + }); + connect(this, &LumaLiftGainParam::gainChanged, [this, indexes]() { + QColor gainColor = m_gain->color(); + emit valueChanged(indexes.value(QStringLiteral("gain_r")), m_locale.toString(gainColor.redF() * GAIN_FACTOR), true); + emit valueChanged(indexes.value(QStringLiteral("gain_g")), m_locale.toString(gainColor.greenF() * GAIN_FACTOR), true); + emit valueChanged(indexes.value(QStringLiteral("gain_b")), m_locale.toString(gainColor.blueF() * GAIN_FACTOR), true); + }); } void LumaLiftGainParam::updateEffect(QDomElement &effect) { QColor lift = m_lift->color(); QColor gamma = m_gamma->color(); QColor gain = m_gain->color(); QMap values; values.insert(QStringLiteral("lift_r"), lift.redF()); values.insert(QStringLiteral("lift_g"), lift.greenF()); values.insert(QStringLiteral("lift_b"), lift.blueF()); values.insert(QStringLiteral("gamma_r"), gamma.redF() * GAMMA_FACTOR); values.insert(QStringLiteral("gamma_g"), gamma.greenF() * GAMMA_FACTOR); values.insert(QStringLiteral("gamma_b"), gamma.blueF() * GAMMA_FACTOR); values.insert(QStringLiteral("gain_r"), gain.redF() * GAIN_FACTOR); values.insert(QStringLiteral("gain_g"), gain.greenF() * GAIN_FACTOR); values.insert(QStringLiteral("gain_b"), gain.blueF() * GAIN_FACTOR); QDomNodeList namenode = effect.childNodes(); for (int i = 0; i < namenode.count(); ++i) { QDomElement pa = namenode.item(i).toElement(); if (pa.tagName() != QLatin1String("parameter")) { continue; } if (values.contains(pa.attribute(QStringLiteral("name")))) { pa.setAttribute(QStringLiteral("value"), (int)(values.value(pa.attribute(QStringLiteral("name"))) * m_locale.toDouble(pa.attribute(QStringLiteral("factor"), QStringLiteral("1"))))); } } } -void LumaLiftGainParam::slotShowComment(bool ) -{ -} +void LumaLiftGainParam::slotShowComment(bool) {} void LumaLiftGainParam::slotRefresh() { - qDebug()<<"//REFRESHING WIDGET START--------------__"; + qDebug() << "//REFRESHING WIDGET START--------------__"; QMap values; for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex local_index = m_model->index(i, 0); QString name = m_model->data(local_index, AssetParameterModel::NameRole).toString(); double val = m_locale.toDouble(m_model->data(local_index, AssetParameterModel::ValueRole).toString()); values.insert(name, val); } QColor lift = QColor::fromRgbF(values.value(QStringLiteral("lift_r")), values.value(QStringLiteral("lift_g")), values.value(QStringLiteral("lift_b"))); QColor gamma = QColor::fromRgbF(values.value(QStringLiteral("gamma_r")) / GAMMA_FACTOR, values.value(QStringLiteral("gamma_g")) / GAMMA_FACTOR, values.value(QStringLiteral("gamma_b")) / GAMMA_FACTOR); QColor gain = QColor::fromRgbF(values.value(QStringLiteral("gain_r")) / GAIN_FACTOR, values.value(QStringLiteral("gain_g")) / GAIN_FACTOR, values.value(QStringLiteral("gain_b")) / GAIN_FACTOR); - qDebug()<<"//REFRESHING WIDGET START 2--------------__"; + qDebug() << "//REFRESHING WIDGET START 2--------------__"; m_lift->setColor(lift); m_gamma->setColor(gamma); m_gain->setColor(gain); - qDebug()<<"//REFRESHING WIDGET START DONE--------------__"; + qDebug() << "//REFRESHING WIDGET START DONE--------------__"; } diff --git a/src/assets/view/widgets/lumaliftgainparam.hpp b/src/assets/view/widgets/lumaliftgainparam.hpp index 06a8ade0a..6a07b3a1e 100644 --- a/src/assets/view/widgets/lumaliftgainparam.hpp +++ b/src/assets/view/widgets/lumaliftgainparam.hpp @@ -1,70 +1,69 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@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) 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 LUMALIFTGAINPARAMWIDGET_H #define LUMALIFTGAINPARAMWIDGET_H #include "abstractparamwidget.hpp" #include #include #include class ColorWheel; /** * @class LumaLiftGainParam * @brief Provides options to choose 3 colors. * @author Jean-Baptiste Mardelle */ class LumaLiftGainParam : public AbstractParamWidget { Q_OBJECT public: /** @brief Sets up the widget. - * @param text (optional) What the color will be used for - * @param color (optional) initial color - * @param alphaEnabled (optional) Should transparent colors be enabled */ + * @param text (optional) What the color will be used for + * @param color (optional) initial color + * @param alphaEnabled (optional) Should transparent colors be enabled */ explicit LumaLiftGainParam(std::shared_ptr model, QModelIndex index, QWidget *parent); void updateEffect(QDomElement &effect); private: QLocale m_locale; ColorWheel *m_lift; ColorWheel *m_gamma; ColorWheel *m_gain; signals: /** @brief Emitted whenever a different color was chosen. */ void liftChanged(); void gammaChanged(); void gainChanged(); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - }; #endif diff --git a/src/assets/view/widgets/positioneditwidget.cpp b/src/assets/view/widgets/positioneditwidget.cpp index bd4685d7f..40ace5f32 100644 --- a/src/assets/view/widgets/positioneditwidget.cpp +++ b/src/assets/view/widgets/positioneditwidget.cpp @@ -1,131 +1,131 @@ /*************************************************************************** positionedit.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 "positioneditwidget.hpp" -#include "kdenlivesettings.h" -#include "timecodedisplay.h" +#include "assets/model/assetparametermodel.hpp" #include "core.h" +#include "kdenlivesettings.h" #include "monitor/monitormanager.h" -#include "assets/model/assetparametermodel.hpp" +#include "timecodedisplay.h" #include #include #include PositionEditWidget::PositionEditWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { auto *layout = new QHBoxLayout(this); QString name = m_model->data(m_index, Qt::DisplayRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); QLabel *label = new QLabel(name, this); m_slider = new QSlider(Qt::Horizontal, this); m_slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); m_display = new TimecodeDisplay(pCore->monitorManager()->timecode(), this); m_display->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred)); layout->addWidget(label); layout->addWidget(m_slider); layout->addWidget(m_display); m_inverted = m_model->data(m_index, AssetParameterModel::DefaultRole).toInt() < 0; slotRefresh(); connect(m_slider, &QAbstractSlider::valueChanged, m_display, static_cast(&TimecodeDisplay::setValue)); connect(m_display, &TimecodeDisplay::timeCodeEditingFinished, m_slider, &QAbstractSlider::setValue); connect(m_slider, &QAbstractSlider::valueChanged, this, &PositionEditWidget::valueChanged); // emit the signal of the base class when appropriate connect(this->m_slider, &QAbstractSlider::valueChanged, [this](int val) { - if (m_inverted) { val = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt() + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt() - val; + if (m_inverted) { + val = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt() + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt() - + val; } else if (!m_model->data(m_index, AssetParameterModel::RelativePosRole).toBool()) { val += m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); } - emit AbstractParamWidget::valueChanged(m_index, QString::number(val), true); }); + emit AbstractParamWidget::valueChanged(m_index, QString::number(val), true); + }); setToolTip(comment); } -PositionEditWidget::~PositionEditWidget() -{ -} +PositionEditWidget::~PositionEditWidget() {} void PositionEditWidget::updateTimecodeFormat() { m_display->slotUpdateTimeCodeFormat(); } int PositionEditWidget::getPosition() const { return m_slider->value(); } void PositionEditWidget::setPosition(int pos) { m_slider->setValue(pos); } void PositionEditWidget::slotUpdatePosition() { m_slider->blockSignals(true); m_slider->setValue(m_display->getValue()); m_slider->blockSignals(false); emit valueChanged(); } void PositionEditWidget::slotRefresh() { int min = m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); int max = min + m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); const QSignalBlocker blocker(m_slider); const QSignalBlocker blocker2(m_display); QVariant value = m_model->data(m_index, AssetParameterModel::ValueRole); int val; if (value.isNull()) { val = m_model->data(m_index, AssetParameterModel::DefaultRole).toInt(); if (m_inverted) { - val = - val; + val = -val; } } else { val = value.toInt(); if (m_inverted) { if (val < 0) { - val = - val; + val = -val; } else { val = max - value.toInt(); } } } m_slider->setRange(0, max - min); m_display->setRange(0, max - min); if (!m_inverted && !m_model->data(m_index, AssetParameterModel::RelativePosRole).toBool()) { val -= min; } m_slider->setValue(val); m_display->setValue(val); } - bool PositionEditWidget::isValid() const { return m_slider->minimum() != m_slider->maximum(); } void PositionEditWidget::slotShowComment(bool show) { Q_UNUSED(show); } diff --git a/src/assets/view/widgets/positioneditwidget.hpp b/src/assets/view/widgets/positioneditwidget.hpp index 9fbd93706..b88ecbe53 100644 --- a/src/assets/view/widgets/positioneditwidget.hpp +++ b/src/assets/view/widgets/positioneditwidget.hpp @@ -1,72 +1,71 @@ /*************************************************************************** positionedit.h - 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. * * * ***************************************************************************/ #ifndef POSITIONWIDGET_H #define POSITIONWIDGET_H +#include "abstractparamwidget.hpp" #include "timecode.h" #include #include -#include "abstractparamwidget.hpp" class QSlider; class TimecodeDisplay; /*@brief This class is used to display a parameter with time value */ class PositionEditWidget : public AbstractParamWidget { Q_OBJECT public: /** @brief Sets up the parameter's GUI.*/ explicit PositionEditWidget(std::shared_ptr model, QModelIndex index, QWidget *parent = nullptr); ~PositionEditWidget(); /** @brief get current position */ int getPosition() const; /** @brief set position */ void setPosition(int pos); /** @brief Call this when the timecode has been changed project-wise */ void updateTimecodeFormat(); /** @brief checks that the allowed time interval is valid */ bool isValid() const; public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - private: TimecodeDisplay *m_display; QSlider *m_slider; bool m_inverted; private slots: void slotUpdatePosition(); signals: void valueChanged(); }; #endif diff --git a/src/assets/view/widgets/slidewidget.cpp b/src/assets/view/widgets/slidewidget.cpp index cc5e7d136..fc24b539a 100644 --- a/src/assets/view/widgets/slidewidget.cpp +++ b/src/assets/view/widgets/slidewidget.cpp @@ -1,228 +1,225 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@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) 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 "slidewidget.hpp" #include "assets/model/assetparametermodel.hpp" - SlideWidget::SlideWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { // setup the comment setupUi(this); QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); setToolTip(comment); slotRefresh(); connect(end_up, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(end_down, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(end_left, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(end_right, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(end_center, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_up, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_down, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_left, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_right, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_center, &QAbstractButton::clicked, this, &SlideWidget::updateValue); connect(start_transp, &QAbstractSlider::valueChanged, this, &SlideWidget::updateValue); connect(end_transp, &QAbstractSlider::valueChanged, this, &SlideWidget::updateValue); // emit the signal of the base class when appropriate connect(this, &SlideWidget::modified, [this](const QString &val) { emit valueChanged(m_index, val, true); }); // setup comment setToolTip(comment); } -void SlideWidget::slotShowComment(bool ) -{ -} +void SlideWidget::slotShowComment(bool) {} void SlideWidget::slotRefresh() { - QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); + QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); QColor bg = QPalette().highlight().color(); setStyleSheet(QStringLiteral("QPushButton:checked {background-color:rgb(%1,%2,%3);}").arg(bg.red()).arg(bg.green()).arg(bg.blue())); wipeInfo w = getWipeInfo(value); switch (w.start) { - case UP: - start_up->setChecked(true); - break; - case DOWN: - start_down->setChecked(true); - break; - case RIGHT: - start_right->setChecked(true); - break; - case LEFT: - start_left->setChecked(true); - break; - default: - start_center->setChecked(true); - break; - } - switch (w.end) { - case UP: - end_up->setChecked(true); - break; - case DOWN: - end_down->setChecked(true); - break; - case RIGHT: - end_right->setChecked(true); - break; - case LEFT: - end_left->setChecked(true); - break; - default: - end_center->setChecked(true); - break; - } - start_transp->setValue(w.startTransparency); - end_transp->setValue(w.endTransparency); + case UP: + start_up->setChecked(true); + break; + case DOWN: + start_down->setChecked(true); + break; + case RIGHT: + start_right->setChecked(true); + break; + case LEFT: + start_left->setChecked(true); + break; + default: + start_center->setChecked(true); + break; + } + switch (w.end) { + case UP: + end_up->setChecked(true); + break; + case DOWN: + end_down->setChecked(true); + break; + case RIGHT: + end_right->setChecked(true); + break; + case LEFT: + end_left->setChecked(true); + break; + default: + end_center->setChecked(true); + break; + } + start_transp->setValue(w.startTransparency); + end_transp->setValue(w.endTransparency); } void SlideWidget::updateValue() { wipeInfo info; if (start_left->isChecked()) { info.start = LEFT; } else if (start_right->isChecked()) { info.start = RIGHT; } else if (start_up->isChecked()) { info.start = UP; } else if (start_down->isChecked()) { info.start = DOWN; } else if (start_center->isChecked()) { info.start = CENTER; } else { info.start = LEFT; } info.startTransparency = start_transp->value(); if (end_left->isChecked()) { info.end = LEFT; } else if (end_right->isChecked()) { info.end = RIGHT; } else if (end_up->isChecked()) { info.end = UP; } else if (end_down->isChecked()) { info.end = DOWN; } else if (end_center->isChecked()) { info.end = CENTER; } else { info.end = RIGHT; } info.endTransparency = end_transp->value(); emit modified(getWipeString(info)); } SlideWidget::wipeInfo SlideWidget::getWipeInfo(QString value) { wipeInfo info; // Convert old geometry values that used a comma as separator if (value.contains(QLatin1Char(','))) { value.replace(',', '/'); } QString start = value.section(QLatin1Char(';'), 0, 0); QString end = value.section(QLatin1Char(';'), 1, 1).section(QLatin1Char('='), 1, 1); if (start.startsWith(QLatin1String("-100%/0"))) { info.start = LEFT; } else if (start.startsWith(QLatin1String("100%/0"))) { info.start = RIGHT; } else if (start.startsWith(QLatin1String("0%/100%"))) { info.start = DOWN; } else if (start.startsWith(QLatin1String("0%/-100%"))) { info.start = UP; } else { info.start = CENTER; } if (start.count(':') == 2) { info.startTransparency = start.section(QLatin1Char(':'), -1).toInt(); } else { info.startTransparency = 100; } if (end.startsWith(QLatin1String("-100%/0"))) { info.end = LEFT; } else if (end.startsWith(QLatin1String("100%/0"))) { info.end = RIGHT; } else if (end.startsWith(QLatin1String("0%/100%"))) { info.end = DOWN; } else if (end.startsWith(QLatin1String("0%/-100%"))) { info.end = UP; } else { info.end = CENTER; } if (end.count(':') == 2) { info.endTransparency = end.section(QLatin1Char(':'), -1).toInt(); } else { info.endTransparency = 100; } return info; } const QString SlideWidget::getWipeString(wipeInfo info) { QString start; QString end; switch (info.start) { case LEFT: start = QStringLiteral("-100%/0%:100%x100%"); break; case RIGHT: start = QStringLiteral("100%/0%:100%x100%"); break; case DOWN: start = QStringLiteral("0%/100%:100%x100%"); break; case UP: start = QStringLiteral("0%/-100%:100%x100%"); break; default: start = QStringLiteral("0%/0%:100%x100%"); break; } start.append(':' + QString::number(info.startTransparency)); switch (info.end) { case LEFT: end = QStringLiteral("-100%/0%:100%x100%"); break; case RIGHT: end = QStringLiteral("100%/0%:100%x100%"); break; case DOWN: end = QStringLiteral("0%/100%:100%x100%"); break; case UP: end = QStringLiteral("0%/-100%:100%x100%"); break; default: end = QStringLiteral("0%/0%:100%x100%"); break; } end.append(':' + QString::number(info.endTransparency)); return QString(start + QStringLiteral(";-1=") + end); } diff --git a/src/assets/view/widgets/slidewidget.hpp b/src/assets/view/widgets/slidewidget.hpp index 1056d9c70..78e1f7621 100644 --- a/src/assets/view/widgets/slidewidget.hpp +++ b/src/assets/view/widgets/slidewidget.hpp @@ -1,77 +1,74 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@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) 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 SLIDEWIDGET_H #define SLIDEWIDGET_H #include "abstractparamwidget.hpp" #include "ui_wipeval_ui.h" #include - /** * @class SlideWidget * @brief Provides options to choose slide. * @author Jean-Baptiste Mardelle */ class SlideWidget : public AbstractParamWidget, public Ui::Wipeval_UI { Q_OBJECT public: - enum WIPE_DIRECTON { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, CENTER = 4 }; struct wipeInfo { WIPE_DIRECTON start; WIPE_DIRECTON end; int startTransparency; int endTransparency; }; /** @brief Sets up the widget. - */ + */ explicit SlideWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); /** @brief Gets the chosen slide. */ QString getSlide() const; private: wipeInfo getWipeInfo(QString value); const QString getWipeString(wipeInfo info); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; /** @brief Updates the different color choosing options to have all selected @param color. */ void updateValue(); signals: /** @brief Emitted whenever a different color was chosen. */ void modified(const QString &); - }; #endif diff --git a/src/assets/view/widgets/switchparamwidget.cpp b/src/assets/view/widgets/switchparamwidget.cpp index 5833ce96f..84c83c5eb 100644 --- a/src/assets/view/widgets/switchparamwidget.cpp +++ b/src/assets/view/widgets/switchparamwidget.cpp @@ -1,58 +1,62 @@ /*************************************************************************** * Copyright (C) 2018 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 "switchparamwidget.hpp" #include "assets/model/assetparametermodel.hpp" SwitchParamWidget::SwitchParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { setupUi(this); // setup the comment QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); setToolTip(comment); m_labelComment->setText(comment); m_widgetComment->setHidden(true); // setup the name m_labelName->setText(m_model->data(m_index, Qt::DisplayRole).toString()); // set check state slotRefresh(); // emit the signal of the base class when appropriate connect(this->m_checkBox, &QCheckBox::stateChanged, [this](int) { - emit valueChanged(m_index, (m_checkBox->isChecked() ? m_model->data(m_index, AssetParameterModel::MaxRole).toString() : m_model->data(m_index, AssetParameterModel::MinRole).toString()), true); }); + emit valueChanged(m_index, + (m_checkBox->isChecked() ? m_model->data(m_index, AssetParameterModel::MaxRole).toString() + : m_model->data(m_index, AssetParameterModel::MinRole).toString()), + true); + }); } void SwitchParamWidget::slotShowComment(bool show) { if (!m_labelComment->text().isEmpty()) { m_widgetComment->setVisible(show); } } void SwitchParamWidget::slotRefresh() { m_checkBox->setChecked(m_model->data(m_index, AssetParameterModel::ValueRole) == m_model->data(m_index, AssetParameterModel::MaxRole)); } diff --git a/src/assets/view/widgets/switchparamwidget.hpp b/src/assets/view/widgets/switchparamwidget.hpp index 64caf3df0..57b08c5ac 100644 --- a/src/assets/view/widgets/switchparamwidget.hpp +++ b/src/assets/view/widgets/switchparamwidget.hpp @@ -1,55 +1,54 @@ /*************************************************************************** * Copyright (C) 2018 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 . * ***************************************************************************/ #ifndef SWITCHPARAMWIDGET_H #define SWITCHPARAMWIDGET_H #include "abstractparamwidget.hpp" #include "ui_boolparamwidget_ui.h" #include /** @brief This class represents a parameter that requires the user to choose tick a checkbox */ class SwitchParamWidget : public AbstractParamWidget, public Ui::BoolParamWidget_UI { Q_OBJECT public: /** @brief Constructor for the widgetComment @param name String containing the name of the parameter @param comment Optional string containing the comment associated to the parameter @param checked Boolean indicating wether the checkbox should initially be checked @param parent Parent widget */ SwitchParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - }; #endif diff --git a/src/assets/view/widgets/urlparamwidget.cpp b/src/assets/view/widgets/urlparamwidget.cpp index 5268b333e..ddadddf0d 100644 --- a/src/assets/view/widgets/urlparamwidget.cpp +++ b/src/assets/view/widgets/urlparamwidget.cpp @@ -1,65 +1,64 @@ /*************************************************************************** * Copyright (C) 2018 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 "urlparamwidget.hpp" #include "assets/model/assetparametermodel.hpp" #include UrlParamWidget::UrlParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { setupUi(this); // setup the comment QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); labelComment->setText(comment); setToolTip(comment); labelComment->setHidden(true); QString filter = m_model->data(m_index, AssetParameterModel::FilterRole).toString(); if (!filter.isEmpty()) { urlwidget->setFilter(filter); } slotRefresh(); // setup the name label->setText(m_model->data(m_index, Qt::DisplayRole).toString()); // set check state slotRefresh(); // emit the signal of the base class when appropriate - connect(this->urlwidget, &KUrlRequester::urlSelected, [this](QUrl url) { - emit valueChanged(m_index, url.toLocalFile(), true); }); + connect(this->urlwidget, &KUrlRequester::urlSelected, [this](QUrl url) { emit valueChanged(m_index, url.toLocalFile(), true); }); } void UrlParamWidget::slotShowComment(bool show) { if (!labelComment->text().isEmpty()) { labelComment->setVisible(show); } } void UrlParamWidget::slotRefresh() { urlwidget->setUrl(QUrl::fromLocalFile(m_model->data(m_index, AssetParameterModel::ValueRole).toString())); } diff --git a/src/assets/view/widgets/urlparamwidget.hpp b/src/assets/view/widgets/urlparamwidget.hpp index 1f62d8b4c..85a680915 100644 --- a/src/assets/view/widgets/urlparamwidget.hpp +++ b/src/assets/view/widgets/urlparamwidget.hpp @@ -1,55 +1,54 @@ /*************************************************************************** * Copyright (C) 2018 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 . * ***************************************************************************/ #ifndef URLPARAMWIDGET_H #define URLPARAMWIDGET_H #include "abstractparamwidget.hpp" #include "ui_urlval_ui.h" #include /** @brief This class represents a parameter that requires the user to choose tick a checkbox */ class UrlParamWidget : public AbstractParamWidget, public Ui::Urlval_UI { Q_OBJECT public: /** @brief Constructor for the widgetComment @param name String containing the name of the parameter @param comment Optional string containing the comment associated to the parameter @param checked Boolean indicating wether the checkbox should initially be checked @param parent Parent widget */ UrlParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent); public slots: /** @brief Toggle the comments on or off */ void slotShowComment(bool show) override; /** @brief refresh the properties to reflect changes in the model */ void slotRefresh() override; - }; #endif diff --git a/src/audiospectrum/audiographspectrum.h b/src/audiospectrum/audiographspectrum.h index ba45aa8cc..3ea8ecb2f 100644 --- a/src/audiospectrum/audiographspectrum.h +++ b/src/audiospectrum/audiographspectrum.h @@ -1,93 +1,93 @@ /*************************************************************************** * 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 . * ***************************************************************************/ /*! -* @class AudioGraphSpectrum -* @brief An audio spectrum allowing to edit audio level / equalize clips or tracks -* @author Jean-Baptiste Mardelle -*/ + * @class AudioGraphSpectrum + * @brief An audio spectrum allowing to edit audio level / equalize clips or tracks + * @author Jean-Baptiste Mardelle + */ #ifndef AUDIOGRAPHSPECTRUM_H #define AUDIOGRAPHSPECTRUM_H #include "../monitor/scopes/sharedframe.h" #include #include #include namespace Mlt { class Filter; } class MonitorManager; class EqualizerWidget : public QWidget { Q_OBJECT public: explicit EqualizerWidget(QWidget *parent = nullptr); }; class AudioGraphWidget : public QWidget { Q_OBJECT public: explicit AudioGraphWidget(QWidget *parent = nullptr); void drawBackground(); public slots: void showAudio(const QVector &bands); protected: void paintEvent(QPaintEvent *pe); void resizeEvent(QResizeEvent *event); private: QVector m_levels; QVector m_dbLabels; QStringList m_freqLabels; QPixmap m_pixmap; QRect m_rect; int m_maxDb; void drawDbLabels(QPainter &p, const QRect &rect); void drawChanLabels(QPainter &p, const QRect &rect, int barWidth); }; class AudioGraphSpectrum : public QWidget { Q_OBJECT public: AudioGraphSpectrum(MonitorManager *manager, QWidget *parent = nullptr); virtual ~AudioGraphSpectrum(); private: MonitorManager *m_manager; Mlt::Filter *m_filter; AudioGraphWidget *m_graphWidget; EqualizerWidget *m_equalizer; public slots: void processSpectrum(const SharedFrame &frame); void refreshPixmap(); }; #endif diff --git a/src/bin/abstractprojectitem.cpp b/src/bin/abstractprojectitem.cpp index bdf70209a..7f3933f49 100644 --- a/src/bin/abstractprojectitem.cpp +++ b/src/bin/abstractprojectitem.cpp @@ -1,291 +1,294 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 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 "abstractprojectitem.h" #include "bin.h" #include "core.h" #include "jobs/jobmanager.h" #include "macros.hpp" #include "projectitemmodel.h" #include "jobs/audiothumbjob.hpp" #include "jobs/loadjob.hpp" #include "jobs/thumbjob.hpp" #include #include #include AbstractProjectItem::AbstractProjectItem(PROJECTITEMTYPE type, const QString &id, const std::shared_ptr &model, bool isRoot) : TreeItem(QList(), std::static_pointer_cast(model), isRoot) , m_name() , m_description() , m_thumbnail(QIcon()) , m_date() , m_binId(id) , m_usage(0) , m_clipStatus(StatusReady) , m_itemType(type) , m_lock(QReadWriteLock::Recursive) , m_isCurrent(false) { Q_ASSERT(!isRoot || type == FolderItem); } bool AbstractProjectItem::operator==(const std::shared_ptr &projectItem) const { // FIXME: only works for folders bool equal = this->m_childItems == projectItem->m_childItems; // equal = equal && (m_parentItem == projectItem->m_parentItem); return equal; } std::shared_ptr AbstractProjectItem::parent() const { return std::static_pointer_cast(m_parentItem.lock()); } void AbstractProjectItem::setRefCount(uint count) { m_usage = count; if (auto ptr = m_model.lock()) - std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::UsageCount); + std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), + AbstractProjectItem::UsageCount); } uint AbstractProjectItem::refCount() const { return m_usage; } void AbstractProjectItem::addRef() { m_usage++; if (auto ptr = m_model.lock()) - std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::UsageCount); + std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), + AbstractProjectItem::UsageCount); } void AbstractProjectItem::removeRef() { m_usage--; if (auto ptr = m_model.lock()) - std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::UsageCount); + std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), + AbstractProjectItem::UsageCount); } const QString &AbstractProjectItem::clipId() const { return m_binId; } QPixmap AbstractProjectItem::roundedPixmap(const QPixmap &source) { QPixmap pix(source.width(), source.height()); pix.fill(Qt::transparent); QPainter p(&pix); p.setRenderHint(QPainter::Antialiasing, true); QPainterPath path; path.addRoundedRect(0.5, 0.5, pix.width() - 1, pix.height() - 1, 4, 4); p.setClipPath(path); p.drawPixmap(0, 0, source); p.end(); return pix; } AbstractProjectItem::PROJECTITEMTYPE AbstractProjectItem::itemType() const { return m_itemType; } QVariant AbstractProjectItem::getData(DataType type) const { QVariant data; switch (type) { case DataName: data = QVariant(m_name); break; case DataDescription: data = QVariant(m_description); break; case DataThumbnail: data = QVariant(m_thumbnail); break; case DataId: data = QVariant(m_id); break; case DataDuration: data = QVariant(m_duration); break; case DataDate: data = QVariant(m_date); break; case UsageCount: data = QVariant(m_usage); break; case ItemTypeRole: data = QVariant(m_itemType); break; case JobType: if (itemType() == ClipItem) { auto jobIds = pCore->jobManager()->getPendingJobsIds(clipId()); if (jobIds.empty()) { jobIds = pCore->jobManager()->getFinishedJobsIds(clipId()); } if (jobIds.size() > 0) { data = QVariant(pCore->jobManager()->getJobType(jobIds[0])); } } break; case JobStatus: if (itemType() == ClipItem) { auto jobIds = pCore->jobManager()->getPendingJobsIds(clipId()); if (jobIds.empty()) { jobIds = pCore->jobManager()->getFinishedJobsIds(clipId()); } if (jobIds.size() > 0) { data = QVariant::fromValue(pCore->jobManager()->getJobStatus(jobIds[0])); } else { data = QVariant::fromValue(JobManagerStatus::NoJob); } } break; case JobProgress: if (itemType() == ClipItem) { auto jobIds = pCore->jobManager()->getPendingJobsIds(clipId()); if (jobIds.size() > 0) { data = QVariant(pCore->jobManager()->getJobProgressForClip(jobIds[0], clipId())); } else { data = QVariant(0); } } break; case JobMessage: if (itemType() == ClipItem) { QString messages; auto jobIds = pCore->jobManager()->getPendingJobsIds(clipId()); for (int job : jobIds) { messages.append(pCore->jobManager()->getJobMessageForClip(job, clipId())); } jobIds = pCore->jobManager()->getFinishedJobsIds(clipId()); for (int job : jobIds) { messages.append(pCore->jobManager()->getJobMessageForClip(job, clipId())); } data = QVariant(messages); } break; case ClipStatus: data = QVariant(m_clipStatus); break; case ClipToolTip: data = QVariant(getToolTip()); break; default: break; } return data; } int AbstractProjectItem::supportedDataCount() const { return 3; } QString AbstractProjectItem::name() const { return m_name; } void AbstractProjectItem::setName(const QString &name) { m_name = name; } QString AbstractProjectItem::description() const { return m_description; } void AbstractProjectItem::setDescription(const QString &description) { m_description = description; } QPoint AbstractProjectItem::zone() const { return QPoint(); } void AbstractProjectItem::setClipStatus(CLIPSTATUS status) { m_clipStatus = status; } bool AbstractProjectItem::statusReady() const { return m_clipStatus == StatusReady; } AbstractProjectItem::CLIPSTATUS AbstractProjectItem::clipStatus() const { return m_clipStatus; } std::shared_ptr AbstractProjectItem::getEnclosingFolder(bool strict) { if (!strict && itemType() == AbstractProjectItem::FolderItem) { return std::static_pointer_cast(shared_from_this()); } if (auto ptr = m_parentItem.lock()) { return std::static_pointer_cast(ptr)->getEnclosingFolder(false); } return std::shared_ptr(); } bool AbstractProjectItem::selfSoftDelete(Fun &undo, Fun &redo) { pCore->jobManager()->slotDiscardClipJobs(clipId()); Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; for (const auto &child : m_childItems) { bool res = std::static_pointer_cast(child)->selfSoftDelete(local_undo, local_redo); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } QString AbstractProjectItem::lastParentId() const { return m_lastParentId; } void AbstractProjectItem::updateParent(std::shared_ptr newParent) { - //bool reload = !m_lastParentId.isEmpty(); + // bool reload = !m_lastParentId.isEmpty(); m_lastParentId.clear(); if (newParent) { m_lastParentId = std::static_pointer_cast(newParent)->clipId(); } TreeItem::updateParent(newParent); } diff --git a/src/bin/binplaylist.cpp b/src/bin/binplaylist.cpp index bbc83e14a..ca8e14a15 100644 --- a/src/bin/binplaylist.cpp +++ b/src/bin/binplaylist.cpp @@ -1,186 +1,186 @@ /*************************************************************************** * 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 "binplaylist.hpp" #include "abstractprojectitem.h" #include "bin/model/markerlistmodel.hpp" #include "core.h" #include "profiles/profilemodel.hpp" #include "projectclip.h" #include QString BinPlaylist::binPlaylistId = QStringLiteral("main bin"); BinPlaylist::BinPlaylist() : m_binPlaylist(new Mlt::Playlist(pCore->getCurrentProfile()->profile())) { m_binPlaylist->set("id", binPlaylistId.toUtf8().constData()); } void BinPlaylist::manageBinItemInsertion(const std::shared_ptr &binElem) { - qDebug() << "MANAGE BIN ITEM INSERT"<clipId(); + qDebug() << "MANAGE BIN ITEM INSERT" << binElem->clipId(); QString id = binElem->clipId(); switch (binElem->itemType()) { case AbstractProjectItem::FolderItem: { // When a folder is inserted, we have to store its path into the properties if (binElem->parent()) { QString propertyName = "kdenlive:folder." + binElem->parent()->clipId() + QLatin1Char('.') + id; m_binPlaylist->set(propertyName.toUtf8().constData(), binElem->name().toUtf8().constData()); } break; } case AbstractProjectItem::ClipItem: { Q_ASSERT(m_allClips.count(id) == 0); auto clip = std::static_pointer_cast(binElem); - qDebug() << "Inserting clip"<clipId(); + qDebug() << "Inserting clip" << binElem->clipId(); if (clip->isValid()) { - qDebug() << "Inserting valid clip"<clipId(); + qDebug() << "Inserting valid clip" << binElem->clipId(); m_binPlaylist->append(*clip->originalProducer().get()); } else { - qDebug() << "Inserting invalid clip"<clipId(); + qDebug() << "Inserting invalid clip" << binElem->clipId(); // if clip is not loaded yet, we insert a dummy producer Mlt::Producer dummy(pCore->getCurrentProfile()->profile(), "color:blue"); dummy.set("kdenlive:id", id.toUtf8().constData()); m_binPlaylist->append(dummy); } m_allClips.insert(id); connect(clip.get(), &ProjectClip::producerChanged, this, &BinPlaylist::changeProducer); break; } default: break; } } void BinPlaylist::manageBinItemDeletion(AbstractProjectItem *binElem) { QString id = binElem->clipId(); switch (binElem->itemType()) { case AbstractProjectItem::FolderItem: { // When a folder is removed, we clear the path info if (!binElem->lastParentId().isEmpty()) { QString propertyName = "kdenlive:folder." + binElem->lastParentId() + QLatin1Char('.') + binElem->clipId(); m_binPlaylist->set(propertyName.toUtf8().constData(), (char *)nullptr); } break; } case AbstractProjectItem::ClipItem: { Q_ASSERT(m_allClips.count(id) > 0); m_allClips.erase(id); removeBinClip(id); disconnect(static_cast(binElem), &ProjectClip::producerChanged, this, &BinPlaylist::changeProducer); } default: break; } } void BinPlaylist::removeBinClip(const QString &id) { // we iterate on the clips of the timeline to find the correct one bool ok = false; int size = m_binPlaylist->count(); for (int i = 0; !ok && i < size; i++) { QScopedPointer prod(m_binPlaylist->get_clip(i)); QString prodId(prod->parent().get("kdenlive:id")); if (prodId == id) { m_binPlaylist->remove(i); ok = true; } } Q_ASSERT(ok); } void BinPlaylist::changeProducer(const QString &id, const std::shared_ptr &producer) { Q_ASSERT(m_allClips.count(id) > 0); removeBinClip(id); m_binPlaylist->append(*producer.get()); } void BinPlaylist::setRetainIn(Mlt::Tractor *modelTractor) { QString retain = QStringLiteral("xml_retain %1").arg(binPlaylistId); modelTractor->set(retain.toUtf8().constData(), m_binPlaylist->get_service(), 0); } void BinPlaylist::saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel) { Q_UNUSED(guideModel) // Clear previous properites Mlt::Properties playlistProps(m_binPlaylist->get_properties()); Mlt::Properties docProperties; docProperties.pass_values(playlistProps, "kdenlive:docproperties."); for (int i = 0; i < docProperties.count(); i++) { QString propName = QStringLiteral("kdenlive:docproperties.%1").arg(docProperties.get_name(i)); playlistProps.set(propName.toUtf8().constData(), (char *)nullptr); } // Clear previous metadata Mlt::Properties docMetadata; docMetadata.pass_values(playlistProps, "kdenlive:docmetadata."); for (int i = 0; i < docMetadata.count(); i++) { QString propName = QStringLiteral("kdenlive:docmetadata.%1").arg(docMetadata.get_name(i)); playlistProps.set(propName.toUtf8().constData(), (char *)nullptr); } QMapIterator i(props); while (i.hasNext()) { i.next(); playlistProps.set(("kdenlive:docproperties." + i.key()).toUtf8().constData(), i.value().toUtf8().constData()); } QMapIterator j(metadata); while (j.hasNext()) { j.next(); playlistProps.set(("kdenlive:docmetadata." + j.key()).toUtf8().constData(), j.value().toUtf8().constData()); } } void BinPlaylist::saveProperty(const QString &name, const QString &value) { m_binPlaylist->set(name.toUtf8().constData(), value.toUtf8().constData()); } QMap BinPlaylist::getProxies(const QString &root) { QMap proxies; int size = m_binPlaylist->count(); for (int i = 0; i < size; i++) { QScopedPointer prod(m_binPlaylist->get_clip(i)); if (!prod->is_valid() || prod->is_blank()) { continue; } QString proxy = prod->parent().get("kdenlive:proxy"); if (proxy.length() > 2) { if (QFileInfo(proxy).isRelative()) { proxy.prepend(root); } QString sourceUrl(prod->parent().get("kdenlive:originalurl")); if (QFileInfo(sourceUrl).isRelative()) { sourceUrl.prepend(root); } proxies.insert(proxy, sourceUrl); } } return proxies; } diff --git a/src/bin/binplaylist.hpp b/src/bin/binplaylist.hpp index aa9470597..124bc6db8 100644 --- a/src/bin/binplaylist.hpp +++ b/src/bin/binplaylist.hpp @@ -1,97 +1,98 @@ /*************************************************************************** * 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 BINPLAYLIST_H #define BINPLAYLIST_H #include "definitions.h" #include #include #include /** @brief This class is a wrapper around a melt playlist that allows to store the Bin. Clips that are in the bin must be added into this playlist so that they are savedn in the project's xml even if not inserted in the actual timeline. The folder structure is also saved as properties. */ class AbstractProjectItem; namespace Mlt { class Playlist; class Producer; class Tractor; -} +} // namespace Mlt class MarkerListModel; class BinPlaylist : public QObject { public: BinPlaylist(); /* @brief This function updates the underlying binPlaylist object to reflect deletion of a bin item @param binElem is the bin item deleted. Note that exceptionnally, this function takes a raw pointer instead of a smart one. This is because the function will be called in the middle of the element's destructor, so no smart pointer is available at that time. */ void manageBinItemDeletion(AbstractProjectItem *binElem); /* @brief This function updates the underlying binPlaylist object to reflect insertion of a bin item @param binElem is the bin item inserted */ void manageBinItemInsertion(const std::shared_ptr &binElem); /* @brief Make sure bin playlist is saved in given tractor. This has a side effect on the tractor */ void setRetainIn(Mlt::Tractor *modelTractor); /** @brief Save document properties in MLT's bin playlist */ void saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel); /** @brief Save a property to main bin */ void saveProperty(const QString &name, const QString &value); /** @brief Retrieve a list of proxy/original urls */ QMap getProxies(const QString &root); // id of the mlt object static QString binPlaylistId; + protected: /* @brief This is an helper function that removes a clip from the playlist given its id */ void removeBinClip(const QString &id); /* @brief This handles the fact that a clip has changed its producer (for example, loading is done) It should be called directly as a slot of ClipController's signal, so you probably don't want to call this directly. @param id: binId of the producer @param producer : new producer */ void changeProducer(const QString &id, const std::shared_ptr &producer); private: /** @brief The MLT playlist holding our Producers */ std::unique_ptr m_binPlaylist; /** @brief Set of the bin inserted */ std::unordered_set m_allClips; }; #endif diff --git a/src/bin/clipcreator.hpp b/src/bin/clipcreator.hpp index 8398bed1a..f501e74a9 100644 --- a/src/bin/clipcreator.hpp +++ b/src/bin/clipcreator.hpp @@ -1,99 +1,99 @@ /*************************************************************************** * 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 CLIPCREATOR_H #define CLIPCREATOR_H #include "definitions.h" #include "undohelper.hpp" #include #include #include /** @brief This namespace provides convenience functions to create clips based on various parameters */ class ProjectItemModel; namespace ClipCreator { /* @brief Create and inserts a color clip @param color : a string of the form "0xff0000ff" (solid red in RGBA) @param duration : duration expressed in number of frames @param name: name of the clip @param parentFolder: the binId of the containing folder @param model: a shared pointer to the bin item model @return the binId of the created clip */ QString createColorClip(const QString &color, int duration, const QString &name, const QString &parentFolder, std::shared_ptr model); /* @brief Create a title clip @param properties : title properties (xmldata, etc) @param duration : duration of the clip @param name: name of the clip @param parentFolder: the binId of the containing folder @param model: a shared pointer to the bin item model */ QString createTitleClip(const std::unordered_map &properties, int duration, const QString &name, const QString &parentFolder, - std::shared_ptr model); + std::shared_ptr model); /* @brief Create a title template @param path : path to the template @param text : text of the template (optional) @param name: name of the clip @param parentFolder: the binId of the containing folder @param model: a shared pointer to the bin item model @return the binId of the created clip */ QString createTitleTemplate(const QString &path, const QString &text, const QString &name, const QString &parentFolder, std::shared_ptr model); /* @brief Create a slideshow clip @param path : path to the selected folder @param duration: this should be nbr of images * duration of one image @param name: name of the clip @param parentFolder: the binId of the containing folder @param properties: description of the slideshow @param model: a shared pointer to the bin item model @return the binId of the created clip */ QString createSlideshowClip(const QString &path, int duration, const QString &name, const QString &parentFolder, const std::unordered_map &properties, std::shared_ptr model); /* @brief Reads a file from disk and create the corresponding clip @param path : path to the file @param parentFolder: the binId of the containing folder @param model: a shared pointer to the bin item model @return the binId of the created clip */ QString createClipFromFile(const QString &path, const QString &parentFolder, std::shared_ptr model, Fun &undo, Fun &redo); bool createClipFromFile(const QString &path, const QString &parentFolder, std::shared_ptr model); /* @brief Iterates recursively through the given url list and add the files it finds, recreating a folder structure @param list: the list of items (can be folders) @param checkRemovable: if true, it will check if files are on removable devices, and warn the user if so @param parentFolder: the binId of the containing folder @param model: a shared pointer to the bin item model */ bool createClipsFromList(const QList &list, bool checkRemovable, const QString &parentFolder, std::shared_ptr model, Fun &undo, Fun &redo); bool createClipsFromList(const QList &list, bool checkRemovable, const QString &parentFolder, std::shared_ptr model); } // namespace ClipCreator #endif diff --git a/src/bin/projectfolder.cpp b/src/bin/projectfolder.cpp index 09fb1e10c..3d80fe93e 100644 --- a/src/bin/projectfolder.cpp +++ b/src/bin/projectfolder.cpp @@ -1,157 +1,155 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 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 "projectfolder.h" #include "bin.h" #include "core.h" #include "projectclip.h" #include "projectitemmodel.h" #include "utils/KoIconUtils.h" #include #include ProjectFolder::ProjectFolder(const QString &id, const QString &name, std::shared_ptr model) : AbstractProjectItem(AbstractProjectItem::FolderItem, id, model) { m_name = name; m_clipStatus = StatusReady; m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("folder")); } std::shared_ptr ProjectFolder::construct(const QString &id, const QString &name, std::shared_ptr model) { std::shared_ptr self(new ProjectFolder(id, name, model)); baseFinishConstruct(self); return self; } ProjectFolder::ProjectFolder(std::shared_ptr model) : AbstractProjectItem(AbstractProjectItem::FolderItem, QString::number(-1), model, true) { m_name = QStringLiteral("root"); } std::shared_ptr ProjectFolder::construct(std::shared_ptr model) { std::shared_ptr self(new ProjectFolder(model)); baseFinishConstruct(self); return self; } -ProjectFolder::~ProjectFolder() -{ -} +ProjectFolder::~ProjectFolder() {} std::shared_ptr ProjectFolder::clip(const QString &id) { for (int i = 0; i < childCount(); ++i) { std::shared_ptr clip = std::static_pointer_cast(child(i))->clip(id); if (clip) { return clip; } } return std::shared_ptr(); } QList> ProjectFolder::childClips() { QList> allChildren; for (int i = 0; i < childCount(); ++i) { std::shared_ptr childItem = std::static_pointer_cast(child(i)); if (childItem->itemType() == ClipItem) { allChildren << std::static_pointer_cast(childItem); } else if (childItem->itemType() == FolderItem) { allChildren << std::static_pointer_cast(childItem)->childClips(); } } return allChildren; } QString ProjectFolder::getToolTip() const { return i18np("%1 clip", "%1 clips", childCount()); } std::shared_ptr ProjectFolder::folder(const QString &id) { if (m_binId == id) { return std::static_pointer_cast(shared_from_this()); } for (int i = 0; i < childCount(); ++i) { std::shared_ptr folderItem = std::static_pointer_cast(child(i))->folder(id); if (folderItem) { return folderItem; } } return std::shared_ptr(); } std::shared_ptr ProjectFolder::clipAt(int index) { if (childCount() == 0) { return std::shared_ptr(); } for (int i = 0; i < childCount(); ++i) { std::shared_ptr clip = std::static_pointer_cast(child(i))->clipAt(index); if (clip) { return clip; } } return std::shared_ptr(); } void ProjectFolder::setBinEffectsEnabled(bool enabled) { for (int i = 0; i < childCount(); ++i) { std::shared_ptr item = std::static_pointer_cast(child(i)); item->setBinEffectsEnabled(enabled); } } QDomElement ProjectFolder::toXml(QDomDocument &document, bool) { QDomElement folder = document.createElement(QStringLiteral("folder")); folder.setAttribute(QStringLiteral("name"), name()); for (int i = 0; i < childCount(); ++i) { folder.appendChild(std::static_pointer_cast(child(i))->toXml(document)); } return folder; } bool ProjectFolder::rename(const QString &name, int column) { Q_UNUSED(column) if (m_name == name) { return false; } // Rename folder if (auto ptr = m_model.lock()) { auto self = std::static_pointer_cast(shared_from_this()); return std::static_pointer_cast(ptr)->requestRenameFolder(self, name); } qDebug() << "ERROR: Impossible to rename folder because model is not available"; Q_ASSERT(false); return false; } diff --git a/src/bin/projectfolderup.cpp b/src/bin/projectfolderup.cpp index 901361814..f1bdf5762 100644 --- a/src/bin/projectfolderup.cpp +++ b/src/bin/projectfolderup.cpp @@ -1,83 +1,79 @@ /* Copyright (C) 2015 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 "projectfolderup.h" #include "projectclip.h" #include "utils/KoIconUtils.h" #include #include ProjectFolderUp::ProjectFolderUp(std::shared_ptr model) : AbstractProjectItem(AbstractProjectItem::FolderUpItem, QString(), model) { m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("go-previous")); m_name = i18n("Back"); } std::shared_ptr ProjectFolderUp::construct(std::shared_ptr model) { std::shared_ptr self(new ProjectFolderUp(model)); baseFinishConstruct(self); return self; } -ProjectFolderUp::~ProjectFolderUp() -{ -} +ProjectFolderUp::~ProjectFolderUp() {} std::shared_ptr ProjectFolderUp::clip(const QString &id) { Q_UNUSED(id) return std::shared_ptr(); } QString ProjectFolderUp::getToolTip() const { return i18n("Go up"); } std::shared_ptr ProjectFolderUp::folder(const QString &id) { Q_UNUSED(id); return std::shared_ptr(); } std::shared_ptr ProjectFolderUp::clipAt(int index) { Q_UNUSED(index); return std::shared_ptr(); } -void ProjectFolderUp::setBinEffectsEnabled(bool) -{ -} +void ProjectFolderUp::setBinEffectsEnabled(bool) {} QDomElement ProjectFolderUp::toXml(QDomDocument &document, bool) { return document.documentElement(); } bool ProjectFolderUp::rename(const QString &, int) { return false; } diff --git a/src/bin/projectitemmodel.cpp b/src/bin/projectitemmodel.cpp index 0122071cb..07358e180 100644 --- a/src/bin/projectitemmodel.cpp +++ b/src/bin/projectitemmodel.cpp @@ -1,844 +1,843 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 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 "projectitemmodel.h" #include "abstractprojectitem.h" #include "binplaylist.hpp" #include "core.h" #include "doc/kdenlivedoc.h" #include "jobs/audiothumbjob.hpp" #include "jobs/jobmanager.h" #include "jobs/loadjob.hpp" #include "jobs/thumbjob.hpp" #include "kdenlivesettings.h" #include "macros.hpp" #include "profiles/profilemodel.hpp" #include "project/projectmanager.h" #include "projectclip.h" #include "projectfolder.h" #include "projectsubclip.h" #include "xml/xml.hpp" #include #include #include #include #include #include ProjectItemModel::ProjectItemModel(QObject *parent) : AbstractTreeModel(parent) , m_lock(QReadWriteLock::Recursive) , m_binPlaylist(new BinPlaylist()) , m_nextId(1) , m_blankThumb() { QPixmap pix(QSize(160, 90)); pix.fill(Qt::lightGray); m_blankThumb.addPixmap(pix); } std::shared_ptr ProjectItemModel::construct(QObject *parent) { std::shared_ptr self(new ProjectItemModel(parent)); self->rootItem = ProjectFolder::construct(self); return self; } -ProjectItemModel::~ProjectItemModel() -{ -} +ProjectItemModel::~ProjectItemModel() {} int ProjectItemModel::mapToColumn(int column) const { switch (column) { case 0: return AbstractProjectItem::DataName; break; case 1: return AbstractProjectItem::DataDate; break; case 2: return AbstractProjectItem::DataDescription; break; default: return AbstractProjectItem::DataName; } } QVariant ProjectItemModel::data(const QModelIndex &index, int role) const { READ_LOCK(); if (!index.isValid()) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { std::shared_ptr item = getBinItemByIndex(index); auto type = static_cast(mapToColumn(index.column())); QVariant ret = item->getData(type); return ret; } if (role == Qt::DecorationRole) { if (index.column() != 0) { return QVariant(); } // Data has to be returned as icon to allow the view to scale it std::shared_ptr item = getBinItemByIndex(index); QVariant thumb = item->getData(AbstractProjectItem::DataThumbnail); QIcon icon; if (thumb.canConvert()) { icon = thumb.value(); } else { qDebug() << "ERROR: invalid icon found"; } return icon; } std::shared_ptr item = getBinItemByIndex(index); return item->getData((AbstractProjectItem::DataType)role); } bool ProjectItemModel::setData(const QModelIndex &index, const QVariant &value, int role) { QWriteLocker locker(&m_lock); std::shared_ptr item = getBinItemByIndex(index); if (item->rename(value.toString(), index.column())) { emit dataChanged(index, index, QVector() << role); return true; } // Item name was not changed return false; } Qt::ItemFlags ProjectItemModel::flags(const QModelIndex &index) const { /*return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable;*/ if (!index.isValid()) { return Qt::ItemIsDropEnabled; } std::shared_ptr item = getBinItemByIndex(index); AbstractProjectItem::PROJECTITEMTYPE type = item->itemType(); switch (type) { case AbstractProjectItem::FolderItem: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; break; case AbstractProjectItem::ClipItem: if (!item->statusReady()) { return Qt::ItemIsSelectable; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable; break; case AbstractProjectItem::SubClipItem: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; break; case AbstractProjectItem::FolderUpItem: return Qt::ItemIsEnabled; break; default: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } } // cppcheck-suppress unusedFunction bool ProjectItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { Q_UNUSED(row) Q_UNUSED(column) if (action == Qt::IgnoreAction) { return true; } if (data->hasUrls()) { emit itemDropped(data->urls(), parent); return true; } if (data->hasFormat(QStringLiteral("kdenlive/producerslist"))) { // Dropping an Bin item const QStringList ids = QString(data->data(QStringLiteral("kdenlive/producerslist"))).split(QLatin1Char(';')); if (ids.constFirst().contains(QLatin1Char('/'))) { // subclip zone QStringList clipData = ids.constFirst().split(QLatin1Char('/')); if (clipData.length() >= 3) { QString id; return requestAddBinSubClip(id, clipData.at(1).toInt(), clipData.at(2).toInt(), clipData.at(0)); } else { // error, malformed clip zone, abort return false; } } else { emit itemDropped(ids, parent); } return true; } if (data->hasFormat(QStringLiteral("kdenlive/effect"))) { // Dropping effect on a Bin item QStringList effectData; effectData << QString::fromUtf8(data->data(QStringLiteral("kdenlive/effect"))); QStringList source = QString::fromUtf8(data->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-')); effectData << source; emit effectDropped(effectData, parent); return true; } if (data->hasFormat(QStringLiteral("kdenlive/clip"))) { const QStringList list = QString(data->data(QStringLiteral("kdenlive/clip"))).split(QLatin1Char(';')); QString id; return requestAddBinSubClip(id, list.at(1).toInt(), list.at(2).toInt(), list.at(0)); } return false; } QVariant ProjectItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { QVariant columnName; switch (section) { case 0: columnName = i18n("Name"); break; case 1: columnName = i18n("Date"); break; case 2: columnName = i18n("Description"); break; default: columnName = i18n("Unknown"); break; } return columnName; } return QAbstractItemModel::headerData(section, orientation, role); } int ProjectItemModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) { return getBinItemByIndex(parent)->supportedDataCount(); } return std::static_pointer_cast(rootItem)->supportedDataCount(); } // cppcheck-suppress unusedFunction Qt::DropActions ProjectItemModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList ProjectItemModel::mimeTypes() const { QStringList types; types << QStringLiteral("kdenlive/producerslist") << QStringLiteral("text/uri-list") << QStringLiteral("kdenlive/clip") << QStringLiteral("kdenlive/effect"); return types; } QMimeData *ProjectItemModel::mimeData(const QModelIndexList &indices) const { // Mime data is a list of id's separated by ';'. // Clip ids are represented like: 2 (where 2 is the clip's id) // Clip zone ids are represented like: 2/10/200 (where 2 is the clip's id, 10 and 200 are in and out points) // Folder ids are represented like: #2 (where 2 is the folder's id) auto *mimeData = new QMimeData(); QStringList list; int duration = 0; for (int i = 0; i < indices.count(); i++) { QModelIndex ix = indices.at(i); if (!ix.isValid() || ix.column() != 0) { continue; } std::shared_ptr item = getBinItemByIndex(ix); AbstractProjectItem::PROJECTITEMTYPE type = item->itemType(); if (type == AbstractProjectItem::ClipItem) { list << item->clipId(); duration += (std::static_pointer_cast(item))->frameDuration(); } else if (type == AbstractProjectItem::SubClipItem) { QPoint p = item->zone(); - list << std::static_pointer_cast(item)->getMasterClip()->clipId() + QLatin1Char('/') + QString::number(p.x()) + QLatin1Char('/') + QString::number(p.y()); + list << std::static_pointer_cast(item)->getMasterClip()->clipId() + QLatin1Char('/') + QString::number(p.x()) + QLatin1Char('/') + + QString::number(p.y()); } else if (type == AbstractProjectItem::FolderItem) { list << "#" + item->clipId(); } } if (!list.isEmpty()) { QByteArray data; data.append(list.join(QLatin1Char(';')).toUtf8()); mimeData->setData(QStringLiteral("kdenlive/producerslist"), data); qDebug() << "/// CLI DURATION: " << duration; mimeData->setText(QString::number(duration)); } return mimeData; } void ProjectItemModel::onItemUpdated(std::shared_ptr item, int role) { auto tItem = std::static_pointer_cast(item); auto ptr = tItem->parentItem().lock(); if (ptr) { auto index = getIndexFromItem(tItem); emit dataChanged(index, index, QVector() << role); } } void ProjectItemModel::onItemUpdated(const QString &binId, int role) { std::shared_ptr item = getItemByBinId(binId); if (item) { onItemUpdated(item, role); } } std::shared_ptr ProjectItemModel::getClipByBinID(const QString &binId) { if (binId.contains(QLatin1Char('_'))) { return getClipByBinID(binId.section(QLatin1Char('_'), 0, 0)); } for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->itemType() == AbstractProjectItem::ClipItem && c->clipId() == binId) { return std::static_pointer_cast(c); } } return nullptr; } bool ProjectItemModel::hasClip(const QString &binId) { return getClipByBinID(binId) != nullptr; } std::shared_ptr ProjectItemModel::getFolderByBinId(const QString &binId) { for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->itemType() == AbstractProjectItem::FolderItem && c->clipId() == binId) { return std::static_pointer_cast(c); } } return nullptr; } std::shared_ptr ProjectItemModel::getItemByBinId(const QString &binId) { for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->clipId() == binId) { return c; } } return nullptr; } void ProjectItemModel::setBinEffectsEnabled(bool enabled) { return std::static_pointer_cast(rootItem)->setBinEffectsEnabled(enabled); } QStringList ProjectItemModel::getEnclosingFolderInfo(const QModelIndex &index) const { QStringList noInfo; noInfo << QString::number(-1); noInfo << QString(); if (!index.isValid()) { return noInfo; } std::shared_ptr currentItem = getBinItemByIndex(index); auto folder = currentItem->getEnclosingFolder(true); if ((folder == nullptr) || folder == rootItem) { return noInfo; } QStringList folderInfo; folderInfo << currentItem->clipId(); folderInfo << currentItem->name(); return folderInfo; } void ProjectItemModel::clean() { std::vector> toDelete; for (int i = 0; i < rootItem->childCount(); ++i) { toDelete.push_back(std::static_pointer_cast(rootItem->child(i))); } Fun undo = []() { return true; }; Fun redo = []() { return true; }; for (const auto &child : toDelete) { requestBinClipDeletion(child, undo, redo); } Q_ASSERT(rootItem->childCount() == 0); m_nextId = 1; } std::shared_ptr ProjectItemModel::getRootFolder() const { return std::static_pointer_cast(rootItem); } void ProjectItemModel::loadSubClips(const QString &id, const QMap &dataMap) { std::shared_ptr clip = getClipByBinID(id); if (!clip) { return; } QMapIterator i(dataMap); QList missingThumbs; int maxFrame = clip->duration().frames(pCore->getCurrentFps()) - 1; Fun undo = []() { return true; }; Fun redo = []() { return true; }; while (i.hasNext()) { i.next(); if (!i.value().contains(QLatin1Char(';'))) { // Problem, the zone has no in/out points continue; } int in = i.value().section(QLatin1Char(';'), 0, 0).toInt(); int out = i.value().section(QLatin1Char(';'), 1, 1).toInt(); if (maxFrame > 0) { out = qMin(out, maxFrame); } QString subId; requestAddBinSubClip(subId, in, out, id, undo, redo); } } std::shared_ptr ProjectItemModel::getBinItemByIndex(const QModelIndex &index) const { return std::static_pointer_cast(getItemById((int)index.internalId())); } bool ProjectItemModel::requestBinClipDeletion(std::shared_ptr clip, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(clip); if (!clip) return false; int parentId = -1; if (auto ptr = clip->parent()) parentId = ptr->getId(); clip->selfSoftDelete(undo, redo); int id = clip->getId(); Fun operation = removeItem_lambda(id); Fun reverse = addItem_lambda(clip, parentId); bool res = operation(); if (res) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return res; } void ProjectItemModel::registerItem(const std::shared_ptr &item) { auto clip = std::static_pointer_cast(item); m_binPlaylist->manageBinItemInsertion(clip); AbstractTreeModel::registerItem(item); } void ProjectItemModel::deregisterItem(int id, TreeItem *item) { auto clip = static_cast(item); m_binPlaylist->manageBinItemDeletion(clip); // TODO : here, we should suspend jobs belonging to the item we delete. They can be restarted if the item is reinserted by undo AbstractTreeModel::deregisterItem(id, item); } int ProjectItemModel::getFreeFolderId() { while (!isIdFree(QString::number(++m_nextId))) { }; return m_nextId; } int ProjectItemModel::getFreeClipId() { while (!isIdFree(QString::number(++m_nextId))) { }; return m_nextId; } bool ProjectItemModel::addItem(std::shared_ptr item, const QString &parentId, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); std::shared_ptr parentItem = getItemByBinId(parentId); if (!parentItem) { qCDebug(KDENLIVE_LOG) << " / / ERROR IN PARENT FOLDER"; return false; } if (item->itemType() == AbstractProjectItem::ClipItem && parentItem->itemType() != AbstractProjectItem::FolderItem) { qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting clip: a clip should be inserted in a folder"; return false; } if (item->itemType() == AbstractProjectItem::SubClipItem && parentItem->itemType() != AbstractProjectItem::ClipItem) { qCDebug(KDENLIVE_LOG) << " / / ERROR when inserting subclip: a subclip should be inserted in a clip"; return false; } Fun operation = addItem_lambda(item, parentItem->getId()); int itemId = item->getId(); Fun reverse = removeItem_lambda(itemId); bool res = operation(); Q_ASSERT(item->isInModel()); if (res) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return res; } bool ProjectItemModel::requestAddFolder(QString &id, const QString &name, const QString &parentId, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (!id.isEmpty() && !isIdFree(id)) { id = QString(); } if (id.isEmpty()) { id = QString::number(getFreeFolderId()); } std::shared_ptr new_folder = ProjectFolder::construct(id, name, std::static_pointer_cast(shared_from_this())); return addItem(new_folder, parentId, undo, redo); } bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, Fun &undo, Fun &redo) { qDebug() << "/////////// requestAddBinClip" << parentId; QWriteLocker locker(&m_lock); if (id.isEmpty()) { id = Xml::getTagContentByAttribute(description, QStringLiteral("property"), QStringLiteral("name"), QStringLiteral("kdenlive:id"), QStringLiteral("-1")); if (id == QStringLiteral("-1") || !isIdFree(id)) { id = QString::number(getFreeClipId()); } } Q_ASSERT(!id.isEmpty() && isIdFree(id)); qDebug() << "/////////// found id" << id; std::shared_ptr new_clip = ProjectClip::construct(id, description, m_blankThumb, std::static_pointer_cast(shared_from_this())); qDebug() << "/////////// constructed "; bool res = addItem(new_clip, parentId, undo, redo); qDebug() << "/////////// added " << res; if (res) { int loadJob = pCore->jobManager()->startJob({id}, -1, QString(), description); pCore->jobManager()->startJob({id}, loadJob, QString(), 150, 0, true); pCore->jobManager()->startJob({id}, loadJob, QString()); } return res; } bool ProjectItemModel::requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, const QString &undoText) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = requestAddBinClip(id, description, parentId, undo, redo); if (res) { pCore->pushUndo(undo, redo, undoText.isEmpty() ? i18n("Add bin clip") : undoText); } return res; } bool ProjectItemModel::requestAddBinClip(QString &id, std::shared_ptr producer, const QString &parentId, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (id.isEmpty()) { id = QString::number(producer->get_int("kdenlive:id")); if (!isIdFree(id)) { id = QString::number(getFreeClipId()); } } Q_ASSERT(!id.isEmpty() && isIdFree(id)); std::shared_ptr new_clip = ProjectClip::construct(id, m_blankThumb, std::static_pointer_cast(shared_from_this()), producer); bool res = addItem(new_clip, parentId, undo, redo); if (res) { int blocking = pCore->jobManager()->getBlockingJobId(id, AbstractClipJob::LOADJOB); pCore->jobManager()->startJob({id}, blocking, QString(), 150, -1, true); pCore->jobManager()->startJob({id}, blocking, QString()); } return res; } bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QString &parentId, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (id.isEmpty()) { id = QString::number(getFreeClipId()); } Q_ASSERT(!id.isEmpty() && isIdFree(id)); auto clip = getClipByBinID(parentId); Q_ASSERT(clip->itemType() == AbstractProjectItem::ClipItem); auto tc = pCore->currentDoc()->timecode().getDisplayTimecodeFromFrames(in, KdenliveSettings::frametimecode()); std::shared_ptr new_clip = ProjectSubClip::construct(id, clip, std::static_pointer_cast(shared_from_this()), in, out, tc); bool res = addItem(new_clip, parentId, undo, redo); if (res) { int parentJob = pCore->jobManager()->getBlockingJobId(parentId, AbstractClipJob::LOADJOB); pCore->jobManager()->startJob({id}, parentJob, QString(), 150, -1, true); } return res; } bool ProjectItemModel::requestAddBinSubClip(QString &id, int in, int out, const QString &parentId) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = requestAddBinSubClip(id, in, out, parentId, undo, redo); if (res) { pCore->pushUndo(undo, redo, i18n("Add a sub clip")); } return res; } Fun ProjectItemModel::requestRenameFolder_lambda(std::shared_ptr folder, const QString &newName) { int id = folder->getId(); return [this, id, newName]() { auto currentFolder = std::static_pointer_cast(m_allItems[id].lock()); if (!currentFolder) { return false; } // For correct propagation of the name change, we remove folder from parent first auto parent = currentFolder->parent(); parent->removeChild(currentFolder); currentFolder->setName(newName); // Reinsert in parent return parent->appendChild(currentFolder); }; } bool ProjectItemModel::requestRenameFolder(std::shared_ptr folder, const QString &name, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); QString oldName = folder->name(); auto operation = requestRenameFolder_lambda(folder, name); if (operation()) { auto reverse = requestRenameFolder_lambda(folder, oldName); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } bool ProjectItemModel::requestRenameFolder(std::shared_ptr folder, const QString &name) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = requestRenameFolder(folder, name, undo, redo); if (res) { pCore->pushUndo(undo, redo, i18n("Rename Folder")); } return res; } bool ProjectItemModel::requestCleanup() { Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool res = true; std::vector> to_delete; // Iterate to find clips that are not in timeline for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->itemType() == AbstractProjectItem::ClipItem && !c->isIncludedInTimeline()) { to_delete.push_back(c); } } // it is important to execute deletion in a separate loop, because otherwise // the iterators of m_allItems get messed up for (const auto &c : to_delete) { res = requestBinClipDeletion(c, undo, redo); if (!res) { bool undone = undo(); Q_ASSERT(undone); return false; } } pCore->pushUndo(undo, redo, i18n("Clean Project")); return true; } std::vector ProjectItemModel::getAllClipIds() const { std::vector result; for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->itemType() == AbstractProjectItem::ClipItem) { result.push_back(c->clipId()); } } return result; } bool ProjectItemModel::loadFolders(Mlt::Properties &folders) { // At this point, we expect the folders properties to have a name of the form "x.y" where x is the id of the parent folder and y the id of the child. // Note that for root folder, x = -1 // The value of the property is the name of the child folder std::unordered_map> downLinks; // key are parents, value are children std::unordered_map upLinks; // key are children, value are parent std::unordered_map newIds; // we store the correspondance to the new ids std::unordered_map folderNames; newIds[-1] = getRootFolder()->clipId(); if (folders.count() == 0) return true; for (int i = 0; i < folders.count(); i++) { QString folderName = folders.get(i); QString id = folders.get_name(i); int parentId = id.section(QLatin1Char('.'), 0, 0).toInt(); int folderId = id.section(QLatin1Char('.'), 1, 1).toInt(); downLinks[parentId].push_back(folderId); upLinks[folderId] = parentId; folderNames[folderId] = folderName; qDebug() << "Found folder " << folderId << "name = " << folderName << "parent=" << parentId; } // In case there are some non-existant parent, we fall back to root for (const auto &f : downLinks) { if (upLinks.count(f.first) == 0) { upLinks[f.first] = -1; } if (f.first != -1 && downLinks.count(upLinks[f.first]) == 0) { qDebug() << "Warning: parent folder " << upLinks[f.first] << "for folder" << f.first << "is invalid. Folder will be placed in topmost directory."; upLinks[f.first] = -1; } } // We now do a BFS to construct the folders in order Q_ASSERT(downLinks.count(-1) > 0); Fun undo = []() { return true; }; Fun redo = []() { return true; }; std::queue queue; std::unordered_set seen; queue.push(-1); while (!queue.empty()) { int current = queue.front(); seen.insert(current); queue.pop(); if (current != -1) { QString id = QString::number(current); bool res = requestAddFolder(id, folderNames[current], newIds[upLinks[current]], undo, redo); if (!res) { bool undone = undo(); Q_ASSERT(undone); return false; } newIds[current] = id; } for (int c : downLinks[current]) { queue.push(c); } } return true; } bool ProjectItemModel::isIdFree(const QString &id) const { for (const auto &clip : m_allItems) { auto c = std::static_pointer_cast(clip.second.lock()); if (c->clipId() == id) { return false; } } return true; } void ProjectItemModel::loadBinPlaylist(Mlt::Tractor *documentTractor, Mlt::Tractor *modelTractor, std::unordered_map &binIdCorresp) { clean(); Mlt::Properties retainList((mlt_properties)documentTractor->get_data("xml_retain")); qDebug() << "Loading bin playlist..."; if (retainList.is_valid() && (retainList.get_data(BinPlaylist::binPlaylistId.toUtf8().constData()) != nullptr)) { Mlt::Playlist playlist((mlt_playlist)retainList.get_data(BinPlaylist::binPlaylistId.toUtf8().constData())); qDebug() << "retain is valid"; if (playlist.is_valid() && playlist.type() == playlist_type) { qDebug() << "playlist is valid"; // Load bin clips qDebug() << "init bin"; // Load folders Mlt::Properties folderProperties; Mlt::Properties playlistProps(playlist.get_properties()); folderProperties.pass_values(playlistProps, "kdenlive:folder."); loadFolders(folderProperties); // Read notes QString notes = playlistProps.get("kdenlive:documentnotes"); pCore->projectManager()->setDocumentNotes(notes); Fun undo = []() { return true; }; Fun redo = []() { return true; }; qDebug() << "Found " << playlist.count() << "clips"; int max = playlist.count(); for (int i = 0; i < max; i++) { QScopedPointer prod(playlist.get_clip(i)); std::shared_ptr producer(new Mlt::Producer(prod->parent())); qDebug() << "dealing with bin clip" << i; if (producer->is_blank() || !producer->is_valid()) { qDebug() << "producer is not valid or blank"; continue; } QString id = qstrdup(producer->get("kdenlive:id")); QString parentId = qstrdup(producer->get("kdenlive:folderid")); if (parentId.isEmpty()) { parentId = QStringLiteral("-1"); } qDebug() << "clip id" << id; if (id.contains(QLatin1Char('_'))) { // TODO refac ? /* // This is a track producer QString mainId = id.section(QLatin1Char('_'), 0, 0); // QString track = id.section(QStringLiteral("_"), 1, 1); if (m_clipList.contains(mainId)) { // The controller for this track producer already exists } else { // Create empty controller for this clip requestClipInfo info; info.imageHeight = 0; info.clipId = id; info.replaceProducer = true; emit slotProducerReady(info, ClipController::mediaUnavailable); } */ } else { QString newId = isIdFree(id) ? id : QString::number(getFreeClipId()); producer->set("_kdenlive_processed", 1); requestAddBinClip(newId, producer, parentId, undo, redo); binIdCorresp[id] = newId; - qDebug() << "Loaded clip "<< id <<"under id"<setRetainIn(modelTractor); } /** @brief Save document properties in MLT's bin playlist */ void ProjectItemModel::saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel) { m_binPlaylist->saveDocumentProperties(props, metadata, guideModel); } void ProjectItemModel::saveProperty(const QString &name, const QString &value) { m_binPlaylist->saveProperty(name, value); } QMap ProjectItemModel::getProxies(const QString &root) { return m_binPlaylist->getProxies(root); } diff --git a/src/bin/projectitemmodel.h b/src/bin/projectitemmodel.h index 3f35c6cd1..76ca2a235 100644 --- a/src/bin/projectitemmodel.h +++ b/src/bin/projectitemmodel.h @@ -1,241 +1,241 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 Jean-Baptiste Mardelle 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 PROJECTITEMMODEL_H #define PROJECTITEMMODEL_H #include "abstractmodel/abstracttreemodel.hpp" #include "definitions.h" #include "undohelper.hpp" #include +#include #include #include -#include class AbstractProjectItem; class BinPlaylist; class MarkerListModel; class ProjectClip; class ProjectFolder; namespace Mlt { class Producer; class Properties; class Tractor; -} +} // namespace Mlt /** * @class ProjectItemModel * @brief Acts as an adaptor to be able to use BinModel with item views. */ class ProjectItemModel : public AbstractTreeModel { Q_OBJECT protected: explicit ProjectItemModel(QObject *parent); public: static std::shared_ptr construct(QObject *parent = nullptr); ~ProjectItemModel(); friend class ProjectClip; /** @brief Returns a clip from the hierarchy, given its id */ std::shared_ptr getClipByBinID(const QString &binId); /** @brief Helper to check whether a clip with a given id exists */ bool hasClip(const QString &binId); /** @brief Gets a folder by its id. If none is found, the root is returned */ std::shared_ptr getFolderByBinId(const QString &binId); /** @brief Gets any item by its id. */ std::shared_ptr getItemByBinId(const QString &binId); /** @brief This function change the global enabled state of the bin effects */ void setBinEffectsEnabled(bool enabled); /** @brief Returns some info about the folder containing the given index */ QStringList getEnclosingFolderInfo(const QModelIndex &index) const; /** @brief Deletes all element and start a fresh model */ void clean(); /** @brief Returns the id of all the clips (excluding folders) */ std::vector getAllClipIds() const; /** @brief Convenience method to access root folder */ std::shared_ptr getRootFolder() const; /** @brief Create the subclips defined in the parent clip. @param id is the id of the parent clip @param data is a definition of the subclips (keys are subclips' names, value are "in:out")*/ void loadSubClips(const QString &id, const QMap &data); /* @brief Convenience method to retrieve a pointer to an element given its index */ std::shared_ptr getBinItemByIndex(const QModelIndex &index) const; /* @brief Load the folders given the property containing them */ bool loadFolders(Mlt::Properties &folders); /* @brief Parse a bin playlist from the document tractor and reconstruct the tree */ void loadBinPlaylist(Mlt::Tractor *documentTractor, Mlt::Tractor *modelTractor, std::unordered_map &binIdCorresp); /** @brief Save document properties in MLT's bin playlist */ void saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel); /** @brief Save a property to main bin */ void saveProperty(const QString &name, const QString &value); /** @brief Returns item data depending on role requested */ QVariant data(const QModelIndex &index, int role) const override; /** @brief Called when user edits an item */ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; /** @brief Allow selection and drag & drop */ Qt::ItemFlags flags(const QModelIndex &index) const override; /** @brief Returns column names in case we want to use columns in QTreeView */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; /** @brief Mandatory reimplementation from QAbstractItemModel */ int columnCount(const QModelIndex &parent = QModelIndex()) const override; /** @brief Returns the MIME type used for Drag actions */ QStringList mimeTypes() const override; /** @brief Create data that will be used for Drag events */ QMimeData *mimeData(const QModelIndexList &indices) const override; /** @brief Set size for thumbnails */ void setIconSize(QSize s); bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; Qt::DropActions supportedDropActions() const override; /* @brief Request deletion of a bin clip from the project bin @param clip : pointer to the clip to delete @param undo,redo: lambdas that are updated to accumulate operation. */ bool requestBinClipDeletion(std::shared_ptr clip, Fun &undo, Fun &redo); /* @brief Request creation of a bin folder @param id Id of the requested bin. If this is empty or invalid (already used, for example), it will be used as a return parameter to give the automatic bin id used. @param name Name of the folder @param parentId Bin id of the parent folder @param undo,redo: lambdas that are updated to accumulate operation. */ bool requestAddFolder(QString &id, const QString &name, const QString &parentId, Fun &undo, Fun &redo); /* @brief Request creation of a bin clip @param id Id of the requested bin. If this is empty, it will be used as a return parameter to give the automatic bin id used. @param description Xml description of the clip @param parentId Bin id of the parent folder @param undo,redo: lambdas that are updated to accumulate operation. */ bool requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, Fun &undo, Fun &redo); bool requestAddBinClip(QString &id, const QDomElement &description, const QString &parentId, const QString &undoText = QString()); /* @brief This is the addition function when we already have a producer for the clip*/ bool requestAddBinClip(QString &id, std::shared_ptr producer, const QString &parentId, Fun &undo, Fun &redo); /* @brief Create a subClip @param id Id of the requested bin. If this is empty, it will be used as a return parameter to give the automatic bin id used. @param parentId Bin id of the parent clip @param in,out : zone that corresponds to the subclip @param undo,redo: lambdas that are updated to accumulate operation. */ bool requestAddBinSubClip(QString &id, int in, int out, const QString &parentId, Fun &undo, Fun &redo); bool requestAddBinSubClip(QString &id, int in, int out, const QString &parentId); /* @brief Request that a folder's name is changed @param clip : pointer to the folder to rename @param name: new name of the folder @param undo,redo: lambdas that are updated to accumulate operation. */ bool requestRenameFolder(std::shared_ptr folder, const QString &name, Fun &undo, Fun &redo); /* Same functions but pushes the undo object directly */ bool requestRenameFolder(std::shared_ptr folder, const QString &name); /* @brief Request that the unused clips are deleted */ bool requestCleanup(); /* @brief Retrieves the next id available for attribution to a folder */ int getFreeFolderId(); /* @brief Retrieves the next id available for attribution to a clip */ int getFreeClipId(); /** @brief Retrieve a list of proxy/original urls */ QMap getProxies(const QString &root); protected: /* @brief Register the existence of a new element */ void registerItem(const std::shared_ptr &item) override; /* @brief Deregister the existence of a new element*/ void deregisterItem(int id, TreeItem *item) override; /* @brief Helper function to generate a lambda that rename a folder */ Fun requestRenameFolder_lambda(std::shared_ptr folder, const QString &newName); /* @brief Helper function to add a given item to the tree */ bool addItem(std::shared_ptr item, const QString &parentId, Fun &undo, Fun &redo); public slots: /** @brief An item in the list was modified, notify */ void onItemUpdated(std::shared_ptr item, int role); void onItemUpdated(const QString &binId, int role); /** @brief Check whether a given id is currently used or not*/ bool isIdFree(const QString &id) const; private: /** @brief Return reference to column specific data */ int mapToColumn(int column) const; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access std::unique_ptr m_binPlaylist; int m_nextId; QIcon m_blankThumb; signals: // thumbs of the given clip were modified, request update of the monitor if need be void refreshAudioThumbs(const QString &id); void refreshClip(const QString &id); void emitMessage(const QString &, int, MessageType); void updateTimelineProducers(const QString &id, const QMap &passProperties); void refreshPanel(const QString &id); void requestAudioThumbs(const QString &id, long duration); // TODO void markersNeedUpdate(const QString &id, const QList &); void itemDropped(const QStringList &, const QModelIndex &); void itemDropped(const QList &, const QModelIndex &); void effectDropped(const QStringList &, const QModelIndex &); void addClipCut(const QString &, int, int); }; #endif diff --git a/src/bin/projectsubclip.cpp b/src/bin/projectsubclip.cpp index 79a0a8427..25b6fb31e 100644 --- a/src/bin/projectsubclip.cpp +++ b/src/bin/projectsubclip.cpp @@ -1,164 +1,164 @@ /* Copyright (C) 2015 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 "projectsubclip.h" #include "projectclip.h" #include "projectitemmodel.h" #include #include #include class ClipController; ProjectSubClip::ProjectSubClip(const QString &id, const std::shared_ptr &parent, const std::shared_ptr &model, int in, int out, const QString &timecode, const QString &name) : AbstractProjectItem(AbstractProjectItem::SubClipItem, id, model) , m_masterClip(parent) , m_in(in) , m_out(out) { m_duration = timecode; QPixmap pix(64, 36); pix.fill(Qt::lightGray); m_thumbnail = QIcon(pix); if (name.isEmpty()) { m_name = i18n("Zone %1", parent->childCount() + 1); } else { m_name = name; } m_clipStatus = StatusReady; // Save subclip in MLT parent->setProducerProperty("kdenlive:clipzone." + m_name, QString::number(in) + QLatin1Char(';') + QString::number(out)); connect(parent.get(), &ProjectClip::thumbReady, this, &ProjectSubClip::gotThumb); } std::shared_ptr ProjectSubClip::construct(const QString &id, std::shared_ptr parent, std::shared_ptr model, int in, int out, const QString &timecode, const QString &name) { std::shared_ptr self(new ProjectSubClip(id, parent, model, in, out, timecode, name)); baseFinishConstruct(self); return self; } ProjectSubClip::~ProjectSubClip() { // controller is deleted in bincontroller } void ProjectSubClip::gotThumb(int pos, const QImage &img) { if (pos == m_in) { setThumbnail(img); disconnect(m_masterClip.get(), &ProjectClip::thumbReady, this, &ProjectSubClip::gotThumb); } } void ProjectSubClip::discard() { if (m_masterClip) { m_masterClip->resetProducerProperty("kdenlive:clipzone." + m_name); } } QString ProjectSubClip::getToolTip() const { return QStringLiteral("test"); } std::shared_ptr ProjectSubClip::clip(const QString &id) { Q_UNUSED(id); return std::shared_ptr(); } std::shared_ptr ProjectSubClip::folder(const QString &id) { Q_UNUSED(id); return std::shared_ptr(); } -void ProjectSubClip::setBinEffectsEnabled(bool) -{ -} +void ProjectSubClip::setBinEffectsEnabled(bool) {} GenTime ProjectSubClip::duration() const { // TODO return GenTime(); } QPoint ProjectSubClip::zone() const { return QPoint(m_in, m_out); } std::shared_ptr ProjectSubClip::clipAt(int ix) { Q_UNUSED(ix); return std::shared_ptr(); } QDomElement ProjectSubClip::toXml(QDomDocument &document, bool) { QDomElement sub = document.createElement(QStringLiteral("subclip")); sub.setAttribute(QStringLiteral("id"), m_masterClip->AbstractProjectItem::clipId()); sub.setAttribute(QStringLiteral("in"), m_in); sub.setAttribute(QStringLiteral("out"), m_out); return sub; } std::shared_ptr ProjectSubClip::subClip(int in, int out) { if (m_in == in && m_out == out) { return std::static_pointer_cast(shared_from_this()); } return std::shared_ptr(); } void ProjectSubClip::setThumbnail(const QImage &img) { QPixmap thumb = roundedPixmap(QPixmap::fromImage(img)); m_thumbnail = QIcon(thumb); - if (auto ptr = m_model.lock()) std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), AbstractProjectItem::DataThumbnail); + if (auto ptr = m_model.lock()) + std::static_pointer_cast(ptr)->onItemUpdated(std::static_pointer_cast(shared_from_this()), + AbstractProjectItem::DataThumbnail); } QPixmap ProjectSubClip::thumbnail(int width, int height) { return m_thumbnail.pixmap(width, height); } bool ProjectSubClip::rename(const QString &name, int column) { // TODO refac: rework this Q_UNUSED(column) if (m_name == name) { return false; } // Rename folder // if (auto ptr = m_model.lock()) std::static_pointer_cast(ptr)->bin()->renameSubClipCommand(m_binId, name, m_name, m_in, m_out); return true; } std::shared_ptr ProjectSubClip::getMasterClip() const { return m_masterClip; } diff --git a/src/capture/managecapturesdialog.cpp b/src/capture/managecapturesdialog.cpp index ec9282628..6081631f4 100644 --- a/src/capture/managecapturesdialog.cpp +++ b/src/capture/managecapturesdialog.cpp @@ -1,149 +1,147 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "managecapturesdialog.h" #include "doc/kthumb.h" #include "kdenlive_debug.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include ManageCapturesDialog::ManageCapturesDialog(const QList &files, QWidget *parent) : QDialog(parent) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); m_view.setupUi(this); m_importButton = m_view.buttonBox->button(QDialogButtonBox::Ok); m_importButton->setText(i18n("import")); m_view.treeWidget->setIconSize(QSize(70, 50)); for (const QUrl &url : files) { QStringList text; text << url.fileName(); KFileItem file(url); file.setDelayedMimeTypes(true); text << KIO::convertSize(file.size()); auto *item = new QTreeWidgetItem(m_view.treeWidget, text); item->setData(0, Qt::UserRole, url.toLocalFile()); item->setToolTip(0, url.toLocalFile()); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); item->setCheckState(0, Qt::Checked); } connect(m_view.treeWidget, &QTreeWidget::itemChanged, this, &ManageCapturesDialog::slotRefreshButtons); connect(m_view.deleteButton, &QAbstractButton::pressed, this, &ManageCapturesDialog::slotDeleteCurrent); connect(m_view.toggleButton, &QAbstractButton::pressed, this, &ManageCapturesDialog::slotToggle); QTreeWidgetItem *item = m_view.treeWidget->topLevelItem(0); if (item) { m_view.treeWidget->setCurrentItem(item); } connect(m_view.treeWidget, &QTreeWidget::itemSelectionChanged, this, &ManageCapturesDialog::slotCheckItemIcon); QTimer::singleShot(500, this, &ManageCapturesDialog::slotCheckItemIcon); m_view.treeWidget->resizeColumnToContents(0); m_view.treeWidget->setEnabled(false); adjustSize(); } -ManageCapturesDialog::~ManageCapturesDialog() -{ -} +ManageCapturesDialog::~ManageCapturesDialog() {} void ManageCapturesDialog::slotCheckItemIcon() { int ct = 0; const int count = m_view.treeWidget->topLevelItemCount(); while (ct < count) { QTreeWidgetItem *item = m_view.treeWidget->topLevelItem(ct); // QTreeWidgetItem *item = m_view.treeWidget->currentItem(); if (item->icon(0).isNull()) { QPixmap p = KThumb::getImage(QUrl(item->data(0, Qt::UserRole).toString()), 0, 70, 50); item->setIcon(0, QIcon(p)); m_view.treeWidget->resizeColumnToContents(0); repaint(); // QTimer::singleShot(400, this, SLOT(slotCheckItemIcon())); } ct++; } m_view.treeWidget->setEnabled(true); } void ManageCapturesDialog::slotRefreshButtons() { const int count = m_view.treeWidget->topLevelItemCount(); bool enabled = false; for (int i = 0; i < count; ++i) { QTreeWidgetItem *item = m_view.treeWidget->topLevelItem(i); if ((item != nullptr) && item->checkState(0) == Qt::Checked) { enabled = true; break; } } m_importButton->setEnabled(enabled); } void ManageCapturesDialog::slotDeleteCurrent() { QTreeWidgetItem *item = m_view.treeWidget->currentItem(); if (!item) { return; } const int i = m_view.treeWidget->indexOfTopLevelItem(item); m_view.treeWidget->takeTopLevelItem(i); // qCDebug(KDENLIVE_LOG) << "DELETING FILE: " << item->text(0); // KIO::NetAccess::del(QUrl(item->text(0)), this); if (!QFile::remove(item->data(0, Qt::UserRole).toString())) { qCDebug(KDENLIVE_LOG) << "// ERRor removing file " << item->data(0, Qt::UserRole).toString(); } delete item; item = nullptr; } void ManageCapturesDialog::slotToggle() { const int count = m_view.treeWidget->topLevelItemCount(); for (int i = 0; i < count; ++i) { QTreeWidgetItem *item = m_view.treeWidget->topLevelItem(i); if (item) { if (item->checkState(0) == Qt::Checked) { item->setCheckState(0, Qt::Unchecked); } else { item->setCheckState(0, Qt::Checked); } } } } QList ManageCapturesDialog::importFiles() const { QList result; const int count = m_view.treeWidget->topLevelItemCount(); for (int i = 0; i < count; ++i) { QTreeWidgetItem *item = m_view.treeWidget->topLevelItem(i); if ((item != nullptr) && item->checkState(0) == Qt::Checked) { result.append(QUrl(item->data(0, Qt::UserRole).toString())); } } return result; } diff --git a/src/capture/mltdevicecapture.h b/src/capture/mltdevicecapture.h index 4a36a1ee6..dfcd401a0 100644 --- a/src/capture/mltdevicecapture.h +++ b/src/capture/mltdevicecapture.h @@ -1,148 +1,148 @@ /*************************************************************************** mltdevicecapture.h - description ------------------- begin : Sun May 21 2011 copyright : (C) 2011 by Jean-Baptiste Mardelle (jb@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) any later version. * * * ***************************************************************************/ /*! * @class MltDeviceCapture * @brief Interface for MLT capture. * Capturing started by MltDeviceCapture::slotStartCapture () * * Capturing is stopped by RecMonitor::slotStopCapture() */ #ifndef MLTDEVICECAPTURE_H #define MLTDEVICECAPTURE_H #include "definitions.h" #include "gentime.h" #include "monitor/abstractmonitor.h" #include #include // include after QTimer to have C++ phtreads defined #include namespace Mlt { class Consumer; class Frame; class Event; class Producer; class Profile; -} +} // namespace Mlt class MltDeviceCapture : public AbstractRender { Q_OBJECT public : enum FailStates { OK = 0, APP_NOEXIST }; /** @brief Build a MLT Renderer * @param winid The parent widget identifier (required for SDL display). Set to 0 for OpenGL rendering * @param profile The MLT profile used for the capture (default one will be used if empty). */ explicit MltDeviceCapture(QString profile, /*VideoSurface *surface,*/ QWidget *parent = nullptr); /** @brief Destroy the MLT Renderer. */ ~MltDeviceCapture(); int doCapture; /** @brief Someone needs us to send again a frame. */ void sendFrameUpdate() override {} void emitFrameUpdated(Mlt::Frame &); void emitFrameNumber(double position); void emitConsumerStopped(); void showFrame(Mlt::Frame &); void showAudio(Mlt::Frame &); void saveFrame(Mlt::Frame &frame); /** @brief Starts the MLT Video4Linux process. * @param surface The widget onto which the frame should be painted * Called by RecMonitor::slotRecord () */ bool slotStartCapture(const QString ¶ms, const QString &path, const QString &playlist, bool livePreview, bool xmlPlaylist = true); bool slotStartPreview(const QString &producer, bool xmlFormat = false); /** @brief Save current frame to file. */ void captureFrame(const QString &path); /** @brief This will add the video clip from path and add it in the overlay track. */ void setOverlay(const QString &path); /** @brief This will add an MLT video effect to the overlay track. */ void setOverlayEffect(const QString &tag, const QStringList ¶meters); /** @brief This will add a horizontal flip effect, easier to work when filming yourself. */ void mirror(bool activate); /** @brief True if we are processing an image (yuv > rgb) when recording. */ bool processingImage; void pause(); private: Mlt::Consumer *m_mltConsumer; Mlt::Producer *m_mltProducer; Mlt::Profile *m_mltProfile; Mlt::Event *m_showFrameEvent; QString m_activeProfile; int m_droppedFrames; /** @brief When true, images will be displayed on monitor while capturing. */ bool m_livePreview; /** @brief Count captured frames, used to display only one in ten images while capturing. */ int m_frameCount; void uyvy2rgb(unsigned char *yuv_buffer, int width, int height); QString m_capturePath; QTimer m_droppedFramesTimer; QMutex m_mutex; /** @brief Build the MLT Consumer object with initial settings. * @param profileName The MLT profile to use for the consumer * @returns true if consumer is valid */ bool buildConsumer(const QString &profileName = QString()); private slots: void slotPreparePreview(); void slotAllowPreview(); /** @brief When capturing, check every second for dropped frames. */ void slotCheckDroppedFrames(); signals: /** @brief A frame's image has to be shown. * * Used in Mac OS X. */ void showImageSignal(const QImage &); void frameSaved(const QString &); void droppedFrames(int); void unblockPreview(); void imageReady(const QImage &); public slots: /** @brief Stops the consumer. */ void stop(); }; #endif diff --git a/src/capture/v4lcapture.cpp b/src/capture/v4lcapture.cpp index 1e1bc209f..cf3e97fe2 100644 --- a/src/capture/v4lcapture.cpp +++ b/src/capture/v4lcapture.cpp @@ -1,135 +1,133 @@ /*************************************************************************** * Copyright (C) 2010 by Jean-Baptiste Mardelle (jb@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) 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 "v4lcapture.h" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include -V4lCaptureHandler::V4lCaptureHandler() -{ -} +V4lCaptureHandler::V4lCaptureHandler() {} // static QStringList V4lCaptureHandler::getDeviceName(const QString &input) { char *src = strdup(input.toUtf8().constData()); QString pixelformatdescription; int fd = open(src, O_RDWR | O_NONBLOCK); if (fd < 0) { free(src); return QStringList(); } struct v4l2_capability cap; char *devName = nullptr; int captureEnabled = 1; if (ioctl(fd, VIDIOC_QUERYCAP, &cap) < 0) { fprintf(stderr, "Cannot get capabilities."); // return nullptr; } else { devName = strdup((char *)cap.card); if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0u) { // Device cannot capture captureEnabled = 0; } } if (captureEnabled != 0) { struct v4l2_format format; memset(&format, 0, sizeof(format)); format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; struct v4l2_fmtdesc fmt; memset(&fmt, 0, sizeof(fmt)); fmt.index = 0; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; struct v4l2_frmsizeenum sizes; memset(&sizes, 0, sizeof(sizes)); struct v4l2_frmivalenum rates; memset(&rates, 0, sizeof(rates)); char value[200]; while (ioctl(fd, VIDIOC_ENUM_FMT, &fmt) != -1) { if (pixelformatdescription.length() > 2000) { break; } if (snprintf(value, sizeof(value), ">%c%c%c%c", fmt.pixelformat >> 0, fmt.pixelformat >> 8, fmt.pixelformat >> 16, fmt.pixelformat >> 24) > 0) { pixelformatdescription.append(value); } fprintf(stderr, "detected format: %s: %c%c%c%c\n", fmt.description, fmt.pixelformat >> 0, fmt.pixelformat >> 8, fmt.pixelformat >> 16, fmt.pixelformat >> 24); sizes.pixel_format = fmt.pixelformat; sizes.index = 0; // Query supported frame size while (ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &sizes) != -1) { struct v4l2_frmsize_discrete image_size = sizes.discrete; // Query supported frame rates rates.index = 0; rates.pixel_format = fmt.pixelformat; rates.width = image_size.width; rates.height = image_size.height; if (pixelformatdescription.length() > 2000) { break; } if (snprintf(value, sizeof(value), ":%dx%d=", image_size.width, image_size.height) > 0) { pixelformatdescription.append(value); } fprintf(stderr, "Size: %dx%d: ", image_size.width, image_size.height); while (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &rates) != -1) { if (pixelformatdescription.length() > 2000) { break; } if (snprintf(value, sizeof(value), "%d/%d,", rates.discrete.denominator, rates.discrete.numerator) > 0) { pixelformatdescription.append(value); } fprintf(stderr, "%d/%d, ", rates.discrete.numerator, rates.discrete.denominator); rates.index++; } fprintf(stderr, "\n"); sizes.index++; } fmt.index++; } } close(fd); free(src); QStringList result; if (devName == nullptr) { return result; } QString deviceName(devName); free(devName); result << (deviceName.isEmpty() ? input : deviceName) << pixelformatdescription; return result; } diff --git a/src/core.cpp b/src/core.cpp index b907f2349..8fe4a0b3f 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1,609 +1,606 @@ /* Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #include "core.h" #include "bin/bin.h" #include "bin/projectitemmodel.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "jobs/jobmanager.h" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "library/librarywidget.h" #include "mainwindow.h" #include "mltconnection.h" #include "mltcontroller/bincontroller.h" #include "monitor/monitormanager.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "project/projectmanager.h" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinewidget.h" #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif std::unique_ptr Core::m_self; Core::Core() : m_mainWindow(nullptr) , m_projectManager(nullptr) , m_monitorManager(nullptr) , m_binWidget(nullptr) , m_library(nullptr) , m_thumbProfile(nullptr) { } void Core::prepareShutdown() { m_guiConstructed = false; } Core::~Core() { if (m_monitorManager) { delete m_monitorManager; } m_binController->destroyBin(); // delete m_binWidget; delete m_projectManager; } void Core::build(const QString &MltPath) { if (m_self) { qDebug() << "DEBUG: Warning : trying to create a second Core"; return; } m_self.reset(new Core()); m_self->initLocale(); qRegisterMetaType("audioShortVector"); qRegisterMetaType>("QVector"); qRegisterMetaType("MessageType"); qRegisterMetaType("stringMap"); qRegisterMetaType("audioByteArray"); qRegisterMetaType>("QList"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType>(); qRegisterMetaType("QDomElement"); qRegisterMetaType("requestClipInfo"); // Open connection with Mlt MltConnection::construct(MltPath); // load the profile from disk ProfileRepository::get()->refresh(); // load default profile m_self->m_profile = KdenliveSettings::default_profile(); if (m_self->m_profile.isEmpty()) { m_self->m_profile = ProjectManager::getDefaultProjectFormat(); KdenliveSettings::setDefault_profile(m_self->m_profile); } // Init producer shown for unavailable media // TODO make it a more proper image, it currently causes a crash on exit ClipController::mediaUnavailable = std::make_shared(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue"); ClipController::mediaUnavailable->set("length", 99999999); m_self->m_projectItemModel = ProjectItemModel::construct(); // Job manger must be created before bin to correctly connect m_self->m_jobManager.reset(new JobManager(m_self.get())); } void Core::initGUI(const QUrl &Url) { m_guiConstructed = true; m_profile = KdenliveSettings::default_profile(); m_currentProfile = m_profile; profileChanged(); m_mainWindow = new MainWindow(); // load default profile and ask user to select one if not found. if (m_profile.isEmpty()) { m_profile = ProjectManager::getDefaultProjectFormat(); profileChanged(); KdenliveSettings::setDefault_profile(m_profile); } if (!ProfileRepository::get()->profileExists(m_profile)) { KMessageBox::sorry(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value.")); // TODO this simple widget should be improved and probably use profileWidget // we get the list of profiles QVector> all_profiles = ProfileRepository::get()->getAllProfiles(); QStringList all_descriptions; for (const auto &profile : all_profiles) { all_descriptions << profile.first; } // ask the user bool ok; QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok); if (ok) { ok = false; for (const auto &profile : all_profiles) { if (profile.first == item) { m_profile = profile.second; ok = true; } } } if (!ok) { KMessageBox::error( m_mainWindow, i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel")); m_profile = QStringLiteral("dv_pal"); } KdenliveSettings::setDefault_profile(m_profile); profileChanged(); } m_projectManager = new ProjectManager(this); m_binWidget = new Bin(m_projectItemModel); m_binController = std::make_shared(); m_library = new LibraryWidget(m_projectManager); connect(m_library, SIGNAL(addProjectClips(QList)), m_binWidget, SLOT(droppedUrls(QList))); connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath); - connect(m_binWidget, &Bin::storeFolder, m_binController.get(), - &BinController::slotStoreFolder); + connect(m_binWidget, &Bin::storeFolder, m_binController.get(), &BinController::slotStoreFolder); // connect(m_binController.get(), &BinController::slotProducerReady, m_binWidget, &Bin::slotProducerReady, Qt::DirectConnection); // connect(m_binController.get(), &BinController::prepareTimelineReplacement, m_binWidget, &Bin::prepareTimelineReplacement, Qt::DirectConnection); // connect(m_binController.get(), &BinController::requestAudioThumb, m_binWidget, &Bin::slotCreateAudioThumb); connect(m_binController.get(), &BinController::abortAudioThumbs, m_binWidget, &Bin::abortAudioThumbs); connect(m_binController.get(), &BinController::setDocumentNotes, m_projectManager, &ProjectManager::setDocumentNotes); m_monitorManager = new MonitorManager(this); // Producer queue, creating MLT::Producers on request /* m_producerQueue = new ProducerQueue(m_binController); connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady); connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady); connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection); connect(m_producerQueue, SIGNAL(addClip(QString, QMap)), m_binWidget, SLOT(slotAddUrl(QString, QMap))); connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int))); connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection); // TODO connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/ m_mainWindow->init(); projectManager()->init(Url, QString()); QTimer::singleShot(0, pCore->projectManager(), &ProjectManager::slotLoadOnOpen); if (qApp->isSessionRestored()) { // NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow m_mainWindow->restore(1, false); } m_mainWindow->show(); } std::unique_ptr &Core::self() { if (!m_self) { qDebug() << "Error : Core has not been created"; } return m_self; } MainWindow *Core::window() { return m_mainWindow; } ProjectManager *Core::projectManager() { return m_projectManager; } MonitorManager *Core::monitorManager() { return m_monitorManager; } Monitor *Core::getMonitor(int id) { if (id == Kdenlive::ClipMonitor) { return m_monitorManager->clipMonitor(); } return m_monitorManager->projectMonitor(); } std::shared_ptr Core::binController() { return m_binController; } Bin *Core::bin() { return m_binWidget; } void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone) { m_binWidget->selectClipById(clipId, frame, zone); } std::shared_ptr Core::jobManager() { return m_jobManager; } LibraryWidget *Core::library() { return m_library; } void Core::initLocale() { QLocale systemLocale = QLocale(); #ifndef Q_OS_MAC setlocale(LC_NUMERIC, nullptr); #else setlocale(LC_NUMERIC_MASK, nullptr); #endif // localeconv()->decimal_point does not give reliable results on Windows #ifndef Q_OS_WIN char *separator = localeconv()->decimal_point; if (QString::fromUtf8(separator) != QChar(systemLocale.decimalPoint())) { // qCDebug(KDENLIVE_LOG)<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------"; // HACK: There is a locale conflict, so set locale to C // Make sure to override exported values or it won't work qputenv("LANG", "C"); #ifndef Q_OS_MAC setlocale(LC_NUMERIC, "C"); #else setlocale(LC_NUMERIC_MASK, "C"); #endif systemLocale = QLocale::c(); } #endif systemLocale.setNumberOptions(QLocale::OmitGroupSeparator); QLocale::setDefault(systemLocale); } std::unique_ptr &Core::getMltRepository() { return MltConnection::self()->getMltRepository(); } std::unique_ptr &Core::getCurrentProfile() const { return ProfileRepository::get()->getProfile(m_currentProfile); } const QString &Core::getCurrentProfilePath() const { return m_currentProfile; } bool Core::setCurrentProfile(const QString &profilePath) { if (m_currentProfile == profilePath) { // no change required return true; } if (ProfileRepository::get()->profileExists(profilePath)) { m_currentProfile = profilePath; m_thumbProfile.reset(); // inform render widget m_mainWindow->updateRenderWidgetProfile(); return true; } return false; } double Core::getCurrentSar() const { return getCurrentProfile()->sar(); } double Core::getCurrentDar() const { return getCurrentProfile()->dar(); } double Core::getCurrentFps() const { return getCurrentProfile()->fps(); } QSize Core::getCurrentFrameDisplaySize() const { return QSize((int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height()); } QSize Core::getCurrentFrameSize() const { return QSize(getCurrentProfile()->width(), getCurrentProfile()->height()); } void Core::requestMonitorRefresh() { if (!m_guiConstructed) return; m_monitorManager->refreshProjectMonitor(); } void Core::refreshProjectRange(QSize range) { if (!m_guiConstructed) return; m_monitorManager->refreshProjectRange(range); } int Core::getItemPosition(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second); } break; case ObjectType::BinClip: return 0; break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } int Core::getItemIn(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second); } break; case ObjectType::TimelineComposition: return 0; break; case ObjectType::BinClip: return 0; break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } int Core::getItemDuration(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second); } break; case ObjectType::BinClip: return m_binWidget->getClipDuration(id.second); break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } int Core::getItemTrack(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: case ObjectType::TimelineComposition: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second); break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } void Core::refreshProjectItem(const ObjectId &id) { if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second); } break; case ObjectType::TimelineTrack: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) { requestMonitorRefresh(); } break; case ObjectType::BinClip: m_monitorManager->refreshClipMonitor(); break; default: qDebug() << "ERROR: unhandled object type"; } } KdenliveDoc *Core::currentDoc() { return m_projectManager->current(); } void Core::profileChanged() { GenTime::setFps(getCurrentFps()); } void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text) { undoStack()->push(new FunctionalUndoCommand(undo, redo, text)); } void Core::pushUndo(QUndoCommand *command) { undoStack()->push(command); } void Core::displayMessage(const QString &message, MessageType type, int timeout) { m_mainWindow->displayMessage(message, type, timeout); } void Core::clearAssetPanel(int itemId) { - if (m_guiConstructed) - m_mainWindow->clearAssetPanel(itemId); + if (m_guiConstructed) m_mainWindow->clearAssetPanel(itemId); } std::shared_ptr Core::getItemEffectStack(int itemType, int itemId) { if (!m_guiConstructed) return nullptr; switch (itemType) { case (int)ObjectType::TimelineClip: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId); case (int)ObjectType::TimelineTrack: // TODO return nullptr; break; case (int)ObjectType::BinClip: return m_binWidget->getClipEffectStack(itemId); default: return nullptr; } } std::shared_ptr Core::undoStack() { return projectManager()->undoStack(); } QMap Core::getVideoTrackNames() { if (!m_guiConstructed) return QMap(); return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(true); } QPair Core::getCompositionATrack(int cid) const { if (!m_guiConstructed) return QPair(); return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid); } bool Core::compositionAutoTrack(int cid) const { return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid); } void Core::setCompositionATrack(int cid, int aTrack) { if (!m_guiConstructed) return; m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack); } std::shared_ptr Core::projectItemModel() { return m_projectItemModel; } void Core::invalidateItem(ObjectId itemId) { if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return; switch (itemId.first) { case ObjectType::TimelineClip: m_mainWindow->getCurrentTimeline()->controller()->invalidateClip(itemId.second); break; case ObjectType::TimelineTrack: // TODO: invalidate all clips in track break; default: // bin clip should automatically be reloaded, compositions should not have effects break; } } double Core::getClipSpeed(int id) const { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id); } void Core::updateItemKeyframes(ObjectId id) { if (id.first == ObjectType::TimelineClip && m_mainWindow) { m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole}); } } void Core::updateItemModel(ObjectId id, const QString &service) { if (!m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade")) && id.first == ObjectType::TimelineClip) { bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black"); - m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ?TimelineModel::FadeInRole : TimelineModel::FadeOutRole}); + m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole}); } } void Core::showClipKeyframes(ObjectId id, bool enable) { if (id.first == ObjectType::TimelineClip) { m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable); } else if (id.first == ObjectType::TimelineComposition) { m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable); } } Mlt::Profile *Core::thumbProfile() { if (!m_thumbProfile) { m_thumbProfile = std::unique_ptr(new Mlt::Profile(m_currentProfile.toStdString().c_str())); m_thumbProfile->set_height(200); int width = 200 * m_thumbProfile->dar(); width += width % 8; m_thumbProfile->set_width(width); } return m_thumbProfile.get(); } void Core::clearSelection() { m_mainWindow->getCurrentTimeline()->controller()->clearSelection(); } void Core::triggerAction(const QString &name) { QAction *action = m_mainWindow->actionCollection()->action(name); if (action) { action->trigger(); } } - diff --git a/src/core.h b/src/core.h index 1fdf9a8a2..0942fb4c5 100644 --- a/src/core.h +++ b/src/core.h @@ -1,204 +1,204 @@ /* Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #ifndef CORE_H #define CORE_H #include "definitions.h" #include "kdenlivecore_export.h" #include "undohelper.hpp" #include #include #include #include class Bin; class BinController; class DocUndoStack; class EffectStackModel; class JobManager; class KdenliveDoc; class LibraryWidget; class MainWindow; class Monitor; class MonitorManager; class ProfileModel; class ProjectItemModel; class ProjectManager; namespace Mlt { class Repository; class Profile; -} +} // namespace Mlt #define EXIT_RESTART (42) #define pCore Core::self() /** * @class Core * @brief Singleton that provides access to the different parts of Kdenlive * * Needs to be initialize before any widgets are created in MainWindow. * Plugins should be loaded after the widget setup. */ class /*KDENLIVECORE_EXPORT*/ Core : public QObject { Q_OBJECT public: Core(const Core &) = delete; Core &operator=(const Core &) = delete; Core(Core &&) = delete; Core &operator=(Core &&) = delete; virtual ~Core(); /** * @brief Setup the basics of the application, in particular the connection * with Mlt * @param MltPath (optional) path to MLT environment */ static void build(const QString &MltPath = QString()); /** * @brief Init the GUI part of the app and show the main window * @param Url (optional) file to open * If Url is present, it will be opened, otherwhise, if openlastproject is * set, latest project will be opened. If no file is open after trying this, * a default new file will be created. */ void initGUI(const QUrl &Url); /** @brief Returns a pointer to the singleton object. */ static std::unique_ptr &self(); /** @brief Returns a pointer to the main window. */ MainWindow *window(); /** @brief Returns a pointer to the project manager. */ ProjectManager *projectManager(); /** @brief Returns a pointer to the current project. */ KdenliveDoc *currentDoc(); /** @brief Returns a pointer to the monitor manager. */ MonitorManager *monitorManager(); /** @brief Returns a pointer to the project bin controller. */ std::shared_ptr binController(); /** @brief Returns a pointer to the view of the project bin. */ Bin *bin(); /** @brief Select a clip in the Bin from its id. */ void selectBinClip(const QString &id, int frame = -1, const QPoint &zone = QPoint()); /** @brief Returns a pointer to the model of the project bin. */ std::shared_ptr projectItemModel(); /** @brief Returns a pointer to the job manager. Please do not store it. */ std::shared_ptr jobManager(); /** @brief Returns a pointer to the library. */ LibraryWidget *library(); /** @brief Returns a pointer to MLT's repository */ std::unique_ptr &getMltRepository(); /** @brief Returns a pointer to the current profile */ std::unique_ptr &getCurrentProfile() const; const QString &getCurrentProfilePath() const; /** @brief Define the active profile * @returns true if profile exists, false if not found */ bool setCurrentProfile(const QString &profilePath); /** @brief Returns Sample Aspect Ratio of current profile */ double getCurrentSar() const; /** @brief Returns Display Aspect Ratio of current profile */ double getCurrentDar() const; /** @brief Returns frame rate of current profile */ double getCurrentFps() const; /** @brief Returns the frame size (width x height) of current profile */ QSize getCurrentFrameSize() const; /** @brief Returns the frame display size (width x height) of current profile */ QSize getCurrentFrameDisplaySize() const; /** @brief Request project monitor refresh */ void requestMonitorRefresh(); /** @brief Request project monitor refresh if current position is inside range*/ void refreshProjectRange(QSize range); /** @brief Request project monitor refresh if referenced item is under cursor */ void refreshProjectItem(const ObjectId &id); /** @brief Returns a reference to a monitor (clip or project monitor) */ Monitor *getMonitor(int id); /** @brief This function must be called whenever the profile used changes */ void profileChanged(); /** @brief Create and push and undo object based on the corresponding functions Note that if you class permits and requires it, you should use the macro PUSH_UNDO instead*/ void pushUndo(const Fun &undo, const Fun &redo, const QString &text); void pushUndo(QUndoCommand *command); /** @brief display a user info/warning message in statusbar */ void displayMessage(const QString &message, MessageType type, int timeout = -1); /** @brief Clear asset view if itemId is displayed. */ void clearAssetPanel(int itemId); /** @brief Returns the effectstack of a given bin clip. */ std::shared_ptr getItemEffectStack(int itemType, int itemId); int getItemPosition(const ObjectId &id); int getItemIn(const ObjectId &id); int getItemTrack(const ObjectId &id); int getItemDuration(const ObjectId &id); /** @brief Get a list of video track names with indexes */ QMap getVideoTrackNames(); /** @brief Returns the composition A track (MLT index / Track id) */ QPair getCompositionATrack(int cid) const; void setCompositionATrack(int cid, int aTrack); /* @brief Return true if composition's a_track is automatic (no forced track) */ bool compositionAutoTrack(int cid) const; std::shared_ptr undoStack(); double getClipSpeed(int id) const; void invalidateItem(ObjectId itemId); void prepareShutdown(); /** the keyframe model changed (effect added, deleted, active effect changed), inform timeline */ void updateItemKeyframes(ObjectId id); /** A fade for clip id changed, update timeline */ void updateItemModel(ObjectId id, const QString &service); /** Show / hide keyframes for a timeline clip */ void showClipKeyframes(ObjectId id, bool enable); Mlt::Profile *thumbProfile(); void clearSelection(); private: explicit Core(); static std::unique_ptr m_self; /** @brief Makes sure Qt's locale and system locale settings match. */ void initLocale(); MainWindow *m_mainWindow; ProjectManager *m_projectManager; MonitorManager *m_monitorManager; std::shared_ptr m_binController; std::shared_ptr m_projectItemModel; std::shared_ptr m_jobManager; Bin *m_binWidget; LibraryWidget *m_library; /** @brief Current project's profile path */ QString m_currentProfile; QString m_profile; std::unique_ptr m_thumbProfile; bool m_guiConstructed = false; public slots: void triggerAction(const QString &name); signals: void coreIsReady(); void updateLibraryPath(); }; #endif diff --git a/src/dialogs/clipcreationdialog.cpp b/src/dialogs/clipcreationdialog.cpp index f7fe7fafc..24152f228 100644 --- a/src/dialogs/clipcreationdialog.cpp +++ b/src/dialogs/clipcreationdialog.cpp @@ -1,437 +1,438 @@ /* Copyright (C) 2015 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 "clipcreationdialog.h" #include "bin/bin.h" #include "bin/bincommands.h" #include "bin/clipcreator.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "definitions.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "project/dialogs/slideshowclip.h" #include "timecodedisplay.h" #include "titler/titlewidget.h" #include "titletemplatedialog.h" #include "ui_colorclip_ui.h" #include "ui_qtextclip_ui.h" #include "utils/devices.hpp" #include "xml/xml.hpp" #include "klocalizedstring.h" #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include // static QStringList ClipCreationDialog::getExtensions() { // Build list of MIME types QStringList mimeTypes = QStringList() << QStringLiteral("") << QStringLiteral("application/x-kdenlivetitle") << QStringLiteral("video/mlt-playlist") << QStringLiteral("text/plain"); // Video MIMEs mimeTypes << QStringLiteral("video/x-flv") << QStringLiteral("application/vnd.rn-realmedia") << QStringLiteral("video/x-dv") << QStringLiteral("video/dv") << QStringLiteral("video/x-msvideo") << QStringLiteral("video/x-matroska") << QStringLiteral("video/mpeg") << QStringLiteral("video/ogg") << QStringLiteral("video/x-ms-wmv") << QStringLiteral("video/mp4") << QStringLiteral("video/quicktime") << QStringLiteral("video/webm") << QStringLiteral("video/3gpp") << QStringLiteral("video/mp2t"); // Audio MIMEs mimeTypes << QStringLiteral("audio/x-flac") << QStringLiteral("audio/x-matroska") << QStringLiteral("audio/mp4") << QStringLiteral("audio/mpeg") << QStringLiteral("audio/x-mp3") << QStringLiteral("audio/ogg") << QStringLiteral("audio/x-wav") << QStringLiteral("audio/x-aiff") << QStringLiteral("audio/aiff") << QStringLiteral("application/ogg") << QStringLiteral("application/mxf") << QStringLiteral("application/x-shockwave-flash") << QStringLiteral("audio/ac3"); // Image MIMEs mimeTypes << QStringLiteral("image/gif") << QStringLiteral("image/jpeg") << QStringLiteral("image/png") << QStringLiteral("image/x-tga") << QStringLiteral("image/x-bmp") << QStringLiteral("image/svg+xml") << QStringLiteral("image/tiff") << QStringLiteral("image/x-xcf") << QStringLiteral("image/x-xcf-gimp") << QStringLiteral("image/x-vnd.adobe.photoshop") << QStringLiteral("image/x-pcx") << QStringLiteral("image/x-exr") << QStringLiteral("image/x-portable-pixmap") << QStringLiteral("application/x-krita"); QMimeDatabase db; QStringList allExtensions; for (const QString &mimeType : mimeTypes) { QMimeType mime = db.mimeTypeForName(mimeType); if (mime.isValid()) { allExtensions.append(mime.globPatterns()); } } // process custom user extensions const QStringList customs = KdenliveSettings::addedExtensions().split(' ', QString::SkipEmptyParts); if (!customs.isEmpty()) { for (const QString &ext : customs) { if (ext.startsWith(QLatin1String("*."))) { allExtensions << ext; } else if (ext.startsWith(QLatin1String("."))) { allExtensions << QStringLiteral("*") + ext; } else if (!ext.contains(QLatin1Char('.'))) { allExtensions << QStringLiteral("*.") + ext; } else { // Unrecognized format qCDebug(KDENLIVE_LOG) << "Unrecognized custom format: " << ext; } } } allExtensions.removeDuplicates(); return allExtensions; } // static void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model) { QScopedPointer dia(new QDialog(qApp->activeWindow())); Ui::ColorClip_UI dia_ui; dia_ui.setupUi(dia.data()); dia->setWindowTitle(i18n("Color Clip")); dia_ui.clip_name->setText(i18n("Color Clip")); QScopedPointer t(new TimecodeDisplay(doc->timecode())); t->setValue(KdenliveSettings::color_duration()); dia_ui.clip_durationBox->addWidget(t.data()); dia_ui.clip_color->setColor(KdenliveSettings::colorclipcolor()); if (dia->exec() == QDialog::Accepted) { QString color = dia_ui.clip_color->color().name(); KdenliveSettings::setColorclipcolor(color); color = color.replace(0, 1, QStringLiteral("0x")) + "ff"; int duration = doc->getFramePos(doc->timecode().getTimecode(t->gentime())); QString name = dia_ui.clip_name->text(); ClipCreator::createColorClip(color, duration, name, parentFolder, model); } } void ClipCreationDialog::createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); QScopedPointer dia(new QDialog(bin)); Ui::QTextClip_UI dia_ui; dia_ui.setupUi(dia.data()); dia->setWindowTitle(i18n("Text Clip")); dia_ui.fgColor->setAlphaChannelEnabled(true); dia_ui.lineColor->setAlphaChannelEnabled(true); dia_ui.bgColor->setAlphaChannelEnabled(true); if (clip) { dia_ui.name->setText(clip->getProducerProperty(QStringLiteral("kdenlive:clipname"))); dia_ui.text->setPlainText(clip->getProducerProperty(QStringLiteral("text"))); dia_ui.fgColor->setColor(clip->getProducerProperty(QStringLiteral("fgcolour"))); dia_ui.bgColor->setColor(clip->getProducerProperty(QStringLiteral("bgcolour"))); dia_ui.pad->setValue(clip->getProducerProperty(QStringLiteral("pad")).toInt()); dia_ui.lineColor->setColor(clip->getProducerProperty(QStringLiteral("olcolour"))); dia_ui.lineWidth->setValue(clip->getProducerProperty(QStringLiteral("outline")).toInt()); dia_ui.font->setCurrentFont(QFont(clip->getProducerProperty(QStringLiteral("family")))); dia_ui.fontSize->setValue(clip->getProducerProperty(QStringLiteral("size")).toInt()); dia_ui.weight->setValue(clip->getProducerProperty(QStringLiteral("weight")).toInt()); dia_ui.italic->setChecked(clip->getProducerProperty(QStringLiteral("style")) == QStringLiteral("italic")); dia_ui.duration->setText(doc->timecode().getTimecodeFromFrames(clip->getProducerProperty(QStringLiteral("out")).toInt())); } else { dia_ui.name->setText(i18n("Text Clip")); dia_ui.fgColor->setColor(titleConfig.readEntry(QStringLiteral("font_color"))); dia_ui.bgColor->setColor(titleConfig.readEntry(QStringLiteral("background_color"))); dia_ui.lineColor->setColor(titleConfig.readEntry(QStringLiteral("font_outline_color"))); dia_ui.lineWidth->setValue(titleConfig.readEntry(QStringLiteral("font_outline")).toInt()); dia_ui.font->setCurrentFont(QFont(titleConfig.readEntry(QStringLiteral("font_family")))); dia_ui.fontSize->setValue(titleConfig.readEntry(QStringLiteral("font_pixel_size")).toInt()); dia_ui.weight->setValue(titleConfig.readEntry(QStringLiteral("font_weight")).toInt()); dia_ui.italic->setChecked(titleConfig.readEntry(QStringLiteral("font_italic")).toInt() != 0); dia_ui.duration->setText(titleConfig.readEntry(QStringLiteral("title_duration"))); } if (dia->exec() == QDialog::Accepted) { // KdenliveSettings::setColorclipcolor(color); QDomDocument xml; QDomElement prod = xml.createElement(QStringLiteral("producer")); xml.appendChild(prod); prod.setAttribute(QStringLiteral("type"), (int)ClipType::QText); int id = bin->getFreeClipId(); prod.setAttribute(QStringLiteral("id"), QString::number(id)); prod.setAttribute(QStringLiteral("in"), QStringLiteral("0")); prod.setAttribute(QStringLiteral("out"), doc->timecode().getFrameCount(dia_ui.duration->text())); QMap properties; properties.insert(QStringLiteral("kdenlive:clipname"), dia_ui.name->text()); if (!parentId.isEmpty()) { properties.insert(QStringLiteral("kdenlive:folderid"), parentId); } properties.insert(QStringLiteral("mlt_service"), QStringLiteral("qtext")); properties.insert(QStringLiteral("out"), QString::number(doc->timecode().getFrameCount(dia_ui.duration->text()))); properties.insert(QStringLiteral("length"), dia_ui.duration->text()); // properties.insert(QStringLiteral("scale"), QStringLiteral("off")); // properties.insert(QStringLiteral("fill"), QStringLiteral("0")); properties.insert(QStringLiteral("text"), dia_ui.text->document()->toPlainText()); properties.insert(QStringLiteral("fgcolour"), dia_ui.fgColor->color().name(QColor::HexArgb)); properties.insert(QStringLiteral("bgcolour"), dia_ui.bgColor->color().name(QColor::HexArgb)); properties.insert(QStringLiteral("olcolour"), dia_ui.lineColor->color().name(QColor::HexArgb)); properties.insert(QStringLiteral("outline"), QString::number(dia_ui.lineWidth->value())); properties.insert(QStringLiteral("pad"), QString::number(dia_ui.pad->value())); properties.insert(QStringLiteral("family"), dia_ui.font->currentFont().family()); properties.insert(QStringLiteral("size"), QString::number(dia_ui.fontSize->value())); properties.insert(QStringLiteral("style"), dia_ui.italic->isChecked() ? QStringLiteral("italic") : QStringLiteral("normal")); properties.insert(QStringLiteral("weight"), QString::number(dia_ui.weight->value())); if (clip) { QMap oldProperties; oldProperties.insert(QStringLiteral("out"), clip->getProducerProperty(QStringLiteral("out"))); oldProperties.insert(QStringLiteral("length"), clip->getProducerProperty(QStringLiteral("length"))); oldProperties.insert(QStringLiteral("kdenlive:clipname"), clip->name()); oldProperties.insert(QStringLiteral("ttl"), clip->getProducerProperty(QStringLiteral("ttl"))); oldProperties.insert(QStringLiteral("loop"), clip->getProducerProperty(QStringLiteral("loop"))); oldProperties.insert(QStringLiteral("crop"), clip->getProducerProperty(QStringLiteral("crop"))); oldProperties.insert(QStringLiteral("fade"), clip->getProducerProperty(QStringLiteral("fade"))); oldProperties.insert(QStringLiteral("luma_duration"), clip->getProducerProperty(QStringLiteral("luma_duration"))); oldProperties.insert(QStringLiteral("luma_file"), clip->getProducerProperty(QStringLiteral("luma_file"))); oldProperties.insert(QStringLiteral("softness"), clip->getProducerProperty(QStringLiteral("softness"))); oldProperties.insert(QStringLiteral("animation"), clip->getProducerProperty(QStringLiteral("animation"))); bin->slotEditClipCommand(clip->AbstractProjectItem::clipId(), oldProperties, properties); } else { Xml::addXmlProperties(prod, properties); QString clipId = QString::number(id); pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Title clip")); } } } // static void ClipCreationDialog::createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr model) { QScopedPointer dia( new SlideshowClip(doc->timecode(), KRecentDirs::dir(QStringLiteral(":KdenliveSlideShowFolder")), nullptr, QApplication::activeWindow())); if (dia->exec() == QDialog::Accepted) { // Ready, create xml KRecentDirs::add(QStringLiteral(":KdenliveSlideShowFolder"), QUrl::fromLocalFile(dia->selectedPath()).adjusted(QUrl::RemoveFilename).toLocalFile()); std::unordered_map properties; properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(dia->clipDuration())); properties[QStringLiteral("loop")] = QString::number(static_cast(dia->loop())); properties[QStringLiteral("crop")] = QString::number(static_cast(dia->crop())); properties[QStringLiteral("fade")] = QString::number(static_cast(dia->fade())); properties[QStringLiteral("luma_duration")] = QString::number(doc->getFramePos(dia->lumaDuration())); properties[QStringLiteral("luma_file")] = dia->lumaFile(); properties[QStringLiteral("softness")] = QString::number(dia->softness()); properties[QStringLiteral("animation")] = dia->animation(); int duration = doc->getFramePos(dia->clipDuration()) * dia->imageCount(); ClipCreator::createSlideshowClip(dia->selectedPath(), duration, dia->clipName(), parentId, properties, model); } } void ClipCreationDialog::createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr model) { // Make sure the titles folder exists QDir dir(doc->projectDataFolder() + QStringLiteral("/titles")); dir.mkpath(QStringLiteral(".")); - QPointer dia_ui = new TitleWidget(QUrl::fromLocalFile(templatePath), doc->timecode(), dir.absolutePath(), pCore->getMonitor(Kdenlive::ProjectMonitor), pCore->bin()); + QPointer dia_ui = + new TitleWidget(QUrl::fromLocalFile(templatePath), doc->timecode(), dir.absolutePath(), pCore->getMonitor(Kdenlive::ProjectMonitor), pCore->bin()); QObject::connect(dia_ui.data(), &TitleWidget::requestBackgroundFrame, pCore->bin(), &Bin::slotGetCurrentProjectImage); if (dia_ui->exec() == QDialog::Accepted) { // Ready, create clip xml std::unordered_map properties; properties[QStringLiteral("xmldata")] = dia_ui->xml().toString(); QString titleSuggestion = dia_ui->titleSuggest(); ClipCreator::createTitleClip(properties, dia_ui->duration() - 1, titleSuggestion.isEmpty() ? i18n("Title clip") : titleSuggestion, parentFolder, model); } delete dia_ui; } void ClipCreationDialog::createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model) { QScopedPointer dia(new TitleTemplateDialog(doc->projectDataFolder(), QApplication::activeWindow())); if (dia->exec() == QDialog::Accepted) { ClipCreator::createTitleTemplate(dia->selectedTemplate(), dia->selectedText(), i18n("Template title clip"), parentFolder, model); } } // void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QList &urls, const QStringList &groupInfo, Bin *bin, // const QMap &data) // { // auto *addClips = new QUndoCommand(); // TODO: check files on removable volume /*listRemovableVolumes(); for (const QUrl &file : urls) { if (QFile::exists(file.path())) { //TODO check for duplicates if (!data.contains("bypassDuplicate") && !getClipByResource(file.path()).empty()) { if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Clip %1
already exists in project, what do you want to do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel) continue; } if (isOnRemovableDevice(file) && !isOnRemovableDevice(m_doc->projectFolder())) { int answer = KMessageBox::warningYesNoCancel(QApplication::activeWindow(), i18n("Clip %1
is on a removable device, will not be available when device is unplugged", file.path()), i18n("File on a Removable Device"), KGuiItem(i18n("Copy file to project folder")), KGuiItem(i18n("Continue")), KStandardGuiItem::cancel(), QString("copyFilesToProjectFolder")); if (answer == KMessageBox::Cancel) continue; else if (answer == KMessageBox::Yes) { // Copy files to project folder QDir sourcesFolder(m_doc->projectFolder().toLocalFile()); sourcesFolder.cd("clips"); KIO::MkdirJob *mkdirJob = KIO::mkdir(QUrl::fromLocalFile(sourcesFolder.absolutePath())); KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow()); if (!mkdirJob->exec()) { KMessageBox::sorry(QApplication::activeWindow(), i18n("Cannot create directory %1", sourcesFolder.absolutePath())); continue; } //KIO::filesize_t m_requestedSize; KIO::CopyJob *copyjob = KIO::copy(file, QUrl::fromLocalFile(sourcesFolder.absolutePath())); //TODO: for some reason, passing metadata does not work... copyjob->addMetaData("group", data.value("group")); copyjob->addMetaData("groupId", data.value("groupId")); copyjob->addMetaData("comment", data.value("comment")); KJobWidgets::setWindow(copyjob, QApplication::activeWindow()); connect(copyjob, &KIO::CopyJob::copyingDone, this, &ClipManager::slotAddCopiedClip); continue; } }*/ // TODO check folders /*QList< QList > foldersList; QMimeDatabase db; for (const QUrl & file : list) { // Check there is no folder here QMimeType type = db.mimeTypeForUrl(file); if (type.inherits("inode/directory")) { // user dropped a folder, import its files list.removeAll(file); QDir dir(file.path()); QStringList result = dir.entryList(QDir::Files); QList folderFiles; folderFiles << file; for (const QString & path : result) { // TODO: create folder command folderFiles.append(QUrl::fromLocalFile(dir.absoluteFilePath(path))); } if (folderFiles.count() > 1) foldersList.append(folderFiles); } }*/ //} void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model) { qDebug() << "/////////// starting to add bin clips"; QList list; QString allExtensions = getExtensions().join(QLatin1Char(' ')); QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files"); QCheckBox *b = new QCheckBox(i18n("Import image sequence")); b->setChecked(KdenliveSettings::autoimagesequence()); QFrame *f = new QFrame(); f->setFrameShape(QFrame::NoFrame); auto *l = new QHBoxLayout; l->addWidget(b); l->addStretch(5); f->setLayout(l); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QScopedPointer dlg(new QDialog((QWidget *)doc->parent())); QScopedPointer fileWidget(new KFileWidget(QUrl::fromLocalFile(clipFolder), dlg.data())); auto *layout = new QVBoxLayout; layout->addWidget(fileWidget.data()); fileWidget->setCustomWidget(f); fileWidget->okButton()->show(); fileWidget->cancelButton()->show(); QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk); QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept); QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept); QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject); dlg->setLayout(layout); fileWidget->setFilter(dialogFilter); fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory); KSharedConfig::Ptr conf = KSharedConfig::openConfig(); QWindow *handle = dlg->windowHandle(); if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) { KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize")); dlg->resize(handle->size()); } if (dlg->exec() == QDialog::Accepted) { KdenliveSettings::setAutoimagesequence(b->isChecked()); list = fileWidget->selectedUrls(); if (!list.isEmpty()) { KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), list.constFirst().adjusted(QUrl::RemoveFilename).toLocalFile()); } if (b->isChecked() && list.count() >= 1) { // Check for image sequence const QUrl &url = list.at(0); QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2); if (fileName.at(fileName.size() - 1).isDigit()) { KFileItem item(url); if (item.mimetype().startsWith(QLatin1String("image"))) { // import as sequence if we found more than one image in the sequence QStringList patternlist; QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist); qCDebug(KDENLIVE_LOG) << " / // IMPORT PATTERN: " << pattern << " COUNT: " << patternlist.count(); int count = patternlist.count(); if (count > 1) { // get image sequence base name while (fileName.at(fileName.size() - 1).isDigit()) { fileName.chop(1); } QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration()); std::unordered_map properties; properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration)); properties[QStringLiteral("loop")] = QString::number(0); properties[QStringLiteral("crop")] = QString::number(0); properties[QStringLiteral("fade")] = QString::number(0); properties[QStringLiteral("luma_duration")] = QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps()))))); int frame_duration = doc->getFramePos(duration) * count; ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, model); return; } } } } } qDebug() << "/////////// found list" << list; KConfigGroup group = conf->group("FileDialogSize"); if (handle) { KWindowConfig::saveWindowSize(handle, group); } Fun undo = []() { return true; }; Fun redo = []() { return true; }; bool created = ClipCreator::createClipsFromList(list, true, parentFolder, model, undo, redo); // We reset the state of the "don't ask again" for the question about removable devices KMessageBox::enableMessage(QStringLiteral("removable")); if (created) { pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size())); } } diff --git a/src/dialogs/clipcreationdialog.h b/src/dialogs/clipcreationdialog.h index af438daf9..657b12155 100644 --- a/src/dialogs/clipcreationdialog.h +++ b/src/dialogs/clipcreationdialog.h @@ -1,50 +1,50 @@ /* Copyright (C) 2015 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 . */ #ifndef CLIPCREATIONDIALOG_H #define CLIPCREATIONDIALOG_H #include "definitions.h" class KdenliveDoc; class QUndoCommand; class Bin; class ProjectClip; class ProjectItemModel; /** * @namespace ClipCreationDialog * @brief This namespace contains a list of static methods displaying widgets * allowing creation of all clip types. */ namespace ClipCreationDialog { QStringList getExtensions(); void createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model); void createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip = nullptr); void createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr model); void createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr model); void createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model); void createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr model); -} +} // namespace ClipCreationDialog #endif diff --git a/src/dialogs/encodingprofilesdialog.cpp b/src/dialogs/encodingprofilesdialog.cpp index a9b45c617..677655ab2 100644 --- a/src/dialogs/encodingprofilesdialog.cpp +++ b/src/dialogs/encodingprofilesdialog.cpp @@ -1,191 +1,191 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "encodingprofilesdialog.h" #include "kdenlivesettings.h" #include "utils/KoIconUtils.h" #include "klocalizedstring.h" #include #include #include #include EncodingProfilesDialog::EncodingProfilesDialog(int profileType, QWidget *parent) : QDialog(parent) , m_configGroup(nullptr) { setupUi(this); setWindowTitle(i18n("Manage Encoding Profiles")); profile_type->addItem(i18n("Proxy clips"), 0); profile_type->addItem(i18n("Timeline preview"), 1); profile_type->addItem(i18n("Video4Linux capture"), 2); profile_type->addItem(i18n("Screen capture"), 3); profile_type->addItem(i18n("Decklink capture"), 4); button_add->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); button_edit->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); button_delete->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-remove"))); button_download->setIcon(KoIconUtils::themedIcon(QStringLiteral("download"))); m_configFile = new KConfig(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); profile_type->setCurrentIndex(profileType); - connect(profile_type, static_cast(&KComboBox::currentIndexChanged), this, &EncodingProfilesDialog::slotLoadProfiles); + connect(profile_type, static_cast(&KComboBox::currentIndexChanged), this, &EncodingProfilesDialog::slotLoadProfiles); connect(profile_list, &QListWidget::currentRowChanged, this, &EncodingProfilesDialog::slotShowParams); connect(button_delete, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotDeleteProfile); connect(button_add, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotAddProfile); connect(button_edit, &QAbstractButton::clicked, this, &EncodingProfilesDialog::slotEditProfile); profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); slotLoadProfiles(); } EncodingProfilesDialog::~EncodingProfilesDialog() { delete m_configGroup; delete m_configFile; } void EncodingProfilesDialog::slotLoadProfiles() { profile_list->blockSignals(true); profile_list->clear(); QString group; switch (profile_type->currentIndex()) { case 0: group = QStringLiteral("proxy"); break; case 2: group = QStringLiteral("video4linux"); break; case 3: group = QStringLiteral("screengrab"); break; case 4: group = QStringLiteral("decklink"); break; case 1: default: group = QStringLiteral("timelinepreview"); break; } delete m_configGroup; m_configGroup = new KConfigGroup(m_configFile, group); QMap values = m_configGroup->entryMap(); QMapIterator i(values); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key(), profile_list); item->setData(Qt::UserRole, i.value()); // cout << i.key() << ": " << i.value() << endl; } profile_list->blockSignals(false); profile_list->setCurrentRow(0); const bool multiProfile(profile_list->count() > 0); button_delete->setEnabled(multiProfile); button_edit->setEnabled(multiProfile); } void EncodingProfilesDialog::slotShowParams() { profile_parameters->clear(); QListWidgetItem *item = profile_list->currentItem(); if (!item) { return; } profile_parameters->setPlainText(item->data(Qt::UserRole).toString().section(QLatin1Char(';'), 0, 0)); } void EncodingProfilesDialog::slotDeleteProfile() { QListWidgetItem *item = profile_list->currentItem(); if (!item) { return; } QString profile = item->text(); m_configGroup->deleteEntry(profile); slotLoadProfiles(); } void EncodingProfilesDialog::slotAddProfile() { QPointer d = new QDialog(this); auto *l = new QVBoxLayout; l->addWidget(new QLabel(i18n("Profile name:"))); auto *pname = new QLineEdit; l->addWidget(pname); l->addWidget(new QLabel(i18n("Parameters:"))); auto *pparams = new QPlainTextEdit; l->addWidget(pparams); l->addWidget(new QLabel(i18n("File extension:"))); auto *pext = new QLineEdit; l->addWidget(pext); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel); connect(box, &QDialogButtonBox::accepted, d.data(), &QDialog::accept); connect(box, &QDialogButtonBox::rejected, d.data(), &QDialog::reject); l->addWidget(box); d->setLayout(l); QListWidgetItem *item = profile_list->currentItem(); if (item) { QString profilestr = item->data(Qt::UserRole).toString(); pparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); pext->setText(profilestr.section(QLatin1Char(';'), 1, 1)); } if (d->exec() == QDialog::Accepted) { m_configGroup->writeEntry(pname->text(), pparams->toPlainText() + QLatin1Char(';') + pext->text()); slotLoadProfiles(); } delete d; } void EncodingProfilesDialog::slotEditProfile() { QPointer d = new QDialog(this); auto *l = new QVBoxLayout; l->addWidget(new QLabel(i18n("Profile name:"))); auto *pname = new QLineEdit; l->addWidget(pname); l->addWidget(new QLabel(i18n("Parameters:"))); auto *pparams = new QPlainTextEdit; l->addWidget(pparams); l->addWidget(new QLabel(i18n("File extension:"))); auto *pext = new QLineEdit; l->addWidget(pext); QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel); connect(box, &QDialogButtonBox::accepted, d.data(), &QDialog::accept); connect(box, &QDialogButtonBox::rejected, d.data(), &QDialog::reject); l->addWidget(box); d->setLayout(l); QListWidgetItem *item = profile_list->currentItem(); if (item) { pname->setText(item->text()); QString profilestr = item->data(Qt::UserRole).toString(); pparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); pext->setText(profilestr.section(QLatin1Char(';'), 1, 1)); pparams->setFocus(); } if (d->exec() == QDialog::Accepted) { m_configGroup->writeEntry(pname->text(), pparams->toPlainText().simplified() + QLatin1Char(';') + pext->text()); slotLoadProfiles(); } delete d; } diff --git a/src/dialogs/kdenlivesettingsdialog.cpp b/src/dialogs/kdenlivesettingsdialog.cpp index 586c46193..9a6901889 100644 --- a/src/dialogs/kdenlivesettingsdialog.cpp +++ b/src/dialogs/kdenlivesettingsdialog.cpp @@ -1,1474 +1,1485 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "kdenlivesettingsdialog.h" #include "clipcreationdialog.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "encodingprofilesdialog.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "profilesdialog.h" #include "project/dialogs/profilewidget.h" #include "renderer.h" #include "utils/KoIconUtils.h" #ifdef USE_V4L #include "capture/v4lcapture.h" #endif #include "kdenlive_debug.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogaction.h" #include "jogshuttle/jogshuttleconfig.h" #include #include #endif KdenliveSettingsDialog::KdenliveSettingsDialog(const QMap &mappable_actions, bool gpuAllowed, QWidget *parent) : KConfigDialog(parent, QStringLiteral("settings"), KdenliveSettings::self()) , m_modified(false) , m_shuttleModified(false) , m_mappable_actions(mappable_actions) { KdenliveSettings::setV4l_format(0); QWidget *p1 = new QWidget; QFontInfo ftInfo(font()); m_configMisc.setupUi(p1); m_page1 = addPage(p1, i18n("Misc")); m_page1->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); // Hide avformat-novalidate trick, causes crash (bug #2205 and #2206) m_configMisc.kcfg_projectloading_avformatnovalidate->setVisible(false); m_configMisc.kcfg_use_exiftool->setEnabled(!QStandardPaths::findExecutable(QStringLiteral("exiftool")).isEmpty()); QWidget *p8 = new QWidget; m_configProject.setupUi(p8); m_page8 = addPage(p8, i18n("Project Defaults")); auto *vbox = new QVBoxLayout; m_pw = new ProfileWidget(this); vbox->addWidget(m_pw); m_configProject.profile_box->setLayout(vbox); m_configProject.profile_box->setTitle(i18n("Select the default profile (preset)")); // Select profile m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile()); connect(m_pw, &ProfileWidget::profileChanged, this, &KdenliveSettingsDialog::slotDialogModified); m_page8->setIcon(KoIconUtils::themedIcon(QStringLiteral("project-defaults"))); connect(m_configProject.kcfg_generateproxy, &QAbstractButton::toggled, m_configProject.kcfg_proxyminsize, &QWidget::setEnabled); m_configProject.kcfg_proxyminsize->setEnabled(KdenliveSettings::generateproxy()); m_configProject.projecturl->setMode(KFile::Directory); m_configProject.projecturl->setUrl(QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder())); connect(m_configProject.kcfg_generateimageproxy, &QAbstractButton::toggled, m_configProject.kcfg_proxyimageminsize, &QWidget::setEnabled); m_configProject.kcfg_proxyimageminsize->setEnabled(KdenliveSettings::generateimageproxy()); QWidget *p3 = new QWidget; m_configTimeline.setupUi(p3); m_page3 = addPage(p3, i18n("Timeline")); m_page3->setIcon(KoIconUtils::themedIcon(QStringLiteral("video-display"))); m_configTimeline.kcfg_trackheight->setMinimum(ftInfo.pixelSize() * 1.5); QWidget *p2 = new QWidget; m_configEnv.setupUi(p2); m_configEnv.mltpathurl->setMode(KFile::Directory); m_configEnv.mltpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_mltpath")); m_configEnv.rendererpathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_rendererpath")); m_configEnv.ffmpegurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffmpegpath")); m_configEnv.ffplayurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffplaypath")); m_configEnv.ffprobeurl->lineEdit()->setObjectName(QStringLiteral("kcfg_ffprobepath")); int maxThreads = QThread::idealThreadCount(); m_configEnv.kcfg_mltthreads->setMaximum(maxThreads > 2 ? maxThreads : 8); m_configEnv.tmppathurl->setMode(KFile::Directory); m_configEnv.tmppathurl->lineEdit()->setObjectName(QStringLiteral("kcfg_currenttmpfolder")); m_configEnv.capturefolderurl->setMode(KFile::Directory); m_configEnv.capturefolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_capturefolder")); m_configEnv.capturefolderurl->setEnabled(!KdenliveSettings::capturetoprojectfolder()); connect(m_configEnv.kcfg_capturetoprojectfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableCaptureFolder); // Library folder m_configEnv.libraryfolderurl->setMode(KFile::Directory); m_configEnv.libraryfolderurl->lineEdit()->setObjectName(QStringLiteral("kcfg_libraryfolder")); m_configEnv.libraryfolderurl->setEnabled(!KdenliveSettings::librarytodefaultfolder()); m_configEnv.kcfg_librarytodefaultfolder->setToolTip(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/library")); connect(m_configEnv.kcfg_librarytodefaultfolder, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEnableLibraryFolder); // Mime types QStringList mimes = ClipCreationDialog::getExtensions(); qSort(mimes); m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' '))); m_page2 = addPage(p2, i18n("Environment")); m_page2->setIcon(KoIconUtils::themedIcon(QStringLiteral("application-x-executable-script"))); QWidget *p4 = new QWidget; m_configCapture.setupUi(p4); m_configCapture.tabWidget->removeTab(0); m_configCapture.tabWidget->removeTab(2); #ifdef USE_V4L // Video 4 Linux device detection for (int i = 0; i < 10; ++i) { QString path = QStringLiteral("/dev/video") + QString::number(i); if (QFile::exists(path)) { QStringList deviceInfo = V4lCaptureHandler::getDeviceName(path); if (!deviceInfo.isEmpty()) { m_configCapture.kcfg_detectedv4ldevices->addItem(deviceInfo.at(0), path); m_configCapture.kcfg_detectedv4ldevices->setItemData(m_configCapture.kcfg_detectedv4ldevices->count() - 1, deviceInfo.at(1), Qt::UserRole + 1); } } } - connect(m_configCapture.kcfg_detectedv4ldevices, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatev4lDevice); - connect(m_configCapture.kcfg_v4l_format, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatev4lCaptureProfile); + connect(m_configCapture.kcfg_detectedv4ldevices, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdatev4lDevice); + connect(m_configCapture.kcfg_v4l_format, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdatev4lCaptureProfile); connect(m_configCapture.config_v4l, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditVideo4LinuxProfile); slotUpdatev4lDevice(); #endif m_page4 = addPage(p4, i18n("Capture")); m_page4->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-record"))); m_configCapture.tabWidget->setCurrentIndex(KdenliveSettings::defaultcapture()); #ifdef Q_WS_MAC m_configCapture.tabWidget->setEnabled(false); m_configCapture.kcfg_defaultcapture->setEnabled(false); m_configCapture.label->setText(i18n("Capture is not yet available on Mac OS X.")); #endif QWidget *p5 = new QWidget; m_configShuttle.setupUi(p5); #ifdef USE_JOGSHUTTLE m_configShuttle.toolBtnReload->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); connect(m_configShuttle.kcfg_enableshuttle, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotCheckShuttle); connect(m_configShuttle.shuttledevicelist, SIGNAL(activated(int)), this, SLOT(slotUpdateShuttleDevice(int))); connect(m_configShuttle.toolBtnReload, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadShuttleDevices); slotCheckShuttle(static_cast(KdenliveSettings::enableshuttle())); m_configShuttle.shuttledisabled->hide(); // Store the button pointers into an array for easier handling them in the other functions. // TODO: impl enumerator or live with cut and paste :-))) setupJogshuttleBtns(KdenliveSettings::shuttledevice()); #if 0 m_shuttle_buttons.push_back(m_configShuttle.shuttle1); m_shuttle_buttons.push_back(m_configShuttle.shuttle2); m_shuttle_buttons.push_back(m_configShuttle.shuttle3); m_shuttle_buttons.push_back(m_configShuttle.shuttle4); m_shuttle_buttons.push_back(m_configShuttle.shuttle5); m_shuttle_buttons.push_back(m_configShuttle.shuttle6); m_shuttle_buttons.push_back(m_configShuttle.shuttle7); m_shuttle_buttons.push_back(m_configShuttle.shuttle8); m_shuttle_buttons.push_back(m_configShuttle.shuttle9); m_shuttle_buttons.push_back(m_configShuttle.shuttle10); m_shuttle_buttons.push_back(m_configShuttle.shuttle11); m_shuttle_buttons.push_back(m_configShuttle.shuttle12); m_shuttle_buttons.push_back(m_configShuttle.shuttle13); m_shuttle_buttons.push_back(m_configShuttle.shuttle14); m_shuttle_buttons.push_back(m_configShuttle.shuttle15); #endif #else /* ! USE_JOGSHUTTLE */ m_configShuttle.kcfg_enableshuttle->hide(); m_configShuttle.kcfg_enableshuttle->setDisabled(true); #endif /* USE_JOGSHUTTLE */ m_page5 = addPage(p5, i18n("JogShuttle")); m_page5->setIcon(KoIconUtils::themedIcon(QStringLiteral("jog-dial"))); QWidget *p6 = new QWidget; m_configSdl.setupUi(p6); m_configSdl.reload_blackmagic->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); connect(m_configSdl.reload_blackmagic, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotReloadBlackMagic); // m_configSdl.kcfg_openglmonitors->setHidden(true); m_page6 = addPage(p6, i18n("Playback")); m_page6->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-playback-start"))); QWidget *p7 = new QWidget; m_configTranscode.setupUi(p7); m_page7 = addPage(p7, i18n("Transcode")); m_page7->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); connect(m_configTranscode.button_add, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotAddTranscode); connect(m_configTranscode.button_delete, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotDeleteTranscode); connect(m_configTranscode.profiles_list, &QListWidget::itemChanged, this, &KdenliveSettingsDialog::slotDialogModified); connect(m_configTranscode.profiles_list, &QListWidget::currentRowChanged, this, &KdenliveSettingsDialog::slotSetTranscodeProfile); connect(m_configTranscode.profile_name, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_description, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_extension, &QLineEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_parameters, &QPlainTextEdit::textChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.profile_audioonly, &QCheckBox::stateChanged, this, &KdenliveSettingsDialog::slotEnableTranscodeUpdate); connect(m_configTranscode.button_update, &QAbstractButton::pressed, this, &KdenliveSettingsDialog::slotUpdateTranscodingProfile); m_configTranscode.profile_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); connect(m_configEnv.kp_image, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditImageApplication); connect(m_configEnv.kp_audio, &QAbstractButton::clicked, this, &KdenliveSettingsDialog::slotEditAudioApplication); loadEncodingProfiles(); - connect(m_configSdl.kcfg_audio_driver, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotCheckAlsaDriver); - connect(m_configSdl.kcfg_audio_backend, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotCheckAudioBackend); + connect(m_configSdl.kcfg_audio_driver, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotCheckAlsaDriver); + connect(m_configSdl.kcfg_audio_backend, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotCheckAudioBackend); initDevices(); - connect(m_configCapture.kcfg_grab_capture_type, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateGrabRegionStatus); + connect(m_configCapture.kcfg_grab_capture_type, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdateGrabRegionStatus); slotUpdateGrabRegionStatus(); loadTranscodeProfiles(); // HACK: check dvgrab version, because only dvgrab >= 3.3 supports // --timestamp option without bug if (KdenliveSettings::dvgrab_path().isEmpty() || !QFile::exists(KdenliveSettings::dvgrab_path())) { QString dvgrabpath = QStandardPaths::findExecutable(QStringLiteral("dvgrab")); KdenliveSettings::setDvgrab_path(dvgrabpath); } // decklink profile QAction *act = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(4); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.decklink_manageprofile->setDefaultAction(act); m_configCapture.decklink_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); m_configCapture.decklink_parameters->setVisible(false); m_configCapture.decklink_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.decklink_parameters->setPlainText(KdenliveSettings::decklink_parameters()); - connect(m_configCapture.kcfg_decklink_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateDecklinkProfile); + connect(m_configCapture.kcfg_decklink_profile, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdateDecklinkProfile); connect(m_configCapture.decklink_showprofileinfo, &QAbstractButton::clicked, m_configCapture.decklink_parameters, &QWidget::setVisible); // ffmpeg profile m_configCapture.v4l_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); m_configCapture.v4l_parameters->setVisible(false); m_configCapture.v4l_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.v4l_parameters->setPlainText(KdenliveSettings::v4l_parameters()); act = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(2); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.v4l_manageprofile->setDefaultAction(act); - connect(m_configCapture.kcfg_v4l_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateV4lProfile); + connect(m_configCapture.kcfg_v4l_profile, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdateV4lProfile); connect(m_configCapture.v4l_showprofileinfo, &QAbstractButton::clicked, m_configCapture.v4l_parameters, &QWidget::setVisible); // screen grab profile m_configCapture.grab_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); m_configCapture.grab_parameters->setVisible(false); m_configCapture.grab_parameters->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 4); m_configCapture.grab_parameters->setPlainText(KdenliveSettings::grab_parameters()); act = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(3); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configCapture.grab_manageprofile->setDefaultAction(act); - connect(m_configCapture.kcfg_grab_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateGrabProfile); + connect(m_configCapture.kcfg_grab_profile, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdateGrabProfile); connect(m_configCapture.grab_showprofileinfo, &QAbstractButton::clicked, m_configCapture.grab_parameters, &QWidget::setVisible); // Timeline preview act = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(1); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configProject.preview_manageprofile->setDefaultAction(act); - connect(m_configProject.kcfg_preview_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdatePreviewProfile); + connect(m_configProject.kcfg_preview_profile, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdatePreviewProfile); connect(m_configProject.preview_showprofileinfo, &QAbstractButton::clicked, m_configProject.previewparams, &QWidget::setVisible); m_configProject.previewparams->setVisible(false); m_configProject.previewparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3); m_configProject.previewparams->setPlainText(KdenliveSettings::previewparams()); m_configProject.preview_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); m_configProject.preview_showprofileinfo->setToolTip(i18n("Show default timeline preview parameters")); m_configProject.preview_manageprofile->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); m_configProject.preview_manageprofile->setToolTip(i18n("Manage timeline preview profiles")); m_configProject.kcfg_preview_profile->setToolTip(i18n("Select default timeline preview profile")); // proxy profile stuff m_configProject.proxy_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); m_configProject.proxy_showprofileinfo->setToolTip(i18n("Show default profile parameters")); m_configProject.proxy_manageprofile->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); m_configProject.proxy_manageprofile->setToolTip(i18n("Manage proxy profiles")); m_configProject.kcfg_proxy_profile->setToolTip(i18n("Select default proxy profile")); m_configProject.proxyparams->setVisible(false); m_configProject.proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 3); m_configProject.proxyparams->setPlainText(KdenliveSettings::proxyparams()); act = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure profiles"), this); act->setData(0); connect(act, &QAction::triggered, this, &KdenliveSettingsDialog::slotManageEncodingProfile); m_configProject.proxy_manageprofile->setDefaultAction(act); connect(m_configProject.proxy_showprofileinfo, &QAbstractButton::clicked, m_configProject.proxyparams, &QWidget::setVisible); - connect(m_configProject.kcfg_proxy_profile, static_cast(&KComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotUpdateProxyProfile); + connect(m_configProject.kcfg_proxy_profile, static_cast(&KComboBox::currentIndexChanged), this, + &KdenliveSettingsDialog::slotUpdateProxyProfile); slotUpdateProxyProfile(-1); slotUpdateV4lProfile(-1); slotUpdateGrabProfile(-1); slotUpdateDecklinkProfile(-1); // enable GPU accel only if Movit is found m_configSdl.kcfg_gpu_accel->setEnabled(gpuAllowed); m_configSdl.kcfg_gpu_accel->setToolTip(i18n("GPU processing needs MLT compiled with Movit and Rtaudio modules")); Render::getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice); if (!Render::getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device)) { // No blackmagic card found m_configSdl.kcfg_external_display->setEnabled(false); } if (!KdenliveSettings::dvgrab_path().isEmpty()) { double dvgrabVersion = 0; auto *versionCheck = new QProcess; versionCheck->setProcessChannelMode(QProcess::MergedChannels); versionCheck->start(QStringLiteral("dvgrab"), QStringList() << QStringLiteral("--version")); if (versionCheck->waitForFinished()) { QString version = QString(versionCheck->readAll()).simplified(); if (version.contains(QLatin1Char(' '))) { version = version.section(QLatin1Char(' '), -1); } dvgrabVersion = version.toDouble(); // qCDebug(KDENLIVE_LOG) << "// FOUND DVGRAB VERSION: " << dvgrabVersion; } delete versionCheck; if (dvgrabVersion < 3.3) { KdenliveSettings::setFirewiretimestamp(false); m_configCapture.kcfg_firewiretimestamp->setEnabled(false); } m_configCapture.dvgrab_info->setText(i18n("dvgrab version %1 at %2", dvgrabVersion, KdenliveSettings::dvgrab_path())); } else { m_configCapture.dvgrab_info->setText(i18n("dvgrab utility not found, please install it for firewire capture")); } } void KdenliveSettingsDialog::setupJogshuttleBtns(const QString &device) { QList list; QList list1; list << m_configShuttle.shuttle1; list << m_configShuttle.shuttle2; list << m_configShuttle.shuttle3; list << m_configShuttle.shuttle4; list << m_configShuttle.shuttle5; list << m_configShuttle.shuttle6; list << m_configShuttle.shuttle7; list << m_configShuttle.shuttle8; list << m_configShuttle.shuttle9; list << m_configShuttle.shuttle10; list << m_configShuttle.shuttle11; list << m_configShuttle.shuttle12; list << m_configShuttle.shuttle13; list << m_configShuttle.shuttle14; list << m_configShuttle.shuttle15; list1 << m_configShuttle.label_2; // #1 list1 << m_configShuttle.label_4; // #2 list1 << m_configShuttle.label_3; // #3 list1 << m_configShuttle.label_7; // #4 list1 << m_configShuttle.label_5; // #5 list1 << m_configShuttle.label_6; // #6 list1 << m_configShuttle.label_8; // #7 list1 << m_configShuttle.label_9; // #8 list1 << m_configShuttle.label_10; // #9 list1 << m_configShuttle.label_11; // #10 list1 << m_configShuttle.label_12; // #11 list1 << m_configShuttle.label_13; // #12 list1 << m_configShuttle.label_14; // #13 list1 << m_configShuttle.label_15; // #14 list1 << m_configShuttle.label_16; // #15 for (int i = 0; i < list.count(); ++i) { list[i]->hide(); list1[i]->hide(); } #ifdef USE_JOGSHUTTLE if (!m_configShuttle.kcfg_enableshuttle->isChecked()) { return; } int keysCount = JogShuttle::keysCount(device); for (int i = 0; i < keysCount; ++i) { m_shuttle_buttons.push_back(list[i]); list[i]->show(); list1[i]->show(); } // populate the buttons with the current configuration. The items are sorted // according to the user-selected language, so they do not appear in random order. QMap mappable_actions(m_mappable_actions); QList action_names = mappable_actions.keys(); QList::Iterator iter = action_names.begin(); // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; while (iter != action_names.end()) { // qCDebug(KDENLIVE_LOG) << *iter; ++iter; } // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; qSort(action_names); iter = action_names.begin(); while (iter != action_names.end()) { // qCDebug(KDENLIVE_LOG) << *iter; ++iter; } // qCDebug(KDENLIVE_LOG) << "::::::::::::::::"; // Here we need to compute the action_id -> index-in-action_names. We iterate over the // action_names, as the sorting may depend on the user-language. QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons()); QMap action_pos; for (const QString &action_id : actions_map) { // This loop find out at what index is the string that would map to the action_id. for (int i = 0; i < action_names.size(); ++i) { if (mappable_actions[action_names.at(i)] == action_id) { action_pos[action_id] = i; break; } } } int i = 0; for (KComboBox *button : m_shuttle_buttons) { button->addItems(action_names); connect(button, SIGNAL(activated(int)), this, SLOT(slotShuttleModified())); ++i; if (i < actions_map.size()) { button->setCurrentIndex(action_pos[actions_map[i]]); } } #endif } -KdenliveSettingsDialog::~KdenliveSettingsDialog() -{ -} +KdenliveSettingsDialog::~KdenliveSettingsDialog() {} void KdenliveSettingsDialog::slotUpdateGrabRegionStatus() { m_configCapture.region_group->setHidden(m_configCapture.kcfg_grab_capture_type->currentIndex() != 1); } void KdenliveSettingsDialog::slotEnableCaptureFolder() { m_configEnv.capturefolderurl->setEnabled(!m_configEnv.kcfg_capturetoprojectfolder->isChecked()); } void KdenliveSettingsDialog::slotEnableLibraryFolder() { m_configEnv.libraryfolderurl->setEnabled(!m_configEnv.kcfg_librarytodefaultfolder->isChecked()); } void KdenliveSettingsDialog::initDevices() { // Fill audio drivers m_configSdl.kcfg_audio_driver->addItem(i18n("Automatic"), QString()); #ifndef Q_WS_MAC m_configSdl.kcfg_audio_driver->addItem(i18n("OSS"), "dsp"); m_configSdl.kcfg_audio_driver->addItem(i18n("ALSA"), "alsa"); m_configSdl.kcfg_audio_driver->addItem(i18n("PulseAudio"), "pulse"); m_configSdl.kcfg_audio_driver->addItem(i18n("OSS with DMA access"), "dma"); m_configSdl.kcfg_audio_driver->addItem(i18n("Esound daemon"), "esd"); m_configSdl.kcfg_audio_driver->addItem(i18n("ARTS daemon"), "artsc"); #endif if (!KdenliveSettings::audiodrivername().isEmpty()) for (int i = 1; i < m_configSdl.kcfg_audio_driver->count(); ++i) { if (m_configSdl.kcfg_audio_driver->itemData(i).toString() == KdenliveSettings::audiodrivername()) { m_configSdl.kcfg_audio_driver->setCurrentIndex(i); KdenliveSettings::setAudio_driver((uint)i); } } // Fill the list of audio playback / recording devices m_configSdl.kcfg_audio_device->addItem(i18n("Default"), QString()); m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("Default"), "default"); if (!QStandardPaths::findExecutable(QStringLiteral("aplay")).isEmpty()) { m_readProcess.setOutputChannelMode(KProcess::OnlyStdoutChannel); m_readProcess.setProgram(QStringLiteral("aplay"), QStringList() << QStringLiteral("-l")); connect(&m_readProcess, &KProcess::readyReadStandardOutput, this, &KdenliveSettingsDialog::slotReadAudioDevices); m_readProcess.execute(5000); } else { // If aplay is not installed on the system, parse the /proc/asound/pcm file QFile file(QStringLiteral("/proc/asound/pcm")); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); QString line = stream.readLine(); QString deviceId; while (!line.isNull()) { if (line.contains(QStringLiteral("playback"))) { deviceId = line.section(QLatin1Char(':'), 0, 0); - m_configSdl.kcfg_audio_device->addItem(line.section(QLatin1Char(':'), 1, 1), - QStringLiteral("plughw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); + m_configSdl.kcfg_audio_device->addItem(line.section(QLatin1Char(':'), 1, 1), QStringLiteral("plughw:%1,%2") + .arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()) + .arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); } if (line.contains(QStringLiteral("capture"))) { deviceId = line.section(QLatin1Char(':'), 0, 0); - m_configCapture.kcfg_v4l_alsadevice->addItem(line.section(QLatin1Char(':'), 1, 1).simplified(), - QStringLiteral("hw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); + m_configCapture.kcfg_v4l_alsadevice->addItem( + line.section(QLatin1Char(':'), 1, 1).simplified(), + QStringLiteral("hw:%1,%2").arg(deviceId.section(QLatin1Char('-'), 0, 0).toInt()).arg(deviceId.section(QLatin1Char('-'), 1, 1).toInt())); } line = stream.readLine(); } file.close(); } else { qCDebug(KDENLIVE_LOG) << " / / / /CANNOT READ PCM"; } } // Add pulseaudio capture option m_configCapture.kcfg_v4l_alsadevice->addItem(i18n("PulseAudio"), "pulse"); if (!KdenliveSettings::audiodevicename().isEmpty()) { // Select correct alsa device int ix = m_configSdl.kcfg_audio_device->findData(KdenliveSettings::audiodevicename()); m_configSdl.kcfg_audio_device->setCurrentIndex(ix); KdenliveSettings::setAudio_device(ix); } if (!KdenliveSettings::v4l_alsadevicename().isEmpty()) { // Select correct alsa device int ix = m_configCapture.kcfg_v4l_alsadevice->findData(KdenliveSettings::v4l_alsadevicename()); m_configCapture.kcfg_v4l_alsadevice->setCurrentIndex(ix); KdenliveSettings::setV4l_alsadevice(ix); } m_configSdl.kcfg_audio_backend->addItem(i18n("SDL"), KdenliveSettings::sdlAudioBackend()); m_configSdl.kcfg_audio_backend->addItem(i18n("RtAudio"), "rtaudio"); if (!KdenliveSettings::audiobackend().isEmpty()) { int ix = m_configSdl.kcfg_audio_backend->findData(KdenliveSettings::audiobackend()); m_configSdl.kcfg_audio_backend->setCurrentIndex(ix); KdenliveSettings::setAudio_backend(ix); } m_configSdl.group_sdl->setEnabled(KdenliveSettings::audiobackend().startsWith(QLatin1String("sdl_audio"))); loadCurrentV4lProfileInfo(); } void KdenliveSettingsDialog::slotReadAudioDevices() { QString result = QString(m_readProcess.readAllStandardOutput()); // qCDebug(KDENLIVE_LOG) << "// / / / / / READING APLAY: "; // qCDebug(KDENLIVE_LOG) << result; const QStringList lines = result.split(QLatin1Char('\n')); for (const QString &devicestr : lines) { ////qCDebug(KDENLIVE_LOG) << "// READING LINE: " << data; if (!devicestr.startsWith(QLatin1Char(' ')) && devicestr.count(QLatin1Char(':')) > 1) { QString card = devicestr.section(QLatin1Char(':'), 0, 0).section(QLatin1Char(' '), -1); QString device = devicestr.section(QLatin1Char(':'), 1, 1).section(QLatin1Char(' '), -1); m_configSdl.kcfg_audio_device->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("plughw:%1,%2").arg(card).arg(device)); - m_configCapture.kcfg_v4l_alsadevice->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), QStringLiteral("hw:%1,%2").arg(card).arg(device)); + m_configCapture.kcfg_v4l_alsadevice->addItem(devicestr.section(QLatin1Char(':'), -1).simplified(), + QStringLiteral("hw:%1,%2").arg(card).arg(device)); } } } void KdenliveSettingsDialog::showPage(int page, int option) { switch (page) { case 1: setCurrentPage(m_page1); break; case 2: setCurrentPage(m_page2); break; case 3: setCurrentPage(m_page3); break; case 4: setCurrentPage(m_page4); m_configCapture.tabWidget->setCurrentIndex(option); break; case 5: setCurrentPage(m_page5); break; case 6: setCurrentPage(m_page6); break; case 7: setCurrentPage(m_page7); break; default: setCurrentPage(m_page1); } } void KdenliveSettingsDialog::slotEditAudioApplication() { KService::Ptr service; QPointer dlg = new KOpenWithDialog(QList(), i18n("Select default audio editor"), m_configEnv.kcfg_defaultaudioapp->text(), this); if (dlg->exec() == QDialog::Accepted) { service = dlg->service(); m_configEnv.kcfg_defaultaudioapp->setText(KRun::binaryName(service->exec(), false)); } delete dlg; } void KdenliveSettingsDialog::slotEditImageApplication() { QPointer dlg = new KOpenWithDialog(QList(), i18n("Select default image editor"), m_configEnv.kcfg_defaultimageapp->text(), this); if (dlg->exec() == QDialog::Accepted) { KService::Ptr service = dlg->service(); m_configEnv.kcfg_defaultimageapp->setText(KRun::binaryName(service->exec(), false)); } delete dlg; } void KdenliveSettingsDialog::slotCheckShuttle(int state) { #ifdef USE_JOGSHUTTLE m_configShuttle.config_group->setEnabled(state != 0); m_configShuttle.shuttledevicelist->clear(); QStringList devNames = KdenliveSettings::shuttledevicenames(); QStringList devPaths = KdenliveSettings::shuttledevicepaths(); if (devNames.count() != devPaths.count()) { return; } for (int i = 0; i < devNames.count(); ++i) { m_configShuttle.shuttledevicelist->addItem(devNames.at(i), devPaths.at(i)); } if (state != 0) { setupJogshuttleBtns(m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString()); } #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::slotUpdateShuttleDevice(int ix) { #ifdef USE_JOGSHUTTLE QString device = m_configShuttle.shuttledevicelist->itemData(ix).toString(); // KdenliveSettings::setShuttledevice(device); setupJogshuttleBtns(device); m_configShuttle.kcfg_shuttledevice->setText(device); #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::updateWidgets() { // Revert widgets to last saved state (for example when user pressed "Cancel") // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG Revert called"; #ifdef USE_JOGSHUTTLE // revert jog shuttle device if (m_configShuttle.shuttledevicelist->count() > 0) { for (int i = 0; i < m_configShuttle.shuttledevicelist->count(); ++i) { if (m_configShuttle.shuttledevicelist->itemData(i) == KdenliveSettings::shuttledevice()) { m_configShuttle.shuttledevicelist->setCurrentIndex(i); break; } } } // Revert jog shuttle buttons QList action_names = m_mappable_actions.keys(); qSort(action_names); QStringList actions_map = JogShuttleConfig::actionMap(KdenliveSettings::shuttlebuttons()); QMap action_pos; for (const QString &action_id : actions_map) { // This loop find out at what index is the string that would map to the action_id. for (int i = 0; i < action_names.size(); ++i) { if (m_mappable_actions[action_names[i]] == action_id) { action_pos[action_id] = i; break; } } } int i = 0; for (KComboBox *button : m_shuttle_buttons) { ++i; if (i < actions_map.size()) { button->setCurrentIndex(action_pos[actions_map[i]]); } } #endif /* USE_JOGSHUTTLE */ } void KdenliveSettingsDialog::accept() { if (m_pw->selectedProfile().isEmpty()) { KMessageBox::error(this, i18n("Please select a video profile")); return; } KConfigDialog::accept(); } void KdenliveSettingsDialog::updateSettings() { // Save changes to settings (for example when user pressed "Apply" or "Ok") // //qCDebug(KDENLIVE_LOG) << "// // // KCONFIG UPDATE called"; if (m_pw->selectedProfile().isEmpty()) { KMessageBox::error(this, i18n("Please select a video profile")); return; } KdenliveSettings::setDefault_profile(m_pw->selectedProfile()); bool resetProfile = false; bool updateCapturePath = false; bool updateLibrary = false; /*if (m_configShuttle.shuttledevicelist->count() > 0) { QString device = m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString(); if (device != KdenliveSettings::shuttledevice()) KdenliveSettings::setShuttledevice(device); }*/ // Capture default folder if (m_configEnv.kcfg_capturetoprojectfolder->isChecked() != KdenliveSettings::capturetoprojectfolder()) { KdenliveSettings::setCapturetoprojectfolder(m_configEnv.kcfg_capturetoprojectfolder->isChecked()); updateCapturePath = true; } if (m_configProject.projecturl->url().toLocalFile() != KdenliveSettings::defaultprojectfolder()) { KdenliveSettings::setDefaultprojectfolder(m_configProject.projecturl->url().toLocalFile()); } if (m_configEnv.capturefolderurl->url().toLocalFile() != KdenliveSettings::capturefolder()) { KdenliveSettings::setCapturefolder(m_configEnv.capturefolderurl->url().toLocalFile()); updateCapturePath = true; } // Library default folder if (m_configEnv.kcfg_librarytodefaultfolder->isChecked() != KdenliveSettings::librarytodefaultfolder()) { KdenliveSettings::setLibrarytodefaultfolder(m_configEnv.kcfg_librarytodefaultfolder->isChecked()); updateLibrary = true; } if (m_configEnv.libraryfolderurl->url().toLocalFile() != KdenliveSettings::libraryfolder()) { KdenliveSettings::setLibraryfolder(m_configEnv.libraryfolderurl->url().toLocalFile()); if (!KdenliveSettings::librarytodefaultfolder()) { updateLibrary = true; } } if (m_configCapture.kcfg_dvgrabfilename->text() != KdenliveSettings::dvgrabfilename()) { KdenliveSettings::setDvgrabfilename(m_configCapture.kcfg_dvgrabfilename->text()); updateCapturePath = true; } if (m_configCapture.kcfg_firewireformat->currentIndex() != KdenliveSettings::firewireformat()) { KdenliveSettings::setFirewireformat(m_configCapture.kcfg_firewireformat->currentIndex()); updateCapturePath = true; } if (m_configCapture.kcfg_v4l_format->currentIndex() != (int)KdenliveSettings::v4l_format()) { saveCurrentV4lProfile(); KdenliveSettings::setV4l_format(0); } // Check if screengrab is fullscreen if (m_configCapture.kcfg_grab_capture_type->currentIndex() != KdenliveSettings::grab_capture_type()) { KdenliveSettings::setGrab_capture_type(m_configCapture.kcfg_grab_capture_type->currentIndex()); emit updateFullScreenGrab(); } // Check encoding profiles // FFmpeg QString profilestr = m_configCapture.kcfg_v4l_profile->itemData(m_configCapture.kcfg_v4l_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::v4l_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::v4l_extension())) { KdenliveSettings::setV4l_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setV4l_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // screengrab profilestr = m_configCapture.kcfg_grab_profile->itemData(m_configCapture.kcfg_grab_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::grab_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::grab_extension())) { KdenliveSettings::setGrab_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setGrab_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // decklink profilestr = m_configCapture.kcfg_decklink_profile->itemData(m_configCapture.kcfg_decklink_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::decklink_parameters() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::decklink_extension())) { KdenliveSettings::setDecklink_parameters(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setDecklink_extension(profilestr.section(QLatin1Char(';'), 1, 1)); } // proxies profilestr = m_configProject.kcfg_proxy_profile->itemData(m_configProject.kcfg_proxy_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::proxyparams() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::proxyextension())) { KdenliveSettings::setProxyparams(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setProxyextension(profilestr.section(QLatin1Char(';'), 1, 1)); } // timeline preview profilestr = m_configProject.kcfg_preview_profile->itemData(m_configProject.kcfg_preview_profile->currentIndex()).toString(); if (!profilestr.isEmpty() && (profilestr.section(QLatin1Char(';'), 0, 0) != KdenliveSettings::previewparams() || profilestr.section(QLatin1Char(';'), 1, 1) != KdenliveSettings::previewextension())) { KdenliveSettings::setPreviewparams(profilestr.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setPreviewextension(profilestr.section(QLatin1Char(';'), 1, 1)); } if (updateCapturePath) { emit updateCaptureFolder(); } if (updateLibrary) { emit updateLibraryFolder(); } QString value = m_configCapture.kcfg_v4l_alsadevice->itemData(m_configCapture.kcfg_v4l_alsadevice->currentIndex()).toString(); if (value != KdenliveSettings::v4l_alsadevicename()) { KdenliveSettings::setV4l_alsadevicename(value); } if (m_configSdl.kcfg_external_display->isChecked() != KdenliveSettings::external_display()) { KdenliveSettings::setExternal_display(m_configSdl.kcfg_external_display->isChecked()); resetProfile = true; } value = m_configSdl.kcfg_audio_driver->itemData(m_configSdl.kcfg_audio_driver->currentIndex()).toString(); if (value != KdenliveSettings::audiodrivername()) { KdenliveSettings::setAudiodrivername(value); resetProfile = true; } if (value == QLatin1String("alsa")) { // Audio device setting is only valid for alsa driver value = m_configSdl.kcfg_audio_device->itemData(m_configSdl.kcfg_audio_device->currentIndex()).toString(); if (value != KdenliveSettings::audiodevicename()) { KdenliveSettings::setAudiodevicename(value); resetProfile = true; } } else if (!KdenliveSettings::audiodevicename().isEmpty()) { KdenliveSettings::setAudiodevicename(QString()); resetProfile = true; } value = m_configSdl.kcfg_audio_backend->itemData(m_configSdl.kcfg_audio_backend->currentIndex()).toString(); if (value != KdenliveSettings::audiobackend()) { KdenliveSettings::setAudiobackend(value); resetProfile = true; } if (m_configSdl.kcfg_window_background->color() != KdenliveSettings::window_background()) { KdenliveSettings::setWindow_background(m_configSdl.kcfg_window_background->color()); resetProfile = true; } if (m_configSdl.kcfg_volume->value() != KdenliveSettings::volume()) { KdenliveSettings::setVolume(m_configSdl.kcfg_volume->value()); resetProfile = true; } if (m_configMisc.kcfg_tabposition->currentIndex() != KdenliveSettings::tabposition()) { KdenliveSettings::setTabposition(m_configMisc.kcfg_tabposition->currentIndex()); } if (m_configTimeline.kcfg_displayallchannels->isChecked() != KdenliveSettings::displayallchannels()) { KdenliveSettings::setDisplayallchannels(m_configTimeline.kcfg_displayallchannels->isChecked()); emit audioThumbFormatChanged(); } if (m_modified) { // The transcoding profiles were modified, save. m_modified = false; saveTranscodeProfiles(); } #ifdef USE_JOGSHUTTLE m_shuttleModified = false; QStringList actions; actions << QStringLiteral("monitor_pause"); // the Job rest position action. for (KComboBox *button : m_shuttle_buttons) { actions << m_mappable_actions[button->currentText()]; } QString maps = JogShuttleConfig::actionMap(actions); // fprintf(stderr, "Shuttle config: %s\n", JogShuttleConfig::actionMap(actions).toLatin1().constData()); if (KdenliveSettings::shuttlebuttons() != maps) { KdenliveSettings::setShuttlebuttons(maps); } #endif bool restart = false; if (m_configSdl.kcfg_gpu_accel->isChecked() != KdenliveSettings::gpu_accel()) { // GPU setting was changed, we need to restart Kdenlive or everything will be corrupted if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive must be restarted to change this setting")) == KMessageBox::Continue) { restart = true; } else { m_configSdl.kcfg_gpu_accel->setChecked(KdenliveSettings::gpu_accel()); } } - + if (m_configTimeline.kcfg_trackheight->value() != KdenliveSettings::trackheight()) { KdenliveSettings::setTrackheight(m_configTimeline.kcfg_trackheight->value()); emit resetView(); } // Mimes if (m_configEnv.kcfg_addedExtensions->text() != KdenliveSettings::addedExtensions()) { // Update list KdenliveSettings::setAddedExtensions(m_configEnv.kcfg_addedExtensions->text()); QStringList mimes = ClipCreationDialog::getExtensions(); qSort(mimes); m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' '))); } KConfigDialog::settingsChangedSlot(); // KConfigDialog::updateSettings(); if (resetProfile) { emit doResetProfile(); } if (restart) { emit restartKdenlive(); } emit checkTabPosition(); } void KdenliveSettingsDialog::slotCheckAlsaDriver() { QString value = m_configSdl.kcfg_audio_driver->itemData(m_configSdl.kcfg_audio_driver->currentIndex()).toString(); m_configSdl.kcfg_audio_device->setEnabled(value == QLatin1String("alsa")); } void KdenliveSettingsDialog::slotCheckAudioBackend() { QString value = m_configSdl.kcfg_audio_backend->itemData(m_configSdl.kcfg_audio_backend->currentIndex()).toString(); m_configSdl.group_sdl->setEnabled(value.startsWith(QLatin1String("sdl_audio"))); } void KdenliveSettingsDialog::loadTranscodeProfiles() { KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries m_configTranscode.profiles_list->blockSignals(true); m_configTranscode.profiles_list->clear(); QMap profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key()); QString profilestr = i.value(); if (profilestr.contains(QLatin1Char(';'))) { item->setToolTip(profilestr.section(QLatin1Char(';'), 1, 1)); } item->setData(Qt::UserRole, profilestr); m_configTranscode.profiles_list->addItem(item); // item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable); } m_configTranscode.profiles_list->blockSignals(false); m_configTranscode.profiles_list->setCurrentRow(0); } void KdenliveSettingsDialog::saveTranscodeProfiles() { QString transcodeFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/kdenlivetranscodingrc"); KSharedConfigPtr config = KSharedConfig::openConfig(transcodeFile); KConfigGroup transConfig(config, "Transcoding"); // read the entries transConfig.deleteGroup(); int max = m_configTranscode.profiles_list->count(); for (int i = 0; i < max; ++i) { QListWidgetItem *item = m_configTranscode.profiles_list->item(i); transConfig.writeEntry(item->text(), item->data(Qt::UserRole).toString()); } config->sync(); } void KdenliveSettingsDialog::slotAddTranscode() { if (!m_configTranscode.profiles_list->findItems(m_configTranscode.profile_name->text(), Qt::MatchExactly).isEmpty()) { KMessageBox::sorry(this, i18n("A profile with that name already exists")); return; } QListWidgetItem *item = new QListWidgetItem(m_configTranscode.profile_name->text()); QString profilestr = m_configTranscode.profile_parameters->toPlainText(); profilestr.append(" %1." + m_configTranscode.profile_extension->text()); profilestr.append(';'); if (!m_configTranscode.profile_description->text().isEmpty()) { profilestr.append(m_configTranscode.profile_description->text()); } if (m_configTranscode.profile_audioonly->isChecked()) { profilestr.append(";audio"); } item->setData(Qt::UserRole, profilestr); m_configTranscode.profiles_list->addItem(item); m_configTranscode.profiles_list->setCurrentItem(item); slotDialogModified(); } void KdenliveSettingsDialog::slotUpdateTranscodingProfile() { QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (!item) { return; } m_configTranscode.button_update->setEnabled(false); item->setText(m_configTranscode.profile_name->text()); QString profilestr = m_configTranscode.profile_parameters->toPlainText(); profilestr.append(" %1." + m_configTranscode.profile_extension->text()); profilestr.append(';'); if (!m_configTranscode.profile_description->text().isEmpty()) { profilestr.append(m_configTranscode.profile_description->text()); } if (m_configTranscode.profile_audioonly->isChecked()) { profilestr.append(QStringLiteral(";audio")); } item->setData(Qt::UserRole, profilestr); slotDialogModified(); } void KdenliveSettingsDialog::slotDeleteTranscode() { QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (item == nullptr) { return; } delete item; slotDialogModified(); } void KdenliveSettingsDialog::slotEnableTranscodeUpdate() { if (!m_configTranscode.profile_box->isEnabled()) { return; } bool allow = true; if (m_configTranscode.profile_name->text().isEmpty() || m_configTranscode.profile_extension->text().isEmpty()) { allow = false; } m_configTranscode.button_update->setEnabled(allow); } void KdenliveSettingsDialog::slotSetTranscodeProfile() { m_configTranscode.profile_box->setEnabled(false); m_configTranscode.button_update->setEnabled(false); m_configTranscode.profile_name->clear(); m_configTranscode.profile_description->clear(); m_configTranscode.profile_extension->clear(); m_configTranscode.profile_parameters->clear(); m_configTranscode.profile_audioonly->setChecked(false); QListWidgetItem *item = m_configTranscode.profiles_list->currentItem(); if (!item) { return; } m_configTranscode.profile_name->setText(item->text()); QString profilestr = item->data(Qt::UserRole).toString(); if (profilestr.contains(QLatin1Char(';'))) { m_configTranscode.profile_description->setText(profilestr.section(QLatin1Char(';'), 1, 1)); if (profilestr.section(QLatin1Char(';'), 2, 2) == QLatin1String("audio")) { m_configTranscode.profile_audioonly->setChecked(true); } profilestr = profilestr.section(QLatin1Char(';'), 0, 0).simplified(); } m_configTranscode.profile_extension->setText(profilestr.section(QLatin1Char('.'), -1)); m_configTranscode.profile_parameters->setPlainText(profilestr.section(QLatin1Char(' '), 0, -2)); m_configTranscode.profile_box->setEnabled(true); } void KdenliveSettingsDialog::slotShuttleModified() { #ifdef USE_JOGSHUTTLE QStringList actions; actions << QStringLiteral("monitor_pause"); // the Job rest position action. for (KComboBox *button : m_shuttle_buttons) { actions << m_mappable_actions[button->currentText()]; } QString maps = JogShuttleConfig::actionMap(actions); m_shuttleModified = KdenliveSettings::shuttlebuttons() != maps; #endif KConfigDialog::updateButtons(); } void KdenliveSettingsDialog::slotDialogModified() { m_modified = true; KConfigDialog::updateButtons(); } // virtual bool KdenliveSettingsDialog::hasChanged() { if (m_modified || m_shuttleModified) { return true; } return KConfigDialog::hasChanged(); } void KdenliveSettingsDialog::slotUpdatev4lDevice() { QString device = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex()).toString(); if (!device.isEmpty()) { m_configCapture.kcfg_video4vdevice->setText(device); } QString info = m_configCapture.kcfg_detectedv4ldevices->itemData(m_configCapture.kcfg_detectedv4ldevices->currentIndex(), Qt::UserRole + 1).toString(); m_configCapture.kcfg_v4l_format->blockSignals(true); m_configCapture.kcfg_v4l_format->clear(); QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); if (QFile::exists(vl4ProfilePath)) { m_configCapture.kcfg_v4l_format->addItem(i18n("Current settings")); } QStringList pixelformats = info.split('>', QString::SkipEmptyParts); QString itemSize; QString pixelFormat; QStringList itemRates; for (int i = 0; i < pixelformats.count(); ++i) { QString format = pixelformats.at(i).section(QLatin1Char(':'), 0, 0); QStringList sizes = pixelformats.at(i).split(':', QString::SkipEmptyParts); pixelFormat = sizes.takeFirst(); for (int j = 0; j < sizes.count(); ++j) { itemSize = sizes.at(j).section(QLatin1Char('='), 0, 0); itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), QString::SkipEmptyParts); for (int k = 0; k < itemRates.count(); ++k) { m_configCapture.kcfg_v4l_format->addItem( QLatin1Char('[') + format + QStringLiteral("] ") + itemSize + QStringLiteral(" (") + itemRates.at(k) + QLatin1Char(')'), QStringList() << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) << itemRates.at(k).section(QLatin1Char('/'), 0, 0) << itemRates.at(k).section(QLatin1Char('/'), 1, 1)); } } } m_configCapture.kcfg_v4l_format->blockSignals(false); slotUpdatev4lCaptureProfile(); } void KdenliveSettingsDialog::slotUpdatev4lCaptureProfile() { QStringList info = m_configCapture.kcfg_v4l_format->itemData(m_configCapture.kcfg_v4l_format->currentIndex(), Qt::UserRole).toStringList(); if (info.isEmpty()) { // No auto info, display the current ones loadCurrentV4lProfileInfo(); return; } m_configCapture.p_size->setText(info.at(1) + QLatin1Char('x') + info.at(2)); m_configCapture.p_fps->setText(info.at(3) + QLatin1Char('/') + info.at(4)); m_configCapture.p_aspect->setText(QStringLiteral("1/1")); m_configCapture.p_display->setText(info.at(1) + QLatin1Char('/') + info.at(2)); m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(601)); m_configCapture.p_progressive->setText(i18n("Progressive")); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists() || !dir.exists(QStringLiteral("video4linux"))) { saveCurrentV4lProfile(); } } void KdenliveSettingsDialog::loadCurrentV4lProfileInfo() { QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } if (!ProfileRepository::get()->profileExists(dir.absoluteFilePath(QStringLiteral("video4linux")))) { // No default formats found, build one std::unique_ptr prof(new ProfileParam(pCore->getCurrentProfile().get())); prof->m_width = 320; prof->m_height = 200; prof->m_frame_rate_num = 15; prof->m_frame_rate_den = 1; prof->m_display_aspect_num = 4; prof->m_display_aspect_den = 3; prof->m_sample_aspect_num = 1; prof->m_sample_aspect_den = 1; prof->m_progressive = 1; prof->m_colorspace = 601; ProfileRepository::get()->saveProfile(prof.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); } auto &prof = ProfileRepository::get()->getProfile(dir.absoluteFilePath(QStringLiteral("video4linux"))); m_configCapture.p_size->setText(QString::number(prof->width()) + QLatin1Char('x') + QString::number(prof->height())); m_configCapture.p_fps->setText(QString::number(prof->frame_rate_num()) + QLatin1Char('/') + QString::number(prof->frame_rate_den())); m_configCapture.p_aspect->setText(QString::number(prof->sample_aspect_num()) + QLatin1Char('/') + QString::number(prof->sample_aspect_den())); m_configCapture.p_display->setText(QString::number(prof->display_aspect_num()) + QLatin1Char('/') + QString::number(prof->display_aspect_den())); m_configCapture.p_colorspace->setText(ProfileRepository::getColorspaceDescription(prof->colorspace())); if (prof->progressive()) { m_configCapture.p_progressive->setText(i18n("Progressive")); } } void KdenliveSettingsDialog::saveCurrentV4lProfile() { std::unique_ptr profile(new ProfileParam(pCore->getCurrentProfile().get())); profile->m_description = QStringLiteral("Video4Linux capture"); profile->m_colorspace = ProfileRepository::getColorspaceFromDescription(m_configCapture.p_colorspace->text()); profile->m_width = m_configCapture.p_size->text().section('x', 0, 0).toInt(); profile->m_height = m_configCapture.p_size->text().section('x', 1, 1).toInt(); profile->m_sample_aspect_num = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_sample_aspect_den = m_configCapture.p_aspect->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_display_aspect_num = m_configCapture.p_display->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_display_aspect_den = m_configCapture.p_display->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_frame_rate_num = m_configCapture.p_fps->text().section(QLatin1Char('/'), 0, 0).toInt(); profile->m_frame_rate_den = m_configCapture.p_fps->text().section(QLatin1Char('/'), 1, 1).toInt(); profile->m_progressive = m_configCapture.p_progressive->text() == i18n("Progressive"); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } ProfileRepository::get()->saveProfile(profile.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); } void KdenliveSettingsDialog::slotManageEncodingProfile() { QAction *act = qobject_cast(sender()); int type = 0; if (act) { type = act->data().toInt(); } QPointer dia = new EncodingProfilesDialog(type); dia->exec(); delete dia; loadEncodingProfiles(); } void KdenliveSettingsDialog::loadEncodingProfiles() { KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); // Load v4l profiles m_configCapture.kcfg_v4l_profile->blockSignals(true); QString currentItem = m_configCapture.kcfg_v4l_profile->currentText(); m_configCapture.kcfg_v4l_profile->clear(); KConfigGroup group(&conf, "video4linux"); QMap values = group.entryMap(); QMapIterator i(values); while (i.hasNext()) { i.next(); if (!i.key().isEmpty()) { m_configCapture.kcfg_v4l_profile->addItem(i.key(), i.value()); } } m_configCapture.kcfg_v4l_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_v4l_profile->setCurrentIndex(m_configCapture.kcfg_v4l_profile->findText(currentItem)); } // Load Screen Grab profiles m_configCapture.kcfg_grab_profile->blockSignals(true); currentItem = m_configCapture.kcfg_grab_profile->currentText(); m_configCapture.kcfg_grab_profile->clear(); KConfigGroup group2(&conf, "screengrab"); values = group2.entryMap(); QMapIterator j(values); while (j.hasNext()) { j.next(); if (!j.key().isEmpty()) { m_configCapture.kcfg_grab_profile->addItem(j.key(), j.value()); } } m_configCapture.kcfg_grab_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_grab_profile->setCurrentIndex(m_configCapture.kcfg_grab_profile->findText(currentItem)); } // Load Decklink profiles m_configCapture.kcfg_decklink_profile->blockSignals(true); currentItem = m_configCapture.kcfg_decklink_profile->currentText(); m_configCapture.kcfg_decklink_profile->clear(); KConfigGroup group3(&conf, "decklink"); values = group3.entryMap(); QMapIterator k(values); while (k.hasNext()) { k.next(); if (!k.key().isEmpty()) { m_configCapture.kcfg_decklink_profile->addItem(k.key(), k.value()); } } m_configCapture.kcfg_decklink_profile->blockSignals(false); if (!currentItem.isEmpty()) { m_configCapture.kcfg_decklink_profile->setCurrentIndex(m_configCapture.kcfg_decklink_profile->findText(currentItem)); } // Load Timeline Preview profiles m_configProject.kcfg_preview_profile->blockSignals(true); currentItem = m_configProject.kcfg_preview_profile->currentText(); m_configProject.kcfg_preview_profile->clear(); KConfigGroup group5(&conf, "timelinepreview"); values = group5.entryMap(); m_configProject.kcfg_preview_profile->addItem(i18n("Automatic")); QMapIterator l(values); while (l.hasNext()) { l.next(); if (!l.key().isEmpty()) { m_configProject.kcfg_preview_profile->addItem(l.key(), l.value()); } } if (!currentItem.isEmpty()) { m_configProject.kcfg_preview_profile->setCurrentIndex(m_configProject.kcfg_preview_profile->findText(currentItem)); } m_configProject.kcfg_preview_profile->blockSignals(false); QString profilestr = m_configProject.kcfg_preview_profile->itemData(m_configProject.kcfg_preview_profile->currentIndex()).toString(); if (profilestr.isEmpty()) { m_configProject.previewparams->clear(); } else { m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } // Load Proxy profiles m_configProject.kcfg_proxy_profile->blockSignals(true); currentItem = m_configProject.kcfg_proxy_profile->currentText(); m_configProject.kcfg_proxy_profile->clear(); KConfigGroup group4(&conf, "proxy"); values = group4.entryMap(); QMapIterator m(values); while (m.hasNext()) { m.next(); if (!m.key().isEmpty()) { m_configProject.kcfg_proxy_profile->addItem(m.key(), m.value()); } } if (!currentItem.isEmpty()) { m_configProject.kcfg_proxy_profile->setCurrentIndex(m_configProject.kcfg_proxy_profile->findText(currentItem)); } m_configProject.kcfg_proxy_profile->blockSignals(false); profilestr = m_configProject.kcfg_proxy_profile->itemData(m_configProject.kcfg_proxy_profile->currentIndex()).toString(); if (profilestr.isEmpty()) { m_configProject.proxyparams->clear(); } else { m_configProject.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } } void KdenliveSettingsDialog::slotUpdateDecklinkProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::decklink_profile(); } else { ix = m_configCapture.kcfg_decklink_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_decklink_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.decklink_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateV4lProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::v4l_profile(); } else { ix = m_configCapture.kcfg_v4l_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_v4l_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.v4l_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateGrabProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::grab_profile(); } else { ix = m_configCapture.kcfg_grab_profile->currentIndex(); } QString profilestr = m_configCapture.kcfg_grab_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configCapture.grab_parameters->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); // } void KdenliveSettingsDialog::slotUpdateProxyProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::proxy_profile(); } else { ix = m_configProject.kcfg_proxy_profile->currentIndex(); } QString profilestr = m_configProject.kcfg_proxy_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configProject.proxyparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } void KdenliveSettingsDialog::slotUpdatePreviewProfile(int ix) { if (ix == -1) { ix = KdenliveSettings::preview_profile(); } else { ix = m_configProject.kcfg_preview_profile->currentIndex(); } QString profilestr = m_configProject.kcfg_preview_profile->itemData(ix).toString(); if (profilestr.isEmpty()) { return; } m_configProject.previewparams->setPlainText(profilestr.section(QLatin1Char(';'), 0, 0)); } void KdenliveSettingsDialog::slotEditVideo4LinuxProfile() { QString vl4ProfilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); QPointer w = new ProfilesDialog(vl4ProfilePath, true); if (w->exec() == QDialog::Accepted) { // save and update profile loadCurrentV4lProfileInfo(); } delete w; } void KdenliveSettingsDialog::slotReloadBlackMagic() { Render::getBlackMagicDeviceList(m_configCapture.kcfg_decklink_capturedevice, true); if (!Render::getBlackMagicOutputDeviceList(m_configSdl.kcfg_blackmagic_output_device, true)) { // No blackmagic card found m_configSdl.kcfg_external_display->setEnabled(false); } m_configSdl.kcfg_external_display->setEnabled(KdenliveSettings::decklink_device_found()); } void KdenliveSettingsDialog::checkProfile() { m_pw->loadProfile(KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile()); } void KdenliveSettingsDialog::slotReloadShuttleDevices() { #ifdef USE_JOGSHUTTLE QString devDirStr = QStringLiteral("/dev/input/by-id"); QDir devDir(devDirStr); if (!devDir.exists()) { devDirStr = QStringLiteral("/dev/input"); } QStringList devNamesList; QStringList devPathList; m_configShuttle.shuttledevicelist->clear(); DeviceMap devMap = JogShuttle::enumerateDevices(devDirStr); DeviceMapIter iter = devMap.begin(); while (iter != devMap.end()) { m_configShuttle.shuttledevicelist->addItem(iter.key(), iter.value()); devNamesList << iter.key(); devPathList << iter.value(); ++iter; } KdenliveSettings::setShuttledevicenames(devNamesList); KdenliveSettings::setShuttledevicepaths(devPathList); QTimer::singleShot(200, this, SLOT(slotUpdateShuttleDevice())); #endif // USE_JOGSHUTTLE } diff --git a/src/dialogs/markerdialog.cpp b/src/dialogs/markerdialog.cpp index ae8ff9df7..077d383c7 100644 --- a/src/dialogs/markerdialog.cpp +++ b/src/dialogs/markerdialog.cpp @@ -1,131 +1,131 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "markerdialog.h" #include "core.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "mltcontroller/clipcontroller.h" #include "kdenlive_debug.h" #include #include #include #include "klocalizedstring.h" MarkerDialog::MarkerDialog(ClipController *clip, const CommentedTime &t, const Timecode &tc, const QString &caption, QWidget *parent) : QDialog(parent) , m_clip(clip) , m_dar(4.0 / 3.0) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setupUi(this); setWindowTitle(caption); // Set up categories for (int i = 0; i < 5; ++i) { marker_type->insertItem(i, i18n("Category %1", i)); marker_type->setItemData(i, CommentedTime::markerColor(i), Qt::DecorationRole); } marker_type->setCurrentIndex(t.markerType()); m_in = new TimecodeDisplay(tc, this); inputLayout->addWidget(m_in); m_in->setValue(t.time()); m_previewTimer = new QTimer(this); if (m_clip != nullptr) { m_in->setRange(0, m_clip->getFramePlaytime()); m_previewTimer->setInterval(500); connect(m_previewTimer, &QTimer::timeout, this, &MarkerDialog::slotUpdateThumb); m_dar = pCore->getCurrentDar(); int width = Kdenlive::DefaultThumbHeight * m_dar; QPixmap p(width, Kdenlive::DefaultThumbHeight); p.fill(Qt::transparent); switch (m_clip->clipType()) { case ClipType::Video: case ClipType::AV: case ClipType::SlideShow: case ClipType::Playlist: m_previewTimer->start(); - connect(this, &MarkerDialog::updateThumb, m_previewTimer, static_cast(&QTimer::start)); + connect(this, &MarkerDialog::updateThumb, m_previewTimer, static_cast(&QTimer::start)); break; case ClipType::Image: case ClipType::Text: case ClipType::QText: case ClipType::Color: m_previewTimer->start(); // p = m_clip->pixmap(m_in->getValue(), width, height); break; // UNKNOWN, AUDIO, VIRTUAL: default: p.fill(Qt::black); } if (!p.isNull()) { clip_thumb->setFixedWidth(p.width()); clip_thumb->setFixedHeight(p.height()); clip_thumb->setPixmap(p); } connect(m_in, &TimecodeDisplay::timeCodeEditingFinished, this, &MarkerDialog::updateThumb); } else { clip_thumb->setHidden(true); label_category->setHidden(true); marker_type->setHidden(true); } marker_comment->setText(t.comment()); marker_comment->selectAll(); marker_comment->setFocus(); adjustSize(); } MarkerDialog::~MarkerDialog() { delete m_previewTimer; } void MarkerDialog::slotUpdateThumb() { m_previewTimer->stop(); int pos = m_in->getValue(); int width = Kdenlive::DefaultThumbHeight * m_dar; /*m_image = KThumb::getFrame(m_producer, pos, swidth, width, 100); const QPixmap p = QPixmap::fromImage(m_image);*/ const QPixmap p = m_clip->pixmap(pos, width, Kdenlive::DefaultThumbHeight); if (!p.isNull()) { clip_thumb->setPixmap(p); } else { qCDebug(KDENLIVE_LOG) << "!!!!!!!!!!! ERROR CREATING THUMB"; } } QImage MarkerDialog::markerImage() const { return clip_thumb->pixmap()->toImage(); } CommentedTime MarkerDialog::newMarker() { KdenliveSettings::setDefault_marker_type(marker_type->currentIndex()); return CommentedTime(m_in->gentime(), marker_comment->text(), marker_type->currentIndex()); } diff --git a/src/dialogs/markerdialog.h b/src/dialogs/markerdialog.h index c48b90423..852099515 100644 --- a/src/dialogs/markerdialog.h +++ b/src/dialogs/markerdialog.h @@ -1,64 +1,64 @@ /*************************************************************************** -* Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 * -***************************************************************************/ + * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 MARKERDIALOG_H #define MARKERDIALOG_H #include "ui_markerdialog_ui.h" #include "definitions.h" #include "timecode.h" #include "timecodedisplay.h" class ClipController; namespace Mlt { } /** * @class MarkerDialog * @brief A dialog for editing markers and guides. * @author Jean-Baptiste Mardelle */ class MarkerDialog : public QDialog, public Ui::MarkerDialog_UI { Q_OBJECT public: explicit MarkerDialog(ClipController *clip, const CommentedTime &t, const Timecode &tc, const QString &caption, QWidget *parent = nullptr); ~MarkerDialog(); CommentedTime newMarker(); QImage markerImage() const; private slots: void slotUpdateThumb(); private: ClipController *m_clip; TimecodeDisplay *m_in; double m_dar; QTimer *m_previewTimer; signals: void updateThumb(); }; #endif diff --git a/src/dialogs/renderwidget.cpp b/src/dialogs/renderwidget.cpp index 9bd7bed29..a1f1473a4 100644 --- a/src/dialogs/renderwidget.cpp +++ b/src/dialogs/renderwidget.cpp @@ -1,2807 +1,2808 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "renderwidget.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "timecode.h" #include "ui_saveprofile_ui.h" #include "utils/KoIconUtils.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif // Render profiles roles enum { GroupRole = Qt::UserRole, ExtensionRole, StandardRole, RenderRole, ParamsRole, EditableRole, ExtraRole, BitratesRole, DefaultBitrateRole, AudioBitratesRole, DefaultAudioBitrateRole, SpeedsRole, ErrorRole }; // Render job roles const int ParametersRole = Qt::UserRole + 1; const int TimeRole = Qt::UserRole + 2; const int ProgressRole = Qt::UserRole + 3; const int ExtraInfoRole = Qt::UserRole + 5; const int DirectRenderType = QTreeWidgetItem::Type; const int ScriptRenderType = QTreeWidgetItem::UserType; // Running job status enum JOBSTATUS { WAITINGJOB = 0, STARTINGJOB, RUNNINGJOB, FINISHEDJOB, FAILEDJOB, ABORTEDJOB }; #ifdef Q_OS_WIN const QLatin1String ScriptFormat(".bat"); QString ScriptSetVar(const QString name, const QString value) { return QString("set ") + name + "=" + value; } QString ScriptGetVar(const QString varName) { return QString('%') + varName + '%'; } #else const QLatin1String ScriptFormat(".sh"); QString ScriptSetVar(const QString &name, const QString &value) { return name + "=\"" + value + '\"'; } QString ScriptGetVar(const QString &varName) { return QString('$') + varName; } #endif static QStringList acodecsList; static QStringList vcodecsList; static QStringList supportedFormats; RenderJobItem::RenderJobItem(QTreeWidget *parent, const QStringList &strings, int type) : QTreeWidgetItem(parent, strings, type) , m_status(-1) { setSizeHint(1, QSize(parent->columnWidth(1), parent->fontMetrics().height() * 3)); setStatus(WAITINGJOB); } void RenderJobItem::setStatus(int status) { if (m_status == status) { return; } m_status = status; switch (status) { case WAITINGJOB: setIcon(0, KoIconUtils::themedIcon(QStringLiteral("media-playback-pause"))); setData(1, Qt::UserRole, i18n("Waiting...")); break; case FINISHEDJOB: setData(1, Qt::UserRole, i18n("Rendering finished")); setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); setData(1, ProgressRole, 100); break; case FAILEDJOB: setData(1, Qt::UserRole, i18n("Rendering crashed")); setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-close"))); setData(1, ProgressRole, 100); break; case ABORTEDJOB: setData(1, Qt::UserRole, i18n("Rendering aborted")); setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-cancel"))); setData(1, ProgressRole, 100); default: break; } } int RenderJobItem::status() const { return m_status; } void RenderJobItem::setMetadata(const QString &data) { m_data = data; } const QString RenderJobItem::metadata() const { return m_data; } RenderWidget::RenderWidget(const QString &projectfolder, bool enableProxy, QWidget *parent) : QDialog(parent) , m_projectFolder(projectfolder) , m_blockProcessing(false) { m_view.setupUi(this); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); setWindowTitle(i18n("Rendering")); m_view.buttonDelete->setIconSize(iconSize); m_view.buttonEdit->setIconSize(iconSize); m_view.buttonSave->setIconSize(iconSize); m_view.buttonFavorite->setIconSize(iconSize); m_view.buttonDelete->setIcon(KoIconUtils::themedIcon(QStringLiteral("trash-empty"))); m_view.buttonDelete->setToolTip(i18n("Delete profile")); m_view.buttonDelete->setEnabled(false); m_view.buttonEdit->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); m_view.buttonEdit->setToolTip(i18n("Edit profile")); m_view.buttonEdit->setEnabled(false); m_view.buttonSave->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-new"))); m_view.buttonSave->setToolTip(i18n("Create new profile")); m_view.hide_log->setIcon(KoIconUtils::themedIcon(QStringLiteral("go-down"))); m_view.buttonFavorite->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); m_view.buttonFavorite->setToolTip(i18n("Copy profile to favorites")); m_view.out_file->button()->setToolTip(i18n("Select output destination")); m_view.advanced_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); m_view.optionsGroup->setVisible(m_view.options->isChecked()); connect(m_view.options, &QAbstractButton::toggled, m_view.optionsGroup, &QWidget::setVisible); m_view.videoLabel->setVisible(m_view.options->isChecked()); connect(m_view.options, &QAbstractButton::toggled, m_view.videoLabel, &QWidget::setVisible); m_view.video->setVisible(m_view.options->isChecked()); connect(m_view.options, &QAbstractButton::toggled, m_view.video, &QWidget::setVisible); m_view.audioLabel->setVisible(m_view.options->isChecked()); connect(m_view.options, &QAbstractButton::toggled, m_view.audioLabel, &QWidget::setVisible); m_view.audio->setVisible(m_view.options->isChecked()); connect(m_view.options, &QAbstractButton::toggled, m_view.audio, &QWidget::setVisible); connect(m_view.quality, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustAVQualities); connect(m_view.video, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::adjustQuality); connect(m_view.speed, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustSpeed); m_view.buttonRender->setEnabled(false); m_view.buttonGenerateScript->setEnabled(false); setRescaleEnabled(false); m_view.guides_box->setVisible(false); m_view.open_dvd->setVisible(false); m_view.create_chapter->setVisible(false); m_view.open_browser->setVisible(false); m_view.error_box->setVisible(false); m_view.tc_type->setEnabled(false); m_view.checkTwoPass->setEnabled(false); m_view.proxy_render->setHidden(!enableProxy); connect(m_view.proxy_render, &QCheckBox::toggled, this, &RenderWidget::slotProxyWarn); KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor bg = scheme.background(KColorScheme::NegativeBackground).color(); m_view.errorBox->setStyleSheet( QStringLiteral("QGroupBox { background-color: rgb(%1, %2, %3); border-radius: 5px;}; ").arg(bg.red()).arg(bg.green()).arg(bg.blue())); int height = QFontInfo(font()).pixelSize(); m_view.errorIcon->setPixmap(KoIconUtils::themedIcon(QStringLiteral("dialog-warning")).pixmap(height, height)); m_view.errorBox->setHidden(true); m_infoMessage = new KMessageWidget; m_view.info->addWidget(m_infoMessage); m_infoMessage->setCloseButtonVisible(false); m_infoMessage->hide(); m_view.encoder_threads->setMinimum(0); m_view.encoder_threads->setMaximum(QThread::idealThreadCount()); m_view.encoder_threads->setToolTip(i18n("Encoding threads (0 is automatic)")); m_view.encoder_threads->setValue(KdenliveSettings::encodethreads()); connect(m_view.encoder_threads, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateEncodeThreads); m_view.rescale_keep->setChecked(KdenliveSettings::rescalekeepratio()); connect(m_view.rescale_width, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleWidth); connect(m_view.rescale_height, static_cast(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleHeight); m_view.rescale_keep->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-link"))); m_view.rescale_keep->setToolTip(i18n("Preserve aspect ratio")); connect(m_view.rescale_keep, &QAbstractButton::clicked, this, &RenderWidget::slotSwitchAspectRatio); connect(m_view.buttonRender, SIGNAL(clicked()), this, SLOT(slotPrepareExport())); connect(m_view.buttonGenerateScript, &QAbstractButton::clicked, this, &RenderWidget::slotGenerateScript); m_view.abort_job->setEnabled(false); m_view.start_script->setEnabled(false); m_view.delete_script->setEnabled(false); connect(m_view.export_audio, &QCheckBox::stateChanged, this, &RenderWidget::slotUpdateAudioLabel); m_view.export_audio->setCheckState(Qt::PartiallyChecked); checkCodecs(); parseProfiles(); parseScriptFiles(); m_view.running_jobs->setUniformRowHeights(false); m_view.scripts_list->setUniformRowHeights(false); connect(m_view.start_script, &QAbstractButton::clicked, this, &RenderWidget::slotStartScript); connect(m_view.delete_script, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteScript); connect(m_view.scripts_list, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckScript); connect(m_view.running_jobs, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckJob); connect(m_view.running_jobs, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotPlayRendering); connect(m_view.buttonSave, &QAbstractButton::clicked, this, &RenderWidget::slotSaveProfile); connect(m_view.buttonEdit, &QAbstractButton::clicked, this, &RenderWidget::slotEditProfile); connect(m_view.buttonDelete, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteProfile); connect(m_view.buttonFavorite, &QAbstractButton::clicked, this, &RenderWidget::slotCopyToFavorites); connect(m_view.abort_job, &QAbstractButton::clicked, this, &RenderWidget::slotAbortCurrentJob); connect(m_view.start_job, &QAbstractButton::clicked, this, &RenderWidget::slotStartCurrentJob); connect(m_view.clean_up, &QAbstractButton::clicked, this, &RenderWidget::slotCLeanUpJobs); connect(m_view.hide_log, &QAbstractButton::clicked, this, &RenderWidget::slotHideLog); connect(m_view.buttonClose, &QAbstractButton::clicked, this, &QWidget::hide); connect(m_view.buttonClose2, &QAbstractButton::clicked, this, &QWidget::hide); connect(m_view.buttonClose3, &QAbstractButton::clicked, this, &QWidget::hide); connect(m_view.rescale, &QAbstractButton::toggled, this, &RenderWidget::setRescaleEnabled); connect(m_view.out_file, SIGNAL(textChanged(QString)), this, SLOT(slotUpdateButtons())); connect(m_view.out_file, SIGNAL(urlSelected(QUrl)), this, SLOT(slotUpdateButtons(QUrl))); connect(m_view.formats, &QTreeWidget::currentItemChanged, this, &RenderWidget::refreshParams); connect(m_view.formats, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotEditItem); connect(m_view.render_guide, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox); connect(m_view.render_zone, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox); connect(m_view.render_full, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox); connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition())); connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition())); connect(m_view.tc_overlay, &QAbstractButton::toggled, m_view.tc_type, &QWidget::setEnabled); // m_view.splitter->setStretchFactor(1, 5); // m_view.splitter->setStretchFactor(0, 2); m_view.out_file->setMode(KFile::File); #if KIO_VERSION >= QT_VERSION_CHECK(5, 33, 0) m_view.out_file->setAcceptMode(QFileDialog::AcceptSave); #elif !defined(KIOWIDGETS_DEPRECATED) m_view.out_file->fileDialog()->setAcceptMode(QFileDialog::AcceptSave); #endif m_view.out_file->setFocusPolicy(Qt::ClickFocus); m_jobsDelegate = new RenderViewDelegate(this); m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File")); m_view.running_jobs->setItemDelegate(m_jobsDelegate); QHeaderView *header = m_view.running_jobs->header(); header->setSectionResizeMode(0, QHeaderView::Fixed); header->resizeSection(0, size + 4); header->setSectionResizeMode(1, QHeaderView::Interactive); m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Script Files")); m_scriptsDelegate = new RenderViewDelegate(this); m_view.scripts_list->setItemDelegate(m_scriptsDelegate); header = m_view.scripts_list->header(); header->setSectionResizeMode(0, QHeaderView::Fixed); header->resizeSection(0, size + 4); // Find path for Kdenlive renderer #ifdef Q_OS_WIN m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render.exe"); #else m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render"); #endif if (!QFile::exists(m_renderer)) { m_renderer = QStandardPaths::findExecutable(QStringLiteral("kdenlive_render")); if (m_renderer.isEmpty()) { m_renderer = QStringLiteral("kdenlive_render"); } } QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); if ((interface == nullptr) || (!interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver")) && !interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager")))) { m_view.shutdown->setEnabled(false); } refreshView(); focusFirstVisibleItem(); adjustSize(); } QSize RenderWidget::sizeHint() const { // Make sure the widget has minimum size on opening return QSize(200, 200); } RenderWidget::~RenderWidget() { m_view.running_jobs->blockSignals(true); m_view.scripts_list->blockSignals(true); m_view.running_jobs->clear(); m_view.scripts_list->clear(); delete m_jobsDelegate; delete m_scriptsDelegate; delete m_infoMessage; } void RenderWidget::slotEditItem(QTreeWidgetItem *item) { if (item->parent() == nullptr) { // This is a top level item - group - don't edit return; } const QString edit = item->data(0, EditableRole).toString(); if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) { slotSaveProfile(); } else { slotEditProfile(); } } void RenderWidget::showInfoPanel() { if (m_view.advanced_params->isVisible()) { m_view.advanced_params->setVisible(false); KdenliveSettings::setShowrenderparams(false); } else { m_view.advanced_params->setVisible(true); KdenliveSettings::setShowrenderparams(true); } } void RenderWidget::setDocumentPath(const QString &path) { if (m_view.out_file->url().adjusted(QUrl::RemoveFilename).toLocalFile() == QUrl::fromLocalFile(m_projectFolder).adjusted(QUrl::RemoveFilename).toLocalFile()) { const QString fileName = m_view.out_file->url().fileName(); m_view.out_file->setUrl(QUrl::fromLocalFile(QDir(path).absoluteFilePath(fileName))); } m_projectFolder = QUrl::fromLocalFile(path).adjusted(QUrl::NormalizePathSegments | QUrl::StripTrailingSlash).toLocalFile() + QDir::separator(); parseScriptFiles(); } void RenderWidget::slotUpdateGuideBox() { m_view.guides_box->setVisible(m_view.render_guide->isChecked()); } void RenderWidget::slotCheckStartGuidePosition() { if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex()) { m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex()); } } void RenderWidget::slotCheckEndGuidePosition() { if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex()) { m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex()); } } void RenderWidget::setGuides(const QList &guidesList, double duration) { m_view.guide_start->clear(); m_view.guide_end->clear(); if (!guidesList.isEmpty()) { m_view.guide_start->addItem(i18n("Beginning"), "0"); m_view.render_guide->setEnabled(true); m_view.create_chapter->setEnabled(true); } else { m_view.render_guide->setEnabled(false); m_view.create_chapter->setEnabled(false); } double fps = pCore->getCurrentProfile()->fps(); for (int i = 0; i < guidesList.count(); i++) { CommentedTime c = guidesList.at(i); GenTime pos = c.time(); const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps); m_view.guide_start->addItem(c.comment() + QLatin1Char('/') + guidePos, pos.seconds()); m_view.guide_end->addItem(c.comment() + QLatin1Char('/') + guidePos, pos.seconds()); } if (!guidesList.isEmpty()) { m_view.guide_end->addItem(i18n("End"), QString::number(duration)); } } /** * Will be called when the user selects an output file via the file dialog. * File extension will be added automatically. */ void RenderWidget::slotUpdateButtons(const QUrl &url) { if (m_view.out_file->url().isEmpty()) { m_view.buttonGenerateScript->setEnabled(false); m_view.buttonRender->setEnabled(false); } else { updateButtons(); // This also checks whether the selected format is available } if (url.isValid()) { QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item == nullptr) || (item->parent() == nullptr)) { // categories have no parent m_view.buttonRender->setEnabled(false); m_view.buttonGenerateScript->setEnabled(false); return; } const QString extension = item->data(0, ExtensionRole).toString(); m_view.out_file->setUrl(filenameWithExtension(url, extension)); } } /** * Will be called when the user changes the output file path in the text line. * File extension must NOT be added, would make editing impossible! */ void RenderWidget::slotUpdateButtons() { if (m_view.out_file->url().isEmpty()) { m_view.buttonRender->setEnabled(false); m_view.buttonGenerateScript->setEnabled(false); } else { updateButtons(); // This also checks whether the selected format is available } } void RenderWidget::slotSaveProfile() { Ui::SaveProfile_UI ui; QPointer d = new QDialog(this); ui.setupUi(d); QString customGroup; QStringList arguments = m_view.advanced_params->toPlainText().split(' ', QString::SkipEmptyParts); if (!arguments.isEmpty()) { ui.parameters->setText(arguments.join(QLatin1Char(' '))); } ui.profile_name->setFocus(); QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item != nullptr) && (item->parent() != nullptr)) { // not a category // Duplicate current item settings customGroup = item->parent()->text(0); ui.extension->setText(item->data(0, ExtensionRole).toString()); if (ui.parameters->toPlainText().contains(QStringLiteral("%bitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) { if (ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) { ui.vbitrates_label->setText(i18n("Qualities")); ui.default_vbitrate_label->setText(i18n("Default quality")); } else { ui.vbitrates_label->setText(i18n("Bitrates")); ui.default_vbitrate_label->setText(i18n("Default bitrate")); } if (item->data(0, BitratesRole).canConvert(QVariant::StringList) && (item->data(0, BitratesRole).toStringList().count() != 0)) { QStringList bitrates = item->data(0, BitratesRole).toStringList(); ui.vbitrates_list->setText(bitrates.join(QLatin1Char(','))); if (item->data(0, DefaultBitrateRole).canConvert(QVariant::String)) { ui.default_vbitrate->setValue(item->data(0, DefaultBitrateRole).toInt()); } } } else { ui.vbitrates->setHidden(true); } if (ui.parameters->toPlainText().contains(QStringLiteral("%audiobitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) { if (ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) { ui.abitrates_label->setText(i18n("Qualities")); ui.default_abitrate_label->setText(i18n("Default quality")); } else { ui.abitrates_label->setText(i18n("Bitrates")); ui.default_abitrate_label->setText(i18n("Default bitrate")); } if ((item != nullptr) && item->data(0, AudioBitratesRole).canConvert(QVariant::StringList) && (item->data(0, AudioBitratesRole).toStringList().count() != 0)) { QStringList bitrates = item->data(0, AudioBitratesRole).toStringList(); ui.abitrates_list->setText(bitrates.join(QLatin1Char(','))); if (item->data(0, DefaultAudioBitrateRole).canConvert(QVariant::String)) { ui.default_abitrate->setValue(item->data(0, DefaultAudioBitrateRole).toInt()); } } } else { ui.abitrates->setHidden(true); } if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) { QStringList speeds = item->data(0, SpeedsRole).toStringList(); ui.speeds_list->setText(speeds.join('\n')); } } if (customGroup.isEmpty()) { customGroup = i18nc("Group Name", "Custom"); } ui.group_name->setText(customGroup); if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) { QString newProfileName = ui.profile_name->text().simplified(); QString newGroupName = ui.group_name->text().simplified(); if (newGroupName.isEmpty()) { newGroupName = i18nc("Group Name", "Custom"); } QDomDocument doc; QDomElement profileElement = doc.createElement(QStringLiteral("profile")); profileElement.setAttribute(QStringLiteral("name"), newProfileName); profileElement.setAttribute(QStringLiteral("category"), newGroupName); profileElement.setAttribute(QStringLiteral("extension"), ui.extension->text().simplified()); QString args = ui.parameters->toPlainText().simplified(); profileElement.setAttribute(QStringLiteral("args"), args); if (args.contains(QStringLiteral("%bitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultbitrate"), QString::number(ui.default_vbitrate->value())); profileElement.setAttribute(QStringLiteral("bitrates"), ui.vbitrates_list->text()); } else if (args.contains(QStringLiteral("%quality"))) { profileElement.setAttribute(QStringLiteral("defaultquality"), QString::number(ui.default_vbitrate->value())); profileElement.setAttribute(QStringLiteral("qualities"), ui.vbitrates_list->text()); } if (args.contains(QStringLiteral("%audiobitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), QString::number(ui.default_abitrate->value())); profileElement.setAttribute(QStringLiteral("audiobitrates"), ui.abitrates_list->text()); } else if (args.contains(QStringLiteral("%audioquality"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultaudioquality"), QString::number(ui.default_abitrate->value())); profileElement.setAttribute(QStringLiteral("audioqualities"), ui.abitrates_list->text()); } QString speeds_list_str = ui.speeds_list->toPlainText(); if (!speeds_list_str.isEmpty()) { profileElement.setAttribute(QStringLiteral("speeds"), speeds_list_str.replace('\n', ';').simplified()); } doc.appendChild(profileElement); saveProfile(doc.documentElement()); parseProfiles(); } delete d; } bool RenderWidget::saveProfile(QDomElement newprofile) { QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } QDomDocument doc; QFile file(dir.absoluteFilePath(QStringLiteral("customprofiles.xml"))); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomElement profiles = doc.documentElement(); if (profiles.isNull() || profiles.tagName() != QLatin1String("profiles")) { doc.clear(); profiles = doc.createElement(QStringLiteral("profiles")); profiles.setAttribute(QStringLiteral("version"), 1); doc.appendChild(profiles); } int version = profiles.attribute(QStringLiteral("version"), nullptr).toInt(); if (version < 1) { doc.clear(); profiles = doc.createElement(QStringLiteral("profiles")); profiles.setAttribute(QStringLiteral("version"), 1); doc.appendChild(profiles); } QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile")); QString newProfileName = newprofile.attribute(QStringLiteral("name")); // Check existing profiles QStringList existingProfileNames; int i = 0; while (!profilelist.item(i).isNull()) { documentElement = profilelist.item(i).toElement(); QString profileName = documentElement.attribute(QStringLiteral("name")); existingProfileNames << profileName; i++; } // Check if a profile with that same name already exists bool ok; while (existingProfileNames.contains(newProfileName)) { QString updatedProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok); if (!ok) { return false; } if (updatedProfileName == newProfileName) { // remove previous profile profiles.removeChild(profilelist.item(existingProfileNames.indexOf(newProfileName))); break; } else { newProfileName = updatedProfileName; newprofile.setAttribute(QStringLiteral("name"), newProfileName); } } profiles.appendChild(newprofile); // QCString save = doc.toString().utf8(); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::sorry(this, i18n("Unable to write to file %1", dir.absoluteFilePath("customprofiles.xml"))); return false; } QTextStream out(&file); out << doc.toString(); if (file.error() != QFile::NoError) { KMessageBox::error(this, i18n("Cannot write to file %1", dir.absoluteFilePath("customprofiles.xml"))); file.close(); return false; } file.close(); return true; } void RenderWidget::slotCopyToFavorites() { QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item == nullptr) || (item->parent() == nullptr)) { return; } QString params = item->data(0, ParamsRole).toString(); QString extension = item->data(0, ExtensionRole).toString(); QString currentProfile = item->text(0); QDomDocument doc; QDomElement profileElement = doc.createElement(QStringLiteral("profile")); profileElement.setAttribute(QStringLiteral("name"), currentProfile); profileElement.setAttribute(QStringLiteral("category"), i18nc("Category Name", "Custom")); profileElement.setAttribute(QStringLiteral("destinationid"), QStringLiteral("favorites")); profileElement.setAttribute(QStringLiteral("extension"), extension); profileElement.setAttribute(QStringLiteral("args"), params); if (params.contains(QStringLiteral("%bitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultbitrate"), item->data(0, DefaultBitrateRole).toString()); profileElement.setAttribute(QStringLiteral("bitrates"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(','))); } else if (params.contains(QStringLiteral("%quality"))) { profileElement.setAttribute(QStringLiteral("defaultquality"), item->data(0, DefaultBitrateRole).toString()); profileElement.setAttribute(QStringLiteral("qualities"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(','))); } if (params.contains(QStringLiteral("%audiobitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), item->data(0, DefaultAudioBitrateRole).toString()); profileElement.setAttribute(QStringLiteral("audiobitrates"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(','))); } else if (params.contains(QStringLiteral("%audioquality"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultaudioquality"), item->data(0, DefaultAudioBitrateRole).toString()); profileElement.setAttribute(QStringLiteral("audioqualities"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(','))); } if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) { // profile has a variable speed profileElement.setAttribute(QStringLiteral("speeds"), item->data(0, SpeedsRole).toStringList().join(QLatin1Char(';'))); } doc.appendChild(profileElement); if (saveProfile(doc.documentElement())) { parseProfiles(profileElement.attribute(QStringLiteral("name"))); } } void RenderWidget::slotEditProfile() { QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item == nullptr) || (item->parent() == nullptr)) { return; } QString params = item->data(0, ParamsRole).toString(); Ui::SaveProfile_UI ui; QPointer d = new QDialog(this); ui.setupUi(d); QString customGroup = item->parent()->text(0); if (customGroup.isEmpty()) { customGroup = i18nc("Group Name", "Custom"); } ui.group_name->setText(customGroup); ui.profile_name->setText(item->text(0)); ui.extension->setText(item->data(0, ExtensionRole).toString()); ui.parameters->setText(params); ui.profile_name->setFocus(); if (params.contains(QStringLiteral("%bitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) { if (params.contains(QStringLiteral("%quality"))) { ui.vbitrates_label->setText(i18n("Qualities")); ui.default_vbitrate_label->setText(i18n("Default quality")); } else { ui.vbitrates_label->setText(i18n("Bitrates")); ui.default_vbitrate_label->setText(i18n("Default bitrate")); } if (item->data(0, BitratesRole).canConvert(QVariant::StringList) && (item->data(0, BitratesRole).toStringList().count() != 0)) { QStringList bitrates = item->data(0, BitratesRole).toStringList(); ui.vbitrates_list->setText(bitrates.join(QLatin1Char(','))); if (item->data(0, DefaultBitrateRole).canConvert(QVariant::String)) { ui.default_vbitrate->setValue(item->data(0, DefaultBitrateRole).toInt()); } } } else { ui.vbitrates->setHidden(true); } if (params.contains(QStringLiteral("%audiobitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) { if (params.contains(QStringLiteral("%audioquality"))) { ui.abitrates_label->setText(i18n("Qualities")); ui.default_abitrate_label->setText(i18n("Default quality")); } else { ui.abitrates_label->setText(i18n("Bitrates")); ui.default_abitrate_label->setText(i18n("Default bitrate")); } if (item->data(0, AudioBitratesRole).canConvert(QVariant::StringList) && (item->data(0, AudioBitratesRole).toStringList().count() != 0)) { QStringList bitrates = item->data(0, AudioBitratesRole).toStringList(); ui.abitrates_list->setText(bitrates.join(QLatin1Char(','))); if (item->data(0, DefaultAudioBitrateRole).canConvert(QVariant::String)) { ui.default_abitrate->setValue(item->data(0, DefaultAudioBitrateRole).toInt()); } } } else { ui.abitrates->setHidden(true); } if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) { QStringList speeds = item->data(0, SpeedsRole).toStringList(); ui.speeds_list->setText(speeds.join('\n')); } d->setWindowTitle(i18n("Edit Profile")); if (d->exec() == QDialog::Accepted) { slotDeleteProfile(true); QString exportFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/customprofiles.xml"); QDomDocument doc; QFile file(exportFile); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomElement profiles = doc.documentElement(); if (profiles.isNull() || profiles.tagName() != QLatin1String("profiles")) { doc.clear(); profiles = doc.createElement(QStringLiteral("profiles")); profiles.setAttribute(QStringLiteral("version"), 1); doc.appendChild(profiles); } int version = profiles.attribute(QStringLiteral("version"), nullptr).toInt(); if (version < 1) { doc.clear(); profiles = doc.createElement(QStringLiteral("profiles")); profiles.setAttribute(QStringLiteral("version"), 1); doc.appendChild(profiles); } QString newProfileName = ui.profile_name->text().simplified(); QString newGroupName = ui.group_name->text().simplified(); if (newGroupName.isEmpty()) { newGroupName = i18nc("Group Name", "Custom"); } QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile")); int i = 0; while (!profilelist.item(i).isNull()) { // make sure a profile with same name doesn't exist documentElement = profilelist.item(i).toElement(); QString profileName = documentElement.attribute(QStringLiteral("name")); if (profileName == newProfileName) { // a profile with that same name already exists bool ok; newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok); if (!ok) { return; } if (profileName == newProfileName) { profiles.removeChild(profilelist.item(i)); break; } } ++i; } QDomElement profileElement = doc.createElement(QStringLiteral("profile")); profileElement.setAttribute(QStringLiteral("name"), newProfileName); profileElement.setAttribute(QStringLiteral("category"), newGroupName); profileElement.setAttribute(QStringLiteral("extension"), ui.extension->text().simplified()); QString args = ui.parameters->toPlainText().simplified(); profileElement.setAttribute(QStringLiteral("args"), args); if (args.contains(QStringLiteral("%bitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultbitrate"), QString::number(ui.default_vbitrate->value())); profileElement.setAttribute(QStringLiteral("bitrates"), ui.vbitrates_list->text()); } else if (args.contains(QStringLiteral("%quality"))) { profileElement.setAttribute(QStringLiteral("defaultquality"), QString::number(ui.default_vbitrate->value())); profileElement.setAttribute(QStringLiteral("qualities"), ui.vbitrates_list->text()); } if (args.contains(QStringLiteral("%audiobitrate"))) { // profile has a variable bitrate profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), QString::number(ui.default_abitrate->value())); profileElement.setAttribute(QStringLiteral("audiobitrates"), ui.abitrates_list->text()); } else if (args.contains(QStringLiteral("%audioquality"))) { profileElement.setAttribute(QStringLiteral("defaultaudioquality"), QString::number(ui.default_abitrate->value())); profileElement.setAttribute(QStringLiteral("audioqualities"), ui.abitrates_list->text()); } QString speeds_list_str = ui.speeds_list->toPlainText(); if (!speeds_list_str.isEmpty()) { // profile has a variable speed profileElement.setAttribute(QStringLiteral("speeds"), speeds_list_str.replace('\n', ';').simplified()); } profiles.appendChild(profileElement); // QCString save = doc.toString().utf8(); delete d; if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Cannot write to file %1", exportFile)); return; } QTextStream out(&file); out << doc.toString(); if (file.error() != QFile::NoError) { KMessageBox::error(this, i18n("Cannot write to file %1", exportFile)); file.close(); return; } file.close(); parseProfiles(); } else { delete d; } } void RenderWidget::slotDeleteProfile(bool dontRefresh) { // TODO: delete a profile installed by KNewStuff the easy way /* QString edit = m_view.formats->currentItem()->data(EditableRole).toString(); if (!edit.endsWith(QLatin1String("customprofiles.xml"))) { // This is a KNewStuff installed file, process through KNS KNS::Engine engine(0); if (engine.init("kdenlive_render.knsrc")) { KNS::Entry::List entries; } return; }*/ QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item == nullptr) || (item->parent() == nullptr)) { return; } QString currentProfile = item->text(0); QString exportFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/customprofiles.xml"); QDomDocument doc; QFile file(exportFile); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomNodeList profiles = doc.elementsByTagName(QStringLiteral("profile")); int i = 0; QString profileName; while (!profiles.item(i).isNull()) { documentElement = profiles.item(i).toElement(); profileName = documentElement.attribute(QStringLiteral("name")); if (profileName == currentProfile) { doc.documentElement().removeChild(profiles.item(i)); break; } ++i; } if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile)); return; } QTextStream out(&file); out << doc.toString(); if (file.error() != QFile::NoError) { KMessageBox::error(this, i18n("Cannot write to file %1", exportFile)); file.close(); return; } file.close(); if (dontRefresh) { return; } parseProfiles(); focusFirstVisibleItem(); } void RenderWidget::updateButtons() { if ((m_view.formats->currentItem() == nullptr) || m_view.formats->currentItem()->isHidden()) { m_view.buttonSave->setEnabled(false); m_view.buttonDelete->setEnabled(false); m_view.buttonEdit->setEnabled(false); m_view.buttonRender->setEnabled(false); m_view.buttonGenerateScript->setEnabled(false); } else { m_view.buttonSave->setEnabled(true); m_view.buttonRender->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull()); m_view.buttonGenerateScript->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull()); QString edit = m_view.formats->currentItem()->data(0, EditableRole).toString(); if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) { m_view.buttonDelete->setEnabled(false); m_view.buttonEdit->setEnabled(false); } else { m_view.buttonDelete->setEnabled(true); m_view.buttonEdit->setEnabled(true); } } } void RenderWidget::focusFirstVisibleItem(const QString &profile) { QTreeWidgetItem *item = nullptr; if (!profile.isEmpty()) { QList items = m_view.formats->findItems(profile, Qt::MatchExactly | Qt::MatchRecursive); if (!items.isEmpty()) { item = items.constFirst(); } } if (!item) { // searched profile not found in any category, select 1st available profile for (int i = 0; i < m_view.formats->topLevelItemCount(); ++i) { item = m_view.formats->topLevelItem(i); if (item->childCount() > 0) { item = item->child(0); break; } } } if (item) { m_view.formats->setCurrentItem(item); item->parent()->setExpanded(true); refreshParams(); } updateButtons(); } void RenderWidget::slotPrepareExport(bool scriptExport, const QString &scriptPath) { if (!QFile::exists(KdenliveSettings::rendererpath())) { KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)")); return; } QString chapterFile; if (m_view.create_chapter->isChecked()) { chapterFile = m_view.out_file->url().toLocalFile() + QStringLiteral(".dvdchapter"); } // mantisbt 1051 QDir dir(m_view.out_file->url().adjusted(QUrl::RemoveFilename).toLocalFile()); if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { KMessageBox::sorry(this, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_view.out_file->url().adjusted(QUrl::RemoveFilename).toLocalFile())); return; } emit prepareRenderingData(scriptExport, m_view.render_zone->isChecked(), chapterFile, scriptPath); } void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QMap &metadata, const QList &playlistPaths, const QList &trackNames, const QString &scriptPath, bool exportAudio) { QTreeWidgetItem *item = m_view.formats->currentItem(); if (!item) { return; } QString destBase = m_view.out_file->url().toLocalFile().trimmed(); if (destBase.isEmpty()) { return; } // script file QFile file(scriptPath); int stemCount = playlistPaths.count(); bool stemExport = (!trackNames.isEmpty()); for (int stemIdx = 0; stemIdx < stemCount; stemIdx++) { QString dest(destBase); // on stem export append track name to each filename if (stemExport) { QFileInfo dfi(dest); QStringList filePath; // construct the full file path filePath << dfi.absolutePath() << QDir::separator() << dfi.completeBaseName() + QLatin1Char('_') << QString(trackNames.at(stemIdx)).replace(QLatin1Char(' '), QLatin1Char('_')) << QStringLiteral(".") << dfi.suffix(); dest = filePath.join(QString()); } // Check whether target file has an extension. // If not, ask whether extension should be added or not. QString extension = item->data(0, ExtensionRole).toString(); if (!dest.endsWith(extension, Qt::CaseInsensitive)) { if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) { dest.append('.' + extension); } } // Checks for image sequence QStringList imageSequences; imageSequences << QStringLiteral("jpg") << QStringLiteral("png") << QStringLiteral("bmp") << QStringLiteral("dpx") << QStringLiteral("ppm") << QStringLiteral("tga") << QStringLiteral("tif"); if (imageSequences.contains(extension)) { // format string for counter? if (!QRegExp(QStringLiteral(".*%[0-9]*d.*")).exactMatch(dest)) { dest = dest.section(QLatin1Char('.'), 0, -2) + QStringLiteral("_%05d.") + extension; } } if (QFile::exists(dest)) { if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) { for (const QString &playlistFilePath : playlistPaths) { QFile playlistFile(playlistFilePath); if (playlistFile.exists()) { playlistFile.remove(); } } return; } } // Generate script file if (scriptExport && stemIdx == 0) { // Generate script file if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath)); return; } QTextStream outStream(&file); #ifndef Q_OS_WIN outStream << "#! /bin/sh" << '\n' << '\n'; #endif outStream << ScriptSetVar("RENDERER", m_renderer) << '\n'; outStream << ScriptSetVar("MELT", KdenliveSettings::rendererpath()) << "\n\n"; } QStringList overlayargs; if (m_view.tc_overlay->isChecked()) { QString filterFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("metadata.properties")); overlayargs << QStringLiteral("meta.attr.timecode=1") << "meta.attr.timecode.markup=#" + QString(m_view.tc_type->currentIndex() != 0 ? "frame" : "timecode"); overlayargs << QStringLiteral("-attach") << QStringLiteral("data_feed:attr_check") << QStringLiteral("-attach"); overlayargs << "data_show:" + filterFile << QStringLiteral("_loader=1") << QStringLiteral("dynamic=1"); } QStringList render_process_args; if (!scriptExport) { render_process_args << QStringLiteral("-erase"); } #ifndef Q_OS_WIN if (KdenliveSettings::usekuiserver()) { render_process_args << QStringLiteral("-kuiserver"); } // get process id render_process_args << QStringLiteral("-pid:%1").arg(QCoreApplication::applicationPid()); #endif // Set locale for render process if required if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) { ; #ifndef Q_OS_MAC const QString currentLocale = setlocale(LC_NUMERIC, nullptr); #else const QString currentLocale = setlocale(LC_NUMERIC_MASK, nullptr); #endif render_process_args << QStringLiteral("-locale:%1").arg(currentLocale); } QString renderArgs = m_view.advanced_params->toPlainText().simplified(); QString std = renderArgs; // Check for fps change double forcedfps = 0; if (std.startsWith(QLatin1String("r="))) { QString sub = std.section(QLatin1Char(' '), 0, 0).toLower(); sub = sub.section(QLatin1Char('='), 1, 1); forcedfps = sub.toDouble(); } else if (std.contains(QStringLiteral(" r="))) { QString sub = std.section(QStringLiteral(" r="), 1, 1); sub = sub.section(QLatin1Char(' '), 0, 0).toLower(); forcedfps = sub.toDouble(); } else if (std.contains(QStringLiteral("mlt_profile="))) { QString sub = std.section(QStringLiteral("mlt_profile="), 1, 1); sub = sub.section(QLatin1Char(' '), 0, 0).toLower(); forcedfps = ProfileRepository::get()->getProfile(sub)->fps(); } bool resizeProfile = false; std::unique_ptr &profile = pCore->getCurrentProfile(); if (renderArgs.contains(QLatin1String("%dv_standard"))) { QString dvstd; if (fmod((double)profile->frame_rate_num() / profile->frame_rate_den(), 30.01) > 27) { dvstd = QStringLiteral("ntsc"); if (!(profile->frame_rate_num() == 30000 && profile->frame_rate_den() == 1001)) { forcedfps = 30000.0 / 1001; } if (!(profile->width() == 720 && profile->height() == 480)) { resizeProfile = true; } } else { dvstd = QStringLiteral("pal"); if (!(profile->frame_rate_num() == 25 && profile->frame_rate_den() == 1)) { forcedfps = 25; } if (!(profile->width() == 720 && profile->height() == 576)) { resizeProfile = true; } } if ((double)profile->display_aspect_num() / profile->display_aspect_den() > 1.5) { dvstd += QLatin1String("_wide"); } renderArgs.replace(QLatin1String("%dv_standard"), dvstd); } // If there is an fps change, we need to use the producer consumer AND update the in/out points if (forcedfps > 0 && qAbs((int)100 * forcedfps - ((int)100 * profile->frame_rate_num() / profile->frame_rate_den())) > 2) { resizeProfile = true; double ratio = profile->frame_rate_num() / profile->frame_rate_den() / forcedfps; if (ratio > 0) { zoneIn /= ratio; zoneOut /= ratio; } } if (m_view.render_guide->isChecked()) { double fps = profile->fps(); double guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble(); double guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble(); render_process_args << "in=" + QString::number((int)GenTime(guideStart).frames(fps)) << "out=" + QString::number((int)GenTime(guideEnd).frames(fps)); } else { render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut); } if (!overlayargs.isEmpty()) { render_process_args << "preargs=" + overlayargs.join(QLatin1Char(' ')); } if (scriptExport) { render_process_args << ScriptGetVar("MELT"); } else { render_process_args << KdenliveSettings::rendererpath(); } render_process_args << profile->path() << item->data(0, RenderRole).toString(); if (!scriptExport && m_view.play_after->isChecked()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(dest); KService::Ptr serv = KMimeTypeTrader::self()->preferredService(mime.name()); if (serv) { KIO::DesktopExecParser parser(*serv, QList() << QUrl::fromLocalFile(QUrl::toPercentEncoding(dest))); render_process_args << parser.resultingArguments().join(QLatin1Char(' ')); } else { // no service found to play MIME type // TODO: inform user // errorMessage(PlaybackError, i18n("No service found to play %1", mime.name())); render_process_args << QStringLiteral("-"); } } else { render_process_args << QStringLiteral("-"); } if (m_view.speed->isEnabled()) { renderArgs.append(QChar(' ') + item->data(0, SpeedsRole).toStringList().at(m_view.speed->value())); } // Project metadata if (m_view.export_meta->isChecked()) { QMap::const_iterator i = metadata.constBegin(); while (i != metadata.constEnd()) { renderArgs.append(QStringLiteral(" %1=%2").arg(i.key(), QString(QUrl::toPercentEncoding(i.value())))); ++i; } } // Adjust frame scale int width; int height; if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) { width = m_view.rescale_width->value(); height = m_view.rescale_height->value(); } else { width = profile->width(); height = profile->height(); } // Adjust scanning if (m_view.scanning_list->currentIndex() == 1) { renderArgs.append(QStringLiteral(" progressive=1")); } else if (m_view.scanning_list->currentIndex() == 2) { renderArgs.append(QStringLiteral(" progressive=0")); } // disable audio if requested if (!exportAudio) { renderArgs.append(QStringLiteral(" an=1 ")); } // Set the thread counts if (!renderArgs.contains(QStringLiteral("threads="))) { renderArgs.append(QStringLiteral(" threads=%1").arg(KdenliveSettings::encodethreads())); } renderArgs.append(QStringLiteral(" real_time=-%1").arg(KdenliveSettings::mltthreads())); // Check if the rendering profile is different from project profile, // in which case we need to use the producer_comsumer from MLT QString subsize; if (std.startsWith(QLatin1String("s="))) { subsize = std.section(QLatin1Char(' '), 0, 0).toLower(); subsize = subsize.section(QLatin1Char('='), 1, 1); } else if (std.contains(QStringLiteral(" s="))) { subsize = std.section(QStringLiteral(" s="), 1, 1); subsize = subsize.section(QLatin1Char(' '), 0, 0).toLower(); } else if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) { subsize = QStringLiteral(" s=%1x%2").arg(width).arg(height); // Add current size parameter renderArgs.append(subsize); } // Check if we need to embed the playlist into the producer consumer // That is required if PAR != 1 if (profile->sample_aspect_num() != profile->sample_aspect_den() && subsize.isEmpty()) { resizeProfile = true; } QStringList paramsList = renderArgs.split(' ', QString::SkipEmptyParts); for (int i = 0; i < paramsList.count(); ++i) { QString paramName = paramsList.at(i).section(QLatin1Char('='), 0, -2); QString paramValue = paramsList.at(i).section(QLatin1Char('='), -1); // If the profiles do not match we need to use the consumer tag if (paramName == QLatin1String("mlt_profile") && paramValue != profile->path()) { resizeProfile = true; } // evaluate expression if (paramValue.startsWith(QLatin1Char('%'))) { if (paramValue.startsWith(QStringLiteral("%bitrate")) || paramValue == QStringLiteral("%quality")) { if (paramValue.contains("+'k'")) paramValue = QString::number(m_view.video->value()) + 'k'; else paramValue = QString::number(m_view.video->value()); } if (paramValue.startsWith(QStringLiteral("%audiobitrate")) || paramValue == QStringLiteral("%audioquality")) { if (paramValue.contains("+'k'")) paramValue = QString::number(m_view.audio->value()) + 'k'; else paramValue = QString::number(m_view.audio->value()); } if (paramValue == QStringLiteral("%dar")) paramValue = '@' + QString::number(profile->display_aspect_num()) + QLatin1Char('/') + QString::number(profile->display_aspect_den()); if (paramValue == QStringLiteral("%passes")) paramValue = QString::number(static_cast(m_view.checkTwoPass->isChecked()) + 1); paramsList[i] = paramName + QLatin1Char('=') + paramValue; } } if (resizeProfile && !KdenliveSettings::gpu_accel()) { render_process_args << "consumer:" + (scriptExport ? ScriptGetVar("SOURCE_" + QString::number(stemIdx)) : QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded()); } else { render_process_args << (scriptExport ? ScriptGetVar("SOURCE_" + QString::number(stemIdx)) : QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded()); } render_process_args << (scriptExport ? ScriptGetVar("TARGET_" + QString::number(stemIdx)) : QUrl::fromLocalFile(dest).toEncoded()); if (KdenliveSettings::gpu_accel()) { render_process_args << QStringLiteral("glsl.=1"); } render_process_args << paramsList; if (scriptExport) { QTextStream outStream(&file); QString stemIdxStr(QString::number(stemIdx)); outStream << ScriptSetVar("SOURCE_" + stemIdxStr, QUrl::fromLocalFile(playlistPaths.at(stemIdx)).toEncoded()) << '\n'; outStream << ScriptSetVar("TARGET_" + stemIdxStr, QUrl::fromLocalFile(dest).toEncoded()) << '\n'; outStream << ScriptSetVar("PARAMETERS_" + stemIdxStr, render_process_args.join(QLatin1Char(' '))) << '\n'; outStream << ScriptGetVar("RENDERER") + " " + ScriptGetVar("PARAMETERS_" + stemIdxStr) << "\n"; if (stemIdx == (stemCount - 1)) { if (file.error() != QFile::NoError) { KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath)); file.close(); return; } file.close(); QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser); QTimer::singleShot(400, this, &RenderWidget::parseScriptFiles); m_view.tabWidget->setCurrentIndex(2); return; } continue; } // Save rendering profile to document QMap renderProps; renderProps.insert(QStringLiteral("rendercategory"), m_view.formats->currentItem()->parent()->text(0)); renderProps.insert(QStringLiteral("renderprofile"), m_view.formats->currentItem()->text(0)); renderProps.insert(QStringLiteral("renderurl"), destBase); renderProps.insert(QStringLiteral("renderzone"), QString::number(static_cast(m_view.render_zone->isChecked()))); renderProps.insert(QStringLiteral("renderguide"), QString::number(static_cast(m_view.render_guide->isChecked()))); renderProps.insert(QStringLiteral("renderstartguide"), QString::number(m_view.guide_start->currentIndex())); renderProps.insert(QStringLiteral("renderendguide"), QString::number(m_view.guide_end->currentIndex())); renderProps.insert(QStringLiteral("renderscanning"), QString::number(m_view.scanning_list->currentIndex())); int export_audio = 0; if (m_view.export_audio->checkState() == Qt::Checked) { export_audio = 2; } else if (m_view.export_audio->checkState() == Qt::Unchecked) { export_audio = 1; } renderProps.insert(QStringLiteral("renderexportaudio"), QString::number(export_audio)); renderProps.insert(QStringLiteral("renderrescale"), QString::number(static_cast(m_view.rescale->isChecked()))); renderProps.insert(QStringLiteral("renderrescalewidth"), QString::number(m_view.rescale_width->value())); renderProps.insert(QStringLiteral("renderrescaleheight"), QString::number(m_view.rescale_height->value())); renderProps.insert(QStringLiteral("rendertcoverlay"), QString::number(static_cast(m_view.tc_overlay->isChecked()))); renderProps.insert(QStringLiteral("rendertctype"), QString::number(m_view.tc_type->currentIndex())); renderProps.insert(QStringLiteral("renderratio"), QString::number(static_cast(m_view.rescale_keep->isChecked()))); renderProps.insert(QStringLiteral("renderplay"), QString::number(static_cast(m_view.play_after->isChecked()))); renderProps.insert(QStringLiteral("rendertwopass"), QString::number(static_cast(m_view.checkTwoPass->isChecked()))); renderProps.insert(QStringLiteral("renderquality"), QString::number(m_view.video->value())); renderProps.insert(QStringLiteral("renderaudioquality"), QString::number(m_view.audio->value())); renderProps.insert(QStringLiteral("renderspeed"), QString::number(m_view.speed->value())); emit selectedRenderProfile(renderProps); // insert item in running jobs list RenderJobItem *renderItem = nullptr; QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1); if (!existing.isEmpty()) { renderItem = static_cast(existing.at(0)); if (renderItem->status() == RUNNINGJOB || renderItem->status() == WAITINGJOB || renderItem->status() == STARTINGJOB) { KMessageBox::information(this, i18n("There is already a job writing file:
%1
Abort the job if you want to overwrite it...", dest), i18n("Already running")); return; } if (renderItem->type() != DirectRenderType) { delete renderItem; renderItem = nullptr; } else { renderItem->setData(1, ProgressRole, 0); renderItem->setStatus(WAITINGJOB); renderItem->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("media-playback-pause"))); renderItem->setData(1, Qt::UserRole, i18n("Waiting...")); renderItem->setData(1, ParametersRole, dest); } } if (!renderItem) { renderItem = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest); } renderItem->setData(1, TimeRole, QDateTime::currentDateTime()); // Set rendering type /*if (group == QLatin1String("dvd")) { if (m_view.open_dvd->isChecked()) { renderItem->setData(0, Qt::UserRole, group); if (renderArgs.contains(QStringLiteral("mlt_profile="))) { //TODO: probably not valid anymore (no more MLT profiles in args) // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd QString prof = renderArgs.section(QStringLiteral("mlt_profile="), 1, 1); prof = prof.section(QLatin1Char(' '), 0, 0); qCDebug(KDENLIVE_LOG) << "// render profile: " << prof; renderItem->setMetadata(prof); } } } else { if (group == QLatin1String("websites") && m_view.open_browser->isChecked()) { renderItem->setData(0, Qt::UserRole, group); // pass the url QString url = m_view.formats->currentItem()->data(ExtraRole).toString(); renderItem->setMetadata(url); } }*/ renderItem->setData(1, ParametersRole, render_process_args); if (!exportAudio) { renderItem->setData(1, ExtraInfoRole, i18n("Video without audio track")); } else { renderItem->setData(1, ExtraInfoRole, QString()); } m_view.running_jobs->setCurrentItem(renderItem); m_view.tabWidget->setCurrentIndex(1); // check render status checkRenderStatus(); } // end loop } void RenderWidget::checkRenderStatus() { // check if we have a job waiting to render if (m_blockProcessing) { return; } RenderJobItem *item = static_cast(m_view.running_jobs->topLevelItem(0)); // Make sure no other rendering is running while (item != nullptr) { if (item->status() == RUNNINGJOB) { return; } item = static_cast(m_view.running_jobs->itemBelow(item)); } item = static_cast(m_view.running_jobs->topLevelItem(0)); bool waitingJob = false; // Find first waiting job while (item != nullptr) { if (item->status() == WAITINGJOB) { item->setData(1, TimeRole, QDateTime::currentDateTime()); waitingJob = true; startRendering(item); item->setStatus(STARTINGJOB); break; } item = static_cast(m_view.running_jobs->itemBelow(item)); } if (!waitingJob && m_view.shutdown->isChecked()) { emit shutdown(); } } void RenderWidget::startRendering(RenderJobItem *item) { if (item->type() == DirectRenderType) { // Normal render process if (!QProcess::startDetached(m_renderer, item->data(1, ParametersRole).toStringList())) { item->setStatus(FAILEDJOB); } else { KNotification::event(QStringLiteral("RenderStarted"), i18n("Rendering %1 started", item->text(1)), QPixmap(), this); } } else if (item->type() == ScriptRenderType) { // Script item if (!QProcess::startDetached(QLatin1Char('"') + item->data(1, ParametersRole).toString() + QLatin1Char('"'))) { item->setStatus(FAILEDJOB); } } } int RenderWidget::waitingJobsCount() const { int count = 0; RenderJobItem *item = static_cast(m_view.running_jobs->topLevelItem(0)); while (item != nullptr) { if (item->status() == WAITINGJOB || item->status() == STARTINGJOB) { count++; } item = static_cast(m_view.running_jobs->itemBelow(item)); } return count; } void RenderWidget::adjustViewToProfile() { m_view.scanning_list->setCurrentIndex(0); m_view.rescale_width->setValue(KdenliveSettings::defaultrescalewidth()); if (!m_view.rescale_keep->isChecked()) { m_view.rescale_height->blockSignals(true); m_view.rescale_height->setValue(KdenliveSettings::defaultrescaleheight()); m_view.rescale_height->blockSignals(false); } refreshView(); } void RenderWidget::refreshView() { m_view.formats->blockSignals(true); QIcon brokenIcon = KoIconUtils::themedIcon(QStringLiteral("dialog-close")); QIcon warningIcon = KoIconUtils::themedIcon(QStringLiteral("dialog-warning")); KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window); const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color(); const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color(); // We borrow a reference to the profile's pointer to query it more easily std::unique_ptr &profile = pCore->getCurrentProfile(); double project_framerate = (double)profile->frame_rate_num() / profile->frame_rate_den(); for (int i = 0; i < m_view.formats->topLevelItemCount(); ++i) { QTreeWidgetItem *group = m_view.formats->topLevelItem(i); for (int j = 0; j < group->childCount(); ++j) { QTreeWidgetItem *item = group->child(j); QString std = item->data(0, StandardRole).toString(); if (std.isEmpty() || (std.contains(QStringLiteral("PAL"), Qt::CaseInsensitive) && profile->frame_rate_num() == 25 && profile->frame_rate_den() == 1) || (std.contains(QStringLiteral("NTSC"), Qt::CaseInsensitive) && profile->frame_rate_num() == 30000 && profile->frame_rate_den() == 1001)) { // Standard OK } else { item->setData(0, ErrorRole, i18n("Standard (%1) not compatible with project profile (%2)", std, project_framerate)); item->setIcon(0, brokenIcon); item->setForeground(0, disabled); continue; } QString params = item->data(0, ParamsRole).toString(); // Make sure the selected profile uses the same frame rate as project profile if (params.contains(QStringLiteral("mlt_profile="))) { QString profile_str = params.section(QStringLiteral("mlt_profile="), 1, 1).section(QLatin1Char(' '), 0, 0); std::unique_ptr &target_profile = ProfileRepository::get()->getProfile(profile_str); if (target_profile->frame_rate_den() > 0) { double profile_rate = (double)target_profile->frame_rate_num() / target_profile->frame_rate_den(); if ((int)(1000.0 * profile_rate) != (int)(1000.0 * project_framerate)) { item->setData(0, ErrorRole, i18n("Frame rate (%1) not compatible with project profile (%2)", profile_rate, project_framerate)); item->setIcon(0, brokenIcon); item->setForeground(0, disabled); continue; } } } // Make sure the selected profile uses an installed avformat codec / format if (!supportedFormats.isEmpty()) { QString format; if (params.startsWith(QLatin1String("f="))) { format = params.section(QStringLiteral("f="), 1, 1); } else if (params.contains(QStringLiteral(" f="))) { format = params.section(QStringLiteral(" f="), 1, 1); } if (!format.isEmpty()) { format = format.section(QLatin1Char(' '), 0, 0).toLower(); if (!supportedFormats.contains(format)) { item->setData(0, ErrorRole, i18n("Unsupported video format: %1", format)); item->setIcon(0, brokenIcon); item->setForeground(0, disabled); continue; } } } if (!acodecsList.isEmpty()) { QString format; if (params.startsWith(QLatin1String("acodec="))) { format = params.section(QStringLiteral("acodec="), 1, 1); } else if (params.contains(QStringLiteral(" acodec="))) { format = params.section(QStringLiteral(" acodec="), 1, 1); } if (!format.isEmpty()) { format = format.section(QLatin1Char(' '), 0, 0).toLower(); if (!acodecsList.contains(format)) { item->setData(0, ErrorRole, i18n("Unsupported audio codec: %1", format)); item->setIcon(0, brokenIcon); item->setForeground(0, disabled); item->setBackground(0, disabledbg); } } } if (!vcodecsList.isEmpty()) { QString format; if (params.startsWith(QLatin1String("vcodec="))) { format = params.section(QStringLiteral("vcodec="), 1, 1); } else if (params.contains(QStringLiteral(" vcodec="))) { format = params.section(QStringLiteral(" vcodec="), 1, 1); } if (!format.isEmpty()) { format = format.section(QLatin1Char(' '), 0, 0).toLower(); if (!vcodecsList.contains(format)) { item->setData(0, ErrorRole, i18n("Unsupported video codec: %1", format)); item->setIcon(0, brokenIcon); item->setForeground(0, disabled); continue; } } } if (params.contains(QStringLiteral(" profile=")) || params.startsWith(QLatin1String("profile="))) { // changed in MLT commit d8a3a5c9190646aae72048f71a39ee7446a3bd45 // (http://www.mltframework.org/gitweb/mlt.git?p=mltframework.org/mlt.git;a=commit;h=d8a3a5c9190646aae72048f71a39ee7446a3bd45) - item->setData(0, ErrorRole, i18n("This render profile uses a 'profile' parameter.
Unless you know what you are doing you will probably " - "have to change it to 'mlt_profile'.")); + item->setData(0, ErrorRole, + i18n("This render profile uses a 'profile' parameter.
Unless you know what you are doing you will probably " + "have to change it to 'mlt_profile'.")); item->setIcon(0, warningIcon); continue; } } } focusFirstVisibleItem(); m_view.formats->blockSignals(false); refreshParams(); } QUrl RenderWidget::filenameWithExtension(QUrl url, const QString &extension) { if (!url.isValid()) { url = QUrl::fromLocalFile(m_projectFolder); } QString directory = url.adjusted(QUrl::RemoveFilename).toLocalFile(); QString filename = url.fileName(); QString ext; if (extension.at(0) == '.') { ext = extension; } else { ext = '.' + extension; } if (filename.isEmpty()) { filename = i18n("untitled"); } int pos = filename.lastIndexOf('.'); if (pos == 0) { filename.append(ext); } else { if (!filename.endsWith(ext, Qt::CaseInsensitive)) { filename = filename.left(pos) + ext; } } return QUrl::fromLocalFile(directory + filename); } void RenderWidget::refreshParams() { // Format not available (e.g. codec not installed); Disable start button QTreeWidgetItem *item = m_view.formats->currentItem(); if ((item == nullptr) || item->parent() == nullptr) { // This is a category item, not a real profile m_view.buttonBox->setEnabled(false); } else { m_view.buttonBox->setEnabled(true); } QString extension; if (item) { extension = item->data(0, ExtensionRole).toString(); } if ((item == nullptr) || item->isHidden() || extension.isEmpty()) { if (!item) { errorMessage(ProfileError, i18n("No matching profile")); } else if (!item->parent()) // category ; else if (extension.isEmpty()) { errorMessage(ProfileError, i18n("Invalid profile")); } m_view.advanced_params->clear(); m_view.buttonRender->setEnabled(false); m_view.buttonGenerateScript->setEnabled(false); return; } QString params = item->data(0, ParamsRole).toString(); errorMessage(ProfileError, item->data(0, ErrorRole).toString()); m_view.advanced_params->setPlainText(params); if (params.contains(QStringLiteral(" s=")) || params.startsWith(QLatin1String("s=")) || params.contains(QLatin1String("%dv_standard"))) { // profile has a fixed size, do not allow resize m_view.rescale->setEnabled(false); setRescaleEnabled(false); } else { m_view.rescale->setEnabled(true); setRescaleEnabled(m_view.rescale->isChecked()); } QUrl url = filenameWithExtension(m_view.out_file->url(), extension); m_view.out_file->setUrl(url); // if (!url.isEmpty()) { // QString path = url.path(); // int pos = path.lastIndexOf('.') + 1; // if (pos == 0) path.append('.' + extension); // else path = path.left(pos) + extension; // m_view.out_file->setUrl(QUrl(path)); // } else { // m_view.out_file->setUrl(QUrl(QDir::homePath() + QStringLiteral("/untitled.") + extension)); // } m_view.out_file->setFilter("*." + extension); QString edit = item->data(0, EditableRole).toString(); if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) { m_view.buttonDelete->setEnabled(false); m_view.buttonEdit->setEnabled(false); } else { m_view.buttonDelete->setEnabled(true); m_view.buttonEdit->setEnabled(true); } // video quality control m_view.video->blockSignals(true); bool quality = false; if ((params.contains(QStringLiteral("%quality")) || params.contains(QStringLiteral("%bitrate"))) && item->data(0, BitratesRole).canConvert(QVariant::StringList)) { // bitrates or quantizers list QStringList qs = item->data(0, BitratesRole).toStringList(); if (qs.count() > 1) { quality = true; int qmax = qs.constFirst().toInt(); int qmin = qs.last().toInt(); if (qmax < qmin) { // always show best quality on right m_view.video->setRange(qmax, qmin); m_view.video->setProperty("decreasing", true); } else { m_view.video->setRange(qmin, qmax); m_view.video->setProperty("decreasing", false); } } } m_view.video->setEnabled(quality); m_view.quality->setEnabled(quality); m_view.qualityLabel->setEnabled(quality); m_view.video->blockSignals(false); // audio quality control quality = false; m_view.audio->blockSignals(true); if ((params.contains(QStringLiteral("%audioquality")) || params.contains(QStringLiteral("%audiobitrate"))) && item->data(0, AudioBitratesRole).canConvert(QVariant::StringList)) { // bitrates or quantizers list QStringList qs = item->data(0, AudioBitratesRole).toStringList(); if (qs.count() > 1) { quality = true; int qmax = qs.constFirst().toInt(); int qmin = qs.last().toInt(); if (qmax < qmin) { m_view.audio->setRange(qmax, qmin); m_view.audio->setProperty("decreasing", true); } else { m_view.audio->setRange(qmin, qmax); m_view.audio->setProperty("decreasing", false); } if (params.contains(QStringLiteral("%audiobitrate"))) { m_view.audio->setSingleStep(32); // 32kbps step } else { m_view.audio->setSingleStep(1); } } } m_view.audio->setEnabled(quality); m_view.audio->blockSignals(false); if (m_view.quality->isEnabled()) { adjustAVQualities(m_view.quality->value()); } if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) { int speed = item->data(0, SpeedsRole).toStringList().count() - 1; m_view.speed->setEnabled(true); m_view.speed->setMaximum(speed); m_view.speed->setValue(speed * 3 / 4); // default to intermediate speed } else { m_view.speed->setEnabled(false); } adjustSpeed(m_view.speed->value()); m_view.checkTwoPass->setEnabled(params.contains(QStringLiteral("passes"))); m_view.encoder_threads->setEnabled(!params.contains(QStringLiteral("threads="))); m_view.buttonRender->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull()); m_view.buttonGenerateScript->setEnabled(m_view.formats->currentItem()->data(0, ErrorRole).isNull()); } void RenderWidget::reloadProfiles() { parseProfiles(); } void RenderWidget::parseProfiles(const QString &selectedProfile) { m_view.formats->clear(); // Parse our xml profile QString exportFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("export/profiles.xml")); parseFile(exportFile, false); // Parse some MLT's profiles parseMltPresets(); QString exportFolder = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/"); QDir directory(exportFolder); QStringList filter; filter << QStringLiteral("*.xml"); QStringList fileList = directory.entryList(filter, QDir::Files); // We should parse customprofiles.xml in last position, so that user profiles // can also override profiles installed by KNewStuff fileList.removeAll(QStringLiteral("customprofiles.xml")); for (const QString &filename : fileList) { parseFile(directory.absoluteFilePath(filename), true); } if (QFile::exists(exportFolder + QStringLiteral("customprofiles.xml"))) { parseFile(exportFolder + QStringLiteral("customprofiles.xml"), true); } focusFirstVisibleItem(selectedProfile); } void RenderWidget::parseMltPresets() { QDir root(KdenliveSettings::mltpath()); if (!root.cd(QStringLiteral("../presets/consumer/avformat"))) { // Cannot find MLT's presets directory qCWarning(KDENLIVE_LOG) << " / / / WARNING, cannot find MLT's preset folder"; return; } if (root.cd(QStringLiteral("lossless"))) { QString groupName = i18n("Lossless/HQ"); QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly); QTreeWidgetItem *groupItem; if (!foundGroup.isEmpty()) { groupItem = foundGroup.takeFirst(); } else { groupItem = new QTreeWidgetItem(QStringList(groupName)); m_view.formats->addTopLevelItem(groupItem); groupItem->setExpanded(true); } const QStringList profiles = root.entryList(QDir::Files, QDir::Name); for (const QString &prof : profiles) { KConfig config(root.absoluteFilePath(prof), KConfig::SimpleConfig); KConfigGroup group = config.group(QByteArray()); QString vcodec = group.readEntry("vcodec"); QString acodec = group.readEntry("acodec"); QString extension = group.readEntry("meta.preset.extension"); QString note = group.readEntry("meta.preset.note"); QString profileName = prof; if (!vcodec.isEmpty() || !acodec.isEmpty()) { profileName.append(" ("); if (!vcodec.isEmpty()) { profileName.append(vcodec); if (!acodec.isEmpty()) { profileName.append("+" + acodec); } } else if (!acodec.isEmpty()) { profileName.append(acodec); } profileName.append(QLatin1Char(')')); } QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(profileName)); item->setData(0, ExtensionRole, extension); item->setData(0, RenderRole, "avformat"); item->setData(0, ParamsRole, QString("properties=lossless/" + prof)); if (!note.isEmpty()) { item->setToolTip(0, note); } groupItem->addChild(item); } } if (root.cd(QStringLiteral("../stills"))) { QString groupName = i18nc("Category Name", "Images sequence"); QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly); QTreeWidgetItem *groupItem; if (!foundGroup.isEmpty()) { groupItem = foundGroup.takeFirst(); } else { groupItem = new QTreeWidgetItem(QStringList(groupName)); m_view.formats->addTopLevelItem(groupItem); groupItem->setExpanded(true); } QStringList profiles = root.entryList(QDir::Files, QDir::Name); for (const QString &prof : profiles) { QTreeWidgetItem *item = loadFromMltPreset(groupName, root.absoluteFilePath(prof), prof); if (!item) { continue; } item->setData(0, ParamsRole, QString("properties=stills/" + prof)); groupItem->addChild(item); } // Add GIF as image sequence root.cdUp(); QTreeWidgetItem *item = loadFromMltPreset(groupName, root.absoluteFilePath(QStringLiteral("GIF")), QStringLiteral("GIF")); if (item) { item->setData(0, ParamsRole, QStringLiteral("properties=GIF")); groupItem->addChild(item); } } } QTreeWidgetItem *RenderWidget::loadFromMltPreset(const QString &groupName, const QString &path, const QString &profileName) { KConfig config(path, KConfig::SimpleConfig); KConfigGroup group = config.group(QByteArray()); QString extension = group.readEntry("meta.preset.extension"); QString note = group.readEntry("meta.preset.note"); if (extension.isEmpty()) { return nullptr; } QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(profileName)); item->setData(0, GroupRole, groupName); item->setData(0, ExtensionRole, extension); item->setData(0, RenderRole, "avformat"); if (!note.isEmpty()) { item->setToolTip(0, note); } return item; } void RenderWidget::parseFile(const QString &exportFile, bool editable) { QDomDocument doc; QFile file(exportFile); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomElement profileElement; QString extension; QDomNodeList groups = doc.elementsByTagName(QStringLiteral("group")); QTreeWidgetItem *item = nullptr; bool replaceVorbisCodec = false; if (acodecsList.contains(QStringLiteral("libvorbis"))) { replaceVorbisCodec = true; } bool replaceLibfaacCodec = false; if (acodecsList.contains(QStringLiteral("libfaac"))) { replaceLibfaacCodec = true; } if (editable || groups.isEmpty()) { QDomElement profiles = doc.documentElement(); if (editable && profiles.attribute(QStringLiteral("version"), nullptr).toInt() < 1) { // this is an old profile version, update it QDomDocument newdoc; QDomElement newprofiles = newdoc.createElement(QStringLiteral("profiles")); newprofiles.setAttribute(QStringLiteral("version"), 1); newdoc.appendChild(newprofiles); QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile")); for (int i = 0; i < profilelist.count(); ++i) { QString category = i18nc("Category Name", "Custom"); QString ext; QDomNode parent = profilelist.at(i).parentNode(); if (!parent.isNull()) { QDomElement parentNode = parent.toElement(); if (parentNode.hasAttribute(QStringLiteral("name"))) { category = parentNode.attribute(QStringLiteral("name")); } ext = parentNode.attribute(QStringLiteral("extension")); } if (!profilelist.at(i).toElement().hasAttribute(QStringLiteral("category"))) { profilelist.at(i).toElement().setAttribute(QStringLiteral("category"), category); } if (!ext.isEmpty()) { profilelist.at(i).toElement().setAttribute(QStringLiteral("extension"), ext); } QDomNode n = profilelist.at(i).cloneNode(); newprofiles.appendChild(newdoc.importNode(n, true)); } if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile)); return; } QTextStream out(&file); out << newdoc.toString(); file.close(); parseFile(exportFile, editable); return; } QDomNode node = doc.elementsByTagName(QStringLiteral("profile")).at(0); if (node.isNull()) { return; } int count = 1; while (!node.isNull()) { QDomElement profile = node.toElement(); QString profileName = profile.attribute(QStringLiteral("name")); QString standard = profile.attribute(QStringLiteral("standard")); QTextDocument docConvert; docConvert.setHtml(profile.attribute(QStringLiteral("args"))); QString params = docConvert.toPlainText().simplified(); if (replaceVorbisCodec && params.contains(QStringLiteral("acodec=vorbis"))) { // replace vorbis with libvorbis params = params.replace(QLatin1String("=vorbis"), QLatin1String("=libvorbis")); } if (replaceLibfaacCodec && params.contains(QStringLiteral("acodec=aac"))) { // replace libfaac with aac params = params.replace(QLatin1String("aac"), QLatin1String("libfaac")); } QString prof_extension = profile.attribute(QStringLiteral("extension")); if (!prof_extension.isEmpty()) { extension = prof_extension; } QString groupName = profile.attribute(QStringLiteral("category"), i18nc("Category Name", "Custom")); QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly); QTreeWidgetItem *groupItem; if (!foundGroup.isEmpty()) { groupItem = foundGroup.takeFirst(); } else { groupItem = new QTreeWidgetItem(QStringList(groupName)); if (editable) { m_view.formats->insertTopLevelItem(0, groupItem); } else { m_view.formats->addTopLevelItem(groupItem); groupItem->setExpanded(true); } } // Check if item with same name already exists and replace it, // allowing to override default profiles QTreeWidgetItem *childitem = nullptr; for (int j = 0; j < groupItem->childCount(); ++j) { if (groupItem->child(j)->text(0) == profileName) { childitem = groupItem->child(j); break; } } if (!childitem) { childitem = new QTreeWidgetItem(QStringList(profileName)); } childitem->setData(0, GroupRole, groupName); childitem->setData(0, ExtensionRole, extension); childitem->setData(0, RenderRole, "avformat"); childitem->setData(0, StandardRole, standard); childitem->setData(0, ParamsRole, params); if (params.contains(QLatin1String("%quality"))) { childitem->setData(0, BitratesRole, profile.attribute(QStringLiteral("qualities")).split(QLatin1Char(','), QString::SkipEmptyParts)); } else if (params.contains(QLatin1String("%bitrate"))) { childitem->setData(0, BitratesRole, profile.attribute(QStringLiteral("bitrates")).split(QLatin1Char(','), QString::SkipEmptyParts)); } if (params.contains(QLatin1String("%audioquality"))) { childitem->setData(0, AudioBitratesRole, profile.attribute(QStringLiteral("audioqualities")).split(QLatin1Char(','), QString::SkipEmptyParts)); } else if (params.contains(QLatin1String("%audiobitrate"))) { childitem->setData(0, AudioBitratesRole, profile.attribute(QStringLiteral("audiobitrates")).split(QLatin1Char(','), QString::SkipEmptyParts)); } if (profile.hasAttribute(QStringLiteral("speeds"))) { childitem->setData(0, SpeedsRole, profile.attribute(QStringLiteral("speeds")).split(QLatin1Char(';'), QString::SkipEmptyParts)); } if (profile.hasAttribute(QStringLiteral("url"))) { childitem->setData(0, ExtraRole, profile.attribute(QStringLiteral("url"))); } if (editable) { childitem->setData(0, EditableRole, exportFile); if (exportFile.endsWith(QLatin1String("customprofiles.xml"))) { childitem->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("favorite"))); } else { childitem->setIcon(0, QIcon::fromTheme(QStringLiteral("applications-internet"))); } } groupItem->addChild(childitem); node = doc.elementsByTagName(QStringLiteral("profile")).at(count); count++; } return; } int i = 0; QString groupName; QString profileName; QString prof_extension; QString renderer; QString params; QString standard; while (!groups.item(i).isNull()) { documentElement = groups.item(i).toElement(); QDomNode gname = documentElement.elementsByTagName(QStringLiteral("groupname")).at(0); groupName = documentElement.attribute(QStringLiteral("name"), i18nc("Attribute Name", "Custom")); extension = documentElement.attribute(QStringLiteral("extension"), QString()); renderer = documentElement.attribute(QStringLiteral("renderer"), QString()); QList foundGroup = m_view.formats->findItems(groupName, Qt::MatchExactly); QTreeWidgetItem *groupItem; if (!foundGroup.isEmpty()) { groupItem = foundGroup.takeFirst(); } else { groupItem = new QTreeWidgetItem(QStringList(groupName)); m_view.formats->addTopLevelItem(groupItem); groupItem->setExpanded(true); } QDomNode n = groups.item(i).firstChild(); while (!n.isNull()) { if (n.toElement().tagName() != QLatin1String("profile")) { n = n.nextSibling(); continue; } profileElement = n.toElement(); profileName = profileElement.attribute(QStringLiteral("name")); standard = profileElement.attribute(QStringLiteral("standard")); params = profileElement.attribute(QStringLiteral("args")).simplified(); if (replaceVorbisCodec && params.contains(QStringLiteral("acodec=vorbis"))) { // replace vorbis with libvorbis params = params.replace(QLatin1String("=vorbis"), QLatin1String("=libvorbis")); } if (replaceLibfaacCodec && params.contains(QStringLiteral("acodec=aac"))) { // replace libfaac with aac params = params.replace(QLatin1String("aac"), QLatin1String("libfaac")); } prof_extension = profileElement.attribute(QStringLiteral("extension")); if (!prof_extension.isEmpty()) { extension = prof_extension; } item = new QTreeWidgetItem(QStringList(profileName)); item->setData(0, GroupRole, groupName); item->setData(0, ExtensionRole, extension); item->setData(0, RenderRole, renderer); item->setData(0, StandardRole, standard); item->setData(0, ParamsRole, params); if (params.contains(QLatin1String("%quality"))) { item->setData(0, BitratesRole, profileElement.attribute(QStringLiteral("qualities")).split(QLatin1Char(','), QString::SkipEmptyParts)); } else if (params.contains(QLatin1String("%bitrate"))) { item->setData(0, BitratesRole, profileElement.attribute(QStringLiteral("bitrates")).split(QLatin1Char(','), QString::SkipEmptyParts)); } if (params.contains(QLatin1String("%audioquality"))) { item->setData(0, AudioBitratesRole, profileElement.attribute(QStringLiteral("audioqualities")).split(QLatin1Char(','), QString::SkipEmptyParts)); } else if (params.contains(QLatin1String("%audiobitrate"))) { item->setData(0, AudioBitratesRole, profileElement.attribute(QStringLiteral("audiobitrates")).split(QLatin1Char(','), QString::SkipEmptyParts)); } if (profileElement.hasAttribute(QStringLiteral("speeds"))) { item->setData(0, SpeedsRole, profileElement.attribute(QStringLiteral("speeds")).split(QLatin1Char(';'), QString::SkipEmptyParts)); } if (profileElement.hasAttribute(QStringLiteral("url"))) { item->setData(0, ExtraRole, profileElement.attribute(QStringLiteral("url"))); } groupItem->addChild(item); n = n.nextSibling(); } ++i; } } void RenderWidget::setRenderJob(const QString &dest, int progress) { RenderJobItem *item; QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1); if (!existing.isEmpty()) { item = static_cast(existing.at(0)); } else { item = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest); if (progress == 0) { item->setStatus(WAITINGJOB); } } item->setData(1, ProgressRole, progress); item->setStatus(RUNNINGJOB); if (progress == 0) { item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("media-record"))); item->setData(1, TimeRole, QDateTime::currentDateTime()); slotCheckJob(); } else { QDateTime startTime = item->data(1, TimeRole).toDateTime(); qint64 elapsedTime = startTime.secsTo(QDateTime::currentDateTime()); qint64 remaining = elapsedTime * (100 - progress) / progress; int days = static_cast(remaining / 86400); int remainingSecs = static_cast(remaining % 86400); QTime when = QTime(0, 0, 0, 0); when = when.addSecs(remainingSecs); QString est = (days > 0) ? i18np("%1 day ", "%1 days ", days) : QString(); est.append(when.toString(QStringLiteral("hh:mm:ss"))); QString t = i18n("Remaining time %1", est); item->setData(1, Qt::UserRole, t); } } void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error) { RenderJobItem *item; QList existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1); if (!existing.isEmpty()) { item = static_cast(existing.at(0)); } else { item = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << dest); } if (!item) { return; } if (status == -1) { // Job finished successfully item->setStatus(FINISHEDJOB); QDateTime startTime = item->data(1, TimeRole).toDateTime(); qint64 elapsedTime = startTime.secsTo(QDateTime::currentDateTime()); int days = static_cast(elapsedTime / 86400); int secs = static_cast(elapsedTime % 86400); QTime when = QTime(0, 0, 0, 0); when = when.addSecs(secs); QString est = (days > 0) ? i18np("%1 day ", "%1 days ", days) : QString(); est.append(when.toString(QStringLiteral("hh:mm:ss"))); QString t = i18n("Rendering finished in %1", est); item->setData(1, Qt::UserRole, t); QString notif = i18n("Rendering of %1 finished in %2", item->text(1), est); KNotification *notify = new KNotification(QStringLiteral("RenderFinished")); notify->setText(notif); #if KNOTIFICATIONS_VERSION >= QT_VERSION_CHECK(5, 29, 0) notify->setUrls({QUrl::fromLocalFile(dest)}); #endif notify->sendEvent(); QString itemGroup = item->data(0, Qt::UserRole).toString(); if (itemGroup == QLatin1String("dvd")) { emit openDvdWizard(item->text(1)); } else if (itemGroup == QLatin1String("websites")) { QString url = item->metadata(); if (!url.isEmpty()) { new KRun(QUrl::fromLocalFile(url), this); } } } else if (status == -2) { // Rendering crashed item->setStatus(FAILEDJOB); m_view.error_log->append(i18n("Rendering of %1 crashed
", dest)); m_view.error_log->append(error); m_view.error_log->append(QStringLiteral("
")); m_view.error_box->setVisible(true); } else if (status == -3) { // User aborted job item->setStatus(ABORTEDJOB); } else { delete item; } slotCheckJob(); checkRenderStatus(); } void RenderWidget::slotAbortCurrentJob() { RenderJobItem *current = static_cast(m_view.running_jobs->currentItem()); if (current) { if (current->status() == RUNNINGJOB) { emit abortProcess(current->text(1)); } else { delete current; slotCheckJob(); checkRenderStatus(); } } } void RenderWidget::slotStartCurrentJob() { RenderJobItem *current = static_cast(m_view.running_jobs->currentItem()); if ((current != nullptr) && current->status() == WAITINGJOB) { startRendering(current); } m_view.start_job->setEnabled(false); } void RenderWidget::slotCheckJob() { bool activate = false; RenderJobItem *current = static_cast(m_view.running_jobs->currentItem()); if (current) { if (current->status() == RUNNINGJOB || current->status() == STARTINGJOB) { m_view.abort_job->setText(i18n("Abort Job")); m_view.start_job->setEnabled(false); } else { m_view.abort_job->setText(i18n("Remove Job")); m_view.start_job->setEnabled(current->status() == WAITINGJOB); } activate = true; } m_view.abort_job->setEnabled(activate); /* for (int i = 0; i < m_view.running_jobs->topLevelItemCount(); ++i) { current = static_cast(m_view.running_jobs->topLevelItem(i)); if (current == static_cast (m_view.running_jobs->currentItem())) { current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 3)); } else current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2)); }*/ } void RenderWidget::slotCLeanUpJobs() { int ix = 0; RenderJobItem *current = static_cast(m_view.running_jobs->topLevelItem(ix)); while (current != nullptr) { if (current->status() == FINISHEDJOB || current->status() == ABORTEDJOB) { delete current; } else { ix++; } current = static_cast(m_view.running_jobs->topLevelItem(ix)); } slotCheckJob(); } void RenderWidget::parseScriptFiles() { QStringList scriptsFilter; scriptsFilter << QLatin1Char('*') + ScriptFormat; m_view.scripts_list->clear(); QTreeWidgetItem *item; // List the project scripts QDir directory(m_projectFolder + QStringLiteral("scripts/")); QStringList scriptFiles = directory.entryList(scriptsFilter, QDir::Files); for (int i = 0; i < scriptFiles.size(); ++i) { QUrl scriptpath = QUrl::fromLocalFile(directory.absoluteFilePath(scriptFiles.at(i))); QString target; QString renderer; QString melt; QFile file(scriptpath.toLocalFile()); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); while (!stream.atEnd()) { QString line = stream.readLine(); if (line.contains(QLatin1String("TARGET_0="))) { target = line.section(QStringLiteral("TARGET_0="), 1); target = target.section(QLatin1Char('"'), 0, 0, QString::SectionSkipEmpty); } else if (line.contains(QLatin1String("RENDERER="))) { renderer = line.section(QStringLiteral("RENDERER="), 1); renderer = renderer.section(QLatin1Char('"'), 0, 0, QString::SectionSkipEmpty); } else if (line.contains(QLatin1String("MELT="))) { melt = line.section(QStringLiteral("MELT="), 1); melt = melt.section(QLatin1Char('"'), 0, 0, QString::SectionSkipEmpty); } } file.close(); } if (target.isEmpty()) { continue; } item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << QString() << scriptpath.fileName()); if (!renderer.isEmpty() && renderer.contains(QLatin1Char('/')) && !QFile::exists(renderer)) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-cancel"))); item->setToolTip(1, i18n("Script contains wrong command: %1", renderer)); item->setData(0, Qt::UserRole, '1'); } else if (!melt.isEmpty() && melt.contains(QLatin1Char('/')) && !QFile::exists(melt)) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-cancel"))); item->setToolTip(1, i18n("Script contains wrong command: %1", melt)); item->setData(0, Qt::UserRole, '1'); } else { item->setIcon(0, QIcon::fromTheme(QStringLiteral("application-x-executable-script"))); } item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2)); item->setData(1, Qt::UserRole, QUrl(QUrl::fromEncoded(target.toUtf8())).url(QUrl::PreferLocalFile)); item->setData(1, Qt::UserRole + 1, scriptpath.toLocalFile()); } QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0); if (script) { m_view.scripts_list->setCurrentItem(script); script->setSelected(true); } } void RenderWidget::slotCheckScript() { QTreeWidgetItem *current = m_view.scripts_list->currentItem(); if (current == nullptr) { return; } m_view.start_script->setEnabled(current->data(0, Qt::UserRole).toString().isEmpty()); m_view.delete_script->setEnabled(true); for (int i = 0; i < m_view.scripts_list->topLevelItemCount(); ++i) { current = m_view.scripts_list->topLevelItem(i); if (current == m_view.scripts_list->currentItem()) { current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 3)); } else { current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 2)); } } } void RenderWidget::slotStartScript() { RenderJobItem *item = static_cast(m_view.scripts_list->currentItem()); if (item) { QString destination = item->data(1, Qt::UserRole).toString(); QString path = item->data(1, Qt::UserRole + 1).toString(); // Insert new job in queue RenderJobItem *renderItem = nullptr; QList existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1); if (!existing.isEmpty()) { renderItem = static_cast(existing.at(0)); if (renderItem->status() == RUNNINGJOB || renderItem->status() == WAITINGJOB || renderItem->status() == STARTINGJOB) { KMessageBox::information( this, i18n("There is already a job writing file:
%1
Abort the job if you want to overwrite it...", destination), i18n("Already running")); return; } if (renderItem->type() != ScriptRenderType) { delete renderItem; renderItem = nullptr; } } if (!renderItem) { renderItem = new RenderJobItem(m_view.running_jobs, QStringList() << QString() << destination, ScriptRenderType); } renderItem->setData(1, ProgressRole, 0); renderItem->setStatus(WAITINGJOB); renderItem->setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause"))); renderItem->setData(1, Qt::UserRole, i18n("Waiting...")); renderItem->setData(1, TimeRole, QDateTime::currentDateTime()); renderItem->setData(1, ParametersRole, path); checkRenderStatus(); m_view.tabWidget->setCurrentIndex(1); } } void RenderWidget::slotDeleteScript() { QTreeWidgetItem *item = m_view.scripts_list->currentItem(); if (item) { QString path = item->data(1, Qt::UserRole + 1).toString(); bool success = true; success &= static_cast(QFile::remove(path + QStringLiteral(".mlt"))); success &= static_cast(QFile::remove(path)); if (!success) { qCWarning(KDENLIVE_LOG) << "// Error removing script or playlist: " << path << ", " << path << ".mlt"; } parseScriptFiles(); } } void RenderWidget::slotGenerateScript() { slotPrepareExport(true); } void RenderWidget::slotHideLog() { m_view.error_box->setVisible(false); } void RenderWidget::setRenderProfile(const QMap &props) { m_view.scanning_list->setCurrentIndex(props.value(QStringLiteral("renderscanning")).toInt()); int exportAudio = props.value(QStringLiteral("renderexportaudio")).toInt(); switch (exportAudio) { case 1: m_view.export_audio->setCheckState(Qt::Unchecked); break; case 2: m_view.export_audio->setCheckState(Qt::Checked); break; default: m_view.export_audio->setCheckState(Qt::PartiallyChecked); } if (props.contains(QStringLiteral("renderrescale"))) { m_view.rescale->setChecked(props.value(QStringLiteral("renderrescale")).toInt() != 0); } if (props.contains(QStringLiteral("renderrescalewidth"))) { m_view.rescale_width->setValue(props.value(QStringLiteral("renderrescalewidth")).toInt()); } if (props.contains(QStringLiteral("renderrescaleheight"))) { m_view.rescale_height->setValue(props.value(QStringLiteral("renderrescaleheight")).toInt()); } if (props.contains(QStringLiteral("rendertcoverlay"))) { m_view.tc_overlay->setChecked(props.value(QStringLiteral("rendertcoverlay")).toInt() != 0); } if (props.contains(QStringLiteral("rendertctype"))) { m_view.tc_type->setCurrentIndex(props.value(QStringLiteral("rendertctype")).toInt()); } if (props.contains(QStringLiteral("renderratio"))) { m_view.rescale_keep->setChecked(props.value(QStringLiteral("renderratio")).toInt() != 0); } if (props.contains(QStringLiteral("renderplay"))) { m_view.play_after->setChecked(props.value(QStringLiteral("renderplay")).toInt() != 0); } if (props.contains(QStringLiteral("rendertwopass"))) { m_view.checkTwoPass->setChecked(props.value(QStringLiteral("rendertwopass")).toInt() != 0); } if (props.value(QStringLiteral("renderzone")) == QLatin1String("1")) { m_view.render_zone->setChecked(true); } else if (props.value(QStringLiteral("renderguide")) == QLatin1String("1")) { m_view.render_guide->setChecked(true); m_view.guide_start->setCurrentIndex(props.value(QStringLiteral("renderstartguide")).toInt()); m_view.guide_end->setCurrentIndex(props.value(QStringLiteral("renderendguide")).toInt()); } else { m_view.render_full->setChecked(true); } slotUpdateGuideBox(); QString url = props.value(QStringLiteral("renderurl")); if (!url.isEmpty()) { m_view.out_file->setUrl(QUrl::fromLocalFile(url)); } if (props.contains(QStringLiteral("renderprofile")) || props.contains(QStringLiteral("rendercategory"))) { focusFirstVisibleItem(props.value(QStringLiteral("renderprofile"))); } if (props.contains(QStringLiteral("renderquality"))) { m_view.video->setValue(props.value(QStringLiteral("renderquality")).toInt()); } else if (props.contains(QStringLiteral("renderbitrate"))) { m_view.video->setValue(props.value(QStringLiteral("renderbitrate")).toInt()); } else { m_view.quality->setValue(m_view.quality->maximum() * 3 / 4); } if (props.contains(QStringLiteral("renderaudioquality"))) { m_view.audio->setValue(props.value(QStringLiteral("renderaudioquality")).toInt()); } if (props.contains(QStringLiteral("renderaudiobitrate"))) { m_view.audio->setValue(props.value(QStringLiteral("renderaudiobitrate")).toInt()); } if (props.contains(QStringLiteral("renderspeed"))) { m_view.speed->setValue(props.value(QStringLiteral("renderspeed")).toInt()); } } bool RenderWidget::startWaitingRenderJobs() { m_blockProcessing = true; QString autoscriptFile = getFreeScriptName(QUrl(), QStringLiteral("auto")); QFile file(autoscriptFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(KDENLIVE_LOG) << "////// ERROR writing to file: " << autoscriptFile; KMessageBox::error(nullptr, i18n("Cannot write to file %1", autoscriptFile)); return false; } QTextStream outStream(&file); #ifndef Q_OS_WIN outStream << "#! /bin/sh" << '\n' << '\n'; #endif RenderJobItem *item = static_cast(m_view.running_jobs->topLevelItem(0)); while (item != nullptr) { if (item->status() == WAITINGJOB) { if (item->type() == DirectRenderType) { // Add render process for item const QString params = item->data(1, ParametersRole).toStringList().join(QLatin1Char(' ')); outStream << '\"' << m_renderer << "\" " << params << '\n'; } else if (item->type() == ScriptRenderType) { // Script item outStream << item->data(1, ParametersRole).toString() << '\n'; } } item = static_cast(m_view.running_jobs->itemBelow(item)); } // erase itself when rendering is finished #ifndef Q_OS_WIN outStream << "rm \"" << autoscriptFile << "\"\n"; #else outStream << "del \"" << autoscriptFile << "\"\n"; #endif if (file.error() != QFile::NoError) { KMessageBox::error(nullptr, i18n("Cannot write to file %1", autoscriptFile)); file.close(); m_blockProcessing = false; return false; } file.close(); QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser); QProcess::startDetached(autoscriptFile, QStringList()); return true; } QString RenderWidget::getFreeScriptName(const QUrl &projectName, const QString &prefix) { int ix = 0; QString scriptsFolder = m_projectFolder + QStringLiteral("scripts/"); QDir dir(m_projectFolder); dir.mkdir(QStringLiteral("scripts")); QString path; QString fileName; if (projectName.isEmpty()) { fileName = i18n("script"); } else { fileName = projectName.fileName().section(QLatin1Char('.'), 0, -2) + QLatin1Char('_'); } while (path.isEmpty() || QFile::exists(path)) { ++ix; path = scriptsFolder + prefix + fileName + QString::number(ix).rightJustified(3, '0', false) + ScriptFormat; } return path; } void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int) { RenderJobItem *renderItem = static_cast(item); if (renderItem->status() != FINISHEDJOB) { return; } new KRun(QUrl::fromLocalFile(item->text(1)), this); } void RenderWidget::errorMessage(RenderError type, const QString &message) { QString fullMessage; m_errorMessages.insert(type, message); QMapIterator i(m_errorMessages); while (i.hasNext()) { i.next(); if (!i.value().isEmpty()) { if (!fullMessage.isEmpty()) { fullMessage.append(QLatin1Char('\n')); } fullMessage.append(i.value()); } } if (!fullMessage.isEmpty()) { m_infoMessage->setMessageType(KMessageWidget::Warning); m_infoMessage->setText(fullMessage); m_infoMessage->show(); } else { m_infoMessage->hide(); } } void RenderWidget::slotUpdateEncodeThreads(int val) { KdenliveSettings::setEncodethreads(val); } void RenderWidget::slotUpdateRescaleWidth(int val) { KdenliveSettings::setDefaultrescalewidth(val); if (!m_view.rescale_keep->isChecked()) { return; } m_view.rescale_height->blockSignals(true); std::unique_ptr &profile = pCore->getCurrentProfile(); m_view.rescale_height->setValue(val * profile->height() / profile->width()); KdenliveSettings::setDefaultrescaleheight(m_view.rescale_height->value()); m_view.rescale_height->blockSignals(false); } void RenderWidget::slotUpdateRescaleHeight(int val) { KdenliveSettings::setDefaultrescaleheight(val); if (!m_view.rescale_keep->isChecked()) { return; } m_view.rescale_width->blockSignals(true); std::unique_ptr &profile = pCore->getCurrentProfile(); m_view.rescale_width->setValue(val * profile->width() / profile->height()); KdenliveSettings::setDefaultrescaleheight(m_view.rescale_width->value()); m_view.rescale_width->blockSignals(false); } void RenderWidget::slotSwitchAspectRatio() { KdenliveSettings::setRescalekeepratio(m_view.rescale_keep->isChecked()); if (m_view.rescale_keep->isChecked()) { slotUpdateRescaleWidth(m_view.rescale_width->value()); } } void RenderWidget::slotUpdateAudioLabel(int ix) { if (ix == Qt::PartiallyChecked) { m_view.export_audio->setText(i18n("Export audio (automatic)")); } else { m_view.export_audio->setText(i18n("Export audio")); } m_view.stemAudioExport->setEnabled(ix != Qt::Unchecked); } bool RenderWidget::automaticAudioExport() const { return (m_view.export_audio->checkState() == Qt::PartiallyChecked); } bool RenderWidget::selectedAudioExport() const { return (m_view.export_audio->checkState() != Qt::Unchecked); } void RenderWidget::updateProxyConfig(bool enable) { m_view.proxy_render->setHidden(!enable); } bool RenderWidget::proxyRendering() { return m_view.proxy_render->isChecked(); } bool RenderWidget::isStemAudioExportEnabled() const { return (m_view.stemAudioExport->isChecked() && m_view.stemAudioExport->isVisible() && m_view.stemAudioExport->isEnabled()); } void RenderWidget::setRescaleEnabled(bool enable) { for (int i = 0; i < m_view.rescale_box->layout()->count(); ++i) { if (m_view.rescale_box->itemAt(i)->widget()) { m_view.rescale_box->itemAt(i)->widget()->setEnabled(enable); } } } void RenderWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { switch (m_view.tabWidget->currentIndex()) { case 1: if (m_view.start_job->isEnabled()) { slotStartCurrentJob(); } break; case 2: if (m_view.start_script->isEnabled()) { slotStartScript(); } break; default: if (m_view.buttonRender->isEnabled()) { slotPrepareExport(); } break; } } else { QDialog::keyPressEvent(e); } } void RenderWidget::adjustAVQualities(int quality) { // calculate video/audio quality indexes from the general quality cursor // taking into account decreasing/increasing video/audio quality parameter double q = (double)quality / m_view.quality->maximum(); int dq = q * (m_view.video->maximum() - m_view.video->minimum()); // prevent video spinbox to update quality cursor (loop) m_view.video->blockSignals(true); m_view.video->setValue(m_view.video->property("decreasing").toBool() ? m_view.video->maximum() - dq : m_view.video->minimum() + dq); m_view.video->blockSignals(false); dq = q * (m_view.audio->maximum() - m_view.audio->minimum()); dq -= dq % m_view.audio->singleStep(); // keep a 32 pitch for bitrates m_view.audio->setValue(m_view.audio->property("decreasing").toBool() ? m_view.audio->maximum() - dq : m_view.audio->minimum() + dq); } void RenderWidget::adjustQuality(int videoQuality) { int dq = videoQuality * m_view.quality->maximum() / (m_view.video->maximum() - m_view.video->minimum()); m_view.quality->blockSignals(true); m_view.quality->setValue(m_view.video->property("decreasing").toBool() ? m_view.video->maximum() - dq : m_view.video->minimum() + dq); m_view.quality->blockSignals(false); } void RenderWidget::adjustSpeed(int speedIndex) { if (m_view.formats->currentItem()) { QStringList speeds = m_view.formats->currentItem()->data(0, SpeedsRole).toStringList(); if (speedIndex < speeds.count()) { m_view.speed->setToolTip(i18n("Codec speed parameters:\n") + speeds.at(speedIndex)); } } } void RenderWidget::checkCodecs() { Mlt::Profile p; auto *consumer = new Mlt::Consumer(p, "avformat"); if (consumer) { consumer->set("vcodec", "list"); consumer->set("acodec", "list"); consumer->set("f", "list"); consumer->start(); vcodecsList.clear(); Mlt::Properties vcodecs((mlt_properties)consumer->get_data("vcodec")); vcodecsList.reserve(vcodecs.count()); for (int i = 0; i < vcodecs.count(); ++i) { vcodecsList << QString(vcodecs.get(i)); } acodecsList.clear(); Mlt::Properties acodecs((mlt_properties)consumer->get_data("acodec")); acodecsList.reserve(acodecs.count()); for (int i = 0; i < acodecs.count(); ++i) { acodecsList << QString(acodecs.get(i)); } supportedFormats.clear(); Mlt::Properties formats((mlt_properties)consumer->get_data("f")); supportedFormats.reserve(formats.count()); for (int i = 0; i < formats.count(); ++i) { supportedFormats << QString(formats.get(i)); } delete consumer; } } void RenderWidget::slotProxyWarn(bool enableProxy) { errorMessage(ProxyWarning, enableProxy ? i18n("Rendering using low quality proxy") : QString()); } diff --git a/src/dialogs/titletemplatedialog.cpp b/src/dialogs/titletemplatedialog.cpp index 4da84962b..4b6982546 100644 --- a/src/dialogs/titletemplatedialog.cpp +++ b/src/dialogs/titletemplatedialog.cpp @@ -1,100 +1,101 @@ /* Copyright (C) 2016 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 "titletemplatedialog.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "utils/KoIconUtils.h" #include "klocalizedstring.h" #include #include #include TitleTemplateDialog::TitleTemplateDialog(const QString &folder, QWidget *parent) : QDialog(parent) { m_view.setupUi(this); // Get the list of existing templates const QStringList filter = {QStringLiteral("*.kdenlivetitle")}; const QString path = folder + QStringLiteral("/titles/"); // Project templates QDir dir(path); const QStringList templateFiles = dir.entryList(filter, QDir::Files); for (const QString &fname : templateFiles) { m_view.template_list->comboBox()->addItem(fname, dir.absoluteFilePath(fname)); } // System templates const QStringList titleTemplates = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory); for (const QString &folderpath : titleTemplates) { QDir sysdir(folderpath); const QStringList filesnames = sysdir.entryList(filter, QDir::Files); for (const QString &fname : filesnames) { m_view.template_list->comboBox()->addItem(fname, sysdir.absoluteFilePath(fname)); } } if (m_view.template_list->comboBox()->count() > 0) { m_view.buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } int current = m_view.template_list->comboBox()->findText(KdenliveSettings::selected_template()); if (current > -1) { m_view.template_list->comboBox()->setCurrentIndex(current); } const QStringList mimeTypeFilters = {QStringLiteral("application/x-kdenlivetitle")}; m_view.template_list->setFilter(mimeTypeFilters.join(' ')); - connect(m_view.template_list->comboBox(), static_cast(&KComboBox::currentIndexChanged), this, &TitleTemplateDialog::updatePreview); + connect(m_view.template_list->comboBox(), static_cast(&KComboBox::currentIndexChanged), this, + &TitleTemplateDialog::updatePreview); updatePreview(); } QString TitleTemplateDialog::selectedTemplate() const { QString textTemplate = m_view.template_list->comboBox()->itemData(m_view.template_list->comboBox()->currentIndex()).toString(); if (textTemplate.isEmpty()) { textTemplate = m_view.template_list->comboBox()->currentText(); } return textTemplate; } QString TitleTemplateDialog::selectedText() const { return m_view.description->toPlainText(); } void TitleTemplateDialog::resizeEvent(QResizeEvent *event) { updatePreview(); QWidget::resizeEvent(event); } void TitleTemplateDialog::updatePreview() { QString textTemplate = m_view.template_list->comboBox()->itemData(m_view.template_list->comboBox()->currentIndex()).toString(); if (textTemplate.isEmpty()) { textTemplate = m_view.template_list->comboBox()->currentText(); } QPixmap pix = KThumb::getImage(QUrl::fromLocalFile(textTemplate), m_view.preview->width()); m_view.preview->setPixmap(pix); KdenliveSettings::setSelected_template(m_view.template_list->comboBox()->currentText()); } diff --git a/src/dialogs/wizard.cpp b/src/dialogs/wizard.cpp index dee5739fa..79775f661 100644 --- a/src/dialogs/wizard.cpp +++ b/src/dialogs/wizard.cpp @@ -1,883 +1,887 @@ /*************************************************************************** * Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@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) 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 "wizard.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "profilesdialog.h" #include "utils/KoIconUtils.h" #include "utils/thememanager.h" #ifdef USE_V4L #include "capture/v4lcapture.h" #endif #include "core.h" #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include // Recommended MLT version const int mltVersionMajor = MLT_MIN_MAJOR_VERSION; const int mltVersionMinor = MLT_MIN_MINOR_VERSION; const int mltVersionRevision = MLT_MIN_PATCH_VERSION; static const char kdenlive_version[] = KDENLIVE_VERSION; static QStringList acodecsList; static QStringList vcodecsList; MyWizardPage::MyWizardPage(QWidget *parent) : QWizardPage(parent) , m_isComplete(false) { } void MyWizardPage::setComplete(bool complete) { m_isComplete = complete; } bool MyWizardPage::isComplete() const { return m_isComplete; } Wizard::Wizard(bool autoClose, QWidget *parent) : QWizard(parent) , m_systemCheckIsOk(false) , m_brokenModule(false) { // Check color theme ThemeManager::instance()->initDarkTheme(); setWindowTitle(i18n("Welcome to Kdenlive")); int logoHeight = fontMetrics().height() * 2.5; setWizardStyle(QWizard::ModernStyle); setOption(QWizard::NoBackButtonOnLastPage, true); // setOption(QWizard::ExtendedWatermarkPixmap, false); m_page = new MyWizardPage(this); m_page->setTitle(i18n("Welcome to Kdenlive %1", QString(kdenlive_version))); m_page->setSubTitle(i18n("Using MLT %1", mlt_version_get_string())); setPixmap(QWizard::LogoPixmap, KoIconUtils::themedIcon(QStringLiteral(":/pics/kdenlive.png")).pixmap(logoHeight, logoHeight)); m_startLayout = new QVBoxLayout; m_errorWidget = new KMessageWidget(this); m_startLayout->addWidget(m_errorWidget); m_errorWidget->setCloseButtonVisible(false); m_errorWidget->hide(); m_page->setLayout(m_startLayout); addPage(m_page); /*QWizardPage *page2 = new QWizardPage; page2->setTitle(i18n("Video Standard")); m_standard.setupUi(page2);*/ setButtonText(QWizard::CancelButton, i18n("Abort")); setButtonText(QWizard::FinishButton, i18n("OK")); slotCheckMlt(); if (!m_errors.isEmpty() || !m_warnings.isEmpty() || !m_infos.isEmpty()) { QLabel *lab = new QLabel(this); lab->setText(i18n("Startup error or warning, check our online manual.")); connect(lab, &QLabel::linkActivated, this, &Wizard::slotOpenManual); m_startLayout->addWidget(lab); } else { // Everything is ok, auto close the wizard m_page->setComplete(true); if (autoClose) { QTimer::singleShot(0, this, &QDialog::accept); return; } auto *lab = new KMessageWidget(this); lab->setText(i18n("Codecs have been updated, everything seems fine.")); lab->setMessageType(KMessageWidget::Positive); lab->setCloseButtonVisible(false); m_startLayout->addWidget(lab); setOption(QWizard::NoCancelButton, true); return; } if (!m_errors.isEmpty()) { auto *errorLabel = new KMessageWidget(this); errorLabel->setText(QStringLiteral("
    ") + m_errors + QStringLiteral("
")); errorLabel->setMessageType(KMessageWidget::Error); errorLabel->setWordWrap(true); errorLabel->setCloseButtonVisible(false); m_startLayout->addWidget(errorLabel); m_page->setComplete(false); errorLabel->show(); if (!autoClose) { setButtonText(QWizard::CancelButton, i18n("Close")); } } else { m_page->setComplete(true); if (!autoClose) { setOption(QWizard::NoCancelButton, true); } } if (!m_warnings.isEmpty()) { auto *errorLabel = new KMessageWidget(this); errorLabel->setText(QStringLiteral("
    ") + m_warnings + QStringLiteral("
")); errorLabel->setMessageType(KMessageWidget::Warning); errorLabel->setWordWrap(true); errorLabel->setCloseButtonVisible(false); m_startLayout->addWidget(errorLabel); errorLabel->show(); } if (!m_infos.isEmpty()) { auto *errorLabel = new KMessageWidget(this); errorLabel->setText(QStringLiteral("
    ") + m_infos + QStringLiteral("
")); errorLabel->setMessageType(KMessageWidget::Information); errorLabel->setWordWrap(true); errorLabel->setCloseButtonVisible(false); m_startLayout->addWidget(errorLabel); errorLabel->show(); } -// build profiles lists -/*QMap profilesInfo = ProfilesDialog::getProfilesInfo(); -QMap::const_iterator i = profilesInfo.constBegin(); -while (i != profilesInfo.constEnd()) { - QMap< QString, QString > profileData = ProfilesDialog::getSettingsFromFile(i.key()); - if (profileData.value(QStringLiteral("width")) == QLatin1String("720")) m_dvProfiles.insert(i.value(), i.key()); - else if (profileData.value(QStringLiteral("width")).toInt() >= 1080) m_hdvProfiles.insert(i.value(), i.key()); - else m_otherProfiles.insert(i.value(), i.key()); - ++i; -} - -m_standard.button_all->setChecked(true); -connect(m_standard.button_all, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); -connect(m_standard.button_hdv, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); -connect(m_standard.button_dv, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); -slotCheckStandard(); -connect(m_standard.profiles_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckSelectedItem())); - -// select default profile -if (!KdenliveSettings::default_profile().isEmpty()) { - for (int i = 0; i < m_standard.profiles_list->count(); ++i) { - if (m_standard.profiles_list->item(i)->data(Qt::UserRole).toString() == KdenliveSettings::default_profile()) { - m_standard.profiles_list->setCurrentRow(i); - m_standard.profiles_list->scrollToItem(m_standard.profiles_list->currentItem()); - break; + // build profiles lists + /*QMap profilesInfo = ProfilesDialog::getProfilesInfo(); + QMap::const_iterator i = profilesInfo.constBegin(); + while (i != profilesInfo.constEnd()) { + QMap< QString, QString > profileData = ProfilesDialog::getSettingsFromFile(i.key()); + if (profileData.value(QStringLiteral("width")) == QLatin1String("720")) m_dvProfiles.insert(i.value(), i.key()); + else if (profileData.value(QStringLiteral("width")).toInt() >= 1080) m_hdvProfiles.insert(i.value(), i.key()); + else m_otherProfiles.insert(i.value(), i.key()); + ++i; + } + + m_standard.button_all->setChecked(true); + connect(m_standard.button_all, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); + connect(m_standard.button_hdv, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); + connect(m_standard.button_dv, SIGNAL(toggled(bool)), this, SLOT(slotCheckStandard())); + slotCheckStandard(); + connect(m_standard.profiles_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckSelectedItem())); + + // select default profile + if (!KdenliveSettings::default_profile().isEmpty()) { + for (int i = 0; i < m_standard.profiles_list->count(); ++i) { + if (m_standard.profiles_list->item(i)->data(Qt::UserRole).toString() == KdenliveSettings::default_profile()) { + m_standard.profiles_list->setCurrentRow(i); + m_standard.profiles_list->scrollToItem(m_standard.profiles_list->currentItem()); + break; + } + } } - } -} -setPage(2, page2); - -QWizardPage *page3 = new QWizardPage; -page3->setTitle(i18n("Additional Settings")); -m_extra.setupUi(page3); -m_extra.projectfolder->setMode(KFile::Directory); -m_extra.projectfolder->setUrl(QUrl(KdenliveSettings::defaultprojectfolder())); -m_extra.videothumbs->setChecked(KdenliveSettings::videothumbnails()); -m_extra.audiothumbs->setChecked(KdenliveSettings::audiothumbnails()); -m_extra.autosave->setChecked(KdenliveSettings::crashrecovery()); -connect(m_extra.videothumbs, SIGNAL(stateChanged(int)), this, SLOT(slotCheckThumbs())); -connect(m_extra.audiothumbs, SIGNAL(stateChanged(int)), this, SLOT(slotCheckThumbs())); -slotCheckThumbs(); -addPage(page3);*/ + setPage(2, page2); + + QWizardPage *page3 = new QWizardPage; + page3->setTitle(i18n("Additional Settings")); + m_extra.setupUi(page3); + m_extra.projectfolder->setMode(KFile::Directory); + m_extra.projectfolder->setUrl(QUrl(KdenliveSettings::defaultprojectfolder())); + m_extra.videothumbs->setChecked(KdenliveSettings::videothumbnails()); + m_extra.audiothumbs->setChecked(KdenliveSettings::audiothumbnails()); + m_extra.autosave->setChecked(KdenliveSettings::crashrecovery()); + connect(m_extra.videothumbs, SIGNAL(stateChanged(int)), this, SLOT(slotCheckThumbs())); + connect(m_extra.audiothumbs, SIGNAL(stateChanged(int)), this, SLOT(slotCheckThumbs())); + slotCheckThumbs(); + addPage(page3);*/ #ifndef Q_WS_MAC -/*QWizardPage *page6 = new QWizardPage; -page6->setTitle(i18n("Capture device")); -m_capture.setupUi(page6); -bool found_decklink = Render::getBlackMagicDeviceList(m_capture.decklink_devices); -KdenliveSettings::setDecklink_device_found(found_decklink); -if (found_decklink) m_capture.decklink_status->setText(i18n("Default Blackmagic Decklink card:")); -else m_capture.decklink_status->setText(i18n("No Blackmagic Decklink device found")); -connect(m_capture.decklink_devices, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDecklinkDevice(int))); -connect(m_capture.button_reload, SIGNAL(clicked()), this, SLOT(slotDetectWebcam())); -connect(m_capture.v4l_devices, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCaptureParameters())); -connect(m_capture.v4l_formats, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSaveCaptureFormat())); -m_capture.button_reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));*/ + /*QWizardPage *page6 = new QWizardPage; + page6->setTitle(i18n("Capture device")); + m_capture.setupUi(page6); + bool found_decklink = Render::getBlackMagicDeviceList(m_capture.decklink_devices); + KdenliveSettings::setDecklink_device_found(found_decklink); + if (found_decklink) m_capture.decklink_status->setText(i18n("Default Blackmagic Decklink card:")); + else m_capture.decklink_status->setText(i18n("No Blackmagic Decklink device found")); + connect(m_capture.decklink_devices, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDecklinkDevice(int))); + connect(m_capture.button_reload, SIGNAL(clicked()), this, SLOT(slotDetectWebcam())); + connect(m_capture.v4l_devices, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateCaptureParameters())); + connect(m_capture.v4l_formats, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSaveCaptureFormat())); + m_capture.button_reload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));*/ #endif // listViewDelegate = new WizardDelegate(treeWidget); // m_check.programList->setItemDelegate(listViewDelegate); // slotDetectWebcam(); // QTimer::singleShot(500, this, SLOT(slotCheckMlt())); } void Wizard::slotDetectWebcam() { #ifdef USE_V4L m_capture.v4l_devices->blockSignals(true); m_capture.v4l_devices->clear(); // Video 4 Linux device detection for (int i = 0; i < 10; ++i) { QString path = "/dev/video" + QString::number(i); if (QFile::exists(path)) { QStringList deviceInfo = V4lCaptureHandler::getDeviceName(path.toUtf8().constData()); if (!deviceInfo.isEmpty()) { m_capture.v4l_devices->addItem(deviceInfo.at(0), path); m_capture.v4l_devices->setItemData(m_capture.v4l_devices->count() - 1, deviceInfo.at(1), Qt::UserRole + 1); } } } if (m_capture.v4l_devices->count() > 0) { m_capture.v4l_status->setText(i18n("Default video4linux device:")); // select default device bool found = false; for (int i = 0; i < m_capture.v4l_devices->count(); ++i) { QString device = m_capture.v4l_devices->itemData(i).toString(); if (device == KdenliveSettings::video4vdevice()) { m_capture.v4l_devices->setCurrentIndex(i); found = true; break; } } slotUpdateCaptureParameters(); if (!found) { m_capture.v4l_devices->setCurrentIndex(0); } } else { m_capture.v4l_status->setText(i18n("No device found, plug your webcam and refresh.")); } m_capture.v4l_devices->blockSignals(false); #endif /* USE_V4L */ } void Wizard::slotUpdateCaptureParameters() { QString device = m_capture.v4l_devices->itemData(m_capture.v4l_devices->currentIndex()).toString(); if (!device.isEmpty()) { KdenliveSettings::setVideo4vdevice(device); } QString formats = m_capture.v4l_devices->itemData(m_capture.v4l_devices->currentIndex(), Qt::UserRole + 1).toString(); m_capture.v4l_formats->blockSignals(true); m_capture.v4l_formats->clear(); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } if (ProfileRepository::get()->profileExists(dir.absoluteFilePath(QStringLiteral("video4linux")))) { auto &profileInfo = ProfileRepository::get()->getProfile(dir.absoluteFilePath(QStringLiteral("video4linux"))); m_capture.v4l_formats->addItem(i18n("Current settings (%1x%2, %3/%4fps)", profileInfo->width(), profileInfo->height(), profileInfo->frame_rate_num(), profileInfo->frame_rate_den()), QStringList() << QStringLiteral("unknown") << QString::number(profileInfo->width()) << QString::number(profileInfo->height()) << QString::number(profileInfo->frame_rate_num()) << QString::number(profileInfo->frame_rate_den())); } QStringList pixelformats = formats.split('>', QString::SkipEmptyParts); QString itemSize; QString pixelFormat; QStringList itemRates; for (int i = 0; i < pixelformats.count(); ++i) { QString format = pixelformats.at(i).section(QLatin1Char(':'), 0, 0); QStringList sizes = pixelformats.at(i).split(':', QString::SkipEmptyParts); pixelFormat = sizes.takeFirst(); for (int j = 0; j < sizes.count(); ++j) { itemSize = sizes.at(j).section(QLatin1Char('='), 0, 0); itemRates = sizes.at(j).section(QLatin1Char('='), 1, 1).split(QLatin1Char(','), QString::SkipEmptyParts); for (int k = 0; k < itemRates.count(); ++k) { QString formatDescription = QLatin1Char('[') + format + QStringLiteral("] ") + itemSize + QStringLiteral(" (") + itemRates.at(k) + QLatin1Char(')'); if (m_capture.v4l_formats->findText(formatDescription) == -1) { - m_capture.v4l_formats->addItem(formatDescription, - QStringList() - << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) - << itemRates.at(k).section(QLatin1Char('/'), 0, 0) << itemRates.at(k).section(QLatin1Char('/'), 1, 1)); + m_capture.v4l_formats->addItem(formatDescription, QStringList() << format << itemSize.section('x', 0, 0) << itemSize.section('x', 1, 1) + << itemRates.at(k).section(QLatin1Char('/'), 0, 0) + << itemRates.at(k).section(QLatin1Char('/'), 1, 1)); } } } } if (!dir.exists(QStringLiteral("video4linux"))) { if (m_capture.v4l_formats->count() > 9) { slotSaveCaptureFormat(); } else { // No existing profile and no autodetected profiles std::unique_ptr profileInfo(new ProfileParam(pCore->getCurrentProfile().get())); profileInfo->m_width = 320; profileInfo->m_height = 200; profileInfo->m_frame_rate_num = 15; profileInfo->m_frame_rate_den = 1; profileInfo->m_display_aspect_num = 4; profileInfo->m_display_aspect_den = 3; profileInfo->m_sample_aspect_num = 1; profileInfo->m_sample_aspect_den = 1; profileInfo->m_progressive = 1; profileInfo->m_colorspace = 601; ProfileRepository::get()->saveProfile(profileInfo.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); m_capture.v4l_formats->addItem(i18n("Default settings (%1x%2, %3/%4fps)", profileInfo->width(), profileInfo->height(), profileInfo->frame_rate_num(), profileInfo->frame_rate_den()), QStringList() << QStringLiteral("unknown") << QString::number(profileInfo->width()) << QString::number(profileInfo->height()) << QString::number(profileInfo->frame_rate_num()) << QString::number(profileInfo->frame_rate_den())); } } m_capture.v4l_formats->blockSignals(false); } void Wizard::checkMltComponents() { m_brokenModule = false; Mlt::Repository *repository = Mlt::Factory::init(); if (!repository) { m_errors.append(i18n("
  • Cannot start MLT backend, check your installation
  • ")); m_systemCheckIsOk = false; } else { int mltVersion = (mltVersionMajor << 16) + (mltVersionMinor << 8) + mltVersionRevision; int runningVersion = mlt_version_get_int(); if (runningVersion < mltVersion) { m_errors.append( i18n("
  • Unsupported MLT version
    Please upgrade to %1.%2.%3
  • ", mltVersionMajor, mltVersionMinor, mltVersionRevision)); m_systemCheckIsOk = false; } // Retrieve the list of available transitions. Mlt::Properties *producers = repository->producers(); QStringList producersItemList; producersItemList.reserve(producers->count()); for (int i = 0; i < producers->count(); ++i) { producersItemList << producers->get_name(i); } delete producers; // Check that we have the frei0r effects installed Mlt::Properties *filters = repository->filters(); bool hasFrei0r = false; QString filterName; for (int i = 0; i < filters->count(); ++i) { filterName = filters->get_name(i); if (filterName.startsWith(QStringLiteral("frei0r."))) { hasFrei0r = true; break; } } delete filters; if (!hasFrei0r) { // Frei0r effects not found m_warnings.append( i18n("
  • Missing package: Frei0r effects (frei0r-plugins)
    provides many effects and transitions. Install recommended
  • ")); } #ifndef Q_OS_WIN // Check that we have the breeze icon theme installed const QStringList iconPaths = QIcon::themeSearchPaths(); bool hasBreeze = false; for (const QString &path : iconPaths) { QDir dir(path); if (dir.exists(QStringLiteral("breeze"))) { hasBreeze = true; break; } } if (!hasBreeze) { // Breeze icons not found m_warnings.append( i18n("
  • Missing package: Breeze icons (breeze-icon-theme)
    provides many icons used in Kdenlive. Install recommended
  • ")); } #endif Mlt::Properties *consumers = repository->consumers(); QStringList consumersItemList; consumersItemList.reserve(consumers->count()); for (int i = 0; i < consumers->count(); ++i) { consumersItemList << consumers->get_name(i); } delete consumers; if (consumersItemList.contains(QStringLiteral("sdl2"))) { // MLT >= 6.6.0 and SDL2 module KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl2_audio")); KdenliveSettings::setAudiobackend(QStringLiteral("sdl2_audio")); } else if (consumersItemList.contains(QStringLiteral("sdl"))) { // MLT < 6.6.0 KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl_audio")); KdenliveSettings::setAudiobackend(QStringLiteral("sdl_audio")); } else if (consumersItemList.contains(QStringLiteral("rtaudio"))) { KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl2_audio")); KdenliveSettings::setAudiobackend(QStringLiteral("rtaudio")); } else { // SDL module m_errors.append(i18n("
  • Missing MLT module: sdl or rtaudio
    required for audio output
  • ")); m_systemCheckIsOk = false; } // AVformat module Mlt::Consumer *consumer = nullptr; Mlt::Profile p; if (consumersItemList.contains(QStringLiteral("avformat"))) { consumer = new Mlt::Consumer(p, "avformat"); } if (consumer == nullptr || !consumer->is_valid()) { m_warnings.append(i18n("
  • Missing MLT module: avformat (FFmpeg)
    required for audio/video
  • ")); m_brokenModule = true; } else { consumer->set("vcodec", "list"); consumer->set("acodec", "list"); consumer->set("f", "list"); consumer->start(); Mlt::Properties vcodecs((mlt_properties)consumer->get_data("vcodec")); for (int i = 0; i < vcodecs.count(); ++i) { vcodecsList << QString(vcodecs.get(i)); } Mlt::Properties acodecs((mlt_properties)consumer->get_data("acodec")); for (int i = 0; i < acodecs.count(); ++i) { acodecsList << QString(acodecs.get(i)); } checkMissingCodecs(); delete consumer; } // Image module if (!producersItemList.contains(QStringLiteral("qimage")) && !producersItemList.contains(QStringLiteral("pixbuf"))) { m_warnings.append(i18n("
  • Missing MLT module: qimage or pixbuf
    required for images and titles
  • ")); m_brokenModule = true; } // Titler module if (!producersItemList.contains(QStringLiteral("kdenlivetitle"))) { m_warnings.append(i18n("
  • Missing MLT module: kdenlivetitle
    required to create titles
  • ")); KdenliveSettings::setHastitleproducer(false); m_brokenModule = true; } else { KdenliveSettings::setHastitleproducer(true); } } if (m_systemCheckIsOk && !m_brokenModule) { // everything is ok return; } if (!m_systemCheckIsOk || m_brokenModule) { // Something is wrong with install if (!m_systemCheckIsOk) { // WARN } } else { // OK } } void Wizard::checkMissingCodecs() { bool replaceVorbisCodec = false; if (acodecsList.contains(QStringLiteral("libvorbis"))) { replaceVorbisCodec = true; } bool replaceLibfaacCodec = false; if (!acodecsList.contains(QStringLiteral("aac")) && acodecsList.contains(QStringLiteral("libfaac"))) { replaceLibfaacCodec = true; } QStringList profilesList; profilesList << QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("export/profiles.xml")); QDir directory = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/")); QStringList filter; filter << QStringLiteral("*.xml"); const QStringList fileList = directory.entryList(filter, QDir::Files); for (const QString &filename : fileList) { profilesList << directory.absoluteFilePath(filename); } // We should parse customprofiles.xml in last position, so that user profiles // can also override profiles installed by KNewStuff QStringList requiredACodecs; QStringList requiredVCodecs; for (const QString &filename : profilesList) { QDomDocument doc; QFile file(filename); doc.setContent(&file, false); file.close(); QString std; QString format; QDomNodeList profiles = doc.elementsByTagName(QStringLiteral("profile")); for (int i = 0; i < profiles.count(); ++i) { std = profiles.at(i).toElement().attribute(QStringLiteral("args")); format.clear(); if (std.startsWith(QLatin1String("acodec="))) { format = std.section(QStringLiteral("acodec="), 1, 1); } else if (std.contains(QStringLiteral(" acodec="))) { format = std.section(QStringLiteral(" acodec="), 1, 1); } if (!format.isEmpty()) { requiredACodecs << format.section(QLatin1Char(' '), 0, 0).toLower(); } format.clear(); if (std.startsWith(QLatin1String("vcodec="))) { format = std.section(QStringLiteral("vcodec="), 1, 1); } else if (std.contains(QStringLiteral(" vcodec="))) { format = std.section(QStringLiteral(" vcodec="), 1, 1); } if (!format.isEmpty()) { requiredVCodecs << format.section(QLatin1Char(' '), 0, 0).toLower(); } } } requiredACodecs.removeDuplicates(); requiredVCodecs.removeDuplicates(); if (replaceVorbisCodec) { int ix = requiredACodecs.indexOf(QStringLiteral("vorbis")); if (ix > -1) { requiredACodecs.replace(ix, QStringLiteral("libvorbis")); } } if (replaceLibfaacCodec) { int ix = requiredACodecs.indexOf(QStringLiteral("aac")); if (ix > -1) { requiredACodecs.replace(ix, QStringLiteral("libfaac")); } } for (int i = 0; i < acodecsList.count(); ++i) { requiredACodecs.removeAll(acodecsList.at(i)); } for (int i = 0; i < vcodecsList.count(); ++i) { requiredVCodecs.removeAll(vcodecsList.at(i)); } /* * Info about missing codecs is given in render widget, no need to put this at first start * if (!requiredACodecs.isEmpty() || !requiredVCodecs.isEmpty()) { QString missing = requiredACodecs.join(QLatin1Char(',')); if (!missing.isEmpty() && !requiredVCodecs.isEmpty()) { missing.append(','); } missing.append(requiredVCodecs.join(QLatin1Char(','))); missing.prepend(i18n("The following codecs were not found on your system. Check our online manual if you need them: ")); m_infos.append(QString("
  • %1
  • ").arg(missing)); }*/ } void Wizard::slotCheckPrograms() { bool allIsOk = true; // Check first in same folder as melt exec const QStringList mltpath = QStringList() << QFileInfo(KdenliveSettings::rendererpath()).canonicalPath(); QString exepath = QStandardPaths::findExecutable(QStringLiteral("ffmpeg%1").arg(FFMPEG_SUFFIX), mltpath); if (exepath.isEmpty()) { exepath = QStandardPaths::findExecutable(QStringLiteral("ffmpeg%1").arg(FFMPEG_SUFFIX)); } QString playpath = QStandardPaths::findExecutable(QStringLiteral("ffplay%1").arg(FFMPEG_SUFFIX), mltpath); if (playpath.isEmpty()) { playpath = QStandardPaths::findExecutable(QStringLiteral("ffplay%1").arg(FFMPEG_SUFFIX)); } QString probepath = QStandardPaths::findExecutable(QStringLiteral("ffprobe%1").arg(FFMPEG_SUFFIX), mltpath); if (probepath.isEmpty()) { probepath = QStandardPaths::findExecutable(QStringLiteral("ffprobe%1").arg(FFMPEG_SUFFIX)); } if (exepath.isEmpty()) { // Check for libav version exepath = QStandardPaths::findExecutable(QStringLiteral("avconv")); if (exepath.isEmpty()) { m_warnings.append(i18n("
  • Missing app: ffmpeg
    required for proxy clips and transcoding
  • ")); allIsOk = false; } } if (playpath.isEmpty()) { // Check for libav version playpath = QStandardPaths::findExecutable(QStringLiteral("avplay")); if (playpath.isEmpty()) { m_infos.append(i18n("
  • Missing app: ffplay
    recommended for some preview jobs
  • ")); } } if (probepath.isEmpty()) { // Check for libav version probepath = QStandardPaths::findExecutable(QStringLiteral("avprobe")); if (probepath.isEmpty()) { m_infos.append(i18n("
  • Missing app: ffprobe
    recommended for extra clip analysis
  • ")); } } if (!exepath.isEmpty()) { KdenliveSettings::setFfmpegpath(exepath); } if (!playpath.isEmpty()) { KdenliveSettings::setFfplaypath(playpath); } if (!probepath.isEmpty()) { KdenliveSettings::setFfprobepath(probepath); } // Deprecated /* #ifndef Q_WS_MAC item = new QTreeWidgetItem(m_treeWidget, QStringList() << QString() << i18n("dvgrab")); item->setData(1, Qt::UserRole, i18n("Required for firewire capture")); item->setSizeHint(0, m_itemSize); if (QStandardPaths::findExecutable(QStringLiteral("dvgrab")).isEmpty()) item->setIcon(0, m_badIcon); else item->setIcon(0, m_okIcon); #endif */ // set up some default applications QString program; if (KdenliveSettings::defaultimageapp().isEmpty()) { program = QStandardPaths::findExecutable(QStringLiteral("gimp")); if (program.isEmpty()) { program = QStandardPaths::findExecutable(QStringLiteral("krita")); } if (!program.isEmpty()) { KdenliveSettings::setDefaultimageapp(program); } } if (KdenliveSettings::defaultaudioapp().isEmpty()) { program = QStandardPaths::findExecutable(QStringLiteral("audacity")); if (program.isEmpty()) { program = QStandardPaths::findExecutable(QStringLiteral("traverso")); } if (!program.isEmpty()) { KdenliveSettings::setDefaultaudioapp(program); } } if (allIsOk) { // OK } else { // WRONG } } void Wizard::installExtraMimes(const QString &baseName, const QStringList &globs) { QMimeDatabase db; QString mimefile = baseName; mimefile.replace('/', '-'); QMimeType mime = db.mimeTypeForName(baseName); QStringList missingGlobs; for (const QString &glob : globs) { QMimeType type = db.mimeTypeForFile(glob, QMimeDatabase::MatchExtension); QString mimeName = type.name(); if (!mimeName.contains(QStringLiteral("audio")) && !mimeName.contains(QStringLiteral("video"))) { missingGlobs << glob; } } if (missingGlobs.isEmpty()) { return; } if (!mime.isValid() || mime.isDefault()) { qCDebug(KDENLIVE_LOG) << "MIME type " << baseName << " not found"; } else { QStringList extensions = mime.globPatterns(); QString comment = mime.comment(); for (const QString &glob : missingGlobs) { if (!extensions.contains(glob)) { extensions << glob; } } // qCDebug(KDENLIVE_LOG) << "EXTS: " << extensions; QDir mimeDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages/")); if (!mimeDir.exists()) { mimeDir.mkpath(QStringLiteral(".")); } QString packageFileName = mimeDir.absoluteFilePath(mimefile + QStringLiteral(".xml")); // qCDebug(KDENLIVE_LOG) << "INSTALLING NEW MIME TO: " << packageFileName; QFile packageFile(packageFileName); if (!packageFile.open(QIODevice::WriteOnly)) { qCCritical(KDENLIVE_LOG) << "Couldn't open" << packageFileName << "for writing"; return; } QXmlStreamWriter writer(&packageFile); writer.setAutoFormatting(true); writer.writeStartDocument(); const QString nsUri = QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info"); writer.writeDefaultNamespace(nsUri); writer.writeStartElement(QStringLiteral("mime-info")); writer.writeStartElement(nsUri, QStringLiteral("mime-type")); writer.writeAttribute(QStringLiteral("type"), baseName); if (!comment.isEmpty()) { writer.writeStartElement(nsUri, QStringLiteral("comment")); writer.writeCharacters(comment); writer.writeEndElement(); // comment } for (const QString &pattern : extensions) { writer.writeStartElement(nsUri, QStringLiteral("glob")); writer.writeAttribute(QStringLiteral("pattern"), pattern); writer.writeEndElement(); // glob } writer.writeEndElement(); // mime-info writer.writeEndElement(); // mime-type writer.writeEndDocument(); } } void Wizard::runUpdateMimeDatabase() { const QString localPackageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/"); // Q_ASSERT(!localPackageDir.isEmpty()); KProcess proc; proc << QStringLiteral("update-mime-database"); proc << localPackageDir; const int exitCode = proc.execute(); if (exitCode != 0) { qCWarning(KDENLIVE_LOG) << proc.program() << "exited with error code" << exitCode; } } void Wizard::slotCheckStandard() { m_standard.profiles_list->clear(); QStringList profiles; if (!m_standard.button_hdv->isChecked()) { // DV standard QMapIterator i(m_dvProfiles); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key(), m_standard.profiles_list); item->setData(Qt::UserRole, i.value()); } } if (!m_standard.button_dv->isChecked()) { // HDV standard QMapIterator i(m_hdvProfiles); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key(), m_standard.profiles_list); item->setData(Qt::UserRole, i.value()); } } if (m_standard.button_all->isChecked()) { QMapIterator i(m_otherProfiles); while (i.hasNext()) { i.next(); auto *item = new QListWidgetItem(i.key(), m_standard.profiles_list); item->setData(Qt::UserRole, i.value()); } // m_standard.profiles_list->sortItems(); } for (int i = 0; i < m_standard.profiles_list->count(); ++i) { QListWidgetItem *item = m_standard.profiles_list->item(i); std::unique_ptr &curProfile = ProfileRepository::get()->getProfile(item->data(Qt::UserRole).toString()); - const QString infoString = QStringLiteral("") + i18n("Frame size:") + QStringLiteral(" %1x%2
    ").arg(curProfile->width()).arg(curProfile->height()) + i18n("Frame rate:") + QStringLiteral(" %1/%2
    ").arg(curProfile->frame_rate_num()).arg(curProfile->frame_rate_den()) + i18n("Pixel aspect ratio:") + QStringLiteral("%1/%2
    ").arg(curProfile->sample_aspect_num()).arg(curProfile->sample_aspect_den()) + i18n("Display aspect ratio:") + QStringLiteral(" %1/%2").arg(curProfile->display_aspect_num()).arg(curProfile->display_aspect_den()); - + const QString infoString = + QStringLiteral("") + i18n("Frame size:") + + QStringLiteral(" %1x%2
    ").arg(curProfile->width()).arg(curProfile->height()) + i18n("Frame rate:") + + QStringLiteral(" %1/%2
    ").arg(curProfile->frame_rate_num()).arg(curProfile->frame_rate_den()) + i18n("Pixel aspect ratio:") + + QStringLiteral("%1/%2
    ").arg(curProfile->sample_aspect_num()).arg(curProfile->sample_aspect_den()) + + i18n("Display aspect ratio:") + QStringLiteral(" %1/%2").arg(curProfile->display_aspect_num()).arg(curProfile->display_aspect_den()); + /*const QString infoString = QStringLiteral("" + i18n("Frame size:") + QStringLiteral(" %1x%2
    ") + i18n("Frame rate:") + QStringLiteral(" %3/%4
    ") + i18n("Pixel aspect ratio:") + QStringLiteral("%5/%6
    ") + i18n("Display aspect ratio:") + QStringLiteral(" %7/%8")) .arg(QString::number(curProfile->width()), QString::number(curProfile->height()), QString::number(curProfile->frame_rate_num()), QString::number(curProfile->frame_rate_den()), QString::number(curProfile->sample_aspect_num()), QString::number(curProfile->sample_aspect_den()), QString::number(curProfile->display_aspect_num()), QString::number(curProfile->display_aspect_den()));*/ item->setToolTip(infoString); } m_standard.profiles_list->setSortingEnabled(true); m_standard.profiles_list->setCurrentRow(0); } void Wizard::slotCheckSelectedItem() { // Make sure we always have an item highlighted m_standard.profiles_list->setCurrentRow(m_standard.profiles_list->currentRow()); } void Wizard::adjustSettings() { // if (m_extra.installmimes->isChecked()) { { QStringList globs; globs << QStringLiteral("*.mts") << QStringLiteral("*.m2t") << QStringLiteral("*.mod") << QStringLiteral("*.ts") << QStringLiteral("*.m2ts") << QStringLiteral("*.m2v"); installExtraMimes(QStringLiteral("video/mpeg"), globs); globs.clear(); globs << QStringLiteral("*.dv"); installExtraMimes(QStringLiteral("video/dv"), globs); runUpdateMimeDatabase(); } } void Wizard::slotCheckMlt() { QString errorMessage; if (KdenliveSettings::rendererpath().isEmpty()) { errorMessage.append(i18n("Your MLT installation cannot be found. Install MLT and restart Kdenlive.\n")); } if (!errorMessage.isEmpty()) { errorMessage.prepend(QStringLiteral("%1
    ").arg(i18n("Fatal Error"))); QLabel *pix = new QLabel(); pix->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(30)); QLabel *label = new QLabel(errorMessage); label->setWordWrap(true); m_startLayout->addSpacing(40); m_startLayout->addWidget(pix); m_startLayout->addWidget(label); m_systemCheckIsOk = false; // Warn } else { m_systemCheckIsOk = true; } if (m_systemCheckIsOk) { checkMltComponents(); } slotCheckPrograms(); } bool Wizard::isOk() const { return m_systemCheckIsOk; } void Wizard::slotOpenManual() { KRun::runUrl(QUrl(QStringLiteral("https://kdenlive.org/troubleshooting")), QStringLiteral("text/html"), this); } void Wizard::slotShowWebInfos() { KRun::runUrl(QUrl("http://kdenlive.org/discover/" + QString(kdenlive_version).section(QLatin1Char(' '), 0, 0)), QStringLiteral("text/html"), this); } void Wizard::slotSaveCaptureFormat() { QStringList format = m_capture.v4l_formats->itemData(m_capture.v4l_formats->currentIndex()).toStringList(); if (format.isEmpty()) { return; } std::unique_ptr profile(new ProfileParam(pCore->getCurrentProfile().get())); profile->m_description = QStringLiteral("Video4Linux capture"); profile->m_colorspace = 601; profile->m_width = format.at(1).toInt(); profile->m_height = format.at(2).toInt(); profile->m_sample_aspect_num = 1; profile->m_sample_aspect_den = 1; profile->m_display_aspect_num = format.at(1).toInt(); profile->m_display_aspect_den = format.at(2).toInt(); profile->m_frame_rate_num = format.at(3).toInt(); profile->m_frame_rate_den = format.at(4).toInt(); profile->m_progressive = 1; QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } ProfileRepository::get()->saveProfile(profile.get(), dir.absoluteFilePath(QStringLiteral("video4linux"))); } void Wizard::slotUpdateDecklinkDevice(uint captureCard) { KdenliveSettings::setDecklink_capturedevice(captureCard); } diff --git a/src/doc/documentchecker.cpp b/src/doc/documentchecker.cpp index f95e0dd0c..b20b57d39 100644 --- a/src/doc/documentchecker.cpp +++ b/src/doc/documentchecker.cpp @@ -1,1175 +1,1177 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 "documentchecker.h" -#include "kthumb.h" #include "effects/effectsrepository.hpp" #include "kdenlivesettings.h" +#include "kthumb.h" #include "titler/titlewidget.h" #include "utils/KoIconUtils.h" #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include const int hashRole = Qt::UserRole; const int sizeRole = Qt::UserRole + 1; const int idRole = Qt::UserRole + 2; const int statusRole = Qt::UserRole + 3; const int typeRole = Qt::UserRole + 4; const int typeOriginalResource = Qt::UserRole + 5; const int clipTypeRole = Qt::UserRole + 6; const int CLIPMISSING = 0; const int CLIPOK = 1; const int CLIPPLACEHOLDER = 2; const int PROXYMISSING = 4; const int SOURCEMISSING = 5; const int LUMAMISSING = 10; const int LUMAOK = 11; const int LUMAPLACEHOLDER = 12; enum MISSINGTYPE { TITLE_IMAGE_ELEMENT = 20, TITLE_FONT_ELEMENT = 21 }; DocumentChecker::DocumentChecker(const QUrl &url, const QDomDocument &doc) : m_url(url) , m_doc(doc) , m_dialog(nullptr) { } bool DocumentChecker::hasErrorInClips() { int max; QDomElement baseElement = m_doc.documentElement(); QString root = baseElement.attribute(QStringLiteral("root")); if (!root.isEmpty()) { QDir dir(root); if (!dir.exists()) { // Looks like project was moved, try recovering root from current project url m_rootReplacement.first = dir.absolutePath() + QDir::separator(); root = m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); baseElement.setAttribute(QStringLiteral("root"), root); root = QDir::cleanPath(root) + QDir::separator(); m_rootReplacement.second = root; } else { root = QDir::cleanPath(root) + QDir::separator(); } } // Check if strorage folder for temp files exists QString storageFolder; QDir projectDir(m_url.adjusted(QUrl::RemoveFilename).toLocalFile()); QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); for (int i = 0; i < playlists.count(); ++i) { if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == QStringLiteral("main bin")) { QString documentid = EffectsList::property(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.documentid")); if (documentid.isEmpty()) { // invalid document id, recreate one documentid = QString::number(QDateTime::currentMSecsSinceEpoch()); // TODO: Warn on invalid doc id EffectsList::setProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.documentid"), documentid); } storageFolder = EffectsList::property(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.storagefolder")); if (!storageFolder.isEmpty() && QFileInfo(storageFolder).isRelative()) { storageFolder.prepend(root); } if (!storageFolder.isEmpty() && !QFile::exists(storageFolder) && projectDir.exists(documentid)) { storageFolder = projectDir.absolutePath(); EffectsList::setProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.storagefolder"), projectDir.absoluteFilePath(documentid)); m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } break; } } QDomNodeList documentProducers = m_doc.elementsByTagName(QStringLiteral("producer")); QDomElement profile = baseElement.firstChildElement(QStringLiteral("profile")); bool hdProfile = true; if (!profile.isNull()) { if (profile.attribute(QStringLiteral("width")).toInt() < 1000) { hdProfile = false; } } // List clips whose proxy is missing QList missingProxies; // List clips who have a working proxy but no source clip QList missingSources; m_safeImages.clear(); m_safeFonts.clear(); m_missingFonts.clear(); max = documentProducers.count(); QStringList verifiedPaths; QStringList serviceToCheck; serviceToCheck << QStringLiteral("kdenlivetitle") << QStringLiteral("qimage") << QStringLiteral("pixbuf") << QStringLiteral("timewarp") << QStringLiteral("framebuffer") << QStringLiteral("xml"); for (int i = 0; i < max; ++i) { QDomElement e = documentProducers.item(i).toElement(); QString service = EffectsList::property(e, QStringLiteral("mlt_service")); if (!service.startsWith(QLatin1String("avformat")) && !serviceToCheck.contains(service)) { continue; } if (service == QLatin1String("qtext")) { checkMissingImagesAndFonts(QStringList(), QStringList(EffectsList::property(e, QStringLiteral("family"))), e.attribute(QStringLiteral("id")), e.attribute(QStringLiteral("name"))); continue; } if (service == QLatin1String("kdenlivetitle")) { // TODO: Check is clip template is missing (xmltemplate) or hash changed QString xml = EffectsList::property(e, QStringLiteral("xmldata")); QStringList images = TitleWidget::extractImageList(xml); QStringList fonts = TitleWidget::extractFontList(xml); checkMissingImagesAndFonts(images, fonts, e.attribute(QStringLiteral("id")), e.attribute(QStringLiteral("name"))); continue; } QString resource = EffectsList::property(e, QStringLiteral("resource")); if (resource.isEmpty()) { continue; } if (service == QLatin1String("timewarp")) { // slowmotion clip, trim speed info resource = EffectsList::property(e, QStringLiteral("warp_resource")); } else if (service == QLatin1String("framebuffer")) { // slowmotion clip, trim speed info resource = resource.section(QLatin1Char('?'), 0, 0); } // Make sure to have absolute paths if (QFileInfo(resource).isRelative()) { resource.prepend(root); } if (verifiedPaths.contains(resource)) { // Don't check same url twice (for example track producers) continue; } QString proxy = EffectsList::property(e, QStringLiteral("kdenlive:proxy")); if (proxy.length() > 1) { if (QFileInfo(proxy).isRelative()) { proxy.prepend(root); } if (!QFile::exists(proxy)) { // Missing clip found // Check if proxy exists in current storage folder bool fixed = false; if (!storageFolder.isEmpty()) { QDir dir(storageFolder + QStringLiteral("/proxy/")); if (dir.exists(QFileInfo(proxy).fileName())) { QString updatedPath = dir.absoluteFilePath(QFileInfo(proxy).fileName()); fixProxyClip(e.attribute(QStringLiteral("id")), EffectsList::property(e, QStringLiteral("kdenlive:proxy")), updatedPath, documentProducers); fixed = true; } } if (!fixed) { missingProxies.append(e); } } QString original = EffectsList::property(e, QStringLiteral("kdenlive:originalurl")); if (QFileInfo(original).isRelative()) { original.prepend(root); } // Check for slideshows bool slideshow = original.contains(QStringLiteral("/.all.")) || original.contains(QLatin1Char('?')) || original.contains(QLatin1Char('%')); if (slideshow && !EffectsList::property(e, QStringLiteral("ttl")).isEmpty()) { original = QFileInfo(original).absolutePath(); } if (!QFile::exists(original)) { // clip has proxy but original clip is missing missingSources.append(e); } verifiedPaths.append(resource); continue; } // Check for slideshows bool slideshow = resource.contains(QStringLiteral("/.all.")) || resource.contains(QLatin1Char('?')) || resource.contains(QLatin1Char('%')); if ((service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) && slideshow) { resource = QFileInfo(resource).absolutePath(); } if (!QFile::exists(resource)) { // Missing clip found m_missingClips.append(e); } // Make sure we don't query same path twice verifiedPaths.append(resource); } // Get list of used Luma files QStringList missingLumas; QStringList filesToCheck; QString filePath; QDomNodeList trans = m_doc.elementsByTagName(QStringLiteral("transition")); max = trans.count(); for (int i = 0; i < max; ++i) { QDomElement transition = trans.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("mlt_service")); QString luma; if (service == QLatin1String("luma")) { luma = getProperty(transition, QStringLiteral("resource")); } else if (service == QLatin1String("composite")) { luma = getProperty(transition, QStringLiteral("luma")); } if (!luma.isEmpty() && !filesToCheck.contains(luma)) { filesToCheck.append(luma); } } QMap autoFixLuma; QString lumaPath; // Check existence of luma files for (const QString &lumafile : filesToCheck) { filePath = lumafile; if (QFileInfo(filePath).isRelative()) { filePath.prepend(root); } if (!QFile::exists(filePath)) { QString lumaName = filePath.section(QLatin1Char('/'), -1); // check if this was an old format luma, not in correct folder QString fixedLuma = filePath.section(QLatin1Char('/'), 0, -2); lumaName.prepend(hdProfile ? QStringLiteral("/HD/") : QStringLiteral("/PAL/")); fixedLuma.append(lumaName); if (QFile::exists(fixedLuma)) { // Auto replace pgm with png for lumas autoFixLuma.insert(filePath, fixedLuma); continue; } // Check MLT folder if (lumaPath.isEmpty()) { QDir dir(QCoreApplication::applicationDirPath()); dir.cdUp(); dir.cd(QStringLiteral("share/kdenlive/lumas/")); lumaPath = dir.absolutePath(); } lumaName.prepend(lumaPath); if (QFile::exists(lumaName)) { autoFixLuma.insert(filePath, lumaName); continue; } if (filePath.endsWith(QLatin1String(".pgm"))) { fixedLuma = filePath.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".png"); } else if (filePath.endsWith(QLatin1String(".png"))) { fixedLuma = filePath.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".pgm"); } if (!fixedLuma.isEmpty() && QFile::exists(fixedLuma)) { // Auto replace pgm with png for lumas autoFixLuma.insert(filePath, fixedLuma); } else { missingLumas.append(lumafile); } } } if (!autoFixLuma.isEmpty()) { for (int i = 0; i < max; ++i) { QDomElement transition = trans.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("mlt_service")); QString luma; if (service == QLatin1String("luma")) { luma = getProperty(transition, QStringLiteral("resource")); } else if (service == QLatin1String("composite")) { luma = getProperty(transition, QStringLiteral("luma")); } if (!luma.isEmpty() && autoFixLuma.contains(luma)) { setProperty(transition, service == QLatin1String("luma") ? QStringLiteral("resource") : QStringLiteral("luma"), autoFixLuma.value(luma)); } } } // Check for missing effects QDomNodeList effs = m_doc.elementsByTagName(QStringLiteral("filter")); max = effs.count(); QStringList filters; for (int i = 0; i < max; ++i) { QDomElement transition = effs.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("kdenlive_id")); filters << service; } QStringList processed; for (const QString &id : filters) { if (!processed.contains(id) && !EffectsRepository::get()->exists(id)) { m_missingFilters << id; } processed << id; } if (!m_missingFilters.isEmpty()) { // Delete missing effects for (int i = 0; i < effs.count(); ++i) { QDomElement e = effs.item(i).toElement(); if (m_missingFilters.contains(getProperty(e, QStringLiteral("kdenlive_id")))) { // Remove clip e.parentNode().removeChild(e); --i; } } } - if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() && m_missingFilters.isEmpty()) { + if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() && + m_missingFilters.isEmpty()) { return false; } m_dialog = new QDialog(); m_dialog->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); m_ui.setupUi(m_dialog); for (const QString &l : missingLumas) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Luma file") << l); item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-close"))); item->setData(0, idRole, l); item->setData(0, statusRole, LUMAMISSING); } m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_missingClips.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty()); max = m_missingClips.count(); m_missingProxyIds.clear(); for (int i = 0; i < max; ++i) { QDomElement e = m_missingClips.at(i).toElement(); QString clipType; ClipType type; int status = CLIPMISSING; const QString service = EffectsList::property(e, QStringLiteral("mlt_service")); QString resource = service == QLatin1String("timewarp") ? EffectsList::property(e, QStringLiteral("warp_resource")) : EffectsList::property(e, QStringLiteral("resource")); bool slideshow = resource.contains(QStringLiteral("/.all.")) || resource.contains(QLatin1Char('?')) || resource.contains(QLatin1Char('%')); if (service == QLatin1String("avformat") || service == QLatin1String("avformat-novalidate") || service == QLatin1String("framebuffer") || service == QLatin1String("timewarp")) { clipType = i18n("Video clip"); type = ClipType::AV; } else if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { if (slideshow) { clipType = i18n("Slideshow clip"); type = ClipType::SlideShow; } else { clipType = i18n("Image clip"); type = ClipType::Image; } } else if (service == QLatin1String("mlt") || service == QLatin1String("xml")) { clipType = i18n("Playlist clip"); type = ClipType::Playlist; } else if (e.tagName() == QLatin1String("missingtitle")) { clipType = i18n("Title Image"); status = TITLE_IMAGE_ELEMENT; type = ClipType::Text; } else { clipType = i18n("Unknown"); type = ClipType::Unknown; } QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType); item->setData(0, statusRole, CLIPMISSING); item->setData(0, clipTypeRole, (int)type); item->setData(0, idRole, e.attribute(QStringLiteral("id"))); item->setToolTip(0, i18n("Missing item")); if (status == TITLE_IMAGE_ELEMENT) { item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-warning"))); item->setToolTip(1, e.attribute(QStringLiteral("name"))); QString imageResource = e.attribute(QStringLiteral("resource")); item->setData(0, typeRole, status); item->setData(0, typeOriginalResource, e.attribute(QStringLiteral("resource"))); if (!m_rootReplacement.first.isEmpty()) { if (imageResource.startsWith(m_rootReplacement.first)) { imageResource.replace(m_rootReplacement.first, m_rootReplacement.second); if (QFile::exists(imageResource)) { item->setData(0, statusRole, CLIPOK); item->setToolTip(0, i18n("Relocated item")); } } } item->setText(1, imageResource); } else { item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-close"))); if (QFileInfo(resource).isRelative()) { resource.prepend(root); } item->setData(0, hashRole, EffectsList::property(e, QStringLiteral("kdenlive:file_hash"))); item->setData(0, sizeRole, EffectsList::property(e, QStringLiteral("kdenlive:file_size"))); if (!m_rootReplacement.first.isEmpty()) { if (resource.startsWith(m_rootReplacement.first)) { resource.replace(m_rootReplacement.first, m_rootReplacement.second); if (QFile::exists(resource)) { item->setData(0, statusRole, CLIPOK); item->setToolTip(0, i18n("Relocated item")); } } } item->setText(1, resource); } } for (const QString &font : m_missingFonts) { QString clipType = i18n("Title Font"); QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType); item->setData(0, statusRole, CLIPPLACEHOLDER); item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-warning"))); QString newft = QFontInfo(QFont(font)).family(); item->setText(1, i18n("%1 will be replaced by %2", font, newft)); item->setData(0, typeRole, TITLE_FONT_ELEMENT); } - + QString infoLabel; if (!m_missingClips.isEmpty()) { infoLabel = i18n("The project file contains missing clips or files."); } if (!m_missingFilters.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } - infoLabel.append(i18np("Missing effect: %2 will be removed from project.", "Missing effects: %2 will be removed from project.", m_missingFilters.count(), m_missingFilters.join(","))); + infoLabel.append(i18np("Missing effect: %2 will be removed from project.", "Missing effects: %2 will be removed from project.", + m_missingFilters.count(), m_missingFilters.join(","))); } if (!missingProxies.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } infoLabel.append(i18n("Missing proxies will be recreated after opening.")); } if (!missingSources.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } infoLabel.append(i18np("The project file contains a missing clip, you can still work with its proxy.", - "The project file contains %1 missing clips, you can still work with their proxies.", - missingSources.count())); + "The project file contains %1 missing clips, you can still work with their proxies.", missingSources.count())); } if (!infoLabel.isEmpty()) { m_ui.infoLabel->setText(infoLabel); } m_ui.removeSelected->setEnabled(!m_missingClips.isEmpty()); m_ui.recursiveSearch->setEnabled(!m_missingClips.isEmpty() || !missingLumas.isEmpty() || !missingSources.isEmpty()); m_ui.usePlaceholders->setEnabled(!m_missingClips.isEmpty()); // Check missing proxies max = missingProxies.count(); if (max > 0) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Proxy clip")); item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-warning"))); item->setText( 1, i18np("%1 missing proxy clip, will be recreated on project opening", "%1 missing proxy clips, will be recreated on project opening", max)); // item->setData(0, hashRole, e.attribute("file_hash")); item->setData(0, statusRole, PROXYMISSING); item->setToolTip(0, i18n("Missing proxy")); } for (int i = 0; i < max; ++i) { QDomElement e = missingProxies.at(i).toElement(); QString realPath = EffectsList::property(e, QStringLiteral("kdenlive:originalurl")); QString id = e.attribute(QStringLiteral("id")); m_missingProxyIds << id; // Tell Kdenlive to recreate proxy e.setAttribute(QStringLiteral("_replaceproxy"), QStringLiteral("1")); // Replace proxy url with real clip in MLT producers QDomElement mltProd; int prodsCount = documentProducers.count(); for (int j = 0; j < prodsCount; ++j) { mltProd = documentProducers.at(j).toElement(); QString prodId = mltProd.attribute(QStringLiteral("id")); QString parentId = prodId; bool slowmotion = false; if (parentId.startsWith(QLatin1String("slowmotion"))) { slowmotion = true; parentId = parentId.section(QLatin1Char(':'), 1, 1); } if (parentId.contains(QLatin1Char('_'))) { parentId = parentId.section(QLatin1Char('_'), 0, 0); } if (parentId == id) { // Hit, we must replace url QString suffix; QString resource = EffectsList::property(mltProd, QStringLiteral("resource")); if (slowmotion) { suffix = QLatin1Char('?') + resource.section(QLatin1Char('?'), -1); } EffectsList::setProperty(mltProd, QStringLiteral("resource"), realPath + suffix); if (prodId == id) { // Only set proxy property on master producer EffectsList::setProperty(mltProd, QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); } } } } if (max > 0) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } // Check clips with available proxies but missing original source clips max = missingSources.count(); if (max > 0) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Source clip")); item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-warning"))); item->setText(1, i18n("%1 missing source clips, you can only use the proxies", max)); // item->setData(0, hashRole, e.attribute("file_hash")); item->setData(0, statusRole, SOURCEMISSING); item->setToolTip(0, i18n("Missing source clip")); for (int i = 0; i < max; ++i) { QDomElement e = missingSources.at(i).toElement(); QString realPath = EffectsList::property(e, QStringLiteral("kdenlive:originalurl")); QString id = e.attribute(QStringLiteral("id")); // Tell Kdenlive the source is missing e.setAttribute(QStringLiteral("_missingsource"), QStringLiteral("1")); QTreeWidgetItem *subitem = new QTreeWidgetItem(item, QStringList() << i18n("Source clip")); // qCDebug(KDENLIVE_LOG)<<"// Adding missing source clip: "<setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-close"))); subitem->setText(1, realPath); subitem->setData(0, hashRole, EffectsList::property(e, QStringLiteral("kdenlive:file_hash"))); subitem->setData(0, sizeRole, EffectsList::property(e, QStringLiteral("kdenlive:file_size"))); subitem->setData(0, statusRole, CLIPMISSING); // int t = e.attribute("type").toInt(); subitem->setData(0, typeRole, EffectsList::property(e, QStringLiteral("mlt_service"))); subitem->setData(0, idRole, id); } } if (max > 0) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } m_ui.treeWidget->resizeColumnToContents(0); connect(m_ui.recursiveSearch, &QAbstractButton::pressed, this, &DocumentChecker::slotSearchClips); connect(m_ui.usePlaceholders, &QAbstractButton::pressed, this, &DocumentChecker::slotPlaceholders); connect(m_ui.removeSelected, &QAbstractButton::pressed, this, &DocumentChecker::slotDeleteSelected); connect(m_ui.treeWidget, &QTreeWidget::itemDoubleClicked, this, &DocumentChecker::slotEditItem); connect(m_ui.treeWidget, &QTreeWidget::itemSelectionChanged, this, &DocumentChecker::slotCheckButtons); // adjustSize(); if (m_ui.treeWidget->topLevelItem(0)) { m_ui.treeWidget->setCurrentItem(m_ui.treeWidget->topLevelItem(0)); } checkStatus(); int acceptMissing = m_dialog->exec(); if (acceptMissing == QDialog::Accepted) { acceptDialog(); } return (acceptMissing != QDialog::Accepted); } DocumentChecker::~DocumentChecker() { delete m_dialog; } QString DocumentChecker::getProperty(const QDomElement &effect, const QString &name) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { return e.firstChild().nodeValue(); } } return QString(); } void DocumentChecker::setProperty(const QDomElement &effect, const QString &name, const QString &value) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { e.firstChild().setNodeValue(value); } } } void DocumentChecker::slotSearchClips() { // QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QString clipFolder = m_url.adjusted(QUrl::RemoveFilename).toLocalFile(); QString newpath = QFileDialog::getExistingDirectory(qApp->activeWindow(), i18n("Clips folder"), clipFolder); if (newpath.isEmpty()) { return; } int ix = 0; bool fixed = false; m_ui.recursiveSearch->setChecked(true); // TODO: make non modal QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); QDir searchDir(newpath); while (child != nullptr) { if (child->data(0, statusRole).toInt() == SOURCEMISSING) { for (int j = 0; j < child->childCount(); ++j) { QTreeWidgetItem *subchild = child->child(j); QString clipPath = searchFileRecursively(searchDir, subchild->data(0, sizeRole).toString(), subchild->data(0, hashRole).toString(), subchild->text(1)); if (!clipPath.isEmpty()) { fixed = true; subchild->setText(1, clipPath); subchild->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); subchild->setData(0, statusRole, CLIPOK); } } } else if (child->data(0, statusRole).toInt() == CLIPMISSING) { bool perfectMatch = true; ClipType type = (ClipType)child->data(0, clipTypeRole).toInt(); QString clipPath; if (type != ClipType::SlideShow) { // Slideshows cannot be found with hash / size clipPath = searchFileRecursively(searchDir, child->data(0, sizeRole).toString(), child->data(0, hashRole).toString(), child->text(1)); } if (clipPath.isEmpty()) { clipPath = searchPathRecursively(searchDir, QUrl::fromLocalFile(child->text(1)).fileName(), type); perfectMatch = false; } if (!clipPath.isEmpty()) { fixed = true; child->setText(1, clipPath); child->setIcon(0, perfectMatch ? KoIconUtils::themedIcon(QStringLiteral("dialog-ok")) : KoIconUtils::themedIcon(QStringLiteral("dialog-warning"))); child->setData(0, statusRole, CLIPOK); } } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { QString fileName = searchLuma(searchDir, child->data(0, idRole).toString()); if (!fileName.isEmpty()) { fixed = true; child->setText(1, fileName); child->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); child->setData(0, statusRole, LUMAOK); } } else if (child->data(0, typeRole).toInt() == TITLE_IMAGE_ELEMENT && child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) { // Search missing title images QString missingFileName = QUrl::fromLocalFile(child->text(1)).fileName(); QString newPath = searchPathRecursively(searchDir, missingFileName); if (!newPath.isEmpty()) { // File found fixed = true; child->setText(1, newPath); child->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); child->setData(0, statusRole, CLIPOK); } } ix++; child = m_ui.treeWidget->topLevelItem(ix); } m_ui.recursiveSearch->setChecked(false); m_ui.recursiveSearch->setEnabled(true); if (fixed) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } checkStatus(); } QString DocumentChecker::searchLuma(const QDir &dir, const QString &file) const { QDir searchPath(KdenliveSettings::mltpath()); QString fname = QUrl::fromLocalFile(file).fileName(); if (file.contains(QStringLiteral("PAL"))) { searchPath.cd(QStringLiteral("../lumas/PAL")); } else { searchPath.cd(QStringLiteral("../lumas/NTSC")); } QFileInfo result(searchPath, fname); if (result.exists()) { return result.filePath(); } // try to find luma in application path searchPath.setPath(QCoreApplication::applicationDirPath()); #ifdef Q_OS_WIN searchPath.cd(QStringLiteral("data/lumas")); #else searchPath.cd(QStringLiteral("../share/apps/kdenlive/lumas")); #endif result.setFile(searchPath, fname); if (result.exists()) { return result.filePath(); } // Try in Kdenlive's standard KDE path QString res = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("lumas/") + fname); if (!res.isEmpty()) { return res; } // Try in user's chosen folder return searchPathRecursively(dir, fname); } QString DocumentChecker::searchPathRecursively(const QDir &dir, const QString &fileName, ClipType type) const { QString foundFileName; QStringList filters; if (type == ClipType::SlideShow) { if (fileName.contains(QLatin1Char('%'))) { filters << fileName.section(QLatin1Char('%'), 0, -2) + QLatin1Char('*'); } else { return QString(); } } else { filters << fileName; } QDir searchDir(dir); searchDir.setNameFilters(filters); QStringList filesAndDirs = searchDir.entryList(QDir::Files | QDir::Readable); if (!filesAndDirs.isEmpty()) { // File Found if (type == ClipType::SlideShow) { return searchDir.absoluteFilePath(fileName); } return searchDir.absoluteFilePath(filesAndDirs.at(0)); } searchDir.setNameFilters(QStringList()); filesAndDirs = searchDir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchPathRecursively(searchDir.absoluteFilePath(filesAndDirs.at(i)), fileName, type); if (!foundFileName.isEmpty()) { break; } } return foundFileName; } QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash, const QString &fileName) const { if (matchSize.isEmpty() && matchHash.isEmpty()) { return searchPathRecursively(dir, QUrl::fromLocalFile(fileName).fileName()); } QString foundFileName; QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { QFile file(dir.absoluteFilePath(filesAndDirs.at(i))); if (QString::number(file.size()) == matchSize) { if (file.open(QIODevice::ReadOnly)) { /* - * 1 MB = 1 second per 450 files (or faster) - * 10 MB = 9 seconds per 450 files (or faster) - */ + * 1 MB = 1 second per 450 files (or faster) + * 10 MB = 9 seconds per 450 files (or faster) + */ if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) { fileData.append(file.readAll()); } } else { fileData = file.readAll(); } file.close(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString::fromLatin1(fileHash.toHex()) == matchHash) { return file.fileName(); } } } ////qCDebug(KDENLIVE_LOG) << filesAndDirs.at(i) << file.size() << fileHash.toHex(); } filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash, fileName); if (!foundFileName.isEmpty()) { break; } } return foundFileName; } void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int) { int t = item->data(0, typeRole).toInt(); if (t == TITLE_FONT_ELEMENT) { return; } //|| t == TITLE_IMAGE_ELEMENT) { QUrl url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(item->text(1)), m_dialog, i18n("Enter new location for file")); if (!url.isValid()) { return; } item->setText(1, url.toLocalFile()); ClipType type = (ClipType)item->data(0, clipTypeRole).toInt(); bool fixed = false; if (type == ClipType::SlideShow && QFile::exists(url.adjusted(QUrl::RemoveFilename).toLocalFile())) { fixed = true; } if (fixed || QFile::exists(url.toLocalFile())) { item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); int id = item->data(0, statusRole).toInt(); if (id < 10) { item->setData(0, statusRole, CLIPOK); } else { item->setData(0, statusRole, LUMAOK); } checkStatus(); } else { item->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-close"))); int id = item->data(0, statusRole).toInt(); if (id < 10) { item->setData(0, statusRole, CLIPMISSING); } else { item->setData(0, statusRole, LUMAMISSING); } checkStatus(); } } void DocumentChecker::acceptDialog() { QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int ix = 0; // prepare transitions QDomNodeList trans = m_doc.elementsByTagName(QStringLiteral("transition")); // prepare filters QDomNodeList filters = m_doc.elementsByTagName(QStringLiteral("filter")); - + // Mark document as modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { if (child->data(0, statusRole).toInt() == SOURCEMISSING) { for (int j = 0; j < child->childCount(); ++j) { fixSourceClipItem(child->child(j), producers); } } else { fixClipItem(child, producers, trans); } ix++; child = m_ui.treeWidget->topLevelItem(ix); } // QDialog::accept(); } void DocumentChecker::fixProxyClip(const QString &id, const QString &oldUrl, const QString &newUrl, const QDomNodeList &producers) { QDomElement e, property; QDomNodeList properties; for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); QString sourceId = e.attribute(QStringLiteral("id")); QString parentId = sourceId.section(QLatin1Char('_'), 0, 0); if (parentId.startsWith(QLatin1String("slowmotion"))) { parentId = parentId.section(QLatin1Char(':'), 1, 1); } if (parentId == id) { // Fix clip QString resource = EffectsList::property(e, QStringLiteral("resource")); // TODO: Slowmmotion clips if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { // fixedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (resource == oldUrl) { EffectsList::setProperty(e, QStringLiteral("resource"), newUrl); } if (sourceId == id) { // Only set originalurl on master producer EffectsList::setProperty(e, QStringLiteral("kdenlive:proxy"), newUrl); } } } } void DocumentChecker::fixSourceClipItem(QTreeWidgetItem *child, const QDomNodeList &producers) { QDomElement e, property; QDomNodeList properties; // int t = child->data(0, typeRole).toInt(); if (child->data(0, statusRole).toInt() == CLIPOK) { QString id = child->data(0, idRole).toString(); for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); QString sourceId = e.attribute(QStringLiteral("id")); QString parentId = sourceId.section(QLatin1Char('_'), 0, 0); if (parentId.startsWith(QLatin1String("slowmotion"))) { parentId = parentId.section(QLatin1Char(':'), 1, 1); } if (parentId == id) { // Fix clip QString resource = EffectsList::property(e, QStringLiteral("resource")); QString fixedResource = child->text(1); if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { fixedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (sourceId == id) { // Only set originalurl on master producer EffectsList::setProperty(e, QStringLiteral("kdenlive:originalurl"), fixedResource); } if (m_missingProxyIds.contains(parentId)) { // Proxy is also missing, replace resource EffectsList::setProperty(e, QStringLiteral("resource"), fixedResource); } } } } } void DocumentChecker::fixClipItem(QTreeWidgetItem *child, const QDomNodeList &producers, const QDomNodeList &trans) { QDomElement e, property; QDomNodeList properties; int t = child->data(0, typeRole).toInt(); if (child->data(0, statusRole).toInt() == CLIPOK) { QString id = child->data(0, idRole).toString(); QString fixedResource = child->text(1); if (t == TITLE_IMAGE_ELEMENT) { // edit images embedded in titles for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0) == id) { // Fix clip properties = e.childNodes(); for (int j = 0; j < properties.count(); ++j) { property = properties.item(j).toElement(); if (property.attribute(QStringLiteral("name")) == QLatin1String("xmldata")) { QString xml = property.firstChild().nodeValue(); xml.replace(child->data(0, typeOriginalResource).toString(), fixedResource); property.firstChild().setNodeValue(xml); break; } } } } } else { // edit clip url /*for (int i = 0; i < infoproducers.count(); ++i) { e = infoproducers.item(i).toElement(); if (e.attribute("id") == id) { // Fix clip e.setAttribute("resource", child->text(1)); e.setAttribute("name", QUrl(child->text(1)).fileName()); e.removeAttribute("_missingsource"); break; } }*/ for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0) == id || e.attribute(QStringLiteral("id")).section(QLatin1Char(':'), 1, 1) == id || e.attribute(QStringLiteral("id")) == id) { // Fix clip QString resource = getProperty(e, QStringLiteral("resource")); QString service = getProperty(e, QStringLiteral("mlt_service")); QString updatedResource = fixedResource; if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { updatedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (service == QLatin1String("timewarp")) { setProperty(e, QStringLiteral("warp_resource"), updatedResource); updatedResource.prepend(getProperty(e, QStringLiteral("warp_speed")) + QLatin1Char(':')); } setProperty(e, QStringLiteral("resource"), updatedResource); } } } } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER && t != TITLE_FONT_ELEMENT && t != TITLE_IMAGE_ELEMENT) { // QString id = child->data(0, idRole).toString(); /*for (int i = 0; i < infoproducers.count(); ++i) { e = infoproducers.item(i).toElement(); if (e.attribute("id") == id) { // Fix clip e.setAttribute("placeholder", '1'); break; } }*/ } else if (child->data(0, statusRole).toInt() == LUMAOK) { for (int i = 0; i < trans.count(); ++i) { QString service = getProperty(trans.at(i).toElement(), QStringLiteral("mlt_service")); QString luma; if (service == QLatin1String("luma")) { luma = getProperty(trans.at(i).toElement(), QStringLiteral("resource")); } else if (service == QLatin1String("composite")) { luma = getProperty(trans.at(i).toElement(), QStringLiteral("luma")); } if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) { setProperty(trans.at(i).toElement(), service == QLatin1String("luma") ? QStringLiteral("resource") : QStringLiteral("luma"), child->text(1)); // qCDebug(KDENLIVE_LOG) << "replace with; " << child->text(1); } } } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { for (int i = 0; i < trans.count(); ++i) { QString service = getProperty(trans.at(i).toElement(), QStringLiteral("mlt_service")); QString luma; if (service == QLatin1String("luma")) { luma = getProperty(trans.at(i).toElement(), QStringLiteral("resource")); } else if (service == QLatin1String("composite")) { luma = getProperty(trans.at(i).toElement(), QStringLiteral("luma")); } if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) { setProperty(trans.at(i).toElement(), service == QLatin1String("luma") ? QStringLiteral("resource") : QStringLiteral("luma"), QString()); } } } } void DocumentChecker::slotPlaceholders() { int ix = 0; QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { if (child->data(0, statusRole).toInt() == CLIPMISSING) { child->setData(0, statusRole, CLIPPLACEHOLDER); child->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { child->setData(0, statusRole, LUMAPLACEHOLDER); child->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("dialog-ok"))); } ix++; child = m_ui.treeWidget->topLevelItem(ix); } checkStatus(); } void DocumentChecker::checkStatus() { bool status = true; int ix = 0; QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { int childStatus = child->data(0, statusRole).toInt(); if (childStatus == CLIPMISSING) { status = false; break; } ix++; child = m_ui.treeWidget->topLevelItem(ix); } m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status); } void DocumentChecker::slotDeleteSelected() { - if (KMessageBox::warningContinueCancel(m_dialog, i18np("This will remove the selected clip from this project", - "This will remove the selected clips from this project", m_ui.treeWidget->selectedItems().count()), + if (KMessageBox::warningContinueCancel(m_dialog, + i18np("This will remove the selected clip from this project", + "This will remove the selected clips from this project", m_ui.treeWidget->selectedItems().count()), i18n("Remove clips")) == KMessageBox::Cancel) { return; } QStringList deletedIds; QStringList deletedLumas; QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); for (QTreeWidgetItem *child : m_ui.treeWidget->selectedItems()) { int id = child->data(0, statusRole).toInt(); if (id == CLIPMISSING) { deletedIds.append(child->data(0, idRole).toString()); delete child; } else if (id == LUMAMISSING) { deletedLumas.append(child->data(0, idRole).toString()); delete child; } } if (!deletedLumas.isEmpty()) { QDomElement e; QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition")); for (const QString &lumaPath : deletedLumas) { for (int i = 0; i < transitions.count(); ++i) { e = transitions.item(i).toElement(); QString service = EffectsList::property(e, QStringLiteral("mlt_service")); QString resource; if (service == QLatin1String("luma")) { resource = EffectsList::property(e, QStringLiteral("resource")); } else if (service == QLatin1String("composite")) { resource = EffectsList::property(e, QStringLiteral("luma")); } if (resource == lumaPath) { EffectsList::removeProperty(e, service == QLatin1String("luma") ? QStringLiteral("resource") : QStringLiteral("luma")); } } } } if (!deletedIds.isEmpty()) { QDomElement e; QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); QDomNode mlt = m_doc.elementsByTagName(QStringLiteral("mlt")).at(0); QDomNode kdenlivedoc = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")).at(0); for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (deletedIds.contains(e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0)) || deletedIds.contains(e.attribute(QStringLiteral("id")).section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0))) { // Remove clip mlt.removeChild(e); --i; } } for (int i = 0; i < playlists.count(); ++i) { QDomNodeList entries = playlists.at(i).toElement().elementsByTagName(QStringLiteral("entry")); for (int j = 0; j < entries.count(); ++j) { e = entries.item(j).toElement(); if (deletedIds.contains(e.attribute(QStringLiteral("producer")).section(QLatin1Char('_'), 0, 0)) || deletedIds.contains(e.attribute(QStringLiteral("producer")).section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0))) { // Replace clip with blank while (e.childNodes().count() > 0) { e.removeChild(e.firstChild()); } e.setTagName(QStringLiteral("blank")); e.removeAttribute(QStringLiteral("producer")); int length = e.attribute(QStringLiteral("out")).toInt() - e.attribute(QStringLiteral("in")).toInt(); e.setAttribute(QStringLiteral("length"), length); j--; } } } m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); checkStatus(); } } void DocumentChecker::checkMissingImagesAndFonts(const QStringList &images, const QStringList &fonts, const QString &id, const QString &baseClip) { QDomDocument doc; for (const QString &img : images) { if (m_safeImages.contains(img)) { continue; } if (!QFile::exists(img)) { QDomElement e = doc.createElement(QStringLiteral("missingtitle")); e.setAttribute(QStringLiteral("type"), TITLE_IMAGE_ELEMENT); e.setAttribute(QStringLiteral("resource"), img); e.setAttribute(QStringLiteral("id"), id); e.setAttribute(QStringLiteral("name"), baseClip); m_missingClips.append(e); } else { m_safeImages.append(img); } } for (const QString &fontelement : fonts) { if (m_safeFonts.contains(fontelement)) { continue; } QFont f(fontelement); ////qCDebug(KDENLIVE_LOG) << "/ / / CHK FONTS: " << fontelement << " = " << QFontInfo(f).family(); if (fontelement != QFontInfo(f).family()) { m_missingFonts << fontelement; } else { m_safeFonts.append(fontelement); } } } void DocumentChecker::slotCheckButtons() { if (m_ui.treeWidget->currentItem()) { QTreeWidgetItem *item = m_ui.treeWidget->currentItem(); int t = item->data(0, typeRole).toInt(); int s = item->data(0, statusRole).toInt(); if (t == TITLE_FONT_ELEMENT || t == TITLE_IMAGE_ELEMENT || s == PROXYMISSING) { m_ui.removeSelected->setEnabled(false); } else { m_ui.removeSelected->setEnabled(true); } } } diff --git a/src/doc/documentvalidator.cpp b/src/doc/documentvalidator.cpp index cf7853d1e..d2d636a65 100644 --- a/src/doc/documentvalidator.cpp +++ b/src/doc/documentvalidator.cpp @@ -1,2177 +1,2176 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 "documentvalidator.h" #include "core.h" #include "definitions.h" #include "effectslist/initeffects.h" #include "mainwindow.h" #include "mltcontroller/bincontroller.h" #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include DocumentValidator::DocumentValidator(const QDomDocument &doc, const QUrl &documentUrl) : m_doc(doc) , m_url(documentUrl) , m_modified(false) { } bool DocumentValidator::validate(const double currentVersion) { QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt")); // At least the root element must be there if (mlt.isNull()) { return false; } QDomElement kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc")); QString rootDir = mlt.attribute(QStringLiteral("root")); if (rootDir == QLatin1String("$CURRENTPATH")) { // The document was extracted from a Kdenlive archived project, fix root directory QString playlist = m_doc.toString(); playlist.replace(QLatin1String("$CURRENTPATH"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile()); m_doc.setContent(playlist); mlt = m_doc.firstChildElement(QStringLiteral("mlt")); kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc")); } else if (rootDir.isEmpty()) { mlt.setAttribute(QStringLiteral("root"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile()); } // Previous MLT / Kdenlive versions used C locale by default QLocale documentLocale = QLocale::c(); if (mlt.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // Check document numeric separator (added in Kdenlive 16.12.1 QDomElement main_playlist = mlt.firstChildElement(QStringLiteral("playlist")); QDomNodeList props = main_playlist.elementsByTagName(QStringLiteral("property")); QChar numericalSeparator; for (int i = 0; i < props.count(); ++i) { QDomNode n = props.at(i); if (n.toElement().attribute(QStringLiteral("name")) == QLatin1String("kdenlive:docproperties.decimalPoint")) { QString sep = n.firstChild().nodeValue(); if (!sep.isEmpty()) { numericalSeparator = sep.at(0); } break; } } bool error = false; if (!numericalSeparator.isNull() && numericalSeparator != QLocale().decimalPoint()) { qCDebug(KDENLIVE_LOG) << " * ** LOCALE CHANGE REQUIRED: " << numericalSeparator << "!=" << QLocale().decimalPoint() << " / " << QLocale::system().decimalPoint(); // Change locale to match document QString requestedLocale = mlt.attribute(QStringLiteral("LC_NUMERIC")); documentLocale = QLocale(requestedLocale); #ifdef Q_OS_WIN // Most locales don't work on windows, so use C whenever possible if (numericalSeparator == QLatin1Char('.')) { #else if (numericalSeparator != documentLocale.decimalPoint() && numericalSeparator == QLatin1Char('.')) { #endif requestedLocale = QStringLiteral("C"); documentLocale = QLocale::c(); } #ifdef Q_OS_MAC setlocale(LC_NUMERIC_MASK, requestedLocale.toUtf8().constData()); #elif defined(Q_OS_WIN) std::locale::global(std::locale(requestedLocale.toUtf8().constData())); #else setlocale(LC_NUMERIC, requestedLocale.toUtf8().constData()); #endif if (numericalSeparator != documentLocale.decimalPoint()) { // Parse installed locales to find one matching const QList list = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale().script(), QLocale::AnyCountry); QLocale matching; for (const QLocale &loc : list) { if (loc.decimalPoint() == numericalSeparator) { matching = loc; qCDebug(KDENLIVE_LOG) << "Warning, document locale: " << mlt.attribute(QStringLiteral("LC_NUMERIC")) << " is not available, using: " << loc.name(); #ifndef Q_OS_MAC setlocale(LC_NUMERIC, loc.name().toUtf8().constData()); #else setlocale(LC_NUMERIC_MASK, loc.name().toUtf8().constData()); #endif documentLocale = matching; break; } } error = numericalSeparator != documentLocale.decimalPoint(); } } else if (numericalSeparator.isNull()) { // Change locale to match document #ifndef Q_OS_MAC const QString newloc = QString::fromLatin1(setlocale(LC_NUMERIC, mlt.attribute(QStringLiteral("LC_NUMERIC")).toUtf8().constData())); #else const QString newloc = setlocale(LC_NUMERIC_MASK, mlt.attribute("LC_NUMERIC").toUtf8().constData()); #endif documentLocale = QLocale(mlt.attribute(QStringLiteral("LC_NUMERIC"))); error = newloc.isEmpty(); } else { // Document separator matching system separator documentLocale = QLocale(); } if (error) { // Requested locale not available, ask for install KMessageBox::sorry(QApplication::activeWindow(), i18n("The document was created in \"%1\" locale, which is not installed on your system. Please install that language pack. " "Until then, Kdenlive might not be able to correctly open the document.", mlt.attribute(QStringLiteral("LC_NUMERIC")))); } // Make sure Qt locale and C++ locale have the same numeric separator, might not be the case // With some locales since C++ and Qt use a different database for locales // localeconv()->decimal_point does not give reliable results on Windows #ifndef Q_OS_WIN char *separator = localeconv()->decimal_point; if (QString::fromUtf8(separator) != QString(documentLocale.decimalPoint())) { KMessageBox::sorry(QApplication::activeWindow(), i18n("There is a locale conflict on your system. The document uses locale %1 which uses a \"%2\" as numeric separator (in " "system libraries) but Qt expects \"%3\". You might not be able to correctly open the project.", mlt.attribute(QStringLiteral("LC_NUMERIC")), documentLocale.decimalPoint(), separator)); // qDebug()<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------"; // HACK: There is a locale conflict, so set locale to at least have correct decimal point if (strncmp(separator, ".", 1) == 0) { documentLocale = QLocale::c(); } else if (strncmp(separator, ",", 1) == 0) { documentLocale = QLocale(QStringLiteral("fr_FR.UTF-8")); } } #endif } documentLocale.setNumberOptions(QLocale::OmitGroupSeparator); if (documentLocale.decimalPoint() != QLocale().decimalPoint()) { // If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default if (!mlt.hasAttribute(QStringLiteral("LC_NUMERIC"))) { #ifndef Q_OS_MAC setlocale(LC_NUMERIC, "C"); #else setlocale(LC_NUMERIC_MASK, "C"); #endif } QLocale::setDefault(documentLocale); if (documentLocale.decimalPoint() != QLocale().decimalPoint()) { KMessageBox::sorry(QApplication::activeWindow(), i18n("There is a locale conflict. The document uses a \"%1\" as numeric separator, but your computer is configured to use " "\"%2\". Change your computer settings or you might not be able to correctly open the project.", documentLocale.decimalPoint(), QLocale().decimalPoint())); } // locale conversion might need to be redone #ifndef Q_OS_MAC initEffects::parseEffectFiles(pCore->getMltRepository(), QString::fromLatin1(setlocale(LC_NUMERIC, nullptr))); #else initEffects::parseEffectFiles(pCore->getMltRepository(), QString::fromLatin1(setlocale(LC_NUMERIC_MASK, nullptr))); #endif } double version = -1; if (kdenliveDoc.isNull() || !kdenliveDoc.hasAttribute(QStringLiteral("version"))) { // Newer Kdenlive document version QDomElement main = mlt.firstChildElement(QStringLiteral("playlist")); version = EffectsList::property(main, QStringLiteral("kdenlive:docproperties.version")).toDouble(); } else { bool ok; version = documentLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok); if (!ok) { // Could not parse version number, there is probably a conflict in decimal separator QLocale tempLocale = QLocale(mlt.attribute(QStringLiteral("LC_NUMERIC"))); version = tempLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok); if (!ok) { version = kdenliveDoc.attribute(QStringLiteral("version")).toDouble(&ok); } if (!ok) { // Last try: replace comma with a dot QString versionString = kdenliveDoc.attribute(QStringLiteral("version")); if (versionString.contains(QLatin1Char(','))) { versionString.replace(QLatin1Char(','), QLatin1Char('.')); } version = versionString.toDouble(&ok); if (!ok) { qCDebug(KDENLIVE_LOG) << "// CANNOT PARSE VERSION NUMBER, ERROR!"; } } } } // Upgrade the document to the latest version if (!upgrade(version, currentVersion)) { return false; } if (version < 0.97) { checkOrphanedProducers(); } return true; /* // Check the syntax (this will be replaced by XSD validation with Qt 4.6) // and correct some errors { // Return (or create) the tractor QDomElement tractor = mlt.firstChildElement("tractor"); if (tractor.isNull()) { m_modified = true; tractor = m_doc.createElement("tractor"); tractor.setAttribute("global_feed", "1"); tractor.setAttribute("in", "0"); tractor.setAttribute("out", "-1"); tractor.setAttribute("id", "maintractor"); mlt.appendChild(tractor); } // Make sure at least one track exists, and they're equal in number to // to the maximum between MLT and Kdenlive playlists and tracks // // In older Kdenlive project files, one playlist is not a real track (the black track), we have: track count = playlist count- 1 // In newer Qt5 Kdenlive, the Bin playlist should not appear as a track. So we should have: track count = playlist count- 2 int trackOffset = 1; QDomNodeList playlists = m_doc.elementsByTagName("playlist"); // Remove "main bin" playlist that simply holds the bin's clips and is not a real playlist for (int i = 0; i < playlists.count(); ++i) { QString playlistId = playlists.at(i).toElement().attribute("id"); if (playlistId == BinController::binPlaylistId()) { // remove pseudo-playlist //playlists.at(i).parentNode().removeChild(playlists.at(i)); trackOffset = 2; break; } } int tracksMax = playlists.count() - trackOffset; // Remove the black track and bin track QDomNodeList tracks = tractor.elementsByTagName("track"); tracksMax = qMax(tracks.count() - 1, tracksMax); QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo"); tracksMax = qMax(tracksinfo.count(), tracksMax); tracksMax = qMax(1, tracksMax); // Force existence of one track if (playlists.count() - trackOffset < tracksMax || tracks.count() < tracksMax || tracksinfo.count() < tracksMax) { qCDebug(KDENLIVE_LOG) << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK"; m_modified = true; int difference; // use the MLT tracks as reference if (tracks.count() - 1 < tracksMax) { // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one. if (tracksinfo.count() != tracks.count() - 1) { // The Kdenlive tracks are not ok, clear and rebuild them QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo"); QDomNode tnode = tinfo.firstChild(); while (!tnode.isNull()) { tinfo.removeChild(tnode); tnode = tinfo.firstChild(); } for (int i = 1; i < tracks.count(); ++i) { QString hide = tracks.at(i).toElement().attribute("hide"); QDomElement newTrack = m_doc.createElement("trackinfo"); if (hide == "video") { // audio track; newTrack.setAttribute("type", "audio"); newTrack.setAttribute("blind", 1); newTrack.setAttribute("mute", 0); newTrack.setAttribute("lock", 0); } else { newTrack.setAttribute("blind", 0); newTrack.setAttribute("mute", 0); newTrack.setAttribute("lock", 0); } tinfo.appendChild(newTrack); } } } if (playlists.count() - 1 < tracksMax) { difference = tracksMax - (playlists.count() - 1); for (int i = 0; i < difference; ++i) { QDomElement playlist = m_doc.createElement("playlist"); mlt.appendChild(playlist); } } if (tracks.count() - 1 < tracksMax) { difference = tracksMax - (tracks.count() - 1); for (int i = 0; i < difference; ++i) { QDomElement track = m_doc.createElement("track"); tractor.appendChild(track); } } if (tracksinfo.count() < tracksMax) { QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo"); if (tracksinfoElm.isNull()) { tracksinfoElm = m_doc.createElement("tracksinfo"); kdenliveDoc.appendChild(tracksinfoElm); } difference = tracksMax - tracksinfo.count(); for (int i = 0; i < difference; ++i) { QDomElement trackinfo = m_doc.createElement("trackinfo"); trackinfo.setAttribute("mute", "0"); trackinfo.setAttribute("locked", "0"); tracksinfoElm.appendChild(trackinfo); } } } // TODO: check the tracks references // TODO: check internal mix transitions } updateEffects(); return true; */ } bool DocumentValidator::upgrade(double version, const double currentVersion) { qCDebug(KDENLIVE_LOG) << "Opening a document with version " << version << " / " << currentVersion; // No conversion needed if (qFuzzyCompare(version, currentVersion)) { return true; } // The document is too new if (version > currentVersion) { // qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version; KMessageBox::sorry( QApplication::activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project")); return false; } // Unsupported document versions if (qFuzzyCompare(version, 0.5) || qFuzzyCompare(version, 0.7)) { // 0.7 is unsupported // qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version; KMessageBox::sorry(QApplication::activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project")); return false; } // QDomNode infoXmlNode; QDomElement infoXml; QDomNodeList docs = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")); if (!docs.isEmpty()) { infoXmlNode = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")).at(0); infoXml = infoXmlNode.toElement(); infoXml.setAttribute(QStringLiteral("upgraded"), 1); } m_doc.documentElement().setAttribute(QStringLiteral("upgraded"), 1); if (version <= 0.6) { QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders QDomNode westley = m_doc.elementsByTagName(QStringLiteral("westley")).at(1); QDomNode tractor = m_doc.elementsByTagName(QStringLiteral("tractor")).at(0); QDomNode multitrack = m_doc.elementsByTagName(QStringLiteral("multitrack")).at(0); QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); QDomNode props = m_doc.elementsByTagName(QStringLiteral("properties")).at(0).toElement(); QString profile = props.toElement().attribute(QStringLiteral("videoprofile")); int startPos = props.toElement().attribute(QStringLiteral("timeline_position")).toInt(); if (profile == QLatin1String("dv_wide")) { profile = QStringLiteral("dv_pal_wide"); } // move playlists outside of tractor and add the tracks instead int max = playlists.count(); if (westley.isNull()) { westley = m_doc.createElement(QStringLiteral("westley")); m_doc.documentElement().appendChild(westley); } if (tractor.isNull()) { // qCDebug(KDENLIVE_LOG) << "// NO MLT PLAYLIST, building empty one"; QDomElement blank_tractor = m_doc.createElement(QStringLiteral("tractor")); westley.appendChild(blank_tractor); QDomElement blank_playlist = m_doc.createElement(QStringLiteral("playlist")); blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track")); westley.insertBefore(blank_playlist, QDomNode()); QDomElement blank_track = m_doc.createElement(QStringLiteral("track")); blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track")); blank_tractor.appendChild(blank_track); QDomNodeList kdenlivetracks = m_doc.elementsByTagName(QStringLiteral("kdenlivetrack")); for (int i = 0; i < kdenlivetracks.count(); ++i) { blank_playlist = m_doc.createElement(QStringLiteral("playlist")); blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("playlist") + QString::number(i)); westley.insertBefore(blank_playlist, QDomNode()); blank_track = m_doc.createElement(QStringLiteral("track")); blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("playlist") + QString::number(i)); blank_tractor.appendChild(blank_track); if (kdenlivetracks.at(i).toElement().attribute(QStringLiteral("cliptype")) == QLatin1String("Sound")) { blank_playlist.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); blank_track.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); } } } else for (int i = 0; i < max; ++i) { QDomNode n = playlists.at(i); westley.insertBefore(n, QDomNode()); QDomElement pl = n.toElement(); QDomElement track = m_doc.createElement(QStringLiteral("track")); QString trackType = pl.attribute(QStringLiteral("hide")); if (!trackType.isEmpty()) { track.setAttribute(QStringLiteral("hide"), trackType); } QString playlist_id = pl.attribute(QStringLiteral("id")); if (playlist_id.isEmpty()) { playlist_id = QStringLiteral("black_track"); pl.setAttribute(QStringLiteral("id"), playlist_id); } track.setAttribute(QStringLiteral("producer"), playlist_id); // tractor.appendChild(track); #define KEEP_TRACK_ORDER 1 #ifdef KEEP_TRACK_ORDER tractor.insertAfter(track, QDomNode()); #else // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0 // insertion sort - O( tracks*tracks ) // Note, this breaks _all_ transitions - but you can move them up and down afterwards. QDomElement tractor_elem = tractor.toElement(); if (!tractor_elem.isNull()) { QDomNodeList tracks = tractor_elem.elementsByTagName("track"); int size = tracks.size(); if (size == 0) { tractor.insertAfter(track, QDomNode()); } else { bool inserted = false; for (int i = 0; i < size; ++i) { QDomElement track_elem = tracks.at(i).toElement(); if (track_elem.isNull()) { tractor.insertAfter(track, QDomNode()); inserted = true; break; } else { // qCDebug(KDENLIVE_LOG) << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer"); if (playlist_id < track_elem.attribute("producer")) { tractor.insertBefore(track, track_elem); inserted = true; break; } } } // Reach here, no insertion, insert last if (!inserted) { tractor.insertAfter(track, QDomNode()); } } } else { qCWarning(KDENLIVE_LOG) << "tractor was not a QDomElement"; tractor.insertAfter(track, QDomNode()); } #endif } tractor.removeChild(multitrack); // audio track mixing transitions should not be added to track view, so add required attribute QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition")); max = transitions.count(); for (int i = 0; i < max; ++i) { QDomElement tr = transitions.at(i).toElement(); if (tr.attribute(QStringLiteral("combine")) == QLatin1String("1") && tr.attribute(QStringLiteral("mlt_service")) == QLatin1String("mix")) { QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added")); QDomText value = m_doc.createTextNode(QStringLiteral("237")); property.appendChild(value); tr.appendChild(property); property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); value = m_doc.createTextNode(QStringLiteral("mix")); property.appendChild(value); tr.appendChild(property); } else { // convert transition QDomNamedNodeMap attrs = tr.attributes(); for (int j = 0; j < attrs.count(); ++j) { QString attrName = attrs.item(j).nodeName(); if (attrName != QLatin1String("in") && attrName != QLatin1String("out") && attrName != QLatin1String("id")) { QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), attrName); QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue()); property.appendChild(value); tr.appendChild(property); } } } } // move transitions after tracks for (int i = 0; i < max; ++i) { tractor.insertAfter(transitions.at(0), QDomNode()); } // Fix filters format QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry")); max = entries.count(); for (int i = 0; i < max; ++i) { QString last_id; int effectix = 0; QDomNode m = entries.at(i).firstChild(); while (!m.isNull()) { if (m.toElement().tagName() == QLatin1String("filter")) { QDomElement filt = m.toElement(); QDomNamedNodeMap attrs = filt.attributes(); QString current_id = filt.attribute(QStringLiteral("kdenlive_id")); if (current_id != last_id) { effectix++; last_id = current_id; } QDomElement e = m_doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive_ix")); QDomText value = m_doc.createTextNode(QString::number(effectix)); e.appendChild(value); filt.appendChild(e); for (int j = 0; j < attrs.count(); ++j) { QDomAttr a = attrs.item(j).toAttr(); if (!a.isNull()) { // qCDebug(KDENLIVE_LOG) << " FILTER; adding :" << a.name() << ':' << a.value(); auto property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), a.name()); auto property_value = m_doc.createTextNode(a.value()); property.appendChild(property_value); filt.appendChild(property); } } } m = m.nextSibling(); } } /* QDomNodeList filters = m_doc.elementsByTagName("filter"); max = filters.count(); QString last_id; int effectix = 0; for (int i = 0; i < max; ++i) { QDomElement filt = filters.at(i).toElement(); QDomNamedNodeMap attrs = filt.attributes(); QString current_id = filt.attribute("kdenlive_id"); if (current_id != last_id) { effectix++; last_id = current_id; } QDomElement e = m_doc.createElement("property"); e.setAttribute("name", "kdenlive_ix"); QDomText value = m_doc.createTextNode(QString::number(1)); e.appendChild(value); filt.appendChild(e); for (int j = 0; j < attrs.count(); ++j) { QDomAttr a = attrs.item(j).toAttr(); if (!a.isNull()) { //qCDebug(KDENLIVE_LOG) << " FILTER; adding :" << a.name() << ':' << a.value(); QDomElement e = m_doc.createElement("property"); e.setAttribute("name", a.name()); QDomText value = m_doc.createTextNode(a.value()); e.appendChild(value); filt.appendChild(e); } } }*/ // fix slowmotion QDomNodeList producers = westley.toElement().elementsByTagName(QStringLiteral("producer")); max = producers.count(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); if (prod.attribute(QStringLiteral("mlt_service")) == QLatin1String("framebuffer")) { QString slowmotionprod = prod.attribute(QStringLiteral("resource")); slowmotionprod.replace(QLatin1Char(':'), QLatin1Char('?')); // qCDebug(KDENLIVE_LOG) << "// FOUND WRONG SLOWMO, new: " << slowmotionprod; prod.setAttribute(QStringLiteral("resource"), slowmotionprod); } } // move producers to correct place, markers to a global list, fix clip descriptions QDomElement markers = m_doc.createElement(QStringLiteral("markers")); // This will get the xml producers: producers = m_doc.elementsByTagName(QStringLiteral("producer")); max = producers.count(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(0).toElement(); // add resource also as a property (to allow path correction in setNewResource()) // TODO: will it work with slowmotion? needs testing /*if (!prod.attribute("resource").isEmpty()) { QDomElement prop_resource = m_doc.createElement("property"); prop_resource.setAttribute("name", "resource"); QDomText resource = m_doc.createTextNode(prod.attribute("resource")); prop_resource.appendChild(resource); prod.appendChild(prop_resource); }*/ QDomNode m = prod.firstChild(); if (!m.isNull()) { if (m.toElement().tagName() == QLatin1String("markers")) { QDomNodeList prodchilds = m.childNodes(); int maxchild = prodchilds.count(); for (int k = 0; k < maxchild; ++k) { QDomElement mark = prodchilds.at(0).toElement(); mark.setAttribute(QStringLiteral("id"), prod.attribute(QStringLiteral("id"))); markers.insertAfter(mark, QDomNode()); } prod.removeChild(m); } else if (prod.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) { // convert title clip if (m.toElement().tagName() == QLatin1String("textclip")) { QDomDocument tdoc; QDomElement titleclip = m.toElement(); QDomElement title = tdoc.createElement(QStringLiteral("kdenlivetitle")); tdoc.appendChild(title); QDomNodeList objects = titleclip.childNodes(); int maxchild = objects.count(); for (int k = 0; k < maxchild; ++k) { QDomElement ob = objects.at(k).toElement(); if (ob.attribute(QStringLiteral("type")) == QLatin1String("3")) { // text object - all of this goes into "xmldata"... QDomElement item = tdoc.createElement(QStringLiteral("item")); item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z"))); item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem")); QDomElement position = tdoc.createElement(QStringLiteral("position")); position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x"))); position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y"))); QDomElement content = tdoc.createElement(QStringLiteral("content")); content.setAttribute(QStringLiteral("font"), ob.attribute(QStringLiteral("font_family"))); content.setAttribute(QStringLiteral("font-size"), ob.attribute(QStringLiteral("font_size"))); content.setAttribute(QStringLiteral("font-bold"), ob.attribute(QStringLiteral("bold"))); content.setAttribute(QStringLiteral("font-italic"), ob.attribute(QStringLiteral("italic"))); content.setAttribute(QStringLiteral("font-underline"), ob.attribute(QStringLiteral("underline"))); QString col = ob.attribute(QStringLiteral("color")); QColor c(col); content.setAttribute(QStringLiteral("font-color"), colorToString(c)); // todo: These fields are missing from the newly generated xmldata: // transform, startviewport, endviewport, background QDomText conttxt = tdoc.createTextNode(ob.attribute(QStringLiteral("text"))); content.appendChild(conttxt); item.appendChild(position); item.appendChild(content); title.appendChild(item); } else if (ob.attribute(QStringLiteral("type")) == QLatin1String("5")) { // rectangle object QDomElement item = tdoc.createElement(QStringLiteral("item")); item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z"))); item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem")); QDomElement position = tdoc.createElement(QStringLiteral("position")); position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x"))); position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y"))); QDomElement content = tdoc.createElement(QStringLiteral("content")); QString col = ob.attribute(QStringLiteral("color")); QColor c(col); content.setAttribute(QStringLiteral("brushcolor"), colorToString(c)); QString rect = QStringLiteral("0,0,"); rect.append(ob.attribute(QStringLiteral("width"))); rect.append(QLatin1String(",")); rect.append(ob.attribute(QStringLiteral("height"))); content.setAttribute(QStringLiteral("rect"), rect); item.appendChild(position); item.appendChild(content); title.appendChild(item); } } prod.setAttribute(QStringLiteral("xmldata"), tdoc.toString()); // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder()); // prod.setAttribute("titlename", titleInfo.at(0)); // prod.setAttribute("resource", titleInfo.at(1)); ////qCDebug(KDENLIVE_LOG)<<"TITLE DATA:\n"< 0) { prod.setAttribute(QStringLiteral("out"), QString::number(duration)); } // The clip goes back in, but text clips should not go back in, at least not modified westley.insertBefore(prod, QDomNode()); } QDomNode westley0 = m_doc.elementsByTagName(QStringLiteral("westley")).at(0); if (!markers.firstChild().isNull()) { westley0.appendChild(markers); } /* * Convert as much of the kdenlivedoc as possible. Use the producer in * westley. First, remove the old stuff from westley, and add a new * empty one. Also, track the max id in order to use it for the adding * of groups/folders */ int max_kproducer_id = 0; westley0.removeChild(infoXmlNode); QDomElement infoXml_new = m_doc.createElement(QStringLiteral("kdenlivedoc")); infoXml_new.setAttribute(QStringLiteral("profile"), profile); infoXml.setAttribute(QStringLiteral("position"), startPos); // Add all the producers that has a resource in westley QDomElement westley_element = westley0.toElement(); if (westley_element.isNull()) { qCWarning(KDENLIVE_LOG) << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc"; } else { QDomNodeList wproducers = westley_element.elementsByTagName(QStringLiteral("producer")); int kmax = wproducers.count(); for (int i = 0; i < kmax; ++i) { QDomElement wproducer = wproducers.at(i).toElement(); if (wproducer.isNull()) { qCWarning(KDENLIVE_LOG) << "Found producer in westley0, that was not a QDomElement"; continue; } if (wproducer.attribute(QStringLiteral("id")) == QLatin1String("black")) { continue; } // We have to do slightly different things, depending on the type // qCDebug(KDENLIVE_LOG) << "Converting producer element with type" << wproducer.attribute("type"); if (wproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) { // qCDebug(KDENLIVE_LOG) << "Found TEXT element in producer" << endl; QDomElement kproducer = wproducer.cloneNode(true).toElement(); kproducer.setTagName(QStringLiteral("kdenlive_producer")); infoXml_new.appendChild(kproducer); /* * TODO: Perhaps needs some more changes here to * "frequency", aspect ratio as a float, frame_size, * channels, and later, resource and title name */ } else { QDomElement kproducer = m_doc.createElement(QStringLiteral("kdenlive_producer")); kproducer.setAttribute(QStringLiteral("id"), wproducer.attribute(QStringLiteral("id"))); if (!wproducer.attribute(QStringLiteral("description")).isEmpty()) { kproducer.setAttribute(QStringLiteral("description"), wproducer.attribute(QStringLiteral("description"))); } kproducer.setAttribute(QStringLiteral("resource"), wproducer.attribute(QStringLiteral("resource"))); kproducer.setAttribute(QStringLiteral("type"), wproducer.attribute(QStringLiteral("type"))); // Testing fix for 358 if (!wproducer.attribute(QStringLiteral("aspect_ratio")).isEmpty()) { kproducer.setAttribute(QStringLiteral("aspect_ratio"), wproducer.attribute(QStringLiteral("aspect_ratio"))); } if (!wproducer.attribute(QStringLiteral("source_fps")).isEmpty()) { kproducer.setAttribute(QStringLiteral("fps"), wproducer.attribute(QStringLiteral("source_fps"))); } if (!wproducer.attribute(QStringLiteral("length")).isEmpty()) { kproducer.setAttribute(QStringLiteral("duration"), wproducer.attribute(QStringLiteral("length"))); } infoXml_new.appendChild(kproducer); } if (wproducer.attribute(QStringLiteral("id")).toInt() > max_kproducer_id) { max_kproducer_id = wproducer.attribute(QStringLiteral("id")).toInt(); } } } #define LOOKUP_FOLDER 1 #ifdef LOOKUP_FOLDER /* * Look through all the folder elements of the old doc, for each folder, * for each producer, get the id, look it up in the new doc, set the * groupname and groupid. Note, this does not work at the moment - at * least one folder shows up missing, and clips with no folder does not * show up. */ // QDomElement infoXml_old = infoXmlNode.toElement(); if (!infoXml_old.isNull()) { QDomNodeList folders = infoXml_old.elementsByTagName(QStringLiteral("folder")); int fsize = folders.size(); int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers for (int i = 0; i < fsize; ++i) { QDomElement folder = folders.at(i).toElement(); if (!folder.isNull()) { QString groupName = folder.attribute(QStringLiteral("name")); // qCDebug(KDENLIVE_LOG) << "groupName: " << groupName << " with groupId: " << groupId; QDomNodeList fproducers = folder.elementsByTagName(QStringLiteral("producer")); int psize = fproducers.size(); for (int j = 0; j < psize; ++j) { QDomElement fproducer = fproducers.at(j).toElement(); if (!fproducer.isNull()) { QString id = fproducer.attribute(QStringLiteral("id")); // This is not very effective, but compared to loading the clips, its a breeze QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName(QStringLiteral("kdenlive_producer")); int kpsize = kdenlive_producers.size(); for (int k = 0; k < kpsize; ++k) { QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure if (id == kproducer.attribute(QStringLiteral("id"))) { // We do not check that it already is part of a folder kproducer.setAttribute(QStringLiteral("groupid"), groupId); kproducer.setAttribute(QStringLiteral("groupname"), groupName); break; } } } } ++groupId; } } } #endif QDomNodeList elements = westley.childNodes(); max = elements.count(); for (int i = 0; i < max; ++i) { QDomElement prod = elements.at(0).toElement(); westley0.insertAfter(prod, QDomNode()); } westley0.appendChild(infoXml_new); westley0.removeChild(westley); // adds information to QDomNodeList kproducers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer")); QDomNodeList avfiles = infoXml_old.elementsByTagName(QStringLiteral("avfile")); // qCDebug(KDENLIVE_LOG) << "found" << avfiles.count() << "s and" << kproducers.count() << "s"; for (int i = 0; i < avfiles.count(); ++i) { QDomElement avfile = avfiles.at(i).toElement(); QDomElement kproducer; if (avfile.isNull()) { qCWarning(KDENLIVE_LOG) << "found an that is not a QDomElement"; } else { QString id = avfile.attribute(QStringLiteral("id")); // this is horrible, must be rewritten, it's just for test for (int j = 0; j < kproducers.count(); ++j) { ////qCDebug(KDENLIVE_LOG) << "checking with id" << kproducers.at(j).toElement().attribute("id"); if (kproducers.at(j).toElement().attribute(QStringLiteral("id")) == id) { kproducer = kproducers.at(j).toElement(); break; } } if (kproducer == QDomElement()) { qCWarning(KDENLIVE_LOG) << "no match for with id =" << id; } else { ////qCDebug(KDENLIVE_LOG) << "ready to set additional 's attributes (id =" << id << ')'; kproducer.setAttribute(QStringLiteral("channels"), avfile.attribute(QStringLiteral("channels"))); kproducer.setAttribute(QStringLiteral("duration"), avfile.attribute(QStringLiteral("duration"))); kproducer.setAttribute(QStringLiteral("frame_size"), avfile.attribute(QStringLiteral("width")) + QLatin1Char('x') + avfile.attribute(QStringLiteral("height"))); kproducer.setAttribute(QStringLiteral("frequency"), avfile.attribute(QStringLiteral("frequency"))); if (kproducer.attribute(QStringLiteral("description")).isEmpty() && !avfile.attribute(QStringLiteral("description")).isEmpty()) { kproducer.setAttribute(QStringLiteral("description"), avfile.attribute(QStringLiteral("description"))); } } } } infoXml = infoXml_new; } if (version <= 0.81) { // Add the tracks information QString tracksOrder = infoXml.attribute(QStringLiteral("tracks")); if (tracksOrder.isEmpty()) { QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track")); for (int i = 0; i < tracks.count(); ++i) { QDomElement track = tracks.at(i).toElement(); if (track.attribute(QStringLiteral("producer")) != QLatin1String("black_track")) { if (track.attribute(QStringLiteral("hide")) == QLatin1String("video")) { tracksOrder.append(QLatin1Char('a')); } else { tracksOrder.append(QLatin1Char('v')); } } } } QDomElement tracksinfo = m_doc.createElement(QStringLiteral("tracksinfo")); for (int i = 0; i < tracksOrder.size(); ++i) { QDomElement trackinfo = m_doc.createElement(QStringLiteral("trackinfo")); if (tracksOrder.data()[i] == QLatin1Char('a')) { trackinfo.setAttribute(QStringLiteral("type"), QStringLiteral("audio")); trackinfo.setAttribute(QStringLiteral("blind"), 1); } else { trackinfo.setAttribute(QStringLiteral("blind"), 0); } trackinfo.setAttribute(QStringLiteral("mute"), 0); trackinfo.setAttribute(QStringLiteral("locked"), 0); tracksinfo.appendChild(trackinfo); } infoXml.appendChild(tracksinfo); } if (version <= 0.82) { // Convert s in s (MLT extreme makeover) QDomNodeList westleyNodes = m_doc.elementsByTagName(QStringLiteral("westley")); for (int i = 0; i < westleyNodes.count(); ++i) { QDomElement westley = westleyNodes.at(i).toElement(); westley.setTagName(QStringLiteral("mlt")); } } if (version <= 0.83) { // Replace point size with pixel size in text titles if (m_doc.toString().contains(QStringLiteral("font-size"))) { KMessageBox::ButtonCode convert = KMessageBox::Continue; QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer")); for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) { QDomElement kproducer = kproducerNodes.at(i).toElement(); if (kproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) { QDomDocument data; data.setContent(kproducer.attribute(QStringLiteral("xmldata"))); QDomNodeList items = data.firstChild().childNodes(); for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) { if (items.at(j).attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) { QDomNamedNodeMap textProperties = items.at(j).namedItem(QStringLiteral("content")).attributes(); if (textProperties.namedItem(QStringLiteral("font-pixel-size")).isNull() && !textProperties.namedItem(QStringLiteral("font-size")).isNull()) { // Ask the user if he wants to convert if (convert != KMessageBox::Yes && convert != KMessageBox::No) { convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo( QApplication::activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do " "you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they " "were first created on, or you could have to adjust their size."), i18n("Update Text Clips")); } if (convert == KMessageBox::Yes) { QFont font; font.setPointSize(textProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt()); QDomElement content = items.at(j).namedItem(QStringLiteral("content")).toElement(); content.setAttribute(QStringLiteral("font-pixel-size"), QFontInfo(font).pixelSize()); content.removeAttribute(QStringLiteral("font-size")); kproducer.setAttribute(QStringLiteral("xmldata"), data.toString()); /* * You may be tempted to delete the preview file * to force its recreation: bad idea (see * http://www.kdenlive.org/mantis/view.php?id=749) */ } } } } } } } // Fill the element QDomElement docProperties = infoXml.firstChildElement(QStringLiteral("documentproperties")); if (docProperties.isNull()) { docProperties = m_doc.createElement(QStringLiteral("documentproperties")); docProperties.setAttribute(QStringLiteral("zonein"), infoXml.attribute(QStringLiteral("zonein"))); docProperties.setAttribute(QStringLiteral("zoneout"), infoXml.attribute(QStringLiteral("zoneout"))); docProperties.setAttribute(QStringLiteral("zoom"), infoXml.attribute(QStringLiteral("zoom"))); docProperties.setAttribute(QStringLiteral("position"), infoXml.attribute(QStringLiteral("position"))); infoXml.appendChild(docProperties); } } if (version <= 0.84) { // update the title clips to use the new MLT kdenlivetitle producer QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer")); for (int i = 0; i < kproducerNodes.count(); ++i) { QDomElement kproducer = kproducerNodes.at(i).toElement(); if (kproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) { QString data = kproducer.attribute(QStringLiteral("xmldata")); QString datafile = kproducer.attribute(QStringLiteral("resource")); if (!datafile.endsWith(QLatin1String(".kdenlivetitle"))) { datafile = QString(); kproducer.setAttribute(QStringLiteral("resource"), QString()); } QString id = kproducer.attribute(QStringLiteral("id")); QDomNodeList mltproducers = m_doc.elementsByTagName(QStringLiteral("producer")); bool foundData = false; bool foundResource = false; bool foundService = false; for (int j = 0; j < mltproducers.count(); ++j) { QDomElement wproducer = mltproducers.at(j).toElement(); if (wproducer.attribute(QStringLiteral("id")) == id) { QDomNodeList props = wproducer.childNodes(); for (int k = 0; k < props.count(); ++k) { if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("xmldata")) { props.at(k).firstChild().setNodeValue(data); foundData = true; } else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("mlt_service")) { props.at(k).firstChild().setNodeValue(QStringLiteral("kdenlivetitle")); foundService = true; } else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("resource")) { props.at(k).firstChild().setNodeValue(datafile); foundResource = true; } } if (!foundData) { QDomElement e = m_doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), QStringLiteral("xmldata")); QDomText value = m_doc.createTextNode(data); e.appendChild(value); wproducer.appendChild(e); } if (!foundService) { QDomElement e = m_doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); QDomText value = m_doc.createTextNode(QStringLiteral("kdenlivetitle")); e.appendChild(value); wproducer.appendChild(e); } if (!foundResource) { QDomElement e = m_doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), QStringLiteral("resource")); QDomText value = m_doc.createTextNode(datafile); e.appendChild(value); wproducer.appendChild(e); } break; } } } } } if (version <= 0.85) { // update the LADSPA effects to use the new ladspa.id format instead of external xml file QDomNodeList effectNodes = m_doc.elementsByTagName(QStringLiteral("filter")); for (int i = 0; i < effectNodes.count(); ++i) { QDomElement effect = effectNodes.at(i).toElement(); if (EffectsList::property(effect, QStringLiteral("mlt_service")) == QLatin1String("ladspa")) { // Needs to be converted QStringList info = getInfoFromEffectName(EffectsList::property(effect, QStringLiteral("kdenlive_id"))); if (info.isEmpty()) { continue; } // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names EffectsList::setProperty(effect, QStringLiteral("kdenlive_id"), info.at(0)); EffectsList::setProperty(effect, QStringLiteral("tag"), info.at(0)); EffectsList::setProperty(effect, QStringLiteral("mlt_service"), info.at(0)); EffectsList::removeProperty(effect, QStringLiteral("src")); for (int j = 1; j < info.size(); ++j) { QString value = EffectsList::property(effect, info.at(j).section(QLatin1Char('='), 0, 0)); if (!value.isEmpty()) { // update parameter name EffectsList::renameProperty(effect, info.at(j).section(QLatin1Char('='), 0, 0), info.at(j).section(QLatin1Char('='), 1, 1)); } } } } } if (version <= 0.86) { // Make sure we don't have avformat-novalidate producers, since it caused crashes QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int max = producers.count(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); if (EffectsList::property(prod, QStringLiteral("mlt_service")) == QLatin1String("avformat-novalidate")) { EffectsList::setProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("avformat")); } } // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last // keyframe to real end of transition // Get profile info (width / height) int profileWidth; int profileHeight; QDomElement profile = m_doc.firstChildElement(QStringLiteral("profile")); if (profile.isNull()) { profile = infoXml.firstChildElement(QStringLiteral("profileinfo")); if (!profile.isNull()) { // old MLT format, we need to add profile QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt")); QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer")); QDomElement pr = profile.cloneNode().toElement(); pr.setTagName(QStringLiteral("profile")); mlt.insertBefore(pr, firstProd); } } if (profile.isNull()) { // could not find profile info, set PAL profileWidth = 720; profileHeight = 576; } else { profileWidth = profile.attribute(QStringLiteral("width")).toInt(); profileHeight = profile.attribute(QStringLiteral("height")).toInt(); } QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition")); max = transitions.count(); for (int i = 0; i < max; ++i) { QDomElement trans = transitions.at(i).toElement(); int out = trans.attribute(QStringLiteral("out")).toInt() - trans.attribute(QStringLiteral("in")).toInt(); QString geom = EffectsList::property(trans, QStringLiteral("geometry")); Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight); Mlt::GeometryItem item; if (g->next_key(&item, out) == 0) { // We have a keyframe just after last frame, try to move it to last frame if (item.frame() == out + 1) { item.frame(out); g->insert(item); g->remove(out + 1); EffectsList::setProperty(trans, QStringLiteral("geometry"), QString::fromLatin1(g->serialise())); } } delete g; } } if (version <= 0.87) { if (!m_doc.firstChildElement(QStringLiteral("mlt")).hasAttribute(QStringLiteral("LC_NUMERIC"))) { m_doc.firstChildElement(QStringLiteral("mlt")).setAttribute(QStringLiteral("LC_NUMERIC"), QStringLiteral("C")); } } if (version <= 0.88) { // convert to new MLT-only format QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); QDomDocumentFragment frag = m_doc.createDocumentFragment(); // Create Bin Playlist QDomElement main_playlist = m_doc.createElement(QStringLiteral("playlist")); QDomElement prop = m_doc.createElement(QStringLiteral("property")); prop.setAttribute(QStringLiteral("name"), QStringLiteral("xml_retain")); QDomText val = m_doc.createTextNode(QStringLiteral("1")); prop.appendChild(val); main_playlist.appendChild(prop); // Move markers QDomNodeList markers = m_doc.elementsByTagName(QStringLiteral("marker")); for (int i = 0; i < markers.count(); ++i) { QDomElement marker = markers.at(i).toElement(); QDomElement property = m_doc.createElement(QStringLiteral("property")); - property.setAttribute(QStringLiteral("name"), - QStringLiteral("kdenlive:marker.") + marker.attribute(QStringLiteral("id")) + QLatin1Char(':') + - marker.attribute(QStringLiteral("time"))); + property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:marker.") + marker.attribute(QStringLiteral("id")) + QLatin1Char(':') + + marker.attribute(QStringLiteral("time"))); QDomText val_node = m_doc.createTextNode(marker.attribute(QStringLiteral("type")) + QLatin1Char(':') + marker.attribute(QStringLiteral("comment"))); property.appendChild(val_node); main_playlist.appendChild(property); } // Move guides QDomNodeList guides = m_doc.elementsByTagName(QStringLiteral("guide")); for (int i = 0; i < guides.count(); ++i) { QDomElement guide = guides.at(i).toElement(); QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:guide.") + guide.attribute(QStringLiteral("time"))); QDomText val_node = m_doc.createTextNode(guide.attribute(QStringLiteral("comment"))); property.appendChild(val_node); main_playlist.appendChild(property); } // Move folders QDomNodeList folders = m_doc.elementsByTagName(QStringLiteral("folder")); for (int i = 0; i < folders.count(); ++i) { QDomElement folder = folders.at(i).toElement(); QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folder.-1.") + folder.attribute(QStringLiteral("id"))); QDomText val_node = m_doc.createTextNode(folder.attribute(QStringLiteral("name"))); property.appendChild(val_node); main_playlist.appendChild(property); } QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt")); main_playlist.setAttribute(QStringLiteral("id"), pCore->binController()->binPlaylistId()); mlt.toElement().setAttribute(QStringLiteral("producer"), pCore->binController()->binPlaylistId()); QStringList ids; QStringList slowmotionIds; QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer")); QDomNodeList kdenlive_producers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer")); // Rename all track producers to correct name: "id_playlistName" instead of "id_trackNumber" QMap trackRenaming; // Create a list of which producers / track on which the producer is QMap playlistForId; QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry")); for (int i = 0; i < entries.count(); i++) { QDomElement entry = entries.at(i).toElement(); QString entryId = entry.attribute(QStringLiteral("producer")); if (entryId == QLatin1String("black")) { continue; } bool audioOnlyProducer = false; if (trackRenaming.contains(entryId)) { // rename entry.setAttribute(QStringLiteral("producer"), trackRenaming.value(entryId)); continue; } if (entryId.endsWith(QLatin1String("_video"))) { // Video only producers are not track aware continue; } if (entryId.endsWith(QLatin1String("_audio"))) { // Audio only producer audioOnlyProducer = true; entryId = entryId.section(QLatin1Char('_'), 0, -2); } if (!entryId.contains(QLatin1Char('_'))) { // not a track producer playlistForId.insert(entryId, entry.parentNode().toElement().attribute(QStringLiteral("id"))); continue; } if (entryId.startsWith(QLatin1String("slowmotion:"))) { // Check broken slowmotion producers (they should not be track aware) QString newId = QStringLiteral("slowmotion:") + entryId.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0) + QLatin1Char(':') + entryId.section(QLatin1Char(':'), 2); trackRenaming.insert(entryId, newId); entry.setAttribute(QStringLiteral("producer"), newId); continue; } QString track = entryId.section(QLatin1Char('_'), 1, 1); QString playlistId = entry.parentNode().toElement().attribute(QStringLiteral("id")); if (track == playlistId) { continue; } QString newId = entryId.section(QLatin1Char('_'), 0, 0) + QLatin1Char('_') + playlistId; if (audioOnlyProducer) { newId.append(QStringLiteral("_audio")); trackRenaming.insert(entryId + QStringLiteral("_audio"), newId); } else { trackRenaming.insert(entryId, newId); } entry.setAttribute(QStringLiteral("producer"), newId); } if (!trackRenaming.isEmpty()) { for (int i = 0; i < producers.count(); ++i) { QDomElement prod = producers.at(i).toElement(); QString id = prod.attribute(QStringLiteral("id")); if (trackRenaming.contains(id)) { prod.setAttribute(QStringLiteral("id"), trackRenaming.value(id)); } } } // Create easily searchable index of original producers QMap m_source_producers; for (int j = 0; j < kdenlive_producers.count(); j++) { QDomElement prod = kdenlive_producers.at(j).toElement(); QString id = prod.attribute(QStringLiteral("id")); m_source_producers.insert(id, prod); } for (int i = 0; i < producers.count(); ++i) { QDomElement prod = producers.at(i).toElement(); QString id = prod.attribute(QStringLiteral("id")); if (id == QLatin1String("black")) { continue; } if (id.startsWith(QLatin1String("slowmotion"))) { // No need to process slowmotion producers QString slowmo = id.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0); if (!slowmotionIds.contains(slowmo)) { slowmotionIds << slowmo; } continue; } QString prodId = id.section(QLatin1Char('_'), 0, 0); if (ids.contains(prodId)) { // Make sure we didn't create a duplicate if (ids.contains(id)) { // we have a duplicate, check if this needs to be a track producer QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); int a_ix = EffectsList::property(prod, QStringLiteral("audio_index")).toInt(); if (service == QLatin1String("xml") || service == QLatin1String("consumer") || (service.contains(QStringLiteral("avformat")) && a_ix != -1)) { // This should be a track producer, rename QString newId = id + QLatin1Char('_') + playlistForId.value(id); prod.setAttribute(QStringLiteral("id"), newId); for (int j = 0; j < entries.count(); j++) { QDomElement entry = entries.at(j).toElement(); QString entryId = entry.attribute(QStringLiteral("producer")); if (entryId == id) { entry.setAttribute(QStringLiteral("producer"), newId); } } } else { // This is a duplicate, remove mlt.removeChild(prod); i--; } } // Already processed, continue continue; } if (id == prodId) { // This is an original producer, move it to the main playlist QDomElement entry = m_doc.createElement(QStringLiteral("entry")); entry.setAttribute(QStringLiteral("producer"), id); main_playlist.appendChild(entry); QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); if (service == QLatin1String("kdenlivetitle")) { fixTitleProducerLocale(prod); } QDomElement source = m_source_producers.value(id); if (!source.isNull()) { updateProducerInfo(prod, source); entry.setAttribute(QStringLiteral("in"), QStringLiteral("0")); entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1)); } frag.appendChild(prod); // Changing prod parent removes it from list, so rewind index i--; } else { QDomElement originalProd = prod.cloneNode().toElement(); originalProd.setAttribute(QStringLiteral("id"), prodId); if (id.endsWith(QLatin1String("_audio"))) { EffectsList::removeProperty(originalProd, QStringLiteral("video_index")); } else if (id.endsWith(QLatin1String("_video"))) { EffectsList::removeProperty(originalProd, QStringLiteral("audio_index")); } QDomElement source = m_source_producers.value(prodId); QDomElement entry = m_doc.createElement(QStringLiteral("entry")); if (!source.isNull()) { updateProducerInfo(originalProd, source); entry.setAttribute(QStringLiteral("in"), QStringLiteral("0")); entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1)); } frag.appendChild(originalProd); entry.setAttribute(QStringLiteral("producer"), prodId); main_playlist.appendChild(entry); } ids.append(prodId); } // Make sure to include producers that were not in timeline for (int j = 0; j < kdenlive_producers.count(); j++) { QDomElement prod = kdenlive_producers.at(j).toElement(); QString id = prod.attribute(QStringLiteral("id")); if (!ids.contains(id)) { // Clip was not in timeline, create it QDomElement originalProd = prod.cloneNode().toElement(); originalProd.setTagName(QStringLiteral("producer")); EffectsList::setProperty(originalProd, QStringLiteral("resource"), originalProd.attribute(QStringLiteral("resource"))); updateProducerInfo(originalProd, prod); originalProd.removeAttribute(QStringLiteral("proxy")); originalProd.removeAttribute(QStringLiteral("type")); originalProd.removeAttribute(QStringLiteral("file_hash")); originalProd.removeAttribute(QStringLiteral("file_size")); originalProd.removeAttribute(QStringLiteral("frame_size")); originalProd.removeAttribute(QStringLiteral("proxy_out")); originalProd.removeAttribute(QStringLiteral("zone_out")); originalProd.removeAttribute(QStringLiteral("zone_in")); originalProd.removeAttribute(QStringLiteral("name")); originalProd.removeAttribute(QStringLiteral("type")); originalProd.removeAttribute(QStringLiteral("duration")); originalProd.removeAttribute(QStringLiteral("cutzones")); int type = prod.attribute(QStringLiteral("type")).toInt(); QString mltService; switch (type) { case 4: mltService = QStringLiteral("colour"); break; case 5: case 7: mltService = QStringLiteral("qimage"); break; case 6: mltService = QStringLiteral("kdenlivetitle"); break; case 9: mltService = QStringLiteral("xml"); break; default: mltService = QStringLiteral("avformat"); break; } EffectsList::setProperty(originalProd, QStringLiteral("mlt_service"), mltService); EffectsList::setProperty(originalProd, QStringLiteral("mlt_type"), QStringLiteral("producer")); QDomElement entry = m_doc.createElement(QStringLiteral("entry")); entry.setAttribute(QStringLiteral("in"), QStringLiteral("0")); entry.setAttribute(QStringLiteral("out"), QString::number(prod.attribute(QStringLiteral("duration")).toInt() - 1)); entry.setAttribute(QStringLiteral("producer"), id); main_playlist.appendChild(entry); if (type == 6) { fixTitleProducerLocale(originalProd); } frag.appendChild(originalProd); ids << id; } } // Set clip folders for (int j = 0; j < kdenlive_producers.count(); j++) { QDomElement prod = kdenlive_producers.at(j).toElement(); QString id = prod.attribute(QStringLiteral("id")); QString folder = prod.attribute(QStringLiteral("groupid")); QDomNodeList mlt_producers = frag.childNodes(); for (int k = 0; k < mlt_producers.count(); k++) { QDomElement mltprod = mlt_producers.at(k).toElement(); if (mltprod.tagName() != QLatin1String("producer")) { continue; } if (mltprod.attribute(QStringLiteral("id")) == id) { if (!folder.isEmpty()) { // We have found our producer, set folder info QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folderid")); QDomText val_node = m_doc.createTextNode(folder); property.appendChild(val_node); mltprod.appendChild(property); } break; } } } // Make sure all slowmotion producers have a master clip for (int i = 0; i < slowmotionIds.count(); i++) { const QString &slo = slowmotionIds.at(i); if (!ids.contains(slo)) { // rebuild producer from Kdenlive's old xml format for (int j = 0; j < kdenlive_producers.count(); j++) { QDomElement prod = kdenlive_producers.at(j).toElement(); QString id = prod.attribute(QStringLiteral("id")); if (id == slo) { // We found the kdenlive_producer, build MLT producer QDomElement original = m_doc.createElement(QStringLiteral("producer")); original.setAttribute(QStringLiteral("in"), 0); original.setAttribute(QStringLiteral("out"), prod.attribute(QStringLiteral("duration")).toInt() - 1); original.setAttribute(QStringLiteral("id"), id); QDomElement property = m_doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("resource")); QDomText val_node = m_doc.createTextNode(prod.attribute(QStringLiteral("resource"))); property.appendChild(val_node); original.appendChild(property); QDomElement prop2 = m_doc.createElement(QStringLiteral("property")); prop2.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); QDomText val2 = m_doc.createTextNode(QStringLiteral("avformat")); prop2.appendChild(val2); original.appendChild(prop2); QDomElement prop3 = m_doc.createElement(QStringLiteral("property")); prop3.setAttribute(QStringLiteral("name"), QStringLiteral("length")); QDomText val3 = m_doc.createTextNode(prod.attribute(QStringLiteral("duration"))); prop3.appendChild(val3); original.appendChild(prop3); QDomElement entry = m_doc.createElement(QStringLiteral("entry")); entry.setAttribute(QStringLiteral("in"), original.attribute(QStringLiteral("in"))); entry.setAttribute(QStringLiteral("out"), original.attribute(QStringLiteral("out"))); entry.setAttribute(QStringLiteral("producer"), id); main_playlist.appendChild(entry); frag.appendChild(original); ids << slo; break; } } } } frag.appendChild(main_playlist); mlt.insertBefore(frag, firstProd); } if (version < 0.91) { // Migrate track properties QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt")); QDomNodeList old_tracks = m_doc.elementsByTagName(QStringLiteral("trackinfo")); QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track")); QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); for (int i = 0; i < old_tracks.count(); i++) { QString playlistName = tracks.at(i + 1).toElement().attribute(QStringLiteral("producer")); // find playlist for track QDomElement trackPlaylist; for (int j = 0; j < playlists.count(); j++) { if (playlists.at(j).toElement().attribute(QStringLiteral("id")) == playlistName) { trackPlaylist = playlists.at(j).toElement(); break; } } if (!trackPlaylist.isNull()) { QDomElement kdenliveTrack = old_tracks.at(i).toElement(); if (kdenliveTrack.attribute(QStringLiteral("type")) == QLatin1String("audio")) { EffectsList::setProperty(trackPlaylist, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1")); } if (kdenliveTrack.attribute(QStringLiteral("locked")) == QLatin1String("1")) { EffectsList::setProperty(trackPlaylist, QStringLiteral("kdenlive:locked_track"), QStringLiteral("1")); } EffectsList::setProperty(trackPlaylist, QStringLiteral("kdenlive:track_name"), kdenliveTrack.attribute(QStringLiteral("trackname"))); } } // Find bin playlist playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); QDomElement playlist; for (int i = 0; i < playlists.count(); i++) { if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == pCore->binController()->binPlaylistId()) { playlist = playlists.at(i).toElement(); break; } } // Migrate document notes QDomNodeList notesList = m_doc.elementsByTagName(QStringLiteral("documentnotes")); if (!notesList.isEmpty()) { QDomElement notes_elem = notesList.at(0).toElement(); QString notes = notes_elem.firstChild().nodeValue(); EffectsList::setProperty(playlist, QStringLiteral("kdenlive:documentnotes"), notes); } // Migrate clip groups QDomNodeList groupElement = m_doc.elementsByTagName(QStringLiteral("groups")); if (!groupElement.isEmpty()) { QDomElement groups = groupElement.at(0).toElement(); QDomDocument d2; d2.importNode(groups, true); EffectsList::setProperty(playlist, QStringLiteral("kdenlive:clipgroups"), d2.toString()); } // Migrate custom effects QDomNodeList effectsElement = m_doc.elementsByTagName(QStringLiteral("customeffects")); if (!effectsElement.isEmpty()) { QDomElement effects = effectsElement.at(0).toElement(); QDomDocument d2; d2.importNode(effects, true); EffectsList::setProperty(playlist, QStringLiteral("kdenlive:customeffects"), d2.toString()); } EffectsList::setProperty(playlist, QStringLiteral("kdenlive:docproperties.version"), QString::number(currentVersion)); if (!infoXml.isNull()) { EffectsList::setProperty(playlist, QStringLiteral("kdenlive:docproperties.projectfolder"), infoXml.attribute(QStringLiteral("projectfolder"))); } // Remove deprecated Kdenlive extra info from xml doc before sending it to MLT QDomElement docXml = mlt.firstChildElement(QStringLiteral("kdenlivedoc")); if (!docXml.isNull()) { mlt.removeChild(docXml); } } if (version < 0.92) { // Luma transition used for wipe is deprecated, we now use a composite, convert QDomNodeList transitionList = m_doc.elementsByTagName(QStringLiteral("transition")); QDomElement trans; for (int i = 0; i < transitionList.count(); i++) { trans = transitionList.at(i).toElement(); QString id = EffectsList::property(trans, QStringLiteral("kdenlive_id")); if (id == QLatin1String("luma")) { EffectsList::setProperty(trans, QStringLiteral("kdenlive_id"), QStringLiteral("wipe")); EffectsList::setProperty(trans, QStringLiteral("mlt_service"), QStringLiteral("composite")); bool reverse = EffectsList::property(trans, QStringLiteral("reverse")).toInt() != 0; EffectsList::setProperty(trans, QStringLiteral("luma_invert"), EffectsList::property(trans, QStringLiteral("invert"))); EffectsList::setProperty(trans, QStringLiteral("luma"), EffectsList::property(trans, QStringLiteral("resource"))); EffectsList::removeProperty(trans, QStringLiteral("invert")); EffectsList::removeProperty(trans, QStringLiteral("reverse")); EffectsList::removeProperty(trans, QStringLiteral("resource")); if (reverse) { EffectsList::setProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100;-1=0%/0%:100%x100%:0")); } else { EffectsList::setProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:0;-1=0%/0%:100%x100%:100")); } EffectsList::setProperty(trans, QStringLiteral("aligned"), QStringLiteral("0")); EffectsList::setProperty(trans, QStringLiteral("fill"), QStringLiteral("1")); } } } if (version < 0.93) { // convert old keyframe filters to animated // these filters were "animated" by adding several instance of the filter, each one having a start and end tag. // We convert by parsing the start and end tags vor values and adding all to the new animated parameter QMap keyframeFilterToConvert; keyframeFilterToConvert.insert(QStringLiteral("volume"), QStringList() << QStringLiteral("gain") << QStringLiteral("end") << QStringLiteral("level")); - keyframeFilterToConvert.insert(QStringLiteral("brightness"), - QStringList() << QStringLiteral("start") << QStringLiteral("end") << QStringLiteral("level")); + keyframeFilterToConvert.insert(QStringLiteral("brightness"), QStringList() + << QStringLiteral("start") << QStringLiteral("end") << QStringLiteral("level")); QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry")); for (int i = 0; i < entries.count(); i++) { QDomNode entry = entries.at(i); QDomNodeList effects = entry.toElement().elementsByTagName(QStringLiteral("filter")); QStringList parsedIds; for (int j = 0; j < effects.count(); j++) { QDomElement eff = effects.at(j).toElement(); QString id = EffectsList::property(eff, QStringLiteral("kdenlive_id")); if (keyframeFilterToConvert.contains(id) && !parsedIds.contains(id)) { parsedIds << id; QMap values; QStringList conversionParams = keyframeFilterToConvert.value(id); int offset = eff.attribute(QStringLiteral("in")).toInt(); int out = eff.attribute(QStringLiteral("out")).toInt(); convertKeyframeEffect(eff, conversionParams, values, offset); EffectsList::removeProperty(eff, conversionParams.at(0)); EffectsList::removeProperty(eff, conversionParams.at(1)); for (int k = j + 1; k < effects.count(); k++) { QDomElement subEffect = effects.at(k).toElement(); QString subId = EffectsList::property(subEffect, QStringLiteral("kdenlive_id")); if (subId == id) { convertKeyframeEffect(subEffect, conversionParams, values, offset); out = subEffect.attribute(QStringLiteral("out")).toInt(); entry.removeChild(subEffect); k--; } } QStringList parsedValues; QLocale locale; QMapIterator l(values); if (id == QLatin1String("volume")) { // convert old volume range (0-300) to new dB values (-60-60) while (l.hasNext()) { l.next(); double v = l.value(); if (v <= 0) { v = -60; } else { v = log10(v) * 20; } parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(v); } } else { while (l.hasNext()) { l.next(); parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(l.value()); } } EffectsList::setProperty(eff, conversionParams.at(2), parsedValues.join(QLatin1Char(';'))); // EffectsList::setProperty(eff, QStringLiteral("kdenlive:sync_in_out"), QStringLiteral("1")); eff.setAttribute(QStringLiteral("out"), out); } } } } if (version < 0.94) { // convert slowmotion effects/producers QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int max = producers.count(); QStringList slowmoIds; for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); QString id = prod.attribute(QStringLiteral("id")); if (id.startsWith(QLatin1String("slowmotion"))) { QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); if (service == QLatin1String("framebuffer")) { // convert to new timewarp producer prod.setAttribute(QStringLiteral("id"), id + QStringLiteral(":1")); slowmoIds << id; EffectsList::setProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("timewarp")); QString resource = EffectsList::property(prod, QStringLiteral("resource")); EffectsList::setProperty(prod, QStringLiteral("warp_resource"), resource.section(QLatin1Char('?'), 0, 0)); EffectsList::setProperty(prod, QStringLiteral("warp_speed"), resource.section(QLatin1Char('?'), 1).section(QLatin1Char(':'), 0, 0)); EffectsList::setProperty(prod, QStringLiteral("resource"), resource.section(QLatin1Char('?'), 1) + QLatin1Char(':') + resource.section(QLatin1Char('?'), 0, 0)); EffectsList::setProperty(prod, QStringLiteral("audio_index"), QStringLiteral("-1")); } } } if (!slowmoIds.isEmpty()) { producers = m_doc.elementsByTagName(QStringLiteral("entry")); max = producers.count(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); QString entryId = prod.attribute(QStringLiteral("producer")); if (slowmoIds.contains(entryId)) { prod.setAttribute(QStringLiteral("producer"), entryId + QStringLiteral(":1")); } } } // qCDebug(KDENLIVE_LOG)<<"------------------------\n"< markersList; for (int i = 0; i < props.count(); ++i) { QDomNode n = props.at(i); QString prop = n.toElement().attribute(QStringLiteral("name")); if (prop.startsWith(QLatin1String("kdenlive:guide."))) { // Process guide double guidePos = prop.section(QLatin1Char('.'), 1).toDouble(); QJsonObject currentGuide; currentGuide.insert(QStringLiteral("pos"), QJsonValue(GenTime(guidePos).frames(pCore->getCurrentFps()))); currentGuide.insert(QStringLiteral("comment"), QJsonValue(n.firstChild().nodeValue())); currentGuide.insert(QStringLiteral("type"), QJsonValue(0)); // Clear entry in old format n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_")); guidesList.push_back(currentGuide); } else if (prop.startsWith(QLatin1String("kdenlive:marker."))) { // Process marker double markerPos = prop.section(QLatin1Char(':'), -1).toDouble(); QString markerBinClip = prop.section(QLatin1Char('.'), 1).section(QLatin1Char(':'), 0, 0); QString markerData = n.firstChild().nodeValue(); int markerType = markerData.section(QLatin1Char(':'), 0, 0).toInt(); QString markerComment = markerData.section(QLatin1Char(':'), 1); QJsonObject currentMarker; currentMarker.insert(QStringLiteral("pos"), QJsonValue(GenTime(markerPos).frames(pCore->getCurrentFps()))); currentMarker.insert(QStringLiteral("comment"), QJsonValue(markerComment)); currentMarker.insert(QStringLiteral("type"), QJsonValue(markerType)); // Clear entry in old format n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_")); if (markersList.contains(markerBinClip)) { // we already have a marker list for this clip QJsonArray markerList = markersList.value(markerBinClip); markerList.push_back(currentMarker); markersList.insert(markerBinClip, markerList); } else { QJsonArray markerList; markerList.push_back(currentMarker); markersList.insert(markerBinClip, markerList); } } } if (!guidesList.isEmpty()) { QJsonDocument json(guidesList); EffectsList::setProperty(main_playlist, QStringLiteral("kdenlive:docproperties.guides"), json.toJson()); } // Update producers QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int max = producers.count(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); if (prod.isNull()) continue; // Move to new kdenlive:id format const QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0); EffectsList::setProperty(prod, QStringLiteral("kdenlive:id"), id); if (markersList.contains(id)) { QJsonDocument json(markersList.value(id)); EffectsList::setProperty(prod, QStringLiteral("kdenlive:markers"), json.toJson()); } // Check image sequences with buggy begin frame number const QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); if (service == QLatin1String("pixbuf") || service == QLatin1String("qimage")) { QString resource = EffectsList::property(prod, QStringLiteral("resource")); if (resource.contains(QStringLiteral("?begin:"))) { resource.replace(QStringLiteral("?begin:"), QStringLiteral("?begin=")); EffectsList::setProperty(prod, QStringLiteral("resource"), resource); } } } } m_modified = true; return true; } void DocumentValidator::convertKeyframeEffect(const QDomElement &effect, const QStringList ¶ms, QMap &values, int offset) { QLocale locale; int in = effect.attribute(QStringLiteral("in")).toInt() - offset; values.insert(in, locale.toDouble(EffectsList::property(effect, params.at(0)))); QString endValue = EffectsList::property(effect, params.at(1)); if (!endValue.isEmpty()) { int out = effect.attribute(QStringLiteral("out")).toInt() - offset; values.insert(out, locale.toDouble(endValue)); } } void DocumentValidator::updateProducerInfo(const QDomElement &prod, const QDomElement &source) { QString pxy = source.attribute(QStringLiteral("proxy")); if (pxy.length() > 1) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:proxy"), pxy); EffectsList::setProperty(prod, QStringLiteral("kdenlive:originalurl"), source.attribute(QStringLiteral("resource"))); } if (source.hasAttribute(QStringLiteral("file_hash"))) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:file_hash"), source.attribute(QStringLiteral("file_hash"))); } if (source.hasAttribute(QStringLiteral("file_size"))) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:file_size"), source.attribute(QStringLiteral("file_size"))); } if (source.hasAttribute(QStringLiteral("name"))) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:clipname"), source.attribute(QStringLiteral("name"))); } if (source.hasAttribute(QStringLiteral("zone_out"))) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:zone_out"), source.attribute(QStringLiteral("zone_out"))); } if (source.hasAttribute(QStringLiteral("zone_in"))) { EffectsList::setProperty(prod, QStringLiteral("kdenlive:zone_in"), source.attribute(QStringLiteral("zone_in"))); } if (source.hasAttribute(QStringLiteral("cutzones"))) { QString zoneData = source.attribute(QStringLiteral("cutzones")); const QStringList zoneList = zoneData.split(QLatin1Char(';')); int ct = 1; for (const QString &data : zoneList) { QString zoneName = data.section(QLatin1Char('-'), 2); if (zoneName.isEmpty()) { zoneName = i18n("Zone %1", ct++); } EffectsList::setProperty(prod, QStringLiteral("kdenlive:clipzone.") + zoneName, data.section(QLatin1Char('-'), 0, 0) + QLatin1Char(';') + data.section(QLatin1Char('-'), 1, 1)); } } } QStringList DocumentValidator::getInfoFromEffectName(const QString &oldName) { QStringList info; // Returns a list to convert old Kdenlive ladspa effects if (oldName == QLatin1String("pitch_shift")) { info << QStringLiteral("ladspa.1433"); info << QStringLiteral("pitch=0"); } else if (oldName == QLatin1String("vinyl")) { info << QStringLiteral("ladspa.1905"); info << QStringLiteral("year=0"); info << QStringLiteral("rpm=1"); info << QStringLiteral("warping=2"); info << QStringLiteral("crackle=3"); info << QStringLiteral("wear=4"); } else if (oldName == QLatin1String("room_reverb")) { info << QStringLiteral("ladspa.1216"); info << QStringLiteral("room=0"); info << QStringLiteral("delay=1"); info << QStringLiteral("damp=2"); } else if (oldName == QLatin1String("reverb")) { info << QStringLiteral("ladspa.1423"); info << QStringLiteral("room=0"); info << QStringLiteral("damp=1"); } else if (oldName == QLatin1String("rate_scale")) { info << QStringLiteral("ladspa.1417"); info << QStringLiteral("rate=0"); } else if (oldName == QLatin1String("pitch_scale")) { info << QStringLiteral("ladspa.1193"); info << QStringLiteral("coef=0"); } else if (oldName == QLatin1String("phaser")) { info << QStringLiteral("ladspa.1217"); info << QStringLiteral("rate=0"); info << QStringLiteral("depth=1"); info << QStringLiteral("feedback=2"); info << QStringLiteral("spread=3"); } else if (oldName == QLatin1String("limiter")) { info << QStringLiteral("ladspa.1913"); info << QStringLiteral("gain=0"); info << QStringLiteral("limit=1"); info << QStringLiteral("release=2"); } else if (oldName == QLatin1String("equalizer_15")) { info << QStringLiteral("ladspa.1197"); info << QStringLiteral("1=0"); info << QStringLiteral("2=1"); info << QStringLiteral("3=2"); info << QStringLiteral("4=3"); info << QStringLiteral("5=4"); info << QStringLiteral("6=5"); info << QStringLiteral("7=6"); info << QStringLiteral("8=7"); info << QStringLiteral("9=8"); info << QStringLiteral("10=9"); info << QStringLiteral("11=10"); info << QStringLiteral("12=11"); info << QStringLiteral("13=12"); info << QStringLiteral("14=13"); info << QStringLiteral("15=14"); } else if (oldName == QLatin1String("equalizer")) { info << QStringLiteral("ladspa.1901"); info << QStringLiteral("logain=0"); info << QStringLiteral("midgain=1"); info << QStringLiteral("higain=2"); } else if (oldName == QLatin1String("declipper")) { info << QStringLiteral("ladspa.1195"); } return info; } QString DocumentValidator::colorToString(const QColor &c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); return ret; } bool DocumentValidator::isProject() const { return m_doc.documentElement().tagName() == QLatin1String("mlt"); } bool DocumentValidator::isModified() const { return m_modified; } bool DocumentValidator::checkMovit() { QString playlist = m_doc.toString(); if (!playlist.contains(QStringLiteral("movit."))) { // Project does not use Movit GLSL effects, we can load it return true; } if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("The project file uses some GPU effects. GPU acceleration is not currently enabled.\nDo you want to convert the " "project to a non-GPU version ?\nThis might result in data loss.")) != KMessageBox::Yes) { return false; } // Try to convert Movit filters to their non GPU equivalent QStringList convertedFilters; QStringList discardedFilters; int ix = MainWindow::videoEffects.hasEffect(QStringLiteral("frei0r.colgate"), QStringLiteral("frei0r.colgate")); bool hasWB = ix > -1; ix = MainWindow::videoEffects.hasEffect(QStringLiteral("frei0r.IIRblur"), QStringLiteral("frei0r.IIRblur")); bool hasBlur = ix > -1; QString compositeTrans; if (MainWindow::transitions.hasTransition(QStringLiteral("qtblend"))) { compositeTrans = QStringLiteral("qtblend"); } else if (MainWindow::transitions.hasTransition(QStringLiteral("frei0r.cairoblend"))) { compositeTrans = QStringLiteral("frei0r.cairoblend"); } // Parse all effects in document QDomNodeList filters = m_doc.elementsByTagName(QStringLiteral("filter")); int max = filters.count(); for (int i = 0; i < max; ++i) { QDomElement filt = filters.at(i).toElement(); QString filterId = filt.attribute(QStringLiteral("id")); if (!filterId.startsWith(QLatin1String("movit."))) { continue; } if (filterId == QLatin1String("movit.white_balance") && hasWB) { // Convert to frei0r.colgate filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.colgate")); EffectsList::setProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.colgate")); EffectsList::setProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.colgate")); EffectsList::setProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.colgate")); EffectsList::renameProperty(filt, QStringLiteral("neutral_color"), QStringLiteral("Neutral Color")); QString value = EffectsList::property(filt, QStringLiteral("color_temperature")); value = factorizeGeomValue(value, 15000.0); EffectsList::setProperty(filt, QStringLiteral("color_temperature"), value); EffectsList::renameProperty(filt, QStringLiteral("color_temperature"), QStringLiteral("Color Temperature")); convertedFilters << filterId; continue; } if (filterId == QLatin1String("movit.blur") && hasBlur) { // Convert to frei0r.IIRblur filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.IIRblur")); EffectsList::setProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.IIRblur")); EffectsList::setProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.IIRblur")); EffectsList::setProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.IIRblur")); EffectsList::renameProperty(filt, QStringLiteral("radius"), QStringLiteral("Amount")); QString value = EffectsList::property(filt, QStringLiteral("Amount")); value = factorizeGeomValue(value, 14.0); EffectsList::setProperty(filt, QStringLiteral("Amount"), value); convertedFilters << filterId; continue; } if (filterId == QLatin1String("movit.mirror")) { // Convert to MLT's mirror filt.setAttribute(QStringLiteral("id"), QStringLiteral("mirror")); EffectsList::setProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("mirror")); EffectsList::setProperty(filt, QStringLiteral("tag"), QStringLiteral("mirror")); EffectsList::setProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("mirror")); EffectsList::setProperty(filt, QStringLiteral("mirror"), QStringLiteral("flip")); convertedFilters << filterId; continue; } if (filterId.startsWith(QLatin1String("movit."))) { // TODO: implement conversion for more filters discardedFilters << filterId; } } // Parse all transitions in document QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition")); max = transitions.count(); for (int i = 0; i < max; ++i) { QDomElement t = transitions.at(i).toElement(); QString transId = EffectsList::property(t, QStringLiteral("mlt_service")); if (!transId.startsWith(QLatin1String("movit."))) { continue; } if (transId == QLatin1String("movit.overlay") && !compositeTrans.isEmpty()) { // Convert to frei0r.cairoblend EffectsList::setProperty(t, QStringLiteral("mlt_service"), compositeTrans); convertedFilters << transId; continue; } if (transId.startsWith(QLatin1String("movit."))) { // TODO: implement conversion for more filters discardedFilters << transId; } } convertedFilters.removeDuplicates(); discardedFilters.removeDuplicates(); if (discardedFilters.isEmpty()) { KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were converted to non GPU versions:"), convertedFilters); } else { KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were deleted from the project:"), discardedFilters); } m_modified = true; QString scene = m_doc.toString(); scene.replace(QLatin1String("movit."), QString()); m_doc.setContent(scene); return true; } QString DocumentValidator::factorizeGeomValue(const QString &value, double factor) { const QStringList vals = value.split(QLatin1Char(';')); QString result; QLocale locale; for (int i = 0; i < vals.count(); i++) { const QString &s = vals.at(i); QString key = s.section(QLatin1Char('='), 0, 0); QString val = s.section(QLatin1Char('='), 1, 1); double v = locale.toDouble(val) / factor; result.append(key + QLatin1Char('=') + locale.toString(v)); if (i + 1 < vals.count()) { result.append(QLatin1Char(';')); } } return result; } void DocumentValidator::checkOrphanedProducers() { QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt")); QDomElement main = mlt.firstChildElement(QStringLiteral("playlist")); QDomNodeList bin_producers = main.childNodes(); QStringList binProducers; for (int k = 0; k < bin_producers.count(); k++) { QDomElement mltprod = bin_producers.at(k).toElement(); if (mltprod.tagName() != QLatin1String("entry")) { continue; } binProducers << mltprod.attribute(QStringLiteral("producer")); } QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int max = producers.count(); QStringList allProducers; for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); if (prod.isNull()) { continue; } allProducers << prod.attribute(QStringLiteral("id")); } QDomDocumentFragment frag = m_doc.createDocumentFragment(); QDomDocumentFragment trackProds = m_doc.createDocumentFragment(); for (int i = 0; i < max; ++i) { QDomElement prod = producers.at(i).toElement(); if (prod.isNull()) { continue; } QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0); if (id.startsWith(QLatin1String("slowmotion")) || id == QLatin1String("black")) { continue; } if (!binProducers.contains(id)) { QString binId = EffectsList::property(prod, QStringLiteral("kdenlive:binid")); if (!binId.isEmpty() && binProducers.contains(binId)) { continue; } qCWarning(KDENLIVE_LOG) << " ///////// WARNING, FOUND UNKNOWN PRODUDER: " << id << " ----------------"; // This producer is unknown to Bin QString service = EffectsList::property(prod, QStringLiteral("mlt_service")); QString distinctiveTag(QStringLiteral("resource")); if (service == QLatin1String("kdenlivetitle")) { distinctiveTag = QStringLiteral("xmldata"); } QString orphanValue = EffectsList::property(prod, distinctiveTag); for (int j = 0; j < max; j++) { // Search for a similar producer QDomElement binProd = producers.at(j).toElement(); binId = binProd.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0); if (service != QLatin1String("timewarp") && (binId.startsWith(QLatin1String("slowmotion")) || !binProducers.contains(binId))) { continue; } QString binService = EffectsList::property(binProd, QStringLiteral("mlt_service")); qCDebug(KDENLIVE_LOG) << " / /LKNG FOR: " << service << " / " << orphanValue << ", checking: " << binProd.attribute(QStringLiteral("id")); if (service != binService) { continue; } QString binValue = EffectsList::property(binProd, distinctiveTag); if (binValue == orphanValue) { // Found probable source producer, replace frag.appendChild(prod); i--; QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry")); for (int k = 0; k < entries.count(); k++) { QDomElement entry = entries.at(k).toElement(); if (entry.attribute(QStringLiteral("producer")) == id) { QString entryId = binId; if (service.contains(QStringLiteral("avformat")) || service == QLatin1String("xml") || service == QLatin1String("consumer")) { // We must use track producer, find track for this entry QString trackPlaylist = entry.parentNode().toElement().attribute(QStringLiteral("id")); entryId.append(QLatin1Char('_') + trackPlaylist); } if (!allProducers.contains(entryId)) { // The track producer does not exist, create a clone for it QDomElement cloned = binProd.cloneNode(true).toElement(); cloned.setAttribute(QStringLiteral("id"), entryId); trackProds.appendChild(cloned); allProducers << entryId; } entry.setAttribute(QStringLiteral("producer"), entryId); m_modified = true; } } continue; } } } } if (!trackProds.isNull()) { QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer")); mlt.insertBefore(trackProds, firstProd); } } void DocumentValidator::fixTitleProducerLocale(QDomElement &producer) { QString data = EffectsList::property(producer, QStringLiteral("xmldata")); QDomDocument doc; doc.setContent(data); QDomNodeList nodes = doc.elementsByTagName(QStringLiteral("position")); bool fixed = false; for (int i = 0; i < nodes.count(); i++) { QDomElement pos = nodes.at(i).toElement(); QString x = pos.attribute(QStringLiteral("x")); QString y = pos.attribute(QStringLiteral("y")); if (x.contains(QLatin1Char(','))) { // x pos was saved in locale format, fix x = x.section(QLatin1Char(','), 0, 0); pos.setAttribute(QStringLiteral("x"), x); fixed = true; } if (y.contains(QLatin1Char(','))) { // x pos was saved in locale format, fix y = y.section(QLatin1Char(','), 0, 0); pos.setAttribute(QStringLiteral("y"), y); fixed = true; } } nodes = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < nodes.count(); i++) { QDomElement pos = nodes.at(i).toElement(); QString x = pos.attribute(QStringLiteral("font-outline")); QString y = pos.attribute(QStringLiteral("textwidth")); if (x.contains(QLatin1Char(','))) { // x pos was saved in locale format, fix x = x.section(QLatin1Char(','), 0, 0); pos.setAttribute(QStringLiteral("font-outline"), x); fixed = true; } if (y.contains(QLatin1Char(','))) { // x pos was saved in locale format, fix y = y.section(QLatin1Char(','), 0, 0); pos.setAttribute(QStringLiteral("textwidth"), y); fixed = true; } } if (fixed) { EffectsList::setProperty(producer, QStringLiteral("xmldata"), doc.toString()); } } diff --git a/src/doc/kdenlivedoc.cpp b/src/doc/kdenlivedoc.cpp index 73d7ec76f..ea702333a 100644 --- a/src/doc/kdenlivedoc.cpp +++ b/src/doc/kdenlivedoc.cpp @@ -1,1632 +1,1631 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 "kdenlivedoc.h" #include "bin/bin.h" #include "bin/bincommands.h" #include "bin/clipcreator.hpp" #include "bin/model/markerlistmodel.hpp" #include "bin/projectclip.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "documentchecker.h" #include "documentvalidator.h" #include "docundostack.hpp" #include "effectslist/initeffects.h" #include "jobs/jobmanager.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/effectscontroller.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "project/clipmanager.h" #include "project/projectcommands.h" #include "renderer.h" #include "titler/titlewidget.h" #include "transitions/transitionsrepository.hpp" #include "utils/KoIconUtils.h" #include #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif const double DOCUMENTVERSION = 0.97; KdenliveDoc::KdenliveDoc(const QUrl &url, const QString &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap &properties, const QMap &metadata, const QPoint &tracks, bool *openBackup, MainWindow *parent) : QObject(parent) , m_autosave(nullptr) , m_url(url) , m_modified(false) , m_documentOpenStatus(CleanProject) , m_projectFolder(projectFolder) { m_commandStack = std::make_shared(undoGroup); m_guideModel.reset(new MarkerListModel(m_commandStack, this)); connect(m_guideModel.get(), &MarkerListModel::modelChanged, this, &KdenliveDoc::guidesChanged); m_clipManager = new ClipManager(this); connect(m_clipManager, SIGNAL(displayMessage(QString, int)), parent, SLOT(slotGotProgressInfo(QString, int))); connect(this, SIGNAL(updateCompositionMode(int)), parent, SLOT(slotUpdateCompositeAction(int))); bool success = false; connect(m_commandStack.get(), &QUndoStack::indexChanged, this, &KdenliveDoc::slotModified); connect(m_commandStack.get(), &DocUndoStack::invalidate, this, &KdenliveDoc::checkPreviewStack); // connect(m_commandStack, SIGNAL(cleanChanged(bool)), this, SLOT(setModified(bool))); // init default document properties m_documentProperties[QStringLiteral("zoom")] = QLatin1Char('7'); m_documentProperties[QStringLiteral("verticalzoom")] = QLatin1Char('1'); m_documentProperties[QStringLiteral("zonein")] = QLatin1Char('0'); m_documentProperties[QStringLiteral("zoneout")] = QStringLiteral("100"); m_documentProperties[QStringLiteral("enableproxy")] = QString::number((int)KdenliveSettings::enableproxy()); m_documentProperties[QStringLiteral("proxyparams")] = KdenliveSettings::proxyparams(); m_documentProperties[QStringLiteral("proxyextension")] = KdenliveSettings::proxyextension(); m_documentProperties[QStringLiteral("previewparameters")] = KdenliveSettings::previewparams(); m_documentProperties[QStringLiteral("previewextension")] = KdenliveSettings::previewextension(); m_documentProperties[QStringLiteral("generateproxy")] = QString::number((int)KdenliveSettings::generateproxy()); m_documentProperties[QStringLiteral("proxyminsize")] = QString::number(KdenliveSettings::proxyminsize()); m_documentProperties[QStringLiteral("generateimageproxy")] = QString::number((int)KdenliveSettings::generateimageproxy()); m_documentProperties[QStringLiteral("proxyimageminsize")] = QString::number(KdenliveSettings::proxyimageminsize()); m_documentProperties[QStringLiteral("proxyimagesize")] = QString::number(KdenliveSettings::proxyimagesize()); m_documentProperties[QStringLiteral("resizepreview")] = QString::number((int)KdenliveSettings::resizepreview()); m_documentProperties[QStringLiteral("previewheight")] = QString::number(KdenliveSettings::previewheight()); // Load properties QMapIterator i(properties); while (i.hasNext()) { i.next(); m_documentProperties[i.key()] = i.value(); } // Load metadata QMapIterator j(metadata); while (j.hasNext()) { j.next(); m_documentMetadata[j.key()] = j.value(); } if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) { setlocale(LC_NUMERIC, ""); QLocale systemLocale = QLocale::system(); systemLocale.setNumberOptions(QLocale::OmitGroupSeparator); QLocale::setDefault(systemLocale); // locale conversion might need to be redone initEffects::parseEffectFiles(pCore->getMltRepository(), QString::fromLatin1(setlocale(LC_NUMERIC, nullptr))); } *openBackup = false; if (url.isValid()) { QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // The file cannot be opened if (KMessageBox::warningContinueCancel(parent, i18n("Cannot open the project file,\nDo you want to open a backup file?"), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) { *openBackup = true; } // KMessageBox::error(parent, KIO::NetAccess::lastErrorString()); } else { qCDebug(KDENLIVE_LOG) << " // / processing file open"; QString errorMsg; int line; int col; QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars); success = m_document.setContent(&file, false, &errorMsg, &line, &col); file.close(); if (!success) { // It is corrupted int answer = KMessageBox::warningYesNoCancel( parent, i18n("Cannot open the project file, error is:\n%1 (line %2, col %3)\nDo you want to open a backup file?", errorMsg, line, col), i18n("Error opening file"), KGuiItem(i18n("Open Backup")), KGuiItem(i18n("Recover"))); if (answer == KMessageBox::Yes) { *openBackup = true; } else if (answer == KMessageBox::No) { // Try to recover broken file produced by Kdenlive 0.9.4 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { int correction = 0; QString playlist = QString::fromUtf8(file.readAll()); while (!success && correction < 2) { int errorPos = 0; line--; col = col - 2; for (int k = 0; k < line && errorPos < playlist.length(); ++k) { errorPos = playlist.indexOf(QLatin1Char('\n'), errorPos); errorPos++; } errorPos += col; if (errorPos >= playlist.length()) { break; } playlist.remove(errorPos, 1); line = 0; col = 0; success = m_document.setContent(playlist, false, &errorMsg, &line, &col); correction++; } if (!success) { KMessageBox::sorry(parent, i18n("Cannot recover this project file")); } else { // Document was modified, ask for backup QDomElement mlt = m_document.documentElement(); mlt.setAttribute(QStringLiteral("modified"), 1); } } } } else { qCDebug(KDENLIVE_LOG) << " // / processing file open: validate"; parent->slotGotProgressInfo(i18n("Validating"), 100); qApp->processEvents(); DocumentValidator validator(m_document, url); success = validator.isProject(); if (!success) { // It is not a project file parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.toLocalFile()), 100); if (KMessageBox::warningContinueCancel( parent, i18n("File %1 is not a valid project file.\nDo you want to open a backup file?", m_url.toLocalFile()), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) { *openBackup = true; } } else { /* * Validate the file against the current version (upgrade * and recover it if needed). It is NOT a passive operation */ // TODO: backup the document or alert the user? success = validator.validate(DOCUMENTVERSION); if (success && !KdenliveSettings::gpu_accel()) { success = validator.checkMovit(); } if (success) { // Let the validator handle error messages qCDebug(KDENLIVE_LOG) << " // / processing file validate ok"; parent->slotGotProgressInfo(i18n("Check missing clips"), 100); qApp->processEvents(); DocumentChecker d(m_url, m_document); success = !d.hasErrorInClips(); if (success) { loadDocumentProperties(); if (m_document.documentElement().hasAttribute(QStringLiteral("upgraded"))) { m_documentOpenStatus = UpgradedProject; pCore->displayMessage(i18n("Your project was upgraded, a backup will be created on next save"), ErrorMessage); } else if (m_document.documentElement().hasAttribute(QStringLiteral("modified")) || validator.isModified()) { m_documentOpenStatus = ModifiedProject; pCore->displayMessage(i18n("Your project was modified on opening, a backup will be created on next save"), ErrorMessage); setModified(true); } } } } } } } // Something went wrong, or a new file was requested: create a new project if (!success) { m_url.clear(); pCore->setCurrentProfile(profileName); m_document = createEmptyDocument(tracks.x(), tracks.y()); updateProjectProfile(false); } if (!m_projectFolder.isEmpty()) { // Ask to create the project directory if it does not exist QDir folder(m_projectFolder); if (!folder.mkpath(QStringLiteral("."))) { // Project folder is not writable m_projectFolder = m_url.toString(QUrl::RemoveFilename | QUrl::RemoveScheme); folder.setPath(m_projectFolder); if (folder.exists()) { KMessageBox::sorry( parent, i18n("The project directory %1, could not be created.\nPlease make sure you have the required permissions.\nDefaulting to system folders", m_projectFolder)); } else { KMessageBox::information(parent, i18n("Document project folder is invalid, using system default folders")); } m_projectFolder.clear(); } } initCacheDirs(); updateProjectFolderPlacesEntry(); } KdenliveDoc::~KdenliveDoc() { if (m_url.isEmpty()) { // Document was never saved, delete cache folder QString documentId = QDir::cleanPath(getDocumentProperty(QStringLiteral("documentid"))); bool ok; documentId.toLongLong(&ok, 10); if (ok && !documentId.isEmpty()) { QDir baseCache = getCacheDir(CacheBase, &ok); if (baseCache.dirName() == documentId && baseCache.entryList(QDir::Files).isEmpty()) { baseCache.removeRecursively(); } } } // qCDebug(KDENLIVE_LOG) << "// DEL CLP MAN"; // Clean up guide model m_guideModel.reset(); delete m_clipManager; // qCDebug(KDENLIVE_LOG) << "// DEL CLP MAN done"; if (m_autosave) { if (!m_autosave->fileName().isEmpty()) { m_autosave->remove(); } delete m_autosave; } } const QByteArray KdenliveDoc::getProjectXml() { return m_document.toString().toUtf8(); } QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks) { QList tracks; // Tracks are added «backwards», so we need to reverse the track numbering // mbt 331: http://www.kdenlive.org/mantis/view.php?id=331 // Better default names for tracks: Audio 1 etc. instead of blank numbers tracks.reserve(audiotracks + videotracks); for (int i = 0; i < audiotracks; ++i) { TrackInfo audioTrack; audioTrack.type = AudioTrack; audioTrack.isMute = false; audioTrack.isBlind = true; audioTrack.isLocked = false; audioTrack.trackName = i18n("Audio %1", audiotracks - i); audioTrack.duration = 0; audioTrack.effectsList = EffectsList(true); tracks.append(audioTrack); } for (int i = 0; i < videotracks; ++i) { TrackInfo videoTrack; videoTrack.type = VideoTrack; videoTrack.isMute = false; videoTrack.isBlind = false; videoTrack.isLocked = false; videoTrack.trackName = i18n("Video %1", i + 1); videoTrack.duration = 0; videoTrack.effectsList = EffectsList(true); tracks.append(videoTrack); } return createEmptyDocument(tracks); } QDomDocument KdenliveDoc::createEmptyDocument(const QList &tracks) { // Creating new document QDomDocument doc; Mlt::Profile docProfile; Mlt::Consumer xmlConsumer(docProfile, "xml:kdenlive_playlist"); xmlConsumer.set("no_profile", 1); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.set("store", "kdenlive"); Mlt::Tractor tractor(docProfile); Mlt::Producer bk(docProfile, "color:black"); tractor.insert_track(bk, 0); for (int i = 0; i < tracks.count(); ++i) { Mlt::Tractor track(docProfile); track.set("kdenlive:track_name", tracks.at(i).trackName.toUtf8().constData()); track.set("kdenlive:trackheight", KdenliveSettings::trackheight()); if (tracks.at(i).type == AudioTrack) { track.set("kdenlive:audio_track", 1); } if (tracks.at(i).isLocked) { track.set("kdenlive:locked_track", 1); } if (tracks.at(i).isMute) { if (tracks.at(i).isBlind) { track.set("hide", 3); } else { track.set("hide", 2); } } else if (tracks.at(i).isBlind) { track.set("hide", 1); } Mlt::Playlist playlist1(docProfile); Mlt::Playlist playlist2(docProfile); track.insert_track(playlist1, 0); track.insert_track(playlist2, 1); tractor.insert_track(track, i + 1); } QScopedPointer field(tractor.field()); QString compositeService = TransitionsRepository::get()->getCompositingTransition(); if (!compositeService.isEmpty()) { for (int i = 0; i <= tracks.count(); i++) { if (i > 0) { Mlt::Transition tr(docProfile, "mix"); tr.set("a_track", 0); tr.set("b_track", i); tr.set("always_active", 1); tr.set("sum", 1); tr.set("internal_added", 237); field->plant_transition(tr, 0, i); } if (i > 0 && tracks.at(i - 1).type == VideoTrack) { Mlt::Transition tr(docProfile, compositeService.toUtf8().constData()); tr.set("a_track", 0); tr.set("b_track", i); tr.set("always_active", 1); tr.set("internal_added", 237); field->plant_transition(tr, 0, i); } } } Mlt::Producer prod(tractor.get_producer()); xmlConsumer.connect(prod); xmlConsumer.run(); QString playlist = QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")); doc.setContent(playlist); return doc; } bool KdenliveDoc::useProxy() const { return m_documentProperties.value(QStringLiteral("enableproxy")).toInt() != 0; } bool KdenliveDoc::autoGenerateProxy(int width) const { return (m_documentProperties.value(QStringLiteral("generateproxy")).toInt() != 0) && width > m_documentProperties.value(QStringLiteral("proxyminsize")).toInt(); } bool KdenliveDoc::autoGenerateImageProxy(int width) const { return (m_documentProperties.value(QStringLiteral("generateimageproxy")).toInt() != 0) && width > m_documentProperties.value(QStringLiteral("proxyimageminsize")).toInt(); } void KdenliveDoc::slotAutoSave(const QString &scene) { if (m_autosave != nullptr) { if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) { // show error: could not open the autosave file qCDebug(KDENLIVE_LOG) << "ERROR; CANNOT CREATE AUTOSAVE FILE"; } if (scene.isEmpty()) { // Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", m_autosave->fileName())); return; } m_autosave->resize(0); m_autosave->write(scene.toUtf8()); m_autosave->flush(); } } void KdenliveDoc::setZoom(int horizontal, int vertical) { m_documentProperties[QStringLiteral("zoom")] = QString::number(horizontal); m_documentProperties[QStringLiteral("verticalzoom")] = QString::number(vertical); } QPoint KdenliveDoc::zoom() const { return QPoint(m_documentProperties.value(QStringLiteral("zoom")).toInt(), m_documentProperties.value(QStringLiteral("verticalzoom")).toInt()); } void KdenliveDoc::setZone(int start, int end) { m_documentProperties[QStringLiteral("zonein")] = QString::number(start); m_documentProperties[QStringLiteral("zoneout")] = QString::number(end); } QPoint KdenliveDoc::zone() const { return QPoint(m_documentProperties.value(QStringLiteral("zonein")).toInt(), m_documentProperties.value(QStringLiteral("zoneout")).toInt()); } QDomDocument KdenliveDoc::xmlSceneList(const QString &scene) { QDomDocument sceneList; sceneList.setContent(scene, true); QDomElement mlt = sceneList.firstChildElement(QStringLiteral("mlt")); if (mlt.isNull() || !mlt.hasChildNodes()) { // scenelist is corrupted return sceneList; } // Set playlist audio volume to 100% QDomElement tractor = mlt.firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } QDomNodeList pls = mlt.elementsByTagName(QStringLiteral("playlist")); QDomElement mainPlaylist; for (int i = 0; i < pls.count(); ++i) { if (pls.at(i).toElement().attribute(QStringLiteral("id")) == pCore->binController()->binPlaylistId()) { mainPlaylist = pls.at(i).toElement(); break; } } // check if project contains custom effects to embed them in project file QDomNodeList effects = mlt.elementsByTagName(QStringLiteral("filter")); int maxEffects = effects.count(); // qCDebug(KDENLIVE_LOG) << "// FOUD " << maxEffects << " EFFECTS+++++++++++++++++++++"; QMap effectIds; for (int i = 0; i < maxEffects; ++i) { QDomNode m = effects.at(i); QDomNodeList params = m.childNodes(); QString id; QString tag; for (int j = 0; j < params.count(); ++j) { QDomElement e = params.item(j).toElement(); if (e.attribute(QStringLiteral("name")) == QLatin1String("kdenlive_id")) { id = e.firstChild().nodeValue(); } if (e.attribute(QStringLiteral("name")) == QLatin1String("tag")) { tag = e.firstChild().nodeValue(); } if (!id.isEmpty() && !tag.isEmpty()) { effectIds.insert(id, tag); } } } // TODO: find a way to process this before rendering MLT scenelist to xml QDomDocument customeffects = initEffects::getUsedCustomEffects(effectIds); if (!customeffects.documentElement().childNodes().isEmpty()) { EffectsList::setProperty(mainPlaylist, QStringLiteral("kdenlive:customeffects"), customeffects.toString()); } // addedXml.appendChild(sceneList.importNode(customeffects.documentElement(), true)); // TODO: move metadata to previous step in saving process QDomElement docmetadata = sceneList.createElement(QStringLiteral("documentmetadata")); QMapIterator j(m_documentMetadata); while (j.hasNext()) { j.next(); docmetadata.setAttribute(j.key(), j.value()); } // addedXml.appendChild(docmetadata); return sceneList; } bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) { QDomDocument sceneList = xmlSceneList(scene); if (sceneList.isNull()) { // Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path)); return false; } // Backup current version backupLastSavedVersion(path); if (m_documentOpenStatus != CleanProject) { // create visible backup file and warn user QString baseFile = path.section(QStringLiteral(".kdenlive"), 0, 0); int ct = 0; QString backupFile = baseFile + QStringLiteral("_backup") + QString::number(ct) + QStringLiteral(".kdenlive"); while (QFile::exists(backupFile)) { ct++; backupFile = baseFile + QStringLiteral("_backup") + QString::number(ct) + QStringLiteral(".kdenlive"); } QString message; if (m_documentOpenStatus == UpgradedProject) { message = i18n("Your project file was upgraded to the latest Kdenlive document version.\nTo make sure you don't lose data, a backup copy called %1 " "was created.", backupFile); } else { message = i18n("Your project file was modified by Kdenlive.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile); } KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile)); if (copyjob->exec()) { KMessageBox::information(QApplication::activeWindow(), message); m_documentOpenStatus = CleanProject; } else { KMessageBox::information( QApplication::activeWindow(), i18n("Your project file was upgraded to the latest Kdenlive document version, but it was not possible to create the backup copy %1.", backupFile)); } } QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(KDENLIVE_LOG) << "////// ERROR writing to file: " << path; KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); return false; } file.write(sceneList.toString().toUtf8()); if (file.error() != QFile::NoError) { KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); file.close(); return false; } file.close(); cleanupBackupFiles(); QFileInfo info(file); QString fileName = QUrl::fromLocalFile(path).fileName().section(QLatin1Char('.'), 0, -2); fileName.append(QLatin1Char('-') + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(QStringLiteral(".kdenlive.png")); QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup")); emit saveTimelinePreview(backupFolder.absoluteFilePath(fileName)); return true; } ClipManager *KdenliveDoc::clipManager() { return m_clipManager; } QString KdenliveDoc::projectTempFolder() const { if (m_projectFolder.isEmpty()) { return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); } return m_projectFolder; } QString KdenliveDoc::projectDataFolder() const { if (m_projectFolder.isEmpty()) { if (KdenliveSettings::customprojectfolder()) { return KdenliveSettings::defaultprojectfolder(); } return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } return m_projectFolder; } void KdenliveDoc::setProjectFolder(const QUrl &url) { if (url == QUrl::fromLocalFile(m_projectFolder)) { return; } setModified(true); QDir dir(url.toLocalFile()); if (!dir.exists()) { dir.mkpath(dir.absolutePath()); } dir.mkdir(QStringLiteral("titles")); /*if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the * new folder %2?", m_projectFolder, url.path())) == KMessageBox::Yes) moveProjectData(url);*/ m_projectFolder = url.toLocalFile(); updateProjectFolderPlacesEntry(); } void KdenliveDoc::moveProjectData(const QString & /*src*/, const QString &dest) { // Move proxies QList> list = pCore->binController()->getControllerList(); QList cacheUrls; for (int i = 0; i < list.count(); ++i) { const std::shared_ptr &clip = list.at(i); if (clip->clipType() == ClipType::Text) { // the image for title clip must be moved QUrl oldUrl = QUrl::fromLocalFile(clip->clipUrl()); if (!oldUrl.isEmpty()) { QUrl newUrl = QUrl::fromLocalFile(dest + QStringLiteral("/titles/") + oldUrl.fileName()); KIO::Job *job = KIO::copy(oldUrl, newUrl); if (job->exec()) { clip->setProducerProperty(QStringLiteral("resource"), newUrl.toLocalFile()); } } continue; } QString proxy = clip->getProducerProperty(QStringLiteral("kdenlive:proxy")); if (proxy.length() > 2 && QFile::exists(proxy)) { QUrl pUrl = QUrl::fromLocalFile(proxy); if (!cacheUrls.contains(pUrl)) { cacheUrls << pUrl; } } } if (!cacheUrls.isEmpty()) { QDir proxyDir(dest + QStringLiteral("/proxy/")); if (proxyDir.mkpath(QStringLiteral("."))) { KIO::CopyJob *job = KIO::move(cacheUrls, QUrl::fromLocalFile(proxyDir.absolutePath())); KJobWidgets::setWindow(job, QApplication::activeWindow()); if (static_cast(job->exec()) > 0) { KMessageBox::sorry(QApplication::activeWindow(), i18n("Moving proxy clips failed: %1", job->errorText())); } } } } bool KdenliveDoc::profileChanged(const QString &profile) const { return pCore->getCurrentProfile() != ProfileRepository::get()->getProfile(profile); } Render *KdenliveDoc::renderer() { return nullptr; } std::shared_ptr KdenliveDoc::commandStack() { return m_commandStack; } int KdenliveDoc::getFramePos(const QString &duration) { return m_timecode.getFrameCount(duration); } QDomDocument KdenliveDoc::toXml() { return m_document; } Timecode KdenliveDoc::timecode() const { return m_timecode; } QDomNodeList KdenliveDoc::producersList() { return m_document.elementsByTagName(QStringLiteral("producer")); } int KdenliveDoc::width() const { return pCore->getCurrentProfile()->width(); } int KdenliveDoc::height() const { return pCore->getCurrentProfile()->height(); } QUrl KdenliveDoc::url() const { return m_url; } void KdenliveDoc::setUrl(const QUrl &url) { m_url = url; } void KdenliveDoc::slotModified() { setModified(!m_commandStack->isClean()); } void KdenliveDoc::setModified(bool mod) { // fix mantis#3160: The document may have an empty URL if not saved yet, but should have a m_autosave in any case if ((m_autosave != nullptr) && mod && KdenliveSettings::crashrecovery()) { emit startAutoSave(); } if (mod == m_modified) { return; } m_modified = mod; emit docModified(m_modified); } bool KdenliveDoc::isModified() const { return m_modified; } const QString KdenliveDoc::description() const { if (!m_url.isValid()) { return i18n("Untitled") + QStringLiteral("[*] / ") + pCore->getCurrentProfile()->description(); } return m_url.fileName() + QStringLiteral(" [*]/ ") + pCore->getCurrentProfile()->description(); } QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const { QString foundFileName; QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { QFile file(dir.absoluteFilePath(filesAndDirs.at(i))); if (file.open(QIODevice::ReadOnly)) { if (QString::number(file.size()) == matchSize) { /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) { fileData.append(file.readAll()); } } else { fileData = file.readAll(); } file.close(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString::fromLatin1(fileHash.toHex()) == matchHash) { return file.fileName(); } qCDebug(KDENLIVE_LOG) << filesAndDirs.at(i) << "size match but not hash"; } } ////qCDebug(KDENLIVE_LOG) << filesAndDirs.at(i) << file.size() << fileHash.toHex(); } filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash); if (!foundFileName.isEmpty()) { break; } } return foundFileName; } // TODO refac : delete std::shared_ptr KdenliveDoc::getBinClip(const QString &clipId) { return pCore->bin()->getBinClip(clipId); } QStringList KdenliveDoc::getBinFolderClipIds(const QString &folderId) const { return pCore->bin()->getBinFolderClipIds(folderId); } void KdenliveDoc::slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path) { Q_UNUSED(group) // TODO refac: this seem to be a duplicate of ClipCreationDialog::createTitleTemplateClip. See if we can merge QString titlesFolder = QDir::cleanPath(m_projectFolder + QStringLiteral("/titles/")); if (path.isEmpty()) { QPointer d = new QFileDialog(QApplication::activeWindow(), i18n("Enter Template Path"), titlesFolder); d->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); d->setFileMode(QFileDialog::ExistingFile); if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) { path = d->selectedUrls().first(); } delete d; } if (path.isEmpty()) { return; } // TODO: rewrite with new title system (just set resource) QString id = ClipCreator::createTitleTemplate(path.toString(), QString(), i18n("Template title clip"), groupId, pCore->projectItemModel()); emit selectLastAddedClip(id); } void KdenliveDoc::cacheImage(const QString &fileId, const QImage &img) const { bool ok = false; QDir dir = getCacheDir(CacheThumbs, &ok); if (ok) { img.save(dir.absoluteFilePath(fileId + QStringLiteral(".png"))); } } void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value) { if (value.isEmpty()) { m_documentProperties.remove(name); return; } m_documentProperties[name] = value; } const QString KdenliveDoc::getDocumentProperty(const QString &name, const QString &defaultValue) const { return m_documentProperties.value(name, defaultValue); } QMap KdenliveDoc::getRenderProperties() const { QMap renderProperties; QMapIterator i(m_documentProperties); while (i.hasNext()) { i.next(); if (i.key().startsWith(QLatin1String("render"))) { if (i.key() == QLatin1String("renderurl")) { // Check that we have a full path QString value = i.value(); if (QFileInfo(value).isRelative()) { value.prepend(m_documentRoot); } renderProperties.insert(i.key(), value); } else { renderProperties.insert(i.key(), i.value()); } } } return renderProperties; } void KdenliveDoc::saveCustomEffects(const QDomNodeList &customeffects) { QDomElement e; QStringList importedEffects; int maxchild = customeffects.count(); for (int i = 0; i < maxchild; ++i) { e = customeffects.at(i).toElement(); const QString id = e.attribute(QStringLiteral("id")); const QString tag = e.attribute(QStringLiteral("tag")); if (!id.isEmpty()) { // Check if effect exists or save it if (MainWindow::customEffects.hasEffect(tag, id) == -1) { QDomDocument doc; doc.appendChild(doc.importNode(e, true)); QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects"); path += id + QStringLiteral(".xml"); if (!QFile::exists(path)) { importedEffects << id; QFile file(path); if (file.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&file); out << doc.toString(); } } } } } if (!importedEffects.isEmpty()) { KMessageBox::informationList(QApplication::activeWindow(), i18n("The following effects were imported from the project:"), importedEffects); } if (!importedEffects.isEmpty()) { emit reloadEffects(); } } void KdenliveDoc::updateProjectFolderPlacesEntry() { /* * For similar and more code have a look at kfileplacesmodel.cpp and the included files: * http://websvn.kde.org/trunk/KDE/kdelibs/kfile/kfileplacesmodel.cpp?view=markup */ const QString file = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/user-places.xbel"); KBookmarkManager *bookmarkManager = KBookmarkManager::managerForExternalFile(file); if (!bookmarkManager) { return; } KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QString kdenliveName = QCoreApplication::applicationName(); QUrl documentLocation = QUrl::fromLocalFile(m_projectFolder); bool exists = false; while (!bookmark.isNull()) { // UDI not empty indicates a device QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); QString appName = bookmark.metaDataItem(QStringLiteral("OnlyInApp")); if (udi.isEmpty() && appName == kdenliveName && bookmark.text() == i18n("Project Folder")) { if (bookmark.url() != documentLocation) { bookmark.setUrl(documentLocation); bookmarkManager->emitChanged(root); } exists = true; break; } bookmark = root.next(bookmark); } // if entry does not exist yet (was not found), well, create it then if (!exists) { bookmark = root.addBookmark(i18n("Project Folder"), documentLocation, QStringLiteral("folder-favorites")); // Make this user selectable ? bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), kdenliveName); bookmarkManager->emitChanged(root); } } // static double KdenliveDoc::getDisplayRatio(const QString &path) { QFile file(path); QDomDocument doc; if (!file.open(QIODevice::ReadOnly)) { qCWarning(KDENLIVE_LOG) << "ERROR, CANNOT READ: " << path; return 0; } if (!doc.setContent(&file)) { qCWarning(KDENLIVE_LOG) << "ERROR, CANNOT READ: " << path; file.close(); return 0; } file.close(); QDomNodeList list = doc.elementsByTagName(QStringLiteral("profile")); if (list.isEmpty()) { return 0; } QDomElement profile = list.at(0).toElement(); double den = profile.attribute(QStringLiteral("display_aspect_den")).toDouble(); if (den > 0) { return profile.attribute(QStringLiteral("display_aspect_num")).toDouble() / den; } return 0; } void KdenliveDoc::backupLastSavedVersion(const QString &path) { // Ensure backup folder exists if (path.isEmpty()) { return; } QFile file(path); QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup")); QString fileName = QUrl::fromLocalFile(path).fileName().section(QLatin1Char('.'), 0, -2); QFileInfo info(file); fileName.append(QLatin1Char('-') + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(QStringLiteral(".kdenlive")); QString backupFile = backupFolder.absoluteFilePath(fileName); if (file.exists()) { // delete previous backup if it was done less than 60 seconds ago QFile::remove(backupFile); if (!QFile::copy(path, backupFile)) { KMessageBox::information(QApplication::activeWindow(), i18n("Cannot create backup copy:\n%1", backupFile)); } } } void KdenliveDoc::cleanupBackupFiles() { QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup")); QString projectFile = url().fileName().section(QLatin1Char('.'), 0, -2); projectFile.append(QLatin1Char('-') + m_documentProperties.value(QStringLiteral("documentid"))); projectFile.append(QStringLiteral("-??")); projectFile.append(QStringLiteral("??")); projectFile.append(QStringLiteral("-??")); projectFile.append(QStringLiteral("-??")); projectFile.append(QStringLiteral("-??")); projectFile.append(QStringLiteral("-??.kdenlive")); QStringList filter; filter << projectFile; backupFolder.setNameFilters(filter); QFileInfoList resultList = backupFolder.entryInfoList(QDir::Files, QDir::Time); QDateTime d = QDateTime::currentDateTime(); QStringList hourList; QStringList dayList; QStringList weekList; QStringList oldList; for (int i = 0; i < resultList.count(); ++i) { if (d.secsTo(resultList.at(i).lastModified()) < 3600) { // files created in the last hour hourList.append(resultList.at(i).absoluteFilePath()); } else if (d.secsTo(resultList.at(i).lastModified()) < 43200) { // files created in the day dayList.append(resultList.at(i).absoluteFilePath()); } else if (d.daysTo(resultList.at(i).lastModified()) < 8) { // files created in the week weekList.append(resultList.at(i).absoluteFilePath()); } else { // older files oldList.append(resultList.at(i).absoluteFilePath()); } } if (hourList.count() > 20) { int step = hourList.count() / 10; for (int i = 0; i < hourList.count(); i += step) { // qCDebug(KDENLIVE_LOG)<<"REMOVE AT: "< 20) { int step = dayList.count() / 10; for (int i = 0; i < dayList.count(); i += step) { dayList.removeAt(i); --i; } } else { dayList.clear(); } if (weekList.count() > 20) { int step = weekList.count() / 10; for (int i = 0; i < weekList.count(); i += step) { weekList.removeAt(i); --i; } } else { weekList.clear(); } if (oldList.count() > 20) { int step = oldList.count() / 10; for (int i = 0; i < oldList.count(); i += step) { oldList.removeAt(i); --i; } } else { oldList.clear(); } QString f; while (hourList.count() > 0) { f = hourList.takeFirst(); QFile::remove(f); QFile::remove(f + QStringLiteral(".png")); } while (dayList.count() > 0) { f = dayList.takeFirst(); QFile::remove(f); QFile::remove(f + QStringLiteral(".png")); } while (weekList.count() > 0) { f = weekList.takeFirst(); QFile::remove(f); QFile::remove(f + QStringLiteral(".png")); } while (oldList.count() > 0) { f = oldList.takeFirst(); QFile::remove(f); QFile::remove(f + QStringLiteral(".png")); } } const QMap KdenliveDoc::metadata() const { return m_documentMetadata; } void KdenliveDoc::setMetadata(const QMap &meta) { setModified(true); m_documentMetadata = meta; } void KdenliveDoc::slotProxyCurrentItem(bool doProxy, QList> clipList, bool force, QUndoCommand *masterCommand) { if (clipList.isEmpty()) { clipList = pCore->bin()->selectedClips(); } bool hasParent = true; if (masterCommand == nullptr) { masterCommand = new QUndoCommand(); if (doProxy) { masterCommand->setText(i18np("Add proxy clip", "Add proxy clips", clipList.count())); } else { masterCommand->setText(i18np("Remove proxy clip", "Remove proxy clips", clipList.count())); } hasParent = false; } // Make sure the proxy folder exists bool ok = false; QDir dir = getCacheDir(CacheProxy, &ok); if (!ok) { // Error } QString extension = QLatin1Char('.') + getDocumentProperty(QStringLiteral("proxyextension")); QString params = getDocumentProperty(QStringLiteral("proxyparams")); if (params.contains(QStringLiteral("-s "))) { QString proxySize = params.section(QStringLiteral("-s "), 1).section(QStringLiteral("x"), 0, 0); extension.prepend(QStringLiteral("-") + proxySize); } // Prepare updated properties QMap newProps; QMap oldProps; if (!doProxy) { newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); } // Parse clips for (int i = 0; i < clipList.count(); ++i) { std::shared_ptr item = clipList.at(i); ClipType t = item->clipType(); // Only allow proxy on some clip types if ((t == ClipType::Video || t == ClipType::AV || t == ClipType::Unknown || t == ClipType::Image || t == ClipType::Playlist || t == ClipType::SlideShow) && item->isReady()) { if ((doProxy && !force && item->hasProxy()) || (!doProxy && !item->hasProxy() && pCore->binController()->hasClip(item->AbstractProjectItem::clipId()))) { continue; } if (doProxy) { newProps.clear(); QString path = dir.absoluteFilePath(item->hash() + (t == ClipType::Image ? QStringLiteral(".png") : extension)); // insert required duration for proxy newProps.insert(QStringLiteral("proxy_out"), item->getProducerProperty(QStringLiteral("out"))); newProps.insert(QStringLiteral("kdenlive:proxy"), path); // We need to insert empty proxy so that undo will work // TODO: how to handle clip properties // oldProps = clip->currentProperties(newProps); oldProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); } else { if (t == ClipType::SlideShow) { // Revert to picture aspect ratio newProps.insert(QStringLiteral("aspect_ratio"), QStringLiteral("1")); } if (!pCore->binController()->hasClip(item->AbstractProjectItem::clipId())) { // Force clip reload newProps.insert(QStringLiteral("resource"), item->url()); } } new EditClipCommand(pCore->bin(), item->AbstractProjectItem::clipId(), oldProps, newProps, true, masterCommand); } else { // Cannot proxy this clip type pCore->bin()->doDisplayMessage(i18n("Clip type does not support proxies"), KMessageWidget::Information); } } if (!hasParent) { if (masterCommand->childCount() > 0) { m_commandStack->push(masterCommand); } else { delete masterCommand; } } } QMap KdenliveDoc::documentProperties() { m_documentProperties.insert(QStringLiteral("version"), QString::number(DOCUMENTVERSION)); m_documentProperties.insert(QStringLiteral("kdenliveversion"), QStringLiteral(KDENLIVE_VERSION)); if (!m_projectFolder.isEmpty()) { m_documentProperties.insert(QStringLiteral("storagefolder"), m_projectFolder + QLatin1Char('/') + m_documentProperties.value(QStringLiteral("documentid"))); } m_documentProperties.insert(QStringLiteral("profile"), pCore->getCurrentProfile()->path()); m_documentProperties.insert(QStringLiteral("position"), QString::number(pCore->monitorManager()->projectMonitor()->position())); if (!m_documentProperties.contains(QStringLiteral("decimalPoint"))) { m_documentProperties.insert(QStringLiteral("decimalPoint"), QLocale().decimalPoint()); } return m_documentProperties; } void KdenliveDoc::loadDocumentProperties() { QDomNodeList list = m_document.elementsByTagName(QStringLiteral("playlist")); QDomElement baseElement = m_document.documentElement(); m_documentRoot = baseElement.attribute(QStringLiteral("root")); if (!m_documentRoot.isEmpty()) { m_documentRoot = QDir::cleanPath(m_documentRoot) + QDir::separator(); } if (!list.isEmpty()) { QDomElement pl = list.at(0).toElement(); if (pl.isNull()) { return; } QDomNodeList props = pl.elementsByTagName(QStringLiteral("property")); QString name; QDomElement e; for (int i = 0; i < props.count(); i++) { e = props.at(i).toElement(); name = e.attribute(QStringLiteral("name")); if (name.startsWith(QLatin1String("kdenlive:docproperties."))) { name = name.section(QLatin1Char('.'), 1); if (name == QStringLiteral("storagefolder")) { // Make sure we have an absolute path QString value = e.firstChild().nodeValue(); if (QFileInfo(value).isRelative()) { value.prepend(m_documentRoot); } m_documentProperties.insert(name, value); } else if (name == QStringLiteral("guides")) { QString guides = e.firstChild().nodeValue(); if (!guides.isEmpty()) { QMetaObject::invokeMethod(m_guideModel.get(), "importFromJson", Qt::QueuedConnection, Q_ARG(const QString &, guides), Q_ARG(bool, true), Q_ARG(bool, false)); } } else { m_documentProperties.insert(name, e.firstChild().nodeValue()); } } else if (name.startsWith(QLatin1String("kdenlive:docmetadata."))) { name = name.section(QLatin1Char('.'), 1); m_documentMetadata.insert(name, e.firstChild().nodeValue()); } } } QString path = m_documentProperties.value(QStringLiteral("storagefolder")); if (!path.isEmpty()) { QDir dir(path); dir.cdUp(); m_projectFolder = dir.absolutePath(); } QString profile = m_documentProperties.value(QStringLiteral("profile")); bool profileFound = pCore->setCurrentProfile(profile); if (!profileFound) { // try to find matching profile from MLT profile properties list = m_document.elementsByTagName(QStringLiteral("profile")); if (!list.isEmpty()) { std::unique_ptr xmlProfile(new ProfileParam(list.at(0).toElement())); profileFound = pCore->setCurrentProfile(ProfileRepository::get()->findMatchingProfile(xmlProfile.get())); } } if (!profileFound) { qDebug() << "ERROR, no matching profile found"; } updateProjectProfile(false); } void KdenliveDoc::updateProjectProfile(bool reloadProducers) { pCore->bin()->abortAudioThumbs(); pCore->jobManager()->slotCancelJobs(); double fps = pCore->getCurrentFps(); double fpsChanged = m_timecode.fps() / fps; m_timecode.setFormat(fps); pCore->monitorManager()->resetProfiles(m_timecode); if (!reloadProducers) { return; } emit updateFps(fpsChanged); if (!qFuzzyCompare(fpsChanged, 1.0)) { pCore->bin()->reloadAllProducers(); } } void KdenliveDoc::resetProfile() { updateProjectProfile(true); emit docModified(true); } void KdenliveDoc::slotSwitchProfile(const QString &profile_path) { pCore->setCurrentProfile(profile_path); updateProjectProfile(true); emit docModified(true); } void KdenliveDoc::switchProfile(std::unique_ptr &profile, const QString &id, const QDomElement &xml) { Q_UNUSED(id) Q_UNUSED(xml) // Request profile update QString matchingProfile = ProfileRepository::get()->findMatchingProfile(profile.get()); if (matchingProfile.isEmpty() && (profile->width() % 8 != 0)) { // Make sure profile width is a multiple of 8, required by some parts of mlt profile->adjustWidth(); matchingProfile = ProfileRepository::get()->findMatchingProfile(profile.get()); } if (!matchingProfile.isEmpty()) { // We found a known matching profile, switch and inform user profile->m_path = matchingProfile; profile->m_description = ProfileRepository::get()->getProfile(matchingProfile)->description(); if (KdenliveSettings::default_profile().isEmpty()) { // Default project format not yet confirmed, propose QString currentProfileDesc = pCore->getCurrentProfile()->description(); KMessageBox::ButtonCode answer = KMessageBox::questionYesNoCancel( QApplication::activeWindow(), i18n("Your default project profile is %1, but your clip's profile is %2.\nDo you want to change default profile for future projects ?", currentProfileDesc, profile->description()), i18n("Change default project profile"), KGuiItem(i18n("Change default to %1", profile->description())), KGuiItem(i18n("Keep current default %1", currentProfileDesc)), KGuiItem(i18n("Ask me later"))); switch (answer) { case KMessageBox::Yes: KdenliveSettings::setDefault_profile(profile->path()); pCore->setCurrentProfile(profile->path()); updateProjectProfile(true); emit docModified(true); return; break; case KMessageBox::No: return; break; default: break; } } // Build actions for the info message (switch / cancel) QList list; QAction *ac = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-ok")), i18n("Switch"), this); connect(ac, &QAction::triggered, [this, &profile]() { this->slotSwitchProfile(profile->path()); }); QAction *ac2 = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); list << ac << ac2; pCore->bin()->doDisplayMessage(i18n("Switch to clip profile %1?", profile->descriptiveString()), KMessageWidget::Information, list); } else { // No known profile, ask user if he wants to use clip profile anyway // Check profile fps so that we don't end up with an fps = 30.003 which would mess things up QString adjustMessage; double fps = (double)profile->frame_rate_num() / profile->frame_rate_den(); double fps_int; double fps_frac = std::modf(fps, &fps_int); if (fps_frac < 0.4) { profile->m_frame_rate_num = (int)fps_int; profile->m_frame_rate_den = 1; } else { // Check for 23.98, 29.97, 59.94 if (qFuzzyCompare(fps_int, 23.0)) { if (qFuzzyCompare(fps, 23.98)) { profile->m_frame_rate_num = 24000; profile->m_frame_rate_den = 1001; } } else if (qFuzzyCompare(fps_int, 29.0)) { if (qFuzzyCompare(fps, 29.97)) { profile->m_frame_rate_num = 30000; profile->m_frame_rate_den = 1001; } } else if (qFuzzyCompare(fps_int, 59.0)) { if (qFuzzyCompare(fps, 59.94)) { profile->m_frame_rate_num = 60000; profile->m_frame_rate_den = 1001; } } else { // Unknown profile fps, warn user adjustMessage = i18n("\nWarning: unknown non integer fps, might cause incorrect duration display."); } } if (qFuzzyCompare((double)profile->m_frame_rate_num / profile->m_frame_rate_den, fps)) { adjustMessage = i18n("\nProfile fps adjusted from original %1", QString::number(fps, 'f', 4)); } if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("No profile found for your clip.\nCreate and switch to new profile (%1x%2, %3fps)?%4", profile->m_width, profile->m_height, QString::number((double)profile->m_frame_rate_num / profile->m_frame_rate_den, 'f', 2), adjustMessage)) == KMessageBox::Continue) { profile->m_description = QStringLiteral("%1x%2 %3fps") .arg(profile->m_width) .arg(profile->m_height) .arg(QString::number((double)profile->m_frame_rate_num / profile->m_frame_rate_den, 'f', 2)); ProfileRepository::get()->saveProfile(profile.get()); pCore->setCurrentProfile(profile->m_path); updateProjectProfile(true); emit docModified(true); } } } void KdenliveDoc::doAddAction(const QString &name, QAction *a, const QKeySequence &shortcut) { pCore->window()->actionCollection()->addAction(name, a); a->setShortcut(shortcut); pCore->window()->actionCollection()->setDefaultShortcut(a, a->shortcut()); } QAction *KdenliveDoc::getAction(const QString &name) { return pCore->window()->actionCollection()->action(name); } void KdenliveDoc::previewProgress(int p) { pCore->window()->setPreviewProgress(p); } void KdenliveDoc::displayMessage(const QString &text, MessageType type, int timeOut) { pCore->window()->displayMessage(text, type, timeOut); } void KdenliveDoc::selectPreviewProfile() { // Read preview profiles and find the best match if (!KdenliveSettings::previewparams().isEmpty()) { setDocumentProperty(QStringLiteral("previewparameters"), KdenliveSettings::previewparams()); setDocumentProperty(QStringLiteral("previewextension"), KdenliveSettings::previewextension()); return; } KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); KConfigGroup group(&conf, "timelinepreview"); QMap values = group.entryMap(); QMapIterator i(values); QStringList matchingProfiles; QStringList fallBackProfiles; QSize pSize = pCore->getCurrentFrameDisplaySize(); QString profileSize = QStringLiteral("%1x%2").arg(pSize.width()).arg(pSize.height()); while (i.hasNext()) { i.next(); // Check for frame rate QString params = i.value(); QStringList data = i.value().split(QLatin1Char(' ')); // Check for size mismatch if (params.contains(QStringLiteral("s="))) { QString paramSize = params.section(QStringLiteral("s="), 1).section(QLatin1Char(' '), 0, 0); if (paramSize != profileSize) { continue; } } bool rateFound = false; for (const QString &arg : data) { if (arg.startsWith(QStringLiteral("r="))) { rateFound = true; double fps = arg.section(QLatin1Char('='), 1).toDouble(); if (fps > 0) { if (qAbs((int)(pCore->getCurrentFps() * 100) - (fps * 100)) <= 1) { matchingProfiles << i.value(); break; } } } } if (!rateFound) { // Profile without fps, can be used as fallBack fallBackProfiles << i.value(); } } QString bestMatch; if (!matchingProfiles.isEmpty()) { bestMatch = matchingProfiles.first(); } else if (!fallBackProfiles.isEmpty()) { bestMatch = fallBackProfiles.first(); } if (!bestMatch.isEmpty()) { setDocumentProperty(QStringLiteral("previewparameters"), bestMatch.section(QLatin1Char(';'), 0, 0)); setDocumentProperty(QStringLiteral("previewextension"), bestMatch.section(QLatin1Char(';'), 1, 1)); } else { setDocumentProperty(QStringLiteral("previewparameters"), QString()); setDocumentProperty(QStringLiteral("previewextension"), QString()); } } void KdenliveDoc::checkPreviewStack() { // A command was pushed in the middle of the stack, remove all cached data from last undos emit removeInvalidUndo(m_commandStack->count()); } void KdenliveDoc::saveMltPlaylist(const QString &fileName) { Q_UNUSED(fileName) // TODO REFAC // m_render->preparePreviewRendering(fileName); } void KdenliveDoc::initCacheDirs() { bool ok = false; QString kdenliveCacheDir; QString documentId = QDir::cleanPath(getDocumentProperty(QStringLiteral("documentid"))); documentId.toLongLong(&ok, 10); if (m_projectFolder.isEmpty()) { kdenliveCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); } else { kdenliveCacheDir = m_projectFolder; } if (!ok || documentId.isEmpty() || kdenliveCacheDir.isEmpty()) { return; } QString basePath = kdenliveCacheDir + QLatin1Char('/') + documentId; QDir dir(basePath); dir.mkpath(QStringLiteral(".")); dir.mkdir(QStringLiteral("preview")); dir.mkdir(QStringLiteral("audiothumbs")); dir.mkdir(QStringLiteral("videothumbs")); QDir cacheDir(kdenliveCacheDir); cacheDir.mkdir(QStringLiteral("proxy")); } QDir KdenliveDoc::getCacheDir(CacheType type, bool *ok) const { QString basePath; QString kdenliveCacheDir; QString documentId = QDir::cleanPath(getDocumentProperty(QStringLiteral("documentid"))); documentId.toLongLong(ok, 10); if (m_projectFolder.isEmpty()) { kdenliveCacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); if (!*ok || documentId.isEmpty() || kdenliveCacheDir.isEmpty()) { *ok = false; return QDir(kdenliveCacheDir); } } else { // Use specified folder to store all files kdenliveCacheDir = m_projectFolder; } basePath = kdenliveCacheDir + QLatin1Char('/') + documentId; switch (type) { case SystemCacheRoot: return QStandardPaths::writableLocation(QStandardPaths::CacheLocation); case CacheRoot: basePath = kdenliveCacheDir; break; case CachePreview: basePath.append(QStringLiteral("/preview")); break; case CacheProxy: basePath = kdenliveCacheDir; basePath.append(QStringLiteral("/proxy")); break; case CacheAudio: basePath.append(QStringLiteral("/audiothumbs")); break; case CacheThumbs: basePath.append(QStringLiteral("/videothumbs")); break; default: break; } QDir dir(basePath); if (!dir.exists()) { *ok = false; } return dir; } QStringList KdenliveDoc::getProxyHashList() { return pCore->bin()->getProxyHashList(); } std::shared_ptr KdenliveDoc::getGuideModel() const { return m_guideModel; } void KdenliveDoc::guidesChanged() { m_documentProperties[QStringLiteral("guides")] = m_guideModel->toJson(); } void KdenliveDoc::groupsChanged(const QString &groups) { m_documentProperties[QStringLiteral("groups")] = groups; } const QString KdenliveDoc::documentRoot() const { return m_documentRoot; } bool KdenliveDoc::updatePreviewSettings(const QString &profile) { if (profile.isEmpty()) { return false; } QString params = profile.section(QLatin1Char(';'), 0, 0); QString ext = profile.section(QLatin1Char(';'), 1, 1); if (params != getDocumentProperty(QStringLiteral("previewparameters")) || ext != getDocumentProperty(QStringLiteral("previewextension"))) { // Timeline preview params changed, delete all existing previews. setDocumentProperty(QStringLiteral("previewparameters"), params); setDocumentProperty(QStringLiteral("previewextension"), ext); return true; } return false; } - diff --git a/src/doc/kthumb.h b/src/doc/kthumb.h index 1f9027412..1b263a643 100644 --- a/src/doc/kthumb.h +++ b/src/doc/kthumb.h @@ -1,47 +1,47 @@ /* Copyright (C) 2016 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 . */ #ifndef KTHUMB_H #define KTHUMB_H #include #include #include namespace Mlt { class Producer; class Frame; -} +} // namespace Mlt namespace KThumb { QPixmap getImage(const QUrl &url, int width, int height = -1); QPixmap getImage(const QUrl &url, int frame, int width, int height = -1); QImage getFrame(Mlt::Producer *producer, int framepos, int displayWidth, int height); QImage getFrame(Mlt::Producer &producer, int framepos, int displayWidth, int height); QImage getFrame(Mlt::Frame *frame, int width, int height, bool forceRescale = false); /** @brief Calculates image variance, useful to know if a thumbnail is interesting. * @return an integer between 0 and 100. 0 means no variance, eg. black image while bigger values mean contrasted image * */ -int imageVariance(const QImage& image); -} +int imageVariance(const QImage &image); +} // namespace KThumb #endif diff --git a/src/dvdwizard/dvdwizardchapters.cpp b/src/dvdwizard/dvdwizardchapters.cpp index 8bce0bdf8..671349879 100644 --- a/src/dvdwizard/dvdwizardchapters.cpp +++ b/src/dvdwizard/dvdwizardchapters.cpp @@ -1,272 +1,272 @@ /*************************************************************************** * Copyright (C) 2009 by Jean-Baptiste Mardelle (jb@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) 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 "dvdwizardchapters.h" DvdWizardChapters::DvdWizardChapters(MonitorManager *manager, DVDFORMAT format, QWidget *parent) : QWizardPage(parent) , m_format(format) , m_monitor(nullptr) , m_manager(manager) { m_view.setupUi(this); - connect(m_view.vob_list, static_cast(&KComboBox::currentIndexChanged), this, &DvdWizardChapters::slotUpdateChaptersList); + connect(m_view.vob_list, static_cast(&KComboBox::currentIndexChanged), this, &DvdWizardChapters::slotUpdateChaptersList); connect(m_view.button_add, &QAbstractButton::clicked, this, &DvdWizardChapters::slotAddChapter); connect(m_view.button_delete, &QAbstractButton::clicked, this, &DvdWizardChapters::slotRemoveChapter); connect(m_view.chapters_list, &QListWidget::itemSelectionChanged, this, &DvdWizardChapters::slotGoToChapter); connect(m_view.chapters_box, &QCheckBox::stateChanged, this, &DvdWizardChapters::slotEnableChapters); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); // Build monitor for chapters auto *vbox = new QVBoxLayout; m_view.video_frame->setLayout(vbox); if (m_format == PAL || m_format == PAL_WIDE) { m_tc.setFormat(25); } else { m_tc.setFormat(30000.0 / 1001); } } DvdWizardChapters::~DvdWizardChapters() { if (m_monitor) { m_monitor->stop(); m_manager->removeMonitor(m_monitor); delete m_monitor; } } void DvdWizardChapters::stopMonitor() { if (m_monitor) { m_monitor->pause(); } } void DvdWizardChapters::slotUpdateChaptersList() { m_monitor->show(); m_manager->activateMonitor(Kdenlive::DvdMonitor); m_monitor->start(); m_monitor->slotOpenDvdFile(m_view.vob_list->currentText()); m_monitor->adjustRulerSize(m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole).toInt()); QStringList currentChaps = m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole + 1).toStringList(); // insert chapters QStringList chaptersString; for (int i = 0; i < currentChaps.count(); ++i) { chaptersString.append(Timecode::getStringTimecode(currentChaps.at(i).toInt(), m_tc.fps(), true)); } m_view.chapters_list->clear(); m_view.chapters_list->addItems(chaptersString); updateMonitorMarkers(); m_monitor->refreshMonitorIfActive(); // bool modified = m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole + 2).toInt(); } void DvdWizardChapters::slotAddChapter() { int pos = m_monitor->position(); QStringList currentChaps = m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole + 1).toStringList(); if (currentChaps.contains(QString::number(pos))) { return; } currentChaps.append(QString::number(pos)); QList chapterTimes; chapterTimes.reserve(currentChaps.count()); for (int i = 0; i < currentChaps.count(); ++i) { chapterTimes.append(currentChaps.at(i).toInt()); } qSort(chapterTimes); // rebuild chapters QStringList chaptersString; currentChaps.clear(); for (int i = 0; i < chapterTimes.count(); ++i) { chaptersString.append(Timecode::getStringTimecode(chapterTimes.at(i), m_tc.fps(), true)); currentChaps.append(QString::number(chapterTimes.at(i))); } // Save item chapters m_view.vob_list->setItemData(m_view.vob_list->currentIndex(), currentChaps, Qt::UserRole + 1); // Mark item as modified m_view.vob_list->setItemData(m_view.vob_list->currentIndex(), 1, Qt::UserRole + 2); m_view.chapters_list->clear(); m_view.chapters_list->addItems(chaptersString); updateMonitorMarkers(); } void DvdWizardChapters::updateMonitorMarkers() { const QStringList chapters = m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole + 1).toStringList(); QList markers; markers.reserve(chapters.count()); for (const QString &frame : chapters) { markers << CommentedTime(GenTime(frame.toInt(), m_tc.fps()), QString()); } // TODO: reimplement makers as model // m_monitor->setMarkers(markers); } void DvdWizardChapters::slotRemoveChapter() { int ix = m_view.chapters_list->currentRow(); QStringList currentChaps = m_view.vob_list->itemData(m_view.vob_list->currentIndex(), Qt::UserRole + 1).toStringList(); currentChaps.removeAt(ix); // Save item chapters m_view.vob_list->setItemData(m_view.vob_list->currentIndex(), currentChaps, Qt::UserRole + 1); // Mark item as modified m_view.vob_list->setItemData(m_view.vob_list->currentIndex(), 1, Qt::UserRole + 2); // rebuild chapters QStringList chaptersString; for (int i = 0; i < currentChaps.count(); ++i) { chaptersString.append(Timecode::getStringTimecode(currentChaps.at(i).toInt(), m_tc.fps(), true)); } m_view.chapters_list->clear(); m_view.chapters_list->addItems(chaptersString); updateMonitorMarkers(); } void DvdWizardChapters::slotGoToChapter() { if (m_view.chapters_list->currentItem()) { m_monitor->setTimePos(m_tc.reformatSeparators(m_view.chapters_list->currentItem()->text())); } } void DvdWizardChapters::createMonitor(DVDFORMAT format) { if (m_monitor == nullptr) { // TODO: allow monitor with different profile for DVD QString profile = DvdWizardVob::getDvdProfile(format); m_monitor = new Monitor(Kdenlive::DvdMonitor, m_manager /*, profile*/, this); m_monitor->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_view.video_frame->layout()->addWidget(m_monitor); m_manager->appendMonitor(m_monitor); m_monitor->setCustomProfile(profile, m_tc); m_manager->activateMonitor(Kdenlive::DvdMonitor); m_monitor->start(); } } void DvdWizardChapters::setVobFiles(DVDFORMAT format, const QStringList &movies, const QStringList &durations, const QStringList &chapters) { m_format = format; if (m_format == PAL || m_format == PAL_WIDE) { m_tc.setFormat(25); } else { m_tc.setFormat(30000.0 / 1001); } m_view.vob_list->blockSignals(true); m_view.vob_list->clear(); for (int i = 0; i < movies.count(); ++i) { m_view.vob_list->addItem(movies.at(i), durations.at(i)); m_view.vob_list->setItemData(i, chapters.at(i).split(QLatin1Char(';')), Qt::UserRole + 1); } m_view.vob_list->blockSignals(false); if (m_view.chapters_box->checkState() == Qt::Checked) { slotUpdateChaptersList(); } } QMap DvdWizardChapters::chaptersData() const { QMap result; int max = m_view.vob_list->count(); for (int i = 0; i < max; ++i) { QString chapters = m_view.vob_list->itemData(i, Qt::UserRole + 1).toStringList().join(QLatin1Char(';')); result.insert(m_view.vob_list->itemText(i), chapters); } return result; } QStringList DvdWizardChapters::selectedTitles() const { QStringList result; int max = m_view.vob_list->count(); for (int i = 0; i < max; ++i) { result.append(m_view.vob_list->itemText(i)); if (!m_view.chapters_box->isChecked()) { continue; } QStringList chapters = m_view.vob_list->itemData(i, Qt::UserRole + 1).toStringList(); for (int j = 0; j < chapters.count(); ++j) { result.append(Timecode::getStringTimecode(chapters.at(j).toInt(), m_tc.fps(), true)); } } return result; } QStringList DvdWizardChapters::chapters(int ix) const { QStringList result; QStringList chapters = m_view.vob_list->itemData(ix, Qt::UserRole + 1).toStringList(); for (int j = 0; j < chapters.count(); ++j) { result.append(Timecode::getStringTimecode(chapters.at(j).toInt(), m_tc.fps(), true)); } return result; } QStringList DvdWizardChapters::selectedTargets() const { QStringList result; int max = m_view.vob_list->count(); for (int i = 0; i < max; ++i) { // rightJustified: fill with 0s to make menus with more than 9 buttons work (now up to 99 buttons possible) result.append(QStringLiteral("jump title ") + QString::number(i + 1).rightJustified(2, '0')); if (!m_view.chapters_box->isChecked()) { continue; } QStringList chapters = m_view.vob_list->itemData(i, Qt::UserRole + 1).toStringList(); for (int j = 0; j < chapters.count(); ++j) { result.append(QStringLiteral("jump title ") + QString::number(i + 1).rightJustified(2, '0') + QStringLiteral(" chapter ") + QString::number(j + 1).rightJustified(2, '0')); } } return result; } QDomElement DvdWizardChapters::toXml() const { QDomDocument doc; QDomElement xml = doc.createElement(QStringLiteral("xml")); doc.appendChild(xml); if (!m_view.chapters_box->isChecked()) { return doc.documentElement(); } for (int i = 0; i < m_view.vob_list->count(); ++i) { QDomElement vob = doc.createElement(QStringLiteral("vob")); vob.setAttribute(QStringLiteral("file"), m_view.vob_list->itemText(i)); vob.setAttribute(QStringLiteral("chapters"), m_view.vob_list->itemData(i, Qt::UserRole + 1).toStringList().join(QLatin1Char(';'))); xml.appendChild(vob); } return doc.documentElement(); } void DvdWizardChapters::slotEnableChapters(int state) { m_view.chapters_frame->setEnabled(state == Qt::Checked); if (state == Qt::Checked) { createMonitor(m_format); slotUpdateChaptersList(); } } diff --git a/src/dvdwizard/dvdwizardvob.cpp b/src/dvdwizard/dvdwizardvob.cpp index 5308e344c..87356f3c8 100644 --- a/src/dvdwizard/dvdwizardvob.cpp +++ b/src/dvdwizard/dvdwizardvob.cpp @@ -1,874 +1,874 @@ /*************************************************************************** * Copyright (C) 2009 by Jean-Baptiste Mardelle (jb@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) 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 "dvdwizardvob.h" #include "dialogs/clipcreationdialog.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "timecode.h" #include #include "kdenlive_debug.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include #include #include #include #include DvdTreeWidget::DvdTreeWidget(QWidget *parent) : QTreeWidget(parent) { setAcceptDrops(true); } void DvdTreeWidget::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { event->setDropAction(Qt::CopyAction); event->setAccepted(true); } else { QTreeWidget::dragEnterEvent(event); } } void DvdTreeWidget::dragMoveEvent(QDragMoveEvent *event) { event->acceptProposedAction(); } void DvdTreeWidget::mouseDoubleClickEvent(QMouseEvent *) { emit addNewClip(); } void DvdTreeWidget::dropEvent(QDropEvent *event) { QList clips = event->mimeData()->urls(); event->accept(); emit addClips(clips); } DvdWizardVob::DvdWizardVob(QWidget *parent) : QWizardPage(parent) , m_installCheck(true) , m_duration(0) { m_view.setupUi(this); m_view.button_add->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_view.button_delete->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_view.button_up->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); m_view.button_down->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); m_vobList = new DvdTreeWidget(this); auto *lay1 = new QVBoxLayout; lay1->setMargin(0); lay1->addWidget(m_vobList); m_view.list_frame->setLayout(lay1); m_vobList->setColumnCount(3); m_vobList->setHeaderHidden(true); m_view.convert_box->setVisible(false); connect(m_vobList, SIGNAL(addClips(QList)), this, SLOT(slotAddVobList(QList))); connect(m_vobList, SIGNAL(addNewClip()), this, SLOT(slotAddVobList())); connect(m_view.button_add, SIGNAL(clicked()), this, SLOT(slotAddVobList())); connect(m_view.button_delete, &QAbstractButton::clicked, this, &DvdWizardVob::slotDeleteVobFile); connect(m_view.button_up, &QAbstractButton::clicked, this, &DvdWizardVob::slotItemUp); connect(m_view.button_down, &QAbstractButton::clicked, this, &DvdWizardVob::slotItemDown); connect(m_view.convert_abort, &QAbstractButton::clicked, this, &DvdWizardVob::slotAbortTranscode); connect(m_vobList, &QTreeWidget::itemSelectionChanged, this, &DvdWizardVob::slotCheckVobList); m_vobList->setIconSize(QSize(60, 45)); QString errorMessage; if (QStandardPaths::findExecutable(QStringLiteral("dvdauthor")).isEmpty()) { errorMessage.append(i18n("Program %1 is required for the DVD wizard.", i18n("dvdauthor"))); } if (QStandardPaths::findExecutable(QStringLiteral("mkisofs")).isEmpty() && QStandardPaths::findExecutable(QStringLiteral("genisoimage")).isEmpty()) { errorMessage.append(i18n("Program %1 or %2 is required for the DVD wizard.", i18n("mkisofs"), i18n("genisoimage"))); } if (!errorMessage.isEmpty()) { m_view.button_add->setEnabled(false); m_view.dvd_profile->setEnabled(false); } m_view.dvd_profile->addItems(QStringList() << i18n("PAL 4:3") << i18n("PAL 16:9") << i18n("NTSC 4:3") << i18n("NTSC 16:9")); connect(m_view.dvd_profile, SIGNAL(activated(int)), this, SLOT(slotCheckProfiles())); m_vobList->header()->setStretchLastSection(false); m_vobList->header()->setSectionResizeMode(0, QHeaderView::Stretch); m_vobList->header()->setSectionResizeMode(1, QHeaderView::Custom); m_vobList->header()->setSectionResizeMode(2, QHeaderView::Custom); m_capacityBar = new KCapacityBar(KCapacityBar::DrawTextInline, this); auto *lay = new QHBoxLayout; lay->addWidget(m_capacityBar); m_view.size_box->setLayout(lay); m_vobList->setItemDelegate(new DvdViewDelegate(m_vobList)); m_transcodeAction = new QAction(i18n("Transcode"), this); connect(m_transcodeAction, &QAction::triggered, this, &DvdWizardVob::slotTranscodeFiles); m_warnMessage = new KMessageWidget; m_warnMessage->setCloseButtonVisible(false); QGridLayout *s = static_cast(layout()); s->addWidget(m_warnMessage, 2, 0, 1, -1); if (!errorMessage.isEmpty()) { m_warnMessage->setMessageType(KMessageWidget::Error); m_warnMessage->setText(errorMessage); m_installCheck = false; } else { m_warnMessage->setMessageType(KMessageWidget::Warning); m_warnMessage->setText(i18n("Your clips do not match selected DVD format, transcoding required.")); m_warnMessage->addAction(m_transcodeAction); m_warnMessage->hide(); } m_view.button_transcode->setHidden(true); slotCheckVobList(); m_transcodeProcess.setProcessChannelMode(QProcess::MergedChannels); connect(&m_transcodeProcess, &QProcess::readyReadStandardOutput, this, &DvdWizardVob::slotShowTranscodeInfo); connect(&m_transcodeProcess, static_cast(&QProcess::finished), this, &DvdWizardVob::slotTranscodeFinished); } DvdWizardVob::~DvdWizardVob() { delete m_capacityBar; // Abort running transcoding if (m_transcodeProcess.state() != QProcess::NotRunning) { disconnect(&m_transcodeProcess, static_cast(&QProcess::finished), this, &DvdWizardVob::slotTranscodeFinished); m_transcodeProcess.close(); m_transcodeProcess.waitForFinished(); } } bool DvdWizardVob::isComplete() const { return m_vobList->topLevelItemCount() > 0; } void DvdWizardVob::slotShowTranscodeInfo() { QString log = QString(m_transcodeProcess.readAll()); if (m_duration == 0) { if (log.contains(QStringLiteral("Duration:"))) { QString durationstr = log.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified(); const QStringList numbers = durationstr.split(QLatin1Char(':')); if (numbers.size() < 3) { return; } m_duration = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); // log_text->setHidden(true); // job_progress->setHidden(false); } else { // log_text->setHidden(false); // job_progress->setHidden(true); } } else if (log.contains(QStringLiteral("time="))) { int progress; QString time = log.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0); if (time.contains(QLatin1Char(':'))) { const QStringList numbers = time.split(QLatin1Char(':')); if (numbers.size() < 3) { return; } progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } else { progress = (int)time.toDouble(); } m_view.convert_progress->setValue((int)(100.0 * progress / m_duration)); } // log_text->setPlainText(log); } void DvdWizardVob::slotAbortTranscode() { if (m_transcodeProcess.state() != QProcess::NotRunning) { m_transcodeProcess.close(); m_transcodeProcess.waitForFinished(); } m_transcodeQueue.clear(); m_view.convert_box->hide(); slotCheckProfiles(); } void DvdWizardVob::slotTranscodeFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitCode == 0 && exitStatus == QProcess::NormalExit) { slotTranscodedClip(m_currentTranscoding.filename, m_currentTranscoding.filename + m_currentTranscoding.params.section(QStringLiteral("%1"), 1, 1).section(QLatin1Char(' '), 0, 0)); if (!m_transcodeQueue.isEmpty()) { m_transcodeProcess.close(); processTranscoding(); return; } } else { // Something failed // TODO show log m_warnMessage->setMessageType(KMessageWidget::Warning); m_warnMessage->setText(i18n("Transcoding failed!")); m_warnMessage->animatedShow(); m_transcodeQueue.clear(); } m_duration = 0; m_transcodeProcess.close(); if (m_transcodeQueue.isEmpty()) { m_view.convert_box->setHidden(true); slotCheckProfiles(); } } void DvdWizardVob::slotCheckProfiles() { bool conflict = false; int comboProfile = m_view.dvd_profile->currentIndex(); for (int i = 0; i < m_vobList->topLevelItemCount(); ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (item->data(0, Qt::UserRole + 1).toInt() != comboProfile) { conflict = true; break; } } m_transcodeAction->setEnabled(conflict); if (conflict) { showProfileError(); } else { m_warnMessage->animatedHide(); } } void DvdWizardVob::slotAddVobList(QList list) { if (list.isEmpty()) { QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' ')); QString dialogFilter = i18n("All Supported Files") + QStringLiteral(" (") + allExtensions + QStringLiteral(");; ") + i18n("MPEG Files") + QStringLiteral(" (*.mpeg *.mpg *.vob);; ") + i18n("All Files") + QStringLiteral(" (*.*)"); list = QFileDialog::getOpenFileUrls(this, i18n("Add new video file"), QUrl::fromLocalFile(KRecentDirs::dir(QStringLiteral(":KdenliveDvdFolder"))), dialogFilter); if (!list.isEmpty()) { KRecentDirs::add(QStringLiteral(":KdenliveDvdFolder"), list.at(0).adjusted(QUrl::RemoveFilename).toLocalFile()); } } for (const QUrl &url : list) { slotAddVobFile(url, QString(), false); } slotCheckVobList(); slotCheckProfiles(); } void DvdWizardVob::slotAddVobFile(const QUrl &url, const QString &chapters, bool checkFormats) { if (!url.isValid()) { return; } QFile f(url.toLocalFile()); qint64 fileSize = f.size(); Mlt::Profile profile; profile.set_explicit(false); QTreeWidgetItem *item = new QTreeWidgetItem(m_vobList, QStringList() << url.toLocalFile() << QString() << QString::number(static_cast(fileSize))); item->setData(2, Qt::UserRole, fileSize); item->setData(0, Qt::DecorationRole, QIcon::fromTheme(QStringLiteral("video-x-generic")).pixmap(60, 45)); item->setToolTip(0, url.toLocalFile()); QString resource = url.toLocalFile(); resource.prepend(QStringLiteral("avformat:")); Mlt::Producer *producer = new Mlt::Producer(profile, resource.toUtf8().data()); if ((producer != nullptr) && producer->is_valid() && !producer->is_blank()) { double fps = profile.fps(); profile.from_producer(*producer); profile.set_explicit(1); - if (!qFuzzyCompare(profile.fps(),fps)) { + if (!qFuzzyCompare(profile.fps(), fps)) { // fps changed, rebuild producer delete producer; producer = new Mlt::Producer(profile, resource.toUtf8().data()); } } if ((producer != nullptr) && producer->is_valid() && !producer->is_blank()) { int width = 45.0 * profile.dar(); if (width % 2 == 1) { width++; } item->setData(0, Qt::DecorationRole, QPixmap::fromImage(KThumb::getFrame(producer, 0, width, 45))); int playTime = producer->get_playtime(); item->setText(1, Timecode::getStringTimecode(playTime, profile.fps())); item->setData(1, Qt::UserRole, playTime); int standard = -1; int aspect = profile.dar() * 100; if (profile.height() == 576 && qFuzzyCompare(profile.fps(), 25.0)) { if (aspect > 150) { standard = 1; } else { standard = 0; } } else if (profile.height() == 480 && qAbs(profile.fps() - 30000.0 / 1001) < 0.2) { if (aspect > 150) { standard = 3; } else { standard = 2; } } QString standardName; switch (standard) { case 3: standardName = i18n("NTSC 16:9"); break; case 2: standardName = i18n("NTSC 4:3"); break; case 1: standardName = i18n("PAL 16:9"); break; case 0: standardName = i18n("PAL 4:3"); break; default: standardName = i18n("Unknown"); } standardName.append(QStringLiteral(" | %1x%2, %3fps").arg(profile.width()).arg(profile.height()).arg(profile.fps())); item->setData(0, Qt::UserRole, standardName); item->setData(0, Qt::UserRole + 1, standard); item->setData(0, Qt::UserRole + 2, QSize(profile.dar() * profile.height(), profile.height())); if (m_vobList->topLevelItemCount() == 1) { // This is the first added movie, auto select DVD format if (standard >= 0) { m_view.dvd_profile->blockSignals(true); m_view.dvd_profile->setCurrentIndex(standard); m_view.dvd_profile->blockSignals(false); } } } else { // Cannot load movie, reject showError(i18n("The clip %1 is invalid.", url.fileName())); } delete producer; if (!chapters.isEmpty()) { item->setData(1, Qt::UserRole + 1, chapters); } else if (QFile::exists(url.toLocalFile() + QStringLiteral(".dvdchapter"))) { // insert chapters as children QFile file(url.toLocalFile() + QStringLiteral(".dvdchapter")); if (file.open(QIODevice::ReadOnly)) { QDomDocument doc; if (!doc.setContent(&file)) { file.close(); return; } file.close(); QDomNodeList chapterNodes = doc.elementsByTagName(QStringLiteral("chapter")); QStringList chaptersList; for (int j = 0; j < chapterNodes.count(); ++j) { chaptersList.append(QString::number(chapterNodes.at(j).toElement().attribute(QStringLiteral("time")).toInt())); } item->setData(1, Qt::UserRole + 1, chaptersList.join(QLatin1Char(';'))); } } else { // Explicitly add a chapter at 00:00:00:00 item->setData(1, Qt::UserRole + 1, "0"); } if (checkFormats) { slotCheckVobList(); slotCheckProfiles(); } } void DvdWizardVob::slotDeleteVobFile() { QTreeWidgetItem *item = m_vobList->currentItem(); if (item == nullptr) { return; } delete item; slotCheckVobList(); slotCheckProfiles(); } void DvdWizardVob::setUrl(const QString &url) { slotAddVobFile(QUrl::fromLocalFile(url)); } QStringList DvdWizardVob::selectedUrls() const { QStringList result; int max = m_vobList->topLevelItemCount(); int i = 0; if (m_view.use_intro->isChecked()) { // First movie is only for intro i = 1; } for (; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (item) { result.append(item->text(0)); } } return result; } QStringList DvdWizardVob::durations() const { QStringList result; int max = m_vobList->topLevelItemCount(); int i = 0; if (m_view.use_intro->isChecked()) { // First movie is only for intro i = 1; } for (; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (item) { result.append(QString::number(item->data(1, Qt::UserRole).toInt())); } } return result; } QStringList DvdWizardVob::chapters() const { QStringList result; int max = m_vobList->topLevelItemCount(); int i = 0; if (m_view.use_intro->isChecked()) { // First movie is only for intro i = 1; } for (; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (item) { result.append(item->data(1, Qt::UserRole + 1).toString()); } } return result; } void DvdWizardVob::updateChapters(const QMap &chaptersdata) { int max = m_vobList->topLevelItemCount(); int i = 0; if (m_view.use_intro->isChecked()) { // First movie is only for intro i = 1; } for (; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (chaptersdata.contains(item->text(0))) { item->setData(1, Qt::UserRole + 1, chaptersdata.value(item->text(0))); } } } int DvdWizardVob::duration(int ix) const { int result = -1; QTreeWidgetItem *item = m_vobList->topLevelItem(ix); if (item) { result = item->data(1, Qt::UserRole).toInt(); } return result; } const QString DvdWizardVob::introMovie() const { QString url; if (m_view.use_intro->isChecked() && m_vobList->topLevelItemCount() > 0) { url = m_vobList->topLevelItem(0)->text(0); } return url; } void DvdWizardVob::setUseIntroMovie(bool use) { m_view.use_intro->setChecked(use); } void DvdWizardVob::slotCheckVobList() { emit completeChanged(); int max = m_vobList->topLevelItemCount(); QTreeWidgetItem *item = m_vobList->currentItem(); bool hasItem = true; if (item == nullptr) { hasItem = false; } m_view.button_delete->setEnabled(hasItem); if (hasItem && m_vobList->indexOfTopLevelItem(item) == 0) { m_view.button_up->setEnabled(false); } else { m_view.button_up->setEnabled(hasItem); } if (hasItem && m_vobList->indexOfTopLevelItem(item) == max - 1) { m_view.button_down->setEnabled(false); } else { m_view.button_down->setEnabled(hasItem); } qint64 totalSize = 0; for (int i = 0; i < max; ++i) { item = m_vobList->topLevelItem(i); if (item) { totalSize += (qint64)item->data(2, Qt::UserRole).toInt(); } } qint64 maxSize = (qint64)47000 * 100000; m_capacityBar->setValue(static_cast(100 * totalSize / maxSize)); m_capacityBar->setText(KIO::convertSize(static_cast(totalSize))); } void DvdWizardVob::slotItemUp() { QTreeWidgetItem *item = m_vobList->currentItem(); if (item == nullptr) { return; } int index = m_vobList->indexOfTopLevelItem(item); if (index == 0) { return; } m_vobList->insertTopLevelItem(index - 1, m_vobList->takeTopLevelItem(index)); } void DvdWizardVob::slotItemDown() { int max = m_vobList->topLevelItemCount(); QTreeWidgetItem *item = m_vobList->currentItem(); if (item == nullptr) { return; } int index = m_vobList->indexOfTopLevelItem(item); if (index == max - 1) { return; } m_vobList->insertTopLevelItem(index + 1, m_vobList->takeTopLevelItem(index)); } DVDFORMAT DvdWizardVob::dvdFormat() const { return (DVDFORMAT)m_view.dvd_profile->currentIndex(); } const QString DvdWizardVob::dvdProfile() const { QString profile; switch (m_view.dvd_profile->currentIndex()) { case PAL_WIDE: profile = QStringLiteral("dv_pal_wide"); break; case NTSC: profile = QStringLiteral("dv_ntsc"); break; case NTSC_WIDE: profile = QStringLiteral("dv_ntsc_wide"); break; default: profile = QStringLiteral("dv_pal"); } return profile; } // static QString DvdWizardVob::getDvdProfile(DVDFORMAT format) { QString profile; switch (format) { case PAL_WIDE: profile = QStringLiteral("dv_pal_wide"); break; case NTSC: profile = QStringLiteral("dv_ntsc"); break; case NTSC_WIDE: profile = QStringLiteral("dv_ntsc_wide"); break; default: profile = QStringLiteral("dv_pal"); } return profile; } void DvdWizardVob::setProfile(const QString &profile) { if (profile == QLatin1String("dv_pal_wide")) { m_view.dvd_profile->setCurrentIndex(PAL_WIDE); } else if (profile == QLatin1String("dv_ntsc")) { m_view.dvd_profile->setCurrentIndex(NTSC); } else if (profile == QLatin1String("dv_ntsc_wide")) { m_view.dvd_profile->setCurrentIndex(NTSC_WIDE); } else { m_view.dvd_profile->setCurrentIndex(PAL); } } void DvdWizardVob::clear() { m_vobList->clear(); } void DvdWizardVob::slotTranscodeFiles() { m_warnMessage->animatedHide(); // Find transcoding info related to selected DVD profile KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QString profileEasyName; QSize destSize; QSize finalSize; switch (m_view.dvd_profile->currentIndex()) { case PAL_WIDE: profileEasyName = QStringLiteral("DVD PAL 16:9"); destSize = QSize(1024, 576); finalSize = QSize(720, 576); break; case NTSC: profileEasyName = QStringLiteral("DVD NTSC 4:3"); destSize = QSize(640, 480); finalSize = QSize(720, 480); break; case NTSC_WIDE: profileEasyName = QStringLiteral("DVD NTSC 16:9"); destSize = QSize(853, 480); finalSize = QSize(720, 480); break; default: profileEasyName = QStringLiteral("DVD PAL 4:3"); destSize = QSize(768, 576); finalSize = QSize(720, 576); } QString params = transConfig.readEntry(profileEasyName); m_transcodeQueue.clear(); m_view.convert_progress->setValue(0); m_duration = 0; // Transcode files that do not match selected profile int max = m_vobList->topLevelItemCount(); int format = m_view.dvd_profile->currentIndex(); m_view.convert_box->setVisible(true); for (int i = 0; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (item->data(0, Qt::UserRole + 1).toInt() != format) { // File needs to be transcoded m_transcodeAction->setEnabled(false); QSize original = item->data(0, Qt::UserRole + 2).toSize(); double input_aspect = (double)original.width() / original.height(); QStringList postParams; if (input_aspect > (double)destSize.width() / destSize.height()) { // letterboxing int conv_height = (int)(destSize.width() / input_aspect); int conv_pad = (int)(((double)(destSize.height() - conv_height)) / 2.0); if (conv_pad % 2 == 1) { conv_pad--; } postParams << QStringLiteral("-vf") << QStringLiteral("scale=%1:%2,pad=%3:%4:0:%5,setdar=%6") .arg(finalSize.width()) .arg(destSize.height() - 2 * conv_pad) .arg(finalSize.width()) .arg(finalSize.height()) .arg(conv_pad) .arg(input_aspect); } else { // pillarboxing int conv_width = (int)(destSize.height() * input_aspect); int conv_pad = (int)(((double)(destSize.width() - conv_width)) / destSize.width() * finalSize.width() / 2.0); if (conv_pad % 2 == 1) { conv_pad--; } postParams << QStringLiteral("-vf") << QStringLiteral("scale=%1:%2,pad=%3:%4:%5:0,setdar=%6") .arg(finalSize.width() - 2 * conv_pad) .arg(destSize.height()) .arg(finalSize.width()) .arg(finalSize.height()) .arg(conv_pad) .arg(input_aspect); } TranscodeJobInfo jobInfo; jobInfo.filename = item->text(0); jobInfo.params = params.section(QLatin1Char(';'), 0, 0); jobInfo.postParams = postParams; m_transcodeQueue << jobInfo; } } processTranscoding(); } void DvdWizardVob::processTranscoding() { if (m_transcodeQueue.isEmpty()) { return; } m_currentTranscoding = m_transcodeQueue.takeFirst(); QStringList parameters; QStringList postParams = m_currentTranscoding.postParams; QString params = m_currentTranscoding.params; QString extension = params.section(QStringLiteral("%1"), 1, 1).section(QLatin1Char(' '), 0, 0); parameters << QStringLiteral("-i") << m_currentTranscoding.filename; if (QFile::exists(m_currentTranscoding.filename + extension)) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", m_currentTranscoding.filename + extension)) == KMessageBox::No) { // TODO inform about abortion m_transcodeQueue.clear(); return; } parameters << QStringLiteral("-y"); } bool replaceVfParams = false; const QStringList splitted = params.split(QLatin1Char(' ')); for (QString s : splitted) { if (replaceVfParams) { parameters << postParams.at(1); replaceVfParams = false; } else if (s.startsWith(QLatin1String("%1"))) { parameters << s.replace(0, 2, m_currentTranscoding.filename); } else if (!postParams.isEmpty() && s == QLatin1String("-vf")) { replaceVfParams = true; parameters << s; } else { parameters << s; } } qCDebug(KDENLIVE_LOG) << " / / /STARTING TCODE JB: \n" << KdenliveSettings::ffmpegpath() << " = " << parameters; m_transcodeProcess.start(KdenliveSettings::ffmpegpath(), parameters); m_view.convert_label->setText(i18n("Transcoding: %1", QUrl::fromLocalFile(m_currentTranscoding.filename).fileName())); } void DvdWizardVob::slotTranscodedClip(const QString &src, const QString &transcoded) { if (transcoded.isEmpty()) { // Transcoding canceled or failed m_transcodeAction->setEnabled(true); return; } int max = m_vobList->topLevelItemCount(); for (int i = 0; i < max; ++i) { QTreeWidgetItem *item = m_vobList->topLevelItem(i); if (QUrl::fromLocalFile(item->text(0)).toLocalFile() == src) { // Replace movie with transcoded version item->setText(0, transcoded); QFile f(transcoded); qint64 fileSize = f.size(); Mlt::Profile profile; profile.set_explicit(false); item->setText(2, KIO::convertSize(static_cast(fileSize))); item->setData(2, Qt::UserRole, fileSize); item->setData(0, Qt::DecorationRole, QIcon::fromTheme(QStringLiteral("video-x-generic")).pixmap(60, 45)); item->setToolTip(0, transcoded); QString resource = transcoded; resource.prepend(QStringLiteral("avformat:")); Mlt::Producer *producer = new Mlt::Producer(profile, resource.toUtf8().data()); if ((producer != nullptr) && producer->is_valid() && !producer->is_blank()) { double fps = profile.fps(); profile.from_producer(*producer); profile.set_explicit(1); - if (!qFuzzyCompare(profile.fps(),fps)) { + if (!qFuzzyCompare(profile.fps(), fps)) { // fps changed, rebuild producer delete producer; producer = new Mlt::Producer(profile, resource.toUtf8().data()); } } if ((producer != nullptr) && producer->is_valid() && !producer->is_blank()) { int width = 45.0 * profile.dar(); if (width % 2 == 1) { width++; } item->setData(0, Qt::DecorationRole, QPixmap::fromImage(KThumb::getFrame(producer, 0, width, 45))); int playTime = producer->get_playtime(); item->setText(1, Timecode::getStringTimecode(playTime, profile.fps())); item->setData(1, Qt::UserRole, playTime); int standard = -1; int aspect = profile.dar() * 100; if (profile.height() == 576) { if (aspect > 150) { standard = 1; } else { standard = 0; } } else if (profile.height() == 480) { if (aspect > 150) { standard = 3; } else { standard = 2; } } QString standardName; switch (standard) { case 3: standardName = i18n("NTSC 16:9"); break; case 2: standardName = i18n("NTSC 4:3"); break; case 1: standardName = i18n("PAL 16:9"); break; case 0: standardName = i18n("PAL 4:3"); break; default: standardName = i18n("Unknown"); } item->setData(0, Qt::UserRole, standardName); item->setData(0, Qt::UserRole + 1, standard); item->setData(0, Qt::UserRole + 2, QSize(profile.dar() * profile.height(), profile.height())); } else { // Cannot load movie, reject showError(i18n("The clip %1 is invalid.", transcoded)); } delete producer; slotCheckVobList(); if (m_transcodeQueue.isEmpty()) { slotCheckProfiles(); } break; } } } void DvdWizardVob::showProfileError() { m_warnMessage->setText(i18n("Your clips do not match selected DVD format, transcoding required.")); m_warnMessage->setCloseButtonVisible(false); m_warnMessage->addAction(m_transcodeAction); m_warnMessage->animatedShow(); } void DvdWizardVob::showError(const QString &error) { m_warnMessage->setText(error); m_warnMessage->setCloseButtonVisible(true); m_warnMessage->removeAction(m_transcodeAction); m_warnMessage->animatedShow(); } diff --git a/src/effects/effectstack/model/effectitemmodel.hpp b/src/effects/effectstack/model/effectitemmodel.hpp index 537ba591c..74a79f39a 100644 --- a/src/effects/effectstack/model/effectitemmodel.hpp +++ b/src/effects/effectstack/model/effectitemmodel.hpp @@ -1,68 +1,68 @@ /*************************************************************************** * 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 EFFECTITEMMODEL_H #define EFFECTITEMMODEL_H #include "abstracteffectitem.hpp" #include "abstractmodel/treeitem.hpp" #include "assets/model/assetparametermodel.hpp" #include class EffectStackModel; /* @brief This represents an effect of the effectstack */ class EffectItemModel : public AbstractEffectItem, public AssetParameterModel { public: /* This construct an effect of the given id @param is a ptr to the model this item belongs to. This is required to send update signals */ static std::shared_ptr construct(const QString &effectId, std::shared_ptr stack); /* This construct an effect with an already existing filter Only used when loading an existing clip */ static std::shared_ptr construct(Mlt::Properties *effect, std::shared_ptr stack); /* @brief This function plants the effect into the given service in last position */ void plant(const std::weak_ptr &service) override; /* @brief This function unplants (removes) the effect from the given service */ void unplant(const std::weak_ptr &service) override; Mlt::Filter &filter() const; /* @brief Return true if the effect applies only to audio */ bool isAudio() const override; - + void setCollapsed(bool collapsed); bool isCollapsed(); protected: EffectItemModel(const QList &data, Mlt::Properties *effect, const QDomElement &xml, const QString &effectId, const std::shared_ptr &stack); void updateEnable() override; }; #endif diff --git a/src/effects/effectstack/view/builtstack.cpp b/src/effects/effectstack/view/builtstack.cpp index 3384691e0..0971117be 100644 --- a/src/effects/effectstack/view/builtstack.cpp +++ b/src/effects/effectstack/view/builtstack.cpp @@ -1,64 +1,62 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "builtstack.hpp" #include "assets/assetpanel.hpp" #include "core.h" #include "effects/effectstack/model/effectstackmodel.hpp" #include "qml/colorwheelitem.h" #include #include #include BuiltStack::BuiltStack(AssetPanel *parent) : QQuickWidget(parent) , m_model(nullptr) { KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); kdeclarative.setupBindings(); qmlRegisterType("Kdenlive.Controls", 1, 0, "ColorWheelItem"); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setMinimumHeight(300); // setClearColor(palette().base().color()); setSource(QUrl(QStringLiteral("qrc:/qml/BuiltStack.qml"))); setFocusPolicy(Qt::StrongFocus); QQuickItem *root = rootObject(); QObject::connect(root, SIGNAL(valueChanged(QString, int)), parent, SLOT(parameterChanged(QString, int))); setResizeMode(QQuickWidget::SizeRootObjectToView); } -BuiltStack::~BuiltStack() -{ -} +BuiltStack::~BuiltStack() {} void BuiltStack::setModel(std::shared_ptr model, ObjectId ownerId) { m_model = model; if (ownerId.first == ObjectType::TimelineClip) { QVariant current_speed((int)(100.0 * pCore->getClipSpeed(ownerId.second))); qDebug() << " CLIP SPEED OFR: " << ownerId.second << " = " << current_speed; QMetaObject::invokeMethod(rootObject(), "setSpeed", Qt::QueuedConnection, Q_ARG(QVariant, current_speed)); } rootContext()->setContextProperty("effectstackmodel", model.get()); QMetaObject::invokeMethod(rootObject(), "resetStack", Qt::QueuedConnection); } diff --git a/src/effects/effectstack/view/collapsibleeffectview.cpp b/src/effects/effectstack/view/collapsibleeffectview.cpp index 114e2625a..ac755fa95 100644 --- a/src/effects/effectstack/view/collapsibleeffectview.cpp +++ b/src/effects/effectstack/view/collapsibleeffectview.cpp @@ -1,749 +1,749 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "collapsibleeffectview.hpp" #include "assets/view/assetparameterview.hpp" #include "core.h" #include "dialogs/clipcreationdialog.h" -#include "monitor/monitor.h" #include "effects/effectsrepository.hpp" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effectslist/effectslist.h" #include "kdenlivesettings.h" #include "mltcontroller/effectscontroller.h" +#include "monitor/monitor.h" #include "utils/KoIconUtils.h" #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CollapsibleEffectView::CollapsibleEffectView(std::shared_ptr effectModel, QSize frameSize, QImage icon, QWidget *parent) : AbstractCollapsibleWidget(parent) /* , m_effect(effect) , m_itemInfo(info) , m_original_effect(original_effect) , m_isMovable(true)*/ , m_view(nullptr) , m_model(effectModel) , m_regionEffect(false) { QString effectId = effectModel->getAssetId(); QString effectName = EffectsRepository::get()->getName(effectId); if (effectId == QLatin1String("region")) { m_regionEffect = true; decoframe->setObjectName(QStringLiteral("decoframegroup")); } filterWheelEvent = true; // decoframe->setProperty("active", true); // m_info.fromString(effect.attribute(QStringLiteral("kdenlive_info"))); // setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); buttonUp->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-up"))); QSize iconSize = buttonUp->iconSize(); buttonUp->setMaximumSize(iconSize); buttonDown->setMaximumSize(iconSize); menuButton->setMaximumSize(iconSize); enabledButton->setMaximumSize(iconSize); buttonDel->setMaximumSize(iconSize); buttonUp->setToolTip(i18n("Move effect up")); buttonDown->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-down"))); buttonDown->setToolTip(i18n("Move effect down")); buttonDel->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-deleffect"))); buttonDel->setToolTip(i18n("Delete effect")); // buttonUp->setEnabled(canMoveUp); // buttonDown->setEnabled(!lastEffect); if (effectId == QLatin1String("speed")) { // Speed effect is a "pseudo" effect, cannot be moved buttonUp->setVisible(false); buttonDown->setVisible(false); m_isMovable = false; setAcceptDrops(false); } else { setAcceptDrops(true); } // checkAll->setToolTip(i18n("Enable/Disable all effects")); // buttonShowComments->setIcon(KoIconUtils::themedIcon("help-about")); // buttonShowComments->setToolTip(i18n("Show additional information for the parameters")); m_collapse = new KDualAction(i18n("Collapse Effect"), i18n("Expand Effect"), this); m_collapse->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("arrow-right"))); m_collapse->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("arrow-down"))); collapseButton->setDefaultAction(m_collapse); connect(m_collapse, &KDualAction::activeChanged, this, &CollapsibleEffectView::slotSwitch); QHBoxLayout *l = static_cast(frame->layout()); m_colorIcon = new QLabel(this); l->insertWidget(0, m_colorIcon); m_colorIcon->setFixedSize(icon.size()); title = new QLabel(this); l->insertWidget(2, title); m_enabledButton = new KDualAction(i18n("Disable Effect"), i18n("Enable Effect"), this); m_enabledButton->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("hint"))); m_enabledButton->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("visibility"))); enabledButton->setDefaultAction(m_enabledButton); m_groupAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("folder-new")), i18n("Create Group"), this); connect(m_groupAction, &QAction::triggered, this, &CollapsibleEffectView::slotCreateGroup); if (m_regionEffect) { effectName.append(':' + QUrl(EffectsList::parameter(m_effect, QStringLiteral("resource"))).fileName()); } // Color thumb m_colorIcon->setPixmap(QPixmap::fromImage(icon)); title->setText(effectName); m_view = new AssetParameterView(this); m_view->setModel(std::static_pointer_cast(effectModel), frameSize); connect(m_view, &AssetParameterView::seekToPos, this, &AbstractCollapsibleWidget::seekToPos); connect(this, &CollapsibleEffectView::refresh, m_view, &AssetParameterView::slotRefresh); QVBoxLayout *lay = new QVBoxLayout(widgetFrame); lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(0); lay->addWidget(m_view); m_menu = new QMenu(this); if (effectModel->rowCount() > 0) { m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, SLOT(slotResetEffect())); } else { collapseButton->setEnabled(false); m_view->setVisible(false); } m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save")), i18n("Save Effect"), this, SLOT(slotSaveEffect())); if (!m_regionEffect) { /*if (m_info.groupIndex == -1) { m_menu->addAction(m_groupAction); }*/ m_menu->addAction(KoIconUtils::themedIcon(QStringLiteral("folder-new")), i18n("Create Region"), this, SLOT(slotCreateRegion())); } // setupWidget(info, metaInfo); menuButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-menu"))); menuButton->setMenu(m_menu); if (!effectModel->isEnabled()) { title->setEnabled(false); m_colorIcon->setEnabled(false); if (KdenliveSettings::disable_effect_parameters()) { widgetFrame->setEnabled(false); } m_enabledButton->setActive(true); } else { m_enabledButton->setActive(false); } connect(m_enabledButton, &KDualAction::activeChangedByUser, this, &CollapsibleEffectView::slotDisable); connect(buttonUp, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectUp); connect(buttonDown, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectDown); connect(buttonDel, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotDeleteEffect); Q_FOREACH (QSpinBox *sp, findChildren()) { sp->installEventFilter(this); sp->setFocusPolicy(Qt::StrongFocus); } Q_FOREACH (KComboBox *cb, findChildren()) { cb->installEventFilter(this); cb->setFocusPolicy(Qt::StrongFocus); } Q_FOREACH (QProgressBar *cb, findChildren()) { cb->installEventFilter(this); cb->setFocusPolicy(Qt::StrongFocus); } m_collapse->setActive(m_model->isCollapsed()); int height = m_collapse->isActive() ? frame->height() + 4 : frame->height() + m_view->contentHeight() + 4; setFixedHeight(height); } CollapsibleEffectView::~CollapsibleEffectView() { qDebug() << "deleting collapsibleeffectview"; - //delete m_view; + // delete m_view; delete m_menu; } void CollapsibleEffectView::setWidgetHeight(qreal value) { widgetFrame->setFixedHeight(m_view->contentHeight() * value); } void CollapsibleEffectView::slotCreateGroup() { emit createGroup(m_model); } void CollapsibleEffectView::slotCreateRegion() { QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' ')); const QString dialogFilter = allExtensions + QLatin1Char(' ') + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n* ") + QLatin1Char('|') + i18n("All Files"); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QPointer d = new QFileDialog(QApplication::activeWindow(), QString(), clipFolder, dialogFilter); d->setFileMode(QFileDialog::ExistingFile); if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) { KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), d->selectedUrls().first().adjusted(QUrl::RemoveFilename).toLocalFile()); emit createRegion(effectIndex(), d->selectedUrls().first()); } delete d; } void CollapsibleEffectView::slotUnGroup() { emit unGroup(this); } bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::Enter) { frame->setProperty("mouseover", true); frame->setStyleSheet(frame->styleSheet()); return QWidget::eventFilter(o, e); } if (e->type() == QEvent::Wheel) { QWheelEvent *we = static_cast(e); if (!filterWheelEvent || we->modifiers() != Qt::NoModifier) { e->accept(); return false; } if (qobject_cast(o)) { - //if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) { + // if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) { e->accept(); return false; } if (qobject_cast(o)) { if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) { e->accept(); return false; } e->ignore(); return true; } if (qobject_cast(o)) { - //if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus)*/ { + // if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus)*/ { e->accept(); return false; } } return QWidget::eventFilter(o, e); } QDomElement CollapsibleEffectView::effect() const { return m_effect; } QDomElement CollapsibleEffectView::effectForSave() const { QDomElement effect = m_effect.cloneNode().toElement(); effect.removeAttribute(QStringLiteral("kdenlive_ix")); /* if (m_paramWidget) { int in = m_paramWidget->range().x(); EffectsController::offsetKeyframes(in, effect); } */ return effect; } bool CollapsibleEffectView::isActive() const { return decoframe->property("active").toBool(); } bool CollapsibleEffectView::isEnabled() const { return m_enabledButton->isActive(); } void CollapsibleEffectView::slotActivateEffect(QModelIndex ix) { // m_colorIcon->setEnabled(active); bool active = ix.row() == m_model->row(); decoframe->setProperty("active", active); decoframe->setStyleSheet(decoframe->styleSheet()); if (active) { pCore->getMonitor(m_model->monitorId)->slotShowEffectScene(needsMonitorEffectScene()); } m_view->initKeyframeView(active); } void CollapsibleEffectView::mousePressEvent(QMouseEvent *e) { m_dragStart = e->globalPos(); emit activateEffect(m_model); QWidget::mousePressEvent(e); } void CollapsibleEffectView::mouseMoveEvent(QMouseEvent *e) { if ((e->globalPos() - m_dragStart).manhattanLength() < QApplication::startDragDistance()) { QPixmap pix = frame->grab(); emit startDrag(pix, m_model); } QWidget::mouseMoveEvent(e); } void CollapsibleEffectView::mouseDoubleClickEvent(QMouseEvent *event) { if (frame->underMouse() && collapseButton->isEnabled()) { event->accept(); m_collapse->setActive(!m_collapse->isActive()); } else { event->ignore(); } } void CollapsibleEffectView::mouseReleaseEvent(QMouseEvent *event) { m_dragStart = QPoint(); if (!decoframe->property("active").toBool()) { // emit activateEffect(effectIndex()); } QWidget::mouseReleaseEvent(event); } void CollapsibleEffectView::slotDisable(bool disable) { QString effectId = m_model->getAssetId(); QString effectName = EffectsRepository::get()->getName(effectId); std::static_pointer_cast(m_model)->markEnabled(effectName, !disable); } void CollapsibleEffectView::slotDeleteEffect() { emit deleteEffect(m_model); } void CollapsibleEffectView::slotEffectUp() { emit moveEffect(qMax(0, m_model->row() - 1), m_model); } void CollapsibleEffectView::slotEffectDown() { emit moveEffect(m_model->row() + 2, m_model); } void CollapsibleEffectView::slotSaveEffect() { QString name = QInputDialog::getText(this, i18n("Save Effect"), i18n("Name for saved effect: ")); if (name.trimmed().isEmpty()) { return; } QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } if (dir.exists(name + QStringLiteral(".xml"))) if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", name + QStringLiteral(".xml"))) == KMessageBox::No) { return; } QDomDocument doc; QDomElement effect = m_effect.cloneNode().toElement(); doc.appendChild(doc.importNode(effect, true)); effect = doc.firstChild().toElement(); effect.removeAttribute(QStringLiteral("kdenlive_ix")); effect.setAttribute(QStringLiteral("id"), name); effect.setAttribute(QStringLiteral("type"), QStringLiteral("custom")); /* if (m_paramWidget) { int in = m_paramWidget->range().x(); EffectsController::offsetKeyframes(in, effect); } */ QDomElement effectname = effect.firstChildElement(QStringLiteral("name")); effect.removeChild(effectname); effectname = doc.createElement(QStringLiteral("name")); QDomText nametext = doc.createTextNode(name); effectname.appendChild(nametext); effect.insertBefore(effectname, QDomNode()); QDomElement effectprops = effect.firstChildElement(QStringLiteral("properties")); effectprops.setAttribute(QStringLiteral("id"), name); effectprops.setAttribute(QStringLiteral("type"), QStringLiteral("custom")); QFile file(dir.absoluteFilePath(name + QStringLiteral(".xml"))); if (file.open(QFile::WriteOnly | QFile::Truncate)) { QTextStream out(&file); out << doc.toString(); } file.close(); emit reloadEffects(); } void CollapsibleEffectView::slotResetEffect() { m_view->resetValues(); } void CollapsibleEffectView::slotSwitch(bool collapse) { int height = collapse ? frame->height() + 4 : frame->height() + m_view->contentHeight() + 4; widgetFrame->setVisible(!collapse); setFixedHeight(height); emit switchHeight(m_model, height); m_model->setCollapsed(collapse); } void CollapsibleEffectView::animationChanged(const QVariant &geom) { parentWidget()->setFixedHeight(geom.toRect().height()); } void CollapsibleEffectView::animationFinished() { if (m_collapse->isActive()) { widgetFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored); } else { widgetFrame->setFixedHeight(m_view->contentHeight()); } } void CollapsibleEffectView::setGroupIndex(int ix) { /*if (m_info.groupIndex == -1 && ix != -1) { m_menu->removeAction(m_groupAction); } else if (m_info.groupIndex != -1 && ix == -1) { m_menu->addAction(m_groupAction); } m_info.groupIndex = ix; m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());*/ } void CollapsibleEffectView::setGroupName(const QString &groupName) { /*m_info.groupName = groupName; m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString());*/ } QString CollapsibleEffectView::infoString() const { - return QString(); //m_info.toString(); + return QString(); // m_info.toString(); } void CollapsibleEffectView::removeFromGroup() { /*if (m_info.groupIndex != -1) { m_menu->addAction(m_groupAction); } m_info.groupIndex = -1; m_info.groupName.clear(); m_effect.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString()); emit parameterChanged(m_original_effect, m_effect, effectIndex());*/ } int CollapsibleEffectView::groupIndex() const { - return -1;//m_info.groupIndex; + return -1; // m_info.groupIndex; } int CollapsibleEffectView::effectIndex() const { if (m_effect.isNull()) { return -1; } return m_effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); } void CollapsibleEffectView::updateWidget(const ItemInfo &info, const QDomElement &effect) { // cleanup /* delete m_paramWidget; m_paramWidget = nullptr; */ m_effect = effect; setupWidget(info); } void CollapsibleEffectView::updateFrameInfo() { /* if (m_paramWidget) { m_paramWidget->refreshFrameInfo(); } */ } void CollapsibleEffectView::setActiveKeyframe(int kf) { Q_UNUSED(kf) /* if (m_paramWidget) { m_paramWidget->setActiveKeyframe(kf); } */ } void CollapsibleEffectView::setupWidget(const ItemInfo &info) { Q_UNUSED(info) /* if (m_effect.isNull()) { // //qCDebug(KDENLIVE_LOG) << "// EMPTY EFFECT STACK"; return; } delete m_paramWidget; m_paramWidget = nullptr; if (m_effect.attribute(QStringLiteral("tag")) == QLatin1String("region")) { m_regionEffect = true; QDomNodeList effects = m_effect.elementsByTagName(QStringLiteral("effect")); QDomNodeList origin_effects = m_original_effect.elementsByTagName(QStringLiteral("effect")); m_paramWidget = new ParameterContainer(m_effect, info, metaInfo, widgetFrame); QWidget *container = new QWidget(widgetFrame); QVBoxLayout *vbox = static_cast(widgetFrame->layout()); vbox->addWidget(container); // m_paramWidget = new ParameterContainer(m_effect.toElement(), info, metaInfo, container); for (int i = 0; i < effects.count(); ++i) { bool canMoveUp = true; if (i == 0 || effects.at(i - 1).toElement().attribute(QStringLiteral("id")) == QLatin1String("speed")) { canMoveUp = false; } CollapsibleEffectView *coll = new CollapsibleEffectView(effects.at(i).toElement(), origin_effects.at(i).toElement(), info, metaInfo, canMoveUp, i == effects.count() - 1, container); m_subParamWidgets.append(coll); connect(coll, &CollapsibleEffectView::parameterChanged, this, &CollapsibleEffectView::slotUpdateRegionEffectParams); // container = new QWidget(widgetFrame); vbox->addWidget(coll); // p = new ParameterContainer(effects.at(i).toElement(), info, isEffect, container); } } else { m_paramWidget = new ParameterContainer(m_effect, info, metaInfo, widgetFrame); connect(m_paramWidget, &ParameterContainer::disableCurrentFilter, this, &CollapsibleEffectView::slotDisable); connect(m_paramWidget, &ParameterContainer::importKeyframes, this, &CollapsibleEffectView::importKeyframes); if (m_effect.firstChildElement(QStringLiteral("parameter")).isNull()) { // Effect has no parameter, don't allow expand collapseButton->setEnabled(false); collapseButton->setVisible(false); widgetFrame->setVisible(false); } } if (collapseButton->isEnabled() && m_info.isCollapsed) { widgetFrame->setVisible(false); collapseButton->setArrowType(Qt::RightArrow); } connect(m_paramWidget, &ParameterContainer::parameterChanged, this, &CollapsibleEffectView::parameterChanged); connect(m_paramWidget, &ParameterContainer::startFilterJob, this, &CollapsibleEffectView::startFilterJob); connect(this, &CollapsibleEffectView::syncEffectsPos, m_paramWidget, &ParameterContainer::syncEffectsPos); connect(m_paramWidget, &ParameterContainer::checkMonitorPosition, this, &CollapsibleEffectView::checkMonitorPosition); connect(m_paramWidget, &ParameterContainer::seekTimeline, this, &CollapsibleEffectView::seekTimeline); connect(m_paramWidget, &ParameterContainer::importClipKeyframes, this, &CollapsibleEffectView::prepareImportClipKeyframes); */ } bool CollapsibleEffectView::isGroup() const { return false; } void CollapsibleEffectView::updateTimecodeFormat() { /* m_paramWidget->updateTimecodeFormat(); if (!m_subParamWidgets.isEmpty()) { // we have a group for (int i = 0; i < m_subParamWidgets.count(); ++i) { m_subParamWidgets.at(i)->updateTimecodeFormat(); } } */ } void CollapsibleEffectView::slotUpdateRegionEffectParams(const QDomElement & /*old*/, const QDomElement & /*e*/, int /*ix*/) { // qCDebug(KDENLIVE_LOG)<<"// EMIT CHANGE SUBEFFECT.....:"; emit parameterChanged(m_original_effect, m_effect, effectIndex()); } void CollapsibleEffectView::slotSyncEffectsPos(int pos) { emit syncEffectsPos(pos); } void CollapsibleEffectView::dragEnterEvent(QDragEnterEvent *event) { Q_UNUSED(event) /* if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effectslist"))) { frame->setProperty("target", true); frame->setStyleSheet(frame->styleSheet()); event->acceptProposedAction(); } else if (m_paramWidget->doesAcceptDrops() && event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry")) && event->source()->objectName() != QStringLiteral("ParameterContainer")) { event->setDropAction(Qt::CopyAction); event->setAccepted(true); } else { QWidget::dragEnterEvent(event); } */ } void CollapsibleEffectView::dragLeaveEvent(QDragLeaveEvent * /*event*/) { frame->setProperty("target", false); frame->setStyleSheet(frame->styleSheet()); } void CollapsibleEffectView::importKeyframes(const QString &kf) { QMap keyframes; if (kf.contains(QLatin1Char('\n'))) { const QStringList params = kf.split(QLatin1Char('\n'), QString::SkipEmptyParts); for (const QString ¶m : params) { keyframes.insert(param.section(QLatin1Char('='), 0, 0), param.section(QLatin1Char('='), 1)); } } else { keyframes.insert(kf.section(QLatin1Char('='), 0, 0), kf.section(QLatin1Char('='), 1)); } emit importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), keyframes); } void CollapsibleEffectView::dropEvent(QDropEvent *event) { if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/geometry"))) { if (event->source()->objectName() == QStringLiteral("ParameterContainer")) { return; } // emit activateEffect(effectIndex()); QString itemData = event->mimeData()->data(QStringLiteral("kdenlive/geometry")); importKeyframes(itemData); return; } frame->setProperty("target", false); frame->setStyleSheet(frame->styleSheet()); const QString effects = QString::fromUtf8(event->mimeData()->data(QStringLiteral("kdenlive/effectslist"))); // event->acceptProposedAction(); QDomDocument doc; doc.setContent(effects, true); QDomElement e = doc.documentElement(); int ix = e.attribute(QStringLiteral("kdenlive_ix")).toInt(); int currentEffectIx = effectIndex(); if (ix == currentEffectIx || e.attribute(QStringLiteral("id")) == QLatin1String("speed")) { // effect dropped on itself, or unmovable speed dropped, reject event->ignore(); return; } if (ix == 0 || e.tagName() == QLatin1String("effectgroup")) { if (e.tagName() == QLatin1String("effectgroup")) { // moving a group QDomNodeList subeffects = e.elementsByTagName(QStringLiteral("effect")); if (subeffects.isEmpty()) { event->ignore(); return; } event->setDropAction(Qt::MoveAction); event->accept(); /* EffectInfo info; info.fromString(subeffects.at(0).toElement().attribute(QStringLiteral("kdenlive_info"))); if (info.groupIndex >= 0) { // Moving group QList effectsIds; // Collect moved effects ids for (int i = 0; i < subeffects.count(); ++i) { QDomElement effect = subeffects.at(i).toElement(); effectsIds << effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); } // emit moveEffect(effectsIds, currentEffectIx, info.groupIndex, info.groupName); } else { // group effect dropped from effect list if (m_info.groupIndex > -1) { // TODO: Should we merge groups?? } emit addEffect(e); }*/ emit addEffect(e); return; } // effect dropped from effects list, add it e.setAttribute(QStringLiteral("kdenlive_ix"), ix); /*if (m_info.groupIndex > -1) { // Dropped on a group e.setAttribute(QStringLiteral("kdenlive_info"), m_info.toString()); }*/ event->setDropAction(Qt::CopyAction); event->accept(); emit addEffect(e); return; } // emit moveEffect(QList() << ix, currentEffectIx, m_info.groupIndex, m_info.groupName); event->setDropAction(Qt::MoveAction); event->accept(); } void CollapsibleEffectView::adjustButtons(int ix, int max) { buttonUp->setEnabled(ix > 0); buttonDown->setEnabled(ix < max - 1); } MonitorSceneType CollapsibleEffectView::needsMonitorEffectScene() const { if (!m_model->isEnabled() || !m_view) { return MonitorSceneDefault; } return m_view->needsMonitorEffectScene(); } void CollapsibleEffectView::setKeyframes(const QString &tag, const QString &keyframes) { Q_UNUSED(tag) Q_UNUSED(keyframes) /* m_paramWidget->setKeyframes(tag, keyframes); */ } bool CollapsibleEffectView::isMovable() const { return m_isMovable; } void CollapsibleEffectView::prepareImportClipKeyframes() { emit importClipKeyframes(AVWidget, m_itemInfo, m_effect.cloneNode().toElement(), QMap()); } diff --git a/src/effects/effectstack/view/collapsibleeffectview.hpp b/src/effects/effectstack/view/collapsibleeffectview.hpp index f3cf48d6e..906a1dfe6 100644 --- a/src/effects/effectstack/view/collapsibleeffectview.hpp +++ b/src/effects/effectstack/view/collapsibleeffectview.hpp @@ -1,167 +1,165 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef COLLAPSIBLEEFFECTVIEW_H #define COLLAPSIBLEEFFECTVIEW_H #include "abstractcollapsiblewidget.h" #include "mltcontroller/effectscontroller.h" #include "timecode.h" #include #include class QLabel; class KDualAction; class EffectItemModel; class AssetParameterView; /**) * @class CollapsibleEffectView * @brief A container for the parameters of an effect * @author Jean-Baptiste Mardelle */ class CollapsibleEffectView : public AbstractCollapsibleWidget { Q_OBJECT public: - - explicit CollapsibleEffectView(std::shared_ptr effectModel, QSize frameSize, QImage icon, - QWidget *parent = nullptr); + explicit CollapsibleEffectView(std::shared_ptr effectModel, QSize frameSize, QImage icon, QWidget *parent = nullptr); ~CollapsibleEffectView(); QLabel *title; void setupWidget(const ItemInfo &info); void updateTimecodeFormat(); /** @brief Install event filter so that scrolling with mouse wheel does not change parameter value. */ bool eventFilter(QObject *o, QEvent *e) override; /** @brief Update effect GUI to reflect parameted changes. */ void updateWidget(const ItemInfo &info, const QDomElement &effect); /** @brief Returns effect xml. */ QDomElement effect() const; /** @brief Returns effect xml with keyframe offset for saving. */ QDomElement effectForSave() const; int groupIndex() const; bool isGroup() const override; int effectIndex() const; void setGroupIndex(int ix); void setGroupName(const QString &groupName); /** @brief Remove this effect from its group. */ void removeFromGroup(); QString infoString() const; bool isActive() const; bool isEnabled() const; /** @brief Should the wheel event be sent to parent widget for scrolling. */ bool filterWheelEvent; /** @brief Show / hide up / down buttons. */ void adjustButtons(int ix, int max); /** @brief Returns this effect's monitor scene type if any is needed. */ MonitorSceneType needsMonitorEffectScene() const; /** @brief Import keyframes from a clip's data. */ void setKeyframes(const QString &tag, const QString &keyframes); /** @brief Pass frame size info (dar, etc). */ void updateFrameInfo(); /** @brief Select active keyframe. */ void setActiveKeyframe(int kf); /** @brief Returns true if effect can be moved (false for speed effect). */ bool isMovable() const; public slots: void slotSyncEffectsPos(int pos); void slotDisable(bool disable); void slotResetEffect(); void importKeyframes(const QString &keyframes); void slotActivateEffect(QModelIndex ix); private slots: void setWidgetHeight(qreal value); void animationFinished(); private slots: void slotSwitch(bool expand); void slotDeleteEffect(); void slotEffectUp(); void slotEffectDown(); void slotSaveEffect(); void slotCreateGroup(); void slotCreateRegion(); void slotUnGroup(); /** @brief A sub effect parameter was changed */ void slotUpdateRegionEffectParams(const QDomElement & /*old*/, const QDomElement & /*e*/, int /*ix*/); void prepareImportClipKeyframes(); void animationChanged(const QVariant &geom); private: AssetParameterView *m_view; std::shared_ptr m_model; KDualAction *m_collapse; QList m_subParamWidgets; QDomElement m_effect; ItemInfo m_itemInfo; QDomElement m_original_effect; QList m_subEffects; QMenu *m_menu; bool m_isMovable; /** @brief True if this is a region effect, which behaves in a special way, like a group. */ bool m_regionEffect; /** @brief The add group action. */ QAction *m_groupAction; KDualAction *m_enabledButton; QLabel *m_colorIcon; QPixmap m_iconPix; QPoint m_dragStart; protected: void mouseDoubleClickEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *e) override; void mouseReleaseEvent(QMouseEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; signals: void parameterChanged(const QDomElement &, const QDomElement &, int); void syncEffectsPos(int); void effectStateChanged(bool, int ix, MonitorSceneType effectNeedsMonitorScene); void deleteEffect(std::shared_ptr effect); void moveEffect(int destRow, std::shared_ptr effect); void checkMonitorPosition(int); void seekTimeline(int); /** @brief Start an MLT filter job on this clip. */ void startFilterJob(QMap &, QMap &, QMap &); /** @brief An effect was reset, trigger param reload. */ void resetEffect(int ix); /** @brief Ask for creation of a group. */ void createGroup(std::shared_ptr effectModel); void unGroup(CollapsibleEffectView *); void createRegion(int, const QUrl &); void deleteGroup(const QDomDocument &); void importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, const QMap &keyframes = QMap()); void switchHeight(std::shared_ptr model, int height); void startDrag(QPixmap, std::shared_ptr effectModel); void activateEffect(std::shared_ptr effectModel); void refresh(); }; #endif diff --git a/src/effects/effectstack/view/effectstackview.cpp b/src/effects/effectstack/view/effectstackview.cpp index 3122a80b4..2f2124fa2 100644 --- a/src/effects/effectstack/view/effectstackview.cpp +++ b/src/effects/effectstack/view/effectstackview.cpp @@ -1,335 +1,335 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "effectstackview.hpp" #include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include "assets/assetpanel.hpp" #include "assets/view/assetparameterview.hpp" #include "builtstack.hpp" #include "collapsibleeffectview.hpp" #include "core.h" -#include "monitor/monitor.h" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "kdenlivesettings.h" +#include "monitor/monitor.h" #include #include #include #include #include #include #include WidgetDelegate::WidgetDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize WidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize s = QStyledItemDelegate::sizeHint(option, index); if (m_height.contains(index)) { s.setHeight(m_height.value(index)); } return s; } void WidgetDelegate::setHeight(const QModelIndex &index, int height) { m_height[index] = height; emit sizeHintChanged(index); } void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt(option); initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); } EffectStackView::EffectStackView(AssetPanel *parent) : QWidget(parent) , m_model(nullptr) , m_thumbnailer(new AssetIconProvider(true)) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(0, 0, 0, 0); m_lay->setSpacing(0); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setAcceptDrops(true); /*m_builtStack = new BuiltStack(parent); m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_lay->addWidget(m_builtStack); m_builtStack->setVisible(KdenliveSettings::showbuiltstack());*/ m_effectsTree = new QTreeView(this); m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_effectsTree->setHeaderHidden(true); m_effectsTree->setRootIsDecorated(false); QString style = QStringLiteral("QTreeView {border: none;}"); // m_effectsTree->viewport()->setAutoFillBackground(false); m_effectsTree->setStyleSheet(style); m_effectsTree->setVisible(!KdenliveSettings::showbuiltstack()); m_lay->addWidget(m_effectsTree); m_lay->setStretch(1, 10); } EffectStackView::~EffectStackView() { delete m_thumbnailer; } void EffectStackView::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } else { event->setDropAction(Qt::CopyAction); } event->setAccepted(true); } else { event->setAccepted(false); } } void EffectStackView::dropEvent(QDropEvent *event) { event->accept(); QString effectId = event->mimeData()->data(QStringLiteral("kdenlive/effect")); int row = m_model->rowCount(); for (int i = 0; i < m_model->rowCount(); i++) { auto item = m_model->getEffectStackRow(i); if (item->childCount() > 0) { // TODO: group continue; } std::shared_ptr eff = std::static_pointer_cast(item); QModelIndex ix = m_model->getIndexFromItem(eff); QWidget *w = m_effectsTree->indexWidget(ix); if (w && w->geometry().contains(event->pos())) { qDebug() << "// DROPPED ON EFF: " << eff->getAssetId(); row = i; break; } } if (event->source() == this) { QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource")); int oldRow = sourceData.section(QLatin1Char('-'), 2, 2).toInt(); qDebug() << "// MOVING EFFECT FROM : " << oldRow << " TO " << row; if (row == oldRow || (row == m_model->rowCount() && oldRow == row - 1)) { return; } m_model->moveEffect(row, m_model->getEffectStackRow(oldRow)); } else { if (row < m_model->rowCount()) { m_model->appendEffect(effectId); m_model->moveEffect(row, m_model->getEffectStackRow(m_model->rowCount() - 1)); } else { m_model->appendEffect(effectId); std::shared_ptr item = m_model->getEffectStackRow(m_model->rowCount() - 1); if (item) { slotActivateEffect(std::static_pointer_cast(item)); } } } } void EffectStackView::setModel(std::shared_ptr model, const QSize frameSize) { qDebug() << "MUTEX LOCK!!!!!!!!!!!! setmodel"; m_mutex.lock(); unsetModel(); m_model = model; m_sourceFrameSize = frameSize; m_effectsTree->setModel(m_model.get()); m_effectsTree->setItemDelegateForColumn(0, new WidgetDelegate(this)); m_effectsTree->setColumnHidden(1, true); m_effectsTree->setAcceptDrops(true); m_effectsTree->setDragDropMode(QAbstractItemView::DragDrop); m_effectsTree->setDragEnabled(true); m_effectsTree->setUniformRowHeights(false); m_mutex.unlock(); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! setmodel"; loadEffects(); connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); - //m_builtStack->setModel(model, stackOwner()); + // m_builtStack->setModel(model, stackOwner()); } void EffectStackView::loadEffects() { qDebug() << "MUTEX LOCK!!!!!!!!!!!! loadEffects: "; QMutexLocker lock(&m_mutex); int max = m_model->rowCount(); if (max == 0) { // blank stack ObjectId item = m_model->getOwnerId(); pCore->getMonitor(item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)->slotShowEffectScene(MonitorSceneDefault); return; } int active = qBound(0, m_model->getActiveEffect(), max - 1); std::shared_ptr activeItem = m_model->getEffectStackRow(active); std::shared_ptr activeModel = std::static_pointer_cast(activeItem); for (int i = 0; i < max; i++) { std::shared_ptr item = m_model->getEffectStackRow(i); QSize size; if (item->childCount() > 0) { // group, create sub stack continue; } std::shared_ptr effectModel = std::static_pointer_cast(item); CollapsibleEffectView *view = nullptr; if (i >= 0 && i <= max) { // We need to rebuild the effect view QImage effectIcon = m_thumbnailer->requestImage(effectModel->getAssetId(), &size, QSize(QStyle::PM_SmallIconSize, QStyle::PM_SmallIconSize)); view = new CollapsibleEffectView(effectModel, m_sourceFrameSize, effectIcon, this); connect(view, &CollapsibleEffectView::deleteEffect, m_model.get(), &EffectStackModel::removeEffect); connect(view, &CollapsibleEffectView::moveEffect, m_model.get(), &EffectStackModel::moveEffect); connect(view, &CollapsibleEffectView::switchHeight, this, &EffectStackView::slotAdjustDelegate, Qt::DirectConnection); connect(view, &CollapsibleEffectView::startDrag, this, &EffectStackView::slotStartDrag); connect(view, &CollapsibleEffectView::createGroup, m_model.get(), &EffectStackModel::slotCreateGroup); connect(view, &CollapsibleEffectView::activateEffect, this, &EffectStackView::slotActivateEffect); connect(view, &CollapsibleEffectView::seekToPos, [this](int pos) { // at this point, the effects returns a pos relative to the clip. We need to convert it to a global time int clipIn = pCore->getItemPosition(m_model->getOwnerId()); emit seekToPos(pos + clipIn); }); connect(this, &EffectStackView::doActivateEffect, view, &CollapsibleEffectView::slotActivateEffect); QModelIndex ix = m_model->getIndexFromItem(effectModel); m_effectsTree->setIndexWidget(ix, view); WidgetDelegate *del = static_cast(m_effectsTree->itemDelegate(ix)); del->setHeight(ix, view->height()); } else { QModelIndex ix = m_model->getIndexFromItem(effectModel); auto w = m_effectsTree->indexWidget(ix); view = static_cast(w); } view->slotActivateEffect(m_model->getIndexFromItem(activeModel)); view->buttonUp->setEnabled(i > 0); view->buttonDown->setEnabled(i < max - 1); } updateTreeHeight(); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! loadEffects"; } void EffectStackView::updateTreeHeight() { // For some reason, the treeview height does not update correctly, so enforce it int totalHeight = 0; for (int j = 0; j < m_model->rowCount(); j++) { std::shared_ptr item2 = m_model->getEffectStackRow(j); std::shared_ptr eff = std::static_pointer_cast(item2); QModelIndex idx = m_model->getIndexFromItem(eff); auto w = m_effectsTree->indexWidget(idx); totalHeight += w->height(); } setMinimumHeight(totalHeight); } void EffectStackView::slotActivateEffect(std::shared_ptr effectModel) { qDebug() << "MUTEX LOCK!!!!!!!!!!!! slotactivateeffect"; QMutexLocker lock(&m_mutex); m_model->setActiveEffect(effectModel->row()); QModelIndex activeIx = m_model->getIndexFromItem(effectModel); emit doActivateEffect(activeIx); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! slotactivateeffect"; } void EffectStackView::slotStartDrag(QPixmap pix, std::shared_ptr effectModel) { auto *drag = new QDrag(this); drag->setPixmap(pix); auto *mime = new QMimeData; mime->setData(QStringLiteral("kdenlive/effect"), effectModel->getAssetId().toUtf8()); // TODO this will break if source effect is not on the stack of a timeline clip QByteArray effectSource; effectSource += QString::number((int)effectModel->getOwnerId().first).toUtf8(); effectSource += '-'; effectSource += QString::number((int)effectModel->getOwnerId().second).toUtf8(); effectSource += '-'; effectSource += QString::number(effectModel->row()).toUtf8(); mime->setData(QStringLiteral("kdenlive/effectsource"), effectSource); // mime->setData(QStringLiteral("kdenlive/effectrow"), QString::number(effectModel->row()).toUtf8()); // Assign ownership of the QMimeData object to the QDrag object. drag->setMimeData(mime); // Start the drag and drop operation drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction); } void EffectStackView::slotAdjustDelegate(std::shared_ptr effectModel, int height) { - qDebug() << "MUTEX LOCK!!!!!!!!!!!! adjustdelegate: "<getIndexFromItem(effectModel); WidgetDelegate *del = static_cast(m_effectsTree->itemDelegate(ix)); del->setHeight(ix, height); updateTreeHeight(); qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! adjustdelegate"; } void EffectStackView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_UNUSED(roles) if (!topLeft.isValid() || !bottomRight.isValid()) { loadEffects(); return; } - for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { - for (int j = topLeft.column(); j <= bottomRight.column(); ++j) { + for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { + for (int j = topLeft.column(); j <= bottomRight.column(); ++j) { CollapsibleEffectView *w = static_cast(m_effectsTree->indexWidget(m_model->index(i, j, topLeft.parent()))); if (w) { w->refresh(); } } } } void EffectStackView::unsetModel(bool reset) { // Release ownership of smart pointer if (m_model) { disconnect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh); } if (reset) { m_model.reset(); } } ObjectId EffectStackView::stackOwner() const { if (m_model) { return m_model->getOwnerId(); } return ObjectId(ObjectType::NoItem, -1); } /* void EffectStackView::switchBuiltStack(bool show) { m_builtStack->setVisible(show); m_effectsTree->setVisible(!show); KdenliveSettings::setShowbuiltstack(show); } */ diff --git a/src/effects/effectstack/view/effectstackview.hpp b/src/effects/effectstack/view/effectstackview.hpp index 4cef3cf29..23f818e41 100644 --- a/src/effects/effectstack/view/effectstackview.hpp +++ b/src/effects/effectstack/view/effectstackview.hpp @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #ifndef EFFECTSTACKVIEW_H #define EFFECTSTACKVIEW_H #include "definitions.h" #include #include #include #include class QVBoxLayout; class QTreeView; class CollapsibleEffectView; class AssetParameterModel; class EffectStackModel; class EffectItemModel; class AssetIconProvider; class BuiltStack; class AssetPanel; class WidgetDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit WidgetDelegate(QObject *parent = nullptr); void setHeight(const QModelIndex &index, int height); QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; private: QMap m_height; }; class EffectStackView : public QWidget { Q_OBJECT public: EffectStackView(AssetPanel *parent); virtual ~EffectStackView(); void setModel(std::shared_ptr model, const QSize frameSize); void unsetModel(bool reset = true); ObjectId stackOwner() const; protected: void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; private: QMutex m_mutex; QVBoxLayout *m_lay; - //BuiltStack *m_builtStack; + // BuiltStack *m_builtStack; QTreeView *m_effectsTree; std::shared_ptr m_model; std::vector m_widgets; AssetIconProvider *m_thumbnailer; /** @brief the frame size of the original clip this effect is applied on - */ + */ QSize m_sourceFrameSize; const QString getStyleSheet(); void updateTreeHeight(); private slots: void refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); void slotAdjustDelegate(std::shared_ptr effectModel, int height); void slotStartDrag(QPixmap pix, std::shared_ptr effectModel); void slotActivateEffect(std::shared_ptr effectModel); void loadEffects(); -// void switchBuiltStack(bool show); + // void switchBuiltStack(bool show); signals: void doActivateEffect(QModelIndex); void seekToPos(int); }; #endif diff --git a/src/effects/effectstack/view/qml/colorwheelitem.cpp b/src/effects/effectstack/view/qml/colorwheelitem.cpp index 57431999b..725e673b1 100644 --- a/src/effects/effectstack/view/qml/colorwheelitem.cpp +++ b/src/effects/effectstack/view/qml/colorwheelitem.cpp @@ -1,334 +1,334 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * Author: Brian Matherly * Author: Jean-Baptiste Mardelle small adaptations for Kdenlive * Some ideas came from Qt-Plus: https://github.com/liuyanghejerry/Qt-Plus * and Steinar Gunderson's Movit demo app. * * 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 3 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, see . */ #include "colorwheelitem.h" #include #include #include #include #include static const qreal WHEEL_SLIDER_RATIO = 10.0; NegQColor NegQColor::fromHsvF(qreal h, qreal s, qreal l, qreal a) { NegQColor color; color.qcolor = QColor::fromHsvF(h, s, l < 0 ? -l : l, a); color.sign_r = l < 0 ? -1 : 1; color.sign_g = l < 0 ? -1 : 1; color.sign_b = l < 0 ? -1 : 1; return color; } NegQColor NegQColor::fromRgbF(qreal r, qreal g, qreal b, qreal a) { NegQColor color; color.qcolor = QColor::fromRgbF(r < 0 ? -r : r, g < 0 ? -g : g, b < 0 ? -b : b, a); color.sign_r = r < 0 ? -1 : 1; color.sign_g = g < 0 ? -1 : 1; color.sign_b = b < 0 ? -1 : 1; return color; } qreal NegQColor::redF() { return qcolor.redF() * sign_r; } qreal NegQColor::greenF() { return qcolor.greenF() * sign_g; } qreal NegQColor::blueF() { return qcolor.blueF() * sign_b; } qreal NegQColor::valueF() { return qcolor.valueF() * sign_g; } int NegQColor::hue() { return qcolor.hue(); } qreal NegQColor::hueF() { return qcolor.hueF(); } qreal NegQColor::saturationF() { return qcolor.saturationF(); } ColorWheelItem::ColorWheelItem(QQuickItem *parent) : QQuickPaintedItem(parent) , m_image() , m_lastPoint(0, 0) , m_size(0, 0) , m_margin(5) , m_color(NegQColor()) , m_isInWheel(false) , m_isInSquare(false) , m_sizeFactor(1) , m_defaultValue(1) , m_zeroShift(0) { setAcceptedMouseButtons(Qt::LeftButton | Qt::MidButton); setAcceptHoverEvents(true); } void ColorWheelItem::setFactorDefaultZero(qreal factor, qreal defvalue, qreal zero) { m_sizeFactor = factor; m_defaultValue = defvalue; m_zeroShift = zero; } QColor ColorWheelItem::color() { return QColor(m_color.redF() * m_sizeFactor, m_color.greenF() * m_sizeFactor, m_color.blueF() * m_sizeFactor); } void ColorWheelItem::setColor(double r, double g, double b) { m_color = NegQColor::fromRgbF(r, g, b); update(); } void ColorWheelItem::setColor(const NegQColor &color) { m_color = color; update(); emit colorChanged(); } double ColorWheelItem::red() { return m_color.redF() * m_sizeFactor; } double ColorWheelItem::green() { return m_color.greenF() * m_sizeFactor; } double ColorWheelItem::blue() { return m_color.blueF() * m_sizeFactor; } int ColorWheelItem::wheelSize() const { qreal ws = (qreal)width() / (1.0 + 1.0 / WHEEL_SLIDER_RATIO); return qMin(ws, height()); } NegQColor ColorWheelItem::colorForPoint(const QPoint &point) { if (!m_image.valid(point)) return NegQColor(); if (m_isInWheel) { qreal w = wheelSize(); qreal xf = qreal(point.x()) / w; qreal yf = 1.0 - qreal(point.y()) / w; qreal xp = 2.0 * xf - 1.0; qreal yp = 2.0 * yf - 1.0; qreal rad = qMin(hypot(xp, yp), 1.0); qreal theta = qAtan2(yp, xp); theta -= 105.0 / 360.0 * 2.0 * M_PI; if (theta < 0.0) theta += 2.0 * M_PI; qreal hue = (theta * 180.0 / M_PI) / 360.0; return NegQColor::fromHsvF(hue, rad, m_color.valueF()); } if (m_isInSquare) { qreal value = 1.0 - qreal(point.y() - m_margin) / (wheelSize() - m_margin * 2); - if (qAbs(m_zeroShift)>0) { + if (qAbs(m_zeroShift) > 0) { value = value - m_zeroShift; } return NegQColor::fromHsvF(m_color.hueF(), m_color.saturationF(), value); } return NegQColor(); } void ColorWheelItem::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_lastPoint = event->pos(); event->accept(); if (m_wheelRegion.contains(m_lastPoint)) { m_isInWheel = true; m_isInSquare = false; NegQColor color = colorForPoint(m_lastPoint); setColor(color); } else if (m_sliderRegion.contains(m_lastPoint)) { m_isInWheel = false; m_isInSquare = true; NegQColor color = colorForPoint(m_lastPoint); setColor(color); } } else if (event->button() == Qt::MidButton) { NegQColor color = NegQColor::fromRgbF(m_defaultValue / m_sizeFactor, m_defaultValue / m_sizeFactor, m_defaultValue / m_sizeFactor); setColor(color); event->accept(); } else { event->ignore(); } } void ColorWheelItem::mouseMoveEvent(QMouseEvent *event) { updateCursor(event->pos()); if (event->buttons() == Qt::NoButton) return; m_lastPoint = event->pos(); if (m_isInWheel) { NegQColor color = colorForPoint(m_lastPoint); setColor(color); } else if (m_isInSquare) { NegQColor color = colorForPoint(m_lastPoint); setColor(color); } event->accept(); } void ColorWheelItem::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_isInWheel = false; m_isInSquare = false; } event->accept(); } void ColorWheelItem::hoverMoveEvent(QHoverEvent *event) { updateCursor(event->pos()); } void ColorWheelItem::paint(QPainter *painter) { QSize size(width(), height()); if (m_size != size) { m_image = QImage(QSize(width(), height()), QImage::Format_ARGB32_Premultiplied); m_image.fill(qRgba(0, 0, 0, 0)); drawWheel(); drawSlider(); m_size = size; } painter->setRenderHint(QPainter::Antialiasing); painter->drawImage(0, 0, m_image); drawWheelDot(*painter); drawSliderBar(*painter); } void ColorWheelItem::drawWheel() { int r = wheelSize(); QPainter painter(&m_image); painter.setRenderHint(QPainter::Antialiasing); m_image.fill(0); // transparent QConicalGradient conicalGradient; conicalGradient.setColorAt(0.0, Qt::red); conicalGradient.setColorAt(60.0 / 360.0, Qt::yellow); conicalGradient.setColorAt(135.0 / 360.0, Qt::green); conicalGradient.setColorAt(180.0 / 360.0, Qt::cyan); conicalGradient.setColorAt(240.0 / 360.0, Qt::blue); conicalGradient.setColorAt(315.0 / 360.0, Qt::magenta); conicalGradient.setColorAt(1.0, Qt::red); QRadialGradient radialGradient(0.0, 0.0, r / 2); radialGradient.setColorAt(0.0, Qt::white); radialGradient.setColorAt(1.0, Qt::transparent); painter.translate(r / 2, r / 2); painter.rotate(-105); QBrush hueBrush(conicalGradient); painter.setPen(Qt::NoPen); painter.setBrush(hueBrush); painter.drawEllipse(QPoint(0, 0), r / 2 - m_margin, r / 2 - m_margin); QBrush saturationBrush(radialGradient); painter.setBrush(saturationBrush); painter.drawEllipse(QPoint(0, 0), r / 2 - m_margin, r / 2 - m_margin); m_wheelRegion = QRegion(r / 2, r / 2, r - 2 * m_margin, r - 2 * m_margin, QRegion::Ellipse); m_wheelRegion.translate(-(r - 2 * m_margin) / 2, -(r - 2 * m_margin) / 2); } void ColorWheelItem::drawWheelDot(QPainter &painter) { int r = wheelSize() / 2; QPen pen(Qt::white); pen.setWidth(2); painter.setPen(pen); painter.setBrush(Qt::black); painter.translate(r, r); painter.rotate(360.0 - m_color.hue()); painter.rotate(-105); painter.drawEllipse(QPointF(m_color.saturationF() * r, 0.0), 4, 4); painter.resetTransform(); } void ColorWheelItem::drawSliderBar(QPainter &painter) { qreal value = 1.0 - m_color.valueF(); if (qAbs(m_zeroShift) > 0) { value -= m_zeroShift; } int ws = wheelSize() * qApp->devicePixelRatio(); int w = (qreal)ws / WHEEL_SLIDER_RATIO; int h = ws - m_margin * 2; QPen pen(Qt::white); pen.setWidth(2); painter.setPen(pen); painter.setBrush(Qt::black); painter.translate(ws, m_margin + value * h); painter.drawRect(0, 0, w, 4); painter.resetTransform(); } void ColorWheelItem::drawSlider() { QPainter painter(&m_image); painter.setRenderHint(QPainter::Antialiasing); int ws = wheelSize(); int w = (qreal)ws / WHEEL_SLIDER_RATIO; int h = ws - m_margin * 2; QLinearGradient gradient(0, 0, w, h); gradient.setColorAt(0.0, Qt::white); gradient.setColorAt(1.0, Qt::black); QBrush brush(gradient); painter.setPen(Qt::NoPen); painter.setBrush(brush); painter.translate(ws, m_margin); painter.drawRect(0, 0, w, h); m_sliderRegion = QRegion(ws, m_margin, w, h); } void ColorWheelItem::updateCursor(const QPoint &pos) { if (m_wheelRegion.contains(pos) || m_sliderRegion.contains(pos)) { setCursor(QCursor(Qt::CrossCursor)); } else { unsetCursor(); } } diff --git a/src/effectslist/effectslist.cpp b/src/effectslist/effectslist.cpp index 819745e03..e0a4bee6c 100644 --- a/src/effectslist/effectslist.cpp +++ b/src/effectslist/effectslist.cpp @@ -1,461 +1,459 @@ /*************************************************************************** effectslist.cpp - description ------------------- begin : Sat Aug 10 2002 copyright : (C) 2002 by Jason Wood email : jasonwood@blueyonder.co.uk ***************************************************************************/ /*************************************************************************** * * * 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 "effectslist.h" #include "kdenlivesettings.h" #include EffectsList::EffectsList(bool indexRequired) : m_useIndex(indexRequired) { m_baseElement = createElement(QStringLiteral("list")); appendChild(m_baseElement); } -EffectsList::~EffectsList() -{ -} +EffectsList::~EffectsList() {} QDomElement EffectsList::getEffectByName(const QString &name) const { QString effectName; QDomNodeList effects = m_baseElement.childNodes(); for (int i = 0; i < effects.count(); ++i) { QDomElement effect = effects.at(i).toElement(); QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) { effectName = i18n(namenode.text().toUtf8().data()); } if (name == effectName) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter")); for (int j = 0; i < params.count(); ++j) { QDomElement e = params.item(j).toElement(); if (!e.hasAttribute(QStringLiteral("value")) && e.attribute(QStringLiteral("type")) != QLatin1String("animatedrect") && e.attribute(QStringLiteral("paramlist")) != QLatin1String("%lumaPaths")) { e.setAttribute(QStringLiteral("value"), e.attribute(QStringLiteral("default"))); } } return effect; } } return QDomElement(); } QDomElement EffectsList::getEffectByTag(const QString &tag, const QString &id) const { QDomNodeList effects = m_baseElement.childNodes(); if (effects.isEmpty()) { return QDomElement(); } for (int i = 0; i < effects.count(); ++i) { QDomElement effect = effects.at(i).toElement(); if (!id.isEmpty()) { if (effect.attribute(QStringLiteral("id")) == id) { if (effect.tagName() == QLatin1String("effectgroup")) { // Effect group QDomNodeList subeffects = effect.elementsByTagName(QStringLiteral("effect")); for (int j = 0; j < subeffects.count(); ++j) { QDomElement sub = subeffects.at(j).toElement(); } } return effect; } } else if (!tag.isEmpty()) { if (effect.attribute(QStringLiteral("tag")) == tag) { return effect; } } } return QDomElement(); } QDomElement EffectsList::effectById(const QString &id) const { QDomNodeList effects = m_baseElement.childNodes(); for (int i = 0; i < effects.count(); ++i) { QDomElement effect = effects.at(i).toElement(); if (!id.isEmpty() && effect.attribute(QStringLiteral("id")) == id) { return effect; } } return QDomElement(); } bool EffectsList::hasTransition(const QString &tag) const { QDomNodeList trans = m_baseElement.childNodes(); for (int i = 0; i < trans.count(); ++i) { QDomElement effect = trans.at(i).toElement(); if (effect.attribute(QStringLiteral("tag")) == tag) { return true; } } return false; } int EffectsList::hasEffect(const QString &tag, const QString &id) const { QDomNodeList effects = m_baseElement.childNodes(); for (int i = 0; i < effects.count(); ++i) { QDomElement effect = effects.at(i).toElement(); if (!id.isEmpty()) { if (effect.attribute(QStringLiteral("id")) == id) { return effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); } } else if (!tag.isEmpty() && effect.attribute(QStringLiteral("tag")) == tag) { return effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); } } return -1; } QStringList EffectsList::effectIdInfo(const int ix) const { QDomElement effect = m_baseElement.childNodes().at(ix).toElement(); return effectInfo(effect); } QStringList EffectsList::effectInfo(const QDomElement &effect) const { QStringList info; if (effect.tagName() == QLatin1String("effectgroup")) { QString groupName = effect.attribute(QStringLiteral("name")); info << groupName << groupName << effect.attribute(QStringLiteral("id")) << QString::number(Kdenlive::groupEffect); } else { if (KdenliveSettings::gpu_accel()) { // Using Movit if (effect.attribute(QStringLiteral("context")) == QLatin1String("nomovit")) { // This effect has a Movit counterpart, so hide it when using Movit return info; } } else { // Not using Movit, don't display movit effects if (effect.attribute(QStringLiteral("tag")).startsWith(QLatin1String("movit."))) { return info; } } QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); QString name = namenode.text(); if (name.isEmpty()) { name = effect.attribute(QStringLiteral("tag")); } info << i18n(name.toUtf8().data()) << effect.attribute(QStringLiteral("tag")) << effect.attribute(QStringLiteral("id")); } return info; } QStringList EffectsList::effectNames() const { QStringList list; QDomNodeList effects = m_baseElement.childNodes(); for (int i = 0; i < effects.count(); ++i) { QDomElement effect = effects.at(i).toElement(); QDomElement namenode = effect.firstChildElement(QStringLiteral("name")); if (!namenode.isNull()) { list.append(i18n(namenode.text().toUtf8().data())); } } return list; } QString EffectsList::getInfo(const QString &tag, const QString &id) const { return getEffectInfo(getEffectByTag(tag, id)); } QString EffectsList::getInfoFromIndex(const int ix) const { return getEffectInfo(m_baseElement.childNodes().at(ix).toElement()); } QString EffectsList::getEffectInfo(const QDomElement &effect) const { QString info; QDomElement namenode = effect.firstChildElement(QStringLiteral("description")); if (!namenode.isNull() && !namenode.firstChild().nodeValue().isEmpty()) { info = i18n(namenode.firstChild().nodeValue().simplified().toUtf8().data()); } namenode = effect.firstChildElement(QStringLiteral("author")); if (!namenode.isNull() && !namenode.text().isEmpty()) { info.append(QStringLiteral("
    ") + i18n("Author:") + QStringLiteral(" ") + i18n(namenode.text().toUtf8().data())); } namenode = effect.firstChildElement(QStringLiteral("version")); if (!namenode.isNull()) { info.append(QStringLiteral(" (v.%1)").arg(namenode.text())); } return info; } // static bool EffectsList::hasKeyFrames(const QDomElement &effect) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe")) { return true; } } return false; } void EffectsList::clone(const EffectsList &original) { setContent(original.toString()); m_baseElement = documentElement(); } void EffectsList::clearList() { while (!m_baseElement.firstChild().isNull()) { m_baseElement.removeChild(m_baseElement.firstChild()); } } // static void EffectsList::setParameter(QDomElement effect, const QString &name, const QString &value) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter")); bool found = false; for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { e.setAttribute(QStringLiteral("value"), value); found = true; break; } } if (!found) { // create property QDomDocument doc = effect.ownerDocument(); QDomElement e = doc.createElement(QStringLiteral("parameter")); e.setAttribute(QStringLiteral("name"), name); QDomText val = doc.createTextNode(value); e.appendChild(val); effect.appendChild(e); } } // static QString EffectsList::parameter(const QDomElement &effect, const QString &name) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { return e.attribute(QStringLiteral("value")); } } return QString(); } // static void EffectsList::setProperty(QDomElement effect, const QString &name, const QString &value) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); // Update property if it already exists bool found = false; for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { e.firstChild().setNodeValue(value); found = true; break; } } if (!found) { // create property QDomDocument doc = effect.ownerDocument(); QDomElement e = doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), name); QDomText val = doc.createTextNode(value); e.appendChild(val); effect.appendChild(e); } } // static void EffectsList::renameProperty(const QDomElement &effect, const QString &oldName, const QString &newName) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); // Update property if it already exists for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == oldName) { e.setAttribute(QStringLiteral("name"), newName); break; } } } // static QString EffectsList::property(const QDomElement &effect, const QString &name) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { return e.firstChild().nodeValue(); } } return QString(); } // static void EffectsList::removeProperty(QDomElement effect, const QString &name) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { effect.removeChild(params.item(i)); break; } } } // static void EffectsList::removeMetaProperties(QDomElement producer) { QDomNodeList params = producer.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")).startsWith(QLatin1String("meta"))) { producer.removeChild(params.item(i)); --i; } } } QDomElement EffectsList::append(const QDomElement &e) { QDomElement result; if (!e.isNull()) { result = m_baseElement.appendChild(importNode(e, true)).toElement(); if (m_useIndex) { updateIndexes(m_baseElement.childNodes(), m_baseElement.childNodes().count() - 1); } } return result; } int EffectsList::count() const { return m_baseElement.childNodes().count(); } bool EffectsList::isEmpty() const { return !m_baseElement.hasChildNodes(); } const QDomElement EffectsList::at(int ix) const { QDomNodeList effects = m_baseElement.childNodes(); if (ix < 0 || ix >= effects.count()) { return QDomElement(); } return effects.at(ix).toElement(); } void EffectsList::removeAt(int ix) { QDomNodeList effects = m_baseElement.childNodes(); if (ix <= 0 || ix > effects.count()) { return; } m_baseElement.removeChild(effects.at(ix - 1)); if (m_useIndex) { updateIndexes(effects, ix - 1); } } QDomElement EffectsList::itemFromIndex(int ix) const { QDomNodeList effects = m_baseElement.childNodes(); if (ix <= 0 || ix > effects.count()) { return QDomElement(); } return effects.at(ix - 1).toElement(); } QDomElement EffectsList::insert(const QDomElement &effect) { QDomNodeList effects = m_baseElement.childNodes(); int ix = effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); QDomElement result; if (ix <= 0 || ix > effects.count()) { ix = effects.count(); result = m_baseElement.appendChild(importNode(effect, true)).toElement(); } else { QDomElement listeffect = effects.at(ix - 1).toElement(); result = m_baseElement.insertBefore(importNode(effect, true), listeffect).toElement(); } if (m_useIndex && ix > 0) { updateIndexes(effects, ix - 1); } return result; } void EffectsList::updateIndexes(const QDomNodeList &effects, int startIndex) { for (int i = startIndex; i < effects.count(); ++i) { QDomElement listeffect = effects.at(i).toElement(); listeffect.setAttribute(QStringLiteral("kdenlive_ix"), i + 1); } } bool EffectsList::enableEffects(const QList &indexes, bool disable) { bool monitorRefresh = false; QDomNodeList effects = m_baseElement.childNodes(); QDomElement effect; for (int i = 0; i < indexes.count(); ++i) { effect = effectFromIndex(effects, indexes.at(i)); effect.setAttribute(QStringLiteral("disable"), (int)disable); if (effect.attribute(QStringLiteral("type")) != QLatin1String("audio")) { monitorRefresh = true; } } return monitorRefresh; } QDomElement EffectsList::effectFromIndex(const QDomNodeList &effects, int ix) { if (ix <= 0 || ix > effects.count()) { return QDomElement(); } return effects.at(ix - 1).toElement(); } void EffectsList::updateEffect(const QDomElement &effect) { QDomNodeList effects = m_baseElement.childNodes(); int ix = effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); QDomElement current = effectFromIndex(effects, ix); if (!current.isNull()) { m_baseElement.insertBefore(importNode(effect, true), current); m_baseElement.removeChild(current); } else { m_baseElement.appendChild(importNode(effect, true)); } } diff --git a/src/effectslist/effectslist.h b/src/effectslist/effectslist.h index 8dc83c0b8..24257f862 100644 --- a/src/effectslist/effectslist.h +++ b/src/effectslist/effectslist.h @@ -1,106 +1,106 @@ /*************************************************************************** effectslist.h - description ------------------- begin : Sat Aug 10 2002 copyright : (C) 2002 by Jason Wood email : jasonwood@blueyonder.co.uk ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ /** * @class EffectsList * @brief List for effects objects. * @author Jason Wood * -*/ + */ #ifndef EFFECTSLIST_H #define EFFECTSLIST_H #include namespace Kdenlive { enum EFFECTTYPE { simpleEffect, groupEffect }; } class EffectsList : public QDomDocument { public: explicit EffectsList(bool indexRequired = false); ~EffectsList(); /** @brief Returns the XML element of an effect. * @param name name of the effect to be returned */ QDomElement getEffectByName(const QString &name) const; QDomElement getEffectByTag(const QString &tag, const QString &id) const; static const int EFFECT_VIDEO = 1; static const int EFFECT_AUDIO = 2; static const int EFFECT_GPU = 3; static const int EFFECT_CUSTOM = 4; static const int EFFECT_FAVORITES = 5; static const int EFFECT_FOLDER = 6; static const int TRANSITION_TYPE = 7; /** @brief Checks the existence of an effect. * @param tag effect tag * @param id effect id * @return effect index if the effect exists, -1 otherwise */ int hasEffect(const QString &tag, const QString &id) const; bool hasTransition(const QString &tag) const; /** @brief Lists the core properties of an effect. * @param ix effect index * @return list of name, tag and id of an effect */ QStringList effectIdInfo(const int ix) const; QStringList effectInfo(const QDomElement &effect) const; /** @brief Lists effects names. */ QStringList effectNames() const; QString getInfo(const QString &tag, const QString &id) const; QDomElement effectById(const QString &id) const; QString getInfoFromIndex(const int ix) const; QString getEffectInfo(const QDomElement &effect) const; void clone(const EffectsList &original); QDomElement append(const QDomElement &e); bool isEmpty() const; int count() const; const QDomElement at(int ix) const; void removeAt(int ix); QDomElement itemFromIndex(int ix) const; QDomElement insert(const QDomElement &effect); void updateEffect(const QDomElement &effect); static bool hasKeyFrames(const QDomElement &effect); static void setParameter(QDomElement effect, const QString &name, const QString &value); static QString parameter(const QDomElement &effect, const QString &name); /** @brief Change the value of a 'property' element from the effect node. */ static void setProperty(QDomElement effect, const QString &name, const QString &value); /** @brief Rename a 'property' element from the effect node. */ static void renameProperty(const QDomElement &effect, const QString &oldName, const QString &newName); /** @brief Get the value of a 'property' element from the effect node. */ static QString property(const QDomElement &effect, const QString &name); /** @brief Delete a 'property' element from the effect node. */ static void removeProperty(QDomElement effect, const QString &name); /** @brief Remove all 'meta.*' properties from a producer, used when replacing proxy producers in xml for rendering. */ static void removeMetaProperties(QDomElement producer); void clearList(); /** @brief Get am effect with effect index equal to ix. */ QDomElement effectFromIndex(const QDomNodeList &effects, int ix); /** @brief Update all effects indexes to make sure they are 1, 2, 3, ... */ void updateIndexes(const QDomNodeList &effects, int startIndex); /** @brief Enable / disable a list of effects */ bool enableEffects(const QList &indexes, bool disable); private: QDomElement m_baseElement; bool m_useIndex; }; #endif diff --git a/src/effectslist/effectslistview.h b/src/effectslist/effectslistview.h index 76630ec75..ad1a97c1c 100644 --- a/src/effectslist/effectslistview.h +++ b/src/effectslist/effectslistview.h @@ -1,188 +1,188 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 EFFECTSLISTVIEW_H #define EFFECTSLISTVIEW_H #include "gentime.h" #include "ui_effectlist_ui.h" #include #include #include #include #include class EffectsList; class EffectsListWidget; class QTreeWidget; class KActionCategory; class QListWidget; class TreeEventEater : public QObject { Q_OBJECT public: explicit TreeEventEater(QObject *parent = nullptr); protected: bool eventFilter(QObject *obj, QEvent *event) override; signals: void clearSearchLine(); }; class MyTreeWidgetSearchLine : public KTreeWidgetSearchLine { Q_OBJECT public: explicit MyTreeWidgetSearchLine(QWidget *parent = nullptr); protected: bool itemMatches(const QTreeWidgetItem *item, const QString &pattern) const override; }; /** * @class MyDropButton * @brief A QToolButton accepting effect drops * @author Jean-Baptiste Mardelle */ class MyDropButton : public QToolButton { Q_OBJECT public: explicit MyDropButton(QWidget *parent = nullptr) : QToolButton(parent) { setAcceptDrops(true); setAutoExclusive(true); setCheckable(true); setAutoRaise(true); } protected: void dragEnterEvent(QDragEnterEvent *event) override { if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effectslist"))) { event->accept(); } } void dropEvent(QDropEvent *event) override { const QString effects = QString::fromUtf8(event->mimeData()->data(QStringLiteral("kdenlive/effectslist"))); QDomDocument doc; doc.setContent(effects, true); QString id = doc.documentElement().attribute(QStringLiteral("id")); if (id.isEmpty()) { id = doc.documentElement().attribute(QStringLiteral("tag")); } emit addEffectToFavorites(id); } signals: void addEffectToFavorites(QString); }; /** * @class EffectsListView * @brief Manages the controls for the treewidget containing the effects. * @author Jean-Baptiste Mardelle */ class EffectsListView : public QWidget, public Ui::EffectList_UI { Q_OBJECT public: enum LISTMODE { EffectMode = 0, TransitionMode = 1 }; explicit EffectsListView(LISTMODE mode = EffectMode, QWidget *parent = nullptr); /** @brief Re-initializes the list of effects. */ void reloadEffectList(QMenu *effectsMenu, KActionCategory *effectActions); QMenu *getEffectsMenu(); // void slotAddEffect(GenTime pos, int track, QString name); /** @brief Palette was changed, update styles. */ void updatePalette(); void refreshIcons(); void creatFavoriteBasket(QListWidget *list); private: /** @brief tells us if this is an effect or transition list - */ + */ LISTMODE m_mode; EffectsListWidget *m_effectsList; MyTreeWidgetSearchLine *m_search_effect; const QString customStyleSheet() const; /** @brief Custom button to display favorite effects, accepts drops to add effect to favorites. - */ + */ MyDropButton *m_effectsFavorites; /** @brief Action triggering remove effect from favorites or delete custom effect, depending on active tab. - */ + */ QAction *m_removeAction; QAction *m_favoriteAction; QMenu *m_contextMenu; private slots: /** @brief Applies the type filter to the effect list. - * @param pos Index of the combo box; where 0 = All, 1 = Video, 2 = Audio, 3 = GPU, 4 = Custom */ + * @param pos Index of the combo box; where 0 = All, 1 = Video, 2 = Audio, 3 = GPU, 4 = Custom */ void filterList(); /** @brief Updates the info panel to match the selected effect. */ void slotUpdateInfo(); /** @brief Toggles the info panel's visibility. */ void showInfoPanel(); /** @brief Emits addEffect signal for the selected effect. */ void slotEffectSelected(); /** @brief Removes the XML file for the selected effect. - * - * Only used for custom effects */ + * + * Only used for custom effects */ void slotRemoveEffect(); /** @brief Makes sure the item fits the type filter. - * @param item Current item - * @param hidden Hidden or not - * - * This is necessary to make the search obey to the type filter. - * Called when the visibility of this item was changed by searching */ + * @param item Current item + * @param hidden Hidden or not + * + * This is necessary to make the search obey to the type filter. + * Called when the visibility of this item was changed by searching */ void slotUpdateSearch(QTreeWidgetItem *item, bool hidden); /** @brief Expands folders that match our search. - * @param text Current search string */ + * @param text Current search string */ void slotAutoExpand(const QString &text); /** @brief Add an effect to the favorites - * @param id id of the effect we want */ + * @param id id of the effect we want */ void slotAddFavorite(const QString &id); /** @brief Add currently selected effect to the favorites */ void slotAddToFavorites(); void slotDisplayMenu(QTreeWidgetItem *item, const QPoint &pos); signals: void addEffect(const QDomElement &); void reloadEffects(); void reloadBasket(); }; #endif diff --git a/src/effectslist/effectslistwidget.cpp b/src/effectslist/effectslistwidget.cpp index f94f728e7..1712d8b67 100644 --- a/src/effectslist/effectslistwidget.cpp +++ b/src/effectslist/effectslistwidget.cpp @@ -1,532 +1,530 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 "effectslistwidget.h" #include "effectslist/effectslist.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "klocalizedstring.h" #include #include #include #include EffectsListWidget::EffectsListWidget(QWidget *parent) : QTreeWidget(parent) { setColumnCount(1); setDragEnabled(true); setAcceptDrops(false); setHeaderHidden(true); setFrameShape(QFrame::NoFrame); setAutoFillBackground(false); setRootIsDecorated(true); setIndentation(10); // setSelectionMode(QAbstractItemView::ExtendedSelection); setDragDropMode(QAbstractItemView::DragOnly); updatePalette(); connect(this, &EffectsListWidget::activated, this, &EffectsListWidget::slotExpandItem); } -EffectsListWidget::~EffectsListWidget() -{ -} +EffectsListWidget::~EffectsListWidget() {} void EffectsListWidget::updatePalette() { QPalette p = qApp->palette(); p.setBrush(QPalette::Base, QBrush(Qt::transparent)); setPalette(p); } void EffectsListWidget::slotExpandItem(const QModelIndex &index) { setExpanded(index, !isExpanded(index)); } void EffectsListWidget::initList(QMenu *effectsMenu, KActionCategory *effectActions, const QString &categoryFile, bool transitionMode) { QString current; QString currentFolder; bool found = false; effectsMenu->clear(); if (currentItem()) { current = currentItem()->text(0); if (currentItem()->parent()) { currentFolder = currentItem()->parent()->text(0); } else if (currentItem()->data(0, TypeRole) == EffectsList::EFFECT_FOLDER) { currentFolder = currentItem()->text(0); } } QTreeWidgetItem *misc = nullptr; QTreeWidgetItem *audio = nullptr; QTreeWidgetItem *custom = nullptr; QList folders; if (!categoryFile.isEmpty()) { QDomDocument doc; QFile file(categoryFile); doc.setContent(&file, false); file.close(); QStringList folderNames; QDomNodeList groups = doc.documentElement().elementsByTagName(QStringLiteral("group")); for (int i = 0; i < groups.count(); ++i) { folderNames << i18n(groups.at(i).firstChild().firstChild().nodeValue().toUtf8().constData()); } for (int i = 0; i < topLevelItemCount(); ++i) { topLevelItem(i)->takeChildren(); QString currentName = topLevelItem(i)->text(0); if (currentName != i18n("Misc") && currentName != i18n("Audio") && currentName != i18nc("Folder Name", "Custom") && !folderNames.contains(currentName)) { takeTopLevelItem(i); --i; } } for (int i = 0; i < groups.count(); ++i) { QTreeWidgetItem *item = findFolder(folderNames.at(i)); if (item) { item->setData(0, IdRole, groups.at(i).toElement().attribute(QStringLiteral("list"))); } else { item = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList(folderNames.at(i))); item->setData(0, TypeRole, QString::number((int)EffectsList::EFFECT_FOLDER)); item->setData(0, IdRole, groups.at(i).toElement().attribute(QStringLiteral("list"))); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); item->setChildIndicatorPolicy(QTreeWidgetItem::DontShowIndicatorWhenChildless); insertTopLevelItem(0, item); } folders.append(item); } misc = findFolder(i18n("Misc")); if (misc == nullptr) { misc = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList(i18n("Misc"))); misc->setData(0, TypeRole, QString::number((int)EffectsList::EFFECT_FOLDER)); misc->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); insertTopLevelItem(0, misc); } audio = findFolder(i18n("Audio")); if (audio == nullptr) { audio = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList(i18n("Audio"))); audio->setData(0, TypeRole, QString::number((int)EffectsList::EFFECT_FOLDER)); audio->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); insertTopLevelItem(0, audio); } custom = findFolder(i18nc("Folder Name", "Custom")); if (custom == nullptr) { custom = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList(i18nc("Folder Name", "Custom"))); custom->setData(0, TypeRole, QString::number((int)EffectsList::EFFECT_FOLDER)); custom->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); insertTopLevelItem(0, custom); } } // insertTopLevelItems(0, folders); if (transitionMode) { loadEffects(&MainWindow::transitions, misc, &folders, EffectsList::TRANSITION_TYPE, current, &found); } else { loadEffects(&MainWindow::videoEffects, misc, &folders, EffectsList::EFFECT_VIDEO, current, &found); loadEffects(&MainWindow::audioEffects, audio, &folders, EffectsList::EFFECT_AUDIO, current, &found); loadEffects(&MainWindow::customEffects, custom, static_cast *>(nullptr), EffectsList::EFFECT_CUSTOM, current, &found); if (!found && !currentFolder.isEmpty()) { // previously selected effect was removed, focus on its parent folder for (int i = 0; i < topLevelItemCount(); ++i) { if (topLevelItem(i)->text(0) == currentFolder) { setCurrentItem(topLevelItem(i)); break; } } } } setSortingEnabled(true); sortByColumn(0, Qt::AscendingOrder); // populate effects menu QMenu *sub1 = nullptr; QMenu *sub2 = nullptr; QMenu *sub3 = nullptr; QMenu *sub4 = nullptr; for (int i = 0; i < topLevelItemCount(); ++i) { if (topLevelItem(i)->data(0, TypeRole) == EffectsList::TRANSITION_TYPE) { QTreeWidgetItem *item = topLevelItem(i); QAction *a = new QAction(item->icon(0), item->text(0), effectsMenu); QStringList effectdata = item->data(0, IdRole).toStringList(); QString id = effectdata.at(1); if (id.isEmpty()) { id = effectdata.at(0); } a->setData(effectdata); a->setIconVisibleInMenu(false); effectsMenu->addAction(a); effectActions->addAction("transition_" + id, a); continue; } if (topLevelItem(i)->childCount() == 0) { continue; } QMenu *sub = new QMenu(topLevelItem(i)->text(0), effectsMenu); effectsMenu->addMenu(sub); int effectsInCategory = topLevelItem(i)->childCount(); bool hasSubCategories = false; if (effectsInCategory > 60) { // create subcategories if there are too many effects hasSubCategories = true; sub1 = new QMenu(i18nc("menu name for effects names between these 2 letters", "0 - F"), sub); sub->addMenu(sub1); sub2 = new QMenu(i18nc("menu name for effects names between these 2 letters", "G - L"), sub); sub->addMenu(sub2); sub3 = new QMenu(i18nc("menu name for effects names between these 2 letters", "M - R"), sub); sub->addMenu(sub3); sub4 = new QMenu(i18nc("menu name for effects names between these 2 letters", "S - Z"), sub); sub->addMenu(sub4); } for (int j = 0; j < effectsInCategory; ++j) { QTreeWidgetItem *item = topLevelItem(i)->child(j); QAction *a = new QAction(item->icon(0), item->text(0), sub); QStringList effectdata = item->data(0, IdRole).toStringList(); QString id = effectdata.at(1); if (id.isEmpty()) { id = effectdata.at(0); } a->setData(effectdata); a->setIconVisibleInMenu(false); if (hasSubCategories) { // put action in sub category QRegExp rx("^[s-z].+"); if (rx.exactMatch(item->text(0).toLower())) { sub4->addAction(a); } else { rx.setPattern(QStringLiteral("^[m-r].+")); if (rx.exactMatch(item->text(0).toLower())) { sub3->addAction(a); } else { rx.setPattern(QStringLiteral("^[g-l].+")); if (rx.exactMatch(item->text(0).toLower())) { sub2->addAction(a); } else { sub1->addAction(a); } } } } else { sub->addAction(a); } effectActions->addAction("video_effect_" + id, a); } } } void EffectsListWidget::loadEffects(const EffectsList *effectlist, QTreeWidgetItem *defaultFolder, const QList *folders, int type, const QString ¤t, bool *found) { QStringList effectInfo, l; QTreeWidgetItem *item; int ct = effectlist->count(); QFontMetrics f(font()); int fontSize = f.height(); for (int ix = 0; ix < ct; ++ix) { const QDomElement effect = effectlist->at(ix); effectInfo = effectlist->effectInfo(effect); if (effectInfo.isEmpty()) { continue; } QTreeWidgetItem *parentItem = nullptr; if (folders) { for (int i = 0; i < folders->count(); ++i) { l = folders->at(i)->data(0, IdRole).toString().split(QLatin1Char(','), QString::SkipEmptyParts); if (l.contains(effectInfo.at(2))) { parentItem = folders->at(i); break; } } } if (parentItem == nullptr) { parentItem = defaultFolder; } QIcon icon2 = generateIcon(fontSize, effectInfo.at(0), effect); item = new QTreeWidgetItem(parentItem, QStringList(effectInfo.takeFirst())); QString tag = effectInfo.at(0); if (type != EffectsList::EFFECT_CUSTOM && tag.startsWith(QLatin1String("movit."))) { // GPU effect effectInfo.append(QString::number(EffectsList::EFFECT_GPU)); item->setData(0, TypeRole, EffectsList::EFFECT_GPU); } else { effectInfo.append(QString::number(type)); item->setData(0, TypeRole, type); } if (effectInfo.count() == 4) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("folder"))); } else { item->setIcon(0, icon2); } item->setData(0, IdRole, effectInfo); item->setToolTip(0, effectlist->getEffectInfo(effect)); if (parentItem == nullptr) { addTopLevelItem(item); } if (item->text(0) == current) { setCurrentItem(item); *found = true; } } } QTreeWidgetItem *EffectsListWidget::findFolder(const QString &name) { QTreeWidgetItem *item = nullptr; QList result = findItems(name, Qt::MatchExactly); if (!result.isEmpty()) { for (int j = 0; j < result.count(); ++j) { if (result.at(j)->data(0, TypeRole) == EffectsList::EFFECT_FOLDER) { item = result.at(j); break; } } } return item; } const QDomElement EffectsListWidget::currentEffect() const { QTreeWidgetItem *item = currentItem(); if (!item) { return QDomElement(); } int type = item->data(0, TypeRole).toInt(); QStringList info = item->data(0, IdRole).toStringList(); return itemEffect(type, info); } QIcon EffectsListWidget::generateIcon(int size, const QString &name, QDomElement info) { QPixmap pix(size, size); if (name.isEmpty()) { pix.fill(Qt::red); return QIcon(pix); } QFont ft = font(); ft.setBold(true); uint hex = qHash(name); QString t = "#" + QString::number(hex, 16).toUpper().left(6); QColor col(t); info.setAttribute(QStringLiteral("effectcolor"), col.name()); bool isAudio = info.attribute(QStringLiteral("type")) == QLatin1String("audio"); if (isAudio) { pix.fill(Qt::transparent); } else { pix.fill(col); } QPainter p(&pix); if (isAudio) { p.setPen(Qt::NoPen); p.setBrush(col); p.drawEllipse(pix.rect()); p.setPen(QPen()); } p.setFont(ft); p.drawText(pix.rect(), Qt::AlignCenter, name.at(0)); p.end(); return QIcon(pix); } const QDomElement EffectsListWidget::itemEffect(int type, const QStringList &effectInfo) { QDomElement effect; if (type == (int)EffectsList::EFFECT_FOLDER) { return effect; } switch (type) { case EffectsList::EFFECT_VIDEO: case EffectsList::EFFECT_GPU: effect = MainWindow::videoEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); break; case EffectsList::EFFECT_AUDIO: effect = MainWindow::audioEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); break; case EffectsList::EFFECT_CUSTOM: effect = MainWindow::customEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); break; case EffectsList::TRANSITION_TYPE: effect = MainWindow::transitions.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); break; default: effect = MainWindow::videoEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); if (!effect.isNull()) { break; } effect = MainWindow::audioEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); if (!effect.isNull()) { break; } effect = MainWindow::customEffects.getEffectByTag(effectInfo.at(0), effectInfo.at(1)).cloneNode().toElement(); break; } return effect; } QString EffectsListWidget::currentInfo() const { QTreeWidgetItem *item = currentItem(); if ((item == nullptr) || item->data(0, TypeRole).toInt() == (int)EffectsList::EFFECT_FOLDER) { return QString(); } QString info; QStringList effectInfo = item->data(0, IdRole).toStringList(); switch (item->data(0, TypeRole).toInt()) { case EffectsList::EFFECT_VIDEO: case EffectsList::EFFECT_GPU: info = MainWindow::videoEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); break; case EffectsList::EFFECT_AUDIO: info = MainWindow::audioEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); break; case EffectsList::EFFECT_CUSTOM: info = MainWindow::customEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); break; case EffectsList::TRANSITION_TYPE: info = MainWindow::transitions.getInfo(effectInfo.at(0), effectInfo.at(1)); break; default: info = MainWindow::videoEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); if (!info.isEmpty()) { break; } info = MainWindow::audioEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); if (!info.isEmpty()) { break; } info = MainWindow::customEffects.getInfo(effectInfo.at(0), effectInfo.at(1)); break; } return info; } // virtual void EffectsListWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) { emit applyEffect(currentEffect()); e->accept(); return; } QTreeWidget::keyPressEvent(e); } // virtual QMimeData *EffectsListWidget::mimeData(const QList list) const { QDomDocument doc; bool transitionMode = false; for (QTreeWidgetItem *item : list) { if ((item->flags() & Qt::ItemIsDragEnabled) != 0) { int type = item->data(0, TypeRole).toInt(); if (type == EffectsList::TRANSITION_TYPE) { transitionMode = true; } QStringList info = item->data(0, IdRole).toStringList(); const QDomElement e = itemEffect(type, info); if (!e.isNull()) { doc.appendChild(doc.importNode(e, true)); } } } auto *mime = new QMimeData; mime->setData(transitionMode ? "kdenlive/transitionslist" : "kdenlive/effectslist", doc.toString().toUtf8()); return mime; } // virtual void EffectsListWidget::dragMoveEvent(QDragMoveEvent *event) { if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effectslist"))) { event->acceptProposedAction(); } else { event->ignore(); } } // virtual void EffectsListWidget::contextMenuEvent(QContextMenuEvent *event) { QTreeWidgetItem *item = itemAt(event->pos()); if ((item != nullptr) && item->data(0, TypeRole) != EffectsList::EFFECT_FOLDER) { emit displayMenu(item, event->globalPos()); } } void EffectsListWidget::setRootOnCustomFolder() { // special case, display only items in "custom" folder" QTreeWidgetItem *item = findFolder(i18nc("Folder Name", "Custom")); if (!item) { // No custom effect, show empty list for (int i = 0; i < topLevelItemCount(); ++i) { QTreeWidgetItem *folder = topLevelItem(i); folder->setHidden(true); } return; } setRootIndex(indexFromItem(item)); for (int j = 0; j < item->childCount(); ++j) { QTreeWidgetItem *child = item->child(j); child->setHidden(false); } } void EffectsListWidget::resetRoot() { setRootIndex(indexFromItem(invisibleRootItem())); } void EffectsListWidget::createTopLevelItems(const QList &list, int effectType) { // Favorites is a pseudo-folder used to store items, not visible to end user, so don't i18n its name QTreeWidgetItem *misc = findFolder(QStringLiteral("TemporaryFolder")); if (misc == nullptr) { misc = new QTreeWidgetItem(this, QStringList(QStringLiteral("TemporaryFolder"))); misc->setData(0, TypeRole, QString::number((int)EffectsList::EFFECT_FOLDER)); misc->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } else { qDeleteAll(misc->takeChildren()); } setIndentation(0); misc->addChildren(list); for (int j = 0; j < misc->childCount(); ++j) { QTreeWidgetItem *child = misc->child(j); child->setHidden(false); child->setData(0, Qt::UserRole, effectType); } setRootIndex(indexFromItem(misc)); } void EffectsListWidget::resetFavorites() { QTreeWidgetItem *misc = findFolder(QStringLiteral("TemporaryFolder")); if (misc) { setRootIndex(indexFromItem(invisibleRootItem())); delete misc; } } diff --git a/src/effectslist/initeffects.cpp b/src/effectslist/initeffects.cpp index 665e81b97..633e40ddb 100644 --- a/src/effectslist/initeffects.cpp +++ b/src/effectslist/initeffects.cpp @@ -1,1059 +1,1060 @@ /*************************************************************************** initeffects.cpp - description ------------------- begin : Jul 2006 copyright : (C) 2006 by Jean-Baptiste Mardelle email : jb@ader.ch copyright : (C) 2008 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 "initeffects.h" #include "effectslist.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "kdenlive_debug.h" #include #include #include #include #include #ifdef Q_OS_MAC #include #endif // static void initEffects::refreshLumas() { // Check for Kdenlive installed luma files, add empty string at start for no luma QStringList imagefiles; QStringList fileFilters; MainWindow::m_lumaFiles.clear(); fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); customLumas.append(QString(mlt_environment("MLT_DATA")) + QStringLiteral("/lumas")); for (const QString &folder : customLumas) { QDir topDir(folder); QStringList folders = topDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); for (const QString &f : folders) { QDir dir(topDir.absoluteFilePath(f)); QStringList filesnames = dir.entryList(fileFilters, QDir::Files); if (MainWindow::m_lumaFiles.contains(f)) { imagefiles = MainWindow::m_lumaFiles.value(f); } for (const QString &fname : filesnames) { imagefiles.append(dir.absoluteFilePath(fname)); } MainWindow::m_lumaFiles.insert(f, imagefiles); } } /* QStringList imagenamelist = QStringList() << i18n("None"); QStringList imagefiles = QStringList() << QString(); QStringList filters; filters << QStringLiteral("*.pgm") << QStringLiteral("*.png"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); for (const QString & folder : customLumas) { QDir directory(folder); QStringList filesnames = directory.entryList(filters, QDir::Files); for (const QString & fname : filesnames) { imagenamelist.append(fname); imagefiles.append(directory.absoluteFilePath(fname)); } } // Check for MLT lumas QUrl folder(QString(mlt_environment("MLT_DATA")) + QDir::separator() + "lumas" + QDir::separator() + QString(mlt_environment("MLT_NORMALISATION"))); QDir lumafolder(folder.path()); QStringList filesnames = lumafolder.entryList(filters, QDir::Files); for (const QString & fname : filesnames) { imagenamelist.append(fname); imagefiles.append(lumafolder.absoluteFilePath(fname)); } //TODO adapt to Wipe transition QDomElement lumaTransition = MainWindow::transitions.getEffectByTag(QStringLiteral("luma"), QStringLiteral("luma")); QDomNodeList params = lumaTransition.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("resource")) { e.setAttribute(QStringLiteral("paramlistdisplay"), imagenamelist.join(QLatin1Char(','))); e.setAttribute(QStringLiteral("paramlist"), imagefiles.join(QLatin1Char(';'))); break; } } QDomElement compositeTransition = MainWindow::transitions.getEffectByTag(QStringLiteral("composite"), QStringLiteral("composite")); params = compositeTransition.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("tag")) == QLatin1String("luma")) { e.setAttribute(QStringLiteral("paramlistdisplay"), imagenamelist.join(QLatin1Char(','))); e.setAttribute(QStringLiteral("paramlist"), imagefiles.join(QLatin1Char(';'))); break; } }*/ } // static QDomDocument initEffects::getUsedCustomEffects(const QMap &effectids) { QMapIterator i(effectids); QDomDocument doc; QDomElement list = doc.createElement(QStringLiteral("customeffects")); doc.appendChild(list); while (i.hasNext()) { i.next(); int ix = MainWindow::customEffects.hasEffect(i.value(), i.key()); if (ix > -1) { QDomElement e = MainWindow::customEffects.at(ix); list.appendChild(doc.importNode(e, true)); } } return doc; } // static bool initEffects::parseEffectFiles(std::unique_ptr &repository, const QString &locale) { bool movit = false; QStringList::Iterator more; QStringList::Iterator it; QStringList fileList; QString itemName; if (!repository) { // qCDebug(KDENLIVE_LOG) << "Repository didn't finish initialisation" ; return movit; } // Warning: Mlt::Factory::init() resets the locale to the default system value, make sure we keep correct locale if (!locale.isEmpty()) { #ifndef Q_OS_MAC setlocale(LC_NUMERIC, locale.toUtf8().constData()); #else setlocale(LC_NUMERIC_MASK, locale.toUtf8().constData()); #endif } // Retrieve the list of MLT's available effects. Mlt::Properties *filters = repository->filters(); QStringList filtersList; int max = filters->count(); filtersList.reserve(max); for (int i = 0; i < max; ++i) { filtersList << filters->get_name(i); } delete filters; // Retrieve the list of available producers. Mlt::Properties *producers = repository->producers(); QStringList producersList; max = producers->count(); producersList.reserve(max); for (int i = 0; i < max; ++i) { producersList << producers->get_name(i); } KdenliveSettings::setProducerslist(producersList); delete producers; if (filtersList.contains(QStringLiteral("glsl.manager"))) { Mlt::Properties *consumers = repository->consumers(); QStringList consumersList; max = consumers->count(); consumersList.reserve(max); for (int i = 0; i < max; ++i) { consumersList << consumers->get_name(i); } delete consumers; movit = true; } else { KdenliveSettings::setGpu_accel(false); } // Retrieve the list of available transitions. Mlt::Properties *transitions = repository->transitions(); QStringList transitionsItemList; max = transitions->count(); for (int i = 0; i < max; ++i) { // qCDebug(KDENLIVE_LOG)<<"TRANSITION "<get_name(i); transitionsItemList << transitions->get_name(i); } delete transitions; // Create structure holding all transitions descriptions so that if an XML file has no description, we take it from MLT QMap transDescriptions; for (const QString &transname : transitionsItemList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("transitions"), transname); if (!doc.isNull()) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); if (!desc.isEmpty()) { transDescriptions.insert(transname, desc); } } } } transitionsItemList.sort(); // Get list of installed luma files refreshLumas(); // Parse xml transition files QStringList direc = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("transitions"), QStandardPaths::LocateDirectory); // Iterate through effects directories to parse all XML files. for (more = direc.begin(); more != direc.end(); ++more) { QDir directory(*more); QStringList filter; filter << QStringLiteral("*.xml"); fileList = directory.entryList(filter, QDir::Files); for (it = fileList.begin(); it != fileList.end(); ++it) { itemName = directory.absoluteFilePath(*it); parseTransitionFile(&MainWindow::transitions, itemName, repository, transitionsItemList, transDescriptions); } } // Remove blacklisted transitions from the list. QFile file(QStringLiteral(":data/blacklisted_transitions.txt")); if (file.open(QIODevice::ReadOnly)) { QTextStream in(&file); while (!in.atEnd()) { QString black = in.readLine().simplified(); if (!black.isEmpty() && !black.startsWith('#') && transitionsItemList.contains(black)) { transitionsItemList.removeAll(black); } } file.close(); } // Fill transitions list. fillTransitionsList(repository, &MainWindow::transitions, transitionsItemList); // Remove blacklisted effects from the filters list. QStringList mltFiltersList = filtersList; QStringList mltBlackList; QFile file2(QStringLiteral(":data/blacklisted_effects.txt")); if (file2.open(QIODevice::ReadOnly)) { QTextStream in(&file2); while (!in.atEnd()) { QString black = in.readLine().simplified(); if (!black.isEmpty() && !black.startsWith('#') && mltFiltersList.contains(black)) { mltFiltersList.removeAll(black); mltBlackList << black; } } file2.close(); } /* * Cleanup the global lists. We use QMap because of its automatic sorting * (by key) and key uniqueness (using insert() instead of insertMulti()). * This introduces some more cycles (while removing them from other parts of * the code and centralising them), but due to the way this methods, QMap * and EffectsList are implemented, there's no easy way to make it * differently without reinplementing something (which should really be * done). */ QDomElement effectInfo; QMap effectsMap; QMap videoEffectsMap; QMap audioEffectsMap; // Create transitions max = MainWindow::transitions.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::transitions.at(i); effectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::transitions.clearList(); for (const QDomElement &effect : effectsMap) { MainWindow::transitions.append(effect); } effectsMap.clear(); // Create structure holding all effects descriptions so that if an XML effect has no description, we take it from MLT QMap effectDescriptions; for (const QString &filtername : mltBlackList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("filters"), filtername); if (!doc.isNull()) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); // WARNING: TEMPORARY FIX for unusable MLT SOX parameters description if (desc.startsWith(QLatin1String("Process audio using a SoX"))) { // Remove MLT's SOX generated effects since the parameters properties are unusable for us continue; } if (!desc.isEmpty()) { effectDescriptions.insert(filtername, desc); } } } } // Create effects from MLT for (const QString &filtername : mltFiltersList) { QDomDocument doc = createDescriptionFromMlt(repository, QStringLiteral("filters"), filtername); // WARNING: TEMPORARY FIX for empty MLT effects descriptions - disable effects without parameters - jbm 09-06-2011 if (!doc.isNull() && doc.elementsByTagName(QStringLiteral("parameter")).count() > 0) { if (doc.documentElement().attribute(QStringLiteral("type")) == QLatin1String("audio")) { if (doc.elementsByTagName(QStringLiteral("description")).count() > 0) { QString desc = doc.documentElement().firstChildElement(QStringLiteral("description")).text(); // WARNING: TEMPORARY FIX for unusable MLT SOX parameters description if (desc.startsWith(QLatin1String("Process audio using a SoX"))) { // Remove MLT's SOX generated effects since the parameters properties are unusable for us } else { audioEffectsMap.insert(doc.documentElement().firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), doc.documentElement()); } } } else { videoEffectsMap.insert(doc.documentElement().firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), doc.documentElement()); } } } // Set the directories to look into for effects. direc = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory); // Iterate through effects directories to parse all XML files. for (more = direc.begin(); more != direc.end(); ++more) { QDir directory(*more); QStringList filter; filter << QStringLiteral("*.xml"); fileList = directory.entryList(filter, QDir::Files); for (it = fileList.begin(); it != fileList.end(); ++it) { itemName = directory.absoluteFilePath(*it); parseEffectFile(&MainWindow::customEffects, &MainWindow::audioEffects, &MainWindow::videoEffects, itemName, filtersList, producersList, repository, effectDescriptions); } } // Create custom effects max = MainWindow::customEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::customEffects.at(i); if (effectInfo.tagName() == QLatin1String("effectgroup")) { effectsMap.insert(effectInfo.attribute(QStringLiteral("name")).toUtf8().data(), effectInfo); } else { effectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toUtf8().data(), effectInfo); } } MainWindow::customEffects.clearList(); for (const QDomElement &effect : effectsMap) { MainWindow::customEffects.append(effect); } effectsMap.clear(); // Create audio effects max = MainWindow::audioEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::audioEffects.at(i); audioEffectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::audioEffects.clearList(); for (const QDomElement &effect : audioEffectsMap) { MainWindow::audioEffects.append(effect); } // Create video effects max = MainWindow::videoEffects.count(); for (int i = 0; i < max; ++i) { effectInfo = MainWindow::videoEffects.at(i); videoEffectsMap.insert(effectInfo.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), effectInfo); } MainWindow::videoEffects.clearList(); for (const QDomElement &effect : videoEffectsMap) { MainWindow::videoEffects.append(effect); } return movit; } // static void initEffects::parseCustomEffectsFile() { MainWindow::customEffects.clearList(); /* * Why a QMap? See parseEffectFiles(). It's probably useless here, but we * cannot be sure about it. */ QMap effectsMap; QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects"); QDir directory = QDir(path); QStringList filter; filter << QStringLiteral("*.xml"); const QStringList fileList = directory.entryList(filter, QDir::Files); /* * We need to declare these variables outside the foreach, or the QMap will * refer to non existing variables (QMap::insert() takes references as * parameters). */ QDomDocument doc; QDomNodeList effects; QDomElement e; int unknownGroupCount = 0; for (const QString &filename : fileList) { QString itemName = directory.absoluteFilePath(filename); QFile file(itemName); doc.setContent(&file, false); file.close(); QDomElement base = doc.documentElement(); if (base.tagName() == QLatin1String("effectgroup")) { QString groupName = base.attribute(QStringLiteral("name")); if (groupName.isEmpty()) { groupName = i18n("Group %1", unknownGroupCount); base.setAttribute(QStringLiteral("name"), groupName); unknownGroupCount++; } effectsMap.insert(groupName.toLower().toUtf8().data(), base); } else if (base.tagName() == QLatin1String("effect")) { effectsMap.insert(base.firstChildElement(QStringLiteral("name")).text().toLower().toUtf8().data(), base); } else { qCDebug(KDENLIVE_LOG) << "Unsupported effect file: " << itemName; } } for (const QDomElement &effect : effectsMap) { MainWindow::customEffects.append(effect); } } // static void initEffects::parseEffectFile(EffectsList *customEffectList, EffectsList *audioEffectList, EffectsList *videoEffectList, const QString &name, const QStringList &filtersList, const QStringList &producersList, std::unique_ptr &repository, const QMap &effectDescriptions) { QDomDocument doc; QFile file(name); doc.setContent(&file, false); file.close(); QDomElement documentElement; QDomNodeList effects; QDomElement base = doc.documentElement(); QStringList addedTags; effects = doc.elementsByTagName(QStringLiteral("effect")); int i = effects.count(); if (i == 0) { qCDebug(KDENLIVE_LOG) << "+++++++++++++\nEffect broken: " << name << "\n+++++++++++"; return; } bool needsLocaleConversion = false; i--; for (; i >= 0; i--) { QLocale locale; QDomNode n = effects.item(i); if (n.isNull()) { continue; } documentElement = n.toElement(); QString tag = documentElement.attribute(QStringLiteral("tag"), QString()); QString id = documentElement.hasAttribute(QStringLiteral("id")) ? documentElement.attribute(QStringLiteral("id")) : tag; if (addedTags.contains(id)) { // We already processed a version of that filter continue; } // If XML has no description, take it fom MLT's descriptions if (effectDescriptions.contains(tag)) { QDomNodeList desc = documentElement.elementsByTagName(QStringLiteral("description")); if (desc.isEmpty()) { QDomElement d = documentElement.ownerDocument().createElement(QStringLiteral("description")); QDomText value = documentElement.ownerDocument().createTextNode(effectDescriptions.value(tag)); d.appendChild(value); documentElement.appendChild(d); } } if (documentElement.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // set a locale for that file locale = QLocale(documentElement.attribute(QStringLiteral("LC_NUMERIC"))); if (locale.decimalPoint() != QLocale().decimalPoint()) { needsLocaleConversion = true; } } locale.setNumberOptions(QLocale::OmitGroupSeparator); if (needsLocaleConversion) { // we need to convert all numbers to the system's locale (for example 0.5 -> 0,5) QChar separator = QLocale().decimalPoint(); QChar oldSeparator = locale.decimalPoint(); QDomNodeList params = documentElement.elementsByTagName(QStringLiteral("parameter")); for (int j = 0; j < params.count(); ++j) { QDomNamedNodeMap attrs = params.at(j).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); } } } } } double version = -1; Mlt::Properties *metadata = repository->metadata(filter_type, tag.toUtf8().data()); if ((metadata != nullptr) && metadata->is_valid()) { version = metadata->get_double("version"); } delete metadata; if (documentElement.hasAttribute(QStringLiteral("version"))) { // a specific version of the filter is required if (locale.toDouble(documentElement.attribute(QStringLiteral("version"))) > version) { continue; } } if (version > -1) { // Add version info to XML QDomNode versionNode = doc.createElement(QStringLiteral("version")); versionNode.appendChild(doc.createTextNode(QLocale().toString(version))); documentElement.appendChild(versionNode); } // Parse effect information. if (base.tagName() != QLatin1String("effectgroup") && (filtersList.contains(tag) || producersList.contains(tag))) { QString type = documentElement.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("audio")) { audioEffectList->append(documentElement); } else if (type == QLatin1String("custom")) { customEffectList->append(documentElement); } else { videoEffectList->append(documentElement); } addedTags << id; } } if (base.tagName() == QLatin1String("effectgroup")) { QString type = base.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("audio")) { audioEffectList->append(base); } else if (type == QLatin1String("custom")) { customEffectList->append(base); } else { videoEffectList->append(base); } } } QDomDocument initEffects::createDescriptionFromMlt(std::unique_ptr &repository, const QString & /*type*/, const QString &filtername) { QDomDocument ret; Mlt::Properties *metadata = repository->metadata(filter_type, filtername.toLatin1().data()); ////qCDebug(KDENLIVE_LOG) << filtername; if ((metadata != nullptr) && metadata->is_valid()) { if ((metadata->get("title") != nullptr) && (metadata->get("identifier") != nullptr) && strlen(metadata->get("title")) > 0) { QDomElement eff = ret.createElement(QStringLiteral("effect")); QString id = metadata->get("identifier"); eff.setAttribute(QStringLiteral("tag"), id); eff.setAttribute(QStringLiteral("id"), id); ////qCDebug(KDENLIVE_LOG)<<"Effect: "<get("title"); name_str[0] = name_str[0].toUpper(); name.appendChild(ret.createTextNode(name_str)); QDomElement desc = ret.createElement(QStringLiteral("description")); desc.appendChild(ret.createTextNode(metadata->get("description"))); QDomElement author = ret.createElement(QStringLiteral("author")); author.appendChild(ret.createTextNode(metadata->get("creator"))); QDomElement version = ret.createElement(QStringLiteral("version")); version.appendChild(ret.createTextNode(metadata->get("version"))); eff.appendChild(name); eff.appendChild(author); eff.appendChild(desc); eff.appendChild(version); Mlt::Properties tags((mlt_properties)metadata->get_data("tags")); if (QString(tags.get(0)) == QLatin1String("Audio")) { eff.setAttribute(QStringLiteral("type"), QStringLiteral("audio")); } /*for (int i = 0; i < tags.count(); ++i) //qCDebug(KDENLIVE_LOG)<get_data("parameters")); for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) { QDomElement params = ret.createElement(QStringLiteral("parameter")); Mlt::Properties paramdesc((mlt_properties)param_props.get_data(param_props.get_name(j))); params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier")); if (params.attribute(QStringLiteral("name")) == QLatin1String("argument")) { // This parameter has to be given as attribute when using command line, do not show it in Kdenlive continue; } if ((paramdesc.get("readonly") != nullptr) && (strcmp(paramdesc.get("readonly"), "yes") == 0)) { // Do not expose readonly parameters continue; } if (paramdesc.get("maximum")) { params.setAttribute(QStringLiteral("max"), paramdesc.get("maximum")); } if (paramdesc.get("minimum")) { params.setAttribute(QStringLiteral("min"), paramdesc.get("minimum")); } QString paramType = paramdesc.get("type"); if (paramType == QLatin1String("integer")) { if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); } else { params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); } } else if (paramType == QLatin1String("float")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); // param type is float, set default decimals to 3 params.setAttribute(QStringLiteral("decimals"), QStringLiteral("3")); } else if (paramType == QLatin1String("boolean")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); } else if (paramType == QLatin1String("geometry")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("geometry")); } else if (paramType == QLatin1String("string")) { // string parameter are not really supported, so if we have a default value, enforce it params.setAttribute(QStringLiteral("type"), QStringLiteral("fixed")); if (paramdesc.get("default")) { QString stringDefault = paramdesc.get("default"); stringDefault.remove(QLatin1Char('\'')); params.setAttribute(QStringLiteral("value"), stringDefault); } else { // String parameter without default, skip it completely continue; } } else { params.setAttribute(QStringLiteral("type"), paramType); if (!QString(paramdesc.get("format")).isEmpty()) { params.setAttribute(QStringLiteral("format"), paramdesc.get("format")); } } if (!params.hasAttribute(QStringLiteral("value"))) { if (paramdesc.get("default")) { params.setAttribute(QStringLiteral("default"), paramdesc.get("default")); } if (paramdesc.get("value")) { params.setAttribute(QStringLiteral("value"), paramdesc.get("value")); } else { params.setAttribute(QStringLiteral("value"), paramdesc.get("default")); } } QString paramName = paramdesc.get("title"); if (!paramName.isEmpty()) { QDomElement pname = ret.createElement(QStringLiteral("name")); pname.appendChild(ret.createTextNode(paramName)); params.appendChild(pname); } if (paramdesc.get("description")) { QDomElement comment = ret.createElement(QStringLiteral("comment")); comment.appendChild(ret.createTextNode(paramdesc.get("description"))); params.appendChild(comment); } eff.appendChild(params); } ret.appendChild(eff); } } delete metadata; metadata = nullptr; /*QString outstr; QTextStream str(&outstr); ret.save(str, 2); //qCDebug(KDENLIVE_LOG) << outstr;*/ return ret; } void initEffects::fillTransitionsList(std::unique_ptr &repository, EffectsList *transitions, QStringList names) { // Remove transitions that are not implemented. int pos = names.indexOf(QStringLiteral("mix")); if (pos != -1) { names.takeAt(pos); } // Remove unused luma transition, replaced with a custom composite Wipe transition pos = names.indexOf(QStringLiteral("luma")); if (pos != -1) { names.takeAt(pos); } // WARNING: this is a hack to get around temporary invalid metadata in MLT, 2nd of june 2011 JBM QStringList customTransitions; customTransitions << QStringLiteral("composite") << QStringLiteral("affine") << QStringLiteral("mix") << QStringLiteral("region"); for (const QString &name : names) { QDomDocument ret; QDomElement ktrans = ret.createElement(QStringLiteral("transition")); ret.appendChild(ktrans); ktrans.setAttribute(QStringLiteral("tag"), name); QDomElement tname = ret.createElement(QStringLiteral("name")); QDomElement desc = ret.createElement(QStringLiteral("description")); ktrans.appendChild(tname); ktrans.appendChild(desc); Mlt::Properties *metadata = nullptr; if (!customTransitions.contains(name)) { metadata = repository->metadata(transition_type, name.toUtf8().data()); } if ((metadata != nullptr) && metadata->is_valid()) { // If possible, set name and description. // qCDebug(KDENLIVE_LOG)<<" / / FOUND TRANS: "<get("title"); if ((metadata->get("title") != nullptr) && (metadata->get("identifier") != nullptr)) { tname.appendChild(ret.createTextNode(metadata->get("title"))); } desc.appendChild(ret.createTextNode(metadata->get("description"))); Mlt::Properties param_props((mlt_properties)metadata->get_data("parameters")); for (int i = 0; param_props.is_valid() && i < param_props.count(); ++i) { QDomElement params = ret.createElement(QStringLiteral("parameter")); Mlt::Properties paramdesc((mlt_properties)param_props.get_data(param_props.get_name(i))); params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier")); if (paramdesc.get("maximum")) { params.setAttribute(QStringLiteral("max"), paramdesc.get_double("maximum")); } if (paramdesc.get("minimum")) { params.setAttribute(QStringLiteral("min"), paramdesc.get_double("minimum")); } if (paramdesc.get("default")) { params.setAttribute(QStringLiteral("default"), paramdesc.get("default")); } if (QString(paramdesc.get("type")) == QLatin1String("integer")) { if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); } else { params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); params.setAttribute(QStringLiteral("factor"), QStringLiteral("100")); } } else if (QString(paramdesc.get("type")) == QLatin1String("float")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("double")); if (paramdesc.get_int("maximum") == 1) { params.setAttribute(QStringLiteral("factor"), 100); params.setAttribute(QStringLiteral("max"), 100); params.setAttribute(QStringLiteral("default"), paramdesc.get_double("default") * 100); } } else if (QString(paramdesc.get("type")) == QLatin1String("boolean")) { params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); } if (!QString(paramdesc.get("format")).isEmpty()) { params.setAttribute(QStringLiteral("type"), QStringLiteral("complex")); params.setAttribute(QStringLiteral("format"), paramdesc.get("format")); } if (paramdesc.get("value")) { params.setAttribute(QStringLiteral("value"), paramdesc.get("value")); } else { params.setAttribute(QStringLiteral("value"), params.attribute(QStringLiteral("default"))); } QDomElement pname = ret.createElement(QStringLiteral("name")); pname.appendChild(ret.createTextNode(paramdesc.get("title"))); params.appendChild(pname); ktrans.appendChild(params); } } else { /* * Check for Kdenlive installed luma files, add empty string at * start for no luma file. */ // Implement default transitions. // TODO: create xml files for transitions in data/transitions instead of hardcoding here QList paramList; if (name == QLatin1String("composite")) { ktrans.setAttribute(QStringLiteral("id"), name); tname.appendChild(ret.createTextNode(i18n("Composite"))); desc.appendChild(ret.createTextNode(i18n("A key-framable alpha-channel compositor for two frames."))); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("geometry"), QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100"), QStringLiteral("-500;-500;-500;-500;0"), QStringLiteral("500;500;500;500;100"))); paramList.append(quickParameterFill(ret, i18n("Alpha Channel Operation"), QStringLiteral("operator"), QStringLiteral("list"), QStringLiteral("over"), QString(), QString(), QStringLiteral("over,and,or,xor"), i18n("Over,And,Or,Xor"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("aligned"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("valign"), QStringLiteral("fixed"), QStringLiteral("middle"), QStringLiteral("middle"), QStringLiteral("middle"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("halign"), QStringLiteral("fixed"), QStringLiteral("centre"), QStringLiteral("centre"), QStringLiteral("centre"))); paramList.append(quickParameterFill(ret, i18n("Fill"), QStringLiteral("fill"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Wipe Method"), QStringLiteral("luma"), QStringLiteral("list"), QString(), QString(), QString(), QStringLiteral("%lumaPaths"), QString())); paramList.append(quickParameterFill(ret, i18n("Wipe Softness"), QStringLiteral("softness"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("100"), QString(), QString(), QStringLiteral("100"))); paramList.append(quickParameterFill(ret, i18n("Wipe Invert"), QStringLiteral("luma_invert"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Progressive Rendering"), QStringLiteral("progressive"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Deinterlace Overlay"), QStringLiteral("deinterlace"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); } else if (name == QLatin1String("affine")) { tname.appendChild(ret.createTextNode(i18n("Affine"))); ret.documentElement().setAttribute(QStringLiteral("showrotation"), QStringLiteral("1")); /*paramList.append(quickParameterFill(ret, i18n("Rotate Y"), "rotate_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Rotate X"), "rotate_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), "rotate_z", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate Y"), "fix_rotate_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate X"), "fix_rotate_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Rotate Z"), "fix_rotate_z", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear Y"), "shear_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear X"), "shear_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Shear Z"), "shear_z", "double", "0", "0", "360"));*/ /*paramList.append(quickParameterFill(ret, i18n("Fix Shear Y"), "fix_shear_y", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Shear X"), "fix_shear_x", "double", "0", "0", "360")); paramList.append(quickParameterFill(ret, i18n("Fix Shear Z"), "fix_shear_z", "double", "0", "0", "360"));*/ paramList.append(quickParameterFill(ret, QStringLiteral("keyed"), QStringLiteral("keyed"), QStringLiteral("fixed"), QStringLiteral("1"), QStringLiteral("1"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("geometry"), QStringLiteral("geometry"), QStringLiteral("0/0:100%x100%:100%"), QStringLiteral("0/0:100%x100%:100%"), QStringLiteral("0/0:100%x100%:100%"), QString(), QString(), QString(), QStringLiteral("true"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Rotate X"), QStringLiteral("rotate_x"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); paramList.append(quickParameterFill(ret, i18n("Rotate Y"), QStringLiteral("rotate_y"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), QStringLiteral("rotate_z"), QStringLiteral("addedgeometry"), QStringLiteral("0"), QStringLiteral("-1800"), QStringLiteral("1800"), QString(), QString(), QStringLiteral("10"))); /*paramList.append(quickParameterFill(ret, i18n("Rotate Y"), "rotate_y", "simplekeyframe", "0", "-1800", "1800", QString(), QString(), "10")); paramList.append(quickParameterFill(ret, i18n("Rotate Z"), "rotate_z", "simplekeyframe", "0", "-1800", "1800", QString(), QString(), "10"));*/ paramList.append(quickParameterFill(ret, i18n("Fix Shear Y"), QStringLiteral("shear_y"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); paramList.append(quickParameterFill(ret, i18n("Fix Shear X"), QStringLiteral("shear_x"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); paramList.append(quickParameterFill(ret, i18n("Fix Shear Z"), QStringLiteral("shear_z"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("360"))); } else if (name == QLatin1String("mix")) { tname.appendChild(ret.createTextNode(i18n("Mix"))); } else if (name == QLatin1String("region")) { ktrans.setAttribute(QStringLiteral("id"), name); tname.appendChild(ret.createTextNode(i18n("Region"))); desc.appendChild(ret.createTextNode(i18n("Use alpha channel of another clip to create a transition."))); paramList.append(quickParameterFill(ret, i18n("Transparency clip"), QStringLiteral("resource"), QStringLiteral("url"), QString(), QString(), QString(), QString(), QString(), QString())); paramList.append(quickParameterFill(ret, i18n("Geometry"), QStringLiteral("composite.geometry"), QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100"), QStringLiteral("-500;-500;-500;-500;0"), QStringLiteral("500;500;500;500;100"))); paramList.append(quickParameterFill(ret, i18n("Alpha Channel Operation"), QStringLiteral("composite.operator"), QStringLiteral("list"), QStringLiteral("over"), QString(), QString(), QStringLiteral("over,and,or,xor"), i18n("Over,And,Or,Xor"))); paramList.append(quickParameterFill(ret, i18n("Align"), QStringLiteral("composite.aligned"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Fill"), QStringLiteral("composite.fill"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Distort"), QStringLiteral("composite.distort"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Wipe File"), QStringLiteral("composite.luma"), QStringLiteral("list"), QString(), QString(), QString(), QStringLiteral("%lumaPaths"), QString())); paramList.append(quickParameterFill(ret, i18n("Wipe Softness"), QStringLiteral("composite.softness"), QStringLiteral("double"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("100"), QString(), QString(), QStringLiteral("100"))); paramList.append(quickParameterFill(ret, i18n("Wipe Invert"), QStringLiteral("composite.luma_invert"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Progressive Rendering"), QStringLiteral("composite.progressive"), QStringLiteral("bool"), QStringLiteral("1"), QStringLiteral("0"), QStringLiteral("1"))); paramList.append(quickParameterFill(ret, i18n("Force Deinterlace Overlay"), QStringLiteral("composite.deinterlace"), QStringLiteral("bool"), QStringLiteral("0"), QStringLiteral("0"), QStringLiteral("1"))); } for (const QDomElement &e : paramList) { ktrans.appendChild(e); } } delete metadata; metadata = nullptr; // Add the transition to the global list. ////qCDebug(KDENLIVE_LOG) << ret.toString(); transitions->append(ret.documentElement()); } // Add some virtual transitions. QString slidetrans = QStringLiteral("") + i18n("Slide") + QStringLiteral("") + i18n("Slide image from one side to another.") + QStringLiteral("") + - i18n("Direction") + QStringLiteral(" ") + + i18n("Direction") + + QStringLiteral(" ") + i18n("Align") + QStringLiteral("") + i18n("Force Progressive Rendering") + QStringLiteral("") + i18n("Force Deinterlace Overlay") + QStringLiteral("") + i18nc("@property: means that the image is inverted", "Invert") + QStringLiteral(""); QDomDocument ret; ret.setContent(slidetrans); transitions->append(ret.documentElement()); QString dissolve = QStringLiteral("") + i18n("Dissolve") + QStringLiteral("") + i18n("Fade out one video while fading in the other video.") + QStringLiteral("") + i18n("Reverse") + QStringLiteral(""); ret.setContent(dissolve); transitions->append(ret.documentElement()); /*QString alphatrans = "" + i18n("Alpha Transparency") + QStringLiteral("") + i18n("Make alpha channel transparent.") + "" + i18n("Direction") + "" + i18n("Rescale") + "" + i18n("Align") + ""; ret.setContent(alphatrans); transitions->append(ret.documentElement());*/ } QDomElement initEffects::quickParameterFill(QDomDocument &doc, const QString &name, const QString &tag, const QString &type, const QString &def, const QString &min, const QString &max, const QString &list, const QString &listdisplaynames, const QString &factor, const QString &opacity) { QDomElement parameter = doc.createElement(QStringLiteral("parameter")); parameter.setAttribute(QStringLiteral("tag"), tag); parameter.setAttribute(QStringLiteral("default"), def); parameter.setAttribute(QStringLiteral("value"), def); parameter.setAttribute(QStringLiteral("type"), type); parameter.setAttribute(QStringLiteral("name"), tag); parameter.setAttribute(QStringLiteral("max"), max); parameter.setAttribute(QStringLiteral("min"), min); if (!list.isEmpty()) { parameter.setAttribute(QStringLiteral("paramlist"), list); } if (!listdisplaynames.isEmpty()) { parameter.setAttribute(QStringLiteral("paramlistdisplay"), listdisplaynames); } if (!factor.isEmpty()) { parameter.setAttribute(QStringLiteral("factor"), factor); } if (!opacity.isEmpty()) { parameter.setAttribute(QStringLiteral("opacity"), opacity); } QDomElement pname = doc.createElement(QStringLiteral("name")); pname.appendChild(doc.createTextNode(name)); parameter.appendChild(pname); return parameter; } // static void initEffects::parseTransitionFile(EffectsList *transitionList, const QString &name, std::unique_ptr &repository, const QStringList &installedTransitions, const QMap &effectDescriptions) { QFile file(name); if (!file.open(QIODevice::ReadOnly)) { return; } QDomDocument doc; QTextStream out(&file); QString fileContent = out.readAll(); file.close(); doc.setContent(fileContent, false); QDomElement documentElement; QDomNodeList effects; QDomElement base = doc.documentElement(); QStringList addedTags; effects = doc.elementsByTagName(QStringLiteral("transition")); int i = effects.count(); if (i == 0) { qCDebug(KDENLIVE_LOG) << "+++++++++++++Transition broken: " << name << "\n+++++++++++"; return; } bool needsLocaleConversion = false; i--; for (; i >= 0; i--) { QLocale locale; QDomNode n = effects.item(i); if (n.isNull()) { continue; } documentElement = n.toElement(); QString tag = documentElement.attribute(QStringLiteral("tag")); if (!installedTransitions.contains(tag)) { // This transition is not available return; } QString id = documentElement.attribute(QStringLiteral("id")); if (addedTags.contains(id)) { // We already processed a version of that filter continue; } // If XML has no description, take it fom MLT's descriptions if (effectDescriptions.contains(id)) { QDomNodeList desc = documentElement.elementsByTagName(QStringLiteral("description")); if (desc.isEmpty()) { QDomElement d = documentElement.ownerDocument().createElement(QStringLiteral("description")); QDomText value = documentElement.ownerDocument().createTextNode(effectDescriptions.value(id)); d.appendChild(value); documentElement.appendChild(d); } } if (documentElement.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // set a locale for that file locale = QLocale(documentElement.attribute(QStringLiteral("LC_NUMERIC"))); if (locale.decimalPoint() != QLocale().decimalPoint()) { needsLocaleConversion = true; } } locale.setNumberOptions(QLocale::OmitGroupSeparator); if (needsLocaleConversion) { // we need to convert all numbers to the system's locale (for example 0.5 -> 0,5) QChar separator = QLocale().decimalPoint(); QChar oldSeparator = locale.decimalPoint(); QDomNodeList params = documentElement.elementsByTagName(QStringLiteral("parameter")); for (int j = 0; j < params.count(); ++j) { QDomNamedNodeMap attrs = params.at(j).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); } } } } } double version = -1; Mlt::Properties *metadata = repository->metadata(transition_type, id.toUtf8().data()); if ((metadata != nullptr) && metadata->is_valid()) { version = metadata->get_double("version"); } delete metadata; if (documentElement.hasAttribute(QStringLiteral("version"))) { // a specific version of the filter is required if (locale.toDouble(documentElement.attribute(QStringLiteral("version"))) > version) { continue; } } if (version > -1) { // Add version info to XML QDomNode versionNode = doc.createElement(QStringLiteral("version")); versionNode.appendChild(doc.createTextNode(QLocale().toString(version))); documentElement.appendChild(versionNode); } addedTags << id; } transitionList->append(base); } diff --git a/src/effectslist/initeffects.h b/src/effectslist/initeffects.h index 6f5f9d898..be8c2d2c3 100644 --- a/src/effectslist/initeffects.h +++ b/src/effectslist/initeffects.h @@ -1,90 +1,90 @@ /*************************************************************************** initeffects.h - description ------------------- begin : Jul 2006 copyright : (C) 2006 by Jean-Baptiste Mardelle email : jb@ader.ch ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef InitEffects_H #define InitEffects_H #include #include #include #include #include /**Init the MLT effects - *@author Jean-Baptiste Mardelle - */ + *@author Jean-Baptiste Mardelle + */ class EffectsList; class initEffects { public: /** @brief Fills the effects and transitions lists. * @ref fillTransitionsList * @ref parseEffectFile * @return true if Movit GPU effects are available * * It checks for all available effects and transitions, removes blacklisted * ones, calls fillTransitionsList() and parseEffectFile() to fill the lists * (with sorted, unique items) and then fills the global lists. */ static bool parseEffectFiles(std::unique_ptr &repository, const QString &locale = QString()); static void refreshLumas(); static QDomDocument createDescriptionFromMlt(std::unique_ptr &repository, const QString &type, const QString &name); static QDomDocument getUsedCustomEffects(const QMap &effectids); /** @brief Fills the transitions list. * @param repository MLT repository * @param transitions list to save the transitions data in * @param names list of transitions names * * It creates an element for each transition, asking to MLT for information * when possible, using default parameters otherwise. It also adds some * "virtual" transition, and removes those not implemented. */ static void fillTransitionsList(std::unique_ptr &repository, EffectsList *transitions, QStringList names); /** @brief Creates an element describing a transition parameter. * @param doc document containing the transition element * @param name parameter name * @param tag parameter tag * @param type parameter type (string, double, bool, etc.) * @return element with the parameter information */ static QDomElement quickParameterFill(QDomDocument &doc, const QString &name, const QString &tag, const QString &type, const QString &def = QString(), const QString &min = QString(), const QString &max = QString(), const QString &list = QString(), const QString &listdisplaynames = QString(), const QString &factor = QString(), const QString &opacity = QString()); /** @brief Parses a file to record information about one or more effects. * @param customEffectList list of custom effect * @param audioEffectList list of audio effects * @param videoEffectList list of video effects * @param name file name * @param filtersList list of filters in the MLT repository * @param producersList list of producers in the MLT repository * @param repository MLT repository */ static void parseEffectFile(EffectsList *customEffectList, EffectsList *audioEffectList, EffectsList *videoEffectList, const QString &name, const QStringList &filtersList, const QStringList &producersList, std::unique_ptr &repository, const QMap &effectDescriptions); static void parseTransitionFile(EffectsList *transitionList, const QString &name, std::unique_ptr &repository, const QStringList &installedTransitions, const QMap &effectDescriptions); /** @brief Reloads information about custom effects. */ static void parseCustomEffectsFile(); private: initEffects(); // disable the constructor }; #endif diff --git a/src/effectstack/effectstackedit.h b/src/effectstack/effectstackedit.h index 7c01298ee..83953a1b5 100644 --- a/src/effectstack/effectstackedit.h +++ b/src/effectstack/effectstackedit.h @@ -1,92 +1,92 @@ /*************************************************************************** effecstackedit.h - description ------------------- begin : Feb 15 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. * * * ***************************************************************************/ #ifndef EFFECTSTACKEDIT_H #define EFFECTSTACKEDIT_H #include "parametercontainer.h" #include "definitions.h" #include "timecode.h" #include #include #include #include class Monitor; class EffectStackEdit : public QScrollArea { Q_OBJECT public: explicit EffectStackEdit(Monitor *monitor, QWidget *parent = nullptr); ~EffectStackEdit(); static QMap iconCache; /** @brief Sets attribute @param name to @param value. - * - * Used to disable the effect, by setting disabled="1" */ + * + * Used to disable the effect, by setting disabled="1" */ void updateParameter(const QString &name, const QString &value); void setFrameSize(const QPoint &p); /** @brief Tells the parameters to update their timecode format according to KdenliveSettings. */ void updateTimecodeFormat(); /** @brief Returns true if this effect wants to keep track of current position in clip. */ bool effectNeedsSyncPosition() const; Monitor *monitor(); /** @brief Install event filter so that scrolling with mouse wheel does not change parameter value. */ bool eventFilter(QObject *o, QEvent *e) override; /** @brief Returns type of monitor scene requested by this transition. */ MonitorSceneType needsMonitorEffectScene() const; /** @brief Set keyframes for this transition. */ void setKeyframes(const QString &tag, const QString &keyframes); void updatePalette(); /** @brief Emit geometry settings. */ void initEffectScene(int pos); bool doesAcceptDrops() const; private: EffectMetaInfo m_metaInfo; QWidget *m_baseWidget; ParameterContainer *m_paramWidget; public slots: /** @brief Called when an effect is selected, builds the UI for this effect. */ void transferParamDesc(const QDomElement &d, const ItemInfo &info, bool isEffect = true); /** @brief Pass position changes of the timeline cursor to the effects to keep their local timelines in sync. */ void slotSyncEffectsPos(int pos); private slots: /** @brief Import keyframes for the transition. */ void importKeyframes(const QString &kf); signals: void parameterChanged(const QDomElement &, const QDomElement &, int); void seekTimeline(int); void displayMessage(const QString &, int); void checkMonitorPosition(int); void syncEffectsPos(int pos); /** @brief Request sending geometry info to monitor overlay. */ void initScene(int); void showComments(bool show); void effectStateChanged(bool enabled); /** @brief Start an MLT filter job on this clip. */ void startFilterJob(QMap &, QMap &, QMap &); void importClipKeyframes(GraphicsRectItem = AVWidget, const QMap &keyframes = QMap()); }; #endif diff --git a/src/effectstack/effectstackview2.h b/src/effectstack/effectstackview2.h index 69f2d1991..daeea0bf3 100644 --- a/src/effectstack/effectstackview2.h +++ b/src/effectstack/effectstackview2.h @@ -1,285 +1,285 @@ /*************************************************************************** effecstackview2.h - description ------------------- begin : Feb 15 2008 copyright : (C) 2008 by Marco Gittler (g.marco@freenet.de) copyright : (C) 2012 by Jean-Baptiste Mardelle (jb@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) any later version. * * * ***************************************************************************/ /** * @class EffectStackView2 * @brief View part of the EffectStack * @author Marco Gittler */ #ifndef EFFECTSTACKVIEW2_H #define EFFECTSTACKVIEW2_H #include "collapsibleeffect.h" #include "collapsiblegroup.h" #include class EffectsList; class ClipItem; class Transition; class EffectSettings; class TransitionSettings; class ClipController; class Monitor; class EffectStackView2 : public QWidget { Q_OBJECT public: explicit EffectStackView2(Monitor *projectMonitor, QWidget *parent = nullptr); virtual ~EffectStackView2(); /** @brief Raises @param dock if a clip is loaded. */ void raiseWindow(QWidget *dock); /** @brief return the current status of effect stack (timeline clip, track or master clip). */ EFFECTMODE effectStatus() const; /** @brief return the index of the track displayed in effect stack */ int trackIndex() const; /** @brief Clears the list of effects and updates the buttons accordingly. */ void clear(); /** @brief Tells the effect editor to update its timecode format. */ void updateTimecodeFormat(); /** @brief Used to trigger drag effects. */ bool eventFilter(QObject *o, QEvent *e) override; CollapsibleEffect *getEffectByIndex(int ix); /** @brief Delete currently selected effect. */ void deleteCurrentEffect(); /** @brief Palette was changed, update style. */ void updatePalette(); /** @brief Color theme was changed, update icons. */ void refreshIcons(); /** @brief Process dropped xml effect. */ void processDroppedEffect(QDomElement e, QDropEvent *event); /** @brief Return the stylesheet required for effect parameters. */ static const QString getStyleSheet(); /** @brief Import keyframes from the clip metadata */ void setKeyframes(const QString &tag, const QString &keyframes); /** @brief Returns the transition setting widget for signal/slot connections */ TransitionSettings *transitionConfig(); /** @brief Dis/Enable the effect stack */ void disableBinEffects(bool disable); void disableTimelineEffects(bool disable); enum STACKSTATUS { NORMALSTATUS = 0, DISABLEBIN = 1, DISABLETIMELINE = 2, DISABLEALL = 3 }; protected: void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dropEvent(QDropEvent *event) override; private: ClipItem *m_clipref; ClipController *m_masterclipref; /** @brief Current status of the effect stack (if it contains a timeline clip, track or master clip effect. */ EFFECTMODE m_status; STACKSTATUS m_stateStatus; /** @brief The track index of currently edited track. */ int m_trackindex; /** @brief The effect currently being dragged, nullptr if no drag happening. */ CollapsibleEffect *m_draggedEffect; /** @brief The effect currently being dragged, nullptr if no drag happening. */ CollapsibleGroup *m_draggedGroup; /** @brief The current number of groups. */ int m_groupIndex; /** @brief The current effect may require an on monitor scene. */ MonitorSceneType m_monitorSceneWanted; QMutex m_mutex; QTimer m_scrollTimer; /** If in track mode: Info of the edited track to be able to access its duration. */ TrackInfo m_trackInfo; QList m_effects; EffectsList m_currentEffectList; QVBoxLayout m_layout; EffectSettings *m_effect; TransitionSettings *m_transition; /** @brief Contains info about effect like is it a track effect, which monitor displays it,... */ EffectMetaInfo m_effectMetaInfo; /** @brief The last mouse click position, used to detect drag events. */ QPoint m_clickPoint; /** @brief Sets the list of effects according to the clip's effect list. */ void setupListView(); /** @brief Build the drag info and start it. */ void startDrag(); /** @brief Connect an effect to its signals. */ void connectEffect(CollapsibleEffect *currentEffect); /** @brief Connect a group to its signals. */ void connectGroup(CollapsibleGroup *group); /** @brief Returns index of currently selected effect in stack. */ int activeEffectIndex() const; /** @brief Returns index of previous effect in stack. */ int getPreviousIndex(int current); /** @brief Returns index of next effect in stack. */ int getNextIndex(int ix); public slots: /** @brief Sets the clip whose effect list should be managed. - * @param c Clip whose effect list should be managed */ + * @param c Clip whose effect list should be managed */ void slotClipItemSelected(ClipItem *c, Monitor *m = nullptr, bool reloadStack = true); /** @brief An effect parameter was changed, refresh effect stack if it was displaying it. - * @param c Clip controller whose effect list should be managed */ + * @param c Clip controller whose effect list should be managed */ void slotRefreshMasterClipEffects(ClipController *c, Monitor *m); /** @brief Display effects for the selected Bin clip. - * @param c Clip controller whose effect list should be managed */ + * @param c Clip controller whose effect list should be managed */ void slotMasterClipItemSelected(ClipController *c, Monitor *m = nullptr); /** @brief Update the clip range (in-out points) - * @param c Clip whose effect list should be managed */ + * @param c Clip whose effect list should be managed */ void slotClipItemUpdate(); void slotTrackItemSelected(int ix, const TrackInfo &info, Monitor *m = nullptr); /** @brief Check if the mouse wheel events should be used for scrolling the widget view. */ void slotCheckWheelEventFilter(); void slotTransitionItemSelected(Transition *t, int nextTrack, const QPoint &p, bool update); /** @brief Select active keyframe in an animated effect. */ void setActiveKeyframe(int frame); private slots: /** @brief Emits seekTimeline with position = clipstart + @param pos. */ void slotSeekTimeline(int pos); /* @brief Define the region filter for current effect. void slotRegionChanged();*/ /** @brief Checks whether the monitor scene has to be displayed. */ void slotCheckMonitorPosition(int renderPos); void slotUpdateEffectParams(const QDomElement &old, const QDomElement &e, int ix); /** @brief Move an effect in the stack. * @param indexes The list of effect index in the stack * @param up true if we want to move effect up, false for down */ void slotMoveEffectUp(const QList &indexes, bool up); /** @brief Delete an effect in the stack. */ void slotDeleteEffect(const QDomElement &effect); /** @brief Delete all effect in a group. */ void slotDeleteGroup(const QDomDocument &doc); /** @brief Pass position changes of the timeline cursor to the effects to keep their local timelines in sync. */ void slotRenderPos(int pos); /** @brief Called whenever an effect is enabled / disabled by user. */ void slotUpdateEffectState(bool disable, int index, MonitorSceneType needsMonitorEffectScene); void slotSetCurrentEffect(int ix); /** @brief Triggers a filter job on this clip. */ void slotStartFilterJob(QMap &, QMap &, QMap &); /** @brief Reset an effect to its default values. */ void slotResetEffect(int ix); /** @brief Create a group containing effect with ix index. */ void slotCreateGroup(int ix); /** @brief Create a region effect with ix index. */ void slotCreateRegion(int ix, const QUrl &url); /** @brief Move an effect. - ** @param currentIndexes the list of effect indexes to move in stack layout - ** @param newIndex the position where the effects will be moved - ** @param groupIndex the index of the group if any (-1 if none) - ** @param groupName the name of the group to paste the effect - */ + ** @param currentIndexes the list of effect indexes to move in stack layout + ** @param newIndex the position where the effects will be moved + ** @param groupIndex the index of the group if any (-1 if none) + ** @param groupName the name of the group to paste the effect + */ void slotMoveEffect(const QList ¤tIndexes, int newIndex, int groupIndex, const QString &groupName = QString()); /** @brief Remove effects from a group */ void slotUnGroup(CollapsibleGroup *group); /** @brief Add en effect to selected clip */ void slotAddEffect(const QDomElement &effect); /** @brief Enable / disable all effects for the clip */ void slotCheckAll(int state); /** @brief Update check all button status */ void slotUpdateCheckAllButton(); /** @brief An effect group was renamed, update effects info */ void slotRenameGroup(CollapsibleGroup *group); /** @brief Dis/Enable monitor effect compare */ void slotSwitchCompare(bool enable); signals: void removeEffectGroup(ClipItem *, int, const QDomDocument &); void removeEffect(ClipItem *, int, const QDomElement &); void removeMasterEffect(const QString &id, const QDomElement &); void addMasterEffect(const QString &id, const QDomElement &); /** Parameters for an effect changed, update the filter in timeline */ void updateEffect(ClipItem *, int, const QDomElement &, const QDomElement &, int, bool); /** Parameters for an effect changed, update the filter in timeline */ void updateMasterEffect(QString, const QDomElement &, const QDomElement &, int ix, bool refreshStack = false); /** An effect in stack was moved, we need to regenerate all effects for this clip in the playlist */ void refreshEffectStack(ClipItem *); /** Enable or disable an effect */ void changeEffectState(ClipItem *, int, const QList &, bool); void changeMasterEffectState(QString id, const QList &, bool); /** An effect in stack was moved */ void changeEffectPosition(ClipItem *, int, const QList &, int); /** An effect in stack was moved for a Bin clip */ void changeEffectPosition(const QString &, const QList &, int); /** an effect was saved, reload list */ void reloadEffects(); /** An effect with position parameter was changed, seek */ void seekTimeline(int); /** The region effect for current effect was changed */ void updateClipRegion(ClipItem *, int, const QString &); void displayMessage(const QString &, int); void showComments(bool show); void startFilterJob(const ItemInfo &info, const QString &clipId, QMap &, QMap &, QMap &); void addEffect(ClipItem *, const QDomElement &, int); void importClipKeyframes(GraphicsRectItem, ItemInfo, const QDomElement &, const QMap &keyframes = QMap()); }; #endif diff --git a/src/effectstack/parametercontainer.h b/src/effectstack/parametercontainer.h index 225afd8a7..a262198d1 100644 --- a/src/effectstack/parametercontainer.h +++ b/src/effectstack/parametercontainer.h @@ -1,153 +1,153 @@ /*************************************************************************** * Copyright (C) 2012 by Jean-Baptiste Mardelle (jb@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) 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 PARAMETERCONTAINER_H #define PARAMETERCONTAINER_H #include "definitions.h" #include #include #include #include class GeometryWidget; class AnimationWidget; class Monitor; class DraggableLabel; class KeyframeEdit; namespace Mlt { } enum EFFECTMODE { EMPTY = 0, TIMELINE_CLIP, TIMELINE_TRACK, MASTER_CLIP, TIMELINE_TRANSITION }; struct EffectMetaInfo { Monitor *monitor; QPoint frameSize; double stretchFactor; EFFECTMODE status; }; enum WIPE_DIRECTON { UP = 0, DOWN = 1, LEFT = 2, RIGHT = 3, CENTER = 4 }; struct wipeInfo { WIPE_DIRECTON start; WIPE_DIRECTON end; int startTransparency; int endTransparency; }; class MySpinBox : public QSpinBox { Q_OBJECT public: explicit MySpinBox(QWidget *parent = nullptr); protected: void focusInEvent(QFocusEvent *) override; void focusOutEvent(QFocusEvent *) override; }; class ParameterContainer : public QObject { Q_OBJECT public: explicit ParameterContainer(const QDomElement &effect, const ItemInfo &info, EffectMetaInfo *metaInfo, QWidget *parent = nullptr); ~ParameterContainer(); void updateTimecodeFormat(); void updateParameter(const QString &key, const QString &value); /** @brief Returns true of this effect requires an on monitor adjustable effect scene. */ MonitorSceneType needsMonitorEffectScene() const; /** @brief Set keyframes for this param. */ void setKeyframes(const QString &tag, const QString &data); /** @brief Update the in / out for the clip. */ void setRange(int inPoint, int outPoint); /** @brief Returns the in / out for the clip. */ QPoint range() const; int contentHeight() const; /** @brief Update frame info (size, dar, ...). */ void refreshFrameInfo(); /** @brief Select active keyframe. */ void setActiveKeyframe(int frame); /** @brief The effect was selected / deselected, so we have to update monitor connections. */ void connectMonitor(bool activate); bool doesAcceptDrops() const; private slots: void slotCollectAllParameters(); void slotStartFilterJobAction(); void toggleSync(bool enable); /** @brief Copy parameter value to clipboard. */ void copyData(const QString &name); void makeDrag(const QString &name); private: /** @brief Updates parameter @param name according to new value of dependency. - * @param name Name of the parameter which will be updated - * @param type Type of the parameter which will be updated - * @param value Value of the dependency parameter */ + * @param name Name of the parameter which will be updated + * @param type Type of the parameter which will be updated + * @param value Value of the dependency parameter */ void meetDependency(const QString &name, const QString &type, const QString &value); wipeInfo getWipeInfo(QString value); QString getWipeString(wipeInfo info); /** @brief Delete all child widgets */ void clearLayout(QLayout *layout); int m_in; int m_out; ItemInfo m_info; QList m_uiItems; QMap m_valueItems; QList m_conditionalWidgets; KeyframeEdit *m_keyframeEditor; GeometryWidget *m_geometryWidget; AnimationWidget *m_animationWidget; EffectMetaInfo *m_metaInfo; QDomElement m_effect; QVBoxLayout *m_vbox; bool m_acceptDrops; MonitorSceneType m_monitorEffectScene; bool m_conditionParameter; signals: void parameterChanged(const QDomElement &, const QDomElement &, int); void syncEffectsPos(int); void disableCurrentFilter(bool); void checkMonitorPosition(int); void seekTimeline(int); void showComments(bool); void importKeyframes(const QString &); /** @brief Start an MLT filter job on this clip. * @param filterParams a QMap containing filter name under the "filter" key, and all filter properties * @param consumerParams a QMap containing consumer name under the "consumer" key, and all consumer properties * @param extraParams a QMap containing extra data used by the job */ void startFilterJob(QMap &filterParams, QMap &consumerParams, QMap &extraParams); /** @brief Request import of keyframes from clip data. */ void importClipKeyframes(); /** @brief Master clip was resized, update effect. */ void updateRange(int inPoint, int outPoint); /** @brief Request sending geometry info to monitor overlay. */ void initScene(int); void updateFrameInfo(const QPoint &size, double stretch); }; #endif diff --git a/src/effectstack/widgets/curves/abstractcurvewidget.h b/src/effectstack/widgets/curves/abstractcurvewidget.h index 4b434eaca..295d80848 100644 --- a/src/effectstack/widgets/curves/abstractcurvewidget.h +++ b/src/effectstack/widgets/curves/abstractcurvewidget.h @@ -1,161 +1,161 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef ABSTRACTCURVEWIDGET_H #define ABSTRACTCURVEWIDGET_H #include "bezier/bpoint.h" #include #include #include /** State of a point being moved */ enum class State_t { NORMAL, DRAG }; /** This class is a workaround to be able to use templating in the actual class Note that Qt doesn't support templated signals, so we have to define a signal for all possible Point type */ class __dummy_AbstractCurveWidget : public QWidget { Q_OBJECT public: __dummy_AbstractCurveWidget(QWidget *parent) : QWidget(parent) { } signals: /** * Emitted whenever a control point has changed position. */ void modified(void); /** Signal sent when the current point changes. The point is returned, as well as a flag that determines if the point is the first or last. */ void currentPoint(const QPointF &p, bool extremal); void currentPoint(const BPoint &p, bool extremal); void resized(const QSize &s); public slots: /** @brief Delete current spline point if it is not a extremal point (first or last) */ virtual void slotDeleteCurrentPoint() = 0; virtual void slotZoomIn() = 0; virtual void slotZoomOut() = 0; virtual void reset() = 0; }; /** @brief Base class of all the widgets representing a curve of points */ template class AbstractCurveWidget : public __dummy_AbstractCurveWidget { public: typedef typename Curve_t::Point_t Point_t; virtual ~AbstractCurveWidget(){}; /** @param parent Optional parent of the widget - */ + */ AbstractCurveWidget(QWidget *parent = nullptr); /** @brief Returns whether the points are controlled with additional handles */ virtual bool hasHandles() { return false; } /** @brief Sets the maximal number of points of the curve */ void setMaxPoints(int max); /** @brief Sets the background pixmap to @param pixmap. The background pixmap will be drawn under the grid and the curve*/ void setPixmap(const QPixmap &pixmap); /** @brief Number of lines used in grid. */ int gridLines() const; /** @brief Sets the number of grid lines to draw (in one direction) to @param lines. */ void setGridLines(int lines); /** @brief Constructs the curve from @param string */ void setFromString(const QString &string); /** @brief Resets the curve to an empty one */ void reset(); /** @brief Returns a string corresponding to the curve */ QString toString(); /** @brief Replaces current point with @param p (index stays the same). * @param final (default = true) emit signal modified? */ void updateCurrentPoint(const Point_t &p, bool final = true); /** @brief Returns the selected point or else empty point. */ Point_t getCurrentPoint(); /** @brief Returns the list of all the points. */ virtual QList getPoints() const = 0; public: /** @brief Delete current spline point if it is not a extremal point (first or last) */ void slotDeleteCurrentPoint(); void slotZoomIn(); void slotZoomOut(); protected: void paintBackground(QPainter *p); int heightForWidth(int w) const override; void resizeEvent(QResizeEvent *event) override; void leaveEvent(QEvent *) override; void mouseReleaseEvent(QMouseEvent *e) override; void keyPressEvent(QKeyEvent *) override; /** Utility function to check if current selected point is the first or the last */ bool isCurrentPointExtremal(); int m_zoomLevel; int m_gridLines; /** Background */ QPixmap m_pixmap; /** A copy of m_pixmap but scaled to fit the size of the edit region */ std::shared_ptr m_pixmapCache; /** Whether we have to regenerate the pixmap cache because the pixmap or the size of the edit region changed. */ bool m_pixmapIsDirty; int m_currentPointIndex; int m_maxPoints; int m_wWidth, m_wHeight; State_t m_state; Curve_t m_curve; double m_grabRadius; }; #include "abstractcurvewidget.ipp" #endif diff --git a/src/effectstack/widgets/curves/curveparamwidget.ipp b/src/effectstack/widgets/curves/curveparamwidget.ipp index 12b2fa8f0..23a0d6451 100644 --- a/src/effectstack/widgets/curves/curveparamwidget.ipp +++ b/src/effectstack/widgets/curves/curveparamwidget.ipp @@ -1,407 +1,399 @@ /*************************************************************************** * 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 "bezier/beziersplineeditor.h" #include "colortools.h" #include "cubic/kis_curve_widget.h" #include "effectstack/dragvalue.h" #include "utils/KoIconUtils.h" #include /*@brief this label is a pixmap corresponding to a legend of the axis*/ template class ValueLabel : public QLabel { public: /**@brief Creates the widget @param isVert This parameter is true if the widget is vertical @param mode This is the original mode @param parent Parent of the widget */ ValueLabel(bool isVert, typename CurveParamWidget::CurveModes mode, QWidget *parent) : QLabel(parent) , m_mode(mode) , m_isVert(isVert) { if (m_isVert) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); setMaximumSize(10, 500); setFixedWidth(10); } else { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setFixedHeight(10); } setScaledContents(true); } public slots: void setMode(typename CurveParamWidget::CurveModes m) { m_mode = m; createPixmap(); } private: using CurveModes = typename CurveParamWidget::CurveModes; void createPixmap() { QTransform t; QSize s = size(); if (!m_isVert) { t.rotate(90); s.setHeight(size().width()); s.setWidth(size().height()); } if (m_mode == CurveModes::Hue) { setPixmap(QPixmap::fromImage(ColorTools::hsvCurvePlane(s, QColor::fromHsv(200, 200, 200), ColorTools::COM_H, ColorTools::COM_H)).transformed(t)); } else if (m_mode == CurveModes::Saturation) { setPixmap(QPixmap()); } else { auto color = CurveParamWidget::modeToColorsRGB(m_mode); setPixmap(QPixmap::fromImage(ColorTools::rgbCurveLine(s, color, palette().background().color().rgb())).transformed(t)); } } typename CurveParamWidget::CurveModes m_mode; bool m_isVert; }; template <> void CurveParamWidget::slotUpdatePointP(double, bool final) { m_edit->updateCurrentPoint(QPointF(m_pX->value(), m_pY->value()), final); } template <> void CurveParamWidget::slotUpdatePointP(double, bool final) { BPoint p = m_edit->getCurrentPoint(); p.setP(QPointF(m_pX->value(), m_pY->value())); m_edit->updateCurrentPoint(p, final); } template <> void CurveParamWidget::slotUpdatePointH1(double /*value*/, bool final) { BPoint p = m_edit->getCurrentPoint(); p.setH1(QPointF(m_h1X->value(), m_h1Y->value())); m_edit->updateCurrentPoint(p, final); } -template void CurveParamWidget::slotUpdatePointH1(double /*value*/, bool /*final*/) -{ -} +template void CurveParamWidget::slotUpdatePointH1(double /*value*/, bool /*final*/) {} template <> void CurveParamWidget::slotUpdatePointH2(double /*value*/, bool final) { BPoint p = m_edit->getCurrentPoint(); p.setH2(QPointF(m_h2X->value(), m_h2Y->value())); m_edit->updateCurrentPoint(p, final); } -template void CurveParamWidget::slotUpdatePointH2(double /*value*/, bool /*final*/) -{ -} +template void CurveParamWidget::slotUpdatePointH2(double /*value*/, bool /*final*/) {} template <> void CurveParamWidget::slotSetHandlesLinked(bool linked) { BPoint p = m_edit->getCurrentPoint(); p.setHandlesLinked(linked); m_edit->updateCurrentPoint(p); } -template void CurveParamWidget::slotSetHandlesLinked(bool /*linked*/) -{ -} +template void CurveParamWidget::slotSetHandlesLinked(bool /*linked*/) {} template <> void CurveParamWidget::slotShowAllHandles(bool show) { m_edit->setShowAllHandles(show); KdenliveSettings::setBezier_showallhandles(show); } -template void CurveParamWidget::slotShowAllHandles(bool /*show*/) -{ -} +template void CurveParamWidget::slotShowAllHandles(bool /*show*/) {} template CurveParamWidget::CurveParamWidget(const QString &spline, QWidget *parent) : AbstractParamWidget(parent) , m_mode(CurveModes::Luma) , m_showPixmap(KdenliveSettings::bezier_showpixmap()) { // construct curve editor m_edit = new CurveWidget_t(this); connect(m_edit, &CurveWidget_t::modified, this, &AbstractParamWidget::valueChanged); using Point_t = typename CurveWidget_t::Point_t; connect(m_edit, static_cast(&CurveWidget_t::currentPoint), this, static_cast::*)(const Point_t &, bool)>(&CurveParamWidget::slotUpdatePointEntries)); // construct and fill layout QVBoxLayout *layout = new QVBoxLayout(this); // grid layout containing the curve and the optional param values QGridLayout *curve_layout = new QGridLayout(); curve_layout->addWidget(m_edit, 0, 1); m_leftParam = new ValueLabel(true, m_mode, this); m_leftParam->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); m_leftParam->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); curve_layout->addWidget(m_leftParam, 0, 0); m_bottomParam = new ValueLabel(false, m_mode, this); m_bottomParam->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); m_bottomParam->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); curve_layout->addWidget(m_bottomParam, 1, 1); // horizontal layout to make sure that everything is centered QHBoxLayout *horiz_layout = new QHBoxLayout; horiz_layout->addLayout(curve_layout); layout->addLayout(horiz_layout); QWidget *widget = new QWidget(this); m_ui.setupUi(widget); layout->addWidget(widget); // set up icons and initial button states m_ui.buttonLinkHandles->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-link"))); m_ui.buttonDeletePoint->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-remove"))); m_ui.buttonZoomIn->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-in"))); m_ui.buttonZoomOut->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-out"))); m_ui.buttonGridChange->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-grid"))); m_ui.buttonShowPixmap->setIcon(QIcon(QPixmap::fromImage(ColorTools::rgbCurvePlane(QSize(16, 16), ColorTools::ColorsRGB::Luma, 0.8)))); m_ui.buttonResetSpline->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); m_ui.buttonShowAllHandles->setIcon(KoIconUtils::themedIcon(QStringLiteral("draw-bezier-curves"))); m_ui.widgetPoint->setEnabled(false); m_edit->setGridLines(KdenliveSettings::bezier_gridlines()); m_ui.buttonShowPixmap->setChecked(KdenliveSettings::bezier_showpixmap()); m_ui.buttonShowAllHandles->setChecked(KdenliveSettings::bezier_showallhandles()); slotShowAllHandles(KdenliveSettings::bezier_showallhandles()); // connect buttons to their slots connect(m_ui.buttonLinkHandles, &QAbstractButton::toggled, this, &CurveParamWidget::slotSetHandlesLinked); connect(m_ui.buttonDeletePoint, &QAbstractButton::clicked, m_edit, &CurveWidget_t::slotDeleteCurrentPoint); connect(m_ui.buttonZoomIn, &QAbstractButton::clicked, m_edit, &CurveWidget_t::slotZoomIn); connect(m_ui.buttonZoomOut, &QAbstractButton::clicked, m_edit, &CurveWidget_t::slotZoomOut); connect(m_ui.buttonGridChange, &QAbstractButton::clicked, this, &CurveParamWidget::slotGridChange); connect(m_ui.buttonShowPixmap, &QAbstractButton::toggled, this, &CurveParamWidget::slotShowPixmap); connect(m_ui.buttonResetSpline, &QAbstractButton::clicked, m_edit, &CurveWidget_t::reset); connect(m_ui.buttonShowAllHandles, &QAbstractButton::toggled, this, &CurveParamWidget::slotShowAllHandles); setupLayoutPoint(); setupLayoutHandles(); m_edit->setFromString(spline); deleteIrrelevantItems(); emit valueChanged(); } template <> void CurveParamWidget::deleteIrrelevantItems() { m_ui.gridLayout->removeWidget(m_ui.buttonShowAllHandles); delete m_ui.buttonShowAllHandles; } template void CurveParamWidget::deleteIrrelevantItems() { // Nothing to do in general } template void CurveParamWidget::setupLayoutPoint() { m_pX = new DragValue(i18n("In"), 0, 3, 0, 1, -1, QString(), false, this); m_pX->setStep(0.001); m_pY = new DragValue(i18n("Out"), 0, 3, 0, 1, -1, QString(), false, this); m_pY->setStep(0.001); m_ui.layoutP->addWidget(m_pX); m_ui.layoutP->addWidget(m_pY); connect(m_pX, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointP); connect(m_pY, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointP); } template <> void CurveParamWidget::setupLayoutHandles() { m_h1X = new DragValue(i18n("X"), 0, 3, -2, 2, -1, QString(), false, this); m_h1X->setStep(0.001); m_h1Y = new DragValue(i18n("Y"), 0, 3, -2, 2, -1, QString(), false, this); m_h1Y->setStep(0.001); m_h2X = new DragValue(i18n("X"), 0, 3, -2, 2, -1, QString(), false, this); m_h2X->setStep(0.001); m_h2Y = new DragValue(i18n("Y"), 0, 3, -2, 2, -1, QString(), false, this); m_h2Y->setStep(0.001); m_ui.layoutH1->addWidget(new QLabel(i18n("Handle 1:"))); m_ui.layoutH1->addWidget(m_h1X); m_ui.layoutH1->addWidget(m_h1Y); m_ui.layoutH2->addWidget(new QLabel(i18n("Handle 2:"))); m_ui.layoutH2->addWidget(m_h2X); m_ui.layoutH2->addWidget(m_h2Y); connect(m_h1X, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointH1); connect(m_h1Y, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointH1); connect(m_h2X, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointH2); connect(m_h2Y, &DragValue::valueChanged, this, &CurveParamWidget::slotUpdatePointH2); } template void CurveParamWidget::setupLayoutHandles() { // Nothing to do in general } template QString CurveParamWidget::toString() const { return m_edit->toString(); } template void CurveParamWidget::setMode(CurveModes mode) { if (m_mode != mode) { m_mode = mode; if (m_showPixmap) { slotShowPixmap(true); } m_leftParam->setMode(mode); m_bottomParam->setMode(mode); } } template void CurveParamWidget::slotGridChange() { m_edit->setGridLines((m_edit->gridLines() + 1) % 9); KdenliveSettings::setBezier_gridlines(m_edit->gridLines()); } template ColorTools::ColorsRGB CurveParamWidget::modeToColorsRGB(CurveModes mode) { switch (mode) { case CurveModes::Red: return ColorTools::ColorsRGB::R; case CurveModes::Green: return ColorTools::ColorsRGB::G; case CurveModes::Blue: return ColorTools::ColorsRGB::B; case CurveModes::Luma: return ColorTools::ColorsRGB::Luma; case CurveModes::Alpha: return ColorTools::ColorsRGB::A; case CurveModes::RGB: case CurveModes::Hue: case CurveModes::Saturation: default: return ColorTools::ColorsRGB::RGB; } return ColorTools::ColorsRGB::RGB; } template void CurveParamWidget::slotShowPixmap(bool show) { m_showPixmap = show; KdenliveSettings::setBezier_showpixmap(show); if (show) { if (m_mode == CurveModes::Hue) { m_edit->setPixmap( QPixmap::fromImage(ColorTools::hsvCurvePlane(m_edit->size(), QColor::fromHsv(200, 200, 200), ColorTools::COM_H, ColorTools::COM_H))); } else if (m_mode == CurveModes::Saturation) { m_edit->setPixmap(QPixmap()); } else { auto color = modeToColorsRGB(m_mode); m_edit->setPixmap(QPixmap::fromImage(ColorTools::rgbCurvePlane(m_edit->size(), color, 1, palette().background().color().rgb()))); } } else { m_edit->setPixmap(QPixmap()); } } template <> void CurveParamWidget::slotUpdatePointEntries(const BPoint &p, bool extremal) { blockSignals(true); if (p == BPoint()) { m_ui.widgetPoint->setEnabled(false); } else { m_ui.widgetPoint->setEnabled(true); // disable irrelevant buttons if the point is extremal m_pX->setEnabled(!extremal); m_ui.buttonDeletePoint->setEnabled(!extremal); m_ui.buttonLinkHandles->setEnabled(!extremal); if (extremal && p.p.x() + 1e-4 >= 1.00) { // last point m_h2X->setEnabled(false); m_h2Y->setEnabled(false); } else { m_h2X->setEnabled(true); m_h2Y->setEnabled(true); } if (extremal && p.p.x() <= 0.01) { // first point m_h1X->setEnabled(false); m_h1Y->setEnabled(false); } else { m_h1X->setEnabled(true); m_h1Y->setEnabled(true); } for (auto elem : {m_pX, m_pY, m_h1X, m_h1Y, m_h2X, m_h2Y}) { elem->blockSignals(true); } m_pX->setValue(p.p.x()); m_pY->setValue(p.p.y()); m_h1X->setValue(p.h1.x()); m_h1Y->setValue(p.h1.y()); m_h2X->setValue(p.h2.x()); m_h2Y->setValue(p.h2.y()); for (auto elem : {m_pX, m_pY, m_h1X, m_h1Y, m_h2X, m_h2Y}) { elem->blockSignals(false); } m_ui.buttonLinkHandles->setChecked(p.handlesLinked); } blockSignals(false); } template void CurveParamWidget::slotUpdatePointEntries(const BPoint &p, bool extremal) { // Wrong slot called in curve widget Q_ASSERT(false); } template <> void CurveParamWidget::slotUpdatePointEntries(const QPointF &p, bool extremal) { blockSignals(true); if (p == QPointF()) { m_ui.widgetPoint->setEnabled(false); } else { m_ui.widgetPoint->setEnabled(true); // disable irrelevant buttons if the point is extremal m_pX->setEnabled(!extremal); m_ui.buttonDeletePoint->setEnabled(!extremal); for (auto elem : {m_pX, m_pY}) { elem->blockSignals(true); } m_pX->setValue(p.x()); m_pY->setValue(p.y()); for (auto elem : {m_pX, m_pY}) { elem->blockSignals(false); } } blockSignals(false); } template void CurveParamWidget::slotUpdatePointEntries(const QPointF &p, bool extremal) { // Wrong slot called in curve widget Q_ASSERT(false); } template void CurveParamWidget::slotShowComment(bool show) { Q_UNUSED(show); } template void CurveParamWidget::setMaxPoints(int max) { m_edit->setMaxPoints(max); } diff --git a/src/effectstack/widgets/geometrywidget.h b/src/effectstack/widgets/geometrywidget.h index 4820916ea..277fc1192 100644 --- a/src/effectstack/widgets/geometrywidget.h +++ b/src/effectstack/widgets/geometrywidget.h @@ -1,205 +1,205 @@ /*************************************************************************** * 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 GEOMETRYWIDGET_H #define GEOMETRYWIDGET_H #include "abstractparamwidget.h" #include "timecode.h" #include "ui_geometrywidget_ui.h" #include #include class QDomElement; class Monitor; class KeyframeHelper; class TimecodeDisplay; class QGraphicsRectItem; class DragValue; struct EffectMetaInfo; class GeometryWidget : public AbstractParamWidget { Q_OBJECT public: /** @brief Sets up the UI and connects it. - * @param monitor Project monitor - * @param timecode Timecode needed by timecode display widget - * @param clipPos Position of the clip in timeline - * @param parent (optional) Parent widget */ + * @param monitor Project monitor + * @param timecode Timecode needed by timecode display widget + * @param clipPos Position of the clip in timeline + * @param parent (optional) Parent widget */ explicit GeometryWidget(EffectMetaInfo *info, int clipPos, bool showRotation, bool useOffset, QWidget *parent = nullptr); virtual ~GeometryWidget(); /** @brief Gets the geometry as a serialized string. */ QString getValue() const; QString getExtraValue(const QString &name) const; /** @brief Updates the timecode display according to settings (frame number or hh:mm:ss:ff) */ void updateTimecodeFormat(); /** @brief Sets the size of the original clip. */ void setFrameSize(const QPoint &size); void addParameter(const QDomElement &elem); void importKeyframes(const QString &data, int maximum); QString offsetAnimation(int offset, bool useOffset); /** @brief Effect param @tag was changed, reload keyframes */ void reload(const QString &tag, const QString &data); /** @brief connect with monitor scene */ void connectMonitor(bool activate); public slots: /** @brief Sets up the rect and the geometry object. - * @param elem DomElement representing this effect parameter - * @param minframe In point of the clip - * @param maxframe Out point of the clip */ + * @param elem DomElement representing this effect parameter + * @param minframe In point of the clip + * @param maxframe Out point of the clip */ void setupParam(const QDomElement &elem, int minframe, int maxframe); /** @brief Updates position of the local timeline to @param relTimelinePos. */ void slotSyncPosition(int relTimelinePos); void slotResetKeyframes(); void slotResetNextKeyframes(); void slotResetPreviousKeyframes(); void slotUpdateRange(int inPoint, int outPoint); /** @brief Send geometry info to the monitor. */ void slotInitScene(int pos); void slotSeekToKeyframe(int index); private: Ui::GeometryWidget_UI m_ui; Monitor *m_monitor; TimecodeDisplay *m_timePos; /** Position of the clip in timeline. */ int m_clipPos; /** In point of the clip (crop from start). */ int m_inPoint; /** Out point of the clip (crop from end). */ int m_outPoint; QGraphicsRectItem *m_previous; KeyframeHelper *m_timeline; /** Stores the different settings in the MLT geometry format. */ Mlt::Geometry *m_geometry; QStringList m_extraGeometryNames; QStringList m_extraFactors; QList m_extraGeometries; DragValue *m_spinX; DragValue *m_spinY; DragValue *m_spinWidth; DragValue *m_spinHeight; DragValue *m_spinSize; DragValue *m_opacity; DragValue *m_rotateX; DragValue *m_rotateY; DragValue *m_rotateZ; QPoint m_frameSize; /** @brief Action switching between profile and source size. */ QAction *m_originalSize; /** @brief Action locking image ratio. */ QAction *m_lockRatio; /** @brief True if this is a fixed parameter (no kexframes allowed). */ bool m_fixedGeom; /** @brief True if there is only one keyframe in this geometry. */ bool m_singleKeyframe; /** @brief Should we use an offset when displaying keyframes (when effect is not synced with producer). */ bool m_useOffset; /** @brief Update monitor rect with current width / height values. */ void updateMonitorGeometry(); /** @brief Calculate the path for rectangle center moves. */ QVariantList calculateCenters(); /** @brief check if we have only one keyframe for this geometry. */ void checkSingleKeyframe(); private slots: /** @brief Updates controls according to position. - * @param pos (optional) Position to update to - * @param seek (optional, default = true) Whether to seek timleine & project monitor to pos - * If pos = -1 (default) the value of m_timePos is used. */ + * @param pos (optional) Position to update to + * @param seek (optional, default = true) Whether to seek timleine & project monitor to pos + * If pos = -1 (default) the value of m_timePos is used. */ void slotPositionChanged(int pos = -1, bool seek = true); /** @brief Seeking requested from timeline. */ void slotRequestSeek(int pos); /** @brief Updates settings after a keyframe was moved to @param pos. */ void slotKeyframeMoved(int pos); /** @brief Adds a keyframe. - * @param pos (optional) Position where the keyframe should be added - * If pos = -1 (default) the value of m_timePos is used. */ + * @param pos (optional) Position where the keyframe should be added + * If pos = -1 (default) the value of m_timePos is used. */ void slotAddKeyframe(int pos = -1); /** @brief Deletes a keyframe. - * @param pos (optional) Position of the keyframe which should be deleted - * If pos = -1 (default) the value of m_timePos is used. */ + * @param pos (optional) Position of the keyframe which should be deleted + * If pos = -1 (default) the value of m_timePos is used. */ void slotDeleteKeyframe(int pos = -1); /** @brief Goes to the next keyframe or to the end if none is available. */ void slotNextKeyframe(); /** @brief Goes to the previous keyframe or to the beginning if none is available. */ void slotPreviousKeyframe(); /** @brief Adds or deletes a keyframe depending on whether there is already a keyframe at the current position. */ void slotAddDeleteKeyframe(); /** @brief Updates the Mlt::Geometry object. */ void slotUpdateGeometry(); void slotUpdateGeometryRect(const QRect r); /** @brief Updates the spinBoxes according to the rect. */ void slotUpdateProperties(QRect rect = QRect()); /** @brief Sets the rect's x position to @param value. */ void slotSetX(double value); /** @brief Sets the rect's y position to @param value. */ void slotSetY(double value); /** @brief Sets the rect's width to @param value. */ void slotSetWidth(double value); /** @brief Sets the rect's height to @param value. */ void slotSetHeight(double value); /** @brief Resizes the rect by @param value (in perecent) compared to the frame size. */ void slotResize(double value); /** @brief Sets the opacity to @param value. */ void slotSetOpacity(double value); /** @brief Moves the rect to the left frame border (x position = 0). */ void slotMoveLeft(); /** @brief Centers the rect horizontally. */ void slotCenterH(); /** @brief Moves the rect to the right frame border (x position = frame width - rect width). */ void slotMoveRight(); /** @brief Moves the rect to the top frame border (y position = 0). */ void slotMoveTop(); /** @brief Centers the rect vertically. */ void slotCenterV(); /** @brief Moves the rect to the bottom frame border (y position = frame height - rect height). */ void slotMoveBottom(); /** @brief Enables/Disables syncing with the timeline according to @param sync. */ void slotSetSynchronize(bool sync); /** @brief Adjust size to source clip original resolution*/ void slotAdjustToSource(); /** @brief Adjust size to project's frame size */ void slotAdjustToFrameSize(); void slotFitToWidth(); void slotFitToHeight(); /** @brief Show / hide previous keyframe in monitor scene. */ void slotShowPreviousKeyFrame(bool show); /** @brief Show / hide keyframe path in monitor scene. */ void slotShowPath(bool show); /** @brief Edit center points for the geometry keyframes. */ void slotUpdateCenters(const QVariantList ¢ers); /** @brief Un/Lock aspect ratio for size in effect parameter. */ void slotLockRatio(); signals: void seekToPos(int); void importClipKeyframes(); }; #endif diff --git a/src/effectstack/widgets/lumaliftgain.h b/src/effectstack/widgets/lumaliftgain.h index ef15151bc..f172158f3 100644 --- a/src/effectstack/widgets/lumaliftgain.h +++ b/src/effectstack/widgets/lumaliftgain.h @@ -1,57 +1,57 @@ /*************************************************************************** * Copyright (C) 2014 by Jean-Baptiste Mardelle (jb@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) 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 LUMALIFTGAINWIDGET_H #define LUMALIFTGAINWIDGET_H #include #include #include class ColorWheel; /** * @class ChooseColorWidget * @brief Provides options to choose 3 colors. * @author Jean-Baptiste Mardelle */ class LumaLiftGain : public QWidget { Q_OBJECT public: /** @brief Sets up the widget. - * @param text (optional) What the color will be used for - * @param color (optional) initial color - * @param alphaEnabled (optional) Should transparent colors be enabled */ + * @param text (optional) What the color will be used for + * @param color (optional) initial color + * @param alphaEnabled (optional) Should transparent colors be enabled */ explicit LumaLiftGain(const QDomNodeList &nodes, QWidget *parent = nullptr); void updateEffect(QDomElement &effect); private: QLocale m_locale; ColorWheel *m_lift; ColorWheel *m_gamma; ColorWheel *m_gain; signals: /** @brief Emitted whenever a different color was chosen. */ void valueChanged(); }; #endif diff --git a/src/effectstack/widgets/selectivecolor.h b/src/effectstack/widgets/selectivecolor.h index 564cba322..898c8e049 100644 --- a/src/effectstack/widgets/selectivecolor.h +++ b/src/effectstack/widgets/selectivecolor.h @@ -1,62 +1,62 @@ /* Copyright (C) 2016 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 . */ #ifndef SELECTIVECOLORWIDGET_H #define SELECTIVECOLORWIDGET_H #include "ui_selectivecolor_ui.h" #include #include #include /** * @class SelectiveColor * @brief Provides options to adjust CMYK factor of a color range. * @author Jean-Baptiste Mardelle */ class SelectiveColor : public QWidget, public Ui::SelectiveColor { Q_OBJECT public: /** @brief Sets up the widget. - * @param text (optional) What the color will be used for - * @param color (optional) initial color - * @param alphaEnabled (optional) Should transparent colors be enabled */ + * @param text (optional) What the color will be used for + * @param color (optional) initial color + * @param alphaEnabled (optional) Should transparent colors be enabled */ explicit SelectiveColor(const QDomElement &effect, QWidget *parent = nullptr); ~SelectiveColor(); void addParam(QDomElement &effect, QString name); void updateEffect(QDomElement &effect); private: QLocale m_locale; private slots: void updateValues(); void effectChanged(); signals: /** @brief Emitted whenever a different color was chosen. */ void valueChanged(); }; #endif diff --git a/src/gentime.h b/src/gentime.h index 661949832..53b6174a1 100644 --- a/src/gentime.h +++ b/src/gentime.h @@ -1,106 +1,106 @@ /*************************************************************************** time.h - description ------------------- begin : Sat Sep 14 2002 copyright : (C) 2002 by Jason Wood email : jasonwood@blueyonder.co.uk ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifndef GENTIME_H #define GENTIME_H #include #include /** * @class GenTime * @brief Encapsulates a time, which can be set in various forms and outputted in various forms. * @author Jason Wood */ class GenTime { public: /** @brief Creates a GenTime object, with a time of 0 seconds. */ GenTime(); /** @brief Creates a GenTime object, with time given in seconds. */ explicit GenTime(double seconds); /** @brief Creates a GenTime object, by passing number of frames and how many frames per second. */ GenTime(int frames, double framesPerSecond); /** @brief Gets the time, in seconds. */ double seconds() const; /** @brief Gets the time, in milliseconds */ double ms() const; /** @brief Gets the time in frames. - * @param framesPerSecond Number of frames per second */ + * @param framesPerSecond Number of frames per second */ int frames(double framesPerSecond) const; QString toString() const; /* * Operators. */ /// Unary minus GenTime operator-(); /// Addition GenTime &operator+=(GenTime op); /// Subtraction GenTime &operator-=(GenTime op); /** @brief Adds two GenTimes. */ GenTime operator+(GenTime op) const; /** @brief Subtracts one genTime from another. */ GenTime operator-(GenTime op) const; /** @brief Multiplies one GenTime by a double value, returning a GenTime. */ GenTime operator*(double op) const; /** @brief Divides one GenTime by a double value, returning a GenTime. */ GenTime operator/(double op) const; /* All the comparison operators considers that two GenTime that differs by less than one frame are equal. The fps used to carry this computation must be set using the static function setFps */ bool operator<(GenTime op) const; bool operator>(GenTime op) const; bool operator>=(GenTime op) const; bool operator<=(GenTime op) const; bool operator==(GenTime op) const; bool operator!=(GenTime op) const; /** @brief Sets the fps used to determine if two GenTimes are equal */ static void setFps(double fps); private: /** Holds the time in seconds for this object. */ double m_time; /** A delta value that is used to get around floating point rounding issues. */ static double s_delta; }; #endif diff --git a/src/jobs/audiothumbjob.cpp b/src/jobs/audiothumbjob.cpp index 82b75daa0..3cf387f3e 100644 --- a/src/jobs/audiothumbjob.cpp +++ b/src/jobs/audiothumbjob.cpp @@ -1,339 +1,340 @@ /*************************************************************************** * 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 "audiothumbjob.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "klocalizedstring.h" #include "lib/audio/audioStreamInfo.h" #include "macros.hpp" #include "utils/thumbnailcache.hpp" #include #include #include #include #include #include AudioThumbJob::AudioThumbJob(const QString &binId) : AbstractClipJob(AUDIOTHUMBJOB, binId) , m_ffmpegProcess(nullptr) { } const QString AudioThumbJob::getDescription() const { return i18n("Extracting audio thumb from clip %1", m_clipId); } bool AudioThumbJob::computeWithMlt() { m_audioLevels.clear(); // MLT audio thumbs: slower but safer QString service = m_prod->get("mlt_service"); if (service == QLatin1String("avformat-novalidate")) { service = QStringLiteral("avformat"); } else if (service.startsWith(QLatin1String("xml"))) { service = QStringLiteral("xml-nogl"); } QScopedPointer audioProducer(new Mlt::Producer(*m_prod->profile(), service.toUtf8().constData(), m_prod->get("resource"))); if (!audioProducer->is_valid()) { return false; } audioProducer->set("video_index", "-1"); Mlt::Filter chans(*m_prod->profile(), "audiochannels"); Mlt::Filter converter(*m_prod->profile(), "audioconvert"); Mlt::Filter levels(*m_prod->profile(), "audiolevel"); audioProducer->attach(chans); audioProducer->attach(converter); audioProducer->attach(levels); int last_val = 0; double framesPerSecond = audioProducer->get_fps(); mlt_audio_format audioFormat = mlt_audio_s16; QStringList keys; keys.reserve(m_channels); for (int i = 0; i < m_channels; i++) { keys << "meta.media.audio_level." + QString::number(i); } for (int z = 0; z < m_lengthInFrames; ++z) { int val = (int)(100.0 * z / m_lengthInFrames); if (last_val != val) { emit jobProgress(val); last_val = val; } QScopedPointer mltFrame(audioProducer->get_frame()); if ((mltFrame != nullptr) && mltFrame->is_valid() && (mltFrame->get_int("test_audio") == 0)) { int samples = mlt_sample_calculator(float(framesPerSecond), m_frequency, z); mltFrame->get_audio(audioFormat, m_frequency, m_channels, samples); for (int channel = 0; channel < m_channels; ++channel) { double level = 256 * qMin(mltFrame->get_double(keys.at(channel).toUtf8().constData()) * 0.9, 1.0); m_audioLevels << level; } } else if (!m_audioLevels.isEmpty()) { for (int channel = 0; channel < m_channels; channel++) { m_audioLevels << m_audioLevels.last(); } } } m_done = true; return true; } bool AudioThumbJob::computeWithFFMPEG() { m_audioLevels.clear(); QStringList args; std::vector> channelFiles; for (int i = 0; i < m_channels; i++) { std::unique_ptr channelTmpfile(new QTemporaryFile()); if (!channelTmpfile->open()) { m_errorMessage.append(i18n("Cannot create temporary file, check disk space and permissions\n")); return false; } channelTmpfile->close(); channelFiles.emplace_back(std::move(channelTmpfile)); } args << QStringLiteral("-i") << QUrl::fromLocalFile(m_prod->get("resource")).toLocalFile(); // Output progress info args << QStringLiteral("-progress"); #ifdef Q_OS_WIN args << QStringLiteral("-"); #else args << QStringLiteral("/dev/stdout"); #endif bool isFFmpeg = KdenliveSettings::ffmpegpath().contains(QLatin1String("ffmpeg")); args << QStringLiteral("-filter_complex:a"); if (m_channels == 1) { args << QStringLiteral("aformat=channel_layouts=mono,%1=100").arg(isFFmpeg ? "aresample=async" : "sample_rates"); args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(m_audioStream > 0 ? ":" + QString::number(m_audioStream) : QString()) << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data") << channelFiles[0]->fileName(); } else { QString aformat = QStringLiteral("[0:a%1]%2=100,channelsplit=channel_layout=%3") .arg(m_audioStream > 0 ? ":" + QString::number(m_audioStream) : QString()) - .arg(isFFmpeg ? "aresample=async" : "aformat=sample_rates=").arg(m_channels > 2 ? "5.1" : "stereo"); + .arg(isFFmpeg ? "aresample=async" : "aformat=sample_rates=") + .arg(m_channels > 2 ? "5.1" : "stereo"); for (int i = 0; i < m_channels; ++i) { aformat.append(QStringLiteral("[0:%1]").arg(i)); } args << aformat; for (int i = 0; i < m_channels; i++) { // Channel 1 args << QStringLiteral("-map") << QStringLiteral("[0:%1]").arg(i) << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data") << channelFiles[size_t(i)]->fileName(); } } m_ffmpegProcess = new QProcess; m_ffmpegProcess->start(KdenliveSettings::ffmpegpath(), args); connect(m_ffmpegProcess, &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress); m_ffmpegProcess->waitForFinished(-1); if (m_ffmpegProcess->exitStatus() != QProcess::CrashExit) { int dataSize = 0; std::vector rawChannels; std::vector sourceChannels; for (size_t i = 0; i < channelFiles.size(); i++) { channelFiles[i]->open(); sourceChannels.emplace_back(channelFiles[i]->readAll()); QByteArray &res = sourceChannels.back(); channelFiles[i]->close(); if (dataSize == 0) { dataSize = res.size(); } if (res.isEmpty() || res.size() != dataSize) { // Something went wrong, abort m_errorMessage.append(i18n("Error reading audio thumbnail created with FFMPEG\n")); return false; } rawChannels.emplace_back((const qint16 *)res.constData()); } int progress = 0; std::vector channelsData; double offset = (double)dataSize / (2.0 * m_lengthInFrames); int intraOffset = 1; if (offset > 1000) { intraOffset = offset / 60; } else if (offset > 250) { intraOffset = offset / 10; } double factor = 800.0 / 32768; for (int i = 0; i < m_lengthInFrames; i++) { channelsData.resize((size_t)rawChannels.size()); std::fill(channelsData.begin(), channelsData.end(), 0); int pos = (int)(i * offset); int steps = 0; for (int j = 0; j < (int)offset && (pos + j < dataSize); j += intraOffset) { steps++; for (size_t k = 0; k < rawChannels.size(); k++) { channelsData[k] += abs(rawChannels[k][pos + j]); } } for (size_t k = 0; k < channelsData.size(); k++) { if (steps != 0) { channelsData[k] /= steps; } m_audioLevels << channelsData[k] * factor; } int p = 80 + (i * 20 / m_lengthInFrames); if (p != progress) { emit jobProgress(p); progress = p; } } m_done = true; return true; } QString err = m_ffmpegProcess->readAllStandardError(); delete m_ffmpegProcess; m_errorMessage += err; m_errorMessage.append(i18n("Failed to create FFmpeg audio thumbnails, we now try to use MLT")); return false; } void AudioThumbJob::updateFfmpegProgress() { QString result = m_ffmpegProcess->readAllStandardOutput(); const QStringList lines = result.split(QLatin1Char('\n')); for (const QString &data : lines) { if (data.startsWith(QStringLiteral("out_time_ms"))) { double ms = data.section(QLatin1Char('='), 1).toDouble(); - emit jobProgress((int) (ms / m_binClip->duration().ms() / 10)); + emit jobProgress((int)(ms / m_binClip->duration().ms() / 10)); } else { m_logDetails += data + QStringLiteral("\n"); } } } bool AudioThumbJob::startJob() { if (m_done) { return true; } m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); if (m_binClip->audioChannels() == 0 || m_binClip->audioThumbCreated()) { // nothing to do m_done = true; m_successful = true; return true; } m_prod = m_binClip->originalProducer(); m_frequency = m_binClip->audioInfo()->samplingRate(); m_frequency = m_frequency <= 0 ? 48000 : m_frequency; m_channels = m_binClip->audioInfo()->channels(); m_channels = m_channels <= 0 ? 2 : m_channels; m_lengthInFrames = m_prod->get_length(); m_audioStream = m_binClip->audioInfo()->ffmpeg_audio_index(); if ((m_prod == nullptr) || !m_prod->is_valid()) { m_done = true; m_successful = false; return false; } m_cachePath = m_binClip->getAudioThumbPath(); // checking for cached thumbs QImage image(m_cachePath); if (!image.isNull()) { // convert cached image int n = image.width() * image.height(); for (int i = 0; i < n; i++) { QRgb p = image.pixel(i / m_channels, i % m_channels); m_audioLevels << qRed(p); m_audioLevels << qGreen(p); m_audioLevels << qBlue(p); m_audioLevels << qAlpha(p); } } if (!m_audioLevels.isEmpty()) { m_done = true; m_successful = true; return true; } bool ok = m_binClip->clipType() == ClipType::Playlist ? false : computeWithFFMPEG(); ok = ok ? ok : computeWithMlt(); Q_ASSERT(ok == m_done); if (ok && m_done && !m_audioLevels.isEmpty()) { // Put into an image for caching. int count = m_audioLevels.size(); image = QImage((int)lrint((count + 3) / 4.0 / m_channels), m_channels, QImage::Format_ARGB32); int n = image.width() * image.height(); for (int i = 0; i < n; i++) { QRgb p; if ((4 * i + 3) < count) { p = qRgba(m_audioLevels.at(4 * i).toInt(), m_audioLevels.at(4 * i + 1).toInt(), m_audioLevels.at(4 * i + 2).toInt(), m_audioLevels.at(4 * i + 3).toInt()); } else { int last = m_audioLevels.last().toInt(); int r = (4 * i + 0) < count ? m_audioLevels.at(4 * i + 0).toInt() : last; int g = (4 * i + 1) < count ? m_audioLevels.at(4 * i + 1).toInt() : last; int b = (4 * i + 2) < count ? m_audioLevels.at(4 * i + 2).toInt() : last; int a = last; p = qRgba(r, g, b, a); } image.setPixel(i / m_channels, i % m_channels, p); } image.save(m_cachePath); m_successful = true; return true; } m_done = true; m_successful = false; return false; } bool AudioThumbJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; return false; } m_resultConsumed = true; if (!m_successful) { return false; } QVariantList old = m_binClip->audioFrameCache; // note that the image is moved into lambda, it won't be available from this class anymore auto operation = [ clip = m_binClip, audio = std::move(m_audioLevels) ]() { clip->updateAudioThumbnail(audio); return true; }; auto reverse = [ clip = m_binClip, audio = std::move(old) ]() { clip->updateAudioThumbnail(audio); return true; }; bool ok = operation(); if (ok) { UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } return ok; } diff --git a/src/jobs/jobmanager.cpp b/src/jobs/jobmanager.cpp index aaf76434a..2c5821aee 100644 --- a/src/jobs/jobmanager.cpp +++ b/src/jobs/jobmanager.cpp @@ -1,385 +1,385 @@ /* Copyright (C) 2014 Jean-Baptiste Mardelle Copyright (C) 2017 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 "jobmanager.h" -#include "bin/projectitemmodel.h" #include "bin/abstractprojectitem.h" +#include "bin/projectitemmodel.h" #include "core.h" #include "macros.hpp" #include "undohelper.hpp" #include #include #include int JobManager::m_currentId = 0; JobManager::JobManager(QObject *parent) : QAbstractListModel(parent) , m_lock(QReadWriteLock::Recursive) { } JobManager::~JobManager() { slotCancelJobs(); } int JobManager::getBlockingJobId(const QString &id, AbstractClipJob::JOBTYPE type) { READ_LOCK(); std::vector result; if (m_jobsByClip.count(id) > 0) { for (int jobId : m_jobsByClip.at(id)) { if (!m_jobs.at(jobId)->m_future.isFinished() && !m_jobs.at(jobId)->m_future.isCanceled()) { if (type == AbstractClipJob::NOJOBTYPE || m_jobs.at(jobId)->m_type == type) { return jobId; } } } } return -1; } std::vector JobManager::getPendingJobsIds(const QString &id, AbstractClipJob::JOBTYPE type) { READ_LOCK(); std::vector result; if (m_jobsByClip.count(id) > 0) { for (int jobId : m_jobsByClip.at(id)) { if (!m_jobs.at(jobId)->m_future.isFinished() && !m_jobs.at(jobId)->m_future.isCanceled()) { if (type == AbstractClipJob::NOJOBTYPE || m_jobs.at(jobId)->m_type == type) { result.push_back(jobId); } } } } return result; } std::vector JobManager::getFinishedJobsIds(const QString &id, AbstractClipJob::JOBTYPE type) { READ_LOCK(); std::vector result; if (m_jobsByClip.count(id) > 0) { for (int jobId : m_jobsByClip.at(id)) { if (m_jobs.at(jobId)->m_future.isFinished() || m_jobs.at(jobId)->m_future.isCanceled()) { if (type == AbstractClipJob::NOJOBTYPE || m_jobs.at(jobId)->m_type == type) { result.push_back(jobId); } } } } return result; } void JobManager::discardJobs(const QString &binId, AbstractClipJob::JOBTYPE type) { QWriteLocker locker(&m_lock); if (m_jobsByClip.count(binId) == 0) { return; } for (int jobId : m_jobsByClip.at(binId)) { if (type == AbstractClipJob::NOJOBTYPE || m_jobs.at(jobId)->m_type == type) { m_jobs.at(jobId)->m_future.cancel(); } } } bool JobManager::hasPendingJob(const QString &clipId, AbstractClipJob::JOBTYPE type, int *foundId) { READ_LOCK(); if (m_jobsByClip.count(clipId) > 0) { for (int jobId : m_jobsByClip.at(clipId)) { if ((type == AbstractClipJob::NOJOBTYPE || m_jobs.at(jobId)->m_type == type) && !m_jobs.at(jobId)->m_future.isFinished() && !m_jobs.at(jobId)->m_future.isCanceled()) { if (foundId) { *foundId = jobId; } return true; } } if (foundId) { *foundId = -1; } } return false; } void JobManager::updateJobCount() { READ_LOCK(); int count = 0; for (const auto &j : m_jobs) { if (!j.second->m_future.isFinished() && !j.second->m_future.isCanceled()) { count++; /*for (int i = 0; i < j.second->m_future.future().resultCount(); ++i) { if (j.second->m_future.future().isResultReadyAt(i)) { count++; } }*/ } } // Set jobs count emit jobCount(count); } /* void JobManager::prepareJobs(const QList &clips, double fps, AbstractClipJob::JOBTYPE jobType, const QStringList ¶ms) { // TODO filter clips QList matching = filterClips(clips, jobType, params); if (matching.isEmpty()) { m_bin->doDisplayMessage(i18n("No valid clip to process"), KMessageWidget::Information); return; } QHash jobs; if (jobType == AbstractClipJob::TRANSCODEJOB) { jobs = CutClipJob::prepareTranscodeJob(fps, matching, params); } else if (jobType == AbstractClipJob::CUTJOB) { ProjectClip *clip = matching.constFirst(); double originalFps = clip->getOriginalFps(); jobs = CutClipJob::prepareCutClipJob(fps, originalFps, clip); } else if (jobType == AbstractClipJob::ANALYSECLIPJOB) { jobs = CutClipJob::prepareAnalyseJob(fps, matching, params); } else if (jobType == AbstractClipJob::FILTERCLIPJOB) { jobs = FilterJob::prepareJob(matching, params); } else if (jobType == AbstractClipJob::PROXYJOB) { jobs = ProxyJob::prepareJob(m_bin, matching); } if (!jobs.isEmpty()) { QHashIterator i(jobs); while (i.hasNext()) { i.next(); launchJob(i.key(), i.value(), false); } slotCheckJobProcess(); } } */ void JobManager::slotDiscardClipJobs(const QString &binId) { QWriteLocker locker(&m_lock); if (m_jobsByClip.count(binId) > 0) { for (int jobId : m_jobsByClip.at(binId)) { Q_ASSERT(m_jobs.count(jobId) > 0); m_jobs[jobId]->m_future.cancel(); } } } void JobManager::slotCancelPendingJobs() { QWriteLocker locker(&m_lock); for (const auto &j : m_jobs) { if (!j.second->m_future.isStarted()) { j.second->m_future.cancel(); } } } void JobManager::slotCancelJobs() { QWriteLocker locker(&m_lock); for (const auto &j : m_jobs) { j.second->m_future.cancel(); } } void JobManager::createJob(std::shared_ptr job) { /* // This thread wait mechanism was broken and caused a race condition locking the application // so I switched to a simpler model bool ok = false; // wait for parents to finish while (!ok) { ok = true; for (int p : parents) { if (!m_jobs[p]->m_completionMutex.tryLock()) { ok = false; qDebug()<<"********\nWAITING FOR JOB COMPLETION MUTEX!!: "<m_id<<" : "<m_id<<"="<m_type; break; } else { qDebug()<<">>>>>>>>>>\nJOB COMPLETION MUTEX DONE: "<m_id; m_jobs[p]->m_completionMutex.unlock(); } } if (!ok) { QThread::msleep(10); } }*/ // connect progress signals QReadLocker locker(&m_lock); for (const auto &it : job->m_indices) { size_t i = it.second; auto binId = it.first; connect(job->m_job[i].get(), &AbstractClipJob::jobProgress, [job, i, binId](int p) { job->m_progress[i] = std::max(job->m_progress[i], p); pCore->projectItemModel()->onItemUpdated(binId, AbstractProjectItem::JobProgress); }); } connect(&job->m_future, &QFutureWatcher::started, this, &JobManager::updateJobCount); connect(&job->m_future, &QFutureWatcher::finished, [ this, id = job->m_id ]() { slotManageFinishedJob(id); }); connect(&job->m_future, &QFutureWatcher::canceled, [ this, id = job->m_id ]() { slotManageCanceledJob(id); }); job->m_actualFuture = QtConcurrent::mapped(job->m_job, AbstractClipJob::execute); job->m_future.setFuture(job->m_actualFuture); // In the unlikely event that the job finished before the signal connection was made, we check manually for finish and cancel /*if (job->m_future.isFinished()) { //emit job->m_future.finished(); slotManageFinishedJob(job->m_id); } if (job->m_future.isCanceled()) { //emit job->m_future.canceled(); slotManageCanceledJob(job->m_id); }*/ } void JobManager::slotManageCanceledJob(int id) { QReadLocker locker(&m_lock); Q_ASSERT(m_jobs.count(id) > 0); if (m_jobs[id]->m_processed) return; m_jobs[id]->m_processed = true; m_jobs[id]->m_completionMutex.unlock(); // send notification to refresh view for (const auto &it : m_jobs[id]->m_indices) { pCore->projectItemModel()->onItemUpdated(it.first, AbstractProjectItem::JobStatus); } - //TODO: delete child jobs + // TODO: delete child jobs updateJobCount(); } void JobManager::slotManageFinishedJob(int id) { qDebug() << "################### JOB finished" << id; QReadLocker locker(&m_lock); Q_ASSERT(m_jobs.count(id) > 0); if (m_jobs[id]->m_processed) return; // send notification to refresh view for (const auto &it : m_jobs[id]->m_indices) { pCore->projectItemModel()->onItemUpdated(it.first, AbstractProjectItem::JobStatus); } bool ok = true; for (bool res : m_jobs[id]->m_future.future()) { ok = ok && res; } if (!ok) { - qDebug()<<" * * * ** * * *\nWARNING + + +\nJOB NOT CORRECT FINISH: "<m_completionMutex.unlock(); updateJobCount(); return; } Fun undo = []() { return true; }; Fun redo = []() { return true; }; for (const auto &j : m_jobs[id]->m_job) { ok = ok && j->commitResult(undo, redo); } m_jobs[id]->m_processed = true; if (!ok) { qDebug() << "ERROR: Job " << id << " failed"; m_jobs[id]->m_failed = true; } m_jobs[id]->m_completionMutex.unlock(); if (ok && !m_jobs[id]->m_undoString.isEmpty()) { pCore->pushUndo(undo, redo, m_jobs[id]->m_undoString); } if (m_jobsByParents.count(id) > 0) { std::vector children = m_jobsByParents[id]; for (int cid : children) { QtConcurrent::run(this, &JobManager::createJob, m_jobs[cid]); } m_jobsByParents.erase(id); } updateJobCount(); } AbstractClipJob::JOBTYPE JobManager::getJobType(int jobId) const { READ_LOCK(); Q_ASSERT(m_jobs.count(jobId) > 0); return m_jobs.at(jobId)->m_type; } JobManagerStatus JobManager::getJobStatus(int jobId) const { READ_LOCK(); Q_ASSERT(m_jobs.count(jobId) > 0); auto job = m_jobs.at(jobId); if (job->m_future.isFinished()) { return JobManagerStatus::Finished; } if (job->m_future.isCanceled()) { return JobManagerStatus::Canceled; } if (job->m_future.isRunning()) { return JobManagerStatus::Running; } return JobManagerStatus::Pending; } int JobManager::getJobProgressForClip(int jobId, const QString &binId) const { READ_LOCK(); Q_ASSERT(m_jobs.count(jobId) > 0); auto job = m_jobs.at(jobId); Q_ASSERT(job->m_indices.count(binId) > 0); size_t ind = job->m_indices.at(binId); return job->m_progress[ind]; } QString JobManager::getJobMessageForClip(int jobId, const QString &binId) const { READ_LOCK(); Q_ASSERT(m_jobs.count(jobId) > 0); auto job = m_jobs.at(jobId); Q_ASSERT(job->m_indices.count(binId) > 0); size_t ind = job->m_indices.at(binId); return job->m_job[ind]->getErrorMessage(); } QVariant JobManager::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } int row = index.row(); if (row >= int(m_jobs.size()) || row < 0) { return QVariant(); } auto it = m_jobs.begin(); std::advance(it, row); switch (role) { case Qt::DisplayRole: return QVariant(it->second->m_job.front()->getDescription()); break; } return QVariant(); } int JobManager::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return int(m_jobs.size()); } diff --git a/src/jobs/jobmanager.h b/src/jobs/jobmanager.h index 2bc69722c..94ffba64f 100644 --- a/src/jobs/jobmanager.h +++ b/src/jobs/jobmanager.h @@ -1,172 +1,170 @@ /* Copyright (C) 2014 Jean-Baptiste Mardelle Copyright (C) 2017 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 JOBMANAGER #define JOBMANAGER #include "abstractclipjob.h" #include "definitions.h" #include #include #include #include #include #include #include #include class AbstractClipJob; /** * @class JobManager * @brief This class is responsible for clip jobs management. * */ enum class JobManagerStatus { NoJob, Pending, Running, Finished, Canceled }; Q_DECLARE_METATYPE(JobManagerStatus) struct Job_t { std::vector> m_job; // List of the jobs std::vector m_progress; // progress of the job, for each clip std::unordered_map m_indices; // keys are binIds, value are ids in the vectors m_job and m_progress; QFutureWatcher m_future; // future of the job QFuture m_actualFuture; QMutex m_completionMutex; // mutex that is locked during execution of the process AbstractClipJob::JOBTYPE m_type; QString m_undoString; int m_id; bool m_processed = false; // flag that we set to true when we are done with this job bool m_failed = false; // flag that we set to true when a problem occured }; class AudioThumbJob; class LoadJob; class SceneSplitJob; class StabilizeJob; class ThumbJob; class JobManager : public QAbstractListModel, public enable_shared_from_this_virtual { Q_OBJECT public: explicit JobManager(QObject *parent); virtual ~JobManager(); /** @brief Start a job This function calls the prepareJob function of the job if it provides one. @param T is the type of job (must inherit from AbstractClipJob) @param binIds is the list of clips to which we apply the job @param parents is the list of the ids of the job that must terminate before this one can start @param args are the arguments to construct the job @param return the id of the created job */ - template - int startJob(const std::vector &binIds, int parentId, QString undoString, Args &&... args); + template int startJob(const std::vector &binIds, int parentId, QString undoString, Args &&... args); // Same function, but we specify the function used to create a new job template - int startJob(const std::vector &binIds, int parentId, QString undoString, - std::function(const QString &, Args...)> createFn, Args &&... args); + int startJob(const std::vector &binIds, int parentId, QString undoString, std::function(const QString &, Args...)> createFn, + Args &&... args); // Same function, but do not call prepareJob - template - int startJob_noprepare(const std::vector &binIds, int parentId, QString undoString, Args &&... args); + template int startJob_noprepare(const std::vector &binIds, int parentId, QString undoString, Args &&... args); /** @brief Discard specific job type for a clip. * @param binId the clip id * @param type The type of job that you want to abort, leave to NOJOBTYPE to abort all jobs */ void discardJobs(const QString &binId, AbstractClipJob::JOBTYPE type = AbstractClipJob::NOJOBTYPE); /** @brief Check if there is a pending / running job a clip. * @param binId the clip id * @param type The type of job that you want to query * @param foundId : if a valid ptr is passed, we store the id of the first matching job found (-1 if not found) */ bool hasPendingJob(const QString &binId, AbstractClipJob::JOBTYPE type, int *foundId = nullptr); /** @brief Get the list of pending or running job ids for given clip. * @param binId the clip id * @param type The type of job that you want to query. Leave to NOJOBTYPE to match all */ std::vector getPendingJobsIds(const QString &binId, AbstractClipJob::JOBTYPE type = AbstractClipJob::NOJOBTYPE); int getBlockingJobId(const QString &id, AbstractClipJob::JOBTYPE type); /** @brief Get the list of finished or cancelled job ids for given clip. * @param binId the clip id * @param type The type of job that you want to query. Leave to NOJOBTYPE to match all */ std::vector getFinishedJobsIds(const QString &binId, AbstractClipJob::JOBTYPE type = AbstractClipJob::NOJOBTYPE); /** @brief return the type of a given job */ AbstractClipJob::JOBTYPE getJobType(int jobId) const; /** @brief return the type of a given job */ JobManagerStatus getJobStatus(int jobId) const; /** @brief return the progress of a given job on a given clip */ int getJobProgressForClip(int jobId, const QString &binId) const; /** @brief return the message of a given job on a given clip */ QString getJobMessageForClip(int jobId, const QString &binId) const; // Mandatory overloads QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: // Helper function to launch a given job. // This has to be launched asynchrnously since it blocks until all parents are finished void createJob(std::shared_ptr job); void updateJobCount(); void slotManageCanceledJob(int id); void slotManageFinishedJob(int id); public slots: /** @brief Discard jobs running on a given clip */ void slotDiscardClipJobs(const QString &binId); /** @brief Discard all running jobs. */ void slotCancelJobs(); /** @brief Discard all pending jobs. */ void slotCancelPendingJobs(); private: /** @brief This is a lock that ensures safety in case of concurrent access */ mutable QReadWriteLock m_lock; /** @brief This is the id of the last created job */ static int m_currentId; /** @brief This is the list of all jobs, ordered by id. A job is represented by a pointer to the job class and a future to the result */ std::map> m_jobs; /** @brief List of all the jobs by clip. */ std::unordered_map> m_jobsByClip; std::unordered_map> m_jobsByParents; signals: void jobCount(int); }; #include "jobmanager.ipp" #endif diff --git a/src/jobs/jobmanager.ipp b/src/jobs/jobmanager.ipp index 345db83d3..b5a6ca1be 100644 --- a/src/jobs/jobmanager.ipp +++ b/src/jobs/jobmanager.ipp @@ -1,121 +1,120 @@ /* Copyright (C) 2014 Jean-Baptiste Mardelle Copyright (C) 2017 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 #include template int JobManager::startJob(const std::vector &binIds, int parentId, QString undoString, std::function(const QString &, Args...)> createFn, Args &&... args) { static_assert(std::is_base_of::value, "Your job must inherit from AbstractClipJob"); - //QWriteLocker locker(&m_lock); + // QWriteLocker locker(&m_lock); int jobId = m_currentId++; std::shared_ptr job(new Job_t()); job->m_completionMutex.lock(); job->m_undoString = std::move(undoString); job->m_id = jobId; for (const auto &id : binIds) { job->m_job.push_back(createFn(id, args...)); job->m_progress.push_back(0); job->m_indices[id] = size_t(int(job->m_job.size()) - 1); job->m_type = job->m_job.back()->jobType(); m_jobsByClip[id].push_back(jobId); } m_lock.lockForWrite(); int insertionRow = static_cast(m_jobs.size()); beginInsertRows(QModelIndex(), insertionRow, insertionRow); Q_ASSERT(m_jobs.count(jobId) == 0); m_jobs[jobId] = job; endInsertRows(); m_lock.unlock(); if (parentId == -1 || m_jobs[parentId]->m_completionMutex.tryLock()) { if (parentId != -1) { m_jobs[parentId]->m_completionMutex.unlock(); } QtConcurrent::run(this, &JobManager::createJob, job); } else { m_jobsByParents[parentId].push_back(jobId); } return jobId; } // we must specialize the second version of startjob depending on the type (some types requires to use a prepareJob method). Because we cannot use partial // specialization for functions, we resort to a static method of a class in this impl namespace we must specialize the second version of startjob depending on // the type (some types requires to use a prepareJob method). Because we cannot use partial specialization for functions, we resort to a static method of a // dummy struct in a namespace namespace impl { // This is a simple member detector borrowed from https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Member_Detector template class Detect_prepareJob { // clang-format off struct Fallback {int prepareJob;}; // add member name "prepareJob" struct Derived : T, Fallback {}; // clang-format on template struct Check; typedef char ArrayOfOne[1]; // typedef for an array of size one. typedef char ArrayOfTwo[2]; // typedef for an array of size two. template static ArrayOfOne &func(Check *); template static ArrayOfTwo &func(...); public: typedef Detect_prepareJob type; enum { value = sizeof(func(0)) == 2 }; }; struct dummy { template static typename std::enable_if::value || Noprepare, int>::type exec(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, Args &&... args) { auto defaultCreate = [](const QString &id, Args... local_args) { return AbstractClipJob::make(id, std::forward(local_args)...); }; using local_createFn_t = std::function(const QString &, Args...)>; return ptr->startJob(binIds, parentId, std::move(undoString), local_createFn_t(std::move(defaultCreate)), std::forward(args)...); } template static typename std::enable_if::value && !Noprepare, int>::type exec(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, Args &&... args) { // For job stabilization, there is a custom preparation function return T::prepareJob(ptr, binIds, parentId, std::move(undoString), std::forward(args)...); } }; } // namespace impl -template -int JobManager::startJob(const std::vector &binIds, int parentId, QString undoString, Args &&... args) +template int JobManager::startJob(const std::vector &binIds, int parentId, QString undoString, Args &&... args) { return impl::dummy::exec(shared_from_this(), binIds, parentId, std::move(undoString), std::forward(args)...); } template int JobManager::startJob_noprepare(const std::vector &binIds, int parentId, QString undoString, Args &&... args) { return impl::dummy::exec(shared_from_this(), binIds, parentId, std::move(undoString), std::forward(args)...); } diff --git a/src/jobs/loadjob.cpp b/src/jobs/loadjob.cpp index 3c4855969..6e1d9a377 100644 --- a/src/jobs/loadjob.cpp +++ b/src/jobs/loadjob.cpp @@ -1,543 +1,542 @@ /*************************************************************************** * 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 "loadjob.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "klocalizedstring.h" #include "macros.hpp" #include "mltcontroller/clip.h" #include "profiles/profilemodel.hpp" #include "project/dialogs/slideshowclip.h" #include "xml/xml.hpp" #include #include #include #include LoadJob::LoadJob(const QString &binId, const QDomElement &xml) : AbstractClipJob(LOADJOB, binId) , m_xml(xml) { } const QString LoadJob::getDescription() const { return i18n("Loading clip %1", m_clipId); } namespace { ClipType getTypeForService(const QString &id, const QString &path) { if (id.isEmpty()) { QString ext = path.section(QLatin1Char('.'), -1); if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) { return ClipType::Playlist; } return ClipType::Unknown; } if (id == QLatin1String("color") || id == QLatin1String("colour")) { return ClipType::Color; } if (id == QLatin1String("kdenlivetitle")) { return ClipType::Text; } if (id == QLatin1String("qtext")) { return ClipType::QText; } if (id == QLatin1String("xml") || id == QLatin1String("consumer")) { return ClipType::Playlist; } if (id == QLatin1String("webvfx")) { return ClipType::WebVfx; } return ClipType::Unknown; } // Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored void processProducerProperties(std::shared_ptr prod, const QDomElement &xml) { // TODO: there is some duplication with clipcontroller > updateproducer that also copies properties QString value; QStringList internalProperties; internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index") << QStringLiteral("video_index") << QStringLiteral("mlt_type"); QDomNodeList props; if (xml.tagName() == QLatin1String("producer")) { props = xml.childNodes(); } else { props = xml.firstChildElement(QStringLiteral("producer")).childNodes(); } for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().tagName() != QStringLiteral("property")) { continue; } QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name")); if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) { value = props.at(i).firstChild().nodeValue(); if (propertyName.startsWith(QLatin1String("kdenlive-force."))) { // this is a special forced property, pass it propertyName.remove(0, 15); } prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData()); } } } } // namespace // static std::shared_ptr LoadJob::loadResource(QString &resource, const QString &type) { if (!resource.startsWith(type)) { resource.prepend(type); } return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData()); } std::shared_ptr LoadJob::loadPlaylist(QString &resource) { std::unique_ptr xmlProfile(new Mlt::Profile()); xmlProfile->set_explicit(0); std::unique_ptr producer(new Mlt::Producer(*xmlProfile, "xml", resource.toUtf8().constData())); if (!producer->is_valid()) { - qDebug()<<"////// ERROR, CANNOT LOAD SELECTED PLAYLIST: "<getCurrentProfile()->isCompatible(xmlProfile.get())) { // We can use the "xml" producer since profile is the same (using it with different profiles corrupts the project. // Beware that "consumer" currently crashes on audio mixes! resource.prepend(QStringLiteral("xml:")); } else { // This is currently crashing so I guess we'd better reject it for now - qDebug()<<"////// ERROR, INCOMPATIBLE PROFILE: "<getCurrentProfile()->set_explicit(1); return std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData()); } void LoadJob::checkProfile() { // Check if clip profile matches QString service = m_producer->get("mlt_service"); // Check for image producer if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { // This is an image, create profile from image size int width = m_producer->get_int("meta.media.width"); int height = m_producer->get_int("meta.media.height"); if (width > 100 && height > 100) { std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); projectProfile->m_width = width; projectProfile->m_height = height; projectProfile->m_sample_aspect_num = 1; projectProfile->m_sample_aspect_den = 1; projectProfile->m_display_aspect_num = width; projectProfile->m_display_aspect_den = height; projectProfile->m_description.clear(); pCore->currentDoc()->switchProfile(projectProfile, m_clipId, m_xml); } else { // Very small image, we probably don't want to use this as profile } } else if (service.contains(QStringLiteral("avformat"))) { std::unique_ptr blankProfile(new Mlt::Profile()); blankProfile->set_explicit(0); blankProfile->from_producer(*m_producer); std::unique_ptr clipProfile(new ProfileParam(blankProfile.get())); std::unique_ptr projectProfile(new ProfileParam(pCore->getCurrentProfile().get())); clipProfile->adjustWidth(); if (clipProfile != projectProfile) { // Profiles do not match, propose profile adjustment pCore->currentDoc()->switchProfile(projectProfile, m_clipId, m_xml); } else if (KdenliveSettings::default_profile().isEmpty()) { // Confirm default project format KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path()); } } } void LoadJob::processSlideShow() { int ttl = EffectsList::property(m_xml, QStringLiteral("ttl")).toInt(); QString anim = EffectsList::property(m_xml, QStringLiteral("animation")); if (!anim.isEmpty()) { auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "affine"); if ((filter != nullptr) && filter->is_valid()) { int cycle = ttl; QString geometry = SlideshowClip::animationToGeometry(anim, cycle); if (!geometry.isEmpty()) { if (anim.contains(QStringLiteral("low-pass"))) { auto *blur = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "boxblur"); if ((blur != nullptr) && blur->is_valid()) { m_producer->attach(*blur); } } filter->set("transition.geometry", geometry.toUtf8().data()); filter->set("transition.cycle", cycle); m_producer->attach(*filter); } } } QString fade = EffectsList::property(m_xml, QStringLiteral("fade")); if (fade == QLatin1String("1")) { // user wants a fade effect to slideshow auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "luma"); if ((filter != nullptr) && filter->is_valid()) { if (ttl != 0) { filter->set("cycle", ttl); } QString luma_duration = EffectsList::property(m_xml, QStringLiteral("luma_duration")); QString luma_file = EffectsList::property(m_xml, QStringLiteral("luma_file")); if (!luma_duration.isEmpty()) { filter->set("duration", luma_duration.toInt()); } if (!luma_file.isEmpty()) { filter->set("luma.resource", luma_file.toUtf8().constData()); QString softness = EffectsList::property(m_xml, QStringLiteral("softness")); if (!softness.isEmpty()) { int soft = softness.toInt(); filter->set("luma.softness", (double)soft / 100.0); } } m_producer->attach(*filter); } } QString crop = EffectsList::property(m_xml, QStringLiteral("crop")); if (crop == QLatin1String("1")) { // user wants to center crop the slides auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "crop"); if ((filter != nullptr) && filter->is_valid()) { filter->set("center", 1); m_producer->attach(*filter); } } } bool LoadJob::startJob() { if (m_done) { return true; } m_resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource")); ClipType type = static_cast(m_xml.attribute(QStringLiteral("type")).toInt()); if (type == ClipType::Unknown) { type = getTypeForService(Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service")), m_resource); } switch (type) { case ClipType::Color: m_producer = loadResource(m_resource, QStringLiteral("color:")); break; case ClipType::Text: case ClipType::TextTemplate: m_producer = loadResource(m_resource, QStringLiteral("kdenlivetitle:")); break; case ClipType::QText: m_producer = loadResource(m_resource, QStringLiteral("qtext:")); break; case ClipType::Playlist: m_producer = loadPlaylist(m_resource); break; case ClipType::SlideShow: default: m_producer = std::make_shared(pCore->getCurrentProfile()->profile(), nullptr, m_resource.toUtf8().constData()); break; } if (!m_producer || m_producer->is_blank() || !m_producer->is_valid()) { qCDebug(KDENLIVE_LOG) << " / / / / / / / / ERROR / / / / // CANNOT LOAD PRODUCER: " << m_resource; m_done = true; m_successful = false; m_errorMessage.append(i18n("ERROR: Could not load clip %1: producer is invalid", m_resource)); return false; } processProducerProperties(m_producer, m_xml); QString clipName = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:clipname")); if (clipName.isEmpty()) { clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName(); } m_producer->set("kdenlive:clipname", clipName.toUtf8().constData()); QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid")); if (!groupId.isEmpty()) { m_producer->set("kdenlive:folderid", groupId.toUtf8().constData()); } int clipOut = 0, duration = 0; if (m_xml.hasAttribute(QStringLiteral("out"))) { clipOut = m_xml.attribute(QStringLiteral("out")).toInt(); } // setup length here as otherwise default length (currently 15000 frames in MLT) will be taken even if outpoint is larger if (type == ClipType::Color || type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Image || type == ClipType::SlideShow) { int length; if (m_xml.hasAttribute(QStringLiteral("length"))) { length = m_xml.attribute(QStringLiteral("length")).toInt(); clipOut = qMax(1, length - 1); } else { length = Xml::getXmlProperty(m_xml, QStringLiteral("length")).toInt(); clipOut -= m_xml.attribute(QStringLiteral("in")).toInt(); if (length < clipOut) { length = clipOut == 1 ? 1 : clipOut + 1; } } // Pass duration if it was forced if (m_xml.hasAttribute(QStringLiteral("duration"))) { duration = m_xml.attribute(QStringLiteral("duration")).toInt(); if (length < duration) { length = duration; if (clipOut > 0) { clipOut = length - 1; } } } if (duration == 0) { duration = length; } m_producer->set("length", length); int kdenlive_duration = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")).toInt(); m_producer->set("kdenlive:duration", kdenlive_duration > 0 ? kdenlive_duration : length); } if (clipOut > 0) { m_producer->set_in_and_out(m_xml.attribute(QStringLiteral("in")).toInt(), clipOut); } if (m_xml.hasAttribute(QStringLiteral("templatetext"))) { m_producer->set("templatetext", m_xml.attribute(QStringLiteral("templatetext")).toUtf8().constData()); } duration = duration > 0 ? duration : m_producer->get_playtime(); if (type == ClipType::SlideShow) { processSlideShow(); } int vindex = -1; double fps = -1; const QString mltService = m_producer->get("mlt_service"); if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) { // MLT playlist, create producer with blank profile to get real profile info QString tmpPath = m_resource; if (tmpPath.startsWith(QLatin1String("consumer:"))) { tmpPath = "xml:" + tmpPath.section(QLatin1Char(':'), 1); } Mlt::Profile original_profile; std::unique_ptr tmpProd(new Mlt::Producer(original_profile, nullptr, tmpPath.toUtf8().constData())); original_profile.set_explicit(1); double originalFps = original_profile.fps(); fps = originalFps; if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) { int originalLength = tmpProd->get_length(); int fixedLength = (int)(originalLength * pCore->getCurrentFps() / originalFps); m_producer->set("length", fixedLength); m_producer->set("out", fixedLength - 1); } } else if (mltService == QLatin1String("avformat")) { // check if there are multiple streams vindex = m_producer->get_int("video_index"); // List streams int streams = m_producer->get_int("meta.media.nb_streams"); m_audio_list.clear(); m_video_list.clear(); for (int i = 0; i < streams; ++i) { QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit(); QString stype = m_producer->get(propertyName.data()); if (stype == QLatin1String("audio")) { m_audio_list.append(i); } else if (stype == QLatin1String("video")) { m_video_list.append(i); } } if (vindex > -1) { char property[200]; snprintf(property, sizeof(property), "meta.media.%d.stream.frame_rate", vindex); fps = m_producer->get_double(property); } if (fps <= 0) { if (m_producer->get_double("meta.media.frame_rate_den") > 0) { fps = m_producer->get_double("meta.media.frame_rate_num") / m_producer->get_double("meta.media.frame_rate_den"); } else { fps = m_producer->get_double("source_fps"); } } } if (fps <= 0 && type == ClipType::Unknown) { // something wrong, maybe audio file with embedded image QMimeDatabase db; QString mime = db.mimeTypeForFile(m_resource).name(); if (mime.startsWith(QLatin1String("audio"))) { m_producer->set("video_index", -1); vindex = -1; } } m_done = m_successful = true; return true; } void LoadJob::processMultiStream() { auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); // We retrieve the folder containing our clip, because we will set the other streams in the same auto parent = pCore->projectItemModel()->getRootFolder()->clipId(); if (auto ptr = m_binClip->parentItem().lock()) { parent = std::static_pointer_cast(ptr)->clipId(); } else { qDebug() << "Warning, something went wrong while accessing parent of bin clip"; } // This helper lambda request addition of a given stream auto addStream = [ this, parentId = std::move(parent) ](int vindex, int aindex, Fun &undo, Fun &redo) { auto clone = Clip::clone(m_producer); clone->set("video_index", vindex); clone->set("audio_index", aindex); QString id; pCore->projectItemModel()->requestAddBinClip(id, clone, parentId, undo, redo); }; Fun undo = []() { return true; }; Fun redo = []() { return true; }; if (KdenliveSettings::automultistreams()) { for (int i = 1; i < m_video_list.count(); ++i) { int vindex = m_video_list.at(i); int aindex = 0; if (i <= m_audio_list.count() - 1) { aindex = m_audio_list.at(i); } addStream(vindex, aindex, undo, redo); } return; } int width = 60.0 * pCore->getCurrentDar(); if (width % 2 == 1) { width++; } QScopedPointer dialog(new QDialog(qApp->activeWindow())); dialog->setWindowTitle(QStringLiteral("Multi Stream Clip")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QWidget *mainWidget = new QWidget(dialog.data()); auto *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept); dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject); okButton->setText(i18n("Import selected clips")); QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", m_resource), mainWidget); mainLayout->addWidget(lab1); QList groupList; QList comboList; // We start loading the list at 1, video index 0 should already be loaded for (int j = 1; j < m_video_list.count(); ++j) { m_producer->set("video_index", m_video_list.at(j)); // TODO this keyframe should be cached QImage thumb = KThumb::getFrame(m_producer.get(), 0, width, 60); QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", m_video_list.at(j)), mainWidget); mainLayout->addWidget(streamFrame); streamFrame->setProperty("vindex", m_video_list.at(j)); groupList << streamFrame; streamFrame->setCheckable(true); streamFrame->setChecked(true); auto *vh = new QVBoxLayout(streamFrame); QLabel *iconLabel = new QLabel(mainWidget); mainLayout->addWidget(iconLabel); iconLabel->setPixmap(QPixmap::fromImage(thumb)); vh->addWidget(iconLabel); if (m_audio_list.count() > 1) { auto *cb = new KComboBox(mainWidget); mainLayout->addWidget(cb); for (int k = 0; k < m_audio_list.count(); ++k) { cb->addItem(i18n("Audio stream %1", m_audio_list.at(k)), m_audio_list.at(k)); } comboList << cb; cb->setCurrentIndex(qMin(j, m_audio_list.count() - 1)); vh->addWidget(cb); } mainLayout->addWidget(streamFrame); } mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { // import selected streams for (int i = 0; i < groupList.count(); ++i) { if (groupList.at(i)->isChecked()) { int vindex = groupList.at(i)->property("vindex").toInt(); int ax = qMin(i, comboList.size() - 1); int aindex = -1; if (ax >= 0) { // only check audio index if we have several audio streams aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt(); } addStream(vindex, aindex, undo, redo); } } } pCore->pushUndo(undo, redo, i18n("Add additional streams for clip")); } bool LoadJob::commitResult(Fun &undo, Fun &redo) { qDebug() << "################### loadjob COMMIT"; Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; return false; } m_resultConsumed = true; auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); if (!m_successful) { pCore->projectItemModel()->requestBinClipDeletion(m_binClip, undo, redo); return false; } if (m_xml.hasAttribute(QStringLiteral("checkProfile")) && m_producer->get_int("video_index") > -1) { checkProfile(); } if (m_video_list.size() > 1) { processMultiStream(); } // note that the image is moved into lambda, it won't be available from this class anymore auto operation = [ clip = m_binClip, prod = std::move(m_producer) ]() { clip->setProducer(prod, true); return true; }; - auto reverse = []() - { + auto reverse = []() { // This is probably not invertible, leave as is. return true; }; bool ok = operation(); if (ok) { if (pCore->projectItemModel()->clipsCount() == 2) { // Always select first added clip pCore->selectBinClip(m_clipId); } UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } return ok; } diff --git a/src/jobs/meltjob.cpp b/src/jobs/meltjob.cpp index 64bc8aa2b..ef3bb6810 100644 --- a/src/jobs/meltjob.cpp +++ b/src/jobs/meltjob.cpp @@ -1,282 +1,281 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * 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 "meltjob.h" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include #include static void consumer_frame_render(mlt_consumer, MeltJob *self, mlt_frame frame_ptr) { Mlt::Frame frame(frame_ptr); self->mltFrameCallback((int)frame.get_position()); } MeltJob::MeltJob(const QString &binId, JOBTYPE type, bool useProducerProfile, int in, int out) : AbstractClipJob(type, binId) , m_profile(pCore->getCurrentProfile()->profile()) , m_useProducerProfile(useProducerProfile) , m_in(in) , m_out(out) , m_requiresFilter(true) { } bool MeltJob::startJob() { auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); m_url = binClip->url(); if (m_url.isEmpty()) { m_errorMessage.append(i18n("No producer for this clip.")); m_successful = false; m_done = true; return false; } /* QString consumerName = m_consumerParams.value(QStringLiteral("consumer")); // safety check, make sure we don't overwrite a source clip if (!m_dest.isEmpty() && !m_dest.endsWith(QStringLiteral(".mlt"))) { m_errorMessage.append(i18n("Invalid destination: %1.", consumerName)); setStatus(JobCrashed); return; } int in = m_producerParams.value(QStringLiteral("in")).toInt(); if (in > 0 && !m_extra.contains(QStringLiteral("offset"))) { m_extra.insert(QStringLiteral("offset"), QString::number(in)); } int out = m_producerParams.value(QStringLiteral("out")).toInt(); QString filterName = m_filterParams.value(QStringLiteral("filter")); // optional params int startPos = -1; int track = -1; // used when triggering a job from an effect if (m_extra.contains(QStringLiteral("clipStartPos"))) { startPos = m_extra.value(QStringLiteral("clipStartPos")).toInt(); } if (m_extra.contains(QStringLiteral("clipTrack"))) { track = m_extra.value(QStringLiteral("clipTrack")).toInt(); } if (!m_extra.contains(QStringLiteral("finalfilter"))) { m_extra.insert(QStringLiteral("finalfilter"), filterName); } if (out != -1 && out <= in) { m_errorMessage.append(i18n("Clip zone undefined (%1 - %2).", in, out)); setStatus(JobCrashed); return; } */ auto &projectProfile = pCore->getCurrentProfile(); Mlt::Profile producerProfile; // bool producerProfile = m_extra.contains(QStringLiteral("producer_profile")); if (m_useProducerProfile) { m_profile = producerProfile; m_profile.set_explicit(0); } else { m_profile = projectProfile->profile(); } /* if (m_extra.contains(QStringLiteral("resize_profile"))) { m_profile->set_height(m_extra.value(QStringLiteral("resize_profile")).toInt()); m_profile->set_width(m_profile->height() * m_profile->sar()); } */ double fps = projectProfile->fps(); int fps_num = projectProfile->frame_rate_num(); int fps_den = projectProfile->frame_rate_den(); m_producer.reset(new Mlt::Producer(m_profile, m_url.toUtf8().constData())); if (m_producer && m_useProducerProfile) { m_profile.from_producer(*m_producer.get()); m_profile.set_explicit(1); } configureProfile(); if (!qFuzzyCompare(m_profile.fps(), fps) || m_useProducerProfile) { // Reload producer // Force same fps as projec profile or the resulting .mlt will not load in our project m_profile.set_frame_rate(fps_num, fps_den); m_producer.reset(new Mlt::Producer(m_profile, m_url.toUtf8().constData())); } if ((m_producer == nullptr) || !m_producer->is_valid()) { // Clip was removed or something went wrong, Notify user? m_errorMessage.append(i18n("Invalid clip")); m_successful = false; m_done = true; return false; } /* // Process producer params QMapIterator i(m_producerParams); QStringList ignoredProps; ignoredProps << QStringLiteral("producer") << QStringLiteral("in") << QStringLiteral("out"); while (i.hasNext()) { i.next(); QString key = i.key(); if (!ignoredProps.contains(key)) { producer->set(i.key().toUtf8().constData(), i.value().toUtf8().constData()); } } */ if (m_out == -1) { m_out = m_producer->get_playtime() - 1; } if (m_in == -1) { m_in = 0; } if (m_out != m_producer->get_playtime() - 1 || m_in != 0) { std::swap(m_wholeProducer, m_producer); m_producer.reset(m_wholeProducer->cut(m_in, m_out)); } configureProducer(); if ((m_producer == nullptr) || !m_producer->is_valid()) { // Clip was removed or something went wrong, Notify user? m_errorMessage.append(i18n("Invalid clip")); m_successful = false; m_done = true; return false; } // Build consumer configureConsumer(); /* if (m_consumerName.contains(QLatin1String(":"))) { m_consumer.reset(new Mlt::Consumer(*m_profile, consumerName.section(QLatin1Char(':'), 0, 0).toUtf8().constData(), m_dest.toUtf8().constData())); } else { m_consumer = new Mlt::Consumer(*m_profile, consumerName.toUtf8().constData()); }*/ if ((m_consumer == nullptr) || !m_consumer->is_valid()) { m_errorMessage.append(i18n("Cannot create consumer.")); m_successful = false; m_done = true; return false; } /* if (!m_consumerParams.contains(QStringLiteral("real_time"))) { m_consumer->set("real_time", -KdenliveSettings::mltthreads()); } */ // Process consumer params /* QMapIterator j(m_consumerParams); ignoredProps.clear(); ignoredProps << QStringLiteral("consumer"); while (j.hasNext()) { j.next(); QString key = j.key(); if (!ignoredProps.contains(key)) { m_consumer->set(j.key().toUtf8().constData(), j.value().toUtf8().constData()); } } if (consumerName.startsWith(QStringLiteral("xml:"))) { // Use relative path in xml m_consumer->set("root", QFileInfo(m_dest).absolutePath().toUtf8().constData()); } */ // Build filter configureFilter(); /* if (!filterName.isEmpty()) { m_filter = new Mlt::Filter(*m_profile, filterName.toUtf8().data()); if ((m_filter == nullptr) || !m_filter->is_valid()) { m_errorMessage = i18n("Filter %1 crashed", filterName); setStatus(JobCrashed); return; } // Process filter params QMapIterator k(m_filterParams); ignoredProps.clear(); ignoredProps << QStringLiteral("filter"); while (k.hasNext()) { k.next(); QString key = k.key(); if (!ignoredProps.contains(key)) { m_filter->set(k.key().toUtf8().constData(), k.value().toUtf8().constData()); } } } */ if (m_requiresFilter && (m_filter == nullptr || !m_filter->is_valid())) { m_errorMessage.append(i18n("Cannot create filter.")); m_successful = false; m_done = true; return false; } Mlt::Tractor tractor(m_profile); Mlt::Playlist playlist; playlist.append(*m_producer.get()); tractor.set_track(playlist, 0); m_consumer->connect(tractor); m_producer->set_speed(0); m_producer->seek(0); m_length = m_producer->get_playtime(); if (m_length == 0) { m_length = m_producer->get_length(); } if (m_filter) { m_producer->attach(*m_filter.get()); } m_showFrameEvent.reset(m_consumer->listen("consumer-frame-render", this, (mlt_listener)consumer_frame_render)); m_producer->set_speed(1); m_consumer->run(); /* QMap jobResults; if (m_jobStatus != JobAborted && m_extra.contains(QStringLiteral("key"))) { QString result = QString::fromLatin1(m_filter->get(m_extra.value(QStringLiteral("key")).toUtf8().constData())); jobResults.insert(m_extra.value(QStringLiteral("key")), result); } if (!jobResults.isEmpty() && m_jobStatus != JobAborted) { emit gotFilterJobResults(m_clipId, startPos, track, jobResults, m_extra); } if (m_jobStatus == JobWorking) { m_jobStatus = JobDone; } */ m_successful = m_done = true; return true; } void MeltJob::mltFrameCallback(int pos) { if (m_length > 0) { emit jobProgress((int)(100 * pos / m_length)); } } - diff --git a/src/jobs/proxyclipjob.cpp b/src/jobs/proxyclipjob.cpp index cf28f0024..9b0fb4b13 100644 --- a/src/jobs/proxyclipjob.cpp +++ b/src/jobs/proxyclipjob.cpp @@ -1,325 +1,322 @@ /*************************************************************************** * * * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "proxyclipjob.h" #include "bin/bin.h" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "macros.hpp" #include #include -#include #include - ProxyJob::ProxyJob(const QString &binId) : AbstractClipJob(PROXYJOB, binId) , m_jobDuration(0) , m_isFfmpegJob(true) , m_done(false) { } const QString ProxyJob::getDescription() const { return i18n("Creating proxy %1", m_clipId); } - bool ProxyJob::startJob() { auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy")); if (QFile::exists(dest)) { // Proxy clip already created m_done = true; return true; } ClipType type = binClip->clipType(); bool result; QString source = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl")); int exif = binClip->getProducerIntProperty(QStringLiteral("_exif_orientation")); if (type == ClipType::Playlist || type == ClipType::SlideShow) { // change FFmpeg params to MLT format m_isFfmpegJob = false; QStringList mltParameters; QTemporaryFile *playlist = nullptr; // set clip origin if (type == ClipType::Playlist) { // Special case: playlists use the special 'consumer' producer to support resizing source.prepend(QStringLiteral("consumer:")); } else { // create temporary playlist to generate proxy // we save a temporary .mlt clip for rendering QDomDocument doc; QDomElement xml = binClip->toXml(doc, false); playlist = new QTemporaryFile(); playlist->setFileTemplate(playlist->fileTemplate() + QStringLiteral(".mlt")); if (playlist->open()) { source = playlist->fileName(); QTextStream out(playlist); out << doc.toString(); playlist->close(); } } mltParameters << source; // set destination mltParameters << QStringLiteral("-consumer") << QStringLiteral("avformat:") + dest; QString parameter = pCore->currentDoc()->getDocumentProperty(QStringLiteral("proxyparams")).simplified(); QStringList params = parameter.split(QLatin1Char('-'), QString::SkipEmptyParts); double display_ratio; if (source.startsWith(QLatin1String("consumer:"))) { display_ratio = KdenliveDoc::getDisplayRatio(source.section(QLatin1Char(':'), 1)); } else { display_ratio = KdenliveDoc::getDisplayRatio(source); } if (display_ratio < 1e-6) { display_ratio = 1; } bool skipNext = false; for (const QString &s : params) { QString t = s.simplified(); if (skipNext) { skipNext = false; continue; } if (t.count(QLatin1Char(' ')) == 0) { t.append(QLatin1String("=1")); } else if (t.startsWith(QLatin1String("vf "))) { skipNext = true; bool ok = false; int width = t.section(QLatin1Char('='), 1, 1).section(QLatin1Char(':'), 0, 0).toInt(&ok); if (!ok) { width = 640; } int height = width / display_ratio; // Make sure we get an even height height += height % 2; mltParameters << QStringLiteral("s=%1x%2").arg(width).arg(height); if (t.contains(QStringLiteral("yadif"))) { mltParameters << QStringLiteral("progressive=1"); } continue; } else { t.replace(QLatin1Char(' '), QLatin1String("=")); } mltParameters << t; } mltParameters.append(QStringLiteral("real_time=-%1").arg(KdenliveSettings::mltthreads())); // TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manualy mltParameters << QStringLiteral("aspect=") + QLocale().toString(display_ratio); // Ask for progress reporting mltParameters << QStringLiteral("progress=1"); m_jobProcess = new QProcess; m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); connect(m_jobProcess, &QProcess::readyReadStandardOutput, this, &ProxyJob::processLogInfo); m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters); m_jobProcess->waitForFinished(-1); result = m_jobProcess->exitStatus() == QProcess::NormalExit; delete playlist; } else if (type == ClipType::Image) { m_isFfmpegJob = false; // Image proxy QImage i(source); if (i.isNull()) { m_done = false; m_errorMessage.append(i18n("Cannot load image %1.", source)); return false; } QImage proxy; // Images are scaled to profile size. // TODO: Make it be configurable? if (i.width() > i.height()) { proxy = i.scaledToWidth(KdenliveSettings::proxyimagesize()); } else { proxy = i.scaledToHeight(KdenliveSettings::proxyimagesize()); } if (exif > 1) { // Rotate image according to exif data QImage processed; QMatrix matrix; switch (exif) { case 2: matrix.scale(-1, 1); break; case 3: matrix.rotate(180); break; case 4: matrix.scale(1, -1); break; case 5: matrix.rotate(270); matrix.scale(-1, 1); break; case 6: matrix.rotate(90); break; case 7: matrix.rotate(90); matrix.scale(-1, 1); break; case 8: matrix.rotate(270); break; } processed = proxy.transformed(matrix); processed.save(dest); } else { proxy.save(dest); } m_done = true; return true; } else { m_isFfmpegJob = true; QStringList parameters; if (KdenliveSettings::ffmpegpath().isEmpty()) { // FFmpeg not detected, cannot process the Job m_errorMessage.prepend(i18n("Failed to create proxy. FFmpeg not found, please set path in Kdenlive's settings Environment")); m_done = true; return false; } const QString proxyParams = pCore->currentDoc()->getDocumentProperty(QStringLiteral("proxyparams")).simplified(); if (proxyParams.contains(QStringLiteral("-noautorotate"))) { // The noautorotate flag must be passed before input source parameters << QStringLiteral("-noautorotate"); } if (proxyParams.contains(QLatin1String("-i "))) { // we have some pre-filename parameters, filename will be inserted later } else { parameters << QStringLiteral("-i") << source; } QString params = proxyParams; for (const QString &s : params.split(QLatin1Char(' '))) { QString t = s.simplified(); if (t != QLatin1String("-noautorotate")) { parameters << t; if (t == QLatin1String("-i")) { parameters << source; } } } // Make sure we don't block when proxy file already exists parameters << QStringLiteral("-y"); parameters << dest; m_jobProcess = new QProcess; m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); connect(m_jobProcess, &QProcess::readyReadStandardOutput, this, &ProxyJob::processLogInfo); m_jobProcess->start(KdenliveSettings::ffmpegpath(), parameters, QIODevice::ReadOnly); m_jobProcess->waitForFinished(-1); result = m_jobProcess->exitStatus() == QProcess::NormalExit; } // remove temporary playlist if it exists if (result) { if (QFileInfo(dest).size() == 0) { // File was not created m_done = false; m_errorMessage.append(i18n("Failed to create proxy clip.")); } else { m_done = true; } } else { // Proxy process crashed QFile::remove(dest); m_done = false; m_errorMessage.append(QString::fromUtf8(m_jobProcess->readAll())); } delete m_jobProcess; return result; } void ProxyJob::processLogInfo() { int progress; const QString log = QString::fromUtf8(m_jobProcess->readAll()); if (m_isFfmpegJob) { // Parse FFmpeg output if (m_jobDuration == 0) { if (log.contains(QLatin1String("Duration:"))) { QString data = log.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified(); QStringList numbers = data.split(QLatin1Char(':')); m_jobDuration = (int)(numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble()); } } else if (log.contains(QLatin1String("time="))) { QString time = log.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0); if (time.contains(QLatin1Char(':'))) { QStringList numbers = time.split(QLatin1Char(':')); progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } else { progress = (int)time.toDouble(); } emit jobProgress((int)(100.0 * progress / m_jobDuration)); } } else { // Parse MLT output if (log.contains(QLatin1String("percentage:"))) { progress = log.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt(); emit jobProgress(progress); } } } bool ProxyJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); binClip->setProducerProperty(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); return false; } m_resultConsumed = true; - auto operation = [ clipId = m_clipId ]() + auto operation = [clipId = m_clipId]() { auto binClip = pCore->projectItemModel()->getClipByBinID(clipId); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:proxy")); binClip->setProducerProperty(QStringLiteral("resource"), dest); pCore->bin()->reloadClip(clipId); return true; }; - auto reverse = [ clipId = m_clipId ]() + auto reverse = [clipId = m_clipId]() { auto binClip = pCore->projectItemModel()->getClipByBinID(clipId); const QString dest = binClip->getProducerProperty(QStringLiteral("kdenlive:originalurl")); binClip->setProducerProperty(QStringLiteral("resource"), dest); pCore->bin()->reloadClip(clipId); return true; }; bool ok = operation(); if (ok) { UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } return ok; return true; } diff --git a/src/jobs/speedjob.cpp b/src/jobs/speedjob.cpp index c3d074c6d..4f7d8a9a4 100644 --- a/src/jobs/speedjob.cpp +++ b/src/jobs/speedjob.cpp @@ -1,140 +1,139 @@ /*************************************************************************** * Copyright (C) 2018 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * 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 "speedjob.hpp" #include "bin/clipcreator.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "jobmanager.h" #include "kdenlivesettings.h" #include "project/clipstabilize.h" #include "ui_scenecutdialog_ui.h" +#include #include #include -#include #include SpeedJob::SpeedJob(const QString &binId, double speed, const QString &destUrl) : MeltJob(binId, SPEEDJOB, false, -1, -1) , m_speed(speed) , m_destUrl(destUrl) { m_requiresFilter = false; } const QString SpeedJob::getDescription() const { return i18n("Change clip speed"); } void SpeedJob::configureConsumer() { m_consumer.reset(new Mlt::Consumer(m_profile, "xml", m_destUrl.toUtf8().constData())); m_consumer->set("terminate_on_pause", 1); m_consumer->set("title", "Speed Change"); m_consumer->set("real_time", -KdenliveSettings::mltthreads()); } void SpeedJob::configureProducer() { if (!qFuzzyCompare(m_speed, 1.0)) { QString resource = m_producer->get("resource"); m_producer.reset(new Mlt::Producer(m_profile, "timewarp", QStringLiteral("%1:%2").arg(m_speed).arg(resource).toUtf8().constData())); } } -void SpeedJob::configureFilter() -{ -} +void SpeedJob::configureFilter() {} // static int SpeedJob::prepareJob(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString) { // Show config dialog bool ok; int speed = QInputDialog::getInt(QApplication::activeWindow(), i18n("Clip Speed"), i18n("Percentage"), 100, -100000, 100000, 1, &ok); if (!ok) { return -1; } std::unordered_map destinations; // keys are binIds, values are path to target files for (const auto &binId : binIds) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); // Filter several clips, destination points to a folder QString mltfile = QFileInfo(binClip->url()).absoluteFilePath() + QStringLiteral(".mlt"); destinations[binId] = mltfile; } - // Now we have to create the jobs objects. This is trickier than usual, since the parameters are differents for each job (each clip has its own destination). We have to construct a lambda that does that. + // Now we have to create the jobs objects. This is trickier than usual, since the parameters are differents for each job (each clip has its own + // destination). We have to construct a lambda that does that. - auto createFn = [ dest = std::move(destinations), fSpeed = speed/100.0 ](const QString &id) + auto createFn = [ dest = std::move(destinations), fSpeed = speed / 100.0 ](const QString &id) { return std::make_shared(id, fSpeed, dest.at(id)); }; - // We are now all set to create the job. Note that we pass all the parameters directly through the lambda, hence there are no extra parameters to the function + // We are now all set to create the job. Note that we pass all the parameters directly through the lambda, hence there are no extra parameters to the + // function using local_createFn_t = std::function(const QString &)>; return ptr->startJob(binIds, parentId, std::move(undoString), local_createFn_t(std::move(createFn))); } bool SpeedJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; return false; } m_resultConsumed = true; if (!m_successful) { return false; } auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); // We store the stabilized clips in a sub folder with this name const QString folderName(i18n("Speed Change")); QString folderId = QStringLiteral("-1"); bool found = false; // We first try to see if it exists auto containingFolder = std::static_pointer_cast(binClip->parent()); for (int i = 0; i < containingFolder->childCount(); ++i) { auto currentItem = std::static_pointer_cast(containingFolder->child(i)); if (currentItem->itemType() == AbstractProjectItem::FolderItem && currentItem->name() == folderName) { found = true; folderId = currentItem->clipId(); break; } } if (!found) { // if it was not found, we create it pCore->projectItemModel()->requestAddFolder(folderId, folderName, binClip->parent()->clipId(), undo, redo); } auto id = ClipCreator::createClipFromFile(m_destUrl, folderId, pCore->projectItemModel(), undo, redo); return id != QStringLiteral("-1"); } - diff --git a/src/jobs/stabilizejob.cpp b/src/jobs/stabilizejob.cpp index f26fe9bca..635008555 100644 --- a/src/jobs/stabilizejob.cpp +++ b/src/jobs/stabilizejob.cpp @@ -1,159 +1,158 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * 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 "stabilizejob.hpp" #include "bin/clipcreator.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "jobmanager.h" #include "kdenlivesettings.h" #include "project/clipstabilize.h" #include #include StabilizeJob::StabilizeJob(const QString &binId, const QString &filterName, const QString &destUrl, const std::unordered_map &filterParams) : MeltJob(binId, STABILIZEJOB, true, -1, -1) , m_filterName(filterName) , m_destUrl(destUrl) , m_filterParams(filterParams) { Q_ASSERT(supportedFilters().count(filterName) > 0); } const QString StabilizeJob::getDescription() const { return i18n("Stabilize clips"); } void StabilizeJob::configureConsumer() { m_consumer.reset(new Mlt::Consumer(m_profile, "xml", m_destUrl.toUtf8().constData())); m_consumer->set("all", 1); m_consumer->set("title", "Stabilized"); m_consumer->set("real_time", -KdenliveSettings::mltthreads()); } void StabilizeJob::configureFilter() { m_filter.reset(new Mlt::Filter(m_profile, m_filterName.toUtf8().data())); if ((m_filter == nullptr) || !m_filter->is_valid()) { m_errorMessage.append(i18n("Cannot create filter %1", m_filterName)); return; } // Process filter params for (const auto &it : m_filterParams) { m_filter->set(it.first.toUtf8().constData(), it.second.toUtf8().constData()); } QString targetFile = m_destUrl + QStringLiteral(".trf"); m_filter->set("filename", targetFile.toUtf8().constData()); } // static std::unordered_set StabilizeJob::supportedFilters() { return {QLatin1String("vidstab"), QLatin1String("videostab2"), QLatin1String("videostab")}; } // static -int StabilizeJob::prepareJob(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, - const QString &filterName) +int StabilizeJob::prepareJob(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, const QString &filterName) { Q_ASSERT(supportedFilters().count(filterName) > 0); if (filterName == QLatin1String("vidstab") || filterName == QLatin1String("videostab2") || filterName == QLatin1String("videostab")) { // vidstab QScopedPointer d(new ClipStabilize(binIds, filterName, 100000)); if (d->exec() == QDialog::Accepted) { std::unordered_map filterParams = d->filterParams(); QString destination = d->destination(); std::unordered_map destinations; // keys are binIds, values are path to target files for (const auto &binId : binIds) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); if (binIds.size() == 1) { // We only have one clip, destination points to the final url destinations[binId] = destination; } else { // Filter several clips, destination points to a folder QString mltfile = destination + QFileInfo(binClip->url()).fileName() + QStringLiteral(".mlt"); destinations[binId] = mltfile; } } // Now we have to create the jobs objects. This is trickier than usual, since the parameters are differents for each job (each clip has its own // destination). We have to construct a lambda that does that. auto createFn = [ dest = std::move(destinations), fName = std::move(filterName), fParams = std::move(filterParams) ](const QString &id) { return std::make_shared(id, fName, dest.at(id), fParams); }; // We are now all set to create the job. Note that we pass all the parameters directly through the lambda, hence there are no extra parameters to // the function using local_createFn_t = std::function(const QString &)>; return ptr->startJob(binIds, parentId, std::move(undoString), local_createFn_t(std::move(createFn))); } } return -1; } bool StabilizeJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { qDebug() << "ERROR: Trying to consume invalid results"; return false; } m_resultConsumed = true; if (!m_successful) { return false; } auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); // We store the stabilized clips in a sub folder with this name const QString folderName(i18n("Stabilized")); QString folderId = QStringLiteral("-1"); bool found = false; // We first try to see if it exists auto containingFolder = std::static_pointer_cast(binClip->parent()); for (int i = 0; i < containingFolder->childCount(); ++i) { auto currentItem = std::static_pointer_cast(containingFolder->child(i)); if (currentItem->itemType() == AbstractProjectItem::FolderItem && currentItem->name() == folderName) { found = true; folderId = currentItem->clipId(); break; } } if (!found) { // if it was not found, we create it pCore->projectItemModel()->requestAddFolder(folderId, folderName, binClip->parent()->clipId(), undo, redo); } auto id = ClipCreator::createClipFromFile(m_destUrl, folderId, pCore->projectItemModel(), undo, redo); return id != QStringLiteral("-1"); } diff --git a/src/jobs/stabilizejob.hpp b/src/jobs/stabilizejob.hpp index 3a53a5e2d..5c2120195 100644 --- a/src/jobs/stabilizejob.hpp +++ b/src/jobs/stabilizejob.hpp @@ -1,72 +1,71 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * 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 . * ***************************************************************************/ #pragma once #include "meltjob.h" #include #include /** * @class StabilizeJob * @brief Stabilize a clip using a mlt filter * */ class JobManager; class StabilizeJob : public MeltJob { Q_OBJECT public: /** @brief Creates a stabilize job job for the given bin clip @brief filterName is the name of the actual melt filter to use @brief destUrl is the path to the file we are going to produce @brief filterParams is a map containing the xml parameters of the filter */ StabilizeJob(const QString &binId, const QString &filterName, const QString &destUrl, const std::unordered_map &filterparams); // This is a special function that prepares the stabilize job for a given list of clips. // Namely, it displays the required UI to configure the job and call startJob with the right set of parameters // Then the job is automatically put in queue. Its id is returned - static int prepareJob(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, - const QString &filterName); + static int prepareJob(std::shared_ptr ptr, const std::vector &binIds, int parentId, QString undoString, const QString &filterName); // Return the list of stabilization filters that we support static std::unordered_set supportedFilters(); bool commitResult(Fun &undo, Fun &redo) override; const QString getDescription() const override; protected: // @brief create and configure consumer void configureConsumer() override; // @brief create and configure filter void configureFilter() override; protected: QString m_filterName; QString m_destUrl; std::unordered_map m_filterParams; }; diff --git a/src/jobs/thumbjob.cpp b/src/jobs/thumbjob.cpp index 173da2128..19f801559 100644 --- a/src/jobs/thumbjob.cpp +++ b/src/jobs/thumbjob.cpp @@ -1,174 +1,174 @@ /*************************************************************************** * 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 "thumbjob.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "bin/projectsubclip.h" #include "core.h" #include "doc/kthumb.h" #include "klocalizedstring.h" #include "macros.hpp" #include "utils/thumbnailcache.hpp" #include #include #include ThumbJob::ThumbJob(const QString &binId, int imageHeight, int frameNumber, bool persistent, bool reloadAllThumbs) : AbstractClipJob(THUMBJOB, binId) , m_frameNumber(frameNumber) , m_fullWidth(imageHeight * pCore->getCurrentDar() + 0.5) , m_imageHeight(imageHeight) , m_persistent(persistent) , m_reloadAll(reloadAllThumbs) , m_subClip(false) { auto item = pCore->projectItemModel()->getItemByBinId(binId); Q_ASSERT(item->itemType() == AbstractProjectItem::ClipItem || item->itemType() == AbstractProjectItem::SubClipItem); if (item->itemType() == AbstractProjectItem::ClipItem) { m_binClip = pCore->projectItemModel()->getClipByBinID(binId); } else if (item->itemType() == AbstractProjectItem::SubClipItem) { m_subClip = true; m_binClip = pCore->projectItemModel()->getClipByBinID(item->parent()->clipId()); m_frameNumber = std::max(m_frameNumber, std::static_pointer_cast(item)->zone().x()); } } const QString ThumbJob::getDescription() const { return i18n("Extracting thumb at frame %1 from clip %2", m_frameNumber, m_clipId); } bool ThumbJob::startJob() { if (m_done) { return true; } // We reload here, because things may have changed since creation of this job if (m_subClip) { auto item = pCore->projectItemModel()->getItemByBinId(m_clipId); m_binClip = std::static_pointer_cast(item->parent()); } else { m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId); } if (m_binClip->clipType() == ClipType::Audio) { // Don't create thumbnail for audio clips m_done = false; return true; } m_prod = m_binClip->thumbProducer(); if ((m_prod == nullptr) || !m_prod->is_valid()) { return false; } m_inCache = false; int max = m_prod->get_length(); m_frameNumber = m_binClip->clipType() == ClipType::Image ? 0 : qBound(0, m_frameNumber, max - 1); // m_frameNumber = ProjectClip::getXmlProperty(info.xml, QStringLiteral("kdenlive:thumbnailFrame"), QStringLiteral("-1")).toInt(); if (ThumbnailCache::get()->hasThumbnail(m_binClip->clipId(), m_frameNumber, !m_persistent)) { m_done = true; m_result = ThumbnailCache::get()->getThumbnail(m_binClip->clipId(), m_frameNumber); m_inCache = true; return true; } if (m_frameNumber > 0) { m_prod->seek(m_frameNumber); } QScopedPointer frame(m_prod->get_frame()); frame->set("deinterlace_method", "onefield"); frame->set("top_field_first", -1); frame->set("rescale.interp", "nearest"); if ((frame != nullptr) && frame->is_valid()) { m_result = KThumb::getFrame(frame.data(), m_fullWidth, m_imageHeight, true); m_done = true; } return m_done; } bool ThumbJob::commitResult(Fun &undo, Fun &redo) { Q_ASSERT(!m_resultConsumed); if (!m_done) { if (m_binClip->clipType() == ClipType::Audio) { // audio files get standard audio icon, ok return true; } qDebug() << "ERROR: Trying to consume invalid results"; return false; } if (!m_inCache) { if (m_result.isNull()) { - qDebug()<<"+++++\nINVALID RESULT IMAGE\n++++++++++++++"; + qDebug() << "+++++\nINVALID RESULT IMAGE\n++++++++++++++"; } else { ThumbnailCache::get()->storeThumbnail(m_binClip->clipId(), m_frameNumber, m_result, m_persistent); } } m_resultConsumed = true; // TODO a refactor of ProjectClip and ProjectSubClip should make that possible without branching (both classes implement setThumbnail) bool ok = false; if (m_subClip) { auto subClip = std::static_pointer_cast(pCore->projectItemModel()->getItemByBinId(m_clipId)); QImage old = subClip->thumbnail(m_result.width(), m_result.height()).toImage(); // note that the image is moved into lambda, it won't be available from this class anymore auto operation = [ clip = subClip, image = std::move(m_result) ]() - { - clip->setThumbnail(image); - return true; - }; + { + clip->setThumbnail(image); + return true; + }; auto reverse = [ clip = subClip, image = std::move(old) ]() - { - clip->setThumbnail(image); - return true; - }; + { + clip->setThumbnail(image); + return true; + }; ok = operation(); if (ok) { UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } } else { QImage old = m_binClip->thumbnail(m_result.width(), m_result.height()).toImage(); // note that the image is moved into lambda, it won't be available from this class anymore auto operation = [ clip = m_binClip, image = std::move(m_result), this ]() { clip->setThumbnail(image); if (m_reloadAll) { clip->updateTimelineClips({TimelineModel::ReloadThumbRole}); } return true; }; auto reverse = [ clip = m_binClip, image = std::move(old), this ]() { clip->setThumbnail(image); if (m_reloadAll) { clip->updateTimelineClips({TimelineModel::ReloadThumbRole}); } return true; }; ok = operation(); if (ok) { UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo); } } return ok; } diff --git a/src/jogshuttle/jogshuttleconfig.cpp b/src/jogshuttle/jogshuttleconfig.cpp index 123918676..2e7c6e388 100644 --- a/src/jogshuttle/jogshuttleconfig.cpp +++ b/src/jogshuttle/jogshuttleconfig.cpp @@ -1,75 +1,75 @@ /*************************************************************************** * Copyright (C) 2010 by Pascal Fleury (fleury@users.sourceforge.net) * * * * 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 "jogshuttleconfig.h" #include #include #include #include #include using std::string; -using std::vector; using std::stringstream; +using std::vector; // these 2 functions will convert the action maps to and from a string representation not unlike this: // button1=rewind_one_frame;button2=forward_one_frame;button15=play static const QChar DELIMITER = ';'; static const QChar KEY_VALUE_SEP = '='; static const QString BUTTON_PREFIX(QStringLiteral("button")); QStringList JogShuttleConfig::actionMap(const QString &actionsConfig) { QStringList actionMap; const QStringList mappings = actionsConfig.split(DELIMITER); for (const QString &mapping : mappings) { QStringList parts = mapping.split(KEY_VALUE_SEP); if (parts.size() != 2) { fprintf(stderr, "Invalid button configuration: %s", mapping.toLatin1().constData()); continue; } // skip the 'button' prefix int button_id = parts[0].midRef(BUTTON_PREFIX.length()).toInt(); // fprintf(stderr, " - Handling map key='%s' (ID=%d), value='%s'\n", parts[0].data().toLatin1(), button_id, parts[1].data().toLatin1()); // DBG while (actionMap.size() <= button_id) { actionMap << QString(); } actionMap[button_id] = parts[1]; } // for (int i = 0; i < actionMap.size(); ++i) fprintf(stderr, "button #%d -> action '%s'\n", i, actionMap[i].data().toLatin1()); //DBG return actionMap; } QString JogShuttleConfig::actionMap(const QStringList &actionMap) { QStringList mappings; for (int i = 0; i < actionMap.size(); ++i) { if (actionMap[i].isEmpty()) { continue; } mappings << QStringLiteral("%1%2%3%4").arg(BUTTON_PREFIX).arg(i).arg(KEY_VALUE_SEP).arg(actionMap[i]); } return mappings.join(DELIMITER); } diff --git a/src/layoutmanagement.cpp b/src/layoutmanagement.cpp index cdfb69dfe..dff410b94 100644 --- a/src/layoutmanagement.cpp +++ b/src/layoutmanagement.cpp @@ -1,126 +1,126 @@ /* Copyright (C) 2012 Jean-Baptiste Mardelle Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #include "layoutmanagement.h" #include "core.h" #include "mainwindow.h" #include #include #include #include #include #include LayoutManagement::LayoutManagement(QObject *parent) : QObject(parent) { // Prepare layout actions KActionCategory *layoutActions = new KActionCategory(i18n("Layouts"), pCore->window()->actionCollection()); m_loadLayout = new KSelectAction(i18n("Load Layout"), pCore->window()->actionCollection()); for (int i = 1; i < 5; ++i) { QAction *load = new QAction(QIcon(), i18n("Layout %1", i), this); load->setData('_' + QString::number(i)); layoutActions->addAction("load_layout" + QString::number(i), load); m_loadLayout->addAction(load); QAction *save = new QAction(QIcon(), i18n("Save As Layout %1", i), this); save->setData('_' + QString::number(i)); layoutActions->addAction("save_layout" + QString::number(i), save); } // Required to enable user to add the load layout action to toolbar layoutActions->addAction(QStringLiteral("load_layouts"), m_loadLayout); - connect(m_loadLayout, static_cast(&KSelectAction::triggered), this, &LayoutManagement::slotLoadLayout); + connect(m_loadLayout, static_cast(&KSelectAction::triggered), this, &LayoutManagement::slotLoadLayout); connect(pCore->window(), &MainWindow::GUISetupDone, this, &LayoutManagement::slotOnGUISetupDone); } void LayoutManagement::initializeLayouts() { QMenu *saveLayout = static_cast(pCore->window()->factory()->container(QStringLiteral("layout_save_as"), pCore->window())); if (m_loadLayout == nullptr || saveLayout == nullptr) { return; } KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup layoutGroup(config, "Layouts"); QStringList entries = layoutGroup.keyList(); QList loadActions = m_loadLayout->actions(); QList saveActions = saveLayout->actions(); for (int i = 1; i < 5; ++i) { // Rename the layouts actions for (const QString &key : entries) { if (key.endsWith(QStringLiteral("_%1").arg(i))) { // Found previously saved layout QString layoutName = key.section(QLatin1Char('_'), 0, -2); for (int j = 0; j < loadActions.count(); ++j) { if (loadActions.at(j)->data().toString().endsWith('_' + QString::number(i))) { loadActions[j]->setText(layoutName); loadActions[j]->setData(key); break; } } for (int j = 0; j < saveActions.count(); ++j) { if (saveActions.at(j)->data().toString().endsWith('_' + QString::number(i))) { saveActions[j]->setText(i18n("Save as %1", layoutName)); saveActions[j]->setData(key); break; } } } } } } void LayoutManagement::slotLoadLayout(QAction *action) { if (!action) { return; } QString layoutId = action->data().toString(); if (layoutId.isEmpty()) { return; } KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup layouts(config, "Layouts"); QByteArray state = QByteArray::fromBase64(layouts.readEntry(layoutId).toLatin1()); pCore->window()->restoreState(state); } void LayoutManagement::slotSaveLayout(QAction *action) { QString originallayoutName = action->data().toString(); int layoutId = originallayoutName.section(QLatin1Char('_'), -1).toInt(); QString layoutName = QInputDialog::getText(pCore->window(), i18n("Save Layout"), i18n("Layout name:"), QLineEdit::Normal, originallayoutName.section(QLatin1Char('_'), 0, -2)); if (layoutName.isEmpty()) { return; } KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup layouts(config, "Layouts"); layouts.deleteEntry(originallayoutName); QByteArray st = pCore->window()->saveState(); layoutName.append('_' + QString::number(layoutId)); layouts.writeEntry(layoutName, st.toBase64()); initializeLayouts(); } void LayoutManagement::slotOnGUISetupDone() { QMenu *saveLayout = static_cast(pCore->window()->factory()->container(QStringLiteral("layout_save_as"), pCore->window())); if (saveLayout) { connect(saveLayout, &QMenu::triggered, this, &LayoutManagement::slotSaveLayout); } initializeLayouts(); } diff --git a/src/lib/external/media_ctrl/mediactrl.c b/src/lib/external/media_ctrl/mediactrl.c index a7f119dba..d7fb0f0a9 100644 --- a/src/lib/external/media_ctrl/mediactrl.c +++ b/src/lib/external/media_ctrl/mediactrl.c @@ -1,400 +1,400 @@ /* -* mediactrl.c -- Jog Shuttle device support -* Copyright (C) 2001-2007 Dan Dennedy -* -* 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 -*/ + * mediactrl.c -- Jog Shuttle device support + * Copyright (C) 2001-2007 Dan Dennedy + * + * 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 #include #include #include #include #include #include #include #include #include #if defined(Q_OS_LINUX) #include #endif #include #include #include "mediactrl.h" static char *_shuttle_name = (char *)"Shuttle"; static char *_jog_name = (char *)"Jog"; /* ShuttlePro v2 keys */ static struct media_ctrl_key mc_shuttle_pro_v2_keys[] = { {0x100, "Button 1", MEDIA_CTRL_F1}, {0x101, "Button 2", MEDIA_CTRL_F2}, {0x102, "Button 3", MEDIA_CTRL_F3}, {0x103, "Button 4", MEDIA_CTRL_F4}, {0x104, "Button 5", MEDIA_CTRL_B1}, {0x105, "Button 6", MEDIA_CTRL_B2}, {0x106, "Button 7", MEDIA_CTRL_B3}, {0x107, "Button 8", MEDIA_CTRL_B4}, {0x108, "Button 9", MEDIA_CTRL_B5}, {0x109, "Button 10", MEDIA_CTRL_B6}, {0x10a, "Button 11", MEDIA_CTRL_B7}, {0x10b, "Button 12", MEDIA_CTRL_B8}, {0x10c, "Button 13", MEDIA_CTRL_B9}, {0x10d, "Button 14", MEDIA_CTRL_B10}, {0x10e, "Button 15", MEDIA_CTRL_B11}, {0, NULL, 0}}; /* ShuttlePro keys */ static struct media_ctrl_key mc_shuttle_pro_keys[] = {{0x100, "Button 1", MEDIA_CTRL_F1}, {0x101, "Button 2", MEDIA_CTRL_F2}, {0x102, "Button 3", MEDIA_CTRL_F3}, {0x103, "Button 4", MEDIA_CTRL_F4}, {0x104, "Button 5", MEDIA_CTRL_B4}, {0x105, "Button 6", MEDIA_CTRL_B2}, {0x106, "Button 7", MEDIA_CTRL_B1}, {0x107, "Button 8", MEDIA_CTRL_B3}, {0x108, "Button 9", MEDIA_CTRL_B5}, {0x109, "Button 10", MEDIA_CTRL_B6}, {0x10a, "Button 11", MEDIA_CTRL_B7}, {0x10b, "Button 12", MEDIA_CTRL_B8}, {0x10c, "Button 13", MEDIA_CTRL_B9}, {0, NULL, 0}}; /* ShuttleXPress keys */ static struct media_ctrl_key mc_shuttle_xpress_keys[] = {{0x104, "Button B1", MEDIA_CTRL_B1}, {0x105, "Button B2", MEDIA_CTRL_B2}, {0x106, "Button B3", MEDIA_CTRL_B3}, {0x107, "Button B4", MEDIA_CTRL_B4}, {0x108, "Button B5", MEDIA_CTRL_B5}, {0, NULL, 0}}; /* JLCooper MCS3 Keys */ static struct media_ctrl_key mc_jlcooper_mcs3_keys[] = {{0x107, "F1", MEDIA_CTRL_F1}, {0x101, "F2", MEDIA_CTRL_F2}, {0x105, "F3", MEDIA_CTRL_F3}, {0x102, "F4", MEDIA_CTRL_F4}, {0x103, "F5", MEDIA_CTRL_F5}, {0x104, "F6", MEDIA_CTRL_F6}, {0x10d, "W1", MEDIA_CTRL_B6}, {0x10e, "W2", MEDIA_CTRL_B4}, {0x100, "W3", MEDIA_CTRL_B2}, {0x106, "W4", MEDIA_CTRL_B1}, {0x110, "W5", MEDIA_CTRL_B3}, {0x111, "W6", MEDIA_CTRL_B5}, {0x115, "W7", MEDIA_CTRL_B7}, {0x116, "STICK_LEFT", MEDIA_CTRL_STICK_LEFT}, {0x113, "STICK_RIGHT", MEDIA_CTRL_STICK_RIGHT}, {0x114, "STICK_UP", MEDIA_CTRL_STICK_UP}, {0x112, "STICK_DOWN", MEDIA_CTRL_STICK_DOWN}, {0x10f, "Rewind", MEDIA_CTRL_REWIND}, {0x108, "Fast Forward", MEDIA_CTRL_FAST_FORWARD}, {0x109, "Stop", MEDIA_CTRL_STOP}, {0x10a, "Play", MEDIA_CTRL_PLAY}, {0x10b, "Record", MEDIA_CTRL_RECORD}, {0, NULL, 0}}; /* Griffin PowerMate */ static struct media_ctrl_key mc_powermate_keys[] = {{BTN_0, "Button", MEDIA_CTRL_B1}, {0, NULL, 0}}; /* X-Keys Jog/Shuttle */ static struct media_ctrl_key mc_x_keys[] = {{0x102, "Button L1", MEDIA_CTRL_F1}, {0x103, "Button L2", MEDIA_CTRL_F9}, {0x104, "Button L3", MEDIA_CTRL_B1}, {0x105, "Button L4", MEDIA_CTRL_B3}, {0x106, "Button L5", MEDIA_CTRL_B5}, {0x10a, "Button L6", MEDIA_CTRL_F2}, {0x10b, "Button L7", MEDIA_CTRL_F10}, {0x10c, "Button L8", MEDIA_CTRL_B2}, {0x10d, "Button L9", MEDIA_CTRL_B4}, {0x10e, "Button L10", MEDIA_CTRL_B6}, {0x112, "Button C1", MEDIA_CTRL_F3}, {0x11a, "Button C2", MEDIA_CTRL_F4}, {0x122, "Button C3", MEDIA_CTRL_F5}, {0x12a, "Button C4", MEDIA_CTRL_F6}, {0x113, "Button C5", MEDIA_CTRL_F11}, {0x11b, "Button C6", MEDIA_CTRL_F12}, {0x123, "Button C7", MEDIA_CTRL_F13}, {0x12b, "Button C8", MEDIA_CTRL_F14}, {0x132, "Button R1", MEDIA_CTRL_F7}, {0x133, "Button R2", MEDIA_CTRL_F15}, {0x134, "Button R3", MEDIA_CTRL_B7}, {0x135, "Button R4", MEDIA_CTRL_B9}, {0x136, "Button R5", MEDIA_CTRL_B11}, {0x13a, "Button R6", MEDIA_CTRL_F8}, {0x13b, "Button R7", MEDIA_CTRL_F16}, {0x13c, "Button R8", MEDIA_CTRL_B8}, {0x13d, "Button R9", MEDIA_CTRL_B10}, {0x13e, "Button R10", MEDIA_CTRL_B12}, {0, NULL, 0}}; struct media_ctrl_key *media_ctrl_get_key(struct media_ctrl *ctrl, int code, int *index) { int i = 0; struct media_ctrl_key *keys = ctrl->device->keys; while (keys[i].key != 0) { if (keys[i].key == code) { if (index != NULL) *index = i; return &keys[i]; } i++; } return NULL; } int media_ctrl_get_keys_count(struct media_ctrl *ctrl) { int i = 0; struct media_ctrl_key *keys = ctrl->device->keys; while (keys[i].key != 0) { i++; } return i; } void translate_contour_hid_event(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me) { me->type = 0; if (ev->type == EV_REL) { int cv; /* First check the outer dial */ if (ev->code == REL_WHEEL) { cv = (signed int)ev->value; if (cv == 1 || cv == -1) cv = 0; if (cv == ctrl->lastshu) return; ctrl->lastshu = cv; /* TODO: review this change */ if (cv > 0) cv -= 1; if (cv < 0) cv += 1; // printf("Shuttle: %d\n", cv); me->type = MEDIA_CTRL_EVENT_SHUTTLE; me->value = cv * 2; me->name = _shuttle_name; } else if (ev->code == REL_DIAL) { int lv; if (ctrl->lastval == -1) ctrl->lastval = ev->value; lv = ctrl->lastval; cv = ev->value; if (lv == cv) return; ctrl->lastval = cv; if (cv < 10 && lv > 0xF0) cv += 0x100; if (lv < 10 && cv > 0xF0) lv += 0x100; me->type = MEDIA_CTRL_EVENT_JOG; me->value = cv - lv; me->name = _jog_name; ctrl->jogpos += me->value; // printf("Jog: %06ld (%d)\n", ctrl->jogpos, me->value); } return; } if (ev->type == EV_KEY) { int index; struct media_ctrl_key *key = media_ctrl_get_key(ctrl, ev->code, &index); if (key == NULL) return; me->type = MEDIA_CTRL_EVENT_KEY; me->code = key->code; me->value = ev->value; me->name = (char *)key->name; me->index = index; // printf("Key: %04x %02x: %s\n", ev->code, ev->value, key->name); } } void translate_compliant(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me) { me->type = 0; // printf("Translate %02x %02x\n", ev->type, ev->code ); if (ev->type == EV_REL) { if (ev->code == REL_DIAL) { me->type = MEDIA_CTRL_EVENT_JOG; me->value = (signed int)ev->value; me->name = _jog_name; ctrl->jogpos += me->value; // printf("Jog: %06ld (%d)\n", ctrl->jogpos, me->value); } return; } if (ev->type == EV_ABS) { // printf("ABS\n" ); if (ev->code == 0x1c || ev->code == ABS_THROTTLE) { // printf("ABS_MISC\n" ); me->type = MEDIA_CTRL_EVENT_SHUTTLE; me->value = (signed int)ev->value; me->name = _shuttle_name; ctrl->shuttlepos = me->value; // printf("Shuttle: %06d (%d)\n", ctrl->shuttlepos, me->value); } } else if (ev->type == EV_KEY) { int index; struct media_ctrl_key *key = media_ctrl_get_key(ctrl, ev->code, &index); if (key == NULL) return; me->type = MEDIA_CTRL_EVENT_KEY; me->code = key->code; me->value = ev->value; me->name = (char *)key->name; me->index = index; // printf("Key: %04x %02x: %s\n", ev->code, ev->value, key->name); } } struct media_ctrl_device supported_devices[] = { {0x0b33, 0x0030, "Contour Design ShuttlePRO v2", mc_shuttle_pro_v2_keys, translate_contour_hid_event}, {0x0b33, 0x0020, "Contour Design ShuttleXpress", mc_shuttle_xpress_keys, translate_contour_hid_event}, {0x0b33, 0x0010, "Contour Design ShuttlePro", mc_shuttle_pro_keys, translate_contour_hid_event}, {0x0b33, 0x0011, "Contour Design ShuttlePro", mc_shuttle_pro_keys, translate_contour_hid_event}, /* Hercules OEM */ {0x05f3, 0x0240, "Contour Design ShuttlePro", mc_shuttle_pro_keys, translate_contour_hid_event}, {0x0760, 0x0001, "JLCooper MCS3", mc_jlcooper_mcs3_keys, translate_compliant}, {0x077d, 0x0410, "Griffin PowerMate", mc_powermate_keys, translate_compliant}, {0x05f3, 0x0241, "X-Keys Editor", mc_x_keys, translate_contour_hid_event}, {0, 0, 0, 0, 0}}; void media_ctrl_read_event(struct media_ctrl *ctrl, struct media_ctrl_event *me) { ssize_t n; struct input_event ev; // struct media_ctrl_event me; if (ctrl->fd > 0) { n = read(ctrl->fd, &ev, sizeof(ev)); } else { return; } if (n != sizeof(ev)) { // printf("JogShuttle::inputCallback: read: (%d) %s\n", errno, strerror(errno)); close(ctrl->fd); ctrl->fd = -1; return; } if (ctrl->device && ctrl->device->translate) ctrl->device->translate(ctrl, &ev, me); else me->type = 0; if (me->type == MEDIA_CTRL_EVENT_JOG) { struct timeval timev; gettimeofday(&timev, NULL); unsigned long now = (unsigned long)timev.tv_usec + (1000000 * (unsigned long)timev.tv_sec); if (now < ctrl->last_jog_time + 40000) { // printf("*** Fast Jog %02d %05d ***\n", me->value, now - ctrl->last_jog_time); ctrl->jogrel = me->value; me->type = MEDIA_CTRL_EVENT_NONE; } else { me->value += ctrl->jogrel; ctrl->jogrel = 0; ctrl->last_jog_time = now; // printf("*** Jog %02d ***\n", me->value); } } } int probe_device(struct media_ctrl *mc) { short devinfo[4]; int i = 0; if (ioctl(mc->fd, EVIOCGID, &devinfo)) { perror("evdev ioctl"); return 0; } do { if (supported_devices[i].vendor == devinfo[1] && supported_devices[i].product == devinfo[2]) { mc->device = &supported_devices[i]; // printf("Success on /dev/input/event%d: %s\n", mc->eventno, mc->device->name); // mc->fd = fd; // mc->translate = mc->device.translate_function; // mc = malloc(sizeof(struct media_ctrl)); mc->jogpos = 0; mc->lastval = -1; mc->last_jog_time = 0; return 1; } // mc->device = NULL; } while (supported_devices[++i].vendor != 0); return 0; } void find_first_device(struct media_ctrl *mc) { char buf[256]; for (int i = 0; i < 32; i++) { sprintf(buf, "/dev/input/event%d", i); int fd = open(buf, O_RDONLY); if (fd < 0) { perror(buf); } else { mc->fd = fd; mc->eventno = i; if (probe_device(mc)) { return; } close(fd); mc->fd = -1; } } } void media_ctrl_close(struct media_ctrl *mc) { if (mc->fd > 0) close(mc->fd); memset(mc, 0, sizeof(struct media_ctrl)); } void media_ctrl_open(struct media_ctrl *mc) { find_first_device(mc); } void media_ctrl_open_dev(struct media_ctrl *mc, const char *devname) { int fd; fd = open(devname, O_RDONLY); if (fd < 0) { perror(devname); mc->fd = -1; } else { mc->fd = fd; // mc->eventno = i; if (probe_device(mc)) { return; } close(fd); mc->fd = -1; } } diff --git a/src/lib/external/media_ctrl/mediactrl.h b/src/lib/external/media_ctrl/mediactrl.h index c01394d59..0e9383641 100644 --- a/src/lib/external/media_ctrl/mediactrl.h +++ b/src/lib/external/media_ctrl/mediactrl.h @@ -1,163 +1,163 @@ /* -* mediactrl.c -- Jog Shuttle device support -* Copyright (C) 2001-2007 Dan Dennedy -* -* 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 -*/ + * mediactrl.c -- Jog Shuttle device support + * Copyright (C) 2001-2007 Dan Dennedy + * + * 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 MEDIACTRL_H #define MEDIACTRL_H -#include #include +#include // just to make the code more readable #define KEY_RELEASE 0x00 #define KEY_PRESS 0x01 // not used yet #define MEDIA_ST_ACTIVE 0x02 #define MEDIA_ST_INACTIVE 0x01 // media ctrl event types #define MEDIA_CTRL_EVENT_NONE 0x00 #define MEDIA_CTRL_EVENT_KEY 0x01 #define MEDIA_CTRL_EVENT_JOG 0x02 #define MEDIA_CTRL_EVENT_SHUTTLE 0x03 #define MEDIA_CTRL_EVENT_STICK 0x04 // the disconnect event - not used yet #define MEDIA_CTRL_DISCONNECT 0x01 #define MEDIA_CTRL_SHIFT 0x01 #define MEDIA_CTRL_PLAY 0x10 #define MEDIA_CTRL_PLAY_FWD 0x10 #define MEDIA_CTRL_REVERSE 0x11 #define MEDIA_CTRL_PLAY_REV 0x11 #define MEDIA_CTRL_STOP 0x12 #define MEDIA_CTRL_PAUSE 0x13 #define MEDIA_CTRL_NEXT 0x14 #define MEDIA_CTRL_PREV 0x15 #define MEDIA_CTRL_RECORD 0x16 #define MEDIA_CTRL_FAST_FORWARD 0x17 #define MEDIA_CTRL_REWIND 0x18 #define MEDIA_CTRL_STICK_LEFT 0x20 #define MEDIA_CTRL_STICK_RIGHT 0x21 #define MEDIA_CTRL_STICK_UP 0x22 #define MEDIA_CTRL_STICK_DOWN 0x23 /* function keys, usually at top of device */ #define MEDIA_CTRL_F1 0x100 #define MEDIA_CTRL_F2 0x101 #define MEDIA_CTRL_F3 0x102 #define MEDIA_CTRL_F4 0x103 #define MEDIA_CTRL_F5 0x104 #define MEDIA_CTRL_F6 0x105 #define MEDIA_CTRL_F7 0x106 #define MEDIA_CTRL_F8 0x107 #define MEDIA_CTRL_F9 0x108 #define MEDIA_CTRL_F10 0x109 #define MEDIA_CTRL_F11 0x10a #define MEDIA_CTRL_F12 0x10b #define MEDIA_CTRL_F13 0x10c #define MEDIA_CTRL_F14 0x10d #define MEDIA_CTRL_F15 0x10e #define MEDIA_CTRL_F16 0x10f #define MEDIA_CTRL_B1 0x110 #define MEDIA_CTRL_B2 0x111 #define MEDIA_CTRL_B3 0x112 #define MEDIA_CTRL_B4 0x113 #define MEDIA_CTRL_B5 0x114 #define MEDIA_CTRL_B6 0x115 #define MEDIA_CTRL_B7 0x116 #define MEDIA_CTRL_B8 0x117 #define MEDIA_CTRL_B9 0x118 #define MEDIA_CTRL_B10 0x119 #define MEDIA_CTRL_B11 0x11a #define MEDIA_CTRL_B12 0x11b #define MEDIA_CTRL_B13 0x11c #define MEDIA_CTRL_B14 0x11d #define MEDIA_CTRL_B15 0x11e #define MEDIA_CTRL_B16 0x11f #ifdef __cplusplus extern "C" { #endif struct media_ctrl_device; struct media_ctrl_key { int key; // internal keycode - do not use const char *name; int code; // eventcode // int action; }; struct media_ctrl_event { struct timeval time; unsigned short type; unsigned short code; char *name; int value; unsigned short index; }; struct media_ctrl { int fd; int eventno; int status; struct media_ctrl_device *device; long jogpos; int shuttlepos; int lastval; int lastshu; int jogrel; // accumulate relative values if events come too fast unsigned long last_jog_time; // last jog event }; struct media_ctrl_device { int vendor; int product; const char *name; struct media_ctrl_key *keys; void (*translate)(struct media_ctrl *ctrl, struct input_event *ev, struct media_ctrl_event *me); }; void media_ctrl_open_dev(struct media_ctrl *, const char *devname); void media_ctrl_close(struct media_ctrl *); void media_ctrl_read_event(struct media_ctrl *, struct media_ctrl_event *); struct media_ctrl_key *media_ctrl_get_keys(struct media_ctrl *); int media_ctrl_get_keys_count(struct media_ctrl *); #ifdef __cplusplus } #endif #endif diff --git a/src/library/librarywidget.h b/src/library/librarywidget.h index 5116f53a2..df9badc20 100644 --- a/src/library/librarywidget.h +++ b/src/library/librarywidget.h @@ -1,214 +1,214 @@ /*************************************************************************** * 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 . * ***************************************************************************/ /*! -* @class LibraryWidget -* @brief A "library" that contains a list of clips to be used across projects -* @author Jean-Baptiste Mardelle -*/ + * @class LibraryWidget + * @brief A "library" that contains a list of clips to be used across projects + * @author Jean-Baptiste Mardelle + */ #ifndef LIBRARYWIDGET_H #define LIBRARYWIDGET_H #include "definitions.h" #include #include #include #include #include #include #include #include #include #include #include #include class ProjectManager; class KJob; class QProgressBar; class QToolBar; /** * @class BinItemDelegate * @brief This class is responsible for drawing items in the QTreeView. */ class LibraryItemDelegate : public QStyledItemDelegate { public: explicit LibraryItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) { } void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); QRect r1 = option.rect; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int decoWidth = 2 * textMargin + r1.height() * 1.8; int mid = (int)((r1.height() / 2)); r1.adjust(decoWidth, 0, 0, -mid); QFont ft = option.font; ft.setBold(true); QFontMetricsF fm(ft); QRect r2 = fm.boundingRect(r1, Qt::AlignLeft | Qt::AlignTop, index.data(Qt::DisplayRole).toString()).toRect(); editor->setGeometry(r2); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSize hint = QStyledItemDelegate::sizeHint(option, index); QString text = index.data(Qt::UserRole + 1).toString(); QRectF r = option.rect; QFont ft = option.font; ft.setBold(true); QFontMetricsF fm(ft); QStyle *style = option.widget ? option.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; int width = fm.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, text).width() + option.decorationSize.width() + 2 * textMargin; hint.setWidth(width); return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMax(hint.height(), option.decorationSize.height()))); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (index.column() == 0) { QRect r1 = option.rect; painter->save(); painter->setClipRect(r1); QStyleOptionViewItem opt(option); initStyleOption(&opt, index); QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; // QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1); style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } else { painter->setPen(option.palette.text().color()); } QRect r = r1; QFont font = painter->font(); font.setBold(true); painter->setFont(font); int decoWidth = 2 * textMargin + r1.height() * 1.8; r.setWidth(r1.height() * 1.8); // Draw thumbnail opt.icon.paint(painter, r); int mid = (int)((r1.height() / 2)); r1.adjust(decoWidth, 0, 0, -mid); QRect r2 = option.rect; r2.adjust(decoWidth, mid, 0, 0); QRectF bounding; painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(Qt::DisplayRole).toString(), &bounding); font.setBold(false); painter->setFont(font); QString subText = index.data(Qt::UserRole + 1).toString(); r2.adjust(0, bounding.bottom() - r2.top(), 0, 0); QColor subTextColor = painter->pen().color(); subTextColor.setAlphaF(.5); painter->setPen(subTextColor); painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop, subText, &bounding); painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } } }; class LibraryTree : public QTreeWidget { Q_OBJECT public: explicit LibraryTree(QWidget *parent = nullptr); protected: QStringList mimeTypes() const override; QMimeData *mimeData(const QList list) const override; void dropEvent(QDropEvent *event) override; void mousePressEvent(QMouseEvent *event) override; public slots: void slotUpdateThumb(const QString &path, const QString &iconPath); void slotUpdateThumb(const QString &path, const QPixmap &pix); signals: void moveData(const QList &, const QString &); void importSequence(const QStringList &, const QString &); }; class LibraryWidget : public QWidget { Q_OBJECT public: explicit LibraryWidget(ProjectManager *m_manager, QWidget *parent = nullptr); void setupActions(const QList &list); public slots: void slotAddToLibrary(); void slotUpdateLibraryPath(); private slots: void slotAddToProject(); void slotDeleteFromLibrary(); void updateActions(); void slotAddFolder(); void slotRenameItem(); void slotMoveData(const QList &, QString); void slotSaveSequence(const QStringList &info, QString dest); void slotItemEdited(QTreeWidgetItem *item, int column); void slotDownloadFinished(KJob *); void slotDownloadProgress(KJob *, int); void slotGotPreview(const KFileItem &item, const QPixmap &pix); void slotItemsAdded(const QUrl &url, const KFileItemList &list); void slotItemsDeleted(const KFileItemList &list); void slotClearAll(); private: LibraryTree *m_libraryTree; QToolBar *m_toolBar; QProgressBar *m_progressBar; QAction *m_addAction; QAction *m_deleteAction; QTimer m_timer; KMessageWidget *m_infoWidget; ProjectManager *m_manager; QList m_folders; KIO::PreviewJob *m_previewJob; KCoreDirLister *m_coreLister; QMutex m_treeMutex; QDir m_directory; void showMessage(const QString &text, KMessageWidget::MessageType type = KMessageWidget::Warning); signals: void addProjectClips(const QList &); void thumbReady(const QString &, const QString &); }; #endif diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 81bb9532f..b29a4de79 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,4140 +1,4139 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 "mainwindow.h" #include "assets/assetpanel.hpp" #include "bin/clipcreator.hpp" #include "bin/generators/generators.h" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "dialogs/clipcreationdialog.h" #include "dialogs/kdenlivesettingsdialog.h" #include "dialogs/renderwidget.h" #include "dialogs/wizard.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "effects/effectlist/view/effectlistwidget.hpp" #include "effectslist/effectbasket.h" #include "effectslist/effectslistview.h" #include "effectslist/effectslistwidget.h" #include "effectslist/initeffects.h" #include "hidetitlebars.h" #include "jobs/jobmanager.h" #include "jobs/scenesplitjob.hpp" #include "jobs/speedjob.hpp" #include "jobs/stabilizejob.hpp" #include "kdenlivesettings.h" #include "layoutmanagement.h" #include "library/librarywidget.h" #include "mainwindowadaptor.h" #include "mltconnection.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/clipcontroller.h" #include "monitor/monitor.h" #include "monitor/monitormanager.h" #include "monitor/scopes/audiographspectrum.h" #include "profiles/profilemodel.hpp" #include "project/clipmanager.h" #include "project/cliptranscode.h" #include "project/dialogs/archivewidget.h" #include "project/dialogs/projectsettings.h" #include "project/projectcommands.h" #include "project/projectmanager.h" #include "renderer.h" #include "scopes/scopemanager.h" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinetabs.hpp" #include "timeline2/view/timelinewidget.h" #include "titler/titlewidget.h" #include "transitions/transitionlist/view/transitionlistwidget.hpp" #include "transitions/transitionsrepository.hpp" #include "utils/resourcewidget.h" #include "utils/thememanager.h" #include "effectslist/effectslistwidget.h" #include "profiles/profilerepository.hpp" #include "widgets/progressbutton.h" #include #include "project/dialogs/temporarydata.h" #include "utils/KoIconUtils.h" #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogmanager.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char version[] = KDENLIVE_VERSION; namespace Mlt { class Producer; } EffectsList MainWindow::videoEffects; EffectsList MainWindow::audioEffects; EffectsList MainWindow::customEffects; EffectsList MainWindow::transitions; QMap MainWindow::m_lumacache; QMap MainWindow::m_lumaFiles; /*static bool sortByNames(const QPair &a, const QPair &b) { return a.first < b.first; }*/ // determine the default KDE style as defined BY THE USER // (as opposed to whatever style KDE considers default) static QString defaultStyle(const char *fallback = nullptr) { KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cg(kdeGlobals, "KDE"); return cg.readEntry("widgetStyle", fallback); } MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent) , m_exitCode(EXIT_SUCCESS) , m_effectList(nullptr) , m_transitionList(nullptr) , m_assetPanel(nullptr) , m_clipMonitor(nullptr) , m_projectMonitor(nullptr) , m_timelineTabs(nullptr) , m_renderWidget(nullptr) , m_messageLabel(nullptr) , m_themeInitialized(false) , m_isDarkTheme(false) { } void MainWindow::init() { QString desktopStyle = QApplication::style()->objectName(); // Init color theme KActionMenu *themeAction = new KActionMenu(i18n("Theme"), this); ThemeManager::instance()->setThemeMenuAction(themeAction); connect(ThemeManager::instance(), &ThemeManager::signalThemeChanged, this, &MainWindow::slotThemeChanged, Qt::DirectConnection); ThemeManager::instance()->setCurrentTheme(KdenliveSettings::colortheme()); if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) { // User wants a custom widget style, init doChangeStyle(); } else { ThemeManager::instance()->slotChangePalette(); } - + // Widget themes for non KDE users KActionMenu *stylesAction = new KActionMenu(i18n("Style"), this); auto *stylesGroup = new QActionGroup(stylesAction); // GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it QStringList availableStyles = QStyleFactory::keys(); if (KdenliveSettings::widgetstyle().isEmpty()) { // First run QStringList incompatibleStyles; incompatibleStyles << QStringLiteral("GTK+") << QStringLiteral("windowsvista") << QStringLiteral("windowsxp"); if (incompatibleStyles.contains(desktopStyle, Qt::CaseInsensitive)) { if (availableStyles.contains(QStringLiteral("breeze"), Qt::CaseInsensitive)) { // Auto switch to Breeze theme KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze")); } else if (availableStyles.contains(QStringLiteral("fusion"), Qt::CaseInsensitive)) { KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion")); } } else { KdenliveSettings::setWidgetstyle(QStringLiteral("Default")); } } // Add default style action QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup); defaultStyle->setData(QStringLiteral("Default")); defaultStyle->setCheckable(true); stylesAction->addAction(defaultStyle); if (KdenliveSettings::widgetstyle() == QLatin1String("Default") || KdenliveSettings::widgetstyle().isEmpty()) { defaultStyle->setChecked(true); } for (const QString &style : availableStyles) { auto *a = new QAction(style, stylesGroup); a->setCheckable(true); a->setData(style); if (KdenliveSettings::widgetstyle() == style) { a->setChecked(true); } stylesAction->addAction(a); } connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle); // QIcon::setThemeSearchPaths(QStringList() <setCurrentProfile(defaultProfile.isEmpty() ? ProjectManager::getDefaultProjectFormat() : defaultProfile); m_commandStack = new QUndoGroup(); // If using a custom profile, make sure the file exists or fallback to default QString currentProfilePath = pCore->getCurrentProfile()->path(); if (currentProfilePath.startsWith(QLatin1Char('/')) && !QFile::exists(currentProfilePath)) { KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25")); pCore->setCurrentProfile(QStringLiteral("atsc_1080p_25")); KdenliveSettings::setDefault_profile(QStringLiteral("atsc_1080p_25")); } m_gpuAllowed = initEffects::parseEffectFiles(pCore->getMltRepository()); // initEffects::parseCustomEffectsFile(); m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this); connect(m_shortcutRemoveFocus, &QShortcut::activated, this, &MainWindow::slotRemoveFocus); /// Add Widgets setDockOptions(dockOptions() | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); setDockOptions(dockOptions() | QMainWindow::GroupedDragging); setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition()); m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar")); m_timelineToolBarContainer = new QWidget(this); auto *ctnLay = new QVBoxLayout; ctnLay->setSpacing(0); ctnLay->setContentsMargins(0, 0, 0, 0); m_timelineToolBarContainer->setLayout(ctnLay); ctnLay->addWidget(m_timelineToolBar); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup mainConfig(config, QStringLiteral("MainWindow")); KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar")); m_timelineToolBar->applySettings(tbGroup); QFrame *fr = new QFrame(this); fr->setFrameShape(QFrame::HLine); fr->setMaximumHeight(1); fr->setLineWidth(1); ctnLay->addWidget(fr); setCentralWidget(m_timelineToolBarContainer); setupActions(); QDockWidget *libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library()); m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this); pCore->bin()->setMonitor(m_clipMonitor); connect(m_clipMonitor, &Monitor::showConfigDialog, this, &MainWindow::slotPreferences); connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(pCore->bin(), &Bin::findInTimeline, this, &MainWindow::slotClipInTimeline); // TODO deprecated, replace with Bin methods if necessary /*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime())); connect(m_projectList, SIGNAL(updateRenderStatus()), this, SLOT(slotCheckRenderStatus())); connect(m_projectList, SIGNAL(updateProfile(QString)), this, SLOT(slotUpdateProjectProfile(QString))); connect(m_projectList, SIGNAL(refreshClip(QString,bool)), pCore->monitorManager(), SLOT(slotRefreshCurrentMonitor(QString))); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), m_projectList, SLOT(slotUpdateClipCut(QPoint)));*/ // TODO refac : reimplement ? // connect(m_clipMonitor, &Monitor::extractZone, pCore->bin(), &Bin::slotStartCutJob); connect(m_clipMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey); m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this); connect(m_projectMonitor, &Monitor::passKeyPress, this, &MainWindow::triggerKey); connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteGuide); connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(m_loopClip, &QAction::triggered, m_projectMonitor, &Monitor::slotLoopClip); pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor); connect(m_clipMonitor, &Monitor::addMasterEffect, pCore->bin(), &Bin::slotAddEffect); m_timelineTabs = new TimelineTabs(this); ctnLay->addWidget(m_timelineTabs); // Audio spectrum scope m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager()); QDockWidget *spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum); connect(this, &MainWindow::reloadTheme, m_audioSpectrum, &AudioGraphSpectrum::refreshPixmap); // Close library and audiospectrum on first run libraryDock->close(); spectrumDock->close(); m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin()); m_assetPanel = new AssetPanel(this); connect(m_assetPanel, &AssetPanel::doSplitEffect, m_projectMonitor, &Monitor::slotSwitchCompare); connect(m_assetPanel, &AssetPanel::doSplitBinEffect, m_clipMonitor, &Monitor::slotSwitchCompare); connect(m_assetPanel, &AssetPanel::changeSpeed, this, &MainWindow::slotChangeSpeed); connect(m_timelineTabs, &TimelineTabs::showTransitionModel, m_assetPanel, &AssetPanel::showTransition); connect(m_timelineTabs, &TimelineTabs::showItemEffectStack, m_assetPanel, &AssetPanel::showEffectStack); connect(pCore->bin(), &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack); connect(this, &MainWindow::clearAssetPanel, m_assetPanel, &AssetPanel::clearAssetPanel); connect(m_assetPanel, &AssetPanel::seekToPos, [this](int pos) { ObjectId oId = m_assetPanel->effectStackOwner(); switch (oId.first) { - case ObjectType::TimelineTrack: - case ObjectType::TimelineClip: - case ObjectType::TimelineComposition: - getCurrentTimeline()->controller()->setPosition(pos); - break; - case ObjectType::BinClip: - m_clipMonitor->requestSeek(pos); - break; - default: - qDebug()<<"ERROR unhandled object type"; - break; + case ObjectType::TimelineTrack: + case ObjectType::TimelineClip: + case ObjectType::TimelineComposition: + getCurrentTimeline()->controller()->setPosition(pos); + break; + case ObjectType::BinClip: + m_clipMonitor->requestSeek(pos); + break; + default: + qDebug() << "ERROR unhandled object type"; + break; } }); m_effectStackDock = addDock(i18n("Properties"), QStringLiteral("effect_stack"), m_assetPanel); m_effectList = new EffectsListView(); // m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList); m_effectList2 = new EffectListWidget(this); connect(m_effectList2, &EffectListWidget::activateAsset, pCore->projectManager(), &ProjectManager::activateAsset); m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList2); m_transitionList = new EffectsListView(EffectsListView::TransitionMode); m_transitionList2 = new TransitionListWidget(this); // m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList); m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList2); // Add monitors here to keep them at the right of the window m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor); m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor); m_undoView = new QUndoView(); m_undoView->setCleanIcon(KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); m_undoView->setEmptyLabel(i18n("Clean")); m_undoView->setGroup(m_commandStack); m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView); // Color and icon theme stuff addAction(QStringLiteral("themes_menu"), themeAction); connect(m_commandStack, &QUndoGroup::cleanChanged, m_saveAction, &QAction::setDisabled); addAction(QStringLiteral("styles_menu"), stylesAction); QAction *iconAction = new QAction(i18n("Force Breeze Icon Theme"), this); iconAction->setCheckable(true); iconAction->setChecked(KdenliveSettings::force_breeze()); addAction(QStringLiteral("force_icon_theme"), iconAction); connect(iconAction, &QAction::triggered, this, &MainWindow::forceIconSet); // Close non-general docks for the initial layout // only show important ones m_undoViewDock->close(); /// Tabify Widgets tabifyDockWidget(m_transitionListDock, m_effectListDock); tabifyDockWidget(m_effectStackDock, pCore->bin()->clipPropertiesDock()); // tabifyDockWidget(m_effectListDock, m_effectStackDock); tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock); bool firstRun = readOptions(); // Monitor Record action addAction(QStringLiteral("switch_monitor_rec"), m_clipMonitor->recAction()); // Build effects menu m_effectsMenu = new QMenu(i18n("Add Effect"), this); m_effectActions = new KActionCategory(i18n("Effects"), actionCollection()); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); m_transitionsMenu = new QMenu(i18n("Add Transition"), this); m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); m_transitionList->reloadEffectList(m_transitionsMenu, m_transitionActions); auto *scmanager = new ScopeManager(this); new LayoutManagement(this); new HideTitleBars(this); m_extraFactory = new KXMLGUIClient(this); buildDynamicActions(); // Create Effect Basket (dropdown list of favorites) m_effectBasket = new EffectBasket(m_effectList); connect(m_effectBasket, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); auto *widgetlist = new QWidgetAction(this); widgetlist->setDefaultWidget(m_effectBasket); // widgetlist->setText(i18n("Favorite Effects")); widgetlist->setToolTip(i18n("Favorite Effects")); widgetlist->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); auto *menu = new QMenu(this); menu->addAction(widgetlist); auto *basketButton = new QToolButton(this); basketButton->setMenu(menu); basketButton->setToolButtonStyle(toolBar()->toolButtonStyle()); basketButton->setDefaultAction(widgetlist); basketButton->setPopupMode(QToolButton::InstantPopup); // basketButton->setText(i18n("Favorite Effects")); basketButton->setToolTip(i18n("Favorite Effects")); basketButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); auto *toolButtonAction = new QWidgetAction(this); toolButtonAction->setText(i18n("Favorite Effects")); toolButtonAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); toolButtonAction->setDefaultWidget(basketButton); addAction(QStringLiteral("favorite_effects"), toolButtonAction); connect(toolButtonAction, &QAction::triggered, basketButton, &QToolButton::showMenu); // Render button ProgressButton *timelineRender = new ProgressButton(i18n("Render"), 100, this); auto *tlrMenu = new QMenu(this); timelineRender->setMenu(tlrMenu); connect(this, &MainWindow::setRenderProgress, timelineRender, &ProgressButton::setProgress); auto *renderButtonAction = new QWidgetAction(this); renderButtonAction->setText(i18n("Render Button")); renderButtonAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("media-record"))); renderButtonAction->setDefaultWidget(timelineRender); addAction(QStringLiteral("project_render_button"), renderButtonAction); // Timeline preview button ProgressButton *timelinePreview = new ProgressButton(i18n("Rendering preview"), 1000, this); auto *tlMenu = new QMenu(this); timelinePreview->setMenu(tlMenu); connect(this, &MainWindow::setPreviewProgress, timelinePreview, &ProgressButton::setProgress); auto *previewButtonAction = new QWidgetAction(this); previewButtonAction->setText(i18n("Timeline Preview")); previewButtonAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("preview-render-on"))); previewButtonAction->setDefaultWidget(timelinePreview); addAction(QStringLiteral("timeline_preview_button"), previewButtonAction); setupGUI(); if (firstRun) { QScreen *current = QApplication::primaryScreen(); if (current) { if (current->availableSize().height() < 1000) { resize(current->availableSize()); } else { resize(current->availableSize() / 1.5); } } } updateActionsToolTip(); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); m_timelineToolBar->setProperty("otherToolbar", true); timelinePreview->setToolButtonStyle(m_timelineToolBar->toolButtonStyle()); connect(m_timelineToolBar, &QToolBar::toolButtonStyleChanged, timelinePreview, &ProgressButton::setToolButtonStyle); timelineRender->setToolButtonStyle(toolBar()->toolButtonStyle()); /*ScriptingPart* sp = new ScriptingPart(this, QStringList()); guiFactory()->addClient(sp);*/ loadGenerators(); loadDockActions(); loadClipActions(); // Connect monitor overlay info menu. QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); connect(monitorOverlay, &QMenu::triggered, this, &MainWindow::slotSwitchMonitorOverlay); m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, nullptr, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); QMenu *clipInTimeline = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); clipInTimeline->setIcon(KoIconUtils::themedIcon(QStringLiteral("go-jump"))); pCore->bin()->setupGeneratorMenu(); connect(pCore->monitorManager(), &MonitorManager::updateOverlayInfos, this, &MainWindow::slotUpdateMonitorOverlays); // Setup and fill effects and transitions menus. QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this)); connect(m, &QMenu::triggered, this, &MainWindow::slotAddVideoEffect); connect(m_effectsMenu, &QMenu::triggered, this, &MainWindow::slotAddVideoEffect); connect(m_transitionsMenu, &QMenu::triggered, this, &MainWindow::slotAddTransition); m_timelineContextMenu = new QMenu(this); m_timelineContextClipMenu = new QMenu(this); m_timelineContextTransitionMenu = new QMenu(this); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space_all_tracks"))); m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree"))); // m_timelineContextClipMenu->addAction(actionCollection()->action("clip_to_project_tree")); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("group_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("ungroup_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("split_audio"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("paste_effects"))); m_timelineContextClipMenu->addSeparator(); QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this)); m_timelineContextClipMenu->addMenu(markersMenu); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addMenu(m_transitionsMenu); m_timelineContextClipMenu->addMenu(m_effectsMenu); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("auto_transition"))); connect(m_effectList, &EffectsListView::addEffect, this, &MainWindow::slotAddEffect); connect(m_effectList, &EffectsListView::reloadEffects, this, &MainWindow::slotReloadEffects); slotConnectMonitors(); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); // TODO: let user select timeline toolbar toolbutton style // connect(toolBar(), &QToolBar::iconSizeChanged, m_timelineToolBar, &QToolBar::setToolButtonStyle); m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu); QAction *prevRender = actionCollection()->action(QStringLiteral("prerender_timeline_zone")); QAction *stopPrevRender = actionCollection()->action(QStringLiteral("stop_prerender_timeline")); tlMenu->addAction(stopPrevRender); tlMenu->addAction(actionCollection()->action(QStringLiteral("set_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("unset_render_timeline_zone"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("clear_render_timeline_zone"))); // Automatic timeline preview action QAction *autoRender = new QAction(KoIconUtils::themedIcon(QStringLiteral("view-refresh")), i18n("Automatic Preview"), this); autoRender->setCheckable(true); autoRender->setChecked(KdenliveSettings::autopreview()); connect(autoRender, &QAction::triggered, this, &MainWindow::slotToggleAutoPreview); tlMenu->addAction(autoRender); tlMenu->addSeparator(); tlMenu->addAction(actionCollection()->action(QStringLiteral("disable_preview"))); tlMenu->addAction(actionCollection()->action(QStringLiteral("manage_cache"))); timelinePreview->defineDefaultAction(prevRender, stopPrevRender); timelinePreview->setAutoRaise(true); QAction *showRender = actionCollection()->action(QStringLiteral("project_render")); tlrMenu->addAction(showRender); tlrMenu->addAction(actionCollection()->action(QStringLiteral("stop_project_render"))); timelineRender->defineDefaultAction(showRender, showRender); timelineRender->setAutoRaise(true); // Populate encoding profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) { KConfigGroup group(&conf, "proxy"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString proxystring = i.value(); KdenliveSettings::setProxyparams(proxystring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setProxyextension(proxystring.section(QLatin1Char(';'), 1, 1)); } } if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) { KConfigGroup group(&conf, "video4linux"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString v4lstring = i.value(); KdenliveSettings::setV4l_parameters(v4lstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setV4l_extension(v4lstring.section(QLatin1Char(';'), 1, 1)); } } if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) { KConfigGroup group(&conf, "screengrab"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString grabstring = i.value(); KdenliveSettings::setGrab_parameters(grabstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setGrab_extension(grabstring.section(QLatin1Char(';'), 1, 1)); } } if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) { KConfigGroup group(&conf, "decklink"); QMap values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString decklinkstring = i.value(); KdenliveSettings::setDecklink_parameters(decklinkstring.section(QLatin1Char(';'), 0, 0)); KdenliveSettings::setDecklink_extension(decklinkstring.section(QLatin1Char(';'), 1, 1)); } } if (!QDir(KdenliveSettings::currenttmpfolder()).isReadable()) KdenliveSettings::setCurrenttmpfolder(QStandardPaths::writableLocation(QStandardPaths::TempLocation)); QTimer::singleShot(0, this, &MainWindow::GUISetupDone); connect(this, &MainWindow::reloadTheme, this, &MainWindow::slotReloadTheme, Qt::UniqueConnection); #ifdef USE_JOGSHUTTLE new JogManager(this); #endif scmanager->slotCheckActiveScopes(); // m_messageLabel->setMessage(QStringLiteral("This is a beta version. Always backup your data"), MltError); } void MainWindow::slotThemeChanged(const QString &theme) { disconnect(this, &MainWindow::reloadTheme, this, &MainWindow::slotReloadTheme); KSharedConfigPtr config = KSharedConfig::openConfig(theme); QPalette plt = KColorScheme::createApplicationPalette(config); setPalette(plt); qApp->setPalette(palette()); // Required for qml palette change QGuiApplication::setPalette(plt); KdenliveSettings::setColortheme(theme); QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (KdenliveSettings::force_breeze() && useDarkIcons != KdenliveSettings::use_dark_breeze()) { // We need to reload icon theme KdenliveSettings::setUse_dark_breeze(useDarkIcons); if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive needs to be restarted to apply color theme change. Restart now ?")) == KMessageBox::Continue) { slotRestart(); } } if (m_assetPanel) { m_assetPanel->updatePalette(); } if (m_effectList) { m_effectList->updatePalette(); } if (m_transitionList) { m_transitionList->updatePalette(); } if (m_clipMonitor) { m_clipMonitor->setPalette(plt); } if (m_projectMonitor) { m_projectMonitor->setPalette(plt); } if (m_timelineTabs) { m_timelineTabs->setPalette(plt); } #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Not required anymore with auto colored icons since KF5 5.23 QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (m_themeInitialized && useDarkIcons != m_isDarkTheme) { if (pCore->bin()) { pCore->bin()->refreshIcons(); } if (m_clipMonitor) { m_clipMonitor->refreshIcons(); } if (m_projectMonitor) { m_projectMonitor->refreshIcons(); } if (pCore->monitorManager()) { pCore->monitorManager()->refreshIcons(); } if (m_effectList) { m_effectList->refreshIcons(); } for (QAction *action : actionCollection()->actions()) { QIcon icon = action->icon(); if (icon.isNull()) { continue; } QString iconName = icon.name(); QIcon newIcon = KoIconUtils::themedIcon(iconName); if (newIcon.isNull()) { continue; } action->setIcon(newIcon); } } m_themeInitialized = true; m_isDarkTheme = useDarkIcons; #endif connect(this, &MainWindow::reloadTheme, this, &MainWindow::slotReloadTheme, Qt::UniqueConnection); } bool MainWindow::event(QEvent *e) { switch (e->type()) { case QEvent::ApplicationPaletteChange: emit reloadTheme(); e->accept(); break; default: break; } return KXmlGuiWindow::event(e); } void MainWindow::updateActionsToolTip() { // Add shortcut to action tooltips QList collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); for (QAction *tempAction : coll->actions()) { // find the shortcut pattern and delete (note the preceding space in the RegEx) QString strippedTooltip = tempAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)"))); // append shortcut if it exists for action if (tempAction->shortcut() == QKeySequence(0)) { tempAction->setToolTip(strippedTooltip); } else { tempAction->setToolTip(strippedTooltip + QStringLiteral(" (") + tempAction->shortcut().toString() + QLatin1Char(')')); } connect(tempAction, &QAction::changed, this, &MainWindow::updateAction); } } } void MainWindow::updateAction() { QAction *action = qobject_cast(sender()); QString toolTip = KLocalizedString::removeAcceleratorMarker(action->toolTip()); QString strippedTooltip = toolTip.remove(QRegExp(QStringLiteral("\\s\\(.*\\)"))); action->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1 (%2)", strippedTooltip, action->shortcut().toString())); } void MainWindow::slotReloadTheme() { ThemeManager::instance()->slotSettingsChanged(); } MainWindow::~MainWindow() { pCore->prepareShutdown(); m_timelineTabs->disconnectTimeline(getMainTimeline()); delete m_audioSpectrum; if (m_projectMonitor) { m_projectMonitor->stop(); } if (m_clipMonitor) { m_clipMonitor->stop(); } ClipController::mediaUnavailable.reset(); delete m_projectMonitor; delete m_clipMonitor; delete m_shortcutRemoveFocus; delete m_effectList2; delete m_transitionList2; qDeleteAll(m_transitions); // Mlt::Factory::close(); } // virtual bool MainWindow::queryClose() { if (m_renderWidget) { int waitingJobs = m_renderWidget->waitingJobsCount(); if (waitingJobs > 0) { - switch (KMessageBox::warningYesNoCancel(this, i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?", - "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", - waitingJobs), - QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) { + switch ( + KMessageBox::warningYesNoCancel(this, + i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?", + "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", waitingJobs), + QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) { case KMessageBox::Yes: // create script with waiting jobs and start it if (!m_renderWidget->startWaitingRenderJobs()) { return false; } break; case KMessageBox::No: // Don't do anything, jobs will be deleted break; default: return false; } } } saveOptions(); // WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here? return pCore->projectManager()->closeCurrentDocument(true, true); } void MainWindow::loadGenerators() { QMenu *addMenu = static_cast(factory()->container(QStringLiteral("generators"), this)); Generators::getGenerators(KdenliveSettings::producerslist(), addMenu); connect(addMenu, &QMenu::triggered, this, &MainWindow::buildGenerator); } void MainWindow::buildGenerator(QAction *action) { Generators gen(m_clipMonitor, action->data().toString(), this); if (gen.exec() == QDialog::Accepted) { pCore->bin()->slotAddClipToProject(gen.getSavedClip()); } } void MainWindow::saveProperties(KConfigGroup &config) { // save properties here KXmlGuiWindow::saveProperties(config); // TODO: fix session management if (qApp->isSavingSession() && pCore->projectManager()) { if (pCore->currentDoc() && !pCore->currentDoc()->url().isEmpty()) { config.writeEntry("kdenlive_lastUrl", pCore->currentDoc()->url().toLocalFile()); } } } void MainWindow::readProperties(const KConfigGroup &config) { // read properties here KXmlGuiWindow::readProperties(config); // TODO: fix session management /*if (qApp->isSessionRestored()) { pCore->projectManager()->openFile(QUrl::fromLocalFile(config.readEntry("kdenlive_lastUrl", QString()))); }*/ } void MainWindow::saveNewToolbarConfig() { KXmlGuiWindow::saveNewToolbarConfig(); // TODO for some reason all dynamically inserted actions are removed by the save toolbar // So we currently re-add them manually.... loadDockActions(); loadClipActions(); pCore->bin()->rebuildMenu(); QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (monitorOverlay) { m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, nullptr, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); } } void MainWindow::slotReloadEffects() { initEffects::parseCustomEffectsFile(); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::slotFullScreen() { KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked()); } void MainWindow::slotAddEffect(const QDomElement &effect) { Q_UNUSED(effect) // TODO refac : reimplement /* if (effect.isNull()) { qCDebug(KDENLIVE_LOG) << "--- ERROR, TRYING TO APPEND nullptr EFFECT"; return; } QDomElement effectToAdd = effect.cloneNode().toElement(); EFFECTMODE status = m_effectStack->effectStatus(); switch (status) { case TIMELINE_TRACK: pCore->projectManager()->currentTimeline()->projectView()->slotAddTrackEffect(effectToAdd, m_effectStack->trackIndex()); break; case TIMELINE_CLIP: pCore->projectManager()->currentTimeline()->projectView()->slotAddEffectToCurrentItem(effectToAdd); break; case MASTER_CLIP: // TODO refac reimplement this. // pCore->bin()->slotEffectDropped(QString(), effectToAdd); break; default: // No clip selected m_messageLabel->setMessage(i18n("Select a clip if you want to apply an effect"), ErrorMessage); } */ } void MainWindow::slotConnectMonitors() { // connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this, // SLOT(slotDeleteProjectClips(QStringList,QMap))); connect(m_clipMonitor, &Monitor::refreshClipThumbnail, pCore->bin(), &Bin::slotRefreshClipThumbnail); connect(m_projectMonitor, &Monitor::requestFrameForAnalysis, this, &MainWindow::slotMonitorRequestRenderFrame); connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay); connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay); } void MainWindow::createSplitOverlay(Mlt::Filter *filter) { getMainTimeline()->controller()->createSplitOverlay(filter); m_projectMonitor->activateSplit(); } void MainWindow::removeSplitOverlay() { getMainTimeline()->controller()->removeSplitOverlay(); } void MainWindow::addAction(const QString &name, QAction *action) { m_actionNames.append(name); actionCollection()->addAction(name, action); actionCollection()->setDefaultShortcut(action, action->shortcut()); // Fix warning about setDefaultShortcut } QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon, const QKeySequence &shortcut) { auto *action = new QAction(text, this); if (!icon.isNull()) { action->setIcon(icon); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } addAction(name, action); connect(action, SIGNAL(triggered(bool)), receiver, member); return action; } void MainWindow::setupActions() { // create edit mode buttons m_normalEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this); m_normalEditTool->setShortcut(i18nc("Normal editing", "n")); m_normalEditTool->setCheckable(true); m_normalEditTool->setChecked(true); m_overwriteEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this); m_overwriteEditTool->setCheckable(true); m_overwriteEditTool->setChecked(false); m_insertEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this); m_insertEditTool->setCheckable(true); m_insertEditTool->setChecked(false); KSelectAction *sceneMode = new KSelectAction(i18n("Timeline Edit Mode"), this); sceneMode->addAction(m_normalEditTool); sceneMode->addAction(m_overwriteEditTool); sceneMode->addAction(m_insertEditTool); sceneMode->setCurrentItem(0); - connect(sceneMode, static_cast(&KSelectAction::triggered), this, &MainWindow::slotChangeEdit); + connect(sceneMode, static_cast(&KSelectAction::triggered), this, &MainWindow::slotChangeEdit); addAction(QStringLiteral("timeline_mode"), sceneMode); KDualAction *ac = new KDualAction(i18n("Don't Use Timeline Zone for Insert"), i18n("Use Timeline Zone for Insert"), this); ac->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("timeline-use-zone-on"))); ac->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("timeline-use-zone-off"))); ac->setShortcut(Qt::Key_G); ac->setActive(KdenliveSettings::useTimelineZoneToEdit()); ac->setAutoToggle(true); connect(ac, &KDualAction::activeChangedByUser, this, &MainWindow::slotSwitchTimelineZone); addAction(QStringLiteral("use_timeline_zone_in_edit"), ac); m_compositeAction = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("composite-track-off")), i18n("Track compositing"), this); m_compositeAction->setToolTip(i18n("Track compositing")); QAction *noComposite = new QAction(KoIconUtils::themedIcon(QStringLiteral("composite-track-off")), i18n("None"), this); noComposite->setCheckable(true); noComposite->setData(0); m_compositeAction->addAction(noComposite); QString compose = TransitionsRepository::get()->getCompositingTransition(); if (compose == QStringLiteral("movit.overlay")) { // Movit, do not show "preview" option since movit is faster QAction *hqComposite = new QAction(KoIconUtils::themedIcon(QStringLiteral("composite-track-on")), i18n("High Quality"), this); hqComposite->setCheckable(true); hqComposite->setData(2); m_compositeAction->addAction(hqComposite); m_compositeAction->setCurrentAction(hqComposite); } else { QAction *previewComposite = new QAction(KoIconUtils::themedIcon(QStringLiteral("composite-track-preview")), i18n("Preview"), this); previewComposite->setCheckable(true); previewComposite->setData(1); m_compositeAction->addAction(previewComposite); if (compose != QStringLiteral("composite")) { QAction *hqComposite = new QAction(KoIconUtils::themedIcon(QStringLiteral("composite-track-on")), i18n("High Quality"), this); hqComposite->setData(2); hqComposite->setCheckable(true); m_compositeAction->addAction(hqComposite); m_compositeAction->setCurrentAction(hqComposite); } else { m_compositeAction->setCurrentAction(previewComposite); } } - connect(m_compositeAction, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateCompositing); + connect(m_compositeAction, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateCompositing); addAction(QStringLiteral("timeline_compositing"), m_compositeAction); m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this); m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_timeFormatButton->addAction(i18n("hh:mm:ss:ff")); m_timeFormatButton->addAction(i18n("Frames")); if (KdenliveSettings::frametimecode()) { m_timeFormatButton->setCurrentItem(1); } else { m_timeFormatButton->setCurrentItem(0); } - connect(m_timeFormatButton, static_cast(&KSelectAction::triggered), - this, &MainWindow::slotUpdateTimecodeFormat); + connect(m_timeFormatButton, static_cast(&KSelectAction::triggered), this, &MainWindow::slotUpdateTimecodeFormat); m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode); m_timeFormatButton->setToolButtonPopupMode(QToolButton::InstantPopup); addAction(QStringLiteral("timeline_timecode"), m_timeFormatButton); // create tools buttons m_buttonSelectTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this); m_buttonSelectTool->setShortcut(i18nc("Selection tool shortcut", "s")); // toolbar->addAction(m_buttonSelectTool); m_buttonSelectTool->setCheckable(true); m_buttonSelectTool->setChecked(true); m_buttonRazorTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-cut")), i18n("Razor tool"), this); m_buttonRazorTool->setShortcut(i18nc("Razor tool shortcut", "x")); // toolbar->addAction(m_buttonRazorTool); m_buttonRazorTool->setCheckable(true); m_buttonRazorTool->setChecked(false); m_buttonSpacerTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this); m_buttonSpacerTool->setShortcut(i18nc("Spacer tool shortcut", "m")); // toolbar->addAction(m_buttonSpacerTool); m_buttonSpacerTool->setCheckable(true); m_buttonSpacerTool->setChecked(false); auto *toolGroup = new QActionGroup(this); toolGroup->addAction(m_buttonSelectTool); toolGroup->addAction(m_buttonRazorTool); toolGroup->addAction(m_buttonSpacerTool); toolGroup->setExclusive(true); // toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); /*QWidget * actionWidget; int max = toolbar->iconSizeDefault() + 2; actionWidget = toolbar->widgetForAction(m_normalEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_insertEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_overwriteEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSelectTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonRazorTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSpacerTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4);*/ connect(toolGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeTool); m_buttonVideoThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this); m_buttonVideoThumbs->setCheckable(true); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); connect(m_buttonVideoThumbs, &QAction::triggered, this, &MainWindow::slotSwitchVideoThumbs); m_buttonAudioThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this); m_buttonAudioThumbs->setCheckable(true); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); connect(m_buttonAudioThumbs, &QAction::triggered, this, &MainWindow::slotSwitchAudioThumbs); m_buttonShowMarkers = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this); m_buttonShowMarkers->setCheckable(true); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); connect(m_buttonShowMarkers, &QAction::triggered, this, &MainWindow::slotSwitchMarkersComments); m_buttonSnap = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-snap")), i18n("Snap"), this); m_buttonSnap->setCheckable(true); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); connect(m_buttonSnap, &QAction::triggered, this, &MainWindow::slotSwitchSnap); m_buttonAutomaticTransition = new QAction(KoIconUtils::themedIcon(QStringLiteral("auto-transition")), i18n("Automatic transitions"), this); m_buttonAutomaticTransition->setCheckable(true); m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions()); connect(m_buttonAutomaticTransition, &QAction::triggered, this, &MainWindow::slotSwitchAutomaticTransition); m_buttonFitZoom = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best")), i18n("Fit zoom to project"), this); m_buttonFitZoom->setCheckable(false); m_zoomOut = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-out")), i18n("Zoom Out"), this); m_zoomOut->setShortcut(Qt::CTRL + Qt::Key_Minus); m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setMaximum(13); m_zoomSlider->setPageStep(1); m_zoomSlider->setInvertedAppearance(true); m_zoomSlider->setInvertedControls(true); m_zoomSlider->setMaximumWidth(150); m_zoomSlider->setMinimumWidth(100); m_zoomIn = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-in")), i18n("Zoom In"), this); m_zoomIn->setShortcut(Qt::CTRL + Qt::Key_Plus); /*actionWidget = toolbar->widgetForAction(m_buttonFitZoom); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomIn); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomOut); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless);*/ connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int))); connect(m_zoomSlider, &QAbstractSlider::sliderMoved, this, &MainWindow::slotShowZoomSliderToolTip); connect(m_buttonFitZoom, &QAction::triggered, this, &MainWindow::slotFitZoom); connect(m_zoomIn, &QAction::triggered, this, &MainWindow::slotZoomIn); connect(m_zoomOut, &QAction::triggered, this, &MainWindow::slotZoomOut); m_trimLabel = new QLabel(QStringLiteral(" "), this); m_trimLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); // m_trimLabel->setAutoFillBackground(true); m_trimLabel->setAlignment(Qt::AlignHCenter); m_trimLabel->setStyleSheet(QStringLiteral("QLabel { background-color :red; }")); KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea); toolbar->setMovable(false); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); /*QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}");*/ toolbar->addWidget(m_trimLabel); toolbar->addAction(m_buttonAutomaticTransition); toolbar->addAction(m_buttonVideoThumbs); toolbar->addAction(m_buttonAudioThumbs); toolbar->addAction(m_buttonShowMarkers); toolbar->addAction(m_buttonSnap); toolbar->addSeparator(); toolbar->addAction(m_buttonFitZoom); toolbar->addAction(m_zoomOut); toolbar->addWidget(m_zoomSlider); toolbar->addAction(m_zoomIn); int small = style()->pixelMetric(QStyle::PM_SmallIconSize); statusBar()->setMaximumHeight(2 * small); m_messageLabel = new StatusBarMessageLabel(this); m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); connect(this, &MainWindow::displayMessage, m_messageLabel, &StatusBarMessageLabel::setMessage); statusBar()->addWidget(m_messageLabel, 0); QWidget *spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); statusBar()->addWidget(spacer, 1); statusBar()->addPermanentWidget(toolbar); toolbar->setIconSize(QSize(small, small)); toolbar->layout()->setContentsMargins(0, 0, 0, 0); statusBar()->setContentsMargins(0, 0, 0, 0); addAction(QStringLiteral("normal_mode"), m_normalEditTool); addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool); addAction(QStringLiteral("insert_mode"), m_insertEditTool); addAction(QStringLiteral("select_tool"), m_buttonSelectTool); addAction(QStringLiteral("razor_tool"), m_buttonRazorTool); addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool); addAction(QStringLiteral("automatic_transition"), m_buttonAutomaticTransition); addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs); addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs); addAction(QStringLiteral("show_markers"), m_buttonShowMarkers); addAction(QStringLiteral("snap"), m_buttonSnap); addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom); addAction(QStringLiteral("zoom_in"), m_zoomIn); addAction(QStringLiteral("zoom_out"), m_zoomOut); KNS3::standardAction(i18n("Download New Wipes..."), this, SLOT(slotGetNewLumaStuff()), actionCollection(), "get_new_lumas"); KNS3::standardAction(i18n("Download New Keyboard Schemes..."), this, SLOT(slotGetNewKeyboardStuff()), actionCollection(), "get_new_keyboardschemes"); KNS3::standardAction(i18n("Download New Render Profiles..."), this, SLOT(slotGetNewRenderStuff()), actionCollection(), "get_new_profiles"); KNS3::standardAction(i18n("Download New Title Templates..."), this, SLOT(slotGetNewTitleStuff()), actionCollection(), "get_new_titles"); addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), KoIconUtils::themedIcon(QStringLiteral("tools-wizard"))); addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()), KoIconUtils::themedIcon(QStringLiteral("configure"))); addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), KoIconUtils::themedIcon(QStringLiteral("media-record")), Qt::CTRL + Qt::Key_Return); addAction(QStringLiteral("stop_project_render"), i18n("Stop Render"), this, SLOT(slotStopRenderProject()), KoIconUtils::themedIcon(QStringLiteral("media-record"))); addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); - - + /*QAction *timelineZone = new QAction(KoIconUtils::themedIcon(QStringLiteral("insert-horizontal-rule")), i18n("Use Timeline Zone in Edit"), this); timelineZone->setCheckable(true); timelineZone->setChecked(KdenliveSettings::useTimelineZoneToEdit()); addAction(QStringLiteral("use_timeline_zone_in_edit"), timelineZone); connect(timelineZone, &QAction::toggled, this, &MainWindow::slotSwitchTimelineZone);*/ - + // TODO // addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem())); m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space); m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space); m_loopClip = new QAction(KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), i18n("Loop selected clip"), this); addAction(QStringLiteral("monitor_loop_clip"), m_loopClip); m_loopClip->setEnabled(false); addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), KoIconUtils::themedIcon(QStringLiteral("media-optical"))); addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()), KoIconUtils::themedIcon(QStringLiteral("document-save-all"))); addAction(QStringLiteral("switch_monitor"), i18n("Switch monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T); addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), pCore->projectManager(), SLOT(slotExpandClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); QAction *overlayInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this); addAction(QStringLiteral("monitor_overlay"), overlayInfo); overlayInfo->setCheckable(true); overlayInfo->setData(0x01); QAction *overlayTCInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this); addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo); overlayTCInfo->setCheckable(true); overlayTCInfo->setData(0x02); QAction *overlayFpsInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Playback Fps"), this); addAction(QStringLiteral("monitor_overlay_fps"), overlayFpsInfo); overlayFpsInfo->setCheckable(true); overlayFpsInfo->setData(0x20); QAction *overlayMarkerInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this); addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo); overlayMarkerInfo->setCheckable(true); overlayMarkerInfo->setData(0x04); QAction *overlayAudioInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this); addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo); overlayAudioInfo->setCheckable(true); overlayAudioInfo->setData(0x10); QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this); dropFrames->setCheckable(true); dropFrames->setChecked(KdenliveSettings::monitor_dropframes()); addAction(QStringLiteral("mlt_realtime"), dropFrames); connect(dropFrames, &QAction::toggled, this, &MainWindow::slotSwitchDropFrames); KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this); monitorGamma->addAction(i18n("sRGB (computer)")); monitorGamma->addAction(i18n("Rec. 709 (TV)")); addAction(QStringLiteral("mlt_gamma"), monitorGamma); monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma()); - connect(monitorGamma, static_cast(&KSelectAction::triggered), this, &MainWindow::slotSetMonitorGamma); + connect(monitorGamma, static_cast(&KSelectAction::triggered), this, &MainWindow::slotSetMonitorGamma); addAction(QStringLiteral("switch_trim"), i18n("Trim Mode"), this, SLOT(slotSwitchTrimMode()), KoIconUtils::themedIcon(QStringLiteral("cursor-arrow"))); // disable shortcut until fully working, Qt::CTRL + Qt::Key_T); addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()), QIcon(), Qt::CTRL + Qt::Key_I); addAction(QStringLiteral("insert_timeline"), i18n("Insert Zone in Timeline"), this, SLOT(slotInsertZoneToTimeline()), QIcon(), Qt::SHIFT + Qt::CTRL + Qt::Key_I); QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this); addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart); resizeStart->setShortcut(Qt::Key_1); connect(resizeStart, &QAction::triggered, this, &MainWindow::slotResizeItemStart); QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this); addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd); resizeEnd->setShortcut(Qt::Key_2); connect(resizeEnd, &QAction::triggered, this, &MainWindow::slotResizeItemEnd); addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left); addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::Key_Home); addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::Key_End); addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right); addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()), KoIconUtils::themedIcon(QStringLiteral("edit-delete")), Qt::Key_Delete); addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P); QAction *stickTransition = new QAction(i18n("Automatic Transition"), this); stickTransition->setData(QStringLiteral("auto")); stickTransition->setCheckable(true); stickTransition->setEnabled(false); addAction(QStringLiteral("auto_transition"), stickTransition); connect(stickTransition, &QAction::triggered, this, &MainWindow::slotAutoTransition); addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G); QAction *ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-ungroup")), Qt::CTRL + Qt::SHIFT + Qt::Key_G); ungroupClip->setData("ungroup_clip"); addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()), KoIconUtils::themedIcon(QStringLiteral("measure"))); addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()), KoIconUtils::themedIcon(QStringLiteral("go-jump-definition"))); addAction(QStringLiteral("overwrite_to_in_point"), i18n("Overwrite Clip Zone in Timeline"), this, SLOT(slotInsertClipOverwrite()), KoIconUtils::themedIcon(QStringLiteral("timeline-overwrite")), Qt::Key_B); addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline"), this, SLOT(slotInsertClipInsert()), KoIconUtils::themedIcon(QStringLiteral("timeline-insert")), Qt::Key_V); addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()), KoIconUtils::themedIcon(QStringLiteral("timeline-extract")), Qt::SHIFT + Qt::Key_X); addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), KoIconUtils::themedIcon(QStringLiteral("timeline-lift")), Qt::Key_Z); addAction(QStringLiteral("set_render_timeline_zone"), i18n("Add Preview Zone"), this, SLOT(slotDefinePreviewRender()), KoIconUtils::themedIcon(QStringLiteral("preview-add-zone"))); addAction(QStringLiteral("unset_render_timeline_zone"), i18n("Remove Preview Zone"), this, SLOT(slotRemovePreviewRender()), KoIconUtils::themedIcon(QStringLiteral("preview-remove-zone"))); addAction(QStringLiteral("clear_render_timeline_zone"), i18n("Remove All Preview Zones"), this, SLOT(slotClearPreviewRender()), KoIconUtils::themedIcon(QStringLiteral("preview-remove-all"))); addAction(QStringLiteral("prerender_timeline_zone"), i18n("Start Preview Render"), this, SLOT(slotPreviewRender()), KoIconUtils::themedIcon(QStringLiteral("preview-render-on")), QKeySequence(Qt::SHIFT + Qt::Key_Return)); addAction(QStringLiteral("stop_prerender_timeline"), i18n("Stop Preview Render"), this, SLOT(slotStopPreviewRender()), KoIconUtils::themedIcon(QStringLiteral("preview-render-off"))); addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus); addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-cut")), Qt::SHIFT + Qt::Key_R); addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction *editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); editClipMarker->setData(QStringLiteral("edit_marker")); addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new")), Qt::Key_Asterisk); QAction *splitAudio = addAction(QStringLiteral("split_audio"), i18n("Split Audio"), this, SLOT(slotSplitAudio()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); // "A+V" as data means this action should only be available for clips with audio AND video splitAudio->setData("A+V"); QAction *setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference())); // "A" as data means this action should only be available for clips with audio setAudioAlignReference->setData("A"); QAction *alignAudio = addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon()); // "A" as data means this action should only be available for clips with audio alignAudio->setData("A"); QAction *audioOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio Only"), this); addAction(QStringLiteral("clip_audio_only"), audioOnly); audioOnly->setData(QVariant::fromValue(PlaylistState::AudioOnly)); audioOnly->setCheckable(true); QAction *videoOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Video Only"), this); addAction(QStringLiteral("clip_video_only"), videoOnly); videoOnly->setData(QVariant::fromValue(PlaylistState::VideoOnly)); videoOnly->setCheckable(true); m_clipTypeGroup = new QActionGroup(this); m_clipTypeGroup->addAction(audioOnly); m_clipTypeGroup->addAction(videoOnly); connect(m_clipTypeGroup, &QActionGroup::triggered, this, &MainWindow::slotUpdateClipType); m_clipTypeGroup->setEnabled(false); addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace())); addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace())); addAction(QStringLiteral("delete_space_all_tracks"), i18n("Remove Space In All Tracks"), this, SLOT(slotRemoveAllSpace())); KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection()); QAction *insertTrack = new QAction(QIcon(), i18n("Insert Track"), this); connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack); timelineActions->addAction(QStringLiteral("insert_track"), insertTrack); QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track"), this); connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack); timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack); deleteTrack->setData("delete_track"); QAction *configTracks = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure Tracks"), this); connect(configTracks, &QAction::triggered, this, &MainWindow::slotConfigTrack); timelineActions->addAction(QStringLiteral("config_tracks"), configTracks); QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this); connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack); timelineActions->addAction(QStringLiteral("select_track"), selectTrack); QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this); selectAll->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all"))); selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll); QAction *unselectAll = KStandardAction::deselect(this, SLOT(slotUnselectAllTracks()), this); unselectAll->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-unselect-all"))); unselectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("unselect_all_tracks"), unselectAll); kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions); // Cached data management addAction(QStringLiteral("manage_cache"), i18n("Manage Cached Data"), this, SLOT(slotManageCache()), KoIconUtils::themedIcon(QStringLiteral("network-server-database"))); QAction *disablePreview = new QAction(i18n("Disable Timeline Preview"), this); disablePreview->setCheckable(true); addAction(QStringLiteral("disable_preview"), disablePreview); addAction(QStringLiteral("add_guide"), i18n("Add Guide"), this, SLOT(slotAddGuide()), KoIconUtils::themedIcon(QStringLiteral("list-add"))); addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()), KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); pasteEffects->setData("paste_effects"); m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection()); m_saveAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-save"))); addAction(QStringLiteral("save_selection"), i18n("Save Selection"), pCore->projectManager(), SLOT(slotSaveSelection()), KoIconUtils::themedIcon(QStringLiteral("document-save"))); QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Timeline Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); pCore->library()->setupActions(QList() << sentToLibrary); KStandardAction::showMenubar(this, SLOT(showMenuBar(bool)), actionCollection()); QAction *a = KStandardAction::quit(this, SLOT(close()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("application-exit"))); // TODO: make the following connection to slotEditKeys work // KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection()); a = KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); a = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); a = KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-fullscreen"))); QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection()); undo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-undo"))); undo->setEnabled(false); connect(m_commandStack, &QUndoGroup::canUndoChanged, undo, &QAction::setEnabled); QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection()); redo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-redo"))); redo->setEnabled(false); connect(m_commandStack, &QUndoGroup::canRedoChanged, redo, &QAction::setEnabled); auto *addClips = new QMenu(this); QAction *addClip = addAction(QStringLiteral("add_clip"), i18n("Add Clip"), pCore->bin(), SLOT(slotAddClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-clip"))); addClips->addAction(addClip); QAction *action = addAction(QStringLiteral("add_color_clip"), i18n("Add Color Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-color-clip"))); action->setData((int)ClipType::Color); addClips->addAction(action); action = addAction(QStringLiteral("add_slide_clip"), i18n("Add Slideshow Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-slide-clip"))); action->setData((int)ClipType::SlideShow); addClips->addAction(action); action = addAction(QStringLiteral("add_text_clip"), i18n("Add Title Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int)ClipType::Text); addClips->addAction(action); action = addAction(QStringLiteral("add_text_template_clip"), i18n("Add Template Title"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int)ClipType::TextTemplate); addClips->addAction(action); /*action = addAction(QStringLiteral("add_qtext_clip"), i18n("Add Simple Text Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) QText); addClips->addAction(action);*/ QAction *addFolder = addAction(QStringLiteral("add_folder"), i18n("Create Folder"), pCore->bin(), SLOT(slotAddFolder()), KoIconUtils::themedIcon(QStringLiteral("folder-new"))); addClips->addAction(addAction(QStringLiteral("download_resource"), i18n("Online Resources"), this, SLOT(slotDownloadResources()), KoIconUtils::themedIcon(QStringLiteral("edit-download")))); QAction *clipProperties = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), pCore->bin(), SLOT(slotSwitchClipProperties()), KoIconUtils::themedIcon(QStringLiteral("document-edit"))); clipProperties->setData("clip_properties"); QAction *openClip = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), pCore->bin(), SLOT(slotOpenClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); openClip->setData("edit_clip"); openClip->setEnabled(false); QAction *deleteClip = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), pCore->bin(), SLOT(slotDeleteClip()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); deleteClip->setData("delete_clip"); deleteClip->setEnabled(false); QAction *reloadClip = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), pCore->bin(), SLOT(slotReloadClip()), KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); reloadClip->setData("reload_clip"); reloadClip->setEnabled(false); QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(), SLOT(slotDisableTimelineEffects(bool)), KoIconUtils::themedIcon(QStringLiteral("favorite"))); disableEffects->setData("disable_timeline_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); QAction *locateClip = addAction(QStringLiteral("locate_clip"), i18n("Locate Clip..."), pCore->bin(), SLOT(slotLocateClip()), KoIconUtils::themedIcon(QStringLiteral("edit-file"))); locateClip->setData("locate_clip"); locateClip->setEnabled(false); QAction *duplicateClip = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), pCore->bin(), SLOT(slotDuplicateClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); duplicateClip->setData("duplicate_clip"); duplicateClip->setEnabled(false); QAction *proxyClip = new QAction(i18n("Proxy Clip"), this); addAction(QStringLiteral("proxy_clip"), proxyClip); proxyClip->setData(QStringList() << QString::number((int)AbstractClipJob::PROXYJOB)); proxyClip->setCheckable(true); proxyClip->setChecked(false); addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(), Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(), Qt::CTRL + Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(), Qt::SHIFT + Qt::Key_T); QHash actions; actions.insert(QStringLiteral("locate"), locateClip); actions.insert(QStringLiteral("reload"), reloadClip); actions.insert(QStringLiteral("duplicate"), duplicateClip); actions.insert(QStringLiteral("proxy"), proxyClip); actions.insert(QStringLiteral("properties"), clipProperties); actions.insert(QStringLiteral("open"), openClip); actions.insert(QStringLiteral("delete"), deleteClip); actions.insert(QStringLiteral("folder"), addFolder); pCore->bin()->setupMenu(addClips, addClip, actions); // Setup effects and transitions actions. KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); // m_transitions = new QAction*[transitions.count()]; for (int i = 0; i < transitions.count(); ++i) { QStringList effectInfo = transitions.effectIdInfo(i); if (effectInfo.isEmpty()) { continue; } auto *transAction = new QAction(effectInfo.at(0), this); transAction->setData(effectInfo); transAction->setIconVisibleInMenu(false); m_transitions << transAction; QString id = effectInfo.at(2); if (id.isEmpty()) { id = effectInfo.at(1); } transitionActions->addAction("transition_" + id, transAction); } // monitor actions addAction(QStringLiteral("extract_frame"), i18n("Extract frame..."), pCore->monitorManager(), SLOT(slotExtractCurrentFrame()), KoIconUtils::themedIcon(QStringLiteral("insert-image"))); addAction(QStringLiteral("extract_frame_to_project"), i18n("Extract frame to project..."), pCore->monitorManager(), SLOT(slotExtractCurrentFrameToProject()), KoIconUtils::themedIcon(QStringLiteral("insert-image"))); } void MainWindow::saveOptions() { KdenliveSettings::self()->save(); } bool MainWindow::readOptions() { KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files")); if (KdenliveSettings::defaultprojectfolder().isEmpty()) { QDir dir(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); dir.mkpath(QStringLiteral(".")); KdenliveSettings::setDefaultprojectfolder(dir.absolutePath()); } if (KdenliveSettings::trackheight() == 0) { QFontMetrics metrics(font()); int trackHeight = 2 * metrics.height(); QStyle *style = qApp->style(); trackHeight += style->pixelMetric(QStyle::PM_ToolBarIconSize) + 2 * style->pixelMetric(QStyle::PM_ToolBarItemMargin) + style->pixelMetric(QStyle::PM_ToolBarItemSpacing) + 2; KdenliveSettings::setTrackheight(trackHeight); } if (KdenliveSettings::trackheight() == 0) { KdenliveSettings::setTrackheight(50); } bool firstRun = false; KConfigGroup initialGroup(config, "version"); if (!initialGroup.exists() || KdenliveSettings::sdlAudioBackend().isEmpty()) { // First run, check if user is on a KDE Desktop firstRun = true; // this is our first run, show Wizard QPointer w = new Wizard(true); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); delete w; } else { delete w; ::exit(1); } } else if (!KdenliveSettings::ffmpegpath().isEmpty() && !QFile::exists(KdenliveSettings::ffmpegpath())) { // Invalid entry for FFmpeg, check system QPointer w = new Wizard(true); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } initialGroup.writeEntry("version", version); return firstRun; } void MainWindow::slotRunWizard() { QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } void MainWindow::slotRefreshProfiles() { KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings"))); if (d) { d->checkProfile(); } } void MainWindow::slotEditProjectSettings() { KdenliveDoc *project = pCore->currentDoc(); QPoint p = getMainTimeline()->getTracksCount(); ProjectSettings *w = new ProjectSettings(project, project->metadata(), getMainTimeline()->controller()->extractCompositionLumas(), p.x(), p.y(), project->projectTempFolder(), true, !project->isModified(), this); connect(w, &ProjectSettings::disableProxies, this, &MainWindow::slotDisableProxies); // connect(w, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange())); connect(w, &ProjectSettings::refreshProfiles, this, &MainWindow::slotRefreshProfiles); if (w->exec() == QDialog::Accepted) { QString profile = w->selectedProfile(); // project->setProjectFolder(w->selectedFolder()); bool modified = false; if (m_renderWidget) { m_renderWidget->setDocumentPath(project->projectDataFolder()); } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { slotSwitchAudioThumbs(); } if (pCore->getCurrentProfile()->path() != profile || project->profileChanged(profile)) { pCore->setCurrentProfile(profile); pCore->projectManager()->slotResetProfiles(); slotUpdateDocumentState(true); } if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams() || project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams()); project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension()); if (pCore->binController()->clipCount() > 0 && KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) == KMessageBox::Yes) { // TODO: rebuild all proxies pCore->bin()->rebuildProxies(); } } if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number((int)w->generateProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number((int)w->generateProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); } if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number((int)w->generateImageProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number((int)w->generateImageProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); } if (project->getDocumentProperty(QStringLiteral("resizepreview")) != QString::number(w->resizePreview())) { modified = true; project->setDocumentProperty(QStringLiteral("resizepreview"), QString::number(w->resizePreview())); } if (project->getDocumentProperty(QStringLiteral("previewheight")) != QString::number(w->previewHeight())) { modified = true; project->setDocumentProperty(QStringLiteral("previewheight"), QString::number(w->previewHeight())); } if (project->getDocumentProperty(QStringLiteral("proxyimagesize")) != QString::number(w->proxyImageSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimagesize"), QString::number(w->proxyImageSize())); } if (w->resizePreviewChanged() || project->updatePreviewSettings(w->selectedPreview())) { // preview setting changed, reset cache and update getMainTimeline()->controller()->resetPreview(); } if (QString::number((int)w->useProxy()) != project->getDocumentProperty(QStringLiteral("enableproxy"))) { project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)w->useProxy())); modified = true; slotUpdateProxySettings(); } if (w->metadata() != project->metadata()) { project->setMetadata(w->metadata()); } QString newProjectFolder = w->storageFolder(); if (newProjectFolder.isEmpty()) { newProjectFolder = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); } if (newProjectFolder != project->projectTempFolder()) { KMessageBox::ButtonCode answer; // Project folder changed: if (project->isModified()) { answer = KMessageBox::warningContinueCancel(this, i18n("The current project has not been saved. This will first save the project, then move " "all temporary files from %1 to %2, and the project file will be reloaded", project->projectTempFolder(), newProjectFolder)); if (answer == KMessageBox::Continue) { pCore->projectManager()->saveFile(); } } else { answer = KMessageBox::warningContinueCancel( this, i18n("This will move all temporary files from %1 to %2, the project file will then be reloaded", project->projectTempFolder(), newProjectFolder)); } if (answer == KMessageBox::Continue) { // Proceeed with move QString documentId = QDir::cleanPath(project->getDocumentProperty(QStringLiteral("documentid"))); bool ok; documentId.toLongLong(&ok, 10); if (!ok || documentId.isEmpty()) { KMessageBox::sorry(this, i18n("Cannot perform operation, invalid document id: %1", documentId)); } else { QDir newDir(newProjectFolder); QDir oldDir(project->projectTempFolder()); if (newDir.exists(documentId)) { KMessageBox::sorry(this, i18n("Cannot perform operation, target directory already exists: %1", newDir.absoluteFilePath(documentId))); } else { // Proceed with the move pCore->projectManager()->moveProjectData(oldDir.absoluteFilePath(documentId), newDir.absolutePath()); } } } } if (modified) { project->setModified(); } } delete w; } void MainWindow::slotDisableProxies() { pCore->currentDoc()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int)false)); pCore->currentDoc()->setModified(); slotUpdateProxySettings(); } void MainWindow::slotStopRenderProject() { if (m_renderWidget) { m_renderWidget->slotAbortCurrentJob(); } } void MainWindow::slotRenderProject() { KdenliveDoc *project = pCore->currentDoc(); if (!m_renderWidget) { QString projectfolder = project ? project->projectDataFolder() + QDir::separator() : KdenliveSettings::defaultprojectfolder(); if (project) { m_renderWidget = new RenderWidget(projectfolder, project->useProxy(), this); connect(m_renderWidget, &RenderWidget::shutdown, this, &MainWindow::slotShutdown); connect(m_renderWidget, &RenderWidget::selectedRenderProfile, this, &MainWindow::slotSetDocumentRenderProfile); connect(m_renderWidget, &RenderWidget::prepareRenderingData, this, &MainWindow::slotPrepareRendering); connect(m_renderWidget, &RenderWidget::abortProcess, this, &MainWindow::abortRenderJob); connect(m_renderWidget, &RenderWidget::openDvdWizard, this, &MainWindow::slotDvdWizard); connect(this, &MainWindow::updateRenderWidgetProfile, m_renderWidget, &RenderWidget::adjustViewToProfile); double projectDuration = GenTime(getMainTimeline()->controller()->duration(), pCore->getCurrentFps()).ms() / 1000; m_renderWidget->setGuides(project->getGuideModel()->getAllMarkers(), projectDuration); m_renderWidget->setDocumentPath(project->projectDataFolder()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } if (m_compositeAction->currentAction()) { - m_renderWidget->errorMessage(RenderWidget::CompositeError, - m_compositeAction->currentAction()->data().toInt() == 1 ? i18n("Rendering using low quality track compositing") - : QString()); + m_renderWidget->errorMessage(RenderWidget::CompositeError, m_compositeAction->currentAction()->data().toInt() == 1 + ? i18n("Rendering using low quality track compositing") + : QString()); } } slotCheckRenderStatus(); m_renderWidget->show(); // m_renderWidget->showNormal(); // What are the following lines supposed to do? // m_renderWidget->enableAudio(false); // m_renderWidget->export_audio; } void MainWindow::slotCheckRenderStatus() { // Make sure there are no missing clips // TODO /*if (m_renderWidget) m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/ } void MainWindow::setRenderingProgress(const QString &url, int progress) { emit setRenderProgress(progress); if (m_renderWidget) { m_renderWidget->setRenderJob(url, progress); } } void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error) { emit setRenderProgress(100); if (m_renderWidget) { m_renderWidget->setRenderStatus(url, status, error); } } void MainWindow::addProjectClip(const QString &url) { if (pCore->currentDoc()) { QStringList ids = pCore->binController()->getBinIdsByResource(QFileInfo(url)); if (!ids.isEmpty()) { // Clip is already in project bin, abort return; } ClipCreator::createClipFromFile(url, pCore->projectItemModel()->getRootFolder()->clipId(), pCore->projectItemModel()); } } void MainWindow::addTimelineClip(const QString &url) { if (pCore->currentDoc()) { QStringList ids = pCore->binController()->getBinIdsByResource(QFileInfo(url)); if (!ids.isEmpty()) { pCore->selectBinClip(ids.constFirst()); slotInsertClipInsert(); } } } void MainWindow::addEffect(const QString &effectName) { QStringList effectInfo; effectInfo << effectName << effectName; const QDomElement effect = EffectsListWidget::itemEffect(5, effectInfo); if (!effect.isNull()) { slotAddEffect(effect); } else { qCDebug(KDENLIVE_LOG) << " * * *EFFECT: " << effectName << " NOT AVAILABLE"; exitApp(); } } void MainWindow::scriptRender(const QString &url) { slotRenderProject(); m_renderWidget->slotPrepareExport(true, url); } void MainWindow::exitApp() { QApplication::exit(0); } void MainWindow::slotCleanProject() { if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) { return; } pCore->bin()->cleanup(); } void MainWindow::slotUpdateMousePosition(int pos) { if (pCore->currentDoc()) { switch (m_timeFormatButton->currentItem()) { case 0: m_timeFormatButton->setText(pCore->currentDoc()->timecode().getTimecodeFromFrames(pos) + QStringLiteral(" / ") + pCore->currentDoc()->timecode().getTimecodeFromFrames(getMainTimeline()->controller()->duration())); break; default: m_timeFormatButton->setText( QStringLiteral("%1 / %2").arg(pos, 6, 10, QLatin1Char('0')).arg(getMainTimeline()->controller()->duration(), 6, 10, QLatin1Char('0'))); } } } void MainWindow::slotUpdateProjectDuration(int pos) { Q_UNUSED(pos) if (pCore->currentDoc()) { slotUpdateMousePosition(getMainTimeline()->controller()->getMousePos()); } } void MainWindow::slotUpdateDocumentState(bool modified) { setWindowTitle(pCore->currentDoc()->description()); setWindowModified(modified); m_saveAction->setEnabled(modified); } void MainWindow::connectDocument() { KdenliveDoc *project = pCore->currentDoc(); connect(project, &KdenliveDoc::startAutoSave, pCore->projectManager(), &ProjectManager::slotStartAutoSave); connect(project, &KdenliveDoc::reloadEffects, this, &MainWindow::slotReloadEffects); KdenliveSettings::setProject_fps(pCore->getCurrentFps()); // TODO REFAC: reconnect to new timeline /* Timeline *trackView = pCore->projectManager()->currentTimeline(); connect(trackView, &Timeline::configTrack, this, &MainWindow::slotConfigTrack); connect(trackView, &Timeline::updateTracksInfo, this, &MainWindow::slotUpdateTrackInfo); connect(trackView, &Timeline::mousePosition, this, &MainWindow::slotUpdateMousePosition); connect(pCore->producerQueue(), &ProducerQueue::infoProcessingFinished, trackView->projectView(), &CustomTrackView::slotInfoProcessingFinished, Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::importKeyframes, this, &MainWindow::slotProcessImportKeyframes); connect(trackView->projectView(), &CustomTrackView::updateTrimMode, this, &MainWindow::setTrimMode); connect(m_projectMonitor, &Monitor::multitrackView, trackView, &Timeline::slotMultitrackView); connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint))); connect(trackView->projectView(), &CustomTrackView::guidesUpdated, this, &MainWindow::slotGuidesUpdated); connect(trackView->projectView(), &CustomTrackView::loadMonitorScene, m_projectMonitor, &Monitor::slotShowEffectScene); connect(trackView->projectView(), &CustomTrackView::setQmlProperty, m_projectMonitor, &Monitor::setQmlProperty); connect(m_projectMonitor, SIGNAL(acceptRipple(bool)), trackView->projectView(), SLOT(slotAcceptRipple(bool))); connect(m_projectMonitor, SIGNAL(switchTrimMode(int)), trackView->projectView(), SLOT(switchTrimMode(int))); connect(project, &KdenliveDoc::saveTimelinePreview, trackView, &Timeline::slotSaveTimelinePreview); connect(trackView, SIGNAL(showTrackEffects(int, TrackInfo)), this, SLOT(slotTrackSelected(int, TrackInfo))); connect(trackView->projectView(), &CustomTrackView::clipItemSelected, this, &MainWindow::slotTimelineClipSelected, Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::setActiveKeyframe, m_effectStack, &EffectStackView2::setActiveKeyframe); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_effectStack, SLOT(slotTransitionItemSelected(Transition *, int, QPoint, bool)), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), this, SLOT(slotActivateTransitionView(Transition *))); connect(trackView->projectView(), &CustomTrackView::zoomIn, this, &MainWindow::slotZoomIn); connect(trackView->projectView(), &CustomTrackView::zoomOut, this, &MainWindow::slotZoomOut); connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int))); connect(trackView, SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType))); connect(trackView->projectView(), SIGNAL(displayMessage(QString, MessageType)), m_messageLabel, SLOT(setMessage(QString, MessageType))); connect(pCore->bin(), &Bin::clipNameChanged, trackView->projectView(), &CustomTrackView::clipNameChanged); connect(trackView->projectView(), SIGNAL(showClipFrame(QString, int)), pCore->bin(), SLOT(selectClipById(QString, int))); connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay())); connect(trackView->projectView(), &CustomTrackView::pauseMonitor, m_projectMonitor, &Monitor::pause, Qt::DirectConnection); connect(m_projectMonitor, &Monitor::addEffect, trackView->projectView(), &CustomTrackView::slotAddEffectToCurrentItem); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition *, int, QPoint, bool)), m_projectMonitor, SLOT(slotSetSelectedClip(Transition *))); connect(pCore->bin(), SIGNAL(gotFilterJobResults(QString, int, int, stringMap, stringMap)), trackView->projectView(), SLOT(slotGotFilterJobResults(QString, int, int, stringMap, stringMap))); //TODO //connect(m_projectList, SIGNAL(addMarkers(QString,QList)), trackView->projectView(), SLOT(slotAddClipMarker(QString,QList))); // Effect stack signals connect(m_effectStack, &EffectStackView2::updateEffect, trackView->projectView(), &CustomTrackView::slotUpdateClipEffect); connect(m_effectStack, &EffectStackView2::updateClipRegion, trackView->projectView(), &CustomTrackView::slotUpdateClipRegion); connect(m_effectStack, SIGNAL(removeEffect(ClipItem *, int, QDomElement)), trackView->projectView(), SLOT(slotDeleteEffect(ClipItem *, int, QDomElement))); connect(m_effectStack, SIGNAL(removeEffectGroup(ClipItem *, int, QDomDocument)), trackView->projectView(), SLOT(slotDeleteEffectGroup(ClipItem *, int, QDomDocument))); connect(m_effectStack, SIGNAL(addEffect(ClipItem *, QDomElement, int)), trackView->projectView(), SLOT(slotAddEffect(ClipItem *, QDomElement, int))); connect(m_effectStack, SIGNAL(changeEffectState(ClipItem *, int, QList, bool)), trackView->projectView(), SLOT(slotChangeEffectState(ClipItem *, int, QList, bool))); connect(m_effectStack, SIGNAL(changeEffectPosition(ClipItem *, int, QList, int)), trackView->projectView(), SLOT(slotChangeEffectPosition(ClipItem *, int, QList, int))); connect(m_effectStack, &EffectStackView2::refreshEffectStack, trackView->projectView(), &CustomTrackView::slotRefreshEffects); connect(m_effectStack, &EffectStackView2::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos); connect(m_effectStack, SIGNAL(importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)), trackView->projectView(), SLOT(slotImportClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap))); // Transition config signals connect(m_effectStack->transitionConfig(), SIGNAL(transitionUpdated(Transition *, QDomElement)), trackView->projectView(), SLOT(slotTransitionUpdated(Transition *, QDomElement))); connect(m_effectStack->transitionConfig(), &TransitionSettings::seekTimeline, trackView->projectView(), &CustomTrackView::seekCursorPos); connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor()), Qt::DirectConnection); connect(project, &KdenliveDoc::updateFps, this, &MainWindow::slotUpdateProfile, Qt::DirectConnection); connect(trackView, &Timeline::zoneMoved, this, &MainWindow::slotZoneMoved); trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineContextClipMenu, m_timelineContextTransitionMenu, m_clipTypeGroup, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); */ connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(project, &KdenliveDoc::docModified, this, &MainWindow::slotUpdateDocumentState); connect(pCore->bin(), SIGNAL(displayMessage(QString, int, MessageType)), m_messageLabel, SLOT(setProgressMessage(QString, int, MessageType))); if (m_renderWidget) { slotCheckRenderStatus(); // m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectDataFolder()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } m_zoomSlider->setValue(project->zoom().x()); m_commandStack->setActiveStack(project->commandStack().get()); setWindowTitle(project->description()); setWindowModified(project->isModified()); m_saveAction->setEnabled(project->isModified()); m_normalEditTool->setChecked(true); connect(m_projectMonitor, &Monitor::durationChanged, this, &MainWindow::slotUpdateProjectDuration); pCore->monitorManager()->setDocument(project); // TODO REFAC: fix // trackView->updateProfile(1.0); // Init document zone // m_projectMonitor->slotZoneMoved(trackView->inPoint(), trackView->outPoint()); // Update the mouse position display so it will display in DF/NDF format by default based on the project setting. // slotUpdateMousePosition(0); // Update guides info in render widget // slotGuidesUpdated(); // set tool to select tool setTrimMode(QString()); m_buttonSelectTool->setChecked(true); connect(m_projectMonitorDock, &QDockWidget::visibilityChanged, m_projectMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection); connect(m_clipMonitorDock, &QDockWidget::visibilityChanged, m_clipMonitor, &Monitor::slotRefreshMonitor, Qt::UniqueConnection); } void MainWindow::slotZoneMoved(int start, int end) { pCore->currentDoc()->setZone(start, end); QPoint zone(start, end); m_projectMonitor->slotLoadClipZone(zone); } void MainWindow::slotGuidesUpdated() { if (m_renderWidget) { double projectDuration = GenTime(getMainTimeline()->controller()->duration() - TimelineModel::seekDuration - 2, pCore->getCurrentFps()).ms() / 1000; m_renderWidget->setGuides(pCore->currentDoc()->getGuideModel()->getAllMarkers(), projectDuration); } } void MainWindow::slotEditKeys() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General")); dialog.configure(); } void MainWindow::slotPreferences(int page, int option) { /* * An instance of your dialog could be already created and could be * cached, in which case you want to display the cached dialog * instead of creating another one */ if (KConfigDialog::showDialog(QStringLiteral("settings"))) { KdenliveSettingsDialog *d = static_cast(KConfigDialog::exists(QStringLiteral("settings"))); if (page != -1) { d->showPage(page, option); } return; } // KConfigDialog didn't find an instance of this dialog, so lets // create it : // Get the mappable actions in localized form QMap actions; KActionCollection *collection = actionCollection(); QRegExp ampEx("&{1,1}"); for (const QString &action_name : m_actionNames) { QString action_text = collection->action(action_name)->text(); action_text.remove(ampEx); actions[action_text] = action_name; } auto *dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this); connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::updateConfiguration); connect(dialog, &KConfigDialog::settingsChanged, this, &MainWindow::configurationChanged); connect(dialog, &KdenliveSettingsDialog::doResetProfile, pCore->projectManager(), &ProjectManager::slotResetProfiles); connect(dialog, &KdenliveSettingsDialog::checkTabPosition, this, &MainWindow::slotCheckTabPosition); connect(dialog, &KdenliveSettingsDialog::restartKdenlive, this, &MainWindow::slotRestart); connect(dialog, &KdenliveSettingsDialog::updateLibraryFolder, pCore.get(), &Core::updateLibraryPath); connect(dialog, &KdenliveSettingsDialog::audioThumbFormatChanged, m_timelineTabs, &TimelineTabs::audioThumbFormatChanged); connect(dialog, &KdenliveSettingsDialog::resetView, this, &MainWindow::resetTimelineTracks); dialog->show(); if (page != -1) { dialog->showPage(page, option); } } void MainWindow::slotCheckTabPosition() { int pos = tabPosition(Qt::LeftDockWidgetArea); if (KdenliveSettings::tabposition() != pos) { setTabPosition(Qt::AllDockWidgetAreas, (QTabWidget::TabPosition)KdenliveSettings::tabposition()); } } void MainWindow::slotRestart() { m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } void MainWindow::closeEvent(QCloseEvent *event) { KXmlGuiWindow::closeEvent(event); if (event->isAccepted()) { #ifdef Q_OS_WIN QProcess::startDetached(QStandardPaths::findExecutable(QStringLiteral("kdeinit5")) + " --terminate"); #endif QApplication::exit(m_exitCode); return; } } void MainWindow::updateConfiguration() { // TODO: we should apply settings to all projects, not only the current one m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); slotSwitchAutomaticTransition(); // Update list of transcoding profiles buildDynamicActions(); loadClipActions(); } void MainWindow::slotSwitchVideoThumbs() { KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails()); m_timelineTabs->showThumbnailsChanged(); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); } void MainWindow::slotSwitchAudioThumbs() { KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails()); m_timelineTabs->showAudioThumbnailsChanged(); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); } void MainWindow::slotSwitchMarkersComments() { KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers()); getMainTimeline()->controller()->showMarkersChanged(); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); } void MainWindow::slotSwitchSnap() { KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints()); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); getMainTimeline()->controller()->snapChanged(KdenliveSettings::snaptopoints()); } void MainWindow::slotSwitchAutomaticTransition() { KdenliveSettings::setAutomatictransitions(!KdenliveSettings::automatictransitions()); m_buttonAutomaticTransition->setChecked(KdenliveSettings::automatictransitions()); } void MainWindow::slotDeleteItem() { if ((QApplication::focusWidget() != nullptr) && (QApplication::focusWidget()->parentWidget() != nullptr) && QApplication::focusWidget()->parentWidget() == pCore->bin()) { pCore->bin()->slotDeleteClip(); } else { QWidget *widget = QApplication::focusWidget(); while ((widget != nullptr) && widget != this) { if (widget == m_effectStackDock) { // TODO refac: reimplement // m_effectStack->deleteCurrentEffect(); return; } widget = widget->parentWidget(); } // effect stack has no focus getMainTimeline()->controller()->deleteSelectedClips(); } } void MainWindow::slotAddClipMarker() { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { return; } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); clip->getMarkerModel()->editMarkerGui(pos, this, true, clip.get()); } void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion) { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { // TODO refac retrieve active clip /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (GenTime(m_projectMonitor->position(), pCore->getCurrentFps()) - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); bool markerFound = false; CommentedTime marker = clip->getMarkerModel()->getMarker(pos, &markerFound); if (!markerFound) { if (allowGuideDeletion && m_projectMonitor->isActive()) { slotDeleteGuide(); } else { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); } return; } clip->getMarkerModel()->removeMarker(pos); } void MainWindow::slotDeleteAllClipMarkers() { std::shared_ptr clip(nullptr); if (m_projectMonitor->isActive()) { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } bool ok = clip->getMarkerModel()->removeAllMarkers(); if (!ok) { m_messageLabel->setMessage(i18n("An error occured while deleting markers"), ErrorMessage); return; } } void MainWindow::slotEditClipMarker() { std::shared_ptr clip(nullptr); GenTime pos; if (m_projectMonitor->isActive()) { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (GenTime(m_projectMonitor->position(), pCore->getCurrentFps()) - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->bin()->getBinClip(item->getBinId()); } } */ } else { clip = m_clipMonitor->currentController(); pos = GenTime(m_clipMonitor->position(), pCore->getCurrentFps()); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to edit marker"), ErrorMessage); return; } QString id = clip->AbstractProjectItem::clipId(); bool markerFound = false; CommentedTime oldMarker = clip->getMarkerModel()->getMarker(pos, &markerFound); if (!markerFound) { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } clip->getMarkerModel()->editMarkerGui(pos, this, false, clip.get()); } void MainWindow::slotAddMarkerGuideQuickly() { if (!getMainTimeline() || !pCore->currentDoc()) { return; } if (m_clipMonitor->isActive()) { std::shared_ptr clip(m_clipMonitor->currentController()); GenTime pos(m_clipMonitor->position(), pCore->getCurrentFps()); if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } CommentedTime marker(pos, pCore->currentDoc()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type()); clip->getMarkerModel()->addMarker(marker.time(), marker.comment(), marker.markerType()); } else { getMainTimeline()->controller()->switchGuide(); } } void MainWindow::slotAddGuide() { getMainTimeline()->controller()->switchGuide(); } void MainWindow::slotInsertSpace() { getMainTimeline()->controller()->insertSpace(); } void MainWindow::slotRemoveSpace() { getMainTimeline()->controller()->removeSpace(-1, -1, false); } void MainWindow::slotRemoveAllSpace() { getMainTimeline()->controller()->removeSpace(-1, -1, true); } void MainWindow::slotInsertTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->addTrack(-1); } void MainWindow::slotDeleteTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->addTrack(-1); } void MainWindow::slotConfigTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->deleteTrack(-1); } void MainWindow::slotSelectTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSelectClipsInTrack(); } */ } void MainWindow::slotSelectAllTracks() { // TODO refac /* pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSelectAllClips(); } */ } void MainWindow::slotUnselectAllTracks() { // TODO refac /* pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clearSelection(); } */ } void MainWindow::slotEditGuide() { getMainTimeline()->controller()->editGuide(); } void MainWindow::slotDeleteGuide() { getMainTimeline()->controller()->switchGuide(-1, true); } void MainWindow::slotDeleteAllGuides() { pCore->currentDoc()->getGuideModel()->removeAllMarkers(); } void MainWindow::slotCutTimelineClip() { getMainTimeline()->controller()->cutClipUnderCursor(); } void MainWindow::slotInsertClipOverwrite() { const QString &binId = m_clipMonitor->activeClipId(); if (binId.isEmpty()) { // No clip in monitor return; } getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), true); } void MainWindow::slotInsertClipInsert() { const QString &binId = m_clipMonitor->activeClipId(); if (binId.isEmpty()) { // No clip in monitor return; } getMainTimeline()->controller()->insertZone(binId, m_clipMonitor->getZoneInfo(), false); } void MainWindow::slotExtractZone() { getMainTimeline()->controller()->extractZone(m_clipMonitor->getZoneInfo()); } void MainWindow::slotLiftZone() { getMainTimeline()->controller()->liftZone(m_clipMonitor->getZoneInfo()); } void MainWindow::slotPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->startPreviewRender(); } } void MainWindow::slotStopPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->stopPreviewRender(); } } void MainWindow::slotDefinePreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->addPreviewRange(true); } } void MainWindow::slotRemovePreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->addPreviewRange(false); } } void MainWindow::slotClearPreviewRender() { if (pCore->currentDoc()) { getCurrentTimeline()->controller()->clearPreviewRange(); } } void MainWindow::slotSelectTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true); } void MainWindow::slotSelectTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true); } void MainWindow::slotDeselectTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, false); } void MainWindow::slotDeselectTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, false); } void MainWindow::slotSelectAddTimelineClip() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineClip, true, true); } void MainWindow::slotSelectAddTimelineTransition() { getCurrentTimeline()->controller()->selectCurrentItem(ObjectType::TimelineComposition, true, true); } void MainWindow::slotGroupClips() { getCurrentTimeline()->controller()->groupSelection(); } void MainWindow::slotUnGroupClips() { getCurrentTimeline()->controller()->unGroupSelection(); } void MainWindow::slotEditItemDuration() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->editItemDuration(); } */ } void MainWindow::slotAddProjectClip(const QUrl &url, const QStringList &folderInfo) { pCore->bin()->droppedUrls(QList() << url, folderInfo); } void MainWindow::slotAddProjectClipList(const QList &urls) { pCore->bin()->droppedUrls(urls); } void MainWindow::slotAddTransition(QAction *result) { if (!result) { return; } // TODO refac /* QStringList info = result->data().toStringList(); if (info.isEmpty() || info.count() < 2) { return; } QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1)); if (pCore->projectManager()->currentTimeline() && !transition.isNull()) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement()); } */ } void MainWindow::slotAddVideoEffect(QAction *result) { if (!result) { return; } QStringList info = result->data().toStringList(); if (info.isEmpty() || info.size() < 3) { return; } QDomElement effect; int effectType = info.last().toInt(); switch (effectType) { case EffectsList::EFFECT_VIDEO: case EffectsList::EFFECT_GPU: effect = videoEffects.getEffectByTag(info.at(0), info.at(1)); break; case EffectsList::EFFECT_AUDIO: effect = audioEffects.getEffectByTag(info.at(0), info.at(1)); break; case EffectsList::EFFECT_CUSTOM: effect = customEffects.getEffectByTag(info.at(0), info.at(1)); break; default: effect = videoEffects.getEffectByTag(info.at(0), info.at(1)); if (!effect.isNull()) { break; } effect = audioEffects.getEffectByTag(info.at(0), info.at(1)); if (!effect.isNull()) { break; } effect = customEffects.getEffectByTag(info.at(0), info.at(1)); break; } if (!effect.isNull()) { slotAddEffect(effect); } else { m_messageLabel->setMessage(i18n("Cannot find effect %1 / %2", info.at(0), info.at(1)), ErrorMessage); } } void MainWindow::slotZoomIn(bool zoomOnMouse) { slotSetZoom(m_zoomSlider->value() - 1, zoomOnMouse); slotShowZoomSliderToolTip(); } void MainWindow::slotZoomOut(bool zoomOnMouse) { slotSetZoom(m_zoomSlider->value() + 1, zoomOnMouse); slotShowZoomSliderToolTip(); } void MainWindow::slotFitZoom() { /* if (pCore->projectManager()->currentTimeline()) { m_zoomSlider->setValue(pCore->projectManager()->currentTimeline()->fitZoom()); // Make sure to reset scroll bar to start pCore->projectManager()->currentTimeline()->projectView()->scrollToStart(); } */ } void MainWindow::slotSetZoom(int value, bool zoomOnMouse) { value = qBound(m_zoomSlider->minimum(), value, m_zoomSlider->maximum()); m_timelineTabs->changeZoom(value, zoomOnMouse); m_zoomOut->setEnabled(value < m_zoomSlider->maximum()); m_zoomIn->setEnabled(value > m_zoomSlider->minimum()); slotUpdateZoomSliderToolTip(value); m_zoomSlider->blockSignals(true); m_zoomSlider->setValue(value); m_zoomSlider->blockSignals(false); } void MainWindow::slotShowZoomSliderToolTip(int zoomlevel) { if (zoomlevel != -1) { slotUpdateZoomSliderToolTip(zoomlevel); } QPoint global = m_zoomSlider->rect().topLeft(); global.ry() += m_zoomSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global)); QApplication::sendEvent(m_zoomSlider, &toolTipEvent); } void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel) { m_zoomSlider->setToolTip(i18n("Zoom Level: %1/13", (13 - zoomlevel))); } void MainWindow::slotGotProgressInfo(const QString &message, int progress, MessageType type) { m_messageLabel->setProgressMessage(message, progress, type); } void MainWindow::customEvent(QEvent *e) { if (e->type() == QEvent::User) { m_messageLabel->setMessage(static_cast(e)->message(), MltError); } } void MainWindow::slotSnapRewind() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->gotoPreviousSnap(); } else { m_clipMonitor->slotSeekToPreviousSnap(); } } void MainWindow::slotSnapForward() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->gotoNextSnap(); } else { m_clipMonitor->slotSeekToNextSnap(); } } void MainWindow::slotClipStart() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->seekCurrentClip(false); } } void MainWindow::slotClipEnd() { if (m_projectMonitor->isActive()) { getMainTimeline()->controller()->seekCurrentClip(true); } } void MainWindow::slotChangeTool(QAction *action) { if (action == m_buttonSelectTool) { slotSetTool(SelectTool); } else if (action == m_buttonRazorTool) { slotSetTool(RazorTool); } else if (action == m_buttonSpacerTool) { slotSetTool(SpacerTool); } } void MainWindow::slotChangeEdit(QAction *action) { Q_UNUSED(action) // TODO refac /* if (!pCore->projectManager()->currentTimeline()) { return; } if (action == m_overwriteEditTool) { pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::OverwriteEdit); } else if (action == m_insertEditTool) { pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::InsertEdit); } else { pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::NormalEdit); } */ } void MainWindow::slotSetTool(ProjectTool tool) { if (pCore->currentDoc()) { // pCore->currentDoc()->setTool(tool); QString message; switch (tool) { case SpacerTool: message = i18n("Ctrl + click to use spacer on current track only"); break; case RazorTool: message = i18n("Click on a clip to cut it, Shift + move to preview cut frame"); break; default: message = i18n("Shift + click to create a selection rectangle, Ctrl + click to add an item to selection"); break; } m_messageLabel->setMessage(message, InformationMessage); getMainTimeline()->setTool(tool); } } void MainWindow::slotCopy() { getMainTimeline()->controller()->copyItem(); } void MainWindow::slotPaste() { getMainTimeline()->controller()->pasteItem(); } void MainWindow::slotPasteEffects() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->pasteClipEffects(); } */ } void MainWindow::slotClipInTimeline(const QString &clipId, QList ids) { Q_UNUSED(clipId) QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); QList actionList; for (int i = 0; i < ids.count(); ++i) { QString track = getMainTimeline()->controller()->getTrackNameFromIndex(pCore->getItemTrack(ObjectId(ObjectType::TimelineClip, ids.at(i)))); QString start = pCore->currentDoc()->timecode().getTimecodeFromFrames(pCore->getItemPosition(ObjectId(ObjectType::TimelineClip, ids.at(i)))); int j = 0; QAction *a = new QAction(track + QStringLiteral(": ") + start, inTimelineMenu); a->setData(ids.at(i)); connect(a, &QAction::triggered, this, &MainWindow::slotSelectClipInTimeline); while (j < actionList.count()) { if (actionList.at(j)->text() > a->text()) { break; } j++; } actionList.insert(j, a); } QList list = inTimelineMenu->actions(); unplugActionList(QStringLiteral("timeline_occurences")); qDeleteAll(list); plugActionList(QStringLiteral("timeline_occurences"), actionList); if (actionList.isEmpty()) { inTimelineMenu->setEnabled(false); } else { inTimelineMenu->setEnabled(true); } } void MainWindow::slotClipInProjectTree() { QList ids = getMainTimeline()->controller()->selection(); if (!ids.isEmpty()) { m_projectBinDock->raise(); ObjectId id(ObjectType::TimelineClip, ids.constFirst()); int start = pCore->getItemIn(id); int duration = pCore->getItemDuration(id); QPoint zone(start, start + duration); - qDebug()<<" - - selecting clip on monitor, zone: "<isActive()) { slotSwitchMonitors(); } int pos = m_projectMonitor->position(); int itemPos = pCore->getItemPosition(id); if (pos >= itemPos && pos < itemPos + duration) { pos -= itemPos; } else { pos = -1; } pCore->selectBinClip(getMainTimeline()->controller()->getClipBinId(ids.constFirst()), pos, zone); } } void MainWindow::slotSelectClipInTimeline() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); QAction *action = qobject_cast(sender()); int clipId = action->data().toInt(); getMainTimeline()->controller()->focusItem(clipId); } /** Gets called when the window gets hidden */ void MainWindow::hideEvent(QHideEvent * /*event*/) { if (isMinimized() && pCore->monitorManager()) { pCore->monitorManager()->pauseActiveMonitor(); } } /*void MainWindow::slotSaveZone(Render *render, const QPoint &zone, DocClipBase *baseClip, QUrl path) { QPointer dialog = new QDialog(this); dialog->setWindowTitle("Save clip zone"); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); QLabel *label1 = new QLabel(i18n("Save clip zone as:"), this); if (path.isEmpty()) { QString tmppath = pCore->currentDoc()->projectFolder().path() + QDir::separator(); if (baseClip == nullptr) { tmppath.append("untitled.mlt"); } else { tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4, '0') + QStringLiteral(".mlt")); } path = QUrl(tmppath); } KUrlRequester *url = new KUrlRequester(path, this); url->setFilter("video/mlt-playlist"); QLabel *label2 = new QLabel(i18n("Description:"), this); QLineEdit *edit = new QLineEdit(this); mainLayout->addWidget(label1); mainLayout->addWidget(url); mainLayout->addWidget(label2); mainLayout->addWidget(edit); mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { if (QFile::exists(url->url().path())) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", url->url().path())) == KMessageBox::No) { slotSaveZone(render, zone, baseClip, url->url()); delete dialog; return; } } if (baseClip && !baseClip->fileURL().isEmpty()) { // create zone from clip url, so that we don't have problems with proxy clips QProcess p; QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.remove("MLT_PROFILE"); p.setProcessEnvironment(env); p.start(KdenliveSettings::rendererpath(), QStringList() << baseClip->fileURL().toLocalFile() << "in=" + QString::number(zone.x()) << "out=" + QString::number(zone.y()) << "-consumer" << "xml:" + url->url().path()); if (!p.waitForStarted(3000)) { KMessageBox::sorry(this, i18n("Cannot start MLT's renderer:\n%1", KdenliveSettings::rendererpath())); } else if (!p.waitForFinished(5000)) { KMessageBox::sorry(this, i18n("Timeout while creating xml output")); } } else render->saveZone(url->url(), edit->text(), zone); } delete dialog; }*/ void MainWindow::slotResizeItemStart() { getMainTimeline()->controller()->setInPoint(); } void MainWindow::slotResizeItemEnd() { getMainTimeline()->controller()->setOutPoint(); } int MainWindow::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec() != 0) { entries = dialog->changedEntries(); } for (const KNS3::Entry &entry : entries) { if (entry.status() == KNS3::Entry::Installed) { qCDebug(KDENLIVE_LOG) << "// Installed files: " << entry.installedFiles(); } } delete dialog; return entries.size(); } void MainWindow::slotGetNewTitleStuff() { if (getNewStuff(QStringLiteral(":data/kdenlive_titles.knsrc")) > 0) { // get project title path QString titlePath = pCore->currentDoc()->projectDataFolder() + QStringLiteral("/titles/"); TitleWidget::refreshTitleTemplates(titlePath); } } void MainWindow::slotGetNewLumaStuff() { if (getNewStuff(QStringLiteral(":data/kdenlive_wipes.knsrc")) > 0) { initEffects::refreshLumas(); // TODO: refresh currently displayd trans ? } } void MainWindow::slotGetNewKeyboardStuff() { if (getNewStuff(QStringLiteral(":data/kdenlive_keyboardschemes.knsrc")) > 0) { - //Is there something to do ? + // Is there something to do ? } } void MainWindow::slotGetNewRenderStuff() { if (getNewStuff(QStringLiteral(":data/kdenlive_renderprofiles.knsrc")) > 0) if (m_renderWidget) { m_renderWidget->reloadProfiles(); } } void MainWindow::slotAutoTransition() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->autoTransition(); } */ } void MainWindow::slotSplitAudio() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->splitAudio(); } */ } void MainWindow::slotSetAudioAlignReference() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->setAudioAlignReference(); } */ } void MainWindow::slotAlignAudio() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->alignAudio(); } */ } void MainWindow::slotUpdateClipType(QAction *action) { Q_UNUSED(action) // TODO refac /* if (pCore->projectManager()->currentTimeline()) { PlaylistState::ClipState state = (PlaylistState::ClipState)action->data().toInt(); pCore->projectManager()->currentTimeline()->projectView()->setClipType(state); } */ } void MainWindow::slotDvdWizard(const QString &url) { // We must stop the monitors since we create a new on in the dvd wizard QPointer w = new DvdWizard(pCore->monitorManager(), url, this); w->exec(); delete w; pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } void MainWindow::slotShowTimeline(bool show) { if (!show) { m_timelineState = saveState(); centralWidget()->setHidden(true); } else { centralWidget()->setHidden(false); restoreState(m_timelineState); } } void MainWindow::loadClipActions() { unplugActionList(QStringLiteral("add_effect")); plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions()); QList clipJobActions = getExtraActions(QStringLiteral("clipjobs")); unplugActionList(QStringLiteral("clip_jobs")); plugActionList(QStringLiteral("clip_jobs"), clipJobActions); QList atcActions = getExtraActions(QStringLiteral("audiotranscoderslist")); unplugActionList(QStringLiteral("audio_transcoders_list")); plugActionList(QStringLiteral("audio_transcoders_list"), atcActions); QList tcActions = getExtraActions(QStringLiteral("transcoderslist")); unplugActionList(QStringLiteral("transcoders_list")); plugActionList(QStringLiteral("transcoders_list"), tcActions); } void MainWindow::loadDockActions() { QList list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions(); // Sort actions QMap sorted; QStringList sortedList; for (QAction *a : list) { sorted.insert(a->text(), a); sortedList << a->text(); } QList orderedList; sortedList.sort(Qt::CaseInsensitive); for (const QString &text : sortedList) { orderedList << sorted.value(text); } unplugActionList(QStringLiteral("dock_actions")); plugActionList(QStringLiteral("dock_actions"), orderedList); } void MainWindow::buildDynamicActions() { KActionCategory *ts = nullptr; if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) { ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs")); delete ts; } ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection()); Mlt::Profile profile; std::unique_ptr filter; for (const QString &stab : {QStringLiteral("vidstab"), QStringLiteral("videostab2"), QStringLiteral("videostab")}) { filter.reset(Mlt::Factory::filter(profile, stab.toUtf8().data())); if ((filter != nullptr) && filter->is_valid()) { QAction *action = new QAction(i18n("Stabilize") + QStringLiteral(" (") + stab + QLatin1Char(')'), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); connect(action, &QAction::triggered, [&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18n("Stabilize clips"), stab); }); break; } } filter.reset(Mlt::Factory::filter(profile, "motion_est")); if (filter) { if (filter->is_valid()) { QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); - connect(action, &QAction::triggered, - [&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18np("Stabilize clip", "Stabilize clips", pCore->bin()->selectedClipsIds().size())); }); + connect(action, &QAction::triggered, [&]() { + pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, + i18np("Stabilize clip", "Stabilize clips", pCore->bin()->selectedClipsIds().size())); + }); } } if (true /* TODO: check if timewarp producer is available */) { QAction *action = new QAction(i18n("Duplicate clip with speed change"), m_extraFactory->actionCollection()); ts->addAction(action->text(), action); connect(action, &QAction::triggered, - [&]() { - pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18n("Change clip speed")); }); + [&]() { pCore->jobManager()->startJob(pCore->bin()->selectedClipsIds(), {}, i18n("Change clip speed")); }); } // TODO refac reimplement analyseclipjob /* QAction *action = new QAction(i18n("Analyse keyframes"), m_extraFactory->actionCollection()); QStringList stabJob(QString::number((int)AbstractClipJob::ANALYSECLIPJOB)); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob); */ kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts); if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist")); delete ts; } if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist")); delete ts; } // TODO refac : reimplement transcode /* ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection()); KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection()); KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QMap profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); QStringList transList; transList << QString::number((int)AbstractClipJob::TRANSCODEJOB); transList << i.value().split(QLatin1Char(';')); auto *a = new QAction(i.key(), m_extraFactory->actionCollection()); a->setData(transList); if (transList.count() > 1) { a->setToolTip(transList.at(1)); } // slottranscode connect(a, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob); if (transList.count() > 3 && transList.at(3) == QLatin1String("audio")) { // This is an audio transcoding action ats->addAction(i.key(), a); } else { ts->addAction(i.key(), a); } } kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts); kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats); */ // Populate View menu with show / hide actions for dock widgets KActionCategory *guiActions = nullptr; if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) { guiActions = kdenliveCategoryMap.take(QStringLiteral("interface")); delete guiActions; } guiActions = new KActionCategory(i18n("Interface"), actionCollection()); QAction *showTimeline = new QAction(i18n("Timeline"), this); showTimeline->setCheckable(true); showTimeline->setChecked(true); connect(showTimeline, &QAction::triggered, this, &MainWindow::slotShowTimeline); guiActions->addAction(showTimeline->text(), showTimeline); actionCollection()->addAction(showTimeline->text(), showTimeline); QList docks = findChildren(); for (int j = 0; j < docks.count(); ++j) { QDockWidget *dock = docks.at(j); QAction *dockInformations = dock->toggleViewAction(); if (!dockInformations) { continue; } dockInformations->setChecked(!dock->isHidden()); guiActions->addAction(dockInformations->text(), dockInformations); } kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions); } QList MainWindow::getExtraActions(const QString &name) { if (!kdenliveCategoryMap.contains(name)) { return QList(); } return kdenliveCategoryMap.value(name)->actions(); } void MainWindow::slotTranscode(const QStringList &urls) { Q_UNUSED(urls) // TODO refac : remove or reimplement transcoding /* QString params; QString desc; if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); QStringList transList = action->data().toStringList(); pCore->bin()->startClipJob(transList); return; } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); return; } qCDebug(KDENLIVE_LOG) << "// TRANSODING FOLDER: " << pCore->bin()->getFolderInfo(); ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getFolderInfo()); connect(d, &ClipTranscode::addClip, this, &MainWindow::slotAddProjectClip); d->show(); */ } void MainWindow::slotTranscodeClip() { // TODO refac : remove or reimplement transcoding /* QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' ')); const QString dialogFilter = i18n("All Supported Files") + QLatin1Char('(') + allExtensions + QStringLiteral(");;") + i18n("All Files") + QStringLiteral("(*)"); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QStringList urls = QFileDialog::getOpenFileNames(this, i18n("Files to transcode"), clipFolder, dialogFilter); if (urls.isEmpty()) { return; } slotTranscode(urls); */ } void MainWindow::slotSetDocumentRenderProfile(const QMap &props) { KdenliveDoc *project = pCore->currentDoc(); bool modified = false; QMapIterator i(props); while (i.hasNext()) { i.next(); if (project->getDocumentProperty(i.key()) == i.value()) { continue; } project->setDocumentProperty(i.key(), i.value()); modified = true; } if (modified) { project->setModified(); } } void MainWindow::slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile, QString scriptPath) { KdenliveDoc *project = pCore->currentDoc(); if (m_renderWidget == nullptr) { return; } QString playlistPath; QString mltSuffix(QStringLiteral(".mlt")); QList playlistPaths; QList trackNames; int tracksCount = 1; bool stemExport = m_renderWidget->isStemAudioExportEnabled(); if (scriptExport) { // QString scriptsFolder = project->projectFolder().path(QUrl::AddTrailingSlash) + "scripts/"; if (scriptPath.isEmpty()) { QString path = m_renderWidget->getFreeScriptName(project->url()); QPointer getUrl = new KUrlRequesterDialog(QUrl::fromLocalFile(path), i18n("Create Render Script"), this); getUrl->urlRequester()->setMode(KFile::File); if (getUrl->exec() == QDialog::Rejected) { delete getUrl; return; } scriptPath = getUrl->selectedUrl().toLocalFile(); delete getUrl; } QFile f(scriptPath); if (f.exists()) { if (KMessageBox::warningYesNo(this, i18n("Script file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) { return; } } playlistPath = scriptPath; } else { QTemporaryFile temp(QDir::tempPath() + QStringLiteral("/kdenlive_rendering_XXXXXX.mlt")); temp.setAutoRemove(false); temp.open(); playlistPath = temp.fileName(); } int in = 0; int out; if (zoneOnly) { in = getMainTimeline()->controller()->zoneIn(); out = getMainTimeline()->controller()->zoneOut(); } else { out = getMainTimeline()->controller()->duration() - TimelineModel::seekDuration - 2; } QString playlistContent = pCore->projectManager()->projectSceneList(project->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile()); if (!chapterFile.isEmpty()) { QDomDocument doc; QDomElement chapters = doc.createElement(QStringLiteral("chapters")); chapters.setAttribute(QStringLiteral("fps"), pCore->getCurrentFps()); doc.appendChild(chapters); const QList guidesList = project->getGuideModel()->getAllMarkers(); for (int i = 0; i < guidesList.count(); i++) { CommentedTime c = guidesList.at(i); int time = c.time().frames(pCore->getCurrentFps()); if (time >= in && time < out) { if (zoneOnly) { time = time - in; } } QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.appendChild(chapter); chapter.setAttribute(QStringLiteral("title"), c.comment()); chapter.setAttribute(QStringLiteral("time"), time); } if (!chapters.childNodes().isEmpty()) { if (!project->getGuideModel()->hasMarker(out)) { // Always insert a guide in pos 0 QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.insertBefore(chapter, QDomNode()); chapter.setAttribute(QStringLiteral("title"), i18nc("the first in a list of chapters", "Start")); chapter.setAttribute(QStringLiteral("time"), QStringLiteral("0")); } // save chapters file QFile file(chapterFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qCWarning(KDENLIVE_LOG) << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } else { file.write(doc.toString().toUtf8()); if (file.error() != QFile::NoError) { qCWarning(KDENLIVE_LOG) << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } file.close(); } } } // check if audio export is selected bool exportAudio; if (m_renderWidget->automaticAudioExport()) { // TODO check if projact contains audio // exportAudio = pCore->projectManager()->currentTimeline()->checkProjectAudio(); exportAudio = true; } else { exportAudio = m_renderWidget->selectedAudioExport(); } // Set playlist audio volume to 100% QDomDocument doc; doc.setContent(playlistContent); QDomElement tractor = doc.documentElement().firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } // Add autoclose to playlists. QDomNodeList playlists = doc.elementsByTagName(QStringLiteral("playlist")); for (int i = 0; i < playlists.length(); ++i) { playlists.item(i).toElement().setAttribute(QStringLiteral("autoclose"), 1); } // Do we want proxy rendering if (project->useProxy() && !m_renderWidget->proxyRendering()) { QString root = doc.documentElement().attribute(QStringLiteral("root")); if (!root.isEmpty() && !root.endsWith(QLatin1Char('/'))) { root.append(QLatin1Char('/')); } // replace proxy clips with originals QMap proxies = pCore->projectItemModel()->getProxies(pCore->currentDoc()->documentRoot()); QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer")); QString producerResource; QString producerService; QString suffix; QString prefix; for (int n = 0; n < producers.length(); ++n) { QDomElement e = producers.item(n).toElement(); producerResource = EffectsList::property(e, QStringLiteral("resource")); producerService = EffectsList::property(e, QStringLiteral("mlt_service")); if (producerResource.isEmpty() || producerService == QLatin1String("color")) { continue; } if (producerService == QLatin1String("timewarp")) { // slowmotion producer prefix = producerResource.section(QLatin1Char(':'), 0, 0) + QLatin1Char(':'); producerResource = producerResource.section(QLatin1Char(':'), 1); } else { prefix.clear(); } if (producerService == QLatin1String("framebuffer")) { // slowmotion producer suffix = QLatin1Char('?') + producerResource.section(QLatin1Char('?'), 1); producerResource = producerResource.section(QLatin1Char('?'), 0, 0); } else { suffix.clear(); } if (!producerResource.isEmpty()) { if (QFileInfo(producerResource).isRelative()) { producerResource.prepend(root); } if (proxies.contains(producerResource)) { QString replacementResource = proxies.value(producerResource); EffectsList::setProperty(e, QStringLiteral("resource"), prefix + replacementResource + suffix); if (producerService == QLatin1String("timewarp")) { EffectsList::setProperty(e, QStringLiteral("warp_resource"), replacementResource); } // We need to delete the "aspect_ratio" property because proxy clips // sometimes have different ratio than original clips EffectsList::removeProperty(e, QStringLiteral("aspect_ratio")); EffectsList::removeMetaProperties(e); } } } } QList docList; // check which audio tracks have to be exported if (stemExport) { // TODO refac /* //TODO port to new timeline model Timeline *ct = pCore->projectManager()->currentTimeline(); int allTracksCount = ct->tracksCount(); // reset tracks count (tracks to be rendered) tracksCount = 0; // begin with track 1 (track zero is a hidden black track) for (int i = 1; i < allTracksCount; i++) { Track *track = ct->track(i); // add only tracks to render list that are not muted and have audio if ((track != nullptr) && !track->info().isMute && track->hasAudio()) { QDomDocument docCopy = doc.cloneNode(true).toDocument(); QString trackName = track->info().trackName; // save track name trackNames << trackName; qCDebug(KDENLIVE_LOG) << "Track-Name: " << trackName; // create stem export doc content QDomNodeList tracks = docCopy.elementsByTagName(QStringLiteral("track")); for (int j = 0; j < allTracksCount; j++) { if (j != i) { // mute other tracks tracks.at(j).toElement().setAttribute(QStringLiteral("hide"), QStringLiteral("both")); } } docList << docCopy; tracksCount++; } } */ } else { docList << doc; } // create full playlistPaths for (int i = 0; i < tracksCount; i++) { QString plPath(playlistPath); // add track number to path name if (stemExport) { plPath = plPath + QLatin1Char('_') + QString(trackNames.at(i)).replace(QLatin1Char(' '), QLatin1Char('_')); } // add mlt suffix if (!plPath.endsWith(mltSuffix)) { plPath += mltSuffix; } playlistPaths << plPath; qCDebug(KDENLIVE_LOG) << "playlistPath: " << plPath << endl; // Do save scenelist QFile file(plPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); return; } file.write(docList.at(i).toString().toUtf8()); if (file.error() != QFile::NoError) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); file.close(); return; } file.close(); } m_renderWidget->slotExport(scriptExport, in, out, project->metadata(), playlistPaths, trackNames, scriptPath, exportAudio); } void MainWindow::slotUpdateTimecodeFormat(int ix) { KdenliveSettings::setFrametimecode(ix == 1); m_clipMonitor->updateTimecodeFormat(); m_projectMonitor->updateTimecodeFormat(); // TODO refac: reimplement ? // m_effectStack->transitionConfig()->updateTimecodeFormat(); // m_effectStack->updateTimecodeFormat(); pCore->bin()->updateTimecodeFormat(); getMainTimeline()->controller()->frameFormatChanged(); m_timeFormatButton->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } void MainWindow::slotRemoveFocus() { getMainTimeline()->setFocus(); } void MainWindow::slotShutdown() { pCore->currentDoc()->setModified(false); // Call shutdown QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface(); if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver"))) { QDBusInterface smserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface")); smserver.call(QStringLiteral("logout"), 1, 2, 2); } else if ((interface != nullptr) && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) { QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager")); smserver.call(QStringLiteral("Shutdown")); } } void MainWindow::slotSwitchMonitors() { pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive()); if (m_projectMonitor->isActive()) { getMainTimeline()->setFocus(); } else { pCore->bin()->focusBinView(); } } void MainWindow::slotSwitchMonitorOverlay(QAction *action) { if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) { m_clipMonitor->switchMonitorInfo(action->data().toInt()); } else { m_projectMonitor->switchMonitorInfo(action->data().toInt()); } } void MainWindow::slotSwitchDropFrames(bool drop) { m_clipMonitor->switchDropFrames(drop); m_projectMonitor->switchDropFrames(drop); } void MainWindow::slotSetMonitorGamma(int gamma) { KdenliveSettings::setMonitor_gamma(gamma); m_clipMonitor->updateMonitorGamma(); m_projectMonitor->updateMonitorGamma(); } void MainWindow::slotInsertZoneToTree() { if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == nullptr) { return; } QPoint info = m_clipMonitor->getZoneInfo(); QString id; pCore->projectItemModel()->requestAddBinSubClip(id, info.x(), info.y(), m_clipMonitor->activeClipId()); } void MainWindow::slotInsertZoneToTimeline() { QPoint info = m_clipMonitor->getZoneInfo(); QString clipData = QString("%1#%2#%3").arg(m_clipMonitor->activeClipId()).arg(info.x()).arg(info.y()); int cid = getMainTimeline()->controller()->insertClip(-1, -1, clipData, true, true, true); if (cid == -1) { pCore->displayMessage(i18n("Cannot insert clip at requested position"), InformationMessage); } else { getMainTimeline()->controller()->seekToClip(cid, true); } } void MainWindow::slotMonitorRequestRenderFrame(bool request) { if (request) { m_projectMonitor->sendFrameForAnalysis(true); return; } for (int i = 0; i < m_gfxScopesList.count(); ++i) { if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() && static_cast(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) { request = true; break; } } #ifdef DEBUG_MAINW qCDebug(KDENLIVE_LOG) << "Any scope accepting new frames? " << request; #endif if (!request) { m_projectMonitor->sendFrameForAnalysis(false); } } void MainWindow::slotUpdateProxySettings() { KdenliveDoc *project = pCore->currentDoc(); if (m_renderWidget) { m_renderWidget->updateProxyConfig(project->useProxy()); } pCore->bin()->refreshProxySettings(); } void MainWindow::slotArchiveProject() { // TODO refac /* QList> list = pCore->binController()->getControllerList(); KdenliveDoc *doc = pCore->currentDoc(); pCore->binController()->saveDocumentProperties(pCore->projectManager()->currentTimeline()->documentProperties(), doc->metadata(), doc->getGuideModel()); QDomDocument xmlDoc = doc->xmlSceneList(m_projectMonitor->sceneList(doc->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile())); QScopedPointer d( new ArchiveWidget(doc->url().fileName(), xmlDoc, list, pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), this)); if (d->exec() != 0) { m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage); } */ } void MainWindow::slotDownloadResources() { QString currentFolder; if (pCore->currentDoc()) { currentFolder = pCore->currentDoc()->projectDataFolder(); } else { currentFolder = KdenliveSettings::defaultprojectfolder(); } auto *d = new ResourceWidget(currentFolder); connect(d, &ResourceWidget::addClip, this, &MainWindow::slotAddProjectClip); d->show(); } void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes) { Q_UNUSED(keyframes) Q_UNUSED(tag) if (type == AVWidget) { // This data should be sent to the effect stack // TODO REFAC reimplement // m_effectStack->setKeyframes(tag, data); } else if (type == TransitionWidget) { // This data should be sent to the transition stack // TODO REFAC reimplement // m_effectStack->transitionConfig()->setKeyframes(tag, data); } else { // Error } } void MainWindow::slotAlignPlayheadToMousePos() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); getMainTimeline()->controller()->seekToMouse(); } void MainWindow::triggerKey(QKeyEvent *ev) { // Hack: The QQuickWindow that displays fullscreen monitor does not integrate quith QActions. // so on keypress events we parse keys and check for shortcuts in all existing actions QKeySequence seq; // Remove the Num modifier or some shortcuts like "*" will not work if (ev->modifiers() != Qt::KeypadModifier) { seq = QKeySequence(ev->key() + static_cast(ev->modifiers())); } else { seq = QKeySequence(ev->key()); } QList collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); for (QAction *tempAction : coll->actions()) { if (tempAction->shortcuts().contains(seq)) { // Trigger action tempAction->trigger(); ev->accept(); return; } } } } QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area) { QDockWidget *dockWidget = new QDockWidget(title, this); dockWidget->setObjectName(objectName); dockWidget->setWidget(widget); addDockWidget(area, dockWidget); connect(dockWidget, &QDockWidget::dockLocationChanged, this, &MainWindow::updateDockTitleBars); connect(dockWidget, &QDockWidget::topLevelChanged, this, &MainWindow::updateDockTitleBars); return dockWidget; } void MainWindow::slotUpdateMonitorOverlays(int id, int code) { QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (!monitorOverlay) { return; } QList actions = monitorOverlay->actions(); for (QAction *ac : actions) { int mid = ac->data().toInt(); if (mid == 0x010) { ac->setEnabled(id == Kdenlive::ClipMonitor); } ac->setChecked(code & mid); } } void MainWindow::slotChangeStyle(QAction *a) { QString style = a->data().toString(); KdenliveSettings::setWidgetstyle(style); doChangeStyle(); } void MainWindow::doChangeStyle() { QString newStyle = KdenliveSettings::widgetstyle(); if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) { newStyle = defaultStyle("Breeze"); } QApplication::setStyle(QStyleFactory::create(newStyle)); // Changing widget style resets color theme, so update color theme again ThemeManager::instance()->slotChangePalette(); } bool MainWindow::isTabbedWith(QDockWidget *widget, const QString &otherWidget) { QList tabbed = tabifiedDockWidgets(widget); for (int i = 0; i < tabbed.count(); i++) { if (tabbed.at(i)->objectName() == otherWidget) { return true; } } return false; } void MainWindow::updateDockTitleBars(bool isTopLevel) { if (!KdenliveSettings::showtitlebars() || !isTopLevel) { return; } QList docks = pCore->window()->findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget *dock = docks.at(i); QWidget *bar = dock->titleBarWidget(); if (dock->isFloating()) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } QList docked = pCore->window()->tabifiedDockWidgets(dock); if (docked.isEmpty()) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } bool hasVisibleDockSibling = false; for (QDockWidget *sub : docked) { if (sub->toggleViewAction()->isChecked()) { // we have another docked widget, so tabs are visible and can be used instead of title bars hasVisibleDockSibling = true; break; } } if (!hasVisibleDockSibling) { if (bar) { dock->setTitleBarWidget(nullptr); delete bar; } continue; } if (!bar) { dock->setTitleBarWidget(new QWidget); } } } void MainWindow::slotToggleAutoPreview(bool enable) { Q_UNUSED(enable) // TODO refac /* KdenliveSettings::setAutopreview(enable); if (enable && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->startPreviewRender(); } */ } void MainWindow::configureToolbars() { // Since our timeline toolbar is a non-standard toolbar (as it is docked in a custom widget, not // in a QToolBarDockArea, we have to hack KXmlGuiWindow to avoid a crash when saving toolbar config. // This is why we hijack the configureToolbars() and temporarily move the toolbar to a standard location QVBoxLayout *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout(); ctnLay->removeWidget(m_timelineToolBar); addToolBar(Qt::BottomToolBarArea, m_timelineToolBar); auto *toolBarEditor = new KEditToolBar(guiFactory(), this); toolBarEditor->setAttribute(Qt::WA_DeleteOnClose); connect(toolBarEditor, SIGNAL(newToolBarConfig()), SLOT(saveNewToolbarConfig())); connect(toolBarEditor, &QDialog::finished, this, &MainWindow::rebuildTimlineToolBar); toolBarEditor->show(); } void MainWindow::rebuildTimlineToolBar() { // Timeline toolbar settings changed, we can now re-add our toolbar to custom location m_timelineToolBar = toolBar(QStringLiteral("timelineToolBar")); removeToolBar(m_timelineToolBar); m_timelineToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); QVBoxLayout *ctnLay = (QVBoxLayout *)m_timelineToolBarContainer->layout(); if (ctnLay) { ctnLay->insertWidget(0, m_timelineToolBar); } m_timelineToolBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_timelineToolBar, &QWidget::customContextMenuRequested, this, &MainWindow::showTimelineToolbarMenu); m_timelineToolBar->setVisible(true); } void MainWindow::showTimelineToolbarMenu(const QPoint &pos) { QMenu menu; menu.addAction(actionCollection()->action(KStandardAction::name(KStandardAction::ConfigureToolbars))); QMenu *contextSize = new QMenu(i18n("Icon Size")); menu.addMenu(contextSize); auto *sizeGroup = new QActionGroup(contextSize); int currentSize = m_timelineToolBar->iconSize().width(); QAction *a = new QAction(i18nc("@item:inmenu Icon size", "Default"), contextSize); a->setData(m_timelineToolBar->iconSizeDefault()); a->setCheckable(true); if (m_timelineToolBar->iconSizeDefault() == currentSize) { a->setChecked(true); } a->setActionGroup(sizeGroup); contextSize->addAction(a); KIconTheme *theme = KIconLoader::global()->theme(); QList avSizes; if (theme) { avSizes = theme->querySizes(KIconLoader::Toolbar); } qSort(avSizes); if (avSizes.count() < 10) { // Fixed or threshold type icons Q_FOREACH (int it, avSizes) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map auto *sizeAction = new QAction(text, contextSize); sizeAction->setData(it); sizeAction->setCheckable(true); sizeAction->setActionGroup(sizeGroup); if (it == currentSize) { sizeAction->setChecked(true); } contextSize->addAction(sizeAction); } } else { // Scalable icons. const int progression[] = {16, 22, 32, 48, 64, 96, 128, 192, 256}; for (uint i = 0; i < 9; i++) { Q_FOREACH (int it, avSizes) { if (it >= progression[i]) { QString text; if (it < 19) { text = i18n("Small (%1x%2)", it, it); } else if (it < 25) { text = i18n("Medium (%1x%2)", it, it); } else if (it < 35) { text = i18n("Large (%1x%2)", it, it); } else { text = i18n("Huge (%1x%2)", it, it); } // save the size in the contextIconSizes map auto *sizeAction = new QAction(text, contextSize); sizeAction->setData(it); sizeAction->setCheckable(true); sizeAction->setActionGroup(sizeGroup); if (it == currentSize) { sizeAction->setChecked(true); } contextSize->addAction(sizeAction); break; } } } } connect(contextSize, &QMenu::triggered, this, &MainWindow::setTimelineToolbarIconSize); menu.exec(m_timelineToolBar->mapToGlobal(pos)); contextSize->deleteLater(); } void MainWindow::setTimelineToolbarIconSize(QAction *a) { if (!a) { return; } int size = a->data().toInt(); m_timelineToolBar->setIconDimensions(size); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup mainConfig(config, QStringLiteral("MainWindow")); KConfigGroup tbGroup(&mainConfig, QStringLiteral("Toolbar timelineToolBar")); m_timelineToolBar->saveSettings(tbGroup); } void MainWindow::slotManageCache() { QDialog d(this); d.setWindowTitle(i18n("Manage Cache Data")); auto *lay = new QVBoxLayout; TemporaryData tmp(pCore->currentDoc(), false, this); connect(&tmp, &TemporaryData::disableProxies, this, &MainWindow::slotDisableProxies); // TODO refac /* connect(&tmp, SIGNAL(disablePreview()), pCore->projectManager()->currentTimeline(), SLOT(invalidateRange())); */ QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject); lay->addWidget(&tmp); lay->addWidget(buttonBox); d.setLayout(lay); d.exec(); } void MainWindow::slotUpdateCompositing(QAction *compose) { int mode = compose->data().toInt(); getMainTimeline()->controller()->switchCompositing(mode); if (m_renderWidget) { m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString()); } } void MainWindow::slotUpdateCompositeAction(int mode) { QList actions = m_compositeAction->actions(); for (int i = 0; i < actions.count(); i++) { if (actions.at(i)->data().toInt() == mode) { m_compositeAction->setCurrentAction(actions.at(i)); break; } } if (m_renderWidget) { m_renderWidget->errorMessage(RenderWidget::CompositeError, mode == 1 ? i18n("Rendering using low quality track compositing") : QString()); } } void MainWindow::showMenuBar(bool show) { if (!show) { KMessageBox::information(this, i18n("This will hide the menu bar completely. You can show it again by typing Ctrl+M."), i18n("Hide menu bar"), QStringLiteral("show-menubar-warning")); } menuBar()->setVisible(show); } void MainWindow::forceIconSet(bool force) { KdenliveSettings::setForce_breeze(force); if (force) { // Check current color theme QColor background = qApp->palette().window().color(); bool useDarkIcons = background.value() < 100; KdenliveSettings::setUse_dark_breeze(useDarkIcons); } if (KMessageBox::warningContinueCancel(this, i18n("Kdenlive needs to be restarted to apply icon theme change. Restart now ?")) == KMessageBox::Continue) { slotRestart(); } } void MainWindow::slotSwitchTrimMode() { // TODO refac /* if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->switchTrimMode(); } */ } -void MainWindow::setTrimMode(const QString &mode) -{ +void MainWindow::setTrimMode(const QString &mode){ Q_UNUSED(mode) // TODO refac /* if (pCore->projectManager()->currentTimeline()) { m_trimLabel->setText(mode); m_trimLabel->setVisible(!mode.isEmpty()); } */ } TimelineWidget *MainWindow::getMainTimeline() const { return m_timelineTabs->getMainTimeline(); } TimelineWidget *MainWindow::getCurrentTimeline() const { return m_timelineTabs->getCurrentTimeline(); } void MainWindow::resetTimelineTracks() { TimelineWidget *current = getCurrentTimeline(); if (current) { current->controller()->resetTrackHeight(); } } void MainWindow::slotChangeSpeed(int speed) { ObjectId owner = m_assetPanel->effectStackOwner(); // TODO: manage bin clips / tracks if (owner.first == ObjectType::TimelineClip) { getCurrentTimeline()->controller()->changeItemSpeed(owner.second, speed); } } void MainWindow::slotSwitchTimelineZone(bool toggled) { KdenliveSettings::setUseTimelineZoneToEdit(toggled); getCurrentTimeline()->controller()->useRulerChanged(); } #ifdef DEBUG_MAINW #undef DEBUG_MAINW #endif diff --git a/src/mainwindow.h b/src/mainwindow.h index 2ff97b12f..52ec91c22 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,488 +1,488 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "bin/bin.h" #include "definitions.h" #include "dvdwizard/dvdwizard.h" #include "effectslist/effectslist.h" #include "gentime.h" #include "kdenlive_debug.h" #include "kdenlivecore_export.h" #include "statusbarmessagelabel.h" class AssetPanel; class AudioGraphSpectrum; class EffectStackView2; class EffectListWidget; class TransitionListWidget; class EffectStackView; class EffectsListView; class KIconLoader; class KdenliveDoc; class Monitor; class Render; class RenderWidget; class TimelineTabs; class TimelineWidget; class Transition; class /*KDENLIVECORE_EXPORT*/ MainWindow : public KXmlGuiWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); /** @brief Initialises the main window. * @param MltPath (optional) path to MLT environment * @param Url (optional) file to open * @param clipsToLoad (optional) a comma separated list of clips to import in project * * If Url is present, it will be opened, otherwhise, if openlastproject is * set, latest project will be opened. If no file is open after trying this, * a default new file will be created. */ void init(); virtual ~MainWindow(); static EffectsList videoEffects; static EffectsList audioEffects; static EffectsList customEffects; static EffectsList transitions; /** @brief Cache for luma files thumbnails. */ static QMap m_lumacache; static QMap m_lumaFiles; /** @brief Adds an action to the action collection and stores the name. */ void addAction(const QString &name, QAction *action); /** @brief Adds an action to the action collection and stores the name. */ QAction *addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon = QIcon(), const QKeySequence &shortcut = QKeySequence()); /** * @brief Adds a new dock widget to this window. * @param title title of the dock widget * @param objectName objectName of the dock widget (required for storing layouts) * @param widget widget to use in the dock * @param area area to which the dock should be added to * @returns the created dock widget */ QDockWidget *addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area = Qt::TopDockWidgetArea); QUndoGroup *m_commandStack; QUndoView *m_undoView; /** @brief holds info about whether movit is available on this system */ bool m_gpuAllowed; int m_exitCode; QMap kdenliveCategoryMap; QList getExtraActions(const QString &name); /** @brief Returns true if docked widget is tabbed with another widget from its object name */ bool isTabbedWith(QDockWidget *widget, const QString &otherWidget); /** @brief Returns a ptr to the main timeline widget of the project */ TimelineWidget *getMainTimeline() const; /* @brief Returns a pointer to the current timeline */ TimelineWidget *getCurrentTimeline() const; protected: /** @brief Closes the window. * @return false if the user presses "Cancel" on a confirmation dialog or * the operation requested (starting waiting jobs or saving file) fails, * true otherwise */ bool queryClose() override; void closeEvent(QCloseEvent *) override; /** @brief Reports a message in the status bar when an error occurs. */ void customEvent(QEvent *e) override; /** @brief Stops the active monitor when the window gets hidden. */ void hideEvent(QHideEvent *e) override; /** @brief Saves the file and the window properties when saving the session. */ void saveProperties(KConfigGroup &config) override; /** @brief Restores the window and the file when a session is loaded. */ void readProperties(const KConfigGroup &config) override; void saveNewToolbarConfig() override; private: /** @brief Sets up all the actions and attaches them to the collection. */ void setupActions(); KColorSchemeManager *m_colorschemes; QDockWidget *m_projectBinDock; QDockWidget *m_effectListDock; EffectsListView *m_effectList; QDockWidget *m_transitionListDock; EffectsListView *m_transitionList; TransitionListWidget *m_transitionList2; EffectListWidget *m_effectList2; AssetPanel *m_assetPanel; QDockWidget *m_effectStackDock; QDockWidget *m_clipMonitorDock; Monitor *m_clipMonitor; QDockWidget *m_projectMonitorDock; Monitor *m_projectMonitor; AudioGraphSpectrum *m_audioSpectrum; QDockWidget *m_undoViewDock; KSelectAction *m_timeFormatButton; KSelectAction *m_compositeAction; TimelineTabs *m_timelineTabs; /** This list holds all the scopes used in Kdenlive, allowing to manage some global settings */ QList m_gfxScopesList; KActionCategory *m_effectActions; KActionCategory *m_transitionActions; QMenu *m_effectsMenu; QMenu *m_transitionsMenu; QMenu *m_timelineContextMenu; QMenu *m_timelineContextClipMenu; QMenu *m_timelineContextTransitionMenu; /** Action names that can be used in the slotDoAction() slot, with their i18n() names */ QStringList m_actionNames; /** @brief Shortcut to remove the focus from any element. * * It allows to get out of e.g. text input fields and to press another * shortcut. */ QShortcut *m_shortcutRemoveFocus; RenderWidget *m_renderWidget; StatusBarMessageLabel *m_messageLabel; QList m_transitions; QAction *m_buttonAudioThumbs; QAction *m_buttonVideoThumbs; QAction *m_buttonShowMarkers; QAction *m_buttonFitZoom; QAction *m_buttonAutomaticTransition; QAction *m_normalEditTool; QAction *m_overwriteEditTool; QAction *m_insertEditTool; QAction *m_buttonSelectTool; QAction *m_buttonRazorTool; QAction *m_buttonSpacerTool; QAction *m_buttonSnap; QAction *m_saveAction; QSlider *m_zoomSlider; QAction *m_zoomIn; QAction *m_zoomOut; QAction *m_loopZone; QAction *m_playZone; QAction *m_loopClip; QAction *m_proxyClip; QActionGroup *m_clipTypeGroup; QString m_theme; KIconLoader *m_iconLoader; KToolBar *m_timelineToolBar; QWidget *m_timelineToolBarContainer; QLabel *m_trimLabel; /** @brief initialize startup values, return true if first run. */ bool readOptions(); void saveOptions(); bool event(QEvent *e) override; void loadGenerators(); /** @brief Instantiates a "Get Hot New Stuff" dialog. * @param configFile configuration file for KNewStuff * @return number of installed items */ int getNewStuff(const QString &configFile = QString()); QStringList m_pluginFileNames; QByteArray m_timelineState; void buildDynamicActions(); void loadClipActions(); QTime m_timer; KXMLGUIClient *m_extraFactory; bool m_themeInitialized; bool m_isDarkTheme; QListWidget *m_effectBasket; /** @brief Update widget style. */ void doChangeStyle(); void updateActionsToolTip(); public slots: void slotGotProgressInfo(const QString &message, int progress, MessageType type = DefaultMessage); void slotReloadEffects(); Q_SCRIPTABLE void setRenderingProgress(const QString &url, int progress); Q_SCRIPTABLE void setRenderingFinished(const QString &url, int status, const QString &error); Q_SCRIPTABLE void addProjectClip(const QString &url); Q_SCRIPTABLE void addTimelineClip(const QString &url); Q_SCRIPTABLE void addEffect(const QString &effectName); Q_SCRIPTABLE void scriptRender(const QString &url); Q_NOREPLY void exitApp(); void slotSwitchVideoThumbs(); void slotSwitchAudioThumbs(); void slotPreferences(int page = -1, int option = -1); void connectDocument(); /** @brief Reload project profile in config dialog if changed. */ void slotRefreshProfiles(); void updateDockTitleBars(bool isTopLevel = true); void configureToolbars() override; /** @brief Decreases the timeline zoom level by 1. */ void slotZoomIn(bool zoomOnMouse = false); /** @brief Increases the timeline zoom level by 1. */ void slotZoomOut(bool zoomOnMouse = false); private slots: /** @brief Shows the shortcut dialog. */ void slotEditKeys(); void loadDockActions(); /** @brief Reflects setting changes to the GUI. */ void updateConfiguration(); void slotConnectMonitors(); void slotUpdateMousePosition(int pos); void slotUpdateProjectDuration(int pos); void slotAddEffect(const QDomElement &effect); void slotEditProjectSettings(); void slotSwitchTimelineZone(bool toggled); void slotSwitchMarkersComments(); void slotSwitchSnap(); void slotSwitchAutomaticTransition(); void slotRenderProject(); void slotStopRenderProject(); void slotFullScreen(); /** @brief if modified is true adds "modified" to the caption and enables the save button. - * (triggered by KdenliveDoc::setModified()) */ + * (triggered by KdenliveDoc::setModified()) */ void slotUpdateDocumentState(bool modified); /** @brief Sets the timeline zoom slider to @param value. - * - * Also disables zoomIn and zoomOut actions if they cannot be used at the moment. */ + * + * Also disables zoomIn and zoomOut actions if they cannot be used at the moment. */ void slotSetZoom(int value, bool zoomOnMouse = false); /** @brief Makes the timeline zoom level fit the timeline content. */ void slotFitZoom(); /** @brief Updates the zoom slider tooltip to fit @param zoomlevel. */ void slotUpdateZoomSliderToolTip(int zoomlevel); /** @brief Displays the zoom slider tooltip. - * @param zoomlevel (optional) The zoom level to show in the tooltip. - * - * Adopted from Dolphin (src/statusbar/dolphinstatusbar.cpp) */ + * @param zoomlevel (optional) The zoom level to show in the tooltip. + * + * Adopted from Dolphin (src/statusbar/dolphinstatusbar.cpp) */ void slotShowZoomSliderToolTip(int zoomlevel = -1); /** @brief Deletes item in timeline, project tree or effect stack depending on focus. */ void slotDeleteItem(); void slotAddClipMarker(); void slotDeleteClipMarker(bool allowGuideDeletion = false); void slotDeleteAllClipMarkers(); void slotEditClipMarker(); /** @brief Adds marker or auide at the current position without showing the marker dialog. * * Adds a marker if clip monitor is active, otherwise a guide. * The comment is set to the current position (therefore not dialog). * This can be useful to mark something during playback. */ void slotAddMarkerGuideQuickly(); void slotCutTimelineClip(); void slotInsertClipOverwrite(); void slotInsertClipInsert(); void slotExtractZone(); void slotLiftZone(); void slotPreviewRender(); void slotStopPreviewRender(); void slotDefinePreviewRender(); void slotRemovePreviewRender(); void slotClearPreviewRender(); void slotSelectTimelineClip(); void slotSelectTimelineTransition(); void slotDeselectTimelineClip(); void slotDeselectTimelineTransition(); void slotSelectAddTimelineClip(); void slotSelectAddTimelineTransition(); void slotAddVideoEffect(QAction *result); void slotAddTransition(QAction *result); void slotAddProjectClip(const QUrl &url, const QStringList &folderInfo); void slotAddProjectClipList(const QList &urls); void slotChangeTool(QAction *action); void slotChangeEdit(QAction *action); void slotSetTool(ProjectTool tool); void slotSnapForward(); void slotSnapRewind(); void slotClipStart(); void slotClipEnd(); void slotSelectClipInTimeline(); void slotClipInTimeline(const QString &clipId, QList ids); void slotInsertSpace(); void slotRemoveSpace(); void slotRemoveAllSpace(); void slotAddGuide(); void slotEditGuide(); void slotDeleteGuide(); void slotDeleteAllGuides(); void slotGuidesUpdated(); void slotCopy(); void slotPaste(); void slotPasteEffects(); void slotResizeItemStart(); void slotResizeItemEnd(); void configureNotifications(); void slotInsertTrack(); void slotDeleteTrack(); /** @brief Shows the configure tracks dialog and updates transitions afterwards. */ void slotConfigTrack(); /** @brief Select all clips in active track. */ void slotSelectTrack(); /** @brief Select all clips in timeline. */ void slotSelectAllTracks(); void slotUnselectAllTracks(); void slotGetNewLumaStuff(); void slotGetNewKeyboardStuff(); void slotGetNewTitleStuff(); void slotGetNewRenderStuff(); void slotAutoTransition(); void slotRunWizard(); void slotZoneMoved(int start, int end); void slotDvdWizard(const QString &url = QString()); void slotGroupClips(); void slotUnGroupClips(); void slotEditItemDuration(); void slotClipInProjectTree(); // void slotClipToProjectTree(); void slotSplitAudio(); void slotSetAudioAlignReference(); void slotAlignAudio(); void slotUpdateClipType(QAction *action); void slotShowTimeline(bool show); void slotTranscode(const QStringList &urls = QStringList()); void slotTranscodeClip(); /** @brief Archive project: creates a copy of the project file with all clips in a new folder. */ void slotArchiveProject(); void slotSetDocumentRenderProfile(const QMap &props); void slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile, QString scriptPath = QString()); /** @brief Switches between displaying frames or timecode. - * @param ix 0 = display timecode, 1 = display frames. */ + * @param ix 0 = display timecode, 1 = display frames. */ void slotUpdateTimecodeFormat(int ix); /** @brief Removes the focus of anything. */ void slotRemoveFocus(); void slotCleanProject(); void slotShutdown(); void slotSwitchMonitors(); void slotSwitchMonitorOverlay(QAction *); void slotSwitchDropFrames(bool drop); void slotSetMonitorGamma(int gamma); void slotCheckRenderStatus(); void slotInsertZoneToTree(); void slotInsertZoneToTimeline(); /** @brief The monitor informs that it needs (or not) to have frames sent by the renderer. */ void slotMonitorRequestRenderFrame(bool request); /** @brief Update project because the use of proxy clips was enabled / disabled. */ void slotUpdateProxySettings(); /** @brief Disable proxies for this project. */ void slotDisableProxies(); /** @brief Open the online services search dialog. */ void slotDownloadResources(); /** @brief Process keyframe data sent from a clip to effect / transition stack. */ void slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString &keyframes); /** @brief Move playhead to mouse curser position if defined key is pressed */ void slotAlignPlayheadToMousePos(); void slotThemeChanged(const QString &); void slotReloadTheme(); /** @brief Close Kdenlive and try to restart it */ void slotRestart(); void triggerKey(QKeyEvent *ev); /** @brief Update monitor overlay actions on monitor switch */ void slotUpdateMonitorOverlays(int id, int code); /** @brief Update widget style */ void slotChangeStyle(QAction *a); /** @brief Create temporary top track to preview an effect */ void createSplitOverlay(Mlt::Filter *filter); void removeSplitOverlay(); /** @brief Create a generator's setup dialog */ void buildGenerator(QAction *action); void slotCheckTabPosition(); /** @brief Toggle automatic timeline preview on/off */ void slotToggleAutoPreview(bool enable); /** @brief Rebuild/reload timeline toolbar. */ void rebuildTimlineToolBar(); void showTimelineToolbarMenu(const QPoint &pos); /** @brief Open Cached Data management dialog. */ void slotManageCache(); void showMenuBar(bool show); /** @brief Change forced icon theme setting (asks for app restart). */ void forceIconSet(bool force); /** @brief Toggle current project's compositing mode. */ void slotUpdateCompositing(QAction *compose); /** @brief Update compositing action to display current project setting. */ void slotUpdateCompositeAction(int mode); /** @brief Cycle through the different timeline trim modes. */ void slotSwitchTrimMode(); void setTrimMode(const QString &mode); /** @brief Set timeline toolbar icon size. */ void setTimelineToolbarIconSize(QAction *a); void slotChangeSpeed(int speed); void updateAction(); /** @brief Request adjust of timeline track height */ void resetTimelineTracks(); signals: Q_SCRIPTABLE void abortRenderJob(const QString &url); void configurationChanged(); void GUISetupDone(); void reloadTheme(); void setPreviewProgress(int); void setRenderProgress(int); void displayMessage(const QString &, MessageType, int); /** @brief Project profile changed, update render widget accordingly. */ void updateRenderWidgetProfile(); /** @brief Clear asset view if itemId is displayed. */ void clearAssetPanel(int itemId = -1); void adjustAssetPanelRange(int itemId, int in, int out); }; #endif diff --git a/src/mltconnection.h b/src/mltconnection.h index 11cb5f7cf..85b183693 100644 --- a/src/mltconnection.h +++ b/src/mltconnection.h @@ -1,65 +1,65 @@ /* Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #ifndef MLTCONNECTION_H #define MLTCONNECTION_H #include #include namespace Mlt { class Repository; } /** * @class MltConnection * @brief Initializes MLT and provides access to its API * This is where the Mlt Factory is initialized, as well as the producers */ class MltConnection { public: /** @brief Open connection to the MLT framework - */ + */ static void construct(const QString &mltPath); /* @brief Returns a pointer to the MLT Repository*/ std::unique_ptr &getMltRepository(); /* @brief Returns a pointer to the instance of the singleton */ static std::unique_ptr &self(); protected: /** @brief Open connection to the MLT framework This constructor should be called only once */ MltConnection(const QString &mltPath); /** @brief Locates the MLT environment. * @param mltPath (optional) path to MLT environment * * It tries to set the paths of the MLT profiles and renderer, using * mltPath, MLT_PREFIX, searching for the binary `melt`, or asking to the * user. It doesn't fill any list of profiles, while its name suggests so. */ void locateMeltAndProfilesPath(const QString &mltPath = QString()); /** @brief Updates the list of available Lumas */ void refreshLumas(); static std::unique_ptr m_self; /** @brief The MLT repository, useful for filter/producer requests */ std::unique_ptr m_repository; }; #endif diff --git a/src/mltcontroller/bincontroller.cpp b/src/mltcontroller/bincontroller.cpp index 3fabd7fb3..f44ee8560 100644 --- a/src/mltcontroller/bincontroller.cpp +++ b/src/mltcontroller/bincontroller.cpp @@ -1,318 +1,309 @@ /*************************************************************************** * Copyright (C) 2014 by Jean-Baptiste Mardelle (jb@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) 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 "bincontroller.h" #include "bin/model/markerlistmodel.hpp" #include "bin/projectitemmodel.h" #include "clip.h" #include "clipcontroller.h" #include "core.h" #include "kdenlivesettings.h" static const char *kPlaylistTrackId = "main bin"; BinController::BinController(const QString &profileName) : QObject() { Q_UNUSED(profileName) // resetProfile(profileName.isEmpty() ? KdenliveSettings::current_profile() : profileName); } BinController::~BinController() { qDebug() << "/// delete bincontroller"; qDebug() << "REMAINING CLIPS: " << m_clipList.keys(); destroyBin(); } void BinController::destroyBin() { if (m_binPlaylist) { // m_binPlaylist.release(); m_binPlaylist->clear(); } qDeleteAll(m_extraClipList); m_extraClipList.clear(); m_clipList.clear(); } void BinController::loadExtraProducer(const QString &id, Mlt::Producer *prod) { if (m_extraClipList.contains(id)) { return; } m_extraClipList.insert(id, prod); } QStringList BinController::getProjectHashes() { QStringList hashes; QMapIterator> i(m_clipList); hashes.reserve(m_clipList.count()); while (i.hasNext()) { i.next(); hashes << i.value()->getClipHash(); } hashes.removeDuplicates(); return hashes; } - void BinController::slotStoreFolder(const QString &folderId, const QString &parentId, const QString &oldParentId, const QString &folderName) { if (!oldParentId.isEmpty()) { // Folder was moved, remove old reference QString oldPropertyName = "kdenlive:folder." + oldParentId + QLatin1Char('.') + folderId; m_binPlaylist->set(oldPropertyName.toUtf8().constData(), (char *)nullptr); } QString propertyName = "kdenlive:folder." + parentId + QLatin1Char('.') + folderId; if (folderName.isEmpty()) { // Remove this folder info m_binPlaylist->set(propertyName.toUtf8().constData(), (char *)nullptr); } else { m_binPlaylist->set(propertyName.toUtf8().constData(), folderName.toUtf8().constData()); } } - const QString BinController::binPlaylistId() { return kPlaylistTrackId; } int BinController::clipCount() const { return m_clipList.size(); } - void BinController::replaceBinPlaylistClip(const QString &id, const std::shared_ptr &producer) { removeBinPlaylistClip(id); m_binPlaylist->append(*producer.get()); } - void BinController::removeBinPlaylistClip(const QString &id) { int size = m_binPlaylist->count(); for (int i = 0; i < size; i++) { QScopedPointer prod(m_binPlaylist->get_clip(i)); QString prodId = prod->parent().get("kdenlive:id"); if (prodId == id) { m_binPlaylist->remove(i); break; } } } bool BinController::hasClip(const QString &id) { return m_clipList.contains(id); } - - std::shared_ptr BinController::getBinProducer(const QString &id) { // TODO: framebuffer speed clips if (!m_clipList.contains(id)) { qDebug() << "ERROR: requesting invalid bin producer: " << id; return nullptr; } return m_clipList[id]->originalProducer(); } void BinController::duplicateFilters(std::shared_ptr original, Mlt::Producer clone) { Mlt::Service clipService(original->get_service()); Mlt::Service dupService(clone.get_service()); for (int ix = 0; ix < clipService.filter_count(); ++ix) { QScopedPointer filter(clipService.filter(ix)); // Only duplicate Kdenlive filters if (filter->is_valid()) { QString effectId = filter->get("kdenlive_id"); if (effectId.isEmpty()) { continue; } // looks like there is no easy way to duplicate a filter, // so we will create a new one and duplicate its properties auto *dup = new Mlt::Filter(*original->profile(), filter->get("mlt_service")); if ((dup != nullptr) && dup->is_valid()) { for (int i = 0; i < filter->count(); ++i) { QString paramName = filter->get_name(i); if (paramName.at(0) != QLatin1Char('_')) { dup->set(filter->get_name(i), filter->get(i)); } } dupService.attach(*dup); } delete dup; } } } - QString BinController::xmlFromId(const QString &id) { if (!m_clipList.contains(id)) { qDebug() << "Error: impossible to retrieve xml from unknown bin clip"; return QString(); } std::shared_ptr controller = m_clipList[id]; std::shared_ptr original = controller->originalProducer(); QString xml = getProducerXML(original); QDomDocument mltData; mltData.setContent(xml); QDomElement producer = mltData.documentElement().firstChildElement(QStringLiteral("producer")); QString str; QTextStream stream(&str); producer.save(stream, 4); return str; } // static QString BinController::getProducerXML(const std::shared_ptr &producer, bool includeMeta) { Mlt::Consumer c(*producer->profile(), "xml", "string"); Mlt::Service s(producer->get_service()); if (!s.is_valid()) { return QString(); } int ignore = s.get_int("ignore_points"); if (ignore != 0) { s.set("ignore_points", 0); } c.set("time_format", "frames"); if (!includeMeta) { c.set("no_meta", 1); } c.set("store", "kdenlive"); c.set("no_root", 1); c.set("root", "/"); c.connect(s); c.start(); if (ignore != 0) { s.set("ignore_points", ignore); } return QString::fromUtf8(c.get("string")); } std::shared_ptr BinController::getController(const QString &id) { if (!m_clipList.contains(id)) { qDebug() << "Error: invalid bin clip requested" << id; Q_ASSERT(false); } return m_clipList.value(id); } const QList> BinController::getControllerList() const { return m_clipList.values(); } const QStringList BinController::getBinIdsByResource(const QFileInfo &url) const { QStringList controllers; QMapIterator> i(m_clipList); while (i.hasNext()) { i.next(); auto ctrl = i.value(); if (QFileInfo(ctrl->clipUrl()) == url) { controllers << i.key(); } } return controllers; } void BinController::updateTrackProducer(const QString &id) { emit updateTimelineProducer(id); } - - void BinController::saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel) { Q_UNUSED(guideModel) // Clear previous properites Mlt::Properties playlistProps(m_binPlaylist->get_properties()); Mlt::Properties docProperties; docProperties.pass_values(playlistProps, "kdenlive:docproperties."); for (int i = 0; i < docProperties.count(); i++) { QString propName = QStringLiteral("kdenlive:docproperties.") + docProperties.get_name(i); playlistProps.set(propName.toUtf8().constData(), (char *)nullptr); } // Clear previous metadata Mlt::Properties docMetadata; docMetadata.pass_values(playlistProps, "kdenlive:docmetadata."); for (int i = 0; i < docMetadata.count(); i++) { QString propName = QStringLiteral("kdenlive:docmetadata.") + docMetadata.get_name(i); playlistProps.set(propName.toUtf8().constData(), (char *)nullptr); } QMapIterator i(props); while (i.hasNext()) { i.next(); playlistProps.set(("kdenlive:docproperties." + i.key()).toUtf8().constData(), i.value().toUtf8().constData()); } QMapIterator j(metadata); while (j.hasNext()) { j.next(); playlistProps.set(("kdenlive:docmetadata." + j.key()).toUtf8().constData(), j.value().toUtf8().constData()); } } void BinController::saveProperty(const QString &name, const QString &value) { m_binPlaylist->set(name.toUtf8().constData(), value.toUtf8().constData()); } const QString BinController::getProperty(const QString &name) { return QString(m_binPlaylist->get(name.toUtf8().constData())); } QMap BinController::getProxies(const QString &root) { QMap proxies; int size = m_binPlaylist->count(); for (int i = 0; i < size; i++) { QScopedPointer prod(m_binPlaylist->get_clip(i)); if (!prod->is_valid() || prod->is_blank()) { continue; } QString proxy = prod->parent().get("kdenlive:proxy"); if (proxy.length() > 2) { if (QFileInfo(proxy).isRelative()) { proxy.prepend(root); } QString sourceUrl(prod->parent().get("kdenlive:originalurl")); if (QFileInfo(sourceUrl).isRelative()) { sourceUrl.prepend(root); } proxies.insert(proxy, sourceUrl); } } return proxies; } diff --git a/src/mltcontroller/bincontroller.h b/src/mltcontroller/bincontroller.h index 6996cf832..00611c305 100644 --- a/src/mltcontroller/bincontroller.h +++ b/src/mltcontroller/bincontroller.h @@ -1,162 +1,157 @@ /*************************************************************************** * Copyright (C) 2014 by Jean-Baptiste Mardelle (jb@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) 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 BINCONTROLLER_H #define BINCONTROLLER_H #include #include "clipcontroller.h" #include "definitions.h" #include #include #include #include class MarkerListModel; namespace Mlt { class Playlist; class Profile; -} +} // namespace Mlt /** * @class BinController * @brief This is where MLT's project clips (the bin clips) are managed * * The project profile, used to build the monitors renderers is stored here */ class BinController : public QObject, public std::enable_shared_from_this { Q_OBJECT public: explicit BinController(const QString &profileName = QString()); virtual ~BinController(); - friend class ClipController; friend class ProjectClip; protected: - public: /** @brief Store a timeline producer in clip list for later re-use * @param id The clip's id * @param producer The MLT producer for this clip * */ void loadExtraProducer(const QString &id, Mlt::Producer *prod); /** @brief Returns the name MLT will use to store our bin's playlist */ static const QString binPlaylistId(); /** @brief Clear the bin's playlist */ void destroyBin(); - /** @brief Returns true if a clip with that id is in our bin's playlist - * @param id The clip's id as stored in DocClipBase - */ + * @param id The clip's id as stored in DocClipBase + */ bool hasClip(const QString &id); - /** @brief Get the MLT Producer for a given id. @param id The clip id as stored in the DocClipBase class */ // TODO? Since MLT requires 1 different producer for each track to correctly handle audio mix, // we should specify on which track the clip should be. // @param track The track on which the producer will be put. Setting a value of -1 will return the master clip contained in the bin playlist // @param clipState The state of the clip (if we need an audio only or video only producer). // @param speed If the clip has a speed effect (framebuffer producer), we indicate the speed here std::shared_ptr getBinProducer(const QString &id); /** @brief Returns the clip data as rendered by MLT's XML consumer, used to duplicate a clip * @param producer The clip's original producer */ static QString getProducerXML(const std::shared_ptr &producer, bool includeMeta = false); /** @brief Returns the clip data as rendered by MLT's XML consumer * @param id The clip's original id * @returns An XML element containing the clip xml */ QString xmlFromId(const QString &id); int clipCount() const; std::shared_ptr getController(const QString &id); const QList> getControllerList() const; void replaceBinPlaylistClip(const QString &id, const std::shared_ptr &producer); /** @brief Get the list of ids whose clip have the resource indicated by @param url */ const QStringList getBinIdsByResource(const QFileInfo &url) const; /** @brief A Bin clip effect was changed, update track producers */ void updateTrackProducer(const QString &id); - /** @brief Save document properties in MLT's bin playlist */ void saveDocumentProperties(const QMap &props, const QMap &metadata, std::shared_ptr guideModel); /** @brief Save a property to main bin */ void saveProperty(const QString &name, const QString &value); /** @brief Save a property from the main bin */ const QString getProperty(const QString &name); /** @brief Return a list of proxy / original url */ QMap getProxies(const QString &root); /** @brief Returns a list of all clips hashes. */ QStringList getProjectHashes(); public slots: /** @brief Stored a Bin Folder id / name to MLT's bin playlist. Using an empry folderName deletes the property */ void slotStoreFolder(const QString &folderId, const QString &parentId, const QString &oldParentId, const QString &folderName); private: /** @brief The MLT playlist holding our Producers */ std::unique_ptr m_binPlaylist; /** @brief The current MLT profile's filename */ QString m_activeProfile; /** @brief Can be used to copy filters from a clip to another */ void duplicateFilters(std::shared_ptr original, Mlt::Producer clone); /** @brief This list holds all producer controllers for the playlist, indexed by id */ QMap> m_clipList; /** @brief This list holds all extra controllers (slowmotion, video only, ... that are in timeline, indexed by id */ QMap m_extraClipList; /** @brief Remove a clip from MLT's special bin playlist */ void removeBinPlaylistClip(const QString &id); signals: void requestAudioThumb(const QString &); void abortAudioThumbs(); void setDocumentNotes(const QString &); void updateTimelineProducer(const QString &); /** @brief We want to replace a clip with another, but before we need to change clip producer id so that there is no interference*/ void prepareTimelineReplacement(const requestClipInfo &, const std::shared_ptr &); /** @brief Indicate which clip we are loading */ void loadingBin(int); void slotProducerReady(const requestClipInfo &info, std::shared_ptr producer); }; #endif diff --git a/src/mltcontroller/clipcontroller.cpp b/src/mltcontroller/clipcontroller.cpp index 9ce2d4216..38d2ade5c 100644 --- a/src/mltcontroller/clipcontroller.cpp +++ b/src/mltcontroller/clipcontroller.cpp @@ -1,833 +1,832 @@ /* Copyright (C) 2012 Till Theato Copyright (C) 2014 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 "clipcontroller.h" #include "bin/model/markerlistmodel.hpp" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "effects/effectstack/model/effectstackmodel.hpp" #include "lib/audio/audioStreamInfo.h" #include "mltcontroller/effectscontroller.h" #include "profiles/profilemodel.hpp" #include "core.h" #include "kdenlive_debug.h" #include #include #include #include std::shared_ptr ClipController::mediaUnavailable; ClipController::ClipController(const QString clipId, std::shared_ptr producer) : selectedEffectIndex(1) , m_audioThumbCreated(false) , m_masterProducer(producer) , m_properties(producer ? new Mlt::Properties(producer->get_properties()) : nullptr) , m_usesProxy(false) , m_audioInfo(nullptr) , m_audioIndex(0) , m_videoIndex(0) , m_clipType(ClipType::Unknown) , m_hasLimitedDuration(true) , m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr) , m_controllerBinId(clipId) { if (m_masterProducer && !m_masterProducer->is_valid()) { qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; return; } if (m_masterProducer) { checkAudioVideo(); } if (m_properties) { setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); m_service = m_properties->get("mlt_service"); QString proxy = m_properties->get("kdenlive:proxy"); QString path = m_properties->get("resource"); if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property path = m_properties->get("kdenlive:originalurl"); if (QFileInfo(path).isRelative()) { path.prepend(pCore->currentDoc()->documentRoot()); } m_usesProxy = true; } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative() && path != QLatin1String("")) { path.prepend(pCore->currentDoc()->documentRoot()); } m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath(); getInfoForProducer(); } else { m_producerLock.lock(); } } ClipController::~ClipController() { delete m_properties; m_masterProducer.reset(); } const QString ClipController::binId() const { return m_controllerBinId; } const std::unique_ptr &ClipController::audioInfo() const { return m_audioInfo; } void ClipController::addMasterProducer(const std::shared_ptr &producer) { qDebug() << "################### ClipController::addmasterproducer"; QString documentRoot = pCore->currentDoc()->documentRoot(); m_masterProducer = producer; m_properties = new Mlt::Properties(m_masterProducer->get_properties()); int id = m_controllerBinId.toInt(); m_effectStack = EffectStackModel::construct(producer, {ObjectType::BinClip, id}, pCore->undoStack()); if (!m_masterProducer->is_valid()) { m_masterProducer = ClipController::mediaUnavailable; m_producerLock.unlock(); qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; } else { checkAudioVideo(); m_producerLock.unlock(); QString proxy = m_properties->get("kdenlive:proxy"); m_service = m_properties->get("mlt_service"); QString path = m_properties->get("resource"); m_usesProxy = false; if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property path = m_properties->get("kdenlive:originalurl"); if (QFileInfo(path).isRelative()) { path.prepend(documentRoot); } m_usesProxy = true; } else if (m_service != QLatin1String("color") && m_service != QLatin1String("colour") && !path.isEmpty() && QFileInfo(path).isRelative()) { path.prepend(documentRoot); } m_path = path.isEmpty() ? QString() : QFileInfo(path).absoluteFilePath(); getInfoForProducer(); emitProducerChanged(m_controllerBinId, producer); setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); } connectEffectStack(); } namespace { QString producerXml(const std::shared_ptr &producer, bool includeMeta) { Mlt::Consumer c(*producer->profile(), "xml", "string"); Mlt::Service s(producer->get_service()); if (!s.is_valid()) { return QString(); } int ignore = s.get_int("ignore_points"); if (ignore != 0) { s.set("ignore_points", 0); } c.set("time_format", "frames"); if (!includeMeta) { c.set("no_meta", 1); } c.set("store", "kdenlive"); c.set("no_root", 1); c.set("root", "/"); c.connect(s); c.start(); if (ignore != 0) { s.set("ignore_points", ignore); } return QString::fromUtf8(c.get("string")); } } // namespace void ClipController::getProducerXML(QDomDocument &document, bool includeMeta) { // TODO refac this is a probable duplicate with Clip::xml if (m_masterProducer) { QString xml = producerXml(m_masterProducer, includeMeta); document.setContent(xml); } else { qCDebug(KDENLIVE_LOG) << " + + ++ NO MASTER PROD"; } } void ClipController::getInfoForProducer() { date = QFileInfo(m_path).lastModified(); m_audioIndex = -1; m_videoIndex = -1; // special case: playlist with a proxy clip have to be detected separately if (m_usesProxy && m_path.endsWith(QStringLiteral(".mlt"))) { m_clipType = ClipType::Playlist; } else if (m_service == QLatin1String("avformat") || m_service == QLatin1String("avformat-novalidate")) { m_audioIndex = getProducerIntProperty(QStringLiteral("audio_index")); m_videoIndex = getProducerIntProperty(QStringLiteral("video_index")); if (m_audioIndex == -1) { m_clipType = ClipType::Video; } else if (m_videoIndex == -1) { m_clipType = ClipType::Audio; } else { m_clipType = ClipType::AV; } } else if (m_service == QLatin1String("qimage") || m_service == QLatin1String("pixbuf")) { if (m_path.contains(QLatin1Char('%')) || m_path.contains(QStringLiteral("/.all."))) { m_clipType = ClipType::SlideShow; } else { m_clipType = ClipType::Image; } m_hasLimitedDuration = false; } else if (m_service == QLatin1String("colour") || m_service == QLatin1String("color")) { m_clipType = ClipType::Color; m_hasLimitedDuration = false; } else if (m_service == QLatin1String("kdenlivetitle")) { if (!m_path.isEmpty()) { m_clipType = ClipType::TextTemplate; } else { m_clipType = ClipType::Text; } m_hasLimitedDuration = false; } else if (m_service == QLatin1String("xml") || m_service == QLatin1String("consumer")) { m_clipType = ClipType::Playlist; } else if (m_service == QLatin1String("webvfx")) { m_clipType = ClipType::WebVfx; } else if (m_service == QLatin1String("qtext")) { m_clipType = ClipType::QText; } else { m_clipType = ClipType::Unknown; } if (m_audioIndex > -1 || m_clipType == ClipType::Playlist) { m_audioInfo.reset(new AudioStreamInfo(m_masterProducer.get(), m_audioIndex)); } if (!m_hasLimitedDuration) { int playtime = m_masterProducer->get_int("kdenlive:duration"); if (playtime <= 0) { // Fix clips having missing kdenlive:duration m_masterProducer->set("kdenlive:duration", m_masterProducer->get_playtime()); m_masterProducer->set("out", m_masterProducer->get_length() - 1); } } } bool ClipController::hasLimitedDuration() const { return m_hasLimitedDuration; } void ClipController::forceLimitedDuration() { m_hasLimitedDuration = true; } std::shared_ptr ClipController::originalProducer() { QMutexLocker lock(&m_producerLock); return m_masterProducer; } Mlt::Producer *ClipController::masterProducer() { return new Mlt::Producer(*m_masterProducer); } bool ClipController::isValid() { if (m_masterProducer == nullptr) { return false; } return m_masterProducer->is_valid(); } // static const char *ClipController::getPassPropertiesList(bool passLength) { if (!passLength) { return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_" "colorspace,set.force_full_luma,file_hash,autorotate"; } return "kdenlive:proxy,kdenlive:originalurl,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,force_" "colorspace,set.force_full_luma,templatetext,file_hash,autorotate,xmldata,length"; } QMap ClipController::getPropertiesFromPrefix(const QString &prefix, bool withPrefix) { Mlt::Properties subProperties; subProperties.pass_values(*m_properties, prefix.toUtf8().constData()); QMap subclipsData; for (int i = 0; i < subProperties.count(); i++) { subclipsData.insert(withPrefix ? QString(prefix + subProperties.get_name(i)) : subProperties.get_name(i), subProperties.get(i)); } return subclipsData; } void ClipController::updateProducer(const std::shared_ptr &producer) { qDebug() << "################### ClipController::updateProducer"; // TODO replace all track producers if (!m_properties) { // producer has not been initialized return addMasterProducer(producer); } Mlt::Properties passProperties; // Keep track of necessary properties QString proxy = producer->get("kdenlive:proxy"); if (proxy.length() > 2) { // This is a proxy producer, read original url from kdenlive property m_usesProxy = true; } else { m_usesProxy = false; } passProperties.pass_list(*m_properties, getPassPropertiesList(m_usesProxy)); delete m_properties; *m_masterProducer = producer.get(); checkAudioVideo(); m_properties = new Mlt::Properties(m_masterProducer->get_properties()); // Pass properties from previous producer m_properties->pass_list(passProperties, getPassPropertiesList(m_usesProxy)); m_producerLock.unlock(); if (!m_masterProducer->is_valid()) { qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; } else { m_effectStack->resetService(m_masterProducer); // URL and name shoule not be updated otherwise when proxying a clip we cannot find back the original url /*m_url = QUrl::fromLocalFile(m_masterProducer->get("resource")); if (m_url.isValid()) { m_name = m_url.fileName(); } */ } qDebug() << "// replace finished: " << binId() << " : " << m_masterProducer->get("resource"); } - const QString ClipController::getStringDuration() { if (m_masterProducer) { int playtime = m_masterProducer->get_int("kdenlive:duration"); if (playtime > 0) { return QString(m_properties->frames_to_time(playtime, mlt_time_smpte_df)); } return m_masterProducer->get_length_time(mlt_time_smpte_df); } return i18n("Unknown"); } GenTime ClipController::getPlaytime() const { if (!m_masterProducer || !m_masterProducer->is_valid()) { return GenTime(); } double fps = pCore->getCurrentFps(); if (!m_hasLimitedDuration) { int playtime = m_masterProducer->get_int("kdenlive:duration"); return GenTime(playtime == 0 ? m_masterProducer->get_playtime() : playtime, fps); } return GenTime(m_masterProducer->get_playtime(), fps); } int ClipController::getFramePlaytime() const { if (!m_masterProducer || !m_masterProducer->is_valid()) { return 0; } if (!m_hasLimitedDuration) { int playtime = m_masterProducer->get_int("kdenlive:duration"); return playtime == 0 ? m_masterProducer->get_playtime() : playtime; } return m_masterProducer->get_playtime(); } QString ClipController::getProducerProperty(const QString &name) const { if (!m_properties) { return QString(); } if (m_usesProxy && name.startsWith(QLatin1String("meta."))) { QString correctedName = QStringLiteral("kdenlive:") + name; return m_properties->get(correctedName.toUtf8().constData()); } return QString(m_properties->get(name.toUtf8().constData())); } int ClipController::getProducerIntProperty(const QString &name) const { if (!m_properties) { return 0; } if (m_usesProxy && name.startsWith(QLatin1String("meta."))) { QString correctedName = QStringLiteral("kdenlive:") + name; return m_properties->get_int(correctedName.toUtf8().constData()); } return m_properties->get_int(name.toUtf8().constData()); } qint64 ClipController::getProducerInt64Property(const QString &name) const { if (!m_properties) { return 0; } return m_properties->get_int64(name.toUtf8().constData()); } double ClipController::getProducerDoubleProperty(const QString &name) const { if (!m_properties) { return 0; } return m_properties->get_double(name.toUtf8().constData()); } QColor ClipController::getProducerColorProperty(const QString &name) const { if (!m_properties) { return QColor(); } mlt_color color = m_properties->get_color(name.toUtf8().constData()); return QColor::fromRgb(color.r, color.g, color.b); } QMap ClipController::currentProperties(const QMap &props) { QMap currentProps; QMap::const_iterator i = props.constBegin(); while (i != props.constEnd()) { currentProps.insert(i.key(), getProducerProperty(i.key())); ++i; } return currentProps; } double ClipController::originalFps() const { if (!m_properties) { return 0; } QString propertyName = QStringLiteral("meta.media.%1.stream.frame_rate").arg(m_videoIndex); return m_properties->get_double(propertyName.toUtf8().constData()); } QString ClipController::videoCodecProperty(const QString &property) const { if (!m_properties) { return QString(); } QString propertyName = QStringLiteral("meta.media.%1.codec.%2").arg(m_videoIndex).arg(property); return m_properties->get(propertyName.toUtf8().constData()); } const QString ClipController::codec(bool audioCodec) const { if ((m_properties == nullptr) || (m_clipType != ClipType::AV && m_clipType != ClipType::Video && m_clipType != ClipType::Audio)) { return QString(); } QString propertyName = QStringLiteral("meta.media.%1.codec.name").arg(audioCodec ? m_audioIndex : m_videoIndex); return m_properties->get(propertyName.toUtf8().constData()); } const QString ClipController::clipUrl() const { return m_path; } QString ClipController::clipName() const { QString name = getProducerProperty(QStringLiteral("kdenlive:clipname")); if (!name.isEmpty()) { return name; } return QFileInfo(m_path).fileName(); } QString ClipController::description() const { if (m_clipType == ClipType::TextTemplate) { QString name = getProducerProperty(QStringLiteral("templatetext")); return name; } QString name = getProducerProperty(QStringLiteral("kdenlive:description")); if (!name.isEmpty()) { return name; } return getProducerProperty(QStringLiteral("meta.attr.comment.markup")); } QString ClipController::serviceName() const { return m_service; } void ClipController::setProducerProperty(const QString &name, int value) { if (!m_masterProducer) return; // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), value); } void ClipController::setProducerProperty(const QString &name, double value) { if (!m_masterProducer) return; // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), value); } void ClipController::setProducerProperty(const QString &name, const QString &value) { if (!m_masterProducer) return; // TODO: also set property on all track producers if (value.isEmpty()) { m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr); } else { m_masterProducer->parent().set(name.toUtf8().constData(), value.toUtf8().constData()); } } void ClipController::resetProducerProperty(const QString &name) { // TODO: also set property on all track producers m_masterProducer->parent().set(name.toUtf8().constData(), (char *)nullptr); } ClipType ClipController::clipType() const { return m_clipType; } const QSize ClipController::getFrameSize() const { if (m_masterProducer == nullptr) { return QSize(); } int width = m_masterProducer->get_int("meta.media.width"); if (width == 0) { width = m_masterProducer->get_int("width"); } int height = m_masterProducer->get_int("meta.media.height"); if (height == 0) { height = m_masterProducer->get_int("height"); } return QSize(width, height); } bool ClipController::hasAudio() const { return m_hasAudio; } void ClipController::checkAudioVideo() { m_masterProducer->seek(0); Mlt::Frame *frame = m_masterProducer->get_frame(); // test_audio returns 1 if there is NO audio (strange but true at the time this code is written) m_hasAudio = frame->get_int("test_audio") == 0; m_hasVideo = frame->get_int("test_image") == 0; } bool ClipController::hasVideo() const { return m_hasVideo; } PlaylistState::ClipState ClipController::defaultState() const { if (hasVideo()) { return PlaylistState::VideoOnly; } if (hasAudio()) { return PlaylistState::AudioOnly; } return PlaylistState::Disabled; } QPixmap ClipController::pixmap(int framePosition, int width, int height) { // TODO refac this should use the new thumb infrastructure m_masterProducer->seek(framePosition); Mlt::Frame *frame = m_masterProducer->get_frame(); if (frame == nullptr || !frame->is_valid()) { QPixmap p(width, height); p.fill(QColor(Qt::red).rgb()); return p; } frame->set("rescale.interp", "bilinear"); frame->set("deinterlace_method", "onefield"); frame->set("top_field_first", -1); if (width == 0) { width = m_masterProducer->get_int("meta.media.width"); if (width == 0) { width = m_masterProducer->get_int("width"); } } if (height == 0) { height = m_masterProducer->get_int("meta.media.height"); if (height == 0) { height = m_masterProducer->get_int("height"); } } // int ow = frameWidth; // int oh = height; mlt_image_format format = mlt_image_rgb24a; width += width % 2; height += height % 2; const uchar *imagedata = frame->get_image(format, width, height); QImage image(imagedata, width, height, QImage::Format_RGBA8888); QPixmap pixmap; pixmap.convertFromImage(image); delete frame; return pixmap; } void ClipController::setZone(const QPoint &zone) { setProducerProperty(QStringLiteral("kdenlive:zone_in"), zone.x()); setProducerProperty(QStringLiteral("kdenlive:zone_out"), zone.y()); } QPoint ClipController::zone() const { int in = getProducerIntProperty(QStringLiteral("kdenlive:zone_in")); int max = getFramePlaytime() - 1; int out = qMin(getProducerIntProperty(QStringLiteral("kdenlive:zone_out")), max); if (out <= in) { out = max; } QPoint zone(in, out); return zone; } const QString ClipController::getClipHash() const { return getProducerProperty(QStringLiteral("kdenlive:file_hash")); } Mlt::Properties &ClipController::properties() { return *m_properties; } void ClipController::addEffect(QDomElement &xml) { Q_UNUSED(xml) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service = m_masterProducer->parent(); ItemInfo info; info.cropStart = GenTime(); info.cropDuration = getPlaytime(); EffectsList eff = effectList(); EffectsController::initEffect(info, eff, getProducerProperty(QStringLiteral("kdenlive:proxy")), xml); // Add effect to list and setup a kdenlive_ix value int kdenlive_ix = 0; for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); int ix = effect->get_int("kdenlive_ix"); if (ix > kdenlive_ix) { kdenlive_ix = ix; } } kdenlive_ix++; xml.setAttribute(QStringLiteral("kdenlive_ix"), kdenlive_ix); EffectsParameterList params = EffectsController::getEffectArgs(xml); EffectManager effect(service); effect.addEffect(params, getPlaytime().frames(pCore->getCurrentFps())); if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); */ } void ClipController::removeEffect(int effectIndex, bool delayRefresh){ Q_UNUSED(effectIndex) Q_UNUSED(delayRefresh) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service(m_masterProducer->parent()); EffectManager effect(service); effect.removeEffect(effectIndex, true); if (!delayRefresh) { if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); } */ } EffectsList ClipController::effectList() { return xmlEffectList(m_masterProducer->profile(), m_masterProducer->parent()); } void ClipController::moveEffect(int oldPos, int newPos) { Q_UNUSED(oldPos) Q_UNUSED(newPos) // TODO refac: this must be rewritten /* QMutexLocker lock(&m_effectMutex); Mlt::Service service(m_masterProducer->parent()); EffectManager effect(service); effect.moveEffect(oldPos, newPos); */ } int ClipController::effectsCount() { int count = 0; Mlt::Service service(m_masterProducer->parent()); for (int ix = 0; ix < service.filter_count(); ++ix) { QScopedPointer effect(service.filter(ix)); QString id = effect->get("kdenlive_id"); if (!id.isEmpty()) { count++; } } return count; } // static EffectsList ClipController::xmlEffectList(Mlt::Profile *profile, Mlt::Service &service) { Q_UNUSED(profile) Q_UNUSED(service) EffectsList effList(true); // TODO refac : rewrite this /* for (int ix = 0; ix < service.filter_count(); ++ix) { QScopedPointer effect(service.filter(ix)); QDomElement clipeffect = Timeline::getEffectByTag(effect->get("tag"), effect->get("kdenlive_id")); QDomElement currenteffect = clipeffect.cloneNode().toElement(); // recover effect parameters QDomNodeList params = currenteffect.elementsByTagName(QStringLiteral("parameter")); if (effect->get_int("disable") == 1) { currenteffect.setAttribute(QStringLiteral("disable"), 1); } for (int i = 0; i < params.count(); ++i) { QDomElement param = params.item(i).toElement(); Timeline::setParam(param, effect->get(param.attribute(QStringLiteral("name")).toUtf8().constData())); } effList.append(currenteffect); } */ return effList; } void ClipController::changeEffectState(const QList &indexes, bool disable) { Q_UNUSED(indexes) Q_UNUSED(disable) // TODO refac : this must be rewritten /* Mlt::Service service = m_masterProducer->parent(); for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); if ((effect != nullptr) && effect->is_valid() && indexes.contains(effect->get_int("kdenlive_ix"))) { effect->set("disable", (int)disable); } } if (auto ptr = m_binController.lock()) ptr->updateTrackProducer(m_controllerBinId); */ } void ClipController::updateEffect(const QDomElement &e, int ix) { Q_UNUSED(e) Q_UNUSED(ix) // TODO refac : this must be rewritten /* QString tag = e.attribute(QStringLiteral("id")); if (tag == QLatin1String("autotrack_rectangle") || tag.startsWith(QLatin1String("ladspa")) || tag == QLatin1String("sox")) { // this filters cannot be edited, remove and re-add it removeEffect(ix, true); QDomElement clone = e.cloneNode().toElement(); addEffect(clone); return; } EffectsParameterList params = EffectsController::getEffectArgs(e); Mlt::Service service = m_masterProducer->parent(); for (int i = 0; i < service.filter_count(); ++i) { QScopedPointer effect(service.filter(i)); if (!effect || !effect->is_valid() || effect->get_int("kdenlive_ix") != ix) { continue; } service.lock(); QString prefix; QString ser = effect->get("mlt_service"); if (ser == QLatin1String("region")) { prefix = QStringLiteral("filter0."); } for (int j = 0; j < params.count(); ++j) { effect->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData()); // qCDebug(KDENLIVE_LOG)<updateTrackProducer(m_controllerBinId); // slotRefreshTracks(); */ } bool ClipController::hasEffects() const { return m_effectStack->rowCount() > 0; } void ClipController::setBinEffectsEnabled(bool enabled) { m_effectStack->setEffectStackEnabled(enabled); } void ClipController::saveZone(QPoint zone, const QDir &dir) { QString path = QString(clipName() + QLatin1Char('_') + QString::number(zone.x()) + QStringLiteral(".mlt")); if (dir.exists(path)) { // TODO ask for overwrite } Mlt::Consumer xmlConsumer(pCore->getCurrentProfile()->profile(), ("xml:" + dir.absoluteFilePath(path)).toUtf8().constData()); xmlConsumer.set("terminate_on_pause", 1); Mlt::Producer prod(m_masterProducer->get_producer()); Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y()); Mlt::Playlist list(pCore->getCurrentProfile()->profile()); list.insert_at(0, *prod2, 0); // list.set("title", desc.toUtf8().constData()); xmlConsumer.connect(list); xmlConsumer.run(); delete prod2; } std::shared_ptr ClipController::getEffectStack() const { return m_effectStack; } void ClipController::addEffect(const QString &effectId) { m_effectStack->appendEffect(effectId, true); } bool ClipController::copyEffect(std::shared_ptr stackModel, int rowId) { m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId)); return true; } std::shared_ptr ClipController::getMarkerModel() const { return m_markerModel; } diff --git a/src/mltcontroller/clippropertiescontroller.cpp b/src/mltcontroller/clippropertiescontroller.cpp index ff2b03e5e..2b9e5ba80 100644 --- a/src/mltcontroller/clippropertiescontroller.cpp +++ b/src/mltcontroller/clippropertiescontroller.cpp @@ -1,1172 +1,1170 @@ /* Copyright (C) 2015 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 "clippropertiescontroller.h" #include "bin/model/markerlistmodel.hpp" #include "bincontroller.h" #include "clipcontroller.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "doc/kdenlivedoc.h" #include "kdenlivesettings.h" #include "profiles/profilerepository.hpp" #include "project/projectmanager.h" #include "timecodedisplay.h" #include "utils/KoIconUtils.h" #include "widgets/choosecolorwidget.h" #include #ifdef KF5_USE_FILEMETADATA #include #include #include #include #endif #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include AnalysisTree::AnalysisTree(QWidget *parent) : QTreeWidget(parent) { setRootIsDecorated(false); setColumnCount(2); setAlternatingRowColors(true); setHeaderHidden(true); setDragEnabled(true); } // virtual QMimeData *AnalysisTree::mimeData(const QList list) const { QString mimeData; for (QTreeWidgetItem *item : list) { if ((item->flags() & Qt::ItemIsDragEnabled) != 0) { mimeData.append(item->text(1)); } } auto *mime = new QMimeData; mime->setData(QStringLiteral("kdenlive/geometry"), mimeData.toUtf8()); return mime; } #ifdef KF5_USE_FILEMETADATA class ExtractionResult : public KFileMetaData::ExtractionResult { public: ExtractionResult(const QString &filename, const QString &mimetype, QTreeWidget *tree) : KFileMetaData::ExtractionResult(filename, mimetype, KFileMetaData::ExtractionResult::ExtractMetaData) , m_tree(tree) { } void append(const QString & /*text*/) override {} void addType(KFileMetaData::Type::Type /*type*/) override {} void add(KFileMetaData::Property::Property property, const QVariant &value) override { bool decode = false; switch (property) { case KFileMetaData::Property::ImageMake: case KFileMetaData::Property::ImageModel: case KFileMetaData::Property::ImageDateTime: case KFileMetaData::Property::BitRate: case KFileMetaData::Property::TrackNumber: case KFileMetaData::Property::ReleaseYear: case KFileMetaData::Property::Composer: case KFileMetaData::Property::Genre: case KFileMetaData::Property::Artist: case KFileMetaData::Property::Album: case KFileMetaData::Property::Title: case KFileMetaData::Property::Comment: case KFileMetaData::Property::Copyright: case KFileMetaData::Property::PhotoFocalLength: case KFileMetaData::Property::PhotoExposureTime: case KFileMetaData::Property::PhotoFNumber: case KFileMetaData::Property::PhotoApertureValue: case KFileMetaData::Property::PhotoWhiteBalance: case KFileMetaData::Property::PhotoGpsLatitude: case KFileMetaData::Property::PhotoGpsLongitude: decode = true; break; default: break; } if (decode) { KFileMetaData::PropertyInfo info(property); if (info.valueType() == QVariant::DateTime) { new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << value.toDateTime().toString(Qt::DefaultLocaleShortDate)); } else if (info.valueType() == QVariant::Int) { int val = value.toInt(); if (property == KFileMetaData::Property::BitRate) { // Adjust unit for bitrate - new QTreeWidgetItem( - m_tree, QStringList() << info.displayName() << QString::number(val / 1000) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")); + new QTreeWidgetItem(m_tree, QStringList() << info.displayName() + << QString::number(val / 1000) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")); } else { new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << QString::number(val)); } } else if (info.valueType() == QVariant::Double) { new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << QString::number(value.toDouble())); } else { new QTreeWidgetItem(m_tree, QStringList() << info.displayName() << value.toString()); } } } private: QTreeWidget *m_tree; }; #endif ClipPropertiesController::ClipPropertiesController(ClipController *controller, QWidget *parent) : QWidget(parent) , m_controller(controller) , m_tc(Timecode(Timecode::HH_MM_SS_HH, pCore->getCurrentFps())) , m_id(controller->binId()) , m_type(controller->clipType()) , m_properties(controller->properties()) , m_textEdit(nullptr) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); auto *lay = new QVBoxLayout; lay->setContentsMargins(0, 0, 0, 0); m_clipLabel = new QLabel(controller->clipName()); lay->addWidget(m_clipLabel); m_tabWidget = new QTabWidget(this); lay->addWidget(m_tabWidget); setLayout(lay); m_tabWidget->setDocumentMode(true); m_tabWidget->setTabPosition(QTabWidget::East); m_forcePage = new QWidget(this); m_propertiesPage = new QWidget(this); m_markersPage = new QWidget(this); m_metaPage = new QWidget(this); m_analysisPage = new QWidget(this); // Clip properties auto *propsBox = new QVBoxLayout; m_propertiesTree = new QTreeWidget(this); m_propertiesTree->setRootIsDecorated(false); m_propertiesTree->setColumnCount(2); m_propertiesTree->setAlternatingRowColors(true); m_propertiesTree->sortByColumn(0, Qt::AscendingOrder); m_propertiesTree->setHeaderHidden(true); propsBox->addWidget(m_propertiesTree); fillProperties(); m_propertiesPage->setLayout(propsBox); // Clip markers auto *mBox = new QVBoxLayout; m_markerTree = new QTreeView; m_markerTree->setRootIsDecorated(false); m_markerTree->setAlternatingRowColors(true); m_markerTree->setHeaderHidden(true); m_markerTree->setSelectionMode(QAbstractItemView::ExtendedSelection); m_markerTree->setModel(controller->getMarkerModel().get()); mBox->addWidget(m_markerTree); auto *bar = new QToolBar; bar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Add marker"), this, SLOT(slotAddMarker())); bar->addAction(KoIconUtils::themedIcon(QStringLiteral("trash-empty")), i18n("Delete marker"), this, SLOT(slotDeleteMarker())); bar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-edit")), i18n("Edit marker"), this, SLOT(slotEditMarker())); bar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save-as")), i18n("Export markers"), this, SLOT(slotSaveMarkers())); bar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-open")), i18n("Import markers"), this, SLOT(slotLoadMarkers())); mBox->addWidget(bar); m_markersPage->setLayout(mBox); connect(m_markerTree, &QAbstractItemView::doubleClicked, this, &ClipPropertiesController::slotSeekToMarker); // metadata auto *m2Box = new QVBoxLayout; auto *metaTree = new QTreeWidget; metaTree->setRootIsDecorated(true); metaTree->setColumnCount(2); metaTree->setAlternatingRowColors(true); metaTree->setHeaderHidden(true); m2Box->addWidget(metaTree); slotFillMeta(metaTree); m_metaPage->setLayout(m2Box); // Clip analysis auto *aBox = new QVBoxLayout; m_analysisTree = new AnalysisTree(this); aBox->addWidget(new QLabel(i18n("Analysis data"))); aBox->addWidget(m_analysisTree); auto *bar2 = new QToolBar; bar2->addAction(KoIconUtils::themedIcon(QStringLiteral("trash-empty")), i18n("Delete analysis"), this, SLOT(slotDeleteAnalysis())); bar2->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save-as")), i18n("Export analysis"), this, SLOT(slotSaveAnalysis())); bar2->addAction(KoIconUtils::themedIcon(QStringLiteral("document-open")), i18n("Import analysis"), this, SLOT(slotLoadAnalysis())); aBox->addWidget(bar2); slotFillAnalysisData(); m_analysisPage->setLayout(aBox); // Force properties auto *vbox = new QVBoxLayout; if (m_type == ClipType::Text || m_type == ClipType::SlideShow || m_type == ClipType::TextTemplate) { QPushButton *editButton = new QPushButton(i18n("Edit Clip"), this); connect(editButton, &QAbstractButton::clicked, this, &ClipPropertiesController::editClip); vbox->addWidget(editButton); } if (m_type == ClipType::Color || m_type == ClipType::Image || m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::TextTemplate) { // Edit duration widget m_originalProperties.insert(QStringLiteral("out"), m_properties.get("out")); int kdenlive_length = m_properties.get_int("kdenlive:duration"); if (kdenlive_length > 0) { m_originalProperties.insert(QStringLiteral("kdenlive:duration"), QString::number(kdenlive_length)); } m_originalProperties.insert(QStringLiteral("length"), m_properties.get("length")); auto *hlay = new QHBoxLayout; QCheckBox *box = new QCheckBox(i18n("Duration"), this); box->setObjectName(QStringLiteral("force_duration")); hlay->addWidget(box); auto *timePos = new TimecodeDisplay(m_tc, this); timePos->setObjectName(QStringLiteral("force_duration_value")); timePos->setValue(kdenlive_length > 0 ? kdenlive_length : m_properties.get_int("length")); int original_length = m_properties.get_int("kdenlive:original_length"); if (original_length > 0) { box->setChecked(true); } else { timePos->setEnabled(false); } hlay->addWidget(timePos); vbox->addLayout(hlay); connect(box, &QAbstractButton::toggled, timePos, &QWidget::setEnabled); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); connect(timePos, &TimecodeDisplay::timeCodeEditingFinished, this, &ClipPropertiesController::slotDurationChanged); connect(this, &ClipPropertiesController::updateTimeCodeFormat, timePos, &TimecodeDisplay::slotUpdateTimeCodeFormat); connect(this, SIGNAL(modified(int)), timePos, SLOT(setValue(int))); } if (m_type == ClipType::TextTemplate) { // Edit text widget QString currentText = m_properties.get("templatetext"); m_originalProperties.insert(QStringLiteral("templatetext"), currentText); m_textEdit = new QTextEdit(this); m_textEdit->setAcceptRichText(false); m_textEdit->setPlainText(currentText); m_textEdit->setPlaceholderText(i18n("Enter template text here")); vbox->addWidget(m_textEdit); QPushButton *button = new QPushButton(i18n("Apply"), this); vbox->addWidget(button); connect(button, &QPushButton::clicked, this, &ClipPropertiesController::slotTextChanged); } else if (m_type == ClipType::Color) { // Edit color widget m_originalProperties.insert(QStringLiteral("resource"), m_properties.get("resource")); mlt_color color = m_properties.get_color("resource"); ChooseColorWidget *choosecolor = new ChooseColorWidget(i18n("Color"), QColor::fromRgb(color.r, color.g, color.b).name(), "", false, this); vbox->addWidget(choosecolor); // connect(choosecolor, SIGNAL(displayMessage(QString,int)), this, SIGNAL(displayMessage(QString,int))); connect(choosecolor, &ChooseColorWidget::modified, this, &ClipPropertiesController::slotColorModified); connect(this, SIGNAL(modified(QColor)), choosecolor, SLOT(slotColorModified(QColor))); } else if (m_type == ClipType::Image) { int transparency = m_properties.get_int("kdenlive:transparency"); m_originalProperties.insert(QStringLiteral("kdenlive:transparency"), QString::number(transparency)); auto *hlay = new QHBoxLayout; QCheckBox *box = new QCheckBox(i18n("Transparent"), this); box->setObjectName(QStringLiteral("kdenlive:transparency")); box->setChecked(transparency == 1); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); hlay->addWidget(box); vbox->addLayout(hlay); } if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Image) { // Aspect ratio int force_ar_num = m_properties.get_int("force_aspect_num"); int force_ar_den = m_properties.get_int("force_aspect_den"); m_originalProperties.insert(QStringLiteral("force_aspect_den"), (force_ar_den == 0) ? QString() : QString::number(force_ar_den)); m_originalProperties.insert(QStringLiteral("force_aspect_num"), (force_ar_num == 0) ? QString() : QString::number(force_ar_num)); auto *hlay = new QHBoxLayout; QCheckBox *box = new QCheckBox(i18n("Aspect Ratio"), this); box->setObjectName(QStringLiteral("force_ar")); vbox->addWidget(box); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); auto *spin1 = new QSpinBox(this); spin1->setMaximum(8000); spin1->setObjectName(QStringLiteral("force_aspect_num_value")); hlay->addWidget(spin1); hlay->addWidget(new QLabel(QStringLiteral(":"))); auto *spin2 = new QSpinBox(this); spin2->setMinimum(1); spin2->setMaximum(8000); spin2->setObjectName(QStringLiteral("force_aspect_den_value")); hlay->addWidget(spin2); if (force_ar_num == 0) { // use current ratio int num = m_properties.get_int("meta.media.sample_aspect_num"); int den = m_properties.get_int("meta.media.sample_aspect_den"); if (den == 0) { num = 1; den = 1; } spin1->setEnabled(false); spin2->setEnabled(false); spin1->setValue(num); spin2->setValue(den); } else { box->setChecked(true); spin1->setEnabled(true); spin2->setEnabled(true); spin1->setValue(force_ar_num); spin2->setValue(force_ar_den); } connect(spin2, static_cast(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged); connect(spin1, static_cast(&QSpinBox::valueChanged), this, &ClipPropertiesController::slotAspectValueChanged); connect(box, &QAbstractButton::toggled, spin1, &QWidget::setEnabled); connect(box, &QAbstractButton::toggled, spin2, &QWidget::setEnabled); vbox->addLayout(hlay); } if (m_type == ClipType::AV || m_type == ClipType::Video) { QLocale locale; // Fps QString force_fps = m_properties.get("force_fps"); m_originalProperties.insert(QStringLiteral("force_fps"), force_fps.isEmpty() ? QStringLiteral("-") : force_fps); auto *hlay = new QHBoxLayout; QCheckBox *box = new QCheckBox(i18n("Frame rate"), this); box->setObjectName(QStringLiteral("force_fps")); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); auto *spin = new QDoubleSpinBox(this); spin->setMaximum(1000); connect(spin, SIGNAL(valueChanged(double)), this, SLOT(slotValueChanged(double))); spin->setObjectName(QStringLiteral("force_fps_value")); if (force_fps.isEmpty()) { spin->setValue(controller->originalFps()); } else { spin->setValue(locale.toDouble(force_fps)); } connect(box, &QAbstractButton::toggled, spin, &QWidget::setEnabled); box->setChecked(!force_fps.isEmpty()); spin->setEnabled(!force_fps.isEmpty()); hlay->addWidget(box); hlay->addWidget(spin); vbox->addLayout(hlay); // Scanning QString force_prog = m_properties.get("force_progressive"); m_originalProperties.insert(QStringLiteral("force_progressive"), force_prog.isEmpty() ? QStringLiteral("-") : force_prog); hlay = new QHBoxLayout; box = new QCheckBox(i18n("Scanning"), this); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); box->setObjectName(QStringLiteral("force_progressive")); auto *combo = new QComboBox(this); combo->addItem(i18n("Interlaced"), 0); combo->addItem(i18n("Progressive"), 1); connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboValueChanged())); combo->setObjectName(QStringLiteral("force_progressive_value")); if (!force_prog.isEmpty()) { combo->setCurrentIndex(force_prog.toInt()); } connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled); box->setChecked(!force_prog.isEmpty()); combo->setEnabled(!force_prog.isEmpty()); hlay->addWidget(box); hlay->addWidget(combo); vbox->addLayout(hlay); // Field order QString force_tff = m_properties.get("force_tff"); m_originalProperties.insert(QStringLiteral("force_tff"), force_tff.isEmpty() ? QStringLiteral("-") : force_tff); hlay = new QHBoxLayout; box = new QCheckBox(i18n("Field order"), this); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); box->setObjectName(QStringLiteral("force_tff")); combo = new QComboBox(this); combo->addItem(i18n("Bottom first"), 0); combo->addItem(i18n("Top first"), 1); connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboValueChanged())); combo->setObjectName(QStringLiteral("force_tff_value")); if (!force_tff.isEmpty()) { combo->setCurrentIndex(force_tff.toInt()); } connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled); box->setChecked(!force_tff.isEmpty()); combo->setEnabled(!force_tff.isEmpty()); hlay->addWidget(box); hlay->addWidget(combo); vbox->addLayout(hlay); // Autorotate QString autorotate = m_properties.get("autorotate"); m_originalProperties.insert(QStringLiteral("autorotate"), autorotate); hlay = new QHBoxLayout; box = new QCheckBox(i18n("Disable autorotate"), this); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); box->setObjectName(QStringLiteral("autorotate")); box->setChecked(autorotate == QLatin1String("0")); hlay->addWidget(box); vbox->addLayout(hlay); // Decoding threads QString threads = m_properties.get("threads"); m_originalProperties.insert(QStringLiteral("threads"), threads); hlay = new QHBoxLayout; hlay->addWidget(new QLabel(i18n("Threads"))); auto *spinI = new QSpinBox(this); spinI->setMaximum(4); spinI->setObjectName(QStringLiteral("threads_value")); if (!threads.isEmpty()) { spinI->setValue(threads.toInt()); } else { spinI->setValue(1); } - connect(spinI, static_cast(&QSpinBox::valueChanged), - this, static_cast(&ClipPropertiesController::slotValueChanged)); + connect(spinI, static_cast(&QSpinBox::valueChanged), this, + static_cast(&ClipPropertiesController::slotValueChanged)); hlay->addWidget(spinI); vbox->addLayout(hlay); // Video index QString vix = m_properties.get("video_index"); m_originalProperties.insert(QStringLiteral("video_index"), vix); hlay = new QHBoxLayout; hlay->addWidget(new QLabel(i18n("Video index"))); spinI = new QSpinBox(this); spinI->setMaximum(m_clipProperties.value(QStringLiteral("video_max")).toInt()); spinI->setObjectName(QStringLiteral("video_index_value")); if (vix.isEmpty()) { spinI->setValue(0); } else { spinI->setValue(vix.toInt()); } - connect(spinI, static_cast(&QSpinBox::valueChanged), - this, static_cast(&ClipPropertiesController::slotValueChanged)); + connect(spinI, static_cast(&QSpinBox::valueChanged), this, + static_cast(&ClipPropertiesController::slotValueChanged)); hlay->addWidget(spinI); vbox->addLayout(hlay); // Audio index QString aix = m_properties.get("audio_index"); m_originalProperties.insert(QStringLiteral("audio_index"), aix); hlay = new QHBoxLayout; hlay->addWidget(new QLabel(i18n("Audio index"))); spinI = new QSpinBox(this); spinI->setMaximum(m_clipProperties.value(QStringLiteral("audio_max")).toInt()); spinI->setObjectName(QStringLiteral("audio_index_value")); if (aix.isEmpty()) { spinI->setValue(0); } else { spinI->setValue(aix.toInt()); } - connect(spinI, static_cast(&QSpinBox::valueChanged), - this, static_cast(&ClipPropertiesController::slotValueChanged)); + connect(spinI, static_cast(&QSpinBox::valueChanged), this, + static_cast(&ClipPropertiesController::slotValueChanged)); hlay->addWidget(spinI); vbox->addLayout(hlay); // Colorspace hlay = new QHBoxLayout; box = new QCheckBox(i18n("Colorspace"), this); box->setObjectName(QStringLiteral("force_colorspace")); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); combo = new QComboBox(this); combo->setObjectName(QStringLiteral("force_colorspace_value")); combo->addItem(ProfileRepository::getColorspaceDescription(601), 601); combo->addItem(ProfileRepository::getColorspaceDescription(709), 709); combo->addItem(ProfileRepository::getColorspaceDescription(240), 240); int force_colorspace = m_properties.get_int("force_colorspace"); m_originalProperties.insert(QStringLiteral("force_colorspace"), force_colorspace == 0 ? QStringLiteral("-") : QString::number(force_colorspace)); int colorspace = controller->videoCodecProperty(QStringLiteral("colorspace")).toInt(); if (force_colorspace > 0) { box->setChecked(true); combo->setEnabled(true); combo->setCurrentIndex(combo->findData(force_colorspace)); } else if (colorspace > 0) { combo->setEnabled(false); combo->setCurrentIndex(combo->findData(colorspace)); } else { combo->setEnabled(false); } connect(box, &QAbstractButton::toggled, combo, &QWidget::setEnabled); connect(combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotComboValueChanged())); hlay->addWidget(box); hlay->addWidget(combo); vbox->addLayout(hlay); // Full luma QString force_luma = m_properties.get("set.force_full_luma"); m_originalProperties.insert(QStringLiteral("set.force_full_luma"), force_luma); hlay = new QHBoxLayout; box = new QCheckBox(i18n("Full luma range"), this); connect(box, &QCheckBox::stateChanged, this, &ClipPropertiesController::slotEnableForce); box->setObjectName(QStringLiteral("set.force_full_luma")); box->setChecked(!force_luma.isEmpty()); hlay->addWidget(box); vbox->addLayout(hlay); } m_forcePage->setLayout(vbox); vbox->addStretch(10); m_tabWidget->addTab(m_propertiesPage, QString()); m_tabWidget->addTab(m_forcePage, QString()); m_tabWidget->addTab(m_markersPage, QString()); m_tabWidget->addTab(m_metaPage, QString()); m_tabWidget->addTab(m_analysisPage, QString()); m_tabWidget->setTabIcon(0, KoIconUtils::themedIcon(QStringLiteral("edit-find"))); m_tabWidget->setTabToolTip(0, i18n("Properties")); m_tabWidget->setTabIcon(1, KoIconUtils::themedIcon(QStringLiteral("document-edit"))); m_tabWidget->setTabToolTip(1, i18n("Force properties")); m_tabWidget->setTabIcon(2, KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); m_tabWidget->setTabToolTip(2, i18n("Markers")); m_tabWidget->setTabIcon(3, KoIconUtils::themedIcon(QStringLiteral("view-grid"))); m_tabWidget->setTabToolTip(3, i18n("Metadata")); m_tabWidget->setTabIcon(4, KoIconUtils::themedIcon(QStringLiteral("visibility"))); m_tabWidget->setTabToolTip(4, i18n("Analysis")); m_tabWidget->setCurrentIndex(KdenliveSettings::properties_panel_page()); if (m_type == ClipType::Color) { m_tabWidget->setTabEnabled(0, false); } connect(m_tabWidget, &QTabWidget::currentChanged, this, &ClipPropertiesController::updateTab); } -ClipPropertiesController::~ClipPropertiesController() -{ -} +ClipPropertiesController::~ClipPropertiesController() {} void ClipPropertiesController::updateTab(int ix) { KdenliveSettings::setProperties_panel_page(ix); } void ClipPropertiesController::slotRefreshTimeCode() { emit updateTimeCodeFormat(); } void ClipPropertiesController::slotReloadProperties() { mlt_color color; m_clipLabel->setText(m_properties.get("kdenlive:clipname")); switch (m_type) { case ClipType::Color: m_originalProperties.insert(QStringLiteral("resource"), m_properties.get("resource")); m_originalProperties.insert(QStringLiteral("out"), m_properties.get("out")); m_originalProperties.insert(QStringLiteral("length"), m_properties.get("length")); emit modified(m_properties.get_int("length")); color = m_properties.get_color("resource"); emit modified(QColor::fromRgb(color.r, color.g, color.b)); break; case ClipType::TextTemplate: m_textEdit->setPlainText(m_properties.get("templatetext")); break; default: break; } } void ClipPropertiesController::slotColorModified(const QColor &newcolor) { QMap properties; properties.insert(QStringLiteral("resource"), newcolor.name(QColor::HexArgb)); QMap oldProperties; oldProperties.insert(QStringLiteral("resource"), m_properties.get("resource")); emit updateClipProperties(m_id, oldProperties, properties); } void ClipPropertiesController::slotDurationChanged(int duration) { QMap properties; int original_length = m_properties.get_int("kdenlive:original_length"); // kdenlive_length is the default duration for image / title clips int kdenlive_length = m_properties.get_int("kdenlive:duration"); int current_length = m_properties.get_int("length"); if (original_length == 0) { m_properties.set("kdenlive:original_length", kdenlive_length > 0 ? kdenlive_length : current_length); } if (kdenlive_length > 0) { // special case, image/title clips store default duration in kdenlive:duration property properties.insert(QStringLiteral("kdenlive:duration"), QString::number(duration)); if (duration > current_length) { properties.insert(QStringLiteral("length"), QString::number(duration)); properties.insert(QStringLiteral("out"), QString::number(duration - 1)); } } else { properties.insert(QStringLiteral("length"), QString::number(duration)); properties.insert(QStringLiteral("out"), QString::number(duration - 1)); } emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::slotEnableForce(int state) { QCheckBox *box = qobject_cast(sender()); if (!box) { return; } QString param = box->objectName(); QMap properties; QLocale locale; if (state == Qt::Unchecked) { // The force property was disable, remove it / reset default if necessary if (param == QLatin1String("force_duration")) { // special case, reset original duration TimecodeDisplay *timePos = findChild(param + QStringLiteral("_value")); timePos->setValue(m_properties.get_int("kdenlive:original_length")); slotDurationChanged(m_properties.get_int("kdenlive:original_length")); m_properties.set("kdenlive:original_length", (char *)nullptr); return; } if (param == QLatin1String("kdenlive:transparency")) { properties.insert(param, QString()); } else if (param == QLatin1String("force_ar")) { properties.insert(QStringLiteral("force_aspect_den"), QString()); properties.insert(QStringLiteral("force_aspect_num"), QString()); properties.insert(QStringLiteral("force_aspect_ratio"), QString()); } else if (param == QLatin1String("autorotate")) { properties.insert(QStringLiteral("autorotate"), QString()); } else { properties.insert(param, QString()); } } else { // A force property was set if (param == QLatin1String("force_duration")) { int original_length = m_properties.get_int("kdenlive:original_length"); if (original_length == 0) { int kdenlive_duration = m_properties.get_int("kdenlive:duration"); m_properties.set("kdenlive:original_length", kdenlive_duration > 0 ? kdenlive_duration : m_properties.get_int("length")); } } else if (param == QLatin1String("force_fps")) { QDoubleSpinBox *spin = findChild(param + QStringLiteral("_value")); if (!spin) { return; } properties.insert(param, locale.toString(spin->value())); } else if (param == QLatin1String("threads")) { QSpinBox *spin = findChild(param + QStringLiteral("_value")); if (!spin) { return; } properties.insert(param, QString::number(spin->value())); } else if (param == QLatin1String("force_colorspace") || param == QLatin1String("force_progressive") || param == QLatin1String("force_tff")) { QComboBox *combo = findChild(param + QStringLiteral("_value")); if (!combo) { return; } properties.insert(param, QString::number(combo->currentData().toInt())); } else if (param == QLatin1String("kdenlive:transparency") || param == QLatin1String("set.force_full_luma")) { properties.insert(param, QStringLiteral("1")); } else if (param == QLatin1String("autorotate")) { properties.insert(QStringLiteral("autorotate"), QStringLiteral("0")); } else if (param == QLatin1String("force_ar")) { QSpinBox *spin = findChild(QStringLiteral("force_aspect_num_value")); QSpinBox *spin2 = findChild(QStringLiteral("force_aspect_den_value")); if ((spin == nullptr) || (spin2 == nullptr)) { return; } properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value())); properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value())); properties.insert(QStringLiteral("force_aspect_ratio"), locale.toString((double)spin->value() / spin2->value())); } } if (properties.isEmpty()) { return; } emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::slotValueChanged(double value) { QDoubleSpinBox *box = qobject_cast(sender()); if (!box) { return; } QString param = box->objectName().section(QLatin1Char('_'), 0, -2); QMap properties; QLocale locale; properties.insert(param, locale.toString(value)); emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::slotValueChanged(int value) { QSpinBox *box = qobject_cast(sender()); if (!box) { return; } QString param = box->objectName().section(QLatin1Char('_'), 0, -2); QMap properties; properties.insert(param, QString::number(value)); emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::slotAspectValueChanged(int) { QSpinBox *spin = findChild(QStringLiteral("force_aspect_num_value")); QSpinBox *spin2 = findChild(QStringLiteral("force_aspect_den_value")); if ((spin == nullptr) || (spin2 == nullptr)) { return; } QMap properties; properties.insert(QStringLiteral("force_aspect_den"), QString::number(spin2->value())); properties.insert(QStringLiteral("force_aspect_num"), QString::number(spin->value())); QLocale locale; properties.insert(QStringLiteral("force_aspect_ratio"), locale.toString((double)spin->value() / spin2->value())); emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::slotComboValueChanged() { QComboBox *box = qobject_cast(sender()); if (!box) { return; } QString param = box->objectName().section(QLatin1Char('_'), 0, -2); QMap properties; properties.insert(param, QString::number(box->currentData().toInt())); emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } void ClipPropertiesController::fillProperties() { m_clipProperties.clear(); QList propertyMap; m_propertiesTree->setSortingEnabled(false); #ifdef KF5_USE_FILEMETADATA // Read File Metadata through KDE's metadata system KFileMetaData::ExtractorCollection metaDataCollection; QMimeDatabase mimeDatabase; QMimeType mimeType; mimeType = mimeDatabase.mimeTypeForFile(m_controller->clipUrl()); for (KFileMetaData::Extractor *plugin : metaDataCollection.fetchExtractors(mimeType.name())) { ExtractionResult extractionResult(m_controller->clipUrl(), mimeType.name(), m_propertiesTree); plugin->extract(&extractionResult); } #endif // Get MLT's metadata if (m_type == ClipType::Image) { int width = m_controller->getProducerIntProperty(QStringLiteral("meta.media.width")); int height = m_controller->getProducerIntProperty(QStringLiteral("meta.media.height")); propertyMap.append(QStringList() << i18n("Image size") << QString::number(width) + QLatin1Char('x') + QString::number(height)); } if (m_type == ClipType::AV || m_type == ClipType::Video || m_type == ClipType::Audio) { int vindex = m_controller->getProducerIntProperty(QStringLiteral("video_index")); int video_max = 0; int default_audio = m_controller->getProducerIntProperty(QStringLiteral("audio_index")); int audio_max = 0; // Find maximum stream index values for (int ix = 0; ix < m_controller->getProducerIntProperty(QStringLiteral("meta.media.nb_streams")); ++ix) { char property[200]; snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix); QString type = m_controller->getProducerProperty(property); if (type == QLatin1String("video")) { video_max = ix; } else if (type == QLatin1String("audio")) { audio_max = ix; } } m_clipProperties.insert(QStringLiteral("default_video"), QString::number(vindex)); m_clipProperties.insert(QStringLiteral("video_max"), QString::number(video_max)); m_clipProperties.insert(QStringLiteral("default_audio"), QString::number(default_audio)); m_clipProperties.insert(QStringLiteral("audio_max"), QString::number(audio_max)); if (vindex > -1) { // We have a video stream char property[200]; snprintf(property, sizeof(property), "meta.media.%d.codec.long_name", vindex); QString codec = m_controller->getProducerProperty(property); if (!codec.isEmpty()) { propertyMap.append(QStringList() << i18n("Video codec") << codec); } int width = m_controller->getProducerIntProperty(QStringLiteral("meta.media.width")); int height = m_controller->getProducerIntProperty(QStringLiteral("meta.media.height")); propertyMap.append(QStringList() << i18n("Frame size") << QString::number(width) + QLatin1Char('x') + QString::number(height)); snprintf(property, sizeof(property), "meta.media.%d.stream.frame_rate", vindex); QString fpsValue = m_controller->getProducerProperty(property); if (!fpsValue.isEmpty()) { propertyMap.append(QStringList() << i18n("Frame rate") << fpsValue); } else { int rate_den = m_controller->getProducerIntProperty(QStringLiteral("meta.media.frame_rate_den")); if (rate_den > 0) { double fps = (double)m_controller->getProducerIntProperty(QStringLiteral("meta.media.frame_rate_num")) / rate_den; propertyMap.append(QStringList() << i18n("Frame rate") << QString::number(fps, 'f', 2)); } } snprintf(property, sizeof(property), "meta.media.%d.codec.bit_rate", vindex); int bitrate = m_controller->getProducerIntProperty(property) / 1000; if (bitrate > 0) { propertyMap.append(QStringList() << i18n("Video bitrate") << QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")); } int scan = m_controller->getProducerIntProperty(QStringLiteral("meta.media.progressive")); propertyMap.append(QStringList() << i18n("Scanning") << (scan == 1 ? i18n("Progressive") : i18n("Interlaced"))); snprintf(property, sizeof(property), "meta.media.%d.codec.sample_aspect_ratio", vindex); double par = m_controller->getProducerDoubleProperty(property); if (par == 0) { // Read media aspect ratio par = m_controller->getProducerDoubleProperty(QStringLiteral("aspect_ratio")); } propertyMap.append(QStringList() << i18n("Pixel aspect ratio") << QString::number(par, 'f', 3)); propertyMap.append(QStringList() << i18n("Pixel format") << m_controller->videoCodecProperty(QStringLiteral("pix_fmt"))); int colorspace = m_controller->videoCodecProperty(QStringLiteral("colorspace")).toInt(); propertyMap.append(QStringList() << i18n("Colorspace") << ProfileRepository::getColorspaceDescription(colorspace)); } if (default_audio > -1) { char property[200]; snprintf(property, sizeof(property), "meta.media.%d.codec.long_name", default_audio); QString codec = m_controller->getProducerProperty(property); if (!codec.isEmpty()) { propertyMap.append(QStringList() << i18n("Audio codec") << codec); } snprintf(property, sizeof(property), "meta.media.%d.codec.channels", default_audio); int channels = m_controller->getProducerIntProperty(property); propertyMap.append(QStringList() << i18n("Audio channels") << QString::number(channels)); snprintf(property, sizeof(property), "meta.media.%d.codec.sample_rate", default_audio); int srate = m_controller->getProducerIntProperty(property); propertyMap.append(QStringList() << i18n("Audio frequency") << QString::number(srate) + QLatin1Char(' ') + i18nc("Herz", "Hz")); snprintf(property, sizeof(property), "meta.media.%d.codec.bit_rate", default_audio); int bitrate = m_controller->getProducerIntProperty(property) / 1000; if (bitrate > 0) { propertyMap.append(QStringList() << i18n("Audio bitrate") << QString::number(bitrate) + QLatin1Char(' ') + i18nc("Kilobytes per seconds", "kb/s")); } } } qint64 filesize = m_controller->getProducerInt64Property(QStringLiteral("kdenlive:file_size")); if (filesize > 0) { QLocale locale(QLocale::system()); // use the user's locale for getting proper separators! propertyMap.append(QStringList() << i18n("File size") << KIO::convertSize((size_t)filesize) + QStringLiteral(" (") + locale.toString(filesize) + QLatin1Char(')')); } for (int i = 0; i < propertyMap.count(); i++) { new QTreeWidgetItem(m_propertiesTree, propertyMap.at(i)); } m_propertiesTree->setSortingEnabled(true); m_propertiesTree->resizeColumnToContents(0); } void ClipPropertiesController::slotSeekToMarker() { auto markerModel = m_controller->getMarkerModel(); auto current = m_markerTree->currentIndex(); if (!current.isValid()) return; GenTime pos(markerModel->data(current, MarkerListModel::PosRole).toDouble()); emit seekToFrame(pos.frames(pCore->getCurrentFps())); } void ClipPropertiesController::slotEditMarker() { auto markerModel = m_controller->getMarkerModel(); auto current = m_markerTree->currentIndex(); if (!current.isValid()) return; GenTime pos(markerModel->data(current, MarkerListModel::PosRole).toDouble()); markerModel->editMarkerGui(pos, this, false, m_controller); } void ClipPropertiesController::slotDeleteMarker() { auto markerModel = m_controller->getMarkerModel(); auto current = m_markerTree->currentIndex(); if (!current.isValid()) return; GenTime pos(markerModel->data(current, MarkerListModel::PosRole).toDouble()); markerModel->removeMarker(pos); } void ClipPropertiesController::slotAddMarker() { auto markerModel = m_controller->getMarkerModel(); GenTime pos(m_controller->originalProducer()->position(), m_tc.fps()); markerModel->editMarkerGui(pos, this, true, m_controller); } void ClipPropertiesController::slotSaveMarkers() { QScopedPointer fd(new QFileDialog(this, i18n("Save Clip Markers"), pCore->projectManager()->current()->projectDataFolder())); fd->setMimeTypeFilters(QStringList() << QStringLiteral("text/plain")); fd->setFileMode(QFileDialog::AnyFile); fd->setAcceptMode(QFileDialog::AcceptSave); if (fd->exec() != QDialog::Accepted) { return; } QStringList selection = fd->selectedFiles(); QString url; if (!selection.isEmpty()) { url = selection.first(); } if (url.isEmpty()) { return; } QFile file(url); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Cannot open file %1", QUrl::fromLocalFile(url).fileName())); return; } file.write(m_controller->getMarkerModel()->toJson().toUtf8()); file.close(); } void ClipPropertiesController::slotLoadMarkers() { QScopedPointer fd(new QFileDialog(this, i18n("Load Clip Markers"), pCore->projectManager()->current()->projectDataFolder())); fd->setMimeTypeFilters(QStringList() << QStringLiteral("text/plain")); fd->setFileMode(QFileDialog::ExistingFile); if (fd->exec() != QDialog::Accepted) { return; } QStringList selection = fd->selectedFiles(); QString url; if (!selection.isEmpty()) { url = selection.first(); } if (url.isEmpty()) { return; } QFile file(url); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { KMessageBox::error(this, i18n("Cannot open file %1", QUrl::fromLocalFile(url).fileName())); return; } QString fileContent = QString::fromUtf8(file.readAll()); file.close(); bool res = m_controller->getMarkerModel()->importFromJson(fileContent, false); if (!res) { KMessageBox::error(this, i18n("An error occurred while parsing the marker file")); } } void ClipPropertiesController::slotFillMeta(QTreeWidget *tree) { tree->clear(); if (m_type != ClipType::AV && m_type != ClipType::Video && m_type != ClipType::Image) { // Currently, we only use exiftool on video files return; } int exifUsed = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:exiftool")); if (exifUsed == 1) { Mlt::Properties subProperties; subProperties.pass_values(m_properties, "kdenlive:meta.exiftool."); if (subProperties.count() > 0) { QTreeWidgetItem *exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString()); exif->setExpanded(true); for (int i = 0; i < subProperties.count(); i++) { new QTreeWidgetItem(exif, QStringList() << subProperties.get_name(i) << subProperties.get(i)); } } } else if (KdenliveSettings::use_exiftool()) { QString url = m_controller->clipUrl(); // Check for Canon THM file url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".THM"); if (QFile::exists(url)) { // Read the exif metadata embedded in the THM file QProcess p; QStringList args; args << QStringLiteral("-g") << QStringLiteral("-args") << url; p.start(QStringLiteral("exiftool"), args); p.waitForFinished(); QString res = p.readAllStandardOutput(); m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1); QTreeWidgetItem *exif = nullptr; QStringList list = res.split(QLatin1Char('\n')); for (const QString &tagline : list) { if (tagline.startsWith(QLatin1String("-File")) || tagline.startsWith(QLatin1String("-ExifTool"))) { continue; } QString tag = tagline.section(QLatin1Char(':'), 1).simplified(); if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) { continue; } if (!tag.section(QLatin1Char('='), 0, 0).isEmpty() && !tag.section(QLatin1Char('='), 1).simplified().isEmpty()) { if (!exif) { exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString()); exif->setExpanded(true); } m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0), tag.section(QLatin1Char('='), 1).simplified()); new QTreeWidgetItem(exif, QStringList() << tag.section(QLatin1Char('='), 0, 0) << tag.section(QLatin1Char('='), 1).simplified()); } } } else { if (m_type == ClipType::Image || m_controller->codec(false) == QLatin1String("h264")) { QProcess p; QStringList args; args << QStringLiteral("-g") << QStringLiteral("-args") << m_controller->clipUrl(); p.start(QStringLiteral("exiftool"), args); p.waitForFinished(); QString res = p.readAllStandardOutput(); if (m_type != ClipType::Image) { m_controller->setProducerProperty(QStringLiteral("kdenlive:exiftool"), 1); } QTreeWidgetItem *exif = nullptr; QStringList list = res.split(QLatin1Char('\n')); for (const QString &tagline : list) { if (m_type != ClipType::Image && !tagline.startsWith(QLatin1String("-H264"))) { continue; } QString tag = tagline.section(QLatin1Char(':'), 1); if (tag.startsWith(QLatin1String("ImageWidth")) || tag.startsWith(QLatin1String("ImageHeight"))) { continue; } if (!exif) { exif = new QTreeWidgetItem(tree, QStringList() << i18n("Exif") << QString()); exif->setExpanded(true); } if (m_type != ClipType::Image) { // Do not store image exif metadata in project file, would be too much noise m_controller->setProducerProperty("kdenlive:meta.exiftool." + tag.section(QLatin1Char('='), 0, 0), tag.section(QLatin1Char('='), 1).simplified()); } new QTreeWidgetItem(exif, QStringList() << tag.section(QLatin1Char('='), 0, 0) << tag.section(QLatin1Char('='), 1).simplified()); } } } } int magic = m_controller->getProducerIntProperty(QStringLiteral("kdenlive:magiclantern")); if (magic == 1) { Mlt::Properties subProperties; subProperties.pass_values(m_properties, "kdenlive:meta.magiclantern."); QTreeWidgetItem *magicL = nullptr; for (int i = 0; i < subProperties.count(); i++) { if (!magicL) { magicL = new QTreeWidgetItem(tree, QStringList() << i18n("Magic Lantern") << QString()); QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png"))); magicL->setIcon(0, icon); magicL->setExpanded(true); } new QTreeWidgetItem(magicL, QStringList() << subProperties.get_name(i) << subProperties.get(i)); } } else if (m_type != ClipType::Image && KdenliveSettings::use_magicLantern()) { QString url = m_controller->clipUrl(); url = url.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".LOG"); if (QFile::exists(url)) { QFile file(url); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { m_controller->setProducerProperty(QStringLiteral("kdenlive:magiclantern"), 1); QTreeWidgetItem *magicL = nullptr; while (!file.atEnd()) { QString line = file.readLine().simplified(); if (line.startsWith('#') || line.isEmpty() || !line.contains(QLatin1Char(':'))) { continue; } if (line.startsWith(QLatin1String("CSV data"))) { break; } m_controller->setProducerProperty("kdenlive:meta.magiclantern." + line.section(QLatin1Char(':'), 0, 0).simplified(), line.section(QLatin1Char(':'), 1).simplified()); if (!magicL) { magicL = new QTreeWidgetItem(tree, QStringList() << i18n("Magic Lantern") << QString()); QIcon icon(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("meta_magiclantern.png"))); magicL->setIcon(0, icon); magicL->setExpanded(true); } - new QTreeWidgetItem(magicL, - QStringList() << line.section(QLatin1Char(':'), 0, 0).simplified() << line.section(QLatin1Char(':'), 1).simplified()); + new QTreeWidgetItem(magicL, QStringList() + << line.section(QLatin1Char(':'), 0, 0).simplified() << line.section(QLatin1Char(':'), 1).simplified()); } } } // if (!meta.isEmpty()) // clip->setMetadata(meta, "Magic Lantern"); // clip->setProperty("magiclantern", "1"); } tree->resizeColumnToContents(0); } void ClipPropertiesController::slotFillAnalysisData() { m_analysisTree->clear(); Mlt::Properties subProperties; subProperties.pass_values(m_properties, "kdenlive:clipanalysis."); if (subProperties.count() > 0) { for (int i = 0; i < subProperties.count(); i++) { new QTreeWidgetItem(m_analysisTree, QStringList() << subProperties.get_name(i) << subProperties.get(i)); } } m_analysisTree->resizeColumnToContents(0); } void ClipPropertiesController::slotDeleteAnalysis() { QTreeWidgetItem *current = m_analysisTree->currentItem(); if (!current) { return; } emit editAnalysis(m_id, "kdenlive:clipanalysis." + current->text(0), QString()); } void ClipPropertiesController::slotSaveAnalysis() { const QString url = QFileDialog::getSaveFileName(this, i18n("Save Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(), i18n("Text File (*.txt)")); if (url.isEmpty()) { return; } KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig); KConfigGroup analysisConfig(config, "Analysis"); QTreeWidgetItem *current = m_analysisTree->currentItem(); analysisConfig.writeEntry(current->text(0), current->text(1)); } void ClipPropertiesController::slotLoadAnalysis() { const QString url = QFileDialog::getOpenFileName(this, i18n("Open Analysis Data"), QFileInfo(m_controller->clipUrl()).absolutePath(), i18n("Text File (*.txt)")); if (url.isEmpty()) { return; } KSharedConfigPtr config = KSharedConfig::openConfig(url, KConfig::SimpleConfig); KConfigGroup transConfig(config, "Analysis"); // read the entries QMap profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); emit editAnalysis(m_id, "kdenlive:clipanalysis." + i.key(), i.value()); } } void ClipPropertiesController::slotTextChanged() { QMap properties; properties.insert(QStringLiteral("templatetext"), m_textEdit->toPlainText()); emit updateClipProperties(m_id, m_originalProperties, properties); m_originalProperties = properties; } diff --git a/src/mltcontroller/effectscontroller.h b/src/mltcontroller/effectscontroller.h index cd39aa819..39aedfe50 100644 --- a/src/mltcontroller/effectscontroller.h +++ b/src/mltcontroller/effectscontroller.h @@ -1,117 +1,117 @@ /* Copyright (C) 2015 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 . */ #ifndef EFFECTSCONTROLLER_H #define EFFECTSCONTROLLER_H #include "definitions.h" #include #include /**) * @class EffectInfo * @brief A class holding some meta info for effects widgets, like state (collapsed or not, ...) * @author Jean-Baptiste Mardelle */ class EffectInfo { public: EffectInfo(); bool isCollapsed; bool groupIsCollapsed; int groupIndex; QString groupName; QString toString() const; void fromString(const QString &value); }; /**) * @class EffectParameter * @brief Base class holding a parameter name / value. Is this really useful? QMap ? * @author Jean-Baptiste Mardelle */ class EffectParameter { public: EffectParameter(const QString &name, const QString &value); QString name() const; QString value() const; void setValue(const QString &value); private: QString m_name; QString m_value; }; /**) * @class EffectsParameterList * @brief Use our own list for effect parameters so that they are not sorted in any ways, because * some effects like sox need a precise order * @author Jean-Baptiste Mardelle */ class EffectsParameterList : public QList { public: EffectsParameterList(); bool hasParam(const QString &name) const; QString paramValue(const QString &name, const QString &defaultValue = QString()) const; void addParam(const QString &name, const QString &value); void removeParam(const QString &name); }; /** * @namespace EffectsController * @brief Provides convenience methods to manage effects and convert between MLT's Filter format and Kdenlive's internal formats (xml or lists). */ namespace EffectsController { /** @brief Gets the effect parameters that will be passed to Mlt. */ EffectsParameterList getEffectArgs(const QDomElement &effect); /** @brief Get effect parameters ready for MLT*/ void adjustEffectParameters(EffectsParameterList ¶meters, const QDomNodeList ¶ms, const QString &prefix = QString()); /** @brief Returns an value from a string by replacing "%width" and "%height" with given profile values: * @param info The struct that gives width & height * @param eval The string to be evaluated, for example: "%width / 2" * @return the evaluated value */ double getStringEval(QString eval, const QPoint &frameSize = QPoint()); QString getStringRectEval(QString eval); /** @brief Initialize some track effects parameters */ void initTrackEffect(const QDomElement &effect); /** @brief Initialize some effects parameters: keyframes, fades, in / out points */ void initEffect(const ItemInfo &info, const EffectsList &list, const QString &proxy, QDomElement effect, int diff = 0, int offset = 0); /** @brief Adjust keyframes to the new clip. */ const QString adjustKeyframes(const QString &keyframes, int oldIn, int newIn, int newEnd); EffectsParameterList addEffect(const QDomElement &effect); /** @brief Keyframe frame numbers are relative to clip's crop start. So when saving an effect, remove clip cropstart from keyframe numbers so that when we apply * effect on another clip, frame numbers are applied relative to the new clip's crop start. */ void offsetKeyframes(int in, const QDomElement &effect); -} +} // namespace EffectsController #endif diff --git a/src/monitor/abstractmonitor.cpp b/src/monitor/abstractmonitor.cpp index 18ee6530b..b5ccfe7df 100644 --- a/src/monitor/abstractmonitor.cpp +++ b/src/monitor/abstractmonitor.cpp @@ -1,44 +1,42 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "abstractmonitor.h" #include "monitormanager.h" #include "kdenlivesettings.h" AbstractMonitor::AbstractMonitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent) : QWidget(parent) , m_id(id) , m_monitorManager(manager) { } -AbstractMonitor::~AbstractMonitor() -{ -} +AbstractMonitor::~AbstractMonitor() {} bool AbstractMonitor::isActive() const { return m_monitorManager->isActive(m_id); } bool AbstractMonitor::slotActivateMonitor() { return m_monitorManager->activateMonitor(m_id); } diff --git a/src/monitor/glwidget.cpp b/src/monitor/glwidget.cpp index 1b6d46b30..8ee362720 100644 --- a/src/monitor/glwidget.cpp +++ b/src/monitor/glwidget.cpp @@ -1,1873 +1,1872 @@ /* * Copyright (c) 2011-2016 Meltytech, LLC * Original author: Dan Dennedy * Modified for Kdenlive: Jean-Baptiste Mardelle * * GL shader based on BSD licensed code from Peter Bengtsson: * http://www.fourcc.org/source/YUV420P-OpenGL-GLSLang.c * * 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 3 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, see . */ #include #include #include #include #include #include #include #include "core.h" #include "glwidget.h" #include "kdenlivesettings.h" #include "mltcontroller/bincontroller.h" #include "profiles/profilemodel.hpp" #include "qml/qmlaudiothumb.h" #include "timeline2/view/qml/timelineitems.h" #include #ifndef GL_UNPACK_ROW_LENGTH #ifdef GL_UNPACK_ROW_LENGTH_EXT #define GL_UNPACK_ROW_LENGTH GL_UNPACK_ROW_LENGTH_EXT #else #error GL_UNPACK_ROW_LENGTH undefined #endif #endif #ifdef QT_NO_DEBUG #define check_error(fn) \ { \ } #else #define check_error(fn) \ { \ - uint err = fn->glGetError(); \ + uint err = fn->glGetError(); \ if (err != GL_NO_ERROR) { \ qCCritical(KDENLIVE_LOG) << "GL error" << hex << err << dec << "at" << __FILE__ << ":" << __LINE__; \ } \ } #endif #ifndef GL_TIMEOUT_IGNORED #define GL_TIMEOUT_IGNORED 0xFFFFFFFFFFFFFFFFull #endif #ifndef Q_OS_WIN using ClientWaitSync_fp = GLenum (*)(GLsync, GLbitfield, GLuint64); static ClientWaitSync_fp ClientWaitSync = nullptr; #endif using namespace Mlt; #define SEEK_INACTIVE (-1) GLWidget::GLWidget(int id, QObject *parent) : QQuickView((QWindow *)parent) , sendFrameForAnalysis(false) , m_glslManager(nullptr) , m_consumer(nullptr) , m_producer(nullptr) , m_id(id) , m_shader(nullptr) , m_initSem(0) , m_analyseSem(1) , m_isInitialized(false) , m_threadStartEvent(nullptr) , m_threadStopEvent(nullptr) , m_threadCreateEvent(nullptr) , m_threadJoinEvent(nullptr) , m_displayEvent(nullptr) , m_frameRenderer(nullptr) , m_projectionLocation(0) , m_modelViewLocation(0) , m_vertexLocation(0) , m_texCoordLocation(0) , m_colorspaceLocation(0) , m_zoom(1.0f) , m_openGLSync(false) , m_sendFrame(false) , m_isZoneMode(false) , m_isLoopMode(false) , m_offset(QPoint(0, 0)) , m_shareContext(nullptr) , m_audioWaveDisplayed(false) , m_fbo(nullptr) { m_texture[0] = m_texture[1] = m_texture[2] = 0; qRegisterMetaType("Mlt::Frame"); qRegisterMetaType("SharedFrame"); qmlRegisterType("AudioThumb", 1, 0, "QmlAudioThumb"); setPersistentOpenGLContext(true); setPersistentSceneGraph(true); setClearBeforeRendering(false); setResizeMode(QQuickView::SizeRootObjectToView); m_monitorProfile = new Mlt::Profile(); m_refreshTimer.setSingleShot(true); m_refreshTimer.setInterval(50); m_blackClip.reset(new Mlt::Producer(*m_monitorProfile, "color:black")); m_blackClip->set("kdenlive:id", "black"); m_blackClip->set("out", 3); connect(&m_refreshTimer, &QTimer::timeout, this, &GLWidget::refresh); m_producer = &*m_blackClip; if (KdenliveSettings::gpu_accel()) { m_glslManager = new Mlt::Filter(*m_monitorProfile, "glsl.manager"); } if (((m_glslManager != nullptr) && !m_glslManager->is_valid())) { delete m_glslManager; m_glslManager = nullptr; KdenliveSettings::setGpu_accel(false); // Need to destroy MLT global reference to prevent filters from trying to use GPU. mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr); emit gpuNotSupported(); } connect(this, &QQuickWindow::sceneGraphInitialized, this, &GLWidget::initializeGL, Qt::DirectConnection); connect(this, &QQuickWindow::beforeRendering, this, &GLWidget::paintGL, Qt::DirectConnection); registerTimelineItems(); m_proxy = new MonitorProxy(this); connect(m_proxy, &MonitorProxy::seekRequestChanged, this, &GLWidget::requestSeek); rootContext()->setContextProperty("controller", m_proxy); } GLWidget::~GLWidget() { delete m_glslManager; delete m_threadStartEvent; delete m_threadStopEvent; delete m_threadCreateEvent; delete m_threadJoinEvent; delete m_displayEvent; if (m_frameRenderer) { if (m_frameRenderer->isRunning()) { QMetaObject::invokeMethod(m_frameRenderer, "cleanup"); m_frameRenderer->quit(); m_frameRenderer->wait(); m_frameRenderer->deleteLater(); } else { delete m_frameRenderer; } } m_blackClip.reset(); delete m_shareContext; delete m_shader; delete m_monitorProfile; } void GLWidget::updateAudioForAnalysis() { if (m_frameRenderer) { m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio(); } } void GLWidget::initializeGL() { if (m_isInitialized || !isVisible() || (openglContext() == nullptr)) return; if (!m_offscreenSurface.isValid()) { m_offscreenSurface.setFormat(openglContext()->format()); m_offscreenSurface.create(); openglContext()->makeCurrent(this); } initializeOpenGLFunctions(); qCDebug(KDENLIVE_LOG) << "OpenGL vendor: " << QString::fromUtf8((const char *)glGetString(GL_VENDOR)); qCDebug(KDENLIVE_LOG) << "OpenGL renderer: " << QString::fromUtf8((const char *)glGetString(GL_RENDERER)); qCDebug(KDENLIVE_LOG) << "OpenGL Threaded: " << openglContext()->supportsThreadedOpenGL(); qCDebug(KDENLIVE_LOG) << "OpenGL ARG_SYNC: " << openglContext()->hasExtension("GL_ARB_sync"); qCDebug(KDENLIVE_LOG) << "OpenGL OpenGLES: " << openglContext()->isOpenGLES(); if ((m_glslManager != nullptr) && openglContext()->isOpenGLES()) { delete m_glslManager; m_glslManager = nullptr; KdenliveSettings::setGpu_accel(false); // Need to destroy MLT global reference to prevent filters from trying to use GPU. mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr); emit gpuNotSupported(); } createShader(); #if !defined(Q_OS_WIN) // getProcAddress is not working for me on Windows. if (KdenliveSettings::gpu_accel()) { m_openGLSync = false; if ((m_glslManager != nullptr) && openglContext()->hasExtension("GL_ARB_sync")) { ClientWaitSync = (ClientWaitSync_fp)openglContext()->getProcAddress("glClientWaitSync"); if (ClientWaitSync) { m_openGLSync = true; } else { qCDebug(KDENLIVE_LOG) << " / / // NO GL SYNC, ERROR"; emit gpuNotSupported(); delete m_glslManager; m_glslManager = nullptr; } } } #endif openglContext()->doneCurrent(); if (m_glslManager) { // Create a context sharing with this context for the RenderThread context. // This is needed because openglContext() is active in another thread // at the time that RenderThread is created. // See this Qt bug for more info: https://bugreports.qt.io/browse/QTBUG-44677 m_shareContext = new QOpenGLContext; m_shareContext->setFormat(openglContext()->format()); m_shareContext->setShareContext(openglContext()); m_shareContext->create(); } m_frameRenderer = new FrameRenderer(openglContext(), &m_offscreenSurface); m_frameRenderer->sendAudioForAnalysis = KdenliveSettings::monitor_audio(); openglContext()->makeCurrent(this); // openglContext()->blockSignals(false); connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::frameDisplayed, Qt::QueuedConnection); connect(m_frameRenderer, &FrameRenderer::textureReady, this, &GLWidget::updateTexture, Qt::DirectConnection); connect(m_frameRenderer, &FrameRenderer::frameDisplayed, this, &GLWidget::onFrameDisplayed, Qt::QueuedConnection); connect(m_frameRenderer, &FrameRenderer::audioSamplesSignal, this, &GLWidget::audioSamplesSignal, Qt::QueuedConnection); connect(this, &GLWidget::textureUpdated, this, &GLWidget::update, Qt::QueuedConnection); m_initSem.release(); m_isInitialized = true; reconfigure(); } void GLWidget::resizeGL(int width, int height) { int x, y, w, h; height -= m_proxy->rulerHeight(); double this_aspect = (double)width / height; double video_aspect = m_monitorProfile->dar(); // Special case optimisation to negate odd effect of sample aspect ratio // not corresponding exactly with image resolution. if ((int)(this_aspect * 1000) == (int)(video_aspect * 1000)) { w = width; h = height; } // Use OpenGL to normalise sample aspect ratio else if (height * video_aspect > width) { w = width; h = width / video_aspect; } else { w = height * video_aspect; h = height; } x = (width - w) / 2; y = (height - h) / 2; m_rect.setRect(x, y, w, h); double scalex = (double)m_rect.width() / m_monitorProfile->width() * m_zoom; double scaley = (double)m_rect.width() / ((double)m_monitorProfile->height() * m_monitorProfile->dar() / m_monitorProfile->width()) / m_monitorProfile->width() * m_zoom; QPoint center = m_rect.center(); QQuickItem *rootQml = rootObject(); if (rootQml) { rootQml->setProperty("center", center); rootQml->setProperty("scalex", scalex); rootQml->setProperty("scaley", scaley); if (rootQml->objectName() == QLatin1String("rootsplit")) { // Adjust splitter pos rootQml->setProperty("splitterPos", x + (rootQml->property("realpercent").toDouble() * w)); } } emit rectChanged(); } void GLWidget::resizeEvent(QResizeEvent *event) { QQuickView::resizeEvent(event); resizeGL(event->size().width(), event->size().height()); } void GLWidget::createShader() { m_shader = new QOpenGLShaderProgram; m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, "uniform highp mat4 projection;" "uniform highp mat4 modelView;" "attribute highp vec4 vertex;" "attribute highp vec2 texCoord;" "varying highp vec2 coordinates;" "void main(void) {" " gl_Position = projection * modelView * vertex;" " coordinates = texCoord;" "}"); if (m_glslManager) { m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D tex;" "varying highp vec2 coordinates;" "void main(void) {" " gl_FragColor = texture2D(tex, coordinates);" "}"); m_shader->link(); m_textureLocation[0] = m_shader->uniformLocation("tex"); } else { m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, "uniform sampler2D Ytex, Utex, Vtex;" "uniform lowp int colorspace;" "varying highp vec2 coordinates;" "void main(void) {" " mediump vec3 texel;" " texel.r = texture2D(Ytex, coordinates).r - 0.0625;" // Y " texel.g = texture2D(Utex, coordinates).r - 0.5;" // U " texel.b = texture2D(Vtex, coordinates).r - 0.5;" // V " mediump mat3 coefficients;" " if (colorspace == 601) {" " coefficients = mat3(" " 1.1643, 1.1643, 1.1643," // column 1 " 0.0, -0.39173, 2.017," // column 2 " 1.5958, -0.8129, 0.0);" // column 3 " } else {" // ITU-R 709 " coefficients = mat3(" " 1.1643, 1.1643, 1.1643," // column 1 " 0.0, -0.213, 2.112," // column 2 " 1.793, -0.533, 0.0);" // column 3 " }" " gl_FragColor = vec4(coefficients * texel, 1.0);" "}"); m_shader->link(); m_textureLocation[0] = m_shader->uniformLocation("Ytex"); m_textureLocation[1] = m_shader->uniformLocation("Utex"); m_textureLocation[2] = m_shader->uniformLocation("Vtex"); m_colorspaceLocation = m_shader->uniformLocation("colorspace"); } m_projectionLocation = m_shader->uniformLocation("projection"); m_modelViewLocation = m_shader->uniformLocation("modelView"); m_vertexLocation = m_shader->attributeLocation("vertex"); m_texCoordLocation = m_shader->attributeLocation("texCoord"); } static void uploadTextures(QOpenGLContext *context, const SharedFrame &frame, GLuint texture[]) { int width = frame.get_image_width(); int height = frame.get_image_height(); const uint8_t *image = frame.get_image(); QOpenGLFunctions *f = context->functions(); // Upload each plane of YUV to a texture. if (texture[0] != 0u) { f->glDeleteTextures(3, texture); } check_error(f); f->glGenTextures(3, texture); check_error(f); f->glBindTexture(GL_TEXTURE_2D, texture[0]); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(f); f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image); check_error(f); f->glBindTexture(GL_TEXTURE_2D, texture[1]); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(f); f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height); check_error(f); f->glBindTexture(GL_TEXTURE_2D, texture[2]); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); check_error(f); f->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); check_error(f); f->glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width / 2, height / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, image + width * height + width / 2 * height / 2); check_error(f); } void GLWidget::clear() { stopGlsl(); update(); } void GLWidget::releaseAnalyse() { m_analyseSem.release(); } void GLWidget::paintGL() { QOpenGLFunctions *f = openglContext()->functions(); int width = this->width() * devicePixelRatio(); int height = this->height() * devicePixelRatio(); f->glDisable(GL_BLEND); f->glDisable(GL_DEPTH_TEST); f->glDepthMask(GL_FALSE); f->glViewport(0, 0, width, height); check_error(f); QColor color(KdenliveSettings::window_background()); f->glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF()); f->glClear(GL_COLOR_BUFFER_BIT); check_error(f); if (!((m_glslManager != nullptr) || openglContext()->supportsThreadedOpenGL())) { m_mutex.lock(); if (!m_sharedFrame.is_valid()) { m_mutex.unlock(); return; } uploadTextures(openglContext(), m_sharedFrame, m_texture); m_mutex.unlock(); } else if (m_glslManager) { m_mutex.lock(); if (m_sharedFrame.is_valid()) { m_texture[0] = *((const GLuint *)m_sharedFrame.get_image()); } } if (!m_texture[0]) { if (m_glslManager) m_mutex.unlock(); return; } // Bind textures. for (uint i = 0; i < 3; ++i) { if (m_texture[i] != 0u) { f->glActiveTexture(GL_TEXTURE0 + i); f->glBindTexture(GL_TEXTURE_2D, m_texture[i]); check_error(f); } } // Init shader program. m_shader->bind(); if (m_glslManager) { m_shader->setUniformValue(m_textureLocation[0], 0); } else { m_shader->setUniformValue(m_textureLocation[0], 0); m_shader->setUniformValue(m_textureLocation[1], 1); m_shader->setUniformValue(m_textureLocation[2], 2); m_shader->setUniformValue(m_colorspaceLocation, m_monitorProfile->colorspace()); } check_error(f); // Setup an orthographic projection. QMatrix4x4 projection; projection.scale(2.0f / (float)width, 2.0f / (float)height); m_shader->setUniformValue(m_projectionLocation, projection); check_error(f); // Set model view. QMatrix4x4 modelView; if (!qFuzzyCompare(m_zoom, 1.0f)) { if ((offset().x() != 0) || (offset().y() != 0)) modelView.translate(-offset().x() * devicePixelRatio(), offset().y() * devicePixelRatio()); modelView.scale(zoom(), zoom()); } m_shader->setUniformValue(m_modelViewLocation, modelView); check_error(f); // Provide vertices of triangle strip. QVector vertices; width = m_rect.width() * devicePixelRatio(); height = m_rect.height() * devicePixelRatio(); vertices << QVector2D(float(-width) / 2.0f, float(-height) / 2.0f + m_proxy->rulerHeight()); vertices << QVector2D(float(-width) / 2.0f, float(height) / 2.0f + m_proxy->rulerHeight()); vertices << QVector2D(float(width) / 2.0f, float(-height) / 2.0f + m_proxy->rulerHeight()); vertices << QVector2D(float(width) / 2.0f, float(height) / 2.0f + m_proxy->rulerHeight()); m_shader->enableAttributeArray(m_vertexLocation); check_error(f); m_shader->setAttributeArray(m_vertexLocation, vertices.constData()); check_error(f); // Provide texture coordinates. QVector texCoord; texCoord << QVector2D(0.0f, 1.0f); texCoord << QVector2D(0.0f, 0.0f); texCoord << QVector2D(1.0f, 1.0f); texCoord << QVector2D(1.0f, 0.0f); m_shader->enableAttributeArray(m_texCoordLocation); check_error(f); m_shader->setAttributeArray(m_texCoordLocation, texCoord.constData()); check_error(f); // Render f->glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size()); check_error(f); if (m_sendFrame && m_analyseSem.tryAcquire(1)) { // Render RGB frame for analysis int fullWidth = m_monitorProfile->width(); int fullHeight = m_monitorProfile->height(); if ((m_fbo == nullptr) || m_fbo->size() != QSize(fullWidth, fullHeight)) { delete m_fbo; QOpenGLFramebufferObjectFormat fmt; fmt.setSamples(1); fmt.setInternalTextureFormat(GL_RGB); // GL_RGBA32F); // which one is the fastest ? m_fbo = new QOpenGLFramebufferObject(fullWidth, fullHeight, fmt); // GL_TEXTURE_2D); } m_fbo->bind(); f->glViewport(0, 0, fullWidth, fullHeight); QMatrix4x4 projection2; projection2.scale(2.0f / (float)width, 2.0f / (float)height); m_shader->setUniformValue(m_projectionLocation, projection2); f->glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size()); check_error(f); m_fbo->release(); emit analyseFrame(m_fbo->toImage()); m_sendFrame = false; } // Cleanup m_shader->disableAttributeArray(m_vertexLocation); m_shader->disableAttributeArray(m_texCoordLocation); m_shader->release(); for (uint i = 0; i < 3; ++i) { if (m_texture[i] != 0u) { f->glActiveTexture(GL_TEXTURE0 + i); f->glBindTexture(GL_TEXTURE_2D, 0); check_error(f); } } f->glActiveTexture(GL_TEXTURE0); check_error(f); if (m_glslManager) { glFinish(); check_error(f); m_mutex.unlock(); } } void GLWidget::slotZoom(bool zoomIn) { if (zoomIn) { if (qFuzzyCompare(m_zoom, 1.0f)) { - setZoom(2.0f); - } else if (qFuzzyCompare(m_zoom, 2.0f)) { - setZoom(3.0f); - } else if (m_zoom < 1.0f) { - setZoom(m_zoom * 2); - } + setZoom(2.0f); + } else if (qFuzzyCompare(m_zoom, 2.0f)) { + setZoom(3.0f); + } else if (m_zoom < 1.0f) { + setZoom(m_zoom * 2); + } } else { if (qFuzzyCompare(m_zoom, 3.0f)) { setZoom(2.0); } else if (qFuzzyCompare(m_zoom, 2.0f)) { setZoom(1.0); } else if (m_zoom > 0.2) { setZoom(m_zoom / 2); } } } void GLWidget::wheelEvent(QWheelEvent *event) { if (((event->modifiers() & Qt::ControlModifier) != 0u) && ((event->modifiers() & Qt::ShiftModifier) != 0u)) { slotZoom(event->delta() > 0); return; } emit mouseSeek(event->delta(), (uint)event->modifiers()); event->accept(); } void GLWidget::requestSeek() { if (m_producer == nullptr) { return; } if (m_proxy->seekPosition() != SEEK_INACTIVE) { if (!qFuzzyIsNull(m_producer->get_speed())) { m_consumer->purge(); } m_producer->seek(m_proxy->seekPosition()); if (m_consumer->is_stopped()) { m_consumer->start(); } m_consumer->set("refresh", 1); } } void GLWidget::seek(int pos) { // Testing puspose only if (m_proxy->seekPosition() == SEEK_INACTIVE) { m_proxy->setSeekPosition(pos); if (!qFuzzyIsNull(m_producer->get_speed())) { m_consumer->purge(); } m_producer->seek(pos); if (m_consumer->is_stopped()) { m_consumer->start(); } m_consumer->set("refresh", 1); } else { m_proxy->setSeekPosition(pos); } } void GLWidget::requestRefresh() { if ((m_producer != nullptr) && qFuzzyIsNull(m_producer->get_speed())) { m_refreshTimer.start(); } } void GLWidget::refresh() { m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); if (m_consumer->is_stopped()) { m_consumer->start(); } m_consumer->set("refresh", 1); } bool GLWidget::checkFrameNumber(int pos) { emit seekPosition(pos); - //TODO: cleanup and move logic to proper proxy class + // TODO: cleanup and move logic to proper proxy class m_proxy->setPosition(pos); if (pos == m_proxy->seekPosition()) { m_proxy->setSeekPosition(SEEK_INACTIVE); return true; } const double speed = m_producer->get_speed(); if (m_proxy->seekPosition() != SEEK_INACTIVE) { m_producer->set_speed(0); m_producer->seek(m_proxy->seekPosition()); if (qFuzzyIsNull(speed)) { m_consumer->set("refresh", 1); } else { m_producer->set_speed(speed); } } else if (qFuzzyIsNull(speed)) { if (m_isLoopMode) { if (pos >= m_producer->get_int("out") - 1) { m_consumer->purge(); m_producer->seek(m_proxy->zoneIn()); m_producer->set_speed(1.0); m_consumer->set("refresh", 1); } return true; } else { return pos < m_producer->get_int("out") - 1.; } } else if (speed < 0. && pos <= 0) { m_producer->set_speed(0); return false; } return true; } void GLWidget::mousePressEvent(QMouseEvent *event) { if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) && !(event->buttons() & Qt::MiddleButton)) { event->ignore(); QQuickView::mousePressEvent(event); return; } if ((event->button() & Qt::LeftButton) != 0u) { if ((event->modifiers() & Qt::ControlModifier) != 0u) { // Pan view m_panStart = event->pos(); setCursor(Qt::ClosedHandCursor); } else { m_dragStart = event->pos(); } } else if ((event->button() & Qt::RightButton) != 0u) { emit showContextMenu(event->globalPos()); } else if ((event->button() & Qt::MiddleButton) != 0u) { m_panStart = event->pos(); setCursor(Qt::ClosedHandCursor); } event->accept(); QQuickView::mousePressEvent(event); } void GLWidget::mouseMoveEvent(QMouseEvent *event) { if ((rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier) && !(event->buttons() & Qt::MiddleButton)) { event->ignore(); QQuickView::mouseMoveEvent(event); return; } /* if (event->modifiers() == Qt::ShiftModifier && m_producer) { emit seekTo(m_producer->get_length() * event->x() / width()); return; }*/ QQuickView::mouseMoveEvent(event); if (!m_panStart.isNull()) { emit panView(m_panStart - event->pos()); m_panStart = event->pos(); event->accept(); QQuickView::mouseMoveEvent(event); return; } if (!(event->buttons() & Qt::LeftButton)) { QQuickView::mouseMoveEvent(event); return; } if (!event->isAccepted() && !m_dragStart.isNull() && (event->pos() - m_dragStart).manhattanLength() >= QApplication::startDragDistance()) { m_dragStart = QPoint(); emit startDrag(); } } void GLWidget::keyPressEvent(QKeyEvent *event) { QQuickView::keyPressEvent(event); if (!event->isAccepted()) { emit passKeyEvent(event); } } void GLWidget::createThread(RenderThread **thread, thread_function_t function, void *data) { #ifdef Q_OS_WIN // On Windows, MLT event consumer-thread-create is fired from the Qt main thread. while (!m_isInitialized) { qApp->processEvents(); } #else if (!m_isInitialized) { m_initSem.acquire(); } #endif (*thread) = new RenderThread(function, data, m_shareContext, &m_offscreenSurface); (*thread)->start(); } static void onThreadCreate(mlt_properties owner, GLWidget *self, RenderThread **thread, int *priority, thread_function_t function, void *data) { Q_UNUSED(owner) Q_UNUSED(priority) // self->clearFrameRenderer(); self->createThread(thread, function, data); self->lockMonitor(); } static void onThreadJoin(mlt_properties owner, GLWidget *self, RenderThread *thread) { Q_UNUSED(owner) if (thread) { thread->quit(); thread->wait(); delete thread; // self->clearFrameRenderer(); self->releaseMonitor(); } } void GLWidget::startGlsl() { if (m_glslManager) { // clearFrameRenderer(); m_glslManager->fire_event("init glsl"); if (m_glslManager->get_int("glsl_supported") == 0) { delete m_glslManager; m_glslManager = nullptr; // Need to destroy MLT global reference to prevent filters from trying to use GPU. mlt_properties_set_data(mlt_global_properties(), "glslManager", nullptr, 0, nullptr, nullptr); emit gpuNotSupported(); } else { emit started(); } } } static void onThreadStarted(mlt_properties owner, GLWidget *self) { Q_UNUSED(owner) self->startGlsl(); } void GLWidget::releaseMonitor() { emit lockMonitor(false); } void GLWidget::lockMonitor() { emit lockMonitor(true); } void GLWidget::stopGlsl() { if (m_consumer) { m_consumer->purge(); } // TODO This is commented out for now because it is causing crashes. // Technically, this should be the correct thing to do, but it appears // some changes have created regression (see shotcut) // with respect to restarting the consumer in GPU mode. // m_glslManager->fire_event("close glsl"); m_texture[0] = 0; } static void onThreadStopped(mlt_properties owner, GLWidget *self) { Q_UNUSED(owner) self->stopGlsl(); } void GLWidget::slotSwitchAudioOverlay(bool enable) { KdenliveSettings::setDisplayAudioOverlay(enable); if (m_audioWaveDisplayed && !enable) { if ((m_producer != nullptr) && m_producer->get_int("video_index") != -1) { // We have a video producer, disable filter removeAudioOverlay(); } } if (enable && !m_audioWaveDisplayed && m_producer != nullptr) { createAudioOverlay(m_producer->get_int("video_index") == -1); } } int GLWidget::setProducer(Mlt::Producer *producer, bool isActive, int position) { int error = 0; QString currentId; int consumerPosition = 0; currentId = m_producer->parent().get("kdenlive:id"); if (producer != nullptr) { m_producer = producer; } else { if (currentId == QLatin1String("black")) { return 0; } if (m_audioWaveDisplayed) { removeAudioOverlay(); } m_producer = &*m_blackClip; } if (m_producer) { m_producer->set_speed(0); if (m_consumer) { consumerPosition = m_consumer->position(); if (!m_consumer->is_stopped()) { m_consumer->stop(); } } error = reconfigure(); if (error == 0) { // The profile display aspect ratio may have changed. resizeGL(width(), height()); } } else { return error; } if (!m_consumer) { return error; } consumerPosition = m_consumer->position(); if (m_producer->get_int("video_index") == -1) { // This is an audio only clip, attach visualization filter. Currently, the filter crashes MLT when Movit accel is used if (!m_audioWaveDisplayed) { createAudioOverlay(true); } else if (m_consumer) { if (KdenliveSettings::gpu_accel()) { removeAudioOverlay(); } else { adjustAudioOverlay(true); } } } else if (m_audioWaveDisplayed && (m_consumer != nullptr)) { // This is not an audio clip, hide wave if (KdenliveSettings::displayAudioOverlay()) { adjustAudioOverlay(m_producer->get_int("video_index") == -1); } else { removeAudioOverlay(); } } else if (KdenliveSettings::displayAudioOverlay()) { createAudioOverlay(false); } if (position == -1 && m_producer->parent().get("kdenlive:id") == currentId) { position = consumerPosition; } if (position != -1) { m_producer->seek(position); } if (isActive) { startConsumer(); } // emit durationChanged(m_producer->get_length() - 1, m_producer->get_in()); m_proxy->setPosition(m_producer->position()); return error; } int GLWidget::droppedFrames() const { return (m_consumer ? m_consumer->get_int("drop_count") : 0); } void GLWidget::resetDrops() { if (m_consumer) { m_consumer->set("drop_count", 0); } } void GLWidget::createAudioOverlay(bool isAudio) { if (!m_consumer) { return; } if (isAudio && KdenliveSettings::gpu_accel()) { // Audiowaveform filter crashes on Movit + audio clips) return; } Mlt::Filter f(*m_monitorProfile, "audiowaveform"); if (f.is_valid()) { // f.set("show_channel", 1); f.set("color.1", "0xffff0099"); f.set("fill", 1); if (isAudio) { // Fill screen f.set("rect", "0,0,100%,100%"); } else { // Overlay on lower part of the screen f.set("rect", "0,80%,100%,20%"); } m_consumer->attach(f); m_audioWaveDisplayed = true; } } void GLWidget::removeAudioOverlay() { Mlt::Service sourceService(m_consumer->get_service()); // move all effects to the correct producer int ct = 0; Mlt::Filter *filter = sourceService.filter(ct); while (filter != nullptr) { QString srv = filter->get("mlt_service"); if (srv == QLatin1String("audiowaveform")) { sourceService.detach(*filter); delete filter; break; } else { ct++; } filter = sourceService.filter(ct); } m_audioWaveDisplayed = false; } void GLWidget::adjustAudioOverlay(bool isAudio) { Mlt::Service sourceService(m_consumer->get_service()); // move all effects to the correct producer int ct = 0; Mlt::Filter *filter = sourceService.filter(ct); while (filter != nullptr) { QString srv = filter->get("mlt_service"); if (srv == QLatin1String("audiowaveform")) { if (isAudio) { filter->set("rect", "0,0,100%,100%"); } else { filter->set("rect", "0,80%,100%,20%"); } break; } else { ct++; } filter = sourceService.filter(ct); } } void GLWidget::stopCapture() { if (strcmp(m_consumer->get("mlt_service"), "multi") == 0) { m_consumer->set("refresh", 0); m_consumer->purge(); m_consumer->stop(); } } int GLWidget::reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile) { QString serviceName = property("mlt_service").toString(); if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") != 0) { if (m_consumer) { m_consumer->purge(); m_consumer->stop(); delete m_consumer; } m_consumer = new Mlt::FilteredConsumer(*profile, "multi"); delete m_threadStartEvent; m_threadStartEvent = nullptr; delete m_threadStopEvent; m_threadStopEvent = nullptr; delete m_threadCreateEvent; delete m_threadJoinEvent; if (m_consumer) { m_threadCreateEvent = m_consumer->listen("consumer-thread-create", this, (mlt_listener)onThreadCreate); m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin); } } if (m_consumer->is_valid()) { // buid sub consumers // m_consumer->set("mlt_image_format", "yuv422"); reloadProfile(); int volume = KdenliveSettings::volume(); m_consumer->set("0", serviceName.toUtf8().constData()); m_consumer->set("0.mlt_image_format", "yuv422"); m_consumer->set("0.terminate_on_pause", 0); // m_consumer->set("0.preview_off", 1); m_consumer->set("0.real_time", 0); m_consumer->set("0.volume", (double)volume / 100); if (serviceName.startsWith(QLatin1String("sdl_audio"))) { #ifdef Q_OS_WIN m_consumer->set("0.audio_buffer", 2048); #else m_consumer->set("0.audio_buffer", 512); #endif QString audioDevice = KdenliveSettings::audiodevicename(); if (!audioDevice.isEmpty()) { m_consumer->set("audio_device", audioDevice.toUtf8().constData()); } QString audioDriver = KdenliveSettings::audiodrivername(); if (!audioDriver.isEmpty()) { m_consumer->set("audio_driver", audioDriver.toUtf8().constData()); } } m_consumer->set("1", "avformat"); m_consumer->set("1.target", path.toUtf8().constData()); // m_consumer->set("1.real_time", -KdenliveSettings::mltthreads()); m_consumer->set("terminate_on_pause", 0); m_consumer->set("1.terminate_on_pause", 0); // m_consumer->set("1.terminate_on_pause", 0);// was commented out. restoring it fixes mantis#3415 - FFmpeg recording freezes QStringList paramList = params.split(' ', QString::SkipEmptyParts); for (int i = 0; i < paramList.count(); ++i) { QString key = "1." + paramList.at(i).section(QLatin1Char('='), 0, 0); QString value = paramList.at(i).section(QLatin1Char('='), 1, 1); if (value == QLatin1String("%threads")) { value = QString::number(QThread::idealThreadCount()); } m_consumer->set(key.toUtf8().constData(), value.toUtf8().constData()); } // Connect the producer to the consumer - tell it to "run" later delete m_displayEvent; if (m_glslManager) { if (m_openGLSync) { m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show); } else { m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_nosync_frame_show); } } else { m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show); } m_consumer->connect(*m_producer); m_consumer->start(); return 0; } return -1; } int GLWidget::reconfigure(Mlt::Profile *profile) { int error = 0; // use SDL for audio, OpenGL for video QString serviceName = property("mlt_service").toString(); if (profile) { reloadProfile(); m_blackClip.reset(new Mlt::Producer(*profile, "color:black")); m_blackClip->set("kdenlive:id", "black"); } if ((m_consumer == nullptr) || !m_consumer->is_valid() || strcmp(m_consumer->get("mlt_service"), "multi") == 0) { if (m_consumer) { m_consumer->purge(); m_consumer->stop(); delete m_consumer; } QString audioBackend = KdenliveSettings::audiobackend(); if (serviceName.isEmpty() || serviceName != audioBackend) { m_consumer = new Mlt::FilteredConsumer(*m_monitorProfile, audioBackend.toLatin1().constData()); if (m_consumer->is_valid()) { serviceName = audioBackend; setProperty("mlt_service", serviceName); } else { // Warning, audio backend unavailable on system delete m_consumer; m_consumer = nullptr; QStringList backends = {"sdl2_audio", "sdl_audio", "rtaudio"}; for (const QString &bk : backends) { if (bk == audioBackend) { // Already tested continue; } m_consumer = new Mlt::FilteredConsumer(*m_monitorProfile, bk.toLatin1().constData()); if (m_consumer->is_valid()) { if (audioBackend == KdenliveSettings::sdlAudioBackend()) { // switch sdl audio backend KdenliveSettings::setSdlAudioBackend(bk); } - qDebug()<<"++++++++\nSwitching audio backend to: "<listen("consumer-thread-create", this, (mlt_listener)onThreadCreate); m_threadJoinEvent = m_consumer->listen("consumer-thread-join", this, (mlt_listener)onThreadJoin); } } if (m_consumer->is_valid()) { // Connect the producer to the consumer - tell it to "run" later if (m_producer) { m_consumer->connect(*m_producer); - //m_producer->set_speed(0.0); + // m_producer->set_speed(0.0); } int dropFrames = realTime(); if (!KdenliveSettings::monitor_dropframes()) { dropFrames = -dropFrames; } m_consumer->set("real_time", dropFrames); if (m_glslManager) { if (!m_threadStartEvent) { m_threadStartEvent = m_consumer->listen("consumer-thread-started", this, (mlt_listener)onThreadStarted); } if (!m_threadStopEvent) { m_threadStopEvent = m_consumer->listen("consumer-thread-stopped", this, (mlt_listener)onThreadStopped); } if (!serviceName.startsWith(QLatin1String("decklink"))) { m_consumer->set("mlt_image_format", "glsl"); } } else { m_consumer->set("mlt_image_format", "yuv422"); } delete m_displayEvent; if (m_glslManager) { m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_gl_frame_show); } else { m_displayEvent = m_consumer->listen("consumer-frame-show", this, (mlt_listener)on_frame_show); } int volume = KdenliveSettings::volume(); if (serviceName.startsWith(QLatin1String("sdl_audio"))) { QString audioDevice = KdenliveSettings::audiodevicename(); if (!audioDevice.isEmpty()) { m_consumer->set("audio_device", audioDevice.toUtf8().constData()); } QString audioDriver = KdenliveSettings::audiodrivername(); if (!audioDriver.isEmpty()) { m_consumer->set("audio_driver", audioDriver.toUtf8().constData()); } } /*if (!m_monitorProfile->progressive()) m_consumer->set("progressive", property("progressive").toBool());*/ m_consumer->set("volume", volume / 100.0); // m_consumer->set("progressive", 1); m_consumer->set("rescale", KdenliveSettings::mltinterpolation().toUtf8().constData()); m_consumer->set("deinterlace_method", KdenliveSettings::mltdeinterlacer().toUtf8().constData()); #ifdef Q_OS_WIN - m_consumer->set("audio_buffer", 2048); + m_consumer->set("audio_buffer", 2048); #else - m_consumer->set("audio_buffer", 512); + m_consumer->set("audio_buffer", 512); #endif m_consumer->set("buffer", 25); m_consumer->set("prefill", 1); m_consumer->set("scrub_audio", 1); if (KdenliveSettings::monitor_gamma() == 0) { m_consumer->set("color_trc", "iec61966_2_1"); } else { m_consumer->set("color_trc", "bt709"); } } else { // Cleanup on error error = 2; } return error; } float GLWidget::zoom() const { return m_zoom; } float GLWidget::scale() const { return (double)m_rect.width() / m_monitorProfile->width() * m_zoom; } Mlt::Profile *GLWidget::profile() { return m_monitorProfile; } void GLWidget::reloadProfile() { auto &profile = pCore->getCurrentProfile(); m_monitorProfile->get_profile()->description = qstrdup(profile->description().toUtf8().constData()); m_monitorProfile->set_colorspace(profile->colorspace()); m_monitorProfile->set_frame_rate(profile->frame_rate_num(), profile->frame_rate_den()); m_monitorProfile->set_height(profile->height()); m_monitorProfile->set_width(profile->width()); m_monitorProfile->set_progressive(static_cast(profile->progressive())); m_monitorProfile->set_sample_aspect(profile->sample_aspect_num(), profile->sample_aspect_den()); m_monitorProfile->set_display_aspect(profile->display_aspect_num(), profile->display_aspect_den()); m_monitorProfile->set_explicit(1); // The profile display aspect ratio may have changed. resizeGL(width(), height()); refreshSceneLayout(); } QSize GLWidget::profileSize() const { return QSize(m_monitorProfile->width(), m_monitorProfile->height()); } QRect GLWidget::displayRect() const { return m_rect; } QPoint GLWidget::offset() const { return QPoint(m_offset.x() - ((int)((float)m_monitorProfile->width() * m_zoom) - width()) / 2, - m_offset.y() - ((int)((float)m_monitorProfile->height() * m_zoom) - height()) / 2); + m_offset.y() - ((int)((float)m_monitorProfile->height() * m_zoom) - height()) / 2); } void GLWidget::setZoom(float zoom) { double zoomRatio = zoom / m_zoom; m_zoom = zoom; emit zoomChanged(); if (rootObject()) { rootObject()->setProperty("zoom", m_zoom); double scalex = rootObject()->property("scalex").toDouble() * zoomRatio; rootObject()->setProperty("scalex", scalex); double scaley = rootObject()->property("scaley").toDouble() * zoomRatio; rootObject()->setProperty("scaley", scaley); } update(); } void GLWidget::onFrameDisplayed(const SharedFrame &frame) { m_mutex.lock(); m_sharedFrame = frame; m_sendFrame = sendFrameForAnalysis; m_mutex.unlock(); update(); } void GLWidget::mouseReleaseEvent(QMouseEvent *event) { QQuickView::mouseReleaseEvent(event); if (m_dragStart.isNull() && m_panStart.isNull() && (rootObject() != nullptr) && rootObject()->objectName() != QLatin1String("root") && !(event->modifiers() & Qt::ControlModifier)) { event->ignore(); return; } if (!m_dragStart.isNull() && m_panStart.isNull() && ((event->button() & Qt::LeftButton) != 0u) && !event->isAccepted()) { emit monitorPlay(); } m_dragStart = QPoint(); m_panStart = QPoint(); setCursor(Qt::ArrowCursor); } void GLWidget::mouseDoubleClickEvent(QMouseEvent *event) { QQuickView::mouseDoubleClickEvent(event); if (event->isAccepted()) { return; } if ((rootObject() == nullptr) || rootObject()->objectName() != QLatin1String("rooteffectscene")) { emit switchFullScreen(); } event->accept(); } void GLWidget::setOffsetX(int x, int max) { m_offset.setX(x); emit offsetChanged(); if (rootObject()) { rootObject()->setProperty("offsetx", m_zoom > 1.0f ? x - max / 2.0 - 10 : 0); } update(); } void GLWidget::setOffsetY(int y, int max) { m_offset.setY(y); if (rootObject()) { rootObject()->setProperty("offsety", m_zoom > 1.0f ? y - max / 2.0 - 10 : 0); } update(); } int GLWidget::realTime() const { if (m_glslManager) { return 1; } return KdenliveSettings::mltthreads(); } Mlt::Consumer *GLWidget::consumer() { return m_consumer; } void GLWidget::updateGamma() { reconfigure(); } const QString GLWidget::sceneList(const QString &root, const QString &fullPath) { QString playlist; qCDebug(KDENLIVE_LOG) << " * * *Setting document xml root: " << root; Mlt::Consumer xmlConsumer(*m_monitorProfile, fullPath.isEmpty() ? "xml:kdenlive_playlist" : fullPath.toUtf8().constData()); if (!root.isEmpty()) { xmlConsumer.set("root", root.toUtf8().constData()); } if (!xmlConsumer.is_valid()) { return QString(); } m_producer->optimise(); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.set("store", "kdenlive"); // Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc) // And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening // xmlConsumer.set("no_meta", 1); Mlt::Producer prod(m_producer->get_producer()); if (!prod.is_valid()) { return QString(); } xmlConsumer.connect(prod); xmlConsumer.run(); playlist = fullPath.isEmpty() ? QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")) : fullPath; return playlist; } void GLWidget::updateTexture(GLuint yName, GLuint uName, GLuint vName) { m_texture[0] = yName; m_texture[1] = uName; m_texture[2] = vName; m_sendFrame = sendFrameForAnalysis; emit textureUpdated(); // update(); } // MLT consumer-frame-show event handler void GLWidget::on_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr) { Mlt::Frame frame(frame_ptr); if (frame.get_int("rendered") != 0) { GLWidget *widget = static_cast(self); int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000; if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) { QMetaObject::invokeMethod(widget->m_frameRenderer, "showFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame)); } } } void GLWidget::on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr) { Mlt::Frame frame(frame_ptr); if (frame.get_int("rendered") != 0) { GLWidget *widget = static_cast(self); int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000; if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) { QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLNoSyncFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame)); } } } void GLWidget::on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr) { Mlt::Frame frame(frame_ptr); if (frame.get_int("rendered") != 0) { GLWidget *widget = static_cast(self); int timeout = (widget->consumer()->get_int("real_time") > 0) ? 0 : 1000; if ((widget->m_frameRenderer != nullptr) && widget->m_frameRenderer->semaphore()->tryAcquire(1, timeout)) { QMetaObject::invokeMethod(widget->m_frameRenderer, "showGLFrame", Qt::QueuedConnection, Q_ARG(Mlt::Frame, frame)); } } } RenderThread::RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface) : QThread(nullptr) , m_function(function) , m_data(data) , m_context(nullptr) , m_surface(surface) { if (context) { m_context = new QOpenGLContext; m_context->setFormat(context->format()); m_context->setShareContext(context); m_context->create(); m_context->moveToThread(this); } } RenderThread::~RenderThread() { // delete m_context; } void RenderThread::run() { if (m_context) { m_context->makeCurrent(m_surface); } m_function(m_data); if (m_context) { m_context->doneCurrent(); delete m_context; } } FrameRenderer::FrameRenderer(QOpenGLContext *shareContext, QSurface *surface) : QThread(nullptr) , m_semaphore(3) , m_context(nullptr) , m_surface(surface) , m_gl32(nullptr) , sendAudioForAnalysis(false) { Q_ASSERT(shareContext); m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0; m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0; if (KdenliveSettings::gpu_accel() || shareContext->supportsThreadedOpenGL()) { m_context = new QOpenGLContext; m_context->setFormat(shareContext->format()); m_context->setShareContext(shareContext); m_context->create(); m_context->moveToThread(this); } setObjectName(QStringLiteral("FrameRenderer")); moveToThread(this); start(); } FrameRenderer::~FrameRenderer() { delete m_context; delete m_gl32; } void FrameRenderer::showFrame(Mlt::Frame frame) { int width = 0; int height = 0; mlt_image_format format = mlt_image_yuv420p; frame.get_image(format, width, height); // Save this frame for future use and to keep a reference to the GL Texture. m_displayFrame = SharedFrame(frame); if ((m_context != nullptr) && m_context->isValid()) { m_context->makeCurrent(m_surface); // Upload each plane of YUV to a texture. QOpenGLFunctions *f = m_context->functions(); uploadTextures(m_context, m_displayFrame, m_renderTexture); f->glBindTexture(GL_TEXTURE_2D, 0); check_error(f); f->glFinish(); for (int i = 0; i < 3; ++i) { std::swap(m_renderTexture[i], m_displayTexture[i]); } emit textureReady(m_displayTexture[0], m_displayTexture[1], m_displayTexture[2]); m_context->doneCurrent(); } // The frame is now done being modified and can be shared with the rest // of the application. emit frameDisplayed(m_displayFrame); m_semaphore.release(); } void FrameRenderer::showGLFrame(Mlt::Frame frame) { if ((m_context != nullptr) && m_context->isValid()) { int width = 0; int height = 0; frame.set("movit.convert.use_texture", 1); mlt_image_format format = mlt_image_glsl_texture; frame.get_image(format, width, height); m_context->makeCurrent(m_surface); GLsync sync = (GLsync)frame.get_data("movit.convert.fence"); if (sync) { #ifdef Q_OS_WIN // On Windows, use QOpenGLFunctions_3_2_Core instead of getProcAddress. if (!m_gl32) { m_gl32 = m_context->versionFunctions(); if (m_gl32) { m_gl32->initializeOpenGLFunctions(); } } if (m_gl32) { m_gl32->glClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED); check_error(m_context->functions()); } #else if (ClientWaitSync) { ClientWaitSync(sync, 0, GL_TIMEOUT_IGNORED); check_error(m_context->functions()); } #endif // Q_OS_WIN } m_context->functions()->glFinish(); m_context->doneCurrent(); // Save this frame for future use and to keep a reference to the GL Texture. m_displayFrame = SharedFrame(frame); } // The frame is now done being modified and can be shared with the rest // of the application. emit frameDisplayed(m_displayFrame); m_semaphore.release(); } void FrameRenderer::showGLNoSyncFrame(Mlt::Frame frame) { if ((m_context != nullptr) && m_context->isValid()) { int width = 0; int height = 0; frame.set("movit.convert.use_texture", 1); mlt_image_format format = mlt_image_glsl_texture; frame.get_image(format, width, height); m_context->makeCurrent(m_surface); m_context->functions()->glFinish(); m_context->doneCurrent(); // Save this frame for future use and to keep a reference to the GL Texture. m_displayFrame = SharedFrame(frame); } // The frame is now done being modified and can be shared with the rest // of the application. emit frameDisplayed(m_displayFrame); m_semaphore.release(); } void FrameRenderer::cleanup() { if ((m_renderTexture[0] != 0u) && (m_renderTexture[1] != 0u) && (m_renderTexture[2] != 0u)) { m_context->makeCurrent(m_surface); m_context->functions()->glDeleteTextures(3, m_renderTexture); if ((m_displayTexture[0] != 0u) && (m_displayTexture[1] != 0u) && (m_displayTexture[2] != 0u)) { m_context->functions()->glDeleteTextures(3, m_displayTexture); } m_context->doneCurrent(); m_renderTexture[0] = m_renderTexture[1] = m_renderTexture[2] = 0; m_displayTexture[0] = m_displayTexture[1] = m_displayTexture[2] = 0; } } void GLWidget::setAudioThumb(int channels, const QVariantList &audioCache) { if (rootObject()) { QmlAudioThumb *audioThumbDisplay = rootObject()->findChild(QStringLiteral("audiothumb")); if (audioThumbDisplay) { QImage img(width(), height() / 6, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); if (!audioCache.isEmpty() && channels > 0) { int audioLevelCount = audioCache.count() - 1; // simplified audio QPainter painter(&img); QRectF mappedRect(0, 0, img.width(), img.height()); int channelHeight = mappedRect.height(); double value; double scale = (double)width() / (audioLevelCount / channels); if (scale < 1) { painter.setPen(QColor(80, 80, 150, 200)); for (int i = 0; i < img.width(); i++) { int framePos = i / scale; value = audioCache.at(qMin(framePos * channels, audioLevelCount)).toDouble() / 256; for (int channel = 1; channel < channels; channel++) { value = qMax(value, audioCache.at(qMin(framePos * channels + channel, audioLevelCount)).toDouble() / 256); } painter.drawLine(i, mappedRect.bottom() - (value * channelHeight), i, mappedRect.bottom()); } } else { QPainterPath positiveChannelPath; positiveChannelPath.moveTo(0, mappedRect.bottom()); for (int i = 0; i < audioLevelCount / channels; i++) { value = audioCache.at(qMin(i * channels, audioLevelCount)).toDouble() / 256; for (int channel = 1; channel < channels; channel++) { value = qMax(value, audioCache.at(qMin(i * channels + channel, audioLevelCount)).toDouble() / 256); } positiveChannelPath.lineTo(i * scale, mappedRect.bottom() - (value * channelHeight)); } positiveChannelPath.lineTo(mappedRect.right(), mappedRect.bottom()); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(QColor(80, 80, 150, 200))); painter.drawPath(positiveChannelPath); } painter.end(); } audioThumbDisplay->setImage(img); } } } void GLWidget::refreshSceneLayout() { if (!rootObject()) { return; } rootObject()->setProperty("profile", QPoint(m_monitorProfile->width(), m_monitorProfile->height())); rootObject()->setProperty("scalex", (double)m_rect.width() / m_monitorProfile->width() * m_zoom); - rootObject()->setProperty("scaley", - (double)m_rect.width() / (((double)m_monitorProfile->height() * m_monitorProfile->dar() / m_monitorProfile->width())) / - m_monitorProfile->width() * m_zoom); + rootObject()->setProperty("scaley", (double)m_rect.width() / (((double)m_monitorProfile->height() * m_monitorProfile->dar() / m_monitorProfile->width())) / + m_monitorProfile->width() * m_zoom); } void GLWidget::switchPlay(bool play, double speed) { // QMutexLocker locker(&m_mutex); m_proxy->setSeekPosition(SEEK_INACTIVE); if ((m_producer == nullptr) || (m_consumer == nullptr)) { return; } if (m_isZoneMode) { resetZoneMode(); } if (play) { double currentSpeed = m_producer->get_speed(); /*if (m_name == Kdenlive::ClipMonitor && m_consumer->position() == m_producer->get_out()) { m_producer->seek(0); }*/ if (m_consumer->get_int("real_time") != realTime()) { m_consumer->set("real_time", realTime()); m_consumer->set("buffer", 25); m_consumer->set("prefill", 1); // Changes to real_time require a consumer restart if running. if (!m_consumer->is_stopped()) { m_consumer->stop(); } } if (qFuzzyIsNull(currentSpeed)) { m_consumer->start(); m_consumer->set("refresh", 1); } else { m_consumer->purge(); } m_producer->set_speed(speed); } else { m_consumer->set("real_time", -1); m_consumer->set("buffer", 0); m_consumer->set("prefill", 0); m_producer->set_speed(0.0); m_producer->seek(m_consumer->position() + 1); m_consumer->purge(); } } bool GLWidget::playZone(bool loop) { if (!m_producer) { return false; } m_proxy->setSeekPosition(SEEK_INACTIVE); m_producer->seek(m_proxy->zoneIn()); m_producer->set_speed(0); m_consumer->purge(); m_producer->set("out", m_proxy->zoneOut()); m_producer->set_speed(1.0); if (m_consumer->is_stopped()) { m_consumer->start(); } m_consumer->set("refresh", 1); m_isZoneMode = true; m_isLoopMode = loop; return true; } bool GLWidget::loopClip() { if (!m_producer) { return false; } m_proxy->setSeekPosition(SEEK_INACTIVE); m_producer->seek(0); m_producer->set_speed(0); m_consumer->purge(); m_producer->set("out", m_producer->get_playtime()); m_producer->set_speed(1.0); if (m_consumer->is_stopped()) { m_consumer->start(); } m_consumer->set("refresh", 1); m_isZoneMode = true; m_isLoopMode = true; return true; } void GLWidget::resetZoneMode() { if (!m_isZoneMode && !m_isLoopMode) { return; } m_producer->set("out", m_producer->get_length()); m_isZoneMode = false; m_isLoopMode = false; } MonitorProxy *GLWidget::getControllerProxy() { return m_proxy; } int GLWidget::getCurrentPos() const { return m_proxy->seekPosition() == SEEK_INACTIVE ? m_consumer->position() : m_proxy->seekPosition(); } void GLWidget::setRulerInfo(int duration, std::shared_ptr model) { rootObject()->setProperty("duration", duration); if (model != nullptr) { // we are resetting marker/snap model, reset zone m_proxy->resetZone(); rootContext()->setContextProperty("markersModel", model.get()); } } void GLWidget::startConsumer() { if (m_consumer == nullptr) { return; } if (m_consumer->is_stopped() && m_consumer->start() == -1) { // ARGH CONSUMER BROKEN!!!! KMessageBox::error( qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it.")); if (m_displayEvent) { delete m_displayEvent; } m_displayEvent = nullptr; delete m_consumer; m_consumer = nullptr; return; } m_consumer->set("refresh", 1); } void GLWidget::stop() { m_refreshTimer.stop(); m_proxy->setSeekPosition(SEEK_INACTIVE); QMutexLocker locker(&m_mutex); if (m_producer) { if (m_isZoneMode) { resetZoneMode(); } m_producer->set_speed(0.0); } if (m_consumer) { m_consumer->purge(); if (!m_consumer->is_stopped()) { m_consumer->stop(); } } } double GLWidget::playSpeed() const { if (m_producer) { return m_producer->get_speed(); } return 0.0; } void GLWidget::setDropFrames(bool drop) { QMutexLocker locker(&m_mutex); if (m_consumer) { int dropFrames = realTime(); if (!drop) { dropFrames = -dropFrames; } m_consumer->stop(); m_consumer->set("real_time", dropFrames); if (m_consumer->start() == -1) { qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor"; } } } int GLWidget::volume() const { if ((m_consumer == nullptr) || (m_producer == nullptr)) { return -1; } if (m_consumer->get("mlt_service") == QStringLiteral("multi")) { return ((int)100 * m_consumer->get_double("0.volume")); } return ((int)100 * m_consumer->get_double("volume")); } void GLWidget::setVolume(double volume) { if (m_consumer) { if (m_consumer->get("mlt_service") == QStringLiteral("multi")) { m_consumer->set("0.volume", volume); } else { m_consumer->set("volume", volume); } } } int GLWidget::duration() const { if (m_producer == nullptr) { return 0; } return m_producer->get_playtime(); } diff --git a/src/monitor/glwidget.h b/src/monitor/glwidget.h index 2b0c9d030..89a96c048 100644 --- a/src/monitor/glwidget.h +++ b/src/monitor/glwidget.h @@ -1,433 +1,434 @@ /* * Copyright (c) 2011-2014 Meltytech, LLC * Author: Dan Dennedy * * 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 3 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, see . */ #ifndef GLWIDGET_H #define GLWIDGET_H +#include +#include +#include #include #include #include #include #include #include #include -#include -#include -#include #include #include #include #include #include "bin/model/markerlistmodel.hpp" #include "definitions.h" -#include "scopes/sharedframe.h" #include "kdenlivesettings.h" +#include "scopes/sharedframe.h" class QOpenGLFunctions_3_2_Core; namespace Mlt { class Filter; class Producer; class Consumer; class Profile; -} +} // namespace Mlt class RenderThread; class FrameRenderer; class MonitorProxy; typedef void *(*thread_function_t)(void *); class GLWidget : public QQuickView, protected QOpenGLFunctions { Q_OBJECT Q_PROPERTY(QRect rect READ rect NOTIFY rectChanged) Q_PROPERTY(float zoom READ zoom NOTIFY zoomChanged) Q_PROPERTY(QPoint offset READ offset NOTIFY offsetChanged) public: friend class MonitorController; friend class Monitor; friend class MonitorProxy; GLWidget(int id, QObject *parent = nullptr); ~GLWidget(); int requestedSeekPosition; void createThread(RenderThread **thread, thread_function_t function, void *data); void startGlsl(); void stopGlsl(); void clear(); int reconfigureMulti(const QString ¶ms, const QString &path, Mlt::Profile *profile); void stopCapture(); int reconfigure(Mlt::Profile *profile = nullptr); /** @brief Get the current MLT producer playlist. * @return A string describing the playlist */ const QString sceneList(const QString &root, const QString &fullPath = QString()); int displayWidth() const { return m_rect.width(); } void updateAudioForAnalysis(); int displayHeight() const { return m_rect.height(); } QObject *videoWidget() { return this; } Mlt::Filter *glslManager() const { return m_glslManager; } QRect rect() const { return m_rect; } QRect effectRect() const { return m_effectRect; } float zoom() const; float scale() const; QPoint offset() const; Mlt::Consumer *consumer(); Mlt::Producer *producer(); QSize profileSize() const; QRect displayRect() const; /** @brief set to true if we want to emit a QImage of the frame for analysis */ bool sendFrameForAnalysis; void updateGamma(); Mlt::Profile *profile(); void reloadProfile(); void lockMonitor(); void releaseMonitor(); int realTime() const; void setAudioThumb(int channels = 0, const QVariantList &audioCache = QList()); int droppedFrames() const; void resetDrops(); bool checkFrameNumber(int pos); /** @brief Return current timeline position */ int getCurrentPos() const; /** @brief Requests a monitor refresh */ void requestRefresh(); void setRulerInfo(int duration, std::shared_ptr model = nullptr); MonitorProxy *getControllerProxy(); bool playZone(bool loop = false); bool loopClip(); void startConsumer(); void stop(); int rulerHeight() const; /** @brief return current play producer's playing speed */ double playSpeed() const; /** @brief Turn drop frame feature on/off */ void setDropFrames(bool drop); /** @brief Returns current audio volume */ int volume() const; /** @brief Set audio volume on consumer */ void setVolume(double volume); /** @brief Returns current producer's duration in frames */ int duration() const; protected: void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; /** @brief Update producer, should ONLY be called from monitor */ int setProducer(Mlt::Producer *producer, bool isActive, int position = -1); public slots: void seek(int pos); void requestSeek(); void setZoom(float zoom); void setOffsetX(int x, int max); void setOffsetY(int y, int max); void slotSwitchAudioOverlay(bool enable); void slotZoom(bool zoomIn); void initializeGL(); void releaseAnalyse(); void switchPlay(bool play, double speed = 1.0); signals: void frameDisplayed(const SharedFrame &frame); void textureUpdated(); void dragStarted(); void seekTo(int x); void gpuNotSupported(); void started(); void paused(); void playing(); void rectChanged(); void zoomChanged(); void offsetChanged(); void monitorPlay(); void switchFullScreen(bool minimizeOnly = false); void mouseSeek(int eventDelta, uint modifiers); void startDrag(); void analyseFrame(const QImage &); void audioSamplesSignal(const audioShortVector &, int, int, int); void showContextMenu(const QPoint &); void lockMonitor(bool); void passKeyEvent(QKeyEvent *); void panView(const QPoint &diff); void seekPosition(int); void activateMonitor(); protected: Mlt::Filter *m_glslManager; Mlt::Consumer *m_consumer; Mlt::Producer *m_producer; Mlt::Profile *m_monitorProfile; QMutex m_mutex; int m_id; private: QRect m_rect; QRect m_effectRect; GLuint m_texture[3]; QOpenGLShaderProgram *m_shader; QPoint m_panStart; QPoint m_dragStart; QSemaphore m_initSem; QSemaphore m_analyseSem; bool m_isInitialized; Mlt::Event *m_threadStartEvent; Mlt::Event *m_threadStopEvent; Mlt::Event *m_threadCreateEvent; Mlt::Event *m_threadJoinEvent; Mlt::Event *m_displayEvent; FrameRenderer *m_frameRenderer; int m_projectionLocation; int m_modelViewLocation; int m_vertexLocation; int m_texCoordLocation; int m_colorspaceLocation; int m_textureLocation[3]; QTimer m_refreshTimer; float m_zoom; bool m_openGLSync; bool m_sendFrame; bool m_isZoneMode; bool m_isLoopMode; SharedFrame m_sharedFrame; QPoint m_offset; QOffscreenSurface m_offscreenSurface; QOpenGLContext *m_shareContext; bool m_audioWaveDisplayed; MonitorProxy *m_proxy; QScopedPointer m_blackClip; static void on_frame_show(mlt_consumer, void *self, mlt_frame frame); static void on_gl_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr); static void on_gl_nosync_frame_show(mlt_consumer, void *self, mlt_frame frame_ptr); void createAudioOverlay(bool isAudio); void removeAudioOverlay(); void adjustAudioOverlay(bool isAudio); QOpenGLFramebufferObject *m_fbo; void refreshSceneLayout(); void resetZoneMode(); private slots: void resizeGL(int width, int height); void updateTexture(GLuint yName, GLuint uName, GLuint vName); void paintGL(); void onFrameDisplayed(const SharedFrame &frame); void refresh(); protected: void resizeEvent(QResizeEvent *event) override; void mousePressEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void keyPressEvent(QKeyEvent *event) override; void createShader(); }; class RenderThread : public QThread { Q_OBJECT public: RenderThread(thread_function_t function, void *data, QOpenGLContext *context, QSurface *surface); ~RenderThread(); protected: void run() override; private: thread_function_t m_function; void *m_data; QOpenGLContext *m_context; QSurface *m_surface; }; class FrameRenderer : public QThread { Q_OBJECT public: explicit FrameRenderer(QOpenGLContext *shareContext, QSurface *surface); ~FrameRenderer(); QSemaphore *semaphore() { return &m_semaphore; } QOpenGLContext *context() const { return m_context; } Q_INVOKABLE void showFrame(Mlt::Frame frame); Q_INVOKABLE void showGLFrame(Mlt::Frame frame); Q_INVOKABLE void showGLNoSyncFrame(Mlt::Frame frame); public slots: void cleanup(); signals: void textureReady(GLuint yName, GLuint uName = 0, GLuint vName = 0); void frameDisplayed(const SharedFrame &frame); void audioSamplesSignal(const audioShortVector &, int, int, int); private: QSemaphore m_semaphore; SharedFrame m_displayFrame; QOpenGLContext *m_context; QSurface *m_surface; public: GLuint m_renderTexture[3]; GLuint m_displayTexture[3]; QOpenGLFunctions_3_2_Core *m_gl32; bool sendAudioForAnalysis; }; class MonitorProxy : public QObject { Q_OBJECT // Q_PROPERTY(int consumerPosition READ consumerPosition NOTIFY consumerPositionChanged) Q_PROPERTY(int position READ position NOTIFY positionChanged) Q_PROPERTY(int seekPosition READ seekPosition WRITE setSeekPosition NOTIFY seekPositionChanged) Q_PROPERTY(int zoneIn READ zoneIn WRITE setZoneIn NOTIFY zoneChanged) Q_PROPERTY(int zoneOut READ zoneOut WRITE setZoneOut NOTIFY zoneChanged) Q_PROPERTY(int rulerHeight READ rulerHeight NOTIFY rulerHeightChanged) Q_PROPERTY(QString markerComment READ markerComment NOTIFY markerCommentChanged) Q_PROPERTY(int overlayType READ overlayType WRITE setOverlayType NOTIFY overlayTypeChanged) public: MonitorProxy(GLWidget *parent) : QObject(parent) , q(parent) , m_position(0) , m_seekPosition(-1) , m_zoneIn(0) , m_zoneOut(-1) , m_rulerHeight(QFontMetrics(QApplication::font()).lineSpacing() * 0.7) { } int seekPosition() const { return m_seekPosition; } int position() const { return m_position; } int rulerHeight() const { return m_rulerHeight; } - int overlayType() const {return (q->m_id == (int)Kdenlive::ClipMonitor ? KdenliveSettings::clipMonitorOverlayGuides() : KdenliveSettings::projectMonitorOverlayGuides()); } - void setOverlayType(int ix) { + int overlayType() const + { + return (q->m_id == (int)Kdenlive::ClipMonitor ? KdenliveSettings::clipMonitorOverlayGuides() : KdenliveSettings::projectMonitorOverlayGuides()); + } + void setOverlayType(int ix) + { if (q->m_id == (int)Kdenlive::ClipMonitor) { KdenliveSettings::setClipMonitorOverlayGuides(ix); } else { KdenliveSettings::setProjectMonitorOverlayGuides(ix); } } QString markerComment() const { return m_markerComment; } Q_INVOKABLE void requestSeekPosition(int pos) { q->activateMonitor(); m_seekPosition = pos; emit seekPositionChanged(); emit seekRequestChanged(); } void setPosition(int pos) { m_position = pos; emit positionChanged(); } void setMarkerComment(const QString &comment) { if (m_markerComment == comment) { return; } m_markerComment = comment; emit markerCommentChanged(); } void setSeekPosition(int pos) { m_seekPosition = pos; emit seekPositionChanged(); } void pauseAndSeek(int pos) { q->switchPlay(false); requestSeekPosition(pos); } int zoneIn() const { return m_zoneIn; } int zoneOut() const { return m_zoneOut; } void setZoneIn(int pos) { if (m_zoneIn > 0) { emit removeSnap(m_zoneIn); } m_zoneIn = pos; if (pos > 0) { emit addSnap(pos); } emit zoneChanged(); } void setZoneOut(int pos) { if (m_zoneOut > 0) { emit removeSnap(m_zoneOut); } m_zoneOut = pos; if (pos > 0) { emit addSnap(pos); } emit zoneChanged(); } Q_INVOKABLE void setZone(int in, int out) { if (m_zoneIn > 0) { emit removeSnap(m_zoneIn); } if (m_zoneOut > 0) { emit removeSnap(m_zoneOut); } m_zoneIn = in; m_zoneOut = out; if (m_zoneIn > 0) { emit addSnap(m_zoneIn); } if (m_zoneOut > 0) { emit addSnap(m_zoneOut); } emit zoneChanged(); } - void setZone(QPoint zone) - { - setZone(zone.x(), zone.y()); - } + void setZone(QPoint zone) { setZone(zone.x(), zone.y()); } void resetZone() { m_zoneIn = 0; m_zoneOut = -1; } QPoint zone() const { return QPoint(m_zoneIn, m_zoneOut); } signals: void positionChanged(); void seekPositionChanged(); void seekRequestChanged(); void zoneChanged(); void markerCommentChanged(); void rulerHeightChanged(); void addSnap(int); void removeSnap(int); Q_INVOKABLE void triggerAction(const QString &name); void overlayTypeChanged(); private: GLWidget *q; int m_position; int m_seekPosition; int m_zoneIn; int m_zoneOut; int m_rulerHeight; QString m_markerComment; }; #endif diff --git a/src/monitor/monitor.cpp b/src/monitor/monitor.cpp index 5a24f30bb..dee61ec16 100644 --- a/src/monitor/monitor.cpp +++ b/src/monitor/monitor.cpp @@ -1,2138 +1,2138 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 "monitor.h" #include "bin/bin.h" #include "bin/projectclip.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "doc/kdenlivedoc.h" #include "doc/kthumb.h" #include "glwidget.h" #include "kdenlivesettings.h" #include "lib/audio/audioStreamInfo.h" #include "mainwindow.h" #include "mltcontroller/bincontroller.h" +#include "mltcontroller/clip.h" #include "mltcontroller/clipcontroller.h" #include "monitorcontroller.hpp" #include "project/projectmanager.h" #include "qmlmanager.h" #include "recmanager.h" #include "scopes/monitoraudiolevel.h" -#include "mltcontroller/clip.h" #include "timeline2/model/snapmodel.hpp" #include "transitions/transitionsrepository.hpp" #include "utils/KoIconUtils.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEEK_INACTIVE (-1) QuickEventEater::QuickEventEater(QObject *parent) : QObject(parent) { } bool QuickEventEater::eventFilter(QObject *obj, QEvent *event) { switch (event->type()) { case QEvent::DragEnter: { QDragEnterEvent *ev = reinterpret_cast(event); if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) { ev->acceptProposedAction(); return true; } break; } case QEvent::DragMove: { QDragEnterEvent *ev = reinterpret_cast(event); if (ev->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) { ev->acceptProposedAction(); return true; } break; } case QEvent::Drop: { QDropEvent *ev = static_cast(event); if (ev) { QStringList effectData; effectData << QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effect"))); QStringList source = QString::fromUtf8(ev->mimeData()->data(QStringLiteral("kdenlive/effectsource"))).split(QLatin1Char('-')); effectData << source; emit addEffect(effectData); ev->accept(); return true; } break; } default: break; } return QObject::eventFilter(obj, event); } QuickMonitorEventEater::QuickMonitorEventEater(QWidget *parent) : QObject(parent) { } bool QuickMonitorEventEater::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *ev = static_cast(event); if (ev) { emit doKeyPressEvent(ev); return true; } } return QObject::eventFilter(obj, event); } Monitor::Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent) : AbstractMonitor(id, manager, parent) , m_controller(nullptr) , m_glMonitor(nullptr) , m_snaps(new SnapModel()) , m_splitEffect(nullptr) , m_splitProducer(nullptr) , m_dragStarted(false) , m_recManager(nullptr) , m_loopClipAction(nullptr) , m_sceneVisibilityAction(nullptr) , m_multitrackView(nullptr) , m_contextMenu(nullptr) , m_loopClipTransition(true) , m_editMarker(nullptr) , m_forceSizeFactor(0) , m_lastMonitorSceneType(MonitorSceneDefault) { auto *layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); // Create container widget m_glWidget = new QWidget; auto *glayout = new QGridLayout(m_glWidget); glayout->setSpacing(0); glayout->setContentsMargins(0, 0, 0, 0); // Create QML OpenGL widget m_glMonitor = new GLWidget((int)id); connect(m_glMonitor, &GLWidget::passKeyEvent, this, &Monitor::doKeyPressEvent); connect(m_glMonitor, &GLWidget::panView, this, &Monitor::panView); connect(m_glMonitor, &GLWidget::seekPosition, this, &Monitor::slotSeekPosition, Qt::DirectConnection); connect(m_glMonitor, &GLWidget::activateMonitor, this, &AbstractMonitor::slotActivateMonitor, Qt::DirectConnection); m_monitorController = new MonitorController(m_glMonitor); m_videoWidget = QWidget::createWindowContainer(qobject_cast(m_glMonitor)); m_videoWidget->setAcceptDrops(true); auto *leventEater = new QuickEventEater(this); m_videoWidget->installEventFilter(leventEater); connect(leventEater, &QuickEventEater::addEffect, this, &Monitor::slotAddEffect); m_qmlManager = new QmlManager(m_glMonitor); connect(m_qmlManager, &QmlManager::effectChanged, this, &Monitor::effectChanged); connect(m_qmlManager, &QmlManager::effectPointsChanged, this, &Monitor::effectPointsChanged); auto *monitorEventEater = new QuickMonitorEventEater(this); m_glWidget->installEventFilter(monitorEventEater); connect(monitorEventEater, &QuickMonitorEventEater::doKeyPressEvent, this, &Monitor::doKeyPressEvent); glayout->addWidget(m_videoWidget, 0, 0); m_verticalScroll = new QScrollBar(Qt::Vertical); glayout->addWidget(m_verticalScroll, 0, 1); m_verticalScroll->hide(); m_horizontalScroll = new QScrollBar(Qt::Horizontal); glayout->addWidget(m_horizontalScroll, 1, 0); m_horizontalScroll->hide(); connect(m_horizontalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetX); connect(m_verticalScroll, &QAbstractSlider::valueChanged, this, &Monitor::setOffsetY); connect(m_glMonitor, &GLWidget::frameDisplayed, this, &Monitor::onFrameDisplayed); connect(m_glMonitor, &GLWidget::mouseSeek, this, &Monitor::slotMouseSeek); connect(m_glMonitor, SIGNAL(monitorPlay()), this, SLOT(slotPlay())); connect(m_glMonitor, &GLWidget::startDrag, this, &Monitor::slotStartDrag); connect(m_glMonitor, SIGNAL(switchFullScreen(bool)), this, SLOT(slotSwitchFullScreen(bool))); connect(m_glMonitor, &GLWidget::zoomChanged, this, &Monitor::setZoom); connect(m_glMonitor, SIGNAL(lockMonitor(bool)), this, SLOT(slotLockMonitor(bool)), Qt::DirectConnection); connect(m_glMonitor, &GLWidget::showContextMenu, this, &Monitor::slotShowMenu); connect(m_glMonitor, &GLWidget::gpuNotSupported, this, &Monitor::gpuError); m_glWidget->setMinimumSize(QSize(320, 180)); layout->addWidget(m_glWidget, 10); layout->addStretch(); // Tool bar buttons m_toolbar = new QToolBar(this); QWidget *sp1 = new QWidget(this); sp1->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_toolbar->addWidget(sp1); if (id == Kdenlive::ClipMonitor) { // Add options for recording m_recManager = new RecManager(this); connect(m_recManager, &RecManager::warningMessage, this, &Monitor::warningMessage); connect(m_recManager, &RecManager::addClipToProject, this, &Monitor::addClipToProject); } if (id != Kdenlive::DvdMonitor) { m_toolbar->addAction(manager->getAction(QStringLiteral("mark_in"))); m_toolbar->addAction(manager->getAction(QStringLiteral("mark_out"))); } m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_backward"))); auto *playButton = new QToolButton(m_toolbar); m_playMenu = new QMenu(i18n("Play..."), this); QAction *originalPlayAction = static_cast(manager->getAction(QStringLiteral("monitor_play"))); m_playAction = new KDualAction(i18n("Play"), i18n("Pause"), this); m_playAction->setInactiveIcon(KoIconUtils::themedIcon(QStringLiteral("media-playback-start"))); m_playAction->setActiveIcon(KoIconUtils::themedIcon(QStringLiteral("media-playback-pause"))); QString strippedTooltip = m_playAction->toolTip().remove(QRegExp(QStringLiteral("\\s\\(.*\\)"))); // append shortcut if it exists for action if (originalPlayAction->shortcut() == QKeySequence(0)) { m_playAction->setToolTip(strippedTooltip); } else { m_playAction->setToolTip(strippedTooltip + QStringLiteral(" (") + originalPlayAction->shortcut().toString() + QLatin1Char(')')); } m_playMenu->addAction(m_playAction); connect(m_playAction, &QAction::triggered, this, &Monitor::slotSwitchPlay); playButton->setMenu(m_playMenu); playButton->setPopupMode(QToolButton::MenuButtonPopup); m_toolbar->addWidget(playButton); m_toolbar->addAction(manager->getAction(QStringLiteral("monitor_seek_forward"))); playButton->setDefaultAction(m_playAction); m_configMenu = new QMenu(i18n("Misc..."), this); if (id != Kdenlive::DvdMonitor) { if (id == Kdenlive::ClipMonitor) { m_markerMenu = new QMenu(i18n("Go to marker..."), this); } else { m_markerMenu = new QMenu(i18n("Go to guide..."), this); } m_markerMenu->setEnabled(false); m_configMenu->addMenu(m_markerMenu); connect(m_markerMenu, &QMenu::triggered, this, &Monitor::slotGoToMarker); m_forceSize = new KSelectAction(KoIconUtils::themedIcon(QStringLiteral("transform-scale")), i18n("Force Monitor Size"), this); QAction *fullAction = m_forceSize->addAction(QIcon(), i18n("Force 100%")); fullAction->setData(100); QAction *halfAction = m_forceSize->addAction(QIcon(), i18n("Force 50%")); halfAction->setData(50); QAction *freeAction = m_forceSize->addAction(QIcon(), i18n("Free Resize")); freeAction->setData(0); m_configMenu->addAction(m_forceSize); m_forceSize->setCurrentAction(freeAction); - connect(m_forceSize, static_cast(&KSelectAction::triggered), this, &Monitor::slotForceSize); + connect(m_forceSize, static_cast(&KSelectAction::triggered), this, &Monitor::slotForceSize); } // Create Volume slider popup m_audioSlider = new QSlider(Qt::Vertical); m_audioSlider->setRange(0, 100); m_audioSlider->setValue(100); connect(m_audioSlider, &QSlider::valueChanged, this, &Monitor::slotSetVolume); auto *widgetslider = new QWidgetAction(this); widgetslider->setText(i18n("Audio volume")); widgetslider->setDefaultWidget(m_audioSlider); auto *menu = new QMenu(this); menu->addAction(widgetslider); m_audioButton = new QToolButton(this); m_audioButton->setMenu(menu); m_audioButton->setToolTip(i18n("Volume")); m_audioButton->setPopupMode(QToolButton::InstantPopup); QIcon icon; if (KdenliveSettings::volume() == 0) { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-muted")); } else { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-medium")); } m_audioButton->setIcon(icon); m_toolbar->addWidget(m_audioButton); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setLayout(layout); setMinimumHeight(200); connect(this, &Monitor::scopesClear, m_glMonitor, &GLWidget::releaseAnalyse, Qt::DirectConnection); connect(m_glMonitor, &GLWidget::analyseFrame, this, &Monitor::frameUpdated); connect(m_glMonitor, &GLWidget::audioSamplesSignal, this, &Monitor::audioSamplesSignal); if (id != Kdenlive::ClipMonitor) { // TODO: reimplement // connect(render, &Render::durationChanged, this, &Monitor::durationChanged); connect(m_glMonitor->getControllerProxy(), &MonitorProxy::zoneChanged, this, &Monitor::updateTimelineClipZone); } else { connect(m_glMonitor->getControllerProxy(), &MonitorProxy::zoneChanged, this, &Monitor::updateClipZone); } connect(m_glMonitor->getControllerProxy(), &MonitorProxy::triggerAction, pCore.get(), &Core::triggerAction); m_sceneVisibilityAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("transform-crop")), i18n("Show/Hide edit mode"), this); m_sceneVisibilityAction->setCheckable(true); m_sceneVisibilityAction->setChecked(KdenliveSettings::showOnMonitorScene()); connect(m_sceneVisibilityAction, &QAction::triggered, this, &Monitor::slotEnableEffectScene); m_toolbar->addAction(m_sceneVisibilityAction); m_toolbar->addSeparator(); m_timePos = new TimecodeDisplay(m_monitorManager->timecode(), this); m_toolbar->addWidget(m_timePos); auto *configButton = new QToolButton(m_toolbar); configButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-menu"))); configButton->setToolTip(i18n("Options")); configButton->setMenu(m_configMenu); configButton->setPopupMode(QToolButton::InstantPopup); m_toolbar->addWidget(configButton); if (m_recManager) { m_toolbar->addAction(m_recManager->switchAction()); } /*QWidget *spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_toolbar->addWidget(spacer);*/ m_toolbar->addSeparator(); int tm = 0; int bm = 0; m_toolbar->getContentsMargins(nullptr, &tm, nullptr, &bm); m_audioMeterWidget = new MonitorAudioLevel(m_glMonitor->profile(), m_toolbar->height() - tm - bm, this); m_toolbar->addWidget(m_audioMeterWidget); if (!m_audioMeterWidget->isValid) { KdenliveSettings::setMonitoraudio(0x01); m_audioMeterWidget->setVisibility(false); } else { m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0); } connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotSeek())); layout->addWidget(m_toolbar); if (m_recManager) { layout->addWidget(m_recManager->toolbar()); } // Load monitor overlay qml loadQmlScene(MonitorSceneDefault); // Info message widget m_infoMessage = new KMessageWidget(this); layout->addWidget(m_infoMessage); m_infoMessage->hide(); } Monitor::~Monitor() { delete m_audioMeterWidget; delete m_glMonitor; delete m_videoWidget; delete m_glWidget; delete m_timePos; } void Monitor::setOffsetX(int x) { m_glMonitor->setOffsetX(x, m_horizontalScroll->maximum()); } void Monitor::setOffsetY(int y) { m_glMonitor->setOffsetY(y, m_verticalScroll->maximum()); } void Monitor::slotGetCurrentImage(bool request) { m_glMonitor->sendFrameForAnalysis = request; m_monitorManager->activateMonitor(m_id); refreshMonitorIfActive(); if (request) { // Update analysis state QTimer::singleShot(500, m_monitorManager, &MonitorManager::checkScopes); } else { m_glMonitor->releaseAnalyse(); } } void Monitor::slotAddEffect(const QStringList &effect) { if (m_id == Kdenlive::ClipMonitor) { if (m_controller) { emit addMasterEffect(m_controller->AbstractProjectItem::clipId(), effect); } } else { emit addEffect(effect); } } void Monitor::refreshIcons() { QList allMenus = this->findChildren(); for (int i = 0; i < allMenus.count(); i++) { QAction *m = allMenus.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setIcon(newIcon); } QList allButtons = this->findChildren(); for (int i = 0; i < allButtons.count(); i++) { KDualAction *m = allButtons.at(i); QIcon ic = m->activeIcon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setActiveIcon(newIcon); ic = m->inactiveIcon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } newIcon = KoIconUtils::themedIcon(ic.name()); m->setInactiveIcon(newIcon); } } QAction *Monitor::recAction() { if (m_recManager) { return m_recManager->switchAction(); } return nullptr; } void Monitor::slotLockMonitor(bool lock) { m_monitorManager->lockMonitor(m_id, lock); } void Monitor::setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu, QAction *loopClip) { delete m_contextMenu; m_contextMenu = new QMenu(this); m_contextMenu->addMenu(m_playMenu); if (goMenu) { m_contextMenu->addMenu(goMenu); } if (markerMenu) { m_contextMenu->addMenu(markerMenu); QList list = markerMenu->actions(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->data().toString() == QLatin1String("edit_marker")) { m_editMarker = list.at(i); break; } } } m_playMenu->addAction(playZone); m_playMenu->addAction(loopZone); if (loopClip) { m_loopClipAction = loopClip; m_playMenu->addAction(loopClip); } // TODO: add save zone to timeline monitor when fixed m_contextMenu->addMenu(m_markerMenu); if (m_id == Kdenlive::ClipMonitor) { m_contextMenu->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save")), i18n("Save zone"), this, SLOT(slotSaveZone())); QAction *extractZone = m_configMenu->addAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Extract Zone"), this, SLOT(slotExtractCurrentZone())); m_contextMenu->addAction(extractZone); } m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame"))); m_contextMenu->addAction(m_monitorManager->getAction(QStringLiteral("extract_frame_to_project"))); if (m_id == Kdenlive::ProjectMonitor) { m_multitrackView = m_contextMenu->addAction(KoIconUtils::themedIcon(QStringLiteral("view-split-left-right")), i18n("Multitrack view"), this, SIGNAL(multitrackView(bool))); m_multitrackView->setCheckable(true); m_configMenu->addAction(m_multitrackView); } else if (m_id == Kdenlive::ClipMonitor) { QAction *setThumbFrame = m_contextMenu->addAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Set current image as thumbnail"), this, SLOT(slotSetThumbFrame())); m_configMenu->addAction(setThumbFrame); } if (overlayMenu) { m_contextMenu->addMenu(overlayMenu); } QAction *overlayAudio = m_contextMenu->addAction(QIcon(), i18n("Overlay audio waveform")); overlayAudio->setCheckable(true); connect(overlayAudio, &QAction::toggled, m_glMonitor, &GLWidget::slotSwitchAudioOverlay); overlayAudio->setChecked(KdenliveSettings::displayAudioOverlay()); QAction *switchAudioMonitor = m_configMenu->addAction(i18n("Show Audio Levels"), this, SLOT(slotSwitchAudioMonitor())); switchAudioMonitor->setCheckable(true); switchAudioMonitor->setChecked((KdenliveSettings::monitoraudio() & m_id) != 0); m_configMenu->addAction(overlayAudio); // For some reason, the frame in QAbstracSpinBox (base class of TimeCodeDisplay) needs to be displayed once, then hidden // or it will never appear (supposed to appear on hover). m_timePos->setFrame(false); } void Monitor::slotGoToMarker(QAction *action) { int pos = action->data().toInt(); slotSeek(pos); } void Monitor::slotForceSize(QAction *a) { int resizeType = a->data().toInt(); int profileWidth = 320; int profileHeight = 200; if (resizeType > 0) { // calculate size QRect r = QApplication::desktop()->screenGeometry(); profileHeight = m_glMonitor->profileSize().height() * resizeType / 100; profileWidth = m_glMonitor->profile()->dar() * profileHeight; if (profileWidth > r.width() * 0.8 || profileHeight > r.height() * 0.7) { // reset action to free resize const QList list = m_forceSize->actions(); for (QAction *ac : list) { if (ac->data().toInt() == m_forceSizeFactor) { m_forceSize->setCurrentAction(ac); break; } } warningMessage(i18n("Your screen resolution is not sufficient for this action")); return; } } switch (resizeType) { case 100: case 50: // resize full size setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); m_videoWidget->setMinimumSize(profileWidth, profileHeight); m_videoWidget->setMaximumSize(profileWidth, profileHeight); setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight())); break; default: // Free resize m_videoWidget->setMinimumSize(profileWidth, profileHeight); m_videoWidget->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); setMinimumSize(QSize(profileWidth, profileHeight + m_toolbar->height() + m_glMonitor->getControllerProxy()->rulerHeight())); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); break; } m_forceSizeFactor = resizeType; updateGeometry(); } QString Monitor::getTimecodeFromFrames(int pos) { return m_monitorManager->timecode().getTimecodeFromFrames(pos); } double Monitor::fps() const { return m_monitorManager->timecode().fps(); } Timecode Monitor::timecode() const { return m_monitorManager->timecode(); } void Monitor::updateMarkers() { if (m_controller) { m_markerMenu->clear(); QList markers = m_controller->getMarkerModel()->getAllMarkers(); if (!markers.isEmpty()) { for (int i = 0; i < markers.count(); ++i) { int pos = (int)markers.at(i).time().frames(m_monitorManager->timecode().fps()); QString position = m_monitorManager->timecode().getTimecode(markers.at(i).time()) + QLatin1Char(' ') + markers.at(i).comment(); QAction *go = m_markerMenu->addAction(position); go->setData(pos); } } m_markerMenu->setEnabled(!m_markerMenu->isEmpty()); } } void Monitor::setGuides(const QMap &guides) { // TODO: load guides model m_markerMenu->clear(); QMapIterator i(guides); QList guidesList; while (i.hasNext()) { i.next(); CommentedTime timeGuide(GenTime(i.key()), i.value()); guidesList << timeGuide; int pos = (int)timeGuide.time().frames(m_monitorManager->timecode().fps()); QString position = m_monitorManager->timecode().getTimecode(timeGuide.time()) + QLatin1Char(' ') + timeGuide.comment(); QAction *go = m_markerMenu->addAction(position); go->setData(pos); } // m_ruler->setMarkers(guidesList); m_markerMenu->setEnabled(!m_markerMenu->isEmpty()); checkOverlay(); } void Monitor::slotSeekToPreviousSnap() { if (m_controller) { m_glMonitor->seek(getSnapForPos(true).frames(m_monitorManager->timecode().fps())); } } void Monitor::slotSeekToNextSnap() { if (m_controller) { m_glMonitor->seek(getSnapForPos(false).frames(m_monitorManager->timecode().fps())); } } int Monitor::position() { return m_glMonitor->getCurrentPos(); } GenTime Monitor::getSnapForPos(bool previous) { int frame = previous ? m_snaps->getPreviousPoint(m_glMonitor->getCurrentPos()) : m_snaps->getNextPoint(m_glMonitor->getCurrentPos()); return GenTime(frame, pCore->getCurrentFps()); } void Monitor::slotLoadClipZone(const QPoint &zone) { m_glMonitor->getControllerProxy()->setZone(zone.x(), zone.y()); checkOverlay(); } void Monitor::slotSetZoneStart() { m_glMonitor->getControllerProxy()->setZoneIn(m_glMonitor->getCurrentPos()); if (m_controller) { m_controller->setZone(m_glMonitor->getControllerProxy()->zone()); } else { // timeline emit timelineZoneChanged(); } checkOverlay(); } void Monitor::slotSetZoneEnd(bool discardLastFrame) { int pos = m_glMonitor->getCurrentPos() - (discardLastFrame ? 1 : 0); m_glMonitor->getControllerProxy()->setZoneOut(pos); if (m_controller) { m_controller->setZone(m_glMonitor->getControllerProxy()->zone()); } checkOverlay(); } // virtual void Monitor::mousePressEvent(QMouseEvent *event) { m_monitorManager->activateMonitor(m_id); if ((event->button() & Qt::RightButton) == 0u) { if (m_glWidget->geometry().contains(event->pos())) { m_DragStartPosition = event->pos(); event->accept(); } } else if (m_contextMenu) { slotActivateMonitor(); m_contextMenu->popup(event->globalPos()); event->accept(); } QWidget::mousePressEvent(event); } void Monitor::slotShowMenu(const QPoint pos) { slotActivateMonitor(); if (m_contextMenu) { m_contextMenu->popup(pos); } } void Monitor::resizeEvent(QResizeEvent *event) { Q_UNUSED(event) if (m_glMonitor->zoom() > 0.0f) { float horizontal = float(m_horizontalScroll->value()) / float(m_horizontalScroll->maximum()); float vertical = float(m_verticalScroll->value()) / float(m_verticalScroll->maximum()); adjustScrollBars(horizontal, vertical); } else { m_horizontalScroll->hide(); m_verticalScroll->hide(); } } void Monitor::adjustScrollBars(float horizontal, float vertical) { if (m_glMonitor->zoom() > 1.0f) { m_horizontalScroll->setPageStep(m_glWidget->width()); m_horizontalScroll->setMaximum((int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_horizontalScroll->pageStep()); m_horizontalScroll->setValue(qRound(horizontal * m_horizontalScroll->maximum())); emit m_horizontalScroll->valueChanged(m_horizontalScroll->value()); m_horizontalScroll->show(); } else { int max = (int)((float)m_glMonitor->profileSize().width() * m_glMonitor->zoom()) - m_glWidget->width(); emit m_horizontalScroll->valueChanged(qRound(0.5 * max)); m_horizontalScroll->hide(); } if (m_glMonitor->zoom() > 1.0f) { m_verticalScroll->setPageStep(m_glWidget->height()); m_verticalScroll->setMaximum((int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_verticalScroll->pageStep()); - m_verticalScroll->setValue((int)((float)m_verticalScroll->maximum()*vertical)); + m_verticalScroll->setValue((int)((float)m_verticalScroll->maximum() * vertical)); emit m_verticalScroll->valueChanged(m_verticalScroll->value()); m_verticalScroll->show(); } else { int max = (int)((float)m_glMonitor->profileSize().height() * m_glMonitor->zoom()) - m_glWidget->height(); emit m_verticalScroll->valueChanged(qRound(0.5 * max)); m_verticalScroll->hide(); } } void Monitor::setZoom() { if (qFuzzyCompare(m_glMonitor->zoom(), 1.0f)) { m_horizontalScroll->hide(); m_verticalScroll->hide(); m_glMonitor->setOffsetX(m_horizontalScroll->value(), m_horizontalScroll->maximum()); m_glMonitor->setOffsetY(m_verticalScroll->value(), m_verticalScroll->maximum()); } else { adjustScrollBars(0.5f, 0.5f); } } void Monitor::slotSwitchFullScreen(bool minimizeOnly) { // TODO: disable screensaver? if (!m_glWidget->isFullScreen() && !minimizeOnly) { // Check if we have a multiple monitor setup int monitors = QApplication::desktop()->screenCount(); int screen = -1; if (monitors > 1) { QRect screenres; // Move monitor widget to the second screen (one screen for Kdenlive, the other one for the Monitor widget // int currentScreen = QApplication::desktop()->screenNumber(this); for (int i = 0; screen == -1 && i < QApplication::desktop()->screenCount(); i++) { if (i != QApplication::desktop()->screenNumber(this->parentWidget()->parentWidget())) { screen = i; } } } m_qmlManager->enableAudioThumbs(false); m_glWidget->setParent(QApplication::desktop()->screen(screen)); m_glWidget->move(QApplication::desktop()->screenGeometry(screen).bottomLeft()); m_glWidget->showFullScreen(); } else { m_glWidget->showNormal(); m_qmlManager->enableAudioThumbs(true); QVBoxLayout *lay = (QVBoxLayout *)layout(); lay->insertWidget(0, m_glWidget, 10); } } void Monitor::reparent() { m_glWidget->setParent(nullptr); m_glWidget->showMinimized(); m_glWidget->showNormal(); QVBoxLayout *lay = (QVBoxLayout *)layout(); lay->insertWidget(0, m_glWidget, 10); } // virtual void Monitor::mouseReleaseEvent(QMouseEvent *event) { if (m_dragStarted) { event->ignore(); return; } if (event->button() != Qt::RightButton) { if (m_glMonitor->geometry().contains(event->pos())) { if (isActive()) { slotPlay(); } else { slotActivateMonitor(); } } // else event->ignore(); //QWidget::mouseReleaseEvent(event); } m_dragStarted = false; event->accept(); QWidget::mouseReleaseEvent(event); } void Monitor::slotStartDrag() { if (m_id == Kdenlive::ProjectMonitor || m_controller == nullptr) { // dragging is only allowed for clip monitor return; } auto *drag = new QDrag(this); auto *mimeData = new QMimeData; QByteArray prodData; QPoint p = m_glMonitor->getControllerProxy()->zone(); if (p.x() == -1 || p.y() == -1) { prodData = m_controller->AbstractProjectItem::clipId().toUtf8(); } else { QStringList list; list.append(m_controller->AbstractProjectItem::clipId()); list.append(QString::number(p.x())); list.append(QString::number(p.y())); prodData.append(list.join(QLatin1Char('/')).toUtf8()); } mimeData->setData(QStringLiteral("kdenlive/producerslist"), prodData); drag->setMimeData(mimeData); /*QPixmap pix = m_currentClip->thumbnail(); drag->setPixmap(pix); drag->setHotSpot(QPoint(0, 50));*/ drag->start(Qt::MoveAction); } void Monitor::enterEvent(QEvent *event) { m_qmlManager->enableAudioThumbs(true); QWidget::enterEvent(event); } void Monitor::leaveEvent(QEvent *event) { m_qmlManager->enableAudioThumbs(false); QWidget::leaveEvent(event); } // virtual void Monitor::mouseMoveEvent(QMouseEvent *event) { if (m_dragStarted || m_controller == nullptr) { return; } if ((event->pos() - m_DragStartPosition).manhattanLength() < QApplication::startDragDistance()) { return; } { auto *drag = new QDrag(this); auto *mimeData = new QMimeData; m_dragStarted = true; QStringList list; list.append(m_controller->AbstractProjectItem::clipId()); QPoint p = m_glMonitor->getControllerProxy()->zone(); list.append(QString::number(p.x())); list.append(QString::number(p.y())); QByteArray clipData; clipData.append(list.join(QLatin1Char(';')).toUtf8()); mimeData->setData(QStringLiteral("kdenlive/clip"), clipData); drag->setMimeData(mimeData); drag->start(Qt::MoveAction); } event->accept(); } /*void Monitor::dragMoveEvent(QDragMoveEvent * event) { event->setDropAction(Qt::IgnoreAction); event->setDropAction(Qt::MoveAction); if (event->mimeData()->hasText()) { event->acceptProposedAction(); } } Qt::DropActions Monitor::supportedDropActions() const { // returns what actions are supported when dropping return Qt::MoveAction; }*/ QStringList Monitor::mimeTypes() const { QStringList qstrList; // list of accepted MIME types for drop qstrList.append(QStringLiteral("kdenlive/clip")); return qstrList; } // virtual void Monitor::wheelEvent(QWheelEvent *event) { slotMouseSeek(event->delta(), event->modifiers()); event->accept(); } void Monitor::mouseDoubleClickEvent(QMouseEvent *event) { slotSwitchFullScreen(); event->accept(); } void Monitor::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { slotSwitchFullScreen(); event->accept(); return; } if (m_glWidget->isFullScreen()) { event->ignore(); emit passKeyPress(event); return; } QWidget::keyPressEvent(event); } void Monitor::slotMouseSeek(int eventDelta, uint modifiers) { if ((modifiers & Qt::ControlModifier) != 0u) { int delta = m_monitorManager->timecode().fps(); if (eventDelta > 0) { delta = 0 - delta; } m_glMonitor->seek(m_glMonitor->getCurrentPos() - delta); } else if ((modifiers & Qt::AltModifier) != 0u) { if (eventDelta >= 0) { emit seekToNextSnap(); } else { emit seekToPreviousSnap(); } } else { if (eventDelta >= 0) { slotForwardOneFrame(); } else { slotRewindOneFrame(); } } } void Monitor::slotSetThumbFrame() { if (m_controller == nullptr) { return; } m_controller->setProducerProperty(QStringLiteral("kdenlive:thumbnailFrame"), m_glMonitor->getCurrentPos()); emit refreshClipThumbnail(m_controller->AbstractProjectItem::clipId()); } void Monitor::slotExtractCurrentZone() { if (m_controller == nullptr) { return; } emit extractZone(m_controller->AbstractProjectItem::clipId()); } std::shared_ptr Monitor::currentController() const { return m_controller; } void Monitor::slotExtractCurrentFrame(QString frameName, bool addToProject) { if (QFileInfo(frameName).fileName().isEmpty()) { // convenience: when extracting an image to be added to the project, // suggest a suitable image file name. In the project monitor, this // suggestion bases on the project file name; in the clip monitor, // the suggestion bases on the clip file name currently shown. // Finally, the frame number is added to this suggestion, prefixed // with "-f", so we get something like clip-f#.png. QString suggestedImageName = QFileInfo(currentController() ? currentController()->clipName() : pCore->currentDoc()->url().isValid() ? pCore->currentDoc()->url().fileName() : i18n("untitled")) .completeBaseName() + QStringLiteral("-f") + QString::number(m_glMonitor->getCurrentPos()).rightJustified(6, QLatin1Char('0')) + QStringLiteral(".png"); frameName = QFileInfo(frameName, suggestedImageName).fileName(); } QString framesFolder = KRecentDirs::dir(QStringLiteral(":KdenliveFramesFolder")); if (framesFolder.isEmpty()) { framesFolder = QDir::homePath(); } QScopedPointer dlg(new QDialog(this)); QScopedPointer fileWidget(new KFileWidget(QUrl::fromLocalFile(framesFolder), dlg.data())); dlg->setWindowTitle(addToProject ? i18n("Save Image") : i18n("Save Image to Project")); auto *layout = new QVBoxLayout; layout->addWidget(fileWidget.data()); QCheckBox *b = nullptr; if (m_id == Kdenlive::ClipMonitor) { b = new QCheckBox(i18n("Export image using source resolution"), dlg.data()); b->setChecked(KdenliveSettings::exportframe_usingsourceres()); fileWidget->setCustomWidget(b); } fileWidget->setConfirmOverwrite(true); fileWidget->okButton()->show(); fileWidget->cancelButton()->show(); QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk); QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept); QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept); QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject); dlg->setLayout(layout); fileWidget->setMimeFilter(QStringList() << QStringLiteral("image/png")); fileWidget->setMode(KFile::File | KFile::LocalOnly); fileWidget->setOperationMode(KFileWidget::Saving); QUrl relativeUrl; relativeUrl.setPath(frameName); #if KIO_VERSION >= QT_VERSION_CHECK(5, 33, 0) fileWidget->setSelectedUrl(relativeUrl); #else fileWidget->setSelection(relativeUrl.toString()); #endif KSharedConfig::Ptr conf = KSharedConfig::openConfig(); QWindow *handle = dlg->windowHandle(); if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) { KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize")); dlg->resize(handle->size()); } if (dlg->exec() == QDialog::Accepted) { QString selectedFile = fileWidget->selectedFile(); if (!selectedFile.isEmpty()) { // Create Qimage with frame QImage frame; // check if we are using a proxy if ((m_controller != nullptr) && !m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")).isEmpty() && m_controller->getProducerProperty(QStringLiteral("kdenlive:proxy")) != QLatin1String("-")) { // using proxy, use original clip url to get frame frame = m_monitorController->extractFrame(m_glMonitor->getCurrentPos(), m_controller->getProducerProperty(QStringLiteral("kdenlive:originalurl")), -1, -1, b != nullptr ? b->isChecked() : false); } else { frame = m_monitorController->extractFrame(m_glMonitor->getCurrentPos(), QString(), -1, -1, b != nullptr ? b->isChecked() : false); } frame.save(selectedFile); if (b != nullptr) { KdenliveSettings::setExportframe_usingsourceres(b->isChecked()); } KRecentDirs::add(QStringLiteral(":KdenliveFramesFolder"), QUrl::fromLocalFile(selectedFile).adjusted(QUrl::RemoveFilename).toLocalFile()); if (addToProject) { QStringList folderInfo = pCore->bin()->getFolderInfo(); pCore->bin()->droppedUrls(QList() << QUrl::fromLocalFile(selectedFile), folderInfo); } } } } void Monitor::setTimePos(const QString &pos) { m_timePos->setValue(pos); slotSeek(); } void Monitor::slotSeek() { slotSeek(m_timePos->getValue()); } void Monitor::slotSeek(int pos) { slotActivateMonitor(); m_glMonitor->seek(pos); } void Monitor::checkOverlay(int pos) { if (m_qmlManager->sceneType() != MonitorSceneDefault) { // we are not in main view, ignore return; } QString overlayText; if (pos == -1) { pos = m_timePos->getValue(); } QPoint zone = m_glMonitor->getControllerProxy()->zone(); std::shared_ptr model; if (m_id == Kdenlive::ClipMonitor && m_controller) { model = m_controller->getMarkerModel(); } else if (m_id == Kdenlive::ProjectMonitor && pCore->currentDoc()) { model = pCore->currentDoc()->getGuideModel(); } if (model) { bool found = false; CommentedTime marker = model->getMarker(GenTime(pos, m_monitorManager->timecode().fps()), &found); if (!found) { if (pos == zone.x()) { overlayText = i18n("In Point"); } else if (pos == zone.y()) { overlayText = i18n("Out Point"); } } else { overlayText = marker.comment(); } } m_glMonitor->getControllerProxy()->setMarkerComment(overlayText); } int Monitor::getZoneStart() { return m_glMonitor->getControllerProxy()->zoneIn(); } int Monitor::getZoneEnd() { return m_glMonitor->getControllerProxy()->zoneOut(); } void Monitor::slotZoneStart() { slotActivateMonitor(); m_glMonitor->getControllerProxy()->pauseAndSeek(m_glMonitor->getControllerProxy()->zoneIn()); } void Monitor::slotZoneEnd() { slotActivateMonitor(); m_glMonitor->getControllerProxy()->pauseAndSeek(m_glMonitor->getControllerProxy()->zoneOut()); } void Monitor::slotRewind(double speed) { slotActivateMonitor(); if (qFuzzyIsNull(speed)) { double currentspeed = m_glMonitor->playSpeed(); if (currentspeed >= 0) { speed = -1; } else switch ((int)currentspeed) { case -1: speed = -2; break; case -2: speed = -3; break; case -3: speed = -5; break; default: speed = -8; } } m_glMonitor->switchPlay(true, speed); m_playAction->setActive(true); } void Monitor::slotForward(double speed) { slotActivateMonitor(); if (qFuzzyIsNull(speed)) { double currentspeed = m_glMonitor->playSpeed(); if (currentspeed <= 0) { speed = 1; } else switch ((int)currentspeed) { case 1: speed = 2; break; case 2: speed = 3; break; case 3: speed = 5; break; default: speed = 8; } } m_glMonitor->switchPlay(true, speed); m_playAction->setActive(true); } void Monitor::slotRewindOneFrame(int diff) { slotActivateMonitor(); m_glMonitor->seek(m_glMonitor->getCurrentPos() - diff); } void Monitor::slotForwardOneFrame(int diff) { slotActivateMonitor(); m_glMonitor->seek(m_glMonitor->getCurrentPos() + diff); } void Monitor::seekCursor(int pos) { Q_UNUSED(pos) // Deprecated shoud not be used, instead requestSeek /*if (m_ruler->slotNewValue(pos)) { m_timePos->setValue(pos); checkOverlay(pos); if (m_id != Kdenlive::ClipMonitor) { emit renderPosition(pos); } }*/ } void Monitor::adjustRulerSize(int length, std::shared_ptr markerModel) { if (m_controller != nullptr) { m_glMonitor->setRulerInfo(length); } else { m_glMonitor->setRulerInfo(length, markerModel); } m_timePos->setRange(0, length); if (markerModel) { connect(markerModel.get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this, SLOT(checkOverlay())); connect(markerModel.get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); connect(markerModel.get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); if (m_controller) { markerModel->registerSnapModel(m_snaps); } } } void Monitor::stop() { m_playAction->setActive(false); m_glMonitor->stop(); } void Monitor::mute(bool mute, bool updateIconOnly) { // TODO: we should set the "audio_off" property to 1 to mute the consumer instead of changing volume QIcon icon; if (mute || KdenliveSettings::volume() == 0) { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-muted")); } else { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-medium")); } m_audioButton->setIcon(icon); if (!updateIconOnly) { m_glMonitor->setVolume(mute ? 0 : (double)KdenliveSettings::volume() / 100.0); } } void Monitor::start() { if (!isVisible() || !isActive()) { return; } m_glMonitor->startConsumer(); } void Monitor::slotRefreshMonitor(bool visible) { if (visible) { if (slotActivateMonitor()) { start(); } } } void Monitor::refreshMonitorIfActive() { if (isActive()) { m_glMonitor->requestRefresh(); } } void Monitor::pause() { if (!m_playAction->isActive()) { return; } slotActivateMonitor(); m_glMonitor->switchPlay(false); m_playAction->setActive(false); } void Monitor::switchPlay(bool play) { m_playAction->setActive(play); m_glMonitor->switchPlay(play); } void Monitor::slotSwitchPlay() { slotActivateMonitor(); m_glMonitor->switchPlay(m_playAction->isActive()); } void Monitor::slotPlay() { m_playAction->trigger(); } void Monitor::slotPlayZone() { slotActivateMonitor(); bool ok = m_glMonitor->playZone(); if (ok) { m_playAction->setActive(true); } } void Monitor::slotLoopZone() { slotActivateMonitor(); bool ok = m_glMonitor->playZone(true); if (ok) { m_playAction->setActive(true); } } void Monitor::slotLoopClip() { slotActivateMonitor(); bool ok = m_glMonitor->loopClip(); if (ok) { m_playAction->setActive(true); } } void Monitor::updateClipProducer(Mlt::Producer *prod) { if (m_glMonitor->setProducer(prod, isActive(), -1)) { prod->set_speed(1.0); } } void Monitor::updateClipProducer(const QString &playlist) { Q_UNUSED(playlist) // TODO // Mlt::Producer *prod = new Mlt::Producer(*m_glMonitor->profile(), playlist.toUtf8().constData()); // m_glMonitor->setProducer(prod, isActive(), render->seekFramePosition()); m_glMonitor->switchPlay(true); } void Monitor::slotOpenClip(std::shared_ptr controller, int in, int out) { Q_UNUSED(out) if (m_controller) { disconnect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this, SLOT(checkOverlay())); disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); disconnect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); } m_controller = controller; loadQmlScene(MonitorSceneDefault); m_snaps.reset(new SnapModel()); if (controller) { connect(m_controller->getMarkerModel().get(), SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &, const QVector &)), this, SLOT(checkOverlay())); connect(m_controller->getMarkerModel().get(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); connect(m_controller->getMarkerModel().get(), SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(checkOverlay())); if (m_recManager->toolbar()->isVisible()) { // we are in record mode, don't display clip return; } m_glMonitor->setRulerInfo(m_controller->frameDuration(), controller->getMarkerModel()); m_timePos->setRange(0, m_controller->frameDuration()); updateMarkers(); connect(m_glMonitor->getControllerProxy(), &MonitorProxy::addSnap, this, &Monitor::addSnapPoint, Qt::DirectConnection); connect(m_glMonitor->getControllerProxy(), &MonitorProxy::removeSnap, this, &Monitor::removeSnapPoint, Qt::DirectConnection); m_glMonitor->getControllerProxy()->setZone(m_controller->zone()); m_snaps->addPoint(m_controller->frameDuration()); // Loading new clip / zone, stop if playing if (m_playAction->isActive()) { m_playAction->setActive(false); } m_glMonitor->setProducer(m_controller->originalProducer().get(), isActive(), in); m_audioMeterWidget->audioChannels = controller->audioInfo() ? controller->audioInfo()->channels() : 0; m_glMonitor->setAudioThumb(controller->audioChannels(), controller->audioFrameCache); // hasEffects = controller->hasEffects(); } else { m_glMonitor->setProducer(nullptr, isActive()); m_glMonitor->setAudioThumb(); m_audioMeterWidget->audioChannels = 0; } if (slotActivateMonitor()) { start(); } checkOverlay(); } const QString Monitor::activeClipId() { if (m_controller) { return m_controller->AbstractProjectItem::clipId(); } return QString(); } void Monitor::slotOpenDvdFile(const QString &file) { // TODO Q_UNUSED(file) m_glMonitor->initializeGL(); // render->loadUrl(file); } void Monitor::slotSaveZone() { // TODO? or deprecate // render->saveZone(pCore->currentDoc()->projectDataFolder(), m_ruler->zone()); } void Monitor::setCustomProfile(const QString &profile, const Timecode &tc) { // TODO or deprecate Q_UNUSED(profile) m_timePos->updateTimeCode(tc); if (true) { return; } slotActivateMonitor(); // render->prepareProfileReset(tc.fps()); if (m_multitrackView) { m_multitrackView->setChecked(false); } // TODO: this is a temporary profile for DVD preview, it should not alter project profile // pCore->setCurrentProfile(profile); m_glMonitor->reloadProfile(); } void Monitor::resetProfile() { m_timePos->updateTimeCode(m_monitorManager->timecode()); m_glMonitor->reloadProfile(); m_glMonitor->rootObject()->setProperty("framesize", QRect(0, 0, m_glMonitor->profileSize().width(), m_glMonitor->profileSize().height())); double fps = m_monitorManager->timecode().fps(); // Update drop frame info m_qmlManager->setProperty(QStringLiteral("dropped"), false); m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2)); } /*void Monitor::saveSceneList(const QString &path, const QDomElement &info) { if (render == nullptr) return; render->saveSceneList(path, info); }*/ const QString Monitor::sceneList(const QString &root, const QString &fullPath) { return m_glMonitor->sceneList(root, fullPath); } void Monitor::updateClipZone() { if (m_controller == nullptr) { return; } m_controller->setZone(m_glMonitor->getControllerProxy()->zone()); } void Monitor::updateTimelineClipZone() { emit zoneUpdated(m_glMonitor->getControllerProxy()->zone()); } void Monitor::switchDropFrames(bool drop) { m_glMonitor->setDropFrames(drop); } void Monitor::switchMonitorInfo(int code) { int currentOverlay; if (m_id == Kdenlive::ClipMonitor) { currentOverlay = KdenliveSettings::displayClipMonitorInfo(); currentOverlay ^= code; KdenliveSettings::setDisplayClipMonitorInfo(currentOverlay); } else { currentOverlay = KdenliveSettings::displayProjectMonitorInfo(); currentOverlay ^= code; KdenliveSettings::setDisplayProjectMonitorInfo(currentOverlay); } updateQmlDisplay(currentOverlay); } void Monitor::updateMonitorGamma() { if (isActive()) { stop(); m_glMonitor->updateGamma(); start(); } else { m_glMonitor->updateGamma(); } } void Monitor::slotEditMarker() { if (m_editMarker) { m_editMarker->trigger(); } } void Monitor::updateTimecodeFormat() { m_timePos->slotUpdateTimeCodeFormat(); m_glMonitor->rootObject()->setProperty("timecode", m_timePos->displayText()); } QPoint Monitor::getZoneInfo() const { if (m_controller == nullptr) { return QPoint(); } return m_controller->zone(); } void Monitor::slotEnableEffectScene(bool enable) { KdenliveSettings::setShowOnMonitorScene(enable); MonitorSceneType sceneType = enable ? m_lastMonitorSceneType : MonitorSceneDefault; slotShowEffectScene(sceneType, true); if (enable) { emit seekPosition(m_glMonitor->getCurrentPos()); } } void Monitor::slotShowEffectScene(MonitorSceneType sceneType, bool temporary) { if (sceneType == MonitorSceneNone) { // We just want to revert to normal scene if (m_qmlManager->sceneType() == MonitorSceneSplit || m_qmlManager->sceneType() == MonitorSceneDefault) { // Ok, nothing to do return; } sceneType = MonitorSceneDefault; } if (!temporary) { m_lastMonitorSceneType = sceneType; } loadQmlScene(sceneType); } void Monitor::slotSeekToKeyFrame() { if (m_qmlManager->sceneType() == MonitorSceneGeometry) { // Adjust splitter pos int kfr = m_glMonitor->rootObject()->property("requestedKeyFrame").toInt(); emit seekToKeyframe(kfr); } } void Monitor::setUpEffectGeometry(const QRect &r, const QVariantList &list, const QVariantList &types) { QQuickItem *root = m_glMonitor->rootObject(); if (!root) { return; } if (!list.isEmpty()) { root->setProperty("centerPointsTypes", types); root->setProperty("centerPoints", list); } if (!r.isEmpty()) { root->setProperty("framesize", r); } } void Monitor::setEffectSceneProperty(const QString &name, const QVariant &value) { QQuickItem *root = m_glMonitor->rootObject(); if (!root) { return; } root->setProperty(name.toUtf8().constData(), value); } QRect Monitor::effectRect() const { QQuickItem *root = m_glMonitor->rootObject(); if (!root) { return QRect(); } return root->property("framesize").toRect(); } QVariantList Monitor::effectPolygon() const { QQuickItem *root = m_glMonitor->rootObject(); if (!root) { return QVariantList(); } return root->property("centerPoints").toList(); } QVariantList Monitor::effectRoto() const { QQuickItem *root = m_glMonitor->rootObject(); if (!root) { return QVariantList(); } QVariantList points = root->property("centerPoints").toList(); QVariantList controlPoints = root->property("centerPointsTypes").toList(); // rotoscoping effect needs a list of QVariantList mix; mix.reserve(points.count() * 3); for (int i = 0; i < points.count(); i++) { mix << controlPoints.at(2 * i); mix << points.at(i); mix << controlPoints.at(2 * i + 1); } return mix; } void Monitor::setEffectKeyframe(bool enable) { QQuickItem *root = m_glMonitor->rootObject(); if (root) { root->setProperty("iskeyframe", enable); } } bool Monitor::effectSceneDisplayed(MonitorSceneType effectType) { return m_qmlManager->sceneType() == effectType; } void Monitor::slotSetVolume(int volume) { KdenliveSettings::setVolume(volume); QIcon icon; double renderVolume = m_glMonitor->volume(); m_glMonitor->setVolume((double)volume / 100.0); if (renderVolume > 0 && volume > 0) { return; } if (volume == 0) { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-muted")); } else { icon = KoIconUtils::themedIcon(QStringLiteral("audio-volume-medium")); } m_audioButton->setIcon(icon); } void Monitor::sendFrameForAnalysis(bool analyse) { m_glMonitor->sendFrameForAnalysis = analyse; } void Monitor::updateAudioForAnalysis() { m_glMonitor->updateAudioForAnalysis(); } void Monitor::onFrameDisplayed(const SharedFrame &frame) { m_monitorManager->frameDisplayed(frame); int position = frame.get_position(); if (!m_glMonitor->checkFrameNumber(position)) { m_playAction->setActive(false); } /* else if (position >= m_length) { m_playAction->setActive(false); }*/ } void Monitor::checkDrops(int dropped) { if (m_droppedTimer.isValid()) { if (m_droppedTimer.hasExpired(1000)) { m_droppedTimer.invalidate(); double fps = m_monitorManager->timecode().fps(); if (dropped == 0) { // No dropped frames since last check m_qmlManager->setProperty(QStringLiteral("dropped"), false); m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2)); } else { m_glMonitor->resetDrops(); fps -= dropped; m_qmlManager->setProperty(QStringLiteral("dropped"), true); m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(fps, 'g', 2)); m_droppedTimer.start(); } } } else if (dropped > 0) { // Start m_dropTimer m_glMonitor->resetDrops(); m_droppedTimer.start(); } } void Monitor::reloadProducer(const QString &id) { if (!m_controller) { return; } if (m_controller->AbstractProjectItem::clipId() == id) { slotOpenClip(m_controller); } } QString Monitor::getMarkerThumb(GenTime pos) { if (!m_controller) { return QString(); } if (!m_controller->getClipHash().isEmpty()) { QString url = m_monitorManager->getCacheFolder(CacheThumbs) .absoluteFilePath(m_controller->getClipHash() + QLatin1Char('#') + QString::number((int)pos.frames(m_monitorManager->timecode().fps())) + QStringLiteral(".png")); if (QFile::exists(url)) { return url; } } return QString(); } const QString Monitor::projectFolder() const { return m_monitorManager->getProjectFolder(); } void Monitor::setPalette(const QPalette &p) { QWidget::setPalette(p); QList allButtons = this->findChildren(); for (int i = 0; i < allButtons.count(); i++) { QToolButton *m = allButtons.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) { continue; } QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setIcon(newIcon); } m_audioMeterWidget->refreshPixmap(); } void Monitor::gpuError() { qCWarning(KDENLIVE_LOG) << " + + + + Error initializing Movit GLSL manager"; warningMessage(i18n("Cannot initialize Movit's GLSL manager, please disable Movit"), -1); } void Monitor::warningMessage(const QString &text, int timeout, const QList &actions) { m_infoMessage->setMessageType(KMessageWidget::Warning); m_infoMessage->setText(text); for (QAction *action : actions) { m_infoMessage->addAction(action); } m_infoMessage->setCloseButtonVisible(true); m_infoMessage->animatedShow(); if (timeout > 0) { QTimer::singleShot(timeout, m_infoMessage, &KMessageWidget::animatedHide); } } void Monitor::activateSplit() { loadQmlScene(MonitorSceneSplit); if (isActive()) { m_glMonitor->requestRefresh(); } else if (slotActivateMonitor()) { start(); } } void Monitor::slotSwitchCompare(bool enable) { if (m_id == Kdenlive::ProjectMonitor) { if (enable) { if (m_qmlManager->sceneType() == MonitorSceneSplit) { // Split scene is already active return; } m_splitEffect = new Mlt::Filter(*profile(), "frei0r.alphagrad"); if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) { m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter m_splitEffect->set("1", 0); // 1 is gradient width m_splitEffect->set("2", -0.747); // 2 is tilt } else { // frei0r.scal0tilt is not available warningMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive")); return; } emit createSplitOverlay(m_splitEffect); return; } // Delete temp track emit removeSplitOverlay(); delete m_splitEffect; m_splitEffect = nullptr; loadQmlScene(MonitorSceneDefault); if (isActive()) { m_glMonitor->requestRefresh(); } else if (slotActivateMonitor()) { start(); } return; } if (m_controller == nullptr || !m_controller->hasEffects()) { // disable split effect if (m_controller) { pCore->displayMessage(i18n("Clip has no effects"), InformationMessage); } else { pCore->displayMessage(i18n("Select a clip in project bin to compare effect"), InformationMessage); } return; } if (enable) { if (m_qmlManager->sceneType() == MonitorSceneSplit) { // Split scene is already active return; } buildSplitEffect(m_controller->masterProducer()); } else if (m_splitEffect) { // TODO m_glMonitor->setProducer(m_controller->originalProducer().get(), isActive(), position()); delete m_splitEffect; m_splitProducer = nullptr; m_splitEffect = nullptr; loadQmlScene(MonitorSceneDefault); } slotActivateMonitor(); } void Monitor::buildSplitEffect(Mlt::Producer *original) { qDebug() << "// BUILDING SPLIT EFFECT!!!"; m_splitEffect = new Mlt::Filter(*profile(), "frei0r.alphagrad"); if ((m_splitEffect != nullptr) && m_splitEffect->is_valid()) { m_splitEffect->set("0", 0.5); // 0 is the Clip left parameter m_splitEffect->set("1", 0); // 1 is gradient width m_splitEffect->set("2", -0.747); // 2 is tilt } else { // frei0r.scal0tilt is not available pCore->displayMessage(i18n("The alphagrad filter is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage); return; } QString splitTransition = TransitionsRepository::get()->getCompositingTransition(); Mlt::Transition t(*profile(), splitTransition.toUtf8().constData()); if (!t.is_valid()) { delete m_splitEffect; pCore->displayMessage(i18n("The cairoblend transition is required for that feature, please install frei0r and restart Kdenlive"), ErrorMessage); return; } Mlt::Tractor trac(*profile()); // TODO: remove usage of Clip class Clip clp(*original); Mlt::Producer *clone = clp.clone(); Clip clp2(*clone); clp2.deleteEffects(); trac.set_track(*original, 0); trac.set_track(*clone, 1); clone->attach(*m_splitEffect); t.set("always_active", 1); trac.plant_transition(t, 0, 1); delete clone; delete original; m_splitProducer = new Mlt::Producer(trac.get_producer()); m_glMonitor->setProducer(m_splitProducer, isActive(), position()); loadQmlScene(MonitorSceneSplit); } QSize Monitor::profileSize() const { return m_glMonitor->profileSize(); } void Monitor::loadQmlScene(MonitorSceneType type) { if (m_id == Kdenlive::DvdMonitor || type == m_qmlManager->sceneType()) { return; } bool sceneWithEdit = type == MonitorSceneGeometry || type == MonitorSceneCorners || type == MonitorSceneRoto; if ((m_sceneVisibilityAction != nullptr) && !m_sceneVisibilityAction->isChecked() && sceneWithEdit) { // User doesn't want effect scenes type = MonitorSceneDefault; } double ratio = (double)m_glMonitor->profileSize().width() / (int)(m_glMonitor->profileSize().height() * m_glMonitor->profile()->dar() + 0.5); m_qmlManager->setScene(m_id, type, m_glMonitor->profileSize(), ratio, m_glMonitor->displayRect(), m_glMonitor->zoom(), m_timePos->maximum()); QQuickItem *root = m_glMonitor->rootObject(); connectQmlToolbar(root); switch (type) { case MonitorSceneSplit: QObject::connect(root, SIGNAL(qmlMoveSplit()), this, SLOT(slotAdjustEffectCompare()), Qt::UniqueConnection); break; case MonitorSceneGeometry: case MonitorSceneCorners: case MonitorSceneRoto: QObject::connect(root, SIGNAL(addKeyframe()), this, SIGNAL(addKeyframe()), Qt::UniqueConnection); QObject::connect(root, SIGNAL(seekToKeyframe()), this, SLOT(slotSeekToKeyFrame()), Qt::UniqueConnection); break; case MonitorSceneRipple: QObject::connect(root, SIGNAL(doAcceptRipple(bool)), this, SIGNAL(acceptRipple(bool)), Qt::UniqueConnection); QObject::connect(root, SIGNAL(switchTrimMode(int)), this, SIGNAL(switchTrimMode(int)), Qt::UniqueConnection); break; case MonitorSceneDefault: QObject::connect(root, SIGNAL(editCurrentMarker()), this, SLOT(slotEditInlineMarker()), Qt::UniqueConnection); m_qmlManager->setProperty(QStringLiteral("timecode"), m_timePos->displayText()); if (m_id == Kdenlive::ClipMonitor) { updateQmlDisplay(KdenliveSettings::displayClipMonitorInfo()); } else if (m_id == Kdenlive::ProjectMonitor) { updateQmlDisplay(KdenliveSettings::displayProjectMonitorInfo()); } break; default: break; } m_qmlManager->setProperty(QStringLiteral("fps"), QString::number(m_monitorManager->timecode().fps(), 'g', 2)); } void Monitor::connectQmlToolbar(QQuickItem *root) { - //TODO: get rid of this horrible hack and use triggerAction in qml + // TODO: get rid of this horrible hack and use triggerAction in qml // Effect monitor toolbar QObject *button = root->findChild(QStringLiteral("nextKeyframe")); if (button) { QObject::connect(button, SIGNAL(clicked()), this, SIGNAL(seekToNextKeyframe()), Qt::UniqueConnection); } button = root->findChild(QStringLiteral("prevKeyframe")); if (button) { QObject::connect(button, SIGNAL(clicked()), this, SIGNAL(seekToPreviousKeyframe()), Qt::UniqueConnection); } button = root->findChild(QStringLiteral("addKeyframe")); if (button) { QObject::connect(button, SIGNAL(clicked()), this, SIGNAL(addKeyframe()), Qt::UniqueConnection); } button = root->findChild(QStringLiteral("removeKeyframe")); if (button) { QObject::connect(button, SIGNAL(clicked()), this, SIGNAL(deleteKeyframe()), Qt::UniqueConnection); } } void Monitor::setQmlProperty(const QString &name, const QVariant &value) { m_qmlManager->setProperty(name, value); } void Monitor::slotAdjustEffectCompare() { QRect r = m_glMonitor->rect(); double percent = 0.5; if (m_qmlManager->sceneType() == MonitorSceneSplit) { // Adjust splitter pos QQuickItem *root = m_glMonitor->rootObject(); percent = 0.5 - ((root->property("splitterPos").toInt() - r.left() - r.width() / 2.0) / (double)r.width() / 2.0) / 0.75; // Store real frame percentage for resize events root->setProperty("realpercent", percent); } if (m_splitEffect) { m_splitEffect->set("0", percent); } m_glMonitor->refresh(); } Mlt::Profile *Monitor::profile() { return m_glMonitor->profile(); } void Monitor::slotSwitchRec(bool enable) { if (!m_recManager) { return; } if (enable) { m_toolbar->setVisible(false); m_recManager->toolbar()->setVisible(true); } else if (m_recManager->toolbar()->isVisible()) { m_recManager->stop(); m_toolbar->setVisible(true); emit refreshCurrentClip(); } } bool Monitor::startCapture(const QString ¶ms, const QString &path, Mlt::Producer *p) { // TODO m_controller = nullptr; if (false) { // render->updateProducer(p)) { m_glMonitor->reconfigureMulti(params, path, p->profile()); return true; } return false; } bool Monitor::stopCapture() { m_glMonitor->stopCapture(); slotOpenClip(nullptr); m_glMonitor->reconfigure(profile()); return true; } void Monitor::doKeyPressEvent(QKeyEvent *ev) { keyPressEvent(ev); } void Monitor::slotEditInlineMarker() { QQuickItem *root = m_glMonitor->rootObject(); if (root) { std::shared_ptr model; if (m_controller) { // We are editing a clip marker model = m_controller->getMarkerModel(); } else { model = pCore->currentDoc()->getGuideModel(); } QString newComment = root->property("markerText").toString(); bool found = false; CommentedTime oldMarker = model->getMarker(m_timePos->gentime(), &found); if (!found || newComment == oldMarker.comment()) { // No change return; } oldMarker.setComment(newComment); model->addMarker(oldMarker.time(), oldMarker.comment(), oldMarker.markerType()); } } void Monitor::prepareAudioThumb(int channels, QVariantList &audioCache) { m_glMonitor->setAudioThumb(channels, audioCache); } void Monitor::slotUpdateQmlTimecode(const QString &tc) { checkDrops(m_glMonitor->droppedFrames()); m_glMonitor->rootObject()->setProperty("timecode", tc); } void Monitor::slotSwitchAudioMonitor() { if (!m_audioMeterWidget->isValid) { KdenliveSettings::setMonitoraudio(0x01); m_audioMeterWidget->setVisibility(false); return; } int currentOverlay = KdenliveSettings::monitoraudio(); currentOverlay ^= m_id; KdenliveSettings::setMonitoraudio(currentOverlay); if ((KdenliveSettings::monitoraudio() & m_id) != 0) { // We want to enable this audio monitor, so make monitor active slotActivateMonitor(); } displayAudioMonitor(isActive()); } void Monitor::displayAudioMonitor(bool isActive) { bool enable = isActive && ((KdenliveSettings::monitoraudio() & m_id) != 0); if (enable) { connect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame, Qt::UniqueConnection); } else { disconnect(m_monitorManager, &MonitorManager::frameDisplayed, m_audioMeterWidget, &ScopeWidget::onNewFrame); } m_audioMeterWidget->setVisibility((KdenliveSettings::monitoraudio() & m_id) != 0); } void Monitor::updateQmlDisplay(int currentOverlay) { m_glMonitor->rootObject()->setVisible((currentOverlay & 0x01) != 0); m_glMonitor->rootObject()->setProperty("showMarkers", currentOverlay & 0x04); m_glMonitor->rootObject()->setProperty("showFps", currentOverlay & 0x20); m_glMonitor->rootObject()->setProperty("showTimecode", currentOverlay & 0x02); bool showTimecodeRelatedInfo = ((currentOverlay & 0x02) != 0) || ((currentOverlay & 0x20) != 0); m_timePos->sendTimecode(showTimecodeRelatedInfo); if (showTimecodeRelatedInfo) { connect(m_timePos, &TimecodeDisplay::emitTimeCode, this, &Monitor::slotUpdateQmlTimecode, Qt::UniqueConnection); } else { disconnect(m_timePos, &TimecodeDisplay::emitTimeCode, this, &Monitor::slotUpdateQmlTimecode); } m_glMonitor->rootObject()->setProperty("showAudiothumb", currentOverlay & 0x10); } void Monitor::clearDisplay() { m_glMonitor->clear(); } void Monitor::panView(QPoint diff) { // Only pan if scrollbars are visible if (m_horizontalScroll->isVisible()) { m_horizontalScroll->setValue(m_horizontalScroll->value() + diff.x()); } if (m_verticalScroll->isVisible()) { m_verticalScroll->setValue(m_verticalScroll->value() + diff.y()); } } void Monitor::requestSeek(int pos) { m_glMonitor->seek(pos); } void Monitor::setProducer(Mlt::Producer *producer, int pos) { m_glMonitor->setProducer(producer, isActive(), pos); } void Monitor::reconfigure() { m_glMonitor->reconfigure(); } void Monitor::slotSeekPosition(int pos) { emit seekPosition(pos); m_timePos->setValue(pos); checkOverlay(); } void Monitor::slotStart() { slotActivateMonitor(); m_glMonitor->switchPlay(false); m_glMonitor->seek(0); } void Monitor::slotEnd() { slotActivateMonitor(); m_glMonitor->switchPlay(false); m_glMonitor->seek(m_glMonitor->duration()); } void Monitor::addSnapPoint(int pos) { m_snaps->addPoint(pos); } void Monitor::removeSnapPoint(int pos) { m_snaps->removePoint(pos); } void Monitor::slotZoomIn() { m_glMonitor->slotZoom(true); } void Monitor::slotZoomOut() { m_glMonitor->slotZoom(false); } diff --git a/src/monitor/monitor.h b/src/monitor/monitor.h index 44c7b3406..40030a966 100644 --- a/src/monitor/monitor.h +++ b/src/monitor/monitor.h @@ -1,375 +1,375 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) 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 MONITOR_H #define MONITOR_H #include "abstractmonitor.h" #include "bin/model/markerlistmodel.hpp" #include "definitions.h" #include "effectslist/effectslist.h" #include "gentime.h" #include "scopes/sharedframe.h" #include "timecodedisplay.h" #include #include #include #include #include #include class SnapModel; class ProjectClip; class MonitorManager; class QSlider; class KDualAction; class KSelectAction; class KMessageWidget; class QQuickItem; class QScrollBar; class RecManager; class QToolButton; class QmlManager; class GLWidget; class MonitorAudioLevel; class MonitorController; namespace Mlt { class Profile; class Filter; -} +} // namespace Mlt class QuickEventEater : public QObject { Q_OBJECT public: explicit QuickEventEater(QObject *parent = nullptr); protected: bool eventFilter(QObject *obj, QEvent *event) override; signals: void addEffect(const QStringList &); }; class QuickMonitorEventEater : public QObject { Q_OBJECT public: explicit QuickMonitorEventEater(QWidget *parent); protected: bool eventFilter(QObject *obj, QEvent *event) override; signals: void doKeyPressEvent(QKeyEvent *); }; class Monitor : public AbstractMonitor { Q_OBJECT public: friend class MonitorManager; Monitor(Kdenlive::MonitorId id, MonitorManager *manager, QWidget *parent = nullptr); ~Monitor(); void resetProfile(); void setCustomProfile(const QString &profile, const Timecode &tc); void setupMenu(QMenu *goMenu, QMenu *overlayMenu, QAction *playZone, QAction *loopZone, QMenu *markerMenu = nullptr, QAction *loopClip = nullptr); const QString sceneList(const QString &root, const QString &fullPath = QString()); const QString activeClipId(); int position(); void updateTimecodeFormat(); void updateMarkers(); /** @brief Controller for the clip currently displayed (only valid for clip monitor). */ std::shared_ptr currentController() const; /** @brief Add timeline guides to the ruler and context menu */ void setGuides(const QMap &guides); void reloadProducer(const QString &id); /** @brief Reimplemented from QWidget, updates the palette colors. */ void setPalette(const QPalette &p); /** @brief Returns a hh:mm:ss timecode from a frame number. */ QString getTimecodeFromFrames(int pos); /** @brief Returns current project's fps. */ double fps() const; /** @brief Returns current project's timecode. */ Timecode timecode() const; /** @brief Get url for the clip's thumbnail */ QString getMarkerThumb(GenTime pos); /** @brief Get current project's folder */ const QString projectFolder() const; /** @brief Get the project's Mlt profile */ Mlt::Profile *profile(); int getZoneStart(); int getZoneEnd(); void setUpEffectGeometry(const QRect &r, const QVariantList &list = QVariantList(), const QVariantList &types = QVariantList()); /** @brief Set a property on the effect scene */ void setEffectSceneProperty(const QString &name, const QVariant &value); /** @brief Returns effective display size */ QSize profileSize() const; QRect effectRect() const; QVariantList effectPolygon() const; QVariantList effectRoto() const; void setEffectKeyframe(bool enable); void sendFrameForAnalysis(bool analyse); void updateAudioForAnalysis(); void switchMonitorInfo(int code); void switchDropFrames(bool drop); void updateMonitorGamma(); void mute(bool, bool updateIconOnly = false) override; bool startCapture(const QString ¶ms, const QString &path, Mlt::Producer *p); bool stopCapture(); void reparent(); /** @brief Returns the action displaying record toolbar */ QAction *recAction(); void refreshIcons(); /** @brief Send audio thumb data to qml for on monitor display */ void prepareAudioThumb(int channels, QVariantList &audioCache); void connectAudioSpectrum(bool activate); /** @brief Set a property on the Qml scene **/ void setQmlProperty(const QString &name, const QVariant &value); void displayAudioMonitor(bool isActive); /** @brief Prepare split effect from timeline clip producer **/ void activateSplit(); /** @brief Clear monitor display **/ void clearDisplay(); void setProducer(Mlt::Producer *producer, int pos = -1); - void reconfigure(); + void reconfigure(); /** @brief Saves current monitor frame to an image file, and add it to project if addToProject is set to true **/ void slotExtractCurrentFrame(QString frameName = QString(), bool addToProject = false); /** @brief Zoom in active monitor */ void slotZoomIn(); /** @brief Zoom out active monitor */ void slotZoomOut(); protected: void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *event) override; /** @brief Move to another position on mouse wheel event. * * Moves towards the end of the clip/timeline on mouse wheel down/back, the * opposite on mouse wheel up/forward. * Ctrl + wheel moves by a second, without Ctrl it moves by a single frame. */ void wheelEvent(QWheelEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; virtual QStringList mimeTypes() const; MonitorController *m_monitorController; private: std::shared_ptr m_controller; /** @brief The QQuickView that handles our monitor display (video and qml overlay) **/ GLWidget *m_glMonitor; /** @brief Container for our QQuickView monitor display (QQuickView needs to be embedded) **/ QWidget *m_glWidget; /** @brief Scrollbar for our monitor view, used when zooming the monitor **/ QScrollBar *m_verticalScroll; /** @brief Scrollbar for our monitor view, used when zooming the monitor **/ QScrollBar *m_horizontalScroll; /** @brief Widget holding the window for the QQuickView **/ QWidget *m_videoWidget; /** @brief Manager for qml overlay for the QQuickView **/ QmlManager *m_qmlManager; std::shared_ptr m_snaps; Mlt::Filter *m_splitEffect; Mlt::Producer *m_splitProducer; int m_length; bool m_dragStarted; // TODO: Move capture stuff in own class RecManager *m_recManager; /** @brief The widget showing current time position **/ TimecodeDisplay *m_timePos; KDualAction *m_playAction; KSelectAction *m_forceSize; /** Has to be available so we can enable and disable it. */ QAction *m_loopClipAction; QAction *m_sceneVisibilityAction; QAction *m_zoomVisibilityAction; QAction *m_multitrackView; QMenu *m_contextMenu; QMenu *m_configMenu; QMenu *m_playMenu; QMenu *m_markerMenu; QPoint m_DragStartPosition; /** true if selected clip is transition, false = selected clip is clip. * Necessary because sometimes we get two signals, e.g. we get a clip and we get selected transition = nullptr. */ bool m_loopClipTransition; GenTime getSnapForPos(bool previous); QToolBar *m_toolbar; QToolButton *m_audioButton; QSlider *m_audioSlider; QAction *m_editMarker; KMessageWidget *m_infoMessage; int m_forceSizeFactor; MonitorSceneType m_lastMonitorSceneType; MonitorAudioLevel *m_audioMeterWidget; QElapsedTimer m_droppedTimer; double m_displayedFps; void adjustScrollBars(float horizontal, float vertical); void loadQmlScene(MonitorSceneType type); void updateQmlDisplay(int currentOverlay); /** @brief Connect qml on monitor toolbar buttons */ void connectQmlToolbar(QQuickItem *root); /** @brief Check and display dropped frames */ void checkDrops(int dropped); /** @brief Create temporary Mlt::Tractor holding a clip and it's effectless clone */ void buildSplitEffect(Mlt::Producer *original); private slots: Q_DECL_DEPRECATED void seekCursor(int pos); void slotSetThumbFrame(); void slotSaveZone(); void slotSeek(); void updateClipZone(); void slotGoToMarker(QAction *action); void slotSetVolume(int volume); void slotEditMarker(); void slotExtractCurrentZone(); void onFrameDisplayed(const SharedFrame &frame); void slotStartDrag(); void setZoom(); void slotEnableEffectScene(bool enable); void slotAdjustEffectCompare(); void slotShowMenu(const QPoint pos); void slotForceSize(QAction *a); void slotSeekToKeyFrame(); /** @brief Display a non blocking error message to user **/ void warningMessage(const QString &text, int timeout = 5000, const QList &actions = QList()); void slotLockMonitor(bool lock); void slotAddEffect(const QStringList &effect); void slotSwitchPlay(); void slotEditInlineMarker(); /** @brief Pass keypress event to mainwindow */ void doKeyPressEvent(QKeyEvent *); /** @brief The timecode was updated, refresh qml display */ void slotUpdateQmlTimecode(const QString &tc); /** @brief There was an error initializing Movit */ void gpuError(); void setOffsetX(int x); void setOffsetY(int y); /** @brief Pan monitor view */ void panView(QPoint diff); /** @brief Project monitor zone changed, inform timeline */ void updateTimelineClipZone(); void slotSeekPosition(int); void addSnapPoint(int pos); void removeSnapPoint(int pos); public slots: void slotOpenDvdFile(const QString &); // void slotSetClipProducer(DocClipBase *clip, QPoint zone = QPoint(), bool forceUpdate = false, int position = -1); void updateClipProducer(Mlt::Producer *prod); void updateClipProducer(const QString &playlist); void slotOpenClip(std::shared_ptr controller, int in = -1, int out = -1); void slotRefreshMonitor(bool visible); void slotSeek(int pos); void stop() override; void start() override; void switchPlay(bool play); void slotPlay() override; void pause(); void slotPlayZone(); void slotLoopZone(); /** @brief Loops the selected item (clip or transition). */ void slotLoopClip(); void slotForward(double speed = 0); void slotRewind(double speed = 0); void slotRewindOneFrame(int diff = 1); void slotForwardOneFrame(int diff = 1); void slotStart(); void slotEnd(); void slotSetZoneStart(); void slotSetZoneEnd(bool discardLastFrame = false); void slotZoneStart(); void slotZoneEnd(); void slotLoadClipZone(const QPoint &zone); void slotSeekToNextSnap(); void slotSeekToPreviousSnap(); void adjustRulerSize(int length, std::shared_ptr markerModel = nullptr); void setTimePos(const QString &pos); QPoint getZoneInfo() const; /** @brief Display the on monitor effect scene (to adjust geometry over monitor). */ void slotShowEffectScene(MonitorSceneType sceneType, bool temporary = false); bool effectSceneDisplayed(MonitorSceneType effectType); /** @brief split screen to compare clip with and without effect */ void slotSwitchCompare(bool enable); void slotMouseSeek(int eventDelta, uint modifiers); void slotSwitchFullScreen(bool minimizeOnly = false) override; /** @brief Display or hide the record toolbar */ void slotSwitchRec(bool enable); /** @brief Request QImage of current frame */ void slotGetCurrentImage(bool request); /** @brief Enable/disable display of monitor's audio levels widget */ void slotSwitchAudioMonitor(); /** @brief Request seeking */ void requestSeek(int pos); /** @brief Check current position to show relevant infos in qml view (markers, zone in/out, etc). */ void checkOverlay(int pos = -1); void refreshMonitorIfActive() override; signals: void seekPosition(int); /** @brief Request a timeline seeking if diff is true, position is a relative offset, otherwise an absolute position */ void seekTimeline(int position); void durationChanged(int); void refreshClipThumbnail(const QString &); void zoneUpdated(const QPoint &); void timelineZoneChanged(); /** @brief Editing transitions / effects over the monitor requires the renderer to send frames as QImage. * This causes a major slowdown, so we only enable it if required */ void requestFrameForAnalysis(bool); /** @brief Request a zone extraction (ffmpeg transcoding). */ void extractZone(const QString &id); void effectChanged(const QRect &); void effectPointsChanged(const QVariantList &); void addKeyframe(); void deleteKeyframe(); void seekToNextKeyframe(); void seekToPreviousKeyframe(); void seekToKeyframe(int); void addClipToProject(const QUrl &); void showConfigDialog(int, int); /** @brief Request display of current bin clip. */ void refreshCurrentClip(); void addEffect(const QStringList &); void addMasterEffect(QString, const QStringList &); void passKeyPress(QKeyEvent *); /** @brief Enable / disable project monitor multitrack view (split view with one track in each quarter). */ void multitrackView(bool); void timeCodeUpdated(const QString &); void addMarker(); void deleteMarker(bool deleteGuide = true); void seekToPreviousSnap(); void seekToNextSnap(); void createSplitOverlay(Mlt::Filter *); void removeSplitOverlay(); void acceptRipple(bool); void switchTrimMode(int); }; #endif diff --git a/src/monitor/qmlmanager.h b/src/monitor/qmlmanager.h index 0bb160654..63eec4240 100644 --- a/src/monitor/qmlmanager.h +++ b/src/monitor/qmlmanager.h @@ -1,65 +1,65 @@ /*************************************************************************** * 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 . * ***************************************************************************/ /*! -* @class QmlManager -* @brief Manages all Qml monitor overlays -* @author Jean-Baptiste Mardelle -*/ + * @class QmlManager + * @brief Manages all Qml monitor overlays + * @author Jean-Baptiste Mardelle + */ #ifndef QMLMANAGER_H #define QMLMANAGER_H #include "definitions.h" class QQuickView; class QmlManager : public QObject { Q_OBJECT public: explicit QmlManager(QQuickView *view); /** @brief Show / hide audio thumbnail preview */ void enableAudioThumbs(bool enable); /** @brief return current active scene type */ MonitorSceneType sceneType() const; /** @brief Set a property on the root item */ void setProperty(const QString &name, const QVariant &value); /** @brief Load a monitor scene */ void setScene(Kdenlive::MonitorId id, MonitorSceneType type, QSize profile, double profileStretch, QRect displayRect, double zoom, int duration); private: QQuickView *m_view; MonitorSceneType m_sceneType; private slots: void effectRectChanged(); void effectPolygonChanged(); void effectRotoChanged(); signals: void effectChanged(const QRect &); void effectPointsChanged(const QVariantList &); }; #endif diff --git a/src/monitor/recmanager.cpp b/src/monitor/recmanager.cpp index 818a16b25..a812f5438 100644 --- a/src/monitor/recmanager.cpp +++ b/src/monitor/recmanager.cpp @@ -1,447 +1,445 @@ /*************************************************************************** * Copyright (C) 2015 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 "recmanager.h" #include "capture/managecapturesdialog.h" #include "capture/mltdevicecapture.h" #include "dialogs/profilesdialog.h" #include "kdenlivesettings.h" #include "monitor.h" #include "utils/KoIconUtils.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include RecManager::RecManager(Monitor *parent) : QObject(parent) , m_monitor(parent) , m_captureProcess(nullptr) , m_recToolbar(new QToolBar(parent)) , m_screenCombo(nullptr) { m_playAction = m_recToolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), i18n("Preview")); m_playAction->setCheckable(true); connect(m_playAction, &QAction::toggled, this, &RecManager::slotPreview); m_recAction = m_recToolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("media-record")), i18n("Record")); m_recAction->setCheckable(true); connect(m_recAction, &QAction::toggled, this, &RecManager::slotRecord); m_showLogAction = new QAction(i18n("Show log"), this); connect(m_showLogAction, &QAction::triggered, this, &RecManager::slotShowLog); m_recVideo = new QCheckBox(i18n("Video")); m_recAudio = new QCheckBox(i18n("Audio")); m_recToolbar->addWidget(m_recVideo); m_recToolbar->addWidget(m_recAudio); m_recAudio->setChecked(KdenliveSettings::v4l_captureaudio()); m_recVideo->setChecked(KdenliveSettings::v4l_capturevideo()); // Check number of monitors for FFmpeg screen capture int screens = QApplication::desktop()->screenCount(); if (screens > 1) { m_screenCombo = new QComboBox(parent); for (int ix = 0; ix < screens; ix++) { m_screenCombo->addItem(i18n("Monitor %1", ix)); } m_recToolbar->addWidget(m_screenCombo); // Update screen grab monitor choice in case we changed from fullscreen m_screenCombo->setEnabled(KdenliveSettings::grab_capture_type() == 0); } QWidget *spacer = new QWidget(parent); spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_recToolbar->addWidget(spacer); m_device_selector = new QComboBox(parent); // TODO: re-implement firewire / decklink capture // m_device_selector->addItems(QStringList() << i18n("Firewire") << i18n("Webcam") << i18n("Screen Grab") << i18n("Blackmagic Decklink")); m_device_selector->addItem(i18n("Webcam"), Video4Linux); m_device_selector->addItem(i18n("Screen Grab"), ScreenGrab); int selectedCapture = m_device_selector->findData(KdenliveSettings::defaultcapture()); if (selectedCapture > -1) { m_device_selector->setCurrentIndex(selectedCapture); } connect(m_device_selector, SIGNAL(currentIndexChanged(int)), this, SLOT(slotVideoDeviceChanged(int))); m_recToolbar->addWidget(m_device_selector); QAction *configureRec = m_recToolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure Recording")); connect(configureRec, &QAction::triggered, this, &RecManager::showRecConfig); m_recToolbar->addSeparator(); m_switchRec = m_recToolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("list-add")), i18n("Show Record Control")); m_switchRec->setCheckable(true); connect(m_switchRec, &QAction::toggled, m_monitor, &Monitor::slotSwitchRec); m_recToolbar->setVisible(false); slotVideoDeviceChanged(); } -RecManager::~RecManager() -{ -} +RecManager::~RecManager() {} void RecManager::showRecConfig() { m_monitor->showConfigDialog(4, m_device_selector->currentData().toInt()); } QToolBar *RecManager::toolbar() const { return m_recToolbar; } QAction *RecManager::switchAction() const { return m_switchRec; } void RecManager::stopCapture() { if (m_captureProcess) { slotRecord(false); } } void RecManager::stop() { if (m_captureProcess) { // Don't stop screen rec when hiding rec toolbar } else { stopCapture(); m_switchRec->setChecked(false); } toolbar()->setVisible(false); } void RecManager::slotRecord(bool record) { if (m_device_selector->currentData().toInt() == Video4Linux) { if (record) { QDir captureFolder; if (KdenliveSettings::capturetoprojectfolder()) { captureFolder = QDir(m_monitor->projectFolder()); } else { captureFolder = QDir(KdenliveSettings::capturefolder()); } QString extension; if (!m_recVideo->isChecked()) { extension = QStringLiteral("wav"); } else { extension = KdenliveSettings::v4l_extension(); } QString path = captureFolder.absoluteFilePath("capture0000." + extension); int i = 1; while (QFile::exists(path)) { QString num = QString::number(i).rightJustified(4, '0', false); path = captureFolder.absoluteFilePath("capture" + num + QLatin1Char('.') + extension); ++i; } QString v4lparameters = KdenliveSettings::v4l_parameters(); // TODO: when recording audio only, allow param configuration? if (!m_recVideo->isChecked()) { v4lparameters.clear(); } // Add alsa audio capture if (!m_recAudio->isChecked()) { // if we do not want audio, make sure that we don't have audio encoding parameters // this is required otherwise the MLT avformat consumer will not close properly if (v4lparameters.contains(QStringLiteral("acodec"))) { QString endParam = v4lparameters.section(QStringLiteral("acodec"), 1); int vcodec = endParam.indexOf(QStringLiteral(" vcodec")); int format = endParam.indexOf(QStringLiteral(" f=")); int cutPosition = -1; if (vcodec > -1) { if (format > -1) { cutPosition = qMin(vcodec, format); } else { cutPosition = vcodec; } } else if (format > -1) { cutPosition = format; } else { // nothing interesting in end params endParam.clear(); } if (cutPosition > -1) { endParam.remove(0, cutPosition); } v4lparameters = QString(v4lparameters.section(QStringLiteral("acodec"), 0, 0) + QStringLiteral("an=1 ") + endParam).simplified(); } } Mlt::Producer *prod = createV4lProducer(); if ((prod != nullptr) && prod->is_valid()) { m_monitor->startCapture(v4lparameters, path, prod); m_captureFile = QUrl::fromLocalFile(path); } else { m_recAction->blockSignals(true); m_recAction->setChecked(false); m_recAction->blockSignals(false); emit warningMessage(i18n("Capture crashed, please check your parameters")); } } else { m_monitor->stopCapture(); emit addClipToProject(m_captureFile); } return; } if (!record) { if (!m_captureProcess) { return; } m_captureProcess->terminate(); QTimer::singleShot(1500, m_captureProcess, &QProcess::kill); return; } if (m_captureProcess) { return; } m_recError.clear(); QString extension = KdenliveSettings::grab_extension(); QDir captureFolder; if (KdenliveSettings::capturetoprojectfolder()) { captureFolder = QDir(m_monitor->projectFolder()); } else { captureFolder = QDir(KdenliveSettings::capturefolder()); } QFileInfo checkCaptureFolder(captureFolder.absolutePath()); if (!checkCaptureFolder.isWritable()) { emit warningMessage(i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", captureFolder.absolutePath())); m_recAction->blockSignals(true); m_recAction->setChecked(false); m_recAction->blockSignals(false); return; } m_captureProcess = new QProcess; connect(m_captureProcess, &QProcess::stateChanged, this, &RecManager::slotProcessStatus); connect(m_captureProcess, &QProcess::readyReadStandardError, this, &RecManager::slotReadProcessInfo); QString path = captureFolder.absoluteFilePath("capture0000." + extension); int i = 1; while (QFile::exists(path)) { QString num = QString::number(i).rightJustified(4, '0', false); path = captureFolder.absoluteFilePath("capture" + num + QLatin1Char('.') + extension); ++i; } m_captureFile = QUrl::fromLocalFile(path); QString captureSize; int screen = -1; if (m_screenCombo) { // Multi monitor setup, capture monitor selected by user screen = m_screenCombo->currentIndex(); } QRect screenSize = QApplication::desktop()->screenGeometry(screen); QStringList captureArgs; captureArgs << QStringLiteral("-f") << QStringLiteral("x11grab"); if (KdenliveSettings::grab_follow_mouse()) { captureArgs << QStringLiteral("-follow_mouse") << QStringLiteral("centered"); } if (!KdenliveSettings::grab_hide_frame()) { captureArgs << QStringLiteral("-show_region") << QStringLiteral("1"); } captureSize = QStringLiteral(":0.0"); if (KdenliveSettings::grab_capture_type() == 0) { // Full screen capture captureArgs << QStringLiteral("-s") << QString::number(screenSize.width()) + QLatin1Char('x') + QString::number(screenSize.height()); captureSize.append(QLatin1Char('+') + QString::number(screenSize.left()) + QLatin1Char('.') + QString::number(screenSize.top())); } else { // Region capture captureArgs << QStringLiteral("-s") << QString::number(KdenliveSettings::grab_width()) + QLatin1Char('x') + QString::number(KdenliveSettings::grab_height()); captureSize.append(QLatin1Char('+') + QString::number(KdenliveSettings::grab_offsetx()) + QLatin1Char(',') + QString::number(KdenliveSettings::grab_offsety())); } // fps captureArgs << QStringLiteral("-r") << QString::number(KdenliveSettings::grab_fps()); if (KdenliveSettings::grab_hide_mouse()) { captureSize.append(QStringLiteral("+nomouse")); } captureArgs << QStringLiteral("-i") << captureSize; if (!KdenliveSettings::grab_parameters().simplified().isEmpty()) { captureArgs << KdenliveSettings::grab_parameters().simplified().split(QLatin1Char(' ')); } captureArgs << path; m_captureProcess->start(KdenliveSettings::ffmpegpath(), captureArgs); if (!m_captureProcess->waitForStarted()) { // Problem launching capture app emit warningMessage(i18n("Failed to start the capture application:\n%1", KdenliveSettings::ffmpegpath())); // delete m_captureProcess; } } void RecManager::slotProcessStatus(QProcess::ProcessState status) { if (status == QProcess::NotRunning) { m_recAction->setEnabled(true); m_recAction->setChecked(false); m_device_selector->setEnabled(true); if (m_captureProcess) { if (m_captureProcess->exitStatus() == QProcess::CrashExit) { emit warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList() << m_showLogAction); } else { if (true) { int code = m_captureProcess->exitCode(); if (code != 0 && code != 255) { emit warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList() << m_showLogAction); } else { // Capture successful, add clip to project emit addClipToProject(m_captureFile); } } } } delete m_captureProcess; m_captureProcess = nullptr; } } void RecManager::slotReadProcessInfo() { QString data = m_captureProcess->readAllStandardError().simplified(); m_recError.append(data + QLatin1Char('\n')); } void RecManager::slotVideoDeviceChanged(int) { int currentItem = m_device_selector->currentData().toInt(); KdenliveSettings::setDefaultcapture(currentItem); switch (currentItem) { case Video4Linux: m_playAction->setEnabled(true); break; case BlackMagic: m_playAction->setEnabled(false); break; default: m_playAction->setEnabled(false); break; } /* m_previewSettings->setEnabled(ix == Video4Linux || ix == BlackMagic); control_frame->setVisible(ix == Video4Linux); monitor_box->setVisible(ix == ScreenBag && monitor_box->count() > 0); m_playAction->setVisible(ix != ScreenBag); m_fwdAction->setVisible(ix == Firewire); m_discAction->setVisible(ix == Firewire); m_rewAction->setVisible(ix == Firewire); m_recAction->setEnabled(ix != Firewire); m_logger.setVisible(ix == BlackMagic); if (m_captureDevice) { // MLT capture still running, abort m_monitorManager->clearScopeSource(); m_captureDevice->stop(); delete m_captureDevice; m_captureDevice = nullptr; } // The m_videoBox container has to be shown once before the MLT consumer is build, or preview will fail switch (ix) { case ScreenBag: } */ } Mlt::Producer *RecManager::createV4lProducer() { QString profilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); Mlt::Profile *vidProfile = new Mlt::Profile(profilePath.toUtf8().constData()); Mlt::Producer *prod = nullptr; if (m_recVideo->isChecked()) { prod = new Mlt::Producer(*vidProfile, QStringLiteral("video4linux2:%1").arg(KdenliveSettings::video4vdevice()).toUtf8().constData()); if ((prod == nullptr) || !prod->is_valid()) { return nullptr; } prod->set("width", vidProfile->width()); prod->set("height", vidProfile->height()); prod->set("framerate", vidProfile->fps()); /*p->set("standard", ui->v4lStandardCombo->currentText().toLatin1().constData()); p->set("channel", ui->v4lChannelSpinBox->value()); p->set("audio_ix", ui->v4lAudioComboBox->currentIndex());*/ prod->set("force_seekable", 0); } if (m_recAudio->isChecked() && (prod != nullptr) && prod->is_valid()) { // Add audio track Mlt::Producer *audio = new Mlt::Producer( *vidProfile, QStringLiteral("alsa:%1?channels=%2").arg(KdenliveSettings::v4l_alsadevicename()).arg(KdenliveSettings::alsachannels()).toUtf8().constData()); audio->set("mlt_service", "avformat-novalidate"); audio->set("audio_index", 0); audio->set("video_index", -1); auto *tractor = new Mlt::Tractor(*vidProfile); tractor->set_track(*prod, 0); delete prod; tractor->set_track(*audio, 1); delete audio; prod = new Mlt::Producer(tractor->get_producer()); delete tractor; } return prod; } void RecManager::slotPreview(bool preview) { if (m_device_selector->currentData().toInt() == Video4Linux) { if (preview) { Mlt::Producer *prod = createV4lProducer(); if ((prod != nullptr) && prod->is_valid()) { m_monitor->updateClipProducer(prod); } else { emit warningMessage(i18n("Capture crashed, please check your parameters")); } } else { m_monitor->slotOpenClip(nullptr); } } /* buildMltDevice(path); bool isXml; producer = getV4lXmlPlaylist(profile, &isXml); //producer = QString("avformat-novalidate:video4linux2:%1?width:%2&height:%3&frame_rate:%4").arg(KdenliveSettings::video4vdevice()).arg(profile.width).arg(profile.height).arg((double) profile.frame_rate_num / profile.frame_rate_den); if (!m_captureDevice->slotStartPreview(producer, isXml)) { // v4l capture failed to start video_frame->setText(i18n("Failed to start Video4Linux,\ncheck your parameters...")); } else { m_playAction->setEnabled(false); m_stopAction->setEnabled(true); m_isPlaying = true; } }*/ } void RecManager::slotShowLog() { KMessageBox::information(QApplication::activeWindow(), m_recError); } diff --git a/src/monitor/recmanager.h b/src/monitor/recmanager.h index 05bc98b14..065388496 100644 --- a/src/monitor/recmanager.h +++ b/src/monitor/recmanager.h @@ -1,98 +1,98 @@ /*************************************************************************** * Copyright (C) 2015 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 . * ***************************************************************************/ /*! -* @class RecManager -* @brief All recording specific features are gathered here -* @author Jean-Baptiste Mardelle -*/ + * @class RecManager + * @brief All recording specific features are gathered here + * @author Jean-Baptiste Mardelle + */ #ifndef RECMANAGER_H #define RECMANAGER_H #include "definitions.h" #include #include class Monitor; class QAction; class QToolBar; class QComboBox; class QCheckBox; namespace Mlt { class Producer; } class RecManager : public QObject { Q_OBJECT enum CaptureDevice { Video4Linux = 0, ScreenGrab = 1, // Not implemented Firewire = 2, BlackMagic = 3 }; public: explicit RecManager(Monitor *parent = nullptr); ~RecManager(); QToolBar *toolbar() const; void stopCapture(); QAction *switchAction() const; /** @brief: stop capture and hide rec panel **/ void stop(); private: Monitor *m_monitor; QAction *m_switchRec; QString m_captureFolder; QUrl m_captureFile; QString m_recError; QProcess *m_captureProcess; QAction *m_recAction; QAction *m_playAction; QAction *m_showLogAction; QToolBar *m_recToolbar; QComboBox *m_screenCombo; QComboBox *m_device_selector; QCheckBox *m_recVideo; QCheckBox *m_recAudio; Mlt::Producer *createV4lProducer(); private slots: void slotRecord(bool record); void slotPreview(bool record); void slotProcessStatus(QProcess::ProcessState status); void slotReadProcessInfo(); void showRecConfig(); void slotVideoDeviceChanged(int ix = -1); void slotShowLog(); signals: void addClipToProject(const QUrl &); void warningMessage(const QString &, int timeout = 5000, const QList &actions = QList()); }; #endif diff --git a/src/monitor/scopes/audiographspectrum.h b/src/monitor/scopes/audiographspectrum.h index a5e9b2bf4..2b9f6f4f1 100644 --- a/src/monitor/scopes/audiographspectrum.h +++ b/src/monitor/scopes/audiographspectrum.h @@ -1,99 +1,99 @@ /*************************************************************************** * 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 . * ***************************************************************************/ /*! -* @class AudioGraphSpectrum -* @brief An audio spectrum -* @author Jean-Baptiste Mardelle -*/ + * @class AudioGraphSpectrum + * @brief An audio spectrum + * @author Jean-Baptiste Mardelle + */ #ifndef AUDIOGRAPHSPECTRUM_H #define AUDIOGRAPHSPECTRUM_H #include "scopewidget.h" #include "sharedframe.h" #include #include #include namespace Mlt { class Filter; } class MonitorManager; /*class EqualizerWidget : public QWidget { Q_OBJECT public: EqualizerWidget(QWidget *parent = nullptr); };*/ class AudioGraphWidget : public QWidget { Q_OBJECT public: explicit AudioGraphWidget(QWidget *parent = nullptr); void drawBackground(); public slots: void showAudio(const QVector &bands); protected: void paintEvent(QPaintEvent *pe) override; void resizeEvent(QResizeEvent *event) override; private: QVector m_levels; QVector m_dbLabels; QStringList m_freqLabels; QPixmap m_pixmap; QRect m_rect; int m_maxDb; void drawDbLabels(QPainter &p, const QRect &rect); void drawChanLabels(QPainter &p, const QRect &rect, int barWidth); }; class AudioGraphSpectrum : public ScopeWidget { Q_OBJECT public: AudioGraphSpectrum(MonitorManager *manager, QWidget *parent = nullptr); virtual ~AudioGraphSpectrum(); private: MonitorManager *m_manager; Mlt::Filter *m_filter; AudioGraphWidget *m_graphWidget; // EqualizerWidget *m_equalizer; void processSpectrum(); void refreshScope(const QSize &size, bool full) override; public slots: void refreshPixmap(); private slots: void activate(bool enable); }; #endif diff --git a/src/monitor/scopes/dataqueue.h b/src/monitor/scopes/dataqueue.h index d679937b6..c73add9c9 100644 --- a/src/monitor/scopes/dataqueue.h +++ b/src/monitor/scopes/dataqueue.h @@ -1,157 +1,155 @@ /* * Copyright (c) 2015 Meltytech, LLC * Author: Brian Matherly * * 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 3 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, see . */ #ifndef DATAQUEUE_H #define DATAQUEUE_H #include #include #include #include /*! \class DataQueue \brief The DataQueue provides a thread safe container for passing data between objects. \threadsafe DataQueue provides a limited size container for passing data between objects. One object can add data to the queue by calling push() while another object can remove items from the queue by calling pop(). DataQueue provides configurable behavior for handling overflows. It can discard the oldest, discard the newest or block the object calling push() until room has been freed in the queue by another object calling pop(). DataQueue is threadsafe and is therefore most appropriate when passing data between objects operating in different thread contexts. */ template class DataQueue { public: //! Overflow behavior modes. typedef enum { OverflowModeDiscardOldest = 0, //!< Discard oldest items OverflowModeDiscardNewest, //!< Discard newest items OverflowModeWait //!< Wait for space to be free } OverflowMode; /*! Constructs a DataQueue. The \a size will be the maximum queue size and the \a mode will dictate overflow behavior. */ explicit DataQueue(int maxSize, OverflowMode mode); //! Destructs a DataQueue. virtual ~DataQueue(); /*! Pushes an item into the queue. If the queue is full and overflow mode is OverflowModeWait then this function will block until pop() is called. */ void push(const T &item); /*! Pops an item from the queue. If the queue is empty then this function will block. If blocking is undesired, then check the return of count() before calling pop(). */ T pop(); //! Returns the number of items in the queue. int count() const; private: QList m_queue; int m_maxSize; OverflowMode m_mode; mutable QMutex m_mutex; QWaitCondition m_notEmptyCondition; QWaitCondition m_notFullCondition; }; template DataQueue::DataQueue(int maxSize, OverflowMode mode) : m_queue() , m_maxSize(maxSize) , m_mode(mode) , m_mutex(QMutex::NonRecursive) , m_notEmptyCondition() , m_notFullCondition() { } -template DataQueue::~DataQueue() -{ -} +template DataQueue::~DataQueue() {} template void DataQueue::push(const T &item) { m_mutex.lock(); if (m_queue.size() == m_maxSize) { switch (m_mode) { case OverflowModeDiscardOldest: m_queue.removeFirst(); m_queue.append(item); break; case OverflowModeDiscardNewest: // This item is the newest so discard it and exit break; case OverflowModeWait: m_notFullCondition.wait(&m_mutex); m_queue.append(item); break; } } else { m_queue.append(item); if (m_queue.size() == 1) { m_notEmptyCondition.wakeOne(); } } m_mutex.unlock(); } template T DataQueue::pop() { T retVal; m_mutex.lock(); if (m_queue.size() == 0) { m_notEmptyCondition.wait(&m_mutex); } retVal = m_queue.takeFirst(); if (m_mode == OverflowModeWait && m_queue.size() == m_maxSize - 1) { m_notFullCondition.wakeOne(); } m_mutex.unlock(); return retVal; } template int DataQueue::count() const { QMutexLocker locker(&m_mutex); return m_queue.size(); } #endif // DATAQUEUE_H diff --git a/src/monitor/scopes/monitoraudiolevel.h b/src/monitor/scopes/monitoraudiolevel.h index 481cdaf2c..e14132e5f 100644 --- a/src/monitor/scopes/monitoraudiolevel.h +++ b/src/monitor/scopes/monitoraudiolevel.h @@ -1,64 +1,64 @@ /* Copyright (C) 2016 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 . */ #ifndef MONITORAUDIOLEVEL_H #define MONITORAUDIOLEVEL_H #include "scopewidget.h" #include namespace Mlt { class Profile; class Filter; -} +} // namespace Mlt class MonitorAudioLevel : public ScopeWidget { Q_OBJECT public: explicit MonitorAudioLevel(Mlt::Profile *profile, int height, QWidget *parent = nullptr); virtual ~MonitorAudioLevel(); void refreshPixmap(); int audioChannels; bool isValid; void setVisibility(bool enable); protected: void paintEvent(QPaintEvent *) override; void resizeEvent(QResizeEvent *event) override; private: Mlt::Filter *m_filter; int m_height; QPixmap m_pixmap; QVector m_peaks; QVector m_values; int m_channelHeight; int m_channelDistance; int m_channelFillHeight; void drawBackground(int channels = 2); void refreshScope(const QSize &size, bool full) override; private slots: void setAudioValues(const QVector &values); }; #endif diff --git a/src/project/dialogs/backupwidget.cpp b/src/project/dialogs/backupwidget.cpp index a9c934631..80a32a6dc 100644 --- a/src/project/dialogs/backupwidget.cpp +++ b/src/project/dialogs/backupwidget.cpp @@ -1,124 +1,122 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "backupwidget.h" #include "kdenlivesettings.h" #include #include #include BackupWidget::BackupWidget(const QUrl &projectUrl, const QUrl &projectFolder, const QString &projectId, QWidget *parent) : QDialog(parent) , m_projectFolder(projectFolder) { setupUi(this); setWindowTitle(i18n("Restore Backup File")); if (!projectUrl.isValid()) { // No url, means we opened the backup dialog from an empty project info_label->setText(i18n("Showing all backup files in folder")); m_projectWildcard = QLatin1Char('*'); } else { info_label->setText(i18n("Showing backup files for %1", projectUrl.fileName())); m_projectWildcard = projectUrl.fileName().section(QLatin1Char('.'), 0, -2); if (!projectId.isEmpty()) { m_projectWildcard.append(QLatin1Char('-') + projectId); } else { // No project id, it was lost, add wildcard m_projectWildcard.append(QLatin1Char('*')); } } m_projectWildcard.append(QStringLiteral("-??")); m_projectWildcard.append(QStringLiteral("??")); m_projectWildcard.append(QStringLiteral("-??")); m_projectWildcard.append(QStringLiteral("-??")); m_projectWildcard.append(QStringLiteral("-??")); m_projectWildcard.append(QStringLiteral("-??.kdenlive")); slotParseBackupFiles(); connect(backup_list, &QListWidget::currentRowChanged, this, &BackupWidget::slotDisplayBackupPreview); backup_list->setCurrentRow(0); backup_list->setMinimumHeight(QFontMetrics(font()).lineSpacing() * 12); slotParseBackupFiles(); } -BackupWidget::~BackupWidget() -{ -} +BackupWidget::~BackupWidget() {} void BackupWidget::slotParseBackupFiles() { QStringList filter; filter << m_projectWildcard; backup_list->clear(); // Parse new XDG backup folder $HOME/.local/share/kdenlive/.backup QDir backupFolder(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/.backup")); backupFolder.setNameFilters(filter); QFileInfoList resultList = backupFolder.entryInfoList(QDir::Files, QDir::Time); for (int i = 0; i < resultList.count(); ++i) { QString label = resultList.at(i).lastModified().toString(Qt::SystemLocaleLongDate); if (m_projectWildcard.startsWith(QLatin1Char('*'))) { // Displaying all backup files, so add project name in the entries label.prepend(resultList.at(i).fileName().section(QLatin1Char('-'), 0, -7) + QStringLiteral(".kdenlive - ")); } auto *item = new QListWidgetItem(label, backup_list); item->setData(Qt::UserRole, resultList.at(i).absoluteFilePath()); item->setToolTip(resultList.at(i).absoluteFilePath()); } // Parse old $HOME/kdenlive/.backup folder QDir dir(m_projectFolder.toLocalFile() + QStringLiteral("/.backup")); if (dir.exists()) { dir.setNameFilters(filter); QFileInfoList resultList2 = dir.entryInfoList(QDir::Files, QDir::Time); for (int i = 0; i < resultList2.count(); ++i) { QString label = resultList2.at(i).lastModified().toString(Qt::SystemLocaleLongDate); if (m_projectWildcard.startsWith(QLatin1Char('*'))) { // Displaying all backup files, so add project name in the entries label.prepend(resultList2.at(i).fileName().section(QLatin1Char('-'), 0, -7) + QStringLiteral(".kdenlive - ")); } auto *item = new QListWidgetItem(label, backup_list); item->setData(Qt::UserRole, resultList2.at(i).absoluteFilePath()); item->setToolTip(resultList2.at(i).absoluteFilePath()); } } buttonBox->button(QDialogButtonBox::Open)->setEnabled(backup_list->count() > 0); } void BackupWidget::slotDisplayBackupPreview() { if (!backup_list->currentItem()) { backup_preview->setPixmap(QPixmap()); return; } const QString path = backup_list->currentItem()->data(Qt::UserRole).toString(); QPixmap pix(path + QStringLiteral(".png")); backup_preview->setPixmap(pix); } QString BackupWidget::selectedFile() const { if (!backup_list->currentItem()) { return QString(); } return backup_list->currentItem()->data(Qt::UserRole).toString(); } diff --git a/src/project/dialogs/profilewidget.cpp b/src/project/dialogs/profilewidget.cpp index 993f70e9c..7565fcd83 100644 --- a/src/project/dialogs/profilewidget.cpp +++ b/src/project/dialogs/profilewidget.cpp @@ -1,266 +1,264 @@ /* Copyright (C) 2016 Jean-Baptiste Mardelle Copyright (C) 2017 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 "profilewidget.h" #include "kxmlgui_version.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "profiles/tree/profilefilter.hpp" #include "profiles/tree/profiletreemodel.hpp" #include "utils/KoIconUtils.h" #include #include #include #include #include ProfileWidget::ProfileWidget(QWidget *parent) : QWidget(parent) { m_originalProfile = QStringLiteral("invalid"); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); auto *lay = new QVBoxLayout; lay->setContentsMargins(0, 0, 0, 0); auto *labelLay = new QHBoxLayout; QLabel *fpsLabel = new QLabel(i18n("Fps"), this); fpsFilt = new QComboBox(this); fpsLabel->setBuddy(fpsFilt); labelLay->addWidget(fpsLabel); labelLay->addWidget(fpsFilt); QLabel *scanningLabel = new QLabel(i18n("Scanning"), this); scanningFilt = new QComboBox(this); scanningLabel->setBuddy(scanningFilt); labelLay->addWidget(scanningLabel); labelLay->addWidget(scanningFilt); labelLay->addStretch(1); auto *manage_profiles = new QToolButton(this); labelLay->addWidget(manage_profiles); manage_profiles->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); manage_profiles->setToolTip(i18n("Manage project profiles")); connect(manage_profiles, &QAbstractButton::clicked, this, &ProfileWidget::slotEditProfiles); lay->addLayout(labelLay); auto *profileSplitter = new QSplitter; m_treeView = new QTreeView(this); m_treeModel = ProfileTreeModel::construct(this); m_filter = new ProfileFilter(this); m_filter->setSourceModel(m_treeModel.get()); m_treeView->setModel(m_filter); for (int i = 1; i < m_treeModel->columnCount(); ++i) { m_treeView->hideColumn(i); } m_treeView->header()->hide(); QItemSelectionModel *selectionModel = m_treeView->selectionModel(); connect(selectionModel, &QItemSelectionModel::currentRowChanged, this, &ProfileWidget::slotChangeSelection); connect(selectionModel, &QItemSelectionModel::selectionChanged, [&](const QItemSelection &selected, const QItemSelection &deselected) { QModelIndex current, old; if (!selected.indexes().isEmpty()) { current = selected.indexes().front(); } if (!deselected.indexes().isEmpty()) { old = deselected.indexes().front(); } slotChangeSelection(current, old); }); profileSplitter->addWidget(m_treeView); m_treeView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_descriptionPanel = new QTextEdit(this); m_descriptionPanel->setReadOnly(true); m_descriptionPanel->viewport()->setCursor(Qt::ArrowCursor); m_descriptionPanel->viewport()->setBackgroundRole(QPalette::Mid); m_descriptionPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_descriptionPanel->setFrameStyle(QFrame::NoFrame); profileSplitter->addWidget(m_descriptionPanel); lay->addWidget(profileSplitter); profileSplitter->setStretchFactor(0, 4); profileSplitter->setStretchFactor(1, 3); refreshFpsCombo(); auto updateFps = [&]() { double current = fpsFilt->currentData().toDouble(); KdenliveSettings::setProfile_fps_filter(fpsFilt->currentText()); m_filter->setFilterFps(current > 0, current); slotFilterChanged(); }; connect(fpsFilt, static_cast(&QComboBox::currentIndexChanged), updateFps); int ix = fpsFilt->findText(KdenliveSettings::profile_fps_filter()); if (ix > -1) { fpsFilt->setCurrentIndex(ix); } scanningFilt->addItem("Any", -1); scanningFilt->addItem("Interlaced", 0); scanningFilt->addItem("Progressive", 1); auto updateScanning = [&]() { int current = scanningFilt->currentData().toInt(); KdenliveSettings::setProfile_scanning_filter(scanningFilt->currentText()); m_filter->setFilterInterlaced(current != -1, current == 0); slotFilterChanged(); }; connect(scanningFilt, static_cast(&QComboBox::currentIndexChanged), updateScanning); ix = scanningFilt->findText(KdenliveSettings::profile_scanning_filter()); if (ix > -1) { scanningFilt->setCurrentIndex(ix); } setLayout(lay); } -ProfileWidget::~ProfileWidget() -{ -} +ProfileWidget::~ProfileWidget() {} void ProfileWidget::refreshFpsCombo() { QLocale locale; QVariant currentValue; if (fpsFilt->count() > 1) { // remember last selected value currentValue = fpsFilt->currentData(); } fpsFilt->clear(); locale.setNumberOptions(QLocale::OmitGroupSeparator); fpsFilt->addItem("Any", -1); auto all_fps = ProfileRepository::get()->getAllFps(); for (double fps : all_fps) { fpsFilt->addItem(locale.toString(fps), fps); } if (currentValue.isValid()) { int ix = fpsFilt->findData(currentValue); if (ix > -1) { fpsFilt->setCurrentIndex(ix); } } } void ProfileWidget::loadProfile(const QString &profile) { auto index = m_treeModel->findProfile(profile); if (index.isValid()) { m_originalProfile = m_currentProfile = m_lastValidProfile = profile; if (!trySelectProfile(profile)) { // When loading a profile, ensure it is visible so reset filters if necessary fpsFilt->setCurrentIndex(0); scanningFilt->setCurrentIndex(0); } } } const QString ProfileWidget::selectedProfile() const { return m_currentProfile; } void ProfileWidget::slotEditProfiles() { auto *w = new ProfilesDialog(m_currentProfile); w->exec(); if (w->profileTreeChanged()) { // Rebuild profiles tree m_treeModel.reset(); m_treeModel = ProfileTreeModel::construct(this); m_filter->setSourceModel(m_treeModel.get()); refreshFpsCombo(); loadProfile(m_currentProfile); } delete w; } void ProfileWidget::fillDescriptionPanel(const QString &profile_path) { QString description; if (profile_path.isEmpty()) { description += i18n("No profile selected"); } else { std::unique_ptr &profile = ProfileRepository::get()->getProfile(profile_path); description += i18n("
    Video Settings
    "); description += i18n("

    Frame size: %1 x %2 (%3:%4)
    ", profile->width(), profile->height(), profile->display_aspect_num(), profile->display_aspect_den()); description += i18n("Frame rate: %1 fps
    ", profile->fps()); description += i18n("Pixel Aspect Ratio: %1
    ", profile->sar()); description += i18n("Color Space: %1
    ", profile->colorspaceDescription()); QString interlaced = i18n("yes"); if (profile->progressive()) { interlaced = i18n("no"); } description += i18n("Interlaced : %1

    ", interlaced); } m_descriptionPanel->setHtml(description); } void ProfileWidget::slotChangeSelection(const QModelIndex ¤t, const QModelIndex &previous) { auto originalIndex = m_filter->mapToSource(current); if (m_treeModel->parent(originalIndex) == QModelIndex()) { // in that case, we have selected a category, which we don't want QItemSelectionModel *selection = m_treeView->selectionModel(); selection->select(previous, QItemSelectionModel::Select); return; } m_currentProfile = m_treeModel->getProfile(originalIndex); if (!m_currentProfile.isEmpty()) { m_lastValidProfile = m_currentProfile; } if (m_originalProfile != m_currentProfile) { emit profileChanged(); } fillDescriptionPanel(m_currentProfile); } bool ProfileWidget::trySelectProfile(const QString &profile) { auto index = m_treeModel->findProfile(profile); if (index.isValid()) { // check if element is visible if (m_filter->isVisible(index)) { // reselect QItemSelectionModel *selection = m_treeView->selectionModel(); selection->select(m_filter->mapFromSource(index), QItemSelectionModel::Select); // expand corresponding category auto parent = m_treeModel->parent(index); m_treeView->expand(m_filter->mapFromSource(parent)); m_treeView->scrollTo(m_filter->mapFromSource(index), QAbstractItemView::PositionAtCenter); return true; } } return false; } void ProfileWidget::slotFilterChanged() { // When filtering change, we must check if the current profile is still visible. if (!trySelectProfile(m_currentProfile)) { // we try to back-up the last valid profile if (!trySelectProfile(m_lastValidProfile)) { // Everything fails, we don't have any profile m_currentProfile = QString(); emit profileChanged(); fillDescriptionPanel(QString()); } } } diff --git a/src/project/dialogs/projectsettings.cpp b/src/project/dialogs/projectsettings.cpp index db8b57d34..59324b17f 100644 --- a/src/project/dialogs/projectsettings.cpp +++ b/src/project/dialogs/projectsettings.cpp @@ -1,813 +1,816 @@ /*************************************************************************** * Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@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) 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 "projectsettings.h" #include "bin/bin.h" #include "core.h" #include "dialogs/encodingprofilesdialog.h" #include "dialogs/profilesdialog.h" #include "doc/kdenlivedoc.h" #include "effectslist/effectslist.h" #include "kdenlivesettings.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/clipcontroller.h" #include "profiles/profilemodel.hpp" #include "project/dialogs/profilewidget.h" #include "project/dialogs/temporarydata.h" #include "titler/titlewidget.h" #include "utils/KoIconUtils.h" #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include class NoEditDelegate : public QStyledItemDelegate { public: NoEditDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) { } QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(parent); Q_UNUSED(option); Q_UNUSED(index); return nullptr; } }; ProjectSettings::ProjectSettings(KdenliveDoc *doc, QMap metadata, const QStringList &lumas, int videotracks, int audiotracks, const QString & /*projectPath*/, bool readOnlyTracks, bool savedProject, QWidget *parent) : QDialog(parent) , m_savedProject(savedProject) , m_lumas(lumas) { setupUi(this); tabWidget->setTabBarAutoHide(true); auto *vbox = new QVBoxLayout; vbox->setContentsMargins(0, 0, 0, 0); m_pw = new ProfileWidget(this); vbox->addWidget(m_pw); profile_box->setLayout(vbox); profile_box->setTitle(i18n("Select the profile (preset) of the project")); list_search->setTreeWidget(files_list); project_folder->setMode(KFile::Directory); m_buttonOk = buttonBox->button(QDialogButtonBox::Ok); // buttonOk->setEnabled(false); audio_thumbs->setChecked(KdenliveSettings::audiothumbnails()); video_thumbs->setChecked(KdenliveSettings::videothumbnails()); audio_tracks->setValue(audiotracks); video_tracks->setValue(videotracks); connect(generate_proxy, &QAbstractButton::toggled, proxy_minsize, &QWidget::setEnabled); connect(generate_imageproxy, &QAbstractButton::toggled, proxy_imageminsize, &QWidget::setEnabled); connect(generate_imageproxy, &QAbstractButton::toggled, image_label, &QWidget::setEnabled); connect(generate_imageproxy, &QAbstractButton::toggled, proxy_imagesize, &QWidget::setEnabled); connect(resize_preview, &QAbstractButton::toggled, preview_height, &QWidget::setEnabled); QString currentProf; if (doc) { currentProf = pCore->getCurrentProfile()->path(); enable_proxy->setChecked(doc->getDocumentProperty(QStringLiteral("enableproxy")).toInt() != 0); generate_proxy->setChecked(doc->getDocumentProperty(QStringLiteral("generateproxy")).toInt() != 0); proxy_minsize->setValue(doc->getDocumentProperty(QStringLiteral("proxyminsize")).toInt()); preview_height->setValue(doc->getDocumentProperty(QStringLiteral("previewheight")).toInt()); resize_preview->setChecked(doc->getDocumentProperty(QStringLiteral("resizepreview")).toInt() != 0); preview_height->setEnabled(resize_preview->isChecked()); m_proxyparameters = doc->getDocumentProperty(QStringLiteral("proxyparams")); generate_imageproxy->setChecked(doc->getDocumentProperty(QStringLiteral("generateimageproxy")).toInt() != 0); proxy_imageminsize->setValue(doc->getDocumentProperty(QStringLiteral("proxyimageminsize")).toInt()); proxy_imagesize->setValue(doc->getDocumentProperty(QStringLiteral("proxyimagesize")).toInt()); m_proxyextension = doc->getDocumentProperty(QStringLiteral("proxyextension")); m_previewparams = doc->getDocumentProperty(QStringLiteral("previewparameters")); m_previewextension = doc->getDocumentProperty(QStringLiteral("previewextension")); QString storageFolder = doc->getDocumentProperty(QStringLiteral("storagefolder")); if (!storageFolder.isEmpty()) { custom_folder->setChecked(true); } project_folder->setUrl(QUrl::fromLocalFile(doc->projectTempFolder())); auto *cacheWidget = new TemporaryData(doc, true, this); connect(cacheWidget, &TemporaryData::disableProxies, this, &ProjectSettings::disableProxies); connect(cacheWidget, &TemporaryData::disablePreview, this, &ProjectSettings::disablePreview); tabWidget->addTab(cacheWidget, i18n("Cache Data")); } else { currentProf = KdenliveSettings::default_profile(); enable_proxy->setChecked(KdenliveSettings::enableproxy()); generate_proxy->setChecked(KdenliveSettings::generateproxy()); proxy_minsize->setValue(KdenliveSettings::proxyminsize()); resize_preview->setChecked(KdenliveSettings::resizepreview()); preview_height->setValue(KdenliveSettings::previewheight()); m_proxyparameters = KdenliveSettings::proxyparams(); generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy()); proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize()); m_proxyextension = KdenliveSettings::proxyextension(); m_previewparams = KdenliveSettings::previewparams(); m_previewextension = KdenliveSettings::previewextension(); custom_folder->setChecked(KdenliveSettings::customprojectfolder()); project_folder->setUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))); } m_resizePreview = resize_preview->isChecked(); m_previewHeight = preview_height->value(); // Select profile m_pw->loadProfile(currentProf); proxy_minsize->setEnabled(generate_proxy->isChecked()); proxy_imageminsize->setEnabled(generate_imageproxy->isChecked()); loadProxyProfiles(); loadPreviewProfiles(); // Proxy GUI stuff proxy_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); proxy_showprofileinfo->setToolTip(i18n("Show default profile parameters")); proxy_manageprofile->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); proxy_manageprofile->setToolTip(i18n("Manage proxy profiles")); connect(proxy_manageprofile, &QAbstractButton::clicked, this, &ProjectSettings::slotManageEncodingProfile); proxy_profile->setToolTip(i18n("Select default proxy profile")); connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams())); proxyparams->setVisible(false); proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); connect(proxy_showprofileinfo, &QAbstractButton::clicked, proxyparams, &QWidget::setVisible); // Preview GUI stuff preview_showprofileinfo->setIcon(KoIconUtils::themedIcon(QStringLiteral("help-about"))); preview_showprofileinfo->setToolTip(i18n("Show default profile parameters")); preview_manageprofile->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); preview_manageprofile->setToolTip(i18n("Manage timeline preview profiles")); connect(preview_manageprofile, &QAbstractButton::clicked, this, &ProjectSettings::slotManagePreviewProfile); preview_profile->setToolTip(i18n("Select default preview profile")); connect(preview_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdatePreviewParams())); previewparams->setVisible(false); previewparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5); connect(preview_showprofileinfo, &QAbstractButton::clicked, previewparams, &QWidget::setVisible); if (readOnlyTracks) { video_tracks->setEnabled(false); audio_tracks->setEnabled(false); } metadata_list->setItemDelegateForColumn(0, new NoEditDelegate(this)); connect(metadata_list, &QTreeWidget::itemDoubleClicked, this, &ProjectSettings::slotEditMetadata); // Metadata list QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Title")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.title.markup")); if (metadata.contains(QStringLiteral("meta.attr.title.markup"))) { item->setText(1, metadata.value(QStringLiteral("meta.attr.title.markup"))); metadata.remove(QStringLiteral("meta.attr.title.markup")); } item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Author")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.author.markup")); if (metadata.contains(QStringLiteral("meta.attr.author.markup"))) { item->setText(1, metadata.value(QStringLiteral("meta.attr.author.markup"))); metadata.remove(QStringLiteral("meta.attr.author.markup")); } else if (metadata.contains(QStringLiteral("meta.attr.artist.markup"))) { item->setText(0, i18n("Artist")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.artist.markup")); item->setText(1, metadata.value(QStringLiteral("meta.attr.artist.markup"))); metadata.remove(QStringLiteral("meta.attr.artist.markup")); } item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Copyright")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.copyright.markup")); if (metadata.contains(QStringLiteral("meta.attr.copyright.markup"))) { item->setText(1, metadata.value(QStringLiteral("meta.attr.copyright.markup"))); metadata.remove(QStringLiteral("meta.attr.copyright.markup")); } item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); item = new QTreeWidgetItem(metadata_list, QStringList() << i18n("Year")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.year.markup")); if (metadata.contains(QStringLiteral("meta.attr.year.markup"))) { item->setText(1, metadata.value(QStringLiteral("meta.attr.year.markup"))); metadata.remove(QStringLiteral("meta.attr.year.markup")); } else if (metadata.contains(QStringLiteral("meta.attr.date.markup"))) { item->setText(0, i18n("Date")); item->setData(0, Qt::UserRole, QStringLiteral("meta.attr.date.markup")); item->setText(1, metadata.value(QStringLiteral("meta.attr.date.markup"))); metadata.remove(QStringLiteral("meta.attr.date.markup")); } item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); QMap::const_iterator meta = metadata.constBegin(); while (meta != metadata.constEnd()) { item = new QTreeWidgetItem(metadata_list, QStringList() << meta.key().section(QLatin1Char('.'), 2, 2)); item->setData(0, Qt::UserRole, meta.key()); item->setText(1, meta.value()); item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); ++meta; } connect(add_metadata, &QAbstractButton::clicked, this, &ProjectSettings::slotAddMetadataField); connect(delete_metadata, &QAbstractButton::clicked, this, &ProjectSettings::slotDeleteMetadataField); add_metadata->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-add"))); delete_metadata->setIcon(KoIconUtils::themedIcon(QStringLiteral("list-remove"))); if (doc != nullptr) { slotUpdateFiles(); connect(delete_unused, &QAbstractButton::clicked, this, &ProjectSettings::slotDeleteUnused); } else { tabWidget->removeTab(2); tabWidget->removeTab(1); } connect(project_folder, &KUrlRequester::textChanged, this, &ProjectSettings::slotUpdateButton); connect(button_export, &QAbstractButton::clicked, this, &ProjectSettings::slotExportToText); // Delete unused files is not implemented delete_unused->setVisible(false); } void ProjectSettings::slotEditMetadata(QTreeWidgetItem *item, int) { metadata_list->editItem(item, 1); } void ProjectSettings::slotDeleteUnused() { QStringList toDelete; // TODO /* QList list = m_projectList->documentClipList(); for (int i = 0; i < list.count(); ++i) { DocClipBase *clip = list.at(i); if (clip->numReferences() == 0 && clip->clipType() != SlideShow) { QUrl url = clip->fileURL(); if (url.isValid() && !toDelete.contains(url.path())) toDelete << url.path(); } } // make sure our urls are not used in another clip for (int i = 0; i < list.count(); ++i) { DocClipBase *clip = list.at(i); if (clip->numReferences() > 0) { QUrl url = clip->fileURL(); if (url.isValid() && toDelete.contains(url.path())) toDelete.removeAll(url.path()); } } if (toDelete.count() == 0) { // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url) if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return; m_projectList->cleanup(); slotUpdateFiles(); return; } if (KMessageBox::warningYesNoList(this, i18n("This will remove the following files from your hard drive.\nThis action cannot be undone, only use if you know what you are doing.\nAre you sure you want to continue?"), toDelete, i18n("Delete unused clips")) != KMessageBox::Yes) return; m_projectList->trashUnusedClips(); slotUpdateFiles(); */ } void ProjectSettings::slotUpdateFiles(bool cacheOnly) { // Get list of current project hashes QStringList hashes = pCore->binController()->getProjectHashes(); m_projectProxies.clear(); m_projectThumbs.clear(); if (cacheOnly) { return; } QList> list = pCore->binController()->getControllerList(); files_list->clear(); // List all files that are used in the project. That also means: // images included in slideshow and titles, files in playlist clips // TODO: images used in luma transitions? // Setup categories QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips")); videos->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("video-x-generic"))); videos->setExpanded(true); QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips")); sounds->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("audio-x-generic"))); sounds->setExpanded(true); QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips")); images->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("image-x-generic"))); images->setExpanded(true); QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips")); slideshows->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("image-x-generic"))); slideshows->setExpanded(true); QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips")); texts->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("text-plain"))); texts->setExpanded(true); QTreeWidgetItem *playlists = new QTreeWidgetItem(files_list, QStringList() << i18n("Playlist clips")); playlists->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("video-mlt-playlist"))); playlists->setExpanded(true); QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips")); others->setIcon(0, KoIconUtils::themedIcon(QStringLiteral("unknown"))); others->setExpanded(true); int count = 0; QStringList allFonts; for (const QString &file : m_lumas) { count++; new QTreeWidgetItem(images, QStringList() << file); } for (int i = 0; i < list.count(); ++i) { const std::shared_ptr &clip = list.at(i); if (clip->clipType() == ClipType::Color) { // ignore color clips in list, there is no real file continue; } if (clip->clipType() == ClipType::SlideShow) { const QStringList subfiles = extractSlideshowUrls(clip->clipUrl()); for (const QString &file : subfiles) { count++; new QTreeWidgetItem(slideshows, QStringList() << file); } continue; } else if (!clip->clipUrl().isEmpty()) { // allFiles.append(clip->fileURL().path()); switch (clip->clipType()) { case ClipType::Text: new QTreeWidgetItem(texts, QStringList() << clip->clipUrl()); break; case ClipType::Audio: new QTreeWidgetItem(sounds, QStringList() << clip->clipUrl()); break; case ClipType::Image: new QTreeWidgetItem(images, QStringList() << clip->clipUrl()); break; case ClipType::Playlist: new QTreeWidgetItem(playlists, QStringList() << clip->clipUrl()); break; case ClipType::Unknown: new QTreeWidgetItem(others, QStringList() << clip->clipUrl()); break; default: new QTreeWidgetItem(videos, QStringList() << clip->clipUrl()); break; } count++; } if (clip->clipType() == ClipType::Text) { const QStringList imagefiles = TitleWidget::extractImageList(clip->getProducerProperty(QStringLiteral("xmldata"))); const QStringList fonts = TitleWidget::extractFontList(clip->getProducerProperty(QStringLiteral("xmldata"))); for (const QString &file : imagefiles) { count++; new QTreeWidgetItem(images, QStringList() << file); } allFonts << fonts; } else if (clip->clipType() == ClipType::Playlist) { const QStringList files = extractPlaylistUrls(clip->clipUrl()); for (const QString &file : files) { count++; new QTreeWidgetItem(others, QStringList() << file); } } } uint used = 0; uint unUsed = 0; qint64 usedSize = 0; qint64 unUsedSize = 0; pCore->bin()->getBinStats(&used, &unUsed, &usedSize, &unUsedSize); allFonts.removeDuplicates(); // Hide unused categories for (int i = 0; i < files_list->topLevelItemCount(); ++i) { if (files_list->topLevelItem(i)->childCount() == 0) { files_list->topLevelItem(i)->setHidden(true); } } files_count->setText(QString::number(count)); fonts_list->addItems(allFonts); if (allFonts.isEmpty()) { fonts_list->setHidden(true); label_fonts->setHidden(true); } used_count->setText(QString::number(used)); used_size->setText(KIO::convertSize(static_cast(usedSize))); unused_count->setText(QString::number(unUsed)); unused_size->setText(KIO::convertSize(static_cast(unUsedSize))); delete_unused->setEnabled(unUsed > 0); } const QString ProjectSettings::selectedPreview() const { return preview_profile->itemData(preview_profile->currentIndex()).toString(); } bool ProjectSettings::resizePreviewChanged() const { return m_resizePreview != resize_preview->isChecked() || m_previewHeight != preview_height->value(); } void ProjectSettings::accept() { if (selectedProfile().isEmpty()) { KMessageBox::error(this, i18n("Please select a video profile")); return; } QString params = preview_profile->itemData(preview_profile->currentIndex()).toString(); if (!params.isEmpty()) { - if (params.section(QLatin1Char(';'), 0, 0) != m_previewparams || params.section(QLatin1Char(';'), 1, 1) != m_previewextension || m_resizePreview != resize_preview->isChecked() || m_previewHeight != preview_height->value()) { + if (params.section(QLatin1Char(';'), 0, 0) != m_previewparams || params.section(QLatin1Char(';'), 1, 1) != m_previewextension || + m_resizePreview != resize_preview->isChecked() || m_previewHeight != preview_height->value()) { // Timeline preview settings changed, warn - if (KMessageBox::warningContinueCancel(this, i18n("You changed the timeline preview profile. This will remove all existing timeline previews for " - "this project.\n Are you sure you want to proceed?"), + if (KMessageBox::warningContinueCancel(this, + i18n("You changed the timeline preview profile. This will remove all existing timeline previews for " + "this project.\n Are you sure you want to proceed?"), i18n("Confirm profile change")) == KMessageBox::Cancel) { return; } } } if (!m_savedProject && selectedProfile() != pCore->getCurrentProfile()->path()) { if (KMessageBox::warningContinueCancel( - this, i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation " - "that might cause some corruption in transitions.\n Are you sure you want to proceed?"), + this, + i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation " + "that might cause some corruption in transitions.\n Are you sure you want to proceed?"), i18n("Confirm profile change")) == KMessageBox::Cancel) { return; } } QDialog::accept(); } void ProjectSettings::slotUpdateButton(const QString &path) { if (path.isEmpty()) { m_buttonOk->setEnabled(false); } else { m_buttonOk->setEnabled(true); slotUpdateFiles(true); } } QString ProjectSettings::selectedProfile() const { return m_pw->selectedProfile(); } QUrl ProjectSettings::selectedFolder() const { return project_folder->url(); } QPoint ProjectSettings::tracks() const { QPoint p; p.setX(video_tracks->value()); p.setY(audio_tracks->value()); return p; } bool ProjectSettings::enableVideoThumbs() const { return video_thumbs->isChecked(); } bool ProjectSettings::enableAudioThumbs() const { return audio_thumbs->isChecked(); } bool ProjectSettings::useProxy() const { return enable_proxy->isChecked(); } bool ProjectSettings::generateProxy() const { return generate_proxy->isChecked(); } bool ProjectSettings::generateImageProxy() const { return generate_imageproxy->isChecked(); } int ProjectSettings::proxyMinSize() const { return proxy_minsize->value(); } int ProjectSettings::proxyImageMinSize() const { return proxy_imageminsize->value(); } int ProjectSettings::proxyImageSize() const { return proxy_imagesize->value(); } QString ProjectSettings::proxyParams() const { QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString(); return params.section(QLatin1Char(';'), 0, 0); } QString ProjectSettings::proxyExtension() const { QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString(); return params.section(QLatin1Char(';'), 1, 1); } int ProjectSettings::previewHeight() const { return preview_height->value(); } bool ProjectSettings::resizePreview() const { return resize_preview->isChecked(); } // static QStringList ProjectSettings::extractPlaylistUrls(const QString &path) { QStringList urls; QDomDocument doc; QFile file(path); if (!file.open(QIODevice::ReadOnly)) { return urls; } if (!doc.setContent(&file)) { file.close(); return urls; } file.close(); QString root = doc.documentElement().attribute(QStringLiteral("root")); if (!root.isEmpty() && !root.endsWith(QLatin1Char('/'))) { root.append(QLatin1Char('/')); } QDomNodeList files = doc.elementsByTagName(QStringLiteral("producer")); for (int i = 0; i < files.count(); ++i) { QDomElement e = files.at(i).toElement(); QString type = EffectsList::property(e, QStringLiteral("mlt_service")); if (type != QLatin1String("colour")) { QString url = EffectsList::property(e, QStringLiteral("resource")); if (type == QLatin1String("timewarp")) { url = EffectsList::property(e, QStringLiteral("warp_resource")); } else if (type == QLatin1String("framebuffer")) { url = url.section(QLatin1Char('?'), 0, 0); } if (!url.isEmpty()) { if (QFileInfo(url).isRelative()) { url.prepend(root); } if (url.section(QLatin1Char('.'), 0, -2).endsWith(QLatin1String("/.all"))) { // slideshow clip, extract image urls urls << extractSlideshowUrls(url); } else { urls << url; } if (url.endsWith(QLatin1String(".mlt")) || url.endsWith(QLatin1String(".kdenlive"))) { // TODO: Do something to avoid infinite loops if 2 files reference themselves... urls << extractPlaylistUrls(url); } } } } // luma files for transitions files = doc.elementsByTagName(QStringLiteral("transition")); for (int i = 0; i < files.count(); ++i) { QDomElement e = files.at(i).toElement(); QString url = EffectsList::property(e, QStringLiteral("resource")); if (!url.isEmpty()) { if (QFileInfo(url).isRelative()) { url.prepend(root); } urls << url; } } return urls; } // static QStringList ProjectSettings::extractSlideshowUrls(const QString &url) { QStringList urls; QString path = QFileInfo(url).absolutePath(); QDir dir(path); if (url.contains(QStringLiteral(".all."))) { // this is a MIME slideshow, like *.jpeg QString ext = url.section(QLatin1Char('.'), -1); QStringList filters; filters << QStringLiteral("*.") + ext; dir.setNameFilters(filters); QStringList result = dir.entryList(QDir::Files); urls.append(path + filters.at(0) + QStringLiteral(" (") + i18np("1 image found", "%1 images found", result.count()) + QLatin1Char(')')); } else { // this is a pattern slideshow, like sequence%4d.jpg QString filter = QFileInfo(url).fileName(); QString ext = filter.section(QLatin1Char('.'), -1); filter = filter.section(QLatin1Char('%'), 0, -2); QString regexp = QLatin1Char('^') + filter + QStringLiteral("\\d+\\.") + ext + QLatin1Char('$'); QRegExp rx(regexp); int count = 0; const QStringList result = dir.entryList(QDir::Files); for (const QString &p : result) { if (rx.exactMatch(p)) { count++; } } urls.append(url + QStringLiteral(" (") + i18np("1 image found", "%1 images found", count) + QLatin1Char(')')); } return urls; } void ProjectSettings::slotExportToText() { const QString savePath = QFileDialog::getSaveFileName(this, QString(), project_folder->url().toLocalFile(), QStringLiteral("text/plain")); if (savePath.isEmpty()) { return; } QString text; text.append(i18n("Project folder: %1", project_folder->url().toLocalFile()) + '\n'); text.append(i18n("Project profile: %1", m_pw->selectedProfile()) + '\n'); text.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n"); for (int i = 0; i < files_list->topLevelItemCount(); ++i) { if (files_list->topLevelItem(i)->childCount() > 0) { text.append('\n' + files_list->topLevelItem(i)->text(0) + ":\n\n"); for (int j = 0; j < files_list->topLevelItem(i)->childCount(); ++j) { text.append(files_list->topLevelItem(i)->child(j)->text(0) + '\n'); } } } QTemporaryFile tmpfile; if (!tmpfile.open()) { qCWarning(KDENLIVE_LOG) << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName(); return; } QFile xmlf(tmpfile.fileName()); if (!xmlf.open(QIODevice::WriteOnly)) { return; } xmlf.write(text.toUtf8()); if (xmlf.error() != QFile::NoError) { xmlf.close(); return; } xmlf.close(); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), QUrl::fromLocalFile(savePath)); copyjob->exec(); } void ProjectSettings::slotUpdateProxyParams() { QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString(); proxyparams->setPlainText(params.section(QLatin1Char(';'), 0, 0)); } void ProjectSettings::slotUpdatePreviewParams() { QString params = preview_profile->itemData(preview_profile->currentIndex()).toString(); previewparams->setPlainText(params.section(QLatin1Char(';'), 0, 0)); } const QMap ProjectSettings::metadata() const { QMap metadata; for (int i = 0; i < metadata_list->topLevelItemCount(); ++i) { QTreeWidgetItem *item = metadata_list->topLevelItem(i); if (!item->text(1).simplified().isEmpty()) { // Insert metadata entry QString key = item->data(0, Qt::UserRole).toString(); if (key.isEmpty()) { key = QStringLiteral("meta.attr.") + item->text(0).simplified() + QStringLiteral(".markup"); } QString value = item->text(1); metadata.insert(key, value); } } return metadata; } void ProjectSettings::slotAddMetadataField() { QString metaField = QInputDialog::getText(this, i18n("Metadata"), i18n("Metadata")); if (metaField.isEmpty()) { return; } QTreeWidgetItem *item = new QTreeWidgetItem(metadata_list, QStringList() << metaField); item->setFlags(Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled); } void ProjectSettings::slotDeleteMetadataField() { QTreeWidgetItem *item = metadata_list->currentItem(); if (item) { delete item; } } void ProjectSettings::slotManageEncodingProfile() { QPointer d = new EncodingProfilesDialog(0); d->exec(); delete d; loadProxyProfiles(); } void ProjectSettings::slotManagePreviewProfile() { QPointer d = new EncodingProfilesDialog(1); d->exec(); delete d; loadPreviewProfiles(); } void ProjectSettings::loadProxyProfiles() { // load proxy profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); KConfigGroup group(&conf, "proxy"); QMap values = group.entryMap(); QMapIterator k(values); int ix = -1; proxy_profile->clear(); while (k.hasNext()) { k.next(); if (!k.key().isEmpty()) { QString params = k.value().section(QLatin1Char(';'), 0, 0); QString extension = k.value().section(QLatin1Char(';'), 1, 1); if (ix == -1 && ((params == m_proxyparameters && extension == m_proxyextension) || (m_proxyparameters.isEmpty() || m_proxyextension.isEmpty()))) { // this is the current profile ix = proxy_profile->count(); } proxy_profile->addItem(k.key(), k.value()); } } if (ix == -1) { // Current project proxy settings not found ix = proxy_profile->count(); proxy_profile->addItem(i18n("Current Settings"), QString(m_proxyparameters + QLatin1Char(';') + m_proxyextension)); } proxy_profile->setCurrentIndex(ix); slotUpdateProxyParams(); } void ProjectSettings::loadPreviewProfiles() { // load proxy profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation); KConfigGroup group(&conf, "timelinepreview"); QMap values = group.entryMap(); QMapIterator k(values); int ix = -1; preview_profile->clear(); while (k.hasNext()) { k.next(); if (!k.key().isEmpty()) { QString params = k.value().section(QLatin1Char(';'), 0, 0); QString extension = k.value().section(QLatin1Char(';'), 1, 1); if (ix == -1 && (params == m_previewparams && extension == m_previewextension)) { // this is the current profile ix = preview_profile->count(); } preview_profile->addItem(k.key(), k.value()); } } if (ix == -1) { // Current project proxy settings not found ix = preview_profile->count(); if (m_previewparams.isEmpty() && m_previewextension.isEmpty()) { // Leave empty, will be automatically detected preview_profile->addItem(i18n("Auto")); } else { preview_profile->addItem(i18n("Current Settings"), QString(m_previewparams + QLatin1Char(';') + m_previewextension)); } } preview_profile->setCurrentIndex(ix); slotUpdatePreviewParams(); } const QString ProjectSettings::storageFolder() const { if (custom_folder->isChecked()) { return project_folder->url().toLocalFile(); } return QString(); } diff --git a/src/project/projectmanager.cpp b/src/project/projectmanager.cpp index 13ae25ea7..27fd86db0 100644 --- a/src/project/projectmanager.cpp +++ b/src/project/projectmanager.cpp @@ -1,908 +1,904 @@ /* Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #include "projectmanager.h" #include "bin/bin.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include "jobs/jobmanager.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "mltcontroller/bincontroller.h" #include "monitor/monitormanager.h" #include "profiles/profilemodel.hpp" #include "project/dialogs/archivewidget.h" #include "project/dialogs/backupwidget.h" #include "project/dialogs/noteswidget.h" #include "project/dialogs/projectsettings.h" // Temporary for testing #include "bin/model/markerlistmodel.hpp" #include "project/notesplugin.h" #include "timeline2/model/builders/meltBuilder.hpp" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinewidget.h" #include "utils/KoIconUtils.h" #include #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include ProjectManager::ProjectManager(QObject *parent) : QObject(parent) , m_project(nullptr) , m_progressDialog(nullptr) { m_fileRevert = KStandardAction::revert(this, SLOT(slotRevert()), pCore->window()->actionCollection()); m_fileRevert->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-revert"))); m_fileRevert->setEnabled(false); QAction *a = KStandardAction::open(this, SLOT(openFile()), pCore->window()->actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-open"))); a = KStandardAction::saveAs(this, SLOT(saveFileAs()), pCore->window()->actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-save-as"))); a = KStandardAction::openNew(this, SLOT(newFile()), pCore->window()->actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-new"))); m_recentFilesAction = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), pCore->window()->actionCollection()); QAction *backupAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-undo")), i18n("Open Backup File"), this); pCore->window()->addAction(QStringLiteral("open_backup"), backupAction); connect(backupAction, SIGNAL(triggered(bool)), SLOT(slotOpenBackup())); m_notesPlugin = new NotesPlugin(this); m_autoSaveTimer.setSingleShot(true); connect(&m_autoSaveTimer, &QTimer::timeout, this, &ProjectManager::slotAutoSave); // Ensure the default data folder exist QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); dir.mkpath(QStringLiteral(".backup")); dir.mkdir(QStringLiteral("titles")); } -ProjectManager::~ProjectManager() -{ -} +ProjectManager::~ProjectManager() {} void ProjectManager::slotLoadOnOpen() { if (m_startUrl.isValid()) { openFile(); } else if (KdenliveSettings::openlastproject()) { openLastFile(); } else { newFile(false); } if (!m_loadClipsOnOpen.isEmpty() && (m_project != nullptr)) { const QStringList list = m_loadClipsOnOpen.split(QLatin1Char(',')); QList urls; urls.reserve(list.count()); for (const QString &path : list) { // qCDebug(KDENLIVE_LOG) << QDir::current().absoluteFilePath(path); urls << QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); } pCore->bin()->droppedUrls(urls); } m_loadClipsOnOpen.clear(); } void ProjectManager::init(const QUrl &projectUrl, const QString &clipList) { m_startUrl = projectUrl; m_loadClipsOnOpen = clipList; } void ProjectManager::newFile(bool showProjectSettings, bool force) { Q_UNUSED(force) // fix mantis#3160 QUrl startFile = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder() + QStringLiteral("/_untitled.kdenlive")); if (checkForBackupFile(startFile)) { return; } m_fileRevert->setEnabled(false); QString profileName = KdenliveSettings::default_profile(); if (profileName.isEmpty()) { profileName = pCore->getCurrentProfile()->path(); } QString projectFolder; QMap documentProperties; QMap documentMetadata; QPoint projectTracks(KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()); pCore->monitorManager()->resetDisplay(); QString documentId = QString::number(QDateTime::currentMSecsSinceEpoch()); documentProperties.insert(QStringLiteral("decimalPoint"), QLocale().decimalPoint()); documentProperties.insert(QStringLiteral("documentid"), documentId); if (!showProjectSettings) { if (!closeCurrentDocument()) { return; } if (KdenliveSettings::customprojectfolder()) { projectFolder = KdenliveSettings::defaultprojectfolder(); if (!projectFolder.endsWith(QLatin1Char('/'))) { projectFolder.append(QLatin1Char('/')); } documentProperties.insert(QStringLiteral("storagefolder"), projectFolder + documentId); } } else { QPointer w = new ProjectSettings(nullptr, QMap(), QStringList(), projectTracks.x(), projectTracks.y(), KdenliveSettings::defaultprojectfolder(), false, true, pCore->window()); connect(w.data(), &ProjectSettings::refreshProfiles, pCore->window(), &MainWindow::slotRefreshProfiles); if (w->exec() != QDialog::Accepted) { delete w; return; } if (!closeCurrentDocument()) { delete w; return; } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { pCore->window()->slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { pCore->window()->slotSwitchAudioThumbs(); } profileName = w->selectedProfile(); projectFolder = w->storageFolder(); projectTracks = w->tracks(); documentProperties.insert(QStringLiteral("enableproxy"), QString::number((int)w->useProxy())); documentProperties.insert(QStringLiteral("generateproxy"), QString::number((int)w->generateProxy())); documentProperties.insert(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); documentProperties.insert(QStringLiteral("proxyparams"), w->proxyParams()); documentProperties.insert(QStringLiteral("proxyextension"), w->proxyExtension()); documentProperties.insert(QStringLiteral("generateimageproxy"), QString::number((int)w->generateImageProxy())); documentProperties.insert(QStringLiteral("resizepreview"), QString::number((int)w->resizePreview())); documentProperties.insert(QStringLiteral("previewheight"), QString::number((int)w->previewHeight())); QString preview = w->selectedPreview(); if (!preview.isEmpty()) { documentProperties.insert(QStringLiteral("previewparameters"), preview.section(QLatin1Char(';'), 0, 0)); documentProperties.insert(QStringLiteral("previewextension"), preview.section(QLatin1Char(';'), 1, 1)); } documentProperties.insert(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); if (!projectFolder.isEmpty()) { if (!projectFolder.endsWith(QLatin1Char('/'))) { projectFolder.append(QLatin1Char('/')); } documentProperties.insert(QStringLiteral("storagefolder"), projectFolder + documentId); } documentMetadata = w->metadata(); delete w; } bool openBackup; m_notesPlugin->clear(); KdenliveDoc *doc = new KdenliveDoc(QUrl(), projectFolder, pCore->window()->m_commandStack, profileName, documentProperties, documentMetadata, projectTracks, &openBackup, pCore->window()); doc->m_autosave = new KAutoSaveFile(startFile, doc); pCore->bin()->setDocument(doc); // TODO REFAC: Delete this /*QList rulerActions; rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("set_render_timeline_zone")); rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("unset_render_timeline_zone")); m_trackView = new Timeline(doc, pCore->window()->kdenliveCategoryMap.value(QStringLiteral("timeline"))->actions(), rulerActions, &ok, pCore->window()); // Set default target tracks to upper audio / lower video tracks m_trackView->audioTarget = projectTracks.y() > 0 ? projectTracks.y() : -1; m_trackView->videoTarget = projectTracks.x() > 0 ? projectTracks.y() + 1 : -1; connect(m_trackView->projectView(), SIGNAL(importPlaylistClips(ItemInfo, QString, QUndoCommand *)), pCore->bin(), SLOT(slotExpandUrl(ItemInfo, QString, QUndoCommand *)), Qt::DirectConnection); m_trackView->loadTimeline(); pCore->window()->m_timelineArea->addTab(m_trackView, QIcon::fromTheme(QStringLiteral("kdenlive")), doc->description());*/ // END of things to delete m_project = doc; updateTimeline(); /*if (!ok) { // MLT is broken //pCore->window()->m_timelineArea->setEnabled(false); //pCore->window()->m_projectList->setEnabled(false); pCore->window()->slotPreferences(6); return; }*/ pCore->window()->connectDocument(); bool disabled = m_project->getDocumentProperty(QStringLiteral("disabletimelineeffects")) == QLatin1String("1"); QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_timeline_effects")); if (disableEffects) { if (disabled != disableEffects->isChecked()) { disableEffects->blockSignals(true); disableEffects->setChecked(disabled); disableEffects->blockSignals(false); } } emit docOpened(m_project); // pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); m_lastSave.start(); pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); pCore->getMonitor(Kdenlive::ClipMonitor)->start(); } bool ProjectManager::closeCurrentDocument(bool saveChanges, bool quit) { if ((m_project != nullptr) && m_project->isModified() && saveChanges) { QString message; if (m_project->url().fileName().isEmpty()) { message = i18n("Save changes to document?"); } else { message = i18n("The project \"%1\" has been changed.\nDo you want to save your changes?", m_project->url().fileName()); } switch (KMessageBox::warningYesNoCancel(pCore->window(), message)) { case KMessageBox::Yes: // save document here. If saving fails, return false; if (!saveFile()) { return false; } break; case KMessageBox::Cancel: return false; break; default: break; } } if (!quit && !qApp->isSavingSession()) { m_autoSaveTimer.stop(); if (m_project) { pCore->jobManager()->slotCancelJobs(); pCore->bin()->abortOperations(); pCore->monitorManager()->clipMonitor()->slotOpenClip(nullptr); pCore->window()->clearAssetPanel(); delete m_project; m_project = nullptr; } pCore->monitorManager()->setDocument(m_project); } return true; } bool ProjectManager::saveFileAs(const QString &outputFileName) { pCore->monitorManager()->pauseActiveMonitor(); // Sync document properties prepareSave(); QString saveFolder = QFileInfo(outputFileName).absolutePath(); QString scene = projectSceneList(saveFolder); if (!m_replacementPattern.isEmpty()) { QMapIterator i(m_replacementPattern); while (i.hasNext()) { i.next(); scene.replace(i.key(), i.value()); } } if (!m_project->saveSceneList(outputFileName, scene)) { return false; } QUrl url = QUrl::fromLocalFile(outputFileName); // Save timeline thumbnails // m_trackView->projectView()->saveThumbnails(); m_project->setUrl(url); // setting up autosave file in ~/.kde/data/stalefiles/kdenlive/ // saved under file name // actual saving by KdenliveDoc::slotAutoSave() called by a timer 3 seconds after the document has been edited // This timer is set by KdenliveDoc::setModified() if (m_project->m_autosave == nullptr) { // The temporary file is not opened or created until actually needed. // The file filename does not have to exist for KAutoSaveFile to be constructed (if it exists, it will not be touched). m_project->m_autosave = new KAutoSaveFile(url, this); } else { m_project->m_autosave->setManagedFile(url); } pCore->window()->setWindowTitle(m_project->description()); m_project->setModified(false); m_recentFilesAction->addUrl(url); // remember folder for next project opening KRecentDirs::add(QStringLiteral(":KdenliveProjectsFolder"), saveFolder); saveRecentFiles(); m_fileRevert->setEnabled(true); pCore->window()->m_undoView->stack()->setClean(); return true; } void ProjectManager::saveRecentFiles() { KSharedConfigPtr config = KSharedConfig::openConfig(); m_recentFilesAction->saveEntries(KConfigGroup(config, "Recent Files")); config->sync(); } void ProjectManager::slotSaveSelection(const QString &path) { Q_UNUSED(path) // TODO refac : look at this // m_trackView->projectView()->exportTimelineSelection(path); } bool ProjectManager::hasSelection() const { return false; // TODO refac : look at this // return m_trackView->projectView()->hasSelection(); } bool ProjectManager::saveFileAs() { QFileDialog fd(pCore->window()); fd.setDirectory(m_project->url().isValid() ? m_project->url().adjusted(QUrl::RemoveFilename).toLocalFile() : KdenliveSettings::defaultprojectfolder()); fd.setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlive")); fd.setAcceptMode(QFileDialog::AcceptSave); fd.setFileMode(QFileDialog::AnyFile); fd.setDefaultSuffix(QStringLiteral("kdenlive")); if (fd.exec() != QDialog::Accepted || fd.selectedFiles().isEmpty()) { return false; } QString outputFile = fd.selectedFiles().constFirst(); #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Since Plasma 5.7 (release at same time as KF 5.23, // the file dialog manages the overwrite check if (QFile::exists(outputFile)) { // Show the file dialog again if the user does not want to overwrite the file if (KMessageBox::questionYesNo(pCore->window(), i18n("File %1 already exists.\nDo you want to overwrite it?", outputFile)) == KMessageBox::No) { return saveFileAs(); } } #endif bool ok = false; QDir cacheDir = m_project->getCacheDir(CacheBase, &ok); if (ok) { QFile file(cacheDir.absoluteFilePath(QString::fromLatin1(QUrl::toPercentEncoding(QStringLiteral(".") + outputFile)))); file.open(QIODevice::ReadWrite | QIODevice::Text); file.close(); } return saveFileAs(outputFile); } bool ProjectManager::saveFile() { if (!m_project) { // Calling saveFile before a project was created, something is wrong qCDebug(KDENLIVE_LOG) << "SaveFile called without project"; return false; } if (m_project->url().isEmpty()) { return saveFileAs(); } bool result = saveFileAs(m_project->url().toLocalFile()); m_project->m_autosave->resize(0); return result; } void ProjectManager::openFile() { if (m_startUrl.isValid()) { openFile(m_startUrl); m_startUrl.clear(); return; } QUrl url = QFileDialog::getOpenFileUrl(pCore->window(), QString(), QUrl::fromLocalFile(KRecentDirs::dir(QStringLiteral(":KdenliveProjectsFolder"))), getMimeType()); if (!url.isValid()) { return; } KRecentDirs::add(QStringLiteral(":KdenliveProjectsFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile()); m_recentFilesAction->addUrl(url); saveRecentFiles(); openFile(url); } void ProjectManager::openLastFile() { if (m_recentFilesAction->selectableActionGroup()->actions().isEmpty()) { // No files in history newFile(false); return; } QAction *firstUrlAction = m_recentFilesAction->selectableActionGroup()->actions().last(); if (firstUrlAction) { firstUrlAction->trigger(); } else { newFile(false); } } // fix mantis#3160 separate check from openFile() so we can call it from newFile() // to find autosaved files (in ~/.local/share/stalefiles/kdenlive) and recover it bool ProjectManager::checkForBackupFile(const QUrl &url) { // Check for autosave file that belong to the url we passed in. QList staleFiles = KAutoSaveFile::staleFiles(url); KAutoSaveFile *orphanedFile = nullptr; // Check if we can have a lock on one of the file, // meaning it is not handled by any Kdenlive instancce if (!staleFiles.isEmpty()) { for (KAutoSaveFile *stale : staleFiles) { if (stale->open(QIODevice::QIODevice::ReadWrite)) { // Found orphaned autosave file orphanedFile = stale; break; } else { // Another Kdenlive instance is probably handling this autosave file staleFiles.removeAll(stale); delete stale; continue; } } } if (orphanedFile) { if (KMessageBox::questionYesNo(nullptr, i18n("Auto-saved files exist. Do you want to recover them now?"), i18n("File Recovery"), KGuiItem(i18n("Recover")), KGuiItem(i18n("Don't recover"))) == KMessageBox::Yes) { doOpenFile(url, orphanedFile); return true; } // remove the stale files for (KAutoSaveFile *stale : staleFiles) { stale->open(QIODevice::ReadWrite); delete stale; } return false; } return false; } void ProjectManager::openFile(const QUrl &url) { QMimeDatabase db; // Make sure the url is a Kdenlive project file QMimeType mime = db.mimeTypeForUrl(url); if (mime.inherits(QStringLiteral("application/x-compressed-tar"))) { // Opening a compressed project file, we need to process it // qCDebug(KDENLIVE_LOG)<<"Opening archive, processing"; QPointer ar = new ArchiveWidget(url); if (ar->exec() == QDialog::Accepted) { openFile(QUrl::fromLocalFile(ar->extractedProjectFile())); } else if (m_startUrl.isValid()) { // we tried to open an invalid file from command line, init new project newFile(false); } delete ar; return; } /*if (!url.fileName().endsWith(".kdenlive")) { // This is not a Kdenlive project file, abort loading KMessageBox::sorry(pCore->window(), i18n("File %1 is not a Kdenlive project file", url.toLocalFile())); if (m_startUrl.isValid()) { // we tried to open an invalid file from command line, init new project newFile(false); } return; }*/ if ((m_project != nullptr) && m_project->url() == url) { return; } if (!closeCurrentDocument()) { return; } if (checkForBackupFile(url)) { return; } pCore->window()->slotGotProgressInfo(i18n("Opening file %1", url.toLocalFile()), 100, InformationMessage); doOpenFile(url, nullptr); } void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale) { Q_ASSERT(m_project == nullptr); m_fileRevert->setEnabled(true); delete m_progressDialog; pCore->monitorManager()->resetDisplay(); m_progressDialog = new QProgressDialog(pCore->window()); m_progressDialog->setWindowTitle(i18n("Loading project")); m_progressDialog->setCancelButton(nullptr); m_progressDialog->setLabelText(i18n("Loading project")); m_progressDialog->setMaximum(0); m_progressDialog->show(); bool openBackup; m_notesPlugin->clear(); KdenliveDoc *doc = new KdenliveDoc(stale ? QUrl::fromLocalFile(stale->fileName()) : url, QString(), pCore->window()->m_commandStack, KdenliveSettings::default_profile().isEmpty() ? pCore->getCurrentProfile()->path() : KdenliveSettings::default_profile(), QMap(), QMap(), QPoint(KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()), &openBackup, pCore->window()); if (stale == nullptr) { stale = new KAutoSaveFile(url, doc); doc->m_autosave = stale; } else { doc->m_autosave = stale; stale->setParent(doc); // if loading from an autosave of unnamed file then keep unnamed if (url.fileName().contains(QStringLiteral("_untitled.kdenlive"))) { doc->setUrl(QUrl()); } else { doc->setUrl(url); } doc->setModified(true); stale->setParent(doc); } m_progressDialog->setLabelText(i18n("Loading clips")); // TODO refac delete this pCore->bin()->setDocument(doc); QList rulerActions; rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("set_render_timeline_zone")); rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("unset_render_timeline_zone")); rulerActions << pCore->window()->actionCollection()->action(QStringLiteral("clear_render_timeline_zone")); /*bool ok; m_trackView = new Timeline(doc, pCore->window()->kdenliveCategoryMap.value(QStringLiteral("timeline"))->actions(), rulerActions, &ok, pCore->window()); connect(m_trackView, &Timeline::startLoadingBin, m_progressDialog, &QProgressDialog::setMaximum, Qt::DirectConnection); connect(m_trackView, &Timeline::resetUsageCount, pCore->bin(), &Bin::resetUsageCount, Qt::DirectConnection); connect(m_trackView, &Timeline::loadingBin, m_progressDialog, &QProgressDialog::setValue, Qt::DirectConnection);*/ // Set default target tracks to upper audio / lower video tracks m_project = doc; /*m_trackView->audioTarget = doc->getDocumentProperty(QStringLiteral("audiotargettrack"), QStringLiteral("-1")).toInt(); m_trackView->videoTarget = doc->getDocumentProperty(QStringLiteral("videotargettrack"), QStringLiteral("-1")).toInt(); m_trackView->loadTimeline(); m_trackView->loadGuides(pCore->binController()->takeGuidesData()); connect(m_trackView->projectView(), SIGNAL(importPlaylistClips(ItemInfo, QString, QUndoCommand *)), pCore->bin(), SLOT(slotExpandUrl(ItemInfo, QString, QUndoCommand *)), Qt::DirectConnection); bool disabled = m_project->getDocumentProperty(QStringLiteral("disabletimelineeffects")) == QLatin1String("1"); QAction *disableEffects = pCore->window()->actionCollection()->action(QStringLiteral("disable_timeline_effects")); if (disableEffects) { if (disabled != disableEffects->isChecked()) { disableEffects->blockSignals(true); disableEffects->setChecked(disabled); disableEffects->blockSignals(false); } }*/ updateTimeline(m_project->getDocumentProperty("position").toInt()); pCore->window()->connectDocument(); QDateTime documentDate = QFileInfo(m_project->url().toLocalFile()).lastModified(); pCore->window()->getMainTimeline()->controller()->loadPreview(m_project->getDocumentProperty(QStringLiteral("previewchunks")), m_project->getDocumentProperty(QStringLiteral("dirtypreviewchunks")), documentDate, m_project->getDocumentProperty(QStringLiteral("disablepreview")).toInt()); emit docOpened(m_project); /*pCore->window()->m_timelineArea->setCurrentIndex(pCore->window()->m_timelineArea->addTab(m_trackView, QIcon::fromTheme(QStringLiteral("kdenlive")), m_project->description())); if (!ok) { pCore->window()->m_timelineArea->setEnabled(false); KMessageBox::sorry(pCore->window(), i18n("Cannot open file %1.\nProject is corrupted.", url.toLocalFile())); pCore->window()->slotGotProgressInfo(QString(), 100); newFile(false, true); return; } m_trackView->setDuration(m_trackView->duration());*/ pCore->window()->slotGotProgressInfo(QString(), 100); if (openBackup) { slotOpenBackup(url); } m_lastSave.start(); delete m_progressDialog; m_progressDialog = nullptr; pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); pCore->getMonitor(Kdenlive::ClipMonitor)->start(); } void ProjectManager::slotRevert() { if (m_project->isModified() && KMessageBox::warningContinueCancel(pCore->window(), i18n("This will delete all changes made since you last saved your project. Are you sure you want to continue?"), i18n("Revert to last saved version")) == KMessageBox::Cancel) { return; } QUrl url = m_project->url(); if (closeCurrentDocument(false)) { doOpenFile(url, nullptr); } } QString ProjectManager::getMimeType(bool open) { QString mimetype = i18n("Kdenlive project (*.kdenlive)"); if (open) { mimetype.append(QStringLiteral(";;") + i18n("Archived project (*.tar.gz)")); } return mimetype; } KdenliveDoc *ProjectManager::current() { return m_project; } void ProjectManager::slotOpenBackup(const QUrl &url) { QUrl projectFile; QUrl projectFolder; QString projectId; if (url.isValid()) { // we could not open the project file, guess where the backups are projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()); projectFile = url; } else { projectFolder = QUrl::fromLocalFile(m_project->projectTempFolder()); projectFile = m_project->url(); projectId = m_project->getDocumentProperty(QStringLiteral("documentid")); } QPointer dia = new BackupWidget(projectFile, projectFolder, projectId, pCore->window()); if (dia->exec() == QDialog::Accepted) { QString requestedBackup = dia->selectedFile(); m_project->backupLastSavedVersion(projectFile.toLocalFile()); closeCurrentDocument(false); doOpenFile(QUrl::fromLocalFile(requestedBackup), nullptr); if (m_project) { m_project->setUrl(projectFile); m_project->setModified(true); pCore->window()->setWindowTitle(m_project->description()); } } delete dia; } KRecentFilesAction *ProjectManager::recentFilesAction() { return m_recentFilesAction; } void ProjectManager::slotStartAutoSave() { if (m_lastSave.elapsed() > 300000) { // If the project was not saved in the last 5 minute, force save m_autoSaveTimer.stop(); slotAutoSave(); } else { m_autoSaveTimer.start(3000); // will trigger slotAutoSave() in 3 seconds } } void ProjectManager::slotAutoSave() { prepareSave(); QString saveFolder = m_project->url().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); QString scene = projectSceneList(saveFolder); if (!m_replacementPattern.isEmpty()) { QMapIterator i(m_replacementPattern); while (i.hasNext()) { i.next(); scene.replace(i.key(), i.value()); } } m_project->slotAutoSave(scene); m_lastSave.start(); } QString ProjectManager::projectSceneList(const QString &outputFolder) { // TODO: re-implement overlay and all // TODO refac: repair this return pCore->monitorManager()->projectMonitor()->sceneList(outputFolder); /*bool multitrackEnabled = m_trackView->multitrackView; if (multitrackEnabled) { // Multitrack view was enabled, disable for auto save m_trackView->slotMultitrackView(false); } m_trackView->connectOverlayTrack(false); QString scene = pCore->monitorManager()->projectMonitor()->sceneList(outputFolder); m_trackView->connectOverlayTrack(true); if (multitrackEnabled) { // Multitrack view was enabled, re-enable for auto save m_trackView->slotMultitrackView(true); } return scene; */ } void ProjectManager::setDocumentNotes(const QString ¬es) { m_notesPlugin->widget()->setHtml(notes); } QString ProjectManager::documentNotes() const { QString text = m_notesPlugin->widget()->toPlainText().simplified(); if (text.isEmpty()) { return QString(); } return m_notesPlugin->widget()->toHtml(); } void ProjectManager::prepareSave() { pCore->projectItemModel()->saveDocumentProperties(pCore->window()->getMainTimeline()->controller()->documentProperties(), m_project->metadata(), - m_project->getGuideModel()); + m_project->getGuideModel()); pCore->projectItemModel()->saveProperty(QStringLiteral("kdenlive:documentnotes"), documentNotes()); pCore->projectItemModel()->saveProperty(QStringLiteral("kdenlive:docproperties.groups"), m_mainTimelineModel->groupsData()); } void ProjectManager::slotResetProfiles() { m_project->resetProfile(); pCore->monitorManager()->resetProfiles(m_project->timecode()); pCore->monitorManager()->updateScopeSource(); } void ProjectManager::slotExpandClip() { // TODO refac // m_trackView->projectView()->expandActiveClip(); } void ProjectManager::disableBinEffects(bool disable) { if (m_project) { if (disable) { m_project->setDocumentProperty(QStringLiteral("disablebineffects"), QString::number((int)true)); } else { m_project->setDocumentProperty(QStringLiteral("disablebineffects"), QString()); } } pCore->monitorManager()->refreshProjectMonitor(); pCore->monitorManager()->refreshClipMonitor(); } void ProjectManager::slotDisableTimelineEffects(bool disable) { if (disable) { m_project->setDocumentProperty(QStringLiteral("disabletimelineeffects"), QString::number((int)true)); } else { m_project->setDocumentProperty(QStringLiteral("disabletimelineeffects"), QString()); } m_mainTimelineModel->setTimelineEffectsEnabled(!disable); pCore->monitorManager()->refreshProjectMonitor(); } void ProjectManager::slotSwitchTrackLock() { pCore->window()->getMainTimeline()->controller()->switchTrackLock(); } void ProjectManager::slotSwitchAllTrackLock() { pCore->window()->getMainTimeline()->controller()->switchTrackLock(true); } void ProjectManager::slotSwitchTrackTarget() { pCore->window()->getMainTimeline()->controller()->switchTargetTrack(); } QString ProjectManager::getDefaultProjectFormat() { // On first run, lets use an HD1080p profile with fps related to timezone country. Then, when the first video is added to a project, if it does not match // our profile, propose a new default. QTimeZone zone; zone = QTimeZone::systemTimeZone(); QList ntscCountries; ntscCountries << QLocale::Canada << QLocale::Chile << QLocale::CostaRica << QLocale::Cuba << QLocale::DominicanRepublic << QLocale::Ecuador; ntscCountries << QLocale::Japan << QLocale::Mexico << QLocale::Nicaragua << QLocale::Panama << QLocale::Peru << QLocale::Philippines; ntscCountries << QLocale::PuertoRico << QLocale::SouthKorea << QLocale::Taiwan << QLocale::UnitedStates; bool ntscProject = ntscCountries.contains(zone.country()); if (!ntscProject) { return QStringLiteral("atsc_1080p_25"); } return QStringLiteral("atsc_1080p_2997"); } void ProjectManager::saveZone(const QStringList &info, const QDir &dir) { pCore->bin()->saveZone(info, dir); } void ProjectManager::moveProjectData(const QString &src, const QString &dest) { // Move tmp folder (thumbnails, timeline preview) KIO::CopyJob *copyJob = KIO::move(QUrl::fromLocalFile(src), QUrl::fromLocalFile(dest)); connect(copyJob, &KJob::result, this, &ProjectManager::slotMoveFinished); connect(copyJob, SIGNAL(percent(KJob *, ulong)), this, SLOT(slotMoveProgress(KJob *, ulong))); m_project->moveProjectData(src, dest); } void ProjectManager::slotMoveProgress(KJob *, unsigned long progress) { pCore->window()->slotGotProgressInfo(i18n("Moving project folder"), static_cast(progress), ProcessingJobMessage); } void ProjectManager::slotMoveFinished(KJob *job) { if (job->error() == 0) { pCore->window()->slotGotProgressInfo(QString(), 100, InformationMessage); KIO::CopyJob *copyJob = static_cast(job); QString newFolder = copyJob->destUrl().toLocalFile(); // Check if project folder is inside document folder, in which case, paths will be relative QDir projectDir(m_project->url().toString(QUrl::RemoveFilename | QUrl::RemoveScheme)); QDir srcDir(m_project->projectTempFolder()); if (srcDir.absolutePath().startsWith(projectDir.absolutePath())) { m_replacementPattern.insert(QStringLiteral(">proxy/"), QStringLiteral(">") + newFolder + QStringLiteral("/proxy/")); } else { m_replacementPattern.insert(m_project->projectTempFolder() + QStringLiteral("/proxy/"), newFolder + QStringLiteral("/proxy/")); } m_project->setProjectFolder(QUrl::fromLocalFile(newFolder)); saveFile(); m_replacementPattern.clear(); slotRevert(); } else { KMessageBox::sorry(pCore->window(), i18n("Error moving project folder: %1", job->errorText())); } } void ProjectManager::updateTimeline(int pos) { pCore->jobManager()->slotCancelJobs(); /*qDebug() << "Loading xml"<getProjectXml().constData(); QFile file("/tmp/data.xml"); if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); stream << m_project->getProjectXml() << endl; }*/ pCore->window()->getMainTimeline()->loading = true; QScopedPointer xmlProd(new Mlt::Producer(pCore->getCurrentProfile()->profile(), "xml-string", m_project->getProjectXml().constData())); Mlt::Service s(*xmlProd); Mlt::Tractor tractor(s); m_mainTimelineModel = TimelineItemModel::construct(&pCore->getCurrentProfile()->profile(), m_project->getGuideModel(), m_project->commandStack()); constructTimelineFromMelt(m_mainTimelineModel, tractor); const QString groupsData = m_project->getDocumentProperty(QStringLiteral("groups")); if (!groupsData.isEmpty()) { m_mainTimelineModel->loadGroups(groupsData); } pCore->monitorManager()->projectMonitor()->setProducer(m_mainTimelineModel->producer(), pos); pCore->window()->getMainTimeline()->setModel(m_mainTimelineModel); pCore->monitorManager()->projectMonitor()->adjustRulerSize(m_mainTimelineModel->duration() - 1, m_project->getGuideModel()); pCore->window()->getMainTimeline()->controller()->setZone(m_project->zone()); m_mainTimelineModel->setUndoStack(m_project->commandStack()); } - void ProjectManager::adjustProjectDuration() { pCore->monitorManager()->projectMonitor()->adjustRulerSize(m_mainTimelineModel->duration() - 1, nullptr); } void ProjectManager::activateAsset(const QVariantMap effectData) { if (pCore->monitorManager()->projectMonitor()->isActive()) { pCore->window()->getMainTimeline()->controller()->addAsset(effectData); } else { QString effect = effectData.value(QStringLiteral("kdenlive/effect")).toString(); QStringList effectString; effectString << effect; pCore->bin()->slotAddEffect(QString(), effectString); } } std::shared_ptr ProjectManager::getGuideModel() { return current()->getGuideModel(); } std::shared_ptr ProjectManager::undoStack() { return current()->commandStack(); } - diff --git a/src/project/projectmanager.h b/src/project/projectmanager.h index 80fa1d8f9..a9fc8d935 100644 --- a/src/project/projectmanager.h +++ b/src/project/projectmanager.h @@ -1,185 +1,185 @@ /* Copyright (C) 2014 Till Theato 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 3 of the License, or (at your option) any later version. */ #ifndef PROJECTMANAGER_H #define PROJECTMANAGER_H #include "kdenlivecore_export.h" #include #include #include #include #include #include #include "timeline2/model/timelineitemmodel.hpp" #include #include #include class KAutoSaveFile; class KJob; class KdenliveDoc; class MarkerListModel; class NotesPlugin; class Project; class QAction; class QProgressDialog; class QUrl; class DocUndoStack; /** * @class ProjectManager * @brief Takes care of interaction with projects. */ class /*KDENLIVECORE_EXPORT*/ ProjectManager : public QObject { Q_OBJECT public: /** @brief Sets up actions to interact for project interaction (undo, redo, open, save, ...) and creates an empty project. */ explicit ProjectManager(QObject *parent = nullptr); virtual ~ProjectManager(); /** @brief Returns a pointer to the currently opened project. A project should always be open. */ KdenliveDoc *current(); /** @brief Store command line args for later opening. */ void init(const QUrl &projectUrl, const QString &clipList); void doOpenFile(const QUrl &url, KAutoSaveFile *stale); KRecentFilesAction *recentFilesAction(); void prepareSave(); /** @brief Disable all bin effects in current project */ void disableBinEffects(bool disable); /** @brief Returns true if there is a selected item in timeline */ bool hasSelection() const; /** @brief Returns current project's xml scene */ QString projectSceneList(const QString &outputFolder); /** @brief returns a default hd profile depending on timezone*/ static QString getDefaultProjectFormat(); void saveZone(const QStringList &info, const QDir &dir); /** @brief Move project data files to new url */ void moveProjectData(const QString &src, const QString &dest); /** @brief Retrieve current project's notes */ QString documentNotes() const; /** @brief Retrieve the current Guide Model The method is virtual to allow mocking */ virtual std::shared_ptr getGuideModel(); /** @brief Return the current undo stack The method is virtual to allow mocking */ virtual std::shared_ptr undoStack(); public slots: void newFile(bool showProjectSettings = true, bool force = false); /** @brief Shows file open dialog. */ void openFile(); void openLastFile(); /** @brief Load files / clips passed on the command line. */ void slotLoadOnOpen(); /** @brief Checks whether a URL is available to save to. - * @return Whether the file was saved. */ + * @return Whether the file was saved. */ bool saveFile(); /** @brief Shows a save file dialog for saving the project. - * @return Whether the file was saved. */ + * @return Whether the file was saved. */ bool saveFileAs(); /** @brief Saves current timeline selection to an MLT playlist. */ void slotSaveSelection(const QString &path = QString()); /** @brief Set properties to match outputFileName and save the document. * Creates an autosave version of the output file too, at * ~/.kde/data/stalefiles/kdenlive/ \n * that will be actually written in KdenliveDoc::slotAutoSave() - * @param outputFileName The URL to save to / The document's URL. - * @return Whether we had success. */ + * @param outputFileName The URL to save to / The document's URL. + * @return Whether we had success. */ bool saveFileAs(const QString &outputFileName); /** @brief Close currently opened document. Returns false if something went wrong (cannot save modifications, ...). */ bool closeCurrentDocument(bool saveChanges = true, bool quit = false); /** @brief Prepares opening @param url. - * - * Checks if already open and whether backup exists */ + * + * Checks if already open and whether backup exists */ void openFile(const QUrl &url); /** @brief Start autosave timer */ void slotStartAutoSave(); /** @brief Update project and monitors profiles */ void slotResetProfiles(); /** @brief Expand current timeline clip (recover clips and tracks from an MLT playlist) */ void slotExpandClip(); /** @brief Dis/enable all timeline effects */ void slotDisableTimelineEffects(bool disable); /** @brief Un/Lock current timeline track */ void slotSwitchTrackLock(); void slotSwitchAllTrackLock(); /** @brief Un/Set current track as target */ void slotSwitchTrackTarget(); /** @brief Set the text for current project's notes */ void setDocumentNotes(const QString ¬es); /** @brief Project's duration changed, adjust monitor, etc. */ void adjustProjectDuration(); /** @brief Add an asset in timeline (effect, transition). */ void activateAsset(const QVariantMap effectData); private slots: void slotRevert(); /** @brief Open the project's backupdialog. */ void slotOpenBackup(const QUrl &url = QUrl()); /** @brief Start autosaving the document. */ void slotAutoSave(); /** @brief Report progress of folder move operation. */ void slotMoveProgress(KJob *, unsigned long progress); void slotMoveFinished(KJob *job); signals: void docOpened(KdenliveDoc *document); // void projectOpened(Project *project); protected: void updateTimeline(int pos = -1); private: /** @brief Checks that the Kdenlive MIME type is correctly installed. - * @param open If set to true, this will return the MIME type allowed for file opening (adds .tar.gz format) - * @return The MIME type */ + * @param open If set to true, this will return the MIME type allowed for file opening (adds .tar.gz format) + * @return The MIME type */ QString getMimeType(bool open = true); /** @brief checks if autoback files exists, recovers from it if user says yes, returns true if files were recovered. */ bool checkForBackupFile(const QUrl &url); KdenliveDoc *m_project; std::shared_ptr m_mainTimelineModel; QTime m_lastSave; QTimer m_autoSaveTimer; QUrl m_startUrl; QString m_loadClipsOnOpen; QMap m_replacementPattern; QAction *m_fileRevert; KRecentFilesAction *m_recentFilesAction; NotesPlugin *m_notesPlugin; QProgressDialog *m_progressDialog; void saveRecentFiles(); }; #endif diff --git a/src/qt-oauth-lib/oauth2.cpp b/src/qt-oauth-lib/oauth2.cpp index 2e2efb5e9..9cfb87e24 100644 --- a/src/qt-oauth-lib/oauth2.cpp +++ b/src/qt-oauth-lib/oauth2.cpp @@ -1,313 +1,313 @@ /******************************************************************************************************** * Copyright (C) 2015 Roger Morton (ttguy1@gmail.com) * * Purpose: implements client access to freesound.org using ver2 of the freesound API. * * Based on code at https://code.google.com/p/qt-oauth-lib/ * Which is Qt Library created by Integrated Computer Solutions, Inc. (ICS) * to provide OAuth2.0 for the Google API. * * Licence: GNU Lesser General Public License * http://www.gnu.org/licenses/lgpl.html * This version of the GNU Lesser General Public License incorporates the terms * and conditions of version 3 of the GNU General Public License http://www.gnu.org/licenses/gpl-3.0-standalone.html * supplemented by the additional permissions listed at http://www.gnu.org/licenses/lgpl.html * * Disclaimer of Warranty. * THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. * EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE * THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. * SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR * OR CORRECTION. * * Limitation of Liability. * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, * OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO * YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING * OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR * DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF * THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *********************************************************************************************************/ #include "oauth2.h" #include "kdenlive_debug.h" #include "logindialog.h" #include #include #include #include #include OAuth2::OAuth2(QWidget *parent) { // m_strEndPoint = "https://www.freesound.org/apiv2/oauth2/logout_and_authorize/"; m_strEndPoint = QStringLiteral("https://www.freesound.org/apiv2/oauth2/authorize/"); m_strClientID = QStringLiteral("33e04f36da52710a28cc"); // obtained when ttguy registered the kdenlive application with freesound m_strRedirectURI = QStringLiteral("https://www.freesound.org/home/app_permissions/permission_granted/"); m_strResponseType = QStringLiteral("code"); m_pLoginDialog = new LoginDialog(parent); m_pParent = parent; m_bAccessTokenRec = false; // read a previously saved access token from settings. If the access token is not more that 24hrs old // it will be valid and will work when OAuth2::obtainAccessToken() is called // if there is no access token in the settings it will be blank and OAuth2::obtainAccessToken() // will logon to freesound // If it is older than 24hrs OAuth2::obtainAccessToken() will fail but then the class will // request a new access token via a saved refresh_token - that is saved in the kdenlive settings file // $HOME/.config/kdenlive.rc // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvn KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup authGroup(config, "FreeSoundAuthentication"); QString strAccessTokenFromSettings = authGroup.readEntry(QStringLiteral("freesound_access_token")); if (!strAccessTokenFromSettings.isEmpty()) { m_bAccessTokenRec = true; m_strAccessToken = strAccessTokenFromSettings; } connect(m_pLoginDialog, &LoginDialog::authCodeObtained, this, &OAuth2::SlotAuthCodeObtained); connect(m_pLoginDialog, &LoginDialog::accessDenied, this, &OAuth2::SlotAccessDenied); connect(m_pLoginDialog, &LoginDialog::canceled, this, &OAuth2::SlotCanceled); connect(m_pLoginDialog, &LoginDialog::useHQPreview, this, &OAuth2::SlotDownloadHQPreview); connect(this, &OAuth2::AuthCodeObtained, this, &OAuth2::SlotAuthCodeObtained); } /** - * @brief OAuth2::getClientID - returns QString of the "clientID" - * @return QString of the "clientID" which is a string that identifies the Kdenlive - * applicaiton to the freesound website when the request for authentication is made - */ + * @brief OAuth2::getClientID - returns QString of the "clientID" + * @return QString of the "clientID" which is a string that identifies the Kdenlive + * applicaiton to the freesound website when the request for authentication is made + */ QString OAuth2::getClientID() const { return m_strClientID; } /** - * @brief OAuth2::getClientSecret - returns QString of the "client secret" - * @return - QString of the "client secret" which is another string that identifies the Kdenlive - * applicaiton to the freesound website when the application asks for an access token - */ + * @brief OAuth2::getClientSecret - returns QString of the "client secret" + * @return - QString of the "client secret" which is another string that identifies the Kdenlive + * applicaiton to the freesound website when the application asks for an access token + */ QString OAuth2::getClientSecret() const { return OAuth2_strClientSecret; } /** -* @brief OAuth2::ForgetAccessToken - clear saved access token from settings and memory /n -* deletes the saved access token from the settings file and from memory. -* Used when the authentication process has failed and has the effect of forcing -* the user to re-authenticate with freesound the next time they try and download a freesound HQ file -*/ + * @brief OAuth2::ForgetAccessToken - clear saved access token from settings and memory /n + * deletes the saved access token from the settings file and from memory. + * Used when the authentication process has failed and has the effect of forcing + * the user to re-authenticate with freesound the next time they try and download a freesound HQ file + */ void OAuth2::ForgetAccessToken() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup authGroup(config, "FreeSoundAuthentication"); authGroup.deleteEntry(QStringLiteral("freesound_access_token")); m_bAccessTokenRec = false; m_strAccessToken.clear(); } /** -* @brief OAuth2::loginUrl - returns QString containing URL to connect to freesound. -* @return - QString containing URL to connect to freesound. Substitutes clientid,redirecturi and response types into the string -*/ + * @brief OAuth2::loginUrl - returns QString containing URL to connect to freesound. + * @return - QString containing URL to connect to freesound. Substitutes clientid,redirecturi and response types into the string + */ QString OAuth2::loginUrl() { QString str = QStringLiteral("%1?client_id=%2&redirect_uri=%3&response_type=%4").arg(m_strEndPoint, m_strClientID, m_strRedirectURI, m_strResponseType); // qCDebug(KDENLIVE_LOG) << "Login URL" << str; return str; } /** * @brief OAuth2::obtainAccessToken - Obtains freesound access token. opens the login dialog to connect to freesound if it needs to. \n * Called by ResourceWidget::slotSaveItem * */ void OAuth2::obtainAccessToken() { if (m_bAccessTokenRec) { emit accessTokenReceived(m_strAccessToken); // if we already have the access token then carry on as if we have already logged on and have the access token } else { // login to free sound via our login dialog QUrl vUrl(loginUrl()); m_pLoginDialog->setLoginUrl(vUrl); m_pLoginDialog->show(); } } /** * @brief OAuth2::SlotAccessDenied - fires when freesound web site denys access */ void OAuth2::SlotAccessDenied() { qCDebug(KDENLIVE_LOG) << "access denied"; emit accessDenied(); } /** * @brief OAuth2::SlotAuthCodeObtained - fires when the LogonDialog has obtained an Auth Code and has sent the LogonDialog::AuthCodeObtained signal * This uses the Authorisation Code we have got to request an access token for downloading files * http://www.freesound.org/docs/api/authentication.html#oauth2-authentication * When the request finishes OAuth2::serviceRequestFinished will be notified and it will extract the access token and emit accessTokenReceived */ void OAuth2::SlotAuthCodeObtained() { m_strAuthorizationCode = m_pLoginDialog->authCode(); // get the Auth code we have obtained // has a lifetime of 10 minutes OAuth2::RequestAccessCode(false, m_strAuthorizationCode); } /** * @brief OAuth2::RequestAccessCode - connect to freesound to exchange a authorization code or a refresh token for an access code * @param pIsReRequest - pass false if you are requesting an access code using a previously obtained authorization code. Pass true if you are requesting a new * access code via refresh token * @param pCode - pass an authorisation code here if pIsReRequest is false. Otherwise pass a refresh token here. */ void OAuth2::RequestAccessCode(bool pIsReRequest, const QString &pCode) { QString vGrantType; QString vCodeTypeParamName; // If the access code is older than 24hrs any request to the API using the token will return a 401 (Unauthorized) response showing an ‘Expired token’ error. // But you can how get a new access token using the refresh token // curl -X POST -d "client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" // "https://www.freesound.org/apiv2/oauth2/access_token/" if (pIsReRequest) { vGrantType = QStringLiteral("refresh_token"); vCodeTypeParamName = QStringLiteral("refresh_token"); } else { vGrantType = QStringLiteral("authorization_code"); vCodeTypeParamName = QStringLiteral("code"); } auto *networkManager = new QNetworkAccessManager(this); QUrl serviceUrl = QUrl(QStringLiteral("https://www.freesound.org/apiv2/oauth2/access_token/")); QUrlQuery postData; postData.addQueryItem(QStringLiteral("client_id"), this->getClientID()); postData.addQueryItem(QStringLiteral("client_secret"), this->getClientSecret()); postData.addQueryItem(QStringLiteral("grant_type"), vGrantType); postData.addQueryItem(vCodeTypeParamName, pCode); connect(networkManager, &QNetworkAccessManager::finished, this, &OAuth2::serviceRequestFinished); QNetworkRequest request(serviceUrl); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); networkManager->post(request, postData.toString(QUrl::FullyEncoded).toUtf8()); } /** * @brief OAuth2::serviceRequestFinished - Fires when we finish downloading the access token. * This is the slot that is notified when OAuth2::SlotAuthCodeObtained has finished downloading the access token. * It parses the result to extract out the access_token, refresh_token and expires_in values. If it * extracts the access token sucessfully if fires the OAuth2::accessTokenReceived signal * which is picked up by ResourceWidget::slotAccessTokenReceived * @param reply * */ void OAuth2::serviceRequestFinished(QNetworkReply *reply) { // QString sRefreshToken; // int iExpiresIn; QString sErrorText; if (reply->isFinished()) { QByteArray sReply = reply->readAll(); QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(sReply, &jsonError); if (jsonError.error != QJsonParseError::NoError) { qCDebug(KDENLIVE_LOG) << "OAuth2::serviceRequestFinished jsonError.error: " << jsonError.errorString(); ForgetAccessToken(); emit accessTokenReceived(QString()); // notifies ResourceWidget::slotAccessTokenReceived - empty string in access token indicates error } else { QVariant data = doc.toVariant(); if (data.canConvert(QVariant::Map)) { QMap map = data.toMap(); if (map.contains(QStringLiteral("access_token"))) { m_strAccessToken = map.value(QStringLiteral("access_token")).toString(); m_bAccessTokenRec = true; } if (map.contains(QStringLiteral("refresh_token"))) { mstr_RefreshToken = map.value(QStringLiteral("refresh_token")).toString(); } if (map.contains(QStringLiteral("expires_in"))) { // iExpiresIn = map.value("expires_in").toInt(); //time in seconds until the access_token expires } if (map.contains(QStringLiteral("error"))) { m_bAccessTokenRec = false; sErrorText = map.value(QStringLiteral("error")).toString(); qCDebug(KDENLIVE_LOG) << "OAuth2::serviceRequestFinished map error: " << sErrorText; ForgetAccessToken(); emit accessTokenReceived(QString()); // notifies ResourceWidget::slotAccessTokenReceived - empty string in access token indicates error } if (m_bAccessTokenRec) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup authGroup(config, "FreeSoundAuthentication"); authGroup.writeEntry(QStringLiteral("freesound_access_token"), m_strAccessToken); authGroup.writeEntry(QStringLiteral("freesound_refresh_token"), mstr_RefreshToken); // access tokens have a limited lifetime of 24 hours. emit accessTokenReceived(m_strAccessToken); // notifies ResourceWidget::slotAccessTokenReceived } else { ForgetAccessToken(); emit accessTokenReceived(QString()); // notifies ResourceWidget::slotAccessTokenReceived - empty string in access token indicates error } } } } reply->deleteLater(); } /** * @brief OAuth2::SlotCanceled * Fires when user cancels out of the free sound login dialog - LoginDialog */ void OAuth2::SlotCanceled() { emit Canceled(); } /** * @brief OAuth2::SlotDownloadHQPreview * Fires when user clicks the Use HQ preview file button on the freesound login page LoginDialog. * emits UseHQPreview signal that is picked up by ResourceWidget::slotFreesoundUseHQPreview() */ void OAuth2::SlotDownloadHQPreview() { emit UseHQPreview(); } /** * @brief OAuth2::obtainNewAccessToken * Use the refresh token to get a new access token - after the access token has expired. Called by * ResourceWidget::DownloadRequestFinished */ void OAuth2::obtainNewAccessToken() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup authGroup(config, "FreeSoundAuthentication"); mstr_RefreshToken = authGroup.readEntry(QStringLiteral("freesound_refresh_token")); OAuth2::RequestAccessCode(true, mstr_RefreshToken); // request new access code via the refresh token method } diff --git a/src/qt-oauth-lib/oauth2.h b/src/qt-oauth-lib/oauth2.h index 0e0d16d4f..441e15240 100644 --- a/src/qt-oauth-lib/oauth2.h +++ b/src/qt-oauth-lib/oauth2.h @@ -1,141 +1,141 @@ /******************************************************************************************************** * Copyright (C) 2015 Roger Morton (ttguy1@gmail.com) * * Purpose: implements client access to freesound.org using ver2 of the freesound API. * * Based on code at https://code.google.com/p/qt-oauth-lib/ * Which is Qt Library created by Integrated Computer Solutions, Inc. (ICS) * to provide OAuth2.0 for the Google API. * * Licence: GNU Lesser General Public License * http://www.gnu.org/licenses/lgpl.html * This version of the GNU Lesser General Public License incorporates the terms * and conditions of version 3 of the GNU General Public License http://www.gnu.org/licenses/gpl-3.0-standalone.html * supplemented by the additional permissions listed at http://www.gnu.org/licenses/lgpl.html * * Disclaimer of Warranty. * THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. * EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE * THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. * SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR * OR CORRECTION. * * Limitation of Liability. * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, * OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO * YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING * OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR * DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF * THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * *********************************************************************************************************/ #ifndef OAUTH2_H #define OAUTH2_H #include #include #include #ifndef DOXYGEN_SHOULD_SKIP_THIS static QString OAuth2_strClientSecret = QStringLiteral("441d88374716e7a3503997151e4780566f007313"); // obtained when ttguy registered the kdenlive application with freesound #endif /* DOXYGEN_SHOULD_SKIP_THIS ^^^ don't make this any more public than it is. This preprocessing directive makes the Doxygen documention ignore this line \ - */ + */ #ifdef QT5_USE_WEBKIT class LoginDialog; /** \brief This object does oAuth2 authentication on the freesound web site. \n Instansiated by ResourceWidget constructor. \n Freesounds OAuth2 authentication API is documented here http://www.freesound.org/docs/api/authentication.html#oauth2-authentication */ class OAuth2 : public QObject { Q_OBJECT public: explicit OAuth2(QWidget *parent = nullptr); void obtainAccessToken(); void obtainNewAccessToken(); void ForgetAccessToken(); QString getClientID() const; QString getClientSecret() const; static QString m_strClientSecret; QString loginUrl(); signals: /** * @brief AuthCodeObtained * Signal that is emitted when login is ended OK and auth code obtained */ void AuthCodeObtained(); /** * @brief accessDenied * signal emitted if the freesound has denied access to the application */ void accessDenied(); /** * @brief accessTokenReceived emitted when we have obtained an access token from freesound. \n Connected to ResourceWidget::slotAccessTokenReceived * @param sAccessToken * */ void accessTokenReceived(const QString &sAccessToken); /** * @brief DownloadCanceled */ void DownloadCanceled(); /** * @brief DownloadHQPreview */ void DownloadHQPreview(); /** * @brief UseHQPreview */ void UseHQPreview(); /** * @brief Canceled */ void Canceled(); private slots: void SlotAccessDenied(); void serviceRequestFinished(QNetworkReply *reply); void SlotAuthCodeObtained(); void SlotCanceled(); void SlotDownloadHQPreview(); private: QString m_strAuthorizationCode; QString m_strAccessToken; QString m_strEndPoint; QString m_strClientID; QString m_strRedirectURI; QString m_strResponseType; QString mstr_RefreshToken; bool m_bAccessTokenRec; void RequestAccessCode(bool pIsReRequest, const QString &pCode); LoginDialog *m_pLoginDialog; QWidget *m_pParent; }; #endif // QT5_USE_WEBKIT #endif // OAUTH2_H diff --git a/src/renderer.cpp b/src/renderer.cpp index 97968349e..04522c1f5 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,1581 +1,1579 @@ /*************************************************************************** krender.cpp - description ------------------- begin : Fri Nov 22 2002 copyright : (C) 2002 by Jason Wood email : jasonwood@blueyonder.co.uk copyright : (C) 2005 Lucio Flavio Correa email : lucio.correa@gmail.com copyright : (C) Marco Gittler email : g.marco@freenet.de copyright : (C) 2006 Jean-Baptiste Mardelle email : jb@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) any later version. * * * ***************************************************************************/ #include "renderer.h" #include "bin/projectclip.h" #include "core.h" #include "definitions.h" #include "dialogs/profilesdialog.h" #include "doc/kthumb.h" #include "kdenlivesettings.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/clip.h" #include "mltcontroller/clipcontroller.h" #include "monitor/glwidget.h" #include "project/dialogs/slideshowclip.h" #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #define SEEK_INACTIVE (-1) Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent) : AbstractRender(rendererName, parent) , byPassSeek(false) , requestedSeekPosition(SEEK_INACTIVE) , showFrameSemaphore(1) , externalConsumer(false) , m_name(rendererName) , m_mltConsumer(nullptr) , m_mltProducer(nullptr) , m_showFrameEvent(nullptr) , m_pauseEvent(nullptr) , m_binController(binController) , m_qmlView(qmlView) , m_isZoneMode(false) , m_isLoopMode(false) , m_blackClip(nullptr) , m_isActive(false) , m_isRefreshing(false) { qRegisterMetaType("stringMap"); analyseAudio = KdenliveSettings::monitor_audio(); // buildConsumer(); if (m_qmlView) { m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black"); m_blackClip->set("id", "black"); m_blackClip->set("mlt_type", "producer"); m_blackClip->set("aspect_ratio", 1); m_blackClip->set("set.test_audio", 0); m_mltProducer = m_blackClip->cut(0, 1); // m_qmlView->setProducer(m_mltProducer); m_mltConsumer = qmlView->consumer(); } /*m_mltConsumer->connect(*m_mltProducer); m_mltProducer->set_speed(0.0);*/ m_refreshTimer.setSingleShot(true); m_refreshTimer.setInterval(50); connect(&m_refreshTimer, &QTimer::timeout, this, &Render::refresh); connect(this, &Render::checkSeeking, this, &Render::slotCheckSeeking); if (m_name == Kdenlive::ProjectMonitor) { // connect(m_binController, &BinController::prepareTimelineReplacement, this, &Render::prepareTimelineReplacement, Qt::DirectConnection); // connect(m_binController, &BinController::replaceTimelineProducer, this, &Render::replaceTimelineProducer, Qt::DirectConnection); // connect(m_binController, &BinController::updateTimelineProducer, this, &Render::updateTimelineProducer); } } Render::~Render() { closeMlt(); } void Render::closeMlt() { delete m_showFrameEvent; delete m_pauseEvent; delete m_mltConsumer; delete m_mltProducer; delete m_blackClip; } void Render::slotSwitchFullscreen() { if (m_mltConsumer) { m_mltConsumer->set("full_screen", 1); } } void Render::prepareProfileReset(double fps) { m_refreshTimer.stop(); m_fps = fps; } void Render::finishProfileReset() { delete m_blackClip; m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black"); m_blackClip->set("id", "black"); m_blackClip->set("mlt_type", "producer"); m_blackClip->set("aspect_ratio", 1); m_blackClip->set("set.test_audio", 0); } void Render::seek(const GenTime &time) { if ((m_mltProducer == nullptr) || !m_isActive) { return; } int pos = time.frames(m_fps); seek(pos); } void Render::silentSeek(int time) { if (m_isActive) { seek(time); return; } m_mltProducer->seek(time); m_mltConsumer->set("refresh", 1); } void Render::seek(int time) { resetZoneMode(); time = qBound(0, time, m_mltProducer->get_length() - 1); if (requestedSeekPosition == SEEK_INACTIVE) { requestedSeekPosition = time; if (!qFuzzyIsNull(m_mltProducer->get_speed())) { m_mltConsumer->purge(); } m_mltProducer->seek(time); if (!externalConsumer) { m_isRefreshing = true; if (m_mltConsumer->is_stopped()) { m_mltConsumer->start(); } m_mltConsumer->set("refresh", 1); } } else { requestedSeekPosition = time; } } int Render::frameRenderWidth() const { return m_qmlView->profile()->width(); } int Render::renderWidth() const { return (int)(m_qmlView->profile()->height() * m_qmlView->profile()->dar() + 0.5); } int Render::renderHeight() const { return m_qmlView->profile()->height(); } QImage Render::extractFrame(int frame_position, const QString &path, int width, int height) { if (width == -1) { width = frameRenderWidth(); height = renderHeight(); } else if (width % 2 == 1) { width++; } if (!path.isEmpty()) { QScopedPointer producer(new Mlt::Producer(*m_qmlView->profile(), path.toUtf8().constData())); if (producer && producer->is_valid()) { QImage img = KThumb::getFrame(producer.data(), frame_position, width, height); return img; } } if ((m_mltProducer == nullptr) || !path.isEmpty()) { QImage pix(width, height, QImage::Format_RGB32); pix.fill(Qt::black); return pix; } Mlt::Frame *frame = nullptr; QImage img; bool profileFromSource = m_mltProducer->get_int("meta.media.width") > width; if (KdenliveSettings::gpu_accel() && !profileFromSource) { QString service = m_mltProducer->get("mlt_service"); QScopedPointer tmpProd(new Mlt::Producer(*m_qmlView->profile(), service.toUtf8().constData(), m_mltProducer->get("resource"))); Mlt::Filter scaler(*m_qmlView->profile(), "swscale"); Mlt::Filter converter(*m_qmlView->profile(), "avcolor_space"); tmpProd->attach(scaler); tmpProd->attach(converter); tmpProd->seek(m_mltProducer->position()); frame = tmpProd->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } else if (profileFromSource) { // Our source clip's resolution is higher than current profile, export at full res QScopedPointer tmpProfile(new Mlt::Profile()); QString service = m_mltProducer->get("mlt_service"); QScopedPointer tmpProd(new Mlt::Producer(*tmpProfile, service.toUtf8().constData(), m_mltProducer->get("resource"))); tmpProfile->from_producer(*tmpProd); width = tmpProfile->width(); height = tmpProfile->height(); if (tmpProd && tmpProd->is_valid()) { Mlt::Filter scaler(*tmpProfile, "swscale"); Mlt::Filter converter(*tmpProfile, "avcolor_space"); tmpProd->attach(scaler); tmpProd->attach(converter); // Clip clp2(*m_mltProducer); Clip(*tmpProd).addEffects(*m_mltProducer); tmpProd->seek(m_mltProducer->position()); frame = tmpProd->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } } else { frame = m_mltProducer->get_frame(); img = KThumb::getFrame(frame, width, height); delete frame; } return img; } int Render::getLength() { if (m_mltProducer) { return mlt_producer_get_playtime(m_mltProducer->get_producer()); } return 0; } bool Render::isValid(const QUrl &url) { Mlt::Producer producer(*m_qmlView->profile(), url.toLocalFile().toUtf8().constData()); return !producer.is_blank(); } double Render::dar() const { return m_qmlView->profile()->dar(); } double Render::sar() const { return m_qmlView->profile()->sar(); } - void Render::loadUrl(const QString &url) { Mlt::Producer *producer = new Mlt::Producer(*m_qmlView->profile(), url.toUtf8().constData()); setProducer(producer, 0, true); } bool Render::updateProducer(Mlt::Producer *producer) { if (m_mltProducer) { if (strcmp(m_mltProducer->get("resource"), "") == 0) { // We need to make some cleanup Mlt::Tractor trac(*m_mltProducer); for (int i = 0; i < trac.count(); i++) { trac.set_track(*m_blackClip, i); } } delete m_mltProducer; m_mltProducer = nullptr; } if (m_mltConsumer) { if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } if ((producer == nullptr) || !producer->is_valid()) { return false; } m_fps = producer->get_fps(); m_mltProducer = producer; if (m_qmlView) { // m_qmlView->setProducer(producer); m_mltConsumer = m_qmlView->consumer(); } return true; } bool Render::setProducer(Mlt::Producer *producer, int position, bool isActive) { m_refreshTimer.stop(); requestedSeekPosition = SEEK_INACTIVE; QMutexLocker locker(&m_mutex); QString currentId; int consumerPosition = 0; if ((producer == nullptr) && (m_mltProducer != nullptr) && m_mltProducer->parent().get("id") == QLatin1String("black")) { // Black clip already displayed no need to refresh return true; } if (m_mltProducer) { currentId = m_mltProducer->get("id"); m_mltProducer->set_speed(0); if (QString(m_mltProducer->get("resource")) == QLatin1String("")) { // We need to make some cleanup Mlt::Tractor trac(*m_mltProducer); for (int i = 0; i < trac.count(); i++) { trac.set_track(*m_blackClip, i); } } delete m_mltProducer; m_mltProducer = nullptr; } if (m_mltConsumer) { if (!m_mltConsumer->is_stopped()) { isActive = true; m_mltConsumer->stop(); } consumerPosition = m_mltConsumer->position(); } blockSignals(true); if ((producer == nullptr) || !producer->is_valid()) { producer = m_blackClip->cut(0, 1); } emit stopped(); if (position == -1 && producer->get("id") == currentId) { position = consumerPosition; } if (position != -1) { producer->seek(position); } m_fps = producer->get_fps(); blockSignals(false); m_mltProducer = producer; m_mltProducer->set_speed(0); if (m_qmlView) { // m_qmlView->setProducer(producer); m_mltConsumer = m_qmlView->consumer(); // m_mltConsumer->set("refresh", 1); } // m_mltConsumer->connect(*producer); if (isActive) { startConsumer(); } emit durationChanged(m_mltProducer->get_length() - 1, m_mltProducer->get_in()); position = m_mltProducer->position(); emit rendererPosition(position); return true; } void Render::startConsumer() { if (m_mltConsumer->is_stopped() && m_mltConsumer->start() == -1) { // ARGH CONSUMER BROKEN!!!! KMessageBox::error( qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it.")); if (m_showFrameEvent) { delete m_showFrameEvent; } m_showFrameEvent = nullptr; if (m_pauseEvent) { delete m_pauseEvent; } m_pauseEvent = nullptr; delete m_mltConsumer; m_mltConsumer = nullptr; return; } m_isRefreshing = true; m_mltConsumer->set("refresh", 1); m_isActive = true; } void Render::checkMaxThreads() { // Make sure we don't use too much threads, MLT avformat does not cope with too much threads // Currently, Kdenlive uses the following avformat threads: // One thread to get info when adding a clip // One thread to create the timeline video thumbnails // One thread to create the audio thumbnails Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qCWarning(KDENLIVE_LOG) << "// TRACTOR PROBLEM" << m_mltProducer->parent().get("mlt_service"); return; } Mlt::Tractor tractor(service); int mltMaxThreads = mlt_service_cache_get_size(service.get_service(), "producer_avformat"); int requestedThreads = tractor.count() + m_qmlView->realTime() + 2; if (requestedThreads > mltMaxThreads) { mlt_service_cache_set_size(service.get_service(), "producer_avformat", requestedThreads); // qCDebug(KDENLIVE_LOG)<<"// MLT threads updated to: "<profile(), "xml:kdenlive_playlist"); if (!root.isEmpty()) { xmlConsumer.set("root", root.toUtf8().constData()); } // qCDebug(KDENLIVE_LOG)<<" ++ + READY TO SAVE: "<profile()->width()<<" / "<profile()->description(); if (!xmlConsumer.is_valid()) { return QString(); } m_mltProducer->optimise(); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.set("store", "kdenlive"); // Disabling meta creates cleaner files, but then we don't have access to metadata on the fly (meta channels, etc) // And we must use "avformat" instead of "avformat-novalidate" on project loading which causes a big delay on project opening // xmlConsumer.set("no_meta", 1); Mlt::Producer prod(m_mltProducer->get_producer()); if (!prod.is_valid()) { return QString(); } xmlConsumer.connect(prod); xmlConsumer.run(); playlist = QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")); return playlist; } void Render::saveZone(const QString &projectFolder, QPoint zone) { QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QString url = QFileDialog::getSaveFileName(qApp->activeWindow(), i18n("Save Zone"), clipFolder, i18n("MLT playlist (*.mlt)")); if (url.isEmpty()) { return; } Mlt::Consumer xmlConsumer(*m_qmlView->profile(), ("xml:" + url).toUtf8().constData()); xmlConsumer.set("terminate_on_pause", 1); m_mltProducer->optimise(); qCDebug(KDENLIVE_LOG) << " - - -- - SAVE ZONE SERVICE: " << m_mltProducer->get("mlt_type"); if (QString(m_mltProducer->get("mlt_type")) != QLatin1String("producer")) { // TODO: broken QString scene = sceneList(projectFolder); Mlt::Producer duplicate(*m_mltProducer->profile(), "xml-string", scene.toUtf8().constData()); duplicate.set_in_and_out(zone.x(), zone.y()); qCDebug(KDENLIVE_LOG) << "/// CUT: " << zone.x() << "x" << zone.y() << " / " << duplicate.get_length(); xmlConsumer.connect(duplicate); xmlConsumer.run(); } else { Mlt::Producer prod(m_mltProducer->get_producer()); Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y()); Mlt::Playlist list(*m_mltProducer->profile()); list.insert_at(0, *prod2, 0); // list.set("title", desc.toUtf8().constData()); xmlConsumer.connect(list); xmlConsumer.run(); delete prod2; } } double Render::fps() const { return m_fps; } int Render::volume() const { if ((m_mltConsumer == nullptr) || (m_mltProducer == nullptr)) { return -1; } if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) { return ((int)100 * m_mltConsumer->get_double("0.volume")); } return ((int)100 * m_mltConsumer->get_double("volume")); } void Render::start() { m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); /*if (m_winid == -1) { //qCDebug(KDENLIVE_LOG) << "----- BROKEN MONITOR: " << m_name << ", RESTART"; return; }*/ if (!m_mltConsumer) { // qCDebug(KDENLIVE_LOG)<<" / - - - STARTED BEFORE CONSUMER!!!"; return; } if (m_mltConsumer->is_stopped()) { if (m_mltConsumer->start() == -1) { // KMessageBox::error(qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or // your driver settings, please fix it.")); qCWarning(KDENLIVE_LOG) << "/ / / / CANNOT START MONITOR"; } else { m_mltConsumer->purge(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } } } void Render::stop() { requestedSeekPosition = SEEK_INACTIVE; m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); m_isActive = false; if (m_mltProducer) { if (m_isZoneMode) { resetZoneMode(); } m_mltProducer->set_speed(0.0); } if (m_mltConsumer) { m_mltConsumer->purge(); if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } m_isRefreshing = false; } void Render::stop(const GenTime &startTime) { requestedSeekPosition = SEEK_INACTIVE; m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); m_isActive = false; if (m_mltProducer) { if (m_isZoneMode) { resetZoneMode(); } m_mltProducer->set_speed(0.0); m_mltProducer->seek((int)startTime.frames(m_fps)); } if (m_mltConsumer) { m_mltConsumer->purge(); } m_isRefreshing = false; } /*void Render::pause() { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return; m_mltProducer->set_speed(0.0); //if (!m_mltConsumer->is_stopped()) m_mltConsumer->stop(); //m_mltProducer->seek(m_mltConsumer->position()); }*/ void Render::setActiveMonitor() { if (!m_isActive) { emit activateMonitor(m_name); } } void Render::switchPlay(bool play, double speed) { QMutexLocker locker(&m_mutex); requestedSeekPosition = SEEK_INACTIVE; if ((m_mltProducer == nullptr) || (m_mltConsumer == nullptr) || !m_isActive) { return; } if (m_isZoneMode) { resetZoneMode(); } if (play) { double currentSpeed = m_mltProducer->get_speed(); if (m_name == Kdenlive::ClipMonitor && m_mltConsumer->position() == m_mltProducer->get_out()) { m_mltProducer->seek(0); } if (m_mltConsumer->get_int("real_time") != m_qmlView->realTime()) { m_mltConsumer->set("real_time", m_qmlView->realTime()); m_mltConsumer->set("buffer", 25); m_mltConsumer->set("prefill", 1); // Changes to real_time require a consumer restart if running. if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } if (qFuzzyIsNull(currentSpeed)) { m_mltConsumer->start(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } else { m_mltConsumer->purge(); } m_mltProducer->set_speed(speed); } else { m_mltConsumer->set("real_time", -1); m_mltConsumer->set("buffer", 0); m_mltConsumer->set("prefill", 0); m_mltProducer->set_speed(0.0); m_mltProducer->seek(m_mltConsumer->position() + 1); m_mltConsumer->purge(); } } void Render::play(double speed) { requestedSeekPosition = SEEK_INACTIVE; if ((m_mltProducer == nullptr) || !m_isActive) { return; } double current_speed = m_mltProducer->get_speed(); if (qFuzzyCompare(current_speed, speed)) { return; } if (m_isZoneMode) { resetZoneMode(); } if (qFuzzyIsNull(speed) && m_mltConsumer->get_int("real_time") != m_qmlView->realTime()) { m_mltConsumer->set("real_time", m_qmlView->realTime()); m_mltConsumer->set("buffer", 25); m_mltConsumer->set("prefill", 1); // Changes to real_time require a consumer restart if running. if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } if (qFuzzyIsNull(current_speed)) { m_mltConsumer->start(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } m_mltProducer->set_speed(speed); } void Render::play(const GenTime &startTime) { requestedSeekPosition = SEEK_INACTIVE; if ((m_mltProducer == nullptr) || (m_mltConsumer == nullptr) || !m_isActive) { return; } m_mltProducer->seek((int)(startTime.frames(m_fps))); m_mltProducer->set_speed(1.0); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } void Render::loopZone(const GenTime &startTime, const GenTime &stopTime) { requestedSeekPosition = SEEK_INACTIVE; if ((m_mltProducer == nullptr) || (m_mltConsumer == nullptr) || !m_isActive) { return; } // m_mltProducer->set("eof", "loop"); m_isLoopMode = true; m_loopStart = startTime; playZone(startTime, stopTime); } bool Render::playZone(const GenTime &startTime, const GenTime &stopTime) { requestedSeekPosition = SEEK_INACTIVE; if ((m_mltProducer == nullptr) || (m_mltConsumer == nullptr) || !m_isActive) { return false; } m_mltProducer->seek((int)(startTime.frames(m_fps))); m_mltProducer->set_speed(0); m_mltConsumer->purge(); m_mltProducer->set("out", (int)(stopTime.frames(m_fps))); m_mltProducer->set_speed(1.0); if (m_mltConsumer->is_stopped()) { m_mltConsumer->start(); } m_isRefreshing = true; m_mltConsumer->set("refresh", 1); m_isZoneMode = true; return true; } void Render::resetZoneMode() { if (!m_isZoneMode && !m_isLoopMode) { return; } m_mltProducer->set("out", m_mltProducer->get_length()); m_isZoneMode = false; m_isLoopMode = false; } void Render::seekToFrame(int pos) { if ((m_mltProducer == nullptr) || !m_isActive) { return; } pos = qBound(0, pos - m_mltProducer->get_in(), m_mltProducer->get_length() - 1); seek(pos); } void Render::seekToFrameDiff(int diff) { if (byPassSeek) { emit renderSeek(diff); return; } if ((m_mltProducer == nullptr) || !m_isActive) { return; } if (requestedSeekPosition == SEEK_INACTIVE) { seek(seekFramePosition() + diff); } else { seek(requestedSeekPosition + diff); } } void Render::doRefresh() { if ((m_mltProducer != nullptr) && qFuzzyIsNull(playSpeed()) && m_isActive) { if (m_isRefreshing) { m_refreshTimer.start(); } else { refresh(); } } } void Render::refresh() { m_refreshTimer.stop(); if ((m_mltProducer == nullptr) || !m_isActive) { return; } QMutexLocker locker(&m_mutex); if (m_mltConsumer) { m_isRefreshing = true; if (m_mltConsumer->is_stopped()) { m_mltConsumer->start(); } m_mltConsumer->purge(); m_mltConsumer->set("refresh", 1); } } void Render::setDropFrames(bool drop) { QMutexLocker locker(&m_mutex); if (m_mltConsumer) { int dropFrames = m_qmlView->realTime(); if (!drop) { dropFrames = -dropFrames; } // m_mltConsumer->stop(); m_mltConsumer->set("real_time", dropFrames); if (m_mltConsumer->start() == -1) { qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor"; } } } void Render::setConsumerProperty(const QString &name, const QString &value) { QMutexLocker locker(&m_mutex); if (m_mltConsumer) { // m_mltConsumer->stop(); m_mltConsumer->set(name.toUtf8().constData(), value.toUtf8().constData()); if (m_isActive && m_mltConsumer->start() == -1) { qCWarning(KDENLIVE_LOG) << "ERROR, Cannot start monitor"; } } } bool Render::isPlaying() const { if ((m_mltConsumer == nullptr) || m_mltConsumer->is_stopped()) { return false; } return !qFuzzyIsNull(playSpeed()); } double Render::playSpeed() const { if (m_mltProducer) { return m_mltProducer->get_speed(); } return 0.0; } GenTime Render::seekPosition() const { if (m_mltConsumer) { return GenTime((int)m_mltConsumer->position(), m_fps); } return GenTime(); } int Render::seekFramePosition() const { if ((m_mltProducer != nullptr) && qFuzzyIsNull(m_mltProducer->get_speed())) { return (int)m_mltProducer->position(); } if (m_mltConsumer) { return (int)m_mltConsumer->position(); } return 0; } void Render::emitFrameUpdated(Mlt::Frame &frame) { Q_UNUSED(frame) return; /*TODO: fix movit crash mlt_image_format format = mlt_image_rgb24; int width = 0; int height = 0; //frame.set("rescale.interp", "bilinear"); //frame.set("deinterlace_method", "onefield"); //frame.set("top_field_first", -1); const uchar* image = frame.get_image(format, width, height); QImage qimage(width, height, QImage::Format_RGB888); //Format_ARGB32_Premultiplied); memcpy(qimage.scanLine(0), image, width * height * 3); emit frameUpdated(qimage); */ } int Render::getCurrentSeekPosition() const { if (requestedSeekPosition != SEEK_INACTIVE) { return requestedSeekPosition; } return (int)m_mltConsumer->position(); } bool Render::checkFrameNumber(int pos) { if (pos == requestedSeekPosition) { requestedSeekPosition = SEEK_INACTIVE; } if (requestedSeekPosition != SEEK_INACTIVE) { double speed = m_mltProducer->get_speed(); m_mltProducer->set_speed(0); m_mltProducer->seek(requestedSeekPosition); if (qFuzzyIsNull(speed)) { m_mltConsumer->set("refresh", 1); } else { m_mltProducer->set_speed(speed); } } else { m_isRefreshing = false; if (m_isZoneMode) { if (pos >= m_mltProducer->get_int("out") - 1) { if (m_isLoopMode) { m_mltConsumer->purge(); m_mltProducer->seek((int)(m_loopStart.frames(m_fps))); m_mltProducer->set_speed(1.0); m_mltConsumer->set("refresh", 1); } else { if (qFuzzyIsNull(m_mltProducer->get_speed())) { return false; } } } } } return true; } void Render::slotCheckSeeking() { if (requestedSeekPosition != SEEK_INACTIVE) { m_mltProducer->seek(requestedSeekPosition); requestedSeekPosition = SEEK_INACTIVE; } } void Render::showAudio(Mlt::Frame &frame) { if (!frame.is_valid() || frame.get_int("test_audio") != 0) { return; } mlt_audio_format audio_format = mlt_audio_s16; // FIXME: should not be hardcoded.. int freq = 48000; int num_channels = 2; int samples = 0; qint16 *data = (qint16 *)frame.get_audio(audio_format, freq, num_channels, samples); if (!data) { return; } // Data format: [ c00 c10 c01 c11 c02 c12 c03 c13 ... c0{samples-1} c1{samples-1} for 2 channels. // So the vector is of size samples*channels. audioShortVector sampleVector(samples * num_channels); - memcpy(sampleVector.data(), data, (unsigned) (samples * num_channels) * sizeof(qint16)); + memcpy(sampleVector.data(), data, (unsigned)(samples * num_channels) * sizeof(qint16)); if (samples > 0) { emit audioSamplesSignal(sampleVector, freq, num_channels, samples); } } /* * MLT playlist direct manipulation. */ void Render::mltCheckLength(Mlt::Tractor *tractor) { int trackNb = tractor->count(); int duration = 0; if (m_isZoneMode) { resetZoneMode(); } if (trackNb == 1) { QScopedPointer trackProducer(tractor->track(0)); duration = trackProducer->get_playtime(); m_mltProducer->set("out", duration); emit durationChanged(duration); return; } while (trackNb > 1) { QScopedPointer trackProducer(tractor->track(trackNb - 1)); int trackDuration = trackProducer->get_playtime(); if (trackDuration > duration) { duration = trackDuration; } trackNb--; } QScopedPointer blackTrackProducer(tractor->track(0)); if (blackTrackProducer->get_playtime() - 1 != duration) { Mlt::Playlist blackTrackPlaylist((mlt_playlist)blackTrackProducer->get_service()); QScopedPointer blackclip(blackTrackPlaylist.get_clip(0)); if (!blackclip || blackclip->is_blank() || blackTrackPlaylist.count() != 1) { blackTrackPlaylist.clear(); m_blackClip->set("length", duration + 1); m_blackClip->set("out", duration); Mlt::Producer *black2 = m_blackClip->cut(0, duration); blackTrackPlaylist.insert_at(0, black2, 1); delete black2; } else { if (duration > blackclip->parent().get_length()) { blackclip->parent().set("length", duration + 1); blackclip->parent().set("out", duration); blackclip->set("length", duration + 1); } blackTrackPlaylist.resize_clip(0, 0, duration); } if (m_mltConsumer->position() > duration) { m_mltConsumer->purge(); m_mltProducer->seek(duration); } m_mltProducer->set("out", duration); emit durationChanged(duration); } } Mlt::Producer *Render::getTrackProducer(const QString &id, int track, bool, bool) { Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qCWarning(KDENLIVE_LOG) << "// TRACTOR PROBLEM"; return nullptr; } Mlt::Tractor tractor(service); // WARNING: Kdenlive's track numbering is 0 for top track, while in MLT 0 is black track and 1 is the bottom track so we MUST reverse track number // TODO: memleak Mlt::Producer destTrackProducer(tractor.track(tractor.count() - track - 1)); Mlt::Playlist destTrackPlaylist((mlt_playlist)destTrackProducer.get_service()); return getProducerForTrack(destTrackPlaylist, id); } Mlt::Producer *Render::getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId) { // TODO: find a better way to check if a producer is already inserted in a track ? QString trackName = trackPlaylist.get("id"); QString clipIdWithTrack = clipId + QLatin1Char('_') + trackName; Mlt::Producer *prod = nullptr; for (int i = 0; i < trackPlaylist.count(); i++) { if (trackPlaylist.is_blank(i)) { continue; } QScopedPointer p(trackPlaylist.get_clip(i)); QString id = p->parent().get("id"); if (id == clipIdWithTrack) { // This producer already exists in the track, reuse it prod = &p->parent(); break; } } if (prod == nullptr) { prod = m_binController->getBinProducer(clipId).get(); } return prod; } Mlt::Tractor *Render::lockService() { // we are going to replace some clips, purge consumer if (!m_mltProducer) { return nullptr; } QMutexLocker locker(&m_mutex); if (m_mltConsumer) { m_mltConsumer->purge(); } Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { return nullptr; } service.lock(); return new Mlt::Tractor(service); } void Render::unlockService(Mlt::Tractor *tractor) { if (tractor) { delete tractor; } if (!m_mltProducer) { return; } Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qCWarning(KDENLIVE_LOG) << "// TRACTOR PROBLEM"; return; } service.unlock(); } void Render::mltInsertSpace(const QMap &trackClipStartList, const QMap &trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset) { if (!m_mltProducer) { // qCDebug(KDENLIVE_LOG) << "PLAYLIST NOT INITIALISED //////"; return; } Mlt::Producer parentProd(m_mltProducer->parent()); if (parentProd.get_producer() == nullptr) { // qCDebug(KDENLIVE_LOG) << "PLAYLIST BROKEN, CANNOT INSERT CLIP //////"; return; } ////qCDebug(KDENLIVE_LOG)<<"// CLP STRT LST: "< 0) { trackPlaylist.insert_blank(clipIndex, diff - 1); } else { if (!trackPlaylist.is_blank(clipIndex)) { clipIndex--; } if (!trackPlaylist.is_blank(clipIndex)) { // qCDebug(KDENLIVE_LOG) << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos; } int position = trackPlaylist.clip_start(clipIndex); int blankDuration = trackPlaylist.clip_length(clipIndex); if (blankDuration + diff == 0) { trackPlaylist.remove(clipIndex); } else { trackPlaylist.remove_region(position, -diff); } } trackPlaylist.consolidate_blanks(0); } // now move transitions mlt_service serv = m_mltProducer->parent().get_service(); mlt_service nextservice = mlt_service_get_producer(serv); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); while (mlt_type == QLatin1String("transition")) { mlt_transition tr = (mlt_transition)nextservice; int currentTrack = mlt_transition_get_b_track(tr); int currentIn = (int)mlt_transition_get_in(tr); int currentOut = (int)mlt_transition_get_out(tr); insertPos = trackTransitionStartList.value(track); if (insertPos != -1) { insertPos += offset; if (track == currentTrack && currentOut > insertPos && resource != QLatin1String("mix")) { mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff); } } nextservice = mlt_service_producer(nextservice); if (nextservice == nullptr) { break; } properties = MLT_SERVICE_PROPERTIES(nextservice); mlt_type = mlt_properties_get(properties, "mlt_type"); resource = mlt_properties_get(properties, "mlt_service"); } } else { for (int trackNb = tractor.count() - 1; trackNb >= 1; --trackNb) { Mlt::Producer trackProducer(tractor.track(trackNb)); Mlt::Playlist trackPlaylist((mlt_playlist)trackProducer.get_service()); // int clipNb = trackPlaylist.count(); insertPos = trackClipStartList.value(trackNb); if (insertPos != -1) { insertPos += offset; /* //qCDebug(KDENLIVE_LOG)<<"-------------\nTRACK "< 0) { trackPlaylist.insert_blank(clipIndex, diff - 1); } else { if (!trackPlaylist.is_blank(clipIndex)) { clipIndex--; } if (!trackPlaylist.is_blank(clipIndex)) { // qCDebug(KDENLIVE_LOG) << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos; } int position = trackPlaylist.clip_start(clipIndex); int blankDuration = trackPlaylist.clip_length(clipIndex); if (diff + blankDuration == 0) { trackPlaylist.remove(clipIndex); } else { trackPlaylist.remove_region(position, -diff); } } trackPlaylist.consolidate_blanks(0); } } // now move transitions mlt_service serv = m_mltProducer->parent().get_service(); mlt_service nextservice = mlt_service_get_producer(serv); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); while (mlt_type == QLatin1String("transition")) { mlt_transition tr = (mlt_transition)nextservice; int currentIn = (int)mlt_transition_get_in(tr); int currentOut = (int)mlt_transition_get_out(tr); int currentTrack = mlt_transition_get_b_track(tr); insertPos = trackTransitionStartList.value(currentTrack); if (insertPos != -1) { insertPos += offset; if (currentOut > insertPos && resource != QLatin1String("mix")) { mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff); } } nextservice = mlt_service_producer(nextservice); if (nextservice == nullptr) { break; } properties = MLT_SERVICE_PROPERTIES(nextservice); mlt_type = mlt_properties_get(properties, "mlt_type"); resource = mlt_properties_get(properties, "mlt_service"); } } service.unlock(); mltCheckLength(&tractor); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } bool Render::mltResizeClipCrop(const ItemInfo &info, GenTime newCropStart) { Mlt::Service service(m_mltProducer->parent().get_service()); int newCropFrame = (int)newCropStart.frames(m_fps); Mlt::Tractor tractor(service); Mlt::Producer trackProducer(tractor.track(info.track)); Mlt::Playlist trackPlaylist((mlt_playlist)trackProducer.get_service()); if (trackPlaylist.is_blank_at(info.startPos.frames(m_fps))) { // qCDebug(KDENLIVE_LOG) << "//////// ERROR RSIZING BLANK CLIP!!!!!!!!!!!"; return false; } service.lock(); int clipIndex = trackPlaylist.get_clip_index_at(info.startPos.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (clip == nullptr) { // qCDebug(KDENLIVE_LOG) << "//////// ERROR RSIZING nullptr CLIP!!!!!!!!!!!"; service.unlock(); return false; } int previousStart = clip->get_in(); int previousOut = clip->get_out(); if (previousStart == newCropFrame) { // qCDebug(KDENLIVE_LOG) << "//////// No ReSIZING Required"; service.unlock(); return true; } int frameOffset = newCropFrame - previousStart; trackPlaylist.resize_clip(clipIndex, newCropFrame, previousOut + frameOffset); service.unlock(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); return true; } QList Render::checkTrackSequence(int track) { QList list; Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qCWarning(KDENLIVE_LOG) << "// TRACTOR PROBLEM"; return list; } Mlt::Tractor tractor(service); service.lock(); Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist)trackProducer.get_service()); int clipNb = trackPlaylist.count(); ////qCDebug(KDENLIVE_LOG) << "// PARSING SCENE TRACK: " << t << ", CLIPS: " << clipNb; for (int i = 0; i < clipNb; ++i) { QScopedPointer c(trackPlaylist.get_clip(i)); int pos = trackPlaylist.clip_start(i); if (!list.contains(pos)) { list.append(pos); } pos += c->get_playtime(); if (!list.contains(pos)) { list.append(pos); } } return list; } void Render::cloneProperties(Mlt::Properties &dest, Mlt::Properties &source) { int count = source.count(); int i = 0; for (i = 0; i < count; i++) { char *value = source.get(i); if (value != nullptr) { char *name = source.get_name(i); if (name != nullptr && name[0] != QLatin1Char('_')) { dest.set(name, value); } } } } void Render::fillSlowMotionProducers() { if (m_mltProducer == nullptr) { return; } Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { return; } Mlt::Tractor tractor(service); int trackNb = tractor.count(); for (int t = 1; t < trackNb; ++t) { Mlt::Producer *tt = tractor.track(t); Mlt::Producer trackProducer(tt); delete tt; Mlt::Playlist trackPlaylist((mlt_playlist)trackProducer.get_service()); if (!trackPlaylist.is_valid()) { continue; } int clipNb = trackPlaylist.count(); for (int i = 0; i < clipNb; ++i) { QScopedPointer c(trackPlaylist.get_clip(i)); auto *nprod = new Mlt::Producer(c->get_parent()); if (nprod) { QString id = nprod->parent().get("id"); if (id.startsWith(QLatin1String("slowmotion:")) && !nprod->is_blank()) { // this is a slowmotion producer, add it to the list QString url = QString::fromUtf8(nprod->get("resource")); int strobe = nprod->get_int("strobe"); if (strobe > 1) { url.append(QStringLiteral("&strobe=") + QString::number(strobe)); } if (!m_slowmotionProducers.contains(url)) { m_slowmotionProducers.insert(url, nprod); } } else { delete nprod; } } } } } // Updates all transitions QList Render::mltInsertTrack(int ix, const QString &name, bool videoTrack) { QList transitionInfos; // Track add / delete was only added recently in MLT (pre 0.9.8 release). #if (LIBMLT_VERSION_INT < 0x0908) Q_UNUSED(ix) Q_UNUSED(name) Q_UNUSED(videoTrack) qCDebug(KDENLIVE_LOG) << "Track insertion requires a more recent MLT version"; return transitionInfos; #else Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qCWarning(KDENLIVE_LOG) << "// TRACTOR PROBLEM"; return QList(); } blockSignals(true); service.lock(); Mlt::Tractor tractor(service); // Find available track name QStringList trackNames; for (int i = 0; i < tractor.count(); i++) { QScopedPointer track(tractor.track(i)); trackNames << track->get("id"); } QString newName = QStringLiteral("playlist0"); int i = 1; while (trackNames.contains(newName)) { newName = QStringLiteral("playlist%1").arg(i); i++; } Mlt::Playlist playlist(*service.profile()); playlist.set("kdenlive:track_name", name.toUtf8().constData()); playlist.set("id", newName.toUtf8().constData()); int ct = tractor.count(); if (ix > ct) { // qCDebug(KDENLIVE_LOG) << "// ERROR, TRYING TO insert TRACK " << ix << ", max: " << ct; ix = ct; } tractor.insert_track(playlist, ix); Mlt::Producer newProd(tractor.track(ix)); if (!videoTrack) { newProd.set("kdenlive:audio_track", 1); newProd.set("hide", 1); } checkMaxThreads(); tractor.refresh(); Mlt::Field *field = tractor.field(); // Add audio mix transition to last track Mlt::Transition mix(*m_qmlView->profile(), "mix"); mix.set("a_track", 0); mix.set("b_track", ix); mix.set("always_active", 1); mix.set("internal_added", 237); // TODO refac /* if (TransitionHandler::sumAudioMixAvailable()) { mix.set("sum", 1); } else { mix.set("combine", 1); } */ field->plant_transition(mix, 0, ix); service.unlock(); blockSignals(false); return transitionInfos; #endif } void Render::sendFrameUpdate() { if (m_mltProducer) { Mlt::Frame *frame = m_mltProducer->get_frame(); emitFrameUpdated(*frame); delete frame; } } Mlt::Producer *Render::getProducer() { return m_mltProducer; } const QString Render::activeClipId() { if (m_mltProducer) { return m_mltProducer->get("id"); } return QString(); } // static bool Render::getBlackMagicDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) { return false; } Mlt::Profile profile; Mlt::Producer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1); found_devices = bm.get_int("devices"); } else { KdenliveSettings::setDecklink_device_found(false); } if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } return true; } bool Render::getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) { return false; } Mlt::Profile profile; Mlt::Consumer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1); found_devices = bm.get_int("devices"); } else { KdenliveSettings::setDecklink_device_found(false); } if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } return true; } // static bool Render::checkX11Grab() { if (KdenliveSettings::rendererpath().isEmpty() || KdenliveSettings::ffmpegpath().isEmpty()) { return false; } QProcess p; QStringList args; args << QStringLiteral("avformat:f-list"); p.start(KdenliveSettings::rendererpath(), args); if (!p.waitForStarted()) { return false; } if (!p.waitForFinished()) { return false; } QByteArray result = p.readAllStandardError(); return result.contains("x11grab"); } double Render::getMltVersionInfo(const QString &tag) { double version = 0; Mlt::Properties *metadata = pCore->getMltRepository()->metadata(producer_type, tag.toUtf8().data()); if ((metadata != nullptr) && metadata->is_valid()) { version = metadata->get_double("version"); } delete metadata; return version; } - void Render::loadExtraProducer(const QString &id, Mlt::Producer *prod) { m_binController->loadExtraProducer(id, prod); } const QString Render::getBinProperty(const QString &name) { return m_binController->getProperty(name); } void Render::setVolume(double volume) { if (m_mltConsumer) { if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) { m_mltConsumer->set("0.volume", volume); } else { m_mltConsumer->set("volume", volume); } } } bool Render::storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace) { if (!m_slowmotionProducers.contains(url)) { m_slowmotionProducers.insert(url, prod); return true; } if (replace) { Mlt::Producer *old = m_slowmotionProducers.take(url); delete old; m_slowmotionProducers.insert(url, prod); return true; } return false; } Mlt::Producer *Render::getSlowmotionProducer(const QString &url) { return m_slowmotionProducers.value(url); } void Render::updateSlowMotionProducers(const QString &id, const QMap &passProperties) { QMapIterator i(m_slowmotionProducers); Mlt::Producer *prod; while (i.hasNext()) { i.next(); prod = i.value(); QString currentId = prod->get("id"); if (currentId.startsWith("slowmotion:" + id + QLatin1Char(':'))) { QMapIterator j(passProperties); while (j.hasNext()) { j.next(); prod->set(j.key().toUtf8().constData(), j.value().toUtf8().constData()); } } } } void Render::preparePreviewRendering(const QString &sceneListFile) { // Save temporary scenelist Mlt::Consumer xmlConsumer(*m_qmlView->profile(), "xml", sceneListFile.toUtf8().constData()); if (!xmlConsumer.is_valid()) { return; } m_mltProducer->optimise(); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.set("no_meta", 1); Mlt::Producer prod(m_mltProducer->get_producer()); if (!prod.is_valid()) { return; } xmlConsumer.connect(prod); xmlConsumer.run(); } diff --git a/src/renderer.h b/src/renderer.h index dfed4acb2..335a96fcc 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -1,357 +1,357 @@ /*************************************************************************** krender.h - description ------------------- begin : Fri Nov 22 2002 copyright : (C) 2002 by Jason Wood (jasonwood@blueyonder.co.uk) copyright : (C) 2010 by Jean-Baptiste Mardelle (jb@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) any later version. * * * ***************************************************************************/ /** * @class Render * @brief Client side of the interface to a renderer. * * REFACTORING NOTE -- There is most likely no point in trying to refactor * the renderer, it is better re-written directly (see refactoring branch) * since there is a lot of code duplication, no documentation, and several * hacks that have emerged from the previous two problems. * * From Kdenlive's point of view, you treat the Render object as the renderer, * and simply use it as if it was local. Calls are asynchronous - you send a * call out, and then receive the return value through the relevant signal that * get's emitted once the call completes. */ #ifndef RENDERER_H #define RENDERER_H #include "definitions.h" #include "gentime.h" #include "mltcontroller/effectscontroller.h" #include "monitor/abstractmonitor.h" #include #include #include #include #include #include #include #include #include #include class KComboBox; class BinController; class ClipController; class GLWidget; namespace Mlt { class Consumer; class Playlist; class Properties; class Tractor; class Transition; class Frame; class Producer; class Profile; class Service; class Event; -} +} // namespace Mlt class MltErrorEvent : public QEvent { public: explicit MltErrorEvent(const QString &message) : QEvent(QEvent::User) , m_message(message) { } QString message() const { return m_message; } private: QString m_message; }; class Render : public AbstractRender { Q_OBJECT public : enum FailStates { OK = 0, APP_NOEXIST }; /** @brief Build a MLT Renderer * @param rendererName A unique identifier for this renderer * @param winid The parent widget identifier (required for SDL display). Set to 0 for OpenGL rendering * @param profile The MLT profile used for the renderer (default one will be used if empty). */ Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent = nullptr); /** @brief Destroy the MLT Renderer. */ virtual ~Render(); /** @brief In some trim modes, arrow keys move the trim pos but not timeline cursor. * if byPassSeek is true, we don't seek renderer but emit a signal for timeline. */ bool byPassSeek; /** @brief Seeks the renderer clip to the given time. */ void seek(const GenTime &time); void seekToFrameDiff(int diff); bool updateProducer(Mlt::Producer *producer); bool setProducer(Mlt::Producer *producer, int position, bool isActive); /** @brief Get the current MLT producer playlist. * @return A string describing the playlist */ const QString sceneList(const QString &root); /** @brief Tells the renderer to play the scene at the specified speed, * @param speed speed to play the scene to * * The speed is relative to normal playback, e.g. 1.0 is normal speed, 0.0 * is paused, -1.0 means play backwards. It does not specify start/stop */ void play(double speed); void switchPlay(bool play, double speed = 1.0); /** @brief Stops playing. * @param startTime time to seek to */ void stop(const GenTime &startTime); int volume() const; QImage extractFrame(int frame_position, const QString &path = QString(), int width = -1, int height = -1); /** @brief Plays the scene starting from a specific time. * @param startTime time to start playing the scene from */ void play(const GenTime &startTime); bool playZone(const GenTime &startTime, const GenTime &stopTime); void loopZone(const GenTime &startTime, const GenTime &stopTime); /** @brief Return true if we are currently playing */ bool isPlaying() const; /** @brief Returns the speed at which the renderer is currently playing. * * It returns 0.0 when the renderer is not playing anything. */ double playSpeed() const; /** @brief Returns the current seek position of the renderer. */ GenTime seekPosition() const; int seekFramePosition() const; void emitFrameUpdated(Mlt::Frame &); double fps() const; /** @brief Returns the width of a frame for this profile. */ int frameRenderWidth() const; /** @brief Returns the display width of a frame for this profile. */ int renderWidth() const; /** @brief Returns the height of a frame for this profile. */ int renderHeight() const; /** @brief Returns display aspect ratio. */ double dar() const; /** @brief Returns sample aspect ratio. */ double sar() const; /** @brief Start the MLT monitor consumer. */ void startConsumer(); /* * Playlist manipulation. */ void mltCheckLength(Mlt::Tractor *tractor); Mlt::Producer *getSlowmotionProducer(const QString &url); void mltInsertSpace(const QMap &trackClipStartList, const QMap &trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset); int mltGetSpaceLength(const GenTime &pos, int track, bool fromBlankStart); bool mltResizeClipCrop(const ItemInfo &info, GenTime newCropStart); QList mltInsertTrack(int ix, const QString &name, bool videoTrack); // const QList producersList(); void setDropFrames(bool show); /** @brief Sets an MLT consumer property. */ void setConsumerProperty(const QString &name, const QString &value); void showAudio(Mlt::Frame &); QList checkTrackSequence(int); void sendFrameUpdate() override; /** @brief Returns a pointer to the main producer. */ Mlt::Producer *getProducer(); /** @brief Returns a pointer to the bin's playlist. */ /** @brief Lock the MLT service */ Mlt::Tractor *lockService(); /** @brief Unlock the MLT service */ void unlockService(Mlt::Tractor *tractor); const QString activeClipId(); /** @brief Fill a combobox with the found blackmagic devices */ static bool getBlackMagicDeviceList(KComboBox *devicelist, bool force = false); static bool getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force = false); /** @brief Get current seek pos requested or SEEK_INACTIVE if we are not currently seeking */ int requestedSeekPosition; /** @brief Get current seek pos requested of current producer pos if not seeking */ int getCurrentSeekPosition() const; /** @brief Create a producer from url and load it in the monitor */ void loadUrl(const QString &url); /** @brief Check if the installed FFmpeg / Libav supports x11grab */ static bool checkX11Grab(); /** @brief Get a track producer from a clip's id * Deprecated, track producers are now handled in timeline/track.cpp */ Q_DECL_DEPRECATED Mlt::Producer *getTrackProducer(const QString &id, int track, bool audioOnly = false, bool videoOnly = false); /** @brief Ask to set this monitor as active */ void setActiveMonitor(); QSemaphore showFrameSemaphore; bool externalConsumer; /** @brief Returns the current version of an MLT's producer module */ double getMltVersionInfo(const QString &tag); /** @brief Load extra producers (video only, slowmotion) from timeline */ void loadExtraProducer(const QString &id, Mlt::Producer *prod); /** @brief Get a property from the bin's playlist */ const QString getBinProperty(const QString &name); void setVolume(double volume); /** @brief Stop all activities in preparation for a change in profile */ void prepareProfileReset(double fps); void finishProfileReset(); void updateSlowMotionProducers(const QString &id, const QMap &passProperties); void preparePreviewRendering(const QString &sceneListFile); void silentSeek(int time); private: /** @brief The name of this renderer. * * Useful to identify the renderers by what they do - e.g. background * rendering, workspace monitor, etc. */ Kdenlive::MonitorId m_name; Mlt::Consumer *m_mltConsumer; Mlt::Producer *m_mltProducer; Mlt::Event *m_showFrameEvent; Mlt::Event *m_pauseEvent; BinController *m_binController; GLWidget *m_qmlView; double m_fps; /** @brief True if we are playing a zone. * * It's determined by the "in" and "out" properties being temporarily * changed. */ bool m_isZoneMode; bool m_isLoopMode; GenTime m_loopStart; Mlt::Producer *m_blackClip; QTimer m_refreshTimer; QMutex m_mutex; QMutex m_infoMutex; QLocale m_locale; /** @brief True if this monitor is active. */ bool m_isActive; /** @brief True if the consumer is currently refreshing itself. */ bool m_isRefreshing; void closeMlt(); QMap m_slowmotionProducers; /** @brief Build the MLT Consumer object with initial settings. * @param profileName The MLT profile to use for the consumer */ // void buildConsumer(); /** @brief Restore normal mode */ void resetZoneMode(); void fillSlowMotionProducers(); /** @brief Make sure we inform MLT if we need a lot of threads for avformat producer */ void checkMaxThreads(); /** @brief Clone serialisable properties only */ void cloneProperties(Mlt::Properties &dest, Mlt::Properties &source); /** @brief Get a track producer from a clip's id */ Mlt::Producer *getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId); private slots: /** @brief Refreshes the monitor display. */ void refresh(); void slotCheckSeeking(); signals: /** @brief The renderer stopped, either playing or rendering. */ void stopped(); /** @brief The renderer started playing. */ void playing(double); /** @brief The renderer started rendering. */ void rendering(const GenTime &); /** @brief An error occurred within this renderer. */ void error(const QString &, const QString &); void durationChanged(int, int offset = 0); void rendererPosition(int); void rendererStopped(int); /** @brief A clip has changed, we must reload timeline producers. */ void replaceTimelineProducer(const QString &); void updateTimelineProducer(const QString &); /** @brief Load project notes. */ void setDocumentNotes(const QString &); /** @brief The renderer received a reply to a getFileProperties request. */ void gotFileProperties(requestClipInfo, ClipController *); /** @brief A frame's image has to be shown. * * Used in Mac OS X. */ void showImageSignal(const QImage &); void showAudioSignal(const QVector &); void checkSeeking(); /** @brief Activate current monitor. */ void activateMonitor(Kdenlive::MonitorId); void mltFrameReceived(Mlt::Frame *); /** @brief We want to replace a clip with another, but before we need to change clip producer id so that there is no interference*/ void prepareTimelineReplacement(const QString &); /** @brief When in bypass seek mode, we don't seek but pass over the position diff. */ void renderSeek(int); public slots: /** @brief Starts the consumer. */ void start(); /** @brief Stops the consumer. */ void stop(); int getLength(); /** @brief Checks if the file is readable by MLT. */ bool isValid(const QUrl &url); void slotSwitchFullscreen(); void seekToFrame(int pos); /** @brief Starts a timer to query for a refresh. */ void doRefresh(); /** @brief Save a part of current timeline to an xml file. */ void saveZone(const QString &projectFolder, QPoint zone); /** @brief Renderer moved to a new frame, check seeking */ bool checkFrameNumber(int pos); /** @brief Keep a reference to slowmo producer. Returns false is producer is already stored */ bool storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace = false); void seek(int time); }; #endif diff --git a/src/scopes/abstractscopewidget.cpp b/src/scopes/abstractscopewidget.cpp index e43d99edf..24e7aba3e 100644 --- a/src/scopes/abstractscopewidget.cpp +++ b/src/scopes/abstractscopewidget.cpp @@ -1,558 +1,556 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * 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) any later version. * ***************************************************************************/ #include "abstractscopewidget.h" #include "monitor/monitor.h" #include "renderer.h" #include #include #include #include #include #include "klocalizedstring.h" #include #include // Uncomment for Scope debugging. //#define DEBUG_ASW #ifdef DEBUG_ASW #include "kdenlive_debug.h" #endif const int REALTIME_FPS = 30; const QColor light(250, 238, 226, 255); const QColor dark(40, 40, 39, 255); const QColor dark2(25, 25, 23, 255); const QColor AbstractScopeWidget::colHighlightLight(18, 130, 255, 255); const QColor AbstractScopeWidget::colHighlightDark(255, 64, 19, 255); const QColor AbstractScopeWidget::colDarkWhite(250, 250, 250); const QPen AbstractScopeWidget::penThick(QBrush(AbstractScopeWidget::colDarkWhite.rgb()), 2, Qt::SolidLine); const QPen AbstractScopeWidget::penThin(QBrush(AbstractScopeWidget::colDarkWhite.rgb()), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penLight(QBrush(QColor(200, 200, 250, 150)), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penLightDots(QBrush(QColor(200, 200, 250, 150)), 1, Qt::DotLine); const QPen AbstractScopeWidget::penLighter(QBrush(QColor(225, 225, 250, 225)), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penDark(QBrush(QColor(0, 0, 20, 250)), 1, Qt::SolidLine); const QPen AbstractScopeWidget::penDarkDots(QBrush(QColor(0, 0, 20, 250)), 1, Qt::DotLine); const QPen AbstractScopeWidget::penBackground(QBrush(dark2), 1, Qt::SolidLine); const QString AbstractScopeWidget::directions[] = {QStringLiteral("North"), QStringLiteral("Northeast"), QStringLiteral("East"), QStringLiteral("Southeast")}; AbstractScopeWidget::AbstractScopeWidget(bool trackMouse, QWidget *parent) : QWidget(parent) , m_mousePos(0, 0) , m_mouseWithinWidget(false) , offset(5) , m_accelFactorHUD(1) , m_accelFactorScope(1) , m_accelFactorBackground(1) , m_semaphoreHUD(1) , m_semaphoreScope(1) , m_semaphoreBackground(1) , initialDimensionUpdateDone(false) , m_requestForcedUpdate(false) , m_rescaleMinDist(4) , m_rescaleVerticalThreshold(2.0f) , m_rescaleActive(false) , m_rescalePropertiesLocked(false) , m_rescaleFirstRescaleDone(true) , m_rescaleDirection(North) { m_scopePalette = QPalette(); m_scopePalette.setBrush(QPalette::Window, QBrush(dark2)); m_scopePalette.setBrush(QPalette::Base, QBrush(dark)); m_scopePalette.setBrush(QPalette::Button, QBrush(dark)); m_scopePalette.setBrush(QPalette::Text, QBrush(light)); m_scopePalette.setBrush(QPalette::WindowText, QBrush(light)); m_scopePalette.setBrush(QPalette::ButtonText, QBrush(light)); setPalette(m_scopePalette); setAutoFillBackground(true); m_aAutoRefresh = new QAction(i18n("Auto Refresh"), this); m_aAutoRefresh->setCheckable(true); m_aRealtime = new QAction(i18n("Realtime (with precision loss)"), this); m_aRealtime->setCheckable(true); m_menu = new QMenu(); // Disabled dark palette on menus since it breaks up with some themes: kdenlive issue #2950 // m_menu->setPalette(m_scopePalette); m_menu->addAction(m_aAutoRefresh); m_menu->addAction(m_aRealtime); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &AbstractScopeWidget::customContextMenuRequested, this, &AbstractScopeWidget::slotContextMenuRequested); connect(this, &AbstractScopeWidget::signalHUDRenderingFinished, this, &AbstractScopeWidget::slotHUDRenderingFinished); connect(this, &AbstractScopeWidget::signalScopeRenderingFinished, this, &AbstractScopeWidget::slotScopeRenderingFinished); connect(this, &AbstractScopeWidget::signalBackgroundRenderingFinished, this, &AbstractScopeWidget::slotBackgroundRenderingFinished); connect(m_aRealtime, &QAction::toggled, this, &AbstractScopeWidget::slotResetRealtimeFactor); connect(m_aAutoRefresh, &QAction::toggled, this, &AbstractScopeWidget::slotAutoRefreshToggled); // Enable mouse tracking if desired. // Causes the mouseMoved signal to be emitted when the mouse moves inside the // widget, even when no mouse button is pressed. this->setMouseTracking(trackMouse); } AbstractScopeWidget::~AbstractScopeWidget() { writeConfig(); delete m_menu; delete m_aAutoRefresh; delete m_aRealtime; } void AbstractScopeWidget::init() { m_widgetName = widgetName(); readConfig(); } void AbstractScopeWidget::readConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); m_aAutoRefresh->setChecked(scopeConfig.readEntry("autoRefresh", true)); m_aRealtime->setChecked(scopeConfig.readEntry("realtime", false)); scopeConfig.sync(); } void AbstractScopeWidget::writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, configName()); scopeConfig.writeEntry("autoRefresh", m_aAutoRefresh->isChecked()); scopeConfig.writeEntry("realtime", m_aRealtime->isChecked()); scopeConfig.sync(); } QString AbstractScopeWidget::configName() { return "Scope_" + m_widgetName; } void AbstractScopeWidget::prodHUDThread() { if (this->visibleRegion().isEmpty()) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating HUD."; #endif } else { if (m_semaphoreHUD.tryAcquire(1)) { Q_ASSERT(!m_threadHUD.isRunning()); m_newHUDFrames.fetchAndStoreRelaxed(0); m_newHUDUpdates.fetchAndStoreRelaxed(0); m_threadHUD = QtConcurrent::run(this, &AbstractScopeWidget::renderHUD, m_accelFactorHUD); #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "HUD thread started in " << m_widgetName; #endif } #ifdef DEBUG_ASW else { qCDebug(KDENLIVE_LOG) << "HUD semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadHUD.isRunning(); } #endif } } void AbstractScopeWidget::prodScopeThread() { // Only start a new thread if the scope is actually visible // and not hidden by another widget on the stack and if user want the scope to update. if (this->visibleRegion().isEmpty() || (!m_aAutoRefresh->isChecked() && !m_requestForcedUpdate)) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating scope."; #endif } else { // Try to acquire the semaphore. This must only succeed if m_threadScope is not running // anymore. Therefore the semaphore must NOT be released before m_threadScope ends. // If acquiring the semaphore fails, the thread is still running. if (m_semaphoreScope.tryAcquire(1)) { Q_ASSERT(!m_threadScope.isRunning()); m_newScopeFrames.fetchAndStoreRelaxed(0); m_newScopeUpdates.fetchAndStoreRelaxed(0); Q_ASSERT(m_accelFactorScope > 0); // See http://doc.qt.nokia.com/latest/qtconcurrentrun.html#run about // running member functions in a thread m_threadScope = QtConcurrent::run(this, &AbstractScopeWidget::renderScope, m_accelFactorScope); m_requestForcedUpdate = false; #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope thread started in " << m_widgetName; #endif } else { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadScope.isRunning(); #endif } } } void AbstractScopeWidget::prodBackgroundThread() { if (this->visibleRegion().isEmpty()) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope " << m_widgetName << " is not visible. Not calculating background."; #endif } else { if (m_semaphoreBackground.tryAcquire(1)) { Q_ASSERT(!m_threadBackground.isRunning()); m_newBackgroundFrames.fetchAndStoreRelaxed(0); m_newBackgroundUpdates.fetchAndStoreRelaxed(0); m_threadBackground = QtConcurrent::run(this, &AbstractScopeWidget::renderBackground, m_accelFactorBackground); #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Background thread started in " << m_widgetName; #endif } else { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Background semaphore locked, not prodding in " << m_widgetName << ". Thread running: " << m_threadBackground.isRunning(); #endif } } } void AbstractScopeWidget::forceUpdate(bool doUpdate) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Forced update called in " << widgetName() << ". Arg: " << doUpdate; #endif if (!doUpdate) { return; } m_requestForcedUpdate = true; m_newHUDUpdates.fetchAndAddRelaxed(1); m_newScopeUpdates.fetchAndAddRelaxed(1); m_newBackgroundUpdates.fetchAndAddRelaxed(1); prodHUDThread(); prodScopeThread(); prodBackgroundThread(); } void AbstractScopeWidget::forceUpdateHUD() { m_newHUDUpdates.fetchAndAddRelaxed(1); prodHUDThread(); } void AbstractScopeWidget::forceUpdateScope() { m_newScopeUpdates.fetchAndAddRelaxed(1); m_requestForcedUpdate = true; prodScopeThread(); } void AbstractScopeWidget::forceUpdateBackground() { m_newBackgroundUpdates.fetchAndAddRelaxed(1); prodBackgroundThread(); } ///// Events ///// void AbstractScopeWidget::resizeEvent(QResizeEvent *event) { // Update the dimension of the available rect for painting m_scopeRect = scopeRect(); forceUpdate(); QWidget::resizeEvent(event); } void AbstractScopeWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); m_scopeRect = scopeRect(); } void AbstractScopeWidget::paintEvent(QPaintEvent *) { QPainter davinci(this); davinci.drawImage(m_scopeRect.topLeft(), m_imgBackground); davinci.drawImage(m_scopeRect.topLeft(), m_imgScope); davinci.drawImage(m_scopeRect.topLeft(), m_imgHUD); } void AbstractScopeWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { // Rescaling mode starts m_rescaleActive = true; m_rescalePropertiesLocked = false; m_rescaleFirstRescaleDone = false; m_rescaleStartPoint = event->pos(); m_rescaleModifiers = event->modifiers(); } } void AbstractScopeWidget::mouseReleaseEvent(QMouseEvent *event) { m_rescaleActive = false; m_rescalePropertiesLocked = false; if (!m_aAutoRefresh->isChecked()) { m_requestForcedUpdate = true; } prodHUDThread(); prodScopeThread(); prodBackgroundThread(); QWidget::mouseReleaseEvent(event); } void AbstractScopeWidget::mouseMoveEvent(QMouseEvent *event) { m_mousePos = event->pos(); m_mouseWithinWidget = true; emit signalMousePositionChanged(); QPoint movement = event->pos() - m_rescaleStartPoint; if (m_rescaleActive) { if (m_rescalePropertiesLocked) { // Direction is known, now adjust parameters // Reset the starting point to make the next moveEvent relative to the current one m_rescaleStartPoint = event->pos(); if (!m_rescaleFirstRescaleDone) { // We have just learned the desired direction; Normalize the movement to one pixel // to avoid a jump by m_rescaleMinDist if (movement.x() != 0) { movement.setX(movement.x() / abs(movement.x())); } if (movement.y() != 0) { movement.setY(movement.y() / abs(movement.y())); } m_rescaleFirstRescaleDone = true; } handleMouseDrag(movement, m_rescaleDirection, m_rescaleModifiers); } else { // Detect the movement direction here. // This algorithm relies on the aspect ratio of dy/dx (size and signum). if (movement.manhattanLength() > m_rescaleMinDist) { float diff = ((float)movement.y()) / movement.x(); if (fabs(diff) > m_rescaleVerticalThreshold || movement.x() == 0) { m_rescaleDirection = North; } else if (fabs(diff) < 1 / m_rescaleVerticalThreshold) { m_rescaleDirection = East; } else if (diff < 0) { m_rescaleDirection = Northeast; } else { m_rescaleDirection = Southeast; } #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Diff is " << diff << "; chose " << directions[m_rescaleDirection] << " as direction"; #endif m_rescalePropertiesLocked = true; } } } } void AbstractScopeWidget::leaveEvent(QEvent *) { m_mouseWithinWidget = false; emit signalMousePositionChanged(); } void AbstractScopeWidget::slotContextMenuRequested(const QPoint &pos) { m_menu->exec(this->mapToGlobal(pos)); } uint AbstractScopeWidget::calculateAccelFactorHUD(uint oldMseconds, uint) { return ceil((float)oldMseconds * REALTIME_FPS / 1000); } uint AbstractScopeWidget::calculateAccelFactorScope(uint oldMseconds, uint) { return ceil((float)oldMseconds * REALTIME_FPS / 1000); } uint AbstractScopeWidget::calculateAccelFactorBackground(uint oldMseconds, uint) { return ceil((float)oldMseconds * REALTIME_FPS / 1000); } ///// Slots ///// void AbstractScopeWidget::slotHUDRenderingFinished(uint mseconds, uint oldFactor) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "HUD rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName; #endif m_threadHUD.waitForFinished(); m_imgHUD = m_threadHUD.result(); m_semaphoreHUD.release(1); this->update(); if (m_aRealtime->isChecked()) { int accel; accel = calculateAccelFactorHUD(mseconds, oldFactor); if (m_accelFactorHUD < 1) { accel = 1; } m_accelFactorHUD = accel; } if ((m_newHUDFrames > 0 && m_aAutoRefresh->isChecked()) || m_newHUDUpdates > 0) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Trying to start a new HUD thread for " << m_widgetName << ". New frames/updates: " << m_newHUDFrames << '/' << m_newHUDUpdates; #endif prodHUDThread(); } } void AbstractScopeWidget::slotScopeRenderingFinished(uint mseconds, uint oldFactor) { // The signal can be received before the thread has really finished. So we // need to wait until it has really finished before starting a new thread. #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName; #endif m_threadScope.waitForFinished(); m_imgScope = m_threadScope.result(); // The scope thread has finished. Now we can release the semaphore, allowing a new thread. // See prodScopeThread where the semaphore is acquired again. m_semaphoreScope.release(1); this->update(); // Calculate the acceleration factor hint to get «realtime» updates. if (m_aRealtime->isChecked()) { int accel; accel = calculateAccelFactorScope(mseconds, oldFactor); if (accel < 1) { // If mseconds happens to be 0. accel = 1; } // Don't directly calculate with m_accelFactorScope as we are dealing with concurrency. // If m_accelFactorScope is set to 0 at the wrong moment, who knows what might happen // then :) Therefore use a local variable. m_accelFactorScope = accel; } if ((m_newScopeFrames > 0 && m_aAutoRefresh->isChecked()) || m_newScopeUpdates > 0) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Trying to start a new scope thread for " << m_widgetName << ". New frames/updates: " << m_newScopeFrames << '/' << m_newScopeUpdates; #endif prodScopeThread(); } } void AbstractScopeWidget::slotBackgroundRenderingFinished(uint mseconds, uint oldFactor) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Background rendering has finished in " << mseconds << " ms, waiting for termination in " << m_widgetName; #endif m_threadBackground.waitForFinished(); m_imgBackground = m_threadBackground.result(); m_semaphoreBackground.release(1); this->update(); if (m_aRealtime->isChecked()) { int accel; accel = calculateAccelFactorBackground(mseconds, oldFactor); if (m_accelFactorBackground < 1) { accel = 1; } m_accelFactorBackground = accel; } if ((m_newBackgroundFrames > 0 && m_aAutoRefresh->isChecked()) || m_newBackgroundUpdates > 0) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Trying to start a new background thread for " << m_widgetName << ". New frames/updates: " << m_newBackgroundFrames << '/' << m_newBackgroundUpdates; #endif prodBackgroundThread(); } } void AbstractScopeWidget::slotRenderZoneUpdated() { m_newHUDFrames.fetchAndAddRelaxed(1); m_newScopeFrames.fetchAndAddRelaxed(1); m_newBackgroundFrames.fetchAndAddRelaxed(1); #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Data incoming at " << widgetName() << ". New frames total HUD/Scope/Background: " << m_newHUDFrames << '/' << m_newScopeFrames << '/' << m_newBackgroundFrames; #endif if (this->visibleRegion().isEmpty()) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Scope of widget " << m_widgetName << " is not at the top, not rendering."; #endif } else { if (m_aAutoRefresh->isChecked()) { prodHUDThread(); prodScopeThread(); prodBackgroundThread(); } } } void AbstractScopeWidget::slotResetRealtimeFactor(bool realtimeChecked) { if (!realtimeChecked) { m_accelFactorHUD = 1; m_accelFactorScope = 1; m_accelFactorBackground = 1; } } bool AbstractScopeWidget::autoRefreshEnabled() const { return m_aAutoRefresh->isChecked(); } void AbstractScopeWidget::slotAutoRefreshToggled(bool autoRefresh) { #ifdef DEBUG_ASW qCDebug(KDENLIVE_LOG) << "Auto-refresh switched to " << autoRefresh << " in " << widgetName() << " (Visible: " << isVisible() << '/' << this->visibleRegion().isEmpty() << ')'; #endif if (isVisible()) { // Notify listeners whether we accept new frames now emit requestAutoRefresh(autoRefresh); } // TODO only if depends on input if (autoRefresh) { // forceUpdate(); m_requestForcedUpdate = true; } } -void AbstractScopeWidget::handleMouseDrag(const QPoint &, const RescaleDirection, const Qt::KeyboardModifiers) -{ -} +void AbstractScopeWidget::handleMouseDrag(const QPoint &, const RescaleDirection, const Qt::KeyboardModifiers) {} #ifdef DEBUG_ASW #undef DEBUG_ASW #endif diff --git a/src/scopes/audioscopes/audiospectrum.cpp b/src/scopes/audioscopes/audiospectrum.cpp index e424b1586..bcbea611a 100644 --- a/src/scopes/audioscopes/audiospectrum.cpp +++ b/src/scopes/audioscopes/audiospectrum.cpp @@ -1,538 +1,538 @@ /*************************************************************************** * Copyright (C) 2010 by Simon Andreas Eugster (simon.eu@gmail.com) * * 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) any later version. * ***************************************************************************/ #include "audiospectrum.h" #include "lib/audio/fftTools.h" #include "lib/external/kiss_fft/tools/kiss_fftr.h" #include #include #include "klocalizedstring.h" #include #include #include // (defined in the header file) #ifdef DEBUG_AUDIOSPEC #include "kdenlive_debug.h" #endif // (defined in the header file) #ifdef DETECT_OVERMODULATION #include #include #endif // Draw lines instead of single pixels. // This is about 25 % faster, especially when enlarging the scope to e.g. 1680x1050 px. #define AUDIOSPEC_LINES #define MIN_DB_VALUE -120 #define MAX_FREQ_VALUE 96000 #define MIN_FREQ_VALUE 1000 #define ALPHA_MOVING_AVG 0.125 #define MAX_OVM_COLOR 0.7 AudioSpectrum::AudioSpectrum(QWidget *parent) : AbstractAudioScopeWidget(true, parent) , m_fftTools() , m_lastFFT() , m_lastFFTLock(1) , m_peaks() #ifdef DEBUG_AUDIOSPEC , m_timeTotal(0) , m_showTotal(0) #endif , m_dBmin(-70) , m_dBmax(0) , m_freqMax(0) , m_customFreq(false) , colorizeFactor(0) { ui = new Ui::AudioSpectrum_UI; ui->setupUi(this); m_aResetHz = new QAction(i18n("Reset maximum frequency to sampling rate"), this); m_aTrackMouse = new QAction(i18n("Track mouse"), this); m_aTrackMouse->setCheckable(true); m_aShowMax = new QAction(i18n("Show maximum"), this); m_aShowMax->setCheckable(true); m_menu->addSeparator(); m_menu->addAction(m_aResetHz); m_menu->addAction(m_aTrackMouse); m_menu->addAction(m_aShowMax); m_menu->removeAction(m_aRealtime); ui->windowSize->addItem(QStringLiteral("256"), QVariant(256)); ui->windowSize->addItem(QStringLiteral("512"), QVariant(512)); ui->windowSize->addItem(QStringLiteral("1024"), QVariant(1024)); ui->windowSize->addItem(QStringLiteral("2048"), QVariant(2048)); ui->windowFunction->addItem(i18n("Rectangular window"), FFTTools::Window_Rect); ui->windowFunction->addItem(i18n("Triangular window"), FFTTools::Window_Triangle); ui->windowFunction->addItem(i18n("Hamming window"), FFTTools::Window_Hamming); connect(m_aResetHz, &QAction::triggered, this, &AudioSpectrum::slotResetMaxFreq); connect(ui->windowFunction, SIGNAL(currentIndexChanged(int)), this, SLOT(forceUpdate())); connect(this, &AudioSpectrum::signalMousePositionChanged, this, &AudioSpectrum::forceUpdateHUD); // Note: These strings are used in both Spectogram and AudioSpectrum. Ideally change both (if necessary) to reduce workload on translators ui->labelFFTSize->setToolTip(i18n("The maximum window size is limited by the number of samples per frame.")); ui->windowSize->setToolTip(i18n("A bigger window improves the accuracy at the cost of computational power.")); ui->windowFunction->setToolTip(i18n("The rectangular window function is good for signals with equal signal strength (narrow peak), but creates more " "smearing. See Window function on Wikipedia.")); AbstractScopeWidget::init(); } AudioSpectrum::~AudioSpectrum() { writeConfig(); delete m_aResetHz; delete m_aTrackMouse; delete ui; } void AudioSpectrum::readConfig() { AbstractScopeWidget::readConfig(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); ui->windowSize->setCurrentIndex(scopeConfig.readEntry("windowSize", 0)); ui->windowFunction->setCurrentIndex(scopeConfig.readEntry("windowFunction", 0)); m_aTrackMouse->setChecked(scopeConfig.readEntry("trackMouse", true)); m_aShowMax->setChecked(scopeConfig.readEntry("showMax", true)); m_dBmax = scopeConfig.readEntry("dBmax", 0); m_dBmin = scopeConfig.readEntry("dBmin", -70); m_freqMax = scopeConfig.readEntry("freqMax", 0); if (m_freqMax == 0) { m_customFreq = false; m_freqMax = 10000; } else { m_customFreq = true; } } void AudioSpectrum::writeConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup scopeConfig(config, AbstractScopeWidget::configName()); scopeConfig.writeEntry("windowSize", ui->windowSize->currentIndex()); scopeConfig.writeEntry("windowFunction", ui->windowFunction->currentIndex()); scopeConfig.writeEntry("trackMouse", m_aTrackMouse->isChecked()); scopeConfig.writeEntry("showMax", m_aShowMax->isChecked()); scopeConfig.writeEntry("dBmax", m_dBmax); scopeConfig.writeEntry("dBmin", m_dBmin); if (m_customFreq) { scopeConfig.writeEntry("freqMax", m_freqMax); } else { scopeConfig.writeEntry("freqMax", 0); } scopeConfig.sync(); } QString AudioSpectrum::widgetName() const { return QStringLiteral("AudioSpectrum"); } QImage AudioSpectrum::renderBackground(uint) { return QImage(); } QImage AudioSpectrum::renderAudioScope(uint, const audioShortVector &audioFrame, const int freq, const int num_channels, const int num_samples, const int) { if (audioFrame.size() > 63 && m_innerScopeRect.width() > 0 && m_innerScopeRect.height() > 0 // <= 0 if widget is too small (resized by user) - ) { + ) { if (!m_customFreq) { m_freqMax = freq / 2; } QTime start = QTime::currentTime(); /*******FIXME!!! #ifdef DETECT_OVERMODULATION bool overmodulated = false; int overmodulateCount = 0; for (int i = 0; i < audioFrame.size(); ++i) { if ( audioFrame[i] == std::numeric_limits::max() || audioFrame[i] == std::numeric_limits::min()) { overmodulateCount++; if (overmodulateCount > 3) { overmodulated = true; break; } } } if (overmodulated) { colorizeFactor = 1; } else { if (colorizeFactor > 0) { colorizeFactor -= .08; if (colorizeFactor < 0) { colorizeFactor = 0; } } } #endif *******/ // Determine the window size to use. It should be // * not bigger than the number of samples actually available // * divisible by 2 int fftWindow = ui->windowSize->itemData(ui->windowSize->currentIndex()).toInt(); if (fftWindow > num_samples) { fftWindow = num_samples; } if ((fftWindow & 1) == 1) { fftWindow--; } // Show the window size used, for information ui->labelFFTSizeNumber->setText(QVariant(fftWindow).toString()); // Get the spectral power distribution of the input samples, // using the given window size and function float freqSpectrum[fftWindow / 2]; FFTTools::WindowType windowType = (FFTTools::WindowType)ui->windowFunction->itemData(ui->windowFunction->currentIndex()).toInt(); m_fftTools.fftNormalized(audioFrame, 0, num_channels, freqSpectrum, windowType, fftWindow, 0); // Store the current FFT window (for the HUD) and run the interpolation // for easy pixel-based dB value access QVector dbMap; m_lastFFTLock.acquire(); m_lastFFT = QVector(fftWindow / 2); memcpy(m_lastFFT.data(), &(freqSpectrum[0]), fftWindow / 2 * sizeof(float)); uint right = ((float)m_freqMax) / (m_freq / 2) * (m_lastFFT.size() - 1); dbMap = FFTTools::interpolatePeakPreserving(m_lastFFT, m_innerScopeRect.width(), 0, right, -180); m_lastFFTLock.release(); #ifdef DEBUG_AUDIOSPEC QTime drawTime = QTime::currentTime(); #endif // Draw the spectrum QImage spectrum(m_scopeRect.size(), QImage::Format_ARGB32); spectrum.fill(qRgba(0, 0, 0, 0)); const uint w = m_innerScopeRect.width(); const uint h = m_innerScopeRect.height(); const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); QColor spectrumColor(AbstractScopeWidget::colDarkWhite); int yMax; #ifdef DETECT_OVERMODULATION if (colorizeFactor > 0) { QColor col = AbstractScopeWidget::colHighlightDark; QColor spec = spectrumColor; float f = std::sin(M_PI_2 * colorizeFactor); spectrumColor = QColor((int)(f * col.red() + (1 - f) * spec.red()), (int)(f * col.green() + (1 - f) * spec.green()), (int)(f * col.blue() + (1 - f) * spec.blue()), spec.alpha()); // Limit the maximum colorization for non-overmodulated frames to better // recognize consecutively overmodulated frames if (colorizeFactor > MAX_OVM_COLOR) { colorizeFactor = MAX_OVM_COLOR; } } #endif #ifdef AUDIOSPEC_LINES QPainter davinci(&spectrum); davinci.setPen(QPen(QBrush(spectrumColor.rgba()), 1, Qt::SolidLine)); #endif for (uint i = 0; i < w; ++i) { yMax = (dbMap[i] - m_dBmin) / (m_dBmax - m_dBmin) * (h - 1); if (yMax < 0) { yMax = 0; } else if (yMax >= (int)h) { yMax = h - 1; } #ifdef AUDIOSPEC_LINES davinci.drawLine(leftDist + i, topDist + h - 1, leftDist + i, topDist + h - 1 - yMax); #else for (int y = 0; y < yMax && y < (int)h; ++y) { spectrum.setPixel(leftDist + i, topDist + h - y - 1, spectrumColor.rgba()); } #endif } // Calculate the peak values. Use the new value if it is bigger, otherwise adapt to lower // values using the Moving Average formula if (m_aShowMax->isChecked()) { davinci.setPen(QPen(QBrush(AbstractScopeWidget::colHighlightLight), 2)); if (m_peaks.size() != fftWindow / 2) { m_peaks = QVector(m_lastFFT); } else { for (int i = 0; i < fftWindow / 2; ++i) { if (m_lastFFT[i] > m_peaks[i]) { m_peaks[i] = m_lastFFT[i]; } else { m_peaks[i] = ALPHA_MOVING_AVG * m_lastFFT[i] + (1 - ALPHA_MOVING_AVG) * m_peaks[i]; } } } int prev = 0; m_peakMap = FFTTools::interpolatePeakPreserving(m_peaks, m_innerScopeRect.width(), 0, right, -180); for (uint i = 0; i < w; ++i) { yMax = (m_peakMap[i] - m_dBmin) / (m_dBmax - m_dBmin) * (h - 1); if (yMax < 0) { yMax = 0; } else if (yMax >= (int)h) { yMax = h - 1; } davinci.drawLine(leftDist + i - 1, topDist + h - prev - 1, leftDist + i, topDist + h - yMax - 1); spectrum.setPixel(leftDist + i, topDist + h - yMax - 1, AbstractScopeWidget::colHighlightLight.rgba()); prev = yMax; } } #ifdef DEBUG_AUDIOSPEC m_showTotal++; m_timeTotal += drawTime.elapsed(); qCDebug(KDENLIVE_LOG) << widgetName() << " took " << drawTime.elapsed() << " ms for drawing. Average: " << ((float)m_timeTotal / m_showTotal); #endif emit signalScopeRenderingFinished(start.elapsed(), 1); return spectrum; } emit signalScopeRenderingFinished(0, 1); return QImage(); } QImage AudioSpectrum::renderHUD(uint) { QTime start = QTime::currentTime(); if (m_innerScopeRect.height() > 0 && m_innerScopeRect.width() > 0) { // May be below 0 if widget is too small // Minimum distance between two lines const uint minDistY = 30; const uint minDistX = 40; const uint textDistX = 10; const uint textDistY = 25; const uint topDist = m_innerScopeRect.top() - m_scopeRect.top(); const uint leftDist = m_innerScopeRect.left() - m_scopeRect.left(); const uint dbDiff = ceil((float)minDistY / m_innerScopeRect.height() * (m_dBmax - m_dBmin)); const int mouseX = m_mousePos.x() - m_innerScopeRect.left(); const int mouseY = m_mousePos.y() - m_innerScopeRect.top(); QImage hud(m_scopeRect.size(), QImage::Format_ARGB32); hud.fill(qRgba(0, 0, 0, 0)); QPainter davinci(&hud); davinci.setPen(AbstractScopeWidget::penLight); int y; for (int db = -dbDiff; db > m_dBmin; db -= dbDiff) { y = topDist + m_innerScopeRect.height() * ((float)db) / (m_dBmin - m_dBmax); if (y - topDist > m_innerScopeRect.height() - minDistY + 10) { // Abort here, there is still a line left for min dB to paint which needs some room. break; } davinci.drawLine(leftDist, y, leftDist + m_innerScopeRect.width() - 1, y); davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, y + 6, i18n("%1 dB", m_dBmax + db)); } davinci.drawLine(leftDist, topDist, leftDist + m_innerScopeRect.width() - 1, topDist); davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, topDist + 6, i18n("%1 dB", m_dBmax)); davinci.drawLine(leftDist, topDist + m_innerScopeRect.height() - 1, leftDist + m_innerScopeRect.width() - 1, topDist + m_innerScopeRect.height() - 1); davinci.drawText(leftDist + m_innerScopeRect.width() + textDistX, topDist + m_innerScopeRect.height() + 6, i18n("%1 dB", m_dBmin)); const uint hzDiff = ceil(((float)minDistX) / m_innerScopeRect.width() * m_freqMax / 1000) * 1000; int x = 0; const int rightBorder = leftDist + m_innerScopeRect.width() - 1; y = topDist + m_innerScopeRect.height() + textDistY; for (int hz = 0; x <= rightBorder; hz += hzDiff) { davinci.setPen(AbstractScopeWidget::penLighter); x = leftDist + m_innerScopeRect.width() * ((float)hz) / m_freqMax; if (x <= rightBorder) { davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() + 6); } if (hz < m_freqMax && x + textDistY < leftDist + m_innerScopeRect.width()) { davinci.drawText(x - 4, y, QVariant(hz / 1000).toString()); } else { x = leftDist + m_innerScopeRect.width(); davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() + 6); davinci.drawText(x - 10, y, i18n("%1 kHz", QString("%1").arg((double)m_freqMax / 1000, 0, 'f', 1))); } if (hz > 0) { // Draw finer lines between the main lines davinci.setPen(AbstractScopeWidget::penLightDots); for (uint dHz = 3; dHz > 0; --dHz) { x = leftDist + m_innerScopeRect.width() * ((float)hz - dHz * hzDiff / 4.0f) / m_freqMax; if (x > rightBorder) { break; } davinci.drawLine(x, topDist, x, topDist + m_innerScopeRect.height() - 1); } } } if (m_aTrackMouse->isChecked() && m_mouseWithinWidget && mouseX < m_innerScopeRect.width() - 1) { davinci.setPen(AbstractScopeWidget::penThin); x = leftDist + mouseX; float db = 0; float freq = ((float)mouseX) / (m_innerScopeRect.width() - 1) * m_freqMax; bool drawDb = false; m_lastFFTLock.acquire(); // We need to test whether the mouse is inside the widget // because the position could already have changed in the meantime (-> crash) if (!m_lastFFT.isEmpty() && mouseX >= 0 && mouseX < m_innerScopeRect.width()) { uint right = ((float)m_freqMax) / (m_freq / 2) * (m_lastFFT.size() - 1); QVector dbMap = FFTTools::interpolatePeakPreserving(m_lastFFT, m_innerScopeRect.width(), 0, right, -120); db = dbMap[mouseX]; y = topDist + m_innerScopeRect.height() - 1 - (dbMap[mouseX] - m_dBmin) / (m_dBmax - m_dBmin) * (m_innerScopeRect.height() - 1); if (y < (int)topDist + m_innerScopeRect.height() - 1) { drawDb = true; davinci.drawLine(x, y, leftDist + m_innerScopeRect.width() - 1, y); } } else { y = topDist + mouseY; } m_lastFFTLock.release(); if (y > (int)topDist + mouseY) { y = topDist + mouseY; } davinci.drawLine(x, y, x, topDist + m_innerScopeRect.height() - 1); if (drawDb) { QPoint dist(20, -20); QRect rect(leftDist + mouseX + dist.x(), topDist + mouseY + dist.y(), 100, 40); if (rect.right() > (int)leftDist + m_innerScopeRect.width() - 1) { // Mirror the rectangle at the y axis to keep it inside the widget rect = QRect(rect.topLeft() - QPoint(rect.width() + 2 * dist.x(), 0), rect.size()); } QRect textRect(rect.topLeft() + QPoint(12, 4), rect.size()); davinci.fillRect(rect, AbstractScopeWidget::penBackground.brush()); davinci.setPen(AbstractScopeWidget::penLighter); davinci.drawRect(rect); davinci.drawText(textRect, QString(i18n("%1 dB", QString("%1").arg(db, 0, 'f', 2)) + '\n' + i18n("%1 kHz", QString("%1").arg(freq / 1000, 0, 'f', 2)))); } } emit signalHUDRenderingFinished(start.elapsed(), 1); return hud; } #ifdef DEBUG_AUDIOSPEC qCDebug(KDENLIVE_LOG) << "Widget is too small for painting inside. Size of inner scope rect is " << m_innerScopeRect.width() << 'x' << m_innerScopeRect.height() << "."; #endif emit signalHUDRenderingFinished(0, 1); return QImage(); } QRect AudioSpectrum::scopeRect() { m_scopeRect = QRect(QPoint(10, // Left ui->verticalSpacer->geometry().top() + 6 // Top ), AbstractAudioScopeWidget::rect().bottomRight()); m_innerScopeRect = QRect(QPoint(m_scopeRect.left() + 6, // Left m_scopeRect.top() + 6 // Top ), QPoint(ui->verticalSpacer->geometry().right() - 70, ui->verticalSpacer->geometry().bottom() - 40)); return m_scopeRect; } void AudioSpectrum::slotResetMaxFreq() { m_customFreq = false; forceUpdateHUD(); forceUpdateScope(); } ///// EVENTS ///// void AudioSpectrum::handleMouseDrag(const QPoint &movement, const RescaleDirection rescaleDirection, const Qt::KeyboardModifiers rescaleModifiers) { if (rescaleDirection == North) { // Nort-South direction: Adjust the dB scale if ((rescaleModifiers & Qt::ShiftModifier) == 0) { // By default adjust the min dB value m_dBmin += movement.y(); } else { // Adjust max dB value if Shift is pressed. m_dBmax += movement.y(); } // Ensure the dB values lie in [-100, 0] (or rather [MIN_DB_VALUE, 0]) // 0 is the upper bound, everything below -70 dB is most likely noise if (m_dBmax > 0) { m_dBmax = 0; } if (m_dBmin < MIN_DB_VALUE) { m_dBmin = MIN_DB_VALUE; } // Ensure there is at least 6 dB between the minimum and the maximum value; // lower values hardly make sense if (m_dBmax - m_dBmin < 6) { if ((rescaleModifiers & Qt::ShiftModifier) == 0) { // min was adjusted; Try to adjust the max value to maintain the // minimum dB difference of 6 dB m_dBmax = m_dBmin + 6; if (m_dBmax > 0) { m_dBmax = 0; m_dBmin = -6; } } else { // max was adjusted, adjust min m_dBmin = m_dBmax - 6; if (m_dBmin < MIN_DB_VALUE) { m_dBmin = MIN_DB_VALUE; m_dBmax = MIN_DB_VALUE + 6; } } } forceUpdateHUD(); forceUpdateScope(); } else if (rescaleDirection == East) { // East-West direction: Adjust the maximum frequency m_freqMax -= 100 * movement.x(); if (m_freqMax < MIN_FREQ_VALUE) { m_freqMax = MIN_FREQ_VALUE; } if (m_freqMax > MAX_FREQ_VALUE) { m_freqMax = MAX_FREQ_VALUE; } m_customFreq = true; forceUpdateHUD(); forceUpdateScope(); } } diff --git a/src/statusbarmessagelabel.cpp b/src/statusbarmessagelabel.cpp index 3064a0048..14db799fa 100644 --- a/src/statusbarmessagelabel.cpp +++ b/src/statusbarmessagelabel.cpp @@ -1,301 +1,297 @@ /*************************************************************************** * Copyright (C) 2006 by Peter Penz * * 2012 Simon A. Eugster * * peter.penz@gmx.at * * Code borrowed from Dolphin, adapted (2008) to Kdenlive by * * Jean-Baptiste Mardelle, jb@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) 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 "statusbarmessagelabel.h" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include FlashLabel::FlashLabel(QWidget *parent) : QWidget(parent) { setAutoFillBackground(true); } -FlashLabel::~FlashLabel() -{ -} +FlashLabel::~FlashLabel() {} void FlashLabel::setColor(const QColor &col) { QPalette pal = palette(); pal.setColor(QPalette::Window, col); setPalette(pal); update(); } QColor FlashLabel::color() const { return palette().window().color(); } StatusBarMessageLabel::StatusBarMessageLabel(QWidget *parent) : FlashLabel(parent) , m_minTextHeight(-1) , m_queueSemaphore(1) { setMinimumHeight(KIconLoader::SizeSmall); auto *lay = new QHBoxLayout(this); m_pixmap = new QLabel(this); m_pixmap->setAlignment(Qt::AlignCenter); m_label = new QLabel(this); m_label->setAlignment(Qt::AlignLeft); m_progress = new QProgressBar(this); lay->addWidget(m_pixmap); lay->addWidget(m_label); lay->addWidget(m_progress); setLayout(lay); m_progress->setVisible(false); lay->setContentsMargins(BorderGap, 0, 2 * BorderGap, 0); m_queueTimer.setSingleShot(true); connect(&m_queueTimer, &QTimer::timeout, this, &StatusBarMessageLabel::slotMessageTimeout); connect(m_label, &QLabel::linkActivated, this, &StatusBarMessageLabel::slotShowJobLog); } -StatusBarMessageLabel::~StatusBarMessageLabel() -{ -} +StatusBarMessageLabel::~StatusBarMessageLabel() {} void StatusBarMessageLabel::mousePressEvent(QMouseEvent *event) { QWidget::mousePressEvent(event); if (m_pixmap->rect().contains(event->localPos().toPoint()) && m_currentMessage.type == MltError) { confirmErrorMessage(); } } void StatusBarMessageLabel::setProgressMessage(const QString &text, int progress, MessageType type, int timeoutMS) { if (type == ProcessingJobMessage) { m_progress->setValue(progress); m_progress->setVisible(progress < 100); } else if (m_currentMessage.type != ProcessingJobMessage || type == OperationCompletedMessage) { m_progress->setVisible(progress < 100); } if (text == m_currentMessage.text) { return; } setMessage(text, type, timeoutMS); } void StatusBarMessageLabel::setMessage(const QString &text, MessageType type, int timeoutMS) { StatusBarMessageItem item(text, type, timeoutMS); if (type == OperationCompletedMessage) { m_progress->setVisible(false); } if (item.type == ErrorMessage || item.type == MltError) { KNotification::event(QStringLiteral("ErrorMessage"), item.text); } m_queueSemaphore.acquire(); if (!m_messageQueue.contains(item)) { if (item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) { qCDebug(KDENLIVE_LOG) << item.text; // Put the new error message at first place and immediately show it if (item.timeoutMillis < 3000) { item.timeoutMillis = 3000; } if (item.type == ProcessingJobMessage) { // This is a job progress info, discard previous ones QList cleanList; for (const StatusBarMessageItem &msg : m_messageQueue) { if (msg.type != ProcessingJobMessage) { cleanList << msg; } } m_messageQueue = cleanList; } m_messageQueue.push_front(item); // In case we are already displaying an error message, add a little delay int delay = 800 * static_cast(m_currentMessage.type == ErrorMessage || m_currentMessage.type == MltError); m_queueTimer.start(delay); } else { // Message with normal priority m_messageQueue.push_back(item); if (m_queueTimer.elapsed() >= m_currentMessage.timeoutMillis) { m_queueTimer.start(0); } } } m_queueSemaphore.release(); } bool StatusBarMessageLabel::slotMessageTimeout() { m_queueSemaphore.acquire(); bool newMessage = false; // Get the next message from the queue, unless the current one needs to be confirmed if (m_currentMessage.type == ProcessingJobMessage) { // Check if we have a job completed message to cancel this one StatusBarMessageItem item; while (!m_messageQueue.isEmpty()) { item = m_messageQueue.at(0); m_messageQueue.removeFirst(); if (item.type == OperationCompletedMessage || item.type == ErrorMessage || item.type == MltError || item.type == ProcessingJobMessage) { m_currentMessage = item; m_label->setText(m_currentMessage.text); newMessage = true; break; } } } else if (!m_messageQueue.isEmpty()) { if (!m_currentMessage.needsConfirmation()) { m_currentMessage = m_messageQueue.at(0); m_label->setText(m_currentMessage.text); m_messageQueue.removeFirst(); newMessage = true; } } // If the queue is empty, add a default (empty) message if (m_messageQueue.isEmpty() && m_currentMessage.type != DefaultMessage) { m_messageQueue.push_back(StatusBarMessageItem()); } // Start a new timer, unless the current message still needs to be confirmed if (!m_messageQueue.isEmpty()) { if (!m_currentMessage.needsConfirmation()) { // If we only have the default message left to show in the queue, // keep the current one for a little longer. m_queueTimer.start(m_currentMessage.timeoutMillis + 4000 * static_cast(m_messageQueue.at(0).type == DefaultMessage)); } } QColor bgColor = KStatefulBrush(KColorScheme::Window, KColorScheme::NegativeBackground, KSharedConfig::openConfig(KdenliveSettings::colortheme())).brush(this).color(); const char *iconName = nullptr; setColor(parentWidget()->palette().window().color()); switch (m_currentMessage.type) { case ProcessingJobMessage: iconName = "chronometer"; m_pixmap->setCursor(Qt::ArrowCursor); break; case OperationCompletedMessage: iconName = "dialog-ok"; m_pixmap->setCursor(Qt::ArrowCursor); break; case InformationMessage: { iconName = "dialog-information"; m_pixmap->setCursor(Qt::ArrowCursor); QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this); anim->setDuration(1500); anim->setEasingCurve(QEasingCurve::InOutQuad); anim->setKeyValueAt(0.2, parentWidget()->palette().highlight().color()); anim->setEndValue(parentWidget()->palette().window().color()); anim->start(QPropertyAnimation::DeleteWhenStopped); break; } case ErrorMessage: { iconName = "dialog-warning"; m_pixmap->setCursor(Qt::ArrowCursor); QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this); anim->setStartValue(bgColor); anim->setKeyValueAt(0.8, bgColor); anim->setEndValue(parentWidget()->palette().window().color()); anim->setEasingCurve(QEasingCurve::OutCubic); anim->setDuration(4000); anim->start(QPropertyAnimation::DeleteWhenStopped); break; } case MltError: { iconName = "dialog-close"; m_pixmap->setCursor(Qt::PointingHandCursor); QPropertyAnimation *anim = new QPropertyAnimation(this, "color", this); anim->setStartValue(bgColor); anim->setEndValue(bgColor); anim->setEasingCurve(QEasingCurve::OutCubic); anim->setDuration(1500); anim->start(QPropertyAnimation::DeleteWhenStopped); break; } case DefaultMessage: m_pixmap->setCursor(Qt::ArrowCursor); default: break; } if (iconName == nullptr) { m_pixmap->setVisible(false); } else { m_pixmap->setPixmap(SmallIcon(iconName)); m_pixmap->setVisible(true); } m_queueSemaphore.release(); return newMessage; } void StatusBarMessageLabel::confirmErrorMessage() { m_currentMessage.confirmed = true; m_queueTimer.start(0); } void StatusBarMessageLabel::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); } void StatusBarMessageLabel::slotShowJobLog(const QString &text) { QDialog d(this); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); QWidget *mainWidget = new QWidget(this); auto *l = new QVBoxLayout; QTextEdit t(&d); t.insertPlainText(QUrl::fromPercentEncoding(text.toUtf8())); t.setReadOnly(true); l->addWidget(&t); mainWidget->setLayout(l); auto *mainLayout = new QVBoxLayout; d.setLayout(mainLayout); mainLayout->addWidget(mainWidget); mainLayout->addWidget(buttonBox); d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::accept); d.exec(); confirmErrorMessage(); } diff --git a/src/timecodedisplay.h b/src/timecodedisplay.h index f204891c2..8d73753ec 100644 --- a/src/timecodedisplay.h +++ b/src/timecodedisplay.h @@ -1,129 +1,129 @@ /* This file is part of the KDE project Copyright (c) 2010 Jean-Baptiste Mardelle This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef TIMECODEDISPLAY_H #define TIMECODEDISPLAY_H #include "gentime.h" #include "timecode.h" #include class MyValidator : public QValidator { public: explicit MyValidator(QObject *parent = nullptr); void fixup(QString &str) const override; QValidator::State validate(QString &str, int &pos) const override; }; /** * @class TimecodeDisplay * @brief A widget for inserting a timecode value. * @author Jean-Baptiste Mardelle * * TimecodeDisplay can be used to insert either frames * or a timecode in the format HH:MM:SS:FF */ class TimecodeDisplay : public QAbstractSpinBox { Q_OBJECT public: /** @brief Constructor for the widget, sets value to 0. - * @param t Timecode object used to setup correct input (frames or HH:MM:SS:FF) - * @param parent parent Widget */ + * @param t Timecode object used to setup correct input (frames or HH:MM:SS:FF) + * @param parent parent Widget */ explicit TimecodeDisplay(const Timecode &t, QWidget *parent = nullptr); /** @brief Returns the minimum value, which can be entered. - * default is 0 */ + * default is 0 */ int minimum() const; /** @brief Returns the maximum value, which can be entered. - * default is no maximum (-1) */ + * default is no maximum (-1) */ int maximum() const; /** @brief Sets the minimum maximum value that can be entered. - * @param min the minimum value - * @param max the maximum value */ + * @param min the minimum value + * @param max the maximum value */ void setRange(int min, int max); /** @brief Returns the current input in frames. */ int getValue() const; /** @brief Returns the current input as a GenTime object. */ GenTime gentime() const; /** @brief Returs the widget's timecode object. */ Timecode timecode() const; /** @brief Sets value's format to frames or HH:MM:SS:FF according to @param frametimecode. - * @param frametimecode true = frames, false = HH:MM:SS:FF - * @param init true = force the change, false = update only if the frametimecode param changed */ + * @param frametimecode true = frames, false = HH:MM:SS:FF + * @param init true = force the change, false = update only if the frametimecode param changed */ void setTimeCodeFormat(bool frametimecode, bool init = false); /** @brief Sets timecode for current project. * @param t the new timecode */ void updateTimeCode(const Timecode &t); void stepBy(int steps) override; const QString displayText() const; /** @brief Send a signal every time the timecode changes. */ void sendTimecode(bool send); private: /** timecode for widget */ Timecode m_timecode; /** Should we display the timecode in frames or in format hh:mm:ss:ff */ bool m_frametimecode; int m_minimum; int m_maximum; int m_value; public slots: /** @brief Sets the value. - * @param value the new value - * The value actually set is forced to be within the legal range: minimum <= value <= maximum */ + * @param value the new value + * The value actually set is forced to be within the legal range: minimum <= value <= maximum */ void setValue(int value); void setValue(const QString &value); void setValue(const GenTime &value); /** @brief Sets value's format according to Kdenlive's settings. - * @param t (optional, if already existing) Timecode object to use */ + * @param t (optional, if already existing) Timecode object to use */ void slotUpdateTimeCodeFormat(); private slots: void slotEditingFinished(); signals: void timeCodeEditingFinished(int value = -1); /** @brief Emit timecode on every change if requested. */ void emitTimeCode(const QString &); protected: void keyPressEvent(QKeyEvent *e) override; void mouseReleaseEvent(QMouseEvent *) override; void wheelEvent(QWheelEvent *e) override; void enterEvent(QEvent *e) override; void leaveEvent(QEvent *e) override; QAbstractSpinBox::StepEnabled stepEnabled() const override; }; #endif diff --git a/src/timeline2/model/compositionmodel.cpp b/src/timeline2/model/compositionmodel.cpp index 457fb98f0..18c672b3e 100644 --- a/src/timeline2/model/compositionmodel.cpp +++ b/src/timeline2/model/compositionmodel.cpp @@ -1,198 +1,199 @@ /*************************************************************************** * 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 "compositionmodel.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "timelinemodel.hpp" #include "trackmodel.hpp" #include "transitions/transitionsrepository.hpp" #include "undohelper.hpp" #include #include #include -CompositionModel::CompositionModel(std::weak_ptr parent, Mlt::Transition *transition, int id, const QDomElement &transitionXml, const QString &transitionId) +CompositionModel::CompositionModel(std::weak_ptr parent, Mlt::Transition *transition, int id, const QDomElement &transitionXml, + const QString &transitionId) : MoveableItem(std::move(parent), id) , AssetParameterModel(transition, transitionXml, transitionId, {ObjectType::TimelineComposition, m_id}) , a_track(-1) { m_compositionName = TransitionsRepository::get()->getName(transitionId); } int CompositionModel::construct(const std::weak_ptr &parent, const QString &transitionId, int id, Mlt::Properties *sourceProperties) { Mlt::Transition *transition = TransitionsRepository::get()->getTransition(transitionId); transition->set_in_and_out(0, 0); auto xml = TransitionsRepository::get()->getXml(transitionId); std::shared_ptr composition(new CompositionModel(parent, transition, id, xml, transitionId)); id = composition->m_id; if (sourceProperties != nullptr) { Mlt::Properties transProps(composition->service()->get_properties()); transProps.inherit(*sourceProperties); } if (auto ptr = parent.lock()) { ptr->registerComposition(composition); } else { qDebug() << "Error : construction of composition failed because parent timeline is not available anymore"; Q_ASSERT(false); } return id; } bool CompositionModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool /*logUndo*/) { QWriteLocker locker(&m_lock); if (size <= 0) { return false; } int delta = getPlaytime() - size; qDebug() << "compo request resize " << size << right << delta; int in = getIn(); int out = getOut(); int old_in = in, old_out = out; if (right) { out -= delta; } else { in += delta; } // if the in becomes negative, we add the necessary length in out. if (in < 0) { out = out - in; in = 0; } std::function track_operation = []() { return true; }; std::function track_reverse = []() { return true; }; if (m_currentTrackId != -1) { if (auto ptr = m_parent.lock()) { track_operation = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, in, out); } else { qDebug() << "Error : Moving composition failed because parent timeline is not available anymore"; Q_ASSERT(false); } } Fun operation = [in, out, track_operation, this]() { if (track_operation()) { return true; } return false; }; if (operation()) { // Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here auto ptr = m_parent.lock(); if (m_currentTrackId != -1 && ptr) { track_reverse = ptr->getTrackById(m_currentTrackId)->requestCompositionResize_lambda(m_id, old_in, old_out); } Fun reverse = [old_in, old_out, track_reverse, this]() { if (track_reverse()) { return true; } return false; }; UPDATE_UNDO_REDO(operation, reverse, undo, redo); return true; } return false; } const QString CompositionModel::getProperty(const QString &name) const { READ_LOCK(); return QString::fromUtf8(service()->get(name.toUtf8().constData())); } Mlt::Transition *CompositionModel::service() const { READ_LOCK(); return static_cast(m_asset.get()); } Mlt::Properties *CompositionModel::properties() { READ_LOCK(); return new Mlt::Properties(m_asset.get()->get_properties()); } int CompositionModel::getPlaytime() const { READ_LOCK(); return getOut() - getIn() + 1; } int CompositionModel::getATrack() const { READ_LOCK(); return a_track == -1 ? -1 : service()->get_int("a_track"); } void CompositionModel::setForceTrack(bool force) { READ_LOCK(); service()->set("force_track", force ? 1 : 0); } int CompositionModel::getForcedTrack() const { QWriteLocker locker(&m_lock); return (service()->get_int("force_track") == 0 || a_track == -1) ? -1 : service()->get_int("a_track"); } void CompositionModel::setATrack(int trackMltPosition, int trackId) { QWriteLocker locker(&m_lock); Q_ASSERT(trackId != getCurrentTrackId()); // can't compose with same track a_track = trackMltPosition; if (a_track >= 0) { service()->set("a_track", trackMltPosition); } emit compositionTrackChanged(); } KeyframeModel *CompositionModel::getEffectKeyframeModel() { if (getKeyframeModel()) { return getKeyframeModel()->getKeyModel(); } return nullptr; } bool CompositionModel::showKeyframes() const { READ_LOCK(); return !service()->get_int("kdenlive:hide_keyframes"); } void CompositionModel::setShowKeyframes(bool show) { QWriteLocker locker(&m_lock); service()->set("kdenlive:hide_keyframes", (int)!show); } const QString &CompositionModel::displayName() const { return m_compositionName; } void CompositionModel::setInOut(int in, int out) { m_position = in; MoveableItem::setInOut(in, out); } diff --git a/src/timeline2/model/compositionmodel.hpp b/src/timeline2/model/compositionmodel.hpp index d745013f6..cd054c4e2 100644 --- a/src/timeline2/model/compositionmodel.hpp +++ b/src/timeline2/model/compositionmodel.hpp @@ -1,113 +1,114 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef COMPOSITIONMODEL_H #define COMPOSITIONMODEL_H #include "assets/model/assetparametermodel.hpp" #include "moveableItem.hpp" #include "undohelper.hpp" #include #include namespace Mlt { class Transition; } class TimelineModel; class TrackModel; class KeyframeModel; /* @brief This class represents a Composition object, as viewed by the backend. In general, the Gui associated with it will send modification queries (such as resize or move), and this class authorize them or not depending on the validity of the modifications */ class CompositionModel : public MoveableItem, public AssetParameterModel { CompositionModel() = delete; protected: /* This constructor is not meant to be called, call the static construct instead */ CompositionModel(std::weak_ptr parent, Mlt::Transition *transition, int id, const QDomElement &transitionXml, const QString &transitionId); public: /* @brief Creates a composition, which then registers itself to the parent timeline Returns the (unique) id of the created composition @param parent is a pointer to the timeline @param transitionId is the id of the transition to be inserted @param id Requested id of the clip. Automatic if -1 */ static int construct(const std::weak_ptr &parent, const QString &transitionId, int id = -1, Mlt::Properties *sourceProperties = nullptr); friend class TrackModel; friend class TimelineModel; /* @brief returns the length of the item on the timeline */ int getPlaytime() const override; /* @brief Returns the id of the second track involved in the composition (a_track in mlt's vocabulary, the b_track beeing the track where the composition is inserted) */ int getATrack() const; /* @brief Defines the forced_track property. If true, the a_track will not change when composition * is moved to another track. When false, the a_track will automatically change to lower video track */ void setForceTrack(bool force); - /* @brief Returns the id of the second track involved in the composition (a_track) or -1 if the a_track should be automatically updated when the composition changes track + /* @brief Returns the id of the second track involved in the composition (a_track) or -1 if the a_track should be automatically updated when the composition + * changes track */ int getForcedTrack() const; /* @brief Sets the id of the second track involved in the composition*/ void setATrack(int trackMltPosition, int trackId); /* @brief returns a property of the current item */ const QString getProperty(const QString &name) const override; /* @brief returns the active effect's keyframe model */ KeyframeModel *getEffectKeyframeModel(); Q_INVOKABLE bool showKeyframes() const; Q_INVOKABLE void setShowKeyframes(bool show); const QString &displayName() const; Mlt::Properties *properties(); protected: Mlt::Transition *service() const override; void setInOut(int in, int out) override; /* @brief Performs a resize of the given composition. Returns true if the operation succeeded, and otherwise nothing is modified This method is protected because it shouldn't be called directly. Call the function in the timeline instead. If a snap point is within reach, the operation will be coerced to use it. @param size is the new size of the composition @param right is true if we change the right side of the composition, false otherwise @param undo Lambda function containing the current undo stack. Will be updated with current operation @param redo Lambda function containing the current redo queue. Will be updated with current operation */ bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) override; private: int a_track; QString m_compositionName; }; #endif diff --git a/src/timeline2/model/groupsmodel.cpp b/src/timeline2/model/groupsmodel.cpp index bba0dcbfb..aa4723f9f 100644 --- a/src/timeline2/model/groupsmodel.cpp +++ b/src/timeline2/model/groupsmodel.cpp @@ -1,806 +1,806 @@ /*************************************************************************** * 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 "groupsmodel.hpp" #include "macros.hpp" #include "timelineitemmodel.hpp" #include #include #include #include #include #include #include GroupsModel::GroupsModel(std::weak_ptr parent) : m_parent(std::move(parent)) , m_lock(QReadWriteLock::Recursive) { } void GroupsModel::promoteToGroup(int gid, GroupType type) { Q_ASSERT(type != GroupType::Leaf); Q_ASSERT(m_groupIds.count(gid) == 0); m_groupIds.insert({gid, type}); auto ptr = m_parent.lock(); if (ptr) { // qDebug() << "Registering group" << gid << "of type" << groupTypeToStr(getType(gid)); ptr->registerGroup(gid); } else { qDebug() << "Impossible to create group because the timeline is not available anymore"; Q_ASSERT(false); } } void GroupsModel::downgradeToLeaf(int gid) { Q_ASSERT(m_groupIds.count(gid) != 0); Q_ASSERT(m_downLink.at(gid).size() == 0); auto ptr = m_parent.lock(); if (ptr) { // qDebug() << "Deregistering group" << gid << "of type" << groupTypeToStr(getType(gid)); ptr->deregisterGroup(gid); m_groupIds.erase(gid); } else { qDebug() << "Impossible to ungroup item because the timeline is not available anymore"; Q_ASSERT(false); } } Fun GroupsModel::groupItems_lambda(int gid, const std::unordered_set &ids, GroupType type, int parent) { QWriteLocker locker(&m_lock); Q_ASSERT(ids.size() == 0 || type != GroupType::Leaf); return [gid, ids, parent, type, this]() { createGroupItem(gid); if (parent != -1) { setGroup(gid, parent); } if (ids.size() > 0) { promoteToGroup(gid, type); std::unordered_set roots; std::transform(ids.begin(), ids.end(), std::inserter(roots, roots.begin()), [&](int id) { return getRootId(id); }); auto ptr = m_parent.lock(); if (!ptr) Q_ASSERT(false); for (int id : roots) { setGroup(getRootId(id), gid); if (type != GroupType::Selection && ptr->isClip(id)) { QModelIndex ix = ptr->makeClipIndexFromID(id); ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); } } } return true; }; } int GroupsModel::groupItems(const std::unordered_set &ids, Fun &undo, Fun &redo, GroupType type, bool force) { QWriteLocker locker(&m_lock); Q_ASSERT(type != GroupType::Leaf); Q_ASSERT(!ids.empty()); if (ids.size() == 1 && !force) { // We do not create a group with only one element. Instead, we return the id of that element return *(ids.begin()); } int gid = TimelineModel::getNextId(); auto operation = groupItems_lambda(gid, ids, type); if (operation()) { auto reverse = destructGroupItem_lambda(gid); UPDATE_UNDO_REDO(operation, reverse, undo, redo); return gid; } return -1; } bool GroupsModel::ungroupItem(int id, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); int gid = getRootId(id); if (m_groupIds.count(gid) == 0) { // element is not part of a group return false; } return destructGroupItem(gid, true, undo, redo); } void GroupsModel::createGroupItem(int id) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) == 0); Q_ASSERT(m_downLink.count(id) == 0); m_upLink[id] = -1; m_downLink[id] = std::unordered_set(); } Fun GroupsModel::destructGroupItem_lambda(int id) { QWriteLocker locker(&m_lock); return [this, id]() { removeFromGroup(id); auto ptr = m_parent.lock(); if (!ptr) Q_ASSERT(false); for (int child : m_downLink[id]) { m_upLink[child] = -1; if (ptr->isClip(child)) { QModelIndex ix = ptr->makeClipIndexFromID(child); ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); } } m_downLink[id].clear(); if (getType(id) != GroupType::Leaf) { downgradeToLeaf(id); } m_downLink.erase(id); m_upLink.erase(id); return true; }; } bool GroupsModel::destructGroupItem(int id, bool deleteOrphan, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) > 0); int parent = m_upLink[id]; auto old_children = m_downLink[id]; auto old_type = getType(id); auto old_parent_type = GroupType::Normal; if (parent != -1) { old_parent_type = getType(parent); } auto operation = destructGroupItem_lambda(id); if (operation()) { auto reverse = groupItems_lambda(id, old_children, old_type, parent); // we may need to reset the group of the parent if (parent != -1) { auto setParent = [&, old_parent_type, parent]() { setType(parent, old_parent_type); return true; }; PUSH_LAMBDA(setParent, reverse); } UPDATE_UNDO_REDO(operation, reverse, undo, redo); if (parent != -1 && m_downLink[parent].empty() && deleteOrphan) { return destructGroupItem(parent, true, undo, redo); } return true; } return false; } bool GroupsModel::destructGroupItem(int id) { QWriteLocker locker(&m_lock); return destructGroupItem_lambda(id)(); } int GroupsModel::getRootId(int id) const { READ_LOCK(); std::unordered_set seen; // we store visited ids to detect cycles int father = -1; do { Q_ASSERT(m_upLink.count(id) > 0); Q_ASSERT(seen.count(id) == 0); seen.insert(id); father = m_upLink.at(id); if (father != -1) { id = father; } } while (father != -1); return id; } bool GroupsModel::isLeaf(int id) const { READ_LOCK(); Q_ASSERT(m_downLink.count(id) > 0); return m_downLink.at(id).empty(); } bool GroupsModel::isInGroup(int id) const { READ_LOCK(); Q_ASSERT(m_downLink.count(id) > 0); return getRootId(id) != id; } int GroupsModel::getSplitPartner(int id) const { READ_LOCK(); Q_ASSERT(m_downLink.count(id) > 0); int groupId = m_upLink.at(id); if (groupId == -1 || getType(groupId) != GroupType::AVSplit) { // clip does not have an AV split partner return -1; } std::unordered_set leaves = getDirectChildren(groupId); if (leaves.size() != 2) { // clip does not have an AV split partner qDebug() << "WRONG SPLIT GROUP SIZE: " << leaves.size(); return -1; } for (const int &child : leaves) { if (child != id) { return child; } } return -1; } std::unordered_set GroupsModel::getSubtree(int id) const { READ_LOCK(); std::unordered_set result; result.insert(id); std::queue queue; queue.push(id); while (!queue.empty()) { int current = queue.front(); queue.pop(); for (const int &child : m_downLink.at(current)) { result.insert(child); queue.push(child); } } return result; } std::unordered_set GroupsModel::getLeaves(int id) const { READ_LOCK(); std::unordered_set result; std::queue queue; queue.push(id); while (!queue.empty()) { int current = queue.front(); queue.pop(); for (const int &child : m_downLink.at(current)) { queue.push(child); } if (m_downLink.at(current).empty()) { result.insert(current); } } return result; } std::unordered_set GroupsModel::getDirectChildren(int id) const { READ_LOCK(); Q_ASSERT(m_downLink.count(id) > 0); return m_downLink.at(id); } int GroupsModel::getDirectAncestor(int id) const { READ_LOCK(); Q_ASSERT(m_upLink.count(id) > 0); return m_upLink.at(id); } void GroupsModel::setGroup(int id, int groupId) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) > 0); Q_ASSERT(groupId == -1 || m_downLink.count(groupId) > 0); Q_ASSERT(id != groupId); removeFromGroup(id); m_upLink[id] = groupId; if (groupId != -1) { m_downLink[groupId].insert(id); auto ptr = m_parent.lock(); if (ptr) { QModelIndex ix; if (ptr->isClip(id)) { ix = ptr->makeClipIndexFromID(id); } else if (ptr->isComposition(id)) { ix = ptr->makeCompositionIndexFromID(id); } if (ix.isValid()) { ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); } } - if (getType(groupId) == GroupType::Leaf) { + if (getType(groupId) == GroupType::Leaf) { promoteToGroup(groupId, GroupType::Normal); } } } void GroupsModel::removeFromGroup(int id) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) > 0); Q_ASSERT(m_downLink.count(id) > 0); int parent = m_upLink[id]; if (parent != -1) { Q_ASSERT(getType(parent) != GroupType::Leaf); m_downLink[parent].erase(id); QModelIndex ix; auto ptr = m_parent.lock(); if (!ptr) Q_ASSERT(false); if (ptr->isClip(id)) { ix = ptr->makeClipIndexFromID(id); } else if (ptr->isComposition(id)) { ix = ptr->makeCompositionIndexFromID(id); } if (ix.isValid()) { ptr->dataChanged(ix, ix, {TimelineModel::GroupedRole}); } if (m_downLink[parent].size() == 0) { downgradeToLeaf(parent); } } m_upLink[id] = -1; } bool GroupsModel::mergeSingleGroups(int id, Fun &undo, Fun &redo) { // The idea is as follow: we start from the leaves, and go up to the root. // In the process, if we find a node with only one children, we flag it for deletion QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) > 0); auto leaves = getLeaves(id); std::unordered_map old_parents, new_parents; std::vector to_delete; std::unordered_set processed; // to avoid going twice along the same branch for (int leaf : leaves) { int current = m_upLink[leaf]; int start = leaf; while (current != m_upLink[id] && processed.count(current) == 0) { processed.insert(current); if (m_downLink[current].size() == 1) { to_delete.push_back(current); } else { if (current != m_upLink[start]) { old_parents[start] = m_upLink[start]; new_parents[start] = current; } start = current; } current = m_upLink[current]; } if (current != m_upLink[start]) { old_parents[start] = m_upLink[start]; new_parents[start] = current; } } auto parent_changer = [this](const std::unordered_map &parents) { auto ptr = m_parent.lock(); if (!ptr) { qDebug() << "Impossible to create group because the timeline is not available anymore"; return false; } for (const auto &group : parents) { int old = m_upLink[group.first]; setGroup(group.first, group.second); } return true; }; Fun reverse = [this, old_parents, parent_changer]() { return parent_changer(old_parents); }; Fun operation = [this, new_parents, parent_changer]() { return parent_changer(new_parents); }; bool res = operation(); if (!res) { bool undone = reverse(); Q_ASSERT(undone); return res; } UPDATE_UNDO_REDO(operation, reverse, undo, redo); for (int gid : to_delete) { Q_ASSERT(m_downLink[gid].size() == 0); res = destructGroupItem(gid, false, undo, redo); if (!res) { bool undone = undo(); Q_ASSERT(undone); return res; } } return true; } bool GroupsModel::split(int id, const std::function &criterion, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); if (isLeaf(id)) { return true; } // This function is valid only for roots (otherwise it is not clear what should be the new parent of the created tree) Q_ASSERT(m_upLink[id] == -1); bool regroup = m_groupIds[id] != GroupType::Selection; // We do a BFS on the tree to copy it // We store corresponding nodes std::unordered_map corresp; // keys are id in the original tree, values are temporary negative id assigned for creation of the new tree corresp[-1] = -1; // These are the nodes to be moved to new tree std::vector to_move; // We store the groups (ie the nodes) that are going to be part of the new tree // Keys are temporary id (negative) and values are the set of children (true ids in the case of leaves and temporary ids for other nodes) std::unordered_map> new_groups; // We store also the target type of the new groups std::unordered_map new_types; std::queue queue; queue.push(id); int tempId = -10; while (!queue.empty()) { int current = queue.front(); queue.pop(); if (!isLeaf(current) || criterion(current)) { if (isLeaf(current)) { if (m_groupIds[getRootId(current)] != GroupType::Selection) { to_move.push_back(current); new_groups[corresp[m_upLink[current]]].insert(current); } } else { corresp[current] = tempId; new_types[tempId] = getType(current); if (m_upLink[current] != -1) new_groups[corresp[m_upLink[current]]].insert(tempId); tempId--; } } for (const int &child : m_downLink.at(current)) { queue.push(child); } } // First, we simulate deletion of elements that we have to remove from the original tree // A side effect of this is that empty groups will be removed for (const auto &leaf : to_move) { destructGroupItem(leaf, true, undo, redo); } // we artificially recreate the leaves Fun operation = [this, to_move]() { for (const auto &leaf : to_move) { createGroupItem(leaf); } return true; }; Fun reverse = [this, to_move]() { for (const auto &group : to_move) { destructGroupItem(group); } return true; }; bool res = operation(); if (!res) { return false; } UPDATE_UNDO_REDO(operation, reverse, undo, redo); // We prune the new_groups to remove empty ones bool finished = false; while (!finished) { finished = true; int selected = INT_MAX; for (const auto &it : new_groups) { if (it.second.size() == 0) { // empty group finished = false; selected = it.first; break; } for (int it2 : it.second) { if (it2 < -1 && new_groups.count(it2) == 0) { // group that has no reference, it is empty too finished = false; selected = it2; break; } } if (!finished) break; } if (!finished) { new_groups.erase(selected); for (auto it = new_groups.begin(); it != new_groups.end(); ++it) { (*it).second.erase(selected); } } } // We now regroup the items of the new tree to recreate hierarchy. // This is equivalent to creating the tree bottom up (starting from the leaves) // At each iteration, we create a new node by grouping together elements that are either leaves or already created nodes. std::unordered_map created_id; // to keep track of node that we create while (!new_groups.empty()) { int selected = INT_MAX; for (const auto &group : new_groups) { // we check that all children are already created bool ok = true; for (int elem : group.second) { if (elem < -1 && created_id.count(elem) == 0) { ok = false; break; } } if (ok) { selected = group.first; break; } } Q_ASSERT(selected != INT_MAX); std::unordered_set group; for (int elem : new_groups[selected]) { group.insert(elem < -1 ? created_id[elem] : elem); } Q_ASSERT(new_types.count(selected) != 0); int gid = groupItems(group, undo, redo, new_types[selected], true); created_id[selected] = gid; new_groups.erase(selected); } if (regroup) { mergeSingleGroups(id, undo, redo); mergeSingleGroups(created_id[corresp[id]], undo, redo); } return res; } void GroupsModel::setInGroupOf(int id, int targetId, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(targetId) > 0); Fun operation = [ this, id, group = m_upLink[targetId] ]() { setGroup(id, group); return true; }; Fun reverse = [ this, id, group = m_upLink[id] ]() { setGroup(id, group); return true; }; operation(); UPDATE_UNDO_REDO(operation, reverse, undo, redo); } bool GroupsModel::createGroupAtSameLevel(int id, std::unordered_set to_add, GroupType type, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_upLink.count(id) > 0); Q_ASSERT(isLeaf(id)); if (to_add.size() == 0) { return true; } int gid = TimelineModel::getNextId(); std::unordered_map old_parents; to_add.insert(id); for (int g : to_add) { Q_ASSERT(m_upLink.count(g) > 0); old_parents[g] = m_upLink[g]; } Fun operation = [ this, id, gid, type, to_add, parent = m_upLink.at(id) ]() { createGroupItem(gid); setGroup(gid, parent); for (const auto &g : to_add) { setGroup(g, gid); } setType(gid, type); return true; }; Fun reverse = [this, id, old_parents, gid]() { for (const auto &g : old_parents) { setGroup(g.first, g.second); } setGroup(gid, -1); destructGroupItem_lambda(gid)(); return true; }; bool success = operation(); if (success) { UPDATE_UNDO_REDO(operation, reverse, undo, redo); } return success; } bool GroupsModel::processCopy(int gid, std::unordered_map &mapping, Fun &undo, Fun &redo) { qDebug() << "processCopy" << gid; if (isLeaf(gid)) { qDebug() << "it is a leaf"; return true; } bool ok = true; std::unordered_set targetGroup; for (int child : m_downLink.at(gid)) { ok = ok && processCopy(child, mapping, undo, redo); if (!ok) { break; } targetGroup.insert(mapping.at(child)); } qDebug() << "processCopy" << gid << "success of child" << ok; if (ok && m_groupIds[gid] != GroupType::Selection) { int id = groupItems(targetGroup, undo, redo); qDebug() << "processCopy" << gid << "created id" << id; if (id != -1) { mapping[gid] = id; return true; } } return ok; } bool GroupsModel::copyGroups(std::unordered_map &mapping, Fun &undo, Fun &redo) { Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; // destruct old groups for the targets items for (const auto &corresp : mapping) { ungroupItem(corresp.second, local_undo, local_redo); } std::unordered_set roots; std::transform(mapping.begin(), mapping.end(), std::inserter(roots, roots.begin()), [&](decltype(*mapping.begin()) corresp) { return getRootId(corresp.first); }); bool res = true; qDebug() << "found" << roots.size() << "roots"; for (int r : roots) { qDebug() << "processing copy for root " << r; res = res && processCopy(r, mapping, local_undo, local_redo); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } GroupType GroupsModel::getType(int id) const { if (m_groupIds.count(id) > 0) { return m_groupIds.at(id); } return GroupType::Leaf; } QJsonObject GroupsModel::toJson(int gid) const { QJsonObject currentGroup; currentGroup.insert(QLatin1String("type"), QJsonValue(groupTypeToStr(getType(gid)))); if (m_groupIds.count(gid) > 0) { // in that case, we have a proper group QJsonArray array; Q_ASSERT(m_downLink.count(gid) > 0); for (int c : m_downLink.at(gid)) { array.push_back(toJson(c)); } currentGroup.insert(QLatin1String("children"), array); } else { // in that case we have a clip or composition if (auto ptr = m_parent.lock()) { Q_ASSERT(ptr->isClip(gid) || ptr->isComposition(gid)); currentGroup.insert(QLatin1String("leaf"), QJsonValue(QLatin1String(ptr->isClip(gid) ? "clip" : "composition"))); int track = ptr->getTrackPosition(ptr->getItemTrackId(gid)); int pos = ptr->getItemPosition(gid); currentGroup.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(track).arg(pos))); } else { qDebug() << "Impossible to create group because the timeline is not available anymore"; Q_ASSERT(false); } } return currentGroup; } const QString GroupsModel::toJson() const { std::unordered_set roots; std::transform(m_groupIds.begin(), m_groupIds.end(), std::inserter(roots, roots.begin()), [&](decltype(*m_groupIds.begin()) g) { return getRootId(g.first); }); QJsonArray list; for (int r : roots) { list.push_back(toJson(r)); } QJsonDocument json(list); return QString(json.toJson()); } int GroupsModel::fromJson(const QJsonObject &o, Fun &undo, Fun &redo) { if (!o.contains(QLatin1String("type"))) { return -1; } auto type = groupTypeFromStr(o.value(QLatin1String("type")).toString()); if (type == GroupType::Leaf) { if (auto ptr = m_parent.lock()) { if (!o.contains(QLatin1String("data")) || !o.contains(QLatin1String("leaf"))) { qDebug() << "Error: missing info in the group structure while parsing json"; return -1; } QString data = o.value(QLatin1String("data")).toString(); QString leaf = o.value(QLatin1String("leaf")).toString(); int trackId = ptr->getTrackIndexFromPosition(data.section(":", 0, 0).toInt()); int pos = data.section(":", 1, 1).toInt(); int id = -1; if (leaf == QLatin1String("clip")) { id = ptr->getClipByPosition(trackId, pos); } else if (leaf == QLatin1String("composition")) { id = ptr->getCompositionByPosition(trackId, pos); } return id; } else { qDebug() << "Impossible to create group because the timeline is not available anymore"; Q_ASSERT(false); } } else { if (!o.contains(QLatin1String("children"))) { qDebug() << "Error: missing info in the group structure while parsing json"; return -1; } auto value = o.value(QLatin1String("children")); if (!value.isArray()) { qDebug() << "Error : Expected json array of children while parsing groups"; return -1; } const auto children = value.toArray(); std::unordered_set ids; for (const auto &c : children) { if (!c.isObject()) { qDebug() << "Error : Expected json object while parsing groups"; return -1; } ids.insert(fromJson(c.toObject(), undo, redo)); } if (ids.count(-1) > 0) { return -1; } return groupItems(ids, undo, redo, type); } return -1; } bool GroupsModel::fromJson(const QString &data) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; auto json = QJsonDocument::fromJson(data.toUtf8()); if (!json.isArray()) { qDebug() << "Error : Json file should be an array"; return false; } const auto list = json.array(); bool ok = true; for (const auto &elem : list) { if (!elem.isObject()) { qDebug() << "Error : Expected json object while parsing groups"; undo(); return false; } ok = ok && fromJson(elem.toObject(), undo, redo); } return ok; } void GroupsModel::setType(int gid, GroupType type) { Q_ASSERT(m_groupIds.count(gid) != 0); if (type == GroupType::Leaf) { Q_ASSERT(m_downLink[gid].size() == 0); if (m_groupIds.count(gid) > 0) { m_groupIds.erase(gid); } } else { m_groupIds[gid] = type; } } diff --git a/src/timeline2/model/moveableItem.hpp b/src/timeline2/model/moveableItem.hpp index 34d70527b..e022bebbb 100644 --- a/src/timeline2/model/moveableItem.hpp +++ b/src/timeline2/model/moveableItem.hpp @@ -1,123 +1,123 @@ /*************************************************************************** * 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 MOVEABLEITEM_H #define MOVEABLEITEM_H #include "timelinemodel.hpp" #include "trackmodel.hpp" #include "undohelper.hpp" #include #include #include /* @brief This is the base class for objects that can move, for example clips and compositions -*/ + */ template class MoveableItem { MoveableItem() = delete; protected: virtual ~MoveableItem() {} public: MoveableItem(std::weak_ptr parent, int id = -1); /* @brief returns (unique) id of current item */ int getId() const; /* @brief returns the length of the item on the timeline */ virtual int getPlaytime() const = 0; /* @brief returns the id of the track in which this items is inserted (-1 if none) */ int getCurrentTrackId() const; /* @brief returns the current position of the item (-1 if not inserted) */ int getPosition() const; /* @brief returns the in and out times of the item */ std::pair getInOut() const; int getIn() const; int getOut() const; friend class TrackModel; friend class TimelineModel; /* Implicit conversion operator to access the underlying producer */ operator Service &() { return *service(); } /* Returns true if the underlying producer is valid */ bool isValid(); /* @brief returns a property of the current item */ virtual const QString getProperty(const QString &name) const = 0; /* @brief true if the item is in current selection and should be moved within a group */ bool isInGroupDrag; protected: /* @brief Returns a pointer to the service. It may be used but do NOT store it*/ virtual Service *service() const = 0; /* @brief Performs a resize of the given item. Returns true if the operation succeeded, and otherwise nothing is modified This method is protected because it shouldn't be called directly. Call the function in the timeline instead. If a snap point is within reach, the operation will be coerced to use it. @param size is the new size of the item @param right is true if we change the right side of the item, false otherwise @param undo Lambda function containing the current undo stack. Will be updated with current operation @param redo Lambda function containing the current redo queue. Will be updated with current operation */ virtual bool requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo = true) = 0; /* Updates the stored position of the item This function is meant to be called by the trackmodel, not directly by the user. If you whish to actually move the item, use the requestMove slot. */ void setPosition(int position); /* Updates the stored track id of the item This function is meant to be called by the timeline, not directly by the user. If you whish to actually change the track the item, use the slot in the timeline slot. */ void setCurrentTrackId(int tid); /* Set in and out of service */ virtual void setInOut(int in, int out); protected: std::weak_ptr m_parent; int m_id; // this is the creation id of the item, used for book-keeping int m_position; int m_currentTrackId; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access }; #include "moveableItem.ipp" #endif diff --git a/src/timeline2/model/timelinefunctions.hpp b/src/timeline2/model/timelinefunctions.hpp index 805fd2042..87065ce63 100644 --- a/src/timeline2/model/timelinefunctions.hpp +++ b/src/timeline2/model/timelinefunctions.hpp @@ -1,92 +1,93 @@ /* Copyright (C) 2017 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 . */ #ifndef TIMELINEFUNCTIONS_H #define TIMELINEFUNCTIONS_H #include "definitions.h" #include "undohelper.hpp" #include #include /** * @namespace TimelineFunction * @brief This namespace contains a list of static methods for advanced timeline editing features * based on timelinemodel methods */ class TimelineItemModel; struct TimelineFunctions { /* @brief Cuts a clip at given position If the clip is part of the group, all clips of the groups are cut at the same position. The group structure is then preserved for clips on both sides Returns true on success @param timeline : ptr to the timeline model @param clipId: Id of the clip to split @param position: position (in frames) where to cut */ static bool requestClipCut(std::shared_ptr timeline, int clipId, int position); /* This is the same function, except that it accumulates undo/redo */ static bool requestClipCut(std::shared_ptr timeline, int clipId, int position, Fun &undo, Fun &redo); /* This is the same function, except that it accumulates undo/redo and do not deal with groups. Do not call directly */ static bool processClipCut(std::shared_ptr timeline, int clipId, int position, int &newId, Fun &undo, Fun &redo); /* @brief Makes a perfect copy of a given clip, but do not insert it */ static bool copyClip(std::shared_ptr timeline, int clipId, int &newId, PlaylistState::ClipState state, Fun &undo, Fun &redo); /* @brief Request the addition of multiple clips to the timeline * If the addition of any of the clips fails, the entire operation is undone. * @returns true on success, false otherwise. * @param binIds the list of bin ids to be inserted * @param trackId the track where the insertion should happen * @param position the position at which the clips should be inserted * @param clipIds a return parameter with the ids assigned to the clips if success, empty otherwise - */ - static bool requestMultipleClipsInsertion(std::shared_ptr timeline, const QStringList &binIds, int trackId, int position, QList &clipIds, bool logUndo, bool refreshView); + */ + static bool requestMultipleClipsInsertion(std::shared_ptr timeline, const QStringList &binIds, int trackId, int position, + QList &clipIds, bool logUndo, bool refreshView); static int requestSpacerStartOperation(std::shared_ptr timeline, int trackId, int position); static bool requestSpacerEndOperation(std::shared_ptr timeline, int clipId, int startPosition, int endPosition); static bool extractZone(std::shared_ptr timeline, QVector tracks, QPoint zone, bool liftOnly); static bool liftZone(std::shared_ptr timeline, int trackId, QPoint zone, Fun &undo, Fun &redo); static bool removeSpace(std::shared_ptr timeline, int trackId, QPoint zone, Fun &undo, Fun &redo); static bool insertSpace(std::shared_ptr timeline, int trackId, QPoint zone, Fun &undo, Fun &redo); static bool insertZone(std::shared_ptr timeline, int trackId, const QString &binId, int insertFrame, QPoint zone, bool overwrite); static bool requestItemCopy(std::shared_ptr timeline, int clipId, int trackId, int position); static void showClipKeyframes(std::shared_ptr timeline, int clipId, bool value); static void showCompositionKeyframes(std::shared_ptr timeline, int compoId, bool value); /* @brief If the clip is activated, disable, otherwise enable * @param timeline: pointer to the timeline that we modify * @param clipId: Id of the clip to modify * @param status: target status of the clip This function creates an undo object and returns true on success */ static bool switchEnableState(std::shared_ptr timeline, int clipId); /* @brief change the clip state and accumulates for undo/redo */ static bool changeClipState(std::shared_ptr timeline, int clipId, PlaylistState::ClipState status, Fun &undo, Fun &redo); static bool requestSplitAudio(std::shared_ptr timeline, int clipId, int audioTarget); static void setCompositionATrack(std::shared_ptr timeline, int cid, int aTrack); }; #endif diff --git a/src/timeline2/model/timelineitemmodel.cpp b/src/timeline2/model/timelineitemmodel.cpp index 73b766392..ce61de492 100644 --- a/src/timeline2/model/timelineitemmodel.cpp +++ b/src/timeline2/model/timelineitemmodel.cpp @@ -1,496 +1,496 @@ /*************************************************************************** * 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 "timelineitemmodel.hpp" #include "assets/keyframes/model/keyframemodel.hpp" #include "bin/model/markerlistmodel.hpp" #include "clipmodel.hpp" #include "compositionmodel.hpp" #include "core.h" #include "doc/docundostack.hpp" #include "groupsmodel.hpp" #include "kdenlivesettings.h" #include "macros.hpp" #include "trackmodel.hpp" #include "transitions/transitionsrepository.hpp" #include #include #include #include #include #include #include TimelineItemModel::TimelineItemModel(Mlt::Profile *profile, std::weak_ptr undo_stack) : TimelineModel(profile, undo_stack) { } void TimelineItemModel::finishConstruct(std::shared_ptr ptr, std::shared_ptr guideModel) { ptr->weak_this_ = ptr; ptr->m_groups = std::unique_ptr(new GroupsModel(ptr)); guideModel->registerSnapModel(ptr->m_snaps); } std::shared_ptr TimelineItemModel::construct(Mlt::Profile *profile, std::shared_ptr guideModel, std::weak_ptr undo_stack) { std::shared_ptr ptr(new TimelineItemModel(profile, std::move(undo_stack))); finishConstruct(ptr, std::move(guideModel)); return ptr; } TimelineItemModel::~TimelineItemModel() = default; QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const { READ_LOCK(); QModelIndex result; if (parent.isValid()) { auto trackId = int(parent.internalId()); Q_ASSERT(isTrack(trackId)); int clipId = getTrackById_const(trackId)->getClipByRow(row); if (clipId != -1) { result = createIndex(row, 0, quintptr(clipId)); } else { int compoId = getTrackById_const(trackId)->getCompositionByRow(row); if (compoId != -1) { result = createIndex(row, 0, quintptr(compoId)); } } } else if (row < getTracksCount() && row >= 0) { auto it = m_allTracks.cbegin(); std::advance(it, row); int trackId = (*it)->getId(); result = createIndex(row, column, quintptr(trackId)); } return result; } /*QModelIndex TimelineItemModel::makeIndex(int trackIndex, int clipIndex) const { return index(clipIndex, 0, index(trackIndex)); }*/ QModelIndex TimelineItemModel::makeClipIndexFromID(int clipId) const { Q_ASSERT(m_allClips.count(clipId) > 0); int trackId = m_allClips.at(clipId)->getCurrentTrackId(); if (trackId == -1) { // Clip is not inserted in a track return QModelIndex(); } int row = getTrackById_const(trackId)->getRowfromClip(clipId); return index(row, 0, makeTrackIndexFromID(trackId)); } QModelIndex TimelineItemModel::makeCompositionIndexFromID(int compoId) const { Q_ASSERT(m_allCompositions.count(compoId) > 0); int trackId = m_allCompositions.at(compoId)->getCurrentTrackId(); return index(getTrackById_const(trackId)->getRowfromComposition(compoId), 0, makeTrackIndexFromID(trackId)); } QModelIndex TimelineItemModel::makeTrackIndexFromID(int trackId) const { // we retrieve iterator Q_ASSERT(m_iteratorTable.count(trackId) > 0); auto it = m_iteratorTable.at(trackId); int ind = (int)std::distance(m_allTracks.begin(), it); return index(ind); } QModelIndex TimelineItemModel::parent(const QModelIndex &index) const { READ_LOCK(); // qDebug() << "TimelineItemModel::parent"<< index; if (index == QModelIndex()) { return index; } const int id = static_cast(index.internalId()); if (!index.isValid() || isTrack(id)) { return QModelIndex(); } if (isClip(id)) { const int trackId = getClipTrackId(id); return makeTrackIndexFromID(trackId); } if (isComposition(id)) { const int trackId = getCompositionTrackId(id); return makeTrackIndexFromID(trackId); } return QModelIndex(); } int TimelineItemModel::rowCount(const QModelIndex &parent) const { READ_LOCK(); if (parent.isValid()) { const int id = (int)parent.internalId(); if (!isTrack(id)) { // clips don't have children // if it is not a track, it is something invalid return 0; } return getTrackClipsCount(id) + getTrackCompositionsCount(id); } return getTracksCount(); } int TimelineItemModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } QHash TimelineItemModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[ResourceRole] = "resource"; roles[ServiceRole] = "mlt_service"; roles[BinIdRole] = "binId"; roles[IsBlankRole] = "blank"; roles[StartRole] = "start"; roles[DurationRole] = "duration"; roles[MarkersRole] = "markers"; roles[KeyframesRole] = "keyframeModel"; roles[ShowKeyframesRole] = "showKeyframes"; roles[StatusRole] = "clipStatus"; roles[InPointRole] = "in"; roles[OutPointRole] = "out"; roles[FramerateRole] = "fps"; roles[GroupedRole] = "grouped"; roles[GroupDragRole] = "groupDrag"; roles[IsMuteRole] = "mute"; roles[IsHiddenRole] = "hidden"; roles[IsAudioRole] = "audio"; roles[AudioLevelsRole] = "audioLevels"; roles[IsCompositeRole] = "composite"; roles[IsLockedRole] = "locked"; roles[FadeInRole] = "fadeIn"; roles[FadeOutRole] = "fadeOut"; roles[IsCompositionRole] = "isComposition"; roles[FileHashRole] = "hash"; roles[SpeedRole] = "speed"; roles[HeightRole] = "trackHeight"; roles[TrackTagRole] = "trackTag"; roles[ItemIdRole] = "item"; roles[ItemATrack] = "a_track"; roles[HasAudio] = "hasAudio"; roles[ReloadThumbRole] = "reloadThumb"; return roles; } QVariant TimelineItemModel::data(const QModelIndex &index, int role) const { READ_LOCK(); if (!m_tractor || !index.isValid()) { // qDebug() << "DATA abort. Index validity="< clip = m_allClips.at(id); // Get data for a clip switch (role) { // TODO case NameRole: case Qt::DisplayRole: { QString result = clip->getProperty("kdenlive:clipname"); if (result.isEmpty()) { result = clip->getProperty("resource"); if (!result.isEmpty()) { result = QFileInfo(result).fileName(); } else { result = clip->getProperty("mlt_service"); } } return result; } case ResourceRole: { QString result = clip->getProperty("resource"); if (result == QLatin1String("")) { result = clip->getProperty("mlt_service"); } return result; } case GroupDragRole: return clip->isInGroupDrag; case BinIdRole: return clip->binId(); case ServiceRole: return clip->getProperty("mlt_service"); break; case AudioLevelsRole: return clip->getAudioWaveform(); case HasAudio: return clip->audioEnabled(); case IsAudioRole: return clip->isAudioOnly(); case MarkersRole: { return QVariant::fromValue(clip->getMarkerModel().get()); } case KeyframesRole: { return QVariant::fromValue(clip->getKeyframeModel()); } case StatusRole: return QVariant::fromValue(clip->clipState()); case StartRole: return clip->getPosition(); case DurationRole: return clip->getPlaytime(); case GroupedRole: return (m_groups->isInGroup(id) && !isInSelection(id)); case InPointRole: return clip->getIn(); case OutPointRole: return clip->getOut(); case IsCompositionRole: return false; case ShowKeyframesRole: return clip->showKeyframes(); case FadeInRole: return clip->fadeIn(); case FadeOutRole: return clip->fadeOut(); case ReloadThumbRole: return clip->forceThumbReload; case SpeedRole: return clip->getSpeed(); default: break; } } else if (isTrack(id)) { // qDebug() << "DATA REQUESTED FOR TRACK "<< id; switch (role) { case NameRole: case Qt::DisplayRole: { QString tName = getTrackById_const(id)->getProperty("kdenlive:track_name").toString(); return tName; } case DurationRole: // qDebug() << "DATA yielding duration" << m_tractor->get_playtime(); return m_tractor->get_playtime(); case IsMuteRole: // qDebug() << "DATA yielding mute" << 0; return getTrackById_const(id)->isMute(); case IsHiddenRole: return getTrackById_const(id)->isHidden(); case IsAudioRole: return getTrackById_const(id)->isAudioTrack(); case TrackTagRole: return getTrackTagById(id); case IsLockedRole: return getTrackById_const(id)->getProperty("kdenlive:locked_track").toInt() == 1; case HeightRole: { int height = getTrackById_const(id)->getProperty("kdenlive:trackheight").toInt(); // qDebug() << "DATA yielding height" << height; return (height > 0 ? height : 60); } case IsCompositeRole: { return Qt::Unchecked; } default: break; } } else if (isComposition(id)) { std::shared_ptr compo = m_allCompositions.at(id); switch (role) { case NameRole: case Qt::DisplayRole: case ResourceRole: case ServiceRole: return compo->displayName(); break; case IsBlankRole: // probably useless return false; case StartRole: return compo->getPosition(); case DurationRole: return compo->getPlaytime(); case GroupedRole: return m_groups->isInGroup(id); case GroupDragRole: return compo->isInGroupDrag; case InPointRole: return 0; case OutPointRole: return 100; case BinIdRole: return 5; case KeyframesRole: { return QVariant::fromValue(compo->getEffectKeyframeModel()); } case ShowKeyframesRole: return compo->showKeyframes(); case ItemATrack: return compo->getForcedTrack(); case MarkersRole: { QVariantList markersList; return markersList; } case IsCompositionRole: return true; default: break; } } else { qDebug() << "UNKNOWN DATA requested " << index << roleNames()[role]; } return QVariant(); } void TimelineItemModel::setTrackProperty(int trackId, const QString &name, const QString &value) { getTrackById(trackId)->setProperty(name, value); QVector roles; if (name == QLatin1String("kdenlive:track_name")) { roles.push_back(NameRole); } else if (name == QLatin1String("kdenlive:locked_track")) { roles.push_back(IsLockedRole); } else if (name == QLatin1String("hide")) { roles.push_back(IsMuteRole); roles.push_back(IsHiddenRole); } if (!roles.isEmpty()) { QModelIndex ix = makeTrackIndexFromID(trackId); emit dataChanged(ix, ix, roles); } } QVariant TimelineItemModel::getTrackProperty(int tid, const QString &name) { return getTrackById(tid)->getProperty(name); } void TimelineItemModel::buildTrackCompositing() { auto it = m_allTracks.cbegin(); QScopedPointer field(m_tractor->field()); field->lock(); QString composite = TransitionsRepository::get()->getCompositingTransition(); while (it != m_allTracks.cend()) { int trackId = getTrackMltIndex((*it)->getId()); if (!composite.isEmpty() && (*it)->getProperty("kdenlive:audio_track").toInt() != 1) { // video track, add composition Mlt::Transition *transition = TransitionsRepository::get()->getTransition(composite); transition->set("internal_added", 237); transition->set("always_active", 1); field->plant_transition(*transition, 0, trackId); transition->set_tracks(0, trackId); } // audio mix Mlt::Transition *transition = TransitionsRepository::get()->getTransition(QStringLiteral("mix")); transition->set("internal_added", 237); transition->set("always_active", 1); transition->set("sum", 1); field->plant_transition(*transition, 0, trackId); transition->set_tracks(0, trackId); ++it; } field->unlock(); if (composite.isEmpty()) { pCore->displayMessage(i18n("Could not setup track compositing, check your install"), MessageType::ErrorMessage); } } const QString TimelineItemModel::groupsData() { return m_groups->toJson(); } bool TimelineItemModel::loadGroups(const QString &groupsData) { return m_groups->fromJson(groupsData); } bool TimelineItemModel::isInSelection(int cid) const { if (m_temporarySelectionGroup == -1 || !m_groups->isInGroup(cid)) { return false; } bool res = (m_groups->getRootId(cid) == m_temporarySelectionGroup); return res; } void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb) { QVector roles; if (start) { roles.push_back(TimelineModel::StartRole); if (updateThumb) { roles.push_back(TimelineModel::InPointRole); } } if (duration) { roles.push_back(TimelineModel::DurationRole); if (updateThumb) { roles.push_back(TimelineModel::OutPointRole); } } emit dataChanged(topleft, bottomright, roles); } void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector &roles) { emit dataChanged(topleft, bottomright, roles); } void TimelineItemModel::_beginRemoveRows(const QModelIndex &i, int j, int k) { // qDebug()<<"FORWARDING beginRemoveRows"< -TrackDialog::TrackDialog(std::shared_ptr model, int trackIndex, QWidget *parent, bool deleteMode) : - QDialog(parent) +TrackDialog::TrackDialog(std::shared_ptr model, int trackIndex, QWidget *parent, bool deleteMode) + : QDialog(parent) , m_audioCount(1) , m_videoCount(1) { - //setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); + // setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QIcon videoIcon = QIcon::fromTheme(QStringLiteral("kdenlive-show-video")); QIcon audioIcon = QIcon::fromTheme(QStringLiteral("kdenlive-show-audio")); setupUi(this); QStringList existingTrackNames; for (int i = model->getTracksCount() - 1; i >= 0; i--) { int tid = model->getTrackIndexFromPosition(i); bool audioTrack = model->getTrackProperty(tid, QStringLiteral("kdenlive:audio_track")) == QLatin1String("1"); if (audioTrack) { m_audioCount++; } else { m_videoCount++; } const QString trackName = model->getTrackProperty(tid, QStringLiteral("kdenlive:track_name")).toString(); existingTrackNames << trackName; // Track index in in MLT, so add + 1 to compensate black track - comboTracks->addItem(audioTrack ? audioIcon : videoIcon, - trackName.isEmpty() ? QString::number(i) : trackName, i + 1); + comboTracks->addItem(audioTrack ? audioIcon : videoIcon, trackName.isEmpty() ? QString::number(i) : trackName, i + 1); } if (trackIndex > -1) { int ix = comboTracks->findData(trackIndex); comboTracks->setCurrentIndex(ix); } trackIndex--; if (deleteMode) { track_name->setVisible(false); video_track->setVisible(false); audio_track->setVisible(false); name_label->setVisible(false); before_select->setVisible(false); label->setText(i18n("Delete Track")); } else { QString proposedName = i18n("Video %1", trackIndex); while (existingTrackNames.contains(proposedName)) { proposedName = i18n("Video %1", ++trackIndex); } track_name->setText(proposedName); } connect(audio_track, &QRadioButton::toggled, this, &TrackDialog::updateName); } void TrackDialog::updateName(bool audioTrack) { QString proposedName = i18n(audioTrack ? "Audio %1" : "Video %1", audioTrack ? m_audioCount : m_videoCount); track_name->setText(proposedName); - } - int TrackDialog::selectedTrack() const { if (comboTracks->count() > 0) { int ix = comboTracks->currentData().toInt(); if (before_select->currentIndex() == 1) { ix--; } return ix; } return -1; } bool TrackDialog::addAudioTrack() const { return !video_track->isChecked(); } const QString TrackDialog::trackName() const { return track_name->text(); } diff --git a/src/timeline2/view/dialogs/trackdialog.h b/src/timeline2/view/dialogs/trackdialog.h index e37071a7c..858c40a3c 100644 --- a/src/timeline2/view/dialogs/trackdialog.h +++ b/src/timeline2/view/dialogs/trackdialog.h @@ -1,50 +1,50 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@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) 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 TRACKDIALOG2_H #define TRACKDIALOG2_H #include "timeline2/model/timelineitemmodel.hpp" #include "ui_addtrack_ui.h" class TrackDialog : public QDialog, public Ui::AddTrack_UI { Q_OBJECT public: explicit TrackDialog(std::shared_ptr model, int trackIndex = -1, QWidget *parent = nullptr, bool deleteMode = false); /** @brief: returns the selected track's trackId */ int selectedTrack() const; /** @brief: returns true if we want to insert an audio track */ bool addAudioTrack() const; /** @brief: returns the newly created track name - */ + */ const QString trackName() const; - + private slots: void updateName(bool audioTrack); - + private: int m_audioCount; int m_videoCount; }; #endif diff --git a/src/timeline2/view/dialogs/tracksconfigdialog.h b/src/timeline2/view/dialogs/tracksconfigdialog.h index 861cb0a67..b495e3045 100644 --- a/src/timeline2/view/dialogs/tracksconfigdialog.h +++ b/src/timeline2/view/dialogs/tracksconfigdialog.h @@ -1,81 +1,81 @@ /*************************************************************************** * 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 TRACKSCONFIGDIALOG_H #define TRACKSCONFIGDIALOG_H #include "ui_tracksconfigdialog_ui.h" #include class TracksDelegate : public QItemDelegate { Q_OBJECT public: explicit TracksDelegate(QObject *parent = nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget *editor, const QModelIndex &index) const override; void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; private slots: void emitCommitData(); }; class TrackInfo; class Timeline; class QTableWidgetItem; /** * @class TracksConfigDialog * @brief A dialog to change the name, type, ... of tracks. * @author Till Theato */ class TracksConfigDialog : public QDialog, public Ui::TracksConfigDialog_UI { Q_OBJECT public: /** @brief Sets up the table. - * @param doc the kdenlive document whose tracks to use - * @param selected the track which should be selected by default - * @param parent the parent widget */ + * @param doc the kdenlive document whose tracks to use + * @param selected the track which should be selected by default + * @param parent the parent widget */ explicit TracksConfigDialog(Timeline *timeline, int selected = -1, QWidget *parent = nullptr); /** @brief Returns the new list of tracks created from the table. */ const QList tracksList(); /** @brief A list of tracks, which sould be deleted. */ QList deletedTracks() const; private slots: /** @brief Updates the "hidden" checkbox if type was changed. */ void slotUpdateRow(QTableWidgetItem *item); private slots: /** @brief Recreates the table from the list of tracks in m_doc. */ void setupOriginal(int selected = -1); /** @brief Marks a track to be deleted. */ void slotDelete(); private: Timeline *m_timeline; QList m_deletedRows; }; #endif diff --git a/src/timeline2/view/previewmanager.cpp b/src/timeline2/view/previewmanager.cpp index f8f08acaf..ad5433bea 100644 --- a/src/timeline2/view/previewmanager.cpp +++ b/src/timeline2/view/previewmanager.cpp @@ -1,698 +1,698 @@ /*************************************************************************** * Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@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) 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 "previewmanager.h" #include "core.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include "timeline2/view/timelinecontroller.h" #include #include #include #include PreviewManager::PreviewManager(TimelineController *controller, Mlt::Tractor *tractor) : QObject() , workingPreview(-1) , m_controller(controller) , m_tractor(tractor) , m_previewTrack(nullptr) , m_overlayTrack(nullptr) , m_previewTrackIndex(-1) , m_initialized(false) , m_abortPreview(false) { m_previewGatherTimer.setSingleShot(true); m_previewGatherTimer.setInterval(200); } PreviewManager::~PreviewManager() { if (m_initialized) { abortRendering(); if (m_undoDir.dirName() == QLatin1String("undo")) { m_undoDir.removeRecursively(); } if ((pCore->currentDoc()->url().isEmpty() && m_cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty()) || m_cacheDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()) { if (m_cacheDir.dirName() == QLatin1String("preview")) { m_cacheDir.removeRecursively(); } } } delete m_previewTrack; } bool PreviewManager::initialize() { // Make sure our document id does not contain .. tricks bool ok; KdenliveDoc *doc = pCore->currentDoc(); QString documentId = QDir::cleanPath(doc->getDocumentProperty(QStringLiteral("documentid"))); documentId.toLongLong(&ok, 10); if (!ok || documentId.isEmpty()) { // Something is wrong, documentId should be a number (ms since epoch), abort pCore->displayMessage(i18n("Wrong document ID, cannot create temporary folder"), ErrorMessage); return false; } m_cacheDir = doc->getCacheDir(CachePreview, &ok); if (!m_cacheDir.exists() || !ok) { pCore->displayMessage(i18n("Cannot create folder %1", m_cacheDir.absolutePath()), ErrorMessage); return false; } if (m_cacheDir.dirName() != QLatin1String("preview") || m_cacheDir == QDir() || (!m_cacheDir.exists(QStringLiteral("undo")) && !m_cacheDir.mkdir(QStringLiteral("undo"))) || !m_cacheDir.absolutePath().contains(documentId)) { pCore->displayMessage(i18n("Something is wrong with cache folder %1", m_cacheDir.absolutePath()), ErrorMessage); return false; } if (!loadParams()) { pCore->displayMessage(i18n("Invalid timeline preview parameters"), ErrorMessage); return false; } m_undoDir = QDir(m_cacheDir.absoluteFilePath(QStringLiteral("undo"))); // Make sure our cache dirs are inside the temporary folder if (!m_cacheDir.makeAbsolute() || !m_undoDir.makeAbsolute() || !m_undoDir.mkpath(QStringLiteral("."))) { pCore->displayMessage(i18n("Something is wrong with cache folders"), ErrorMessage); return false; } connect(this, &PreviewManager::cleanupOldPreviews, this, &PreviewManager::doCleanupOldPreviews); connect(doc, &KdenliveDoc::removeInvalidUndo, this, &PreviewManager::slotRemoveInvalidUndo, Qt::DirectConnection); m_previewTimer.setSingleShot(true); m_previewTimer.setInterval(3000); connect(&m_previewTimer, &QTimer::timeout, this, &PreviewManager::startPreviewRender); connect(this, &PreviewManager::previewRender, this, &PreviewManager::gotPreviewRender); connect(&m_previewGatherTimer, &QTimer::timeout, this, &PreviewManager::slotProcessDirtyChunks); m_initialized = true; return true; } bool PreviewManager::buildPreviewTrack() { if (m_previewTrack != nullptr) { return false; } // Create overlay track qDebug() << "/// BUILDING PREVIEW TRACK\n----------------------\n----------------__"; m_previewTrack = new Mlt::Playlist(*m_tractor->profile()); m_tractor->lock(); reconnectTrack(); m_tractor->unlock(); return true; } void PreviewManager::loadChunks(QVariantList previewChunks, QVariantList dirtyChunks, const QDateTime &documentDate) { if (previewChunks.isEmpty()) { previewChunks = m_renderedChunks; } if (dirtyChunks.isEmpty()) { dirtyChunks = m_dirtyChunks; } for (const auto &frame : previewChunks) { const QString fileName = m_cacheDir.absoluteFilePath(QStringLiteral("%1.%2").arg(frame.toInt()).arg(m_extension)); QFile file(fileName); if (file.exists()) { if (!documentDate.isNull() && QFileInfo(file).lastModified() > documentDate) { // Timeline preview file was created after document, invalidate file.remove(); dirtyChunks << frame; } else { gotPreviewRender(frame.toInt(), fileName, 1000); } } else { dirtyChunks << frame; } } if (!previewChunks.isEmpty()) { m_controller->renderedChunksChanged(); } if (!dirtyChunks.isEmpty()) { for (const auto &i : dirtyChunks) { if (!m_dirtyChunks.contains(i)) { m_dirtyChunks << i; } } m_controller->dirtyChunksChanged(); } } void PreviewManager::deletePreviewTrack() { m_tractor->lock(); disconnectTrack(); delete m_previewTrack; m_previewTrack = nullptr; m_dirtyChunks.clear(); m_renderedChunks.clear(); m_controller->dirtyChunksChanged(); m_controller->renderedChunksChanged(); m_tractor->unlock(); } const QDir PreviewManager::getCacheDir() const { return m_cacheDir; } void PreviewManager::reconnectTrack() { disconnectTrack(); if (!m_previewTrack && !m_overlayTrack) { m_previewTrackIndex = -1; return; } m_previewTrackIndex = m_tractor->count(); int increment = 0; if (m_previewTrack) { m_tractor->insert_track(*m_previewTrack, m_previewTrackIndex); std::shared_ptr tk(m_tractor->track(m_previewTrackIndex)); tk->set("hide", 2); tk->set("id", "timeline_preview"); increment++; } if (m_overlayTrack) { m_tractor->insert_track(*m_overlayTrack, m_previewTrackIndex + increment); std::shared_ptr tk(m_tractor->track(m_previewTrackIndex + increment)); tk->set("hide", 2); tk->set("id", "timeline_overlay"); } } void PreviewManager::disconnectTrack() { if (m_previewTrackIndex > -1) { Mlt::Producer *prod = m_tractor->track(m_previewTrackIndex); if (strcmp(prod->get("id"), "timeline_preview") == 0 || strcmp(prod->get("id"), "timeline_overlay") == 0) { m_tractor->remove_track(m_previewTrackIndex); } delete prod; if (m_tractor->count() == m_previewTrackIndex + 1) { // overlay track still here, remove Mlt::Producer *trkprod = m_tractor->track(m_previewTrackIndex); if (strcmp(trkprod->get("id"), "timeline_overlay") == 0) { m_tractor->remove_track(m_previewTrackIndex); } delete trkprod; } } m_previewTrackIndex = -1; } bool PreviewManager::loadParams() { KdenliveDoc *doc = pCore->currentDoc(); m_extension = doc->getDocumentProperty(QStringLiteral("previewextension")); m_consumerParams = doc->getDocumentProperty(QStringLiteral("previewparameters")).split(QLatin1Char(' '), QString::SkipEmptyParts); if (m_consumerParams.isEmpty() || m_extension.isEmpty()) { doc->selectPreviewProfile(); m_consumerParams = doc->getDocumentProperty(QStringLiteral("previewparameters")).split(QLatin1Char(' '), QString::SkipEmptyParts); m_extension = doc->getDocumentProperty(QStringLiteral("previewextension")); } if (m_consumerParams.isEmpty() || m_extension.isEmpty()) { return false; } // Remove the r= and s= parameter (forcing framerate / frame size) as it causes rendering failure. // These parameters should be provided by MLT's profile for (int i = 0; i < m_consumerParams.count(); i++) { if (m_consumerParams.at(i).startsWith(QStringLiteral("r=")) || m_consumerParams.at(i).startsWith(QStringLiteral("s="))) { m_consumerParams.removeAt(i); i--; } } if (doc->getDocumentProperty(QStringLiteral("resizepreview")).toInt() != 0) { int resizeWidth = doc->getDocumentProperty(QStringLiteral("previewheight")).toInt(); - m_consumerParams << QStringLiteral("s=%1x%2").arg((int) (resizeWidth * pCore->getCurrentDar())).arg(resizeWidth); + m_consumerParams << QStringLiteral("s=%1x%2").arg((int)(resizeWidth * pCore->getCurrentDar())).arg(resizeWidth); } m_consumerParams << QStringLiteral("an=1"); if (KdenliveSettings::gpu_accel()) { m_consumerParams << QStringLiteral("glsl.=1"); } return true; } void PreviewManager::invalidatePreviews(const QVariantList chunks) { QMutexLocker lock(&m_previewMutex); bool timer = false; if (m_previewTimer.isActive()) { m_previewTimer.stop(); timer = true; } KdenliveDoc *doc = pCore->currentDoc(); int stackIx = doc->commandStack()->index(); int stackMax = doc->commandStack()->count(); if (stackIx == stackMax && !m_undoDir.exists(QString::number(stackIx - 1))) { // We just added a new command in stack, archive existing chunks int ix = stackIx - 1; m_undoDir.mkdir(QString::number(ix)); bool foundPreviews = false; for (const auto &i : chunks) { QString current = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); if (m_cacheDir.rename(current, QStringLiteral("undo/%1/%2").arg(ix).arg(current))) { foundPreviews = true; } } if (!foundPreviews) { // No preview files found, remove undo folder m_undoDir.rmdir(QString::number(ix)); } else { // new chunks archived, cleanup old ones emit cleanupOldPreviews(); } } else { // Restore existing chunks, delete others // Check if we just undo the last stack action, then backup, otherwise delete bool lastUndo = false; if (stackIx == stackMax - 1) { if (!m_undoDir.exists(QString::number(stackMax))) { lastUndo = true; bool foundPreviews = false; m_undoDir.mkdir(QString::number(stackMax)); for (const auto &i : chunks) { QString current = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); if (m_cacheDir.rename(current, QStringLiteral("undo/%1/%2").arg(stackMax).arg(current))) { foundPreviews = true; } } if (!foundPreviews) { m_undoDir.rmdir(QString::number(stackMax)); } } } bool moveFile = true; QDir tmpDir = m_undoDir; if (!tmpDir.cd(QString::number(stackIx))) { moveFile = false; } QVariantList foundChunks; for (const auto &i : chunks) { QString cacheFileName = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); if (!lastUndo) { m_cacheDir.remove(cacheFileName); } if (moveFile) { if (QFile::copy(tmpDir.absoluteFilePath(cacheFileName), m_cacheDir.absoluteFilePath(cacheFileName))) { foundChunks << i; m_dirtyChunks.removeAll(i); m_renderedChunks << i; } else { qDebug() << "// ERROR PROCESSE CHUNK: " << i << ", " << cacheFileName; } } } if (!foundChunks.isEmpty()) { qSort(foundChunks); m_controller->dirtyChunksChanged(); m_controller->renderedChunksChanged(); reloadChunks(foundChunks); } } doc->setModified(true); if (timer) { m_previewTimer.start(); } } void PreviewManager::doCleanupOldPreviews() { if (m_undoDir.dirName() != QLatin1String("undo")) { return; } QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); // Use QCollator to do a natural sorting so that 10 is after 2 QCollator collator; collator.setNumericMode(true); std::sort(dirs.begin(), dirs.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(file1, file2) < 0; }); bool ok; while (dirs.count() > 5) { QDir tmp = m_undoDir; QString dirName = dirs.takeFirst(); dirName.toInt(&ok); if (ok && tmp.cd(dirName)) { tmp.removeRecursively(); } } } void PreviewManager::clearPreviewRange() { m_previewGatherTimer.stop(); abortPreview(); m_tractor->lock(); bool hasPreview = m_previewTrack != nullptr; for (const auto &ix : m_renderedChunks) { m_cacheDir.remove(QStringLiteral("%1.%2").arg(ix.toInt()).arg(m_extension)); if (!hasPreview) { continue; } int trackIx = m_previewTrack->get_clip_index_at(ix.toInt()); if (!m_previewTrack->is_blank(trackIx)) { Mlt::Producer *prod = m_previewTrack->replace_with_blank(trackIx); delete prod; } } if (hasPreview) { m_previewTrack->consolidate_blanks(); } m_tractor->unlock(); m_renderedChunks.clear(); m_controller->renderedChunksChanged(); } void PreviewManager::addPreviewRange(const QPoint zone, bool add) { int chunkSize = KdenliveSettings::timelinechunks(); int startChunk = zone.x() / chunkSize; int endChunk = rintl(zone.y() / chunkSize); QList toRemove; qDebug() << " // / RESUQEST CHUNKS; " << startChunk << " = " << endChunk; for (int i = startChunk; i <= endChunk; i++) { int frame = i * chunkSize; if (add) { if (!m_renderedChunks.contains(frame)) { m_dirtyChunks << frame; } } else { if (m_renderedChunks.contains(frame)) { toRemove << frame; } else { m_dirtyChunks.removeAll(frame); } } } if (add) { qDebug() << "CHUNKS CHANGED: " << m_dirtyChunks; m_controller->dirtyChunksChanged(); if (!m_previewThread.isRunning() && KdenliveSettings::autopreview()) { m_previewTimer.start(); } } else { // Remove processed chunks bool isRendering = m_previewThread.isRunning(); m_previewGatherTimer.stop(); abortPreview(); m_tractor->lock(); bool hasPreview = m_previewTrack != nullptr; for (int ix : toRemove) { m_cacheDir.remove(QStringLiteral("%1.%2").arg(ix).arg(m_extension)); if (!hasPreview) { continue; } int trackIx = m_previewTrack->get_clip_index_at(ix); if (!m_previewTrack->is_blank(trackIx)) { Mlt::Producer *prod = m_previewTrack->replace_with_blank(trackIx); delete prod; } } if (hasPreview) { m_previewTrack->consolidate_blanks(); } m_tractor->unlock(); if (isRendering || KdenliveSettings::autopreview()) { m_previewTimer.start(); } } } void PreviewManager::abortRendering() { if (!m_previewThread.isRunning()) { return; } m_abortPreview = true; emit abortPreview(); m_previewThread.waitForFinished(); // Re-init time estimation emit previewRender(0, QString(), 0); } void PreviewManager::startPreviewRender() { if (m_renderedChunks.isEmpty() && m_dirtyChunks.isEmpty()) { m_controller->addPreviewRange(true); } if (!m_dirtyChunks.isEmpty()) { // Abort any rendering abortRendering(); m_waitingThumbs.clear(); const QString sceneList = QStringLiteral("xml:") + m_cacheDir.absoluteFilePath(QStringLiteral("preview.mlt")); pCore->getMonitor(Kdenlive::ProjectMonitor)->sceneList(m_cacheDir.absolutePath(), sceneList); // pCore->currentDoc()->saveMltPlaylist(sceneList); m_previewThread = QtConcurrent::run(this, &PreviewManager::doPreviewRender, sceneList); } } void PreviewManager::doPreviewRender(const QString &scene) { int progress; int chunkSize = KdenliveSettings::timelinechunks(); // initialize progress bar emit previewRender(0, QString(), 0); int ct = 0; qSort(m_dirtyChunks); while (!m_dirtyChunks.isEmpty()) { workingPreview = m_dirtyChunks.takeFirst().toInt(); m_controller->workingPreviewChanged(); ct++; QString fileName = QStringLiteral("%1.%2").arg(workingPreview).arg(m_extension); if (m_dirtyChunks.isEmpty()) { progress = 1000; } else { progress = (double)(ct) / (ct + m_dirtyChunks.count()) * 1000; } if (m_cacheDir.exists(fileName)) { // This chunk already exists emit previewRender(workingPreview, m_cacheDir.absoluteFilePath(fileName), progress); continue; } // Build rendering process QStringList args; args << scene; args << QStringLiteral("in=") + QString::number(workingPreview); args << QStringLiteral("out=") + QString::number(workingPreview + chunkSize - 1); args << QStringLiteral("-consumer") << QStringLiteral("avformat:") + m_cacheDir.absoluteFilePath(fileName); args << m_consumerParams; QProcess previewProcess; connect(this, &PreviewManager::abortPreview, &previewProcess, &QProcess::kill, Qt::DirectConnection); previewProcess.start(KdenliveSettings::rendererpath(), args); if (previewProcess.waitForStarted()) { previewProcess.waitForFinished(-1); if (previewProcess.exitStatus() != QProcess::NormalExit || previewProcess.exitCode() != 0) { // Something went wrong if (m_abortPreview) { emit previewRender(0, QString(), 1000); } else { emit previewRender(workingPreview, previewProcess.readAllStandardError(), -1); } // working chunk failed, re-add it to list m_dirtyChunks << workingPreview; qSort(m_dirtyChunks); QFile::remove(m_cacheDir.absoluteFilePath(fileName)); break; } else { emit previewRender(workingPreview, m_cacheDir.absoluteFilePath(fileName), progress); } } else { emit previewRender(workingPreview, QString(), -1); break; } } // QFile::remove(scene); workingPreview = -1; m_controller->workingPreviewChanged(); m_abortPreview = false; } void PreviewManager::slotProcessDirtyChunks() { if (m_dirtyChunks.isEmpty()) { return; } invalidatePreviews(m_dirtyChunks); if (KdenliveSettings::autopreview()) { m_previewTimer.start(); } } void PreviewManager::slotRemoveInvalidUndo(int ix) { QMutexLocker lock(&m_previewMutex); if (m_undoDir.dirName() != QLatin1String("undo")) { // Make sure we delete correct folder return; } QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); bool ok; for (const QString &dir : dirs) { if (dir.toInt(&ok) >= ix && ok) { QDir tmp = m_undoDir; if (tmp.cd(dir)) { tmp.removeRecursively(); } } } } void PreviewManager::invalidatePreview(int startFrame, int endFrame) { int chunkSize = KdenliveSettings::timelinechunks(); int start = startFrame - startFrame % chunkSize; int end = endFrame - endFrame % chunkSize + chunkSize; qSort(m_renderedChunks); m_previewGatherTimer.stop(); abortPreview(); m_tractor->lock(); bool hasPreview = m_previewTrack != nullptr; bool chunksChanged = false; for (int i = start; i <= end; i += chunkSize) { if (m_renderedChunks.contains(i) && hasPreview) { int ix = m_previewTrack->get_clip_index_at(i); if (m_previewTrack->is_blank(ix)) { continue; } Mlt::Producer *prod = m_previewTrack->replace_with_blank(ix); delete prod; m_renderedChunks.removeAll(QVariant(i)); m_dirtyChunks << QVariant(i); chunksChanged = true; } } if (chunksChanged) { m_previewTrack->consolidate_blanks(); m_controller->renderedChunksChanged(); m_controller->dirtyChunksChanged(); } m_tractor->unlock(); m_previewGatherTimer.start(); } void PreviewManager::reloadChunks(const QVariantList chunks) { if (m_previewTrack == nullptr || chunks.isEmpty()) { return; } m_tractor->lock(); for (const auto &ix : chunks) { if (m_previewTrack->is_blank_at(ix.toInt())) { const QString fileName = m_cacheDir.absoluteFilePath(QStringLiteral("%1.%2").arg(ix.toInt()).arg(m_extension)); Mlt::Producer prod(*m_tractor->profile(), nullptr, fileName.toUtf8().constData()); if (prod.is_valid()) { // m_ruler->updatePreview(ix, true); prod.set("mlt_service", "avformat-novalidate"); m_previewTrack->insert_at(ix.toInt(), &prod, 1); } } } m_previewTrack->consolidate_blanks(); m_tractor->unlock(); } void PreviewManager::gotPreviewRender(int frame, const QString &file, int progress) { if (m_previewTrack == nullptr) { return; } if (file.isEmpty() || progress < 0) { pCore->currentDoc()->previewProgress(progress); if (progress < 0) { pCore->displayMessage(i18n("Preview rendering failed, check your parameters. %1Show details...%2", QString("")), QStringLiteral("")), MltError); } return; } m_tractor->lock(); if (m_previewTrack->is_blank_at(frame)) { Mlt::Producer prod(*m_tractor->profile(), file.toUtf8().constData()); if (prod.is_valid()) { m_renderedChunks << frame; m_controller->renderedChunksChanged(); // m_ruler->updatePreview(frame, true, true); prod.set("mlt_service", "avformat-novalidate"); m_previewTrack->insert_at(frame, &prod, 1); } else { qCDebug(KDENLIVE_LOG) << "* * * INVALID PROD: " << file; } } else { qCDebug(KDENLIVE_LOG) << "* * * NON EMPTY PROD: " << frame; } m_previewTrack->consolidate_blanks(); m_tractor->unlock(); pCore->currentDoc()->previewProgress(progress); pCore->currentDoc()->setModified(true); } int PreviewManager::setOverlayTrack(Mlt::Playlist *overlay) { m_overlayTrack = overlay; reconnectTrack(); return m_previewTrackIndex; } void PreviewManager::removeOverlayTrack() { delete m_overlayTrack; m_overlayTrack = nullptr; reconnectTrack(); } QPair PreviewManager::previewChunks() const { QStringList renderedChunks; QStringList dirtyChunks; for (const QVariant &frame : m_renderedChunks) { renderedChunks << frame.toString(); } for (const QVariant &frame : m_dirtyChunks) { dirtyChunks << frame.toString(); } return {renderedChunks, dirtyChunks}; } bool PreviewManager::hasOverlayTrack() const { return m_overlayTrack != nullptr; } bool PreviewManager::hasPreviewTrack() const { return m_previewTrack != nullptr; } int PreviewManager::addedTracks() const { if (m_previewTrack) { if (m_overlayTrack) { return 2; } return 1; } else if (m_overlayTrack) { return 1; } return -1; } diff --git a/src/timeline2/view/previewmanager.h b/src/timeline2/view/previewmanager.h index 515666a6e..a81389d3c 100644 --- a/src/timeline2/view/previewmanager.h +++ b/src/timeline2/view/previewmanager.h @@ -1,143 +1,143 @@ /*************************************************************************** * Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@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) 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 PREVIEWMANAGER_H #define PREVIEWMANAGER_H #include "definitions.h" #include #include #include #include class TimelineController; namespace Mlt { class Tractor; class Playlist; -} +} // namespace Mlt /** * @namespace PreviewManager * @brief Handles timeline preview. * This manager creates an additional video track on top of the current timeline and renders * chunks (small video files of 25 frames) that are added on this track when rendrerd. * This allow us to get a preview with a smooth playback of our project. * Only the preview zone is rendered. Once defined, a preview zone shows as a red line below * the timeline ruler. As chunks are rendered, the zone turns to green. */ class PreviewManager : public QObject { Q_OBJECT public: friend class TimelineController; explicit PreviewManager(TimelineController *controller, Mlt::Tractor *tractor); virtual ~PreviewManager(); /** @brief: initialize base variables, return false if error. */ bool initialize(); /** @brief: a timeline operation caused changes to frames between startFrame and endFrame. */ void invalidatePreview(int startFrame, int endFrame); /** @brief: after a small delay (some operations trigger several invalidatePreview calls), take care of these invalidated chunks. */ void invalidatePreviews(const QVariantList chunks); /** @brief: user adds current timeline zone to the preview zone. */ void addPreviewRange(const QPoint zone, bool add); /** @brief: Remove all existing previews. */ void clearPreviewRange(); /** @brief: stops current rendering process. */ void abortRendering(); /** @brief: rendering parameters have changed, reload them. */ bool loadParams(); /** @brief: Create the preview track if not existing. */ bool buildPreviewTrack(); /** @brief: Delete the preview track. */ void deletePreviewTrack(); /** @brief: Whenever we save or render our project, we remove the preview track so it is not saved. */ void reconnectTrack(); /** @brief: After project save or render, re-add our preview track. */ void disconnectTrack(); /** @brief: Returns directory currently used to store the preview files. */ const QDir getCacheDir() const; /** @brief: Load existing ruler chunks. */ void loadChunks(QVariantList previewChunks, QVariantList dirtyChunks, const QDateTime &documentDate); int setOverlayTrack(Mlt::Playlist *overlay); /** @brief Remove the effect compare overlay track */ void removeOverlayTrack(); /** @brief The current preview chunk being processed, -1 if none */ int workingPreview; /** @brief Returns the list of existing chunks */ QPair previewChunks() const; bool hasOverlayTrack() const; bool hasPreviewTrack() const; int addedTracks() const; private: TimelineController *m_controller; Mlt::Tractor *m_tractor; Mlt::Playlist *m_previewTrack; Mlt::Playlist *m_overlayTrack; int m_previewTrackIndex; /** @brief: The directory used to store the preview files. */ QDir m_cacheDir; /** @brief: The directory used to store undo history of preview files (child of m_cacheDir). */ QDir m_undoDir; QMutex m_previewMutex; QStringList m_consumerParams; QString m_extension; /** @brief: Timer used to autostart preview rendering. */ QTimer m_previewTimer; /** @brief: Since some timeline operations generate several invalidate calls, use a timer to get them all. */ QTimer m_previewGatherTimer; bool m_initialized; bool m_abortPreview; QList m_waitingThumbs; QFuture m_previewThread; /** @brief: After an undo/redo, if we have preview history, use it. */ void reloadChunks(const QVariantList chunks); private slots: /** @brief: To avoid filling the hard drive, remove preview undo history after 5 steps. */ void doCleanupOldPreviews(); /** @brief: Start the real rendering process. */ void doPreviewRender(const QString &scene); /** @brief: If user does an undo, then makes a new timeline operation, delete undo history of more recent stack . */ void slotRemoveInvalidUndo(int ix); /** @brief: When the timer collecting invalid zones is done, process. */ void slotProcessDirtyChunks(); public slots: /** @brief: Prepare and start rendering. */ void startPreviewRender(); /** @brief: A chunk has been created, notify ruler. */ void gotPreviewRender(int frame, const QString &file, int progress); protected: QVariantList m_renderedChunks; QVariantList m_dirtyChunks; signals: void abortPreview(); void cleanupOldPreviews(); void previewRender(int frame, const QString &file, int progress); }; #endif diff --git a/src/timeline2/view/qml/timelineitems.cpp b/src/timeline2/view/qml/timelineitems.cpp index f1c712a27..3073bedef 100644 --- a/src/timeline2/view/qml/timelineitems.cpp +++ b/src/timeline2/view/qml/timelineitems.cpp @@ -1,167 +1,168 @@ #include "kdenlivesettings.h" #include #include #include #include #include class TimelineTriangle : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(QColor fillColor MEMBER m_color) public: TimelineTriangle() { setAntialiasing(true); } void paint(QPainter *painter) override { QPainterPath path; path.moveTo(0, 0); path.lineTo(width(), 0); path.lineTo(0, height()); painter->fillPath(path, m_color); painter->setPen(Qt::white); painter->drawLine(width(), 0, 0, height()); } + private: QColor m_color; }; class TimelinePlayhead : public QQuickPaintedItem { void paint(QPainter *painter) override { QPainterPath path; path.moveTo(width(), 0); path.lineTo(width() / 2.0, height()); path.lineTo(0, 0); QPalette p; painter->fillPath(path, p.color(QPalette::WindowText)); } }; class TimelineWaveform : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(QVariant levels MEMBER m_audioLevels NOTIFY propertyChanged) Q_PROPERTY(QColor fillColor MEMBER m_color NOTIFY propertyChanged) Q_PROPERTY(int inPoint MEMBER m_inPoint NOTIFY inPointChanged) Q_PROPERTY(int outPoint MEMBER m_outPoint NOTIFY outPointChanged) Q_PROPERTY(bool format MEMBER m_format NOTIFY propertyChanged) Q_PROPERTY(bool showItem MEMBER m_showItem) public: TimelineWaveform() { setAntialiasing(false); setClip(true); setEnabled(false); setRenderTarget(QQuickPaintedItem::FramebufferObject); setMipmap(true); - setTextureSize(QSize(width(),height())); + setTextureSize(QSize(width(), height())); connect(this, SIGNAL(propertyChanged()), this, SLOT(update())); // Fill gradient m_gradient.setStart(0, 0); m_gradient.setFinalStop(0, height()); m_gradient.setColorAt(1, QColor(129, 233, 139)); m_gradient.setColorAt(0.4, QColor(129, 233, 139)); m_gradient.setColorAt(0.2, QColor(233, 215, 129)); m_gradient.setColorAt(0.1, QColor(255, 0, 0)); m_gradient.setSpread(QGradient::ReflectSpread); } void paint(QPainter *painter) override { if (!m_showItem) return; QVariantList data = m_audioLevels.toList(); if (data.isEmpty()) return; const qreal indicesPrPixel = qreal(m_outPoint - m_inPoint) / width(); QPen pen = painter->pen(); pen.setWidthF(0.5); pen.setColor(Qt::black); if (!KdenliveSettings::displayallchannels()) { m_gradient.setFinalStop(0, height()); painter->setBrush(m_gradient); // Draw merged channels QPainterPath path; path.moveTo(-1, height()); int i = 0; int lastIdx = -1; for (; i < width(); ++i) { int idx = m_inPoint + int(i * indicesPrPixel); if (lastIdx == idx) { continue; } lastIdx = idx; if (idx + 1 >= data.length()) break; qreal level = qMax(data.at(idx).toReal(), data.at(idx + 1).toReal()) / 256; path.lineTo(i, height() - level * height()); } path.lineTo(i, height()); painter->drawPath(path); } else { // Fill gradient m_gradient.setFinalStop(0, height() / 4); painter->setBrush(m_gradient); // Draw separate channels QMap positiveChannelPaths; QMap negativeChannelPaths; // TODO: get channels count int channels = 2; int i = 0; for (int channel = 0; channel < channels; channel++) { int y = height() - (2 * channel + 1) * height() / 4; positiveChannelPaths[channel].moveTo(-1, y); negativeChannelPaths[channel].moveTo(-1, y); // Draw channel median line painter->drawLine(0, y, width(), y); int lastIdx = -1; for (i = 0; i < width(); ++i) { int idx = m_inPoint + int(i * indicesPrPixel); if (lastIdx == idx) { continue; } lastIdx = idx; if (idx + channel >= data.length()) break; qreal level = data.at(idx + channel).toReal() / 256; positiveChannelPaths[channel].lineTo(i, y - level * height() / 4); negativeChannelPaths[channel].lineTo(i, y + level * height() / 4); } } for (int channel = 0; channel < channels; channel++) { int y = height() - (2 * channel + 1) * height() / 4; positiveChannelPaths[channel].lineTo(i, y); negativeChannelPaths[channel].lineTo(i, y); painter->drawPath(positiveChannelPaths.value(channel)); painter->drawPath(negativeChannelPaths.value(channel)); } } } signals: void propertyChanged(); void inPointChanged(); void outPointChanged(); private: QVariant m_audioLevels; int m_inPoint; int m_outPoint; QColor m_color; bool m_format; QLinearGradient m_gradient; bool m_showItem; }; void registerTimelineItems() { qmlRegisterType("Kdenlive.Controls", 1, 0, "TimelineTriangle"); qmlRegisterType("Kdenlive.Controls", 1, 0, "TimelinePlayhead"); qmlRegisterType("Kdenlive.Controls", 1, 0, "TimelineWaveform"); } #include "timelineitems.moc" diff --git a/src/timeline2/view/qmltypes/thumbnailprovider.cpp b/src/timeline2/view/qmltypes/thumbnailprovider.cpp index 782e69a7f..310f32d2b 100644 --- a/src/timeline2/view/qmltypes/thumbnailprovider.cpp +++ b/src/timeline2/view/qmltypes/thumbnailprovider.cpp @@ -1,142 +1,140 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * * 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 3 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, see . */ #include "thumbnailprovider.h" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "utils/thumbnailcache.hpp" #include #include #include #include #include ThumbnailProvider::ThumbnailProvider() : QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading) - //, m_profile(pCore->getCurrentProfilePath().toUtf8().constData()) +//, m_profile(pCore->getCurrentProfilePath().toUtf8().constData()) { } -ThumbnailProvider::~ThumbnailProvider() -{ -} +ThumbnailProvider::~ThumbnailProvider() {} void ThumbnailProvider::resetProject() { m_producers.clear(); } QImage ThumbnailProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { QImage result; // id is binID/#frameNumber QString binId = id.section('/', 0, 0); bool ok; int frameNumber = id.section('#', -1).toInt(&ok); if (ok) { if (ThumbnailCache::get()->hasThumbnail(binId, frameNumber, false)) { result = ThumbnailCache::get()->getThumbnail(binId, frameNumber); *size = result.size(); return result; } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binId); if (binClip) { std::shared_ptr prod = binClip->thumbProducer(); result = makeThumbnail(prod, frameNumber, requestedSize); ThumbnailCache::get()->storeThumbnail(binId, frameNumber, result, false); } /*if (m_producers.contains(binId.toInt())) { producer = m_producers.object(binId.toInt()); } else { m_binClip->thumbProducer(); if (!resource.isEmpty()) { producer = new Mlt::Producer(m_profile, service.toUtf8().constData(), resource.toUtf8().constData()); } else { producer = new Mlt::Producer(m_profile, service.toUtf8().constData()); } std::shared_ptr binClip = pCore->projectItemModel()->getClipByBinID(binId); if (binClip) { std::shared_ptr projectProducer = binClip->originalProducer(); Mlt::Properties original(projectProducer->get_properties()); Mlt::Properties cloneProps(producer->get_properties()); cloneProps.pass_list(original, "video_index,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff," "force_colorspace,set.force_full_luma,templatetext,autorotate,xmldata"); } Mlt::Filter scaler(m_profile, "swscale"); Mlt::Filter padder(m_profile, "resize"); Mlt::Filter converter(m_profile, "avcolor_space"); producer->attach(scaler); producer->attach(padder); producer->attach(converter); m_producers.insert(binId.toInt(), producer); } if ((producer != nullptr) && producer->is_valid()) { // result = KThumb::getFrame(producer, frameNumber, 0, 0); result = makeThumbnail(producer, frameNumber, requestedSize); ThumbnailCache::get()->storeThumbnail(binId, frameNumber, result, false); //m_cache->insertImage(key, result); } else { qDebug() << "INVALID PRODUCER; " << service << " / " << resource; }*/ } if (size) *size = result.size(); return result; } QString ThumbnailProvider::cacheKey(Mlt::Properties &properties, const QString &service, const QString &resource, const QString &hash, int frameNumber) { QString time = properties.frames_to_time(frameNumber, mlt_time_clock); // Reduce the precision to centiseconds to increase chance for cache hit // without much loss of accuracy. time = time.left(time.size() - 1); QString key; if (hash.isEmpty()) { key = QString("%1 %2 %3").arg(service).arg(resource).arg(time); QCryptographicHash hash2(QCryptographicHash::Sha1); hash2.addData(key.toUtf8()); key = hash2.result().toHex(); } else { key = QString("%1 %2").arg(hash).arg(time); } return key; } -QImage ThumbnailProvider::makeThumbnail(std::shared_ptrproducer, int frameNumber, const QSize &requestedSize) +QImage ThumbnailProvider::makeThumbnail(std::shared_ptr producer, int frameNumber, const QSize &requestedSize) { Q_UNUSED(requestedSize) producer->seek(frameNumber); QScopedPointer frame(producer->get_frame()); if (frame == nullptr || !frame->is_valid()) { return QImage(); } mlt_image_format format = mlt_image_rgb24a; int ow = 0; int oh = 0; const uchar *imagedata = frame->get_image(format, ow, oh); if (imagedata) { QImage result(ow, oh, QImage::Format_RGBA8888); memcpy(result.bits(), imagedata, (unsigned)(ow * oh * 4)); if (!result.isNull()) { return result; } } return QImage(); } diff --git a/src/timeline2/view/qmltypes/thumbnailprovider.h b/src/timeline2/view/qmltypes/thumbnailprovider.h index 723aa24b1..9ed39bf63 100644 --- a/src/timeline2/view/qmltypes/thumbnailprovider.h +++ b/src/timeline2/view/qmltypes/thumbnailprovider.h @@ -1,43 +1,43 @@ /* * Copyright (c) 2013-2016 Meltytech, LLC * Author: Dan Dennedy * * 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 3 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, see . */ #ifndef THUMBNAILPROVIDER_H #define THUMBNAILPROVIDER_H #include #include #include +#include #include #include -#include class ThumbnailProvider : public QQuickImageProvider { public: explicit ThumbnailProvider(); virtual ~ThumbnailProvider(); QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); void resetProject(); private: QString cacheKey(Mlt::Properties &properties, const QString &service, const QString &resource, const QString &hash, int frameNumber); QImage makeThumbnail(std::shared_ptr producer, int frameNumber, const QSize &requestedSize); QCache m_producers; }; #endif // THUMBNAILPROVIDER_H diff --git a/src/titler/KoSliderCombo.h b/src/titler/KoSliderCombo.h index 7d037f997..46157cdbe 100644 --- a/src/titler/KoSliderCombo.h +++ b/src/titler/KoSliderCombo.h @@ -1,138 +1,138 @@ /* This file is part of the KDE project Copyright (c) 2007 Casper Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOSLIDERCOMBO_H_ #define KOSLIDERCOMBO_H_ #include /** * @short A widget for qreal values with a popup slider * * KoSliderCombo combines a numerical input and a dropdown slider in a way that takes up as * little screen space as possible. * * It allows the user to either enter a floating point value or quickly set the value using a slider * * One signal is emitted when the value changes. The signal is even emitted when the slider * is moving. The second argument of the signal however tells you if the value is final or not. A * final value is produced by entering a value numerically or by releasing the slider. * * The input of the numerical line edit is constrained to numbers and decimal signs. */ class KoSliderCombo : public QComboBox { Q_OBJECT public: /** * Constructor for the widget, where value is set to 0 * * @param parent parent QWidget */ explicit KoSliderCombo(QWidget *parent = nullptr); /** * Destructor */ virtual ~KoSliderCombo(); /** * The precision of values given as the number of digits after the period. * default is 2 */ qreal decimals() const; /** * The minimum value that can be entered. * default is 0 */ qreal minimum() const; /** * The maximum value that can be entered. * default is 100 */ qreal maximum() const; /** * Sets the precision of the entered values. * @param number the number of digits after the period */ void setDecimals(int number); /** * Sets the minimum value that can be entered. * @param min the minimum value */ void setMinimum(qreal min); /** * Sets the maximum value that can be entered. * @param max the maximum value */ void setMaximum(qreal max); /** - * The value shown. - */ + * The value shown. + */ qreal value() const; QSize minimumSizeHint() const override; ///< reimplemented from KComboBox QSize sizeHint() const override; ///< reimplemented from KComboBox public slots: /** - * Sets the value. - * The value actually set is forced to be within the legal range: minimum <= value <= maximum - * @param value the new value - */ + * Sets the value. + * The value actually set is forced to be within the legal range: minimum <= value <= maximum + * @param value the new value + */ void setValue(qreal value); signals: /** * Emitted every time the value changes (by calling setValue() or * by user interaction). * @param value the new value * @param final if the value is final ie not produced during sliding (on slider release it's final) */ void valueChanged(qreal value, bool final); protected: void paintEvent(QPaintEvent *) override; ///< reimplemented from KComboBox void hideEvent(QHideEvent *) override; ///< reimplemented from KComboBox void changeEvent(QEvent *e) override; ///< reimplemented from KComboBox void mousePressEvent(QMouseEvent *e) override; ///< reimplemented from KComboBox void keyPressEvent(QKeyEvent *e) override; ///< reimplemented from KComboBox void wheelEvent(QWheelEvent *e) override; ///< reimplemented from KComboBox private: Q_PRIVATE_SLOT(d, void sliderValueChanged(int value)) Q_PRIVATE_SLOT(d, void sliderReleased()) Q_PRIVATE_SLOT(d, void lineEditFinished()) class KoSliderComboPrivate; KoSliderComboPrivate *const d; }; #endif diff --git a/src/titler/titledocument.cpp b/src/titler/titledocument.cpp index 4ce77cdde..3b5c657f7 100644 --- a/src/titler/titledocument.cpp +++ b/src/titler/titledocument.cpp @@ -1,696 +1,697 @@ /*************************************************************************** titledocument.h - description ------------------- begin : Feb 28 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 "titledocument.h" #include "gradientwidget.h" #include "graphicsscenerectmove.h" #include "kdenlivesettings.h" #include "timecode.h" #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include #include #include QByteArray fileToByteArray(const QString &filename) { QByteArray ret; QFile file(filename); if (file.open(QIODevice::ReadOnly)) { while (!file.atEnd()) { ret.append(file.readLine()); } } return ret; } TitleDocument::TitleDocument() { m_scene = nullptr; m_width = 0; m_height = 0; } void TitleDocument::setScene(QGraphicsScene *_scene, int width, int height) { m_scene = _scene; m_width = width; m_height = height; } int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed) { if (embed) { if (!item->data(Qt::UserRole + 1).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), item->data(Qt::UserRole + 1).toString()); } else if (!item->data(Qt::UserRole).toString().isEmpty()) { content.setAttribute(QStringLiteral("base64"), fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data()); } content.removeAttribute(QStringLiteral("url")); } else { // save for project files to disk QString base64 = item->data(Qt::UserRole + 1).toString(); if (!base64.isEmpty()) { QString titlePath; if (!m_projectPath.isEmpty()) { titlePath = m_projectPath; } else { titlePath = QStringLiteral("/tmp/titles"); } QString filename = extractBase64Image(titlePath, base64); if (!filename.isEmpty()) { content.setAttribute(QStringLiteral("url"), filename); content.removeAttribute(QStringLiteral("base64")); } } else { return 1; } } return 0; } // static const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data) { QString filename = titlePath + QString(QCryptographicHash::hash(data.toLatin1(), QCryptographicHash::Md5).toHex().append(".titlepart")); QDir dir; dir.mkpath(titlePath); QFile f(filename); if (f.open(QIODevice::WriteOnly)) { f.write(QByteArray::fromBase64(data.toLatin1())); f.close(); return filename; } return QString(); } QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed) { QDomDocument doc; QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle")); main.setAttribute(QStringLiteral("width"), m_width); main.setAttribute(QStringLiteral("height"), m_height); // Save locale #ifndef Q_OS_MAC const char *locale = setlocale(LC_NUMERIC, nullptr); #else const char *locale = setlocale(LC_NUMERIC_MASK, nullptr); #endif main.setAttribute(QStringLiteral("LC_NUMERIC"), locale); doc.appendChild(main); QTextCursor cur; QTextBlockFormat format; for (QGraphicsItem *item : m_scene->items()) { if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) { continue; } QDomElement e = doc.createElement(QStringLiteral("item")); QDomElement content = doc.createElement(QStringLiteral("content")); QFont font; QString gradient; MyTextItem *t; double xPosition = item->pos().x(); switch (item->type()) { case 7: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 13: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem")); content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); base64ToUrl(item, content, embed); break; case 3: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem")); content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast(item)->rect().normalized())); content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast(item)->pen().color())); if (static_cast(item)->pen() == Qt::NoPen) { content.setAttribute(QStringLiteral("penwidth"), 0); } else { content.setAttribute(QStringLiteral("penwidth"), static_cast(item)->pen().width()); } content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast(item)->brush().color())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } break; case 8: e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem")); t = static_cast(item); // Don't save empty text nodes if (t->toPlainText().simplified().isEmpty()) { continue; } // content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml())); content.appendChild(doc.createTextNode(t->toPlainText())); font = t->font(); content.setAttribute(QStringLiteral("font"), font.family()); content.setAttribute(QStringLiteral("font-weight"), font.weight()); content.setAttribute(QStringLiteral("font-pixel-size"), font.pixelSize()); content.setAttribute(QStringLiteral("font-italic"), static_cast(font.italic())); content.setAttribute(QStringLiteral("font-underline"), static_cast(font.underline())); content.setAttribute(QStringLiteral("letter-spacing"), QString::number(font.letterSpacing())); gradient = item->data(TitleDocument::Gradient).toString(); if (!gradient.isEmpty()) { content.setAttribute(QStringLiteral("gradient"), gradient); } cur = QTextCursor(t->document()); cur.select(QTextCursor::Document); format = cur.blockFormat(); if (t->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (t->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2; double offset = qMin(xcenter, m_width - xcenter); xPosition = xcenter - offset; content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset)); } else if (t->alignment() == Qt::AlignRight) { // grow to the left double offset = item->pos().x() + (t->baseBoundingRect().width()); xPosition = 0; content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } else { // left align, grow on right side double offset = m_width - item->pos().x(); content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); } } else { content.setAttribute(QStringLiteral("box-width"), QString::number(t->baseBoundingRect().width())); } content.setAttribute(QStringLiteral("box-height"), QString::number(t->baseBoundingRect().height())); if (!t->data(TitleDocument::LineSpacing).isNull()) { content.setAttribute(QStringLiteral("line-spacing"), QString::number(t->data(TitleDocument::LineSpacing).toInt())); } { QTextCursor cursor(t->document()); cursor.select(QTextCursor::Document); QColor fontcolor = cursor.charFormat().foreground().color(); content.setAttribute(QStringLiteral("font-color"), colorToString(fontcolor)); if (!t->data(TitleDocument::OutlineWidth).isNull()) { content.setAttribute(QStringLiteral("font-outline"), QString::number(t->data(TitleDocument::OutlineWidth).toDouble())); } if (!t->data(TitleDocument::OutlineColor).isNull()) { QVariant variant = t->data(TitleDocument::OutlineColor); QColor outlineColor = variant.value(); content.setAttribute(QStringLiteral("font-outline-color"), colorToString(outlineColor)); } } if (!t->data(100).isNull()) { QStringList effectParams = t->data(100).toStringList(); QString effectName = effectParams.takeFirst(); content.setAttribute(QStringLiteral("textwidth"), QString::number(t->sceneBoundingRect().width())); content.setAttribute(effectName, effectParams.join(QLatin1Char(';'))); } // Only save when necessary. if (t->data(OriginXLeft).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-x-inverted"), t->data(OriginXLeft).toInt()); } if (t->data(OriginYTop).toInt() == AxisInverted) { content.setAttribute(QStringLiteral("kdenlive-axis-y-inverted"), t->data(OriginYTop).toInt()); } if (t->textWidth() > 0) { content.setAttribute(QStringLiteral("alignment"), (int)t->alignment()); } content.setAttribute(QStringLiteral("shadow"), t->shadowInfo().join(QLatin1Char(';'))); break; default: continue; } // position QDomElement pos = doc.createElement(QStringLiteral("position")); pos.setAttribute(QStringLiteral("x"), QString::number(xPosition)); pos.setAttribute(QStringLiteral("y"), QString::number(item->pos().y())); QTransform transform = item->transform(); QDomElement tr = doc.createElement(QStringLiteral("transform")); if (!item->data(TitleDocument::ZoomFactor).isNull()) { tr.setAttribute(QStringLiteral("zoom"), QString::number(item->data(TitleDocument::ZoomFactor).toInt())); } if (!item->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = item->data(TitleDocument::RotateFactor).toList(); tr.setAttribute(QStringLiteral("rotation"), QStringLiteral("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble())); } tr.appendChild(doc.createTextNode(QStringLiteral("%1,%2,%3,%4,%5,%6,%7,%8,%9") .arg(transform.m11()) .arg(transform.m12()) .arg(transform.m13()) .arg(transform.m21()) .arg(transform.m22()) .arg(transform.m23()) .arg(transform.m31()) .arg(transform.m32()) .arg(transform.m33()))); e.setAttribute(QStringLiteral("z-index"), item->zValue()); pos.appendChild(tr); e.appendChild(pos); e.appendChild(content); if (item->zValue() > -1000) { main.appendChild(e); } } if ((startv != nullptr) && (endv != nullptr)) { QDomElement endport = doc.createElement(QStringLiteral("endviewport")); QDomElement startport = doc.createElement(QStringLiteral("startviewport")); QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height()); endport.setAttribute(QStringLiteral("rect"), rectFToString(r)); QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height()); startport.setAttribute(QStringLiteral("rect"), rectFToString(r2)); main.appendChild(startport); main.appendChild(endport); } QDomElement backgr = doc.createElement(QStringLiteral("background")); QColor color = getBackgroundColor(); backgr.setAttribute(QStringLiteral("color"), colorToString(color)); main.appendChild(backgr); return doc; } /** \brief Get the background color (incl. alpha) from the document, if possibly - * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ + * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ QColor TitleDocument::getBackgroundColor() const { QColor color(0, 0, 0, 0); if (m_scene) { QList items = m_scene->items(); for (int i = 0; i < items.size(); ++i) { if ((int)items.at(i)->zValue() == -1100) { color = static_cast(items.at(i))->brush().color(); return color; } } } return color; } bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed) { if (!m_scene) { return false; } QDomDocument doc = xml(startv, endv, embed); doc.documentElement().setAttribute(QStringLiteral("duration"), duration); // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12 doc.documentElement().setAttribute(QStringLiteral("out"), duration); QTemporaryFile tmpfile; if (!tmpfile.open()) { qCWarning(KDENLIVE_LOG) << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName(); return false; } QFile xmlf(tmpfile.fileName()); if (!xmlf.open(QIODevice::WriteOnly)) { return false; } xmlf.write(doc.toString().toUtf8()); if (xmlf.error() != QFile::NoError) { xmlf.close(); return false; } xmlf.close(); KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(tmpfile.fileName()), url, -1, KIO::Overwrite); return copyjob->exec(); } int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, const QString &projectpath) { m_projectPath = projectpath; QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle")); // TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) { int doc_width = doc.documentElement().attribute(QStringLiteral("width")).toInt(); int doc_height = doc.documentElement().attribute(QStringLiteral("height")).toInt(); if (doc_width != m_width || doc_height != m_height) { KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile")); // TODO: convert using QTransform m_width = doc_width; m_height = doc_height; } } else { // Document has no size info, it is likely an old version title, so ignore viewport data QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("endviewport")); if (!viewportlist.isEmpty()) { doc.documentElement().removeChild(viewportlist.at(0)); } } if (doc.documentElement().hasAttribute(QStringLiteral("duration"))) { *duration = doc.documentElement().attribute(QStringLiteral("duration")).toInt(); } else if (doc.documentElement().hasAttribute(QStringLiteral("out"))) { *duration = doc.documentElement().attribute(QStringLiteral("out")).toInt(); } else { *duration = Timecode().getFrameCount(KdenliveSettings::title_duration()); } int maxZValue = 0; if (!titles.isEmpty()) { QDomNodeList items = titles.item(0).childNodes(); for (int i = 0; i < items.count(); ++i) { QGraphicsItem *gitem = nullptr; QDomNode itemNode = items.item(i); // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("type").nodeValue(); int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt(); double xPosition = itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("x")).nodeValue().toDouble(); if (zValue > -1000) { if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) { QDomNamedNodeMap txtProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QFont font(txtProperties.namedItem(QStringLiteral("font")).nodeValue()); QDomNode node = txtProperties.namedItem(QStringLiteral("font-bold")); if (!node.isNull()) { // Old: Bold/Not bold. font.setBold(node.nodeValue().toInt() != 0); } else { // New: Font weight (QFont::) font.setWeight(txtProperties.namedItem(QStringLiteral("font-weight")).nodeValue().toInt()); } // font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt()); font.setItalic(txtProperties.namedItem(QStringLiteral("font-italic")).nodeValue().toInt() != 0); font.setUnderline(txtProperties.namedItem(QStringLiteral("font-underline")).nodeValue().toInt() != 0); // Older Kdenlive version did not store pixel size but point size if (txtProperties.namedItem(QStringLiteral("font-pixel-size")).isNull()) { - KMessageBox::information(QApplication::activeWindow(), i18n("Some of your text clips were saved with size in points, which means " - "different sizes on different displays. They will be converted to pixel " - "size, making them portable, but you could have to adjust their size."), + KMessageBox::information(QApplication::activeWindow(), + i18n("Some of your text clips were saved with size in points, which means " + "different sizes on different displays. They will be converted to pixel " + "size, making them portable, but you could have to adjust their size."), i18n("Text Clips Updated")); QFont f2; f2.setPointSize(txtProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt()); font.setPixelSize(QFontInfo(f2).pixelSize()); } else { font.setPixelSize(txtProperties.namedItem(QStringLiteral("font-pixel-size")).nodeValue().toInt()); } font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt()); QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue())); MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), nullptr); m_scene->addItem(txt); txt->setFont(font); txt->setTextInteractionFlags(Qt::NoTextInteraction); QTextCursor cursor(txt->document()); cursor.select(QTextCursor::Document); QTextCharFormat cformat = cursor.charFormat(); if (txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble() > 0.0) { txt->setData(TitleDocument::OutlineWidth, txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble()); txt->setData(TitleDocument::OutlineColor, stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())); cformat.setTextOutline(QPen(QColor(stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())), txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); } if (!txtProperties.namedItem(QStringLiteral("line-spacing")).isNull()) { int lineSpacing = txtProperties.namedItem(QStringLiteral("line-spacing")).nodeValue().toInt(); QTextBlockFormat format = cursor.blockFormat(); format.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight); cursor.setBlockFormat(format); txt->setData(TitleDocument::LineSpacing, lineSpacing); } txt->setTextColor(col); cformat.setForeground(QBrush(col)); cursor.setCharFormat(cformat); if (!txtProperties.namedItem(QStringLiteral("gradient")).isNull()) { // Gradient color QString data = txtProperties.namedItem(QStringLiteral("gradient")).nodeValue(); txt->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, txt->boundingRect().width(), txt->boundingRect().height()); cformat.setForeground(QBrush(gr)); cursor.setCharFormat(cformat); } if (!txtProperties.namedItem(QStringLiteral("alignment")).isNull()) { txt->setAlignment((Qt::Alignment)txtProperties.namedItem(QStringLiteral("alignment")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).isNull()) { txt->setData(OriginXLeft, txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).isNull()) { txt->setData(OriginYTop, txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).nodeValue().toInt()); } if (!txtProperties.namedItem(QStringLiteral("shadow")).isNull()) { QString info = txtProperties.namedItem(QStringLiteral("shadow")).nodeValue(); txt->loadShadow(info.split(QLatin1Char(';'))); } // Effects if (!txtProperties.namedItem(QStringLiteral("typewriter")).isNull()) { QStringList effData = QStringList() << QStringLiteral("typewriter") << txtProperties.namedItem(QStringLiteral("typewriter")).nodeValue(); txt->setData(100, effData); } if (txt->toPlainText() == QLatin1String("%s")) { // template text box, adjust size for later remplacement text if (txt->alignment() == Qt::AlignHCenter) { // grow dimensions on both sides double width = txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble(); double xcenter = (width - xPosition) / 2.0; xPosition = xcenter - txt->boundingRect().width() / 2; } else if (txt->alignment() == Qt::AlignRight) { // grow to the left xPosition = xPosition + txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble() - txt->boundingRect().width(); } else { // left align, grow on right side, nothing to do } } gitem = txt; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsRectItem")) { QDomNamedNodeMap rectProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); QString rect = rectProperties.namedItem(QStringLiteral("rect")).nodeValue(); QString br_str = rectProperties.namedItem(QStringLiteral("brushcolor")).nodeValue(); QString pen_str = rectProperties.namedItem(QStringLiteral("pencolor")).nodeValue(); double penwidth = rectProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble(); auto *rec = new MyRectItem(); rec->setRect(stringToRect(rect)); if (penwidth > 0) { rec->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); } else { rec->setPen(Qt::NoPen); } if (!rectProperties.namedItem(QStringLiteral("gradient")).isNull()) { // Gradient color QString data = rectProperties.namedItem(QStringLiteral("gradient")).nodeValue(); rec->setData(TitleDocument::Gradient, data); QLinearGradient gr = GradientWidget::gradientFromString(data, rec->rect().width(), rec->rect().height()); rec->setBrush(QBrush(gr)); } else { rec->setBrush(QBrush(stringToColor(br_str))); } m_scene->addItem(rec); gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QPixmap pix; if (base64.isEmpty()) { pix.load(url); } else { pix.loadFromData(QByteArray::fromBase64(base64.toLatin1())); } auto *rec = new MyPixmapItem(pix); m_scene->addItem(rec); rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsSvgItem")) { QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); QGraphicsSvgItem *rec = nullptr; if (base64.isEmpty()) { rec = new MySvgItem(url); } else { rec = new MySvgItem(); QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec); rec->setSharedRenderer(renderer); // QString elem=rec->elementId(); // QRectF bounds = renderer->boundsOnElement(elem); } if (rec) { m_scene->addItem(rec); rec->setData(Qt::UserRole, url); if (!base64.isEmpty()) { rec->setData(Qt::UserRole + 1, base64); } gitem = rec; } } } // pos and transform if (gitem) { QPointF p(xPosition, itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("y")).nodeValue().toDouble()); gitem->setPos(p); QDomElement trans = itemNode.namedItem(QStringLiteral("position")).firstChild().toElement(); gitem->setTransform(stringToTransform(trans.firstChild().nodeValue())); QString rotate = trans.attribute(QStringLiteral("rotation")); if (!rotate.isEmpty()) { gitem->setData(TitleDocument::RotateFactor, stringToList(rotate)); } QString zoom = trans.attribute(QStringLiteral("zoom")); if (!zoom.isEmpty()) { gitem->setData(TitleDocument::ZoomFactor, zoom.toInt()); } if (zValue >= maxZValue) { maxZValue = zValue + 1; } gitem->setZValue(zValue); gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); // effects QDomNode eff = itemNode.namedItem(QStringLiteral("effect")); if (!eff.isNull()) { QDomElement e = eff.toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("blur")) { auto *blur = new QGraphicsBlurEffect(); blur->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); gitem->setGraphicsEffect(blur); } else if (e.attribute(QStringLiteral("type")) == QLatin1String("shadow")) { auto *shadow = new QGraphicsDropShadowEffect(); shadow->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); shadow->setOffset(e.attribute(QStringLiteral("xoffset")).toInt(), e.attribute(QStringLiteral("yoffset")).toInt()); gitem->setGraphicsEffect(shadow); } } } if (itemNode.nodeName() == QLatin1String("background")) { // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("color").nodeValue(); QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue())); // color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt()); QList sceneItems = m_scene->items(); for (int j = 0; j < sceneItems.size(); ++j) { if ((int)sceneItems.at(j)->zValue() == -1100) { static_cast(sceneItems.at(j))->setBrush(QBrush(color)); break; } } } else if (itemNode.nodeName() == QLatin1String("startviewport") && (startv != nullptr)) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); startv->setRect(0, 0, r.width(), r.height()); startv->setPos(r.topLeft()); } else if (itemNode.nodeName() == QLatin1String("endviewport") && (endv != nullptr)) { QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); QRectF r = stringToRect(rect); endv->setRect(0, 0, r.width(), r.height()); endv->setPos(r.topLeft()); } } } return maxZValue; } QString TitleDocument::colorToString(const QColor &c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); return ret; } QString TitleDocument::rectFToString(const QRectF &c) { QString ret = QStringLiteral("%1,%2,%3,%4"); ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height()); return ret; } QRectF TitleDocument::stringToRect(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 4) { return QRectF(); } return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized(); } QColor TitleDocument::stringToColor(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 4) { return QColor(); } return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt()); } QTransform TitleDocument::stringToTransform(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 9) { return QTransform(); } return QTransform(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()); } QList TitleDocument::stringToList(const QString &s) { QStringList l = s.split(QLatin1Char(',')); if (l.size() < 3) { return QList(); } return QList() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble()); } int TitleDocument::frameWidth() const { return m_width; } int TitleDocument::frameHeight() const { return m_height; } diff --git a/src/titler/titlewidget.cpp b/src/titler/titlewidget.cpp index cacf96da7..b781f37ac 100644 --- a/src/titler/titlewidget.cpp +++ b/src/titler/titlewidget.cpp @@ -1,2931 +1,2934 @@ /*************************************************************************** titlewidget.cpp - description ------------------- begin : Feb 28 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 "titlewidget.h" #include "KoSliderCombo.h" #include "doc/kthumb.h" #include "gradientwidget.h" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include "utils/KoIconUtils.h" #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QList titletemplates; // What exactly is this variable good for? int settingUp = 0; const int IMAGEITEM = 7; const int RECTITEM = 3; const int TEXTITEM = 8; const int NOEFFECT = 0; const int BLUREFFECT = 1; const int SHADOWEFFECT = 2; const int TYPEWRITEREFFECT = 3; TitleWidget::TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Monitor *monitor, QWidget *parent) : QDialog(parent) , Ui::TitleWidget_UI() , m_startViewport(nullptr) , m_endViewport(nullptr) , m_count(0) , m_unicodeDialog(new UnicodeDialog(UnicodeDialog::InputHex)) , m_projectTitlePath(projectTitlePath) , m_tc(tc) , m_fps(monitor->fps()) { setupUi(this); setMinimumSize(200, 200); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); frame_properties->setEnabled(false); frame_properties->setFixedHeight(frame_toolbar->height()); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); rectBColor->setAlphaChannelEnabled(true); rectFColor->setAlphaChannelEnabled(true); fontColorButton->setAlphaChannelEnabled(true); textOutlineColor->setAlphaChannelEnabled(true); shadowColor->setAlphaChannelEnabled(true); auto *colorGroup = new QButtonGroup(this); colorGroup->addButton(gradient_color); colorGroup->addButton(plain_color); auto *alignGroup = new QButtonGroup(this); alignGroup->addButton(buttonAlignLeft); alignGroup->addButton(buttonAlignCenter); alignGroup->addButton(buttonAlignRight); textOutline->setMinimum(0); textOutline->setMaximum(200); // textOutline->setDecimals(0); textOutline->setValue(0); textOutline->setToolTip(i18n("Outline width")); backgroundAlpha->setMinimum(0); backgroundAlpha->setMaximum(255); bgAlphaSlider->setMinimum(0); bgAlphaSlider->setMaximum(255); backgroundAlpha->setValue(0); backgroundAlpha->setToolTip(i18n("Background color opacity")); itemrotatex->setMinimum(-360); itemrotatex->setMaximum(360); // itemrotatex->setDecimals(0); itemrotatex->setValue(0); itemrotatex->setToolTip(i18n("Rotation around the X axis")); itemrotatey->setMinimum(-360); itemrotatey->setMaximum(360); // itemrotatey->setDecimals(0); itemrotatey->setValue(0); itemrotatey->setToolTip(i18n("Rotation around the Y axis")); itemrotatez->setMinimum(-360); itemrotatez->setMaximum(360); // itemrotatez->setDecimals(0); itemrotatez->setValue(0); itemrotatez->setToolTip(i18n("Rotation around the Z axis")); rectLineWidth->setMinimum(0); rectLineWidth->setMaximum(500); // rectLineWidth->setDecimals(0); rectLineWidth->setValue(0); rectLineWidth->setToolTip(i18n("Border width")); itemzoom->setSuffix(i18n("%")); QSize profileSize = monitor->profileSize(); m_frameWidth = (int)(profileSize.height() * monitor->profile()->dar() + 0.5); m_frameHeight = profileSize.height(); showToolbars(TITLE_SELECT); splitter->setStretchFactor(0, 20); // If project is drop frame, set the input mask as such. title_duration->setInputMask(m_tc.mask()); title_duration->setText(m_tc.reformatSeparators(KdenliveSettings::title_duration())); connect(backgroundColor, &KColorButton::changed, this, &TitleWidget::slotChangeBackground); connect(backgroundAlpha, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotChangeBackground); connect(shadowBox, &QGroupBox::toggled, this, &TitleWidget::slotUpdateShadow); connect(shadowColor, &KColorButton::changed, this, &TitleWidget::slotUpdateShadow); connect(blur_radius, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(shadowX, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(shadowY, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow); connect(fontColorButton, &KColorButton::changed, this, &TitleWidget::slotUpdateText); connect(plain_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(gradient_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(gradients_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(textOutlineColor, &KColorButton::changed, this, &TitleWidget::slotUpdateText); connect(font_family, &QFontComboBox::currentFontChanged, this, &TitleWidget::slotUpdateText); connect(font_size, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(letter_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(line_spacing, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(textOutline, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText); connect(font_weight_box, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateText())); connect(rectFColor, &KColorButton::changed, this, &TitleWidget::rectChanged); connect(rectBColor, &KColorButton::changed, this, &TitleWidget::rectChanged); connect(plain_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged); connect(gradient_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged); connect(gradients_rect_combo, SIGNAL(currentIndexChanged(int)), this, SLOT(rectChanged())); connect(rectLineWidth, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::rectChanged); connect(zValue, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::zIndexChanged); connect(itemzoom, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemScaled); connect(itemrotatex, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateX); connect(itemrotatey, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateY); connect(itemrotatez, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateZ); connect(itemhcenter, &QAbstractButton::clicked, this, &TitleWidget::itemHCenter); connect(itemvcenter, &QAbstractButton::clicked, this, &TitleWidget::itemVCenter); connect(itemtop, &QAbstractButton::clicked, this, &TitleWidget::itemTop); connect(itembottom, &QAbstractButton::clicked, this, &TitleWidget::itemBottom); connect(itemleft, &QAbstractButton::clicked, this, &TitleWidget::itemLeft); connect(itemright, &QAbstractButton::clicked, this, &TitleWidget::itemRight); connect(effect_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotAddEffect(int))); connect(typewriter_delay, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotEditTypewriter); connect(typewriter_start, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotEditTypewriter); connect(origin_x_left, &QAbstractButton::clicked, this, &TitleWidget::slotOriginXClicked); connect(origin_y_top, &QAbstractButton::clicked, this, &TitleWidget::slotOriginYClicked); connect(monitor, &Monitor::frameUpdated, this, &TitleWidget::slotGotBackground); // Position and size m_signalMapper = new QSignalMapper(this); m_signalMapper->setMapping(value_w, ValueWidth); m_signalMapper->setMapping(value_h, ValueHeight); m_signalMapper->setMapping(value_x, ValueX); m_signalMapper->setMapping(value_y, ValueY); - connect(value_w, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); - connect(value_h, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); - connect(value_x, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); - connect(value_y, static_cast(&QSpinBox::valueChanged), m_signalMapper, static_cast(&QSignalMapper::map)); + connect(value_w, static_cast(&QSpinBox::valueChanged), m_signalMapper, + static_cast(&QSignalMapper::map)); + connect(value_h, static_cast(&QSpinBox::valueChanged), m_signalMapper, + static_cast(&QSignalMapper::map)); + connect(value_x, static_cast(&QSpinBox::valueChanged), m_signalMapper, + static_cast(&QSignalMapper::map)); + connect(value_y, static_cast(&QSpinBox::valueChanged), m_signalMapper, + static_cast(&QSignalMapper::map)); connect(m_signalMapper, SIGNAL(mapped(int)), this, SLOT(slotValueChanged(int))); connect(buttonFitZoom, &QAbstractButton::clicked, this, &TitleWidget::slotAdjustZoom); connect(buttonRealSize, &QAbstractButton::clicked, this, &TitleWidget::slotZoomOneToOne); connect(buttonItalic, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonUnder, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignLeft, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignRight, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(buttonAlignCenter, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText); connect(edit_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient); connect(edit_rect_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient); connect(displayBg, &QCheckBox::stateChanged, this, &TitleWidget::displayBackgroundFrame); connect(m_unicodeDialog, &UnicodeDialog::charSelected, this, &TitleWidget::slotInsertUnicodeString); // mbd connect(this, &QDialog::accepted, this, &TitleWidget::slotAccepted); font_weight_box->blockSignals(true); font_weight_box->addItem(i18nc("Font style", "Light"), QFont::Light); font_weight_box->addItem(i18nc("Font style", "Normal"), QFont::Normal); font_weight_box->addItem(i18nc("Font style", "Demi-Bold"), QFont::DemiBold); font_weight_box->addItem(i18nc("Font style", "Bold"), QFont::Bold); font_weight_box->addItem(i18nc("Font style", "Black"), QFont::Black); font_weight_box->setToolTip(i18n("Font weight")); font_weight_box->setCurrentIndex(1); font_weight_box->blockSignals(false); buttonFitZoom->setIconSize(iconSize); buttonRealSize->setIconSize(iconSize); buttonItalic->setIconSize(iconSize); buttonUnder->setIconSize(iconSize); buttonAlignCenter->setIconSize(iconSize); buttonAlignLeft->setIconSize(iconSize); buttonAlignRight->setIconSize(iconSize); buttonFitZoom->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best"))); buttonRealSize->setIcon(KoIconUtils::themedIcon(QStringLiteral("zoom-original"))); buttonItalic->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-text-italic"))); buttonUnder->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-text-underline"))); buttonAlignCenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-center"))); buttonAlignLeft->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-left"))); buttonAlignRight->setIcon(KoIconUtils::themedIcon(QStringLiteral("format-justify-right"))); edit_gradient->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); edit_rect_gradient->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-edit"))); buttonAlignRight->setToolTip(i18n("Align right")); buttonAlignLeft->setToolTip(i18n("Align left")); buttonAlignCenter->setToolTip(i18n("Align center")); buttonAlignLeft->setChecked(true); m_unicodeAction = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-unicode")), QString(), this); m_unicodeAction->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_U); m_unicodeAction->setToolTip(getTooltipWithShortcut(i18n("Insert Unicode character"), m_unicodeAction)); connect(m_unicodeAction, &QAction::triggered, this, &TitleWidget::slotInsertUnicode); buttonInsertUnicode->setDefaultAction(m_unicodeAction); m_zUp = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-up")), QString(), this); m_zUp->setShortcut(Qt::Key_PageUp); m_zUp->setToolTip(i18n("Raise object")); connect(m_zUp, &QAction::triggered, this, &TitleWidget::slotZIndexUp); zUp->setDefaultAction(m_zUp); m_zDown = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-down")), QString(), this); m_zDown->setShortcut(Qt::Key_PageDown); m_zDown->setToolTip(i18n("Lower object")); connect(m_zDown, &QAction::triggered, this, &TitleWidget::slotZIndexDown); zDown->setDefaultAction(m_zDown); m_zTop = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-top")), QString(), this); // TODO mbt 1414: Shortcut should change z index only if // cursor is NOT in a text field ... // m_zTop->setShortcut(Qt::Key_Home); m_zTop->setToolTip(i18n("Raise object to top")); connect(m_zTop, &QAction::triggered, this, &TitleWidget::slotZIndexTop); zTop->setDefaultAction(m_zTop); m_zBottom = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-bottom")), QString(), this); // TODO mbt 1414 // m_zBottom->setShortcut(Qt::Key_End); m_zBottom->setToolTip(i18n("Lower object to bottom")); connect(m_zBottom, &QAction::triggered, this, &TitleWidget::slotZIndexBottom); zBottom->setDefaultAction(m_zBottom); m_selectAll = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all")), QString(), this); m_selectAll->setShortcut(Qt::CTRL + Qt::Key_A); connect(m_selectAll, &QAction::triggered, this, &TitleWidget::slotSelectAll); buttonSelectAll->setDefaultAction(m_selectAll); m_selectText = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-texts")), QString(), this); m_selectText->setShortcut(Qt::CTRL + Qt::Key_T); connect(m_selectText, &QAction::triggered, this, &TitleWidget::slotSelectText); buttonSelectText->setDefaultAction(m_selectText); buttonSelectText->setEnabled(false); m_selectRects = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-rects")), QString(), this); m_selectRects->setShortcut(Qt::CTRL + Qt::Key_R); connect(m_selectRects, &QAction::triggered, this, &TitleWidget::slotSelectRects); buttonSelectRects->setDefaultAction(m_selectRects); buttonSelectRects->setEnabled(false); m_selectImages = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-images")), QString(), this); m_selectImages->setShortcut(Qt::CTRL + Qt::Key_I); connect(m_selectImages, &QAction::triggered, this, &TitleWidget::slotSelectImages); buttonSelectImages->setDefaultAction(m_selectImages); buttonSelectImages->setEnabled(false); m_unselectAll = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-unselect-all")), QString(), this); m_unselectAll->setShortcut(Qt::SHIFT + Qt::CTRL + Qt::Key_A); connect(m_unselectAll, &QAction::triggered, this, &TitleWidget::slotSelectNone); buttonUnselectAll->setDefaultAction(m_unselectAll); buttonUnselectAll->setEnabled(false); zDown->setIconSize(iconSize); zTop->setIconSize(iconSize); zBottom->setIconSize(iconSize); zDown->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-down"))); zTop->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-top"))); zBottom->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zindex-bottom"))); connect(zDown, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexDown); connect(zTop, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexTop); connect(zBottom, &QAbstractButton::clicked, this, &TitleWidget::slotZIndexBottom); origin_x_left->setToolTip(i18n("Invert x axis and change 0 point")); origin_y_top->setToolTip(i18n("Invert y axis and change 0 point")); rectBColor->setToolTip(i18n("Select fill color")); rectFColor->setToolTip(i18n("Select border color")); zoom_slider->setToolTip(i18n("Zoom")); buttonRealSize->setToolTip(i18n("Original size (1:1)")); buttonFitZoom->setToolTip(i18n("Fit zoom")); backgroundColor->setToolTip(i18n("Select background color")); backgroundAlpha->setToolTip(i18n("Background opacity")); buttonSelectAll->setToolTip(getTooltipWithShortcut(i18n("Select all"), m_selectAll)); buttonSelectText->setToolTip(getTooltipWithShortcut(i18n("Select text items in current selection"), m_selectText)); buttonSelectRects->setToolTip(getTooltipWithShortcut(i18n("Select rect items in current selection"), m_selectRects)); buttonSelectImages->setToolTip(getTooltipWithShortcut(i18n("Select image items in current selection"), m_selectImages)); buttonUnselectAll->setToolTip(getTooltipWithShortcut(i18n("Unselect all"), m_unselectAll)); itemhcenter->setIconSize(iconSize); itemvcenter->setIconSize(iconSize); itemtop->setIconSize(iconSize); itembottom->setIconSize(iconSize); itemright->setIconSize(iconSize); itemleft->setIconSize(iconSize); itemhcenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-hor"))); itemhcenter->setToolTip(i18n("Align item horizontally")); itemvcenter->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-vert"))); itemvcenter->setToolTip(i18n("Align item vertically")); itemtop->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-top"))); itemtop->setToolTip(i18n("Align item to top")); itembottom->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-bottom"))); itembottom->setToolTip(i18n("Align item to bottom")); itemright->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-right"))); itemright->setToolTip(i18n("Align item to right")); itemleft->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-left"))); itemleft->setToolTip(i18n("Align item to left")); auto *layout = new QHBoxLayout; frame_toolbar->setLayout(layout); layout->setContentsMargins(0, 0, 0, 0); QToolBar *m_toolbar = new QToolBar(QStringLiteral("titleToolBar"), this); m_toolbar->setIconSize(iconSize); m_buttonCursor = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("transform-move")), i18n("Selection Tool")); m_buttonCursor->setCheckable(true); m_buttonCursor->setShortcut(Qt::ALT + Qt::Key_S); m_buttonCursor->setToolTip(i18n("Selection Tool") + QLatin1Char(' ') + m_buttonCursor->shortcut().toString()); connect(m_buttonCursor, &QAction::triggered, this, &TitleWidget::slotSelectTool); m_buttonText = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("insert-text")), i18n("Add Text")); m_buttonText->setCheckable(true); m_buttonText->setShortcut(Qt::ALT + Qt::Key_T); m_buttonText->setToolTip(i18n("Add Text") + QLatin1Char(' ') + m_buttonText->shortcut().toString()); connect(m_buttonText, &QAction::triggered, this, &TitleWidget::slotTextTool); m_buttonRect = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-rect")), i18n("Add Rectangle")); m_buttonRect->setCheckable(true); m_buttonRect->setShortcut(Qt::ALT + Qt::Key_R); m_buttonRect->setToolTip(i18n("Add Rectangle") + QLatin1Char(' ') + m_buttonRect->shortcut().toString()); connect(m_buttonRect, &QAction::triggered, this, &TitleWidget::slotRectTool); m_buttonImage = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("insert-image")), i18n("Add Image")); m_buttonImage->setCheckable(false); m_buttonImage->setShortcut(Qt::ALT + Qt::Key_I); m_buttonImage->setToolTip(i18n("Add Image") + QLatin1Char(' ') + m_buttonImage->shortcut().toString()); connect(m_buttonImage, &QAction::triggered, this, &TitleWidget::slotImageTool); m_toolbar->addSeparator(); m_buttonLoad = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-open")), i18n("Open Document")); m_buttonLoad->setCheckable(false); m_buttonLoad->setShortcut(Qt::CTRL + Qt::Key_O); m_buttonLoad->setToolTip(i18n("Open Document") + QLatin1Char(' ') + m_buttonLoad->shortcut().toString()); connect(m_buttonLoad, SIGNAL(triggered()), this, SLOT(loadTitle())); m_buttonSave = m_toolbar->addAction(KoIconUtils::themedIcon(QStringLiteral("document-save-as")), i18n("Save As")); m_buttonSave->setCheckable(false); m_buttonSave->setShortcut(Qt::CTRL + Qt::Key_S); m_buttonSave->setToolTip(i18n("Save As") + QLatin1Char(' ') + m_buttonSave->shortcut().toString()); connect(m_buttonSave, SIGNAL(triggered()), this, SLOT(saveTitle())); layout->addWidget(m_toolbar); // initialize graphic scene m_scene = new GraphicsSceneRectMove(this); graphicsView->setScene(m_scene); graphicsView->setMouseTracking(true); graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); graphicsView->setDragMode(QGraphicsView::RubberBandDrag); graphicsView->setRubberBandSelectionMode(Qt::ContainsItemBoundingRect); m_titledocument.setScene(m_scene, m_frameWidth, m_frameHeight); connect(m_scene, &QGraphicsScene::changed, this, &TitleWidget::slotChanged); connect(font_size, static_cast(&QSpinBox::valueChanged), m_scene, &GraphicsSceneRectMove::slotUpdateFontSize); connect(use_grid, &QAbstractButton::toggled, m_scene, &GraphicsSceneRectMove::slotUseGrid); // Video frame rect QPen framepen; framepen.setColor(Qt::red); m_frameBorder = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_frameBorder->setPen(framepen); m_frameBorder->setZValue(1000); m_frameBorder->setBrush(Qt::transparent); m_frameBorder->setFlags(nullptr); m_frameBorder->setData(-1, -1); graphicsView->scene()->addItem(m_frameBorder); // semi transparent safe zones framepen.setColor(QColor(255, 0, 0, 100)); QGraphicsRectItem *safe1 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.05, m_frameHeight * 0.05, m_frameWidth * 0.9, m_frameHeight * 0.9), m_frameBorder); safe1->setBrush(Qt::transparent); safe1->setPen(framepen); safe1->setFlags(nullptr); safe1->setData(-1, -1); QGraphicsRectItem *safe2 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.1, m_frameHeight * 0.1, m_frameWidth * 0.8, m_frameHeight * 0.8), m_frameBorder); safe2->setBrush(Qt::transparent); safe2->setPen(framepen); safe2->setFlags(nullptr); safe2->setData(-1, -1); m_frameBackground = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_frameBackground->setZValue(-1100); m_frameBackground->setBrush(Qt::transparent); m_frameBackground->setFlags(nullptr); graphicsView->scene()->addItem(m_frameBackground); m_frameImage = new QGraphicsPixmapItem(); QTransform qtrans; qtrans.scale(2.0, 2.0); m_frameImage->setTransform(qtrans); m_frameImage->setZValue(-1200); m_frameImage->setFlags(nullptr); displayBackgroundFrame(); graphicsView->scene()->addItem(m_frameImage); connect(m_scene, &QGraphicsScene::selectionChanged, this, &TitleWidget::selectionChanged); connect(m_scene, &GraphicsSceneRectMove::itemMoved, this, &TitleWidget::selectionChanged); connect(m_scene, &GraphicsSceneRectMove::sceneZoom, this, &TitleWidget::slotZoom); connect(m_scene, &GraphicsSceneRectMove::actionFinished, this, &TitleWidget::slotSelectTool); connect(m_scene, &GraphicsSceneRectMove::newRect, this, &TitleWidget::slotNewRect); connect(m_scene, &GraphicsSceneRectMove::newText, this, &TitleWidget::slotNewText); connect(zoom_slider, &QAbstractSlider::valueChanged, this, &TitleWidget::slotUpdateZoom); connect(zoom_spin, static_cast(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateZoom); // mbd: load saved settings loadGradients(); readChoices(); // Hide effects not implemented tabWidget->removeTab(3); graphicsView->show(); graphicsView->setInteractive(true); // qCDebug(KDENLIVE_LOG) << "// TITLE WIDGWT: " << graphicsView->viewport()->width() << 'x' << graphicsView->viewport()->height(); m_startViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); // Setting data at -1 so that the item is recognized as undeletable by graphicsscenerectmove m_startViewport->setData(-1, -1); m_endViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight)); m_endViewport->setData(-1, -1); m_startViewport->setData(0, m_frameWidth); m_startViewport->setData(1, m_frameHeight); m_endViewport->setData(0, m_frameWidth); m_endViewport->setData(1, m_frameHeight); // scale the view so that the title widget is not too big at startup graphicsView->scale(.5, .5); if (url.isValid()) { loadTitle(url); } else { prepareTools(nullptr); slotTextTool(); QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom); } initAnimation(); QColor color = backgroundColor->color(); m_scene->setBackgroundBrush(QBrush(color)); color.setAlpha(backgroundAlpha->value()); m_frameBackground->setBrush(color); connect(anim_start, &QAbstractButton::toggled, this, &TitleWidget::slotAnimStart); connect(anim_end, &QAbstractButton::toggled, this, &TitleWidget::slotAnimEnd); connect(templateBox, SIGNAL(currentIndexChanged(int)), this, SLOT(templateIndexChanged(int))); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(KdenliveSettings::hastitleproducer()); if (titletemplates.isEmpty()) { refreshTitleTemplates(m_projectTitlePath); } // templateBox->setIconSize(QSize(60,60)); templateBox->clear(); templateBox->addItem(QString()); for (const TitleTemplate &t : titletemplates) { templateBox->addItem(t.icon, t.name, t.file); } lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } TitleWidget::~TitleWidget() { m_scene->blockSignals(true); delete m_buttonRect; delete m_buttonText; delete m_buttonImage; delete m_buttonCursor; delete m_buttonSave; delete m_buttonLoad; delete m_unicodeAction; delete m_zUp; delete m_zDown; delete m_zTop; delete m_zBottom; delete m_selectAll; delete m_selectText; delete m_selectRects; delete m_selectImages; delete m_unselectAll; delete m_unicodeDialog; delete m_frameBorder; delete m_frameImage; delete m_startViewport; delete m_endViewport; delete m_scene; delete m_signalMapper; } // static QStringList TitleWidget::extractImageList(const QString &xml) { QStringList result; if (xml.isEmpty()) { return result; } QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("url"))) { result.append(images.at(i).toElement().attribute(QStringLiteral("url"))); } } return result; } // static QStringList TitleWidget::extractFontList(const QString &xml) { QStringList result; if (xml.isEmpty()) { return result; } QDomDocument doc; doc.setContent(xml); QDomNodeList images = doc.elementsByTagName(QStringLiteral("content")); for (int i = 0; i < images.count(); ++i) { if (images.at(i).toElement().hasAttribute(QStringLiteral("font"))) { result.append(images.at(i).toElement().attribute(QStringLiteral("font"))); } } return result; } // static void TitleWidget::refreshTitleTemplates(const QString &projectPath) { QStringList filters = QStringList() << QStringLiteral("*.kdenlivetitle"); titletemplates.clear(); // project templates QDir dir(projectPath); QStringList templateFiles = dir.entryList(filters, QDir::Files); for (const QString &fname : templateFiles) { TitleTemplate t; t.name = fname; t.file = dir.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1)); titletemplates.append(t); } // system templates QStringList titleTemplates = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory); for (const QString &folderpath : titleTemplates) { QDir folder(folderpath); QStringList filesnames = folder.entryList(filters, QDir::Files); for (const QString &fname : filesnames) { TitleTemplate t; t.name = fname; t.file = folder.absoluteFilePath(fname); t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1)); titletemplates.append(t); } } } void TitleWidget::templateIndexChanged(int index) { QString item = templateBox->itemData(index).toString(); if (!item.isEmpty()) { if (lastDocumentHash != QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex()) { if (KMessageBox::questionYesNo(this, i18n("Do you really want to load a new template? Changes in this title will be lost!")) == KMessageBox::No) { return; } } loadTitle(QUrl::fromLocalFile(item)); // mbt 1607: Add property to distinguish between unchanged template titles and user titles. // Text of unchanged template titles should be selected when clicked. QList list = graphicsView->scene()->items(); for (QGraphicsItem *qgItem : list) { if (qgItem->type() == TEXTITEM) { MyTextItem *i = static_cast(qgItem); i->setProperty("isTemplate", "true"); i->setProperty("templateText", i->toHtml()); } } lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex(); } } // virtual void TitleWidget::resizeEvent(QResizeEvent * /*event*/) { // slotAdjustZoom(); } // virtual void TitleWidget::keyPressEvent(QKeyEvent *e) { if (e->key() != Qt::Key_Escape && e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter) { QDialog::keyPressEvent(e); } } void TitleWidget::slotTextTool() { m_scene->setTool(TITLE_TEXT); showToolbars(TITLE_TEXT); checkButton(TITLE_TEXT); } void TitleWidget::slotRectTool() { m_scene->setTool(TITLE_RECTANGLE); showToolbars(TITLE_RECTANGLE); checkButton(TITLE_RECTANGLE); // Disable dragging mode, would make dragging a rect impossible otherwise ;) graphicsView->setDragMode(QGraphicsView::NoDrag); } void TitleWidget::slotSelectTool() { m_scene->setTool(TITLE_SELECT); // Enable rubberband selecting mode. graphicsView->setDragMode(QGraphicsView::RubberBandDrag); // Find out which toolbars need to be shown, depending on selected item TITLETOOL t = TITLE_SELECT; QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { switch (l.at(0)->type()) { case TEXTITEM: t = TITLE_TEXT; break; case RECTITEM: t = TITLE_RECTANGLE; break; case IMAGEITEM: t = TITLE_IMAGE; break; } } enableToolbars(t); if (t == TITLE_RECTANGLE && (l.at(0) == m_endViewport || l.at(0) == m_startViewport)) { // graphicsView->centerOn(l.at(0)); t = TITLE_SELECT; } showToolbars(t); if (!l.isEmpty()) { updateCoordinates(l.at(0)); updateDimension(l.at(0)); updateRotZoom(l.at(0)); } checkButton(TITLE_SELECT); } void TitleWidget::slotImageTool() { QList supported = QImageReader::supportedImageFormats(); QStringList mimeTypeFilters; QString allExtensions = i18n("All Images") + QStringLiteral(" ("); for (const QByteArray &mimeType : supported) { mimeTypeFilters.append(i18n("%1 Image", QString(mimeType)) + QStringLiteral("( *.") + QString(mimeType) + QLatin1Char(')')); allExtensions.append(QStringLiteral("*.") + mimeType + QLatin1Char(' ')); } mimeTypeFilters.sort(); allExtensions.append(QLatin1Char(')')); mimeTypeFilters.prepend(allExtensions); QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveImageFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QFileDialog dialog(this, i18n("Add Image"), clipFolder); dialog.setAcceptMode(QFileDialog::AcceptOpen); dialog.setNameFilters(mimeTypeFilters); if (dialog.exec() != QDialog::Accepted) { return; } QUrl url = QUrl::fromLocalFile(dialog.selectedFiles().at(0)); if (url.isValid()) { KRecentDirs::add(QStringLiteral(":KdenliveImageFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile()); if (url.toLocalFile().endsWith(QLatin1String(".svg"))) { MySvgItem *svg = new MySvgItem(url.toLocalFile()); svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); svg->setZValue(m_count++); svg->setData(Qt::UserRole, url.toLocalFile()); m_scene->addNewItem(svg); prepareTools(svg); } else { QPixmap pix(url.toLocalFile()); auto *image = new MyPixmapItem(pix); image->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); image->setData(Qt::UserRole, url.toLocalFile()); image->setZValue(m_count++); m_scene->addNewItem(image); prepareTools(image); } } m_scene->setTool(TITLE_SELECT); showToolbars(TITLE_SELECT); checkButton(TITLE_SELECT); } void TitleWidget::showToolbars(TITLETOOL toolType) { // toolbar_stack->setEnabled(toolType != TITLE_SELECT); switch (toolType) { case TITLE_IMAGE: toolbar_stack->setCurrentIndex(2); break; case TITLE_RECTANGLE: toolbar_stack->setCurrentIndex(1); break; case TITLE_TEXT: default: toolbar_stack->setCurrentIndex(0); break; } } void TitleWidget::enableToolbars(TITLETOOL toolType) { // TITLETOOL is defined in effectstack/graphicsscenerectmove.h bool enable = false; if (toolType == TITLE_RECTANGLE || toolType == TITLE_IMAGE) { enable = true; } value_w->setEnabled(enable); value_h->setEnabled(enable); } void TitleWidget::checkButton(TITLETOOL toolType) { bool bSelect = false; bool bText = false; bool bRect = false; bool bImage = false; switch (toolType) { case TITLE_SELECT: bSelect = true; break; case TITLE_TEXT: bText = true; break; case TITLE_RECTANGLE: bRect = true; break; case TITLE_IMAGE: bImage = true; break; default: break; } m_buttonCursor->setChecked(bSelect); m_buttonText->setChecked(bText); m_buttonRect->setChecked(bRect); m_buttonImage->setChecked(bImage); } void TitleWidget::displayBackgroundFrame() { QRectF r = m_frameBorder->sceneBoundingRect(); if (!displayBg->isChecked()) { QPixmap pattern(20, 20); pattern.fill(Qt::gray); QColor bgcolor(180, 180, 180); QPainter p(&pattern); p.fillRect(QRect(0, 0, 10, 10), bgcolor); p.fillRect(QRect(10, 10, 20, 20), bgcolor); p.end(); QBrush br(pattern); QPixmap bg((int)(r.width() / 2), (int)(r.height() / 2)); QPainter p2(&bg); p2.fillRect(bg.rect(), br); p2.end(); m_frameImage->setPixmap(bg); } else { emit requestBackgroundFrame(m_clipId, true); } } void TitleWidget::slotGotBackground(const QImage &img) { QRectF r = m_frameBorder->sceneBoundingRect(); m_frameImage->setPixmap(QPixmap::fromImage(img.scaled(r.width() / 2, r.height() / 2))); emit requestBackgroundFrame(m_clipId, false); } void TitleWidget::initAnimation() { align_box->setEnabled(false); QPen startpen(Qt::DotLine); QPen endpen(Qt::DashDotLine); startpen.setColor(QColor(100, 200, 100, 140)); endpen.setColor(QColor(200, 100, 100, 140)); m_startViewport->setPen(startpen); m_endViewport->setPen(endpen); m_startViewport->setZValue(-1000); m_endViewport->setZValue(-1000); m_startViewport->setFlags(nullptr); m_endViewport->setFlags(nullptr); graphicsView->scene()->addItem(m_startViewport); graphicsView->scene()->addItem(m_endViewport); connect(keep_aspect, &QAbstractButton::toggled, this, &TitleWidget::slotKeepAspect); connect(resize50, &QAbstractButton::clicked, this, &TitleWidget::slotResize50); connect(resize100, &QAbstractButton::clicked, this, &TitleWidget::slotResize100); connect(resize200, &QAbstractButton::clicked, this, &TitleWidget::slotResize200); } void TitleWidget::slotUpdateZoom(int pos) { zoom_spin->setValue(pos); zoom_slider->setValue(pos); m_scene->setZoom((double)pos / 100); } void TitleWidget::slotZoom(bool up) { int pos = zoom_slider->value(); if (up) { pos++; } else { pos--; } zoom_slider->setValue(pos); } void TitleWidget::slotAdjustZoom() { /*double scalex = graphicsView->width() / (double)(m_frameWidth * 1.2); double scaley = graphicsView->height() / (double)(m_frameHeight * 1.2); if (scalex > scaley) scalex = scaley; int zoompos = (int)(scalex * 7 + 0.5);*/ graphicsView->fitInView(m_frameBorder, Qt::KeepAspectRatio); int zoompos = graphicsView->matrix().m11() * 100; zoom_slider->setValue(zoompos); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotZoomOneToOne() { zoom_slider->setValue(100); graphicsView->centerOn(m_frameBorder); } void TitleWidget::slotNewRect(QGraphicsRectItem *rect) { updateAxisButtons(rect); // back to default if (rectLineWidth->value() == 0) { rect->setPen(Qt::NoPen); } else { QPen penf(rectFColor->color()); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rect->setPen(penf); } if (plain_rect->isChecked()) { rect->setBrush(QBrush(rectBColor->color())); rect->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rect->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rect->boundingRect().width(), rect->boundingRect().height()); rect->setBrush(QBrush(gr)); } rect->setZValue(m_count++); rect->setData(TitleDocument::ZoomFactor, 100); prepareTools(rect); // setCurrentItem(rect); // graphicsView->setFocus(); } void TitleWidget::slotNewText(MyTextItem *tt) { updateAxisButtons(tt); // back to default letter_spacing->blockSignals(true); line_spacing->blockSignals(true); letter_spacing->setValue(0); line_spacing->setValue(0); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); letter_spacing->setEnabled(true); line_spacing->setEnabled(true); QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); // mbd: issue 551: font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); tt->setFont(font); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); tt->setTextColor(color); tt->document()->setDocumentMargin(0); QTextCursor cur(tt->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); QTextCharFormat cformat = cur.charFormat(); double outlineWidth = textOutline->value() / 10.0; tt->setData(TitleDocument::OutlineWidth, outlineWidth); tt->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) { cformat.setTextOutline(QPen(outlineColor, outlineWidth)); } tt->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); if (gradient_color->isChecked()) { QString gradientData = gradients_combo->currentData().toString(); tt->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, tt->boundingRect().width(), tt->boundingRect().height()); cformat.setForeground(QBrush(gr)); } else { cformat.setForeground(QBrush(color)); } cur.setCharFormat(cformat); cur.setBlockFormat(format); tt->setTextCursor(cur); tt->setZValue(m_count++); setCurrentItem(tt); prepareTools(tt); } void TitleWidget::setFontBoxWeight(int weight) { int index = font_weight_box->findData(weight); if (index < 0) { index = font_weight_box->findData(QFont::Normal); } font_weight_box->setCurrentIndex(index); } void TitleWidget::setCurrentItem(QGraphicsItem *item) { m_scene->setSelectedItem(item); } void TitleWidget::zIndexChanged(int v) { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { l[i]->setZValue(v); } } void TitleWidget::selectionChanged() { if (m_scene->tool() != TITLE_SELECT) { return; } // qCDebug(KDENLIVE_LOG) << "Number of selected items: " << graphicsView->scene()->selectedItems().length() << '\n'; QList l; // mbt 1607: One text item might have grabbed the keyboard. // Ungrab it for all items that are not selected, otherwise // text input would only work for the text item that grabbed // the keyboard last. l = graphicsView->scene()->items(); for (QGraphicsItem *item : l) { if (item->type() == TEXTITEM && !item->isSelected()) { MyTextItem *i = static_cast(item); i->clearFocus(); } } l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { buttonUnselectAll->setEnabled(true); // Enable all z index buttons if items selected. // We can selectively disable them later. zUp->setEnabled(true); zDown->setEnabled(true); zTop->setEnabled(true); zBottom->setEnabled(true); } else { buttonUnselectAll->setEnabled(false); } if (l.size() >= 2) { buttonSelectText->setEnabled(true); buttonSelectRects->setEnabled(true); buttonSelectImages->setEnabled(true); } else { buttonSelectText->setEnabled(false); buttonSelectRects->setEnabled(false); buttonSelectImages->setEnabled(false); } if (l.size() == 0) { prepareTools(nullptr); } else if (l.size() == 1) { prepareTools(l.at(0)); } else { /* For multiple selected objects we need to decide which tools to show. */ int firstType = l.at(0)->type(); bool allEqual = true; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != firstType) { allEqual = false; break; } } // qCDebug(KDENLIVE_LOG) << "All equal? " << allEqual << ".\n"; if (allEqual) { prepareTools(l.at(0)); } else { // Get the default toolset, but enable the property frame (x,y,w,h) prepareTools(nullptr); frame_properties->setEnabled(true); // Enable x/y/w/h if it makes sense. value_x->setEnabled(true); value_y->setEnabled(true); bool containsTextitem = false; for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == TEXTITEM) { containsTextitem = true; break; } } if (!containsTextitem) { value_w->setEnabled(true); value_h->setEnabled(true); } } // Disable z index buttons if they don't make sense for the current selection int firstZindex = l.at(0)->zValue(); allEqual = true; for (int i = 0; i < l.size(); ++i) { if ((int)l[i]->zValue() != firstZindex) { allEqual = false; break; } } if (!allEqual) { zUp->setEnabled(false); zDown->setEnabled(false); } } } void TitleWidget::slotValueChanged(int type) { /* type tells us which QSpinBox value has changed. */ QList l = graphicsView->scene()->selectedItems(); // qCDebug(KDENLIVE_LOG) << l.size() << " items to be resized\n"; // Get the updated value here already to do less coding afterwards int val = 0; switch (type) { case ValueWidth: val = value_w->value(); break; case ValueHeight: val = value_h->value(); break; case ValueX: val = value_x->value(); break; case ValueY: val = value_y->value(); break; } for (int k = 0; k < l.size(); ++k) { // qCDebug(KDENLIVE_LOG) << "Type of item " << k << ": " << l.at(k)->type() << '\n'; if (l.at(k)->type() == TEXTITEM) { // Just update the position. We don't allow setting width/height for text items yet. switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; } } else if (l.at(k)->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(l.at(k)); switch (type) { case ValueX: updatePosition(l.at(k), val, l.at(k)->pos().y()); break; case ValueY: updatePosition(l.at(k), l.at(k)->pos().x(), val); break; case ValueWidth: rec->setRect(QRect(0, 0, val, rec->rect().height())); break; case ValueHeight: rec->setRect(QRect(0, 0, rec->rect().width(), val)); break; } } else if (l.at(k)->type() == IMAGEITEM) { if (type == ValueX) { updatePosition(l.at(k), val, l.at(k)->pos().y()); } else if (type == ValueY) { updatePosition(l.at(k), l.at(k)->pos().x(), val); } else { // Width/height has changed. This is more complex. QGraphicsItem *i = l.at(k); Transform t = m_transformations.value(i); // Ratio width:height double phi = (double)i->boundingRect().width() / i->boundingRect().height(); // TODO: proper calculation for rotation around 3 axes double alpha = (double)t.rotatez / 180.0 * M_PI; // New length double length; // Scaling factor double scale = 1; // We want to keep the aspect ratio of the image as the user does not yet have the possibility // to restore the original ratio. You rarely want to change it anyway. switch (type) { case ValueWidth: // Add 0.5 because otherwise incrementing by 1 might have no effect length = val / (cos(alpha) + 1 / phi * sin(alpha)) + 0.5; scale = length / i->boundingRect().width(); break; case ValueHeight: length = val / (phi * sin(alpha) + cos(alpha)) + 0.5; scale = length / i->boundingRect().height(); break; } t.scalex = scale; t.scaley = scale; QTransform qtrans; qtrans.scale(scale, scale); qtrans.rotate(t.rotatex, Qt::XAxis); qtrans.rotate(t.rotatey, Qt::YAxis); qtrans.rotate(t.rotatez, Qt::ZAxis); i->setTransform(qtrans); // qCDebug(KDENLIVE_LOG) << "scale is: " << scale << '\n'; // qCDebug(KDENLIVE_LOG) << i->boundingRect().width() << ": new width\n"; m_transformations[i] = t; if (l.size() == 1) { // Only update the w/h values if the selection contains just one item. // Otherwise, what should we do? ;) // (Use the values of the first item? Of the second? Of the x-th?) updateDimension(i); // Update rotation/zoom values. // These values are not yet able to handle multiple items! updateRotZoom(i); } } } } } void TitleWidget::updateDimension(QGraphicsItem *i) { bool wBlocked = value_w->signalsBlocked(); bool hBlocked = value_h->signalsBlocked(); bool zBlocked = zValue->signalsBlocked(); value_w->blockSignals(true); value_h->blockSignals(true); zValue->blockSignals(true); zValue->setValue((int)i->zValue()); if (i->type() == IMAGEITEM) { // Get multipliers for rotation/scaling /*Transform t = m_transformations.value(i); QRectF r = i->boundingRect(); int width = (int) ( abs(r.width()*t.scalex * cos(t.rotate/180.0*M_PI)) + abs(r.height()*t.scaley * sin(t.rotate/180.0*M_PI)) ); int height = (int) ( abs(r.height()*t.scaley * cos(t.rotate/180*M_PI)) + abs(r.width()*t.scalex * sin(t.rotate/180*M_PI)) );*/ value_w->setValue(i->sceneBoundingRect().width()); value_h->setValue(i->sceneBoundingRect().height()); } else if (i->type() == RECTITEM) { QGraphicsRectItem *r = static_cast(i); // qCDebug(KDENLIVE_LOG) << "Rect width is: " << r->rect().width() << ", was: " << value_w->value() << '\n'; value_w->setValue((int)r->rect().width()); value_h->setValue((int)r->rect().height()); } else if (i->type() == TEXTITEM) { MyTextItem *t = static_cast(i); value_w->setValue((int)t->boundingRect().width()); value_h->setValue((int)t->boundingRect().height()); } zValue->blockSignals(zBlocked); value_w->blockSignals(wBlocked); value_h->blockSignals(hBlocked); } void TitleWidget::updateCoordinates(QGraphicsItem *i) { // Block signals emitted by this method value_x->blockSignals(true); value_y->blockSignals(true); if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast(i); // Set the correct x coordinate value if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth, coordinate axis is inverted value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->boundingRect().width())); } else { // Origin is at 0 (default) value_x->setValue((int)rec->pos().x()); } // Same for y if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->boundingRect().height())); } else { value_y->setValue((int)rec->pos().y()); } } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(i); if (origin_x_left->isChecked()) { // Origin (0 point) is at m_frameWidth value_x->setValue((int)(m_frameWidth - rec->pos().x() - rec->rect().width())); } else { // Origin is at 0 (default) value_x->setValue((int)rec->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - rec->pos().y() - rec->rect().height())); } else { value_y->setValue((int)rec->pos().y()); } } else if (i->type() == IMAGEITEM) { if (origin_x_left->isChecked()) { value_x->setValue((int)(m_frameWidth - i->pos().x() - i->sceneBoundingRect().width())); } else { value_x->setValue((int)i->pos().x()); } if (origin_y_top->isChecked()) { value_y->setValue((int)(m_frameHeight - i->pos().y() - i->sceneBoundingRect().height())); } else { value_y->setValue((int)i->pos().y()); } } // Stop blocking signals now value_x->blockSignals(false); value_y->blockSignals(false); } void TitleWidget::updateRotZoom(QGraphicsItem *i) { itemzoom->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); Transform t = m_transformations.value(i); if (!i->data(TitleDocument::ZoomFactor).isNull()) { itemzoom->setValue(i->data(TitleDocument::ZoomFactor).toInt()); } else { itemzoom->setValue((int)(t.scalex * 100.0 + 0.5)); } itemrotatex->setValue((int)(t.rotatex)); itemrotatey->setValue((int)(t.rotatey)); itemrotatez->setValue((int)(t.rotatez)); itemzoom->blockSignals(false); itemrotatex->blockSignals(false); itemrotatey->blockSignals(false); itemrotatez->blockSignals(false); } void TitleWidget::updatePosition(QGraphicsItem *i) { updatePosition(i, value_x->value(), value_y->value()); } void TitleWidget::updatePosition(QGraphicsItem *i, int x, int y) { if (i->type() == TEXTITEM) { MyTextItem *rec = static_cast(i); int posX; if (origin_x_left->isChecked()) { /* * Origin of the X axis is at m_frameWidth, and distance from right * border of the item to the right border of the frame is taken. See * comment to slotOriginXClicked(). */ posX = m_frameWidth - x - rec->boundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { /* Same for y axis */ posY = m_frameHeight - y - rec->boundingRect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == RECTITEM) { QGraphicsRectItem *rec = static_cast(i); int posX; if (origin_x_left->isChecked()) { posX = m_frameWidth - x - rec->rect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - rec->rect().height(); } else { posY = y; } rec->setPos(posX, posY); } else if (i->type() == IMAGEITEM) { int posX; if (origin_x_left->isChecked()) { // Use the sceneBoundingRect because this also regards transformations like zoom posX = m_frameWidth - x - i->sceneBoundingRect().width(); } else { posX = x; } int posY; if (origin_y_top->isChecked()) { posY = m_frameHeight - y - i->sceneBoundingRect().height(); } else { posY = y; } i->setPos(posX, posY); } } void TitleWidget::updateTextOriginX() { if (origin_x_left->isChecked()) { origin_x_left->setText(i18n("\u2212X")); } else { origin_x_left->setText(i18n("+X")); } } void TitleWidget::slotOriginXClicked() { // Update the text displayed on the button. updateTextOriginX(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); // Remember x axis setting l.at(0)->setData(TitleDocument::OriginXLeft, origin_x_left->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateTextOriginY() { if (origin_y_top->isChecked()) { origin_y_top->setText(i18n("\u2212Y")); } else { origin_y_top->setText(i18n("+Y")); } } void TitleWidget::slotOriginYClicked() { // Update the text displayed on the button. updateTextOriginY(); QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { updateCoordinates(l.at(0)); l.at(0)->setData(TitleDocument::OriginYTop, origin_y_top->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault); } graphicsView->setFocus(); } void TitleWidget::updateAxisButtons(QGraphicsItem *i) { int xAxis = i->data(TitleDocument::OriginXLeft).toInt(); int yAxis = i->data(TitleDocument::OriginYTop).toInt(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); if (xAxis == TitleDocument::AxisInverted) { origin_x_left->setChecked(true); } else { origin_x_left->setChecked(false); } updateTextOriginX(); if (yAxis == TitleDocument::AxisInverted) { origin_y_top->setChecked(true); } else { origin_y_top->setChecked(false); } updateTextOriginY(); origin_x_left->blockSignals(false); origin_y_top->blockSignals(false); } void TitleWidget::slotChangeBackground() { QColor color = backgroundColor->color(); m_scene->setBackgroundBrush(QBrush(color)); color.setAlpha(backgroundAlpha->value()); m_frameBackground->setBrush(QBrush(color)); } void TitleWidget::slotChanged() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1 && l.at(0)->type() == TEXTITEM) { textChanged(static_cast(l.at(0))); } } void TitleWidget::textChanged(MyTextItem *i) { /* * If the user has set origin_x_left (the same for y), we need to look * whether a text element has been selected. If yes, we need to ensure that * the right border of the text field remains fixed also when some text has * been entered. * * This is also known as right-justified, with the difference that it is not * valid for text but for its boundingRect. Text may still be * left-justified. */ updateDimension(i); if (origin_x_left->isChecked() || origin_y_top->isChecked()) { if (!i->document()->isEmpty()) { updatePosition(i); } else { /* * Don't do anything if the string is empty. If the position were * updated here, a newly created text field would be set to the * position of the last selected text field. */ } } // mbt 1607: Template text has changed; don't auto-select content anymore. if (i->property("isTemplate").isValid()) { if (i->property("templateText").isValid()) { if (i->property("templateText") == i->toHtml()) { // Unchanged, do nothing. } else { i->setProperty("isTemplate", QVariant::Invalid); i->setProperty("templateText", QVariant::Invalid); } } } } void TitleWidget::slotInsertUnicode() { m_unicodeDialog->exec(); } void TitleWidget::slotInsertUnicodeString(const QString &string) { const QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { if (l.at(0)->type() == TEXTITEM) { MyTextItem *t = static_cast(l.at(0)); t->textCursor().insertText(string); } } } void TitleWidget::slotUpdateText() { QFont font = font_family->currentFont(); font.setPixelSize(font_size->value()); font.setItalic(buttonItalic->isChecked()); font.setUnderline(buttonUnder->isChecked()); font.setWeight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing->value()); QColor color = fontColorButton->color(); QColor outlineColor = textOutlineColor->color(); QString gradientData; if (gradient_color->isChecked()) { // user wants a gradient gradientData = gradients_combo->currentData().toString(); } double outlineWidth = textOutline->value() / 10.0; int i; QList l = graphicsView->scene()->selectedItems(); for (i = 0; i < l.length(); ++i) { MyTextItem *item = nullptr; if (l.at(i)->type() == TEXTITEM) { item = static_cast(l.at(i)); } if (!item) { // No text item, try next one. continue; } // Set alignment of all text in the text item QTextCursor cur(item->document()); cur.select(QTextCursor::Document); QTextBlockFormat format = cur.blockFormat(); item->setData(TitleDocument::LineSpacing, line_spacing->value()); format.setLineHeight(line_spacing->value(), QTextBlockFormat::LineDistanceHeight); if (buttonAlignLeft->isChecked() || buttonAlignCenter->isChecked() || buttonAlignRight->isChecked()) { if (buttonAlignCenter->isChecked()) { item->setAlignment(Qt::AlignHCenter); } else if (buttonAlignRight->isChecked()) { item->setAlignment(Qt::AlignRight); } else if (buttonAlignLeft->isChecked()) { item->setAlignment(Qt::AlignLeft); } } else { item->setAlignment(Qt::AlignLeft); } // Set font properties item->setFont(font); QTextCharFormat cformat = cur.charFormat(); item->setData(TitleDocument::OutlineWidth, outlineWidth); item->setData(TitleDocument::OutlineColor, outlineColor); if (outlineWidth > 0.0) { cformat.setTextOutline(QPen(outlineColor, outlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); } if (gradientData.isEmpty()) { cformat.setForeground(QBrush(color)); } else { QLinearGradient gr = GradientWidget::gradientFromString(gradientData, item->boundingRect().width(), item->boundingRect().height()); cformat.setForeground(QBrush(gr)); } // Store gradient in item properties item->setData(TitleDocument::Gradient, gradientData); cur.setCharFormat(cformat); cur.setBlockFormat(format); // item->setTextCursor(cur); cur.clearSelection(); item->setTextCursor(cur); item->setTextColor(color); } } void TitleWidget::rectChanged() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.length(); ++i) { if (l.at(i)->type() == RECTITEM && (settingUp == 0)) { QGraphicsRectItem *rec = static_cast(l.at(i)); QColor f = rectFColor->color(); if (rectLineWidth->value() == 0) { rec->setPen(Qt::NoPen); } else { QPen penf(f); penf.setWidth(rectLineWidth->value()); penf.setJoinStyle(Qt::RoundJoin); rec->setPen(penf); } if (plain_rect->isChecked()) { rec->setBrush(QBrush(rectBColor->color())); rec->setData(TitleDocument::Gradient, QVariant()); } else { // gradient QString gradientData = gradients_rect_combo->currentData().toString(); rec->setData(TitleDocument::Gradient, gradientData); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, rec->boundingRect().width(), rec->boundingRect().height()); rec->setBrush(QBrush(gr)); } } } } void TitleWidget::itemScaled(int val) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations.value(l.at(0)); x.scalex = (double)val / 100.0; x.scaley = (double)val / 100.0; QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); l[0]->setData(TitleDocument::ZoomFactor, val); m_transformations[l.at(0)] = x; updateDimension(l.at(0)); } } void TitleWidget::itemRotateX(int val) { itemRotate(val, 0); } void TitleWidget::itemRotateY(int val) { itemRotate(val, 1); } void TitleWidget::itemRotateZ(int val) { itemRotate(val, 2); } void TitleWidget::itemRotate(int val, int axis) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { Transform x = m_transformations[l.at(0)]; switch (axis) { case 0: x.rotatex = val; break; case 1: x.rotatey = val; break; case 2: x.rotatez = val; break; } l[0]->setData(TitleDocument::RotateFactor, QList() << QVariant(x.rotatex) << QVariant(x.rotatey) << QVariant(x.rotatez)); QTransform qtrans; qtrans.scale(x.scalex, x.scaley); qtrans.rotate(x.rotatex, Qt::XAxis); qtrans.rotate(x.rotatey, Qt::YAxis); qtrans.rotate(x.rotatez, Qt::ZAxis); l[0]->setTransform(qtrans); m_transformations[l.at(0)] = x; if (l[0]->data(TitleDocument::ZoomFactor).isNull()) { l[0]->setData(TitleDocument::ZoomFactor, 100); } updateDimension(l.at(0)); } } void TitleWidget::itemHCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int width = (int)br.width(); int newPos = (int)((m_frameWidth - width) / 2); newPos += item->pos().x() - br.left(); // Check item transformation item->setPos(newPos, item->pos().y()); updateCoordinates(item); } } void TitleWidget::itemVCenter() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); int height = (int)br.height(); int newPos = (int)((m_frameHeight - height) / 2); newPos += item->pos().y() - br.top(); // Check item transformation item->setPos(item->pos().x(), newPos); updateCoordinates(item); } } void TitleWidget::itemTop() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.top() > 0) { diff = -br.top(); } else { diff = -br.bottom(); } item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemBottom() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.bottom() > m_frameHeight) { diff = m_frameHeight - br.top(); } else { diff = m_frameHeight - br.bottom(); } item->moveBy(0, diff); updateCoordinates(item); } } void TitleWidget::itemLeft() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.left() > 0) { diff = -br.left(); } else { diff = -br.right(); } item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::itemRight() { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QGraphicsItem *item = l.at(0); QRectF br = item->sceneBoundingRect(); double diff; if (br.right() < m_frameWidth) { diff = m_frameWidth - br.right(); } else { diff = m_frameWidth - br.left(); } item->moveBy(diff, 0); updateCoordinates(item); } } void TitleWidget::loadTitle(QUrl url) { if (!url.isValid()) { url = QFileDialog::getOpenFileUrl(this, i18n("Load Title"), QUrl::fromLocalFile(m_projectTitlePath), i18n("Kdenlive title (*.kdenlivetitle)")); } if (url.isValid()) { QList items = m_scene->items(); items.removeAll(m_frameBorder); items.removeAll(m_frameBackground); items.removeAll(m_frameImage); for (int i = 0; i < items.size(); ++i) { if (items.at(i)->zValue() > -1000) { delete items.at(i); } } m_scene->clearTextSelection(); QDomDocument doc; QFile file(url.toLocalFile()); doc.setContent(&file, false); file.close(); setXml(doc); } } void TitleWidget::saveTitle(QUrl url) { if (anim_start->isChecked()) { slotAnimStart(false); } if (anim_end->isChecked()) { slotAnimEnd(false); } bool embed_image = false; // If we have images in the title, ask for embed QList list = graphicsView->scene()->items(); QGraphicsPixmapItem pix; int pixmapType = pix.type(); for (const QGraphicsItem *item : list) { if (item->type() == pixmapType && item != m_frameImage) { embed_image = true; break; } } - if (embed_image && - KMessageBox::questionYesNo(this, i18n("Do you want to embed Images into this TitleDocument?\nThis is most needed for sharing Titles.")) != - KMessageBox::Yes) { + if (embed_image && KMessageBox::questionYesNo( + this, i18n("Do you want to embed Images into this TitleDocument?\nThis is most needed for sharing Titles.")) != KMessageBox::Yes) { embed_image = false; } if (!url.isValid()) { QPointer fs = new QFileDialog(this, i18n("Save Title"), m_projectTitlePath); fs->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); fs->setFileMode(QFileDialog::AnyFile); fs->setAcceptMode(QFileDialog::AcceptSave); fs->setDefaultSuffix(QStringLiteral("kdenlivetitle")); // TODO: KF5 porting? // fs->setConfirmOverwrite(true); // fs->setKeepLocation(true); if ((fs->exec() != 0) && !fs->selectedUrls().isEmpty()) { url = fs->selectedUrls().constFirst(); } delete fs; } if (url.isValid()) { if (!m_titledocument.saveDocument(url, m_startViewport, m_endViewport, m_tc.getFrameCount(title_duration->text()), embed_image)) { KMessageBox::error(this, i18n("Cannot write to file %1", url.toLocalFile())); } } } QDomDocument TitleWidget::xml() { QDomDocument doc = m_titledocument.xml(m_startViewport, m_endViewport); doc.documentElement().setAttribute(QStringLiteral("duration"), m_tc.getFrameCount(title_duration->text())); doc.documentElement().setAttribute(QStringLiteral("out"), m_tc.getFrameCount(title_duration->text())); return doc; } int TitleWidget::duration() const { return m_tc.getFrameCount(title_duration->text()); } void TitleWidget::setXml(const QDomDocument &doc, const QString &id) { m_clipId = id; int duration; m_count = m_titledocument.loadFromXml(doc, m_startViewport, m_endViewport, &duration, m_projectTitlePath); adjustFrameSize(); title_duration->setText(m_tc.getTimecode(GenTime(duration, m_fps))); /*if (doc.documentElement().hasAttribute("out")) { GenTime duration = GenTime(doc.documentElement().attribute("out").toDouble() / 1000.0); title_duration->setText(m_tc.getTimecode(duration)); } else title_duration->setText(m_tc.getTimecode(GenTime(5000)));*/ QDomElement e = doc.documentElement(); m_transformations.clear(); QList items = graphicsView->scene()->items(); const double PI = 4.0 * atan(1.0); for (int i = 0; i < items.count(); ++i) { QTransform t = items.at(i)->transform(); Transform x; x.scalex = t.m11(); x.scaley = t.m22(); if (!items.at(i)->data(TitleDocument::RotateFactor).isNull()) { QList rotlist = items.at(i)->data(TitleDocument::RotateFactor).toList(); if (rotlist.count() >= 3) { x.rotatex = rotlist[0].toDouble(); x.rotatey = rotlist[1].toDouble(); x.rotatez = rotlist[2].toDouble(); // Try to adjust zoom t.rotate(x.rotatex * (-1), Qt::XAxis); t.rotate(x.rotatey * (-1), Qt::YAxis); t.rotate(x.rotatez * (-1), Qt::ZAxis); x.scalex = t.m11(); x.scaley = t.m22(); } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 0; } } else { x.rotatex = 0; x.rotatey = 0; x.rotatez = 180. / PI * atan2(-t.m21(), t.m11()); } m_transformations[items.at(i)] = x; } // mbd: Update the GUI color selectors to match the stuff from the loaded document QColor background_color = m_titledocument.getBackgroundColor(); backgroundAlpha->blockSignals(true); backgroundColor->blockSignals(true); backgroundAlpha->setValue(background_color.alpha()); background_color.setAlpha(255); backgroundColor->setColor(background_color); backgroundAlpha->blockSignals(false); backgroundColor->blockSignals(false); /*startViewportX->setValue(m_startViewport->data(0).toInt()); startViewportY->setValue(m_startViewport->data(1).toInt()); startViewportSize->setValue(m_startViewport->data(2).toInt()); endViewportX->setValue(m_endViewport->data(0).toInt()); endViewportY->setValue(m_endViewport->data(1).toInt()); endViewportSize->setValue(m_endViewport->data(2).toInt());*/ QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom); slotSelectTool(); selectionChanged(); } void TitleWidget::slotAccepted() { if (anim_start->isChecked()) { slotAnimStart(false); } if (anim_end->isChecked()) { slotAnimEnd(false); } writeChoices(); } void TitleWidget::writeChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // Write the entries titleConfig.writeEntry("dialog_geometry", saveGeometry().toBase64()); titleConfig.writeEntry("font_family", font_family->currentFont()); // titleConfig.writeEntry("font_size", font_size->value()); titleConfig.writeEntry("font_pixel_size", font_size->value()); titleConfig.writeEntry("font_color", fontColorButton->color()); titleConfig.writeEntry("font_outline_color", textOutlineColor->color()); titleConfig.writeEntry("font_outline", textOutline->value()); titleConfig.writeEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); titleConfig.writeEntry("font_italic", buttonItalic->isChecked()); titleConfig.writeEntry("font_underlined", buttonUnder->isChecked()); titleConfig.writeEntry("rect_background_color", rectBColor->color()); titleConfig.writeEntry("rect_foreground_color", rectFColor->color()); titleConfig.writeEntry("rect_background_alpha", rectBColor->color().alpha()); titleConfig.writeEntry("rect_foreground_alpha", rectFColor->color().alpha()); titleConfig.writeEntry("rect_line_width", rectLineWidth->value()); titleConfig.writeEntry("background_color", backgroundColor->color()); titleConfig.writeEntry("background_alpha", backgroundAlpha->value()); titleConfig.writeEntry("use_grid", use_grid->isChecked()); //! \todo Not sure if I should sync - it is probably safe to do it config->sync(); } void TitleWidget::readChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // read the entries const QByteArray geometry = titleConfig.readEntry("dialog_geometry", QByteArray()); restoreGeometry(QByteArray::fromBase64(geometry)); font_family->setCurrentFont(titleConfig.readEntry("font_family", font_family->currentFont())); font_size->setValue(titleConfig.readEntry("font_pixel_size", font_size->value())); m_scene->slotUpdateFontSize(font_size->value()); QColor fontColor = QColor(titleConfig.readEntry("font_color", fontColorButton->color())); QColor outlineColor = QColor(titleConfig.readEntry("font_outline_color", textOutlineColor->color())); fontColor.setAlpha(titleConfig.readEntry("font_alpha", fontColor.alpha())); outlineColor.setAlpha(titleConfig.readEntry("font_outline_alpha", outlineColor.alpha())); fontColorButton->setColor(fontColor); textOutlineColor->setColor(outlineColor); textOutline->setValue(titleConfig.readEntry("font_outline", textOutline->value())); int weight; if (titleConfig.readEntry("font_bold", false)) { weight = QFont::Bold; } else { weight = titleConfig.readEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt()); } setFontBoxWeight(weight); buttonItalic->setChecked(titleConfig.readEntry("font_italic", buttonItalic->isChecked())); buttonUnder->setChecked(titleConfig.readEntry("font_underlined", buttonUnder->isChecked())); QColor fgColor = QColor(titleConfig.readEntry("rect_foreground_color", rectFColor->color())); QColor bgColor = QColor(titleConfig.readEntry("rect_background_color", rectBColor->color())); fgColor.setAlpha(titleConfig.readEntry("rect_foreground_alpha", fgColor.alpha())); bgColor.setAlpha(titleConfig.readEntry("rect_background_alpha", bgColor.alpha())); rectFColor->setColor(fgColor); rectBColor->setColor(bgColor); rectLineWidth->setValue(titleConfig.readEntry("rect_line_width", rectLineWidth->value())); backgroundColor->setColor(titleConfig.readEntry("background_color", backgroundColor->color())); backgroundAlpha->setValue(titleConfig.readEntry("background_alpha", backgroundAlpha->value())); use_grid->setChecked(titleConfig.readEntry("use_grid", false)); m_scene->slotUseGrid(use_grid->isChecked()); } void TitleWidget::adjustFrameSize() { m_frameWidth = m_titledocument.frameWidth(); m_frameHeight = m_titledocument.frameHeight(); m_frameBorder->setRect(0, 0, m_frameWidth, m_frameHeight); displayBackgroundFrame(); } void TitleWidget::slotAnimStart(bool anim) { if (anim && anim_end->isChecked()) { anim_end->setChecked(false); m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { if (!list.at(i)->data(-1).isNull()) { continue; } list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_startViewport->data(0).isNull()); m_startViewport->setZValue(1100); QColor col = m_startViewport->pen().color(); col.setAlpha(100); m_startViewport->setBrush(col); m_startViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_startViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_startViewport->childItems().isEmpty()) { addAnimInfoText(); } } else { m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); m_startViewport->setFlags(nullptr); if (!anim_end->isChecked()) { deleteAnimInfoText(); } } } void TitleWidget::slotAnimEnd(bool anim) { if (anim && anim_start->isChecked()) { anim_start->setChecked(false); m_startViewport->setZValue(-1000); m_startViewport->setBrush(QBrush()); } slotSelectTool(); QList list = m_scene->items(); for (int i = 0; i < list.count(); ++i) { if (list.at(i)->zValue() > -1000) { if (!list.at(i)->data(-1).isNull()) { continue; } list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim); list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim); } } align_box->setEnabled(anim); itemzoom->setEnabled(!anim); itemrotatex->setEnabled(!anim); itemrotatey->setEnabled(!anim); itemrotatez->setEnabled(!anim); frame_toolbar->setEnabled(!anim); toolbar_stack->setEnabled(!anim); if (anim) { keep_aspect->setChecked(!m_endViewport->data(0).isNull()); m_endViewport->setZValue(1100); QColor col = m_endViewport->pen().color(); col.setAlpha(100); m_endViewport->setBrush(col); m_endViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable); m_endViewport->setSelected(true); selectionChanged(); slotSelectTool(); if (m_endViewport->childItems().isEmpty()) { addAnimInfoText(); } } else { m_endViewport->setZValue(-1000); m_endViewport->setBrush(QBrush()); m_endViewport->setFlags(nullptr); if (!anim_start->isChecked()) { deleteAnimInfoText(); } } } void TitleWidget::addAnimInfoText() { // add text to anim viewport QGraphicsTextItem *t = new QGraphicsTextItem(i18nc("Indicates the start of an animation", "Start"), m_startViewport); QGraphicsTextItem *t2 = new QGraphicsTextItem(i18nc("Indicates the end of an animation", "End"), m_endViewport); QFont font = t->font(); font.setPixelSize(m_startViewport->rect().width() / 10); QColor col = m_startViewport->pen().color(); col.setAlpha(255); t->setDefaultTextColor(col); t->setFont(font); font.setPixelSize(m_endViewport->rect().width() / 10); col = m_endViewport->pen().color(); col.setAlpha(255); t2->setDefaultTextColor(col); t2->setFont(font); } void TitleWidget::updateInfoText() { // update info text font if (!m_startViewport->childItems().isEmpty()) { MyTextItem *item = static_cast(m_startViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_startViewport->rect().width() / 10); item->setFont(font); } } if (!m_endViewport->childItems().isEmpty()) { MyTextItem *item = static_cast(m_endViewport->childItems().at(0)); if (item) { QFont font = item->font(); font.setPixelSize(m_endViewport->rect().width() / 10); item->setFont(font); } } } void TitleWidget::deleteAnimInfoText() { // end animation editing, remove info text while (!m_startViewport->childItems().isEmpty()) { QGraphicsItem *item = m_startViewport->childItems().at(0); if (m_scene) { m_scene->removeItem(item); } } while (!m_endViewport->childItems().isEmpty()) { QGraphicsItem *item = m_endViewport->childItems().at(0); if (m_scene) { m_scene->removeItem(item); } } } void TitleWidget::slotKeepAspect(bool keep) { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setData(0, keep ? m_frameWidth : QVariant()); m_endViewport->setData(1, keep ? m_frameHeight : QVariant()); } else { m_startViewport->setData(0, keep ? m_frameWidth : QVariant()); m_startViewport->setData(1, keep ? m_frameHeight : QVariant()); } } void TitleWidget::slotResize50() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } else { m_startViewport->setRect(0, 0, m_frameWidth / 2, m_frameHeight / 2); } } void TitleWidget::slotResize100() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } else { m_startViewport->setRect(0, 0, m_frameWidth, m_frameHeight); } } void TitleWidget::slotResize200() { if ((int)m_endViewport->zValue() == 1100) { m_endViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } else { m_startViewport->setRect(0, 0, m_frameWidth * 2, m_frameHeight * 2); } } void TitleWidget::slotAddEffect(int /*ix*/) { QList list = graphicsView->scene()->selectedItems(); /* int effect = effect_list->itemData(ix).toInt(); if (list.size() == 1) { if (effect == NOEFFECT) effect_stack->setHidden(true); else { effect_stack->setCurrentIndex(effect - 1); effect_stack->setHidden(false); } } else // Hide the effects stack when more than one element is selected. effect_stack->setHidden(true); for (QGraphicsItem * item : list) { switch (effect) { case NOEFFECT: item->setData(100, QVariant()); item->setGraphicsEffect(0); break; case TYPEWRITEREFFECT: if (item->type() == TEXTITEM) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + QLatin1Char(';') + QString::number(typewriter_start->value()); item->setData(100, effdata); } break; // Do not remove the non-QGraphicsEffects. case BLUREFFECT: item->setGraphicsEffect(new QGraphicsBlurEffect()); break; case SHADOWEFFECT: item->setGraphicsEffect(new QGraphicsDropShadowEffect()); break; } }*/ } void TitleWidget::slotEditTypewriter(int /*ix*/) { QList l = graphicsView->scene()->selectedItems(); if (l.size() == 1) { QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + QLatin1Char(';') + QString::number(typewriter_start->value()); l[0]->setData(100, effdata); } } qreal TitleWidget::zIndexBounds(bool maxBound, bool intersectingOnly) { qreal bound = maxBound ? -99 : 99; const QList l = graphicsView->scene()->selectedItems(); if (!l.isEmpty()) { QList lItems; // Get items (all or intersecting only) if (intersectingOnly) { lItems = graphicsView->scene()->items(l[0]->sceneBoundingRect(), Qt::IntersectsItemShape); } else { lItems = graphicsView->scene()->items(); } if (!lItems.isEmpty()) { int n = lItems.size(); qreal z; if (maxBound) { for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z > bound && !lItems[i]->isSelected()) { bound = z; } else if (z - 1 > bound) { // To get the maximum index even if it is of an item of the current selection. // Used when updating multiple items, to get all to the same level. // Otherwise, the maximum would stay at -99 if the highest item is in the selection. bound = z - 1; } } } else { // Get minimum z index. for (int i = 0; i < n; ++i) { z = lItems[i]->zValue(); if (z < bound && !lItems[i]->isSelected() && z > -999) { // There are items at the very bottom (background e.g.) with z-index < -1000. bound = z; } else if (z + 1 < bound && z > -999) { bound = z + 1; } } } } } return bound; } void TitleWidget::slotZIndexUp() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal max = zIndexBounds(true, true); if (currentZ <= max) { l[0]->setZValue(currentZ + 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexTop() { QList l = graphicsView->scene()->selectedItems(); qreal max = zIndexBounds(true, false); // qCDebug(KDENLIVE_LOG) << "Max z-index is " << max << ".\n"; for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ <= max) { // qCDebug(KDENLIVE_LOG) << "Updating item " << i << ", is " << currentZ << ".\n"; l[i]->setZValue(max + 1); } else { // qCDebug(KDENLIVE_LOG) << "Not updating " << i << ", is " << currentZ << ".\n"; } } // Update the z index value in the GUI if (!l.isEmpty()) { updateDimension(l[0]); } } void TitleWidget::slotZIndexDown() { QList l = graphicsView->scene()->selectedItems(); if (l.size() >= 1) { qreal currentZ = l[0]->zValue(); qreal min = zIndexBounds(false, true); if (currentZ >= min) { l[0]->setZValue(currentZ - 1); updateDimension(l[0]); } } } void TitleWidget::slotZIndexBottom() { QList l = graphicsView->scene()->selectedItems(); qreal min = zIndexBounds(false, false); for (int i = 0; i < l.size(); ++i) { qreal currentZ = l[i]->zValue(); if (currentZ >= min) { l[i]->setZValue(min - 1); } } // Update the z index value in the GUI if (!l.isEmpty()) { updateDimension(l[0]); } } void TitleWidget::slotSelectAll() { QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(true); } } void TitleWidget::selectItems(int itemType) { QList l; if (!graphicsView->scene()->selectedItems().isEmpty()) { l = graphicsView->scene()->selectedItems(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() != itemType) { l.at(i)->setSelected(false); } } } else { l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { if (l.at(i)->type() == itemType) { l.at(i)->setSelected(true); } } } } void TitleWidget::slotSelectText() { selectItems(TEXTITEM); } void TitleWidget::slotSelectRects() { selectItems(RECTITEM); } void TitleWidget::slotSelectImages() { selectItems(IMAGEITEM); } void TitleWidget::slotSelectNone() { graphicsView->blockSignals(true); QList l = graphicsView->scene()->items(); for (int i = 0; i < l.size(); ++i) { l.at(i)->setSelected(false); } graphicsView->blockSignals(false); selectionChanged(); } QString TitleWidget::getTooltipWithShortcut(const QString &tipText, QAction *button) { return tipText + QStringLiteral(" ") + button->shortcut().toString() + QStringLiteral(""); } void TitleWidget::prepareTools(QGraphicsItem *referenceItem) { // Let some GUI elements block signals. We may want to change their values without any sideeffects. // Additionally, store the previous blocking state to avoid side effects when this function is called from within another one. // Note: Disabling an element also blocks signals. So disabled elements don't need to be set to blocking too. bool blockOX = origin_x_left->signalsBlocked(); bool blockOY = origin_y_top->signalsBlocked(); bool blockEff = effect_list->signalsBlocked(); bool blockRX = itemrotatex->signalsBlocked(); bool blockRY = itemrotatey->signalsBlocked(); bool blockRZ = itemrotatez->signalsBlocked(); bool blockZoom = itemzoom->signalsBlocked(); bool blockX = value_x->signalsBlocked(); bool blockY = value_y->signalsBlocked(); bool blockW = value_w->signalsBlocked(); bool blockH = value_h->signalsBlocked(); origin_x_left->blockSignals(true); origin_y_top->blockSignals(true); effect_list->blockSignals(true); itemrotatex->blockSignals(true); itemrotatey->blockSignals(true); itemrotatez->blockSignals(true); itemzoom->blockSignals(true); value_x->blockSignals(true); value_y->blockSignals(true); value_w->blockSignals(true); value_h->blockSignals(true); if (referenceItem == nullptr) { // qCDebug(KDENLIVE_LOG) << "nullptr item.\n"; effect_list->setCurrentIndex(0); origin_x_left->setChecked(false); origin_y_top->setChecked(false); updateTextOriginX(); updateTextOriginY(); enableToolbars(TITLE_SELECT); showToolbars(TITLE_SELECT); itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); frame_properties->setEnabled(false); toolbar_stack->setEnabled(false); /*letter_spacing->setEnabled(false); line_spacing->setEnabled(false); letter_spacing->setValue(0); line_spacing->setValue(0);*/ } else { toolbar_stack->setEnabled(true); frame_properties->setEnabled(true); if (referenceItem != m_startViewport && referenceItem != m_endViewport) { itemzoom->setEnabled(true); itemrotatex->setEnabled(true); itemrotatey->setEnabled(true); itemrotatez->setEnabled(true); } else { itemzoom->setEnabled(false); itemrotatex->setEnabled(false); itemrotatey->setEnabled(false); itemrotatez->setEnabled(false); updateInfoText(); } letter_spacing->setEnabled(referenceItem->type() == TEXTITEM); line_spacing->setEnabled(referenceItem->type() == TEXTITEM); if (referenceItem->type() == TEXTITEM) { showToolbars(TITLE_TEXT); MyTextItem *i = static_cast(referenceItem); if (!i->document()->isEmpty()) { // We have an existing text item selected if (!i->data(100).isNull()) { // Item has an effect QStringList effdata = i->data(100).toStringList(); QString effectName = effdata.takeFirst(); if (effectName == QLatin1String("typewriter")) { QStringList params = effdata.at(0).split(QLatin1Char(';')); typewriter_delay->setValue(params.at(0).toInt()); typewriter_start->setValue(params.at(1).toInt()); effect_list->setCurrentIndex(effect_list->findData((int)TYPEWRITEREFFECT)); } } else { /*if (i->graphicsEffect()) { QGraphicsBlurEffect *blur = static_cast (i->graphicsEffect()); if (blur) { effect_list->setCurrentIndex(effect_list->findData((int) BLUREFFECT)); int rad = (int) blur->blurRadius(); blur_radius->setValue(rad); effect_stack->setHidden(false); } else { QGraphicsDropShadowEffect *shad = static_cast (i->graphicsEffect()); if (shad) { effect_list->setCurrentIndex(effect_list->findData((int) SHADOWEFFECT)); shadow_radius->setValue(shad->blurRadius()); shadow_x->setValue(shad->xOffset()); shadow_y->setValue(shad->yOffset()); effect_stack->setHidden(false); } } } else { effect_list->setCurrentIndex(effect_list->findData((int) NOEFFECT)); effect_stack->setHidden(true); }*/ } font_size->blockSignals(true); font_family->blockSignals(true); font_weight_box->blockSignals(true); buttonItalic->blockSignals(true); buttonUnder->blockSignals(true); fontColorButton->blockSignals(true); buttonAlignLeft->blockSignals(true); buttonAlignRight->blockSignals(true); buttonAlignCenter->blockSignals(true); QFont font = i->font(); font_family->setCurrentFont(font); font_size->setValue(font.pixelSize()); m_scene->slotUpdateFontSize(font.pixelSize()); buttonItalic->setChecked(font.italic()); buttonUnder->setChecked(font.underline()); setFontBoxWeight(font.weight()); QTextCursor cursor(i->document()); cursor.select(QTextCursor::Document); QColor color = cursor.charFormat().foreground().color(); fontColorButton->setColor(color); if (!i->data(TitleDocument::OutlineWidth).isNull()) { textOutline->blockSignals(true); textOutline->setValue(i->data(TitleDocument::OutlineWidth).toDouble() * 10); textOutline->blockSignals(false); } else { textOutline->blockSignals(true); textOutline->setValue(0); textOutline->blockSignals(false); } if (!i->data(TitleDocument::OutlineColor).isNull()) { textOutlineColor->blockSignals(true); QVariant variant = i->data(TitleDocument::OutlineColor); color = variant.value(); textOutlineColor->setColor(color); textOutlineColor->blockSignals(false); } if (!i->data(TitleDocument::Gradient).isNull()) { gradients_combo->blockSignals(true); gradient_color->setChecked(true); QString gradientData = i->data(TitleDocument::Gradient).toString(); int ix = gradients_combo->findData(gradientData); if (ix == -1) { // This gradient does not exist in our settings, store it storeGradient(gradientData); ix = gradients_combo->findData(gradientData); } gradients_combo->setCurrentIndex(ix); gradients_combo->blockSignals(false); } else { plain_color->setChecked(true); } if (i->alignment() == Qt::AlignHCenter) { buttonAlignCenter->setChecked(true); } else if (i->alignment() == Qt::AlignRight) { buttonAlignRight->setChecked(true); } else { buttonAlignLeft->setChecked(true); } QStringList sInfo = i->shadowInfo(); if (sInfo.count() >= 5) { shadowBox->setChecked(static_cast(sInfo.at(0).toInt())); shadowBox->blockSignals(true); shadowColor->setColor(QColor(sInfo.at(1))); blur_radius->setValue(sInfo.at(2).toInt()); shadowX->setValue(sInfo.at(3).toInt()); shadowY->setValue(sInfo.at(4).toInt()); shadowBox->blockSignals(false); } letter_spacing->blockSignals(true); line_spacing->blockSignals(true); QTextCursor cur = i->textCursor(); QTextBlockFormat format = cur.blockFormat(); letter_spacing->setValue(font.letterSpacing()); line_spacing->setValue(format.lineHeight()); letter_spacing->blockSignals(false); line_spacing->blockSignals(false); font_size->blockSignals(false); font_family->blockSignals(false); font_weight_box->blockSignals(false); buttonItalic->blockSignals(false); buttonUnder->blockSignals(false); fontColorButton->blockSignals(false); buttonAlignLeft->blockSignals(false); buttonAlignRight->blockSignals(false); buttonAlignCenter->blockSignals(false); // mbt 1607: Select text if the text item is an unchanged template item. if (i->property("isTemplate").isValid()) { cur.setPosition(0, QTextCursor::MoveAnchor); cur.select(QTextCursor::Document); i->setTextCursor(cur); // Make text editable now. i->grabKeyboard(); i->setTextInteractionFlags(Qt::TextEditorInteraction); } } updateAxisButtons(i); updateCoordinates(i); updateDimension(i); enableToolbars(TITLE_TEXT); } else if ((referenceItem)->type() == RECTITEM) { showToolbars(TITLE_RECTANGLE); settingUp = 1; QGraphicsRectItem *rec = static_cast(referenceItem); if (rec == m_startViewport || rec == m_endViewport) { enableToolbars(TITLE_SELECT); } else { QColor fcol = rec->pen().color(); QColor bcol = rec->brush().color(); rectFColor->setColor(fcol); QString gradientData = rec->data(TitleDocument::Gradient).toString(); if (gradientData.isEmpty()) { plain_rect->setChecked(true); rectBColor->setColor(bcol); } else { gradient_rect->setChecked(true); gradients_rect_combo->blockSignals(true); int ix = gradients_rect_combo->findData(gradientData); if (ix == -1) { storeGradient(gradientData); ix = gradients_rect_combo->findData(gradientData); } gradients_rect_combo->setCurrentIndex(ix); gradients_rect_combo->blockSignals(false); } settingUp = 0; if (rec->pen() == Qt::NoPen) { rectLineWidth->setValue(0); } else { rectLineWidth->setValue(rec->pen().width()); } enableToolbars(TITLE_RECTANGLE); } updateAxisButtons(referenceItem); updateCoordinates(rec); updateDimension(rec); } else if (referenceItem->type() == IMAGEITEM) { showToolbars(TITLE_IMAGE); updateCoordinates(referenceItem); updateDimension(referenceItem); enableToolbars(TITLE_IMAGE); } else { showToolbars(TITLE_SELECT); enableToolbars(TITLE_SELECT); frame_properties->setEnabled(false); } zValue->setValue((int)referenceItem->zValue()); if (!referenceItem->data(TitleDocument::ZoomFactor).isNull()) { itemzoom->setValue(referenceItem->data(TitleDocument::ZoomFactor).toInt()); } else { itemzoom->setValue((int)(m_transformations.value(referenceItem).scalex * 100.0 + 0.5)); } itemrotatex->setValue((int)(m_transformations.value(referenceItem).rotatex)); itemrotatey->setValue((int)(m_transformations.value(referenceItem).rotatey)); itemrotatez->setValue((int)(m_transformations.value(referenceItem).rotatez)); } effect_list->blockSignals(blockEff); itemrotatex->blockSignals(blockRX); itemrotatey->blockSignals(blockRY); itemrotatez->blockSignals(blockRZ); itemzoom->blockSignals(blockZoom); origin_x_left->blockSignals(blockOX); origin_y_top->blockSignals(blockOY); value_x->blockSignals(blockX); value_y->blockSignals(blockY); value_w->blockSignals(blockW); value_h->blockSignals(blockH); } void TitleWidget::slotEditGradient() { QToolButton *caller = qobject_cast(QObject::sender()); if (!caller) { return; } QComboBox *combo = nullptr; if (caller == edit_gradient) { combo = gradients_combo; } else { combo = gradients_rect_combo; } QMap gradients; for (int i = 0; i < combo->count(); i++) { gradients.insert(combo->itemText(i), combo->itemData(i).toString()); } GradientWidget d(gradients, combo->currentIndex()); if (d.exec() == QDialog::Accepted) { // Save current gradients QMap gradMap = d.gradients(); QList icons = d.icons(); QMap::const_iterator i = gradMap.constBegin(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); group.deleteGroup(); combo->clear(); gradients_rect_combo->clear(); int ix = 0; while (i != gradMap.constEnd()) { group.writeEntry(i.key(), i.value()); gradients_combo->addItem(icons.at(ix), i.key(), i.value()); gradients_rect_combo->addItem(icons.at(ix), i.key(), i.value()); ++i; ix++; } group.sync(); combo->setCurrentIndex(d.selectedGradient()); } } void TitleWidget::storeGradient(const QString &gradientData) { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); int ix = qMax(1, values.count()); QString gradName = i18n("Gradient %1", ix); while (values.contains(gradName)) { ix++; gradName = i18n("Gradient %1", ix); } group.writeEntry(gradName, gradientData); group.sync(); QPixmap pix(30, 30); pix.fill(Qt::transparent); QLinearGradient gr = GradientWidget::gradientFromString(gradientData, pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, gradName, gradientData); gradients_rect_combo->addItem(icon, gradName, gradientData); } void TitleWidget::loadGradients() { QMap gradients; gradients_combo->blockSignals(true); gradients_rect_combo->blockSignals(true); QString grad_data = gradients_combo->currentData().toString(); QString rect_data = gradients_rect_combo->currentData().toString(); gradients_combo->clear(); gradients_rect_combo->clear(); KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup group(config, "TitleGradients"); QMap values = group.entryMap(); if (values.isEmpty()) { // Ensure we at least always have one sample black to white gradient values.insert(i18n("Gradient"), QStringLiteral("#ffffffff;#ff000000;0;100;90")); } QMapIterator k(values); while (k.hasNext()) { k.next(); QPixmap pix(30, 30); pix.fill(Qt::transparent); QLinearGradient gr = GradientWidget::gradientFromString(k.value(), pix.width(), pix.height()); gr.setStart(0, pix.height() / 2); gr.setFinalStop(pix.width(), pix.height() / 2); QPainter painter(&pix); painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr)); painter.end(); QIcon icon(pix); gradients_combo->addItem(icon, k.key(), k.value()); gradients_rect_combo->addItem(icon, k.key(), k.value()); } int ix = gradients_combo->findData(grad_data); if (ix >= 0) { gradients_combo->setCurrentIndex(ix); } ix = gradients_rect_combo->findData(rect_data); if (ix >= 0) { gradients_rect_combo->setCurrentIndex(ix); } gradients_combo->blockSignals(false); gradients_rect_combo->blockSignals(false); } void TitleWidget::slotUpdateShadow() { QList l = graphicsView->scene()->selectedItems(); for (int i = 0; i < graphicsView->scene()->selectedItems().length(); ++i) { MyTextItem *item = nullptr; if (l.at(i)->type() == TEXTITEM) { item = static_cast(l.at(i)); } if (!item) { // No text item, try next one. continue; } item->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color()); } } const QString TitleWidget::titleSuggest() { // Find top item to extract title proposal QList list = graphicsView->scene()->items(); int y = m_frameHeight; QString title; for (QGraphicsItem *qgItem : list) { if (qgItem->pos().y() < y && qgItem->type() == TEXTITEM) { MyTextItem *i = static_cast(qgItem); QString currentTitle = i->toPlainText(); if (currentTitle.length() > 2) { title = currentTitle.length() > 12 ? currentTitle.left(12) + QStringLiteral("...") : currentTitle; y = qgItem->pos().y(); } } } return title; } diff --git a/src/titler/titlewidget.h b/src/titler/titlewidget.h index 5920182a7..f38bef926 100644 --- a/src/titler/titlewidget.h +++ b/src/titler/titlewidget.h @@ -1,362 +1,362 @@ /*************************************************************************** titlewidget.h - description ------------------- begin : Feb 28 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. * * * ***************************************************************************/ #ifndef TITLEWIDGET_H #define TITLEWIDGET_H #include "graphicsscenerectmove.h" #include "timecode.h" #include "titler/titledocument.h" #include "titler/unicodedialog.h" #include "ui_titlewidget_ui.h" #include #include class Monitor; class TitleTemplate { public: QString file; QString name; QIcon icon; }; class Transform { public: Transform() { scalex = 1.0; scaley = 1.0; rotatex = 0.0; rotatey = 0.0; rotatez = 0.0; } double scalex, scaley; int rotatex, rotatey, rotatez; }; /*! \class TitleWidget \brief Title creation dialog Instances of TitleWidget classes are instansiated by KdenliveDoc::slotCreateTextClip () */ class TitleWidget : public QDialog, public Ui::TitleWidget_UI { Q_OBJECT public: /** @brief Draws the dialog and loads a title document (if any). * @param url title document to load * @param tc timecode of the project * @param projectPath default path to save to or load from title documents * @param render project renderer * @param parent (optional) parent widget */ explicit TitleWidget(const QUrl &url, const Timecode &tc, const QString &projectTitlePath, Monitor *monitor, QWidget *parent = nullptr); virtual ~TitleWidget(); QDomDocument xml(); void setXml(const QDomDocument &doc, const QString &id = QString()); /** @brief Checks for the images referenced by a title clip. * @param xml XML data representing the title * @return list of the image files */ static QStringList extractImageList(const QString &xml); /** @brief Checks for the fonts referenced by a title clip.\n * Called by DocumentChecker::hasErrorInClips () \n * @param xml XML data representing the title * @return list of the fonts in the title */ static QStringList extractFontList(const QString &xml); /** @brief Returns clip duration. */ int duration() const; /** @brief Retrieves a list of all available title templates. */ static void refreshTitleTemplates(const QString &projectPath); /** @brief Returns a title name suggestion based on content */ const QString titleSuggest(); protected: void resizeEvent(QResizeEvent *event) override; void keyPressEvent(QKeyEvent *e) override; private: /** @brief Rectangle describing the animation start viewport. */ QGraphicsRectItem *m_startViewport; /** @brief Rectangle describing the animation end viewport. */ QGraphicsRectItem *m_endViewport; /** @brief Scene for the titler. */ GraphicsSceneRectMove *m_scene; /** @brief Initialises the animation properties (viewport size, etc.). */ void initAnimation(); QMap m_transformations; TitleDocument m_titledocument; QGraphicsRectItem *m_frameBorder; QGraphicsRectItem *m_frameBackground; QGraphicsPixmapItem *m_frameImage; int m_frameWidth; int m_frameHeight; int m_count; /** @brief Dialog for entering Unicode characters in text fields. */ UnicodeDialog *m_unicodeDialog; /** @brief Project path for storing title documents. */ QString m_projectTitlePath; Timecode m_tc; /** @brief The project framerate. */ double m_fps; /** @brief The bin id of the clip currently edited, can be empty if this is a new clip. */ QString m_clipId; QAction *m_buttonRect; QAction *m_buttonText; QAction *m_buttonImage; QAction *m_buttonCursor; QAction *m_buttonSave; QAction *m_buttonLoad; QAction *m_unicodeAction; QAction *m_zUp; QAction *m_zDown; QAction *m_zTop; QAction *m_zBottom; QAction *m_selectAll; QAction *m_selectText; QAction *m_selectRects; QAction *m_selectImages; QAction *m_unselectAll; QString lastDocumentHash; // See http://doc.trolltech.com/4.5/signalsandslots.html#advanced-signals-and-slots-usage. QSignalMapper *m_signalMapper; enum ValueType { ValueWidth = 1, ValueHeight = 2, ValueX = 4, ValueY = 8 }; /** @brief Sets the font weight value in the combo box. (#909) */ void setFontBoxWeight(int weight); /** @brief Stores the choices of font, background and rectangle values. */ void writeChoices(); /** @brief Reads the last stored choices into the dialog. */ void readChoices(); /** @brief Updates the displayed X/Y coordinates. */ void updateCoordinates(QGraphicsItem *i); /** @brief Updates the displayed width/height/zindex values. */ void updateDimension(QGraphicsItem *i); /** @brief Updates the displayed rotation/zoom values. Changes values of rotation/zoom GUI elements. */ void updateRotZoom(QGraphicsItem *i); /** @brief Updates the item position (position read directly from the GUI). Does not change GUI elements. */ void updatePosition(QGraphicsItem *i); /** @brief Updates the item position. Does not change GUI elements. */ void updatePosition(QGraphicsItem *i, int x, int y); void textChanged(MyTextItem *i); void updateAxisButtons(QGraphicsItem *i); void updateTextOriginX(); void updateTextOriginY(); /** @brief Enables the toolbars suiting to toolType. */ void enableToolbars(TITLETOOL toolType); /** @brief Shows the toolbars suiting to toolType. */ void showToolbars(TITLETOOL toolType); /** @brief Set up the tools suiting referenceItem */ void prepareTools(QGraphicsItem *referenceItem); /** @brief Checks a tool button. */ void checkButton(TITLETOOL toolType); void adjustFrameSize(); /** @brief Adds a "start" and "end" info text to the animation viewports. */ void addAnimInfoText(); /** @brief Updates the font for the "start" and "end" info text. */ void updateInfoText(); /** @brief Removes the "start" and "end" info text from animation viewports. */ void deleteAnimInfoText(); qreal maxZIndex(); /** @brief Gets the minimum/maximum Z index of items. * @param maxBound true: use maximum Z index; false: use minimum * @param intersectingOnly if true, consider only the items intersecting * with the currently selected item */ qreal zIndexBounds(bool maxBound, bool intersectingOnly); void itemRotate(int val, int axis); void selectItems(int itemType); /** @brief Appends the shortcut of a QAction to a tooltip text */ - QString getTooltipWithShortcut(const QString& tipText, QAction* button); + QString getTooltipWithShortcut(const QString &tipText, QAction *button); void loadGradients(); void storeGradient(const QString &gradientData); public slots: void slotNewText(MyTextItem *tt); void slotNewRect(QGraphicsRectItem *rect); void slotChangeBackground(); /** @brief Sets up the tools (toolbars etc.) according to the selected item. */ void selectionChanged(); void rectChanged(); void zIndexChanged(int); void itemScaled(int); void itemRotateX(int); void itemRotateY(int); void itemRotateZ(int); /** Save a title to a title file */ void saveTitle(QUrl url = QUrl()); /** Load a title from a title file */ void loadTitle(QUrl url = QUrl()); void slotGotBackground(const QImage &img); private slots: /** @brief Switches the origin of the X axis between left and right border. * * It's called when the origin of the X coordinate has been changed. The X * origin will either be at the left or at the right side of the frame. * * When the origin of the X axis is at the left side, the user can enter the * distance between an element's left border and the left side of the frame. * * When on the right, the distance from the right border of the frame to the * right border of the element can be entered. This would result in negative * values as long as the element's right border is at the left of the * frame's right border. As that is usually the case, I additionally invert * the X axis. * * Default value is left. * * |----l----->|#######|----r--->| * | |---w-->| | * | |#######| | * | | * |----------m_frameWidth------>| * | | * * Left selected: Value = l * Right selected: Value = r * * To calculate between the two coorindate systems: * l = m_frameWidth - w - r * r = m_frameWidth - w - l */ void slotOriginXClicked(); /** @brief Same as slotOriginXClicked(), but for the Y axis; default is top. * @ref slotOriginXClicked */ void slotOriginYClicked(); /** @brief Updates coordinates of text fields if necessary. * * It's called when something changes in the QGraphicsScene. */ void slotChanged(); /** * Reacts to changes of widht/height/x/y QSpinBox values. * @brief Updates width, height, and position of the selected items. * @param valueType of type ValueType */ void slotValueChanged(int valueType); void slotZoom(bool up); void slotUpdateZoom(int pos); void slotAdjustZoom(); void slotZoomOneToOne(); void slotSelectAll(); void slotSelectText(); void slotSelectRects(); void slotSelectImages(); void slotSelectNone(); /** Called whenever text properties change (font e.g.) */ void slotUpdateText(); void slotInsertUnicode(); - void slotInsertUnicodeString(const QString& string); + void slotInsertUnicodeString(const QString &string); void displayBackgroundFrame(); void setCurrentItem(QGraphicsItem *item); void slotTextTool(); void slotRectTool(); void slotSelectTool(); void slotImageTool(); void slotAnimStart(bool); void slotAnimEnd(bool); void slotKeepAspect(bool keep); void itemHCenter(); void itemVCenter(); void itemTop(); void itemBottom(); void itemLeft(); void itemRight(); void slotResize50(); void slotResize100(); void slotResize200(); /** @brief Called when accepted, stores user selections for next time use. * @ref writeChoices */ void slotAccepted(); /** @brief Adds an effect to an element. * @param ix index of the effect in the effects menu * * The current implementation allows for one QGraphicsEffect to be added * along with the typewriter effect. This is not clear to the user: the * stack would help, and would permit us to make more QGraphicsEffects * coexist (with different layers of QGraphicsItems). */ void slotAddEffect(int ix); void slotEditTypewriter(int ix); /** @brief Changes the Z index of objects. */ void slotZIndexUp(); void slotZIndexDown(); void slotZIndexTop(); void slotZIndexBottom(); /** Called when the user wants to apply a different template to the title */ void templateIndexChanged(int); void slotEditGradient(); void slotUpdateShadow(); signals: void requestBackgroundFrame(const QString &clipId, bool request); }; #endif diff --git a/src/titler/unicodedialog.cpp b/src/titler/unicodedialog.cpp index 155ae3d88..bb122a78a 100644 --- a/src/titler/unicodedialog.cpp +++ b/src/titler/unicodedialog.cpp @@ -1,422 +1,418 @@ /*************************************************************************** * Copyright (C) 2008 by Simon Andreas Eugster (simon.eu@gmail.com) * * * * 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 "unicodedialog.h" #include #include #include #include #include #include #include /// CONSTANTS const int MAX_LENGTH_HEX = 4; const uint MAX_UNICODE_V1 = 65535; UnicodeDialog::UnicodeDialog(InputMethod inputMeth, QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Details")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); auto *mainLayout = new QVBoxLayout(this); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); mUnicodeWidget = new UnicodeWidget(inputMeth); connect(mUnicodeWidget, &UnicodeWidget::charSelected, this, &UnicodeDialog::charSelected); mainLayout->addWidget(mUnicodeWidget); mainLayout->addWidget(buttonBox); connect(okButton, &QAbstractButton::clicked, this, &UnicodeDialog::slotAccept); } -UnicodeDialog::~UnicodeDialog() -{ -} +UnicodeDialog::~UnicodeDialog() {} void UnicodeDialog::slotAccept() { mUnicodeWidget->slotReturnPressed(); accept(); } /// CONSTRUCTORS/DECONSTRUCTORS UnicodeWidget::UnicodeWidget(UnicodeDialog::InputMethod inputMeth, QWidget *parent) : QWidget(parent) , inputMethod(inputMeth) , m_lastCursorPos(0) { setupUi(this); readChoices(); showLastUnicode(); connect(unicodeNumber, &QLineEdit::textChanged, this, &UnicodeWidget::slotTextChanged); connect(unicodeNumber, &QLineEdit::returnPressed, this, &UnicodeWidget::slotReturnPressed); connect(arrowUp, &QAbstractButton::clicked, this, &UnicodeWidget::slotPrevUnicode); connect(arrowDown, &QAbstractButton::clicked, this, &UnicodeWidget::slotNextUnicode); switch (inputMethod) { case UnicodeDialog::InputHex: unicodeNumber->setMaxLength(MAX_LENGTH_HEX); break; case UnicodeDialog::InputDec: break; } arrowUp->setShortcut(Qt::Key_Up); arrowDown->setShortcut(Qt::Key_Down); unicode_link->setText(i18n("Information about unicode characters: http://decodeunicode.org")); arrowUp->setToolTip(i18n("Previous Unicode character (Arrow Up)")); arrowDown->setToolTip(i18n("Next Unicode character (Arrow Down)")); unicodeNumber->setToolTip(i18n("Enter your Unicode number here. Allowed characters: [0-9] and [a-f].")); unicodeNumber->selectAll(); // Selection will be reset by setToolTip and similar, so set it here } -UnicodeWidget::~UnicodeWidget() -{ -} +UnicodeWidget::~UnicodeWidget() {} /// METHODS void UnicodeWidget::showLastUnicode() { unicodeNumber->setText(m_lastUnicodeNumber); unicodeNumber->selectAll(); slotTextChanged(m_lastUnicodeNumber); } bool UnicodeWidget::controlCharacter(const QString &text) { bool isControlCharacter = false; QString t = text.toLower(); switch (inputMethod) { case UnicodeDialog::InputHex: if (t.isEmpty() || (t.length() == 1 && !(t == QLatin1String("9") || t == QLatin1String("a") || t == QLatin1String("d"))) || (t.length() == 2 && t.at(0) == QChar('1'))) { isControlCharacter = true; } break; case UnicodeDialog::InputDec: bool ok; isControlCharacter = controlCharacter(text.toUInt(&ok, 16)); break; } return isControlCharacter; } bool UnicodeWidget::controlCharacter(uint value) { bool isControlCharacter = false; if (value < 32 && !(value == 9 || value == 10 || value == 13)) { isControlCharacter = true; } return isControlCharacter; } QString UnicodeWidget::trimmedUnicodeNumber(QString text) { while (!text.isEmpty() && text.at(0) == QChar('0')) { text = text.remove(0, 1); } return text; } QString UnicodeWidget::unicodeInfo(const QString &unicode) { QString infoText(i18n("(no character selected)")); if (unicode.isEmpty()) { return infoText; } QString u = trimmedUnicodeNumber(unicode).toLower(); if (controlCharacter(u)) { infoText = i18n( "Control character. Cannot be inserted/printed. See Wikipedia:Control_character"); } else if (u == QLatin1String("a")) { infoText = i18n("Line Feed (newline character, \\\\n)"); } else if (u == QLatin1String("20")) { infoText = i18n("Standard space character. (Other space characters: U+00a0, U+2000–200b, U+202f)"); } else if (u == QLatin1String("a0")) { infoText = i18n("No-break space. &nbsp; in HTML. See U+2009 and U+0020."); } else if (u == QLatin1String("ab") || u == QLatin1String("bb") || u == QLatin1String("2039") || u == QLatin1String("203a")) { infoText = i18n("

    « (u+00ab, &lfquo; in HTML) and » (u+00bb, &rfquo; in " "HTML) are called Guillemets or angle quotes. Usage in different countries: France (with non-breaking Space 0x00a0), Switzerland, Germany, " "Finland and Sweden.

    and (U+2039/203a, &lsaquo;/&rsaquo;) are " "their single quote equivalents.

    See Wikipedia:Guillemets

    "); } else if (u == QLatin1String("2002")) { infoText = i18n("En Space (width of an n)"); } else if (u == QLatin1String("2003")) { infoText = i18n("Em Space (width of an m)"); } else if (u == QLatin1String("2004")) { infoText = i18n("Three-Per-Em Space. Width: 1/3 of one em"); } else if (u == QLatin1String("2005")) { infoText = i18n("Four-Per-Em Space. Width: 1/4 of one em"); } else if (u == QLatin1String("2006")) { infoText = i18n("Six-Per-Em Space. Width: 1/6 of one em"); } else if (u == QLatin1String("2007")) { infoText = i18n("Figure space (non-breaking). Width of a digit if digits have fixed width in this font."); } else if (u == QLatin1String("2008")) { infoText = i18n("Punctuation Space. Width the same as between a punctuation character and the next character."); } else if (u == QLatin1String("2009")) { infoText = i18n("Thin space, in HTML also &thinsp;. See U+202f and Wikipedia:Space_(punctuation)"); } else if (u == QLatin1String("200a")) { infoText = i18n("Hair Space. Thinner than U+2009."); } else if (u == QLatin1String("2019")) { infoText = i18n("Punctuation Apostrophe. Should be used instead of U+0027. See Wikipedia:Apostrophe"); } else if (u == QLatin1String("2013")) { infoText = i18n("

    An en Dash (dash of the width of an n).

    Usage examples: In English language for value ranges (1878–1903), for " "relationships/connections (Zurich–Dublin). In the German language it is also used (with spaces!) for showing thoughts: " "“Es war – wie immer in den Ferien – ein regnerischer Tag.

    See Wikipedia:Dash

    "); } else if (u == QLatin1String("2014")) { infoText = i18n("

    An em Dash (dash of the width of an m).

    Usage examples: In English language to mark—like here—thoughts. " "Traditionally without spaces.

    See Wikipedia:Dash

    "); } else if (u == QLatin1String("202f")) { infoText = i18n("

    Narrow no-break space. Has the same width as U+2009.

    Usage: For units (spaces are marked with U+2423, ␣): " "230␣V, −21␣°C, 50␣lb, but 90° (no space). In German for abbreviations (like: " "i. d. R. instead of i. d. R. with U+00a0).

    See Wikipedia:de:Schmales_Leerzeichen

    "); } else if (u == QLatin1String("2026")) { infoText = i18n("Ellipsis: If text has been left o… See Wikipedia:Ellipsis"); } else if (u == QLatin1String("2212")) { infoText = i18n("Minus sign. For numbers: −42"); } else if (u == QLatin1String("2423")) { infoText = i18n("Open box; stands for a space."); } else if (u == QLatin1String("2669")) { infoText = i18n("Quarter note (Am.) or crochet (Brit.). See Wikipedia:Quarter_note"); } else if (u == QLatin1String("266a") || u == QLatin1String("266b")) { infoText = i18n("Eighth note (Am.) or quaver (Brit.). Half as long as a quarter note (U+2669). See Wikipedia:Eighth_note"); } else if (u == QLatin1String("266c")) { infoText = i18n("Sixteenth note (Am.) or semiquaver (Brit.). Half as long as an eighth note (U+266a). See Wikipedia:Sixteenth_note"); } else if (u == QLatin1String("1D162")) { infoText = i18n("Thirty-second note (Am.) or demisemiquaver (Brit.). Half as long as a sixteenth note (U+266b). See Wikipedia:Thirty-second_note"); } else { infoText = i18n("No additional information available for this character."); } return infoText; } QString UnicodeWidget::validateText(const QString &text) { QRegExp regex("([0-9]|[a-f])", Qt::CaseInsensitive, QRegExp::RegExp2); QString newText; int pos = 0; switch (inputMethod) { case UnicodeDialog::InputHex: // Remove all characters we don't want while ((pos = regex.indexIn(text, pos)) != -1) { newText += regex.cap(1); pos++; } break; case UnicodeDialog::InputDec: // TODO break; } return newText; } void UnicodeWidget::updateOverviewChars(uint unicode) { QString left; QString right; uint i; for (i = 1; i <= 4; ++i) { if (unicode > i && !controlCharacter(unicode - i)) { left = QLatin1Char(' ') + left; left = QChar(unicode - i) + left; } } for (i = 1; i <= 8; ++i) { if (unicode + i <= MAX_UNICODE_V1 && !controlCharacter(unicode + i)) { right += QChar(unicode + i); right += ' '; } } leftChars->setText(left); rightChars->setText(right); } void UnicodeWidget::clearOverviewChars() { leftChars->clear(); rightChars->clear(); } QString UnicodeWidget::nextUnicode(const QString &text, Direction direction) { uint value = 0; QString newText; bool ok; switch (inputMethod) { case UnicodeDialog::InputHex: value = text.toUInt(&ok, 16); switch (direction) { case Backward: value--; break; default: value++; break; } // Wrapping if (value == (uint)-1) { value = MAX_UNICODE_V1; } if (value > MAX_UNICODE_V1) { value = 0; } newText.setNum(value, 16); break; case UnicodeDialog::InputDec: break; } return newText; } void UnicodeWidget::readChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); // Default is 2013 because there is also (perhaps interesting) information. m_lastUnicodeNumber = titleConfig.readEntry("unicode_number", QStringLiteral("2013")); } void UnicodeWidget::writeChoices() { // Get a pointer to a shared configuration instance, then get the TitleWidget group. KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup titleConfig(config, "TitleWidget"); titleConfig.writeEntry("unicode_number", m_lastUnicodeNumber); } /// SLOTS /** * \brief Validates the entered Unicode number and displays its Unicode character. */ void UnicodeWidget::slotTextChanged(const QString &text) { unicodeNumber->blockSignals(true); QString newText = validateText(text); if (newText.isEmpty()) { unicodeChar->clear(); unicodeNumber->clear(); clearOverviewChars(); m_lastCursorPos = 0; m_lastUnicodeNumber = QString(); labelInfoText->setText(unicodeInfo(QString())); } else { int cursorPos = unicodeNumber->cursorPosition(); unicodeNumber->setText(newText); unicodeNumber->setCursorPosition(cursorPos); // Get the decimal number as uint to create the QChar from bool ok; uint value = 0; switch (inputMethod) { case UnicodeDialog::InputHex: value = newText.toUInt(&ok, 16); break; case UnicodeDialog::InputDec: value = newText.toUInt(&ok, 10); break; } updateOverviewChars(value); if (!ok) { // Impossible! validateText never fails! } // If an invalid character has been entered: // Reset the cursor position because the entered char has been deleted. if (text != newText && newText == m_lastUnicodeNumber) { unicodeNumber->setCursorPosition(m_lastCursorPos); } m_lastCursorPos = unicodeNumber->cursorPosition(); m_lastUnicodeNumber = newText; labelInfoText->setText(unicodeInfo(newText)); unicodeChar->setText(QChar(value)); } unicodeNumber->blockSignals(false); } /** * When return pressed, we return the selected unicode character * if it was not a control character. */ void UnicodeWidget::slotReturnPressed() { unicodeNumber->setFocus(); const QString text = trimmedUnicodeNumber(unicodeNumber->text()); if (!controlCharacter(text)) { emit charSelected(unicodeChar->text()); writeChoices(); } } void UnicodeWidget::slotNextUnicode() { const QString text = unicodeNumber->text(); unicodeNumber->setText(nextUnicode(text, Forward)); } void UnicodeWidget::slotPrevUnicode() { const QString text = unicodeNumber->text(); unicodeNumber->setText(nextUnicode(text, Backward)); } void UnicodeWidget::wheelEvent(QWheelEvent *event) { if (frame->underMouse()) { if (event->delta() > 0) { slotNextUnicode(); } else { slotPrevUnicode(); } } } diff --git a/src/utils/abstractservice.cpp b/src/utils/abstractservice.cpp index ab5a896e5..3afa651c1 100644 --- a/src/utils/abstractservice.cpp +++ b/src/utils/abstractservice.cpp @@ -1,63 +1,59 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "abstractservice.h" #include AbstractService::AbstractService(QListWidget *listWidget, QObject *parent) : QObject(parent) , hasPreview(false) , hasMetadata(false) , inlineDownload(false) , serviceType(NOSERVICE) , m_listWidget(listWidget) { } AbstractService::~AbstractService() = default; -void AbstractService::slotStartSearch(const QString &, int) -{ -} +void AbstractService::slotStartSearch(const QString &, int) {} OnlineItemInfo AbstractService::displayItemDetails(QListWidgetItem * /*item*/) { return {}; } bool AbstractService::startItemPreview(QListWidgetItem *) { return false; } -void AbstractService::stopItemPreview(QListWidgetItem *) -{ -} +void AbstractService::stopItemPreview(QListWidgetItem *) {} QString AbstractService::getExtension(QListWidgetItem *) { return QString(); } QString AbstractService::getDefaultDownloadName(QListWidgetItem *) { return QString(); } diff --git a/src/utils/freesound.cpp b/src/utils/freesound.cpp index a81c9eaeb..a7a384567 100644 --- a/src/utils/freesound.cpp +++ b/src/utils/freesound.cpp @@ -1,353 +1,352 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "freesound.h" #include "kdenlive_debug.h" #include #include #include #include #include #include "kdenlivesettings.h" #include "qt-oauth-lib/oauth2.h" #include #include #include /** * @brief FreeSound::FreeSound * @param listWidget * @param parent */ FreeSound::FreeSound(QListWidget *listWidget, QObject *parent) : AbstractService(listWidget, parent) , m_previewProcess(new QProcess) { serviceType = FREESOUND; hasPreview = true; hasMetadata = true; connect(m_previewProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(slotPreviewErrored(QProcess::ProcessError))); connect(m_previewProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotPreviewFinished(int, QProcess::ExitStatus))); } FreeSound::~FreeSound() { delete m_previewProcess; } /** * @brief FreeSound::slotStartSearch * @param searchText * @param page * Called by ResourceWidget::slotStartSearch */ void FreeSound::slotStartSearch(const QString &searchText, int page) { // ver 2 of freesound API m_listWidget->clear(); QString uri = QStringLiteral("https://www.freesound.org/apiv2/search/text/?format=json&query="); uri.append(searchText); if (page > 1) { uri.append(QStringLiteral("&page=") + QString::number(page)); } uri.append(QStringLiteral("&token=") + OAuth2_strClientSecret); // qCDebug(KDENLIVE_LOG)<error() != 0) { qCDebug(KDENLIVE_LOG) << job->errorString(); } else { m_listWidget->blockSignals(true); // stop the listWidget from emiting signals.Ie clicking on the list while we are busy here will do nothing KIO::StoredTransferJob *storedQueryJob = static_cast(job); QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(storedQueryJob->data(), &jsonError); if (jsonError.error != QJsonParseError::NoError) { // There was an error parsing data KMessageBox::sorry(m_listWidget, jsonError.errorString(), i18n("Error Loading Data")); } QVariant data = doc.toVariant(); QVariant sounds; if (data.canConvert(QVariant::Map)) { QMap map = data.toMap(); QMap::const_iterator i = map.constBegin(); while (i != map.constEnd()) { if (i.key() == QLatin1String("count")) { emit searchInfo(i18np("Found %1 result", "Found %1 results", i.value().toInt())); } else if (i.key() == QLatin1String("num_pages")) { emit maxPages(i.value().toInt()); } else if (i.key() == QLatin1String("results")) { sounds = i.value(); if (sounds.canConvert(QVariant::List)) { QList soundsList = sounds.toList(); for (int j = 0; j < soundsList.count(); ++j) { if (soundsList.at(j).canConvert(QVariant::Map)) { QMap soundmap = soundsList.at(j).toMap(); if (soundmap.contains(QStringLiteral("name"))) { QListWidgetItem *item = new QListWidgetItem(soundmap.value(QStringLiteral("name")).toString(), m_listWidget); QVariant vid = soundmap.value(QStringLiteral("id")); item->setData(idRole, vid); QVariant authorInfo = soundmap.value(QStringLiteral("username")); item->setData(authorRole, authorInfo); item->setData(authorUrl, QStringLiteral("http://freesound.org/people/") + soundmap.value(QStringLiteral("username")).toString()); item->setData(licenseRole, soundmap.value(QStringLiteral("license"))); - item->setData(infoData, - QStringLiteral("http://www.freesound.org/apiv2/sounds/") + vid.toString() + - QStringLiteral("/?format=json&token=") + OAuth2_strClientSecret); + item->setData(infoData, QStringLiteral("http://www.freesound.org/apiv2/sounds/") + vid.toString() + + QStringLiteral("/?format=json&token=") + OAuth2_strClientSecret); } } } } } ++i; } } m_listWidget->blockSignals(false); // enable listWidget to send signals again. It will register clicks now m_listWidget->setCurrentRow(0); emit searchDone(); } } /** * @brief FreeSound::displayItemDetails * @param item * @return * This is a slot * Called by ResourceWidget::slotUpdateCurrentSound */ OnlineItemInfo FreeSound::displayItemDetails(QListWidgetItem *item) { OnlineItemInfo info; m_metaInfo.clear(); if (!item) { return info; } info.itemPreview = item->data(previewRole).toString(); info.itemId = item->data(idRole).toString(); info.itemName = item->text(); info.infoUrl = item->data(infoUrl).toString(); info.author = item->data(authorRole).toString(); info.authorUrl = item->data(authorUrl).toString(); m_metaInfo.insert(i18n("Duration"), item->data(durationRole).toString()); info.license = item->data(licenseRole).toString(); info.description = item->data(descriptionRole).toString(); QString extraInfoUrl = item->data(infoData).toString(); if (!extraInfoUrl.isEmpty()) { KJob *resolveJob = KIO::storedGet(QUrl(extraInfoUrl), KIO::NoReload, KIO::HideProgressInfo); // connect (obj,signal,obj, slot) // when the KJob resolveJob emits a result signal slotParseResults will be notified connect(resolveJob, &KJob::result, this, &FreeSound::slotParseResults); } return info; } /** * @brief FreeSound::slotParseResults * @param job * emits gotMetaInfo which is connected to slotSetMetadata and to slotDisplayMetaInfo * Fires when displayItemDetails has finished downloading the extra info from freesound */ void FreeSound::slotParseResults(KJob *job) { KIO::StoredTransferJob *storedQueryJob = static_cast(job); QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(storedQueryJob->data(), &jsonError); if (jsonError.error != QJsonParseError::NoError) { // There was an error parsing data KMessageBox::sorry(m_listWidget, jsonError.errorString(), i18n("Error Loading Data")); } QVariant data = doc.toVariant(); QString html = QStringLiteral("").arg(qApp->palette().alternateBase().color().name()); if (data.canConvert(QVariant::Map)) { QMap info = data.toMap(); html += QLatin1String(""); if (info.contains(QStringLiteral("duration"))) { html += QLatin1String(""); html += QStringLiteral(""); m_metaInfo.remove(i18n("Duration")); m_metaInfo.insert(i18n("Duration"), info.value(QStringLiteral("duration")).toString()); } if (info.contains(QStringLiteral("samplerate"))) { html += QLatin1String(""); html += QStringLiteral(""); } if (info.contains(QStringLiteral("channels"))) { html += QLatin1String(""); html += QStringLiteral(""); } if (info.contains(QStringLiteral("filesize"))) { html += QLatin1String(""); KIO::filesize_t fSize = info.value(QStringLiteral("filesize")).toDouble(); html += QStringLiteral(""); } if (info.contains(QStringLiteral("license"))) { m_metaInfo.insert(QStringLiteral("license"), info.value(QStringLiteral("license")).toString()); } html += QLatin1String("
    ") + i18n("Duration (s)") + QStringLiteral("") + QString::number(info.value(QStringLiteral("duration")).toDouble()) + QStringLiteral("
    ") + i18n("Samplerate") + QStringLiteral("") + QString::number(info.value(QStringLiteral("samplerate")).toDouble()) + QStringLiteral("
    ") + i18n("Channels") + QStringLiteral("") + QString::number(info.value(QStringLiteral("channels")).toInt()) + QStringLiteral("
    ") + i18n("File size") + QStringLiteral("") + KIO::convertSize(fSize) + QStringLiteral("
    "); if (info.contains(QStringLiteral("description"))) { m_metaInfo.insert(QStringLiteral("description"), info.value(QStringLiteral("description")).toString()); } if (info.contains(QStringLiteral("previews"))) { QMap previews = info.value(QStringLiteral("previews")).toMap(); if (previews.contains(QStringLiteral("preview-lq-mp3"))) { m_metaInfo.insert(QStringLiteral("itemPreview"), previews.value(QStringLiteral("preview-lq-mp3")).toString()); } if (previews.contains(QStringLiteral("preview-hq-mp3"))) { // Can use the HQ preview as alternative download if user does not have a freesound account m_metaInfo.insert(QStringLiteral("HQpreview"), previews.value(QStringLiteral("preview-hq-mp3")).toString()); } } if (info.contains(QStringLiteral("images"))) { QMap images = info.value(QStringLiteral("images")).toMap(); if (images.contains(QStringLiteral("waveform_m"))) { m_metaInfo.insert(QStringLiteral("itemImage"), images.value(QStringLiteral("waveform_m")).toString()); } } if (info.contains( QStringLiteral("download"))) { // this URL will start a download of the actual sound - if used in a browser while logged on to freesound m_metaInfo.insert(QStringLiteral("itemDownload"), info.value(QStringLiteral("download")).toString()); } if (info.contains(QStringLiteral("type"))) { // wav, aif, mp3 etc m_metaInfo.insert(QStringLiteral("fileType"), info.value(QStringLiteral("type")).toString()); } } emit gotMetaInfo(html); emit gotMetaInfo(m_metaInfo); emit gotThumb(m_metaInfo.value(QStringLiteral("itemImage"))); } /** * @brief FreeSound::startItemPreview * Starts playing preview version of the sound using ffplay * @param item * @return */ bool FreeSound::startItemPreview(QListWidgetItem *item) { if (!item) { return false; } const QString url = m_metaInfo.value(QStringLiteral("itemPreview")); if (url.isEmpty()) { return false; } if (m_previewProcess) { if (m_previewProcess->state() != QProcess::NotRunning) { m_previewProcess->close(); } qCDebug(KDENLIVE_LOG) << KdenliveSettings::ffplaypath() + QLatin1Char(' ') + url + QStringLiteral(" -nodisp -autoexit"); m_previewProcess->start(KdenliveSettings::ffplaypath(), QStringList() << url << QStringLiteral("-nodisp") << QStringLiteral("-autoexit")); } return true; } /** * @brief FreeSound::stopItemPreview */ void FreeSound::stopItemPreview(QListWidgetItem * /*item*/) { if ((m_previewProcess != nullptr) && m_previewProcess->state() != QProcess::NotRunning) { m_previewProcess->close(); } } /** * @brief FreeSound::getExtension * @param item * @return */ QString FreeSound::getExtension(QListWidgetItem *item) { if (!item) { return QString(); } QString sItem = item->text(); if (sItem.contains(QLatin1String("."))) { const QString sExt = sItem.section(QLatin1Char('.'), -1); return QStringLiteral("*.") + sExt; } return QString(); // return null if file name has no dots - ie no extension } /** * @brief FreeSound::getDefaultDownloadName * @param item * @return */ QString FreeSound::getDefaultDownloadName(QListWidgetItem *item) { if (!item) { return QString(); } return item->text(); } /** * @brief FreeSound::slotPreviewFinished * @param exitCode * @param exitStatus * Connected to the QProcess m_previewProcess finished signal * emits signal picked up by ResourceWidget that ResouceWidget uses * to set the Preview button back to the text Preview (it will say "Stop" before this.) */ void FreeSound::slotPreviewFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitCode); Q_UNUSED(exitStatus); emit previewFinished(); } void FreeSound::slotPreviewErrored(QProcess::ProcessError error) { qCDebug(KDENLIVE_LOG) << error; } diff --git a/src/utils/resourcewidget.cpp b/src/utils/resourcewidget.cpp index f6d37ddc8..7e67cf9fb 100644 --- a/src/utils/resourcewidget.cpp +++ b/src/utils/resourcewidget.cpp @@ -1,868 +1,868 @@ /*************************************************************************** * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) 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 "resourcewidget.h" #include "archiveorg.h" #include "freesound.h" #include "kdenlivesettings.h" #include "openclipart.h" #include "kdenlive_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QT5_USE_WEBKIT #include "qt-oauth-lib/oauth2.h" #endif ResourceWidget::ResourceWidget(const QString &folder, QWidget *parent) : QDialog(parent) , m_pOAuth2(nullptr) , m_folder(folder) , m_currentService(nullptr) , m_movie(nullptr) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setupUi(this); setAttribute(Qt::WA_DeleteOnClose); m_tmpThumbFile = new QTemporaryFile; service_list->addItem(i18n("Freesound Audio Library"), AbstractService::FREESOUND); service_list->addItem(i18n("Archive.org Video Library"), AbstractService::ARCHIVEORG); service_list->addItem(i18n("Open Clip Art Graphic Library"), AbstractService::OPENCLIPART); setWindowTitle(i18n("Search Online Resources")); QPalette p = palette(); p.setBrush(QPalette::Base, p.window()); info_browser->setPalette(p); connect(button_search, &QAbstractButton::clicked, this, &ResourceWidget::slotStartSearch); connect(search_results, &QListWidget::currentRowChanged, this, &ResourceWidget::slotUpdateCurrentSound); connect(button_preview, &QAbstractButton::clicked, this, &ResourceWidget::slotPlaySound); connect(button_import, SIGNAL(clicked()), this, SLOT(slotSaveItem())); connect(item_license, SIGNAL(leftClickedUrl(QString)), this, SLOT(slotOpenUrl(QString))); connect(service_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChangeService())); m_networkManager = new QNetworkConfigurationManager(this); if (!m_networkManager->isOnline()) { slotOnlineChanged(false); } connect(m_networkManager, &QNetworkConfigurationManager::onlineStateChanged, this, &ResourceWidget::slotOnlineChanged); connect(page_next, &QAbstractButton::clicked, this, &ResourceWidget::slotNextPage); connect(page_prev, &QAbstractButton::clicked, this, &ResourceWidget::slotPreviousPage); connect(page_number, static_cast(&QSpinBox::valueChanged), this, &ResourceWidget::slotStartSearch); connect(info_browser, &QTextBrowser::anchorClicked, this, &ResourceWidget::slotOpenLink); m_networkAccessManager = new QNetworkAccessManager(this); m_autoPlay = new QAction(i18n("Auto Play"), this); m_autoPlay->setCheckable(true); auto *resourceMenu = new QMenu; resourceMenu->addAction(m_autoPlay); config_button->setMenu(resourceMenu); config_button->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); sound_box->setEnabled(false); search_text->setFocus(); connect(search_text, SIGNAL(returnPressed()), this, SLOT(slotStartSearch())); #ifdef QT5_USE_WEBKIT m_pOAuth2 = new OAuth2(this); connect(m_pOAuth2, &OAuth2::accessTokenReceived, this, &ResourceWidget::slotAccessTokenReceived); connect(m_pOAuth2, &OAuth2::accessDenied, this, &ResourceWidget::slotFreesoundAccessDenied); connect(m_pOAuth2, &OAuth2::UseHQPreview, this, &ResourceWidget::slotFreesoundUseHQPreview); connect(m_pOAuth2, &OAuth2::Canceled, this, &ResourceWidget::slotFreesoundCanceled); #endif m_currentService = new FreeSound(search_results); m_currentService->slotStartSearch(QStringLiteral("dummy"), 0); // Run a dummy search to initialise the search. // for reasons I (ttguy) can not fathom the first search that gets run fails // with The file or folder http://www.freesound.org/apiv2/search/t does not exist. // but subsequent identicle search will work. With this kludge in place we do not have to click the search button // twice to get search to run slotChangeService(); loadConfig(); } /** * @brief ResourceWidget::~ResourceWidget */ ResourceWidget::~ResourceWidget() { delete m_currentService; delete m_tmpThumbFile; delete m_movie; delete m_networkAccessManager; saveConfig(); } /** * @brief ResourceWidget::loadConfig */ void ResourceWidget::loadConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup resourceConfig(config, "ResourceWidget"); const QList size = {100, 400}; splitter->setSizes(resourceConfig.readEntry("mainSplitter", size)); } /** * @brief ResourceWidget::saveConfig */ void ResourceWidget::saveConfig() { KSharedConfigPtr config = KSharedConfig::openConfig(); KConfigGroup resourceConfig(config, "ResourceWidget"); resourceConfig.writeEntry(QStringLiteral("mainsplitter"), splitter->size()); config->sync(); } /** * @brief ResourceWidget::slotStartSearch * @param page * connected to the button_search clicked signal in ResourceWidget constructor * also connected to the page_number value changed signal in ResourceWidget constructor * Calls slotStartSearch on the object for the currently selected service. Ie calls * FreeSound::slotStartSearch , ArchiveOrg:slotStartSearch and OpenClipArt:slotStartSearch */ void ResourceWidget::slotStartSearch(int page) { this->setCursor(Qt::WaitCursor); info_browser->clear(); page_number->blockSignals(true); page_number->setValue(page); page_number->blockSignals(false); m_currentService->slotStartSearch(search_text->text(), page); } /** * @brief ResourceWidget::slotUpdateCurrentSound - Fires when user selects a different item in the list of found items * This is not just for sounds. It fires for clip art and videos too. */ void ResourceWidget::slotUpdateCurrentSound() { if (!m_autoPlay->isChecked()) { m_currentService->stopItemPreview(nullptr); button_preview->setText(i18n("Preview")); } item_license->clear(); m_title.clear(); GifLabel->clear(); m_desc.clear(); m_meta.clear(); QListWidgetItem *item = search_results->currentItem(); // get the item the user selected if (!item) { sound_box->setEnabled(false); // sound_box is the group of objects in the Online resources window on the RHS with the // preview and import buttons and the details about the currently selected item. // if nothing is selected then we disable all these return; } m_currentInfo = m_currentService->displayItemDetails(item); // Not so much displaying the items details // This getting the items details into m_currentInfo if (m_autoPlay->isChecked() && m_currentService->hasPreview) { m_currentService->startItemPreview(item); } sound_box->setEnabled(true); // enable the group with the preview and import buttons QString title = "

    " + m_currentInfo.itemName; // title is not just a title. It is a whole lot of HTML for displaying the // the info for the current item. // updateLayout() adds m_image,m_desc and m_meta to the title to make up the html that displays on the widget if (!m_currentInfo.infoUrl.isEmpty()) { title += QStringLiteral(" (%2)").arg(m_currentInfo.infoUrl, i18nc("the url link pointing to a web page", "link")); } title.append(QStringLiteral("

    ")); if (!m_currentInfo.authorUrl.isEmpty()) { title += QStringLiteral("").arg(m_currentInfo.authorUrl); if (!m_currentInfo.author.isEmpty()) { title.append(m_currentInfo.author); } else { title.append(i18n("Author")); } title.append(QStringLiteral("
    ")); } else if (!m_currentInfo.author.isEmpty()) { title.append(m_currentInfo.author + QStringLiteral("
    ")); } else { title.append(QStringLiteral("
    ")); } slotSetTitle(title); // updates the m_title var with the new HTML. Calls updateLayout() // that sets/ updates the html in info_browser if (!m_currentInfo.description.isEmpty()) { slotSetDescription(m_currentInfo.description); } if (!m_currentInfo.license.isEmpty()) { parseLicense(m_currentInfo.license); } } /** * @brief Downloads thumbnail file from url and saves it to tmp directory - temporyFile * * The same temp file is recycled. * Loads the image into the ResourceWidget window. * Connected to signal AbstractService::GotThumb * */ void ResourceWidget::slotLoadThumb(const QString &url) { QUrl img(url); if (img.isEmpty()) { return; } m_tmpThumbFile->close(); if (m_tmpThumbFile->open()) { KIO::FileCopyJob *copyjob = KIO::file_copy(img, QUrl::fromLocalFile(m_tmpThumbFile->fileName()), -1, KIO::HideProgressInfo | KIO::Overwrite); if (copyjob->exec()) { slotSetImage(m_tmpThumbFile->fileName()); } } } /** * @brief ResourceWidget::slotLoadPreview * @param url * Only in use by the ArchiveOrg video search. This slot starts a job to copy down the amimated GIF for use as a preview file * slotLoadAnimatedGif is called when the download finishes. * The clipart search does not have a preview option and the freesound search loads the preview file on demand via slotPlaySound() */ void ResourceWidget::slotLoadPreview(const QString &url) { QUrl gif_url(url); if (gif_url.isEmpty()) { return; } m_tmpThumbFile->close(); if (m_tmpThumbFile->open()) { KIO::FileCopyJob *copyjob = KIO::file_copy(gif_url, QUrl::fromLocalFile(m_tmpThumbFile->fileName()), -1, KIO::HideProgressInfo | KIO::Overwrite); connect(copyjob, &KJob::result, this, &ResourceWidget::slotLoadAnimatedGif); copyjob->start(); } } /** * @brief ResourceWidget::slotLoadAnimatedGif * @param job * Notified when the download of the animated gif is completed. connected via ResourceWidget::slotLoadPreview * Displays this gif in the QLabel */ void ResourceWidget::slotLoadAnimatedGif(KJob *job) { if (job->error() == 0) { delete m_movie; m_movie = new QMovie(m_tmpThumbFile->fileName()); GifLabel->clear(); GifLabel->setMovie(m_movie); // pass a pointer to a QMovie m_movie->start(); } } /** * @brief ResourceWidget::slotDisplayMetaInfo - Fires when meta info has been updated * * connected to gotMetaInfo(QMap) slot in each of the services classes (FreeSound, ArchiveOrg). Copies itemDownload * from metaInfo into m_currentInfo - used by FreeSound case because with new freesound API the * item download data is obtained from a secondary location and is populated into metaInfo */ void ResourceWidget::slotDisplayMetaInfo(const QMap &metaInfo) { if (metaInfo.contains(QStringLiteral("license"))) { parseLicense(metaInfo.value(QStringLiteral("license"))); } if (metaInfo.contains(QStringLiteral("description"))) { slotSetDescription(metaInfo.value(QStringLiteral("description"))); } if (metaInfo.contains(QStringLiteral("itemDownload"))) { m_currentInfo.itemDownload = metaInfo.value(QStringLiteral("itemDownload")); } if (metaInfo.contains(QStringLiteral("itemPreview"))) { if (m_autoPlay->isChecked()) { m_currentService->startItemPreview(search_results->currentItem()); button_preview->setText(i18n("Stop")); } } if (metaInfo.contains(QStringLiteral("fileType"))) { m_currentInfo.fileType = metaInfo.value(QStringLiteral("fileType")); } if (metaInfo.contains(QStringLiteral("HQpreview"))) { m_currentInfo.HQpreview = metaInfo.value(QStringLiteral("HQpreview")); } } /** * @brief ResourceWidget::slotPlaySound * fires when button_preview is clicked. This button is clicked again to stop the preview */ void ResourceWidget::slotPlaySound() { if (!m_currentService) { return; } QString caption; const QString sPreview = i18n("Preview"); caption = button_preview->text(); if (caption.contains(sPreview)) { const bool started = m_currentService->startItemPreview(search_results->currentItem()); if (started) { button_preview->setText(i18n("Stop")); } } else { m_currentService->stopItemPreview(search_results->currentItem()); button_preview->setText(sPreview); } } /** * @brief Fires when import button on the ResourceWidget is clicked and also called by slotOpenLink() * * Opens a dialog for user to choose a save location. * If not freesound Starts a file download job and Connects the job to slotGotFile(). If is freesound creates an OAuth2 object to connect to freesounds oauth2 authentication. The OAuth2 object is connected to a number of slots including ResourceWidget::slotAccessTokenReceived Calls OAuth2::obtainAccessToken to kick off the freesound authentication */ void ResourceWidget::slotSaveItem(const QString &originalUrl) { QUrl saveUrl; QListWidgetItem *item = search_results->currentItem(); if (!item) { return; } QString path = m_folder; QString ext; QString sFileExt; if (!path.endsWith(QLatin1Char('/'))) { path.append(QLatin1Char('/')); } if (!originalUrl.isEmpty()) { path.append(QUrl(originalUrl).fileName()); ext = "*." + QUrl(originalUrl).fileName().section(QLatin1Char('.'), -1); m_currentInfo.itemDownload = originalUrl; } else { path.append(m_currentService->getDefaultDownloadName(item)); if (m_currentService->serviceType == AbstractService::FREESOUND) { #ifdef QT5_USE_WEBKIT sFileExt = m_currentService->getExtension(search_results->currentItem()); #else sFileExt = QStringLiteral("*.") + m_currentInfo.HQpreview.section(QLatin1Char('.'), -1); #endif if (sFileExt.isEmpty()) { sFileExt = QStringLiteral("*.") + m_currentInfo.fileType; // if the file name had no extension then use the file type freesound says it is. } ext = "Audio (" + sFileExt + QStringLiteral(");;All Files(*.*)"); } else if (m_currentService->serviceType == AbstractService::OPENCLIPART) { ext = "Images (" + m_currentService->getExtension(search_results->currentItem()) + QStringLiteral(");;All Files(*.*)"); } else { ext = "Video (" + m_currentService->getExtension(search_results->currentItem()) + QStringLiteral(");;All Files(*.*)"); } } QUrl srcUrl(m_currentInfo.itemDownload); mSaveLocation = GetSaveFileNameAndPathS(path, ext); if (mSaveLocation.isEmpty()) { // user canceled save return; } if (m_currentService->serviceType != AbstractService::FREESOUND) { saveUrl = QUrl::fromLocalFile(mSaveLocation); } slotSetDescription(QString()); button_import->setEnabled(false); // disable buttons while download runs. enabled in slotGotFile #ifdef QT5_USE_WEBKIT if (m_currentService->serviceType == AbstractService::FREESOUND) { // open a dialog to authenticate with free sound and download the file m_pOAuth2->obtainAccessToken(); // when job finished ResourceWidget::slotAccessTokenReceived will be called } else { // not freesound - do file download via a KIO file copy job DoFileDownload(srcUrl, QUrl(saveUrl)); } #else saveUrl = QUrl::fromLocalFile(mSaveLocation); if (m_currentService->serviceType == AbstractService::FREESOUND) { // No OAuth, default to HQ preview srcUrl = QUrl(m_currentInfo.HQpreview); } DoFileDownload(srcUrl, QUrl(saveUrl)); #endif } /** * @brief ResourceWidget::DoFileDownload * @param srcUrl source url * @param saveUrl destination url * Called by ResourceWidget::slotSaveItem() for non freesound downloads. Called by ResourceWidget::slotFreesoundUseHQPreview() * when user chooses to use HQ preview file from freesound * Starts a file copy job to download the file. When file finishes dowloading slotGotFile will be called */ void ResourceWidget::DoFileDownload(const QUrl &srcUrl, const QUrl &saveUrl) { KIO::FileCopyJob *getJob = KIO::file_copy(srcUrl, saveUrl, -1, KIO::Overwrite); KFileItem info(srcUrl); getJob->setSourceSize(info.size()); getJob->setProperty("license", item_license->text()); getJob->setProperty("licenseurl", item_license->url()); getJob->setProperty("originurl", m_currentInfo.itemDownload); if (!m_currentInfo.authorUrl.isEmpty()) { getJob->setProperty("author", m_currentInfo.authorUrl); } else if (!m_currentInfo.author.isEmpty()) { getJob->setProperty("author", m_currentInfo.author); } connect(getJob, &KJob::result, this, &ResourceWidget::slotGotFile); getJob->start(); } /** * @brief ResourceWidget::slotFreesoundUseHQPreview * Fires when user clicks the Use HQ preview button on the free sound login dialog */ void ResourceWidget::slotFreesoundUseHQPreview() { mSaveLocation = mSaveLocation + QStringLiteral(".mp3"); // HQ previews are .mp3 files - so append this to file name previously chosen if (QFile::exists(mSaveLocation)) { // check that this newly created file name file does not already exist int ret = QMessageBox::warning(this, i18n("File Exists"), i18n("HQ preview files are all mp3 files. We have added .mp3 as a file extension to the destination file name you chose. " "However, there is an existing file of this name present. \n Do you want to overwrite the existing file?. ") + QStringLiteral("\n") + mSaveLocation, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret == QMessageBox::No) { button_import->setEnabled(true); return; } } const QUrl saveUrl = QUrl::fromLocalFile(mSaveLocation); QUrl srcUrl(m_currentInfo.HQpreview); DoFileDownload(srcUrl, saveUrl); } /** * @brief ResourceWidget::slotFreesoundCanceled * Fires when user cancels out of the Freesound Login dialog */ void ResourceWidget::slotFreesoundCanceled() { button_import->setEnabled(true); } /** * @brief ResourceWidget::slotGotFile - fires when the file copy job started by ResourceWidget::slotSaveItem() completes * emits addClip which causes clip to be added to the project bin. * Enables the import button * @param job */ void ResourceWidget::slotGotFile(KJob *job) { button_import->setEnabled(true); if (job->error() != 0) { const QString errTxt = job->errorString(); KMessageBox::sorry(this, errTxt, i18n("Error Loading Data")); qCDebug(KDENLIVE_LOG) << "//file import job errored: " << errTxt; return; } KIO::FileCopyJob *copyJob = static_cast(job); const QUrl filePath = copyJob->destUrl(); KMessageBox::information(this, i18n("Resource saved to ") + filePath.toLocalFile(), i18n("Data Imported")); emit addClip(filePath, QStringList()); } /** * @brief ResourceWidget::slotOpenUrl. Opens the file on the URL using the associated application via a KRun object * * * called by slotOpenLink() so this will open .html in the users associated browser * @param url */ void ResourceWidget::slotOpenUrl(const QString &url) { new KRun(QUrl(url), this); } /** * @brief ResourceWidget::slotChangeService - fires when user changes what online resource they want to search agains via the dropdown list Also fires when widget first opens */ void ResourceWidget::slotChangeService() { info_browser->clear(); delete m_currentService; m_currentService = nullptr; AbstractService::SERVICETYPE service = static_cast(service_list->itemData(service_list->currentIndex()).toInt()); if (service == AbstractService::FREESOUND) { m_currentService = new FreeSound(search_results); } else if (service == AbstractService::OPENCLIPART) { m_currentService = new OpenClipArt(search_results); } else if (service == AbstractService::ARCHIVEORG) { m_currentService = new ArchiveOrg(search_results); connect(m_currentService, SIGNAL(gotPreview(QString)), this, SLOT(slotLoadPreview(QString))); } else { return; } connect(m_currentService, SIGNAL(gotMetaInfo(QString)), this, SLOT(slotSetMetadata(QString))); connect(m_currentService, SIGNAL(gotMetaInfo(QMap)), this, SLOT(slotDisplayMetaInfo(QMap))); connect(m_currentService, &AbstractService::maxPages, this, &ResourceWidget::slotSetMaximum); connect(m_currentService, &AbstractService::searchInfo, search_info, &QLabel::setText); connect(m_currentService, &AbstractService::gotThumb, this, &ResourceWidget::slotLoadThumb); connect(m_currentService, &AbstractService::searchDone, this, &ResourceWidget::slotSearchFinished); if (m_currentService->hasPreview) { connect(m_currentService, SIGNAL(previewFinished()), this, SLOT(slotPreviewFinished())); } button_preview->setVisible(m_currentService->hasPreview); button_import->setVisible(!m_currentService->inlineDownload); search_info->setText(QString()); if (!search_text->text().isEmpty()) { slotStartSearch(); // automatically kick of a search if we have search text and we switch services. } } void ResourceWidget::slotSearchFinished() { this->setCursor(Qt::ArrowCursor); } /** * @brief ResourceWidget::slotSetMaximum * @param max */ void ResourceWidget::slotSetMaximum(int max) { page_number->setMaximum(max); } /** * @brief ResourceWidget::slotOnlineChanged * @param online */ void ResourceWidget::slotOnlineChanged(bool online) { button_search->setEnabled(online); search_info->setText(online ? QString() : i18n("You need to be online\n for searching")); } /** * @brief ResourceWidget::slotNextPage */ void ResourceWidget::slotNextPage() { const int ix = page_number->value(); if (search_results->count() > 0) { page_number->setValue(ix + 1); } } /** * @brief ResourceWidget::slotPreviousPage */ void ResourceWidget::slotPreviousPage() { const int ix = page_number->value(); if (ix > 1) { page_number->setValue(ix - 1); } } /** * @brief ResourceWidget::parseLicense provides a name for the licence based on the license URL * called by ResourceWidget::slotDisplayMetaInfo and by ResourceWidget::slotUpdateCurrentSound * @param licenseUrl */ void ResourceWidget::parseLicense(const QString &licenseUrl) { QString licenseName; // TODO translate them ? if (licenseUrl.contains(QStringLiteral("/sampling+/"))) { licenseName = QStringLiteral("Sampling+"); } else if (licenseUrl.contains(QStringLiteral("/by/"))) { licenseName = QStringLiteral("Attribution"); } else if (licenseUrl.contains(QStringLiteral("/by-nd/"))) { licenseName = QStringLiteral("Attribution-NoDerivs"); } else if (licenseUrl.contains(QStringLiteral("/by-nc-sa/"))) { licenseName = QStringLiteral("Attribution-NonCommercial-ShareAlike"); } else if (licenseUrl.contains(QStringLiteral("/by-sa/"))) { licenseName = QStringLiteral("Attribution-ShareAlike"); } else if (licenseUrl.contains(QStringLiteral("/by-nc/"))) { licenseName = QStringLiteral("Attribution-NonCommercial"); } else if (licenseUrl.contains(QStringLiteral("/by-nc-nd/"))) { licenseName = QStringLiteral("Attribution-NonCommercial-NoDerivs"); } else if (licenseUrl.contains(QLatin1String("/publicdomain/zero/"))) { licenseName = QStringLiteral("Creative Commons 0"); } else if (licenseUrl.endsWith(QLatin1String("/publicdomain")) || licenseUrl.contains(QLatin1String("openclipart.org/share"))) { licenseName = QStringLiteral("Public Domain"); } else { licenseName = i18n("Unknown"); } item_license->setText(licenseName); item_license->setUrl(licenseUrl); } /** * @brief ResourceWidget::slotOpenLink. Fires when Url in the resource wizard is clicked * @param url * Connected to anchorClicked(). If the url ends in _import it downloads the file at the end of the url via slotSaveItem(). * We have created URLs deliberately tagged with _import in ArchiveOrg::slotParseResults. If the URL is tagged in this way we remove the tag * and download the file. * Otherwise it opens the URL via slotOpenUrl() which opens the URL in the systems default web browser */ void ResourceWidget::slotOpenLink(const QUrl &url) { QString path = url.toEncoded(); if (path.endsWith(QLatin1String("_import"))) { // path.chop(7); // import file in Kdenlive slotSaveItem(path); } else { slotOpenUrl(path); } } /** * @brief ResourceWidget::slotSetDescription Updates the display with the description text * @param desc /n * The description is either the detailed description of the file or is progress messages on the download process * -*/ + */ void ResourceWidget::slotSetDescription(const QString &desc) { if (m_desc != desc) { m_desc = desc; updateLayout(); } } /** * @brief ResourceWidget::slotSetMetadata updates the display with the metadata. * @param desc /n * The meta data is info on the sounds length, samplerate, filesize and number of channels. This is called when gotMetaInfo(Qstring) signal fires. That signal * is passing html in the parameter * This function is updating the html (m_meta) in the ResourceWidget and then calls updateLayout() * which updates actual widget */ void ResourceWidget::slotSetMetadata(const QString &metadata) { if (m_meta != metadata) { m_meta = metadata; updateLayout(); } } /** * @brief ResourceWidget::slotSetImage Sets a thumbnail on the widget * @param desc * called by ResourceWidget::slotLoadThumb \n * This sets a thumb nail image onto a label on the resource widget. If it is a animated .gif it will play */ void ResourceWidget::slotSetImage(const QString &desc) { QPixmap pic(desc); GifLabel->setPixmap(pic); // pass a pointer as a parameter. Display the pic in our lable } /** @brief updates the display with infomation on the seleted item. The title consists of the sounds file name and the author * * Called by ResourceWidget::slotUpdateCurrentSound() -*/ + */ void ResourceWidget::slotSetTitle(const QString &title) { if (m_title != title) { m_title = title; updateLayout(); } } /** * @brief ResourceWidget::updateLayout * This concats the html in m_title, m_desc and m_meta and sets the resulting * html markup into the content of the ResourceWidget \n * Called by slotSetTitle() , slotSetMetadata() ,slotSetDescription() */ void ResourceWidget::updateLayout() { QString content = m_title; if (!m_desc.isEmpty()) { content.append(m_desc); } if (!m_meta.isEmpty()) { content.append(m_meta); } info_browser->setHtml(content); } /** * @brief ResourceWidget::slotPreviewFinished * connected to FreeSound previewFinished signal */ void ResourceWidget::slotPreviewFinished() { button_preview->setText(i18n("Preview")); } /** * @brief ResourceWidget::slotFreesoundAccessDenied * Will fire if freesound denies access - eg wrong password entered. */ void ResourceWidget::slotFreesoundAccessDenied() { button_import->setEnabled(true); info_browser->setHtml(QStringLiteral("") + i18n("Access Denied from Freesound. Have you authorised the Kdenlive application on your freesound account?") + QStringLiteral("")); } /** * @brief ResourceWidget::slotAccessTokenReceived * @param sAccessToken - the access token obtained from freesound website \n * Connected to OAuth2::accessTokenReceived signal in ResourceWidget constructor. * Fires when the OAuth2 object has obtained an access token. This slot then goes ahead * and starts the download of the requested file. ResourceWidget::DownloadRequestFinished will be * notified when that job finishes */ void ResourceWidget::slotAccessTokenReceived(const QString &sAccessToken) { // qCDebug(KDENLIVE_LOG) << "slotAccessTokenReceived: " <") + i18n("Starting File Download") + QStringLiteral("
    "); updateLayout(); QNetworkReply *reply2 = m_networkAccessManager->get(request); connect(reply2, &QIODevice::readyRead, this, &ResourceWidget::slotReadyRead); connect(m_networkAccessManager, &QNetworkAccessManager::finished, this, &ResourceWidget::DownloadRequestFinished); } else { m_meta = QString(); m_desc = QStringLiteral("
    ") + i18n("Error Getting Access Token from Freesound.") + QStringLiteral(""); m_desc.append(QStringLiteral("
    ") + i18n("Try importing again to obtain a new freesound connection") + QStringLiteral("")); updateLayout(); } } /** * @brief ResourceWidget::GetSaveFileNameAndPathS * @param path - starting path where dialog will open on * @param ext - file exension filter to have in play on the dialog * @return QString of the chosen path * Prompts user to choose a file name and path as to where to save a file. * Returns a QString of the path and file name or empty string if user cancels */ QString ResourceWidget::GetSaveFileNameAndPathS(const QString &path, const QString &ext) { QString saveUrlstring = QFileDialog::getSaveFileName(this, QString(), path, ext); if (saveUrlstring.isEmpty()) // only check if the save url is empty (ie if user cancels the save.) // If the URL has no file at the end we trap this error in slotGotFile. { return saveUrlstring; } if (QFile::exists(saveUrlstring)) { int ret = QMessageBox::warning(this, i18n("File Exists"), i18n("Do you want to overwrite the existing file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (ret == QMessageBox::No) { return QString(); } } return saveUrlstring; } /** * @brief ResourceWidget::slotReadyRead * Fires each time the download of the freesound file grabs some more data. * Prints dots to the dialog indicating download is progressing. */ void ResourceWidget::slotReadyRead() { m_desc.append(QLatin1Char('.')); updateLayout(); } /** * @brief ResourceWidget::DownloadRequestFinished * @param reply * Fires when the download of the freesound file completes. * If the download was successfull this saves the data from memory to the file system. Emits an ResourceWidget::addClip signal. * MainWindow::slotDownloadResources() links this signal to MainWindow::slotAddProjectClip * If the download has failed with AuthenticationRequiredError then it requests a new access token via the refresh token method * and then the download process will retry. * If the download fails for other reasons this reports an error and clears out the access token from memory. * If the user requests the file import again it will request a login to free sound again and the download might suceed on this second try */ void ResourceWidget::DownloadRequestFinished(QNetworkReply *reply) { button_import->setEnabled(true); if (reply->isFinished()) { if (reply->error() == QNetworkReply::NoError) { QByteArray aSoundData = reply->readAll(); QFile file(mSaveLocation); if (file.open(QIODevice::WriteOnly)) { file.write(aSoundData); file.close(); KMessageBox::information(this, i18n("Resource saved to ") + mSaveLocation, i18n("Data Imported")); emit addClip(QUrl(mSaveLocation), QStringList()); // MainWindow::slotDownloadResources() links this signal to MainWindow::slotAddProjectClip m_desc.append(QStringLiteral("
    ") + i18n("Saved file to") + QStringLiteral("
    ")); m_desc.append(mSaveLocation); updateLayout(); } else { #ifdef QT5_USE_WEBKIT m_pOAuth2->ForgetAccessToken(); #endif m_desc.append(QStringLiteral("
    ") + i18n("Error Saving File")); updateLayout(); } } else { if (reply->error() == QNetworkReply::AuthenticationRequiredError) { #ifdef QT5_USE_WEBKIT m_pOAuth2->obtainNewAccessToken(); #endif } else { #ifdef QT5_USE_WEBKIT m_pOAuth2->ForgetAccessToken(); #endif m_desc.append(QStringLiteral("
    ") + i18n("Error Downloading File. Error code: ") + reply->error() + QStringLiteral("
    ")); m_desc.append(QStringLiteral("
    ") + i18n("Try importing again to obtain a new freesound connection") + QStringLiteral("")); updateLayout(); } } } reply->deleteLater(); } diff --git a/src/utils/thumbnailcache.cpp b/src/utils/thumbnailcache.cpp index 7037309cf..bfaab0a8b 100644 --- a/src/utils/thumbnailcache.cpp +++ b/src/utils/thumbnailcache.cpp @@ -1,204 +1,204 @@ /*************************************************************************** * 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 "thumbnailcache.hpp" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "doc/kdenlivedoc.h" #include #include #include std::unique_ptr ThumbnailCache::instance; std::once_flag ThumbnailCache::m_onceFlag; class ThumbnailCache::Cache_t { public: Cache_t(int maxCost) : m_maxCost(maxCost) { } bool contains(const QString &key) const { return m_cache.count(key) > 0; } void remove(const QString &key) { if (!contains(key)) { return; } auto it = m_cache.at(key); m_currentCost -= (*it).second.second; m_data.erase(it); m_cache.erase(key); } void insert(const QString &key, const QImage &img, int cost) { if (cost > m_maxCost) { return; } m_data.push_front({key, {img, cost}}); auto it = m_data.begin(); m_cache[key] = it; m_currentCost += cost; while (m_currentCost > m_maxCost) { remove(m_data.back().first); } } QImage get(const QString &key) { if (!contains(key)) { return QImage(); } // when a get operation occurs, we put the corresponding list item in front to remember last access std::pair> data; auto it = m_cache.at(key); std::swap(data, (*it)); // take data out without copy QImage result = data.second.first; // a copy occurs here m_data.erase(it); // delete old iterator m_cache[key] = m_data.emplace(m_data.begin(), std::move(data)); // reinsert without copy and store iterator return result; } protected: int m_maxCost; int m_currentCost{0}; std::list>> m_data; // the data is stored as (key,(image, cost)) std::unordered_map m_cache; }; ThumbnailCache::ThumbnailCache() : m_volatileCache(new Cache_t(10000000)) { } std::unique_ptr &ThumbnailCache::get() { std::call_once(m_onceFlag, [] { instance.reset(new ThumbnailCache()); }); return instance; } bool ThumbnailCache::hasThumbnail(const QString &binId, int pos, bool volatileOnly) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = getKey(binId, pos, &ok); if (ok && m_volatileCache->contains(key)) { return true; } if (!ok || volatileOnly) { return false; } QDir thumbFolder = getDir(&ok); return ok && thumbFolder.exists(key); } QImage ThumbnailCache::getThumbnail(const QString &binId, int pos, bool volatileOnly) const { QMutexLocker locker(&m_mutex); bool ok = false; auto key = getKey(binId, pos, &ok); if (ok && m_volatileCache->contains(key)) { return m_volatileCache->get(key); } if (!ok || volatileOnly) { return QImage(); } QDir thumbFolder = getDir(&ok); if (ok && thumbFolder.exists(key)) { return QImage(thumbFolder.absoluteFilePath(key)); } return QImage(); } void ThumbnailCache::storeThumbnail(const QString &binId, int pos, const QImage &img, bool persistent) { QMutexLocker locker(&m_mutex); bool ok = false; const QString key = getKey(binId, pos, &ok); if (!ok) { return; } if (persistent) { QDir thumbFolder = getDir(&ok); if (ok) { if (!img.save(thumbFolder.absoluteFilePath(key))) { - qDebug()<<".............\nAAAAAAAAAAAARGH ERROR SAVING THUMB"; + qDebug() << ".............\nAAAAAAAAAAAARGH ERROR SAVING THUMB"; } m_storedOnDisk[binId].push_back(pos); // if volatile cache also contains this entry, update it if (m_volatileCache->contains(key)) { m_volatileCache->remove(key); } else { m_storedVolatile[binId].push_back(pos); } m_volatileCache->insert(key, img, img.byteCount()); } } else { m_volatileCache->insert(key, img, img.byteCount()); m_storedVolatile[binId].push_back(pos); } } void ThumbnailCache::invalidateThumbsForClip(const QString &binId) { QMutexLocker locker(&m_mutex); if (m_storedVolatile.find(binId) != m_storedVolatile.end()) { bool ok = false; for (int pos : m_storedVolatile.at(binId)) { auto key = getKey(binId, pos, &ok); if (ok) { m_volatileCache->remove(key); } } m_storedVolatile.erase(binId); } bool ok = false; QDir thumbFolder = getDir(&ok); if (ok && m_storedOnDisk.find(binId) != m_storedOnDisk.end()) { // Remove persistent cache for (int pos : m_storedOnDisk.at(binId)) { auto key = getKey(binId, pos, &ok); if (ok) { QFile::remove(thumbFolder.absoluteFilePath(key)); } } m_storedOnDisk.erase(binId); } } // static QString ThumbnailCache::getKey(const QString &binId, int pos, bool *ok) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); *ok = binClip != nullptr; return *ok ? binClip->hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".png") : QString(); } // static QDir ThumbnailCache::getDir(bool *ok) { return pCore->currentDoc()->getCacheDir(CacheThumbs, ok); } diff --git a/src/widgets/choosecolorwidget.cpp b/src/widgets/choosecolorwidget.cpp index 6d0f576ab..e8805d736 100644 --- a/src/widgets/choosecolorwidget.cpp +++ b/src/widgets/choosecolorwidget.cpp @@ -1,141 +1,141 @@ /*************************************************************************** * 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 "choosecolorwidget.h" #include "colorpickerwidget.h" #include #include #include #include static QColor stringToColor(QString strColor) { bool ok = false; QColor color("black"); uint intval = 0; if (strColor.startsWith(QLatin1String("0x"))) { if (strColor.length() == 10) { // 0xRRGGBBAA intval = strColor.toUInt(&ok, 16); color.setRgb((int)((intval >> 24) & 0xff), // r - (intval >> 16) & 0xff, // g - (intval >> 8) & 0xff, // b - intval & 0xff); // a + (intval >> 16) & 0xff, // g + (intval >> 8) & 0xff, // b + intval & 0xff); // a } else { // 0xRRGGBB, 0xRGB color.setNamedColor(strColor.replace(0, 2, QLatin1Char('#'))); } } else { if (strColor.length() == 9) { // #AARRGGBB strColor = strColor.replace('#', QLatin1String("0x")); intval = strColor.toUInt(&ok, 16); - color.setRgb((intval >> 16) & 0xff, // r - (intval >> 8) & 0xff, // g - intval&0xff, // b + color.setRgb((intval >> 16) & 0xff, // r + (intval >> 8) & 0xff, // g + intval & 0xff, // b (int)((intval >> 24) & 0xff)); // a } else if (strColor.length() == 8) { // 0xRRGGBB strColor = strColor.replace('#', QLatin1String("0x")); color.setNamedColor(strColor); } else { // #RRGGBB, #RGB color.setNamedColor(strColor); } } return color; } static QString colorToString(const QColor &color, bool alpha) { QString colorStr; QTextStream stream(&colorStr); stream << "0x"; stream.setIntegerBase(16); stream.setFieldWidth(2); stream.setFieldAlignment(QTextStream::AlignRight); stream.setPadChar('0'); stream << color.red() << color.green() << color.blue(); if (alpha) { stream << color.alpha(); } else { // MLT always wants 0xRRGGBBAA format stream << "ff"; } return colorStr; } ChooseColorWidget::ChooseColorWidget(const QString &text, const QString &color, const QString &comment, bool alphaEnabled, QWidget *parent) : QWidget(parent) { auto *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); QLabel *label = new QLabel(text, this); QWidget *rightSide = new QWidget(this); auto *rightSideLayout = new QHBoxLayout(rightSide); rightSideLayout->setContentsMargins(0, 0, 0, 0); rightSideLayout->setSpacing(0); m_button = new KColorButton(stringToColor(color), rightSide); if (alphaEnabled) { m_button->setAlphaChannelEnabled(alphaEnabled); } // m_button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); auto *picker = new ColorPickerWidget(rightSide); layout->addWidget(label, 1); layout->addWidget(rightSide, 1); rightSideLayout->addStretch(); rightSideLayout->addWidget(m_button, 2); rightSideLayout->addWidget(picker); connect(picker, &ColorPickerWidget::colorPicked, this, &ChooseColorWidget::setColor); connect(picker, &ColorPickerWidget::disableCurrentFilter, this, &ChooseColorWidget::disableCurrentFilter); connect(m_button, &KColorButton::changed, this, &ChooseColorWidget::modified); // connect the signal of the derived class to the signal of the base class connect(this, &ChooseColorWidget::modified, [this](const QColor &) { emit valueChanged(); }); // setup comment setToolTip(comment); } QString ChooseColorWidget::getColor() const { bool alphaChannel = m_button->isAlphaChannelEnabled(); return colorToString(m_button->color(), alphaChannel); } void ChooseColorWidget::setColor(const QColor &color) { m_button->setColor(color); } void ChooseColorWidget::slotColorModified(const QColor &color) { blockSignals(true); m_button->setColor(color); blockSignals(false); } diff --git a/src/widgets/choosecolorwidget.h b/src/widgets/choosecolorwidget.h index 287af559a..86eedabce 100644 --- a/src/widgets/choosecolorwidget.h +++ b/src/widgets/choosecolorwidget.h @@ -1,69 +1,69 @@ /*************************************************************************** * 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 CHOOSECOLORWIDGET_H #define CHOOSECOLORWIDGET_H #include class KColorButton; /** * @class ChooseColorWidget * @brief Provides options to choose a color. Two mechanisms are provided: color-picking directly on the screen and choosing from a list * @author Till Theato */ class ChooseColorWidget : public QWidget { Q_OBJECT public: /** @brief Sets up the widget. - * @param name (optional) What the color will be used for (name of the parameter) - * @param color (optional) initial color - * @param comment (optional) Comment about the parameter - * @param alphaEnabled (optional) Should transparent colors be enabled - * @param parent(optional) Parent widget - */ + * @param name (optional) What the color will be used for (name of the parameter) + * @param color (optional) initial color + * @param comment (optional) Comment about the parameter + * @param alphaEnabled (optional) Should transparent colors be enabled + * @param parent(optional) Parent widget + */ explicit ChooseColorWidget(const QString &name = QString(), const QString &color = QStringLiteral("0xffffffff"), const QString &comment = QString(), bool alphaEnabled = false, QWidget *parent = 0); /** @brief Gets the chosen color. */ QString getColor() const; private: KColorButton *m_button; public slots: void slotColorModified(const QColor &color); private slots: /** @brief Updates the different color choosing options to have all selected @param color. */ void setColor(const QColor &color); signals: /** @brief Emitted whenever a different color was chosen. */ void modified(QColor = QColor()); void disableCurrentFilter(bool); void valueChanged(); }; #endif diff --git a/src/widgets/colorpickerwidget.cpp b/src/widgets/colorpickerwidget.cpp index 71e17d934..413c70b53 100644 --- a/src/widgets/colorpickerwidget.cpp +++ b/src/widgets/colorpickerwidget.cpp @@ -1,256 +1,257 @@ /*************************************************************************** * 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 "colorpickerwidget.h" #include "utils/KoIconUtils.h" #include #include #include #include #include #include #include #include #include #ifdef Q_WS_X11 #include #include #endif MyFrame::MyFrame(QWidget *parent) : QFrame(parent) { setFrameStyle(QFrame::Box | QFrame::Plain); setWindowOpacity(0.5); setWindowFlags(Qt::FramelessWindowHint); } // virtual void MyFrame::hideEvent(QHideEvent *event) { QFrame::hideEvent(event); // We need a timer here since hiding the frame will trigger a monitor refresh timer that will // repaint the monitor after 70 ms. QTimer::singleShot(250, this, &MyFrame::getColor); } ColorPickerWidget::ColorPickerWidget(QWidget *parent) : QWidget(parent) , m_filterActive(false) { #ifdef Q_WS_X11 m_image = nullptr; #endif auto *layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); auto *button = new QToolButton(this); button->setIcon(KoIconUtils::themedIcon(QStringLiteral("color-picker"))); - button->setToolTip(QStringLiteral("

    ") + i18n("Pick a color on the screen. By pressing the mouse button and then moving your mouse you can select a " - "section of the screen from which to get an average color.") + + button->setToolTip(QStringLiteral("

    ") + + i18n("Pick a color on the screen. By pressing the mouse button and then moving your mouse you can select a " + "section of the screen from which to get an average color.") + QStringLiteral("

    ")); button->setAutoRaise(true); connect(button, &QAbstractButton::clicked, this, &ColorPickerWidget::slotSetupEventFilter); layout->addWidget(button); setFocusPolicy(Qt::StrongFocus); m_grabRectFrame = new MyFrame(); m_grabRectFrame->hide(); } ColorPickerWidget::~ColorPickerWidget() { delete m_grabRectFrame; if (m_filterActive) { removeEventFilter(this); } } void ColorPickerWidget::slotGetAverageColor() { disconnect(m_grabRectFrame, SIGNAL(getColor()), this, SLOT(slotGetAverageColor())); m_grabRect = m_grabRect.normalized(); int numPixel = m_grabRect.width() * m_grabRect.height(); int sumR = 0; int sumG = 0; int sumB = 0; /* Only getting the image once for the whole rect results in a vast speed improvement. */ #ifdef Q_WS_X11 Window root = RootWindow(QX11Info::display(), QX11Info::appScreen()); m_image = XGetImage(QX11Info::display(), root, m_grabRect.x(), m_grabRect.y(), m_grabRect.width(), m_grabRect.height(), -1, ZPixmap); #else QScreen *currentScreen = QApplication::primaryScreen(); if (currentScreen) { m_image = currentScreen->grabWindow(0, m_grabRect.x(), m_grabRect.y(), m_grabRect.width(), m_grabRect.height()).toImage(); } #endif for (int x = 0; x < m_grabRect.width(); ++x) { for (int y = 0; y < m_grabRect.height(); ++y) { QColor color = grabColor(QPoint(x, y), false); sumR += color.red(); sumG += color.green(); sumB += color.blue(); } } #ifdef Q_WS_X11 XDestroyImage(m_image); m_image = nullptr; #endif emit colorPicked(QColor(sumR / numPixel, sumG / numPixel, sumB / numPixel)); emit disableCurrentFilter(false); } void ColorPickerWidget::mousePressEvent(QMouseEvent *event) { if (event->button() != Qt::LeftButton) { closeEventFilter(); emit disableCurrentFilter(false); event->accept(); return; } if (m_filterActive) { m_grabRect = QRect(event->globalPos(), QSize(1, 1)); m_grabRectFrame->setGeometry(m_grabRect); m_grabRectFrame->show(); } QWidget::mousePressEvent(event); } void ColorPickerWidget::mouseReleaseEvent(QMouseEvent *event) { if (m_filterActive) { closeEventFilter(); m_grabRect.setWidth(event->globalX() - m_grabRect.x()); m_grabRect.setHeight(event->globalY() - m_grabRect.y()); m_grabRect = m_grabRect.normalized(); if (m_grabRect.width() * m_grabRect.height() == 0) { m_grabRectFrame->hide(); emit colorPicked(grabColor(event->globalPos())); emit disableCurrentFilter(false); } else { // delay because m_grabRectFrame does not hide immediately connect(m_grabRectFrame, SIGNAL(getColor()), this, SLOT(slotGetAverageColor())); m_grabRectFrame->hide(); } return; } QWidget::mouseReleaseEvent(event); } void ColorPickerWidget::mouseMoveEvent(QMouseEvent *event) { if (m_filterActive) { m_grabRect.setWidth(event->globalX() - m_grabRect.x()); m_grabRect.setHeight(event->globalY() - m_grabRect.y()); m_grabRectFrame->setGeometry(m_grabRect.normalized()); } QWidget::mouseMoveEvent(event); } void ColorPickerWidget::slotSetupEventFilter() { emit disableCurrentFilter(true); m_filterActive = true; setFocus(); installEventFilter(this); grabMouse(QCursor(KoIconUtils::themedIcon(QStringLiteral("color-picker")).pixmap(32, 32), 16, 2)); grabKeyboard(); } void ColorPickerWidget::closeEventFilter() { m_filterActive = false; releaseMouse(); releaseKeyboard(); removeEventFilter(this); } bool ColorPickerWidget::eventFilter(QObject *object, QEvent *event) { // Close color picker on any key press if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) { closeEventFilter(); emit disableCurrentFilter(false); event->setAccepted(true); return true; } return QObject::eventFilter(object, event); } QColor ColorPickerWidget::grabColor(const QPoint &p, bool destroyImage) { #ifdef Q_WS_X11 /* we use the X11 API directly in this case as we are not getting back a valid return from QPixmap::grabWindow in the case where the application is using an argb visual */ if (!qApp->desktop()->geometry().contains(p)) { return QColor(); } unsigned long xpixel; if (m_image == nullptr) { Window root = RootWindow(QX11Info::display(), QX11Info::appScreen()); m_image = XGetImage(QX11Info::display(), root, p.x(), p.y(), 1, 1, -1, ZPixmap); xpixel = XGetPixel(m_image, 0, 0); } else { xpixel = XGetPixel(m_image, p.x(), p.y()); } if (destroyImage) { XDestroyImage(m_image); m_image = 0; } XColor xcol; xcol.pixel = xpixel; xcol.flags = DoRed | DoGreen | DoBlue; XQueryColor(QX11Info::display(), DefaultColormap(QX11Info::display(), QX11Info::appScreen()), &xcol); return QColor::fromRgbF(xcol.red / 65535.0, xcol.green / 65535.0, xcol.blue / 65535.0); #else Q_UNUSED(destroyImage) if (m_image.isNull()) { QScreen *currentScreen = QApplication::primaryScreen(); if (currentScreen) { QPixmap pm = currentScreen->grabWindow(0, p.x(), p.y(), 1, 1); QImage i = pm.toImage(); return i.pixel(0, 0); } return qRgb(0, 0, 0); } return m_image.pixel(p.x(), p.y()); #endif } diff --git a/src/widgets/doublewidget.h b/src/widgets/doublewidget.h index 806958b56..a9ddc40d4 100644 --- a/src/widgets/doublewidget.h +++ b/src/widgets/doublewidget.h @@ -1,88 +1,88 @@ /*************************************************************************** * Copyright (C) 2010 by Till Theato (root@ttill.de) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef DOUBLEWIDGET_H #define DOUBLEWIDGET_H #include class DragValue; /** * @class DoubleWidget * @brief Widget to choose a double parameter (for a effect) with the help of a slider and a spinbox. * @author Till Theato * * The widget does only handle integers, so the parameter has to be converted into the proper double range afterwards. */ class DoubleWidget : public QWidget { Q_OBJECT public: /** @brief Sets up the parameter's GUI. - * @param name Name of the parameter - * @param value Value of the parameter - * @param min Minimum value - * @param max maximum value - * @param defaultValue Value used when using reset functionality - * @param comment A comment explaining the parameter. Will be shown in a tooltip. - * @param suffix (optional) Suffix to display in spinbox - * @param parent (optional) Parent Widget */ + * @param name Name of the parameter + * @param value Value of the parameter + * @param min Minimum value + * @param max maximum value + * @param defaultValue Value used when using reset functionality + * @param comment A comment explaining the parameter. Will be shown in a tooltip. + * @param suffix (optional) Suffix to display in spinbox + * @param parent (optional) Parent Widget */ explicit DoubleWidget(const QString &name, double value, double min, double max, double defaultValue, const QString &comment, int id, const QString &suffix = QString(), int decimals = 0, QWidget *parent = nullptr); ~DoubleWidget(); /** @brief The factor by which real param value is multiplicated to give the slider value. */ double factor; /** @brief Gets the parameter's value. */ double getValue(); /** @brief Returns minimum size for QSpinBox, used to set all spinboxes to the same width. */ int spinSize(); void setSpinSize(int width); void enableEdit(bool enable); /** @brief Returns true if widget is currently being edited */ bool hasEditFocus() const; public slots: /** @brief Sets the value to @param value. */ void setValue(double value); /** @brief Sets value to m_default. */ void slotReset(); /** @brief Shows/Hides the comment label. */ void slotShowComment(bool show); private slots: void slotSetValue(double value, bool final); private: DragValue *m_dragVal; signals: void valueChanged(double); // same signal as valueChanged, but add an extra boolean to tell if user is dragging value or not void valueChanging(double, bool); }; #endif diff --git a/src/widgets/dragvalue.cpp b/src/widgets/dragvalue.cpp index 410b385b7..9feb25891 100644 --- a/src/widgets/dragvalue.cpp +++ b/src/widgets/dragvalue.cpp @@ -1,549 +1,549 @@ /*************************************************************************** * 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 "utils/KoIconUtils.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, 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); if (decimals == 0) { m_label->setMaximum(max - min); m_label->setStep(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); l->addWidget(m_intEdit); - connect(m_intEdit, static_cast(&QSpinBox::valueChanged), - this, static_cast(&DragValue::slotSetValue)); + 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(KoIconUtils::themedIcon(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(KoIconUtils::themedIcon(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_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 (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 (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::Maximum); if (showSlider) { setRange(0, 1000); } else { setRange(0, range); QSize sh; const QFontMetrics &fm = fontMetrics(); sh.setWidth(fm.width(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 (nv != value()) { setNewValue(nv, KdenliveSettings::dragvalue_directupdate()); } } else { double nv = minimum() + ((double)maximum() - minimum()) / width() * e->pos().x(); if (nv != value()) { 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); 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 cfef94a3d..621f8973f 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 - */ + * @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); virtual ~DragValue(); /** @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 448184b66..4fcd61724 100644 --- a/src/widgets/geometrywidget.cpp +++ b/src/widgets/geometrywidget.cpp @@ -1,439 +1,442 @@ /*************************************************************************** * 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 "core.h" #include "monitor/monitor.h" #include "utils/KoIconUtils.h" -#include #include +#include -GeometryWidget::GeometryWidget(Monitor *monitor, QPair range, const QRect &rect, const QSize frameSize, bool useRatioLock, bool useOpacity, QWidget *parent) +GeometryWidget::GeometryWidget(Monitor *monitor, QPair range, const QRect &rect, const QSize frameSize, bool useRatioLock, bool useOpacity, + QWidget *parent) : QWidget(parent) , m_min(range.first) , m_max(range.second) , m_active(false) , m_monitor(monitor) , m_opacity(nullptr) { Q_UNUSED(useRatioLock) setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); 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); 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); 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); connect(m_spinWidth, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectWidth); horLayout->addWidget(m_spinWidth); // Lock ratio stuff m_lockRatio = new QAction(KoIconUtils::themedIcon(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); 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->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); connect(m_opacity, &DragValue::valueChanged, this, &GeometryWidget::slotAdjustRectKeyframeValue); horLayout2->addWidget(m_opacity); } horLayout2->addStretch(10); // Build buttons m_originalSize = new QAction(KoIconUtils::themedIcon(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(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best")), i18n("Adjust and center in frame"), this); connect(adjustSize, &QAction::triggered, this, &GeometryWidget::slotAdjustToFrameSize); QAction *fitToWidth = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-width")), i18n("Fit to width"), this); connect(fitToWidth, &QAction::triggered, this, &GeometryWidget::slotFitToWidth); QAction *fitToHeight = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-fit-height")), i18n("Fit to height"), this); connect(fitToHeight, &QAction::triggered, this, &GeometryWidget::slotFitToHeight); QAction *alignleft = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-left")), i18n("Align left"), this); connect(alignleft, &QAction::triggered, this, &GeometryWidget::slotMoveLeft); QAction *alignhcenter = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-hor")), i18n("Center horizontally"), this); connect(alignhcenter, &QAction::triggered, this, &GeometryWidget::slotCenterH); QAction *alignright = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-right")), i18n("Align right"), this); connect(alignright, &QAction::triggered, this, &GeometryWidget::slotMoveRight); QAction *aligntop = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-top")), i18n("Align top"), this); connect(aligntop, &QAction::triggered, this, &GeometryWidget::slotMoveTop); QAction *alignvcenter = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-align-vert")), i18n("Center vertically"), this); connect(alignvcenter, &QAction::triggered, this, &GeometryWidget::slotCenterV); QAction *alignbottom = new QAction(KoIconUtils::themedIcon(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(); } 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()); + 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() { QAction *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()); + 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 ((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); - //slotAdjustRectKeyframeValue(); + // slotAdjustRectKeyframeValue(); emit valueChanged(getValue()); - //setupMonitor(); + // setupMonitor(); } 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); - m_opacity->setValue((int) (opacity * 100)); + m_opacity->setValue((int)(opacity * 100)); m_opacity->blockSignals(false); } m_spinX->blockSignals(false); m_spinY->blockSignals(false); m_spinWidth->blockSignals(false); m_spinHeight->blockSignals(false); m_monitor->setUpEffectGeometry(r); } - const QString GeometryWidget::getValue() const { if (m_opacity) { - return QStringLiteral("%1 %2 %3 %4 %5").arg(m_spinX->value()).arg(m_spinY->value()).arg(m_spinWidth->value()).arg( m_spinHeight->value()).arg(m_opacity->value() / 100.0); + return QStringLiteral("%1 %2 %3 %4 %5") + .arg(m_spinX->value()) + .arg(m_spinY->value()) + .arg(m_spinWidth->value()) + .arg(m_spinHeight->value()) + .arg(m_opacity->value() / 100.0); } - return QStringLiteral("%1 %2 %3 %4").arg(m_spinX->value()).arg(m_spinY->value()).arg(m_spinWidth->value()).arg( m_spinHeight->value()); + 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) +void GeometryWidget::slotSetRange(QPair range) { m_min = range.first; m_max = range.second; } diff --git a/src/widgets/geometrywidget.h b/src/widgets/geometrywidget.h index 86775d833..51593d2f0 100644 --- a/src/widgets/geometrywidget.h +++ b/src/widgets/geometrywidget.h @@ -1,105 +1,106 @@ /*************************************************************************** * Copyright (C) 2017 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 . * ***************************************************************************/ #ifndef GEOMETRYWIDGET2_H #define GEOMETRYWIDGET2_H #include #include #include #include #include class QAction; class QMenu; class KSelectAction; class DragValue; class Monitor; /** * @brief A widget for modifying numbers by dragging, using the mouse wheel or entering them with the keyboard. */ class GeometryWidget : public QWidget { Q_OBJECT public: /** - * @brief Default constructor. - * @param monitor The monitor attached to this stack - * @param range The in / out points of the clip, useful to desactivate monitor scene when out of bounds - * @param rect The default geometry value - * @param frameSize The frame size of the original source video - * @param useRatioLock When true, width/height will keep the profile's aspect ratio on resize - */ - explicit GeometryWidget(Monitor *monitor, QPair range, const QRect &rect, const QSize frameSize, bool useRatioLock, bool useOpacity, QWidget *parent = nullptr); + * @brief Default constructor. + * @param monitor The monitor attached to this stack + * @param range The in / out points of the clip, useful to desactivate monitor scene when out of bounds + * @param rect The default geometry value + * @param frameSize The frame size of the original source video + * @param useRatioLock When true, width/height will keep the profile's aspect ratio on resize + */ + explicit GeometryWidget(Monitor *monitor, QPair range, const QRect &rect, const QSize frameSize, bool useRatioLock, bool useOpacity, + QWidget *parent = nullptr); void setValue(const QRect r, double opacity = 1); void connectMonitor(bool activate); private: int m_min; int m_max; bool m_active; Monitor *m_monitor; DragValue *m_spinX; DragValue *m_spinY; DragValue *m_spinWidth; DragValue *m_spinHeight; DragValue *m_spinSize; DragValue *m_opacity; QSize m_defaultSize; QSize m_sourceSize; QAction *m_originalSize; QAction *m_lockRatio; const QString getValue() const; void adjustSizeValue(); public slots: void slotUpdateGeometryRect(const QRect r); void slotSetRange(QPair); private slots: void slotAdjustRectKeyframeValue(); void slotAdjustToSource(); void slotAdjustToFrameSize(); void slotFitToWidth(); void slotFitToHeight(); void slotResize(double value); /** @brief Moves the rect to the left frame border (x position = 0). */ void slotMoveLeft(); /** @brief Centers the rect horizontally. */ void slotCenterH(); /** @brief Moves the rect to the right frame border (x position = frame width - rect width). */ void slotMoveRight(); /** @brief Moves the rect to the top frame border (y position = 0). */ void slotMoveTop(); /** @brief Centers the rect vertically. */ void slotCenterV(); /** @brief Moves the rect to the bottom frame border (y position = frame height - rect height). */ void slotMoveBottom(); /** @brief Un/Lock aspect ratio for size in effect parameter. */ void slotLockRatio(); void slotAdjustRectHeight(); void slotAdjustRectWidth(); signals: void valueChanged(const QString val); }; #endif diff --git a/src/widgets/positionwidget.cpp b/src/widgets/positionwidget.cpp index f9e80b207..c9c7c7f67 100644 --- a/src/widgets/positionwidget.cpp +++ b/src/widgets/positionwidget.cpp @@ -1,99 +1,97 @@ /*************************************************************************** positionedit.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 "positionwidget.h" #include "kdenlivesettings.h" #include "timecodedisplay.h" #include #include #include PositionWidget::PositionWidget(const QString &name, int pos, int min, int max, const Timecode &tc, const QString &comment, QWidget *parent) : QWidget(parent) { auto *layout = new QHBoxLayout(this); QLabel *label = new QLabel(name, this); m_slider = new QSlider(Qt::Horizontal, this); m_slider->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); m_slider->setRange(min, max); m_display = new TimecodeDisplay(tc, this); m_display->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred)); m_display->setRange(min, max); layout->addWidget(label); layout->addWidget(m_slider); layout->addWidget(m_display); m_slider->setValue(pos); m_display->setValue(pos); connect(m_slider, SIGNAL(valueChanged(int)), m_display, SLOT(setValue(int))); connect(m_slider, &QAbstractSlider::valueChanged, this, &PositionWidget::valueChanged); connect(m_display, &TimecodeDisplay::timeCodeEditingFinished, this, &PositionWidget::slotUpdatePosition); setToolTip(comment); } -PositionWidget::~PositionWidget() -{ -} +PositionWidget::~PositionWidget() {} void PositionWidget::updateTimecodeFormat() { m_display->slotUpdateTimeCodeFormat(); } int PositionWidget::getPosition() const { return m_slider->value(); } void PositionWidget::setPosition(int pos) { m_slider->setValue(pos); } void PositionWidget::slotUpdatePosition() { m_slider->blockSignals(true); m_slider->setValue(m_display->getValue()); m_slider->blockSignals(false); emit valueChanged(); } void PositionWidget::setRange(int min, int max, bool absolute) { if (absolute) { m_slider->setRange(min, max); m_display->setRange(min, max); } else { m_slider->setRange(0, max - min); m_display->setRange(0, max - min); } } bool PositionWidget::isValid() const { return m_slider->minimum() != m_slider->maximum(); } void PositionWidget::slotShowComment(bool show) { Q_UNUSED(show); } diff --git a/tests/keyframetest.cpp b/tests/keyframetest.cpp index 5ac1e5484..83a13e29c 100644 --- a/tests/keyframetest.cpp +++ b/tests/keyframetest.cpp @@ -1,239 +1,239 @@ #include "test_utils.hpp" using namespace fakeit; bool test_model_equality(std::shared_ptr m1, std::shared_ptr m2) { // we cheat a bit by simply comparing the underlying map qDebug() << "Equality test" << m1->m_keyframeList.size() << m2->m_keyframeList.size(); return m1->m_keyframeList == m2->m_keyframeList; } bool check_anim_identity(std::shared_ptr m) { auto m2 = std::shared_ptr(new KeyframeModel(m->m_model, m->m_index, m->m_undoStack)); m2->parseAnimProperty(m->getAnimProperty()); return test_model_equality(m, m2); } TEST_CASE("Keyframe model", "[KeyframeModel]") { std::shared_ptr undoStack = std::make_shared(nullptr); std::shared_ptr guideModel = std::make_shared(undoStack); // Here we do some trickery to enable testing. // We mock the project class so that the undoStack function returns our undoStack Mock pmMock; When(Method(pmMock, undoStack)).AlwaysReturn(undoStack); ProjectManager &mocked = pmMock.get(); pCore->m_projectManager = &mocked; Mlt::Profile pr; std::shared_ptr producer = std::make_shared(pr, "color", "red"); auto effectstack = EffectStackModel::construct(producer, {ObjectType::TimelineClip, 0}, undoStack); effectstack->appendEffect(QStringLiteral("audiobalance")); REQUIRE(effectstack->checkConsistency()); REQUIRE(effectstack->rowCount() == 1); auto effect = std::dynamic_pointer_cast(effectstack->getEffectStackRow(0)); effect->prepareKeyframes(); qDebug() << effect->getAssetId() << effect->getAllParameters(); REQUIRE(effect->rowCount() == 1); QModelIndex index = effect->index(0, 0); auto model = std::shared_ptr(new KeyframeModel(effect, index, undoStack)); SECTION("Add/remove + undo") { auto state0 = [&]() { REQUIRE(model->rowCount() == 1); REQUIRE(check_anim_identity(model)); }; state0(); REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42)); auto state1 = [&]() { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(1.1))); bool ok; auto k = model->getKeyframe(GenTime(1.1), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k == k1); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k == k2); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k == k4); model->getNextKeyframe(GenTime(10), &ok); REQUIRE_FALSE(ok); }; state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33)); auto state2 = [&]() { REQUIRE(model->rowCount() == 3); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(1.1))); REQUIRE(model->hasKeyframe(GenTime(12.6))); bool ok; auto k = model->getKeyframe(GenTime(1.1), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto kk = model->getKeyframe(GenTime(12.6), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k == k1); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k == k2); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k == k4); auto k5 = model->getNextKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k5 == kk); }; state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); REQUIRE(model->removeKeyframe(GenTime(1.1))); auto state3 = [&]() { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(12.6))); bool ok; auto k = model->getKeyframe(GenTime(1.1), &ok); REQUIRE_FALSE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto kk = model->getKeyframe(GenTime(12.6), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(0.655555), &ok); REQUIRE(ok); REQUIRE(k == k0); auto k2 = model->getNextKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(kk == k2); auto k3 = model->getPrevKeyframe(GenTime(0.5), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k0 == k4); auto k5 = model->getNextKeyframe(GenTime(10), &ok); REQUIRE(ok); REQUIRE(k5 == kk); }; state3(); undoStack->undo(); state2(); undoStack->undo(); state1(); undoStack->undo(); state0(); undoStack->redo(); state1(); undoStack->redo(); state2(); undoStack->redo(); state3(); REQUIRE(model->removeAllKeyframes()); state0(); undoStack->undo(); state3(); undoStack->redo(); state0(); } SECTION("Move keyframes + undo") { auto state0 = [&]() { REQUIRE(model->rowCount() == 1); REQUIRE(check_anim_identity(model)); }; state0(); REQUIRE(model->addKeyframe(GenTime(1.1), KeyframeType::Linear, 42)); auto state1 = [&](double pos) { REQUIRE(model->rowCount() == 2); REQUIRE(check_anim_identity(model)); REQUIRE(model->hasKeyframe(GenTime(pos))); bool ok; auto k = model->getKeyframe(GenTime(pos), &ok); REQUIRE(ok); auto k0 = model->getKeyframe(GenTime(0), &ok); REQUIRE(ok); auto k1 = model->getClosestKeyframe(GenTime(pos + 10), &ok); REQUIRE(ok); REQUIRE(k == k1); auto k2 = model->getNextKeyframe(GenTime(pos - 0.3), &ok); REQUIRE(ok); REQUIRE(k == k2); auto k3 = model->getPrevKeyframe(GenTime(pos - 0.3), &ok); REQUIRE(ok); REQUIRE(k3 == k0); auto k4 = model->getPrevKeyframe(GenTime(pos + 0.3), &ok); REQUIRE(ok); REQUIRE(k == k4); model->getNextKeyframe(GenTime(pos + 0.3), &ok); REQUIRE_FALSE(ok); }; state1(1.1); REQUIRE(model->moveKeyframe(GenTime(1.1), GenTime(2.6), -1, true)); state1(2.6); undoStack->undo(); state1(1.1); undoStack->redo(); state1(2.6); REQUIRE(model->moveKeyframe(GenTime(2.6), GenTime(6.1), -1, true)); state1(6.1); undoStack->undo(); state1(2.6); undoStack->undo(); state1(1.1); undoStack->redo(); state1(2.6); undoStack->redo(); state1(6.1); REQUIRE(model->addKeyframe(GenTime(12.6), KeyframeType::Discrete, 33)); - REQUIRE_FALSE(model->moveKeyframe(GenTime(6.1), GenTime(12.6), -1, true)); + REQUIRE_FALSE(model->moveKeyframe(GenTime(6.1), GenTime(12.6), -1, true)); undoStack->undo(); state1(6.1); } } diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 9a8c6ae28..7d361903e 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -1,78 +1,78 @@ #pragma once #include "bin/model/markerlistmodel.hpp" #include "catch.hpp" #include "doc/docundostack.hpp" #include #include #include #include #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" #pragma GCC diagnostic push #include "fakeit.hpp" #include #include #include #include #define private public #define protected public #include "assets/keyframes/model/keyframemodel.hpp" #include "assets/model/assetparametermodel.hpp" #include "bin/clipcreator.hpp" #include "bin/projectclip.h" #include "bin/projectfolder.h" #include "bin/projectitemmodel.h" #include "core.h" #include "effects/effectsrepository.hpp" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "project/projectmanager.h" #include "timeline2/model/clipmodel.hpp" #include "timeline2/model/compositionmodel.hpp" #include "timeline2/model/groupsmodel.hpp" #include "timeline2/model/timelinefunctions.hpp" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/model/timelinemodel.hpp" #include "timeline2/model/trackmodel.hpp" using namespace fakeit; #define RESET(mock) \ mock.Reset(); \ Fake(Method(mock, adjustAssetRange)); \ Spy(Method(mock, _resetView)); \ Spy(Method(mock, _beginInsertRows)); \ Spy(Method(mock, _beginRemoveRows)); \ Spy(Method(mock, _endInsertRows)); \ Spy(Method(mock, _endRemoveRows)); \ Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))); \ Spy(OverloadedMethod(mock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector &))); #define NO_OTHERS() \ VerifyNoOtherInvocations(Method(timMock, _beginRemoveRows)); \ VerifyNoOtherInvocations(Method(timMock, _beginInsertRows)); \ VerifyNoOtherInvocations(Method(timMock, _endRemoveRows)); \ VerifyNoOtherInvocations(Method(timMock, _endInsertRows)); \ VerifyNoOtherInvocations(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))); \ - VerifyNoOtherInvocations(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector &))); \ + VerifyNoOtherInvocations(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, const QVector &))); \ RESET(timMock); #define CHECK_MOVE(times) \ Verify(Method(timMock, _beginRemoveRows) + Method(timMock, _endRemoveRows) + Method(timMock, _beginInsertRows) + Method(timMock, _endInsertRows)) \ .Exactly(times); \ NO_OTHERS(); #define CHECK_INSERT(times) \ Verify(Method(timMock, _beginInsertRows) + Method(timMock, _endInsertRows)).Exactly(times); \ NO_OTHERS(); #define CHECK_REMOVE(times) \ Verify(Method(timMock, _beginRemoveRows) + Method(timMock, _endRemoveRows)).Exactly(times); \ NO_OTHERS(); #define CHECK_RESIZE(times) \ Verify(OverloadedMethod(timMock, notifyChange, void(const QModelIndex &, const QModelIndex &, bool, bool, bool))).Exactly(times); \ NO_OTHERS(); QString createProducer(Mlt::Profile &prof, std::string color, std::shared_ptr binModel, int length = 20, bool limited = true); QString createProducerWithSound(Mlt::Profile &prof, std::shared_ptr binModel, int length = 20, bool limited = true);