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: