From 82f92cffd3eea90dd1e8d853e7d7912be9776a66 Mon Sep 17 00:00:00 2001 From: Ilya Fedin Date: Sat, 27 Feb 2021 04:45:28 +0400 Subject: [PATCH] Port XDPFileDialog to gdbus --- .../platform/linux/linux_xdp_file_dialog.cpp | 985 ++++++++++-------- .../platform/linux/linux_xdp_file_dialog.h | 106 -- 2 files changed, 549 insertions(+), 542 deletions(-) diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp index 4c0eb6d659..dfc6a9b725 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.cpp @@ -9,24 +9,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/platform_file_utilities.h" #include "platform/linux/specific_linux.h" +#include "base/platform/base_platform_info.h" +#include "base/platform/linux/base_linux_glibmm_helper.h" #include "storage/localstorage.h" +#include "base/openssl_help.h" #include "base/qt_adapters.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include namespace Platform { namespace FileDialog { @@ -36,11 +31,22 @@ namespace { const char *filterRegExp = "^(.*)\\(([a-zA-Z0-9_.,*? +;#\\-\\[\\]@\\{\\}/!<>\\$%&=^~:\\|]*)\\)$"; -QStringList makeFilterList(const QString &filter) { +auto QStringListToStd(const QStringList &list) { + std::vector result; + ranges::transform( + list, + ranges::back_inserter(result), + &QString::toStdString); + return result; +} + +auto MakeFilterList(const QString &filter) { + std::vector result; QString f(filter); - if (f.isEmpty()) - return QStringList(); + if (f.isEmpty()) { + return result; + } QString sep(QLatin1String(";;")); int i = f.indexOf(sep, 0); @@ -51,7 +57,528 @@ QStringList makeFilterList(const QString &filter) { } } - return f.split(sep); + ranges::transform( + f.split(sep), + ranges::back_inserter(result), + [](const QString &string) { + return string.simplified().toStdString(); + }); + + return result; +} + +// This is a patched copy of file dialog from qxdgdesktopportal theme plugin. +// It allows using XDP file dialog flexibly, +// without relying on QT_QPA_PLATFORMTHEME variable. +// +// XDP file dialog is a dialog obtained via a DBus service +// provided by the current desktop environment. +class XDPFileDialog : public QDialog, public sigc::trackable { +public: + enum ConditionType : uint { + GlobalPattern = 0, + MimeType = 1 + }; + // Filters a(sa(us)) + // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] + typedef std::tuple FilterCondition; + typedef std::vector FilterConditionList; + typedef std::tuple Filter; + typedef std::vector FilterList; + + XDPFileDialog( + QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &directory = QString(), + const QString &nameFilter = QString(), + const QStringList &mimeTypeFilters = QStringList()); + ~XDPFileDialog(); + + void setVisible(bool visible) override; + + void setWindowTitle(const QString &windowTitle) { + _windowTitle = windowTitle.toStdString(); + } + void setAcceptLabel(const QString &acceptLabel) { + _acceptLabel = acceptLabel.toStdString(); + } + void setAcceptMode(QFileDialog::AcceptMode acceptMode) { + _acceptMode = acceptMode; + } + void setFileMode(QFileDialog::FileMode fileMode) { + _fileMode = fileMode; + } + void setOption(QFileDialog::Option option, bool on = true) { + if (on) { + _options |= option; + } else { + _options &= ~option; + } + } + + bool defaultNameFilterDisables() const; + QUrl directory() const; + void setDirectory(const QUrl &directory); + void selectFile(const QUrl &filename); + QList selectedFiles() const; + void setFilter(); + void selectNameFilter(const QString &filter); + QString selectedNameFilter() const; + void selectMimeTypeFilter(const QString &filter); + QString selectedMimeTypeFilter() const; + + int exec() override; + +private: + void openPortal(); + void gotResponse( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters); + + void showHelper( + Qt::WindowFlags windowFlags, + Qt::WindowModality windowModality, + QWindow *parent); + void hideHelper(); + + rpl::producer<> accepted(); + rpl::producer<> rejected(); + + Glib::RefPtr _dbusConnection; + Glib::RefPtr _cancellable; + uint _requestSignalId = 0; + + // Options + WId _winId = 0; + QFileDialog::Options _options; + QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; + QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; + bool _modal = false; + Glib::ustring _windowTitle = "Choose file"; + Glib::ustring _acceptLabel; + Glib::ustring _directory; + std::vector _nameFilters; + std::vector _mimeTypesFilters; + // maps user-visible name for portal to full name filter + std::map _userVisibleToNameFilter; + Glib::ustring _selectedMimeTypeFilter; + Glib::ustring _selectedNameFilter; + std::vector _selectedFiles; + + rpl::event_stream<> _accept; + rpl::event_stream<> _reject; + rpl::lifetime _lifetime; +}; + +XDPFileDialog::XDPFileDialog( + QWidget *parent, + const QString &caption, + const QString &directory, + const QString &nameFilter, + const QStringList &mimeTypeFilters) +: QDialog(parent) +, _windowTitle(caption.toStdString()) +, _directory(directory.toStdString()) +, _nameFilters(MakeFilterList(nameFilter)) +, _mimeTypesFilters(QStringListToStd(mimeTypeFilters)) +, _selectedMimeTypeFilter(!_mimeTypesFilters.empty() + ? _mimeTypesFilters[0] + : Glib::ustring()) +, _selectedNameFilter(!_nameFilters.empty() + ? _nameFilters[0] + : Glib::ustring()) { + accepted( + ) | rpl::start_with_next([=] { + accept(); + }, _lifetime); + + rejected( + ) | rpl::start_with_next([=] { + reject(); + }, _lifetime); +} + +XDPFileDialog::~XDPFileDialog() { + if (_cancellable) { + _cancellable->cancel(); + } + + if (_dbusConnection && _requestSignalId != 0) { + _dbusConnection->signal_unsubscribe(_requestSignalId); + } +} + +void XDPFileDialog::openPortal() { + std::stringstream parentWindowId; + + if (IsX11()) { + parentWindowId << "x11:" << std::hex << _winId; + } + + std::map options; + if (!_acceptLabel.empty()) { + options["accept_label"] = Glib::Variant::create( + _acceptLabel); + } + + options["modal"] = Glib::Variant::create(_modal); + options["multiple"] = Glib::Variant::create( + _fileMode == QFileDialog::ExistingFiles); + + options["directory"] = Glib::Variant::create( + _fileMode == QFileDialog::Directory + || _options.testFlag(QFileDialog::ShowDirsOnly)); + + if (_acceptMode == QFileDialog::AcceptSave) { + if (!_directory.empty()) { + options["current_folder"] = Glib::Variant::create( + _directory +'\0'); + } + + if (!_selectedFiles.empty()) { + options["current_file"] = Glib::Variant::create( + _selectedFiles[0] + '\0'); + } + } + + // Insert filters + FilterList filterList; + auto selectedFilterIndex = filterList.size() - 1; + + _userVisibleToNameFilter.clear(); + + if (!_mimeTypesFilters.empty()) { + for (const auto &mimeTypeFilter : _mimeTypesFilters) { + const auto mimeType = QMimeDatabase().mimeTypeForName( + QString::fromStdString(mimeTypeFilter)); + + // Creates e.g. (1, "image/png") + const auto filterCondition = FilterCondition{ + MimeType, + mimeTypeFilter, + }; + + // Creates e.g. [("Images", [((1, "image/png"))])] + filterList.push_back({ + mimeType.comment().toStdString(), + FilterConditionList{filterCondition}, + }); + + if (!_selectedMimeTypeFilter.empty() + && _selectedMimeTypeFilter == mimeTypeFilter) { + selectedFilterIndex = filterList.size() - 1; + } + } + } else if (!_nameFilters.empty()) { + for (const auto &nameFilter : _nameFilters) { + // Do parsing: + // Supported format is ("Images (*.png *.jpg)") + const QRegularExpression regexp( + QString::fromLatin1(filterRegExp)); + + const QRegularExpressionMatch match = regexp.match( + QString::fromStdString(nameFilter)); + + if (match.hasMatch()) { + const auto userVisibleName = match.captured(1).toStdString(); + const auto filterStrings = QStringListToStd( + match.captured(2).split( + QLatin1Char(' '), + base::QStringSkipEmptyParts)); + + if (filterStrings.empty()) { + LOG(( + "XDP File Dialog Error: " + "Filter %1 is empty and will be ignored.") + .arg(QString::fromStdString(userVisibleName))); + continue; + } + + FilterConditionList filterConditions; + for (const auto &filterString : filterStrings) { + filterConditions.push_back({ + GlobalPattern, + filterString, + }); + } + + filterList.push_back({ + userVisibleName, + filterConditions, + }); + + _userVisibleToNameFilter[userVisibleName] = nameFilter; + + if (!_selectedNameFilter.empty() + && _selectedNameFilter == nameFilter) { + selectedFilterIndex = filterList.size() - 1; + } + } + } + } + + if (!filterList.empty()) { + options["filters"] = Glib::Variant::create(filterList); + } + + if (selectedFilterIndex != -1) { + options["current_filter"] = Glib::Variant::create( + filterList[selectedFilterIndex]); + } + + const auto handleToken = Glib::ustring("tdesktop") + + std::to_string(openssl::RandomValue()); + + options["handle_token"] = Glib::Variant::create( + handleToken); + + // TODO choices a(ssa(ss)s) + // List of serialized combo boxes to add to the file chooser. + + try { + _dbusConnection = Gio::DBus::Connection::get_sync( + Gio::DBus::BusType::BUS_TYPE_SESSION); + + auto uniqueName = _dbusConnection->get_unique_name(); + uniqueName.erase(0, 1); + uniqueName.replace(uniqueName.find('.'), 1, 1, '_'); + + const auto requestPath = Glib::ustring( + "/org/freedesktop/portal/desktop/request/") + + uniqueName + + '/' + + handleToken; + + _requestSignalId = _dbusConnection->signal_subscribe( + sigc::mem_fun(this, &XDPFileDialog::gotResponse), + {}, + "org.freedesktop.portal.Request", + "Response", + requestPath); + + // synchronize functor deletion by this cancellable + _cancellable = Gio::Cancellable::create(); + + _dbusConnection->call( + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.FileChooser", + _acceptMode == QFileDialog::AcceptSave + ? "SaveFile" + : "OpenFile", + base::Platform::MakeGlibVariant(std::tuple{ + Glib::ustring(parentWindowId.str()), + _windowTitle, + options, + }), + [=](const Glib::RefPtr &result) { + try { + _dbusConnection->call_finish(result); + } catch (const Glib::Error &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + + crl::on_main([=] { + _reject.fire({}); + }); + } + }, + _cancellable, + "org.freedesktop.portal.Desktop"); + } catch (const Glib::Error &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + } +} + +bool XDPFileDialog::defaultNameFilterDisables() const { + return false; +} + +void XDPFileDialog::setDirectory(const QUrl &directory) { + _directory = directory.path().toStdString(); +} + +QUrl XDPFileDialog::directory() const { + return QString::fromStdString(_directory); +} + +void XDPFileDialog::selectFile(const QUrl &filename) { + _selectedFiles.push_back(filename.path().toStdString()); +} + +QList XDPFileDialog::selectedFiles() const { + QList files; + ranges::transform( + _selectedFiles, + ranges::back_inserter(files), + [](const Glib::ustring &string) { + return QUrl(QString::fromStdString(string)); + }); + return files; +} + +void XDPFileDialog::setFilter() { +} + +void XDPFileDialog::selectMimeTypeFilter(const QString &filter) { +} + +QString XDPFileDialog::selectedMimeTypeFilter() const { + return QString::fromStdString(_selectedMimeTypeFilter); +} + +void XDPFileDialog::selectNameFilter(const QString &filter) { +} + +QString XDPFileDialog::selectedNameFilter() const { + return QString::fromStdString(_selectedNameFilter); +} + +int XDPFileDialog::exec() { + bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose, false); + + bool wasShowModal = testAttribute(Qt::WA_ShowModal); + setAttribute(Qt::WA_ShowModal, true); + setResult(0); + + show(); + + QPointer guard = this; + + // HACK we have to avoid returning until we emit + // that the dialog was accepted or rejected + QEventLoop loop; + rpl::lifetime lifetime; + + accepted( + ) | rpl::start_with_next([&] { + loop.quit(); + }, lifetime); + + rejected( + ) | rpl::start_with_next([&] { + loop.quit(); + }, lifetime); + + loop.exec(); + + if (guard.isNull()) { + return QDialog::Rejected; + } + + setAttribute(Qt::WA_ShowModal, wasShowModal); + + return result(); +} + +void XDPFileDialog::setVisible(bool visible) { + if (visible) { + if (testAttribute(Qt::WA_WState_ExplicitShowHide) + && !testAttribute(Qt::WA_WState_Hidden)) { + return; + } + } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) + && testAttribute(Qt::WA_WState_Hidden)) { + return; + } + + if (visible) { + showHelper( + windowFlags(), + windowModality(), + parentWidget() + ? parentWidget()->windowHandle() + : nullptr); + } else { + hideHelper(); + } + + // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below + // updates the state correctly, but skips showing the non-native version: + setAttribute(Qt::WA_DontShowOnScreen); + + QDialog::setVisible(visible); +} + +void XDPFileDialog::hideHelper() { +} + +void XDPFileDialog::showHelper( + Qt::WindowFlags windowFlags, + Qt::WindowModality windowModality, + QWindow *parent) { + _modal = windowModality != Qt::NonModal; + _winId = parent ? parent->winId() : 0; + + openPortal(); +} + +void XDPFileDialog::gotResponse( + const Glib::RefPtr &connection, + const Glib::ustring &sender_name, + const Glib::ustring &object_path, + const Glib::ustring &interface_name, + const Glib::ustring &signal_name, + const Glib::VariantContainerBase ¶meters) { + try { + auto parametersCopy = parameters; + + const auto response = base::Platform::GlibVariantCast( + parametersCopy.get_child(0)); + + const auto results = base::Platform::GlibVariantCast< + std::map< + Glib::ustring, + Glib::VariantBase + >>(parametersCopy.get_child(1)); + + if (!response) { + if (const auto i = results.find("uris"); i != end(results)) { + _selectedFiles = base::Platform::GlibVariantCast< + std::vector>(i->second); + } + + if (const auto i = results.find("current_filter"); + i != end(results)) { + auto selectedFilter = base::Platform::GlibVariantCast< + Filter>(i->second); + + if (!std::get<1>(selectedFilter).empty() + && std::get<0>( + std::get<1>(selectedFilter)[0]) == MimeType) { + // s.a. XDPFileDialog::openPortal + // which basically does the inverse + _selectedMimeTypeFilter = std::get<1>( + std::get<1>(selectedFilter)[0]); + _selectedNameFilter.clear(); + } else { + _selectedNameFilter = + _userVisibleToNameFilter[std::get<0>(selectedFilter)]; + _selectedMimeTypeFilter.clear(); + } + } + + _accept.fire({}); + } else { + _reject.fire({}); + } + } catch (const std::exception &e) { + LOG(("XDP File Dialog Error: %1").arg( + QString::fromStdString(e.what()))); + } +} + +rpl::producer<> XDPFileDialog::accepted() { + return _accept.events(); +} + +rpl::producer<> XDPFileDialog::rejected() { + return _reject.events(); } } // namespace @@ -75,11 +602,13 @@ bool Get( InitLastPath(); } - XDPFileDialog dialog(parent, caption, QString(), filter); + XDPFileDialog dialog(parent, caption, QString(), filter, QStringList()); dialog.setModal(true); if (type == Type::ReadFile || type == Type::ReadFiles) { - dialog.setFileMode((type == Type::ReadFiles) ? QFileDialog::ExistingFiles : QFileDialog::ExistingFile); + dialog.setFileMode((type == Type::ReadFiles) + ? QFileDialog::ExistingFiles + : QFileDialog::ExistingFile); dialog.setAcceptMode(QFileDialog::AcceptOpen); } else if (type == Type::ReadFolder) { dialog.setAcceptMode(QFileDialog::AcceptOpen); @@ -129,422 +658,6 @@ bool Get( return false; } -QDBusArgument &operator <<(QDBusArgument &arg, const XDPFileDialog::FilterCondition &filterCondition) { - arg.beginStructure(); - arg << filterCondition.type << filterCondition.pattern; - arg.endStructure(); - return arg; -} - -const QDBusArgument &operator >>(const QDBusArgument &arg, XDPFileDialog::FilterCondition &filterCondition) { - uint type; - QString filterPattern; - arg.beginStructure(); - arg >> type >> filterPattern; - filterCondition.type = (XDPFileDialog::ConditionType)type; - filterCondition.pattern = filterPattern; - arg.endStructure(); - - return arg; -} - -QDBusArgument &operator <<(QDBusArgument &arg, const XDPFileDialog::Filter filter) { - arg.beginStructure(); - arg << filter.name << filter.filterConditions; - arg.endStructure(); - return arg; -} - -const QDBusArgument &operator >>(const QDBusArgument &arg, XDPFileDialog::Filter &filter) { - QString name; - XDPFileDialog::FilterConditionList filterConditions; - arg.beginStructure(); - arg >> name >> filterConditions; - filter.name = name; - filter.filterConditions = filterConditions; - arg.endStructure(); - - return arg; -} - -class XDPFileDialogPrivate { -public: - XDPFileDialogPrivate() { - } - - WId winId = 0; - bool directoryMode = false; - bool modal = false; - bool multipleFiles = false; - bool saveFile = false; - QString acceptLabel; - QString directory; - QString title; - QStringList nameFilters; - QStringList mimeTypesFilters; - // maps user-visible name for portal to full name filter - QMap userVisibleToNameFilter; - QString selectedMimeTypeFilter; - QString selectedNameFilter; - QStringList selectedFiles; -}; - -XDPFileDialog::XDPFileDialog(QWidget *parent, const QString &caption, const QString &directory, const QString &filter) -: QDialog(parent) -, d_ptr(new XDPFileDialogPrivate()) -, _windowTitle(caption) -, _initialDirectory(directory) { - Q_D(XDPFileDialog); - - auto filters = makeFilterList(filter); - const int numFilters = filters.count(); - _nameFilters.reserve(numFilters); - for (int i = 0; i < numFilters; ++i) { - _nameFilters << filters[i].simplified(); - } - - accepted( - ) | rpl::start_with_next([=] { - Q_EMIT accept(); - }, _lifetime); - - rejected( - ) | rpl::start_with_next([=] { - Q_EMIT reject(); - }, _lifetime); -} - -XDPFileDialog::~XDPFileDialog() { -} - -void XDPFileDialog::initializeDialog() { - Q_D(XDPFileDialog); - - if (_fileMode == QFileDialog::ExistingFiles) - d->multipleFiles = true; - - if (_fileMode == QFileDialog::Directory || _options.testFlag(QFileDialog::ShowDirsOnly)) - d->directoryMode = true; - -#if 0 // it is commented in GtkFileDialog for some reason, do the same - if (options()->isLabelExplicitlySet(QFileDialog::Accept)) - d->acceptLabel = options()->labelText(QFileDialog::Accept); -#endif - - if (!_windowTitle.isEmpty()) - d->title = _windowTitle; - - if (_acceptMode == QFileDialog::AcceptSave) - d->saveFile = true; - - if (!_nameFilters.isEmpty()) - d->nameFilters = _nameFilters; - -#if 0 // what is the right way to implement this? - if (!options()->mimeTypeFilters().isEmpty()) - d->mimeTypesFilters = options()->mimeTypeFilters(); - - if (!options()->initiallySelectedMimeTypeFilter().isEmpty()) - d->selectedMimeTypeFilter = options()->initiallySelectedMimeTypeFilter(); -#endif - - const auto initialNameFilter = _nameFilters.isEmpty() ? QString() : _nameFilters.front(); - if (!initialNameFilter.isEmpty()) - d->selectedNameFilter = initialNameFilter; - - setDirectory(_initialDirectory); -} - -void XDPFileDialog::openPortal() { - Q_D(XDPFileDialog); - - 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, 16); - - 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); - options.insert(QLatin1String("directory"), d->directoryMode); - - 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')); - } - - // Insert filters - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - - FilterList filterList; - auto selectedFilterIndex = filterList.size() - 1; - - d->userVisibleToNameFilter.clear(); - - if (!d->mimeTypesFilters.isEmpty()) { - for (const QString &mimeTypefilter : d->mimeTypesFilters) { - QMimeDatabase mimeDatabase; - QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeTypefilter); - - // Creates e.g. (1, "image/png") - FilterCondition filterCondition; - filterCondition.type = MimeType; - filterCondition.pattern = mimeTypefilter; - - // Creates e.g. [((1, "image/png"))] - FilterConditionList filterConditions; - filterConditions << filterCondition; - - // Creates e.g. [("Images", [((1, "image/png"))])] - Filter filter; - filter.name = mimeType.comment(); - filter.filterConditions = filterConditions; - - filterList << filter; - - if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter) - selectedFilterIndex = filterList.size() - 1; - } - } else if (!d->nameFilters.isEmpty()) { - for (const QString &nameFilter : d->nameFilters) { - // Do parsing: - // Supported format is ("Images (*.png *.jpg)") - QRegularExpression regexp(QString::fromLatin1(filterRegExp)); - QRegularExpressionMatch match = regexp.match(nameFilter); - if (match.hasMatch()) { - QString userVisibleName = match.captured(1); - QStringList filterStrings = match.captured(2).split(QLatin1Char(' '), base::QStringSkipEmptyParts); - - if (filterStrings.isEmpty()) { - LOG(("XDP File Dialog Error: Filter %1 is empty and will be ignored.").arg(userVisibleName)); - continue; - } - - FilterConditionList filterConditions; - for (const QString &filterString : filterStrings) { - FilterCondition filterCondition; - filterCondition.type = GlobalPattern; - filterCondition.pattern = filterString; - filterConditions << filterCondition; - } - - Filter filter; - filter.name = userVisibleName; - 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)); - - if (selectedFilterIndex != -1) - options.insert(QLatin1String("current_filter"), QVariant::fromValue(filterList[selectedFilterIndex])); - - options.insert(QLatin1String("handle_token"), 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; - - QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); - QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); - connect(watcher, &QDBusPendingCallWatcher::finished, this, [this] (QDBusPendingCallWatcher *watcher) { - QDBusPendingReply reply = *watcher; - if (reply.isError()) { - _reject.fire({}); - } else { - QDBusConnection::sessionBus().connect( - nullptr, - reply.value().path(), - QLatin1String("org.freedesktop.portal.Request"), - QLatin1String("Response"), - this, - SLOT(gotResponse(uint,QVariantMap))); - } - }); -} - -bool XDPFileDialog::defaultNameFilterDisables() const { - return false; -} - -void XDPFileDialog::setDirectory(const QUrl &directory) { - Q_D(XDPFileDialog); - - d->directory = directory.path(); -} - -QUrl XDPFileDialog::directory() const { - Q_D(const XDPFileDialog); - - return d->directory; -} - -void XDPFileDialog::selectFile(const QUrl &filename) { - Q_D(XDPFileDialog); - - d->selectedFiles << filename.path(); -} - -QList XDPFileDialog::selectedFiles() const { - Q_D(const XDPFileDialog); - - QList files; - for (const QString &file : d->selectedFiles) { - files << QUrl(file); - } - return files; -} - -void XDPFileDialog::setFilter() { - Q_D(XDPFileDialog); -} - -void XDPFileDialog::selectMimeTypeFilter(const QString &filter) { - Q_D(XDPFileDialog); -} - -QString XDPFileDialog::selectedMimeTypeFilter() const { - Q_D(const XDPFileDialog); - return d->selectedMimeTypeFilter; -} - -void XDPFileDialog::selectNameFilter(const QString &filter) { - Q_D(XDPFileDialog); -} - -QString XDPFileDialog::selectedNameFilter() const { - Q_D(const XDPFileDialog); - return d->selectedNameFilter; -} - -int XDPFileDialog::exec() { - Q_D(XDPFileDialog); - - bool deleteOnClose = testAttribute(Qt::WA_DeleteOnClose); - setAttribute(Qt::WA_DeleteOnClose, false); - - bool wasShowModal = testAttribute(Qt::WA_ShowModal); - setAttribute(Qt::WA_ShowModal, true); - setResult(0); - - show(); - - QPointer guard = this; - - // HACK we have to avoid returning until we emit that the dialog was accepted or rejected - QEventLoop loop; - rpl::lifetime lifetime; - - accepted( - ) | rpl::start_with_next([&] { - loop.quit(); - }, lifetime); - - rejected( - ) | rpl::start_with_next([&] { - loop.quit(); - }, lifetime); - - loop.exec(); - - if (guard.isNull()) - return QDialog::Rejected; - - setAttribute(Qt::WA_ShowModal, wasShowModal); - - return result(); -} - -void XDPFileDialog::setVisible(bool visible) { - if (visible) { - if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) { - return; - } - } else if (testAttribute(Qt::WA_WState_ExplicitShowHide) && testAttribute(Qt::WA_WState_Hidden)) { - return; - } - - if (visible) { - showHelper(windowFlags(), windowModality(), parentWidget() ? parentWidget()->windowHandle() : nullptr); - } else { - hideHelper(); - } - - // Set WA_DontShowOnScreen so that QDialog::setVisible(visible) below - // updates the state correctly, but skips showing the non-native version: - setAttribute(Qt::WA_DontShowOnScreen); - - QDialog::setVisible(visible); -} - -void XDPFileDialog::hideHelper() { - Q_D(XDPFileDialog); -} - -void XDPFileDialog::showHelper(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) { - Q_D(XDPFileDialog); - - initializeDialog(); - - d->modal = windowModality != Qt::NonModal; - d->winId = parent ? parent->winId() : 0; - - openPortal(); -} - -void XDPFileDialog::gotResponse(uint response, const QVariantMap &results) { - Q_D(XDPFileDialog); - - if (!response) { - if (results.contains(QLatin1String("uris"))) - d->selectedFiles = results.value(QLatin1String("uris")).toStringList(); - - if (results.contains(QLatin1String("current_filter"))) { - const Filter selectedFilter = qdbus_cast(results.value(QStringLiteral("current_filter"))); - if (!selectedFilter.filterConditions.empty() && selectedFilter.filterConditions[0].type == MimeType) { - // s.a. XDPFileDialog::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(); - } - } - _accept.fire({}); - } else { - _reject.fire({}); - } -} - -rpl::producer<> XDPFileDialog::accepted() { - return _accept.events(); -} - -rpl::producer<> XDPFileDialog::rejected() { - return _reject.events(); -} - } // namespace XDP } // namespace FileDialog } // namespace Platform diff --git a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h index 57bc43e569..1ded00e064 100644 --- a/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h +++ b/Telegram/SourceFiles/platform/linux/linux_xdp_file_dialog.h @@ -9,14 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/file_utilities.h" -#include -#include - namespace Platform { namespace FileDialog { namespace XDP { -class XDPFileDialogPrivate; using Type = ::FileDialog::internal::Type; bool Use(Type type = Type::ReadFile); @@ -29,108 +25,6 @@ bool Get( Type type, QString startFile); -// This is a patched copy of file dialog from qxdgdesktopportal theme plugin. -// It allows using XDP file dialog flexibly, -// without relying on QT_QPA_PLATFORMTHEME variable. -// -// XDP file dialog is a dialog obtained via a DBus service -// provided by the current desktop environment. -class XDPFileDialog : public QDialog { - Q_OBJECT - Q_DECLARE_PRIVATE(XDPFileDialog) -public: - enum ConditionType : uint { - GlobalPattern = 0, - MimeType = 1 - }; - // Filters a(sa(us)) - // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] - struct FilterCondition { - ConditionType type; - QString pattern; // E.g. '*ico' or 'image/png' - }; - typedef QVector FilterConditionList; - - struct Filter { - QString name; // E.g. 'Images' or 'Text - FilterConditionList filterConditions;; // E.g. [(0, '*.ico'), (1, 'image/png')] or [(0, '*.txt')] - }; - typedef QVector FilterList; - - XDPFileDialog( - QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &directory = QString(), - const QString &filter = QString()); - ~XDPFileDialog(); - - void setVisible(bool visible) override; - - void setWindowTitle(const QString &windowTitle) { - _windowTitle = windowTitle; - } - void setAcceptMode(QFileDialog::AcceptMode acceptMode) { - _acceptMode = acceptMode; - } - void setFileMode(QFileDialog::FileMode fileMode) { - _fileMode = fileMode; - } - void setOption(QFileDialog::Option option, bool on = true) { - if (on) { - _options |= option; - } else { - _options &= ~option; - } - } - - bool defaultNameFilterDisables() const; - QUrl directory() const; - void setDirectory(const QUrl &directory); - void selectFile(const QUrl &filename); - QList selectedFiles() const; - void setFilter(); - void selectNameFilter(const QString &filter); - QString selectedNameFilter() const; - void selectMimeTypeFilter(const QString &filter); - QString selectedMimeTypeFilter() const; - - int exec() override; - -private Q_SLOTS: - void gotResponse(uint response, const QVariantMap &results); - -private: - void initializeDialog(); - void openPortal(); - - void showHelper(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent); - void hideHelper(); - - rpl::producer<> accepted(); - rpl::producer<> rejected(); - - QScopedPointer d_ptr; - - // Options - QFileDialog::Options _options; - QString _windowTitle = "Choose file"; - QString _initialDirectory; - QStringList _initialFiles; - QStringList _nameFilters; - QFileDialog::AcceptMode _acceptMode = QFileDialog::AcceptOpen; - QFileDialog::FileMode _fileMode = QFileDialog::ExistingFile; - - rpl::event_stream<> _accept; - rpl::event_stream<> _reject; - rpl::lifetime _lifetime; -}; - } // namespace XDP } // namespace FileDialog } // namespace Platform - -Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterCondition); -Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterConditionList); -Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::Filter); -Q_DECLARE_METATYPE(Platform::FileDialog::XDP::XDPFileDialog::FilterList); -