Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
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
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->directory.isEmpty())
173 options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0'));
174
175 if (d->saveFile && !d->selectedFiles.isEmpty()) {
176 // current_file for the file to be pre-selected, current_name for the file name to be
177 // pre-filled current_file accepts absolute path and requires the file to exist while
178 // current_name accepts just file name
179 QFileInfo selectedFileInfo(d->selectedFiles.constFirst());
180 if (selectedFileInfo.exists())
181 options.insert("current_file"_L1,
182 QFile::encodeName(d->selectedFiles.constFirst()).append('\0'));
183 options.insert("current_name"_L1, selectedFileInfo.fileName());
184 }
185
186 // Insert filters
187 qDBusRegisterMetaType<FilterCondition>();
188 qDBusRegisterMetaType<FilterConditionList>();
189 qDBusRegisterMetaType<Filter>();
190 qDBusRegisterMetaType<FilterList>();
191
192 FilterList filterList;
193 auto selectedFilterIndex = filterList.size() - 1;
194
195 d->userVisibleToNameFilter.clear();
196
197 if (!d->mimeTypesFilters.isEmpty()) {
198 for (const QString &mimeTypefilter : d->mimeTypesFilters) {
199 QMimeDatabase mimeDatabase;
200 QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeTypefilter);
201
202 // Creates e.g. (1, "image/png")
203 FilterCondition filterCondition;
204 filterCondition.type = MimeType;
205 filterCondition.pattern = mimeTypefilter;
206
207 // Creates e.g. [((1, "image/png"))]
208 FilterConditionList filterConditions;
209 filterConditions << filterCondition;
210
211 // Creates e.g. [("Images", [((1, "image/png"))])]
213 filter.name = mimeType.comment();
214 filter.filterConditions = filterConditions;
215
216 if (filter.name.isEmpty())
217 filter.name = mimeTypefilter;
218
219 filterList << filter;
220
221 if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter)
222 selectedFilterIndex = filterList.size() - 1;
223 }
224 } else if (!d->nameFilters.isEmpty()) {
225 for (const QString &nameFilter : d->nameFilters) {
226 // Do parsing:
227 // Supported format is ("Images (*.png *.jpg)")
229 QRegularExpressionMatch match = regexp.match(nameFilter);
230 if (match.hasMatch()) {
231 QString userVisibleName = match.captured(1);
232 QStringList filterStrings = match.captured(2).split(u' ', Qt::SkipEmptyParts);
233
234 if (filterStrings.isEmpty()) {
235 qWarning() << "Filter " << userVisibleName << " is empty and will be ignored.";
236 continue;
237 }
238
239 FilterConditionList filterConditions;
240 for (const QString &filterString : filterStrings) {
241 FilterCondition filterCondition;
242 filterCondition.type = GlobalPattern;
243 filterCondition.pattern = filterString;
244 filterConditions << filterCondition;
245 }
246
248 filter.name = userVisibleName;
249 filter.filterConditions = filterConditions;
250
251 filterList << filter;
252
253 d->userVisibleToNameFilter.insert(userVisibleName, nameFilter);
254
255 if (!d->selectedNameFilter.isEmpty() && d->selectedNameFilter == nameFilter)
256 selectedFilterIndex = filterList.size() - 1;
257 }
258 }
259 }
260
261 if (!filterList.isEmpty())
262 options.insert("filters"_L1, QVariant::fromValue(filterList));
263
264 if (selectedFilterIndex != -1)
265 options.insert("current_filter"_L1, QVariant::fromValue(filterList[selectedFilterIndex]));
266
267 options.insert("handle_token"_L1, QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate()));
268
269 // TODO choices a(ssa(ss)s)
270 // List of serialized combo boxes to add to the file chooser.
271
272 auto unixServices = dynamic_cast<QGenericUnixServices *>(
274 if (parent && unixServices)
275 message << unixServices->portalWindowIdentifier(parent);
276 else
277 message << QString();
278
279 message << d->title << options;
280
281 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
283 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, d, windowFlags, windowModality, parent] (QDBusPendingCallWatcher *watcher) {
284 QDBusPendingReply<QDBusObjectPath> reply = *watcher;
285 // Any error means the dialog is not shown and we need to fallback
286 d->failedToOpen = reply.isError();
287 if (d->failedToOpen) {
288 if (d->nativeFileDialog) {
289 d->nativeFileDialog->show(windowFlags, windowModality, parent);
290 if (d->loop.isRunning())
291 d->nativeFileDialog->exec();
292 } else {
293 Q_EMIT reject();
294 }
295 } else {
296 QDBusConnection::sessionBus().connect(nullptr,
297 reply.value().path(),
298 "org.freedesktop.portal.Request"_L1,
299 "Response"_L1,
300 this,
301 SLOT(gotResponse(uint,QVariantMap)));
302 }
303 watcher->deleteLater();
304 });
305}
306
308{
309 return false;
310}
311
313{
315
316 if (d->nativeFileDialog) {
317 d->nativeFileDialog->setOptions(options());
318 d->nativeFileDialog->setDirectory(directory);
319 }
320
321 d->directory = directory.path();
322}
323
325{
327
328 if (d->nativeFileDialog && useNativeFileDialog())
329 return d->nativeFileDialog->directory();
330
331 return d->directory;
332}
333
335{
337
338 if (d->nativeFileDialog) {
339 d->nativeFileDialog->setOptions(options());
340 d->nativeFileDialog->selectFile(filename);
341 }
342
343 d->selectedFiles << filename.path();
344}
345
347{
349
350 if (d->nativeFileDialog && useNativeFileDialog())
351 return d->nativeFileDialog->selectedFiles();
352
353 QList<QUrl> files;
354 for (const QString &file : d->selectedFiles) {
355 files << QUrl(file);
356 }
357 return files;
358}
359
361{
363
364 if (d->nativeFileDialog) {
365 d->nativeFileDialog->setOptions(options());
366 d->nativeFileDialog->setFilter();
367 }
368}
369
371{
373 if (d->nativeFileDialog) {
374 d->nativeFileDialog->setOptions(options());
375 d->nativeFileDialog->selectMimeTypeFilter(filter);
376 }
377}
378
380{
382 return d->selectedMimeTypeFilter;
383}
384
386{
388
389 if (d->nativeFileDialog) {
390 d->nativeFileDialog->setOptions(options());
391 d->nativeFileDialog->selectNameFilter(filter);
392 }
393}
394
396{
398 return d->selectedNameFilter;
399}
400
402{
404
405 if (d->nativeFileDialog && useNativeFileDialog()) {
406 d->nativeFileDialog->exec();
407 return;
408 }
409
410 // HACK we have to avoid returning until we emit that the dialog was accepted or rejected
411 d->loop.exec();
412}
413
415{
417
418 if (d->nativeFileDialog)
419 d->nativeFileDialog->hide();
420}
421
422bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
423{
425
426 initializeDialog();
427
428 if (d->nativeFileDialog && useNativeFileDialog(OpenFallback))
429 return d->nativeFileDialog->show(windowFlags, windowModality, parent);
430
431 openPortal(windowFlags, windowModality, parent);
432
433 return true;
434}
435
436void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap &results)
437{
439
440 if (!response) {
441 if (results.contains("uris"_L1))
442 d->selectedFiles = results.value("uris"_L1).toStringList();
443
444 if (results.contains("current_filter"_L1)) {
445 const Filter selectedFilter = qdbus_cast<Filter>(results.value(QStringLiteral("current_filter")));
446 if (!selectedFilter.filterConditions.empty() && selectedFilter.filterConditions[0].type == MimeType) {
447 // s.a. QXdgDesktopPortalFileDialog::openPortal which basically does the inverse
448 d->selectedMimeTypeFilter = selectedFilter.filterConditions[0].pattern;
449 d->selectedNameFilter.clear();
450 } else {
451 d->selectedNameFilter = d->userVisibleToNameFilter.value(selectedFilter.name);
452 d->selectedMimeTypeFilter.clear();
453 }
454 }
455 Q_EMIT accept();
456 } else {
457 Q_EMIT reject();
458 }
459}
460
461bool QXdgDesktopPortalFileDialog::useNativeFileDialog(QXdgDesktopPortalFileDialog::FallbackType fallbackType) const
462{
464
465 if (d->failedToOpen && fallbackType != OpenFallback)
466 return true;
467
468 if (d->fileChooserPortalVersion < 3) {
469 if (options()->fileMode() == QFileDialogOptions::Directory)
470 return true;
471 else if (options()->fileMode() == QFileDialogOptions::DirectoryOnly)
472 return true;
473 }
474
475 return false;
476}
477
479
480#include "moc_qxdgdesktopportalfiledialog_p.cpp"
\inmodule QtDBus
static QDBusConnection sessionBus()
Returns a QDBusConnection object opened with the session bus.
\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 QtCore
Definition qeventloop.h:16
QStringList mimeTypeFilters() const
QString initiallySelectedNameFilter() const
QString labelText(DialogLabel label) const
QStringList nameFilters() const
QString initiallySelectedMimeTypeFilter() const
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:664
\inmodule QtCore
\inmodule QtCore
Definition qmimetype.h:25
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
The QPlatformFileDialogHelper class allows for platform-specific customization of file dialogs.
const QSharedPointer< QFileDialogOptions > & options() 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:129
\inmodule QtCore
Definition qurl.h:94
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2468
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:536
\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:128
const char * mimeType
#define qWarning
Definition qlogging.h:166
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
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:34
static 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:45