diff options
Diffstat (limited to 'src/plugins/platformthemes/xdgdesktopportal')
7 files changed, 329 insertions, 282 deletions
diff --git a/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt new file mode 100644 index 0000000000..6228e83ec7 --- /dev/null +++ b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt @@ -0,0 +1,22 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## QXdgDesktopPortalThemePlugin Plugin: +##################################################################### + +qt_internal_add_plugin(QXdgDesktopPortalThemePlugin + OUTPUT_NAME qxdgdesktopportal + PLUGIN_TYPE platformthemes + DEFAULT_IF FALSE + SOURCES + main.cpp + qxdgdesktopportalfiledialog.cpp qxdgdesktopportalfiledialog_p.h + qxdgdesktopportaltheme.cpp qxdgdesktopportaltheme.h + LIBRARIES + Qt::Core + Qt::CorePrivate + Qt::DBus + Qt::Gui + Qt::GuiPrivate +) diff --git a/src/plugins/platformthemes/xdgdesktopportal/main.cpp b/src/plugins/platformthemes/xdgdesktopportal/main.cpp index 64a03d479f..efbc16b3d2 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/main.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/main.cpp @@ -1,47 +1,13 @@ -/**************************************************************************** -** -** Copyright (C) 2017-2018 Red Hat, Inc -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017-2018 Red Hat, Inc +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <qpa/qplatformthemeplugin.h> #include "qxdgdesktopportaltheme.h" QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + class QXdgDesktopPortalThemePlugin : public QPlatformThemePlugin { Q_OBJECT @@ -54,9 +20,9 @@ public: QPlatformTheme *QXdgDesktopPortalThemePlugin::create(const QString &key, const QStringList ¶ms) { Q_UNUSED(params); - if (!key.compare(QLatin1String("xdgdesktopportal"), Qt::CaseInsensitive) || - !key.compare(QLatin1String("flatpak"), Qt::CaseInsensitive) || - !key.compare(QLatin1String("snap"), Qt::CaseInsensitive)) + if (!key.compare("xdgdesktopportal"_L1, Qt::CaseInsensitive) || + !key.compare("flatpak"_L1, Qt::CaseInsensitive) || + !key.compare("snap"_L1, Qt::CaseInsensitive)) return new QXdgDesktopPortalTheme; return nullptr; diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp index dcf52921aa..1c162be8fc 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp @@ -1,62 +1,33 @@ -/**************************************************************************** -** -** Copyright (C) 2017-2018 Red Hat, Inc -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017-2018 Red Hat, Inc +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qxdgdesktopportalfiledialog_p.h" -#include <QtCore/qeventloop.h> +#include <private/qgenericunixservices_p.h> +#include <private/qguiapplication_p.h> +#include <qpa/qplatformintegration.h> -#include <QtDBus/QtDBus> #include <QDBusConnection> #include <QDBusMessage> #include <QDBusPendingCall> #include <QDBusPendingCallWatcher> #include <QDBusPendingReply> +#include <QDBusMetaType> +#include <QEventLoop> #include <QFile> +#include <QFileInfo> #include <QMetaType> #include <QMimeType> #include <QMimeDatabase> #include <QRandomGenerator> #include <QWindow> +#include <QRegularExpression> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + QDBusArgument &operator <<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::FilterCondition &filterCondition) { arg.beginStructure(); @@ -102,33 +73,43 @@ const QDBusArgument &operator >>(const QDBusArgument &arg, QXdgDesktopPortalFile class QXdgDesktopPortalFileDialogPrivate { public: - QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog) + QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog, uint fileChooserPortalVersion) : nativeFileDialog(nativeFileDialog) + , fileChooserPortalVersion(fileChooserPortalVersion) { } - WId winId = 0; - bool modal = false; - bool multipleFiles = false; - bool saveFile = false; + QEventLoop loop; QString acceptLabel; QString directory; QString title; QStringList nameFilters; QStringList mimeTypesFilters; + // maps user-visible name for portal to full name filter + QMap<QString, QString> userVisibleToNameFilter; + QString selectedMimeTypeFilter; + QString selectedNameFilter; QStringList selectedFiles; - QPlatformFileDialogHelper *nativeFileDialog = nullptr; + std::unique_ptr<QPlatformFileDialogHelper> nativeFileDialog; + uint fileChooserPortalVersion = 0; + bool failedToOpen = false; + bool directoryMode = false; + bool multipleFiles = false; + bool saveFile = false; }; -QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog) +QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog, uint fileChooserPortalVersion) : QPlatformFileDialogHelper() - , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog)) + , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog, fileChooserPortalVersion)) { Q_D(QXdgDesktopPortalFileDialog); if (d->nativeFileDialog) { - connect(d->nativeFileDialog, SIGNAL(accept()), this, SIGNAL(accept())); - connect(d->nativeFileDialog, SIGNAL(reject()), this, SIGNAL(reject())); + connect(d->nativeFileDialog.get(), SIGNAL(accept()), this, SIGNAL(accept())); + connect(d->nativeFileDialog.get(), SIGNAL(reject()), this, SIGNAL(reject())); } + + d->loop.connect(this, SIGNAL(accept()), SLOT(quit())); + d->loop.connect(this, SIGNAL(reject()), SLOT(quit())); } QXdgDesktopPortalFileDialog::~QXdgDesktopPortalFileDialog() @@ -145,6 +126,9 @@ void QXdgDesktopPortalFileDialog::initializeDialog() if (options()->fileMode() == QFileDialogOptions::ExistingFiles) d->multipleFiles = true; + if (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly) + d->directoryMode = true; + if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) d->acceptLabel = options()->labelText(QFileDialogOptions::Accept); @@ -160,32 +144,43 @@ void QXdgDesktopPortalFileDialog::initializeDialog() if (!options()->mimeTypeFilters().isEmpty()) d->mimeTypesFilters = options()->mimeTypeFilters(); + if (!options()->initiallySelectedMimeTypeFilter().isEmpty()) + d->selectedMimeTypeFilter = options()->initiallySelectedMimeTypeFilter(); + + if (!options()->initiallySelectedNameFilter().isEmpty()) + d->selectedNameFilter = options()->initiallySelectedNameFilter(); + setDirectory(options()->initialDirectory()); } -void QXdgDesktopPortalFileDialog::openPortal() +void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) { - Q_D(const QXdgDesktopPortalFileDialog); - - QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"), - QLatin1String("/org/freedesktop/portal/desktop"), - QLatin1String("org.freedesktop.portal.FileChooser"), - d->saveFile ? QLatin1String("SaveFile") : QLatin1String("OpenFile")); - QString parentWindowId = QLatin1String("x11:") + QString::number(d->winId); + Q_D(QXdgDesktopPortalFileDialog); + QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.FileChooser"_L1, + d->saveFile ? "SaveFile"_L1 : "OpenFile"_L1); QVariantMap options; if (!d->acceptLabel.isEmpty()) - options.insert(QLatin1String("accept_label"), d->acceptLabel); - - options.insert(QLatin1String("modal"), d->modal); - options.insert(QLatin1String("multiple"), d->multipleFiles); - - if (d->saveFile) { - if (!d->directory.isEmpty()) - options.insert(QLatin1String("current_folder"), QFile::encodeName(d->directory).append('\0')); - - if (!d->selectedFiles.isEmpty()) - options.insert(QLatin1String("current_file"), QFile::encodeName(d->selectedFiles.first()).append('\0')); + options.insert("accept_label"_L1, d->acceptLabel); + + options.insert("modal"_L1, windowModality != Qt::NonModal); + options.insert("multiple"_L1, d->multipleFiles); + options.insert("directory"_L1, d->directoryMode); + + if (!d->directory.isEmpty()) + options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0')); + + if (d->saveFile && !d->selectedFiles.isEmpty()) { + // current_file for the file to be pre-selected, current_name for the file name to be + // pre-filled current_file accepts absolute path and requires the file to exist while + // current_name accepts just file name + QFileInfo selectedFileInfo(d->selectedFiles.constFirst()); + if (selectedFileInfo.exists()) + options.insert("current_file"_L1, + QFile::encodeName(d->selectedFiles.constFirst()).append('\0')); + options.insert("current_name"_L1, selectedFileInfo.fileName()); } // Insert filters @@ -195,6 +190,9 @@ void QXdgDesktopPortalFileDialog::openPortal() qDBusRegisterMetaType<FilterList>(); FilterList filterList; + auto selectedFilterIndex = filterList.size() - 1; + + d->userVisibleToNameFilter.clear(); if (!d->mimeTypesFilters.isEmpty()) { for (const QString &mimeTypefilter : d->mimeTypesFilters) { @@ -215,17 +213,28 @@ void QXdgDesktopPortalFileDialog::openPortal() filter.name = mimeType.comment(); filter.filterConditions = filterConditions; + if (filter.name.isEmpty()) + filter.name = mimeTypefilter; + filterList << filter; + + if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter) + selectedFilterIndex = filterList.size() - 1; } } else if (!d->nameFilters.isEmpty()) { - for (const QString &filter : d->nameFilters) { + for (const QString &nameFilter : d->nameFilters) { // Do parsing: // Supported format is ("Images (*.png *.jpg)") QRegularExpression regexp(QPlatformFileDialogHelper::filterRegExp); - QRegularExpressionMatch match = regexp.match(filter); + QRegularExpressionMatch match = regexp.match(nameFilter); if (match.hasMatch()) { QString userVisibleName = match.captured(1); - QStringList filterStrings = match.captured(2).split(QLatin1Char(' '), QString::SkipEmptyParts); + QStringList filterStrings = match.captured(2).split(u' ', Qt::SkipEmptyParts); + + if (filterStrings.isEmpty()) { + qWarning() << "Filter " << userVisibleName << " is empty and will be ignored."; + continue; + } FilterConditionList filterConditions; for (const QString &filterString : filterStrings) { @@ -240,34 +249,58 @@ void QXdgDesktopPortalFileDialog::openPortal() filter.filterConditions = filterConditions; filterList << filter; + + d->userVisibleToNameFilter.insert(userVisibleName, nameFilter); + + if (!d->selectedNameFilter.isEmpty() && d->selectedNameFilter == nameFilter) + selectedFilterIndex = filterList.size() - 1; } } } if (!filterList.isEmpty()) - options.insert(QLatin1String("filters"), QVariant::fromValue(filterList)); + options.insert("filters"_L1, QVariant::fromValue(filterList)); + + if (selectedFilterIndex != -1) + options.insert("current_filter"_L1, QVariant::fromValue(filterList[selectedFilterIndex])); - options.insert(QLatin1String("handle_token"), QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate())); + options.insert("handle_token"_L1, QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate())); // TODO choices a(ssa(ss)s) // List of serialized combo boxes to add to the file chooser. - message << parentWindowId << d->title << options; + auto unixServices = dynamic_cast<QGenericUnixServices *>( + QGuiApplicationPrivate::platformIntegration()->services()); + if (parent && unixServices) + message << unixServices->portalWindowIdentifier(parent); + else + message << QString(); + + message << d->title << options; QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); - connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) { + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, d, windowFlags, windowModality, parent] (QDBusPendingCallWatcher *watcher) { QDBusPendingReply<QDBusObjectPath> reply = *watcher; - if (reply.isError()) { - Q_EMIT reject(); + // Any error means the dialog is not shown and we need to fallback + d->failedToOpen = reply.isError(); + if (d->failedToOpen) { + if (d->nativeFileDialog) { + d->nativeFileDialog->show(windowFlags, windowModality, parent); + if (d->loop.isRunning()) + d->nativeFileDialog->exec(); + } else { + Q_EMIT reject(); + } } else { QDBusConnection::sessionBus().connect(nullptr, reply.value().path(), - QLatin1String("org.freedesktop.portal.Request"), - QLatin1String("Response"), + "org.freedesktop.portal.Request"_L1, + "Response"_L1, this, SLOT(gotResponse(uint,QVariantMap))); } + watcher->deleteLater(); }); } @@ -292,7 +325,7 @@ QUrl QXdgDesktopPortalFileDialog::directory() const { Q_D(const QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog()) return d->nativeFileDialog->directory(); return d->directory; @@ -314,7 +347,7 @@ QList<QUrl> QXdgDesktopPortalFileDialog::selectedFiles() const { Q_D(const QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog()) return d->nativeFileDialog->selectedFiles(); QList<QUrl> files; @@ -334,6 +367,21 @@ void QXdgDesktopPortalFileDialog::setFilter() } } +void QXdgDesktopPortalFileDialog::selectMimeTypeFilter(const QString &filter) +{ + Q_D(QXdgDesktopPortalFileDialog); + if (d->nativeFileDialog) { + d->nativeFileDialog->setOptions(options()); + d->nativeFileDialog->selectMimeTypeFilter(filter); + } +} + +QString QXdgDesktopPortalFileDialog::selectedMimeTypeFilter() const +{ + Q_D(const QXdgDesktopPortalFileDialog); + return d->selectedMimeTypeFilter; +} + void QXdgDesktopPortalFileDialog::selectNameFilter(const QString &filter) { Q_D(QXdgDesktopPortalFileDialog); @@ -346,24 +394,21 @@ void QXdgDesktopPortalFileDialog::selectNameFilter(const QString &filter) QString QXdgDesktopPortalFileDialog::selectedNameFilter() const { - // TODO - return QString(); + Q_D(const QXdgDesktopPortalFileDialog); + return d->selectedNameFilter; } void QXdgDesktopPortalFileDialog::exec() { Q_D(QXdgDesktopPortalFileDialog); - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) { + if (d->nativeFileDialog && useNativeFileDialog()) { d->nativeFileDialog->exec(); return; } // HACK we have to avoid returning until we emit that the dialog was accepted or rejected - QEventLoop loop; - loop.connect(this, SIGNAL(accept()), SLOT(quit())); - loop.connect(this, SIGNAL(reject()), SLOT(quit())); - loop.exec(); + d->loop.exec(); } void QXdgDesktopPortalFileDialog::hide() @@ -380,13 +425,10 @@ bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowMo initializeDialog(); - d->modal = windowModality != Qt::NonModal; - d->winId = parent ? parent->winId() : 0; - - if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) + if (d->nativeFileDialog && useNativeFileDialog(OpenFallback)) return d->nativeFileDialog->show(windowFlags, windowModality, parent); - openPortal(); + openPortal(windowFlags, windowModality, parent); return true; } @@ -396,13 +438,43 @@ void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap & Q_D(QXdgDesktopPortalFileDialog); if (!response) { - if (results.contains(QLatin1String("uris"))) - d->selectedFiles = results.value(QLatin1String("uris")).toStringList(); - + if (results.contains("uris"_L1)) + d->selectedFiles = results.value("uris"_L1).toStringList(); + + if (results.contains("current_filter"_L1)) { + const Filter selectedFilter = qdbus_cast<Filter>(results.value(QStringLiteral("current_filter"))); + if (!selectedFilter.filterConditions.empty() && selectedFilter.filterConditions[0].type == MimeType) { + // s.a. QXdgDesktopPortalFileDialog::openPortal which basically does the inverse + d->selectedMimeTypeFilter = selectedFilter.filterConditions[0].pattern; + d->selectedNameFilter.clear(); + } else { + d->selectedNameFilter = d->userVisibleToNameFilter.value(selectedFilter.name); + d->selectedMimeTypeFilter.clear(); + } + } Q_EMIT accept(); } else { Q_EMIT reject(); } } +bool QXdgDesktopPortalFileDialog::useNativeFileDialog(QXdgDesktopPortalFileDialog::FallbackType fallbackType) const +{ + Q_D(const QXdgDesktopPortalFileDialog); + + if (d->failedToOpen && fallbackType != OpenFallback) + return true; + + if (d->fileChooserPortalVersion < 3) { + if (options()->fileMode() == QFileDialogOptions::Directory) + return true; + else if (options()->fileMode() == QFileDialogOptions::DirectoryOnly) + return true; + } + + return false; +} + QT_END_NAMESPACE + +#include "moc_qxdgdesktopportalfiledialog_p.cpp" diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h index c1f1a2c005..f309307cd6 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog_p.h @@ -1,46 +1,10 @@ -/**************************************************************************** -** -** Copyright (C) 2017-2018 Red Hat, Inc -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017-2018 Red Hat, Inc +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QXDGDESKTOPPORTALFILEDIALOG_P_H #define QXDGDESKTOPPORTALFILEDIALOG_P_H #include <qpa/qplatformdialoghelper.h> -#include <QVector> +#include <QList> QT_BEGIN_NAMESPACE @@ -51,6 +15,11 @@ class QXdgDesktopPortalFileDialog : public QPlatformFileDialogHelper Q_OBJECT Q_DECLARE_PRIVATE(QXdgDesktopPortalFileDialog) public: + enum FallbackType { + GenericFallback, + OpenFallback + }; + enum ConditionType : uint { GlobalPattern = 0, MimeType = 1 @@ -61,15 +30,15 @@ public: ConditionType type; QString pattern; // E.g. '*ico' or 'image/png' }; - typedef QVector<FilterCondition> FilterConditionList; + typedef QList<FilterCondition> FilterConditionList; struct Filter { QString name; // E.g. 'Images' or 'Text FilterConditionList filterConditions;; // E.g. [(0, '*.ico'), (1, 'image/png')] or [(0, '*.txt')] }; - typedef QVector<Filter> FilterList; + typedef QList<Filter> FilterList; - QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog = nullptr); + QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog = nullptr, uint fileChooserPortalVersion = 0); ~QXdgDesktopPortalFileDialog(); bool defaultNameFilterDisables() const override; @@ -80,6 +49,8 @@ public: void setFilter() override; void selectNameFilter(const QString &filter) override; QString selectedNameFilter() const override; + void selectMimeTypeFilter(const QString &filter) override; + QString selectedMimeTypeFilter() const override; void exec() override; bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override; @@ -90,7 +61,8 @@ private Q_SLOTS: private: void initializeDialog(); - void openPortal(); + void openPortal(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent); + bool useNativeFileDialog(FallbackType fallbackType = GenericFallback) const; QScopedPointer<QXdgDesktopPortalFileDialogPrivate> d_ptr; }; diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp index fb65f6d909..355d3e6cc9 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qxdgdesktopportaltheme.h" #include "qxdgdesktopportalfiledialog_p.h" @@ -45,13 +9,29 @@ #include <qpa/qplatformthemefactory_p.h> #include <qpa/qplatformintegration.h> +#include <QDBusConnection> +#include <QDBusMessage> +#include <QDBusPendingCall> +#include <QDBusPendingCallWatcher> +#include <QDBusPendingReply> +#include <QDBusReply> + QT_BEGIN_NAMESPACE -class QXdgDesktopPortalThemePrivate : public QPlatformThemePrivate -{ +using namespace Qt::StringLiterals; + +class QXdgDesktopPortalThemePrivate : public QObject + { + Q_OBJECT public: + enum XdgColorschemePref { + None, + PreferDark, + PreferLight + }; + QXdgDesktopPortalThemePrivate() - : QPlatformThemePrivate() + : QObject() { } ~QXdgDesktopPortalThemePrivate() @@ -59,7 +39,44 @@ public: delete baseTheme; } - QPlatformTheme *baseTheme; + /*! \internal + + Converts the given Freedesktop color scheme setting \a colorschemePref to a Qt::ColorScheme value. + Specification: https://github.com/flatpak/xdg-desktop-portal/blob/d7a304a00697d7d608821253cd013f3b97ac0fb6/data/org.freedesktop.impl.portal.Settings.xml#L33-L45 + + Unfortunately the enum numerical values are not defined identically, so we have to convert them. + + The mapping is as follows: + + Enum Index: Freedesktop definition | Qt definition + ----------------------------------- | ------------- + 0: No preference | 0: Unknown + 1: Prefer dark appearance | 2: Dark + 2: Prefer light appearance | 1: Light + */ + static Qt::ColorScheme colorSchemeFromXdgPref(const XdgColorschemePref colorschemePref) + { + switch (colorschemePref) { + case PreferDark: return Qt::ColorScheme::Dark; + case PreferLight: return Qt::ColorScheme::Light; + default: return Qt::ColorScheme::Unknown; + } + } + +public Q_SLOTS: + void settingChanged(const QString &group, const QString &key, + const QDBusVariant &value) + { + if (group == "org.freedesktop.appearance"_L1 && key == "color-scheme"_L1) { + colorScheme = colorSchemeFromXdgPref(static_cast<XdgColorschemePref>(value.variant().toUInt())); + QWindowSystemInterface::handleThemeChange(); + } + } + +public: + QPlatformTheme *baseTheme = nullptr; + uint fileChooserPortalVersion = 0; + Qt::ColorScheme colorScheme = Qt::ColorScheme::Unknown; }; QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() @@ -70,7 +87,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() QStringList themeNames; themeNames += QGuiApplicationPrivate::platform_integration->themeNames(); // 1) Look for a theme plugin. - for (const QString &themeName : qAsConst(themeNames)) { + for (const QString &themeName : std::as_const(themeNames)) { d->baseTheme = QPlatformThemeFactory::create(themeName, nullptr); if (d->baseTheme) break; @@ -79,7 +96,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() // 2) If no theme plugin was found ask the platform integration to // create a theme if (!d->baseTheme) { - for (const QString &themeName : qAsConst(themeNames)) { + for (const QString &themeName : std::as_const(themeNames)) { d->baseTheme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(themeName); if (d->baseTheme) break; @@ -90,6 +107,42 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() // 3) Fall back on the built-in "null" platform theme. if (!d->baseTheme) d->baseTheme = new QPlatformTheme; + + // Get information about portal version + QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.DBus.Properties"_L1, + "Get"_L1); + message << "org.freedesktop.portal.FileChooser"_L1 << "version"_L1; + QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [d] (QDBusPendingCallWatcher *watcher) { + QDBusPendingReply<QVariant> reply = *watcher; + if (reply.isValid()) { + d->fileChooserPortalVersion = reply.value().toUInt(); + } + watcher->deleteLater(); + }); + + // Get information about system theme preference + message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, + "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, + "Read"_L1); + message << "org.freedesktop.appearance"_L1 << "color-scheme"_L1; + + // this must not be asyncCall() because we have to set appearance now + QDBusReply<QVariant> reply = QDBusConnection::sessionBus().call(message); + if (reply.isValid()) { + const QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(reply.value()); + const QXdgDesktopPortalThemePrivate::XdgColorschemePref xdgPref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(dbusVariant.variant().toUInt()); + d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(xdgPref); + } + + QDBusConnection::sessionBus().connect( + "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, + "org.freedesktop.portal.Settings"_L1, "SettingChanged"_L1, d_ptr.get(), + SLOT(settingChanged(QString, QString, QDBusVariant))); } QPlatformMenuItem* QXdgDesktopPortalTheme::createPlatformMenuItem() const @@ -130,9 +183,12 @@ QPlatformDialogHelper* QXdgDesktopPortalTheme::createPlatformDialogHelper(Dialog { Q_D(const QXdgDesktopPortalTheme); - if (type == FileDialog) { + if (type == FileDialog && d->fileChooserPortalVersion) { + // Older versions of FileChooser portal don't support opening directories, therefore we fallback + // to native file dialog opened inside the sandbox to open a directory. if (d->baseTheme->usePlatformNativeDialog(type)) - return new QXdgDesktopPortalFileDialog(static_cast<QPlatformFileDialogHelper*>(d->baseTheme->createPlatformDialogHelper(type))); + return new QXdgDesktopPortalFileDialog(static_cast<QPlatformFileDialogHelper*>(d->baseTheme->createPlatformDialogHelper(type)), + d->fileChooserPortalVersion); return new QXdgDesktopPortalFileDialog; } @@ -166,6 +222,14 @@ QVariant QXdgDesktopPortalTheme::themeHint(ThemeHint hint) const return d->baseTheme->themeHint(hint); } +Qt::ColorScheme QXdgDesktopPortalTheme::colorScheme() const +{ + Q_D(const QXdgDesktopPortalTheme); + if (d->colorScheme == Qt::ColorScheme::Unknown) + return d->baseTheme->colorScheme(); + return d->colorScheme; +} + QPixmap QXdgDesktopPortalTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const { Q_D(const QXdgDesktopPortalTheme); @@ -200,3 +264,5 @@ QString QXdgDesktopPortalTheme::standardButtonText(int button) const } QT_END_NAMESPACE + +#include "qxdgdesktopportaltheme.moc" diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h index d38b3ddda3..1ac04c45e6 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2017 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QXDGDESKTOPPORTALTHEME_H #define QXDGDESKTOPPORTALTHEME_H @@ -70,6 +34,8 @@ public: QVariant themeHint(ThemeHint hint) const override; + Qt::ColorScheme colorScheme() const override; + QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override; QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = { }) const override; diff --git a/src/plugins/platformthemes/xdgdesktopportal/xdgdesktopportal.pro b/src/plugins/platformthemes/xdgdesktopportal/xdgdesktopportal.pro deleted file mode 100644 index 0a71484cf9..0000000000 --- a/src/plugins/platformthemes/xdgdesktopportal/xdgdesktopportal.pro +++ /dev/null @@ -1,17 +0,0 @@ -TARGET = qxdgdesktopportal - -PLUGIN_TYPE = platformthemes -PLUGIN_EXTENDS = - -PLUGIN_CLASS_NAME = QXdgDesktopPortalThemePlugin -load(qt_plugin) - -QT += core-private dbus gui-private theme_support-private - -HEADERS += \ - qxdgdesktopportaltheme.h \ - qxdgdesktopportalfiledialog_p.h - -SOURCES += \ - main.cpp \ - qxdgdesktopportaltheme.cpp \ - qxdgdesktopportalfiledialog.cpp |