File indexing completed on 2024-05-12 16:40:54
0001 /* This file is part of the KDE project 0002 Copyright (C) 2013 - 2014 Yue Liu <yue.liu@mail.com> 0003 Copyright (C) 2017 Jarosław Staniek <staniek@kde.org> 0004 0005 Based on Calligra libs' KoFileDialog 0006 0007 This library is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU Library General Public 0009 License as published by the Free Software Foundation; either 0010 version 2 of the License, or (at your option) any later version. 0011 0012 This library is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 Library General Public License for more details. 0016 0017 You should have received a copy of the GNU Library General Public License 0018 along with this library; see the file COPYING.LIB. If not, write to 0019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 * Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KexiFileDialog.h" 0024 #include <kexiutils/utils.h> 0025 0026 #include <QFileDialog> 0027 #include <QApplication> 0028 #include <QImageReader> 0029 #include <QClipboard> 0030 0031 #include <kconfiggroup.h> 0032 #include <ksharedconfig.h> 0033 #include <klocalizedstring.h> 0034 0035 #include <QUrl> 0036 #include <QMimeDatabase> 0037 #include <QMimeType> 0038 0039 class Q_DECL_HIDDEN KexiFileDialog::Private 0040 { 0041 public: 0042 Private(QWidget *parent_, 0043 KexiFileDialog::DialogType dialogType_, 0044 const QString caption_, 0045 const QString defaultDir_, 0046 const QString dialogName_) 0047 : parent(parent_) 0048 , type(dialogType_) 0049 , dialogName(dialogName_) 0050 , caption(caption_) 0051 , defaultDirectory(defaultDir_) 0052 , hideDetails(false) 0053 { 0054 // Force the native file dialogs on Windows. Except for KDE, the native file dialogs are only possible 0055 // using the static methods. The Qt documentation is wrong here, if it means what it says " By default, 0056 // the native file dialog is used unless you use a subclass of QFileDialog that contains the Q_OBJECT 0057 // macro." 0058 #if defined Q_OS_WIN || defined Q_OS_MACOS 0059 useStaticForNative = true; 0060 swapExtensionOrder = false; 0061 #else 0062 // Non-static KDE file is broken when called with QFileDialog::AcceptSave: 0063 // then the directory above defaultdir is opened, and defaultdir is given as the default file name... 0064 // 0065 // So: in X11, use static methods inside KDE, which give working native dialogs, but non-static outside 0066 // KDE, which gives working Qt dialogs. 0067 // 0068 // Only show the GTK dialog in Gnome, where people deserve it 0069 const QByteArray desktopSession = KexiUtils::detectedDesktopSession(); 0070 if (desktopSession == "KDE") { 0071 useStaticForNative = true; 0072 swapExtensionOrder = false; 0073 } else if (desktopSession == "GNOME") { 0074 useStaticForNative = true; 0075 QClipboard *cb = QApplication::clipboard(); 0076 cb->blockSignals(true); 0077 swapExtensionOrder = true; 0078 } else { 0079 useStaticForNative = false; 0080 swapExtensionOrder = false; 0081 } 0082 #endif 0083 } 0084 0085 ~Private() 0086 { 0087 const QByteArray desktopSession = KexiUtils::detectedDesktopSession(); 0088 if (desktopSession == "GNOME") { 0089 useStaticForNative = true; 0090 QClipboard *cb = QApplication::clipboard(); 0091 cb->blockSignals(false); 0092 } 0093 } 0094 0095 QWidget *parent; 0096 KexiFileDialog::DialogType type; 0097 QString dialogName; 0098 QString caption; 0099 QString defaultDirectory; 0100 QStringList filterList; 0101 QString defaultFilter; 0102 QScopedPointer<QFileDialog> fileDialog; 0103 QMimeType mimeType; 0104 bool useStaticForNative; 0105 bool hideDetails; 0106 bool swapExtensionOrder; 0107 }; 0108 0109 KexiFileDialog::KexiFileDialog(QWidget *parent, 0110 KexiFileDialog::DialogType type, 0111 const QString &dialogName) 0112 : d(new Private(parent, type, "", getUsedDir(dialogName), dialogName)) 0113 { 0114 } 0115 0116 KexiFileDialog::~KexiFileDialog() 0117 { 0118 delete d; 0119 } 0120 0121 void KexiFileDialog::setCaption(const QString &caption) 0122 { 0123 d->caption = caption; 0124 } 0125 0126 void KexiFileDialog::setDefaultDir(const QString &defaultDir, bool override) 0127 { 0128 if (override || d->defaultDirectory.isEmpty() || !QFile(d->defaultDirectory).exists()) { 0129 QFileInfo f(defaultDir); 0130 d->defaultDirectory = f.absoluteFilePath(); 0131 } 0132 } 0133 0134 void KexiFileDialog::setOverrideDir(const QString &overrideDir) 0135 { 0136 d->defaultDirectory = overrideDir; 0137 } 0138 0139 void KexiFileDialog::setImageFilters() 0140 { 0141 QStringList imageMimeTypes; 0142 foreach(const QByteArray &mimeType, QImageReader::supportedMimeTypes()) { 0143 imageMimeTypes << QLatin1String(mimeType); 0144 } 0145 setMimeTypeFilters(imageMimeTypes); 0146 } 0147 0148 void KexiFileDialog::setNameFilter(const QString &filter) 0149 { 0150 d->filterList.clear(); 0151 if (d->type == KexiFileDialog::SaveFile) { 0152 QStringList mimeList; 0153 d->filterList << splitNameFilter(filter, &mimeList); 0154 d->defaultFilter = d->filterList.first(); 0155 } 0156 else { 0157 d->filterList << filter; 0158 } 0159 } 0160 0161 void KexiFileDialog::setNameFilters(const QStringList &filterList, 0162 QString defaultFilter) 0163 { 0164 d->filterList.clear(); 0165 0166 if (d->type == KexiFileDialog::SaveFile) { 0167 QStringList mimeList; 0168 foreach(const QString &filter, filterList) { 0169 d->filterList << splitNameFilter(filter, &mimeList); 0170 } 0171 0172 if (!defaultFilter.isEmpty()) { 0173 mimeList.clear(); 0174 QStringList defaultFilters = splitNameFilter(defaultFilter, &mimeList); 0175 if (defaultFilters.size() > 0) { 0176 defaultFilter = defaultFilters.first(); 0177 } 0178 } 0179 } 0180 else { 0181 d->filterList = filterList; 0182 } 0183 d->defaultFilter = defaultFilter; 0184 0185 } 0186 0187 void KexiFileDialog::setMimeTypeFilters(const QStringList &filterList, 0188 QString defaultFilter) 0189 { 0190 d->filterList = getFilterStringListFromMime(filterList, true); 0191 0192 if (!defaultFilter.isEmpty()) { 0193 QStringList defaultFilters = getFilterStringListFromMime(QStringList() << defaultFilter, false); 0194 if (defaultFilters.size() > 0) { 0195 defaultFilter = defaultFilters.first(); 0196 } 0197 } 0198 d->defaultFilter = defaultFilter; 0199 } 0200 0201 void KexiFileDialog::setHideNameFilterDetailsOption() 0202 { 0203 d->hideDetails = true; 0204 } 0205 0206 QStringList KexiFileDialog::nameFilters() const 0207 { 0208 return d->filterList; 0209 } 0210 0211 QString KexiFileDialog::selectedNameFilter() const 0212 { 0213 if (!d->useStaticForNative) { 0214 return d->fileDialog->selectedNameFilter(); 0215 } 0216 else { 0217 return d->defaultFilter; 0218 } 0219 } 0220 0221 QString KexiFileDialog::selectedMimeType() const 0222 { 0223 if (d->mimeType.isValid()) { 0224 return d->mimeType.name(); 0225 } 0226 else { 0227 return ""; 0228 } 0229 } 0230 0231 void KexiFileDialog::createFileDialog() 0232 { 0233 d->fileDialog.reset(new QFileDialog(d->parent, d->caption, d->defaultDirectory)); 0234 0235 if (d->type == SaveFile) { 0236 d->fileDialog->setAcceptMode(QFileDialog::AcceptSave); 0237 d->fileDialog->setFileMode(QFileDialog::AnyFile); 0238 } 0239 else { // open / import 0240 0241 d->fileDialog->setAcceptMode(QFileDialog::AcceptOpen); 0242 0243 if (d->type == ImportDirectory 0244 || d->type == OpenDirectory) 0245 { 0246 d->fileDialog->setFileMode(QFileDialog::Directory); 0247 d->fileDialog->setOption(QFileDialog::ShowDirsOnly, true); 0248 } 0249 else { // open / import file(s) 0250 if (d->type == OpenFile 0251 || d->type == ImportFile) 0252 { 0253 d->fileDialog->setFileMode(QFileDialog::ExistingFile); 0254 } 0255 else { // files 0256 d->fileDialog->setFileMode(QFileDialog::ExistingFiles); 0257 } 0258 } 0259 } 0260 0261 d->fileDialog->setNameFilters(d->filterList); 0262 if (!d->defaultFilter.isEmpty()) { 0263 d->fileDialog->selectNameFilter(d->defaultFilter); 0264 } 0265 0266 if (d->type == ImportDirectory || 0267 d->type == ImportFile || d->type == ImportFiles || 0268 d->type == SaveFile) { 0269 d->fileDialog->setWindowModality(Qt::WindowModal); 0270 } 0271 0272 if (d->hideDetails) { 0273 d->fileDialog->setOption(QFileDialog::HideNameFilterDetails); 0274 } 0275 0276 connect(d->fileDialog.data(), SIGNAL(filterSelected(QString)), this, SLOT(filterSelected(QString))); 0277 } 0278 0279 QString KexiFileDialog::fileName() 0280 { 0281 QString url; 0282 if (!d->useStaticForNative) { 0283 0284 if (!d->fileDialog) { 0285 createFileDialog(); 0286 } 0287 0288 if (d->fileDialog->exec() == QDialog::Accepted) { 0289 url = d->fileDialog->selectedFiles().first(); 0290 } 0291 } 0292 else { 0293 switch (d->type) { 0294 case OpenFile: 0295 { 0296 url = QFileDialog::getOpenFileName(d->parent, 0297 d->caption, 0298 d->defaultDirectory, 0299 d->filterList.join(";;"), 0300 &d->defaultFilter); 0301 break; 0302 } 0303 case OpenDirectory: 0304 { 0305 url = QFileDialog::getExistingDirectory(d->parent, 0306 d->caption, 0307 d->defaultDirectory, 0308 QFileDialog::ShowDirsOnly); 0309 break; 0310 } 0311 case ImportFile: 0312 { 0313 url = QFileDialog::getOpenFileName(d->parent, 0314 d->caption, 0315 d->defaultDirectory, 0316 d->filterList.join(";;"), 0317 &d->defaultFilter); 0318 break; 0319 } 0320 case ImportDirectory: 0321 { 0322 url = QFileDialog::getExistingDirectory(d->parent, 0323 d->caption, 0324 d->defaultDirectory, 0325 QFileDialog::ShowDirsOnly); 0326 break; 0327 } 0328 case SaveFile: 0329 { 0330 url = QFileDialog::getSaveFileName(d->parent, 0331 d->caption, 0332 d->defaultDirectory, 0333 d->filterList.join(";;"), 0334 &d->defaultFilter); 0335 break; 0336 } 0337 default: 0338 ; 0339 } 0340 } 0341 0342 if (!url.isEmpty()) { 0343 0344 if (d->type == SaveFile && QFileInfo(url).suffix().isEmpty()) { 0345 int start = d->defaultFilter.lastIndexOf("*.") + 1; 0346 int end = d->defaultFilter.lastIndexOf(" )"); 0347 int n = end - start; 0348 QString extension = d->defaultFilter.mid(start, n); 0349 url.append(extension); 0350 } 0351 0352 QMimeDatabase db; 0353 d->mimeType = db.mimeTypeForFile(url); 0354 saveUsedDir(url, d->dialogName); 0355 } 0356 return url; 0357 } 0358 0359 QStringList KexiFileDialog::fileNames() 0360 { 0361 QStringList urls; 0362 0363 if (!d->useStaticForNative) { 0364 if (!d->fileDialog) { 0365 createFileDialog(); 0366 } 0367 if (d->fileDialog->exec() == QDialog::Accepted) { 0368 urls = d->fileDialog->selectedFiles(); 0369 } 0370 } 0371 else { 0372 switch (d->type) { 0373 case OpenFiles: 0374 case ImportFiles: 0375 { 0376 urls = QFileDialog::getOpenFileNames(d->parent, 0377 d->caption, 0378 d->defaultDirectory, 0379 d->filterList.join(";;"), 0380 &d->defaultFilter); 0381 break; 0382 } 0383 default: 0384 ; 0385 } 0386 } 0387 if (urls.size() > 0) { 0388 saveUsedDir(urls.first(), d->dialogName); 0389 } 0390 return urls; 0391 } 0392 0393 void KexiFileDialog::filterSelected(const QString &filter) 0394 { 0395 // "Windows BMP image ( *.bmp )"; 0396 int start = filter.lastIndexOf("*.") + 2; 0397 int end = filter.lastIndexOf(" )"); 0398 int n = end - start; 0399 QString extension = filter.mid(start, n); 0400 d->defaultFilter = filter; 0401 d->fileDialog->setDefaultSuffix(extension); 0402 } 0403 0404 QStringList KexiFileDialog::splitNameFilter(const QString &nameFilter, QStringList *mimeList) 0405 { 0406 Q_ASSERT(mimeList); 0407 0408 QStringList filters; 0409 QString description; 0410 0411 if (nameFilter.contains("(")) { 0412 description = nameFilter.left(nameFilter.indexOf("(") -1).trimmed(); 0413 } 0414 0415 QStringList entries = nameFilter.mid(nameFilter.indexOf("(") + 1).split(" ",QString::SkipEmptyParts ); 0416 0417 foreach(QString entry, entries) { 0418 0419 entry = entry.remove("*"); 0420 entry = entry.remove(")"); 0421 0422 QMimeDatabase db; 0423 QMimeType mime = db.mimeTypeForName("bla" + entry); 0424 if (mime.name() != "application/octet-stream") { 0425 if (!mimeList->contains(mime.name())) { 0426 mimeList->append(mime.name()); 0427 filters.append(mime.comment() + " ( *" + entry + " )"); 0428 } 0429 } 0430 else { 0431 filters.append(entry.remove(".").toUpper() + " " + description + " ( *." + entry + " )"); 0432 } 0433 } 0434 0435 return filters; 0436 } 0437 0438 const QStringList KexiFileDialog::getFilterStringListFromMime(const QStringList &mimeList, 0439 bool withAllSupportedEntry) 0440 { 0441 QStringList mimeSeen; 0442 0443 QStringList ret; 0444 if (withAllSupportedEntry) { 0445 ret << QString(); 0446 } 0447 0448 for (QStringList::ConstIterator 0449 it = mimeList.begin(); it != mimeList.end(); ++it) { 0450 QMimeDatabase db; 0451 QMimeType mimeType = db.mimeTypeForName(*it); 0452 if (!mimeType.isValid()) { 0453 continue; 0454 } 0455 if (!mimeSeen.contains(mimeType.name())) { 0456 QString oneFilter; 0457 QStringList patterns = mimeType.globPatterns(); 0458 QStringList::ConstIterator jt; 0459 for (jt = patterns.constBegin(); jt != patterns.constEnd(); ++jt) { 0460 if (d->swapExtensionOrder) { 0461 oneFilter.prepend(*jt + " "); 0462 if (withAllSupportedEntry) { 0463 ret[0].prepend(*jt + " "); 0464 } 0465 } 0466 else { 0467 oneFilter.append(*jt + " "); 0468 if (withAllSupportedEntry) { 0469 ret[0].append(*jt + " "); 0470 } 0471 } 0472 } 0473 oneFilter = mimeType.comment() + " ( " + oneFilter + ")"; 0474 ret << oneFilter; 0475 mimeSeen << mimeType.name(); 0476 } 0477 } 0478 0479 if (withAllSupportedEntry) { 0480 ret[0] = i18n("All supported formats") + " ( " + ret[0] + (")"); 0481 } 0482 return ret; 0483 } 0484 0485 QString KexiFileDialog::getUsedDir(const QString &dialogName) 0486 { 0487 if (dialogName.isEmpty()) return ""; 0488 0489 KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); 0490 QString dir = group.readEntry(dialogName); 0491 0492 return dir; 0493 } 0494 0495 void KexiFileDialog::saveUsedDir(const QString &fileName, 0496 const QString &dialogName) 0497 { 0498 0499 if (dialogName.isEmpty()) return; 0500 0501 QFileInfo fileInfo(fileName); 0502 KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); 0503 group.writeEntry(dialogName, fileInfo.absolutePath()); 0504 0505 }