File indexing completed on 2024-11-24 04:42:25

0001 /*
0002  *  lib/file.cpp  -  functions to handle files
0003  *  Program:  kalarm
0004  *  SPDX-FileCopyrightText: 2005-2023 David Jarvie <djarvie@kde.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "file.h"
0010 
0011 #include "lib/autoqpointer.h"
0012 #include "lib/messagebox.h"
0013 
0014 #include <KLocalizedString>
0015 #include <KIO/StatJob>
0016 #include <KJobWidgets>
0017 #include <KFileItem>
0018 
0019 #include <QDir>
0020 #include <QRegularExpression>
0021 #include <QFileDialog>
0022 
0023 namespace File
0024 {
0025 
0026 /******************************************************************************
0027 * Check from its mime type whether a file appears to be a text or image file.
0028 * If a text file, its type is distinguished.
0029 * Reply = file type.
0030 */
0031 Type fileType(const QMimeType& mimetype)
0032 {
0033     if (mimetype.inherits(QStringLiteral("text/html")))
0034         return Type::TextFormatted;
0035     if (mimetype.inherits(QStringLiteral("application/x-executable")))
0036         return Type::TextApplication;
0037     if (mimetype.inherits(QStringLiteral("text/plain")))
0038         return Type::TextPlain;
0039     if (mimetype.name().startsWith(QLatin1String("image/")))
0040         return Type::Image;
0041     return Type::Unknown;
0042 }
0043 
0044 /******************************************************************************
0045 * Check that a file exists and is a plain readable file.
0046 * Updates 'filename' and 'url' even if an error occurs, since 'filename' may
0047 * be needed subsequently by showFileErrMessage().
0048 * 'filename' is in user input format and may be a local file path or URL.
0049 */
0050 Error checkFileExists(QString& filename, QUrl& url, QWidget* messageParent)
0051 {
0052     // Convert any relative file path to absolute
0053     // (using home directory as the default).
0054     // This also supports absolute paths and absolute urls.
0055     Error err = Error::None;
0056     url = QUrl::fromUserInput(filename, QDir::homePath(), QUrl::AssumeLocalFile);
0057     if (filename.isEmpty())
0058     {
0059         url = QUrl();
0060         err = Error::Blank;    // blank file name
0061     }
0062     else if (!url.isValid())
0063         err = Error::Nonexistent;
0064     else if (url.isLocalFile())
0065     {
0066         // It's a local file
0067         filename = url.toLocalFile();
0068         QFileInfo info(filename);
0069         if      (info.isDir())        err = Error::Directory;
0070         else if (!info.exists())      err = Error::Nonexistent;
0071         else if (!info.isReadable())  err = Error::Unreadable;
0072     }
0073     else
0074     {
0075         filename = url.toDisplayString();
0076         auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, KIO::StatDetail::StatDefaultDetails);
0077         KJobWidgets::setWindow(statJob, messageParent);
0078         if (!statJob->exec())
0079             err = Error::Nonexistent;
0080         else
0081         {
0082             KFileItem fi(statJob->statResult(), url);
0083             if (fi.isDir())             err = Error::Directory;
0084             else if (!fi.isReadable())  err = Error::Unreadable;
0085         }
0086     }
0087     return err;
0088 }
0089 
0090 /******************************************************************************
0091 * Display an error message appropriate to 'err'.
0092 * Display a Continue/Cancel error message if 'errmsgParent' non-null.
0093 * Reply = true to continue, false to cancel.
0094 */
0095 bool showFileErrMessage(const QString& filename, Error err, Error blankError, QWidget* errmsgParent)
0096 {
0097     if (err != Error::None)
0098     {
0099         // If file is a local file, remove "file://" from name
0100         const QString file = pathOrUrl(filename);
0101 
0102         QString errmsg;
0103         switch (err)
0104         {
0105             case Error::Blank:
0106                 if (blankError == Error::BlankDisplay)
0107                     errmsg = i18nc("@info", "Please select a file to display");
0108                 else if (blankError == Error::BlankPlay)
0109                     errmsg = i18nc("@info", "Please select a file to play");
0110                 else
0111                     qFatal("showFileErrMessage: Program error");
0112                 KAMessageBox::error(errmsgParent, errmsg);
0113                 return false;
0114             case Error::Directory:
0115                 KAMessageBox::error(errmsgParent, xi18nc("@info", "<filename>%1</filename> is a folder", file));
0116                 return false;
0117             case Error::Nonexistent:   errmsg = xi18nc("@info", "<filename>%1</filename> not found", file);  break;
0118             case Error::Unreadable:    errmsg = xi18nc("@info", "<filename>%1</filename> is not readable", file);  break;
0119             case Error::NotTextImage:  errmsg = xi18nc("@info", "<filename>%1</filename> appears not to be a text or image file", file);  break;
0120             default:
0121                 break;
0122         }
0123         if (KAMessageBox::warningContinueCancel(errmsgParent, errmsg)
0124             == KMessageBox::Cancel)
0125             return false;
0126     }
0127     return true;
0128 }
0129 
0130 /******************************************************************************
0131 * If a url string is a local file, strip off the 'file:/' prefix.
0132 */
0133 QString pathOrUrl(const QString& url)
0134 {
0135     static const QRegularExpression re(QStringLiteral("^file:/+"));
0136     const QRegularExpressionMatch match = re.match(url);
0137     return match.hasMatch() ? url.mid(match.capturedEnd(0) - 1) : url;
0138 }
0139 
0140 /******************************************************************************
0141 * Display a modal dialog to choose an existing file, initially highlighting
0142 * any specified file.
0143 * @param file        Updated with the file which was selected, or empty if no file
0144 *                    was selected.
0145 * @param initialFile The file to initially highlight - must be a full path name or URL.
0146 * @param defaultDir The directory to start in if @p initialFile is empty. If empty,
0147 *                   the user's home directory will be used. Updated to the
0148 *                   directory containing the selected file, if a file is chosen.
0149 * @param existing true to return only existing files, false to allow new ones.
0150 * Reply = true if 'file' value can be used.
0151 *       = false if the dialogue was deleted while visible (indicating that
0152 *         the parent widget was probably also deleted).
0153 */
0154 bool browseFile(QString& file, const QString& caption, QString& defaultDir,
0155                 const QString& initialFile, bool existing, QWidget* parent)
0156 {
0157     return browseFile(file, caption, defaultDir, {}, initialFile, existing, parent);
0158 }
0159 
0160 bool browseFile(QString& file, const QString& caption, QString& defaultDir,
0161                 const QString& fileNameFilter, const QString& initialFile,
0162                 bool existing, QWidget* parent)
0163 {
0164     file.clear();
0165     static const QRegularExpression re(QStringLiteral("/[^/]*$"));
0166     const QString initialDir = !initialFile.isEmpty() ? pathOrUrl(initialFile).remove(re)
0167                              : !defaultDir.isEmpty()  ? defaultDir
0168                              :                          QDir::homePath();
0169     // Use AutoQPointer to guard against crash on application exit while
0170     // the dialogue is still open. It prevents double deletion (both on
0171     // deletion of parent, and on return from this function).
0172     AutoQPointer<QFileDialog> fileDlg = new QFileDialog(parent, caption, initialDir);
0173     fileDlg->setAcceptMode(existing ? QFileDialog::AcceptOpen : QFileDialog::AcceptSave);
0174     fileDlg->setFileMode(existing ? QFileDialog::ExistingFile : QFileDialog::AnyFile);
0175     QStringList nameFilters;
0176     if (!fileNameFilter.isEmpty())
0177         nameFilters << fileNameFilter;
0178     nameFilters << QStringLiteral("%1 (*)").arg(i18nc("@item:inlistbox File type", "All files"));
0179     fileDlg->setNameFilters(nameFilters);
0180     if (!initialFile.isEmpty())
0181         fileDlg->selectFile(initialFile);
0182     if (fileDlg->exec() != QDialog::Accepted)
0183         return static_cast<bool>(fileDlg);   // return false if dialog was deleted
0184     const QList<QUrl> urls = fileDlg->selectedUrls();
0185     if (urls.isEmpty())
0186         return true;
0187     const QUrl& url = urls[0];
0188     defaultDir = url.isLocalFile() ? KIO::upUrl(url).toLocalFile() : url.adjusted(QUrl::RemoveFilename).path();
0189     bool localOnly = true;
0190     file = localOnly ? url.toDisplayString(QUrl::PreferLocalFile) : url.toDisplayString();
0191     return true;
0192 }
0193 
0194 } // namespace File
0195 
0196 // vim: et sw=4: