Additional filters for file pickers / better way of handling file filters

This commit is contained in:
Pedro Rodrigues 2024-04-18 02:30:26 +00:00 committed by Jean-Baptiste Mardelle
parent b581ed8aef
commit 19ef5271a3
12 changed files with 366 additions and 115 deletions

3
.gitignore vendored
View file

@ -17,3 +17,6 @@ packaging/flatpak/.flatpak-builder
.cache
.clangd
compile_commands.json
# VSCode
.vscode/

View file

@ -84,6 +84,7 @@ list(APPEND kdenlive_SRCS
statusbarmessagelabel.cpp
undohelper.cpp
glaxnimatelauncher.cpp
filefilter.cpp
)
if(CRASH_AUTO_TEST)

View file

@ -54,6 +54,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "undohelper.hpp"
#include "utils/thumbnailcache.hpp"
#include "xml/xml.hpp"
#include "filefilter.h"
#include "utils/KMessageBox_KdenliveCompat.h"
#include <KActionMenu>
@ -2051,8 +2052,9 @@ void Bin::slotReplaceClip()
}
if (currentItem) {
Q_EMIT openClip(std::shared_ptr<ProjectClip>());
auto filter = FileFilter::Builder().setCategories({FileFilter::AllSupported}).toQFilter();
QString fileName = QFileDialog::getOpenFileName(this, i18nc("@title:window", "Open Replacement for %1", currentItem->clipName()),
QFileInfo(currentItem->url()).absolutePath(), ClipCreationDialog::getExtensionsFilter());
QFileInfo(currentItem->url()).absolutePath(), filter);
if (!fileName.isEmpty()) {
QMap<QString, QString> sourceProps;
QMap<QString, QString> newProps;

View file

@ -6,7 +6,7 @@
#include "clipcreator.hpp"
#include "bin/bin.h"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "filefilter.h"
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
@ -462,7 +462,7 @@ const QString ClipCreator::createClipsFromList(const QList<QUrl> &list, bool che
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
QStringList subfolders = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
dir.setNameFilters(ClipCreationDialog::getExtensions());
dir.setNameFilters(FileFilter::getExtensions());
QStringList result = dir.entryList(QDir::Files);
QList<QUrl> folderFiles;
for (const QString &path : qAsConst(result)) {

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "bin.h"
#include "clipcreator.hpp"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "filefilter.h"
#include "kdenlivesettings.h"
#include "mainwindow.h"
#include "project/dialogs/slideshowclip.h"
@ -160,21 +160,14 @@ MediaBrowser::MediaBrowser(QWidget *parent)
}
});
auto dialogFilter = FileFilter::Builder().defaultCategories().toKFilter();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
m_filterCombo->setFilter(dialogFilter);
m_op->setNameFilter(m_filterCombo->currentFilter());
#else
QStringList allExtensions = ClipCreationDialog::getExtensions();
const QList<KFileFilter> filters{
KFileFilter(i18n("All Supported Files"), allExtensions, {}),
KFileFilter(i18n("All Files"), {QStringLiteral("*")}, {}),
};
m_filterCombo->setFilters(filters, filters.first());
m_op->setNameFilter(filters.first().toFilterString());
m_filterCombo->setFilters(dialogFilter, dialogFilter.first());
m_op->setNameFilter(dialogFilter.first().toFilterString());
#endif
// Setup mime filter combo

View file

