Warning, file /frameworks/kio/src/filewidgets/kfilewidget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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"