1 /*
2     SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
3     SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org>
4     SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
5     SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org>
6 
7     SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kurlnavigator.h"
11 
12 #include "kurlnavigatorbutton_p.h"
13 #include "kurlnavigatordropdownbutton_p.h"
14 #include "kurlnavigatorpathselectoreventfilter_p.h"
15 #include "kurlnavigatorplacesselector_p.h"
16 #include "kurlnavigatorprotocolcombo_p.h"
17 #include "kurlnavigatortogglebutton_p.h"
18 #include "urlutil_p.h"
19 
20 #include <KIO/StatJob>
21 #include <KLocalizedString>
22 #include <kfileitem.h>
23 #include <kfileplacesmodel.h>
24 #include <kprotocolinfo.h>
25 #include <kurifilter.h>
26 #include <kurlcombobox.h>
27 #include <kurlcompletion.h>
28 
29 #include <QActionGroup>
30 #include <QApplication>
31 #include <QClipboard>
32 #include <QDir>
33 #include <QDropEvent>
34 #include <QHBoxLayout>
35 #include <QKeyEvent>
36 #include <QMenu>
37 #include <QMimeData>
38 #include <QMimeDatabase>
39 #include <QTimer>
40 #include <QUrlQuery>
41 
42 #include <algorithm>
43 
44 using namespace KDEPrivate;
45 
46 struct LocationData {
LocationDataLocationData47     explicit LocationData(const QUrl &u)
48         : url(u)
49     {
50     }
51 
52     QUrl url;
53     QUrl rootUrl; // KDE5: remove after the deprecated methods have been removed
54     QPoint pos; // KDE5: remove after the deprecated methods have been removed
55     QByteArray state;
56 };
57 
58 class KUrlNavigatorPrivate
59 {
60 public:
61     KUrlNavigatorPrivate(KUrlNavigator *qq, KFilePlacesModel *placesModel);
62 
~KUrlNavigatorPrivate()63     ~KUrlNavigatorPrivate()
64     {
65         m_dropDownButton->removeEventFilter(q);
66         m_pathBox->removeEventFilter(q);
67         m_toggleEditableMode->removeEventFilter(q);
68 
69         for (KUrlNavigatorButton *button : std::as_const(m_navButtons)) {
70             button->removeEventFilter(q);
71         }
72     }
73 
74     /** Applies the edited URL in m_pathBox to the URL navigator */
75     void applyUncommittedUrl();
76 
77     void slotReturnPressed();
78     void slotProtocolChanged(const QString &);
79     void openPathSelectorMenu();
80 
81     /**
82      * Appends the widget at the end of the URL navigator. It is assured
83      * that the filler widget remains as last widget to fill the remaining
84      * width.
85      */
86     void appendWidget(QWidget *widget, int stretch = 0);
87 
88     /**
89      * This slot is connected to the clicked signal of the navigation bar button. It calls switchView().
90      * Moreover, if switching from "editable" mode to the breadcrumb view, it calls applyUncommittedUrl().
91      */
92     void slotToggleEditableButtonPressed();
93 
94     /**
95      * Switches the navigation bar between the breadcrumb view and the
96      * traditional view (see setUrlEditable()).
97      */
98     void switchView();
99 
100     /** Emits the signal urlsDropped(). */
101     void dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton);
102 
103     /**
104      * Is invoked when a navigator button has been clicked.
105      * Different combinations of mouse clicks and keyboard modifiers have different effects on how
106      * the url is opened. The behaviours are the following:
107      * - shift+middle-click or ctrl+shift+left-click => activeTabRequested() signal is emitted
108      * - ctrl+left-click or middle-click => tabRequested() signal is emitted
109      * - shift+left-click => newWindowRequested() signal is emitted
110      * - left-click => open the new url in-place
111      */
112     void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers);
113 
114     void openContextMenu(const QPoint &p);
115 
116     void slotPathBoxChanged(const QString &text);
117 
118     void updateContent();
119 
120     /**
121      * Updates all buttons to have one button for each part of the
122      * current URL. Existing buttons, which are available by m_navButtons,
123      * are reused if possible. If the URL is longer, new buttons will be
124      * created, if the URL is shorter, the remaining buttons will be deleted.
125      * @param startIndex    Start index of URL part (/), where the buttons
126      *                      should be created for each following part.
127      */
128     void updateButtons(int startIndex);
129 
130     /**
131      * Updates the visibility state of all buttons describing the URL. If the
132      * width of the URL navigator is too small, the buttons representing the upper
133      * paths of the URL will be hidden and moved to a drop down menu.
134      */
135     void updateButtonVisibility();
136 
137     /**
138      * @return Text for the first button of the URL navigator.
139      */
140     QString firstButtonText() const;
141 
142     /**
143      * Returns the URL that should be applied for the button with the index \a index.
144      */
145     QUrl buttonUrl(int index) const;
146 
147     void switchToBreadcrumbMode();
148 
149     /**
150      * Deletes all URL navigator buttons. m_navButtons is
151      * empty after this operation.
152      */
153     void deleteButtons();
154 
155     /**
156      * Retrieves the place url for the current url.
157      * E. g. for the path "fish://root@192.168.0.2/var/lib" the string
158      * "fish://root@192.168.0.2" will be returned, which leads to the
159      * navigation indication 'Custom Path > var > lib". For e. g.
160      * "settings:///System/" the path "settings://" will be returned.
161      */
162     QUrl retrievePlaceUrl() const;
163 
164     /**
165      * Returns true, if the MIME type of the path represents a
166      * compressed file like TAR or ZIP, as listed in @p archiveMimetypes
167      */
168     bool isCompressedPath(const QUrl &path, const QStringList &archiveMimetypes) const;
169 
170     void removeTrailingSlash(QString &url) const;
171 
172     /**
173      * Returns the current history index, if \a historyIndex is
174      * smaller than 0. If \a historyIndex is greater or equal than
175      * the number of available history items, the largest possible
176      * history index is returned. For the other cases just \a historyIndex
177      * is returned.
178      */
179     int adjustedHistoryIndex(int historyIndex) const;
180 
181     KUrlNavigator *const q;
182 
183     QHBoxLayout *m_layout = new QHBoxLayout(q);
184     QList<LocationData> m_history;
185     QList<KUrlNavigatorButton *> m_navButtons;
186     QStringList m_customProtocols;
187     QUrl m_homeUrl;
188     KUrlNavigatorPlacesSelector *m_placesSelector = nullptr;
189     KUrlComboBox *m_pathBox = nullptr;
190     KUrlNavigatorProtocolCombo *m_protocols = nullptr;
191     KUrlNavigatorDropDownButton *m_dropDownButton = nullptr;
192     KUrlNavigatorButtonBase *m_toggleEditableMode = nullptr;
193     QWidget *m_dropWidget = nullptr;
194 
195     bool m_editable = false;
196     bool m_active = true;
197     bool m_showPlacesSelector = false;
198     bool m_showFullPath = false;
199     int m_historyIndex = 0;
200 
201     struct {
202         bool showHidden = false;
203         bool sortHiddenLast = false;
204     } m_subfolderOptions;
205 };
206 
KUrlNavigatorPrivate(KUrlNavigator * qq,KFilePlacesModel * placesModel)207 KUrlNavigatorPrivate::KUrlNavigatorPrivate(KUrlNavigator *qq, KFilePlacesModel *placesModel)
208     : q(qq)
209     , m_showPlacesSelector(placesModel != nullptr)
210 {
211     m_layout->setSpacing(0);
212     m_layout->setContentsMargins(0, 0, 0, 0);
213 
214     // initialize the places selector
215     q->setAutoFillBackground(false);
216 
217     if (placesModel != nullptr) {
218         m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel);
219         q->connect(m_placesSelector, &KUrlNavigatorPlacesSelector::placeActivated, q, &KUrlNavigator::setLocationUrl);
220         q->connect(m_placesSelector, &KUrlNavigatorPlacesSelector::tabRequested, q, &KUrlNavigator::tabRequested);
221 
222         auto updateContentFunc = [this]() {
223             updateContent();
224         };
225         q->connect(placesModel, &KFilePlacesModel::rowsInserted, q, updateContentFunc);
226         q->connect(placesModel, &KFilePlacesModel::rowsRemoved, q, updateContentFunc);
227         q->connect(placesModel, &KFilePlacesModel::dataChanged, q, updateContentFunc);
228     }
229 
230     // create protocol combo
231     m_protocols = new KUrlNavigatorProtocolCombo(QString(), q);
232     q->connect(m_protocols, &KUrlNavigatorProtocolCombo::activated, q, [this](const QString &protocol) {
233         slotProtocolChanged(protocol);
234     });
235 
236     // create drop down button for accessing all paths of the URL
237     m_dropDownButton = new KUrlNavigatorDropDownButton(q);
238     m_dropDownButton->setForegroundRole(QPalette::WindowText);
239     m_dropDownButton->installEventFilter(q);
240     q->connect(m_dropDownButton, &KUrlNavigatorDropDownButton::clicked, q, [this]() {
241         openPathSelectorMenu();
242     });
243 
244     // initialize the path box of the traditional view
245     m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q);
246     m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow);
247     m_pathBox->installEventFilter(q);
248 
249     KUrlCompletion *kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
250     m_pathBox->setCompletionObject(kurlCompletion);
251     m_pathBox->setAutoDeleteCompletionObject(true);
252 
253     // TODO KF6: remove this QOverload, only KUrlComboBox::returnPressed(const QString &) will remain
254     q->connect(m_pathBox, qOverload<const QString &>(&KUrlComboBox::returnPressed), q, [this]() {
255         slotReturnPressed();
256     });
257     q->connect(m_pathBox, &KUrlComboBox::urlActivated, q, &KUrlNavigator::setLocationUrl);
258     q->connect(m_pathBox, &QComboBox::editTextChanged, q, [this](const QString &text) {
259         slotPathBoxChanged(text);
260     });
261 
262     // create toggle button which allows to switch between
263     // the breadcrumb and traditional view
264     m_toggleEditableMode = new KUrlNavigatorToggleButton(q);
265     m_toggleEditableMode->installEventFilter(q);
266     m_toggleEditableMode->setMinimumWidth(20);
267     q->connect(m_toggleEditableMode, &KUrlNavigatorToggleButton::clicked, q, [this]() {
268         slotToggleEditableButtonPressed();
269     });
270 
271     if (m_placesSelector != nullptr) {
272         m_layout->addWidget(m_placesSelector);
273     }
274     m_layout->addWidget(m_protocols);
275     m_layout->addWidget(m_dropDownButton);
276     m_layout->addWidget(m_pathBox, 1);
277     m_layout->addWidget(m_toggleEditableMode);
278 
279     q->setContextMenuPolicy(Qt::CustomContextMenu);
280     q->connect(q, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) {
281         openContextMenu(pos);
282     });
283 }
284 
appendWidget(QWidget * widget,int stretch)285 void KUrlNavigatorPrivate::appendWidget(QWidget *widget, int stretch)
286 {
287     m_layout->insertWidget(m_layout->count() - 1, widget, stretch);
288 }
289 
applyUncommittedUrl()290 void KUrlNavigatorPrivate::applyUncommittedUrl()
291 {
292     auto applyUrl = [this](QUrl url) {
293         // Parts of the following code have been taken from the class KateFileSelector
294         // located in kate/app/katefileselector.hpp of Kate.
295         // SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
296         // SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
297         // SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
298 
299         // For example "desktop:/" _not_ "desktop:", see the comment in slotProtocolChanged()
300         if (!url.isEmpty() && url.path().isEmpty() && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) {
301             url.setPath(QStringLiteral("/"));
302         }
303 
304         const auto urlStr = url.toString();
305         QStringList urls = m_pathBox->urls();
306         urls.removeAll(urlStr);
307         urls.prepend(urlStr);
308         m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
309 
310         q->setLocationUrl(url);
311         // The URL might have been adjusted by KUrlNavigator::setUrl(), hence
312         // synchronize the result in the path box.
313         m_pathBox->setUrl(q->locationUrl());
314     };
315 
316     const QString text = m_pathBox->currentText().trimmed();
317 
318     KUriFilterData filteredData(text);
319     filteredData.setCheckForExecutables(false);
320     // Using kshorturifilter to fix up e.g. "ftp.kde.org" ---> "ftp://ftp.kde.org"
321     const auto filtersList = QStringList{QStringLiteral("kshorturifilter")};
322     if (KUriFilter::self()->filterUri(filteredData, filtersList)) {
323         applyUrl(filteredData.uri()); // The text was filtered
324         return;
325     }
326 
327     QUrl url = q->locationUrl();
328     QString path = url.path();
329     if (!path.endsWith(QLatin1Char('/'))) {
330         path += QLatin1Char('/');
331     }
332     url.setPath(path + text);
333 
334     // Dirs and symlinks to dirs
335     constexpr auto details = KIO::StatBasic | KIO::StatResolveSymlink;
336     auto *job = KIO::statDetails(url, KIO::StatJob::DestinationSide, details, KIO::HideProgressInfo);
337     q->connect(job, &KJob::result, q, [job, text, applyUrl]() {
338         // If there is a dir matching "text" relative to the current url, use that, e.g.
339         // typing "bar" while at "/path/to/foo", the url becomes "/path/to/foo/bar/"
340         if (!job->error() && job->statResult().isDir()) {
341             applyUrl(job->url());
342         } else { // ... otherwise fallback to whatever QUrl::fromUserInput() returns
343             applyUrl(QUrl::fromUserInput(text));
344         }
345     });
346 }
347 
slotReturnPressed()348 void KUrlNavigatorPrivate::slotReturnPressed()
349 {
350     applyUncommittedUrl();
351 
352     Q_EMIT q->returnPressed();
353 
354     if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
355         // Pressing Ctrl+Return automatically switches back to the breadcrumb mode.
356         // The switch must be done asynchronously, as we are in the context of the
357         // editor.
358         QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection);
359     }
360 }
361 
slotProtocolChanged(const QString & protocol)362 void KUrlNavigatorPrivate::slotProtocolChanged(const QString &protocol)
363 {
364     Q_ASSERT(m_editable);
365 
366     QUrl url;
367     url.setScheme(protocol);
368     if (KProtocolInfo::protocolClass(protocol) == QLatin1String(":local")) {
369         // E.g. "file:/" or "desktop:/", _not_ "file:" or "desktop:" respectively.
370         // This is the more expected behaviour, "file:somedir" treats somedir as
371         // a path relative to current dir; file:/somedir is an absolute path to /somedir.
372         url.setPath(QStringLiteral("/"));
373     } else {
374         // With no authority set we'll get e.g. "ftp:" instead of "ftp://".
375         // We want the latter, so let's set an empty authority.
376         url.setAuthority(QString());
377     }
378 
379     m_pathBox->setEditUrl(url);
380 }
381 
openPathSelectorMenu()382 void KUrlNavigatorPrivate::openPathSelectorMenu()
383 {
384     if (m_navButtons.count() <= 0) {
385         return;
386     }
387 
388     const QUrl firstVisibleUrl = m_navButtons.constFirst()->url();
389 
390     QString spacer;
391     QPointer<QMenu> popup = new QMenu(q);
392 
393     auto *popupFilter = new KUrlNavigatorPathSelectorEventFilter(popup.data());
394     q->connect(popupFilter, &KUrlNavigatorPathSelectorEventFilter::tabRequested, q, &KUrlNavigator::tabRequested);
395     popup->installEventFilter(popupFilter);
396 
397     popup->setLayoutDirection(Qt::LeftToRight);
398 
399     const QUrl placeUrl = retrievePlaceUrl();
400     int idx = placeUrl.path().count(QLatin1Char('/')); // idx points to the first directory
401     // after the place path
402 
403     const QString path = m_history.at(m_historyIndex).url.path();
404     QString dirName = path.section(QLatin1Char('/'), idx, idx);
405     if (dirName.isEmpty()) {
406         if (placeUrl.isLocalFile()) {
407             dirName = QStringLiteral("/");
408         } else {
409             dirName = placeUrl.toDisplayString();
410         }
411     }
412     do {
413         const QString text = spacer + dirName;
414 
415         QAction *action = new QAction(text, popup);
416         const QUrl currentUrl = buttonUrl(idx);
417         if (currentUrl == firstVisibleUrl) {
418             popup->addSeparator();
419         }
420         action->setData(QVariant(currentUrl.toString()));
421         popup->addAction(action);
422 
423         ++idx;
424         spacer.append(QLatin1String("  "));
425         dirName = path.section(QLatin1Char('/'), idx, idx);
426     } while (!dirName.isEmpty());
427 
428     const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight());
429     const QAction *activatedAction = popup->exec(pos);
430     if (activatedAction != nullptr) {
431         const QUrl url(activatedAction->data().toString());
432         q->setLocationUrl(url);
433     }
434 
435     // Delete the menu, unless it has been deleted in its own nested event loop already.
436     if (popup) {
437         popup->deleteLater();
438     }
439 }
440 
slotToggleEditableButtonPressed()441 void KUrlNavigatorPrivate::slotToggleEditableButtonPressed()
442 {
443     if (m_editable) {
444         applyUncommittedUrl();
445     }
446 
447     switchView();
448 }
449 
switchView()450 void KUrlNavigatorPrivate::switchView()
451 {
452     m_toggleEditableMode->setFocus();
453     m_editable = !m_editable;
454     m_toggleEditableMode->setChecked(m_editable);
455     updateContent();
456     if (q->isUrlEditable()) {
457         m_pathBox->setFocus();
458     }
459 
460     q->requestActivation();
461     Q_EMIT q->editableStateChanged(m_editable);
462 }
463 
dropUrls(const QUrl & destination,QDropEvent * event,KUrlNavigatorButton * dropButton)464 void KUrlNavigatorPrivate::dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton)
465 {
466     if (event->mimeData()->hasUrls()) {
467         m_dropWidget = qobject_cast<QWidget *>(dropButton);
468         Q_EMIT q->urlsDropped(destination, event);
469     }
470 }
471 
slotNavigatorButtonClicked(const QUrl & url,Qt::MouseButton button,Qt::KeyboardModifiers modifiers)472 void KUrlNavigatorPrivate::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers)
473 {
474     if ((button & Qt::MiddleButton && modifiers & Qt::ShiftModifier) || (button & Qt::LeftButton && modifiers & (Qt::ControlModifier | Qt::ShiftModifier))) {
475         Q_EMIT q->activeTabRequested(url);
476     } else if (button & Qt::MiddleButton || (button & Qt::LeftButton && modifiers & Qt::ControlModifier)) {
477         Q_EMIT q->tabRequested(url);
478     } else if (button & Qt::LeftButton && modifiers & Qt::ShiftModifier) {
479         Q_EMIT q->newWindowRequested(url);
480     } else if (button & Qt::LeftButton) {
481         q->setLocationUrl(url);
482     }
483 }
484 
openContextMenu(const QPoint & p)485 void KUrlNavigatorPrivate::openContextMenu(const QPoint &p)
486 {
487     q->setActive(true);
488 
489     QPointer<QMenu> popup = new QMenu(q);
490 
491     // provide 'Copy' action, which copies the current URL of
492     // the URL navigator into the clipboard
493     QAction *copyAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy"));
494 
495     // provide 'Paste' action, which copies the current clipboard text
496     // into the URL navigator
497     QAction *pasteAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste"));
498     QClipboard *clipboard = QApplication::clipboard();
499     pasteAction->setEnabled(!clipboard->text().isEmpty());
500 
501     popup->addSeparator();
502 
503     // We are checking for receivers because it's odd to have a tab entry even
504     // if it's not supported, like in the case of the open dialog
505     if (q->receivers(SIGNAL(tabRequested(QUrl))) > 0) {
506         for (auto button : std::as_const(m_navButtons)) {
507             if (button->geometry().contains(p)) {
508                 const auto url = button->url();
509                 QAction *openInTab = popup->addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18n("Open %1 in tab", button->text()));
510                 q->connect(openInTab, &QAction::triggered, q, [this, url]() {
511                     Q_EMIT q->tabRequested(url);
512                 });
513                 break;
514             }
515         }
516     }
517 
518     // provide radiobuttons for toggling between the edit and the navigation mode
519     QAction *editAction = popup->addAction(i18n("Edit"));
520     editAction->setCheckable(true);
521 
522     QAction *navigateAction = popup->addAction(i18n("Navigate"));
523     navigateAction->setCheckable(true);
524 
525     QActionGroup *modeGroup = new QActionGroup(popup);
526     modeGroup->addAction(editAction);
527     modeGroup->addAction(navigateAction);
528     if (q->isUrlEditable()) {
529         editAction->setChecked(true);
530     } else {
531         navigateAction->setChecked(true);
532     }
533 
534     popup->addSeparator();
535 
536     // allow showing of the full path
537     QAction *showFullPathAction = popup->addAction(i18n("Show Full Path"));
538     showFullPathAction->setCheckable(true);
539     showFullPathAction->setChecked(q->showFullPath());
540 
541     QAction *activatedAction = popup->exec(QCursor::pos());
542     if (activatedAction == copyAction) {
543         QMimeData *mimeData = new QMimeData();
544         mimeData->setText(q->locationUrl().toDisplayString(QUrl::PreferLocalFile));
545         clipboard->setMimeData(mimeData);
546     } else if (activatedAction == pasteAction) {
547         q->setLocationUrl(QUrl::fromUserInput(clipboard->text()));
548     } else if (activatedAction == editAction) {
549         q->setUrlEditable(true);
550     } else if (activatedAction == navigateAction) {
551         q->setUrlEditable(false);
552     } else if (activatedAction == showFullPathAction) {
553         q->setShowFullPath(showFullPathAction->isChecked());
554     }
555 
556     // Delete the menu, unless it has been deleted in its own nested event loop already.
557     if (popup) {
558         popup->deleteLater();
559     }
560 }
561 
slotPathBoxChanged(const QString & text)562 void KUrlNavigatorPrivate::slotPathBoxChanged(const QString &text)
563 {
564     if (text.isEmpty()) {
565         const QString protocol = q->locationUrl().scheme();
566         m_protocols->setProtocol(protocol);
567         if (m_customProtocols.count() != 1) {
568             m_protocols->show();
569         }
570     } else {
571         m_protocols->hide();
572     }
573 }
574 
updateContent()575 void KUrlNavigatorPrivate::updateContent()
576 {
577     const QUrl currentUrl = q->locationUrl();
578     if (m_placesSelector != nullptr) {
579         m_placesSelector->updateSelection(currentUrl);
580     }
581 
582     if (m_editable) {
583         m_protocols->hide();
584         m_dropDownButton->hide();
585 
586         deleteButtons();
587         m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
588         q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
589 
590         m_pathBox->show();
591         m_pathBox->setUrl(currentUrl);
592     } else {
593         m_pathBox->hide();
594 
595         m_protocols->hide();
596 
597         m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
598         q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
599 
600         // Calculate the start index for the directories that should be shown as buttons
601         // and create the buttons
602         QUrl placeUrl;
603         if ((m_placesSelector != nullptr) && !m_showFullPath) {
604             placeUrl = m_placesSelector->selectedPlaceUrl();
605         }
606 
607         if (!placeUrl.isValid()) {
608             placeUrl = retrievePlaceUrl();
609         }
610         QString placePath = placeUrl.path();
611         removeTrailingSlash(placePath);
612 
613         const int startIndex = placePath.count(QLatin1Char('/'));
614         updateButtons(startIndex);
615     }
616 }
617 
updateButtons(int startIndex)618 void KUrlNavigatorPrivate::updateButtons(int startIndex)
619 {
620     QUrl currentUrl = q->locationUrl();
621     if (!currentUrl.isValid()) { // QFileDialog::setDirectory not called yet
622         return;
623     }
624 
625     const QString path = currentUrl.path();
626 
627     bool createButton = false;
628     const int oldButtonCount = m_navButtons.count();
629 
630     int idx = startIndex;
631     bool hasNext = true;
632     do {
633         createButton = (idx - startIndex >= oldButtonCount);
634         const bool isFirstButton = (idx == startIndex);
635         const QString dirName = path.section(QLatin1Char('/'), idx, idx);
636         hasNext = isFirstButton || !dirName.isEmpty();
637         if (hasNext) {
638             KUrlNavigatorButton *button = nullptr;
639             if (createButton) {
640                 button = new KUrlNavigatorButton(buttonUrl(idx), q);
641                 button->installEventFilter(q);
642                 button->setForegroundRole(QPalette::WindowText);
643                 q->connect(button, &KUrlNavigatorButton::urlsDroppedOnNavButton, q, [this, button](const QUrl &destination, QDropEvent *event) {
644                     dropUrls(destination, event, button);
645                 });
646 
647                 q->connect(button, &KUrlNavigatorButton::navigatorButtonActivated, q, [this](const QUrl &url, Qt::MouseButton btn, Qt::KeyboardModifiers modifiers) {
648                     slotNavigatorButtonClicked(url, btn, modifiers);
649                 });
650 
651                 q->connect(button, &KUrlNavigatorButton::finishedTextResolving, q, [this]() {
652                     updateButtonVisibility();
653                 });
654 
655                 appendWidget(button);
656             } else {
657                 button = m_navButtons[idx - startIndex];
658                 button->setUrl(buttonUrl(idx));
659             }
660 
661             if (isFirstButton) {
662                 button->setText(firstButtonText());
663             }
664             button->setActive(q->isActive());
665 
666             if (createButton) {
667                 if (!isFirstButton) {
668                     q->setTabOrder(m_navButtons.constLast(), button);
669                 }
670                 m_navButtons.append(button);
671             }
672 
673             ++idx;
674             button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx));
675         }
676     } while (hasNext);
677 
678     // delete buttons which are not used anymore
679     const int newButtonCount = idx - startIndex;
680     if (newButtonCount < oldButtonCount) {
681         const auto itBegin = m_navButtons.begin() + newButtonCount;
682         const auto itEnd = m_navButtons.end();
683         for (auto it = itBegin; it != itEnd; ++it) {
684             auto *navBtn = *it;
685             navBtn->hide();
686             navBtn->deleteLater();
687         }
688         m_navButtons.erase(itBegin, itEnd);
689     }
690 
691     q->setTabOrder(m_dropDownButton, m_navButtons.constFirst());
692     q->setTabOrder(m_navButtons.constLast(), m_toggleEditableMode);
693 
694     updateButtonVisibility();
695 }
696 
updateButtonVisibility()697 void KUrlNavigatorPrivate::updateButtonVisibility()
698 {
699     if (m_editable) {
700         return;
701     }
702 
703     const int buttonsCount = m_navButtons.count();
704     if (buttonsCount == 0) {
705         m_dropDownButton->hide();
706         return;
707     }
708 
709     // Subtract all widgets from the available width, that must be shown anyway
710     int availableWidth = q->width() - m_toggleEditableMode->minimumWidth();
711 
712     if ((m_placesSelector != nullptr) && m_placesSelector->isVisible()) {
713         availableWidth -= m_placesSelector->width();
714     }
715 
716     if ((m_protocols != nullptr) && m_protocols->isVisible()) {
717         availableWidth -= m_protocols->width();
718     }
719 
720     // Check whether buttons must be hidden at all...
721     int requiredButtonWidth = 0;
722     for (const KUrlNavigatorButton *button : std::as_const(m_navButtons)) {
723         requiredButtonWidth += button->minimumWidth();
724     }
725 
726     if (requiredButtonWidth > availableWidth) {
727         // At least one button must be hidden. This implies that the
728         // drop-down button must get visible, which again decreases the
729         // available width.
730         availableWidth -= m_dropDownButton->width();
731     }
732 
733     // Hide buttons...
734     bool isLastButton = true;
735     bool hasHiddenButtons = false;
736     QList<KUrlNavigatorButton *> buttonsToShow;
737     for (auto it = m_navButtons.crbegin(); it != m_navButtons.crend(); ++it) {
738         KUrlNavigatorButton *button = *it;
739         availableWidth -= button->minimumWidth();
740         if ((availableWidth <= 0) && !isLastButton) {
741             button->hide();
742             hasHiddenButtons = true;
743         } else {
744             // Don't show the button immediately, as setActive()
745             // might change the size and a relayout gets triggered
746             // after showing the button. So the showing of all buttons
747             // is postponed until all buttons have the correct
748             // activation state.
749             buttonsToShow.append(button);
750         }
751         isLastButton = false;
752     }
753 
754     // All buttons have the correct activation state and
755     // can be shown now
756     for (KUrlNavigatorButton *button : std::as_const(buttonsToShow)) {
757         button->show();
758     }
759 
760     if (hasHiddenButtons) {
761         m_dropDownButton->show();
762     } else {
763         // Check whether going upwards is possible. If this is the case, show the drop-down button.
764         QUrl url(m_navButtons.front()->url());
765         const bool visible = !url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash)
766                 && url.scheme() != QLatin1String("baloosearch")
767                 && url.scheme() != QLatin1String("filenamesearch");
768         m_dropDownButton->setVisible(visible);
769     }
770 }
771 
firstButtonText() const772 QString KUrlNavigatorPrivate::firstButtonText() const
773 {
774     QString text;
775 
776     // The first URL navigator button should get the name of the
777     // place instead of the directory name
778     if ((m_placesSelector != nullptr) && !m_showFullPath) {
779         text = m_placesSelector->selectedPlaceText();
780     }
781 
782     const QUrl currentUrl = q->locationUrl();
783 
784     if (text.isEmpty()) {
785         if (currentUrl.isLocalFile()) {
786 #ifdef Q_OS_WIN
787             text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath();
788 #else
789             text = QStringLiteral("/");
790 #endif
791         }
792     }
793 
794     if (text.isEmpty()) {
795         if (currentUrl.path().isEmpty() || currentUrl.path() == QLatin1Char('/')) {
796             QUrlQuery query(currentUrl);
797             text = query.queryItemValue(QStringLiteral("title"));
798         }
799     }
800 
801     if (text.isEmpty()) {
802         text = currentUrl.scheme() + QLatin1Char(':');
803         if (!currentUrl.host().isEmpty()) {
804             text += QLatin1Char(' ') + currentUrl.host();
805         }
806     }
807 
808     return text;
809 }
810 
buttonUrl(int index) const811 QUrl KUrlNavigatorPrivate::buttonUrl(int index) const
812 {
813     if (index < 0) {
814         index = 0;
815     }
816 
817     // Keep scheme, hostname etc. as this is needed for e. g. browsing
818     // FTP directories
819     QUrl url = q->locationUrl();
820     QString path = url.path();
821 
822     if (!path.isEmpty()) {
823         if (index == 0) {
824             // prevent the last "/" from being stripped
825             // or we end up with an empty path
826 #ifdef Q_OS_WIN
827             path = path.length() > 1 ? path.left(2) : QDir::rootPath();
828 #else
829             path = QStringLiteral("/");
830 #endif
831         } else {
832             path = path.section(QLatin1Char('/'), 0, index);
833         }
834     }
835 
836     url.setPath(path);
837     return url;
838 }
839 
switchToBreadcrumbMode()840 void KUrlNavigatorPrivate::switchToBreadcrumbMode()
841 {
842     q->setUrlEditable(false);
843 }
844 
deleteButtons()845 void KUrlNavigatorPrivate::deleteButtons()
846 {
847     for (KUrlNavigatorButton *button : std::as_const(m_navButtons)) {
848         button->hide();
849         button->deleteLater();
850     }
851     m_navButtons.clear();
852 }
853 
retrievePlaceUrl() const854 QUrl KUrlNavigatorPrivate::retrievePlaceUrl() const
855 {
856     QUrl currentUrl = q->locationUrl();
857     currentUrl.setPath(QString());
858     return currentUrl;
859 }
860 
isCompressedPath(const QUrl & url,const QStringList & archiveMimetypes) const861 bool KUrlNavigatorPrivate::isCompressedPath(const QUrl &url, const QStringList &archiveMimetypes) const
862 {
863     QMimeDatabase db;
864     const QMimeType mime = db.mimeTypeForUrl(QUrl(url.toString(QUrl::StripTrailingSlash)));
865     return std::any_of(archiveMimetypes.begin(), archiveMimetypes.end(), [mime](const QString &archiveType) {
866         return mime.inherits(archiveType);
867     });
868 }
869 
removeTrailingSlash(QString & url) const870 void KUrlNavigatorPrivate::removeTrailingSlash(QString &url) const
871 {
872     const int length = url.length();
873     if ((length > 0) && (url.at(length - 1) == QLatin1Char('/'))) {
874         url.remove(length - 1, 1);
875     }
876 }
877 
adjustedHistoryIndex(int historyIndex) const878 int KUrlNavigatorPrivate::adjustedHistoryIndex(int historyIndex) const
879 {
880     const int historySize = m_history.size();
881     if (historyIndex < 0) {
882         historyIndex = m_historyIndex;
883     } else if (historyIndex >= historySize) {
884         historyIndex = historySize - 1;
885         Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0
886     }
887     return historyIndex;
888 }
889 
890 // ------------------------------------------------------------------------------------------------
891 
KUrlNavigator(QWidget * parent)892 KUrlNavigator::KUrlNavigator(QWidget *parent)
893     : KUrlNavigator(nullptr, QUrl{}, parent)
894 {
895 }
896 
KUrlNavigator(KFilePlacesModel * placesModel,const QUrl & url,QWidget * parent)897 KUrlNavigator::KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent)
898     : QWidget(parent)
899     , d(new KUrlNavigatorPrivate(this, placesModel))
900 {
901     d->m_history.prepend(LocationData{url.adjusted(QUrl::NormalizePathSegments)});
902 
903     setLayoutDirection(Qt::LeftToRight);
904 
905     const int minHeight = d->m_pathBox->sizeHint().height();
906     setMinimumHeight(minHeight);
907 
908     setMinimumWidth(100);
909 
910     d->updateContent();
911 }
912 
913 KUrlNavigator::~KUrlNavigator() = default;
914 
locationUrl(int historyIndex) const915 QUrl KUrlNavigator::locationUrl(int historyIndex) const
916 {
917     historyIndex = d->adjustedHistoryIndex(historyIndex);
918     return d->m_history.at(historyIndex).url;
919 }
920 
saveLocationState(const QByteArray & state)921 void KUrlNavigator::saveLocationState(const QByteArray &state)
922 {
923     d->m_history[d->m_historyIndex].state = state;
924 }
925 
locationState(int historyIndex) const926 QByteArray KUrlNavigator::locationState(int historyIndex) const
927 {
928     historyIndex = d->adjustedHistoryIndex(historyIndex);
929     return d->m_history.at(historyIndex).state;
930 }
931 
goBack()932 bool KUrlNavigator::goBack()
933 {
934     const int count = d->m_history.size();
935     if (d->m_historyIndex < count - 1) {
936         const QUrl newUrl = locationUrl(d->m_historyIndex + 1);
937         Q_EMIT urlAboutToBeChanged(newUrl);
938 
939         ++d->m_historyIndex;
940         d->updateContent();
941 
942         Q_EMIT historyChanged();
943         Q_EMIT urlChanged(locationUrl());
944         return true;
945     }
946 
947     return false;
948 }
949 
goForward()950 bool KUrlNavigator::goForward()
951 {
952     if (d->m_historyIndex > 0) {
953         const QUrl newUrl = locationUrl(d->m_historyIndex - 1);
954         Q_EMIT urlAboutToBeChanged(newUrl);
955 
956         --d->m_historyIndex;
957         d->updateContent();
958 
959         Q_EMIT historyChanged();
960         Q_EMIT urlChanged(locationUrl());
961         return true;
962     }
963 
964     return false;
965 }
966 
goUp()967 bool KUrlNavigator::goUp()
968 {
969     const QUrl currentUrl = locationUrl();
970     const QUrl upUrl = KIO::upUrl(currentUrl);
971     if (upUrl != currentUrl) { // TODO use url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash)
972         setLocationUrl(upUrl);
973         return true;
974     }
975 
976     return false;
977 }
978 
goHome()979 void KUrlNavigator::goHome()
980 {
981     if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) {
982         setLocationUrl(QUrl::fromLocalFile(QDir::homePath()));
983     } else {
984         setLocationUrl(d->m_homeUrl);
985     }
986 }
987 
setHomeUrl(const QUrl & url)988 void KUrlNavigator::setHomeUrl(const QUrl &url)
989 {
990     d->m_homeUrl = url;
991 }
992 
homeUrl() const993 QUrl KUrlNavigator::homeUrl() const
994 {
995     return d->m_homeUrl;
996 }
997 
setUrlEditable(bool editable)998 void KUrlNavigator::setUrlEditable(bool editable)
999 {
1000     if (d->m_editable != editable) {
1001         d->switchView();
1002     }
1003 }
1004 
isUrlEditable() const1005 bool KUrlNavigator::isUrlEditable() const
1006 {
1007     return d->m_editable;
1008 }
1009 
setShowFullPath(bool show)1010 void KUrlNavigator::setShowFullPath(bool show)
1011 {
1012     if (d->m_showFullPath != show) {
1013         d->m_showFullPath = show;
1014         d->updateContent();
1015     }
1016 }
1017 
showFullPath() const1018 bool KUrlNavigator::showFullPath() const
1019 {
1020     return d->m_showFullPath;
1021 }
1022 
setActive(bool active)1023 void KUrlNavigator::setActive(bool active)
1024 {
1025     if (active != d->m_active) {
1026         d->m_active = active;
1027 
1028         d->m_dropDownButton->setActive(active);
1029         for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) {
1030             button->setActive(active);
1031         }
1032 
1033         update();
1034         if (active) {
1035             Q_EMIT activated();
1036         }
1037     }
1038 }
1039 
isActive() const1040 bool KUrlNavigator::isActive() const
1041 {
1042     return d->m_active;
1043 }
1044 
setPlacesSelectorVisible(bool visible)1045 void KUrlNavigator::setPlacesSelectorVisible(bool visible)
1046 {
1047     if (visible == d->m_showPlacesSelector) {
1048         return;
1049     }
1050 
1051     if (visible && (d->m_placesSelector == nullptr)) {
1052         // the places selector cannot get visible as no
1053         // places model is available
1054         return;
1055     }
1056 
1057     d->m_showPlacesSelector = visible;
1058     d->m_placesSelector->setVisible(visible);
1059 }
1060 
isPlacesSelectorVisible() const1061 bool KUrlNavigator::isPlacesSelectorVisible() const
1062 {
1063     return d->m_showPlacesSelector;
1064 }
1065 
uncommittedUrl() const1066 QUrl KUrlNavigator::uncommittedUrl() const
1067 {
1068     KUriFilterData filteredData(d->m_pathBox->currentText().trimmed());
1069     filteredData.setCheckForExecutables(false);
1070     if (KUriFilter::self()->filterUri(filteredData, QStringList{QStringLiteral("kshorturifilter")})) {
1071         return filteredData.uri();
1072     } else {
1073         return QUrl::fromUserInput(filteredData.typedString());
1074     }
1075 }
1076 
setLocationUrl(const QUrl & newUrl)1077 void KUrlNavigator::setLocationUrl(const QUrl &newUrl)
1078 {
1079     if (newUrl == locationUrl()) {
1080         return;
1081     }
1082 
1083     QUrl url = newUrl.adjusted(QUrl::NormalizePathSegments);
1084 
1085     // This will be used below; we define it here because in the lower part of the
1086     // code locationUrl() and url become the same URLs
1087     QUrl firstChildUrl = KIO::UrlUtil::firstChildUrl(locationUrl(), url);
1088 
1089     const QString scheme = url.scheme();
1090     if (!scheme.isEmpty()) {
1091         // Check if the URL represents a tar-, zip- or 7z-file, or an archive file supported by krarc.
1092         const QStringList archiveMimetypes = KProtocolInfo::archiveMimetypes(scheme);
1093 
1094         if (!archiveMimetypes.isEmpty()) {
1095             // Check whether the URL is really part of the archive file, otherwise
1096             // replace it by the local path again.
1097             bool insideCompressedPath = d->isCompressedPath(url, archiveMimetypes);
1098             if (!insideCompressedPath) {
1099                 QUrl prevUrl = url;
1100                 QUrl parentUrl = KIO::upUrl(url);
1101                 while (parentUrl != prevUrl) {
1102                     if (d->isCompressedPath(parentUrl, archiveMimetypes)) {
1103                         insideCompressedPath = true;
1104                         break;
1105                     }
1106                     prevUrl = parentUrl;
1107                     parentUrl = KIO::upUrl(parentUrl);
1108                 }
1109             }
1110             if (!insideCompressedPath) {
1111                 // drop the tar:, zip:, sevenz: or krarc: protocol since we are not
1112                 // inside the compressed path
1113                 url.setScheme(QStringLiteral("file"));
1114                 firstChildUrl.setScheme(QStringLiteral("file"));
1115             }
1116         }
1117     }
1118 
1119     // Check whether current history element has the same URL.
1120     // If this is the case, just ignore setting the URL.
1121     const LocationData &data = d->m_history.at(d->m_historyIndex);
1122     const bool isUrlEqual = url.matches(locationUrl(), QUrl::StripTrailingSlash) || (!url.isValid() && url.matches(data.url, QUrl::StripTrailingSlash));
1123     if (isUrlEqual) {
1124         return;
1125     }
1126 
1127     Q_EMIT urlAboutToBeChanged(url);
1128 
1129     if (d->m_historyIndex > 0) {
1130         // If an URL is set when the history index is not at the end (= 0),
1131         // then clear all previous history elements so that a new history
1132         // tree is started from the current position.
1133         auto begin = d->m_history.begin();
1134         auto end = begin + d->m_historyIndex;
1135         d->m_history.erase(begin, end);
1136         d->m_historyIndex = 0;
1137     }
1138 
1139     Q_ASSERT(d->m_historyIndex == 0);
1140     d->m_history.insert(0, LocationData{url});
1141 
1142     // Prevent an endless growing of the history: remembering
1143     // the last 100 Urls should be enough...
1144     const int historyMax = 100;
1145     if (d->m_history.size() > historyMax) {
1146         auto begin = d->m_history.begin() + historyMax;
1147         auto end = d->m_history.end();
1148         d->m_history.erase(begin, end);
1149     }
1150 
1151     Q_EMIT historyChanged();
1152     Q_EMIT urlChanged(url);
1153 
1154     KUrlCompletion *urlCompletion = qobject_cast<KUrlCompletion *>(d->m_pathBox->completionObject());
1155     if (urlCompletion) {
1156         urlCompletion->setDir(url);
1157     }
1158 
1159     if (firstChildUrl.isValid()) {
1160         Q_EMIT urlSelectionRequested(firstChildUrl);
1161     }
1162 
1163     d->updateContent();
1164 
1165     requestActivation();
1166 }
1167 
requestActivation()1168 void KUrlNavigator::requestActivation()
1169 {
1170     setActive(true);
1171 }
1172 
setFocus()1173 void KUrlNavigator::setFocus()
1174 {
1175     if (isUrlEditable()) {
1176         d->m_pathBox->setFocus();
1177     } else {
1178         QWidget::setFocus();
1179     }
1180 }
1181 
1182 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
setUrl(const QUrl & url)1183 void KUrlNavigator::setUrl(const QUrl &url)
1184 {
1185     // deprecated
1186     setLocationUrl(url);
1187 }
1188 #endif
1189 
1190 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
saveRootUrl(const QUrl & url)1191 void KUrlNavigator::saveRootUrl(const QUrl &url)
1192 {
1193     // deprecated
1194     d->m_history[d->m_historyIndex].rootUrl = url;
1195 }
1196 #endif
1197 
1198 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
savePosition(int x,int y)1199 void KUrlNavigator::savePosition(int x, int y)
1200 {
1201     // deprecated
1202     d->m_history[d->m_historyIndex].pos = QPoint(x, y);
1203 }
1204 #endif
1205 
keyPressEvent(QKeyEvent * event)1206 void KUrlNavigator::keyPressEvent(QKeyEvent *event)
1207 {
1208     if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
1209         setUrlEditable(false);
1210     } else {
1211         QWidget::keyPressEvent(event);
1212     }
1213 }
1214 
keyReleaseEvent(QKeyEvent * event)1215 void KUrlNavigator::keyReleaseEvent(QKeyEvent *event)
1216 {
1217     QWidget::keyReleaseEvent(event);
1218 }
1219 
mousePressEvent(QMouseEvent * event)1220 void KUrlNavigator::mousePressEvent(QMouseEvent *event)
1221 {
1222     if (event->button() == Qt::MiddleButton) {
1223         requestActivation();
1224     }
1225     QWidget::mousePressEvent(event);
1226 }
1227 
mouseReleaseEvent(QMouseEvent * event)1228 void KUrlNavigator::mouseReleaseEvent(QMouseEvent *event)
1229 {
1230     if (event->button() == Qt::MiddleButton) {
1231         const QRect bounds = d->m_toggleEditableMode->geometry();
1232         if (bounds.contains(event->pos())) {
1233             // The middle mouse button has been clicked above the
1234             // toggle-editable-mode-button. Paste the clipboard content
1235             // as location URL.
1236             QClipboard *clipboard = QApplication::clipboard();
1237             const QMimeData *mimeData = clipboard->mimeData();
1238             if (mimeData->hasText()) {
1239                 const QString text = mimeData->text();
1240                 setLocationUrl(QUrl::fromUserInput(text));
1241             }
1242         }
1243     }
1244     QWidget::mouseReleaseEvent(event);
1245 }
1246 
resizeEvent(QResizeEvent * event)1247 void KUrlNavigator::resizeEvent(QResizeEvent *event)
1248 {
1249     QTimer::singleShot(0, this, [this]() {
1250         d->updateButtonVisibility();
1251     });
1252     QWidget::resizeEvent(event);
1253 }
1254 
wheelEvent(QWheelEvent * event)1255 void KUrlNavigator::wheelEvent(QWheelEvent *event)
1256 {
1257     setActive(true);
1258     QWidget::wheelEvent(event);
1259 }
1260 
eventFilter(QObject * watched,QEvent * event)1261 bool KUrlNavigator::eventFilter(QObject *watched, QEvent *event)
1262 {
1263     switch (event->type()) {
1264     case QEvent::FocusIn:
1265         if (watched == d->m_pathBox) {
1266             requestActivation();
1267             setFocus();
1268         }
1269         for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) {
1270             button->setShowMnemonic(true);
1271         }
1272         break;
1273 
1274     case QEvent::FocusOut:
1275         for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) {
1276             button->setShowMnemonic(false);
1277         }
1278         break;
1279 
1280     default:
1281         break;
1282     }
1283 
1284     return QWidget::eventFilter(watched, event);
1285 }
1286 
historySize() const1287 int KUrlNavigator::historySize() const
1288 {
1289     return d->m_history.count();
1290 }
1291 
historyIndex() const1292 int KUrlNavigator::historyIndex() const
1293 {
1294     return d->m_historyIndex;
1295 }
1296 
editor() const1297 KUrlComboBox *KUrlNavigator::editor() const
1298 {
1299     return d->m_pathBox;
1300 }
1301 
setCustomProtocols(const QStringList & protocols)1302 void KUrlNavigator::setCustomProtocols(const QStringList &protocols)
1303 {
1304     d->m_customProtocols = protocols;
1305     d->m_protocols->setCustomProtocols(d->m_customProtocols);
1306 }
1307 
customProtocols() const1308 QStringList KUrlNavigator::customProtocols() const
1309 {
1310     return d->m_customProtocols;
1311 }
1312 
dropWidget() const1313 QWidget *KUrlNavigator::dropWidget() const
1314 {
1315     return d->m_dropWidget;
1316 }
1317 
setShowHiddenFolders(bool showHiddenFolders)1318 void KUrlNavigator::setShowHiddenFolders(bool showHiddenFolders)
1319 {
1320     d->m_subfolderOptions.showHidden = showHiddenFolders;
1321 }
1322 
showHiddenFolders() const1323 bool KUrlNavigator::showHiddenFolders() const
1324 {
1325     return d->m_subfolderOptions.showHidden;
1326 }
1327 
setSortHiddenFoldersLast(bool sortHiddenFoldersLast)1328 void KUrlNavigator::setSortHiddenFoldersLast(bool sortHiddenFoldersLast)
1329 {
1330     d->m_subfolderOptions.sortHiddenLast = sortHiddenFoldersLast;
1331 }
1332 
sortHiddenFoldersLast() const1333 bool KUrlNavigator::sortHiddenFoldersLast() const
1334 {
1335     return d->m_subfolderOptions.sortHiddenLast;
1336 }
1337 
1338 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
url() const1339 const QUrl &KUrlNavigator::url() const
1340 {
1341     // deprecated
1342 
1343     // Workaround required because of flawed interface ('const QUrl&' is returned
1344     // instead of 'QUrl'): remember the URL to prevent a dangling pointer
1345     static QUrl url;
1346     url = locationUrl();
1347     return url;
1348 }
1349 #endif
1350 
1351 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
url(int index) const1352 QUrl KUrlNavigator::url(int index) const
1353 {
1354     // deprecated
1355     return d->buttonUrl(index);
1356 }
1357 #endif
1358 
1359 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
historyUrl(int historyIndex) const1360 QUrl KUrlNavigator::historyUrl(int historyIndex) const
1361 {
1362     // deprecated
1363     return locationUrl(historyIndex);
1364 }
1365 #endif
1366 
1367 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
savedRootUrl() const1368 const QUrl &KUrlNavigator::savedRootUrl() const
1369 {
1370     // deprecated
1371 
1372     // Workaround required because of flawed interface ('const QUrl&' is returned
1373     // instead of 'QUrl'): remember the root URL to prevent a dangling pointer
1374     static QUrl rootUrl;
1375     rootUrl = d->m_history[d->m_historyIndex].rootUrl;
1376     return rootUrl;
1377 }
1378 #endif
1379 
1380 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
savedPosition() const1381 QPoint KUrlNavigator::savedPosition() const
1382 {
1383     // deprecated
1384     return d->m_history[d->m_historyIndex].pos;
1385 }
1386 #endif
1387 
1388 #if KIOFILEWIDGETS_BUILD_DEPRECATED_SINCE(4, 5)
setHomeUrl(const QString & homeUrl)1389 void KUrlNavigator::setHomeUrl(const QString &homeUrl)
1390 {
1391     // deprecated
1392     setLocationUrl(QUrl::fromUserInput(homeUrl));
1393 }
1394 #endif
1395 
1396 #include "moc_kurlnavigator.cpp"
1397