File indexing completed on 2024-11-17 05:01:37
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2013 Aleix Pol Gonzalez <aleixpol@blue-systems.com> 0003 SPDX-FileCopyrightText: 2014 Martin Klapetek <mklapetek@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "kdeplatformfiledialoghelper.h" 0009 #include "kdirselectdialog_p.h" 0010 0011 #include <KIO/StatJob> 0012 #include <KJobWidgets> 0013 #include <KProtocolInfo> 0014 #include <KSharedConfig> 0015 #include <KWindowConfig> 0016 #include <kdiroperator.h> 0017 #include <kfilefiltercombo.h> 0018 #include <kfilewidget.h> 0019 #include <kio_version.h> 0020 #include <klocalizedstring.h> 0021 0022 #include <QDialogButtonBox> 0023 #include <QMimeDatabase> 0024 #include <QPushButton> 0025 #include <QTextStream> 0026 #include <QVBoxLayout> 0027 #include <QWindow> 0028 0029 /* 0030 * Map a Qt filter string into a KDE one. 0031 */ 0032 QString KDEPlatformFileDialogHelper::qt2KdeFilter(const QStringList &f) 0033 { 0034 QStringList qtFilters(f); 0035 // escape slashes to avoid them being interpreted as MIME types 0036 qtFilters.replaceInStrings(QStringLiteral("/"), QStringLiteral("\\/")); 0037 0038 QStringList outFilters; 0039 0040 for (const QString &filter : std::as_const(qtFilters)) { 0041 const QString name = filter.left(filter.indexOf(QLatin1Char('('))).trimmed(); 0042 const QStringList extensions = cleanFilterList(filter); 0043 0044 if (name.isEmpty()) { 0045 outFilters << extensions.join(QLatin1Char(' ')); 0046 } else { 0047 outFilters << extensions.join(QLatin1Char(' ')) + QLatin1Char('|') + name; 0048 } 0049 } 0050 0051 return outFilters.join(QLatin1Char('\n')); 0052 } 0053 0054 namespace 0055 { 0056 /* 0057 * Map a KDE filter string into a Qt one. 0058 */ 0059 static QString kde2QtFilter(const QStringList &list, const QString &kde, const QString &filterText) 0060 { 0061 QStringList::const_iterator it(list.constBegin()), end(list.constEnd()); 0062 int pos; 0063 0064 for (; it != end; ++it) { 0065 if (-1 != (pos = it->indexOf(kde)) && pos > 0 && (QLatin1Char('(') == (*it)[pos - 1] || QLatin1Char(' ') == (*it)[pos - 1]) 0066 && it->length() >= kde.length() + pos && (QLatin1Char(')') == (*it)[pos + kde.length()] || QLatin1Char(' ') == (*it)[pos + kde.length()]) 0067 && (filterText.isEmpty() || it->startsWith(filterText))) { 0068 return *it; 0069 } 0070 } 0071 return QString(); 0072 } 0073 } 0074 0075 KDEPlatformFileDialog::KDEPlatformFileDialog() 0076 : KDEPlatformFileDialogBase() 0077 , m_fileWidget(new KFileWidget(QUrl(), this)) 0078 { 0079 auto v = new QVBoxLayout; 0080 v->setContentsMargins({}); 0081 setLayout(v); 0082 connect(m_fileWidget, &KFileWidget::filterChanged, this, &KDEPlatformFileDialogBase::filterSelected); 0083 layout()->addWidget(m_fileWidget); 0084 m_fileWidget->okButton()->show(); 0085 m_fileWidget->cancelButton()->show(); 0086 0087 connect(this, &QDialog::rejected, m_fileWidget, &KFileWidget::slotCancel); 0088 // Also call the cancel function when the dialog is closed via the escape key 0089 // or titlebar close button to make sure we always save the view config 0090 connect(this, &KDEPlatformFileDialog::rejected, m_fileWidget, &KFileWidget::slotCancel); 0091 connect(m_fileWidget->okButton(), &QAbstractButton::clicked, m_fileWidget, &KFileWidget::slotOk); 0092 connect(m_fileWidget, &KFileWidget::accepted, m_fileWidget, &KFileWidget::accept); 0093 connect(m_fileWidget, &KFileWidget::accepted, this, &QDialog::accept); 0094 connect(m_fileWidget->cancelButton(), &QAbstractButton::clicked, this, &QDialog::reject); 0095 connect(m_fileWidget->dirOperator(), &KDirOperator::urlEntered, this, &KDEPlatformFileDialogBase::directoryEntered); 0096 } 0097 0098 QUrl KDEPlatformFileDialog::directory() 0099 { 0100 return m_fileWidget->baseUrl(); 0101 } 0102 0103 QList<QUrl> KDEPlatformFileDialog::selectedFiles() 0104 { 0105 return m_fileWidget->selectedUrls(); 0106 } 0107 0108 void KDEPlatformFileDialog::selectFile(const QUrl &filename) 0109 { 0110 const QUrl dirUrl = filename.adjusted(QUrl::RemoveFilename); 0111 m_fileWidget->setUrl(dirUrl); 0112 m_fileWidget->setSelectedUrl(filename); 0113 } 0114 0115 void KDEPlatformFileDialog::setViewMode(QFileDialogOptions::ViewMode view) 0116 { 0117 switch (view) { 0118 case QFileDialogOptions::ViewMode::Detail: 0119 m_fileWidget->setViewMode(KFile::FileView::Detail); 0120 break; 0121 case QFileDialogOptions::ViewMode::List: 0122 m_fileWidget->setViewMode(KFile::FileView::Simple); 0123 break; 0124 default: 0125 m_fileWidget->setViewMode(KFile::FileView::Default); 0126 break; 0127 } 0128 } 0129 0130 void KDEPlatformFileDialog::setFileMode(QFileDialogOptions::FileMode mode) 0131 { 0132 switch (mode) { 0133 case QFileDialogOptions::FileMode::AnyFile: 0134 m_fileWidget->setMode(KFile::File); 0135 break; 0136 case QFileDialogOptions::FileMode::ExistingFile: 0137 m_fileWidget->setMode(KFile::Mode::File | KFile::Mode::ExistingOnly); 0138 break; 0139 case QFileDialogOptions::FileMode::Directory: 0140 m_fileWidget->setMode(KFile::Mode::Directory | KFile::Mode::ExistingOnly); 0141 break; 0142 case QFileDialogOptions::FileMode::ExistingFiles: 0143 m_fileWidget->setMode(KFile::Mode::Files | KFile::Mode::ExistingOnly); 0144 break; 0145 default: 0146 m_fileWidget->setMode(KFile::File); 0147 break; 0148 } 0149 } 0150 0151 void KDEPlatformFileDialog::setCustomLabel(QFileDialogOptions::DialogLabel label, const QString &text) 0152 { 0153 if (label == QFileDialogOptions::Accept) { // OK button 0154 m_fileWidget->okButton()->setText(text); 0155 } else if (label == QFileDialogOptions::Reject) { // Cancel button 0156 m_fileWidget->cancelButton()->setText(text); 0157 } else if (label == QFileDialogOptions::LookIn) { // Location label 0158 m_fileWidget->setLocationLabel(text); 0159 } 0160 } 0161 0162 QString KDEPlatformFileDialog::selectedMimeTypeFilter() 0163 { 0164 if (m_fileWidget->filterWidget()->isMimeFilter()) { 0165 const auto mimeTypeFromFilter = QMimeDatabase().mimeTypeForName(m_fileWidget->filterWidget()->currentFilter()); 0166 // If one does not call selectMimeTypeFilter(), KFileFilterCombo::currentFilter() returns invalid mimeTypes, 0167 // such as "application/json application/zip". 0168 if (mimeTypeFromFilter.isValid()) { 0169 return mimeTypeFromFilter.name(); 0170 } 0171 } 0172 0173 if (selectedFiles().isEmpty()) { 0174 return QString(); 0175 } 0176 0177 // Works for both KFile::File and KFile::Files modes. 0178 return QMimeDatabase().mimeTypeForUrl(selectedFiles().at(0)).name(); 0179 } 0180 0181 QString KDEPlatformFileDialog::selectedNameFilter() 0182 { 0183 return m_fileWidget->filterWidget()->currentFilter(); 0184 } 0185 0186 QString KDEPlatformFileDialog::currentFilterText() 0187 { 0188 return m_fileWidget->filterWidget()->currentText(); 0189 } 0190 0191 void KDEPlatformFileDialog::selectMimeTypeFilter(const QString &filter) 0192 { 0193 m_fileWidget->filterWidget()->setCurrentFilter(filter); 0194 } 0195 0196 void KDEPlatformFileDialog::selectNameFilter(const QString &filter) 0197 { 0198 m_fileWidget->filterWidget()->setCurrentFilter(filter); 0199 } 0200 0201 void KDEPlatformFileDialog::setDirectory(const QUrl &directory) 0202 { 0203 if (!directory.isLocalFile()) { 0204 // Short-circuit: Avoid stat if the effective URL hasn't changed 0205 if (directory == m_fileWidget->baseUrl()) { 0206 return; 0207 } 0208 0209 // Qt can not determine if the remote URL points to a file or a 0210 // directory, that is why options()->initialDirectory() always returns 0211 // the full URL. 0212 KIO::StatJob *job = KIO::stat(directory); 0213 KJobWidgets::setWindow(job, this); 0214 if (job->exec()) { 0215 KIO::UDSEntry entry = job->statResult(); 0216 if (!entry.isDir()) { 0217 // this is probably a file remove the file part 0218 m_fileWidget->setUrl(directory.adjusted(QUrl::RemoveFilename)); 0219 m_fileWidget->setSelectedUrl(directory); 0220 } else { 0221 m_fileWidget->setUrl(directory); 0222 } 0223 } 0224 } else { 0225 m_fileWidget->setUrl(directory); 0226 } 0227 } 0228 0229 bool KDEPlatformFileDialogHelper::isSupportedUrl(const QUrl &url) const 0230 { 0231 return KProtocolInfo::protocols().contains(url.scheme()); 0232 } 0233 0234 //////////////////////////////////////////////// 0235 0236 KDEPlatformFileDialogHelper::KDEPlatformFileDialogHelper() 0237 : QPlatformFileDialogHelper() 0238 , m_dialog(new KDEPlatformFileDialog) 0239 { 0240 connect(m_dialog, &KDEPlatformFileDialogBase::closed, this, &KDEPlatformFileDialogHelper::saveSize); 0241 connect(m_dialog, &QDialog::finished, this, &KDEPlatformFileDialogHelper::saveSize); 0242 connect(m_dialog, &KDEPlatformFileDialogBase::currentChanged, this, &QPlatformFileDialogHelper::currentChanged); 0243 connect(m_dialog, &KDEPlatformFileDialogBase::directoryEntered, this, &QPlatformFileDialogHelper::directoryEntered); 0244 connect(m_dialog, &KDEPlatformFileDialogBase::fileSelected, this, &QPlatformFileDialogHelper::fileSelected); 0245 connect(m_dialog, &KDEPlatformFileDialogBase::filesSelected, this, &QPlatformFileDialogHelper::filesSelected); 0246 connect(m_dialog, &KDEPlatformFileDialogBase::filterSelected, this, &QPlatformFileDialogHelper::filterSelected); 0247 connect(m_dialog, &QDialog::accepted, this, &QPlatformDialogHelper::accept); 0248 connect(m_dialog, &QDialog::rejected, this, &QPlatformDialogHelper::reject); 0249 } 0250 0251 KDEPlatformFileDialogHelper::~KDEPlatformFileDialogHelper() 0252 { 0253 saveSize(); 0254 delete m_dialog; 0255 } 0256 0257 void KDEPlatformFileDialogHelper::initializeDialog() 0258 { 0259 m_dialogInitialized = true; 0260 if (options()->testOption(QFileDialogOptions::ShowDirsOnly)) { 0261 m_dialog->deleteLater(); 0262 KDirSelectDialog *dialog = new KDirSelectDialog(options()->initialDirectory()); 0263 m_dialog = dialog; 0264 connect(dialog, &QDialog::accepted, this, &QPlatformDialogHelper::accept); 0265 connect(dialog, &QDialog::rejected, this, &QPlatformDialogHelper::reject); 0266 if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) { // OK button 0267 dialog->setOkButtonText(options()->labelText(QFileDialogOptions::Accept)); 0268 } else if (options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) { // Cancel button 0269 dialog->setCancelButtonText(options()->labelText(QFileDialogOptions::Reject)); 0270 } else if (options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) { // Location label 0271 // Not implemented yet. 0272 } 0273 0274 if (!options()->windowTitle().isEmpty()) 0275 m_dialog->setWindowTitle(options()->windowTitle()); 0276 } else { 0277 // needed for accessing m_fileWidget 0278 KDEPlatformFileDialog *dialog = qobject_cast<KDEPlatformFileDialog *>(m_dialog); 0279 dialog->m_fileWidget->setOperationMode(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? KFileWidget::Opening : KFileWidget::Saving); 0280 if (options()->windowTitle().isEmpty()) { 0281 dialog->setWindowTitle(options()->acceptMode() == QFileDialogOptions::AcceptOpen ? i18nc("@title:window", "Open File") 0282 : i18nc("@title:window", "Save File")); 0283 } else { 0284 dialog->setWindowTitle(options()->windowTitle()); 0285 } 0286 if (!m_directorySet) { 0287 setDirectory(options()->initialDirectory()); 0288 } 0289 // dialog->setViewMode(options()->viewMode()); // don't override our options, fixes remembering the chosen view mode and sizes! 0290 dialog->setFileMode(options()->fileMode()); 0291 0292 // custom labels 0293 if (options()->isLabelExplicitlySet(QFileDialogOptions::Accept)) { // OK button 0294 dialog->setCustomLabel(QFileDialogOptions::Accept, options()->labelText(QFileDialogOptions::Accept)); 0295 } else if (options()->isLabelExplicitlySet(QFileDialogOptions::Reject)) { // Cancel button 0296 dialog->setCustomLabel(QFileDialogOptions::Reject, options()->labelText(QFileDialogOptions::Reject)); 0297 } else if (options()->isLabelExplicitlySet(QFileDialogOptions::LookIn)) { // Location label 0298 dialog->setCustomLabel(QFileDialogOptions::LookIn, options()->labelText(QFileDialogOptions::LookIn)); 0299 } 0300 0301 const QStringList mimeFilters = options()->mimeTypeFilters(); 0302 const QStringList nameFilters = options()->nameFilters(); 0303 if (!mimeFilters.isEmpty()) { 0304 QString defaultMimeFilter; 0305 if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { 0306 defaultMimeFilter = options()->initiallySelectedMimeTypeFilter(); 0307 if (defaultMimeFilter.isEmpty()) { 0308 defaultMimeFilter = mimeFilters.at(0); 0309 } 0310 } 0311 dialog->m_fileWidget->setMimeFilter(mimeFilters, defaultMimeFilter); 0312 0313 if (mimeFilters.contains(QStringLiteral("inode/directory"))) 0314 dialog->m_fileWidget->setMode(dialog->m_fileWidget->mode() | KFile::Directory); 0315 } else if (!nameFilters.isEmpty()) { 0316 dialog->m_fileWidget->setFilter(qt2KdeFilter(nameFilters)); 0317 } 0318 0319 if (!options()->initiallySelectedMimeTypeFilter().isEmpty()) { 0320 selectMimeTypeFilter(options()->initiallySelectedMimeTypeFilter()); 0321 } else if (!options()->initiallySelectedNameFilter().isEmpty()) { 0322 selectNameFilter(options()->initiallySelectedNameFilter()); 0323 } 0324 0325 // overwrite option 0326 if (options()->testOption(QFileDialogOptions::FileDialogOption::DontConfirmOverwrite)) { 0327 dialog->m_fileWidget->setConfirmOverwrite(false); 0328 } else if (options()->acceptMode() == QFileDialogOptions::AcceptSave) { 0329 dialog->m_fileWidget->setConfirmOverwrite(true); 0330 } 0331 0332 const QStringList schemes = options()->supportedSchemes(); 0333 dialog->m_fileWidget->setSupportedSchemes(schemes); 0334 } 0335 } 0336 0337 void KDEPlatformFileDialogHelper::exec() 0338 { 0339 restoreSize(); 0340 m_dialog->exec(); 0341 } 0342 0343 void KDEPlatformFileDialogHelper::hide() 0344 { 0345 m_dialog->hide(); 0346 } 0347 0348 void KDEPlatformFileDialogHelper::saveSize() 0349 { 0350 KSharedConfig::Ptr conf = KSharedConfig::openConfig(); 0351 KConfigGroup group = conf->group("FileDialogSize"); 0352 KWindowConfig::saveWindowSize(m_dialog->windowHandle(), group); 0353 } 0354 0355 void KDEPlatformFileDialogHelper::restoreSize() 0356 { 0357 m_dialog->winId(); // ensure there's a window created 0358 KSharedConfig::Ptr conf = KSharedConfig::openConfig(); 0359 0360 // see the note below 0361 m_dialog->windowHandle()->resize(m_dialog->sizeHint()); 0362 0363 KWindowConfig::restoreWindowSize(m_dialog->windowHandle(), conf->group("FileDialogSize")); 0364 // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform 0365 // window was created -> QTBUG-40584. We therefore copy the size here. 0366 // TODO: remove once this was resolved in QWidget QPA 0367 m_dialog->resize(m_dialog->windowHandle()->size()); 0368 } 0369 0370 bool KDEPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) 0371 { 0372 initializeDialog(); 0373 m_dialog->setWindowFlags(windowFlags); 0374 m_dialog->setWindowModality(windowModality); 0375 restoreSize(); 0376 m_dialog->windowHandle()->setTransientParent(parent); 0377 m_dialog->show(); 0378 return true; 0379 } 0380 0381 QVariant KDEPlatformFileDialogHelper::styleHint(StyleHint hint) const 0382 { 0383 if (hint == DialogIsQtWindow) { 0384 return true; 0385 } 0386 0387 return QPlatformDialogHelper::styleHint(hint); 0388 } 0389 0390 QList<QUrl> KDEPlatformFileDialogHelper::selectedFiles() const 0391 { 0392 return m_dialog->selectedFiles(); 0393 } 0394 0395 QString KDEPlatformFileDialogHelper::selectedMimeTypeFilter() const 0396 { 0397 return m_dialog->selectedMimeTypeFilter(); 0398 } 0399 0400 void KDEPlatformFileDialogHelper::selectMimeTypeFilter(const QString &filter) 0401 { 0402 m_dialog->selectMimeTypeFilter(filter); 0403 } 0404 0405 QString KDEPlatformFileDialogHelper::selectedNameFilter() const 0406 { 0407 return kde2QtFilter(options()->nameFilters(), m_dialog->selectedNameFilter(), m_dialog->currentFilterText()); 0408 } 0409 0410 QUrl KDEPlatformFileDialogHelper::directory() const 0411 { 0412 return m_dialog->directory(); 0413 } 0414 0415 void KDEPlatformFileDialogHelper::selectFile(const QUrl &filename) 0416 { 0417 m_dialog->selectFile(filename); 0418 m_fileSelected = true; 0419 } 0420 0421 void KDEPlatformFileDialogHelper::setDirectory(const QUrl &directory) 0422 { 0423 if (!directory.isEmpty()) { 0424 m_dialog->setDirectory(directory); 0425 m_directorySet = true; 0426 } 0427 } 0428 0429 void KDEPlatformFileDialogHelper::selectNameFilter(const QString &filter) 0430 { 0431 m_dialog->selectNameFilter(qt2KdeFilter(QStringList(filter))); 0432 } 0433 0434 void KDEPlatformFileDialogHelper::setFilter() 0435 { 0436 } 0437 0438 bool KDEPlatformFileDialogHelper::defaultNameFilterDisables() const 0439 { 0440 return false; 0441 }