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 }