@ -26,6 +26,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "utils/qcolorutils.h"
#include "widgets/timecodedisplay.h"
#include "xml/xml.hpp"
#include "filefilter.h"
#include <KDirOperator>
#include <KFileWidget>
@ -48,90 +49,6 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <unordered_map>
#include <utility>
// static
QStringList ClipCreationDialog::getExtensions()
{
// Build list of MIME types
QStringList mimeTypes = QStringList() << QStringLiteral("application/x-kdenlivetitle") << QStringLiteral("video/mlt-playlist")
<< QStringLiteral("text/plain") << QStringLiteral("application/x-kdenlive");
// 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/AMR") << 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") << QStringLiteral("audio/aac");
// 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")
<< QStringLiteral("image/webp") << QStringLiteral("image/jp2") << QStringLiteral("image/avif") << QStringLiteral("image/heif")
<< QStringLiteral("image/jxl");
// Lottie animations
bool allowLottie = KdenliveSettings::producerslist().contains(QLatin1String("glaxnimate"));
if (allowLottie) {
mimeTypes << QStringLiteral("application/json");
}
// Some newer mimetypes might not be registered on some older Operating Systems, so register manually
QMap<QString, QString> manualMap;
manualMap.insert(QStringLiteral("image/avif"), QStringLiteral("*.avif"));
manualMap.insert(QStringLiteral("image/heif"), QStringLiteral("*.heif"));
manualMap.insert(QStringLiteral("image/x-exr"), QStringLiteral("*.exr"));
manualMap.insert(QStringLiteral("image/jp2"), QStringLiteral("*.jp2"));
manualMap.insert(QStringLiteral("image/jxl"), QStringLiteral("*.jxl"));
QMimeDatabase db;
QStringList allExtensions;
for (const QString &mimeType : qAsConst(mimeTypes)) {
QMimeType mime = db.mimeTypeForName(mimeType);
if (mime.isValid()) {
allExtensions.append(mime.globPatterns());
} else if (manualMap.contains(mimeType)) {
allExtensions.append(manualMap.value(mimeType));
}
}
if (allowLottie) {
allExtensions.append(QStringLiteral("*.rawr"));
}
// process custom user extensions
const QStringList customs = KdenliveSettings::addedExtensions().split(' ', Qt::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;
}
QString ClipCreationDialog::getExtensionsFilter(const QStringList &additionalFilters)
{
const QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
QString filter = i18n("All Supported Files") + " (" + allExtensions + ')';
if (!additionalFilters.isEmpty()) {
filter += ";;";
filter.append(additionalFilters.join(";;"));
}
return filter;
}
// static
void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
@ -525,17 +442,12 @@ void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QString &par
QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject);
dlg->setLayout(layout);
auto dialogFilter = FileFilter::Builder().defaultCategories().toKFilter();
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QString allExtensions = getExtensions().join(QLatin1Char(' '));
QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
fileWidget->setFilter(dialogFilter);
#else
const QStringList allExtensions = getExtensions();
const QList<KFileFilter> filters{
KFileFilter(i18n("All Supported Files"), allExtensions, {}),
KFileFilter(i18n("All Files"), {QStringLiteral("*")}, {}),
};
fileWidget->setFilters(filters);
fileWidget->setFilters(dialogFilter);
#endif
fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory);

View file

@ -22,8 +22,6 @@ class ProjectItemModel;
*/
namespace ClipCreationDialog {
QStringList getExtensions();
QString getExtensionsFilter(const QStringList& additionalFilters = QStringList());
void createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model);
void createQTextClip(const QString &parentId, Bin *bin, ProjectClip *clip = nullptr);
void createAnimationClip(KdenliveDoc *doc, const QString &parentId);
@ -32,4 +30,5 @@ void createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QStrin
void createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model);
void createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model);
const QString createPlaylistClip(const QString &name, std::pair<int, int> tracks, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model);
} // namespace ClipCreationDialog

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "kdenlivesettingsdialog.h"
#include "clipcreationdialog.h"
#include "filefilter.h"
#include "core.h"
#include "dialogs/customcamcorderdialog.h"
#include "dialogs/profilesdialog.h"
@ -382,7 +382,7 @@ void KdenliveSettingsDialog::initEnviromentPage()
connect(m_configEnv.kcfg_videotodefaultfolder, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &KdenliveSettingsDialog::slotEnableVideoFolder);
// Mime types
QStringList mimes = ClipCreationDialog::getExtensions();
QStringList mimes = FileFilter::getExtensions();
std::sort(mimes.begin(), mimes.end());
m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
@ -1334,7 +1334,7 @@ void KdenliveSettingsDialog::updateSettings()
if (m_configEnv.kcfg_addedExtensions->text() != KdenliveSettings::addedExtensions()) {
// Update list
KdenliveSettings::setAddedExtensions(m_configEnv.kcfg_addedExtensions->text());
QStringList mimes = ClipCreationDialog::getExtensions();
QStringList mimes = FileFilter::getExtensions();
std::sort(mimes.begin(), mimes.end());
m_configEnv.supportedmimes->setPlainText(mimes.join(QLatin1Char(' ')));
}

View file

