Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qxdgdesktopportalfiledialog.cpp
Go to the documentation of this file.
1// Copyright (C) 2017-2018 Red Hat, Inc
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <private/qgenericunixservices_p.h>
7#include <private/qguiapplication_p.h>
8#include <qpa/qplatformintegration.h>
9
10#include <QDBusConnection>
11#include <QDBusMessage>
12#include <QDBusPendingCall>
13#include <QDBusPendingCallWatcher>
14#include <QDBusPendingReply>
15#include <QDBusMetaType>
16
17#include <QEventLoop>
18#include <QFile>
19#include <QFileInfo>
20#include <QMetaType>
21#include <QMimeType>
22#include <QMimeDatabase>
23#include <QRandomGenerator>
24#include <QWindow>
25#include <QRegularExpression>
26
28
29using namespace Qt::StringLiterals;
30
32{
33 arg.beginStructure();
34 arg << filterCondition.type << filterCondition.pattern;
35 arg.endStructure();
36 return arg;
37}
38
40{
41 uint type;
42 QString filterPattern;
43 arg.beginStructure();
44 arg >> type >> filterPattern;
46 filterCondition.pattern = filterPattern;
47 arg.endStructure();
48
49 return arg;
50}
51
53{
54 arg.beginStructure();
55 arg << filter.name << filter.filterConditions;
56 arg.endStructure();
57 return arg;
58}
59
61{
64 arg.beginStructure();
65 arg >> name >> filterConditions;
66 filter.name = name;
67 filter.filterConditions = filterConditions;
68 arg.endStructure();
69
70 return arg;
71}
72
74{
75public:
79 { }
80
87 // maps user-visible name for portal to full name filter
92 std::unique_ptr<QPlatformFileDialogHelper> nativeFileDialog;
94 bool failedToOpen = false;
95 bool directoryMode = false;
96 bool multipleFiles = false;
97 bool saveFile = false;
98};
99
102 , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog, fileChooserPortalVersion))
103{
105
106 if (d->nativeFileDialog) {
107 connect(d->nativeFileDialog.get(), SIGNAL(accept()), this, SIGNAL(accept()));
108 connect(d->nativeFileDialog.get(), SIGNAL(reject()), this, SIGNAL(reject()));
109 }
110
111 d->loop.connect(this, SIGNAL(accept()), SLOT(quit()));
112 d->loop.connect(this, SIGNAL(reject()), SLOT(quit()));
113}
114
116{
117}
118
119void QXdgDesktopPortalFileDialog::initializeDialog()
120{
122
123 if (d->nativeFileDialog)
124 d->nativeFileDialog->setOptions(options());
125
126 if (options()->fileMode() == QFileDialogOptions::ExistingFiles)
127 d->multipleFiles = true;
128
130 d->directoryMode = true;
131
132 if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept))
134
135 if (!options()->windowTitle().isEmpty())
136 d->title = options()->windowTitle();
137
138 if (options()->acceptMode() == QFileDialogOptions::AcceptSave)
139 d->saveFile = true;
140
141 if (!options()->nameFilters().isEmpty())
142 d->nameFilters = options()->nameFilters();
143
144 if (!options()->mimeTypeFilters().isEmpty())
145 d->mimeTypesFilters = options()->mimeTypeFilters();
146
147 if (!options()->initiallySelectedMimeTypeFilter().isEmpty())
148 d->selectedMimeTypeFilter = options()->initiallySelectedMimeTypeFilter();
149
150 if (!options()->initiallySelectedNameFilter().isEmpty())
151 d->selectedNameFilter = options()->initiallySelectedNameFilter();
152
153 setDirectory(options()->initialDirectory());
154}
155
156void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
157{
159
160 QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1,
161 "/org/freedesktop/portal/desktop"_L1,
162 "org.freedesktop.portal.FileChooser"_L1,
163 d->saveFile ? "SaveFile"_L1 : "OpenFile"_L1);
165 if (!d->acceptLabel.isEmpty())
166 options.insert("accept_label"_L1, d->acceptLabel);
167
168 options.insert("modal"_L1, windowModality != Qt::NonModal);
169 options.insert("multiple"_L1, d->multipleFiles);
170 options.insert("directory"_L1, d->directoryMode);
171
172 if (d->saveFile) {
173 if (!d->directory.isEmpty())
174 options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0'));
175
176 if (!d->selectedFiles.isEmpty()) {
177 // current_file for the file to be pre-selected, current_name for the file name to be pre-filled
178 // current_file accepts absolute path and requires the file to exist
179 // while current_name accepts just file name
180 QFileInfo selectedFileInfo(d->selectedFiles.first());
181 if (selectedFileInfo.exists())
182 options.insert("current_file"_L1, QFile::encodeName(d->selectedFiles.first()).append('\0'));
183 options.insert("current_name"_L1, selectedFileInfo.fileName());
184 }
185 }
186
187 // Insert filters
188 qDBusRegisterMetaType<FilterCondition>();
189 qDBusRegisterMetaType<FilterConditionList>();
190 qDBusRegisterMetaType<Filter>();
191 qDBusRegisterMetaType<FilterList>();
192
193 FilterList filterList;
194 auto selectedFilterIndex = filterList.size() - 1;
195
196 d->userVisibleToNameFilter.clear();
197
198 if (!d->mimeTypesFilters.isEmpty()) {
199 for (const QString &mimeTypefilter : d->mimeTypesFilters) {
200 QMimeDatabase mimeDatabase;
201 QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeTypefilter);
202
203 // Creates e.g. (1, "image/png")
204 FilterCondition filterCondition;
205 filterCondition.type = MimeType;
206 filterCondition.pattern = mimeTypefilter;
207
208 // Creates e.g. [((1, "image/png"))]
209 FilterConditionList filterConditions;
210 filterConditions << filterCondition;
211
212 // Creates e.g. [("Images", [((1, "image/png"))])]
214 filter.name = mimeType.comment();
215 filter.filterConditions = filterConditions;
216
217 filterList << filter;
218
219 if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter)
220 selectedFilterIndex = filterList.size() - 1;
221 }
222 } else if (!d->nameFilters.isEmpty()) {
223 for (const QString &nameFilter : d->nameFilters) {
224 // Do parsing:
225 // Supported format is ("Images (*.png *.jpg)")
227 QRegularExpressionMatch match = regexp.match(nameFilter);
228 if (match.hasMatch()) {
229 QString userVisibleName = match.captured(1);
230 QStringList filterStrings = match.captured(2).split(u' ', Qt::SkipEmptyParts);
231
232 if (filterStrings.isEmpty()) {
233 qWarning() << "Filter " << userVisibleName << " is empty and will be ignored.";
234 continue;
235 }
236
237 FilterConditionList filterConditions;
238 for (const QString &filterString : filterStrings) {
239 FilterCondition filterCondition;
240 filterCondition.type = GlobalPattern;
241 filterCondition.pattern = filterString;
242 filterConditions << filterCondition;
243 }
244
246 filter.name = userVisibleName;
247 filter.filterConditions = filterConditions;
248
249 filterList << filter;
250
251 d->userVisibleToNameFilter.insert(userVisibleName, nameFilter);
252
253 if (!d->selectedNameFilter.isEmpty() && d->selectedNameFilter == nameFilter)
254 selectedFilterIndex = filterList.size() - 1;
255 }
256 }
257 }
258
259 if (!filterList.isEmpty())
260 options.insert("filters"_L1, QVariant::fromValue(filterList));
261
262 if (selectedFilterIndex != -1)
263 options.insert("current_filter"_L1, QVariant::fromValue(filterList[selectedFilterIndex]));
264
265 options.insert("handle_token"_L1, QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate()));
266
267 // TODO choices a(ssa(ss)s)
268 // List of serialized combo boxes to add to the file chooser.
269
270 auto unixServices = dynamic_cast<QGenericUnixServices *>(
272 if (parent && unixServices)
273 message << unixServices->portalWindowIdentifier(parent);
274 else
275 message << QString();
276
277 message << d->title << options;
278
281 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, d, windowFlags, windowModality, parent] (QDBusPendingCallWatcher *watcher) {
283 // Any error means the dialog is not shown and we need to fallback
284 d->failedToOpen = reply.isError();
285 if (d->failedToOpen) {
286 if (d->nativeFileDialog) {
287 d->nativeFileDialog->show(windowFlags, windowModality, parent);
288 if (d->loop.isRunning())
289 d->nativeFileDialog->exec();
290 } else {
291 Q_EMIT reject();
292 }
293 } else {
295 reply.value().path(),
296 "org.freedesktop.portal.Request"_L1,
297 "Response"_L1,
298 this,
299 SLOT(gotResponse(uint,QVariantMap)));
300 }
301 watcher->deleteLater();
302 });
303}
304
306{
307 return false;
308}
309
311{
313
314 if (d->nativeFileDialog) {
315 d->nativeFileDialog->setOptions(options());
316 d->nativeFileDialog->setDirectory(directory);
317 }
318
319 d->directory = directory.path();
320}
321
323{
325
326 if (d->nativeFileDialog && useNativeFileDialog())
327 return d->nativeFileDialog->directory();
328
329 return d->directory;
330}
331
333{
335
336 if (d->nativeFileDialog) {
337 d->nativeFileDialog->setOptions(options());
338 d->nativeFileDialog->selectFile(filename);
339 }
340
341 d->selectedFiles << filename.path();
342}
343
345{
347
348 if (d->nativeFileDialog && useNativeFileDialog())
349 return d->nativeFileDialog->selectedFiles();
350
352 for (const QString &file : d->selectedFiles) {
353 files << QUrl(file);
354 }
355 return files;
356}
357
359{
361
362 if (d->nativeFileDialog) {
363 d->nativeFileDialog->setOptions(options());
364 d->nativeFileDialog->setFilter();
365 }
366}
367
369{
371 if (d->nativeFileDialog) {
372 d->nativeFileDialog->setOptions(options());
373 d->nativeFileDialog->selectMimeTypeFilter(filter);
374 }
375}
376
378{
380 return d->selectedMimeTypeFilter;
381}
382
384{
386
387 if (d->nativeFileDialog) {
388 d->nativeFileDialog->setOptions(options());
389 d->nativeFileDialog->selectNameFilter(filter);
390 }
391}
392
394{
396 return d->selectedNameFilter;
397}
398
400{
402
403 if (d->nativeFileDialog && useNativeFileDialog()) {
404 d->nativeFileDialog->exec();
405 return;
406 }
407
408 // HACK we have to avoid returning until we emit that the dialog was accepted or rejected
409 d->loop.exec();
410}
411
413{
415
416 if (d->nativeFileDialog)
417 d->nativeFileDialog->hide();
418}
419
420bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
421{
423
424 initializeDialog();
425
426 if (d->nativeFileDialog && useNativeFileDialog(OpenFallback))
427 return d->nativeFileDialog->show(windowFlags, windowModality, parent);
428
429 openPortal(windowFlags, windowModality, parent);
430
431 return true;
432}
433
434void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap &results)
435{
437
438 if (!response) {
439 if (results.contains("uris"_L1))
440 d->selectedFiles = results.value("uris"_L1).toStringList();
441
442 if (results.contains("current_filter"_L1)) {
443 const Filter selectedFilter = qdbus_cast<Filter>(results.value(QStringLiteral("current_filter")));
444 if (!selectedFilter.filterConditions.empty() && selectedFilter.filterConditions[0].type == MimeType) {
445 // s.a. QXdgDesktopPortalFileDialog::openPortal which basically does the inverse
446 d->selectedMimeTypeFilter = selectedFilter.filterConditions[0].pattern;
447 d->selectedNameFilter.clear();
448 } else {
449 d->selectedNameFilter = d->userVisibleToNameFilter.value(selectedFilter.name);
450 d->selectedMimeTypeFilter.clear();
451 }
452 }
453 Q_EMIT accept();
454 } else {
455 Q_EMIT reject();
456 }
457}
458
459bool QXdgDesktopPortalFileDialog::useNativeFileDialog(QXdgDesktopPortalFileDialog::FallbackType fallbackType) const
460{
462
463 if (d->failedToOpen && fallbackType != OpenFallback)
464 return true;
465
466 if (d->fileChooserPortalVersion < 3) {
467 if (options()->fileMode() == QFileDialogOptions::Directory)
468 return true;
469 else if (options()->fileMode() == QFileDialogOptions::DirectoryOnly)
470 return true;
471 }
472
473 return false;
474}
475
477
478#include "moc_qxdgdesktopportalfiledialog_p.cpp"
QByteArray & append(char c)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\inmodule QtDBus
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
Connects the signal specified by the service, path, interface and name parameters to the slot slot in...
static QDBusConnection sessionBus()
Returns a QDBusConnection object opened with the session bus.
QDBusPendingCall asyncCall(const QDBusMessage &message, int timeout=-1) const
\inmodule QtDBus
static QDBusMessage createMethodCall(const QString &destination, const QString &path, const QString &interface, const QString &method)
Constructs a new DBus message representing a method call.
void finished(QDBusPendingCallWatcher *self=nullptr)
This signal is emitted when the pending call has finished and its reply is available.
\inmodule QtDBus
\inmodule QtDBus
\inmodule QtCore
Definition qeventloop.h:16
QStringList mimeTypeFilters() const
QString initiallySelectedNameFilter() const
QString labelText(DialogLabel label) const
QStringList nameFilters() const
QString initiallySelectedMimeTypeFilter() const
\inmodule QtCore \reentrant
Definition qfileinfo.h:22
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
virtual QString portalWindowIdentifier(QWindow *window)
static QPlatformIntegration * platformIntegration()
T value(qsizetype i) const
Definition qlist.h:661
Definition qmap.h:186
\inmodule QtCore
QMimeType mimeTypeForName(const QString &nameOrAlias) const
Returns a MIME type for nameOrAlias or an invalid one if none found.
\inmodule QtCore
Definition qmimetype.h:25
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:311
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
The QPlatformFileDialogHelper class allows for platform-specific customization of file dialogs.
const QSharedPointer< QFileDialogOptions > & options() const
virtual QPlatformServices * services() const
static Q_DECL_CONST_FUNCTION QRandomGenerator * global()
\threadsafe
Definition qrandom.h:275
\inmodule QtCore \reentrant
\inmodule QtCore \reentrant
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
\inmodule QtCore
Definition qurl.h:94
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2465
static auto fromValue(T &&value) noexcept(std::is_nothrow_copy_constructible_v< T > &&Private::CanUseInternalSpace< T >) -> std::enable_if_t< std::conjunction_v< std::is_copy_constructible< T >, std::is_destructible< T > >, QVariant >
Definition qvariant.h:531
\inmodule QtGui
Definition qwindow.h:63
std::unique_ptr< QPlatformFileDialogHelper > nativeFileDialog
QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog, uint fileChooserPortalVersion)
void selectNameFilter(const QString &filter) override
QString selectedMimeTypeFilter() const override
QList< FilterCondition > FilterConditionList
bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override
void selectMimeTypeFilter(const QString &filter) override
void setDirectory(const QUrl &directory) override
void selectFile(const QUrl &filename) override
QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog=nullptr, uint fileChooserPortalVersion=0)
QList< QUrl > selectedFiles() const override
Combined button and popup list for selecting options.
WindowModality
@ NonModal
@ SkipEmptyParts
Definition qnamespace.h:127
const char * mimeType
#define qWarning
Definition qlogging.h:162
#define SLOT(a)
Definition qobjectdefs.h:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
GLenum type
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLuint GLsizei const GLchar * message
GLuint name
SSL_CTX int(*) void arg)
#define QStringLiteral(str)
#define Q_EMIT
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
unsigned int uint
Definition qtypes.h:29
static QT_BEGIN_NAMESPACE QString windowTitle(HWND hwnd)
QDBusArgument & operator<<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::FilterCondition &filterCondition)
const QDBusArgument & operator>>(const QDBusArgument &arg, QXdgDesktopPortalFileDialog::FilterCondition &filterCondition)
QFutureWatcher< int > watcher
QFile file
[0]
QStringList mimeTypeFilters({"image/jpeg", "image/png", "application/octet-stream" })
[12]
QStringList files
[8]
QNetworkReply * reply
bool contains(const AT &t) const noexcept
Definition qlist.h:44
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent