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