File indexing completed on 2024-05-12 16:40:57

0001 /* This file is part of the KDE project
0002    Copyright (C) 2003-2014 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KexiStartupFileHandler.h"
0021 #include <kexi_global.h>
0022 #include <core/kexi.h>
0023 #include <KexiFileFilters.h>
0024 #include <kexiutils/utils.h>
0025 #include <kexiutils/KexiContextMessage.h>
0026 
0027 #include <KDbDriver>
0028 #include <KDbUtils>
0029 
0030 #include <KMessageBox>
0031 #include <KFileWidget>
0032 #include <KFile>
0033 #include <KUrlComboBox>
0034 #include <KActionCollection>
0035 //removed in KEXI3 #include <KFileDialog>
0036 #define KIOWIDGETS_NO_DEPRECATED
0037 #include <KUrlRequester>
0038 #include <KLocalizedString>
0039 
0040 #include <QDebug>
0041 #include <QEvent>
0042 #include <QAction>
0043 #include <QEventLoop>
0044 #include <QMimeDatabase>
0045 #include <QMimeType>
0046 #include <QStandardPaths>
0047 #include <QUrl>
0048 
0049 //! @internal
0050 class Q_DECL_HIDDEN KexiStartupFileHandler::Private
0051 {
0052 public:
0053     Private()
0054             : confirmOverwrites(true)
0055             //, filtersUpdated(false)
0056     {
0057     }
0058     ~Private() {
0059         if (messageWidgetLoop) {
0060             messageWidgetLoop->exit(0);
0061             messageWidgetLoop->processEvents(); // for safe exit
0062             messageWidgetLoop->exit(0);
0063             delete messageWidgetLoop;
0064         }
0065     }
0066 
0067     void setUrl(const QUrl &url)
0068     {
0069         if (requester) {
0070             requester->setUrl(url);
0071         }
0072 /*removed in KEXI3
0073         else {
0074             dialog->setUrl(url);
0075         }*/
0076     }
0077 
0078 // removed in KEXI3    QPointer<KFileDialog> dialog;
0079     QPointer<KUrlRequester> requester;
0080     QString lastFileName;
0081     KexiFileFilters::Mode mode;
0082     QSet<QString> additionalMimeTypes, excludedMimeTypes;
0083     QString defaultExtension;
0084     bool confirmOverwrites;
0085     QString recentDirClass;
0086 
0087     QPointer<QEventLoop> messageWidgetLoop;
0088     //! Used in KexiStartupFileHandler::askForOverwriting() to remember path that
0089     //! was recently accepted for overwrite by the user.
0090     QString recentFilePathConfirmed;
0091 };
0092 
0093 //------------------
0094 
0095 /* removed in KEXI3
0096 KexiStartupFileHandler::KexiStartupFileHandler(
0097     const QUrl &startDirOrVariable, Mode mode, KFileDialog *dialog)
0098     :  QObject(dialog->parent())
0099     , d(new Private)
0100 {
0101     d->dialog = dialog;
0102     init(startDirOrVariable, mode);
0103 }*/
0104 
0105 KexiStartupFileHandler::KexiStartupFileHandler(
0106     const QUrl &startDirOrVariable, KexiFileFilters::Mode mode, KUrlRequester *requester)
0107     :  QObject(requester->parent())
0108     , d(new Private)
0109 {
0110     d->requester = requester;
0111 //removed in KEXI3    d->dialog = d->requester->fileDialog();
0112     init(startDirOrVariable, mode);
0113 }
0114 
0115 void KexiStartupFileHandler::init(const QUrl &startDirOrVariable, KexiFileFilters::Mode mode)
0116 {
0117 //removed in KEXI3    connect(d->dialog, SIGNAL(accepted()), this, SLOT(slotAccepted()));
0118     QUrl url;
0119     if (startDirOrVariable.scheme() == "kfiledialog") {
0120         url = KFileWidget::getStartUrl(startDirOrVariable, d->recentDirClass);
0121     }
0122     else {
0123         url = startDirOrVariable;
0124     }
0125     if (url.toLocalFile().isEmpty() || !QDir(url.toLocalFile()).exists()) {
0126         url = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
0127         QDir docDir(url.toLocalFile());
0128         if (!docDir.exists()) { // create if missing
0129             (void)docDir.mkpath(QString());
0130         }
0131     }
0132     d->setUrl(url);
0133     setMode(mode);
0134 /*removed in KEXI3
0135     QAction *previewAction = d->dialog->actionCollection()->action("preview");
0136     if (previewAction)
0137         previewAction->setChecked(false);*/
0138 }
0139 
0140 KexiStartupFileHandler::~KexiStartupFileHandler()
0141 {
0142     saveRecentDir();
0143     delete d;
0144 }
0145 
0146 void KexiStartupFileHandler::saveRecentDir()
0147 {
0148     if (!d->recentDirClass.isEmpty()) {
0149         qDebug() << d->recentDirClass;
0150 
0151         QUrl dirUrl;
0152         if (d->requester)
0153             dirUrl = d->requester->url();
0154 //removed in KEXI3        else if (d->dialog)
0155 //removed in KEXI3            dirUrl = d->dialog->selectedUrl();
0156         qDebug() << dirUrl;
0157         if (dirUrl.isValid() && dirUrl.isLocalFile()) {
0158             dirUrl = dirUrl.adjusted(QUrl::RemoveFilename);
0159             dirUrl.setPath(dirUrl.path() + QString());
0160             qDebug() << "Added" << dirUrl.path() << "to recent dirs class" << d->recentDirClass;
0161             KexiUtils::addRecentDir(d->recentDirClass, dirUrl.path());
0162         }
0163     }
0164 }
0165 
0166 KexiFileFilters::Mode KexiStartupFileHandler::mode() const
0167 {
0168     return d->mode;
0169 }
0170 
0171 void KexiStartupFileHandler::setMode(KexiFileFilters::Mode mode)
0172 {
0173     //delayed
0174     d->mode = mode;
0175     updateFilters();
0176 }
0177 
0178 QStringList KexiStartupFileHandler::additionalMimeTypes() const
0179 {
0180     return d->additionalMimeTypes.toList();
0181 }
0182 
0183 void KexiStartupFileHandler::setAdditionalMimeTypes(const QStringList &mimeTypes)
0184 {
0185     //delayed
0186     d->additionalMimeTypes = mimeTypes.toSet();
0187     updateFilters();
0188 }
0189 
0190 QStringList KexiStartupFileHandler::excludedMimeTypes() const
0191 {
0192     return d->excludedMimeTypes.toList();
0193 }
0194 
0195 void KexiStartupFileHandler::setExcludedMimeTypes(const QStringList &mimeTypes)
0196 {
0197     //delayed
0198     d->excludedMimeTypes.clear();
0199     //convert to lowercase
0200     for(const QString& mimeType : mimeTypes) {
0201         d->excludedMimeTypes.insert(mimeType.toLower());
0202     }
0203     updateFilters();
0204 }
0205 
0206 void KexiStartupFileHandler::updateFilters()
0207 {
0208     d->lastFileName.clear();
0209 //removed in KEXI3    d->dialog->clearFilter();
0210 
0211     QString filter;
0212     QMimeDatabase db;
0213     QMimeType mime;
0214     QStringList allfilters;
0215 
0216     KexiFileFiltersFormat format;
0217     format.type = KexiFileFiltersFormat::Type::KDE;
0218     format.addAllFiles = true;
0219     const QString separator(KexiFileFilters::separator(format));
0220     if (d->mode == KexiFileFilters::Opening || d->mode == KexiFileFilters::SavingFileBasedDB) {
0221         mime = db.mimeTypeForName(KDb::defaultFileBasedDriverMimeType());
0222         if (mime.isValid() && !d->excludedMimeTypes.contains(mime.name().toLower())) {
0223             if (!filter.isEmpty()) {
0224                 filter += separator;
0225             }
0226             filter += KexiFileFilters::toString(mime, format);
0227             allfilters += mime.globPatterns();
0228         }
0229     }
0230     if (d->mode == KexiFileFilters::Opening || d->mode == KexiFileFilters::SavingServerBasedDB) {
0231         mime = db.mimeTypeForName("application/x-kexiproject-shortcut");
0232         if (mime.isValid() && !d->excludedMimeTypes.contains(mime.name().toLower())) {
0233             if (!filter.isEmpty()) {
0234                 filter += separator;
0235             }
0236             filter += KexiFileFilters::toString(mime, format);
0237             allfilters += mime.globPatterns();
0238         }
0239     }
0240     if (d->mode == KexiFileFilters::Opening || d->mode == KexiFileFilters::SavingServerBasedDB) {
0241         mime = db.mimeTypeForName("application/x-kexi-connectiondata");
0242         if (mime.isValid() && !d->excludedMimeTypes.contains(mime.name().toLower())) {
0243             if (!filter.isEmpty()) {
0244                 filter += separator;
0245             }
0246             filter += KexiFileFilters::toString(mime, format);
0247             allfilters += mime.globPatterns();
0248         }
0249     }
0250 
0251 //! @todo hardcoded for MSA:
0252     if (d->mode == KexiFileFilters::Opening || d->mode == KexiFileFilters::CustomOpening) {
0253         mime = db.mimeTypeForName("application/vnd.ms-access");
0254         if (mime.isValid() && !d->excludedMimeTypes.contains(mime.name().toLower())) {
0255             if (!filter.isEmpty()) {
0256                 filter += separator;
0257             }
0258             filter += KexiFileFilters::toString(mime, format);
0259             allfilters += mime.globPatterns();
0260         }
0261     }
0262 
0263     foreach(const QString& mimeName, d->additionalMimeTypes) {
0264         if (mimeName == "all/allfiles") {
0265             continue;
0266         }
0267         if (d->excludedMimeTypes.contains(mimeName.toLower())) {
0268             continue;
0269         }
0270         if (!filter.isEmpty()) {
0271             filter += separator;
0272         }
0273         filter += KexiFileFilters::toString(mimeName, format);
0274         mime = db.mimeTypeForName(mimeName);
0275         allfilters += mime.globPatterns();
0276     }
0277 
0278     //remove duplicates made because upper- and lower-case extenstions are used:
0279     QStringList allfiltersUnique = allfilters.toSet().toList();
0280     allfiltersUnique.sort();
0281 
0282     if (allfiltersUnique.count() > 1) {//prepend "all supoported files" entry
0283         if (!filter.isEmpty()) {
0284             filter += separator;
0285         }
0286         filter.prepend(
0287             KexiFileFilters::toString(allfiltersUnique, xi18n("All Supported Files"), format));
0288     }
0289 
0290     d->requester->setFilter(filter);
0291 
0292     if (d->mode == KexiFileFilters::Opening || d->mode == KexiFileFilters::CustomOpening) {
0293         d->requester->setMode(KFile::ExistingOnly | KFile::LocalOnly | KFile::File);
0294 //removed in KEXI3        d->dialog->setOperationMode(KFileDialog::Opening);
0295     } else {
0296         d->requester->setMode(KFile::LocalOnly | KFile::File);
0297 //removed in KEXI3        d->dialog->setOperationMode(KFileDialog::Saving);
0298     }
0299 }
0300 
0301 //! @todo
0302 /*TODO
0303 QString KexiStartupFileDialog::selectedFile() const
0304 {
0305 #ifdef Q_OS_WIN
0306 // QString path = selectedFile();
0307   //js @todo
0308 // qDebug() << "selectedFile() == " << path << " '" << url().fileName() << "' " << m_lineEdit->text();
0309   QString path = dir()->absolutePath();
0310   if (!path.endsWith('/') && !path.endsWith("\\"))
0311     path.append("/");
0312   path += m_lineEdit->text();
0313 // QString path = QFileInfo(selectedFile()).dirPath(true) + "/" + m_lineEdit->text();
0314 #else
0315 // QString path = locationEdit->currentText().trimmed(); //url.path().trimmed(); that does not work, if the full path is not in the location edit !!!!!
0316   QString path( KFileWidget::selectedFile() );
0317   qDebug() << "prev selectedFile() == " << path;
0318   qDebug() << "locationEdit == " << locationEdit()->currentText().trimmed();
0319   //make sure user-entered path is acceped:
0320 //! @todo KEXI3 setSelection( locationEdit()->currentText().trimmed() );
0321 // path = KFileWidget::selectedFile();
0322   path = locationEdit()->currentText().trimmed();
0323   qDebug() << "selectedFile() == " << path;
0324 
0325 #endif
0326 
0327   if (!currentFilter().isEmpty()) {
0328     if (d->mode & SavingFileBasedDB) {
0329       const QStringList filters( currentFilter().split(' ') );
0330       qDebug()<< " filter == " << filters;
0331       QString ext( QFileInfo(path).suffix() );
0332       bool hasExtension = false;
0333       foreach (const QString& filter, filters) {
0334         const QString f( filter.trimmed() );
0335         hasExtension = !f.mid(2).isEmpty() && ext==f.mid(2);
0336         if (hasExtension)
0337           break;
0338       }
0339       if (!hasExtension) {
0340         //no extension: add one
0341         QString defaultExtension( d->defaultExtension );
0342         if (defaultExtension.isEmpty())
0343           defaultExtension = filters.first().trimmed().mid(2); //first one
0344         path += (QString(".")+defaultExtension);
0345         qDebug() << "KexiStartupFileDialog::checkURL(): append extension, " << path;
0346       }
0347     }
0348   }
0349   qDebug() << "KexiStartupFileDialog::currentFileName() == " << path;
0350   return path;
0351 }
0352 */
0353 
0354 bool KexiStartupFileHandler::checkSelectedUrl()
0355 {
0356     //qDebug() << "d->highlightedUrl: " << d->highlightedUrl;
0357 
0358     QUrl url;
0359     if (d->requester)
0360         url = d->requester->url();
0361 //removed in KEXI3    else
0362 //removed in KEXI3       url = d->dialog->selectedUrl();
0363     qDebug() << url;
0364 #if 0
0365     if (/*d->highlightedUrl.isEmpty() &&*/ !locationEdit()->lineEdit()->text().isEmpty()) {
0366         qDebug() << locationEdit()->lineEdit()->text();
0367         //qDebug() << locationEdit()->urls();
0368         qDebug() << baseUrl();
0369 
0370         d->highlightedUrl = baseUrl();
0371         const QString firstUrl(locationEdit()->lineEdit()->text());   // FIXME: find first...
0372         if (QDir::isAbsolutePath(firstUrl))
0373             d->highlightedUrl = QUrl::fromLocalFile(firstUrl);
0374         else
0375             d->highlightedUrl.addPath(firstUrl);
0376     }
0377 #endif
0378     //qDebug() << "d->highlightedUrl: " << d->highlightedUrl;
0379     if (!url.isValid() || QFileInfo(url.path()).isDir()) {
0380         KMessageBox::error(d->requester->parentWidget(), xi18n("Enter a filename."));
0381         return false;
0382     }
0383 
0384     if (!d->requester->filter().isEmpty()) {
0385         if (d->mode == KexiFileFilters::SavingFileBasedDB) {
0386             const QStringList filters( d->requester->filter().split('\n') );
0387             QString path = url.toLocalFile();
0388             qDebug()<< "filters:" << filters << "path:" << path;
0389             QString ext( QFileInfo(path).suffix() );
0390             bool hasExtension = false;
0391             for (const QString &filter : filters) {
0392                 QStringList filterPatterns = filter.split('|').first().split(' ');
0393                 for (const QString &filterPattern : filterPatterns) {
0394                     const QString f( filterPattern.trimmed() );
0395                     if (!f.midRef(2).isEmpty() && ext == f.midRef(2)) {
0396                         hasExtension = true;
0397                         break;
0398                     }
0399                 }
0400                 if (hasExtension) {
0401                     break;
0402                 }
0403             }
0404             if (!hasExtension) {
0405                 //no extension: add one
0406                 QString defaultExtension( d->defaultExtension );
0407                 if (defaultExtension.isEmpty()) {
0408                     defaultExtension = filters.first().trimmed().mid(2); //first one
0409                 }
0410                 path += (QLatin1String(".") + defaultExtension);
0411                 qDebug() << "appended extension, result:" << path;
0412                 url = QUrl(path);
0413                 d->setUrl(url);
0414             }
0415         }
0416     }
0417 
0418 // qDebug() << "KexiStartupFileDialog::checkURL() path: " << d->highlightedUrl;
0419 // qDebug() << "KexiStartupFileDialog::checkURL() fname: " << url.fileName();
0420 //! @todo if ( url.isLocalFile() ) {
0421     QFileInfo fi(url.toLocalFile());
0422     if (d->mode & KFile::ExistingOnly) {
0423         if (!fi.exists()) {
0424             KMessageBox::error(d->requester->parentWidget(),
0425                                xi18nc("@info", "The file <filename>%1</filename> does not exist.",
0426                                       QDir::toNativeSeparators(url.toLocalFile())));
0427             return false;
0428         } else if (mode() & KFile::File) {
0429             if (!fi.isFile()) {
0430                 KMessageBox::error(d->requester->parentWidget(),
0431                                    xi18nc("@info", "Enter a filename."));
0432                 return false;
0433             } else if (!fi.isReadable()) {
0434                 KMessageBox::error(d->requester->parentWidget(),
0435                                    xi18nc("@info", "The file <filename>%1</filename> is not readable.",
0436                                           QDir::toNativeSeparators(url.toLocalFile())));
0437                 return false;
0438             }
0439         }
0440     }
0441     else if (d->confirmOverwrites && !askForOverwriting(url.toLocalFile()))
0442     {
0443         return false;
0444     }
0445     return true;
0446 }
0447 
0448 void KexiStartupFileHandler::messageWidgetActionYesTriggered()
0449 {
0450     d->messageWidgetLoop->exit(1);
0451 }
0452 
0453 void KexiStartupFileHandler::messageWidgetActionNoTriggered()
0454 {
0455     d->messageWidgetLoop->exit(0);
0456 }
0457 
0458 void KexiStartupFileHandler::updateUrl(const QString &name)
0459 {
0460     QUrl url = d->requester->url();
0461     QString path = url.toLocalFile();
0462     if (!QFileInfo(path).isDir() && !path.endsWith('/')) {
0463         url = url.adjusted(QUrl::RemoveFilename);
0464         path = url.toLocalFile();
0465     }
0466     QString fn = KDbUtils::stringToFileName(name);
0467     if (!fn.isEmpty() && !fn.endsWith(".kexi"))
0468         fn += ".kexi";
0469     url.setPath(QDir(path).absoluteFilePath(fn));
0470     d->requester->setUrl(url);
0471 }
0472 
0473 bool KexiStartupFileHandler::askForOverwriting(const QString& filePath)
0474 {
0475     QFileInfo fi(filePath);
0476     if (d->recentFilePathConfirmed == filePath) {
0477         return true;
0478     }
0479     d->recentFilePathConfirmed.clear();
0480     if (!fi.exists())
0481         return true;
0482     KexiContextMessage message(
0483         xi18n("This file already exists. Do you want to overwrite it?"));
0484     QScopedPointer<QAction> messageWidgetActionYes(new QAction(xi18n("Overwrite"), 0));
0485     connect(messageWidgetActionYes.data(), SIGNAL(triggered()),
0486             this, SLOT(messageWidgetActionYesTriggered()));
0487     message.addAction(messageWidgetActionYes.data());
0488     QScopedPointer<QAction> messageWidgetActionNo(new QAction(KStandardGuiItem::no().text(), 0));
0489     connect(messageWidgetActionNo.data(), SIGNAL(triggered()),
0490             this, SLOT(messageWidgetActionNoTriggered()));
0491     message.addAction(messageWidgetActionNo.data());
0492     message.setDefaultAction(messageWidgetActionNo.data());
0493     emit askForOverwriting(message);
0494     if (!d->messageWidgetLoop) {
0495         d->messageWidgetLoop = new QEventLoop;
0496     }
0497     bool ok = d->messageWidgetLoop->exec();
0498     if (ok) {
0499         d->recentFilePathConfirmed = filePath;
0500     }
0501     return ok;
0502 }
0503 
0504 /*removed in KEXI3
0505 void KexiStartupFileHandler::setLocationText(const QString& fn)
0506 {
0507     d->dialog->locationEdit()->setUrl(QUrl(fn));
0508 }*/
0509 
0510 void KexiStartupFileHandler::setDefaultExtension(const QString& ext)
0511 {
0512     d->defaultExtension = ext;
0513 }
0514 
0515 void KexiStartupFileHandler::setConfirmOverwrites(bool set)
0516 {
0517     d->confirmOverwrites = set;
0518 }
0519 
0520 void KexiStartupFileHandler::slotAccepted()
0521 {
0522     checkSelectedUrl();
0523 }
0524