File indexing completed on 2024-04-14 04:36:11

0001 /* This file is part of the KDE project
0002    Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr>
0003    Copyright (C) 2004 Alexander Dymo <cloudtemple@mskat.net>
0004    Copyright (C) 2016-2018 Jarosław Staniek <staniek@kde.org>
0005    Copyright (C) 2018 Dmitry Baryshev <dmitrymq@gmail.com>
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 "KPropertyUrlEditor_p.h"
0024 #include "KProperty.h"
0025 #include "KPropertyComposedUrl.h"
0026 #include "KPropertyEditorItemEvent.h"
0027 #include "KPropertyEditorView.h"
0028 #include "KPropertyGenericSelectionEditor.h"
0029 #include "KPropertySet.h"
0030 #include "kproperty_debug.h"
0031 
0032 #include <QEvent>
0033 #include <QFileDialog>
0034 #include <QFileInfo>
0035 #include <QKeyEvent>
0036 #include <QLineEdit>
0037 #include <QLocale>
0038 
0039 KPropertyUrlEditorPrivate::KPropertyUrlEditorPrivate(KPropertyGenericSelectionEditor *editor,
0040                                                      const KProperty &property)
0041     : QObject()
0042     , fileMode(property.option("fileMode").toByteArray().toLower())
0043     , confirmOverwrites(property.option("confirmOverwrites", false).toBool())
0044     , propertyName(property.name())
0045     , m_editor(editor)
0046 {
0047     lineEdit = new QLineEdit;
0048     lineEdit->setClearButtonEnabled(true);
0049     lineEdit->installEventFilter(m_editor);
0050 
0051     isComposedUrl = property.type() == KProperty::ComposedUrl;
0052     setValue(property.value());
0053 }
0054 
0055 void KPropertyUrlEditorPrivate::setValue(const QVariant &newValue)
0056 {
0057     if (isComposedUrl && newValue.type() == QVariant::Url) {
0058         KPropertyComposedUrl composedUrl = value.value<KPropertyComposedUrl>();
0059         const QUrl newUrlValue = newValue.toUrl();
0060 
0061         if (newUrlValue.isRelative()) {
0062             composedUrl.setRelativePath(newUrlValue.path());
0063         } else {
0064             composedUrl.setAbsoluteUrl(newUrlValue);
0065         }
0066 
0067         value = QVariant::fromValue(composedUrl);
0068     } else {
0069         value = newValue;
0070     }
0071 }
0072 
0073 void KPropertyUrlEditorPrivate::updateLineEdit(const QString &textToDisplay)
0074 {
0075     lineEdit->setText(textToDisplay);
0076     savedText = lineEdit->text();
0077 }
0078 
0079 bool KPropertyUrlEditorPrivate::checkAndUpdate(QUrl *url) const
0080 {
0081     Q_ASSERT(url);
0082 
0083     if (url->isEmpty()) {
0084         return true;
0085     }
0086     if (!url->isValid()) {
0087         return false;
0088     }
0089 
0090     QUrl finalUrlForChecking;
0091 
0092     // handle relative URLs. We pass absolute URLs or truly relative URLs
0093     // into this function. We don't pass weird things like QUrl("C:/report.txt")
0094     // (which is considered to be relative). That's why isRelative() is enough
0095     if (url->isRelative()) {
0096         const KPropertyComposedUrl composedUrl = value.value<KPropertyComposedUrl>();
0097 
0098         // regular QUrl types must be absolute
0099         if (!isComposedUrl) {
0100             kprWarning() << "Property" << propertyName << "doesn't support relative URLs:" << *url;
0101             return false;
0102             // complex url may be relative, so check its base URL
0103         } else if (!composedUrl.baseUrl().isValid()) {
0104             kprWarning() << "The base URL in property" << propertyName
0105                          << "is invalid:" << composedUrl;
0106             return false;
0107         } else {
0108             finalUrlForChecking = composedUrl.baseUrl().resolved(*url);
0109         }
0110     } else {
0111         finalUrlForChecking = *url;
0112     }
0113 
0114     if (!finalUrlForChecking.isLocalFile()) {
0115         // Non-file protocols are only allowed for "" fileMode
0116         return fileMode.isEmpty();
0117     }
0118     const QString path = finalUrlForChecking.toLocalFile();
0119     const QFileInfo info(path);
0120     if (!info.isNativePath()) {
0121         return false;
0122     }
0123     //! @todo check for illegal characters -- https://stackoverflow.com/a/45282192
0124     if (fileMode == "existingfile") {
0125         //! @todo display error for false
0126         return info.isFile() && info.exists();
0127     } else if (fileMode == "dirsonly") {
0128         //! @todo display error for false
0129         if (info.isDir() && info.exists()) {
0130             if (url->isRelative()) {
0131                 url->setPath(fixUp(url->toString(), /* isRelative */ true));
0132             } else {
0133                 url->setPath(fixUp(path, /* isRelative */ false));
0134             }
0135             return true;
0136         } else {
0137             return false;
0138         }
0139     }
0140     // fileMode is "":
0141     //! @todo support confirmOverwrites, display error
0142     // if (info.exists()) {
0143     //}
0144     return true;
0145 }
0146 
0147 bool KPropertyUrlEditorPrivate::enterPressed(QEvent *event) const
0148 {
0149     if (event->type() == QEvent::KeyPress) {
0150         const int key = static_cast<QKeyEvent *>(event)->key();
0151         switch (key) {
0152         case Qt::Key_Enter:
0153         case Qt::Key_Return:
0154         case Qt::Key_Down:
0155         case Qt::Key_Up:
0156             return true;
0157         default:
0158             break;
0159         }
0160     }
0161     return false;
0162 }
0163 
0164 QString KPropertyUrlEditorPrivate::fixUp(const QString &path, bool isRelative) const
0165 {
0166     Q_UNUSED(isRelative)
0167     QString result = path;
0168     if (path.endsWith(QLatin1Char('/'))
0169 #ifdef Q_OS_WIN
0170         && (isRelative || path.length() > 3) // "C:" would not be a valid path on Windows
0171 #endif
0172     ) {
0173         result.chop(1);
0174     }
0175     return result;
0176 }
0177 
0178 QUrl KPropertyUrlEditorPrivate::getUrl()
0179 {
0180     QString caption;
0181     if (fileMode == "existingfile") {
0182         caption = QObject::tr("Select Existing File");
0183     } else if (fileMode == "dirsonly") {
0184         caption = QObject::tr("Select Existing Directory");
0185     } else {
0186         caption = QObject::tr("Select File");
0187     }
0188 
0189     QUrl dirUrlForFileDialog;
0190 
0191     if (isComposedUrl) {
0192         const KPropertyComposedUrl composedUrl = value.value<KPropertyComposedUrl>();
0193         dirUrlForFileDialog = composedUrl.value();
0194     } else {
0195         dirUrlForFileDialog = value.toUrl();
0196     }
0197 
0198     // Try to obtain URL from a custom dialog
0199     if (m_editor->parentWidget()) {
0200         KPropertyEditorView *view
0201             = qobject_cast<KPropertyEditorView *>(m_editor->parentWidget()->parentWidget());
0202         KProperty *property = &view->propertySet()->property(propertyName);
0203         if (property) {
0204             QVariantMap parameters;
0205             parameters[QStringLiteral("url")] = dirUrlForFileDialog;
0206             parameters[QStringLiteral("caption")] = caption;
0207             KPropertyEditorItemEvent event(*property, QStringLiteral("getOpenFileUrl"), parameters);
0208             emit view->handlePropertyEditorItemEvent(&event);
0209             if (event.hasResult()) {
0210                 return event.result().toUrl();
0211             }
0212         }
0213     }
0214 
0215     // Use default dialogs
0216     //! @todo filters, more options, supportedSchemes, localFilesOnly?
0217     QFileDialog::Options options;
0218     if (fileMode == "existingfile") {
0219         return QFileDialog::getOpenFileUrl(m_editor, caption, dirUrlForFileDialog, QString(),
0220                                            nullptr, options);
0221     } else if (fileMode == "dirsonly") {
0222         options |= QFileDialog::ShowDirsOnly;
0223         return QFileDialog::getExistingDirectoryUrl(m_editor, caption, dirUrlForFileDialog,
0224                                                     options);
0225     } else {
0226         if (!confirmOverwrites) {
0227             options |= QFileDialog::DontConfirmOverwrite;
0228         }
0229         return QFileDialog::getSaveFileUrl(m_editor, caption, dirUrlForFileDialog, QString(),
0230                                            nullptr, options);
0231     }
0232     return QUrl();
0233 }
0234 
0235 void KPropertyUrlEditorPrivate::processEvent(QObject *o, QEvent *event)
0236 {
0237     if (o == lineEdit && (event->type() == QEvent::FocusOut || enterPressed(event))) {
0238         const QString enteredText = lineEdit->text();
0239 
0240         if (savedText != enteredText) { // text changed since the recent setValue(): update
0241             QUrl newUrl(enteredText);
0242 
0243             // "a/b"               -> set this as a path
0244             // "kde.org"           -> "http://kde.org" (for regular non-composed URLs)
0245             // "kde.org"           -> "kde.org" (for composed URLs)
0246             // "C:\report.txt"     -> "file:///C:/report.txt"
0247             // "http://google.com" -> convert with more strict rules
0248             if (!newUrl.scheme().isEmpty() || (newUrl.scheme().isEmpty() && !isComposedUrl)
0249                 || QDir::isAbsolutePath(enteredText)) {
0250                 newUrl = QUrl::fromUserInput(enteredText);
0251             } else {
0252                 newUrl.clear();
0253                 newUrl.setPath(QDir::fromNativeSeparators(enteredText));
0254             }
0255 
0256             if (checkAndUpdate(&newUrl)) {
0257                 setValue(newUrl);
0258                 savedText = enteredText;
0259                 emit commitData();
0260             } else { // invalid URL: revert text to last saved value which is valid,
0261                 // emit no change
0262                 kprWarning() << "URL" << newUrl << "is not valid";
0263                 lineEdit->setText(savedText);
0264             }
0265         }
0266     }
0267 }