@ -9,7 +9,7 @@
#include "assets/view/widgets/colorwheel.h"
#include "assets/view/widgets/keyframewidget.hpp"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "filefilter.h"
#include "effects/effectsrepository.hpp"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "kdenlivesettings.h"
@ -292,7 +292,7 @@ void CollapsibleEffectView::slotCreateGroup()
void CollapsibleEffectView::slotCreateRegion()
{
const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)"));
auto dialogFilter = FileFilter::Builder().setCategories({FileFilter::AllSupported, FileFilter::All}).toQFilter();
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
if (clipFolder.isEmpty()) {
clipFolder = QDir::homePath();

275
src/filefilter.cpp Normal file
View file

@ -0,0 +1,275 @@
/* SPDX-FileCopyrightText: 2024 Pedro Oliva Rodrigues <pedroolivarodrigues@outlook.com>
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */
#include "filefilter.h"
#include "kdenlivesettings.h"
#include "kdenlive_debug.h"
#include <KLocalizedString>
#include <QMimeDatabase>
#include <map>
#define qsl QStringLiteral
using namespace FileFilter;
using FileFilterDb = std::map<Category, QStringList>;
static FileFilterDb supported_file_exts;
static void initFilterDb() {
// init mime types
FileFilterDb supported_mime_types = {
{Other, {
qsl("application/x-kdenlivetitle"),
qsl("video/mlt-playlist"),
qsl("text/plain"),
qsl("application/x-kdenlive")
}},
{Video, {
qsl("video/x-flv"),
qsl("video/x-dv"),
qsl("video/dv"),
qsl("video/x-msvideo"),
qsl("video/x-matroska"),
qsl("video/mpeg"),
qsl("video/ogg"),
qsl("video/x-ms-wmv"),
qsl("video/mp4"),
qsl("video/quicktime"),
qsl("video/webm"),
qsl("video/3gpp"),
qsl("video/mp2t")
}},
{Audio, {
qsl("audio/AMR"),
qsl("audio/x-flac"),
qsl("audio/x-matroska"),
qsl("audio/mp4"),
qsl("audio/mpeg"),
qsl("audio/x-mp3"),
qsl("audio/ogg"),
qsl("audio/x-wav"),
qsl("audio/x-aiff"),
qsl("audio/aiff"),
qsl("application/ogg"),
qsl("application/mxf"),
qsl("application/x-shockwave-flash"),
qsl("audio/ac3"),
qsl("audio/aac")
}},
{Image, {
qsl("image/gif"),
qsl("image/jpeg"),
qsl("image/png"),
qsl("image/x-tga"),
qsl("image/x-bmp"),
qsl("image/tiff"),
qsl("image/x-xcf"),
qsl("image/x-xcf-gimp"),
qsl("image/x-pcx"),
qsl("image/x-exr"),
qsl("image/x-portable-pixmap"),
qsl("application/x-krita"),
qsl("image/webp"),
qsl("image/jp2"),
qsl("image/avif"),
qsl("image/heif"),
qsl("image/jxl")
}}
};
// Lottie animations
bool allowLottie = KdenliveSettings::producerslist().contains("glaxnimate");
if (allowLottie) {
supported_mime_types[Other].append(qsl("application/json"));
}
// Some newer mimetypes might not be registered on some older Operating Systems, so register manually
QMap<QString, QString> manualMap = {
{qsl("image/avif"), qsl("*.avif")},
{qsl("image/heif"), qsl("*.heif")},
{qsl("image/x-exr"), qsl("*.exr")},
{qsl("image/jp2"), qsl("*.jp2")},
{qsl("image/jxl"), qsl("*.jxl")}
};
// init file_exts
QMimeDatabase mimedb;
for (const auto& item : supported_mime_types) {
// TODO: Use C++17 unpacking here
Category category;
QStringList mimes;
std::tie(category, mimes) = item;
for (const auto& name : mimes) {
QMimeType mime = mimedb.mimeTypeForName(name);
QStringList globs;
if (mime.isValid()) {
globs.append(mime.globPatterns());
} else if (manualMap.contains(name)) {
globs.append(manualMap.value(name));
}
supported_file_exts[category].append(globs);
}
}
}
static QString i18n_label(Category type) {
switch (type)
{
case Audio:
return i18n("Audio Files");
case Image:
return i18n("Image Files");
case Video:
return i18n("Video Files");
case User:
return i18n("User Files");
case Other:
return i18n("Other Files");
case All:
return i18n("All Files");
case AllSupported:
return i18n("All Supported Files");
default:
// 💀💀💀
return "????";
}
}
static QStringList query(Category cat)
{
switch (cat)
{
case Audio:
case Image:
case Other:
case Video:
return supported_file_exts.at(cat);
case User: {
const QStringList customs = KdenliveSettings::addedExtensions().split(' ', Qt::SkipEmptyParts);
QStringList user;
if (customs.isEmpty()) {
return {};
}
for (const auto& ext : customs) {
if (ext.startsWith("*.")) {
user.append(ext);
} else if (ext.startsWith(".")) {
user.append(qsl("*") + ext);
} else if (!ext.startsWith('.')) {
user.append(qsl("*.") + ext);
} else {
qCDebug(KDENLIVE_LOG) << "Unrecognized custom format: " << ext;
}
}
return user;
}
case AllSupported: {
QStringList supported;
for (auto cat : {Video, Audio, Image, Other}) {
supported.append(supported_file_exts.at(cat));
}
return supported;
}
default:
qCDebug(KDENLIVE_LOG) << "Unrecognized category type: " << (int)cat;
// fallthrough
case All:
return {"*"};
}
}
QStringList FileFilter::getExtensions()
{
auto categories = {
AllSupported,
All,
Video,
Audio,
Image,
Other,
User
};
auto list = Builder().setCategories(categories).toExtensionsList();
list.removeDuplicates();
return list;
}
Builder::Builder() {
if (supported_file_exts.empty()) {
initFilterDb();
}
}
Builder &Builder::setCategories(std::initializer_list<Category> types)
{
m_types.assign(types);
return *this;
}
QString Builder::toQFilter() const
{
QStringList filters;
for (auto cat : m_types) {
QString label = i18n_label(cat);
QStringList extensions = query(cat);
filters.append(qsl("%1 (%2)").arg(label, extensions.join(' ')));
}
return filters.join(";;");
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QList<KFileFilter> Builder::toKFilter() const
{
QList<KFileFilter> filters;
for (auto cat : m_types) {
QString label = i18n_label(cat);
QStringList extensions = query(cat);
filters.append(KFileFilter(label, extensions, {}));
}
return filters;
}
#else
QString Builder::toKFilter() const
{
QStringList filters;
for (auto cat : m_types) {
QString label = i18n_label(cat);
QStringList extensions = query(cat);
filters.append(qsl("%2|%1").arg(label, extensions.join(' ')));
}
return filters.join("\n");
}
#endif
QStringList Builder::toExtensionsList() const
{
QStringList allExtensions;
for (auto cat : m_types) {
allExtensions.append(query(cat));
}
return allExtensions;
}

65
src/filefilter.h Normal file
View file

@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2024 Pedro Oliva Rodrigues <pedroolivarodrigues@outlook.com>
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */
#pragma once
#include <QString>
#include <vector>
#include <initializer_list>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <KFileFilter>
#endif
/* @brief This namespace contains methods to query the supported file types and
create file filters for file pickers.
*/
namespace FileFilter {
enum Category {
Video, // mp4, webm ...
Audio, // mp3, aac, ...
Image, // png, jpeg, ...
Other, // kdenlivetitle, mlt-playlist, ...
AllSupported, // all of the above in one
User, // from 'KdenliveSettings::addedExtensions'
All // all files (*.*)
};
QStringList getExtensions();
/* @class FileFilter::Builder
@brief A class that creates file filter for file pickers.
*/
class Builder {
public:
Builder();
Builder& setCategories(std::initializer_list<Category> cats);
Builder& defaultCategories() {
auto def = {
AllSupported,
All,
Video,
Audio,
Image,
Other,
User
};
return setCategories(def);
}
QString toQFilter() const;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QList<KFileFilter> toKFilter() const;
#else
QString toKFilter() const;
#endif
QStringList toExtensionsList() const;
private:
std::vector<Category> m_types;
};
} // namespace FileFilter

View file

@ -66,6 +66,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "transitions/transitionsrepository.hpp"
#include "utils/thememanager.h"
#include "widgets/progressbutton.h"
#include "filefilter.h"
#include <config-kdenlive.h>
#ifdef USE_JOGSHUTTLE
@ -4068,7 +4069,7 @@ void MainWindow::slotFriendlyTranscode(const QString &binId, bool checkProfile)
void MainWindow::slotTranscodeClip()
{
const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)"));
const QString dialogFilter = FileFilter::Builder().defaultCategories().toQFilter();
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
QStringList urls = QFileDialog::getOpenFileNames(this, i18nc("@title:window", "Files to Transcode"), clipFolder, dialogFilter);
if (urls.isEmpty()) {