File indexing completed on 2024-11-17 05:01:39
0001 /* 0002 0003 SPDX-FileCopyrightText: 2017-2018 Red Hat Inc 0004 Contact: https://www.qt.io/licensing/ 0005 0006 This file is part of the plugins of the Qt Toolkit. 0007 0008 SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KFQF-Accepted-GPL OR LicenseRef-Qt-Commercial 0009 0010 */ 0011 0012 #include "qxdgdesktopportalfiledialog_p.h" 0013 0014 #include <qeventloop.h> 0015 0016 #include <QDBusConnection> 0017 #include <QDBusMessage> 0018 #include <QDBusPendingCall> 0019 #include <QDBusPendingCallWatcher> 0020 #include <QDBusPendingReply> 0021 #include <QtDBus> 0022 0023 #include <QFile> 0024 #include <QMetaType> 0025 #include <QMimeDatabase> 0026 #include <QMimeType> 0027 #include <QRandomGenerator> 0028 #include <QWindow> 0029 0030 QT_BEGIN_NAMESPACE 0031 0032 QDBusArgument &operator<<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::FilterCondition &filterCondition) 0033 { 0034 arg.beginStructure(); 0035 arg << filterCondition.type << filterCondition.pattern; 0036 arg.endStructure(); 0037 return arg; 0038 } 0039 0040 const QDBusArgument &operator>>(const QDBusArgument &arg, QXdgDesktopPortalFileDialog::FilterCondition &filterCondition) 0041 { 0042 uint type; 0043 QString filterPattern; 0044 arg.beginStructure(); 0045 arg >> type >> filterPattern; 0046 filterCondition.type = (QXdgDesktopPortalFileDialog::ConditionType)type; 0047 filterCondition.pattern = filterPattern; 0048 arg.endStructure(); 0049 0050 return arg; 0051 } 0052 0053 QDBusArgument &operator<<(QDBusArgument &arg, const QXdgDesktopPortalFileDialog::Filter filter) 0054 { 0055 arg.beginStructure(); 0056 arg << filter.name << filter.filterConditions; 0057 arg.endStructure(); 0058 return arg; 0059 } 0060 0061 const QDBusArgument &operator>>(const QDBusArgument &arg, QXdgDesktopPortalFileDialog::Filter &filter) 0062 { 0063 QString name; 0064 QXdgDesktopPortalFileDialog::FilterConditionList filterConditions; 0065 arg.beginStructure(); 0066 arg >> name >> filterConditions; 0067 filter.name = name; 0068 filter.filterConditions = filterConditions; 0069 arg.endStructure(); 0070 0071 return arg; 0072 } 0073 0074 class QXdgDesktopPortalFileDialogPrivate 0075 { 0076 public: 0077 QXdgDesktopPortalFileDialogPrivate(QPlatformFileDialogHelper *nativeFileDialog) 0078 : nativeFileDialog(nativeFileDialog) 0079 { 0080 } 0081 0082 WId winId = 0; 0083 bool modal = false; 0084 bool multipleFiles = false; 0085 bool selectDirectory = false; 0086 bool saveFile = false; 0087 QString acceptLabel; 0088 QUrl directory; 0089 QString title; 0090 QStringList nameFilters; 0091 QStringList mimeTypesFilters; 0092 QList<QUrl> selectedFiles; 0093 QPlatformFileDialogHelper *nativeFileDialog = nullptr; 0094 }; 0095 0096 QXdgDesktopPortalFileDialog::QXdgDesktopPortalFileDialog(QPlatformFileDialogHelper *nativeFileDialog) 0097 : QPlatformFileDialogHelper() 0098 , d_ptr(new QXdgDesktopPortalFileDialogPrivate(nativeFileDialog)) 0099 { 0100 Q_D(QXdgDesktopPortalFileDialog); 0101 0102 if (d->nativeFileDialog) { 0103 connect(d->nativeFileDialog, SIGNAL(accept()), this, SIGNAL(accept())); 0104 connect(d->nativeFileDialog, SIGNAL(reject()), this, SIGNAL(reject())); 0105 } 0106 } 0107 0108 QXdgDesktopPortalFileDialog::~QXdgDesktopPortalFileDialog() 0109 { 0110 } 0111 0112 void QXdgDesktopPortalFileDialog::initializeDialog() 0113 { 0114 Q_D(QXdgDesktopPortalFileDialog); 0115 0116 if (d->nativeFileDialog) 0117 d->nativeFileDialog->setOptions(options()); 0118 0119 if (options()->fileMode() == QFileDialogOptions::ExistingFiles) 0120 d->multipleFiles = true; 0121 0122 if (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly) { 0123 d->selectDirectory = true; 0124 } 0125 0126 if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) 0127 d->acceptLabel = options()->labelText(QFileDialogOptions::Accept); 0128 0129 if (!options()->windowTitle().isEmpty()) 0130 d->title = options()->windowTitle(); 0131 0132 if (options()->acceptMode() == QFileDialogOptions::AcceptSave) 0133 d->saveFile = true; 0134 0135 if (!options()->nameFilters().isEmpty()) 0136 d->nameFilters = options()->nameFilters(); 0137 0138 if (!options()->mimeTypeFilters().isEmpty()) 0139 d->mimeTypesFilters = options()->mimeTypeFilters(); 0140 0141 setDirectory(options()->initialDirectory()); 0142 } 0143 0144 void QXdgDesktopPortalFileDialog::openPortal() 0145 { 0146 Q_D(const QXdgDesktopPortalFileDialog); 0147 0148 QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), 0149 QStringLiteral("/org/freedesktop/portal/desktop"), 0150 QStringLiteral("org.freedesktop.portal.FileChooser"), 0151 d->saveFile ? QStringLiteral("SaveFile") : QStringLiteral("OpenFile")); 0152 QString parentWindowId = QStringLiteral("x11:") + QString::number(d->winId, 16); 0153 0154 QVariantMap options; 0155 if (!d->acceptLabel.isEmpty()) 0156 options.insert(QStringLiteral("accept_label"), d->acceptLabel); 0157 0158 options.insert(QStringLiteral("modal"), d->modal); 0159 options.insert(QStringLiteral("multiple"), d->multipleFiles); 0160 options.insert(QStringLiteral("directory"), d->selectDirectory); 0161 0162 if (d->saveFile) { 0163 if (!d->directory.isEmpty()) 0164 options.insert(QStringLiteral("current_folder"), QFile::encodeName(d->directory.toLocalFile()).append('\0')); 0165 0166 if (!d->selectedFiles.isEmpty()) 0167 options.insert(QStringLiteral("current_file"), QFile::encodeName(d->selectedFiles.first().toLocalFile()).append('\0')); 0168 } 0169 0170 // Insert filters 0171 qDBusRegisterMetaType<FilterCondition>(); 0172 qDBusRegisterMetaType<FilterConditionList>(); 0173 qDBusRegisterMetaType<Filter>(); 0174 qDBusRegisterMetaType<FilterList>(); 0175 0176 FilterList filterList; 0177 0178 if (!d->mimeTypesFilters.isEmpty()) { 0179 for (const QString &mimeTypefilter : d->mimeTypesFilters) { 0180 QMimeDatabase mimeDatabase; 0181 QMimeType mimeType = mimeDatabase.mimeTypeForName(mimeTypefilter); 0182 0183 // Creates e.g. (1, "image/png") 0184 FilterCondition filterCondition; 0185 filterCondition.type = MimeType; 0186 filterCondition.pattern = mimeTypefilter; 0187 0188 // Creates e.g. [((1, "image/png"))] 0189 FilterConditionList filterConditions; 0190 filterConditions << filterCondition; 0191 0192 // Creates e.g. [("Images", [((1, "image/png"))])] 0193 Filter filter; 0194 filter.name = mimeType.comment(); 0195 filter.filterConditions = filterConditions; 0196 0197 filterList << filter; 0198 } 0199 } else if (!d->nameFilters.isEmpty()) { 0200 for (const QString &filter : d->nameFilters) { 0201 // Do parsing: 0202 // Supported format is ("Images (*.png *.jpg)") 0203 QRegularExpression regexp(QString::fromLatin1(QPlatformFileDialogHelper::filterRegExp)); 0204 QRegularExpressionMatch match = regexp.match(filter); 0205 if (match.hasMatch()) { 0206 QString userVisibleName = match.captured(1); 0207 QStringList filterStrings = match.captured(2).split(QLatin1Char(' '), Qt::SkipEmptyParts); 0208 0209 FilterConditionList filterConditions; 0210 for (const QString &filterString : std::as_const(filterStrings)) { 0211 FilterCondition filterCondition; 0212 filterCondition.type = GlobalPattern; 0213 filterCondition.pattern = filterString; 0214 filterConditions << filterCondition; 0215 } 0216 0217 Filter filter; 0218 filter.name = userVisibleName; 0219 filter.filterConditions = filterConditions; 0220 0221 filterList << filter; 0222 } 0223 } 0224 } 0225 0226 if (!filterList.isEmpty()) 0227 options.insert(QStringLiteral("filters"), QVariant::fromValue(filterList)); 0228 0229 options.insert(QStringLiteral("handle_token"), QStringLiteral("qt%1").arg(QRandomGenerator::global()->generate())); 0230 0231 // TODO choices a(ssa(ss)s) 0232 // List of serialized combo boxes to add to the file chooser. 0233 0234 message << parentWindowId << d->title << options; 0235 0236 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); 0237 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); 0238 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) { 0239 QDBusPendingReply<QDBusObjectPath> reply = *watcher; 0240 if (reply.isError()) { 0241 Q_EMIT reject(); 0242 } else { 0243 QDBusConnection::sessionBus().connect({}, 0244 reply.value().path(), 0245 QStringLiteral("org.freedesktop.portal.Request"), 0246 QStringLiteral("Response"), 0247 this, 0248 SLOT(gotResponse(uint, QVariantMap))); 0249 } 0250 }); 0251 } 0252 0253 bool QXdgDesktopPortalFileDialog::defaultNameFilterDisables() const 0254 { 0255 return false; 0256 } 0257 0258 void QXdgDesktopPortalFileDialog::setDirectory(const QUrl &directory) 0259 { 0260 Q_D(QXdgDesktopPortalFileDialog); 0261 0262 if (d->nativeFileDialog) { 0263 d->nativeFileDialog->setOptions(options()); 0264 d->nativeFileDialog->setDirectory(directory); 0265 } 0266 0267 d->directory = directory; 0268 } 0269 0270 QUrl QXdgDesktopPortalFileDialog::directory() const 0271 { 0272 Q_D(const QXdgDesktopPortalFileDialog); 0273 0274 if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) 0275 return d->nativeFileDialog->directory(); 0276 0277 return d->directory; 0278 } 0279 0280 void QXdgDesktopPortalFileDialog::selectFile(const QUrl &filename) 0281 { 0282 Q_D(QXdgDesktopPortalFileDialog); 0283 0284 if (d->nativeFileDialog) { 0285 d->nativeFileDialog->setOptions(options()); 0286 d->nativeFileDialog->selectFile(filename); 0287 } 0288 0289 d->selectedFiles << filename; 0290 } 0291 0292 QList<QUrl> QXdgDesktopPortalFileDialog::selectedFiles() const 0293 { 0294 Q_D(const QXdgDesktopPortalFileDialog); 0295 0296 if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) 0297 return d->nativeFileDialog->selectedFiles(); 0298 0299 return d->selectedFiles; 0300 } 0301 0302 void QXdgDesktopPortalFileDialog::setFilter() 0303 { 0304 Q_D(QXdgDesktopPortalFileDialog); 0305 0306 if (d->nativeFileDialog) { 0307 d->nativeFileDialog->setOptions(options()); 0308 d->nativeFileDialog->setFilter(); 0309 } 0310 } 0311 0312 void QXdgDesktopPortalFileDialog::selectNameFilter(const QString &filter) 0313 { 0314 Q_D(QXdgDesktopPortalFileDialog); 0315 0316 if (d->nativeFileDialog) { 0317 d->nativeFileDialog->setOptions(options()); 0318 d->nativeFileDialog->selectNameFilter(filter); 0319 } 0320 } 0321 0322 QString QXdgDesktopPortalFileDialog::selectedNameFilter() const 0323 { 0324 // TODO 0325 return QString(); 0326 } 0327 0328 void QXdgDesktopPortalFileDialog::exec() 0329 { 0330 Q_D(QXdgDesktopPortalFileDialog); 0331 0332 if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) { 0333 d->nativeFileDialog->exec(); 0334 return; 0335 } 0336 0337 // HACK we have to avoid returning until we emit that the dialog was accepted or rejected 0338 QEventLoop loop; 0339 QObject::connect(this, &QXdgDesktopPortalFileDialog::accept, &loop, &QEventLoop::quit); 0340 QObject::connect(this, &QXdgDesktopPortalFileDialog::reject, &loop, &QEventLoop::quit); 0341 loop.exec(); 0342 } 0343 0344 void QXdgDesktopPortalFileDialog::hide() 0345 { 0346 Q_D(QXdgDesktopPortalFileDialog); 0347 0348 if (d->nativeFileDialog) 0349 d->nativeFileDialog->hide(); 0350 } 0351 0352 bool QXdgDesktopPortalFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) 0353 { 0354 Q_D(QXdgDesktopPortalFileDialog); 0355 0356 initializeDialog(); 0357 0358 d->modal = windowModality != Qt::NonModal; 0359 d->winId = parent ? parent->winId() : 0; 0360 0361 if (d->nativeFileDialog && (options()->fileMode() == QFileDialogOptions::Directory || options()->fileMode() == QFileDialogOptions::DirectoryOnly)) 0362 return d->nativeFileDialog->show(windowFlags, windowModality, parent); 0363 0364 openPortal(); 0365 0366 return true; 0367 } 0368 0369 void QXdgDesktopPortalFileDialog::gotResponse(uint response, const QVariantMap &results) 0370 { 0371 Q_D(QXdgDesktopPortalFileDialog); 0372 0373 if (!response) { 0374 if (results.contains(QStringLiteral("uris"))) { 0375 const QStringList uris = results.value(QStringLiteral("uris")).toStringList(); 0376 d->selectedFiles.clear(); 0377 d->selectedFiles.reserve(uris.size()); 0378 for (const QString &uri : uris) { 0379 // uris are expected to have proper "file:" scheme set 0380 d->selectedFiles.append(QUrl(uri)); 0381 } 0382 } 0383 Q_EMIT accept(); 0384 } else { 0385 Q_EMIT reject(); 0386 } 0387 } 0388 0389 QT_END_NAMESPACE