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 }