1 // -*- c++ -*-
2 /*
3     This file is part of the KDE libraries
4     SPDX-FileCopyrightText: 1997, 1998 Richard Moore <rich@kde.org>
5     SPDX-FileCopyrightText: 1998 Stephan Kulow <coolo@kde.org>
6     SPDX-FileCopyrightText: 1998 Daniel Grana <grana@ie.iwi.unibe.ch>
7     SPDX-FileCopyrightText: 1999, 2000, 2001, 2002, 2003 Carsten Pfeiffer <pfeiffer@kde.org>
8     SPDX-FileCopyrightText: 2003 Clarence Dang <dang@kde.org>
9     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
10     SPDX-FileCopyrightText: 2008 Rafael Fernández López <ereslibre@kde.org>
11 
12     SPDX-License-Identifier: LGPL-2.0-or-later
13 */
14 
15 #include "kfilewidget.h"
16 
17 #include "../pathhelpers_p.h" // concatPaths() and isAbsoluteLocalPath()
18 #include "kfilebookmarkhandler_p.h"
19 #include "kfileplacesmodel.h"
20 #include "kfileplacesview.h"
21 #include "kfilepreviewgenerator.h"
22 #include "kfilewidgetdocktitlebar_p.h"
23 #include "kurlcombobox.h"
24 #include "kurlnavigator.h"
25 #include <KActionCollection>
26 #include <KActionMenu>
27 #include <KConfigGroup>
28 #include <KDirLister>
29 #include <KFileItem>
30 #include <KLocalizedString>
31 #include <KSharedConfig>
32 #include <KToolBar>
33 #include <config-kiofilewidgets.h>
34 #include <defaults-kfile.h>
35 #include <kdiroperator.h>
36 #include <kfilefiltercombo.h>
37 #include <kfileitemdelegate.h>
38 #include <kimagefilepreview.h>
39 #include <kio/job.h>
40 #include <kio/jobuidelegate.h>
41 #include <kio/scheduler.h>
42 #include <kprotocolmanager.h>
43 #include <krecentdirs.h>
44 #include <krecentdocument.h>
45 #include <kurlcompletion.h>
46 
47 #include <QAbstractProxyModel>
48 #include <QApplication>
49 #include <QCheckBox>
50 #include <QDebug>
51 #include <QDesktopWidget>
52 #include <QDockWidget>
53 #include <QHelpEvent>
54 #include <QIcon>
55 #include <QLabel>
56 #include <QLayout>
57 #include <QLineEdit>
58 #include <QLoggingCategory>
59 #include <QMenu>
60 #include <QMimeDatabase>
61 #include <QPushButton>
62 #include <QSplitter>
63 #include <QStandardPaths>
64 #include <QTimer>
65 
66 #include <KIconLoader>
67 #include <KJobWidgets>
68 #include <KMessageBox>
69 #include <KShell>
70 #include <kurlauthorized.h>
71 
72 #include <algorithm>
73 #include <array>
74 
75 Q_DECLARE_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW)
76 Q_LOGGING_CATEGORY(KIO_KFILEWIDGETS_FW, "kf.kio.kfilewidgets.kfilewidget", QtInfoMsg)
77 
78 class KFileWidgetPrivate
79 {
80 public:
KFileWidgetPrivate(KFileWidget * qq)81     explicit KFileWidgetPrivate(KFileWidget *qq)
82         : q(qq)
83     {
84     }
85 
~KFileWidgetPrivate()86     ~KFileWidgetPrivate()
87     {
88         m_iconSizeSlider->removeEventFilter(q);
89         m_locationEdit->removeEventFilter(q);
90 
91         delete m_bookmarkHandler; // Should be deleted before m_ops!
92         // Must be deleted before m_ops, otherwise the unit test crashes due to the
93         // connection to the QDockWidget::visibilityChanged signal, which may get
94         // emitted after this object is destroyed
95         delete m_placesDock;
96         delete m_ops;
97     }
98 
99     void updateLocationWhatsThis();
100     void updateAutoSelectExtension();
101     void initPlacesPanel();
102     void setPlacesViewSplitterSizes();
103     void setLafBoxColumnWidth();
104     void initGUI();
105     void readViewConfig();
106     void writeViewConfig();
107     void setNonExtSelection();
108     void setLocationText(const QUrl &);
109     void setLocationText(const QList<QUrl> &);
110     void appendExtension(QUrl &url);
111     void updateLocationEditExtension(const QString &);
112     QString findMatchingFilter(const QString &filter, const QString &filename) const;
113     void updateFilter();
114     void updateFilterText();
115     /**
116      * Parses the string "line" for files. If line doesn't contain any ", the
117      * whole line will be interpreted as one file. If the number of " is odd,
118      * an empty list will be returned. Otherwise, all items enclosed in " "
119      * will be returned as correct urls.
120      */
121     QList<QUrl> tokenize(const QString &line) const;
122     /**
123      * Reads the recent used files and inserts them into the location combobox
124      */
125     void readRecentFiles();
126     /**
127      * Saves the entries from the location combobox.
128      */
129     void saveRecentFiles();
130     /**
131      * called when an item is highlighted/selected in multiselection mode.
132      * handles setting the m_locationEdit.
133      */
134     void multiSelectionChanged();
135 
136     /**
137      * Returns the absolute version of the URL specified in m_locationEdit.
138      */
139     QUrl getCompleteUrl(const QString &) const;
140 
141     /**
142      * Sets the dummy entry on the history combo box. If the dummy entry
143      * already exists, it is overwritten with this information.
144      */
145     void setDummyHistoryEntry(const QString &text, const QIcon &icon = QIcon(), bool usePreviousPixmapIfNull = true);
146 
147     /**
148      * Removes the dummy entry of the history combo box.
149      */
150     void removeDummyHistoryEntry();
151 
152     /**
153      * Asks for overwrite confirmation using a KMessageBox and returns
154      * true if the user accepts.
155      *
156      * @since 4.2
157      */
158     bool toOverwrite(const QUrl &);
159 
160     // private slots
161     void slotLocationChanged(const QString &);
162     void urlEntered(const QUrl &);
163     void enterUrl(const QUrl &);
164     void enterUrl(const QString &);
165     void locationAccepted(const QString &);
166     void slotFilterChanged();
167     void fileHighlighted(const KFileItem &);
168     void fileSelected(const KFileItem &);
169     void slotLoadingFinished();
170     void fileCompletion(const QString &);
171     void togglePlacesPanel(bool show, QObject *sender = nullptr);
172     void toggleBookmarks(bool);
173     void slotAutoSelectExtClicked();
174     void placesViewSplitterMoved(int, int);
175     void activateUrlNavigator();
176     void zoomOutIconsSize();
177     void zoomInIconsSize();
178     void slotIconSizeSliderMoved(int);
179     void slotIconSizeChanged(int);
180     void slotViewDoubleClicked(const QModelIndex &);
181     void slotViewKeyEnterReturnPressed();
182 
183     void addToRecentDocuments();
184 
185     QString locationEditCurrentText() const;
186 
187     /**
188      * KIO::NetAccess::mostLocalUrl local replacement.
189      * This method won't show any progress dialogs for stating, since
190      * they are very annoying when stating.
191      */
192     QUrl mostLocalUrl(const QUrl &url);
193 
194     void setInlinePreviewShown(bool show);
195 
196     KFileWidget *const q;
197 
198     // the last selected url
199     QUrl m_url;
200 
201     // now following all kind of widgets, that I need to rebuild
202     // the geometry management
203     QBoxLayout *m_boxLayout = nullptr;
204     QGridLayout *m_lafBox = nullptr;
205     QVBoxLayout *m_vbox = nullptr;
206 
207     QLabel *m_locationLabel = nullptr;
208     QWidget *m_opsWidget = nullptr;
209 
210     QLabel *m_filterLabel = nullptr;
211     KUrlNavigator *m_urlNavigator = nullptr;
212     QPushButton *m_okButton = nullptr;
213     QPushButton *m_cancelButton = nullptr;
214     QDockWidget *m_placesDock = nullptr;
215     KFilePlacesView *m_placesView = nullptr;
216     QSplitter *m_placesViewSplitter = nullptr;
217     // caches the places view width. This value will be updated when the splitter
218     // is moved. This allows us to properly set a value when the dialog itself
219     // is resized
220     int m_placesViewWidth = -1;
221 
222     QWidget *m_labeledCustomWidget = nullptr;
223     QWidget *m_bottomCustomWidget = nullptr;
224 
225     // Automatically Select Extension stuff
226     QCheckBox *m_autoSelectExtCheckBox = nullptr;
227     QString m_extension; // current extension for this filter
228 
229     QList<QUrl> m_urlList; // the list of selected urls
230 
231     KFileWidget::OperationMode m_operationMode = KFileWidget::Opening;
232 
233     // The file class used for KRecentDirs
234     QString m_fileClass;
235 
236     KFileBookmarkHandler *m_bookmarkHandler = nullptr;
237 
238     KActionMenu *m_bookmarkButton = nullptr;
239 
240     KToolBar *m_toolbar = nullptr;
241     KUrlComboBox *m_locationEdit = nullptr;
242     KDirOperator *m_ops = nullptr;
243     KFileFilterCombo *m_filterWidget = nullptr;
244     QTimer m_filterDelayTimer;
245 
246     KFilePlacesModel *m_model = nullptr;
247 
248     // whether or not the _user_ has checked the above box
249     bool m_autoSelectExtChecked = false;
250 
251     // indicates if the location edit should be kept or cleared when changing
252     // directories
253     bool m_keepLocation = false;
254 
255     // the KDirOperators view is set in KFileWidget::show(), so to avoid
256     // setting it again and again, we have this nice little boolean :)
257     bool m_hasView = false;
258 
259     bool m_hasDefaultFilter = false; // necessary for the m_operationMode
260     bool m_inAccept = false; // true between beginning and end of accept()
261     bool m_dummyAdded = false; // if the dummy item has been added. This prevents the combo from having a
262     // blank item added when loaded
263     bool m_confirmOverwrite = false;
264     bool m_differentHierarchyLevelItemsEntered = false;
265 
266     const std::array<KIconLoader::StdSizes, 6> m_stdIconSizes = {KIconLoader::SizeSmall,
267                                                                  KIconLoader::SizeSmallMedium,
268                                                                  KIconLoader::SizeMedium,
269                                                                  KIconLoader::SizeLarge,
270                                                                  KIconLoader::SizeHuge,
271                                                                  KIconLoader::SizeEnormous};
272 
273     QSlider *m_iconSizeSlider = nullptr;
274     QAction *m_zoomOutAction = nullptr;
275     QAction *m_zoomInAction = nullptr;
276 
277     // The group which stores app-specific settings. These settings are recent
278     // files and urls. Visual settings (view mode, sorting criteria...) are not
279     // app-specific and are stored in kdeglobals
280     KConfigGroup m_configGroup;
281 };
282 
283 Q_GLOBAL_STATIC(QUrl, lastDirectory) // to set the start path
284 
285 static const char autocompletionWhatsThisText[] = I18N_NOOP(
286     "<qt>While typing in the text area, you may be presented "
287     "with possible matches. "
288     "This feature can be controlled by clicking with the right mouse button "
289     "and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
290 
291 // returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars
containsProtocolSection(const QString & string)292 static bool containsProtocolSection(const QString &string)
293 {
294     int len = string.length();
295     static const char prot[] = ":/";
296     for (int i = 0; i < len;) {
297         i = string.indexOf(QLatin1String(prot), i);
298         if (i == -1) {
299             return false;
300         }
301         int j = i - 1;
302         for (; j >= 0; j--) {
303             const QChar &ch(string[j]);
304             if (ch.toLatin1() == 0 || !ch.isLetter()) {
305                 break;
306             }
307             if (ch.isSpace() && (i - j - 1) >= 2) {
308                 return true;
309             }
310         }
311         if (j < 0 && i >= 2) {
312             return true; // at least two letters before ":/"
313         }
314         i += 3; // skip : and / and one char
315     }
316     return false;
317 }
318 
319 // this string-to-url conversion function handles relative paths, full paths and URLs
320 // without the http-prepending that QUrl::fromUserInput does.
urlFromString(const QString & str)321 static QUrl urlFromString(const QString &str)
322 {
323     if (isAbsoluteLocalPath(str)) {
324         return QUrl::fromLocalFile(str);
325     }
326     QUrl url(str);
327     if (url.isRelative()) {
328         url.clear();
329         url.setPath(str);
330     }
331     return url;
332 }
333 
KFileWidget(const QUrl & _startDir,QWidget * parent)334 KFileWidget::KFileWidget(const QUrl &_startDir, QWidget *parent)
335     : QWidget(parent)
336     , d(new KFileWidgetPrivate(this))
337 {
338     QUrl startDir(_startDir);
339     // qDebug() << "startDir" << startDir;
340     QString filename;
341 
342     d->m_okButton = new QPushButton(this);
343     KGuiItem::assign(d->m_okButton, KStandardGuiItem::ok());
344     d->m_okButton->setDefault(true);
345     d->m_cancelButton = new QPushButton(this);
346     KGuiItem::assign(d->m_cancelButton, KStandardGuiItem::cancel());
347     // The dialog shows them
348     d->m_okButton->hide();
349     d->m_cancelButton->hide();
350 
351     d->m_opsWidget = new QWidget(this);
352     QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->m_opsWidget);
353     opsWidgetLayout->setContentsMargins(0, 0, 0, 0);
354     opsWidgetLayout->setSpacing(0);
355     // d->m_toolbar = new KToolBar(this, true);
356     d->m_toolbar = new KToolBar(d->m_opsWidget, true);
357     d->m_toolbar->setObjectName(QStringLiteral("KFileWidget::toolbar"));
358     d->m_toolbar->setMovable(false);
359     opsWidgetLayout->addWidget(d->m_toolbar);
360 
361     d->m_model = new KFilePlacesModel(this);
362 
363     // Resolve this now so that a 'kfiledialog:' URL, if specified,
364     // does not get inserted into the urlNavigator history.
365     d->m_url = getStartUrl(startDir, d->m_fileClass, filename);
366     startDir = d->m_url;
367 
368     // Don't pass startDir to the KUrlNavigator at this stage: as well as
369     // the above, it may also contain a file name which should not get
370     // inserted in that form into the old-style navigation bar history.
371     // Wait until the KIO::stat has been done later.
372     //
373     // The stat cannot be done before this point, bug 172678.
374     d->m_urlNavigator = new KUrlNavigator(d->m_model, QUrl(), d->m_opsWidget); // d->m_toolbar);
375     d->m_urlNavigator->setPlacesSelectorVisible(false);
376     opsWidgetLayout->addWidget(d->m_urlNavigator);
377 
378     d->m_ops = new KDirOperator(QUrl(), d->m_opsWidget);
379     d->m_ops->installEventFilter(this);
380     d->m_ops->setObjectName(QStringLiteral("KFileWidget::ops"));
381     d->m_ops->setIsSaving(d->m_operationMode == Saving);
382     d->m_ops->setNewFileMenuSelectDirWhenAlreadyExist(true);
383     d->m_ops->showOpenWithActions(true);
384     opsWidgetLayout->addWidget(d->m_ops);
385     connect(d->m_ops, &KDirOperator::urlEntered, this, [this](const QUrl &url) {
386         d->urlEntered(url);
387     });
388     connect(d->m_ops, &KDirOperator::fileHighlighted, this, [this](const KFileItem &item) {
389         d->fileHighlighted(item);
390     });
391     connect(d->m_ops, &KDirOperator::fileSelected, this, [this](const KFileItem &item) {
392         d->fileSelected(item);
393     });
394     connect(d->m_ops, &KDirOperator::finishedLoading, this, [this]() {
395         d->slotLoadingFinished();
396     });
397     connect(d->m_ops, &KDirOperator::keyEnterReturnPressed, this, [this]() {
398         d->slotViewKeyEnterReturnPressed();
399     });
400 
401     d->m_ops->setupMenu(KDirOperator::SortActions | KDirOperator::FileActions | KDirOperator::ViewActions);
402     KActionCollection *coll = d->m_ops->actionCollection();
403     coll->addAssociatedWidget(this);
404 
405     // add nav items to the toolbar
406     //
407     // NOTE:  The order of the button icons here differs from that
408     // found in the file manager and web browser, but has been discussed
409     // and agreed upon on the kde-core-devel mailing list:
410     //
411     // http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
412 
413     coll->action(QStringLiteral("up"))
414         ->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
415                             "For instance, if the current location is file:/home/konqi clicking this "
416                             "button will take you to file:/home.</qt>"));
417 
418     coll->action(QStringLiteral("back"))->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
419     coll->action(QStringLiteral("forward"))->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
420 
421     coll->action(QStringLiteral("reload"))->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
422     coll->action(QStringLiteral("mkdir"))->setShortcuts(KStandardShortcut::createFolder());
423     coll->action(QStringLiteral("mkdir"))->setWhatsThis(i18n("Click this button to create a new folder."));
424 
425     QAction *goToNavigatorAction = coll->addAction(QStringLiteral("gotonavigator"), this, [this]() {
426         d->activateUrlNavigator();
427     });
428     goToNavigatorAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_L));
429 
430     KToggleAction *showSidebarAction = new KToggleAction(i18n("Show Places Panel"), this);
431     coll->addAction(QStringLiteral("togglePlacesPanel"), showSidebarAction);
432     showSidebarAction->setShortcut(QKeySequence(Qt::Key_F9));
433     connect(showSidebarAction, &QAction::toggled, this, [this](bool show) {
434         d->togglePlacesPanel(show);
435     });
436 
437     KToggleAction *showBookmarksAction = new KToggleAction(i18n("Show Bookmarks Button"), this);
438     coll->addAction(QStringLiteral("toggleBookmarks"), showBookmarksAction);
439     connect(showBookmarksAction, &QAction::toggled, this, [this](bool show) {
440         d->toggleBookmarks(show);
441     });
442 
443     // Build the settings menu
444     KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("configure")), i18n("Options"), this);
445     coll->addAction(QStringLiteral("extra menu"), menu);
446     menu->setWhatsThis(
447         i18n("<qt>This is the preferences menu for the file dialog. "
448              "Various options can be accessed from this menu including: <ul>"
449              "<li>how files are sorted in the list</li>"
450              "<li>types of view, including icon and list</li>"
451              "<li>showing of hidden files</li>"
452              "<li>the Places panel</li>"
453              "<li>file previews</li>"
454              "<li>separating folders from files</li></ul></qt>"));
455 
456     menu->addAction(coll->action(QStringLiteral("allow expansion")));
457     menu->addSeparator();
458     menu->addAction(coll->action(QStringLiteral("show hidden")));
459     menu->addAction(showSidebarAction);
460     menu->addAction(showBookmarksAction);
461     menu->addAction(coll->action(QStringLiteral("preview")));
462 
463     menu->setPopupMode(QToolButton::InstantPopup);
464     connect(menu->menu(), &QMenu::aboutToShow, d->m_ops, &KDirOperator::updateSelectionDependentActions);
465 
466     d->m_iconSizeSlider = new QSlider(this);
467     d->m_iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
468     d->m_iconSizeSlider->setMinimumWidth(40);
469     d->m_iconSizeSlider->setOrientation(Qt::Horizontal);
470     d->m_iconSizeSlider->setMinimum(KIconLoader::SizeSmall);
471     d->m_iconSizeSlider->setMaximum(KIconLoader::SizeEnormous);
472     d->m_iconSizeSlider->installEventFilter(this);
473 
474     connect(d->m_iconSizeSlider, &QAbstractSlider::valueChanged, this, [this](int value) {
475         d->slotIconSizeChanged(value);
476     });
477 
478     connect(d->m_iconSizeSlider, &QAbstractSlider::sliderMoved, this, [this](int value) {
479         d->slotIconSizeSliderMoved(value);
480     });
481 
482     connect(d->m_ops, &KDirOperator::currentIconSizeChanged, this, [this](int value) {
483         d->m_iconSizeSlider->setValue(value);
484         d->m_zoomOutAction->setDisabled(value <= d->m_iconSizeSlider->minimum());
485         d->m_zoomInAction->setDisabled(value >= d->m_iconSizeSlider->maximum());
486     });
487 
488     d->m_zoomOutAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-out")), i18n("Zoom out"), this);
489     connect(d->m_zoomOutAction, &QAction::triggered, this, [this]() {
490         d->zoomOutIconsSize();
491     });
492 
493     d->m_zoomInAction = new QAction(QIcon::fromTheme(QStringLiteral("file-zoom-in")), i18n("Zoom in"), this);
494     connect(d->m_zoomInAction, &QAction::triggered, this, [this]() {
495         d->zoomInIconsSize();
496     });
497 
498     d->m_bookmarkButton = new KActionMenu(QIcon::fromTheme(QStringLiteral("bookmarks")), i18n("Bookmarks"), this);
499     d->m_bookmarkButton->setPopupMode(QToolButton::InstantPopup);
500     coll->addAction(QStringLiteral("bookmark"), d->m_bookmarkButton);
501     d->m_bookmarkButton->setWhatsThis(
502         i18n("<qt>This button allows you to bookmark specific locations. "
503              "Click on this button to open the bookmark menu where you may add, "
504              "edit or select a bookmark.<br /><br />"
505              "These bookmarks are specific to the file dialog, but otherwise operate "
506              "like bookmarks elsewhere in KDE.</qt>"));
507 
508     QWidget *midSpacer = new QWidget(this);
509     midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
510 
511     d->m_toolbar->addAction(coll->action(QStringLiteral("back")));
512     d->m_toolbar->addAction(coll->action(QStringLiteral("forward")));
513     d->m_toolbar->addAction(coll->action(QStringLiteral("up")));
514     d->m_toolbar->addAction(coll->action(QStringLiteral("reload")));
515     d->m_toolbar->addSeparator();
516     d->m_toolbar->addAction(coll->action(QStringLiteral("icons view")));
517     d->m_toolbar->addAction(coll->action(QStringLiteral("compact view")));
518     d->m_toolbar->addAction(coll->action(QStringLiteral("details view")));
519     d->m_toolbar->addSeparator();
520     d->m_toolbar->addAction(coll->action(QStringLiteral("inline preview")));
521     d->m_toolbar->addAction(coll->action(QStringLiteral("sorting menu")));
522     d->m_toolbar->addAction(d->m_bookmarkButton);
523 
524     d->m_toolbar->addWidget(midSpacer);
525 
526     d->m_toolbar->addAction(d->m_zoomOutAction);
527     d->m_toolbar->addWidget(d->m_iconSizeSlider);
528     d->m_toolbar->addAction(d->m_zoomInAction);
529     d->m_toolbar->addSeparator();
530     d->m_toolbar->addAction(coll->action(QStringLiteral("mkdir")));
531     d->m_toolbar->addAction(menu);
532 
533     d->m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
534     d->m_toolbar->setMovable(false);
535 
536     KUrlComboBox *pathCombo = d->m_urlNavigator->editor();
537     KUrlCompletion *pathCompletionObj = new KUrlCompletion(KUrlCompletion::DirCompletion);
538     pathCombo->setCompletionObject(pathCompletionObj);
539     pathCombo->setAutoDeleteCompletionObject(true);
540 
541     connect(d->m_urlNavigator, &KUrlNavigator::urlChanged, this, [this](const QUrl &url) {
542         d->enterUrl(url);
543     });
544     connect(d->m_urlNavigator, &KUrlNavigator::returnPressed, d->m_ops, qOverload<>(&QWidget::setFocus));
545 
546     // the Location label/edit
547     d->m_locationLabel = new QLabel(i18n("&Name:"), this);
548     d->m_locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this);
549     d->m_locationEdit->installEventFilter(this);
550     // Properly let the dialog be resized (to smaller). Otherwise we could have
551     // huge dialogs that can't be resized to smaller (it would be as big as the longest
552     // item in this combo box). (ereslibre)
553     d->m_locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
554     connect(d->m_locationEdit, &KUrlComboBox::editTextChanged, this, [this](const QString &text) {
555         d->slotLocationChanged(text);
556     });
557 
558     d->updateLocationWhatsThis();
559     d->m_locationLabel->setBuddy(d->m_locationEdit);
560 
561     KUrlCompletion *fileCompletionObj = new KUrlCompletion(KUrlCompletion::FileCompletion);
562     d->m_locationEdit->setCompletionObject(fileCompletionObj);
563     d->m_locationEdit->setAutoDeleteCompletionObject(true);
564     connect(fileCompletionObj, &KUrlCompletion::match, this, [this](const QString &match) {
565         d->fileCompletion(match);
566     });
567 
568     connect(d->m_locationEdit, qOverload<const QString &>(&KUrlComboBox::returnPressed), this, [this](const QString &text) {
569         d->locationAccepted(text);
570     });
571 
572     // the Filter label/edit
573     d->m_filterLabel = new QLabel(this);
574     d->m_filterWidget = new KFileFilterCombo(this);
575     d->updateFilterText();
576     // Properly let the dialog be resized (to smaller). Otherwise we could have
577     // huge dialogs that can't be resized to smaller (it would be as big as the longest
578     // item in this combo box). (ereslibre)
579     d->m_filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
580     d->m_filterLabel->setBuddy(d->m_filterWidget);
581     connect(d->m_filterWidget, &KFileFilterCombo::filterChanged, this, [this]() {
582         d->slotFilterChanged();
583     });
584 
585     d->m_filterDelayTimer.setSingleShot(true);
586     d->m_filterDelayTimer.setInterval(300);
587     connect(d->m_filterWidget, &QComboBox::editTextChanged, &d->m_filterDelayTimer, qOverload<>(&QTimer::start));
588     connect(&d->m_filterDelayTimer, &QTimer::timeout, this, [this]() {
589         d->slotFilterChanged();
590     });
591 
592     // the Automatically Select Extension checkbox
593     // (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
594     d->m_autoSelectExtCheckBox = new QCheckBox(this);
595     connect(d->m_autoSelectExtCheckBox, &QCheckBox::clicked, this, [this]() {
596         d->slotAutoSelectExtClicked();
597     });
598 
599     d->initGUI(); // activate GM
600 
601     // read our configuration
602     KSharedConfig::Ptr config = KSharedConfig::openConfig();
603     config->reparseConfiguration(); // grab newly added dirs by other processes (#403524)
604     KConfigGroup group(config, ConfigGroup);
605     readConfig(group);
606 
607     coll->action(QStringLiteral("inline preview"))->setChecked(d->m_ops->isInlinePreviewShown());
608     d->m_iconSizeSlider->setValue(d->m_ops->iconSize());
609 
610     KFilePreviewGenerator *pg = d->m_ops->previewGenerator();
611     if (pg) {
612         coll->action(QStringLiteral("inline preview"))->setChecked(pg->isPreviewShown());
613     }
614 
615     // getStartUrl() above will have resolved the startDir parameter into
616     // a directory and file name in the two cases: (a) where it is a
617     // special "kfiledialog:" URL, or (b) where it is a plain file name
618     // only without directory or protocol.  For any other startDir
619     // specified, it is not possible to resolve whether there is a file name
620     // present just by looking at the URL; the only way to be sure is
621     // to stat it.
622     bool statRes = false;
623     if (filename.isEmpty()) {
624         KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo);
625         KJobWidgets::setWindow(statJob, this);
626         statRes = statJob->exec();
627         // qDebug() << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir();
628         if (!statRes || !statJob->statResult().isDir()) {
629             filename = startDir.fileName();
630             startDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
631             // qDebug() << "statJob -> startDir" << startDir << "filename" << filename;
632         }
633     }
634 
635     d->m_ops->setUrl(startDir, true);
636     d->m_urlNavigator->setLocationUrl(startDir);
637     if (d->m_placesView) {
638         d->m_placesView->setUrl(startDir);
639     }
640 
641     // We have a file name either explicitly specified, or have checked that
642     // we could stat it and it is not a directory.  Set it.
643     if (!filename.isEmpty()) {
644         QLineEdit *lineEdit = d->m_locationEdit->lineEdit();
645         // qDebug() << "selecting filename" << filename;
646         if (statRes) {
647             d->setLocationText(QUrl(filename));
648         } else {
649             lineEdit->setText(filename);
650             // Preserve this filename when clicking on the view (cf fileHighlighted)
651             lineEdit->setModified(true);
652         }
653         lineEdit->selectAll();
654     }
655 
656     d->m_locationEdit->setFocus();
657 }
658 
~KFileWidget()659 KFileWidget::~KFileWidget()
660 {
661     KSharedConfig::Ptr config = KSharedConfig::openConfig();
662     config->sync();
663 }
664 
setLocationLabel(const QString & text)665 void KFileWidget::setLocationLabel(const QString &text)
666 {
667     d->m_locationLabel->setText(text);
668 }
669 
setFilter(const QString & filter)670 void KFileWidget::setFilter(const QString &filter)
671 {
672     int pos = filter.indexOf(QLatin1Char('/'));
673 
674     // Check for an un-escaped '/', if found
675     // interpret as a MIME filter.
676 
677     if (pos > 0 && filter[pos - 1] != QLatin1Char('\\')) {
678         QStringList filters = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
679         setMimeFilter(filters);
680         return;
681     }
682 
683     // Strip the escape characters from
684     // escaped '/' characters.
685 
686     QString copy(filter);
687     for (pos = 0; (pos = copy.indexOf(QLatin1String("\\/"), pos)) != -1; ++pos) {
688         copy.remove(pos, 1);
689     }
690 
691     d->m_ops->clearFilter();
692     d->m_filterWidget->setFilter(copy);
693     d->m_ops->setNameFilter(d->m_filterWidget->currentFilter());
694     d->m_ops->updateDir();
695     d->m_hasDefaultFilter = false;
696     d->m_filterWidget->setEditable(true);
697 
698     d->updateAutoSelectExtension();
699 }
700 
currentFilter() const701 QString KFileWidget::currentFilter() const
702 {
703     return d->m_filterWidget->currentFilter();
704 }
705 
setMimeFilter(const QStringList & mimeTypes,const QString & defaultType)706 void KFileWidget::setMimeFilter(const QStringList &mimeTypes, const QString &defaultType)
707 {
708     d->m_filterWidget->setMimeFilter(mimeTypes, defaultType);
709 
710     QStringList types =
711         d->m_filterWidget->currentFilter().split(QLatin1Char(' '), Qt::SkipEmptyParts); // QStringList::split(" ", d->m_filterWidget->currentFilter());
712 
713     types.append(QStringLiteral("inode/directory"));
714     d->m_ops->clearFilter();
715     d->m_ops->setMimeFilter(types);
716     d->m_hasDefaultFilter = !defaultType.isEmpty();
717     d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || d->m_operationMode != Saving);
718 
719     d->updateAutoSelectExtension();
720     d->updateFilterText();
721 }
722 
clearFilter()723 void KFileWidget::clearFilter()
724 {
725     d->m_filterWidget->setFilter(QString());
726     d->m_ops->clearFilter();
727     d->m_hasDefaultFilter = false;
728     d->m_filterWidget->setEditable(true);
729 
730     d->updateAutoSelectExtension();
731 }
732 
currentMimeFilter() const733 QString KFileWidget::currentMimeFilter() const
734 {
735     int i = d->m_filterWidget->currentIndex();
736     if (d->m_filterWidget->showsAllTypes() && i == 0) {
737         return QString(); // The "all types" item has no MIME type
738     }
739 
740     return d->m_filterWidget->filters().at(i);
741 }
742 
currentFilterMimeType()743 QMimeType KFileWidget::currentFilterMimeType()
744 {
745     QMimeDatabase db;
746     return db.mimeTypeForName(currentMimeFilter());
747 }
748 
setPreviewWidget(KPreviewWidgetBase * w)749 void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w)
750 {
751     d->m_ops->setPreviewWidget(w);
752     d->m_ops->clearHistory();
753     d->m_hasView = true;
754 }
755 
getCompleteUrl(const QString & _url) const756 QUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const
757 {
758     //     qDebug() << "got url " << _url;
759 
760     const QString url = KShell::tildeExpand(_url);
761     QUrl u;
762 
763     if (isAbsoluteLocalPath(url)) {
764         u = QUrl::fromLocalFile(url);
765     } else {
766         QUrl relativeUrlTest(m_ops->url());
767         relativeUrlTest.setPath(concatPaths(relativeUrlTest.path(), url));
768         if (!m_ops->dirLister()->findByUrl(relativeUrlTest).isNull() || !KProtocolInfo::isKnownProtocol(relativeUrlTest)) {
769             u = relativeUrlTest;
770         } else {
771             // Try to preserve URLs if they have a scheme (for example,
772             // "https://example.com/foo.txt") and otherwise resolve relative
773             // paths to absolute ones (e.g. "foo.txt" -> "file:///tmp/foo.txt").
774             u = QUrl(url);
775             if (u.isRelative()) {
776                 u = relativeUrlTest;
777             }
778         }
779     }
780 
781     return u;
782 }
783 
sizeHint() const784 QSize KFileWidget::sizeHint() const
785 {
786     int fontSize = fontMetrics().height();
787     const QSize goodSize(48 * fontSize, 30 * fontSize);
788     const QSize screenSize = QApplication::desktop()->availableGeometry(this).size();
789     const QSize minSize(screenSize / 2);
790     const QSize maxSize(screenSize * qreal(0.9));
791     return (goodSize.expandedTo(minSize).boundedTo(maxSize));
792 }
793 
794 static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url);
795 
796 /**
797  * Escape the given Url so that is fit for use in the selected list of file. This
798  * mainly handles double quote (") characters. These are used to separate entries
799  * in the list, however, if `"` appears in the filename (or path), this will be
800  * escaped as `\"`. Later, the tokenizer is able to understand the difference
801  * and do the right thing
802  */
803 static QString escapeDoubleQuotes(QString &&path);
804 
805 // Called by KFileDialog
slotOk()806 void KFileWidget::slotOk()
807 {
808     //     qDebug() << "slotOk\n";
809 
810     const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText()));
811 
812     QList<QUrl> locationEditCurrentTextList(d->tokenize(locationEditCurrentText));
813     KFile::Modes mode = d->m_ops->mode();
814 
815     // if there is nothing to do, just return from here
816     if (locationEditCurrentTextList.isEmpty()) {
817         return;
818     }
819 
820     // Make sure that one of the modes was provided
821     if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) {
822         mode |= KFile::File;
823         // qDebug() << "No mode() provided";
824     }
825 
826     // Clear the list as we are going to refill it
827     d->m_urlList.clear();
828 
829     const bool directoryMode = (mode & KFile::Directory);
830     const bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files);
831 
832     // if we are on file mode, and the list of provided files/folder is greater than one, inform
833     // the user about it
834     if (locationEditCurrentTextList.count() > 1) {
835         if (mode & KFile::File) {
836             KMessageBox::sorry(this, i18n("You can only select one file"), i18n("More than one file provided"));
837             return;
838         }
839 
840         /**
841          * Logic of the next part of code (ends at "end multi relative urls").
842          *
843          * We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'.
844          * Why we need to support this ? Because we provide tree views, which aren't plain.
845          *
846          * Now, how does this logic work. It will get the first element on the list (with no filename),
847          * following the previous example say "/home/foo" and set it as the top most url.
848          *
849          * After this, it will iterate over the rest of items and check if this URL (topmost url)
850          * contains the url being iterated.
851          *
852          * As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping
853          * filename), and a false will be returned. Then we upUrl the top most url, resulting in
854          * "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we
855          * have "/" against "/boot/grub", what returns true for us, so we can say that the closest
856          * common ancestor of both is "/".
857          *
858          * This example has been written for 2 urls, but this works for any number of urls.
859          */
860         if (!d->m_differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this
861             int start = 0;
862             QUrl topMostUrl;
863             KIO::StatJob *statJob = nullptr;
864             bool res = false;
865 
866             // we need to check for a valid first url, so in theory we only iterate one time over
867             // this loop. However it can happen that the user did
868             // "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first
869             // candidate.
870             while (!res && start < locationEditCurrentTextList.count()) {
871                 topMostUrl = locationEditCurrentTextList.at(start);
872                 statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo);
873                 KJobWidgets::setWindow(statJob, this);
874                 res = statJob->exec();
875                 start++;
876             }
877 
878             Q_ASSERT(statJob);
879 
880             // if this is not a dir, strip the filename. after this we have an existent and valid
881             // dir (we stated correctly the file).
882             if (!statJob->statResult().isDir()) {
883                 topMostUrl = topMostUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
884             }
885 
886             // now the funny part. for the rest of filenames, go and look for the closest ancestor
887             // of all them.
888             for (int i = start; i < locationEditCurrentTextList.count(); ++i) {
889                 QUrl currUrl = locationEditCurrentTextList.at(i);
890                 KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo);
891                 KJobWidgets::setWindow(statJob, this);
892                 int res = statJob->exec();
893                 if (res) {
894                     // again, we don't care about filenames
895                     if (!statJob->statResult().isDir()) {
896                         currUrl = currUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
897                     }
898 
899                     // iterate while this item is contained on the top most url
900                     while (!topMostUrl.matches(currUrl, QUrl::StripTrailingSlash) && !topMostUrl.isParentOf(currUrl)) {
901                         topMostUrl = KIO::upUrl(topMostUrl);
902                     }
903                 }
904             }
905 
906             // now recalculate all paths for them being relative in base of the top most url
907             QStringList stringList;
908             stringList.reserve(locationEditCurrentTextList.count());
909             for (int i = 0; i < locationEditCurrentTextList.count(); ++i) {
910                 Q_ASSERT(topMostUrl.isParentOf(locationEditCurrentTextList[i]));
911                 QString relativePath = relativePathOrUrl(topMostUrl, locationEditCurrentTextList[i]);
912                 stringList << escapeDoubleQuotes(std::move(relativePath));
913             }
914 
915             d->m_ops->setUrl(topMostUrl, true);
916             const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true);
917             d->m_locationEdit->lineEdit()->setText(QStringLiteral("\"%1\"").arg(stringList.join(QStringLiteral("\" \""))));
918             d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked);
919 
920             d->m_differentHierarchyLevelItemsEntered = true;
921             slotOk();
922             return;
923         }
924         /**
925          * end multi relative urls
926          */
927     } else if (!locationEditCurrentTextList.isEmpty()) {
928         // if we are on file or files mode, and we have an absolute url written by
929         // the user:
930         //  * convert it to relative and call slotOk again if the protocol supports listing.
931         //  * use the full url if the protocol doesn't support listing
932         // This is because when using a protocol that supports listing we want to show the directory
933         // the user just opened/saved from the next time they open the dialog, it makes sense usability wise.
934         // If the protocol doesn't support listing (i.e. http:// ) the user would end up with the dialog
935         // showing an "empty directory" which is bad usability wise.
936         if (!locationEditCurrentText.isEmpty() && !onlyDirectoryMode
937             && (isAbsoluteLocalPath(locationEditCurrentText) || containsProtocolSection(locationEditCurrentText))) {
938             QUrl url = urlFromString(locationEditCurrentText);
939             if (KProtocolManager::supportsListing(url)) {
940                 QString fileName;
941                 if (d->m_operationMode == Opening) {
942                     KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
943                     KJobWidgets::setWindow(statJob, this);
944                     int res = statJob->exec();
945                     if (res) {
946                         if (!statJob->statResult().isDir()) {
947                             fileName = url.fileName();
948                             url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash
949                         } else {
950                             if (!url.path().endsWith(QLatin1Char('/'))) {
951                                 url.setPath(url.path() + QLatin1Char('/'));
952                             }
953                         }
954                     }
955                 } else {
956                     const QUrl directory = url.adjusted(QUrl::RemoveFilename);
957                     // Check if the folder exists
958                     KIO::StatJob *statJob = KIO::stat(directory, KIO::HideProgressInfo);
959                     KJobWidgets::setWindow(statJob, this);
960                     int res = statJob->exec();
961                     if (res) {
962                         if (statJob->statResult().isDir()) {
963                             url = url.adjusted(QUrl::StripTrailingSlash);
964                             fileName = url.fileName();
965                             url = url.adjusted(QUrl::RemoveFilename);
966                         }
967                     }
968                 }
969                 d->m_ops->setUrl(url, true);
970                 const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true);
971                 d->m_locationEdit->lineEdit()->setText(fileName);
972                 d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked);
973                 slotOk();
974                 return;
975             } else {
976                 locationEditCurrentTextList = {url};
977             }
978         }
979     }
980 
981     // restore it
982     d->m_differentHierarchyLevelItemsEntered = false;
983 
984     // locationEditCurrentTextList contains absolute paths
985     // this is the general loop for the File and Files mode. Obviously we know
986     // that the File mode will iterate only one time here
987     QList<QUrl>::ConstIterator it = locationEditCurrentTextList.constBegin();
988     bool filesInList = false;
989     while (it != locationEditCurrentTextList.constEnd()) {
990         QUrl url(*it);
991 
992         if (d->m_operationMode == Saving && !directoryMode) {
993             d->appendExtension(url);
994         }
995 
996         d->m_url = url;
997         KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
998         KJobWidgets::setWindow(statJob, this);
999         int res = statJob->exec();
1000 
1001         if (!KUrlAuthorized::authorizeUrlAction(QStringLiteral("open"), QUrl(), url)) {
1002             QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_url.toDisplayString());
1003             KMessageBox::error(this, msg);
1004             return;
1005         }
1006 
1007         // if we are on local mode, make sure we haven't got a remote base url
1008         if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->m_url).isLocalFile()) {
1009             KMessageBox::sorry(this, i18n("You can only select local files"), i18n("Remote files not accepted"));
1010             return;
1011         }
1012 
1013         const auto &supportedSchemes = d->m_model->supportedSchemes();
1014         if (!supportedSchemes.isEmpty() && !supportedSchemes.contains(d->m_url.scheme())) {
1015             KMessageBox::sorry(this,
1016                                i18np("The selected URL uses an unsupported scheme. "
1017                                      "Please use the following scheme: %2",
1018                                      "The selected URL uses an unsupported scheme. "
1019                                      "Please use one of the following schemes: %2",
1020                                      supportedSchemes.size(),
1021                                      supportedSchemes.join(QLatin1String(", "))),
1022                                i18n("Unsupported URL scheme"));
1023             return;
1024         }
1025 
1026         // if we are given a folder when not on directory mode, let's get into it
1027         if (res && !directoryMode && statJob->statResult().isDir()) {
1028             // check if we were given more than one folder, in that case we don't know to which one
1029             // cd
1030             ++it;
1031             while (it != locationEditCurrentTextList.constEnd()) {
1032                 QUrl checkUrl(*it);
1033                 KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo);
1034                 KJobWidgets::setWindow(checkStatJob, this);
1035                 bool res = checkStatJob->exec();
1036                 if (res && checkStatJob->statResult().isDir()) {
1037                     KMessageBox::sorry(this,
1038                                        i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide "
1039                                             "which one to enter. Please select only one folder to list it."),
1040                                        i18n("More than one folder provided"));
1041                     return;
1042                 } else if (res) {
1043                     filesInList = true;
1044                 }
1045                 ++it;
1046             }
1047             if (filesInList) {
1048                 KMessageBox::information(
1049                     this,
1050                     i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"),
1051                     i18n("Files and folders selected"));
1052             }
1053             d->m_ops->setUrl(url, true);
1054             const bool signalsBlocked = d->m_locationEdit->lineEdit()->blockSignals(true);
1055             d->m_locationEdit->lineEdit()->setText(QString());
1056             d->m_locationEdit->lineEdit()->blockSignals(signalsBlocked);
1057             return;
1058         } else if (res && onlyDirectoryMode && !statJob->statResult().isDir()) {
1059             // if we are given a file when on directory only mode, reject it
1060             return;
1061         } else if (!(mode & KFile::ExistingOnly) || res) {
1062             // if we don't care about ExistingOnly flag, add the file even if
1063             // it doesn't exist. If we care about it, don't add it to the list
1064             if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) {
1065                 d->m_urlList << url;
1066             }
1067             filesInList = true;
1068         } else {
1069             KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.toDisplayString(QUrl::PreferLocalFile)), i18n("Cannot open file"));
1070             return; // do not emit accepted() if we had ExistingOnly flag and stat failed
1071         }
1072 
1073         if ((d->m_operationMode == Saving) && d->m_confirmOverwrite && !d->toOverwrite(url)) {
1074             return;
1075         }
1076 
1077         ++it;
1078     }
1079 
1080     // if we have reached this point and we didn't return before, that is because
1081     // we want this dialog to be accepted
1082     Q_EMIT accepted();
1083 }
1084 
accept()1085 void KFileWidget::accept()
1086 {
1087     d->m_inAccept = true;
1088 
1089     *lastDirectory() = d->m_ops->url();
1090     if (!d->m_fileClass.isEmpty()) {
1091         KRecentDirs::add(d->m_fileClass, d->m_ops->url().toString());
1092     }
1093 
1094     // clear the topmost item, we insert it as full path later on as item 1
1095     d->m_locationEdit->setItemText(0, QString());
1096 
1097     const QList<QUrl> list = selectedUrls();
1098     int atmost = d->m_locationEdit->maxItems(); // don't add more items than necessary
1099     for (const auto &url : list) {
1100         if (atmost-- == 0) {
1101             break;
1102         }
1103 
1104         // we strip the last slash (-1) because KUrlComboBox does that as well
1105         // when operating in file-mode. If we wouldn't , dupe-finding wouldn't
1106         // work.
1107         const QString file = url.toDisplayString(QUrl::StripTrailingSlash | QUrl::PreferLocalFile);
1108 
1109         // remove dupes
1110         for (int i = 1; i < d->m_locationEdit->count(); ++i) {
1111             if (d->m_locationEdit->itemText(i) == file) {
1112                 d->m_locationEdit->removeItem(i--);
1113                 break;
1114             }
1115         }
1116         // FIXME I don't think this works correctly when the KUrlComboBox has some default urls.
1117         // KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping
1118         // track of maxItems, and we shouldn't be able to insert items as we please.
1119         d->m_locationEdit->insertItem(1, file);
1120     }
1121 
1122     d->writeViewConfig();
1123     d->saveRecentFiles();
1124 
1125     d->addToRecentDocuments();
1126 
1127     if (!(mode() & KFile::Files)) { // single selection
1128         Q_EMIT fileSelected(d->m_url);
1129     }
1130 
1131     d->m_ops->close();
1132 }
1133 
fileHighlighted(const KFileItem & i)1134 void KFileWidgetPrivate::fileHighlighted(const KFileItem &i)
1135 {
1136     if ((m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty())) { // don't disturb
1137         return;
1138     }
1139 
1140     if (!i.isNull() && i.isDir() && !(m_ops->mode() & KFile::Directory)) {
1141         return;
1142     }
1143 
1144     const bool modified = m_locationEdit->lineEdit()->isModified();
1145 
1146     if (!(m_ops->mode() & KFile::Files)) {
1147         if (i.isNull()) {
1148             if (!modified) {
1149                 setLocationText(QUrl());
1150             }
1151             return;
1152         }
1153 
1154         m_url = i.url();
1155 
1156         if (!m_locationEdit->hasFocus()) { // don't disturb while editing
1157             setLocationText(m_url);
1158         }
1159 
1160         Q_EMIT q->fileHighlighted(m_url);
1161     } else {
1162         multiSelectionChanged();
1163         Q_EMIT q->selectionChanged();
1164     }
1165 
1166     m_locationEdit->lineEdit()->setModified(false);
1167 
1168     // When saving, and when double-click mode is being used, highlight the
1169     // filename after a file is single-clicked so the user has a chance to quickly
1170     // rename it if desired
1171     // Note that double-clicking will override this and overwrite regardless of
1172     // single/double click mouse setting (see slotViewDoubleClicked() )
1173     if (m_operationMode == KFileWidget::Saving) {
1174         m_locationEdit->setFocus();
1175     }
1176 }
1177 
fileSelected(const KFileItem & i)1178 void KFileWidgetPrivate::fileSelected(const KFileItem &i)
1179 {
1180     if (!i.isNull() && i.isDir()) {
1181         return;
1182     }
1183 
1184     if (!(m_ops->mode() & KFile::Files)) {
1185         if (i.isNull()) {
1186             setLocationText(QUrl());
1187             return;
1188         }
1189         setLocationText(i.url());
1190     } else {
1191         multiSelectionChanged();
1192         Q_EMIT q->selectionChanged();
1193     }
1194 
1195     // Same as above in fileHighlighted(), but for single-click mode
1196     if (m_operationMode == KFileWidget::Saving) {
1197         m_locationEdit->setFocus();
1198     } else {
1199         q->slotOk();
1200     }
1201 }
1202 
1203 // I know it's slow to always iterate thru the whole filelist
1204 // (d->m_ops->selectedItems()), but what can we do?
multiSelectionChanged()1205 void KFileWidgetPrivate::multiSelectionChanged()
1206 {
1207     if (m_locationEdit->hasFocus() && !m_locationEdit->currentText().isEmpty()) { // don't disturb
1208         return;
1209     }
1210 
1211     const KFileItemList list = m_ops->selectedItems();
1212 
1213     if (list.isEmpty()) {
1214         setLocationText(QUrl());
1215         return;
1216     }
1217 
1218     setLocationText(list.urlList());
1219 }
1220 
setDummyHistoryEntry(const QString & text,const QIcon & icon,bool usePreviousPixmapIfNull)1221 void KFileWidgetPrivate::setDummyHistoryEntry(const QString &text, const QIcon &icon, bool usePreviousPixmapIfNull)
1222 {
1223     // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1224     // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1225     // KDirOperator's view-selection in there
1226     const QSignalBlocker blocker(m_locationEdit);
1227 
1228     bool dummyExists = m_dummyAdded;
1229 
1230     int cursorPosition = m_locationEdit->lineEdit()->cursorPosition();
1231 
1232     if (m_dummyAdded) {
1233         if (!icon.isNull()) {
1234             m_locationEdit->setItemIcon(0, icon);
1235             m_locationEdit->setItemText(0, text);
1236         } else {
1237             if (!usePreviousPixmapIfNull) {
1238                 m_locationEdit->setItemIcon(0, QPixmap());
1239             }
1240             m_locationEdit->setItemText(0, text);
1241         }
1242     } else {
1243         if (!text.isEmpty()) {
1244             if (!icon.isNull()) {
1245                 m_locationEdit->insertItem(0, icon, text);
1246             } else {
1247                 if (!usePreviousPixmapIfNull) {
1248                     m_locationEdit->insertItem(0, QPixmap(), text);
1249                 } else {
1250                     m_locationEdit->insertItem(0, text);
1251                 }
1252             }
1253             m_dummyAdded = true;
1254             dummyExists = true;
1255         }
1256     }
1257 
1258     if (dummyExists && !text.isEmpty()) {
1259         m_locationEdit->setCurrentIndex(0);
1260     }
1261 
1262     m_locationEdit->lineEdit()->setCursorPosition(cursorPosition);
1263 }
1264 
removeDummyHistoryEntry()1265 void KFileWidgetPrivate::removeDummyHistoryEntry()
1266 {
1267     if (!m_dummyAdded) {
1268         return;
1269     }
1270 
1271     // Block m_locationEdit signals as setCurrentItem() will cause textChanged() to get
1272     // emitted, so slotLocationChanged() will be called. Make sure we don't clear the
1273     // KDirOperator's view-selection in there
1274     const QSignalBlocker blocker(m_locationEdit);
1275 
1276     if (m_locationEdit->count()) {
1277         m_locationEdit->removeItem(0);
1278     }
1279     m_locationEdit->setCurrentIndex(-1);
1280     m_dummyAdded = false;
1281 }
1282 
setLocationText(const QUrl & url)1283 void KFileWidgetPrivate::setLocationText(const QUrl &url)
1284 {
1285     if (!url.isEmpty()) {
1286         if (!url.isRelative()) {
1287             const QUrl directory = url.adjusted(QUrl::RemoveFilename);
1288             if (!directory.path().isEmpty()) {
1289                 q->setUrl(directory, false);
1290             } else {
1291                 q->setUrl(url, false);
1292             }
1293         }
1294 
1295         const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(url), QIcon::fromTheme(QStringLiteral("application-octet-stream")));
1296         setDummyHistoryEntry(url.fileName(), mimeTypeIcon);
1297     } else {
1298         removeDummyHistoryEntry();
1299     }
1300 
1301     if (m_operationMode == KFileWidget::Saving) {
1302         setNonExtSelection();
1303     }
1304 }
1305 
relativePathOrUrl(const QUrl & baseUrl,const QUrl & url)1306 static QString relativePathOrUrl(const QUrl &baseUrl, const QUrl &url)
1307 {
1308     if (baseUrl.isParentOf(url)) {
1309         const QString basePath(QDir::cleanPath(baseUrl.path()));
1310         QString relPath(QDir::cleanPath(url.path()));
1311         relPath.remove(0, basePath.length());
1312         if (relPath.startsWith(QLatin1Char('/'))) {
1313             relPath.remove(0, 1);
1314         }
1315         return relPath;
1316     } else {
1317         return url.toDisplayString();
1318     }
1319 }
1320 
escapeDoubleQuotes(QString && path)1321 static QString escapeDoubleQuotes(QString &&path)
1322 {
1323     // First escape the escape character that we are using
1324     path.replace(QStringLiteral("\\"), QStringLiteral("\\\\"));
1325     // Second, escape the quotes
1326     path.replace(QStringLiteral("\""), QStringLiteral("\\\""));
1327     return path;
1328 }
1329 
setLocationText(const QList<QUrl> & urlList)1330 void KFileWidgetPrivate::setLocationText(const QList<QUrl> &urlList)
1331 {
1332     const QUrl currUrl = m_ops->url();
1333 
1334     if (urlList.count() > 1) {
1335         QString urls;
1336         for (const QUrl &url : urlList) {
1337             urls += QStringLiteral("\"%1\" ").arg(escapeDoubleQuotes(relativePathOrUrl(currUrl, url)));
1338         }
1339         urls.chop(1);
1340 
1341         setDummyHistoryEntry(urls, QIcon(), false);
1342     } else if (urlList.count() == 1) {
1343         const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(urlList[0]), QIcon::fromTheme(QStringLiteral("application-octet-stream")));
1344         setDummyHistoryEntry(escapeDoubleQuotes(relativePathOrUrl(currUrl, urlList[0])), mimeTypeIcon);
1345     } else {
1346         removeDummyHistoryEntry();
1347     }
1348 
1349     if (m_operationMode == KFileWidget::Saving) {
1350         setNonExtSelection();
1351     }
1352 }
1353 
updateLocationWhatsThis()1354 void KFileWidgetPrivate::updateLocationWhatsThis()
1355 {
1356     QString whatsThisText;
1357     if (m_operationMode == KFileWidget::Saving) {
1358         whatsThisText = QLatin1String("<qt>") + i18n("This is the name to save the file as.") + i18n(autocompletionWhatsThisText);
1359     } else if (m_ops->mode() & KFile::Files) {
1360         whatsThisText = QLatin1String("<qt>")
1361             + i18n("This is the list of files to open. More than "
1362                    "one file can be specified by listing several "
1363                    "files, separated by spaces.")
1364             + i18n(autocompletionWhatsThisText);
1365     } else {
1366         whatsThisText = QLatin1String("<qt>") + i18n("This is the name of the file to open.") + i18n(autocompletionWhatsThisText);
1367     }
1368 
1369     m_locationLabel->setWhatsThis(whatsThisText);
1370     m_locationEdit->setWhatsThis(whatsThisText);
1371 }
1372 
initPlacesPanel()1373 void KFileWidgetPrivate::initPlacesPanel()
1374 {
1375     if (m_placesDock) {
1376         return;
1377     }
1378 
1379     m_placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
1380     m_placesDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
1381     m_placesDock->setTitleBarWidget(new KDEPrivate::KFileWidgetDockTitleBar(m_placesDock));
1382 
1383     m_placesView = new KFilePlacesView(m_placesDock);
1384     m_placesView->setModel(m_model);
1385     m_placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1386 
1387     m_placesView->setObjectName(QStringLiteral("url bar"));
1388     QObject::connect(m_placesView, &KFilePlacesView::urlChanged, q, [this](const QUrl &url) {
1389         enterUrl(url);
1390     });
1391 
1392     // need to set the current url of the urlbar manually (not via urlEntered()
1393     // here, because the initial url of KDirOperator might be the same as the
1394     // one that will be set later (and then urlEntered() won't be emitted).
1395     // TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
1396     m_placesView->setUrl(m_url);
1397 
1398     m_placesDock->setWidget(m_placesView);
1399     m_placesViewSplitter->insertWidget(0, m_placesDock);
1400 
1401     // initialize the size of the splitter
1402     m_placesViewWidth = m_configGroup.readEntry(SpeedbarWidth, m_placesView->sizeHint().width());
1403 
1404     // Needed for when the dialog is shown with the places panel initially hidden
1405     setPlacesViewSplitterSizes();
1406 
1407     QObject::connect(m_placesDock, &QDockWidget::visibilityChanged, q, [this](bool visible) {
1408         togglePlacesPanel(visible, m_placesDock);
1409     });
1410 }
1411 
setPlacesViewSplitterSizes()1412 void KFileWidgetPrivate::setPlacesViewSplitterSizes()
1413 {
1414     if (m_placesViewWidth > 0) {
1415         QList<int> sizes = m_placesViewSplitter->sizes();
1416         sizes[0] = m_placesViewWidth;
1417         sizes[1] = q->width() - m_placesViewWidth - m_placesViewSplitter->handleWidth();
1418         m_placesViewSplitter->setSizes(sizes);
1419     }
1420 }
1421 
setLafBoxColumnWidth()1422 void KFileWidgetPrivate::setLafBoxColumnWidth()
1423 {
1424     // In order to perfectly align the filename widget with KDirOperator's icon view
1425     // - m_placesViewWidth needs to account for the size of the splitter handle
1426     // - the m_lafBox grid layout spacing should only affect the label, but not the line edit
1427     const int adjustment = m_placesViewSplitter->handleWidth() - m_lafBox->horizontalSpacing();
1428     m_lafBox->setColumnMinimumWidth(0, m_placesViewWidth + adjustment);
1429 }
1430 
initGUI()1431 void KFileWidgetPrivate::initGUI()
1432 {
1433     delete m_boxLayout; // deletes all sub layouts
1434 
1435     m_boxLayout = new QVBoxLayout(q);
1436     m_boxLayout->setContentsMargins(0, 0, 0, 0); // no additional margin to the already existing
1437 
1438     m_placesViewSplitter = new QSplitter(q);
1439     m_placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
1440     m_placesViewSplitter->setChildrenCollapsible(false);
1441     m_boxLayout->addWidget(m_placesViewSplitter);
1442 
1443     QObject::connect(m_placesViewSplitter, &QSplitter::splitterMoved, q, [this](int pos, int index) {
1444         placesViewSplitterMoved(pos, index);
1445     });
1446     m_placesViewSplitter->insertWidget(0, m_opsWidget);
1447 
1448     m_vbox = new QVBoxLayout();
1449     m_vbox->setContentsMargins(0, 0, 0, 0);
1450     m_boxLayout->addLayout(m_vbox);
1451 
1452     m_lafBox = new QGridLayout();
1453 
1454     m_lafBox->addWidget(m_locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight);
1455     m_lafBox->addWidget(m_locationEdit, 0, 1, Qt::AlignVCenter);
1456     m_lafBox->addWidget(m_okButton, 0, 2, Qt::AlignVCenter);
1457 
1458     m_lafBox->addWidget(m_filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight);
1459     m_lafBox->addWidget(m_filterWidget, 1, 1, Qt::AlignVCenter);
1460     m_lafBox->addWidget(m_cancelButton, 1, 2, Qt::AlignVCenter);
1461 
1462     m_lafBox->setColumnStretch(1, 4);
1463 
1464     m_vbox->addLayout(m_lafBox);
1465 
1466     // Add the "Automatically Select Extension" checkbox
1467     const int spacingHint = q->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
1468     m_vbox->addSpacing(spacingHint);
1469     m_vbox->addWidget(m_autoSelectExtCheckBox);
1470 
1471     q->setTabOrder(m_ops, m_autoSelectExtCheckBox);
1472     q->setTabOrder(m_autoSelectExtCheckBox, m_locationEdit);
1473     q->setTabOrder(m_locationEdit, m_filterWidget);
1474     q->setTabOrder(m_filterWidget, m_okButton);
1475     q->setTabOrder(m_okButton, m_cancelButton);
1476     q->setTabOrder(m_cancelButton, m_urlNavigator);
1477     q->setTabOrder(m_urlNavigator, m_ops);
1478 }
1479 
slotFilterChanged()1480 void KFileWidgetPrivate::slotFilterChanged()
1481 {
1482     //     qDebug();
1483 
1484     m_filterDelayTimer.stop();
1485 
1486     QString filter = m_filterWidget->currentFilter();
1487     m_ops->clearFilter();
1488 
1489     if (filter.contains(QLatin1Char('/'))) {
1490         QStringList types = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
1491         types.prepend(QStringLiteral("inode/directory"));
1492         m_ops->setMimeFilter(types);
1493     } else if (filter.contains(QLatin1Char('*')) || filter.contains(QLatin1Char('?')) || filter.contains(QLatin1Char('['))) {
1494         m_ops->setNameFilter(filter);
1495     } else {
1496         m_ops->setNameFilter(QLatin1Char('*') + filter.replace(QLatin1Char(' '), QLatin1Char('*')) + QLatin1Char('*'));
1497     }
1498 
1499     updateAutoSelectExtension();
1500 
1501     m_ops->updateDir();
1502 
1503     Q_EMIT q->filterChanged(filter);
1504 }
1505 
setUrl(const QUrl & url,bool clearforward)1506 void KFileWidget::setUrl(const QUrl &url, bool clearforward)
1507 {
1508     //     qDebug();
1509 
1510     d->m_ops->setUrl(url, clearforward);
1511 }
1512 
1513 // Protected
urlEntered(const QUrl & url)1514 void KFileWidgetPrivate::urlEntered(const QUrl &url)
1515 {
1516     //     qDebug();
1517 
1518     KUrlComboBox *pathCombo = m_urlNavigator->editor();
1519     if (pathCombo->count() != 0) { // little hack
1520         pathCombo->setUrl(url);
1521     }
1522 
1523     bool blocked = m_locationEdit->blockSignals(true);
1524     if (m_keepLocation) {
1525         const QUrl currentUrl = urlFromString(locationEditCurrentText());
1526         // iconNameForUrl will get the icon or fallback to a generic one
1527         m_locationEdit->setItemIcon(0, QIcon::fromTheme(KIO::iconNameForUrl(currentUrl)));
1528         // Preserve the text when clicking on the view (cf fileHighlighted)
1529         m_locationEdit->lineEdit()->setModified(true);
1530     }
1531 
1532     m_locationEdit->blockSignals(blocked);
1533 
1534     m_urlNavigator->setLocationUrl(url);
1535 
1536     // is triggered in ctor before completion object is set
1537     KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
1538     if (completion) {
1539         completion->setDir(url);
1540     }
1541 
1542     if (m_placesView) {
1543         m_placesView->setUrl(url);
1544     }
1545 }
1546 
locationAccepted(const QString & url)1547 void KFileWidgetPrivate::locationAccepted(const QString &url)
1548 {
1549     Q_UNUSED(url);
1550     //     qDebug();
1551     q->slotOk();
1552 }
1553 
enterUrl(const QUrl & url)1554 void KFileWidgetPrivate::enterUrl(const QUrl &url)
1555 {
1556     //     qDebug();
1557 
1558     // append '/' if needed: url combo does not add it
1559     // tokenize() expects it because it uses QUrl::adjusted(QUrl::RemoveFilename)
1560     QUrl u(url);
1561     if (!u.path().isEmpty() && !u.path().endsWith(QLatin1Char('/'))) {
1562         u.setPath(u.path() + QLatin1Char('/'));
1563     }
1564     q->setUrl(u);
1565 
1566     // We need to check window()->focusWidget() instead of m_locationEdit->hasFocus
1567     // because when the window is showing up m_locationEdit
1568     // may still not have focus but it'll be the one that will have focus when the window
1569     // gets it and we don't want to steal its focus either
1570     if (q->window()->focusWidget() != m_locationEdit) {
1571         m_ops->setFocus();
1572     }
1573 }
1574 
enterUrl(const QString & url)1575 void KFileWidgetPrivate::enterUrl(const QString &url)
1576 {
1577     //     qDebug();
1578 
1579     enterUrl(urlFromString(KUrlCompletion::replacedPath(url, true, true)));
1580 }
1581 
toOverwrite(const QUrl & url)1582 bool KFileWidgetPrivate::toOverwrite(const QUrl &url)
1583 {
1584     //     qDebug();
1585 
1586     KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
1587     KJobWidgets::setWindow(statJob, q);
1588     bool res = statJob->exec();
1589 
1590     if (res) {
1591         int ret = KMessageBox::warningContinueCancel(q,
1592                                                      i18n("The file \"%1\" already exists. Do you wish to overwrite it?", url.fileName()),
1593                                                      i18n("Overwrite File?"),
1594                                                      KStandardGuiItem::overwrite(),
1595                                                      KStandardGuiItem::cancel(),
1596                                                      QString(),
1597                                                      KMessageBox::Notify | KMessageBox::Dangerous);
1598 
1599         if (ret != KMessageBox::Continue) {
1600             return false;
1601         }
1602         return true;
1603     }
1604 
1605     return true;
1606 }
1607 
1608 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 33)
setSelection(const QString & url)1609 void KFileWidget::setSelection(const QString &url)
1610 {
1611     //     qDebug() << "setSelection " << url;
1612 
1613     if (url.isEmpty()) {
1614         return;
1615     }
1616 
1617     QUrl u = d->getCompleteUrl(url);
1618     if (!u.isValid()) {
1619         // Relative path was treated as URL, but it was found to be invalid.
1620         qWarning() << url << " is not a correct argument for setSelection!";
1621         return;
1622     }
1623 
1624     setSelectedUrl(urlFromString(url));
1625 }
1626 #endif
1627 
setSelectedUrl(const QUrl & url)1628 void KFileWidget::setSelectedUrl(const QUrl &url)
1629 {
1630     // Honor protocols that do not support directory listing
1631     if (!url.isRelative() && !KProtocolManager::supportsListing(url)) {
1632         return;
1633     }
1634     d->setLocationText(url);
1635 }
1636 
setSelectedUrls(const QList<QUrl> & urls)1637 void KFileWidget::setSelectedUrls(const QList<QUrl> &urls)
1638 {
1639     if (urls.isEmpty()) {
1640         return;
1641     }
1642 
1643     // Honor protocols that do not support directory listing
1644     if (!urls[0].isRelative() && !KProtocolManager::supportsListing(urls[0])) {
1645         return;
1646     }
1647     d->setLocationText(urls);
1648 }
1649 
slotLoadingFinished()1650 void KFileWidgetPrivate::slotLoadingFinished()
1651 {
1652     const QString currentText = m_locationEdit->currentText();
1653     if (currentText.isEmpty()) {
1654         return;
1655     }
1656 
1657     m_ops->blockSignals(true);
1658     QUrl u(m_ops->url());
1659     if (currentText.startsWith(QLatin1Char('/'))) {
1660         u.setPath(currentText);
1661     } else {
1662         u.setPath(concatPaths(m_ops->url().path(), currentText));
1663     }
1664     m_ops->setCurrentItem(u);
1665     m_ops->blockSignals(false);
1666 }
1667 
fileCompletion(const QString & match)1668 void KFileWidgetPrivate::fileCompletion(const QString &match)
1669 {
1670     //     qDebug();
1671 
1672     if (match.isEmpty() || m_locationEdit->currentText().contains(QLatin1Char('"'))) {
1673         return;
1674     }
1675 
1676     const QUrl url = urlFromString(match);
1677     const QIcon mimeTypeIcon = QIcon::fromTheme(KIO::iconNameForUrl(url), QIcon::fromTheme(QStringLiteral("application-octet-stream")));
1678     setDummyHistoryEntry(m_locationEdit->currentText(), mimeTypeIcon, !m_locationEdit->currentText().isEmpty());
1679 }
1680 
slotLocationChanged(const QString & text)1681 void KFileWidgetPrivate::slotLocationChanged(const QString &text)
1682 {
1683     //     qDebug();
1684 
1685     m_locationEdit->lineEdit()->setModified(true);
1686 
1687     if (text.isEmpty() && m_ops->view()) {
1688         m_ops->view()->clearSelection();
1689     }
1690 
1691     if (text.isEmpty()) {
1692         removeDummyHistoryEntry();
1693     } else {
1694         setDummyHistoryEntry(text);
1695     }
1696 
1697     if (!m_locationEdit->lineEdit()->text().isEmpty()) {
1698         const QList<QUrl> urlList(tokenize(text));
1699         m_ops->setCurrentItems(urlList);
1700     }
1701 
1702     updateFilter();
1703 }
1704 
selectedUrl() const1705 QUrl KFileWidget::selectedUrl() const
1706 {
1707     //     qDebug();
1708 
1709     if (d->m_inAccept) {
1710         return d->m_url;
1711     } else {
1712         return QUrl();
1713     }
1714 }
1715 
selectedUrls() const1716 QList<QUrl> KFileWidget::selectedUrls() const
1717 {
1718     //     qDebug();
1719 
1720     QList<QUrl> list;
1721     if (d->m_inAccept) {
1722         if (d->m_ops->mode() & KFile::Files) {
1723             list = d->m_urlList;
1724         } else {
1725             list.append(d->m_url);
1726         }
1727     }
1728     return list;
1729 }
1730 
tokenize(const QString & line) const1731 QList<QUrl> KFileWidgetPrivate::tokenize(const QString &line) const
1732 {
1733     qCDebug(KIO_KFILEWIDGETS_FW) << "Tokenizing:" << line;
1734 
1735     QList<QUrl> urls;
1736     QUrl u(m_ops->url().adjusted(QUrl::RemoveFilename));
1737     if (!u.path().endsWith(QLatin1Char('/'))) {
1738         u.setPath(u.path() + QLatin1Char('/'));
1739     }
1740 
1741     // A helper that creates, validates and appends a new url based
1742     // on the given filename.
1743     auto addUrl = [u, &urls](const QString &partial_name) {
1744         if (partial_name.trimmed().isEmpty()) {
1745             return;
1746         }
1747 
1748         // We have to use setPath here, so that something like "test#file"
1749         // isn't interpreted to have path "test" and fragment "file".
1750         QUrl partial_url;
1751         partial_url.setPath(partial_name);
1752 
1753         // This returns QUrl(partial_name) for absolute URLs.
1754         // Otherwise, returns the concatenated url.
1755         const QUrl finalUrl = u.resolved(partial_url);
1756 
1757         if (finalUrl.isValid()) {
1758             urls.append(finalUrl);
1759         } else {
1760             // This can happen in the first quote! (ex: ' "something here"')
1761             qCDebug(KIO_KFILEWIDGETS_FW) << "Discarding Invalid" << finalUrl;
1762         }
1763     };
1764 
1765     // An iterative approach here where we toggle the "escape" flag
1766     // if we hit `\`. If we hit `"` and the escape flag is false,
1767     // we split
1768     QString partial_name;
1769     bool escape = false;
1770     for (int i = 0; i < line.length(); i++) {
1771         const QChar ch = line[i];
1772 
1773         // Handle any character previously escaped
1774         if (escape) {
1775             partial_name += ch;
1776             escape = false;
1777             continue;
1778         }
1779 
1780         // Handle escape start
1781         if (ch.toLatin1() == '\\') {
1782             escape = true;
1783             continue;
1784         }
1785 
1786         // Handle UNESCAPED quote (") since the above ifs are
1787         // dealing with the escaped ones
1788         if (ch.toLatin1() == '"') {
1789             addUrl(partial_name);
1790             partial_name.clear();
1791             continue;
1792         }
1793 
1794         // Any other character just append
1795         partial_name += ch;
1796     }
1797 
1798     // Handle the last item which is buffered in partial_name. This is
1799     // required for single-file selection dialogs since the name will not
1800     // be wrapped in quotes
1801     if (!partial_name.isEmpty()) {
1802         addUrl(partial_name);
1803         partial_name.clear();
1804     }
1805 
1806     return urls;
1807 }
1808 
selectedFile() const1809 QString KFileWidget::selectedFile() const
1810 {
1811     //     qDebug();
1812 
1813     if (d->m_inAccept) {
1814         const QUrl url = d->mostLocalUrl(d->m_url);
1815         if (url.isLocalFile()) {
1816             return url.toLocalFile();
1817         } else {
1818             KMessageBox::sorry(const_cast<KFileWidget *>(this), i18n("You can only select local files."), i18n("Remote Files Not Accepted"));
1819         }
1820     }
1821     return QString();
1822 }
1823 
selectedFiles() const1824 QStringList KFileWidget::selectedFiles() const
1825 {
1826     //     qDebug();
1827 
1828     QStringList list;
1829 
1830     if (d->m_inAccept) {
1831         if (d->m_ops->mode() & KFile::Files) {
1832             const QList<QUrl> urls = d->m_urlList;
1833             for (const auto &u : urls) {
1834                 const QUrl url = d->mostLocalUrl(u);
1835                 if (url.isLocalFile()) {
1836                     list.append(url.toLocalFile());
1837                 }
1838             }
1839         }
1840 
1841         else { // single-selection mode
1842             if (d->m_url.isLocalFile()) {
1843                 list.append(d->m_url.toLocalFile());
1844             }
1845         }
1846     }
1847 
1848     return list;
1849 }
1850 
baseUrl() const1851 QUrl KFileWidget::baseUrl() const
1852 {
1853     return d->m_ops->url();
1854 }
1855 
resizeEvent(QResizeEvent * event)1856 void KFileWidget::resizeEvent(QResizeEvent *event)
1857 {
1858     QWidget::resizeEvent(event);
1859 
1860     if (d->m_placesDock) {
1861         // we don't want our places dock actually changing size when we resize
1862         // and qt doesn't make it easy to enforce such a thing with QSplitter
1863         d->setPlacesViewSplitterSizes();
1864     }
1865 }
1866 
showEvent(QShowEvent * event)1867 void KFileWidget::showEvent(QShowEvent *event)
1868 {
1869     if (!d->m_hasView) { // delayed view-creation
1870         Q_ASSERT(d);
1871         Q_ASSERT(d->m_ops);
1872         d->m_ops->setView(KFile::Default);
1873         d->m_ops->view()->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum));
1874         d->m_hasView = true;
1875 
1876         connect(d->m_ops->view(), &QAbstractItemView::doubleClicked, this, [this](const QModelIndex &index) {
1877             d->slotViewDoubleClicked(index);
1878         });
1879     }
1880     d->m_ops->clearHistory();
1881 
1882     QWidget::showEvent(event);
1883 }
1884 
eventFilter(QObject * watched,QEvent * event)1885 bool KFileWidget::eventFilter(QObject *watched, QEvent *event)
1886 {
1887     const bool res = QWidget::eventFilter(watched, event);
1888 
1889     QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event);
1890     if (keyEvent) {
1891         if (watched == d->m_iconSizeSlider) {
1892             if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
1893                 d->slotIconSizeSliderMoved(d->m_iconSizeSlider->value());
1894             }
1895         } else if (watched == d->m_locationEdit && event->type() == QEvent::KeyPress) {
1896             if (keyEvent->modifiers() & Qt::AltModifier) {
1897                 switch (keyEvent->key()) {
1898                 case Qt::Key_Up:
1899                     d->m_ops->actionCollection()->action(QStringLiteral("up"))->trigger();
1900                     break;
1901                 case Qt::Key_Left:
1902                     d->m_ops->actionCollection()->action(QStringLiteral("back"))->trigger();
1903                     break;
1904                 case Qt::Key_Right:
1905                     d->m_ops->actionCollection()->action(QStringLiteral("forward"))->trigger();
1906                     break;
1907                 default:
1908                     break;
1909                 }
1910             }
1911         } else if (watched == d->m_ops && event->type() == QEvent::KeyPress && (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter)) {
1912             // ignore return events from the KDirOperator
1913             // they are not needed, activated is used to handle this case
1914             event->accept();
1915             return true;
1916         }
1917     }
1918 
1919     return res;
1920 }
1921 
setMode(KFile::Modes m)1922 void KFileWidget::setMode(KFile::Modes m)
1923 {
1924     //     qDebug();
1925 
1926     d->m_ops->setMode(m);
1927     if (d->m_ops->dirOnlyMode()) {
1928         d->m_filterWidget->setDefaultFilter(i18n("*|All Folders"));
1929     } else {
1930         d->m_filterWidget->setDefaultFilter(i18n("*|All Files"));
1931     }
1932 
1933     d->updateAutoSelectExtension();
1934 }
1935 
mode() const1936 KFile::Modes KFileWidget::mode() const
1937 {
1938     return d->m_ops->mode();
1939 }
1940 
readViewConfig()1941 void KFileWidgetPrivate::readViewConfig()
1942 {
1943     m_ops->setViewConfig(m_configGroup);
1944     m_ops->readConfig(m_configGroup);
1945     KUrlComboBox *combo = m_urlNavigator->editor();
1946 
1947     KCompletion::CompletionMode cm =
1948         (KCompletion::CompletionMode)m_configGroup.readEntry(PathComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup));
1949     if (cm != KCompletion::CompletionPopup) {
1950         combo->setCompletionMode(cm);
1951     }
1952 
1953     cm = (KCompletion::CompletionMode)m_configGroup.readEntry(LocationComboCompletionMode, static_cast<int>(KCompletion::CompletionPopup));
1954     if (cm != KCompletion::CompletionPopup) {
1955         m_locationEdit->setCompletionMode(cm);
1956     }
1957 
1958     // Show or don't show the places panel
1959     togglePlacesPanel(m_configGroup.readEntry(ShowSpeedbar, true));
1960 
1961     // show or don't show the bookmarks
1962     toggleBookmarks(m_configGroup.readEntry(ShowBookmarks, false));
1963 
1964     // does the user want Automatically Select Extension?
1965     m_autoSelectExtChecked = m_configGroup.readEntry(AutoSelectExtChecked, DefaultAutoSelectExtChecked);
1966     updateAutoSelectExtension();
1967 
1968     // should the URL navigator use the breadcrumb navigation?
1969     m_urlNavigator->setUrlEditable(!m_configGroup.readEntry(BreadcrumbNavigation, true));
1970 
1971     // should the URL navigator show the full path?
1972     m_urlNavigator->setShowFullPath(m_configGroup.readEntry(ShowFullPath, false));
1973 
1974     int w1 = q->minimumSize().width();
1975     int w2 = m_toolbar->sizeHint().width();
1976     if (w1 < w2) {
1977         q->setMinimumWidth(w2);
1978     }
1979 }
1980 
writeViewConfig()1981 void KFileWidgetPrivate::writeViewConfig()
1982 {
1983     // these settings are global settings; ALL instances of the file dialog
1984     // should reflect them.
1985     // There is no way to tell KFileOperator::writeConfig() to write to
1986     // kdeglobals so we write settings to a temporary config group then copy
1987     // them all to kdeglobals
1988     KConfig tmp(QString(), KConfig::SimpleConfig);
1989     KConfigGroup tmpGroup(&tmp, ConfigGroup);
1990 
1991     KUrlComboBox *pathCombo = m_urlNavigator->editor();
1992     // saveDialogSize( tmpGroup, KConfigGroup::Persistent | KConfigGroup::Global );
1993     tmpGroup.writeEntry(PathComboCompletionMode, static_cast<int>(pathCombo->completionMode()));
1994     tmpGroup.writeEntry(LocationComboCompletionMode, static_cast<int>(m_locationEdit->completionMode()));
1995 
1996     const bool showPlacesPanel = m_placesDock && !m_placesDock->isHidden();
1997     tmpGroup.writeEntry(ShowSpeedbar, showPlacesPanel);
1998     if (m_placesViewWidth > 0) {
1999         tmpGroup.writeEntry(SpeedbarWidth, m_placesViewWidth);
2000     }
2001 
2002     tmpGroup.writeEntry(ShowBookmarks, m_bookmarkHandler != nullptr);
2003     tmpGroup.writeEntry(AutoSelectExtChecked, m_autoSelectExtChecked);
2004     tmpGroup.writeEntry(BreadcrumbNavigation, !m_urlNavigator->isUrlEditable());
2005     tmpGroup.writeEntry(ShowFullPath, m_urlNavigator->showFullPath());
2006 
2007     m_ops->writeConfig(tmpGroup);
2008 
2009     // Copy saved settings to kdeglobals
2010     tmpGroup.copyTo(&m_configGroup, KConfigGroup::Persistent | KConfigGroup::Global);
2011 }
2012 
readRecentFiles()2013 void KFileWidgetPrivate::readRecentFiles()
2014 {
2015     //     qDebug();
2016 
2017     const bool oldState = m_locationEdit->blockSignals(true);
2018     m_locationEdit->setMaxItems(m_configGroup.readEntry(RecentFilesNumber, DefaultRecentURLsNumber));
2019     m_locationEdit->setUrls(m_configGroup.readPathEntry(RecentFiles, QStringList()), KUrlComboBox::RemoveBottom);
2020     m_locationEdit->setCurrentIndex(-1);
2021     m_locationEdit->blockSignals(oldState);
2022 
2023     KUrlComboBox *combo = m_urlNavigator->editor();
2024     combo->setUrls(m_configGroup.readPathEntry(RecentURLs, QStringList()), KUrlComboBox::RemoveTop);
2025     combo->setMaxItems(m_configGroup.readEntry(RecentURLsNumber, DefaultRecentURLsNumber));
2026     combo->setUrl(m_ops->url());
2027     // since we delayed this moment, initialize the directory of the completion object to
2028     // our current directory (that was very probably set on the constructor)
2029     KUrlCompletion *completion = dynamic_cast<KUrlCompletion *>(m_locationEdit->completionObject());
2030     if (completion) {
2031         completion->setDir(m_ops->url());
2032     }
2033 }
2034 
saveRecentFiles()2035 void KFileWidgetPrivate::saveRecentFiles()
2036 {
2037     //     qDebug();
2038     m_configGroup.writePathEntry(RecentFiles, m_locationEdit->urls());
2039 
2040     KUrlComboBox *pathCombo = m_urlNavigator->editor();
2041     m_configGroup.writePathEntry(RecentURLs, pathCombo->urls());
2042 }
2043 
okButton() const2044 QPushButton *KFileWidget::okButton() const
2045 {
2046     return d->m_okButton;
2047 }
2048 
cancelButton() const2049 QPushButton *KFileWidget::cancelButton() const
2050 {
2051     return d->m_cancelButton;
2052 }
2053 
2054 // Called by KFileDialog
slotCancel()2055 void KFileWidget::slotCancel()
2056 {
2057     d->writeViewConfig();
2058     d->m_ops->close();
2059 }
2060 
setKeepLocation(bool keep)2061 void KFileWidget::setKeepLocation(bool keep)
2062 {
2063     d->m_keepLocation = keep;
2064 }
2065 
keepsLocation() const2066 bool KFileWidget::keepsLocation() const
2067 {
2068     return d->m_keepLocation;
2069 }
2070 
setOperationMode(OperationMode mode)2071 void KFileWidget::setOperationMode(OperationMode mode)
2072 {
2073     //     qDebug();
2074 
2075     d->m_operationMode = mode;
2076     d->m_keepLocation = (mode == Saving);
2077     d->m_filterWidget->setEditable(!d->m_hasDefaultFilter || mode != Saving);
2078     if (mode == Opening) {
2079         // don't use KStandardGuiItem::open() here which has trailing ellipsis!
2080         d->m_okButton->setText(i18n("&Open"));
2081         d->m_okButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
2082         // hide the new folder actions...usability team says they shouldn't be in open file dialog
2083         actionCollection()->removeAction(actionCollection()->action(QStringLiteral("mkdir")));
2084     } else if (mode == Saving) {
2085         KGuiItem::assign(d->m_okButton, KStandardGuiItem::save());
2086         d->setNonExtSelection();
2087     } else {
2088         KGuiItem::assign(d->m_okButton, KStandardGuiItem::ok());
2089     }
2090     d->updateLocationWhatsThis();
2091     d->updateAutoSelectExtension();
2092 
2093     if (d->m_ops) {
2094         d->m_ops->setIsSaving(mode == Saving);
2095     }
2096     d->updateFilterText();
2097 }
2098 
operationMode() const2099 KFileWidget::OperationMode KFileWidget::operationMode() const
2100 {
2101     return d->m_operationMode;
2102 }
2103 
slotAutoSelectExtClicked()2104 void KFileWidgetPrivate::slotAutoSelectExtClicked()
2105 {
2106     //     qDebug() << "slotAutoSelectExtClicked(): "
2107     //                          << m_autoSelectExtCheckBox->isChecked() << endl;
2108 
2109     // whether the _user_ wants it on/off
2110     m_autoSelectExtChecked = m_autoSelectExtCheckBox->isChecked();
2111 
2112     // update the current filename's extension
2113     updateLocationEditExtension(m_extension /* extension hasn't changed */);
2114 }
2115 
placesViewSplitterMoved(int pos,int index)2116 void KFileWidgetPrivate::placesViewSplitterMoved(int pos, int index)
2117 {
2118     //     qDebug();
2119 
2120     // we need to record the size of the splitter when the splitter changes size
2121     // so we can keep the places box the right size!
2122     if (m_placesDock && index == 1) {
2123         m_placesViewWidth = pos;
2124         //         qDebug() << "setting m_lafBox minwidth to" << m_placesViewWidth;
2125         setLafBoxColumnWidth();
2126     }
2127 }
2128 
activateUrlNavigator()2129 void KFileWidgetPrivate::activateUrlNavigator()
2130 {
2131     //     qDebug();
2132 
2133     QLineEdit *lineEdit = m_urlNavigator->editor()->lineEdit();
2134 
2135     // If the text field currently has focus and everything is selected,
2136     // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
2137     if (m_urlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text()) {
2138         m_urlNavigator->setUrlEditable(false);
2139     } else {
2140         m_urlNavigator->setUrlEditable(true);
2141         m_urlNavigator->setFocus();
2142         lineEdit->selectAll();
2143     }
2144 }
2145 
zoomOutIconsSize()2146 void KFileWidgetPrivate::zoomOutIconsSize()
2147 {
2148     const int currValue = m_ops->iconSize();
2149 
2150     // Jump to the nearest standard size
2151     auto r_itEnd = m_stdIconSizes.crend();
2152     auto it = std::find_if(m_stdIconSizes.crbegin(), r_itEnd, [currValue](KIconLoader::StdSizes size) {
2153         return size < currValue;
2154     });
2155 
2156     Q_ASSERT(it != r_itEnd);
2157 
2158     const int nearestSize = *it;
2159 
2160     m_iconSizeSlider->setValue(nearestSize);
2161     slotIconSizeSliderMoved(nearestSize);
2162 }
2163 
zoomInIconsSize()2164 void KFileWidgetPrivate::zoomInIconsSize()
2165 {
2166     const int currValue = m_ops->iconSize();
2167 
2168     // Jump to the nearest standard size
2169     auto itEnd = m_stdIconSizes.cend();
2170     auto it = std::find_if(m_stdIconSizes.cbegin(), itEnd, [currValue](KIconLoader::StdSizes size) {
2171         return size > currValue;
2172     });
2173 
2174     Q_ASSERT(it != itEnd);
2175 
2176     const int nearestSize = *it;
2177 
2178     m_iconSizeSlider->setValue(nearestSize);
2179     slotIconSizeSliderMoved(nearestSize);
2180 }
2181 
slotIconSizeChanged(int _value)2182 void KFileWidgetPrivate::slotIconSizeChanged(int _value)
2183 {
2184     m_ops->setIconSize(_value);
2185 
2186     switch (_value) {
2187     case KIconLoader::SizeSmall:
2188     case KIconLoader::SizeSmallMedium:
2189     case KIconLoader::SizeMedium:
2190     case KIconLoader::SizeLarge:
2191     case KIconLoader::SizeHuge:
2192     case KIconLoader::SizeEnormous:
2193         m_iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", _value));
2194         break;
2195     default:
2196         m_iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", _value));
2197         break;
2198     }
2199 }
2200 
slotIconSizeSliderMoved(int _value)2201 void KFileWidgetPrivate::slotIconSizeSliderMoved(int _value)
2202 {
2203     // Force this to be called in case this slot is called first on the
2204     // slider move.
2205     slotIconSizeChanged(_value);
2206 
2207     QPoint global(m_iconSizeSlider->rect().topLeft());
2208     global.ry() += m_iconSizeSlider->height() / 2;
2209     QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_iconSizeSlider->mapToGlobal(global));
2210     QApplication::sendEvent(m_iconSizeSlider, &toolTipEvent);
2211 }
2212 
slotViewDoubleClicked(const QModelIndex & index)2213 void KFileWidgetPrivate::slotViewDoubleClicked(const QModelIndex &index)
2214 {
2215     // double clicking to save should only work on files
2216     if (m_operationMode == KFileWidget::Saving && index.isValid() && m_ops->selectedItems().constFirst().isFile()) {
2217         q->slotOk();
2218     }
2219 }
2220 
slotViewKeyEnterReturnPressed()2221 void KFileWidgetPrivate::slotViewKeyEnterReturnPressed()
2222 {
2223     // an enter/return event occurred in the view
2224     // when we are saving one file and there is no selection in the view (otherwise we get an activated event)
2225     if (m_operationMode == KFileWidget::Saving && (m_ops->mode() & KFile::File) && m_ops->selectedItems().isEmpty()) {
2226         q->slotOk();
2227     }
2228 }
2229 
getExtensionFromPatternList(const QStringList & patternList)2230 static QString getExtensionFromPatternList(const QStringList &patternList)
2231 {
2232     //     qDebug();
2233 
2234     QString ret;
2235     //     qDebug() << "\tgetExtension " << patternList;
2236 
2237     QStringList::ConstIterator patternListEnd = patternList.end();
2238     for (QStringList::ConstIterator it = patternList.begin(); it != patternListEnd; ++it) {
2239         //         qDebug() << "\t\ttry: \'" << (*it) << "\'";
2240 
2241         // is this pattern like "*.BMP" rather than useless things like:
2242         //
2243         // README
2244         // *.
2245         // *.*
2246         // *.JP*G
2247         // *.JP?
2248         // *.[Jj][Pp][Gg]
2249         if ((*it).startsWith(QLatin1String("*.")) && (*it).length() > 2 && (*it).indexOf(QLatin1Char('*'), 2) < 0 && (*it).indexOf(QLatin1Char('?'), 2) < 0
2250             && (*it).indexOf(QLatin1Char('['), 2) < 0 && (*it).indexOf(QLatin1Char(']'), 2) < 0) {
2251             ret = (*it).mid(1);
2252             break;
2253         }
2254     }
2255 
2256     return ret;
2257 }
2258 
stripUndisplayable(const QString & string)2259 static QString stripUndisplayable(const QString &string)
2260 {
2261     QString ret = string;
2262 
2263     ret.remove(QLatin1Char(':'));
2264     ret = KLocalizedString::removeAcceleratorMarker(ret);
2265 
2266     return ret;
2267 }
2268 
2269 // QString KFileWidget::currentFilterExtension()
2270 //{
2271 //    return d->m_extension;
2272 //}
2273 
updateAutoSelectExtension()2274 void KFileWidgetPrivate::updateAutoSelectExtension()
2275 {
2276     if (!m_autoSelectExtCheckBox) {
2277         return;
2278     }
2279 
2280     QMimeDatabase db;
2281     //
2282     // Figure out an extension for the Automatically Select Extension thing
2283     // (some Windows users apparently don't know what to do when confronted
2284     // with a text file called "COPYING" but do know what to do with
2285     // COPYING.txt ...)
2286     //
2287 
2288     //     qDebug() << "Figure out an extension: ";
2289     QString lastExtension = m_extension;
2290     m_extension.clear();
2291 
2292     // Automatically Select Extension is only valid if the user is _saving_ a _file_
2293     if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2294         //
2295         // Get an extension from the filter
2296         //
2297 
2298         QString filter = m_filterWidget->currentFilter();
2299         if (!filter.isEmpty()) {
2300             // if the currently selected filename already has an extension which
2301             // is also included in the currently allowed extensions, keep it
2302             // otherwise use the default extension
2303             QString currentExtension = db.suffixForFileName(locationEditCurrentText());
2304             if (currentExtension.isEmpty()) {
2305                 currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1);
2306             }
2307             // qDebug() << "filter:" << filter << "locationEdit:" << locationEditCurrentText() << "currentExtension:" << currentExtension;
2308 
2309             QString defaultExtension;
2310             QStringList extensionList;
2311 
2312             // e.g. "*.cpp"
2313             if (filter.indexOf(QLatin1Char('/')) < 0) {
2314                 extensionList = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts);
2315                 defaultExtension = getExtensionFromPatternList(extensionList);
2316             }
2317             // e.g. "text/html"
2318             else {
2319                 QMimeType mime = db.mimeTypeForName(filter);
2320                 if (mime.isValid()) {
2321                     extensionList = mime.globPatterns();
2322                     defaultExtension = mime.preferredSuffix();
2323                     if (!defaultExtension.isEmpty()) {
2324                         defaultExtension.prepend(QLatin1Char('.'));
2325                     }
2326                 }
2327             }
2328 
2329             if ((!currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension))
2330                 || filter == QLatin1String("application/octet-stream")) {
2331                 m_extension = QLatin1Char('.') + currentExtension;
2332             } else {
2333                 m_extension = defaultExtension;
2334             }
2335 
2336             // qDebug() << "List:" << extensionList << "auto-selected extension:" << m_extension;
2337         }
2338 
2339         //
2340         // GUI: checkbox
2341         //
2342 
2343         QString whatsThisExtension;
2344         if (!m_extension.isEmpty()) {
2345             // remember: sync any changes to the string with below
2346             m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension (%1)", m_extension));
2347             whatsThisExtension = i18n("the extension <b>%1</b>", m_extension);
2348 
2349             m_autoSelectExtCheckBox->setEnabled(true);
2350             m_autoSelectExtCheckBox->setChecked(m_autoSelectExtChecked);
2351         } else {
2352             // remember: sync any changes to the string with above
2353             m_autoSelectExtCheckBox->setText(i18n("Automatically select filename e&xtension"));
2354             whatsThisExtension = i18n("a suitable extension");
2355 
2356             m_autoSelectExtCheckBox->setChecked(false);
2357             m_autoSelectExtCheckBox->setEnabled(false);
2358         }
2359 
2360         const QString locationLabelText = stripUndisplayable(m_locationLabel->text());
2361         m_autoSelectExtCheckBox->setWhatsThis(QLatin1String("<qt>")
2362                                               + i18n("This option enables some convenient features for "
2363                                                      "saving files with extensions:<br />"
2364                                                      "<ol>"
2365                                                      "<li>Any extension specified in the <b>%1</b> text "
2366                                                      "area will be updated if you change the file type "
2367                                                      "to save in.<br />"
2368                                                      "<br /></li>"
2369                                                      "<li>If no extension is specified in the <b>%2</b> "
2370                                                      "text area when you click "
2371                                                      "<b>Save</b>, %3 will be added to the end of the "
2372                                                      "filename (if the filename does not already exist). "
2373                                                      "This extension is based on the file type that you "
2374                                                      "have chosen to save in.<br />"
2375                                                      "<br />"
2376                                                      "If you do not want KDE to supply an extension for the "
2377                                                      "filename, you can either turn this option off or you "
2378                                                      "can suppress it by adding a period (.) to the end of "
2379                                                      "the filename (the period will be automatically "
2380                                                      "removed)."
2381                                                      "</li>"
2382                                                      "</ol>"
2383                                                      "If unsure, keep this option enabled as it makes your "
2384                                                      "files more manageable.",
2385                                                      locationLabelText,
2386                                                      locationLabelText,
2387                                                      whatsThisExtension)
2388                                               + QLatin1String("</qt>"));
2389 
2390         m_autoSelectExtCheckBox->show();
2391 
2392         // update the current filename's extension
2393         updateLocationEditExtension(lastExtension);
2394     }
2395     // Automatically Select Extension not valid
2396     else {
2397         m_autoSelectExtCheckBox->setChecked(false);
2398         m_autoSelectExtCheckBox->hide();
2399     }
2400 }
2401 
2402 // Updates the extension of the filename specified in d->m_locationEdit if the
2403 // Automatically Select Extension feature is enabled.
2404 // (this prevents you from accidentally saving "file.kwd" as RTF, for example)
updateLocationEditExtension(const QString & lastExtension)2405 void KFileWidgetPrivate::updateLocationEditExtension(const QString &lastExtension)
2406 {
2407     if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2408         return;
2409     }
2410 
2411     const QString urlStr = locationEditCurrentText();
2412     if (urlStr.isEmpty()) {
2413         return;
2414     }
2415 
2416     const int fileNameOffset = urlStr.lastIndexOf(QLatin1Char('/')) + 1;
2417     QStringView fileName = QStringView(urlStr).mid(fileNameOffset);
2418 
2419     const int dot = fileName.lastIndexOf(QLatin1Char('.'));
2420     const int len = fileName.length();
2421     if (dot > 0 && // has an extension already and it's not a hidden file
2422                    // like ".hidden" (but we do accept ".hidden.ext")
2423         dot != len - 1 // and not deliberately suppressing extension
2424     ) {
2425         const QUrl url = getCompleteUrl(urlStr);
2426         //     qDebug() << "updateLocationEditExtension (" << url << ")";
2427         // exists?
2428         KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2429         KJobWidgets::setWindow(statJob, q);
2430         bool result = statJob->exec();
2431         if (result) {
2432             //             qDebug() << "\tfile exists";
2433 
2434             if (statJob->statResult().isDir()) {
2435                 //                 qDebug() << "\tisDir - won't alter extension";
2436                 return;
2437             }
2438 
2439             // --- fall through ---
2440         }
2441 
2442         //
2443         // try to get rid of the current extension
2444         //
2445 
2446         // catch "double extensions" like ".tar.gz"
2447         if (!lastExtension.isEmpty() && fileName.endsWith(lastExtension)) {
2448             fileName.chop(lastExtension.length());
2449         } else if (!m_extension.isEmpty() && fileName.endsWith(m_extension)) {
2450             fileName.chop(m_extension.length());
2451         } else { // can only handle "single extensions"
2452             fileName.truncate(dot);
2453         }
2454 
2455         // add extension
2456         const QString newText = QStringView(urlStr).left(fileNameOffset) + fileName + m_extension;
2457         if (newText != locationEditCurrentText()) {
2458             m_locationEdit->setItemText(m_locationEdit->currentIndex(), newText);
2459             m_locationEdit->lineEdit()->setModified(true);
2460         }
2461     }
2462 }
2463 
findMatchingFilter(const QString & filter,const QString & filename) const2464 QString KFileWidgetPrivate::findMatchingFilter(const QString &filter, const QString &filename) const
2465 {
2466     // e.g.: '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
2467     const QStringList patterns = filter.left(filter.indexOf(QLatin1Char('|'))).split(QLatin1Char(' '), Qt::SkipEmptyParts);
2468 
2469     QRegularExpression rx;
2470     for (const QString &p : patterns) {
2471         rx.setPattern(QRegularExpression::wildcardToRegularExpression(p));
2472         if (rx.match(filename).hasMatch()) {
2473             return p;
2474         }
2475     }
2476     return QString();
2477 }
2478 
2479 // Updates the filter if the extension of the filename specified in d->m_locationEdit is changed
2480 // (this prevents you from accidentally saving "file.kwd" as RTF, for example)
updateFilter()2481 void KFileWidgetPrivate::updateFilter()
2482 {
2483     //     qDebug();
2484 
2485     if ((m_operationMode == KFileWidget::Saving) && (m_ops->mode() & KFile::File)) {
2486         QString urlStr = locationEditCurrentText();
2487         if (urlStr.isEmpty()) {
2488             return;
2489         }
2490 
2491         if (m_filterWidget->isMimeFilter()) {
2492             QMimeDatabase db;
2493             QMimeType mime = db.mimeTypeForFile(urlStr, QMimeDatabase::MatchExtension);
2494             if (mime.isValid() && !mime.isDefault()) {
2495                 if (m_filterWidget->currentFilter() != mime.name() && m_filterWidget->filters().indexOf(mime.name()) != -1) {
2496                     m_filterWidget->setCurrentFilter(mime.name());
2497                 }
2498             }
2499         } else {
2500             QString filename = urlStr.mid(urlStr.lastIndexOf(QLatin1Char('/')) + 1); // only filename
2501             // accept any match to honor the user's selection; see later code handling the "*" match
2502             if (!findMatchingFilter(m_filterWidget->currentFilter(), filename).isEmpty()) {
2503                 return;
2504             }
2505             const QStringList list = m_filterWidget->filters();
2506             for (const QString &filter : list) {
2507                 QString match = findMatchingFilter(filter, filename);
2508                 if (!match.isEmpty()) {
2509                     if (match != QLatin1String("*")) { // never match the catch-all filter
2510                         m_filterWidget->setCurrentFilter(filter);
2511                     }
2512                     return; // do not repeat, could match a later filter
2513                 }
2514             }
2515         }
2516     }
2517 }
2518 
2519 // applies only to a file that doesn't already exist
appendExtension(QUrl & url)2520 void KFileWidgetPrivate::appendExtension(QUrl &url)
2521 {
2522     //     qDebug();
2523 
2524     if (!m_autoSelectExtCheckBox->isChecked() || m_extension.isEmpty()) {
2525         return;
2526     }
2527 
2528     QString fileName = url.fileName();
2529     if (fileName.isEmpty()) {
2530         return;
2531     }
2532 
2533     //     qDebug() << "appendExtension(" << url << ")";
2534 
2535     const int len = fileName.length();
2536     const int dot = fileName.lastIndexOf(QLatin1Char('.'));
2537 
2538     const bool suppressExtension = (dot == len - 1);
2539     const bool unspecifiedExtension = !fileName.endsWith(m_extension);
2540 
2541     // don't KIO::Stat if unnecessary
2542     if (!(suppressExtension || unspecifiedExtension)) {
2543         return;
2544     }
2545 
2546     // exists?
2547     KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2548     KJobWidgets::setWindow(statJob, q);
2549     bool res = statJob->exec();
2550     if (res) {
2551         //         qDebug() << "\tfile exists - won't append extension";
2552         return;
2553     }
2554 
2555     // suppress automatically append extension?
2556     if (suppressExtension) {
2557         //
2558         // Strip trailing dot
2559         // This allows lazy people to have m_autoSelectExtCheckBox->isChecked
2560         // but don't want a file extension to be appended
2561         // e.g. "README." will make a file called "README"
2562         //
2563         // If you really want a name like "README.", then type "README.."
2564         // and the trailing dot will be removed (or just stop being lazy and
2565         // turn off this feature so that you can type "README.")
2566         //
2567         //         qDebug() << "\tstrip trailing dot";
2568         QString path = url.path();
2569         path.chop(1);
2570         url.setPath(path);
2571     }
2572     // evilmatically append extension :) if the user hasn't specified one
2573     else if (unspecifiedExtension) {
2574         //         qDebug() << "\tappending extension \'" << m_extension << "\'...";
2575         url = url.adjusted(QUrl::RemoveFilename); // keeps trailing slash
2576         url.setPath(url.path() + fileName + m_extension);
2577         //         qDebug() << "\tsaving as \'" << url << "\'";
2578     }
2579 }
2580 
2581 // adds the selected files/urls to 'recent documents'
addToRecentDocuments()2582 void KFileWidgetPrivate::addToRecentDocuments()
2583 {
2584     int m = m_ops->mode();
2585     int atmost = KRecentDocument::maximumItems();
2586     // don't add more than we need. KRecentDocument::add() is pretty slow
2587 
2588     if (m & KFile::LocalOnly) {
2589         const QStringList files = q->selectedFiles();
2590         QStringList::ConstIterator it = files.begin();
2591         for (; it != files.end() && atmost > 0; ++it) {
2592             KRecentDocument::add(QUrl::fromLocalFile(*it));
2593             atmost--;
2594         }
2595     }
2596 
2597     else { // urls
2598         const QList<QUrl> urls = q->selectedUrls();
2599         QList<QUrl>::ConstIterator it = urls.begin();
2600         for (; it != urls.end() && atmost > 0; ++it) {
2601             if ((*it).isValid()) {
2602                 KRecentDocument::add(*it);
2603                 atmost--;
2604             }
2605         }
2606     }
2607 }
2608 
locationEdit() const2609 KUrlComboBox *KFileWidget::locationEdit() const
2610 {
2611     return d->m_locationEdit;
2612 }
2613 
filterWidget() const2614 KFileFilterCombo *KFileWidget::filterWidget() const
2615 {
2616     return d->m_filterWidget;
2617 }
2618 
actionCollection() const2619 KActionCollection *KFileWidget::actionCollection() const
2620 {
2621     return d->m_ops->actionCollection();
2622 }
2623 
togglePlacesPanel(bool show,QObject * sender)2624 void KFileWidgetPrivate::togglePlacesPanel(bool show, QObject *sender)
2625 {
2626     if (show) {
2627         initPlacesPanel();
2628         m_placesDock->show();
2629         setLafBoxColumnWidth();
2630 
2631         // check to see if they have a home item defined, if not show the home button
2632         QUrl homeURL;
2633         homeURL.setPath(QDir::homePath());
2634         KFilePlacesModel *model = static_cast<KFilePlacesModel *>(m_placesView->model());
2635         for (int rowIndex = 0; rowIndex < model->rowCount(); rowIndex++) {
2636             QModelIndex index = model->index(rowIndex, 0);
2637             QUrl url = model->url(index);
2638 
2639             if (homeURL.matches(url, QUrl::StripTrailingSlash)) {
2640                 m_toolbar->removeAction(m_ops->actionCollection()->action(QStringLiteral("home")));
2641                 break;
2642             }
2643         }
2644     } else {
2645         if (sender == m_placesDock && m_placesDock && m_placesDock->isVisibleTo(q)) {
2646             // we didn't *really* go away! the dialog was simply hidden or
2647             // we changed virtual desktops or ...
2648             return;
2649         }
2650 
2651         if (m_placesDock) {
2652             m_placesDock->hide();
2653         }
2654 
2655         QAction *homeAction = m_ops->actionCollection()->action(QStringLiteral("home"));
2656         QAction *reloadAction = m_ops->actionCollection()->action(QStringLiteral("reload"));
2657         if (!m_toolbar->actions().contains(homeAction)) {
2658             m_toolbar->insertAction(reloadAction, homeAction);
2659         }
2660 
2661         // reset the lafbox to not follow the width of the splitter
2662         m_lafBox->setColumnMinimumWidth(0, 0);
2663     }
2664 
2665     static_cast<KToggleAction *>(q->actionCollection()->action(QStringLiteral("togglePlacesPanel")))->setChecked(show);
2666 
2667     // if we don't show the places panel, at least show the places menu
2668     m_urlNavigator->setPlacesSelectorVisible(!show);
2669 }
2670 
toggleBookmarks(bool show)2671 void KFileWidgetPrivate::toggleBookmarks(bool show)
2672 {
2673     if (show) {
2674         if (m_bookmarkHandler) {
2675             return;
2676         }
2677         m_bookmarkHandler = new KFileBookmarkHandler(q);
2678         q->connect(m_bookmarkHandler, &KFileBookmarkHandler::openUrl, q, [this](const QString &path) {
2679             enterUrl(path);
2680         });
2681         m_bookmarkButton->setMenu(m_bookmarkHandler->menu());
2682     } else if (m_bookmarkHandler) {
2683         m_bookmarkButton->setMenu(nullptr);
2684         delete m_bookmarkHandler;
2685         m_bookmarkHandler = nullptr;
2686     }
2687 
2688     if (m_bookmarkButton) {
2689         m_bookmarkButton->setVisible(show);
2690     }
2691 
2692     static_cast<KToggleAction *>(q->actionCollection()->action(QStringLiteral("toggleBookmarks")))->setChecked(show);
2693 }
2694 
2695 // static, overloaded
getStartUrl(const QUrl & startDir,QString & recentDirClass)2696 QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass)
2697 {
2698     QString fileName; // result discarded
2699     return getStartUrl(startDir, recentDirClass, fileName);
2700 }
2701 
2702 // static, overloaded
getStartUrl(const QUrl & startDir,QString & recentDirClass,QString & fileName)2703 QUrl KFileWidget::getStartUrl(const QUrl &startDir, QString &recentDirClass, QString &fileName)
2704 {
2705     recentDirClass.clear();
2706     fileName.clear();
2707     QUrl ret;
2708 
2709     bool useDefaultStartDir = startDir.isEmpty();
2710     if (!useDefaultStartDir) {
2711         if (startDir.scheme() == QLatin1String("kfiledialog")) {
2712             //  The startDir URL with this protocol may be in the format:
2713             //                                                    directory()   fileName()
2714             //  1.  kfiledialog:///keyword                           "/"         keyword
2715             //  2.  kfiledialog:///keyword?global                    "/"         keyword
2716             //  3.  kfiledialog:///keyword/                          "/"         keyword
2717             //  4.  kfiledialog:///keyword/?global                   "/"         keyword
2718             //  5.  kfiledialog:///keyword/filename                /keyword      filename
2719             //  6.  kfiledialog:///keyword/filename?global         /keyword      filename
2720 
2721             QString keyword;
2722             QString urlDir = startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path();
2723             QString urlFile = startDir.fileName();
2724             if (urlDir == QLatin1String("/")) { // '1'..'4' above
2725                 keyword = urlFile;
2726                 fileName.clear();
2727             } else { // '5' or '6' above
2728                 keyword = urlDir.mid(1);
2729                 fileName = urlFile;
2730             }
2731 
2732             if (startDir.query() == QLatin1String("global")) {
2733                 recentDirClass = QStringLiteral("::%1").arg(keyword);
2734             } else {
2735                 recentDirClass = QStringLiteral(":%1").arg(keyword);
2736             }
2737 
2738             ret = QUrl::fromLocalFile(KRecentDirs::dir(recentDirClass));
2739         } else { // not special "kfiledialog" URL
2740             // "foo.png" only gives us a file name, the default start dir will be used.
2741             // "file:foo.png" (from KHTML/webkit, due to fromPath()) means the same
2742             //   (and is the reason why we don't just use QUrl::isRelative()).
2743 
2744             // In all other cases (startDir contains a directory path, or has no
2745             // fileName for us anyway, such as smb://), startDir is indeed a dir url.
2746 
2747             if (!startDir.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path().isEmpty() || startDir.fileName().isEmpty()) {
2748                 // can use start directory
2749                 ret = startDir; // will be checked by stat later
2750                 // If we won't be able to list it (e.g. http), then use default
2751                 if (!KProtocolManager::supportsListing(ret)) {
2752                     useDefaultStartDir = true;
2753                     fileName = startDir.fileName();
2754                 }
2755             } else { // file name only
2756                 fileName = startDir.fileName();
2757                 useDefaultStartDir = true;
2758             }
2759         }
2760     }
2761 
2762     if (useDefaultStartDir) {
2763         if (lastDirectory()->isEmpty()) {
2764             *lastDirectory() = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation));
2765             const QUrl home(QUrl::fromLocalFile(QDir::homePath()));
2766             // if there is no docpath set (== home dir), we prefer the current
2767             // directory over it. We also prefer the homedir when our CWD is
2768             // different from our homedirectory or when the document dir
2769             // does not exist
2770             if (lastDirectory()->adjusted(QUrl::StripTrailingSlash) == home.adjusted(QUrl::StripTrailingSlash) //
2771                 || QDir::currentPath() != QDir::homePath() //
2772                 || !QDir(lastDirectory()->toLocalFile()).exists()) {
2773                 *lastDirectory() = QUrl::fromLocalFile(QDir::currentPath());
2774             }
2775         }
2776         ret = *lastDirectory();
2777     }
2778 
2779     // qDebug() << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
2780     return ret;
2781 }
2782 
setStartDir(const QUrl & directory)2783 void KFileWidget::setStartDir(const QUrl &directory)
2784 {
2785     if (directory.isValid()) {
2786         *lastDirectory() = directory;
2787     }
2788 }
2789 
setNonExtSelection()2790 void KFileWidgetPrivate::setNonExtSelection()
2791 {
2792     // Enhanced rename: Don't highlight the file extension.
2793     QString filename = locationEditCurrentText();
2794     QMimeDatabase db;
2795     QString extension = db.suffixForFileName(filename);
2796 
2797     if (!extension.isEmpty()) {
2798         m_locationEdit->lineEdit()->setSelection(0, filename.length() - extension.length() - 1);
2799     } else {
2800         int lastDot = filename.lastIndexOf(QLatin1Char('.'));
2801         if (lastDot > 0) {
2802             m_locationEdit->lineEdit()->setSelection(0, lastDot);
2803         } else {
2804             m_locationEdit->lineEdit()->selectAll();
2805         }
2806     }
2807 }
2808 
2809 // Sets the filter text to "File type" if the dialog is saving and a MIME type
2810 // filter has been set; otherwise, the text is "Filter:"
updateFilterText()2811 void KFileWidgetPrivate::updateFilterText()
2812 {
2813     QString label;
2814     QString whatsThisText;
2815 
2816     if (m_operationMode == KFileWidget::Saving && m_filterWidget->isMimeFilter()) {
2817         label = i18n("&File type:");
2818         whatsThisText = i18n("<qt>This is the file type selector. It is used to select the format that the file will be saved as.</qt>");
2819     } else {
2820         label = i18n("&Filter:");
2821         whatsThisText = i18n(
2822             "<qt>This is the filter to apply to the file list. "
2823             "File names that do not match the filter will not be shown.<p>"
2824             "You may select from one of the preset filters in the "
2825             "drop down menu, or you may enter a custom filter "
2826             "directly into the text area.</p><p>"
2827             "Wildcards such as * and ? are allowed.</p></qt>");
2828     }
2829 
2830     if (m_filterLabel) {
2831         m_filterLabel->setText(label);
2832         m_filterLabel->setWhatsThis(whatsThisText);
2833     }
2834     if (m_filterWidget) {
2835         m_filterWidget->setWhatsThis(whatsThisText);
2836     }
2837 }
2838 
2839 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(5, 66)
toolBar() const2840 KToolBar *KFileWidget::toolBar() const
2841 {
2842     return d->m_toolbar;
2843 }
2844 #endif
2845 
setCustomWidget(QWidget * widget)2846 void KFileWidget::setCustomWidget(QWidget *widget)
2847 {
2848     delete d->m_bottomCustomWidget;
2849     d->m_bottomCustomWidget = widget;
2850 
2851     // add it to the dialog, below the filter list box.
2852 
2853     // Change the parent so that this widget is a child of the main widget
2854     d->m_bottomCustomWidget->setParent(this);
2855 
2856     d->m_vbox->addWidget(d->m_bottomCustomWidget);
2857     // d->m_vbox->addSpacing(3); // can't do this every time...
2858 
2859     // FIXME: This should adjust the tab orders so that the custom widget
2860     // comes after the Cancel button. The code appears to do this, but the result
2861     // somehow screws up the tab order of the file path combo box. Not a major
2862     // problem, but ideally the tab order with a custom widget should be
2863     // the same as the order without one.
2864     setTabOrder(d->m_cancelButton, d->m_bottomCustomWidget);
2865     setTabOrder(d->m_bottomCustomWidget, d->m_urlNavigator);
2866 }
2867 
setCustomWidget(const QString & text,QWidget * widget)2868 void KFileWidget::setCustomWidget(const QString &text, QWidget *widget)
2869 {
2870     delete d->m_labeledCustomWidget;
2871     d->m_labeledCustomWidget = widget;
2872 
2873     QLabel *label = new QLabel(text, this);
2874     label->setAlignment(Qt::AlignRight);
2875     d->m_lafBox->addWidget(label, 2, 0, Qt::AlignVCenter);
2876     d->m_lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter);
2877 }
2878 
dirOperator()2879 KDirOperator *KFileWidget::dirOperator()
2880 {
2881     return d->m_ops;
2882 }
2883 
readConfig(KConfigGroup & group)2884 void KFileWidget::readConfig(KConfigGroup &group)
2885 {
2886     d->m_configGroup = group;
2887     d->readViewConfig();
2888     d->readRecentFiles();
2889 }
2890 
locationEditCurrentText() const2891 QString KFileWidgetPrivate::locationEditCurrentText() const
2892 {
2893     return QDir::fromNativeSeparators(m_locationEdit->currentText());
2894 }
2895 
mostLocalUrl(const QUrl & url)2896 QUrl KFileWidgetPrivate::mostLocalUrl(const QUrl &url)
2897 {
2898     if (url.isLocalFile()) {
2899         return url;
2900     }
2901 
2902     KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
2903     KJobWidgets::setWindow(statJob, q);
2904     bool res = statJob->exec();
2905 
2906     if (!res) {
2907         return url;
2908     }
2909 
2910     const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
2911     if (!path.isEmpty()) {
2912         QUrl newUrl;
2913         newUrl.setPath(path);
2914         return newUrl;
2915     }
2916 
2917     return url;
2918 }
2919 
setInlinePreviewShown(bool show)2920 void KFileWidgetPrivate::setInlinePreviewShown(bool show)
2921 {
2922     m_ops->setInlinePreviewShown(show);
2923 }
2924 
setConfirmOverwrite(bool enable)2925 void KFileWidget::setConfirmOverwrite(bool enable)
2926 {
2927     d->m_confirmOverwrite = enable;
2928 }
2929 
setInlinePreviewShown(bool show)2930 void KFileWidget::setInlinePreviewShown(bool show)
2931 {
2932     d->setInlinePreviewShown(show);
2933 }
2934 
dialogSizeHint() const2935 QSize KFileWidget::dialogSizeHint() const
2936 {
2937     int fontSize = fontMetrics().height();
2938     QSize goodSize(48 * fontSize, 30 * fontSize);
2939     QSize screenSize = QApplication::desktop()->availableGeometry(this).size();
2940     QSize minSize(screenSize / 2);
2941     QSize maxSize(screenSize * qreal(0.9));
2942     return (goodSize.expandedTo(minSize).boundedTo(maxSize));
2943 }
2944 
setViewMode(KFile::FileView mode)2945 void KFileWidget::setViewMode(KFile::FileView mode)
2946 {
2947     d->m_ops->setView(mode);
2948     d->m_hasView = true;
2949 }
2950 
setSupportedSchemes(const QStringList & schemes)2951 void KFileWidget::setSupportedSchemes(const QStringList &schemes)
2952 {
2953     d->m_model->setSupportedSchemes(schemes);
2954     d->m_ops->setSupportedSchemes(schemes);
2955     d->m_urlNavigator->setCustomProtocols(schemes);
2956 }
2957 
supportedSchemes() const2958 QStringList KFileWidget::supportedSchemes() const
2959 {
2960     return d->m_model->supportedSchemes();
2961 }
2962 
2963 #include "moc_kfilewidget.cpp"
2964