diff --git a/src/assets/abstractassetsrepository.ipp b/src/assets/abstractassetsrepository.ipp
index 3e4c82024..5768e55cd 100644
--- a/src/assets/abstractassetsrepository.ipp
+++ b/src/assets/abstractassetsrepository.ipp
@@ -1,322 +1,321 @@
/***************************************************************************
* 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
#ifdef Q_OS_MAC
#include
#endif
template AbstractAssetsRepository::AbstractAssetsRepository() = default;
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 blacklist
parseAssetList(assetBlackListPath(), m_blacklist);
// Parse preferred list
parseAssetList(assetPreferredListPath(), m_preferred_list);
// 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;
}
// qDebug() << "trying to parse " < customAssets;
// reverse order to prioritize local install
QListIterator dirs_it(asset_dirs);
for (dirs_it.toBack(); dirs_it.hasPrevious();) { auto dir=dirs_it.previous();
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::parseAssetList(const QString &filePath, QSet &destination)
{
if (filePath.isEmpty())
return;
QFile assetFile(filePath);
if (assetFile.open(QIODevice::ReadOnly)) {
QTextStream stream(&assetFile);
QString line;
while (stream.readLineInto(&line)) {
line = line.simplified();
if (!line.isEmpty() && !line.startsWith('#')) {
destination.insert(line);
}
}
}
}
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) {
QString id = metadata->get("identifier");
res.name = metadata->get("title");
res.name[0] = res.name[0].toUpper();
res.description = metadata->get("description");
res.description.append(QString(" (%1)").arg(id));
res.author = metadata->get("creator");
res.version_str = metadata->get("version");
res.version = ceil(100 * metadata->get_double("version"));
res.id = res.mltId = assetId;
parseType(metadata, res);
// Create params
QDomDocument doc;
QDomElement eff = doc.createElement(QStringLiteral("effect"));
eff.setAttribute(QStringLiteral("tag"), id);
eff.setAttribute(QStringLiteral("id"), id);
////qCDebug(KDENLIVE_LOG)<<"Effect: "<get_data("parameters"));
for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) {
QDomElement params = doc.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") && (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 = doc.createElement(QStringLiteral("name"));
pname.appendChild(doc.createTextNode(paramName));
params.appendChild(pname);
}
if (paramdesc.get("description")) {
QDomElement comment = doc.createElement(QStringLiteral("comment"));
comment.appendChild(doc.createTextNode(paramdesc.get("description")));
params.appendChild(comment);
}
eff.appendChild(params);
}
doc.appendChild(eff);
res.xml = eff;
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::parseInfoFromXml(const QDomElement ¤tAsset, Info &res) const
{
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 (m_assets.at(tag).version < (int)(100 * currentAsset.attribute(QStringLiteral("version")).toDouble())) {
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;
- res.description.append(QString(" (%1)").arg(tag));
}
// 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.cloneNode().toElement();
}
diff --git a/src/assets/assetlist/view/qml/assetList.qml b/src/assets/assetlist/view/qml/assetList.qml
index f2dcdec1c..bcb825696 100644
--- a/src/assets/assetlist/view/qml/assetList.qml
+++ b/src/assets/assetlist/view/qml/assetList.qml
@@ -1,378 +1,378 @@
/***************************************************************************
* 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 . *
***************************************************************************/
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.5
import QtQuick.Controls.Styles 1.4
import QtQuick.Window 2.2
import QtQml.Models 2.2
Rectangle {
id: listRoot
SystemPalette { id: activePalette }
color: activePalette.window
function expandNodes(indexes) {
for(var i = 0; i < indexes.length; i++) {
if (indexes[i].valid) {
treeView.expand(indexes[i]);
}
}
}
function rowPosition(model, index) {
var pos = 0;
for(var i = 0; i < index.parent.row; i++) {
var catIndex = model.getCategory(i);
if (treeView.isExpanded(catIndex)) {
pos += model.rowCount(catIndex);
}
pos ++;
}
pos += index.row + 2;
return pos;
}
ColumnLayout {
anchors.fill: parent
spacing: 0
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
spacing: 4
ExclusiveGroup { id: filterGroup}
ToolButton {
id: searchList
iconName: "edit-find"
checkable: true
tooltip: isEffectList ? i18n("Find effect") : i18n("Find composition")
onCheckedChanged: {
searchInput.visible = searchList.checked
searchInput.focus = searchList.checked
if (!searchList.checked) {
searchInput.text = ''
treeView.focus = true
}
}
}
ToolButton {
id: showAll
iconName: "show-all-effects"
checkable: true
checked: true
exclusiveGroup: filterGroup
tooltip: isEffectList ? i18n("Main effects") : i18n("Main compositions")
onClicked: {
assetlist.setFilterType("")
}
}
ToolButton {
id: showVideo
visible: isEffectList
iconName: "kdenlive-show-video"
iconSource: 'qrc:///pics/kdenlive-show-video.svgz'
checkable:true
exclusiveGroup: filterGroup
tooltip: i18n("Show all video effects")
onClicked: {
assetlist.setFilterType("video")
}
}
ToolButton {
id: showAudio
visible: isEffectList
iconName: "kdenlive-show-audio"
iconSource: 'qrc:///pics/kdenlive-show-audio.svgz'
checkable:true
exclusiveGroup: filterGroup
tooltip: i18n("Show all audio effects")
onClicked: {
assetlist.setFilterType("audio")
}
}
ToolButton {
id: showCustom
visible: isEffectList
iconName: "kdenlive-custom-effect"
checkable:true
exclusiveGroup: filterGroup
tooltip: i18n("Show all custom effects")
onClicked: {
assetlist.setFilterType("custom")
}
}
ToolButton {
id: showFavorites
iconName: "favorite"
checkable:true
exclusiveGroup: filterGroup
tooltip: i18n("Show favorite items")
onClicked: {
assetlist.setFilterType("favorites")
}
}
ToolButton {
id: downloadTransitions
visible: !isEffectList
iconName: "edit-download"
tooltip: i18n("Download New Wipes...")
onClicked: {
assetlist.downloadNewLumas()
}
}
Rectangle {
//This is a spacer
Layout.fillHeight: false
Layout.fillWidth: true
color: "transparent"
}
ToolButton {
id: showDescription
iconName: "help-about"
checkable:true
tooltip: isEffectList ? i18n("Show/hide description of the effects") : i18n("Show/hide description of the compositions")
onCheckedChanged:{
assetlist.showDescription = checked
}
Component.onCompleted: checked = assetlist.showDescription
}
}
TextField {
id: searchInput
Layout.fillWidth:true
visible: false
Image {
id: clear
source: 'image://icon/edit-clear'
width: parent.height * 0.8
height: width
anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter }
opacity: 0
MouseArea {
anchors.fill: parent
onClicked: { searchInput.text = ''; searchInput.focus = true; searchList.checked = false; }
}
}
states: State {
name: "hasText"; when: searchInput.text != ''
PropertyChanges { target: clear; opacity: 1 }
}
transitions: [
Transition {
from: ""; to: "hasText"
NumberAnimation { properties: "opacity" }
},
Transition {
from: "hasText"; to: ""
NumberAnimation { properties: "opacity" }
}
]
onTextChanged: {
var current = sel.currentIndex
var rowModelIndex = assetListModel.getModelIndex(sel.currentIndex);
assetlist.setFilterName(text)
if (text.length > 0) {
sel.setCurrentIndex(assetListModel.firstVisibleItem(current), ItemSelectionModel.ClearAndSelect)
} else {
sel.clearCurrentIndex()
sel.setCurrentIndex(assetListModel.getProxyIndex(rowModelIndex), ItemSelectionModel.ClearAndSelect)
}
treeView.__listView.positionViewAtIndex(rowPosition(assetListModel, sel.currentIndex), ListView.Visible)
}
/*onEditingFinished: {
if (!assetContextMenu.isDisplayed) {
searchList.checked = false
}
}*/
Keys.onDownPressed: {
sel.setCurrentIndex(assetListModel.getNextChild(sel.currentIndex), ItemSelectionModel.ClearAndSelect)
treeView.expand(sel.currentIndex.parent)
treeView.__listView.positionViewAtIndex(rowPosition(assetListModel, sel.currentIndex), ListView.Visible)
}
Keys.onUpPressed: {
sel.setCurrentIndex(assetListModel.getPreviousChild(sel.currentIndex), ItemSelectionModel.ClearAndSelect)
treeView.expand(sel.currentIndex.parent)
treeView.__listView.positionViewAtIndex(rowPosition(assetListModel, sel.currentIndex), ListView.Visible)
}
Keys.onReturnPressed: {
if (sel.hasSelection) {
assetlist.activate(sel.currentIndex)
searchList.checked = false
}
}
}
ItemSelectionModel {
id: sel
model: assetListModel
onSelectionChanged: {
- assetDescription.text = assetlist.getDescription(sel.currentIndex)
+ assetDescription.text = i18n(assetlist.getDescription(sel.currentIndex))
}
}
SplitView {
orientation: Qt.Vertical
Layout.fillHeight: true
Layout.fillWidth: true
TreeView {
id: treeView
Layout.fillHeight: true
Layout.fillWidth: true
alternatingRowColors: false
headerVisible: false
selection: sel
selectionMode: SelectionMode.SingleSelection
itemDelegate: Rectangle {
id: assetDelegate
// These anchors are important to allow "copy" dragging
anchors.verticalCenter: parent ? parent.verticalCenter : undefined
anchors.right: parent ? parent.right : undefined
property bool isItem : styleData.value !== "root" && styleData.value !== ""
property string mimeType : isItem ? assetlist.getMimeType(styleData.value) : ""
height: assetText.implicitHeight
color: dragArea.containsMouse ? activePalette.highlight : "transparent"
Drag.active: isItem ? dragArea.drag.active : false
Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.CopyAction
Drag.mimeData: isItem ? assetlist.getMimeData(styleData.value) : {}
Drag.keys:[
isItem ? assetlist.getMimeType(styleData.value) : ""
]
Row {
anchors.fill:parent
anchors.leftMargin: 1
anchors.topMargin: 1
anchors.bottomMargin: 1
spacing: 4
Image{
id: assetThumb
anchors.verticalCenter: parent.verticalCenter
visible: assetDelegate.isItem
property bool isFavorite: model == undefined || model.favorite === undefined ? false : model.favorite
height: parent.height * 0.8
width: height
source: 'image://asseticon/' + styleData.value
}
Label {
id: assetText
font.bold : assetThumb.isFavorite
text: i18n(assetlist.getName(styleData.index))
}
}
MouseArea {
id: dragArea
anchors.fill: assetDelegate
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
drag.target: undefined
onReleased: {
drag.target = undefined
}
onPressed: {
if (assetDelegate.isItem) {
//sel.select(styleData.index, ItemSelectionModel.Select)
sel.setCurrentIndex(styleData.index, ItemSelectionModel.ClearAndSelect)
if (mouse.button === Qt.LeftButton) {
drag.target = parent
parent.grabToImage(function(result) {
parent.Drag.imageSource = result.url
})
} else {
drag.target = undefined
assetContextMenu.isItemFavorite = assetThumb.isFavorite
assetContextMenu.popup()
mouse.accepted = false
}
console.log(parent.Drag.keys)
} else {
if (treeView.isExpanded(styleData.index)) {
treeView.collapse(styleData.index)
} else {
treeView.expand(styleData.index)
}
}
treeView.focus = true
}
onDoubleClicked: {
if (isItem) {
assetlist.activate(styleData.index)
}
}
}
}
Menu {
id: assetContextMenu
property bool isItemFavorite
property bool isDisplayed: false
MenuItem {
id: favMenu
text: assetContextMenu.isItemFavorite ? i18n("Remove from favorites") : i18n("Add to favorites")
property url thumbSource
onTriggered: {
assetlist.setFavorite(sel.currentIndex, !assetContextMenu.isItemFavorite)
}
}
onAboutToShow: {
isDisplayed = true;
}
onAboutToHide: {
isDisplayed = false;
}
}
TableViewColumn { role: "identifier"; title: i18n("Name"); }
model: assetListModel
Keys.onDownPressed: {
sel.setCurrentIndex(assetListModel.getNextChild(sel.currentIndex), ItemSelectionModel.ClearAndSelect)
treeView.expand(sel.currentIndex.parent)
treeView.__listView.positionViewAtIndex(rowPosition(assetListModel, sel.currentIndex), ListView.Visible)
}
Keys.onUpPressed: {
sel.setCurrentIndex(assetListModel.getPreviousChild(sel.currentIndex), ItemSelectionModel.ClearAndSelect)
treeView.expand(sel.currentIndex.parent)
treeView.__listView.positionViewAtIndex(rowPosition(assetListModel, sel.currentIndex), ListView.Visible)
}
Keys.onReturnPressed: {
if (sel.hasSelection) {
assetlist.activate(sel.currentIndex)
}
}
}
TextArea {
id: assetDescription
text: ""
visible: showDescription.checked
readOnly: true
Layout.fillWidth: true
states: State {
name: "hasDescription"; when: assetDescription.text != '' && showDescription.checked
PropertyChanges { target: assetDescription; visible: true}
}
}
}
}
}