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

source code of kio/src/filewidgets/kurlnavigator.cpp