File indexing completed on 2023-10-01 04:05:50
0001 // -*- c++ -*- 0002 /* 0003 This file is part of the KDE libraries 0004 SPDX-FileCopyrightText: 1997, 1998 Richard Moore <rich@kde.org> 0005 SPDX-FileCopyrightText: 1998 Stephan Kulow <coolo@kde.org> 0006 SPDX-FileCopyrightText: 1998 Daniel Grana <grana@ie.iwi.unibe.ch> 0007 SPDX-FileCopyrightText: 1999, 2000, 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org> 0008 SPDX-FileCopyrightText: 2003 Clarence Dang <dang@kde.org> 0009 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> 0010 SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org> 0011 0012 SPDX-License-Identifier: LGPL-2.0-or-later 0013 */ 0014 0015 #include "kfilewidget.h" 0016 0017 #include "../utils_p.h" 0018 #include "kfilebookmarkhandler_p.h" 0019 #include "kfileplacesmodel.h" 0020 #include "kfileplacesview.h" 0021 #include "kfilepreviewgenerator.h" 0022 #include "kfilewidgetdocktitlebar_p.h" 0023 #include "kurlcombobox.h" 0024 #include "kurlnavigator.h" 0025 0026 #include <config-kiofilewidgets.h> 0027 0028 #include <defaults-kfile.h> 0029 #include <kdiroperator.h> 0030 #include <kfilefiltercombo.h> 0031 #include <kfileitemdelegate.h> 0032 #include <kio/job.h> 0033 #include <kio/jobuidelegate.h> 0034 #include <kio/statjob.h> 0035 #include <kprotocolmanager.h> 0036 #include <krecentdirs.h> 0037 #include <krecentdocument.h> 0038 #include <kurlauthorized.h> 0039 #include <kurlcompletion.h> 0040 0041 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 0042 #include <KActionCollection> 0043 #endif 0044 0045 #include <KActionMenu> 0046 #include <KConfigGroup> 0047 #include <KDirLister> 0048 #include <KFileItem> 0049 #include <KFilePlacesModel> 0050 #include <KIconLoader> 0051 #include <KJobWidgets> 0052 #include <KLocalizedString> 0053 #include <KMessageBox> 0054 #include <KMessageWidget> 0055 #include <KSharedConfig> 0056 #include <KShell> 0057 #include <KStandardAction> 0058 #include <KToolBar> 0059 0060 #include <QAbstractProxyModel> 0061 #include <QApplication> 0062 #include <QCheckBox> 0063 #include <QDebug> 0064 #include <QDockWidget> 0065 #include <QHelpEvent> 0066 #include <QIcon> 0067 #include <QLabel> 0068 #include <QLayout> 0069 #include <QLineEdit> 0070 #include <QLoggingCategory> 0071 #include <QMenu> 0072 #include <QMimeDatabase> 0073 #include <QPushButton> 0074 #include <QScreen> 0075 #include <QSplitter> 0076 #include <QStandardPaths> 0077 #include <QTimer> 0078 0079 #include <algorithm> 0080 #include <array> 0081 0082 Q_DECLARE_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW) 0083 Q_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW, "kf.kio.kfilewidgets.kfilewidget", QtInfoMsg) 0084 0085 class KFileWidgetPrivate 0086 { 0087 public: 0088 explicit KFileWidgetPrivate(KFileWidget *qq) 0089 : q(qq) 0090 { 0091 } 0092 0093 ~KFileWidgetPrivate() 0094 { 0095 delete m_bookmarkHandler; // Should be deleted before m_ops! 0096 // Must be deleted before m_ops, otherwise the unit test crashes due to the 0097 // connection to the QDockWidget::visibilityChanged signal, which may get 0098 // emitted after this object is destroyed 0099 delete m_placesDock; 0100 delete m_ops; 0101 } 0102 0103 QSize screenSize() const 0104 { 0105 return q->parentWidget() ? q->parentWidget()->screen()->availableGeometry().size() // 0106 : QGuiApplication::primaryScreen()->availableGeometry().size(); 0107 } 0108 0109 void initDirOpWidgets(); 0110 void initToolbar(); 0111 void initZoomWidget(); 0112 void initLocationWidget(); 0113 void initFilterWidget(); 0114 void updateLocationWhatsThis(); 0115 void updateAutoSelectExtension(); 0116 void initPlacesPanel(); 0117 void setPlacesViewSplitterSizes(); 0118 void setLafBoxColumnWidth(); 0119 void initGUI(); 0120 void readViewConfig(); 0121 void writeViewConfig(); 0122 void setNonExtSelection(); 0123 void setLocationText(const QUrl &); 0124 void setLocationText(const QList<QUrl> &); 0125 void appendExtension(QUrl &url); 0126 void updateLocationEditExtension(const QString &); 0127 QString findMatchingFilter(const QString &filter, const QString &filename) const; 0128 void updateFilter(); 0129 void updateFilterText(); 0130 /** 0131 * Parses the string "line" for files. If line doesn't contain any ", the 0132 * whole line will be interpreted as one file. If the number of " is odd, 0133 * an empty list will be returned. Otherwise, all items enclosed in " " 0134 * will be returned as correct urls. 0135 */ 0136 QList<QUrl> tokenize(const QString &line) const; 0137 /** 0138 * Reads the recent used files and inserts them into the location combobox 0139 */ 0140 void readRecentFiles(); 0141 /** 0142 * Saves the entries from the location combobox. 0143 */ 0144 void saveRecentFiles(); 0145 /** 0146 * called when an item is highlighted/selected in multiselection mode. 0147 * handles setting the m_locationEdit. 0148 */ 0149 void multiSelectionChanged(); 0150 0151 /** 0152 * Returns the absolute version of the URL specified in m_locationEdit. 0153 */ 0154 QUrl getCompleteUrl(const QString &) const; 0155 0156 /** 0157 * Sets the dummy entry on the history combo box. If the dummy entry 0158 * already exists, it is overwritten with this information. 0159 */ 0160 void setDummyHistoryEntry(const QString &text, const QIcon &icon = QIcon(), bool usePreviousPixmapIfNull = true); 0161 0162 /** 0163 * Removes the dummy entry of the history combo box. 0164 */ 0165 void removeDummyHistoryEntry(); 0166 0167 /** 0168 * Asks for overwrite confirmation using a KMessageBox and returns 0169 * true if the user accepts. 0170 * 0171 * @since 4.2 0172 */ 0173 bool toOverwrite(const QUrl &); 0174 0175 // private slots 0176 void slotLocationChanged(const QString &); 0177 void urlEntered(const QUrl &); 0178 void enterUrl(const QUrl &); 0179 void enterUrl(const QString &); 0180 void locationAccepted(const QString &); 0181 void slotFilterChanged(); 0182 void fileHighlighted(const KFileItem &); 0183 void fileSelected(const KFileItem &); 0184 void slotLoadingFinished(); 0185 void fileCompletion(const QString &); 0186 void togglePlacesPanel(bool show, QObject *sender = nullptr); 0187 void toggleBookmarks(bool); 0188 void slotAutoSelectExtClicked(); 0189 void placesViewSplitterMoved(int, int); 0190 void activateUrlNavigator(); 0191 0192 enum ZoomState { 0193 ZoomOut, 0194 ZoomIn, 0195 }; 0196 void changeIconsSize(ZoomState zoom); 0197 void slotDirOpIconSizeChanged(int size); 0198 void slotIconSizeSliderMoved(int); 0199 void slotIconSizeChanged(int); 0200 void slotViewDoubleClicked(const QModelIndex &); 0201 void slotViewKeyEnterReturnPressed(); 0202 0203 void addToRecentDocuments(); 0204 0205 QString locationEditCurrentText() const; 0206 0207 /** 0208 * KIO::NetAccess::mostLocalUrl local replacement. 0209 * This method won't show any progress dialogs for stating, since 0210 * they are very annoying when stating. 0211 */ 0212 QUrl mostLocalUrl(const QUrl &url); 0213 0214 void setInlinePreviewShown(bool show); 0215 0216 KFileWidget *const q; 0217 0218 // the last selected url 0219 QUrl m_url; 0220 0221 // now following all kind of widgets, that I need to rebuild 0222 // the geometry management 0223 QBoxLayout *m_boxLayout = nullptr; 0224 QGridLayout *m_lafBox = nullptr; 0225 QVBoxLayout *m_vbox = nullptr; 0226 0227 QLabel *m_locationLabel = nullptr; 0228 QWidget *m_opsWidget = nullptr; 0229 QVBoxLayout *m_opsWidgetLayout = nullptr; 0230 0231 QLabel *m_filterLabel = nullptr; 0232 KUrlNavigator *m_urlNavigator = nullptr; 0233 KMessageWidget *m_messageWidget = nullptr; 0234 QPushButton *m_okButton = nullptr; 0235 QPushButton *m_cancelButton = nullptr; 0236 QDockWidget *m_placesDock = nullptr; 0237 KFilePlacesView *m_placesView = nullptr; 0238 QSplitter *m_placesViewSplitter = nullptr; 0239 // caches the places view width. This value will be updated when the splitter 0240 // is moved. This allows us to properly set a value when the dialog itself 0241 // is resized 0242 int m_placesViewWidth = -1; 0243 0244 QWidget *m_labeledCustomWidget = nullptr; 0245 QWidget *m_bottomCustomWidget = nullptr; 0246 0247 // Automatically Select Extension stuff 0248 QCheckBox *m_autoSelectExtCheckBox = nullptr; 0249 QString m_extension; // current extension for this filter 0250 0251 QList<QUrl> m_urlList; // the list of selected urls 0252 0253 KFileWidget::OperationMode m_operationMode = KFileWidget::Opening; 0254 0255 // The file class used for KRecentDirs 0256 QString m_fileClass; 0257 0258 KFileBookmarkHandler *m_bookmarkHandler = nullptr; 0259 0260 KActionMenu *m_bookmarkButton = nullptr; 0261 0262 KToolBar *m_toolbar = nullptr; 0263 KUrlComboBox *m_locationEdit = nullptr; 0264 KDirOperator *m_ops = nullptr; 0265 KFileFilterCombo *m_filterWidget = nullptr; 0266 QTimer m_filterDelayTimer; 0267 0268 KFilePlacesModel *m_model = nullptr; 0269 0270 // whether or not the _user_ has checked the above box 0271 bool m_autoSelectExtChecked = false; 0272 0273 // indicates if the location edit should be kept or cleared when changing 0274 // directories 0275 bool m_keepLocation = false; 0276 0277 // the KDirOperators view is set in KFileWidget::show(), so to avoid 0278 // setting it again and again, we have this nice little boolean :) 0279 bool m_hasView = false; 0280 0281 bool m_hasDefaultFilter = false; // necessary for the m_operationMode 0282 bool m_inAccept = false; // true between beginning and end of accept() 0283 bool m_dummyAdded = false; // if the dummy item has been added. This prevents the combo from having a 0284 // blank item added when loaded 0285 bool m_confirmOverwrite = false; 0286 bool m_differentHierarchyLevelItemsEntered = false; 0287 0288 const std::array<short, 8> m_stdIconSizes = { 0289 KIconLoader::SizeSmall, 0290 KIconLoader::SizeSmallMedium, 0291 KIconLoader::SizeMedium, 0292 KIconLoader::SizeLarge, 0293 KIconLoader::SizeHuge, 0294 KIconLoader::SizeEnormous, 0295 256, 0296 512, 0297 }; 0298 0299 QSlider *m_iconSizeSlider = nullptr; 0300 QAction *m_zoomOutAction = nullptr; 0301 QAction *m_zoomInAction = nullptr; 0302 0303 // The group which stores app-specific settings. These settings are recent 0304 // files and urls. Visual settings (view mode, sorting criteria...) are not 0305 // app-specific and are stored in kdeglobals 0306 KConfigGroup m_configGroup; 0307 0308 KToggleAction *m_toggleBookmarksAction = nullptr; 0309 KToggleAction *m_togglePlacesPanelAction = nullptr; 0310 }; 0311 0312 Q_GLOBAL_STATIC(QUrl, lastDirectory) // to set the start path 0313 0314 // returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars 0315 static bool containsProtocolSection(const QString &string) 0316 { 0317 int len = string.length(); 0318 static const char prot[] = ":/"; 0319 for (int i = 0; i < len;) { 0320 i = string.indexOf(QLatin1String(prot), i); 0321 if (i == -1) { 0322 return false; 0323 } 0324 int j = i - 1; 0325 for (; j >= 0; j--) { 0326 const QChar &ch(string[j]); 0327 if (ch.toLatin1() == 0 || !ch.isLetter()) { 0328 break; 0329 } 0330 if (ch.isSpace() && (i - j - 1) >= 2) { 0331 return true; 0332 } 0333 } 0334 if (j < 0 && i >= 2) { 0335 return true; // at least two letters before ":/" 0336 } 0337 i += 3; // skip : and / and one char 0338 } 0339 return false; 0340 } 0341 0342 // this string-to-url conversion function handles relative paths, full paths and URLs 0343 // without the http-prepending that QUrl::fromUserInput does. 0344 static QUrl urlFromString(const QString &str) 0345 { 0346 if (Utils::isAbsoluteLocalPath(str)) { 0347 return QUrl::fromLocalFile(str); 0348 } 0349 QUrl url(str); 0350 if (url.isRelative()) { 0351 url.clear(); 0352 url.setPath(str); 0353 } 0354 return url; 0355 } 0356 0357 KFileWidget::KFileWidget(const QUrl &_startDir, QWidget *parent) 0358 : QWidget(parent) 0359 , d(new KFileWidgetPrivate(this)) 0360 { 0361 QUrl startDir(_startDir); 0362 // qDebug() << "startDir" << startDir; 0363 QString filename; 0364 0365 d->m_okButton = new QPushButton(this); 0366 KGuiItem::assign(d->m_okButton, KStandardGuiItem::ok()); 0367 d->m_okButton->setDefault(true); 0368 d->m_cancelButton = new QPushButton(this); 0369 KGuiItem::assign(d->m_cancelButton, KStandardGuiItem::cancel()); 0370 // The dialog shows them 0371 d->m_okButton->hide(); 0372 d->m_cancelButton->hide(); 0373 0374 d->initDirOpWidgets(); 0375 0376 // Resolve this now so that a 'kfiledialog:' URL, if specified, 0377 // does not get inserted into the urlNavigator history. 0378 d->m_url = getStartUrl(startDir, d->m_fileClass, filename); 0379 startDir = d->m_url; 0380 0381 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 0382 KActionCollection *coll = d->m_ops->actionCollection(); 0383 coll->addAssociatedWidget(this); 0384 #else 0385 const auto operatorActions = d->m_ops->allActions(); 0386 for (QAction *action : operatorActions) { 0387 addAction(action); 0388 } 0389 #endif 0390 0391 QAction *goToNavigatorAction = new QAction(this); 0392 0393 connect(goToNavigatorAction, &QAction::triggered, this, [this]() { 0394 d->activateUrlNavigator(); 0395 }); 0396 0397 goToNavigatorAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L)); 0398 0399 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 0400 coll->addAction(QStringLiteral("gotonavigator"), goToNavigatorAction); 0401 #else 0402 addAction(goToNavigatorAction); 0403 #endif 0404 0405 KUrlComboBox *pathCombo = d->m_urlNavigator->editor(); 0406 KUrlCompletion *pathCompletionObj = new KUrlCompletion(KUrlCompletion::DirCompletion); 0407 pathCombo->setCompletionObject(pathCompletionObj); 0408 pathCombo->setAutoDeleteCompletionObject(true); 0409 0410 connect(d->m_urlNavigator, &KUrlNavigator::urlChanged, this, [this](const QUrl &url) { 0411 d->enterUrl(url); 0412 }); 0413 connect(d->m_urlNavigator, &KUrlNavigator::returnPressed, d->m_ops, qOverload<>(&QWidget::setFocus)); 0414 0415 // Location, "Name:", line-edit and label 0416 d->initLocationWidget(); 0417 0418 // "Filter:" line-edit and label 0419 d->initFilterWidget(); 0420 0421 // the Automatically Select Extension checkbox 0422 // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig()) 0423 d->m_autoSelectExtCheckBox = new QCheckBox(this); 0424 connect(d->m_autoSelectExtCheckBox, &QCheckBox::clicked, this, [this]() { 0425 d->slotAutoSelectExtClicked(); 0426 }); 0427 0428 d->initGUI(); // activate GM 0429 0430 // read our configuration 0431 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0432 config->reparseConfiguration(); // grab newly added dirs by other processes (#403524) 0433 KConfigGroup group(config, ConfigGroup); 0434 readConfig(group); 0435 0436 d->m_ops->action(KDirOperator::ShowPreview)->setChecked(d->m_ops->isInlinePreviewShown()); 0437 d->slotDirOpIconSizeChanged(d->m_ops->iconSize()); 0438 0439 KFilePreviewGenerator *pg = d->m_ops->previewGenerator(); 0440 if (pg) { 0441 d->m_ops->action(KDirOperator::ShowPreview)->setChecked(pg->isPreviewShown()); 0442 } 0443 0444 // getStartUrl() above will have resolved the startDir parameter into 0445 // a directory and file name in the two cases: (a) where it is a 0446 // special "kfiledialog:" URL, or (b) where it is a plain file name 0447 // only without directory or protocol. For any other startDir 0448 // specified, it is not possible to resolve whether there is a file name 0449 // present just by looking at the URL; the only way to be sure is 0450 // to stat it. 0451 bool statRes = false; 0452 if (filename.isEmpty()) { 0453 KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo); 0454 KJobWidgets::setWindow(statJob, this); 0455 statRes = statJob->exec(); 0456 // qDebug() << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir(); 0457 if (!statRes || !statJob->statResult().isDir()) { 0458 filename = startDir.fileName(); 0459 startDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0460 // qDebug() << "statJob -> startDir" << startDir << "filename" << filename; 0461 } 0462 } 0463 0464 d->m_ops->setUrl(startDir, true); 0465 d->m_urlNavigator->setLocationUrl(startDir); 0466 if (d->m_placesView) { 0467 d->m_placesView->setUrl(startDir); 0468 } 0469 0470 // We have a file name either explicitly specified, or have checked that 0471 // we could stat it and it is not a directory. Set it. 0472 if (!filename.isEmpty()) { 0473 QLineEdit *lineEdit = d->m_locationEdit->lineEdit(); 0474 // qDebug() << "selecting filename" << filename; 0475 if (statRes) { 0476 d->setLocationText(QUrl(filename)); 0477 } else { 0478 lineEdit->setText(filename); 0479 // Preserve this filename when clicking on the view (cf fileHighlighted) 0480 lineEdit->setModified(true); 0481 } 0482 lineEdit->selectAll(); 0483 } 0484 0485 d->m_locationEdit->setFocus(); 0486 0487 const QAction *showHiddenAction = d->m_ops->action(KDirOperator::ShowHiddenFiles); 0488 Q_ASSERT(showHiddenAction); 0489 d->m_urlNavigator->setShowHiddenFolders(showHiddenAction->isChecked()); 0490 connect(showHiddenAction, &QAction::toggled, this, [this](bool checked) { 0491 d->m_urlNavigator->setShowHiddenFolders(checked); 0492 }); 0493 0494 const QAction *hiddenFilesLastAction = d->m_ops->action(KDirOperator::SortHiddenFilesLast); 0495 Q_ASSERT(hiddenFilesLastAction); 0496 d->m_urlNavigator->setSortHiddenFoldersLast(hiddenFilesLastAction->isChecked()); 0497 connect(hiddenFilesLastAction, &QAction::toggled, this, [this](bool checked) { 0498 d->m_urlNavigator->setSortHiddenFoldersLast(checked); 0499 }); 0500 } 0501 0502 KFileWidget::~KFileWidget() 0503 { 0504 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0505 config->sync(); 0506 d->m_ops->removeEventFilter(this); 0507 d->m_locationEdit->removeEventFilter(this); 0508 } 0509 0510 void KFileWidget::setLocationLabel(const QString &text) 0511 { 0512 d->m_locationLabel->setText(text); 0513 } 0514 0515 void KFileWidget::setFilter(const QString &filter) 0516 { 0517 int pos = filter.indexOf(QLatin1Char('/')); 0518 0519 // Check for an un-escaped '/', if found 0520 // interpret as a MIME filter. 0521 0522 if (pos > 0 && filter[pos - 1] != QLatin1Char('\\')) { 0523 QStringList filters = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 0524 setMimeFilter(filters); 0525 return; 0526 } 0527 0528 // Strip the escape characters from 0529 // escaped '/' characters. 0530 0531 QString copy(filter); 0532 for (pos = 0; (pos = copy.indexOf(QLatin1String("\\/"), pos)) != -1; ++pos) { 0533 copy.remove(pos, 1); 0534 } 0535 0536 d->m_ops->clearFilter(); 0537 d->m_filterWidget->setFilter(copy); 0538 d->m_ops->setNameFilter(d->m_filterWidget->currentFilter()); 0539 d->m_ops->updateDir(); 0540 d->m_hasDefaultFilter = false; 0541 d->m_filterWidget->setEditable(true); 0542 0543 d->updateAutoSelectExtension(); 0544 } 0545 0546 QString KFileWidget::currentFilter() const 0547 { 0548 return d->m_filterWidget->currentFilter(); 0549 } 0550 0551 void KFileWidget::setMimeFilter(const QStringList &mimeTypes, const QString &defaultType) 0552 { 0553 d->m_filterWidget->setMimeFilter(mimeTypes, defaultType); 0554 0555 QStringList types = 0556 d->m_filterWidget->currentFilter().split(QLatin1Char(' '), Qt::SkipEmptyParts); // QStringList::split(" ", d->m_filterWidget->currentFilter()); 0557 0558 types.append(QStringLiteral("inode/directory")); 0559 d->m_ops->clearFilter(); 0560 d->m_ops->setMimeFilter(types); 0561 d->m_hasDefaultFilter = !defaultType.isEmpty(); 0562 d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || d->m_operationMode != Saving); 0563 0564 d->updateAutoSelectExtension(); 0565 d->updateFilterText(); 0566 } 0567 0568 void KFileWidget::clearFilter() 0569 { 0570 d->m_filterWidget->setFilter(QString()); 0571 d->m_ops->clearFilter(); 0572 d->m_hasDefaultFilter = false; 0573 d->m_filterWidget->setEditable(true); 0574 0575 d->updateAutoSelectExtension(); 0576 } 0577 0578 QString KFileWidget::currentMimeFilter() const 0579 { 0580 int i = d->m_filterWidget->currentIndex(); 0581 if (d->m_filterWidget->showsAllTypes() && i == 0) { 0582 return QString(); // The "all types" item has no MIME type 0583 } 0584 0585 return d->m_filterWidget->filters().at(i); 0586 } 0587 0588 QMimeType KFileWidget::currentFilterMimeType() 0589 { 0590 QMimeDatabase db; 0591 return db.mimeTypeForName(currentMimeFilter()); 0592 } 0593 0594 void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) 0595 { 0596 d->m_ops->setPreviewWidget(w); 0597 d->m_ops->clearHistory(); 0598 d->m_hasView = true; 0599 } 0600 0601 QUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const 0602 { 0603 // qDebug() << "got url " << _url; 0604 0605 const QString url = KShell::tildeExpand(_url); 0606 QUrl u; 0607 0608 if (Utils::isAbsoluteLocalPath(url)) { 0609 u = QUrl::fromLocalFile(url); 0610 } else { 0611 QUrl relativeUrlTest(m_ops->url()); 0612 relativeUrlTest.setPath(Utils::concatPaths(relativeUrlTest.path(), url)); 0613 if (!m_ops->dirLister()->findByUrl(relativeUrlTest).isNull() || !KProtocolInfo::isKnownProtocol(relativeUrlTest)) { 0614 u = relativeUrlTest; 0615 } else { 0616 // Try to preserve URLs if they have a scheme (for example, 0617 // "https://example.com/foo.txt") and otherwise resolve relative 0618 // paths to absolute ones (e.g. "foo.txt" -> "file:///tmp/foo.txt"). 0619 u = QUrl(url); 0620 if (u.isRelative()) { 0621 u = relativeUrlTest; 0622 } 0623 } 0624 } 0625 0626 return u; 0627 } 0628 0629 QSize KFileWidget::sizeHint() const 0630 { 0631 int fontSize = fontMetrics().height(); 0632 const QSize goodSize(48 * fontSize, 30 * fontSize); 0633 const QSize scrnSize = d->screenSize(); 0634 const QSize minSize(scrnSize / 2); 0635 const QSize maxSize(scrnSize * qreal(0.9)); 0636 return (goodSize.expandedTo(minSize).boundedTo(maxSize)); 0637 } 0638 0639 static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url); 0640 0641 /** 0642 * Escape the given Url so that is fit for use in the selected list of file. This 0643 * mainly handles double quote (") characters. These are used to separate entries 0644 * in the list, however, if `"` appears in the filename (or path), this will be 0645 * escaped as `\"`. Later, the tokenizer is able to understand the difference 0646 * and do the right thing 0647 */ 0648 static QString escapeDoubleQuotes(QString &&path); 0649 0650 // Called by KFileDialog 0651 void KFileWidget::slotOk() 0652 { 0653 // qDebug() << "slotOk\n"; 0654 0655 const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText())); 0656 0657 QList<QUrl> locationEditCurrentTextList(d->tokenize(locationEditCurrentText)); 0658 KFile::Modes mode = d->m_ops->mode(); 0659 0660 // if there is nothing to do, just return from here 0661 if (locationEditCurrentTextList.isEmpty()) { 0662 return; 0663 } 0664 0665 // Make sure that one of the modes was provided 0666 if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) { 0667 mode |= KFile::File; 0668 // qDebug() << "No mode() provided"; 0669 } 0670 0671 // Clear the list as we are going to refill it 0672 d->m_urlList.clear(); 0673 0674 const bool directoryMode = (mode & KFile::Directory); 0675 const bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files); 0676 0677 // if we are on file mode, and the list of provided files/folder is greater than one, inform 0678 // the user about it 0679 if (locationEditCurrentTextList.count() > 1) { 0680 if (mode & KFile::File) { 0681 KMessageBox::error(this, i18n("You can only select one file"), i18n("More than one file provided")); 0682 return; 0683 } 0684 0685 /** 0686 * Logic of the next part of code (ends at "end multi relative urls"). 0687 * 0688 * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'. 0689 * Why we need to support this ? Because we provide tree views, which aren't plain. 0690 * 0691 * Now, how does this logic work. It will get the first element on the list (with no filename), 0692 * following the previous example say "/home/foo" and set it as the top most url. 0693 * 0694 * After this, it will iterate over the rest of items and check if this URL (topmost url) 0695 * contains the url being iterated. 0696 * 0697 * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping 0698 * filename), and a false will be returned. Then we upUrl the top most url, resulting in 0699 * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we 0700 * have "/" against "/boot/grub", what returns true for us, so we can say that the closest 0701 * common ancestor of both is "/". 0702 * 0703 * This example has been written for 2 urls, but this works for any number of urls. 0704 */ 0705 if (!d->m_differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this 0706 int start = 0; 0707 QUrl topMostUrl; 0708 KIO::StatJob *statJob = nullptr; 0709 bool res = false; 0710 0711 // we need to check for a valid first url, so in theory we only iterate one time over 0712 // this loop. However it can happen that the user did 0713 // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first 0714 // candidate. 0715 while (!res && start < locationEditCurrentTextList.count()) { 0716 topMostUrl = locationEditCurrentTextList.at(start); 0717 statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo); 0718 KJobWidgets::setWindow(statJob, this); 0719 res = statJob->exec(); 0720 start++; 0721 } 0722 0723 Q_ASSERT(statJob); 0724 0725 // if this is not a dir, strip the filename. after this we have an existent and valid 0726 // dir (we stated correctly the file). 0727 if (!statJob->statResult().isDir()) { 0728 topMostUrl = topMostUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0729 } 0730 0731 // now the funny part. for the rest of filenames, go and look for the closest ancestor 0732 // of all them. 0733 for (int i = start; i < locationEditCurrentTextList.count(); ++i) { 0734 QUrl currUrl = locationEditCurrentTextList.at(i); 0735 KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo); 0736 KJobWidgets::setWindow(statJob, this); 0737 int res = statJob->exec(); 0738 if (res) { 0739 // again, we don't care about filenames 0740 if (!statJob->statResult().isDir()) { 0741 currUrl = currUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0742 } 0743 0744 // iterate while this item is contained on the top most url 0745 while (!topMostUrl.matches(currUrl, QUrl::StripTrailingSlash) && !topMostUrl.isParentOf(currUrl)) { 0746 topMostUrl = KIO::upUrl(topMostUrl); 0747 } 0748 } 0749 } 0750 0751 // now recalculate all paths for them being relative in base of the top most url 0752 QStringList stringList; 0753 stringList.reserve(locationEditCurrentTextList.count()); 0754 for (int i = 0; i < locationEditCurrentTextList.count(); ++i) { 0755 Q_ASSERT(topMostUrl.isParentOf(locationEditCurrentTextList[i])); 0756 QString relativePath = relativePathOrUrl(topMostUrl, locationEditCurrentTextList[i]); 0757 stringList << escapeDoubleQuotes(std::move(relativePath)); 0758 } 0759 0760 d->m_ops->setUrl(topMostUrl, true); 0761 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true); 0762 d->m_locationEdit->lineEdit()->setText(QStringLiteral("\"%1\"").arg(stringList.join(QStringLiteral("\" \"")))); 0763 d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked); 0764 0765 d->m_differentHierarchyLevelItemsEntered = true; 0766 slotOk(); 0767 return; 0768 } 0769 /** 0770 * end multi relative urls 0771 */ 0772 } else if (!locationEditCurrentTextList.isEmpty()) { 0773 // if we are on file or files mode, and we have an absolute url written by 0774 // the user: 0775 // * convert it to relative and call slotOk again if the protocol supports listing. 0776 // * use the full url if the protocol doesn't support listing 0777 // This is because when using a protocol that supports listing we want to show the directory 0778 // the user just opened/saved from the next time they open the dialog, it makes sense usability wise. 0779 // If the protocol doesn't support listing (i.e. http:// ) the user would end up with the dialog 0780 // showing an "empty directory" which is bad usability wise. 0781 if (!locationEditCurrentText.isEmpty() && !onlyDirectoryMode 0782 && (Utils::isAbsoluteLocalPath(locationEditCurrentText) || containsProtocolSection(locationEditCurrentText))) { 0783 QUrl url = urlFromString(locationEditCurrentText); 0784 if (KProtocolManager::supportsListing(url)) { 0785 QString fileName; 0786 if (d->m_operationMode == Opening) { 0787 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 0788 KJobWidgets::setWindow(statJob, this); 0789 int res = statJob->exec(); 0790 if (res) { 0791 if (!statJob->statResult().isDir()) { 0792 fileName = url.fileName(); 0793 url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash 0794 } else { 0795 Utils::appendSlashToPath(url); 0796 } 0797 } 0798 } else { 0799 const QUrl directory = url.adjusted(QUrl::RemoveFilename); 0800 // Check if the folder exists 0801 KIO::StatJob *statJob = KIO::stat(directory, KIO::HideProgressInfo); 0802 KJobWidgets::setWindow(statJob, this); 0803 int res = statJob->exec(); 0804 if (res) { 0805 if (statJob->statResult().isDir()) { 0806 url = url.adjusted(QUrl::StripTrailingSlash); 0807 fileName = url.fileName(); 0808 url = url.adjusted(QUrl::RemoveFilename); 0809 } 0810 } 0811 } 0812 d->m_ops->setUrl(url, true); 0813 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true); 0814 d->m_locationEdit->lineEdit()->setText(fileName); 0815 d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked); 0816 slotOk(); 0817 return; 0818 } else { 0819 locationEditCurrentTextList = {url}; 0820 } 0821 } 0822 } 0823 0824 // restore it 0825 d->m_differentHierarchyLevelItemsEntered = false; 0826 0827 // locationEditCurrentTextList contains absolute paths 0828 // this is the general loop for the File and Files mode. Obviously we know 0829 // that the File mode will iterate only one time here 0830 QList<QUrl>::ConstIterator it = locationEditCurrentTextList.constBegin(); 0831 bool filesInList = false; 0832 while (it != locationEditCurrentTextList.constEnd()) { 0833 QUrl url(*it); 0834 0835 if (d->m_operationMode == Saving && !directoryMode) { 0836 d->appendExtension(url); 0837 } 0838 0839 d->m_url = url; 0840 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 0841 KJobWidgets::setWindow(statJob, this); 0842 int res = statJob->exec(); 0843 0844 if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), url)) { 0845 QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_url.toDisplayString()); 0846 KMessageBox::error(this, msg); 0847 return; 0848 } 0849 0850 // if we are on local mode, make sure we haven't got a remote base url 0851 if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->m_url).isLocalFile()) { 0852 KMessageBox::error(this, i18n("You can only select local files"), i18n("Remote files not accepted")); 0853 return; 0854 } 0855 0856 const auto &supportedSchemes = d->m_model->supportedSchemes(); 0857 if (!supportedSchemes.isEmpty() && !supportedSchemes.contains(d->m_url.scheme())) { 0858 KMessageBox::error(this, 0859 i18np("The selected URL uses an unsupported scheme. " 0860 "Please use the following scheme: %2", 0861 "The selected URL uses an unsupported scheme. " 0862 "Please use one of the following schemes: %2", 0863 supportedSchemes.size(), 0864 supportedSchemes.join(QLatin1String(", "))), 0865 i18n("Unsupported URL scheme")); 0866 return; 0867 } 0868 0869 // if we are given a folder when not on directory mode, let's get into it 0870 if (res && !directoryMode && statJob->statResult().isDir()) { 0871 // check if we were given more than one folder, in that case we don't know to which one 0872 // cd 0873 ++it; 0874 while (it != locationEditCurrentTextList.constEnd()) { 0875 QUrl checkUrl(*it); 0876 KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo); 0877 KJobWidgets::setWindow(checkStatJob, this); 0878 bool res = checkStatJob->exec(); 0879 if (res && checkStatJob->statResult().isDir()) { 0880 KMessageBox::error(this, 0881 i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide " 0882 "which one to enter. Please select only one folder to list it."), 0883 i18n("More than one folder provided")); 0884 return; 0885 } else if (res) { 0886 filesInList = true; 0887 } 0888 ++it; 0889 } 0890 if (filesInList) { 0891 KMessageBox::information( 0892 this, 0893 i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), 0894 i18n("Files and folders selected")); 0895 } 0896 d->m_ops->setUrl(url, true); 0897 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true); 0898 d->m_locationEdit->lineEdit()->setText(QString()); 0899 d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked); 0900 return; 0901 } else if (res && onlyDirectoryMode && !statJob->statResult().isDir()) { 0902 // if we are given a file when on directory only mode, reject it 0903 return; 0904 } else if (!(mode & KFile::ExistingOnly) || res) { 0905 // if we don't care about ExistingOnly flag, add the file even if 0906 // it doesn't exist. If we care about it, don't add it to the list 0907 if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) { 0908 d->m_urlList << url; 0909 } 0910 filesInList = true; 0911 } else { 0912 KMessageBox::error(this, i18n("The file \"%1\" could not be found", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Cannot open file")); 0913 return; // do not emit accepted() if we had ExistingOnly flag and stat failed 0914 } 0915 0916 if ((d->m_operationMode == Saving) && d->m_confirmOverwrite && !d->toOverwrite(url)) { 0917 return; 0918 } 0919 0920 ++it; 0921 } 0922 0923 // if we have reached this point and we didn't return before, that is because 0924 // we want this dialog to be accepted 0925 Q_EMIT accepted(); 0926 } 0927 0928 void KFileWidget::accept() 0929 { 0930 d->m_inAccept = true; 0931 0932 *lastDirectory() = d->m_ops->url(); 0933 if (!d->m_fileClass.isEmpty()) { 0934 KRecentDirs::add(d->m_fileClass, d->m_ops->url().toString()); 0935 } 0936 0937 // clear the topmost item, we insert it as full path later on as item 1 0938 d->m_locationEdit->setItemText(0, QString()); 0939 0940 const QList<QUrl> list = selectedUrls(); 0941 int atmost = d->m_locationEdit->maxItems(); // don't add more items than necessary 0942 for (const auto &url : list) { 0943 if (atmost-- == 0) { 0944 break; 0945 } 0946 0947 // we strip the last slash (-1) because KUrlComboBox does that as well 0948 // when operating in file-mode. If we wouldn't , dupe-finding wouldn't 0949 // work. 0950 const QString file = url.toDisplayString(QUrl::StripTrailingSlash | QUrl::PreferLocalFile); 0951 0952 // remove dupes 0953 for (int i = 1; i < d->m_locationEdit->count(); ++i) { 0954 if (d->m_locationEdit->itemText(i) == file) { 0955 d->m_locationEdit->removeItem(i--); 0956 break; 0957 } 0958 } 0959 // FIXME I don't think this works correctly when the KUrlComboBox has some default urls. 0960 // KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping 0961 // track of maxItems, and we shouldn't be able to insert items as we please. 0962 d->m_locationEdit->insertItem(1, file); 0963 } 0964 0965 d->writeViewConfig(); 0966 d->saveRecentFiles(); 0967 0968 d->addToRecentDocuments(); 0969 0970 if (!(mode() & KFile::Files)) { // single selection 0971 Q_EMIT fileSelected(d->m_url); 0972 } 0973 0974 d->m_ops->close(); 0975 } 0976 0977 void KFileWidgetPrivate::fileHighlighted(const KFileItem &i) 0978 { 0979 if ((m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty())) { // don't disturb 0980 return; 0981 } 0982 0983 if (!i.isNull() && i.isDir() && !(m_ops->mode() & KFile::Directory)) { 0984 return; 0985 } 0986 0987 const bool modified = m_locationEdit->lineEdit()->isModified(); 0988 0989 if (!(m_ops->mode() & KFile::Files)) { 0990 if (i.isNull()) { 0991 if (!modified) { 0992 setLocationText(QUrl()); 0993 } 0994 return; 0995 } 0996 0997 m_url = i.url(); 0998 0999 if (!m_locationEdit->hasFocus()) { // don't disturb while editing 1000 setLocationText(m_url); 1001 } 1002 1003 Q_EMIT q->fileHighlighted(m_url); 1004 } else { 1005 multiSelectionChanged(); 1006 Q_EMIT q->selectionChanged(); 1007 } 1008 1009 m_locationEdit->lineEdit()->setModified(false); 1010 1011 // When saving, and when double-click mode is being used, highlight the 1012 // filename after a file is single-clicked so the user has a chance to quickly 1013 // rename it if desired 1014 // Note that double-clicking will override this and overwrite regardless of 1015 // single/double click mouse setting (see slotViewDoubleClicked() ) 1016 if (m_operationMode == KFileWidget::Saving) { 1017 m_locationEdit->setFocus(); 1018 } 1019 } 1020 1021 void KFileWidgetPrivate::fileSelected(const KFileItem &i) 1022 { 1023 if (!i.isNull() && i.isDir()) { 1024 return; 1025 } 1026 1027 if (!(m_ops->mode() & KFile::Files)) { 1028 if (i.isNull()) { 1029 setLocationText(QUrl()); 1030 return; 1031 } 1032 setLocationText(i.targetUrl()); 1033 } else { 1034 multiSelectionChanged(); 1035 Q_EMIT q->selectionChanged(); 1036 } 1037 1038 // Same as above in fileHighlighted(), but for single-click mode 1039 if (m_operationMode == KFileWidget::Saving) { 1040 m_locationEdit->setFocus(); 1041 } else { 1042 q->slotOk(); 1043 } 1044 } 1045 1046 // I know it's slow to always iterate thru the whole filelist 1047 // (d->m_ops->selectedItems()), but what can we do? 1048 void KFileWidgetPrivate::multiSelectionChanged() 1049 { 1050 if (m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty()) { // don't disturb 1051 return; 1052 } 1053 1054 const KFileItemList list = m_ops->selectedItems(); 1055 1056 if (list.isEmpty()) { 1057 setLocationText(QUrl()); 1058 return; 1059 } 1060 1061 setLocationText(list.targetUrlList()); 1062 } 1063 1064 void KFileWidgetPrivate::setDummyHistoryEntry(const QString &text, const QIcon &icon, bool usePreviousPixmapIfNull) 1065 { 1066 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get 1067 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the 1068 // KDirOperator's view-selection in there 1069 const QSignalBlocker blocker(m_locationEdit); 1070 1071 bool dummyExists = m_dummyAdded; 1072 1073 int cursorPosition = m_locationEdit->lineEdit()->cursorPosition(); 1074 1075 if (m_dummyAdded) { 1076 if (!icon.isNull()) { 1077 m_locationEdit->setItemIcon(0, icon); 1078 m_locationEdit->setItemText(0, text); 1079 } else { 1080 if (!usePreviousPixmapIfNull) { 1081 m_locationEdit->setItemIcon(0, QPixmap()); 1082 } 1083 m_locationEdit->setItemText(0, text); 1084 } 1085 } else { 1086 if (!text.isEmpty()) { 1087 if (!icon.isNull()) { 1088 m_locationEdit->insertItem(0, icon, text); 1089 } else { 1090 if (!usePreviousPixmapIfNull) { 1091 m_locationEdit->insertItem(0, QPixmap(), text); 1092 } else { 1093 m_locationEdit->insertItem(0, text); 1094 } 1095 } 1096 m_dummyAdded = true; 1097 dummyExists = true; 1098 } 1099 } 1100 1101 if (dummyExists && !text.isEmpty()) { 1102 m_locationEdit->setCurrentIndex(0); 1103 } 1104 1105 m_locationEdit->lineEdit()->setCursorPosition(cursorPosition); 1106 } 1107 1108 void KFileWidgetPrivate::removeDummyHistoryEntry() 1109 { 1110 if (!m_dummyAdded) { 1111 return; 1112 } 1113 1114 // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get 1115 // emitted, so slotLocationChanged() will be called. Make sure we don't clear the 1116 // KDirOperator's view-selection in there 1117 const QSignalBlocker blocker(m_locationEdit); 1118 1119 if (m_locationEdit->count()) { 1120 m_locationEdit->removeItem(0); 1121 } 1122 m_locationEdit->setCurrentIndex(-1); 1123 m_dummyAdded = false; 1124 } 1125 1126 void KFileWidgetPrivate::setLocationText(const QUrl &url) 1127 { 1128 if (!url.isEmpty()) { 1129 if (!url.isRelative()) { 1130 const QUrl directory = url.adjusted(QUrl::RemoveFilename); 1131 if (!directory.path().isEmpty()) { 1132 q->setUrl(directory, false); 1133 } else { 1134 q->setUrl(url, false); 1135 } 1136 } 1137 1138 const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(url), QIcon::fromTheme(QStringLiteral("application-octet-stream"))); 1139 setDummyHistoryEntry(escapeDoubleQuotes(url.fileName()), mimeTypeIcon); 1140 } else { 1141 removeDummyHistoryEntry(); 1142 } 1143 1144 if (m_operationMode == KFileWidget::Saving) { 1145 setNonExtSelection(); 1146 } 1147 } 1148 1149 static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url) 1150 { 1151 if (baseUrl.isParentOf(url)) { 1152 const QString basePath(QDir::cleanPath(baseUrl.path())); 1153 QString relPath(QDir::cleanPath(url.path())); 1154 relPath.remove(0, basePath.length()); 1155 if (relPath.startsWith(QLatin1Char('/'))) { 1156 relPath.remove(0, 1); 1157 } 1158 return relPath; 1159 } else { 1160 return url.toDisplayString(); 1161 } 1162 } 1163 1164 static QString escapeDoubleQuotes(QString &&path) 1165 { 1166 // First escape the escape character that we are using 1167 path.replace(QStringLiteral("\\"), QStringLiteral("\\\\")); 1168 // Second, escape the quotes 1169 path.replace(QStringLiteral("\""), QStringLiteral("\\\"")); 1170 return path; 1171 } 1172 1173 void KFileWidgetPrivate::initDirOpWidgets() 1174 { 1175 m_opsWidget = new QWidget(q); 1176 m_opsWidgetLayout = new QVBoxLayout(m_opsWidget); 1177 m_opsWidgetLayout->setContentsMargins(0, 0, 0, 0); 1178 m_opsWidgetLayout->setSpacing(0); 1179 1180 m_model = new KFilePlacesModel(q); 1181 1182 // Don't pass "startDir" (KFileWidget constructor 1st arg) to the 1183 // KUrlNavigator at this stage: it may also contain a file name which 1184 // should not get inserted in that form into the old-style navigation 1185 // bar history. Wait until the KIO::stat has been done later. 1186 // 1187 // The stat cannot be done before this point, bug 172678. 1188 m_urlNavigator = new KUrlNavigator(m_model, QUrl(), m_opsWidget); // d->m_toolbar); 1189 m_urlNavigator->setPlacesSelectorVisible(false); 1190 1191 m_messageWidget = new KMessageWidget(q); 1192 m_messageWidget->setMessageType(KMessageWidget::Error); 1193 m_messageWidget->hide(); 1194 1195 m_ops = new KDirOperator(QUrl(), m_opsWidget); 1196 m_ops->installEventFilter(q); 1197 m_ops->setObjectName(QStringLiteral("KFileWidget::ops")); 1198 m_ops->setIsSaving(m_operationMode == KFileWidget::Saving); 1199 m_ops->setNewFileMenuSelectDirWhenAlreadyExist(true); 1200 m_ops->showOpenWithActions(true); 1201 1202 q->connect(m_ops, &KDirOperator::urlEntered, q, [this](const QUrl &url) { 1203 urlEntered(url); 1204 }); 1205 q->connect(m_ops, &KDirOperator::fileHighlighted, q, [this](const KFileItem &item) { 1206 fileHighlighted(item); 1207 }); 1208 q->connect(m_ops, &KDirOperator::fileSelected, q, [this](const KFileItem &item) { 1209 fileSelected(item); 1210 }); 1211 q->connect(m_ops, &KDirOperator::finishedLoading, q, [this]() { 1212 slotLoadingFinished(); 1213 }); 1214 q->connect(m_ops, &KDirOperator::keyEnterReturnPressed, q, [this]() { 1215 slotViewKeyEnterReturnPressed(); 1216 }); 1217 q->connect(m_ops, &KDirOperator::renamingFinished, q, [this](const QList<QUrl> &urls) { 1218 // Update file names in location text field after renaming selected files 1219 q->setSelectedUrls(urls); 1220 }); 1221 1222 m_ops->dirLister()->setAutoErrorHandlingEnabled(false); 1223 q->connect(m_ops->dirLister(), &KDirLister::jobError, q, [this](KIO::Job *job) { 1224 m_messageWidget->setText(job->errorString()); 1225 m_messageWidget->animatedShow(); 1226 }); 1227 1228 m_ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions); 1229 1230 initToolbar(); 1231 1232 m_opsWidgetLayout->addWidget(m_toolbar); 1233 m_opsWidgetLayout->addWidget(m_urlNavigator); 1234 m_opsWidgetLayout->addWidget(m_messageWidget); 1235 m_opsWidgetLayout->addWidget(m_ops); 1236 } 1237 1238 void KFileWidgetPrivate::initZoomWidget() 1239 { 1240 m_iconSizeSlider = new QSlider(q); 1241 m_iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); 1242 m_iconSizeSlider->setMinimumWidth(40); 1243 m_iconSizeSlider->setOrientation(Qt::Horizontal); 1244 m_iconSizeSlider->setMinimum(0); 1245 m_iconSizeSlider->setMaximum(m_stdIconSizes.size() - 1); 1246 m_iconSizeSlider->setSingleStep(1); 1247 m_iconSizeSlider->setPageStep(1); 1248 m_iconSizeSlider->setTickPosition(QSlider::TicksBelow); 1249 1250 q->connect(m_iconSizeSlider, &QAbstractSlider::valueChanged, q, [this](int step) { 1251 slotIconSizeChanged(m_stdIconSizes[step]); 1252 }); 1253 1254 q->connect(m_iconSizeSlider, &QAbstractSlider::sliderMoved, q, [this](int step) { 1255 slotIconSizeSliderMoved(m_stdIconSizes[step]); 1256 }); 1257 1258 q->connect(m_ops, &KDirOperator::currentIconSizeChanged, q, [this](int iconSize) { 1259 slotDirOpIconSizeChanged(iconSize); 1260 }); 1261 1262 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1263 KActionCollection *coll = m_ops->actionCollection(); 1264 #endif 1265 1266 m_zoomOutAction = KStandardAction::create( 1267 KStandardAction::ZoomOut, 1268 q, 1269 [this]() { 1270 changeIconsSize(ZoomOut); 1271 }, 1272 q); 1273 1274 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1275 coll->addAction(QStringLiteral("zoom_out"), m_zoomOutAction); 1276 #else 1277 q->addAction(m_zoomOutAction); 1278 #endif 1279 1280 m_zoomInAction = KStandardAction::create( 1281 KStandardAction::ZoomIn, 1282 q, 1283 [this]() { 1284 changeIconsSize(ZoomIn); 1285 }, 1286 q); 1287 1288 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1289 coll->addAction(QStringLiteral("zoom_in"), m_zoomInAction); 1290 #else 1291 q->addAction(m_zoomInAction); 1292 #endif 1293 } 1294 1295 void KFileWidgetPrivate::initToolbar() 1296 { 1297 m_toolbar = new KToolBar(m_opsWidget, true); 1298 m_toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar")); 1299 m_toolbar->setMovable(false); 1300 1301 // add nav items to the toolbar 1302 // 1303 // NOTE: The order of the button icons here differs from that 1304 // found in the file manager and web browser, but has been discussed 1305 // and agreed upon on the kde-core-devel mailing list: 1306 // 1307 // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2 1308 1309 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1310 KActionCollection *coll = m_ops->actionCollection(); 1311 #endif 1312 1313 m_ops->action(KDirOperator::Up) 1314 ->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />" 1315 "For instance, if the current location is file:/home/konqi clicking this " 1316 "button will take you to file:/home.</qt>")); 1317 1318 m_ops->action(KDirOperator::Back)->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history.")); 1319 m_ops->action(KDirOperator::Forward)->setWhatsThis(i18n("Click this button to move forward one step in the browsing history.")); 1320 1321 m_ops->action(KDirOperator::Reload)->setWhatsThis(i18n("Click this button to reload the contents of the current location.")); 1322 m_ops->action(KDirOperator::NewFolder)->setShortcuts(KStandardShortcut::createFolder()); 1323 m_ops->action(KDirOperator::NewFolder)->setWhatsThis(i18n("Click this button to create a new folder.")); 1324 1325 m_togglePlacesPanelAction = new KToggleAction(i18n("Show Places Panel"), q); 1326 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1327 coll->addAction(QStringLiteral("togglePlacesPanel"), m_togglePlacesPanelAction); 1328 #else 1329 q->addAction(m_togglePlacesPanelAction); 1330 #endif 1331 m_togglePlacesPanelAction->setShortcut(QKeySequence(Qt::Key_F9)); 1332 q->connect(m_togglePlacesPanelAction, &QAction::toggled, q, [this](bool show) { 1333 togglePlacesPanel(show); 1334 }); 1335 1336 m_toggleBookmarksAction = new KToggleAction(i18n("Show Bookmarks Button"), q); 1337 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1338 coll->addAction(QStringLiteral("toggleBookmarks"), m_toggleBookmarksAction); 1339 #else 1340 q->addAction(m_toggleBookmarksAction); 1341 #endif 1342 q->connect(m_toggleBookmarksAction, &QAction::toggled, q, [this](bool show) { 1343 toggleBookmarks(show); 1344 }); 1345 1346 // Build the settings menu 1347 KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), q); 1348 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1349 coll->addAction(QStringLiteral("extra menu"), menu); 1350 #else 1351 q->addAction(menu); 1352 #endif 1353 menu->setWhatsThis( 1354 i18n("<qt>This is the preferences menu for the file dialog. " 1355 "Various options can be accessed from this menu including: <ul>" 1356 "<li>how files are sorted in the list</li>" 1357 "<li>types of view, including icon and list</li>" 1358 "<li>showing of hidden files</li>" 1359 "<li>the Places panel</li>" 1360 "<li>file previews</li>" 1361 "<li>separating folders from files</li></ul></qt>")); 1362 1363 menu->addAction(m_ops->action(KDirOperator::AllowExpansionInDetailsView)); 1364 menu->addSeparator(); 1365 menu->addAction(m_ops->action(KDirOperator::ShowHiddenFiles)); 1366 menu->addAction(m_togglePlacesPanelAction); 1367 menu->addAction(m_toggleBookmarksAction); 1368 menu->addAction(m_ops->action(KDirOperator::ShowPreviewPanel)); 1369 1370 menu->setPopupMode(QToolButton::InstantPopup); 1371 q->connect(menu->menu(), &QMenu::aboutToShow, m_ops, &KDirOperator::updateSelectionDependentActions); 1372 1373 m_bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), q); 1374 m_bookmarkButton->setPopupMode(QToolButton::InstantPopup); 1375 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 1376 coll->addAction(QStringLiteral("bookmark"), m_bookmarkButton); 1377 #else 1378 q->addAction(m_bookmarkButton); 1379 #endif 1380 m_bookmarkButton->setWhatsThis( 1381 i18n("<qt>This button allows you to bookmark specific locations. " 1382 "Click on this button to open the bookmark menu where you may add, " 1383 "edit or select a bookmark.<br /><br />" 1384 "These bookmarks are specific to the file dialog, but otherwise operate " 1385 "like bookmarks elsewhere in KDE.</qt>")); 1386 1387 QWidget *midSpacer = new QWidget(q); 1388 midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 1389 1390 m_toolbar->addAction(m_ops->action(KDirOperator::Back)); 1391 m_toolbar->addAction(m_ops->action(KDirOperator::Forward)); 1392 m_toolbar->addAction(m_ops->action(KDirOperator::Up)); 1393 m_toolbar->addAction(m_ops->action(KDirOperator::Reload)); 1394 m_toolbar->addSeparator(); 1395 m_toolbar->addAction(m_ops->action(KDirOperator::ViewIconsView)); 1396 m_toolbar->addAction(m_ops->action(KDirOperator::ViewCompactView)); 1397 m_toolbar->addAction(m_ops->action(KDirOperator::ViewDetailsView)); 1398 m_toolbar->addSeparator(); 1399 m_toolbar->addAction(m_ops->action(KDirOperator::ShowPreview)); 1400 m_toolbar->addAction(m_ops->action(KDirOperator::SortMenu)); 1401 m_toolbar->addAction(m_bookmarkButton); 1402 1403 m_toolbar->addWidget(midSpacer); 1404 1405 initZoomWidget(); 1406 m_toolbar->addAction(m_zoomOutAction); 1407 m_toolbar->addWidget(m_iconSizeSlider); 1408 m_toolbar->addAction(m_zoomInAction); 1409 m_toolbar->addSeparator(); 1410 1411 m_toolbar->addAction(m_ops->action(KDirOperator::NewFolder)); 1412 m_toolbar->addAction(menu); 1413 1414 m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); 1415 m_toolbar->setMovable(false); 1416 } 1417 1418 void KFileWidgetPrivate::initLocationWidget() 1419 { 1420 m_locationLabel = new QLabel(i18n("&Name:"), q); 1421 m_locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, q); 1422 m_locationEdit->installEventFilter(q); 1423 // Properly let the dialog be resized (to smaller). Otherwise we could have 1424 // huge dialogs that can't be resized to smaller (it would be as big as the longest 1425 // item in this combo box). (ereslibre) 1426 m_locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); 1427 q->connect(m_locationEdit, &KUrlComboBox::editTextChanged, q, [this](const QString &text) { 1428 slotLocationChanged(text); 1429 }); 1430 1431 updateLocationWhatsThis(); 1432 m_locationLabel->setBuddy(m_locationEdit); 1433 1434 KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion); 1435 m_locationEdit->setCompletionObject(fileCompletionObj); 1436 m_locationEdit->setAutoDeleteCompletionObject(true); 1437 q->connect(fileCompletionObj, &KUrlCompletion::match, q, [this](const QString &match) { 1438 fileCompletion(match); 1439 }); 1440 1441 q->connect(m_locationEdit, qOverload<const QString &>(&KUrlComboBox::returnPressed), q, [this](const QString &text) { 1442 locationAccepted(text); 1443 }); 1444 } 1445 1446 void KFileWidgetPrivate::initFilterWidget() 1447 { 1448 m_filterLabel = new QLabel(q); 1449 m_filterWidget = new KFileFilterCombo(q); 1450 updateFilterText(); 1451 // Properly let the dialog be resized (to smaller). Otherwise we could have 1452 // huge dialogs that can't be resized to smaller (it would be as big as the longest 1453 // item in this combo box). (ereslibre) 1454 m_filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); 1455 m_filterLabel->setBuddy(m_filterWidget); 1456 q->connect(m_filterWidget, &KFileFilterCombo::filterChanged, q, [this]() { 1457 slotFilterChanged(); 1458 }); 1459 1460 m_filterDelayTimer.setSingleShot(true); 1461 m_filterDelayTimer.setInterval(300); 1462 q->connect(m_filterWidget, &QComboBox::editTextChanged, &m_filterDelayTimer, qOverload<>(&QTimer::start)); 1463 q->connect(&m_filterDelayTimer, &QTimer::timeout, q, [this]() { 1464 slotFilterChanged(); 1465 }); 1466 } 1467 1468 void KFileWidgetPrivate::setLocationText(const QList<QUrl> &urlList) 1469 { 1470 const QUrl currUrl = m_ops->url(); 1471 1472 if (urlList.count() > 1) { 1473 QString urls; 1474 for (const QUrl &url : urlList) { 1475 urls += QStringLiteral("\"%1\" ").arg(escapeDoubleQuotes(relativePathOrUrl(currUrl, url))); 1476 } 1477 urls.chop(1); 1478 1479 setDummyHistoryEntry(urls, QIcon(), false); 1480 } else if (urlList.count() == 1) { 1481 const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(urlList[0]), QIcon::fromTheme(QStringLiteral("application-octet-stream"))); 1482 setDummyHistoryEntry(escapeDoubleQuotes(relativePathOrUrl(currUrl, urlList[0])), mimeTypeIcon); 1483 } else { 1484 removeDummyHistoryEntry(); 1485 } 1486 1487 if (m_operationMode == KFileWidget::Saving) { 1488 setNonExtSelection(); 1489 } 1490 } 1491 1492 void KFileWidgetPrivate::updateLocationWhatsThis() 1493 { 1494 const QString autocompletionWhatsThisText = i18n( 1495 "<qt>While typing in the text area, you may be presented " 1496 "with possible matches. " 1497 "This feature can be controlled by clicking with the right mouse button " 1498 "and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>"); 1499 1500 QString whatsThisText; 1501 if (m_operationMode == KFileWidget::Saving) { 1502 whatsThisText = QLatin1String("<qt>") + i18n("This is the name to save the file as.") + autocompletionWhatsThisText; 1503 } else if (m_ops->mode() & KFile::Files) { 1504 whatsThisText = QLatin1String("<qt>") 1505 + i18n("This is the list of files to open. More than " 1506 "one file can be specified by listing several " 1507 "files, separated by spaces.") 1508 + autocompletionWhatsThisText; 1509 } else { 1510 whatsThisText = QLatin1String("<qt>") + i18n("This is the name of the file to open.") + autocompletionWhatsThisText; 1511 } 1512 1513 m_locationLabel->setWhatsThis(whatsThisText); 1514 m_locationEdit->setWhatsThis(whatsThisText); 1515 } 1516 1517 void KFileWidgetPrivate::initPlacesPanel() 1518 { 1519 if (m_placesDock) { 1520 return; 1521 } 1522 1523 m_placesDock = new QDockWidget(i18nc("@title:window", "Places"), q); 1524 m_placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures); 1525 m_placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(m_placesDock)); 1526 1527 m_placesView = new KFilePlacesView(m_placesDock); 1528 m_placesView->setModel(m_model); 1529 m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 1530 1531 m_placesView->setObjectName(QStringLiteral("url bar")); 1532 QObject::connect(m_placesView, &KFilePlacesView::urlChanged, q, [this](const QUrl &url) { 1533 enterUrl(url); 1534 }); 1535 1536 QObject::connect(qobject_cast<KFilePlacesModel *>(m_placesView->model()), &KFilePlacesModel::errorMessage, q, [this](const QString &errorMessage) { 1537 m_messageWidget->setText(errorMessage); 1538 m_messageWidget->animatedShow(); 1539 }); 1540 1541 // need to set the current url of the urlbar manually (not via urlEntered() 1542 // here, because the initial url of KDirOperator might be the same as the 1543 // one that will be set later (and then urlEntered() won't be emitted). 1544 // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone. 1545 m_placesView->setUrl(m_url); 1546 1547 m_placesDock->setWidget(m_placesView); 1548 m_placesViewSplitter->insertWidget(0, m_placesDock); 1549 1550 // initialize the size of the splitter 1551 m_placesViewWidth = m_configGroup.readEntry(SpeedbarWidth, m_placesView->sizeHint().width()); 1552 1553 // Needed for when the dialog is shown with the places panel initially hidden 1554 setPlacesViewSplitterSizes(); 1555 1556 QObject::connect(m_placesDock, &QDockWidget::visibilityChanged, q, [this](bool visible) { 1557 togglePlacesPanel(visible, m_placesDock); 1558 }); 1559 } 1560 1561 void KFileWidgetPrivate::setPlacesViewSplitterSizes() 1562 { 1563 if (m_placesViewWidth > 0) { 1564 QList<int> sizes = m_placesViewSplitter->sizes(); 1565 sizes[0] = m_placesViewWidth; 1566 sizes[1] = q->width() - m_placesViewWidth - m_placesViewSplitter->handleWidth(); 1567 m_placesViewSplitter->setSizes(sizes); 1568 } 1569 } 1570 1571 void KFileWidgetPrivate::setLafBoxColumnWidth() 1572 { 1573 // In order to perfectly align the filename widget with KDirOperator's icon view 1574 // - m_placesViewWidth needs to account for the size of the splitter handle 1575 // - the m_lafBox grid layout spacing should only affect the label, but not the line edit 1576 const int adjustment = m_placesViewSplitter->handleWidth() - m_lafBox->horizontalSpacing(); 1577 m_lafBox->setColumnMinimumWidth(0, m_placesViewWidth + adjustment); 1578 } 1579 1580 void KFileWidgetPrivate::initGUI() 1581 { 1582 delete m_boxLayout; // deletes all sub layouts 1583 1584 m_boxLayout = new QVBoxLayout(q); 1585 m_boxLayout->setContentsMargins(0, 0, 0, 0); // no additional margin to the already existing 1586 1587 m_placesViewSplitter = new QSplitter(q); 1588 m_placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 1589 m_placesViewSplitter->setChildrenCollapsible(false); 1590 m_boxLayout->addWidget(m_placesViewSplitter); 1591 1592 QObject::connect(m_placesViewSplitter, &QSplitter::splitterMoved, q, [this](int pos, int index) { 1593 placesViewSplitterMoved(pos, index); 1594 }); 1595 m_placesViewSplitter->insertWidget(0, m_opsWidget); 1596 1597 m_vbox = new QVBoxLayout(); 1598 m_vbox->setContentsMargins(0, 0, 0, 0); 1599 m_boxLayout->addLayout(m_vbox); 1600 1601 m_lafBox = new QGridLayout(); 1602 1603 m_lafBox->addWidget(m_locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight); 1604 m_lafBox->addWidget(m_locationEdit, 0, 1, Qt::AlignVCenter); 1605 m_lafBox->addWidget(m_okButton, 0, 2, Qt::AlignVCenter); 1606 1607 m_lafBox->addWidget(m_filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight); 1608 m_lafBox->addWidget(m_filterWidget, 1, 1, Qt::AlignVCenter); 1609 m_lafBox->addWidget(m_cancelButton, 1, 2, Qt::AlignVCenter); 1610 1611 m_lafBox->setColumnStretch(1, 4); 1612 1613 m_vbox->addLayout(m_lafBox); 1614 1615 // Add the "Automatically Select Extension" checkbox 1616 const int spacingHint = q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing); 1617 m_vbox->addSpacing(spacingHint); 1618 m_vbox->addWidget(m_autoSelectExtCheckBox); 1619 1620 q->setTabOrder(m_ops, m_autoSelectExtCheckBox); 1621 q->setTabOrder(m_autoSelectExtCheckBox, m_locationEdit); 1622 q->setTabOrder(m_locationEdit, m_filterWidget); 1623 q->setTabOrder(m_filterWidget, m_okButton); 1624 q->setTabOrder(m_okButton, m_cancelButton); 1625 q->setTabOrder(m_cancelButton, m_urlNavigator); 1626 q->setTabOrder(m_urlNavigator, m_ops); 1627 } 1628 1629 void KFileWidgetPrivate::slotFilterChanged() 1630 { 1631 // qDebug(); 1632 1633 m_filterDelayTimer.stop(); 1634 1635 QString filter = m_filterWidget->currentFilter(); 1636 m_ops->clearFilter(); 1637 1638 if (filter.contains(QLatin1Char('/'))) { 1639 QStringList types = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 1640 types.prepend(QStringLiteral("inode/directory")); 1641 m_ops->setMimeFilter(types); 1642 } else if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) { 1643 m_ops->setNameFilter(filter); 1644 } else { 1645 m_ops->setNameFilter(QLatin1Char('*') + filter.replace(QLatin1Char(' '), QLatin1Char('*')) + QLatin1Char('*')); 1646 } 1647 1648 updateAutoSelectExtension(); 1649 1650 m_ops->updateDir(); 1651 1652 Q_EMIT q->filterChanged(filter); 1653 } 1654 1655 void KFileWidget::setUrl(const QUrl &url, bool clearforward) 1656 { 1657 // qDebug(); 1658 1659 d->m_ops->setUrl(url, clearforward); 1660 } 1661 1662 // Protected 1663 void KFileWidgetPrivate::urlEntered(const QUrl &url) 1664 { 1665 // qDebug(); 1666 1667 KUrlComboBox *pathCombo = m_urlNavigator->editor(); 1668 if (pathCombo->count() != 0) { // little hack 1669 pathCombo->setUrl(url); 1670 } 1671 1672 bool blocked = m_locationEdit->blockSignals(true); 1673 if (m_keepLocation) { 1674 const QUrl currentUrl = urlFromString(locationEditCurrentText()); 1675 // iconNameForUrl will get the icon or fallback to a generic one 1676 m_locationEdit->setItemIcon(0, QIcon::fromTheme(KIO::iconNameForUrl(currentUrl))); 1677 // Preserve the text when clicking on the view (cf fileHighlighted) 1678 m_locationEdit->lineEdit()->setModified(true); 1679 } 1680 1681 m_locationEdit->blockSignals(blocked); 1682 1683 m_urlNavigator->setLocationUrl(url); 1684 1685 // is triggered in ctor before completion object is set 1686 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject()); 1687 if (completion) { 1688 completion->setDir(url); 1689 } 1690 1691 if (m_placesView) { 1692 m_placesView->setUrl(url); 1693 } 1694 1695 m_messageWidget->hide(); 1696 } 1697 1698 void KFileWidgetPrivate::locationAccepted(const QString &url) 1699 { 1700 Q_UNUSED(url); 1701 // qDebug(); 1702 q->slotOk(); 1703 } 1704 1705 void KFileWidgetPrivate::enterUrl(const QUrl &url) 1706 { 1707 // qDebug(); 1708 1709 // append '/' if needed: url combo does not add it 1710 // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename) 1711 QUrl u(url); 1712 Utils::appendSlashToPath(u); 1713 q->setUrl(u); 1714 1715 // We need to check window()->focusWidget() instead of m_locationEdit->hasFocus 1716 // because when the window is showing up m_locationEdit 1717 // may still not have focus but it'll be the one that will have focus when the window 1718 // gets it and we don't want to steal its focus either 1719 if (q->window()->focusWidget() != m_locationEdit) { 1720 m_ops->setFocus(); 1721 } 1722 } 1723 1724 void KFileWidgetPrivate::enterUrl(const QString &url) 1725 { 1726 // qDebug(); 1727 1728 enterUrl(urlFromString(KUrlCompletion::replacedPath(url, true, true))); 1729 } 1730 1731 bool KFileWidgetPrivate::toOverwrite(const QUrl &url) 1732 { 1733 // qDebug(); 1734 1735 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 1736 KJobWidgets::setWindow(statJob, q); 1737 bool res = statJob->exec(); 1738 1739 if (res) { 1740 int ret = KMessageBox::warningContinueCancel(q, 1741 i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()), 1742 i18n("Overwrite File?"), 1743 KStandardGuiItem::overwrite(), 1744 KStandardGuiItem::cancel(), 1745 QString(), 1746 KMessageBox::Notify | KMessageBox::Dangerous); 1747 1748 if (ret != KMessageBox::Continue) { 1749 m_locationEdit->setFocus(); 1750 setNonExtSelection(); 1751 1752 return false; 1753 } 1754 return true; 1755 } 1756 1757 return true; 1758 } 1759 1760 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 33) 1761 void KFileWidget::setSelection(const QString &url) 1762 { 1763 // qDebug() << "setSelection " << url; 1764 1765 if (url.isEmpty()) { 1766 return; 1767 } 1768 1769 QUrl u = d->getCompleteUrl(url); 1770 if (!u.isValid()) { 1771 // Relative path was treated as URL, but it was found to be invalid. 1772 qWarning() << url << " is not a correct argument for setSelection!"; 1773 return; 1774 } 1775 1776 setSelectedUrl(urlFromString(url)); 1777 } 1778 #endif 1779 1780 void KFileWidget::setSelectedUrl(const QUrl &url) 1781 { 1782 // Honor protocols that do not support directory listing 1783 if (!url.isRelative() && !KProtocolManager::supportsListing(url)) { 1784 return; 1785 } 1786 d->setLocationText(url); 1787 } 1788 1789 void KFileWidget::setSelectedUrls(const QList<QUrl> &urls) 1790 { 1791 if (urls.isEmpty()) { 1792 return; 1793 } 1794 1795 // Honor protocols that do not support directory listing 1796 if (!urls[0].isRelative() && !KProtocolManager::supportsListing(urls[0])) { 1797 return; 1798 } 1799 d->setLocationText(urls); 1800 } 1801 1802 void KFileWidgetPrivate::slotLoadingFinished() 1803 { 1804 const QString currentText = m_locationEdit->currentText(); 1805 if (currentText.isEmpty()) { 1806 return; 1807 } 1808 1809 m_ops->blockSignals(true); 1810 QUrl u(m_ops->url()); 1811 if (currentText.startsWith(QLatin1Char('/'))) { 1812 u.setPath(currentText); 1813 } else { 1814 u.setPath(Utils::concatPaths(m_ops->url().path(), currentText)); 1815 } 1816 m_ops->setCurrentItem(u); 1817 m_ops->blockSignals(false); 1818 } 1819 1820 void KFileWidgetPrivate::fileCompletion(const QString &match) 1821 { 1822 // qDebug(); 1823 1824 if (match.isEmpty() || m_locationEdit->currentText().contains(QLatin1Char('"'))) { 1825 return; 1826 } 1827 1828 const QUrl url = urlFromString(match); 1829 const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(url), QIcon::fromTheme(QStringLiteral("application-octet-stream"))); 1830 setDummyHistoryEntry(m_locationEdit->currentText(), mimeTypeIcon, !m_locationEdit->currentText().isEmpty()); 1831 } 1832 1833 void KFileWidgetPrivate::slotLocationChanged(const QString &text) 1834 { 1835 // qDebug(); 1836 1837 m_locationEdit->lineEdit()->setModified(true); 1838 1839 if (text.isEmpty() && m_ops->view()) { 1840 m_ops->view()->clearSelection(); 1841 } 1842 1843 if (text.isEmpty()) { 1844 removeDummyHistoryEntry(); 1845 } else { 1846 setDummyHistoryEntry(text); 1847 } 1848 1849 if (!m_locationEdit->lineEdit()->text().isEmpty()) { 1850 const QList<QUrl> urlList(tokenize(text)); 1851 m_ops->setCurrentItems(urlList); 1852 } 1853 1854 updateFilter(); 1855 } 1856 1857 QUrl KFileWidget::selectedUrl() const 1858 { 1859 // qDebug(); 1860 1861 if (d->m_inAccept) { 1862 return d->m_url; 1863 } else { 1864 return QUrl(); 1865 } 1866 } 1867 1868 QList<QUrl> KFileWidget::selectedUrls() const 1869 { 1870 // qDebug(); 1871 1872 QList<QUrl> list; 1873 if (d->m_inAccept) { 1874 if (d->m_ops->mode() & KFile::Files) { 1875 list = d->m_urlList; 1876 } else { 1877 list.append(d->m_url); 1878 } 1879 } 1880 return list; 1881 } 1882 1883 QList<QUrl> KFileWidgetPrivate::tokenize(const QString &line) const 1884 { 1885 qCDebug(KIO_KFILEWIDGETS_FW) << "Tokenizing:" << line; 1886 1887 QList<QUrl> urls; 1888 QUrl baseUrl(m_ops->url().adjusted(QUrl::RemoveFilename)); 1889 Utils::appendSlashToPath(baseUrl); 1890 1891 // A helper that creates, validates and appends a new url based 1892 // on the given filename. 1893 auto addUrl = [baseUrl, &urls](const QString &partial_name) { 1894 if (partial_name.trimmed().isEmpty()) { 1895 return; 1896 } 1897 1898 // url could be absolute 1899 QUrl partial_url(partial_name); 1900 if (!partial_url.isValid() 1901 || partial_url.isRelative() 1902 // the text might look like a url scheme but not be a real one 1903 || (!partial_url.scheme().isEmpty() && (!partial_name.contains(QStringLiteral("://")) || !KProtocolInfo::isKnownProtocol(partial_url.scheme())))) { 1904 // We have to use setPath here, so that something like "test#file" 1905 // isn't interpreted to have path "test" and fragment "file". 1906 partial_url.clear(); 1907 partial_url.setPath(partial_name); 1908 } 1909 1910 // This returns QUrl(partial_name) for absolute URLs. 1911 // Otherwise, returns the concatenated url. 1912 if (partial_url.isRelative() || baseUrl.isParentOf(partial_url)) { 1913 partial_url = baseUrl.resolved(partial_url); 1914 } 1915 1916 if (partial_url.isValid()) { 1917 urls.append(partial_url); 1918 } else { 1919 // This can happen in the first quote! (ex: ' "something here"') 1920 qCDebug(KIO_KFILEWIDGETS_FW) << "Discarding Invalid" << partial_url; 1921 } 1922 }; 1923 1924 // An iterative approach here where we toggle the "escape" flag 1925 // if we hit `\`. If we hit `"` and the escape flag is false, 1926 // we split 1927 QString partial_name; 1928 bool escape = false; 1929 for (int i = 0; i < line.length(); i++) { 1930 const QChar ch = line[i]; 1931 1932 // Handle any character previously escaped 1933 if (escape) { 1934 partial_name += ch; 1935 escape = false; 1936 continue; 1937 } 1938 1939 // Handle escape start 1940 if (ch.toLatin1() == '\\') { 1941 escape = true; 1942 continue; 1943 } 1944 1945 // Handle UNESCAPED quote (") since the above ifs are 1946 // dealing with the escaped ones 1947 if (ch.toLatin1() == '"') { 1948 addUrl(partial_name); 1949 partial_name.clear(); 1950 continue; 1951 } 1952 1953 // Any other character just append 1954 partial_name += ch; 1955 } 1956 1957 // Handle the last item which is buffered in partial_name. This is 1958 // required for single-file selection dialogs since the name will not 1959 // be wrapped in quotes 1960 if (!partial_name.isEmpty()) { 1961 addUrl(partial_name); 1962 partial_name.clear(); 1963 } 1964 1965 return urls; 1966 } 1967 1968 QString KFileWidget::selectedFile() const 1969 { 1970 // qDebug(); 1971 1972 if (d->m_inAccept) { 1973 const QUrl url = d->mostLocalUrl(d->m_url); 1974 if (url.isLocalFile()) { 1975 return url.toLocalFile(); 1976 } else { 1977 KMessageBox::error(const_cast<KFileWidget *>(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted")); 1978 } 1979 } 1980 return QString(); 1981 } 1982 1983 QStringList KFileWidget::selectedFiles() const 1984 { 1985 // qDebug(); 1986 1987 QStringList list; 1988 1989 if (d->m_inAccept) { 1990 if (d->m_ops->mode() & KFile::Files) { 1991 const QList<QUrl> urls = d->m_urlList; 1992 for (const auto &u : urls) { 1993 const QUrl url = d->mostLocalUrl(u); 1994 if (url.isLocalFile()) { 1995 list.append(url.toLocalFile()); 1996 } 1997 } 1998 } 1999 2000 else { // single-selection mode 2001 if (d->m_url.isLocalFile()) { 2002 list.append(d->m_url.toLocalFile()); 2003 } 2004 } 2005 } 2006 2007 return list; 2008 } 2009 2010 QUrl KFileWidget::baseUrl() const 2011 { 2012 return d->m_ops->url(); 2013 } 2014 2015 void KFileWidget::resizeEvent(QResizeEvent *event) 2016 { 2017 QWidget::resizeEvent(event); 2018 2019 if (d->m_placesDock) { 2020 // we don't want our places dock actually changing size when we resize 2021 // and qt doesn't make it easy to enforce such a thing with QSplitter 2022 d->setPlacesViewSplitterSizes(); 2023 } 2024 } 2025 2026 void KFileWidget::showEvent(QShowEvent *event) 2027 { 2028 if (!d->m_hasView) { // delayed view-creation 2029 Q_ASSERT(d); 2030 Q_ASSERT(d->m_ops); 2031 d->m_ops->setViewMode(KFile::Default); 2032 d->m_hasView = true; 2033 2034 connect(d->m_ops->view(), &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &index) { 2035 d->slotViewDoubleClicked(index); 2036 }); 2037 } 2038 d->m_ops->clearHistory(); 2039 2040 QWidget::showEvent(event); 2041 } 2042 2043 bool KFileWidget::eventFilter(QObject *watched, QEvent *event) 2044 { 2045 const bool res = QWidget::eventFilter(watched, event); 2046 2047 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event); 2048 if (!keyEvent) { 2049 return res; 2050 } 2051 2052 const auto type = event->type(); 2053 const auto key = keyEvent->key(); 2054 2055 if (watched == d->m_ops && type == QEvent::KeyPress && (key == Qt::Key_Return || key == Qt::Key_Enter)) { 2056 // ignore return events from the KDirOperator 2057 // they are not needed, activated is used to handle this case 2058 event->accept(); 2059 return true; 2060 } 2061 2062 return res; 2063 } 2064 2065 void KFileWidget::setMode(KFile::Modes m) 2066 { 2067 // qDebug(); 2068 2069 d->m_ops->setMode(m); 2070 if (d->m_ops->dirOnlyMode()) { 2071 d->m_filterWidget->setDefaultFilter(i18n("*|All Folders")); 2072 } else { 2073 d->m_filterWidget->setDefaultFilter(i18n("*|All Files")); 2074 } 2075 2076 d->updateAutoSelectExtension(); 2077 } 2078 2079 KFile::Modes KFileWidget::mode() const 2080 { 2081 return d->m_ops->mode(); 2082 } 2083 2084 void KFileWidgetPrivate::readViewConfig() 2085 { 2086 m_ops->setViewConfig(m_configGroup); 2087 m_ops->readConfig(m_configGroup); 2088 KUrlComboBox *combo = m_urlNavigator->editor(); 2089 2090 KCompletion::CompletionMode cm = 2091 (KCompletion::CompletionMode)m_configGroup.readEntry(PathComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup)); 2092 if (cm != KCompletion::CompletionPopup) { 2093 combo->setCompletionMode(cm); 2094 } 2095 2096 cm = (KCompletion::CompletionMode)m_configGroup.readEntry(LocationComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup)); 2097 if (cm != KCompletion::CompletionPopup) { 2098 m_locationEdit->setCompletionMode(cm); 2099 } 2100 2101 // Show or don't show the places panel 2102 togglePlacesPanel(m_configGroup.readEntry(ShowSpeedbar, true)); 2103 2104 // show or don't show the bookmarks 2105 toggleBookmarks(m_configGroup.readEntry(ShowBookmarks, false)); 2106 2107 // does the user want Automatically Select Extension? 2108 m_autoSelectExtChecked = m_configGroup.readEntry(AutoSelectExtChecked, DefaultAutoSelectExtChecked); 2109 updateAutoSelectExtension(); 2110 2111 // should the URL navigator use the breadcrumb navigation? 2112 m_urlNavigator->setUrlEditable(!m_configGroup.readEntry(BreadcrumbNavigation, true)); 2113 2114 // should the URL navigator show the full path? 2115 m_urlNavigator->setShowFullPath(m_configGroup.readEntry(ShowFullPath, false)); 2116 2117 int w1 = q->minimumSize().width(); 2118 int w2 = m_toolbar->sizeHint().width(); 2119 if (w1 < w2) { 2120 q->setMinimumWidth(w2); 2121 } 2122 } 2123 2124 void KFileWidgetPrivate::writeViewConfig() 2125 { 2126 // these settings are global settings; ALL instances of the file dialog 2127 // should reflect them. 2128 // There is no way to tell KFileOperator::writeConfig() to write to 2129 // kdeglobals so we write settings to a temporary config group then copy 2130 // them all to kdeglobals 2131 KConfig tmp(QString(), KConfig::SimpleConfig); 2132 KConfigGroup tmpGroup(&tmp, ConfigGroup); 2133 2134 KUrlComboBox *pathCombo = m_urlNavigator->editor(); 2135 // saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global ); 2136 tmpGroup.writeEntry(PathComboCompletionMode, static_cast<int>(pathCombo->completionMode())); 2137 tmpGroup.writeEntry(LocationComboCompletionMode, static_cast<int>(m_locationEdit->completionMode())); 2138 2139 const bool showPlacesPanel = m_placesDock && !m_placesDock->isHidden(); 2140 tmpGroup.writeEntry(ShowSpeedbar, showPlacesPanel); 2141 if (m_placesViewWidth > 0) { 2142 tmpGroup.writeEntry(SpeedbarWidth, m_placesViewWidth); 2143 } 2144 2145 tmpGroup.writeEntry(ShowBookmarks, m_bookmarkHandler != nullptr); 2146 tmpGroup.writeEntry(AutoSelectExtChecked, m_autoSelectExtChecked); 2147 tmpGroup.writeEntry(BreadcrumbNavigation, !m_urlNavigator->isUrlEditable()); 2148 tmpGroup.writeEntry(ShowFullPath, m_urlNavigator->showFullPath()); 2149 2150 m_ops->writeConfig(tmpGroup); 2151 2152 // Copy saved settings to kdeglobals 2153 tmpGroup.copyTo(&m_configGroup, KConfigGroup::Persistent | KConfigGroup::Global); 2154 } 2155 2156 void KFileWidgetPrivate::readRecentFiles() 2157 { 2158 // qDebug(); 2159 2160 const bool oldState = m_locationEdit->blockSignals(true); 2161 m_locationEdit->setMaxItems(m_configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber)); 2162 m_locationEdit->setUrls(m_configGroup.readPathEntry(RecentFiles, QStringList()), KUrlComboBox::RemoveBottom); 2163 m_locationEdit->setCurrentIndex(-1); 2164 m_locationEdit->blockSignals(oldState); 2165 2166 KUrlComboBox *combo = m_urlNavigator->editor(); 2167 combo->setUrls(m_configGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop); 2168 combo->setMaxItems(m_configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber)); 2169 combo->setUrl(m_ops->url()); 2170 // since we delayed this moment, initialize the directory of the completion object to 2171 // our current directory (that was very probably set on the constructor) 2172 KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject()); 2173 if (completion) { 2174 completion->setDir(m_ops->url()); 2175 } 2176 } 2177 2178 void KFileWidgetPrivate::saveRecentFiles() 2179 { 2180 // qDebug(); 2181 m_configGroup.writePathEntry(RecentFiles, m_locationEdit->urls()); 2182 2183 KUrlComboBox *pathCombo = m_urlNavigator->editor(); 2184 m_configGroup.writePathEntry(RecentURLs, pathCombo->urls()); 2185 } 2186 2187 QPushButton *KFileWidget::okButton() const 2188 { 2189 return d->m_okButton; 2190 } 2191 2192 QPushButton *KFileWidget::cancelButton() const 2193 { 2194 return d->m_cancelButton; 2195 } 2196 2197 // Called by KFileDialog 2198 void KFileWidget::slotCancel() 2199 { 2200 d->writeViewConfig(); 2201 d->m_ops->close(); 2202 } 2203 2204 void KFileWidget::setKeepLocation(bool keep) 2205 { 2206 d->m_keepLocation = keep; 2207 } 2208 2209 bool KFileWidget::keepsLocation() const 2210 { 2211 return d->m_keepLocation; 2212 } 2213 2214 void KFileWidget::setOperationMode(OperationMode mode) 2215 { 2216 // qDebug(); 2217 2218 d->m_operationMode = mode; 2219 d->m_keepLocation = (mode == Saving); 2220 d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || mode != Saving); 2221 if (mode == Opening) { 2222 // don't use KStandardGuiItem::open() here which has trailing ellipsis! 2223 d->m_okButton->setText(i18n("&Open")); 2224 d->m_okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); 2225 // hide the new folder actions...usability team says they shouldn't be in open file dialog 2226 d->m_ops->action(KDirOperator::NewFolder)->setEnabled(false); 2227 d->m_toolbar->removeAction(d->m_ops->action(KDirOperator::NewFolder)); 2228 } else if (mode == Saving) { 2229 KGuiItem::assign(d->m_okButton, KStandardGuiItem::save()); 2230 d->setNonExtSelection(); 2231 } else { 2232 KGuiItem::assign(d->m_okButton, KStandardGuiItem::ok()); 2233 } 2234 d->updateLocationWhatsThis(); 2235 d->updateAutoSelectExtension(); 2236 2237 if (d->m_ops) { 2238 d->m_ops->setIsSaving(mode == Saving); 2239 } 2240 d->updateFilterText(); 2241 } 2242 2243 KFileWidget::OperationMode KFileWidget::operationMode() const 2244 { 2245 return d->m_operationMode; 2246 } 2247 2248 void KFileWidgetPrivate::slotAutoSelectExtClicked() 2249 { 2250 // qDebug() << "slotAutoSelectExtClicked(): " 2251 // << m_autoSelectExtCheckBox->isChecked() << endl; 2252 2253 // whether the _user_ wants it on/off 2254 m_autoSelectExtChecked = m_autoSelectExtCheckBox->isChecked(); 2255 2256 // update the current filename's extension 2257 updateLocationEditExtension(m_extension /* extension hasn't changed */); 2258 } 2259 2260 void KFileWidgetPrivate::placesViewSplitterMoved(int pos, int index) 2261 { 2262 // qDebug(); 2263 2264 // we need to record the size of the splitter when the splitter changes size 2265 // so we can keep the places box the right size! 2266 if (m_placesDock && index == 1) { 2267 m_placesViewWidth = pos; 2268 // qDebug() << "setting m_lafBox minwidth to" << m_placesViewWidth; 2269 setLafBoxColumnWidth(); 2270 } 2271 } 2272 2273 void KFileWidgetPrivate::activateUrlNavigator() 2274 { 2275 // qDebug(); 2276 2277 QLineEdit *lineEdit = m_urlNavigator->editor()->lineEdit(); 2278 2279 // If the text field currently has focus and everything is selected, 2280 // pressing the keyboard shortcut returns the whole thing to breadcrumb mode 2281 if (m_urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) { 2282 m_urlNavigator->setUrlEditable(false); 2283 } else { 2284 m_urlNavigator->setUrlEditable(true); 2285 m_urlNavigator->setFocus(); 2286 lineEdit->selectAll(); 2287 } 2288 } 2289 2290 void KFileWidgetPrivate::slotDirOpIconSizeChanged(int size) 2291 { 2292 auto beginIt = m_stdIconSizes.cbegin(); 2293 auto endIt = m_stdIconSizes.cend(); 2294 auto it = std::lower_bound(beginIt, endIt, size); 2295 const int sliderStep = it != endIt ? it - beginIt : 0; 2296 m_iconSizeSlider->setValue(sliderStep); 2297 m_zoomOutAction->setDisabled(it == beginIt); 2298 m_zoomInAction->setDisabled(it == (endIt - 1)); 2299 } 2300 2301 void KFileWidgetPrivate::changeIconsSize(ZoomState zoom) 2302 { 2303 int step = m_iconSizeSlider->value(); 2304 2305 if (zoom == ZoomOut) { 2306 if (step == 0) { 2307 return; 2308 } 2309 --step; 2310 } else { // ZoomIn 2311 if (step == static_cast<int>(m_stdIconSizes.size() - 1)) { 2312 return; 2313 } 2314 ++step; 2315 } 2316 2317 m_iconSizeSlider->setValue(step); 2318 slotIconSizeSliderMoved(m_stdIconSizes[step]); 2319 } 2320 2321 void KFileWidgetPrivate::slotIconSizeChanged(int _value) 2322 { 2323 m_ops->setIconSize(_value); 2324 m_iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", _value)); 2325 } 2326 2327 void KFileWidgetPrivate::slotIconSizeSliderMoved(int size) 2328 { 2329 // Force this to be called in case this slot is called first on the 2330 // slider move. 2331 slotIconSizeChanged(size); 2332 2333 QPoint global(m_iconSizeSlider->rect().topLeft()); 2334 global.ry() += m_iconSizeSlider->height() / 2; 2335 QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_iconSizeSlider->mapToGlobal(global)); 2336 QApplication::sendEvent(m_iconSizeSlider, &toolTipEvent); 2337 } 2338 2339 void KFileWidgetPrivate::slotViewDoubleClicked(const QModelIndex &index) 2340 { 2341 // double clicking to save should only work on files 2342 if (m_operationMode == KFileWidget::Saving && index.isValid() && m_ops->selectedItems().constFirst().isFile()) { 2343 q->slotOk(); 2344 } 2345 } 2346 2347 void KFileWidgetPrivate::slotViewKeyEnterReturnPressed() 2348 { 2349 // an enter/return event occurred in the view 2350 // when we are saving one file and there is no selection in the view (otherwise we get an activated event) 2351 if (m_operationMode == KFileWidget::Saving && (m_ops->mode() & KFile::File) && m_ops->selectedItems().isEmpty()) { 2352 q->slotOk(); 2353 } 2354 } 2355 2356 static QString getExtensionFromPatternList(const QStringList &patternList) 2357 { 2358 // qDebug(); 2359 2360 QString ret; 2361 // qDebug() << "\tgetExtension " << patternList; 2362 2363 QStringList::ConstIterator patternListEnd = patternList.end(); 2364 for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) { 2365 // qDebug() << "\t\ttry: \'" << (*it) << "\'"; 2366 2367 // is this pattern like "*.BMP" rather than useless things like: 2368 // 2369 // README 2370 // *. 2371 // *.* 2372 // *.JP*G 2373 // *.JP? 2374 // *.[Jj][Pp][Gg] 2375 if ((*it).startsWith(QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(QLatin1Char('*'), 2) < 0 && (*it).indexOf(QLatin1Char('?'), 2) < 0 2376 && (*it).indexOf(QLatin1Char('['), 2) < 0 && (*it).indexOf(QLatin1Char(']'), 2) < 0) { 2377 ret = (*it).mid(1); 2378 break; 2379 } 2380 } 2381 2382 return ret; 2383 } 2384 2385 static QString stripUndisplayable(const QString &string) 2386 { 2387 QString ret = string; 2388 2389 ret.remove(QLatin1Char(':')); 2390 ret = KLocalizedString::removeAcceleratorMarker(ret); 2391 2392 return ret; 2393 } 2394 2395 // QString KFileWidget::currentFilterExtension() 2396 //{ 2397 // return d->m_extension; 2398 //} 2399 2400 void KFileWidgetPrivate::updateAutoSelectExtension() 2401 { 2402 if (!m_autoSelectExtCheckBox) { 2403 return; 2404 } 2405 2406 QMimeDatabase db; 2407 // 2408 // Figure out an extension for the Automatically Select Extension thing 2409 // (some Windows users apparently don't know what to do when confronted 2410 // with a text file called "COPYING" but do know what to do with 2411 // COPYING.txt ...) 2412 // 2413 2414 // qDebug() << "Figure out an extension: "; 2415 QString lastExtension = m_extension; 2416 m_extension.clear(); 2417 2418 // Automatically Select Extension is only valid if the user is _saving_ a _file_ 2419 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) { 2420 // 2421 // Get an extension from the filter 2422 // 2423 2424 QString filter = m_filterWidget->currentFilter(); 2425 if (!filter.isEmpty()) { 2426 // if the currently selected filename already has an extension which 2427 // is also included in the currently allowed extensions, keep it 2428 // otherwise use the default extension 2429 QString currentExtension = db.suffixForFileName(locationEditCurrentText()); 2430 if (currentExtension.isEmpty()) { 2431 currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1); 2432 } 2433 // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension; 2434 2435 QString defaultExtension; 2436 QStringList extensionList; 2437 2438 // e.g. "*.cpp" 2439 if (filter.indexOf(QLatin1Char('/')) < 0) { 2440 extensionList = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 2441 defaultExtension = getExtensionFromPatternList(extensionList); 2442 } 2443 // e.g. "text/html" 2444 else { 2445 QMimeType mime = db.mimeTypeForName(filter); 2446 if (mime.isValid()) { 2447 extensionList = mime.globPatterns(); 2448 defaultExtension = mime.preferredSuffix(); 2449 if (!defaultExtension.isEmpty()) { 2450 defaultExtension.prepend(QLatin1Char('.')); 2451 } 2452 } 2453 } 2454 2455 if ((!currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension)) 2456 || filter == QLatin1String("application/octet-stream")) { 2457 m_extension = QLatin1Char('.') + currentExtension; 2458 } else { 2459 m_extension = defaultExtension; 2460 } 2461 2462 // qDebug() << "List:" << extensionList << "auto-selected extension:" << m_extension; 2463 } 2464 2465 // 2466 // GUI: checkbox 2467 // 2468 2469 QString whatsThisExtension; 2470 if (!m_extension.isEmpty()) { 2471 // remember: sync any changes to the string with below 2472 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", m_extension)); 2473 whatsThisExtension = i18n("the extension <b>%1</b>", m_extension); 2474 2475 m_autoSelectExtCheckBox->setEnabled(true); 2476 m_autoSelectExtCheckBox->setChecked(m_autoSelectExtChecked); 2477 } else { 2478 // remember: sync any changes to the string with above 2479 m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension")); 2480 whatsThisExtension = i18n("a suitable extension"); 2481 2482 m_autoSelectExtCheckBox->setChecked(false); 2483 m_autoSelectExtCheckBox->setEnabled(false); 2484 } 2485 2486 const QString locationLabelText = stripUndisplayable(m_locationLabel->text()); 2487 m_autoSelectExtCheckBox->setWhatsThis(QLatin1String("<qt>") 2488 + i18n("This option enables some convenient features for " 2489 "saving files with extensions:<br />" 2490 "<ol>" 2491 "<li>Any extension specified in the <b>%1</b> text " 2492 "area will be updated if you change the file type " 2493 "to save in.<br />" 2494 "<br /></li>" 2495 "<li>If no extension is specified in the <b>%2</b> " 2496 "text area when you click " 2497 "<b>Save</b>, %3 will be added to the end of the " 2498 "filename (if the filename does not already exist). " 2499 "This extension is based on the file type that you " 2500 "have chosen to save in.<br />" 2501 "<br />" 2502 "If you do not want KDE to supply an extension for the " 2503 "filename, you can either turn this option off or you " 2504 "can suppress it by adding a period (.) to the end of " 2505 "the filename (the period will be automatically " 2506 "removed)." 2507 "</li>" 2508 "</ol>" 2509 "If unsure, keep this option enabled as it makes your " 2510 "files more manageable.", 2511 locationLabelText, 2512 locationLabelText, 2513 whatsThisExtension) 2514 + QLatin1String("</qt>")); 2515 2516 m_autoSelectExtCheckBox->show(); 2517 2518 // update the current filename's extension 2519 updateLocationEditExtension(lastExtension); 2520 } 2521 // Automatically Select Extension not valid 2522 else { 2523 m_autoSelectExtCheckBox->setChecked(false); 2524 m_autoSelectExtCheckBox->hide(); 2525 } 2526 } 2527 2528 // Updates the extension of the filename specified in d->m_locationEdit if the 2529 // Automatically Select Extension feature is enabled. 2530 // (this prevents you from accidentally saving "file.kwd" as RTF, for example) 2531 void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension) 2532 { 2533 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) { 2534 return; 2535 } 2536 2537 const QString urlStr = locationEditCurrentText(); 2538 if (urlStr.isEmpty()) { 2539 return; 2540 } 2541 2542 const int fileNameOffset = urlStr.lastIndexOf(QLatin1Char('/')) + 1; 2543 QStringView fileName = QStringView(urlStr).mid(fileNameOffset); 2544 2545 const int dot = fileName.lastIndexOf(QLatin1Char('.')); 2546 const int len = fileName.length(); 2547 if (dot > 0 && // has an extension already and it's not a hidden file 2548 // like ".hidden" (but we do accept ".hidden.ext") 2549 dot != len - 1 // and not deliberately suppressing extension 2550 ) { 2551 const QUrl url = getCompleteUrl(urlStr); 2552 // qDebug() << "updateLocationEditExtension (" << url << ")"; 2553 // exists? 2554 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 2555 KJobWidgets::setWindow(statJob, q); 2556 bool result = statJob->exec(); 2557 if (result) { 2558 // qDebug() << "\tfile exists"; 2559 2560 if (statJob->statResult().isDir()) { 2561 // qDebug() << "\tisDir - won't alter extension"; 2562 return; 2563 } 2564 2565 // --- fall through --- 2566 } 2567 2568 // 2569 // try to get rid of the current extension 2570 // 2571 2572 // catch "double extensions" like ".tar.gz" 2573 if (!lastExtension.isEmpty() && fileName.endsWith(lastExtension)) { 2574 fileName.chop(lastExtension.length()); 2575 } else if (!m_extension.isEmpty() && fileName.endsWith(m_extension)) { 2576 fileName.chop(m_extension.length()); 2577 } else { // can only handle "single extensions" 2578 fileName.truncate(dot); 2579 } 2580 2581 // add extension 2582 const QString newText = QStringView(urlStr).left(fileNameOffset) + fileName + m_extension; 2583 if (newText != locationEditCurrentText()) { 2584 m_locationEdit->setItemText(m_locationEdit->currentIndex(), newText); 2585 m_locationEdit->lineEdit()->setModified(true); 2586 } 2587 } 2588 } 2589 2590 QString KFileWidgetPrivate::findMatchingFilter(const QString &filter, const QString &filename) const 2591 { 2592 // e.g.: '*.foo *.bar|Foo type' -> '*.foo', '*.bar' 2593 const QStringList patterns = filter.left(filter.indexOf(QLatin1Char('|'))).split(QLatin1Char(' '), Qt::SkipEmptyParts); 2594 2595 QRegularExpression rx; 2596 for (const QString &p : patterns) { 2597 rx.setPattern(QRegularExpression::wildcardToRegularExpression(p)); 2598 if (rx.match(filename).hasMatch()) { 2599 return p; 2600 } 2601 } 2602 return QString(); 2603 } 2604 2605 // Updates the filter if the extension of the filename specified in d->m_locationEdit is changed 2606 // (this prevents you from accidentally saving "file.kwd" as RTF, for example) 2607 void KFileWidgetPrivate::updateFilter() 2608 { 2609 // qDebug(); 2610 2611 if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) { 2612 QString urlStr = locationEditCurrentText(); 2613 if (urlStr.isEmpty()) { 2614 return; 2615 } 2616 2617 if (m_filterWidget->isMimeFilter()) { 2618 QMimeDatabase db; 2619 QMimeType mime = db.mimeTypeForFile(urlStr, QMimeDatabase::MatchExtension); 2620 if (mime.isValid() && !mime.isDefault()) { 2621 if (m_filterWidget->currentFilter() != mime.name() && m_filterWidget->filters().indexOf(mime.name()) != -1) { 2622 m_filterWidget->setCurrentFilter(mime.name()); 2623 } 2624 } 2625 } else { 2626 QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename 2627 // accept any match to honor the user's selection; see later code handling the "*" match 2628 if (!findMatchingFilter(m_filterWidget->currentFilter(), filename).isEmpty()) { 2629 return; 2630 } 2631 const QStringList list = m_filterWidget->filters(); 2632 for (const QString &filter : list) { 2633 QString match = findMatchingFilter(filter, filename); 2634 if (!match.isEmpty()) { 2635 if (match != QLatin1String("*")) { // never match the catch-all filter 2636 m_filterWidget->setCurrentFilter(filter); 2637 } 2638 return; // do not repeat, could match a later filter 2639 } 2640 } 2641 } 2642 } 2643 } 2644 2645 // applies only to a file that doesn't already exist 2646 void KFileWidgetPrivate::appendExtension(QUrl &url) 2647 { 2648 // qDebug(); 2649 2650 if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) { 2651 return; 2652 } 2653 2654 QString fileName = url.fileName(); 2655 if (fileName.isEmpty()) { 2656 return; 2657 } 2658 2659 // qDebug() << "appendExtension(" << url << ")"; 2660 2661 const int len = fileName.length(); 2662 const int dot = fileName.lastIndexOf(QLatin1Char('.')); 2663 2664 const bool suppressExtension = (dot == len - 1); 2665 const bool unspecifiedExtension = !fileName.endsWith(m_extension); 2666 2667 // don't KIO::Stat if unnecessary 2668 if (!(suppressExtension || unspecifiedExtension)) { 2669 return; 2670 } 2671 2672 // exists? 2673 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 2674 KJobWidgets::setWindow(statJob, q); 2675 bool res = statJob->exec(); 2676 if (res) { 2677 // qDebug() << "\tfile exists - won't append extension"; 2678 return; 2679 } 2680 2681 // suppress automatically append extension? 2682 if (suppressExtension) { 2683 // 2684 // Strip trailing dot 2685 // This allows lazy people to have m_autoSelectExtCheckBox->isChecked 2686 // but don't want a file extension to be appended 2687 // e.g. "README." will make a file called "README" 2688 // 2689 // If you really want a name like "README.", then type "README.." 2690 // and the trailing dot will be removed (or just stop being lazy and 2691 // turn off this feature so that you can type "README.") 2692 // 2693 // qDebug() << "\tstrip trailing dot"; 2694 QString path = url.path(); 2695 path.chop(1); 2696 url.setPath(path); 2697 } 2698 // evilmatically append extension :) if the user hasn't specified one 2699 else if (unspecifiedExtension) { 2700 // qDebug() << "\tappending extension \'" << m_extension << "\'..."; 2701 url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash 2702 url.setPath(url.path() + fileName + m_extension); 2703 // qDebug() << "\tsaving as \'" << url << "\'"; 2704 } 2705 } 2706 2707 // adds the selected files/urls to 'recent documents' 2708 void KFileWidgetPrivate::addToRecentDocuments() 2709 { 2710 int m = m_ops->mode(); 2711 int atmost = KRecentDocument::maximumItems(); 2712 // don't add more than we need. KRecentDocument::add() is pretty slow 2713 2714 if (m & KFile::LocalOnly) { 2715 const QStringList files = q->selectedFiles(); 2716 QStringList::ConstIterator it = files.begin(); 2717 for (; it != files.end() && atmost > 0; ++it) { 2718 KRecentDocument::add(QUrl::fromLocalFile(*it)); 2719 atmost--; 2720 } 2721 } 2722 2723 else { // urls 2724 const QList<QUrl> urls = q->selectedUrls(); 2725 QList<QUrl>::ConstIterator it = urls.begin(); 2726 for (; it != urls.end() && atmost > 0; ++it) { 2727 if ((*it).isValid()) { 2728 KRecentDocument::add(*it); 2729 atmost--; 2730 } 2731 } 2732 } 2733 } 2734 2735 KUrlComboBox *KFileWidget::locationEdit() const 2736 { 2737 return d->m_locationEdit; 2738 } 2739 2740 KFileFilterCombo *KFileWidget::filterWidget() const 2741 { 2742 return d->m_filterWidget; 2743 } 2744 2745 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 100) 2746 KActionCollection *KFileWidget::actionCollection() const 2747 { 2748 return d->m_ops->actionCollection(); 2749 } 2750 #endif 2751 2752 void KFileWidgetPrivate::togglePlacesPanel(bool show, QObject *sender) 2753 { 2754 if (show) { 2755 initPlacesPanel(); 2756 m_placesDock->show(); 2757 setLafBoxColumnWidth(); 2758 2759 // check to see if they have a home item defined, if not show the home button 2760 QUrl homeURL; 2761 homeURL.setPath(QDir::homePath()); 2762 KFilePlacesModel *model = static_cast<KFilePlacesModel *>(m_placesView->model()); 2763 for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) { 2764 QModelIndex index = model->index(rowIndex, 0); 2765 QUrl url = model->url(index); 2766 2767 if (homeURL.matches(url, QUrl::StripTrailingSlash)) { 2768 m_toolbar->removeAction(m_ops->action(KDirOperator::Home)); 2769 break; 2770 } 2771 } 2772 } else { 2773 if (sender == m_placesDock && m_placesDock && m_placesDock->isVisibleTo(q)) { 2774 // we didn't *really* go away! the dialog was simply hidden or 2775 // we changed virtual desktops or ... 2776 return; 2777 } 2778 2779 if (m_placesDock) { 2780 m_placesDock->hide(); 2781 } 2782 2783 QAction *homeAction = m_ops->action(KDirOperator::Home); 2784 QAction *reloadAction = m_ops->action(KDirOperator::Reload); 2785 if (!m_toolbar->actions().contains(homeAction)) { 2786 m_toolbar->insertAction(reloadAction, homeAction); 2787 } 2788 2789 // reset the lafbox to not follow the width of the splitter 2790 m_lafBox->setColumnMinimumWidth(0, 0); 2791 } 2792 2793 m_togglePlacesPanelAction->setChecked(show); 2794 2795 // if we don't show the places panel, at least show the places menu 2796 m_urlNavigator->setPlacesSelectorVisible(!show); 2797 } 2798 2799 void KFileWidgetPrivate::toggleBookmarks(bool show) 2800 { 2801 if (show) { 2802 if (m_bookmarkHandler) { 2803 return; 2804 } 2805 m_bookmarkHandler = new KFileBookmarkHandler(q); 2806 q->connect(m_bookmarkHandler, &KFileBookmarkHandler::openUrl, q, [this](const QString &path) { 2807 enterUrl(path); 2808 }); 2809 m_bookmarkButton->setMenu(m_bookmarkHandler->menu()); 2810 } else if (m_bookmarkHandler) { 2811 m_bookmarkButton->setMenu(nullptr); 2812 delete m_bookmarkHandler; 2813 m_bookmarkHandler = nullptr; 2814 } 2815 2816 if (m_bookmarkButton) { 2817 m_bookmarkButton->setVisible(show); 2818 } 2819 2820 m_toggleBookmarksAction->setChecked(show); 2821 } 2822 2823 // static, overloaded 2824 QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass) 2825 { 2826 QString fileName; // result discarded 2827 return getStartUrl(startDir, recentDirClass, fileName); 2828 } 2829 2830 // static, overloaded 2831 QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName) 2832 { 2833 recentDirClass.clear(); 2834 fileName.clear(); 2835 QUrl ret; 2836 2837 bool useDefaultStartDir = startDir.isEmpty(); 2838 if (!useDefaultStartDir) { 2839 if (startDir.scheme() == QLatin1String("kfiledialog")) { 2840 // The startDir URL with this protocol may be in the format: 2841 // directory() fileName() 2842 // 1. kfiledialog:///keyword "/" keyword 2843 // 2. kfiledialog:///keyword?global "/" keyword 2844 // 3. kfiledialog:///keyword/ "/" keyword 2845 // 4. kfiledialog:///keyword/?global "/" keyword 2846 // 5. kfiledialog:///keyword/filename /keyword filename 2847 // 6. kfiledialog:///keyword/filename?global /keyword filename 2848 2849 QString keyword; 2850 QString urlDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path(); 2851 QString urlFile = startDir.fileName(); 2852 if (urlDir == QLatin1String("/")) { // '1'..'4' above 2853 keyword = urlFile; 2854 fileName.clear(); 2855 } else { // '5' or '6' above 2856 keyword = urlDir.mid(1); 2857 fileName = urlFile; 2858 } 2859 2860 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 96) 2861 const QLatin1String query(startDir.query() == QLatin1String("global") ? "::%1" : ":%1"); 2862 #else 2863 const QLatin1String query(":%1"); 2864 #endif 2865 recentDirClass = query.arg(keyword); 2866 2867 ret = QUrl::fromLocalFile(KRecentDirs::dir(recentDirClass)); 2868 } else { // not special "kfiledialog" URL 2869 // "foo.png" only gives us a file name, the default start dir will be used. 2870 // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same 2871 // (and is the reason why we don't just use QUrl::isRelative()). 2872 2873 // In all other cases (startDir contains a directory path, or has no 2874 // fileName for us anyway, such as smb://), startDir is indeed a dir url. 2875 2876 if (!startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || startDir.fileName().isEmpty()) { 2877 // can use start directory 2878 ret = startDir; // will be checked by stat later 2879 // If we won't be able to list it (e.g. http), then use default 2880 if (!KProtocolManager::supportsListing(ret)) { 2881 useDefaultStartDir = true; 2882 fileName = startDir.fileName(); 2883 } 2884 } else { // file name only 2885 fileName = startDir.fileName(); 2886 useDefaultStartDir = true; 2887 } 2888 } 2889 } 2890 2891 if (useDefaultStartDir) { 2892 if (lastDirectory()->isEmpty()) { 2893 *lastDirectory() = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); 2894 const QUrl home(QUrl::fromLocalFile(QDir::homePath())); 2895 // if there is no docpath set (== home dir), we prefer the current 2896 // directory over it. We also prefer the homedir when our CWD is 2897 // different from our homedirectory or when the document dir 2898 // does not exist 2899 if (lastDirectory()->adjusted(QUrl::StripTrailingSlash) == home.adjusted(QUrl::StripTrailingSlash) // 2900 || QDir::currentPath() != QDir::homePath() // 2901 || !QDir(lastDirectory()->toLocalFile()).exists()) { 2902 *lastDirectory() = QUrl::fromLocalFile(QDir::currentPath()); 2903 } 2904 } 2905 ret = *lastDirectory(); 2906 } 2907 2908 // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName; 2909 return ret; 2910 } 2911 2912 void KFileWidget::setStartDir(const QUrl &directory) 2913 { 2914 if (directory.isValid()) { 2915 *lastDirectory() = directory; 2916 } 2917 } 2918 2919 void KFileWidgetPrivate::setNonExtSelection() 2920 { 2921 // Enhanced rename: Don't highlight the file extension. 2922 QString filename = locationEditCurrentText(); 2923 QMimeDatabase db; 2924 QString extension = db.suffixForFileName(filename); 2925 2926 if (!extension.isEmpty()) { 2927 m_locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1); 2928 } else { 2929 int lastDot = filename.lastIndexOf(QLatin1Char('.')); 2930 if (lastDot > 0) { 2931 m_locationEdit->lineEdit()->setSelection(0, lastDot); 2932 } else { 2933 m_locationEdit->lineEdit()->selectAll(); 2934 } 2935 } 2936 } 2937 2938 // Sets the filter text to "File type" if the dialog is saving and a MIME type 2939 // filter has been set; otherwise, the text is "Filter:" 2940 void KFileWidgetPrivate::updateFilterText() 2941 { 2942 QString label; 2943 QString whatsThisText; 2944 2945 if (m_operationMode == KFileWidget::Saving && m_filterWidget->isMimeFilter()) { 2946 label = i18n("&File type:"); 2947 whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format that the file will be saved as.</qt>"); 2948 } else { 2949 label = i18n("&Filter:"); 2950 whatsThisText = i18n( 2951 "<qt>This is the filter to apply to the file list. " 2952 "File names that do not match the filter will not be shown.<p>" 2953 "You may select from one of the preset filters in the " 2954 "drop down menu, or you may enter a custom filter " 2955 "directly into the text area.</p><p>" 2956 "Wildcards such as * and ? are allowed.</p></qt>"); 2957 } 2958 2959 if (m_filterLabel) { 2960 m_filterLabel->setText(label); 2961 m_filterLabel->setWhatsThis(whatsThisText); 2962 } 2963 if (m_filterWidget) { 2964 m_filterWidget->setWhatsThis(whatsThisText); 2965 } 2966 } 2967 2968 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 66) 2969 KToolBar *KFileWidget::toolBar() const 2970 { 2971 return d->m_toolbar; 2972 } 2973 #endif 2974 2975 void KFileWidget::setCustomWidget(QWidget *widget) 2976 { 2977 delete d->m_bottomCustomWidget; 2978 d->m_bottomCustomWidget = widget; 2979 2980 // add it to the dialog, below the filter list box. 2981 2982 // Change the parent so that this widget is a child of the main widget 2983 d->m_bottomCustomWidget->setParent(this); 2984 2985 d->m_vbox->addWidget(d->m_bottomCustomWidget); 2986 // d->m_vbox->addSpacing(3); // can't do this every time... 2987 2988 // FIXME: This should adjust the tab orders so that the custom widget 2989 // comes after the Cancel button. The code appears to do this, but the result 2990 // somehow screws up the tab order of the file path combo box. Not a major 2991 // problem, but ideally the tab order with a custom widget should be 2992 // the same as the order without one. 2993 setTabOrder(d->m_cancelButton, d->m_bottomCustomWidget); 2994 setTabOrder(d->m_bottomCustomWidget, d->m_urlNavigator); 2995 } 2996 2997 void KFileWidget::setCustomWidget(const QString &text, QWidget *widget) 2998 { 2999 delete d->m_labeledCustomWidget; 3000 d->m_labeledCustomWidget = widget; 3001 3002 QLabel *label = new QLabel(text, this); 3003 label->setAlignment(Qt::AlignRight); 3004 d->m_lafBox->addWidget(label, 2, 0, Qt::AlignVCenter); 3005 d->m_lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter); 3006 } 3007 3008 KDirOperator *KFileWidget::dirOperator() 3009 { 3010 return d->m_ops; 3011 } 3012 3013 void KFileWidget::readConfig(KConfigGroup &group) 3014 { 3015 d->m_configGroup = group; 3016 d->readViewConfig(); 3017 d->readRecentFiles(); 3018 } 3019 3020 QString KFileWidgetPrivate::locationEditCurrentText() const 3021 { 3022 return QDir::fromNativeSeparators(m_locationEdit->currentText()); 3023 } 3024 3025 QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url) 3026 { 3027 if (url.isLocalFile()) { 3028 return url; 3029 } 3030 3031 KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); 3032 KJobWidgets::setWindow(statJob, q); 3033 bool res = statJob->exec(); 3034 3035 if (!res) { 3036 return url; 3037 } 3038 3039 const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH); 3040 if (!path.isEmpty()) { 3041 QUrl newUrl; 3042 newUrl.setPath(path); 3043 return newUrl; 3044 } 3045 3046 return url; 3047 } 3048 3049 void KFileWidgetPrivate::setInlinePreviewShown(bool show) 3050 { 3051 m_ops->setInlinePreviewShown(show); 3052 } 3053 3054 void KFileWidget::setConfirmOverwrite(bool enable) 3055 { 3056 d->m_confirmOverwrite = enable; 3057 } 3058 3059 void KFileWidget::setInlinePreviewShown(bool show) 3060 { 3061 d->setInlinePreviewShown(show); 3062 } 3063 3064 QSize KFileWidget::dialogSizeHint() const 3065 { 3066 int fontSize = fontMetrics().height(); 3067 QSize goodSize(48 * fontSize, 30 * fontSize); 3068 const QSize scrnSize = d->screenSize(); 3069 QSize minSize(scrnSize / 2); 3070 QSize maxSize(scrnSize * qreal(0.9)); 3071 return (goodSize.expandedTo(minSize).boundedTo(maxSize)); 3072 } 3073 3074 void KFileWidget::setViewMode(KFile::FileView mode) 3075 { 3076 d->m_ops->setViewMode(mode); 3077 d->m_hasView = true; 3078 } 3079 3080 void KFileWidget::setSupportedSchemes(const QStringList &schemes) 3081 { 3082 d->m_model->setSupportedSchemes(schemes); 3083 d->m_ops->setSupportedSchemes(schemes); 3084 d->m_urlNavigator->setSupportedSchemes(schemes); 3085 } 3086 3087 QStringList KFileWidget::supportedSchemes() const 3088 { 3089 return d->m_model->supportedSchemes(); 3090 } 3091 3092 #include "moc_kfilewidget.cpp"