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 <QTimer> |
42 | #include <QUrlQuery> |
43 | |
44 | #include <algorithm> |
45 | #include <numeric> |
46 | |
47 | using namespace KDEPrivate; |
48 | |
49 | struct KUrlNavigatorData { |
50 | QByteArray state; |
51 | }; |
52 | Q_DECLARE_METATYPE(KUrlNavigatorData) |
53 | |
54 | class KUrlNavigatorPrivate |
55 | { |
56 | public: |
57 | KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, KFilePlacesModel *placesModel); |
58 | |
59 | ~KUrlNavigatorPrivate() |
60 | { |
61 | m_dropDownButton->removeEventFilter(obj: q); |
62 | m_pathBox->removeEventFilter(obj: q); |
63 | m_toggleEditableMode->removeEventFilter(obj: q); |
64 | |
65 | for (KUrlNavigatorButton *button : std::as_const(t&: m_navButtons)) { |
66 | button->removeEventFilter(obj: q); |
67 | } |
68 | } |
69 | |
70 | /** Applies the edited URL in m_pathBox to the URL navigator */ |
71 | void applyUncommittedUrl(); |
72 | void slotApplyUrl(QUrl url); |
73 | // Returns true if "text" matched a URI filter (i.e. was fitlered), |
74 | // otherwise returns false |
75 | bool slotCheckFilters(const QString &text); |
76 | |
77 | void slotReturnPressed(); |
78 | void slotSchemeChanged(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 | KUrlNavigator *const q; |
165 | |
166 | QHBoxLayout *m_layout = new QHBoxLayout(q); |
167 | KCoreUrlNavigator *m_coreUrlNavigator = nullptr; |
168 | QList<KUrlNavigatorButton *> m_navButtons; |
169 | QStringList m_supportedSchemes; |
170 | QUrl m_homeUrl; |
171 | KUrlNavigatorPlacesSelector *m_placesSelector = nullptr; |
172 | KUrlComboBox *m_pathBox = nullptr; |
173 | KUrlNavigatorSchemeCombo *m_schemes = nullptr; |
174 | KUrlNavigatorDropDownButton *m_dropDownButton = nullptr; |
175 | KUrlNavigatorButtonBase *m_toggleEditableMode = nullptr; |
176 | QWidget *m_dropWidget = nullptr; |
177 | QWidget *m_badgeWidgetContainer = nullptr; |
178 | |
179 | bool m_editable = false; |
180 | bool m_active = true; |
181 | bool m_showPlacesSelector = false; |
182 | bool m_showFullPath = false; |
183 | |
184 | struct { |
185 | bool showHidden = false; |
186 | bool sortHiddenLast = false; |
187 | } m_subfolderOptions; |
188 | }; |
189 | |
190 | KUrlNavigatorPrivate::KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, KFilePlacesModel *placesModel) |
191 | : q(qq) |
192 | , m_coreUrlNavigator(new KCoreUrlNavigator(url, qq)) |
193 | , m_showPlacesSelector(placesModel != nullptr) |
194 | { |
195 | m_layout->setSpacing(0); |
196 | m_layout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
197 | |
198 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::currentLocationUrlChanged, context: q, slot: [this]() { |
199 | Q_EMIT q->urlChanged(url: m_coreUrlNavigator->currentLocationUrl()); |
200 | }); |
201 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::currentUrlAboutToChange, context: q, slot: [this](const QUrl &url) { |
202 | Q_EMIT q->urlAboutToBeChanged(newUrl: url); |
203 | }); |
204 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::historySizeChanged, context: q, slot: [this]() { |
205 | Q_EMIT q->historyChanged(); |
206 | }); |
207 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::historyIndexChanged, context: q, slot: [this]() { |
208 | Q_EMIT q->historyChanged(); |
209 | }); |
210 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::historyChanged, context: q, slot: [this]() { |
211 | Q_EMIT q->historyChanged(); |
212 | }); |
213 | q->connect(sender: m_coreUrlNavigator, signal: &KCoreUrlNavigator::urlSelectionRequested, context: q, slot: [this](const QUrl &url) { |
214 | Q_EMIT q->urlSelectionRequested(url); |
215 | }); |
216 | |
217 | // initialize the places selector |
218 | q->setAutoFillBackground(false); |
219 | |
220 | if (placesModel != nullptr) { |
221 | m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel); |
222 | q->connect(sender: m_placesSelector, signal: &KUrlNavigatorPlacesSelector::placeActivated, context: q, slot: &KUrlNavigator::setLocationUrl); |
223 | q->connect(sender: m_placesSelector, signal: &KUrlNavigatorPlacesSelector::tabRequested, context: q, slot: &KUrlNavigator::tabRequested); |
224 | |
225 | auto updateContentFunc = [this]() { |
226 | updateContent(); |
227 | }; |
228 | q->connect(sender: placesModel, signal: &KFilePlacesModel::rowsInserted, context: q, slot&: updateContentFunc); |
229 | q->connect(sender: placesModel, signal: &KFilePlacesModel::rowsRemoved, context: q, slot&: updateContentFunc); |
230 | q->connect(sender: placesModel, signal: &KFilePlacesModel::dataChanged, context: q, slot&: updateContentFunc); |
231 | } |
232 | |
233 | // create scheme combo |
234 | m_schemes = new KUrlNavigatorSchemeCombo(QString(), q); |
235 | q->connect(sender: m_schemes, signal: &KUrlNavigatorSchemeCombo::activated, context: q, slot: [this](const QString &schene) { |
236 | slotSchemeChanged(schene); |
237 | }); |
238 | |
239 | // create drop down button for accessing all paths of the URL |
240 | m_dropDownButton = new KUrlNavigatorDropDownButton(q); |
241 | m_dropDownButton->setForegroundRole(QPalette::WindowText); |
242 | m_dropDownButton->installEventFilter(filterObj: q); |
243 | q->connect(sender: m_dropDownButton, signal: &KUrlNavigatorDropDownButton::clicked, context: q, slot: [this]() { |
244 | openPathSelectorMenu(); |
245 | }); |
246 | |
247 | // initialize the path box of the traditional view |
248 | m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q); |
249 | m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); |
250 | m_pathBox->installEventFilter(filterObj: q); |
251 | |
252 | KUrlCompletion *kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion); |
253 | m_pathBox->setCompletionObject(compObj: kurlCompletion); |
254 | m_pathBox->setAutoDeleteCompletionObject(true); |
255 | |
256 | // TODO KF6: remove this QOverload, only KUrlComboBox::returnPressed(const QString &) will remain |
257 | q->connect(sender: m_pathBox, signal: &KUrlComboBox::returnPressed, context: q, slot: [this]() { |
258 | slotReturnPressed(); |
259 | }); |
260 | q->connect(sender: m_pathBox, signal: &KUrlComboBox::urlActivated, context: q, slot: &KUrlNavigator::setLocationUrl); |
261 | q->connect(sender: m_pathBox, signal: &QComboBox::editTextChanged, context: q, slot: [this](const QString &text) { |
262 | slotPathBoxChanged(text); |
263 | }); |
264 | |
265 | m_badgeWidgetContainer = new QWidget(q); |
266 | auto badgeLayout = new QHBoxLayout(m_badgeWidgetContainer); |
267 | badgeLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0); |
268 | |
269 | // create toggle button which allows to switch between |
270 | // the breadcrumb and traditional view |
271 | m_toggleEditableMode = new KUrlNavigatorToggleButton(q); |
272 | m_toggleEditableMode->installEventFilter(filterObj: q); |
273 | m_toggleEditableMode->setMinimumWidth(20); |
274 | q->connect(sender: m_toggleEditableMode, signal: &KUrlNavigatorToggleButton::clicked, context: q, slot: [this]() { |
275 | slotToggleEditableButtonPressed(); |
276 | }); |
277 | |
278 | if (m_placesSelector != nullptr) { |
279 | m_layout->addWidget(m_placesSelector); |
280 | } |
281 | m_layout->addWidget(m_schemes); |
282 | m_layout->addWidget(m_dropDownButton); |
283 | m_layout->addWidget(m_pathBox, stretch: 1); |
284 | m_layout->addWidget(m_badgeWidgetContainer); |
285 | m_layout->addWidget(m_toggleEditableMode); |
286 | |
287 | q->setContextMenuPolicy(Qt::CustomContextMenu); |
288 | q->connect(sender: q, signal: &QWidget::customContextMenuRequested, context: q, slot: [this](const QPoint &pos) { |
289 | openContextMenu(p: pos); |
290 | }); |
291 | } |
292 | |
293 | void KUrlNavigatorPrivate::appendWidget(QWidget *widget, int stretch) |
294 | { |
295 | // insert to the left of: m_badgeWidgetContainer, m_toggleEditableMode |
296 | m_layout->insertWidget(index: m_layout->count() - 2, widget, stretch); |
297 | } |
298 | |
299 | void KUrlNavigatorPrivate::slotApplyUrl(QUrl url) |
300 | { |
301 | // Parts of the following code have been taken from the class KateFileSelector |
302 | // located in kate/app/katefileselector.hpp of Kate. |
303 | // SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org> |
304 | // SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org> |
305 | // SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk> |
306 | |
307 | // For example "desktop:/" _not_ "desktop:", see the comment in slotSchemeChanged() |
308 | if (!url.isEmpty() && url.path().isEmpty() && KProtocolInfo::protocolClass(protocol: url.scheme()) == QLatin1String(":local" )) { |
309 | url.setPath(QStringLiteral("/" )); |
310 | } |
311 | |
312 | const auto urlStr = url.toString(); |
313 | QStringList urls = m_pathBox->urls(); |
314 | urls.removeAll(t: urlStr); |
315 | urls.prepend(t: urlStr); |
316 | m_pathBox->setUrls(urls, remove: KUrlComboBox::RemoveBottom); |
317 | |
318 | q->setLocationUrl(url); |
319 | // The URL might have been adjusted by KUrlNavigator::setUrl(), hence |
320 | // synchronize the result in the path box. |
321 | m_pathBox->setUrl(q->locationUrl()); |
322 | } |
323 | |
324 | bool KUrlNavigatorPrivate::slotCheckFilters(const QString &text) |
325 | { |
326 | KUriFilterData filteredData(text); |
327 | filteredData.setCheckForExecutables(false); |
328 | // Using kshorturifilter to fix up e.g. "ftp.kde.org" ---> "ftp://ftp.kde.org" |
329 | const auto filtersList = QStringList{QStringLiteral("kshorturifilter" )}; |
330 | const bool wasFiltered = KUriFilter::self()->filterUri(data&: filteredData, filters: filtersList); |
331 | if (wasFiltered) { |
332 | slotApplyUrl(url: filteredData.uri()); // The text was filtered |
333 | } |
334 | return wasFiltered; |
335 | } |
336 | |
337 | void KUrlNavigatorPrivate::applyUncommittedUrl() |
338 | { |
339 | const QString text = m_pathBox->currentText().trimmed(); |
340 | QUrl url = q->locationUrl(); |
341 | |
342 | // Using the stat job below, check if the url and text match a local dir; but first |
343 | // handle a special case where "url" is empty in the unittests which use |
344 | // KUrlNavigator::setLocationUrl(QUrl()); in practice (e.g. in Dolphin, or KFileWidget), |
345 | // locationUrl() is never empty |
346 | if (url.isEmpty() && !text.isEmpty()) { |
347 | if (slotCheckFilters(text)) { |
348 | return; |
349 | } |
350 | } |
351 | |
352 | // Treat absolute paths as absolute paths. |
353 | // Relative paths get appended to the current path. |
354 | if (text.startsWith(c: QLatin1Char('/'))) { |
355 | url.setPath(path: text); |
356 | } else { |
357 | url.setPath(path: Utils::concatPaths(path1: url.path(), path2: text)); |
358 | } |
359 | |
360 | // Dirs and symlinks to dirs |
361 | constexpr auto details = KIO::StatBasic | KIO::StatResolveSymlink; |
362 | auto *job = KIO::stat(url, side: KIO::StatJob::DestinationSide, details, flags: KIO::HideProgressInfo); |
363 | q->connect(sender: job, signal: &KJob::result, context: q, slot: [this, job, text]() { |
364 | // If there is a dir matching "text" relative to the current url, use that, e.g.: |
365 | // - typing "bar" while at "/path/to/foo" ---> "/path/to/foo/bar/" |
366 | // - typing ".config" while at "/home/foo" ---> "/home/foo/.config" |
367 | if (!job->error() && job->statResult().isDir()) { |
368 | slotApplyUrl(url: job->url()); |
369 | return; |
370 | } |
371 | |
372 | // Check if text matches a URI filter |
373 | if (slotCheckFilters(text)) { |
374 | return; |
375 | } |
376 | |
377 | // ... otherwise fallback to whatever QUrl::fromUserInput() returns |
378 | slotApplyUrl(url: QUrl::fromUserInput(userInput: text)); |
379 | }); |
380 | } |
381 | |
382 | void KUrlNavigatorPrivate::slotReturnPressed() |
383 | { |
384 | applyUncommittedUrl(); |
385 | |
386 | Q_EMIT q->returnPressed(); |
387 | |
388 | if (QApplication::keyboardModifiers() & Qt::ControlModifier) { |
389 | // Pressing Ctrl+Return automatically switches back to the breadcrumb mode. |
390 | // The switch must be done asynchronously, as we are in the context of the |
391 | // editor. |
392 | auto switchModeFunc = [this]() { |
393 | switchToBreadcrumbMode(); |
394 | }; |
395 | QMetaObject::invokeMethod(object: q, function&: switchModeFunc, type: Qt::QueuedConnection); |
396 | } |
397 | } |
398 | |
399 | void KUrlNavigatorPrivate::slotSchemeChanged(const QString &scheme) |
400 | { |
401 | Q_ASSERT(m_editable); |
402 | |
403 | QUrl url; |
404 | url.setScheme(scheme); |
405 | if (KProtocolInfo::protocolClass(protocol: scheme) == QLatin1String(":local" )) { |
406 | // E.g. "file:/" or "desktop:/", _not_ "file:" or "desktop:" respectively. |
407 | // This is the more expected behaviour, "file:somedir" treats somedir as |
408 | // a path relative to current dir; file:/somedir is an absolute path to /somedir. |
409 | url.setPath(QStringLiteral("/" )); |
410 | } else { |
411 | // With no authority set we'll get e.g. "ftp:" instead of "ftp://". |
412 | // We want the latter, so let's set an empty authority. |
413 | url.setAuthority(authority: QString()); |
414 | } |
415 | |
416 | m_pathBox->setEditUrl(url); |
417 | } |
418 | |
419 | void KUrlNavigatorPrivate::() |
420 | { |
421 | if (m_navButtons.count() <= 0) { |
422 | return; |
423 | } |
424 | |
425 | const QUrl firstVisibleUrl = m_navButtons.constFirst()->url(); |
426 | |
427 | QString spacer; |
428 | QPointer<QMenu> = new QMenu(q); |
429 | |
430 | auto * = new KUrlNavigatorPathSelectorEventFilter(popup.data()); |
431 | q->connect(sender: popupFilter, signal: &KUrlNavigatorPathSelectorEventFilter::tabRequested, context: q, slot: &KUrlNavigator::tabRequested); |
432 | popup->installEventFilter(filterObj: popupFilter); |
433 | |
434 | const QUrl placeUrl = retrievePlaceUrl(); |
435 | int idx = placeUrl.path().count(c: QLatin1Char('/')); // idx points to the first directory |
436 | // after the place path |
437 | |
438 | const QString path = m_coreUrlNavigator->locationUrl(historyIndex: m_coreUrlNavigator->historyIndex()).path(); |
439 | QString dirName = path.section(asep: QLatin1Char('/'), astart: idx, aend: idx); |
440 | if (dirName.isEmpty()) { |
441 | if (placeUrl.isLocalFile()) { |
442 | dirName = QStringLiteral("/" ); |
443 | } else { |
444 | dirName = placeUrl.toDisplayString(); |
445 | } |
446 | } |
447 | do { |
448 | const QString text = spacer + dirName; |
449 | |
450 | QAction *action = new QAction(text, popup); |
451 | const QUrl currentUrl = buttonUrl(index: idx); |
452 | if (currentUrl == firstVisibleUrl) { |
453 | popup->addSeparator(); |
454 | } |
455 | action->setData(QVariant(currentUrl.toString())); |
456 | popup->addAction(action); |
457 | |
458 | ++idx; |
459 | spacer.append(s: QLatin1String(" " )); |
460 | dirName = path.section(asep: QLatin1Char('/'), astart: idx, aend: idx); |
461 | } while (!dirName.isEmpty()); |
462 | |
463 | const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight()); |
464 | const QAction *activatedAction = popup->exec(pos); |
465 | if (activatedAction != nullptr) { |
466 | const QUrl url(activatedAction->data().toString()); |
467 | q->setLocationUrl(url); |
468 | } |
469 | |
470 | // Delete the menu, unless it has been deleted in its own nested event loop already. |
471 | if (popup) { |
472 | popup->deleteLater(); |
473 | } |
474 | } |
475 | |
476 | void KUrlNavigatorPrivate::slotToggleEditableButtonPressed() |
477 | { |
478 | if (m_editable) { |
479 | applyUncommittedUrl(); |
480 | } |
481 | |
482 | switchView(); |
483 | } |
484 | |
485 | void KUrlNavigatorPrivate::switchView() |
486 | { |
487 | m_toggleEditableMode->setFocus(); |
488 | m_editable = !m_editable; |
489 | m_toggleEditableMode->setChecked(m_editable); |
490 | updateContent(); |
491 | if (q->isUrlEditable()) { |
492 | m_pathBox->setFocus(); |
493 | } |
494 | |
495 | q->requestActivation(); |
496 | Q_EMIT q->editableStateChanged(editable: m_editable); |
497 | } |
498 | |
499 | void KUrlNavigatorPrivate::dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton) |
500 | { |
501 | if (event->mimeData()->hasUrls()) { |
502 | m_dropWidget = qobject_cast<QWidget *>(o: dropButton); |
503 | Q_EMIT q->urlsDropped(destination, event); |
504 | } |
505 | } |
506 | |
507 | void KUrlNavigatorPrivate::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) |
508 | { |
509 | if ((button & Qt::MiddleButton && modifiers & Qt::ShiftModifier) || (button & Qt::LeftButton && modifiers & (Qt::ControlModifier | Qt::ShiftModifier))) { |
510 | Q_EMIT q->activeTabRequested(url); |
511 | } else if (button & Qt::MiddleButton || (button & Qt::LeftButton && modifiers & Qt::ControlModifier)) { |
512 | Q_EMIT q->tabRequested(url); |
513 | } else if (button & Qt::LeftButton && modifiers & Qt::ShiftModifier) { |
514 | Q_EMIT q->newWindowRequested(url); |
515 | } else if (button & Qt::LeftButton) { |
516 | q->setLocationUrl(url); |
517 | } |
518 | } |
519 | |
520 | void KUrlNavigatorPrivate::(const QPoint &p) |
521 | { |
522 | q->setActive(true); |
523 | |
524 | QPointer<QMenu> = new QMenu(q); |
525 | |
526 | // provide 'Copy' action, which copies the current URL of |
527 | // the URL navigator into the clipboard |
528 | QAction *copyAction = popup->addAction(icon: QIcon::fromTheme(QStringLiteral("edit-copy" )), i18n("Copy" )); |
529 | |
530 | // provide 'Paste' action, which copies the current clipboard text |
531 | // into the URL navigator |
532 | QAction *pasteAction = popup->addAction(icon: QIcon::fromTheme(QStringLiteral("edit-paste" )), i18n("Paste" )); |
533 | QClipboard *clipboard = QApplication::clipboard(); |
534 | pasteAction->setEnabled(!clipboard->text().isEmpty()); |
535 | |
536 | popup->addSeparator(); |
537 | |
538 | // We are checking whether the signal is connected because it's odd to have a tab entry even |
539 | // if it's not supported, like in the case of the open dialog |
540 | const bool isTabSignal = q->isSignalConnected(signal: QMetaMethod::fromSignal(signal: &KUrlNavigator::tabRequested)); |
541 | const bool isWindowSignal = q->isSignalConnected(signal: QMetaMethod::fromSignal(signal: &KUrlNavigator::newWindowRequested)); |
542 | if (isTabSignal || isWindowSignal) { |
543 | auto it = std::find_if(first: m_navButtons.cbegin(), last: m_navButtons.cend(), pred: [&p](const KUrlNavigatorButton *button) { |
544 | return button->geometry().contains(p); |
545 | }); |
546 | if (it != m_navButtons.cend()) { |
547 | const auto *button = *it; |
548 | const QUrl url = button->url(); |
549 | const QString text = button->text(); |
550 | |
551 | if (isTabSignal) { |
552 | QAction *openInTab = popup->addAction(icon: QIcon::fromTheme(QStringLiteral("tab-new" )), i18nc("@item:inmenu" , "Open \"%1\" in New Tab" , text)); |
553 | q->connect(sender: openInTab, signal: &QAction::triggered, context: q, slot: [this, url]() { |
554 | Q_EMIT q->tabRequested(url); |
555 | }); |
556 | } |
557 | |
558 | if (isWindowSignal) { |
559 | QAction *openInWindow = |
560 | popup->addAction(icon: QIcon::fromTheme(QStringLiteral("window-new" )), i18nc("@item:inmenu" , "Open \"%1\" in New Window" , text)); |
561 | q->connect(sender: openInWindow, signal: &QAction::triggered, context: q, slot: [this, url]() { |
562 | Q_EMIT q->newWindowRequested(url); |
563 | }); |
564 | } |
565 | } |
566 | } |
567 | |
568 | // provide radiobuttons for toggling between the edit and the navigation mode |
569 | QAction *editAction = popup->addAction(i18n("Edit" )); |
570 | editAction->setCheckable(true); |
571 | |
572 | QAction *navigateAction = popup->addAction(i18n("Navigate" )); |
573 | navigateAction->setCheckable(true); |
574 | |
575 | QActionGroup *modeGroup = new QActionGroup(popup); |
576 | modeGroup->addAction(a: editAction); |
577 | modeGroup->addAction(a: navigateAction); |
578 | if (q->isUrlEditable()) { |
579 | editAction->setChecked(true); |
580 | } else { |
581 | navigateAction->setChecked(true); |
582 | } |
583 | |
584 | popup->addSeparator(); |
585 | |
586 | // allow showing of the full path |
587 | QAction *showFullPathAction = popup->addAction(i18n("Show Full Path" )); |
588 | showFullPathAction->setCheckable(true); |
589 | showFullPathAction->setChecked(q->showFullPath()); |
590 | |
591 | QAction *activatedAction = popup->exec(pos: QCursor::pos()); |
592 | if (activatedAction == copyAction) { |
593 | QMimeData *mimeData = new QMimeData(); |
594 | mimeData->setText(q->locationUrl().toDisplayString(options: QUrl::PreferLocalFile)); |
595 | clipboard->setMimeData(data: mimeData); |
596 | } else if (activatedAction == pasteAction) { |
597 | q->setLocationUrl(QUrl::fromUserInput(userInput: clipboard->text())); |
598 | } else if (activatedAction == editAction) { |
599 | q->setUrlEditable(true); |
600 | } else if (activatedAction == navigateAction) { |
601 | q->setUrlEditable(false); |
602 | } else if (activatedAction == showFullPathAction) { |
603 | q->setShowFullPath(showFullPathAction->isChecked()); |
604 | } |
605 | |
606 | // Delete the menu, unless it has been deleted in its own nested event loop already. |
607 | if (popup) { |
608 | popup->deleteLater(); |
609 | } |
610 | } |
611 | |
612 | void KUrlNavigatorPrivate::slotPathBoxChanged(const QString &text) |
613 | { |
614 | if (text.isEmpty()) { |
615 | const QString scheme = q->locationUrl().scheme(); |
616 | m_schemes->setScheme(scheme); |
617 | if (m_supportedSchemes.count() != 1) { |
618 | m_schemes->show(); |
619 | } |
620 | } else { |
621 | m_schemes->hide(); |
622 | } |
623 | } |
624 | |
625 | void KUrlNavigatorPrivate::updateContent() |
626 | { |
627 | const QUrl currentUrl = q->locationUrl(); |
628 | if (m_placesSelector != nullptr) { |
629 | m_placesSelector->updateSelection(url: currentUrl); |
630 | } |
631 | |
632 | if (m_editable) { |
633 | m_schemes->hide(); |
634 | m_dropDownButton->hide(); |
635 | m_badgeWidgetContainer->hide(); |
636 | |
637 | deleteButtons(); |
638 | m_toggleEditableMode->setSizePolicy(hor: QSizePolicy::Fixed, ver: QSizePolicy::Preferred); |
639 | q->setSizePolicy(hor: QSizePolicy::Minimum, ver: QSizePolicy::Fixed); |
640 | |
641 | m_pathBox->show(); |
642 | m_pathBox->setUrl(currentUrl); |
643 | } else { |
644 | m_pathBox->hide(); |
645 | m_badgeWidgetContainer->show(); |
646 | |
647 | m_schemes->hide(); |
648 | |
649 | m_toggleEditableMode->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Preferred); |
650 | q->setSizePolicy(hor: QSizePolicy::Expanding, ver: QSizePolicy::Fixed); |
651 | |
652 | // Calculate the start index for the directories that should be shown as buttons |
653 | // and create the buttons |
654 | QUrl placeUrl; |
655 | if ((m_placesSelector != nullptr) && !m_showFullPath) { |
656 | placeUrl = m_placesSelector->selectedPlaceUrl(); |
657 | } |
658 | |
659 | if (!placeUrl.isValid()) { |
660 | placeUrl = retrievePlaceUrl(); |
661 | } |
662 | QString placePath = Utils::trailingSlashRemoved(path: placeUrl.path()); |
663 | |
664 | const int startIndex = placePath.count(c: QLatin1Char('/')); |
665 | updateButtons(startIndex); |
666 | } |
667 | } |
668 | |
669 | void KUrlNavigatorPrivate::updateButtons(int startIndex) |
670 | { |
671 | QUrl currentUrl = q->locationUrl(); |
672 | if (!currentUrl.isValid()) { // QFileDialog::setDirectory not called yet |
673 | return; |
674 | } |
675 | |
676 | const QString path = currentUrl.path(); |
677 | |
678 | const int oldButtonCount = m_navButtons.count(); |
679 | |
680 | int idx = startIndex; |
681 | bool hasNext = true; |
682 | do { |
683 | const bool createButton = (idx - startIndex) >= oldButtonCount; |
684 | const bool isFirstButton = (idx == startIndex); |
685 | const QString dirName = path.section(asep: QLatin1Char('/'), astart: idx, aend: idx); |
686 | hasNext = isFirstButton || !dirName.isEmpty(); |
687 | if (hasNext) { |
688 | KUrlNavigatorButton *button = nullptr; |
689 | if (createButton) { |
690 | button = new KUrlNavigatorButton(buttonUrl(index: idx), q); |
691 | button->installEventFilter(filterObj: q); |
692 | button->setForegroundRole(QPalette::WindowText); |
693 | q->connect(sender: button, signal: &KUrlNavigatorButton::urlsDroppedOnNavButton, context: q, slot: [this, button](const QUrl &destination, QDropEvent *event) { |
694 | dropUrls(destination, event, dropButton: button); |
695 | }); |
696 | |
697 | auto activatedFunc = [this](const QUrl &url, Qt::MouseButton btn, Qt::KeyboardModifiers modifiers) { |
698 | slotNavigatorButtonClicked(url, button: btn, modifiers); |
699 | }; |
700 | q->connect(sender: button, signal: &KUrlNavigatorButton::navigatorButtonActivated, context: q, slot&: activatedFunc); |
701 | |
702 | q->connect(sender: button, signal: &KUrlNavigatorButton::finishedTextResolving, context: q, slot: [this]() { |
703 | updateButtonVisibility(); |
704 | }); |
705 | |
706 | appendWidget(widget: button); |
707 | } else { |
708 | button = m_navButtons[idx - startIndex]; |
709 | button->setUrl(buttonUrl(index: idx)); |
710 | } |
711 | |
712 | if (isFirstButton) { |
713 | button->setText(firstButtonText()); |
714 | } |
715 | button->setActive(q->isActive()); |
716 | |
717 | if (createButton) { |
718 | if (!isFirstButton) { |
719 | q->setTabOrder(m_navButtons.constLast(), button); |
720 | } |
721 | m_navButtons.append(t: button); |
722 | } |
723 | |
724 | ++idx; |
725 | button->setActiveSubDirectory(path.section(asep: QLatin1Char('/'), astart: idx, aend: idx)); |
726 | } |
727 | } while (hasNext); |
728 | |
729 | // delete buttons which are not used anymore |
730 | const int newButtonCount = idx - startIndex; |
731 | if (newButtonCount < oldButtonCount) { |
732 | const auto itBegin = m_navButtons.begin() + newButtonCount; |
733 | const auto itEnd = m_navButtons.end(); |
734 | for (auto it = itBegin; it != itEnd; ++it) { |
735 | auto *navBtn = *it; |
736 | navBtn->hide(); |
737 | navBtn->deleteLater(); |
738 | } |
739 | m_navButtons.erase(abegin: itBegin, aend: itEnd); |
740 | } |
741 | |
742 | q->setTabOrder(m_dropDownButton, m_navButtons.constFirst()); |
743 | q->setTabOrder(m_navButtons.constLast(), m_toggleEditableMode); |
744 | |
745 | updateButtonVisibility(); |
746 | } |
747 | |
748 | void KUrlNavigatorPrivate::updateButtonVisibility() |
749 | { |
750 | if (m_editable) { |
751 | return; |
752 | } |
753 | |
754 | const int buttonsCount = m_navButtons.count(); |
755 | if (buttonsCount == 0) { |
756 | m_dropDownButton->hide(); |
757 | return; |
758 | } |
759 | |
760 | // Subtract all widgets from the available width, that must be shown anyway |
761 | int availableWidth = q->width() - m_toggleEditableMode->minimumWidth(); |
762 | |
763 | availableWidth -= m_badgeWidgetContainer->width(); |
764 | |
765 | if ((m_placesSelector != nullptr) && m_placesSelector->isVisible()) { |
766 | availableWidth -= m_placesSelector->width(); |
767 | } |
768 | |
769 | if ((m_schemes != nullptr) && m_schemes->isVisible()) { |
770 | availableWidth -= m_schemes->width(); |
771 | } |
772 | |
773 | // Check whether buttons must be hidden at all... |
774 | int requiredButtonWidth = 0; |
775 | for (const auto *button : std::as_const(t&: m_navButtons)) { |
776 | requiredButtonWidth += button->minimumWidth(); |
777 | } |
778 | |
779 | if (requiredButtonWidth > availableWidth) { |
780 | // At least one button must be hidden. This implies that the |
781 | // drop-down button must get visible, which again decreases the |
782 | // available width. |
783 | availableWidth -= m_dropDownButton->width(); |
784 | } |
785 | |
786 | // Hide buttons... |
787 | bool isLastButton = true; |
788 | bool hasHiddenButtons = false; |
789 | QList<KUrlNavigatorButton *> buttonsToShow; |
790 | for (auto it = m_navButtons.crbegin(); it != m_navButtons.crend(); ++it) { |
791 | KUrlNavigatorButton *button = *it; |
792 | availableWidth -= button->minimumWidth(); |
793 | if ((availableWidth <= 0) && !isLastButton) { |
794 | button->hide(); |
795 | hasHiddenButtons = true; |
796 | } else { |
797 | // Don't show the button immediately, as setActive() |
798 | // might change the size and a relayout gets triggered |
799 | // after showing the button. So the showing of all buttons |
800 | // is postponed until all buttons have the correct |
801 | // activation state. |
802 | buttonsToShow.append(t: button); |
803 | } |
804 | isLastButton = false; |
805 | } |
806 | |
807 | // All buttons have the correct activation state and |
808 | // can be shown now |
809 | for (KUrlNavigatorButton *button : std::as_const(t&: buttonsToShow)) { |
810 | button->show(); |
811 | } |
812 | |
813 | if (hasHiddenButtons) { |
814 | m_dropDownButton->show(); |
815 | } else { |
816 | // Check whether going upwards is possible. If this is the case, show the drop-down button. |
817 | QUrl url(m_navButtons.front()->url()); |
818 | const bool visible = !url.matches(url: KIO::upUrl(url), options: QUrl::StripTrailingSlash) // |
819 | && url.scheme() != QLatin1String("baloosearch" ) // |
820 | && url.scheme() != QLatin1String("filenamesearch" ); |
821 | m_dropDownButton->setVisible(visible); |
822 | } |
823 | } |
824 | |
825 | QString KUrlNavigatorPrivate::firstButtonText() const |
826 | { |
827 | QString text; |
828 | |
829 | // The first URL navigator button should get the name of the |
830 | // place instead of the directory name |
831 | if ((m_placesSelector != nullptr) && !m_showFullPath) { |
832 | text = m_placesSelector->selectedPlaceText(); |
833 | } |
834 | |
835 | const QUrl currentUrl = q->locationUrl(); |
836 | |
837 | if (text.isEmpty()) { |
838 | if (currentUrl.isLocalFile()) { |
839 | #ifdef Q_OS_WIN |
840 | text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath(); |
841 | #else |
842 | text = QStringLiteral("/" ); |
843 | #endif |
844 | } |
845 | } |
846 | |
847 | if (text.isEmpty()) { |
848 | if (currentUrl.path().isEmpty() || currentUrl.path() == QLatin1Char('/')) { |
849 | QUrlQuery query(currentUrl); |
850 | text = query.queryItemValue(QStringLiteral("title" )); |
851 | } |
852 | } |
853 | |
854 | if (text.isEmpty()) { |
855 | text = currentUrl.scheme() + QLatin1Char(':'); |
856 | if (!currentUrl.host().isEmpty()) { |
857 | text += QLatin1Char(' ') + currentUrl.host(); |
858 | } |
859 | } |
860 | |
861 | return text; |
862 | } |
863 | |
864 | QUrl KUrlNavigatorPrivate::buttonUrl(int index) const |
865 | { |
866 | if (index < 0) { |
867 | index = 0; |
868 | } |
869 | |
870 | // Keep scheme, hostname etc. as this is needed for e. g. browsing |
871 | // FTP directories |
872 | QUrl url = q->locationUrl(); |
873 | QString path = url.path(); |
874 | |
875 | if (!path.isEmpty()) { |
876 | if (index == 0) { |
877 | // prevent the last "/" from being stripped |
878 | // or we end up with an empty path |
879 | #ifdef Q_OS_WIN |
880 | path = path.length() > 1 ? path.left(2) : QDir::rootPath(); |
881 | #else |
882 | path = QStringLiteral("/" ); |
883 | #endif |
884 | } else { |
885 | path = path.section(asep: QLatin1Char('/'), astart: 0, aend: index); |
886 | } |
887 | } |
888 | |
889 | url.setPath(path); |
890 | return url; |
891 | } |
892 | |
893 | void KUrlNavigatorPrivate::switchToBreadcrumbMode() |
894 | { |
895 | q->setUrlEditable(false); |
896 | } |
897 | |
898 | void KUrlNavigatorPrivate::deleteButtons() |
899 | { |
900 | for (KUrlNavigatorButton *button : std::as_const(t&: m_navButtons)) { |
901 | button->hide(); |
902 | button->deleteLater(); |
903 | } |
904 | m_navButtons.clear(); |
905 | } |
906 | |
907 | QUrl KUrlNavigatorPrivate::retrievePlaceUrl() const |
908 | { |
909 | QUrl currentUrl = q->locationUrl(); |
910 | currentUrl.setPath(path: QString()); |
911 | return currentUrl; |
912 | } |
913 | |
914 | // ------------------------------------------------------------------------------------------------ |
915 | |
916 | KUrlNavigator::KUrlNavigator(QWidget *parent) |
917 | : KUrlNavigator(nullptr, QUrl{}, parent) |
918 | { |
919 | } |
920 | |
921 | KUrlNavigator::KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent) |
922 | : QWidget(parent) |
923 | , d(new KUrlNavigatorPrivate(url, this, placesModel)) |
924 | { |
925 | const int minHeight = d->m_pathBox->sizeHint().height(); |
926 | setMinimumHeight(minHeight); |
927 | |
928 | setMinimumWidth(100); |
929 | |
930 | d->updateContent(); |
931 | } |
932 | |
933 | KUrlNavigator::~KUrlNavigator() |
934 | { |
935 | d->m_dropDownButton->removeEventFilter(obj: this); |
936 | d->m_pathBox->removeEventFilter(obj: this); |
937 | for (auto *button : std::as_const(t&: d->m_navButtons)) { |
938 | button->removeEventFilter(obj: this); |
939 | } |
940 | } |
941 | |
942 | QUrl KUrlNavigator::locationUrl(int historyIndex) const |
943 | { |
944 | return d->m_coreUrlNavigator->locationUrl(historyIndex); |
945 | } |
946 | |
947 | void KUrlNavigator::saveLocationState(const QByteArray &state) |
948 | { |
949 | auto current = d->m_coreUrlNavigator->locationState().value<KUrlNavigatorData>(); |
950 | current.state = state; |
951 | d->m_coreUrlNavigator->saveLocationState(state: QVariant::fromValue(value: current)); |
952 | } |
953 | |
954 | QByteArray KUrlNavigator::locationState(int historyIndex) const |
955 | { |
956 | return d->m_coreUrlNavigator->locationState(historyIndex).value<KUrlNavigatorData>().state; |
957 | } |
958 | |
959 | bool KUrlNavigator::goBack() |
960 | { |
961 | return d->m_coreUrlNavigator->goBack(); |
962 | } |
963 | |
964 | bool KUrlNavigator::goForward() |
965 | { |
966 | return d->m_coreUrlNavigator->goForward(); |
967 | } |
968 | |
969 | bool KUrlNavigator::goUp() |
970 | { |
971 | return d->m_coreUrlNavigator->goUp(); |
972 | } |
973 | |
974 | void KUrlNavigator::goHome() |
975 | { |
976 | if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) { |
977 | setLocationUrl(QUrl::fromLocalFile(localfile: QDir::homePath())); |
978 | } else { |
979 | setLocationUrl(d->m_homeUrl); |
980 | } |
981 | } |
982 | |
983 | void KUrlNavigator::setHomeUrl(const QUrl &url) |
984 | { |
985 | d->m_homeUrl = url; |
986 | } |
987 | |
988 | QUrl KUrlNavigator::homeUrl() const |
989 | { |
990 | return d->m_homeUrl; |
991 | } |
992 | |
993 | void KUrlNavigator::setUrlEditable(bool editable) |
994 | { |
995 | if (d->m_editable != editable) { |
996 | d->switchView(); |
997 | } |
998 | } |
999 | |
1000 | bool KUrlNavigator::isUrlEditable() const |
1001 | { |
1002 | return d->m_editable; |
1003 | } |
1004 | |
1005 | void KUrlNavigator::setShowFullPath(bool show) |
1006 | { |
1007 | if (d->m_showFullPath != show) { |
1008 | d->m_showFullPath = show; |
1009 | d->updateContent(); |
1010 | } |
1011 | } |
1012 | |
1013 | bool KUrlNavigator::showFullPath() const |
1014 | { |
1015 | return d->m_showFullPath; |
1016 | } |
1017 | |
1018 | void KUrlNavigator::setActive(bool active) |
1019 | { |
1020 | if (active != d->m_active) { |
1021 | d->m_active = active; |
1022 | |
1023 | d->m_dropDownButton->setActive(active); |
1024 | for (KUrlNavigatorButton *button : std::as_const(t&: d->m_navButtons)) { |
1025 | button->setActive(active); |
1026 | } |
1027 | |
1028 | update(); |
1029 | if (active) { |
1030 | Q_EMIT activated(); |
1031 | } |
1032 | } |
1033 | } |
1034 | |
1035 | bool KUrlNavigator::isActive() const |
1036 | { |
1037 | return d->m_active; |
1038 | } |
1039 | |
1040 | void KUrlNavigator::setPlacesSelectorVisible(bool visible) |
1041 | { |
1042 | if (visible == d->m_showPlacesSelector) { |
1043 | return; |
1044 | } |
1045 | |
1046 | if (visible && (d->m_placesSelector == nullptr)) { |
1047 | // the places selector cannot get visible as no |
1048 | // places model is available |
1049 | return; |
1050 | } |
1051 | |
1052 | d->m_showPlacesSelector = visible; |
1053 | d->m_placesSelector->setVisible(visible); |
1054 | } |
1055 | |
1056 | bool KUrlNavigator::isPlacesSelectorVisible() const |
1057 | { |
1058 | return d->m_showPlacesSelector; |
1059 | } |
1060 | |
1061 | QUrl KUrlNavigator::uncommittedUrl() const |
1062 | { |
1063 | KUriFilterData filteredData(d->m_pathBox->currentText().trimmed()); |
1064 | filteredData.setCheckForExecutables(false); |
1065 | if (KUriFilter::self()->filterUri(data&: filteredData, filters: QStringList{QStringLiteral("kshorturifilter" )})) { |
1066 | return filteredData.uri(); |
1067 | } else { |
1068 | return QUrl::fromUserInput(userInput: filteredData.typedString()); |
1069 | } |
1070 | } |
1071 | |
1072 | void KUrlNavigator::setLocationUrl(const QUrl &newUrl) |
1073 | { |
1074 | d->m_coreUrlNavigator->setCurrentLocationUrl(newUrl); |
1075 | |
1076 | d->updateContent(); |
1077 | |
1078 | requestActivation(); |
1079 | } |
1080 | |
1081 | void KUrlNavigator::requestActivation() |
1082 | { |
1083 | setActive(true); |
1084 | } |
1085 | |
1086 | void KUrlNavigator::setFocus() |
1087 | { |
1088 | if (isUrlEditable()) { |
1089 | d->m_pathBox->setFocus(); |
1090 | } else { |
1091 | QWidget::setFocus(); |
1092 | } |
1093 | } |
1094 | |
1095 | void KUrlNavigator::keyPressEvent(QKeyEvent *event) |
1096 | { |
1097 | if (isUrlEditable() && (event->key() == Qt::Key_Escape)) { |
1098 | setUrlEditable(false); |
1099 | } else { |
1100 | QWidget::keyPressEvent(event); |
1101 | } |
1102 | } |
1103 | |
1104 | void KUrlNavigator::keyReleaseEvent(QKeyEvent *event) |
1105 | { |
1106 | QWidget::keyReleaseEvent(event); |
1107 | } |
1108 | |
1109 | void KUrlNavigator::mousePressEvent(QMouseEvent *event) |
1110 | { |
1111 | if (event->button() == Qt::MiddleButton) { |
1112 | requestActivation(); |
1113 | } |
1114 | QWidget::mousePressEvent(event); |
1115 | } |
1116 | |
1117 | void KUrlNavigator::mouseReleaseEvent(QMouseEvent *event) |
1118 | { |
1119 | if (event->button() == Qt::MiddleButton) { |
1120 | const QRect bounds = d->m_toggleEditableMode->geometry(); |
1121 | if (bounds.contains(p: event->pos())) { |
1122 | // The middle mouse button has been clicked above the |
1123 | // toggle-editable-mode-button. Paste the clipboard content |
1124 | // as location URL. |
1125 | QClipboard *clipboard = QApplication::clipboard(); |
1126 | const QMimeData *mimeData = clipboard->mimeData(); |
1127 | if (mimeData->hasText()) { |
1128 | const QString text = mimeData->text(); |
1129 | setLocationUrl(QUrl::fromUserInput(userInput: text)); |
1130 | } |
1131 | } |
1132 | } |
1133 | QWidget::mouseReleaseEvent(event); |
1134 | } |
1135 | |
1136 | void KUrlNavigator::resizeEvent(QResizeEvent *event) |
1137 | { |
1138 | QTimer::singleShot(interval: 0, receiver: this, slot: [this]() { |
1139 | d->updateButtonVisibility(); |
1140 | }); |
1141 | QWidget::resizeEvent(event); |
1142 | } |
1143 | |
1144 | void KUrlNavigator::wheelEvent(QWheelEvent *event) |
1145 | { |
1146 | setActive(true); |
1147 | QWidget::wheelEvent(event); |
1148 | } |
1149 | |
1150 | bool KUrlNavigator::eventFilter(QObject *watched, QEvent *event) |
1151 | { |
1152 | switch (event->type()) { |
1153 | case QEvent::FocusIn: |
1154 | if (watched == d->m_pathBox) { |
1155 | requestActivation(); |
1156 | setFocus(); |
1157 | } |
1158 | for (KUrlNavigatorButton *button : std::as_const(t&: d->m_navButtons)) { |
1159 | button->setShowMnemonic(true); |
1160 | } |
1161 | break; |
1162 | |
1163 | case QEvent::FocusOut: |
1164 | for (KUrlNavigatorButton *button : std::as_const(t&: d->m_navButtons)) { |
1165 | button->setShowMnemonic(false); |
1166 | } |
1167 | break; |
1168 | |
1169 | default: |
1170 | break; |
1171 | } |
1172 | |
1173 | return QWidget::eventFilter(watched, event); |
1174 | } |
1175 | |
1176 | int KUrlNavigator::historySize() const |
1177 | { |
1178 | return d->m_coreUrlNavigator->historySize(); |
1179 | } |
1180 | |
1181 | int KUrlNavigator::historyIndex() const |
1182 | { |
1183 | return d->m_coreUrlNavigator->historyIndex(); |
1184 | } |
1185 | |
1186 | KUrlComboBox *KUrlNavigator::editor() const |
1187 | { |
1188 | return d->m_pathBox; |
1189 | } |
1190 | |
1191 | void KUrlNavigator::setSupportedSchemes(const QStringList &schemes) |
1192 | { |
1193 | d->m_supportedSchemes = schemes; |
1194 | d->m_schemes->setSupportedSchemes(d->m_supportedSchemes); |
1195 | } |
1196 | |
1197 | QStringList KUrlNavigator::supportedSchemes() const |
1198 | { |
1199 | return d->m_supportedSchemes; |
1200 | } |
1201 | |
1202 | QWidget *KUrlNavigator::dropWidget() const |
1203 | { |
1204 | return d->m_dropWidget; |
1205 | } |
1206 | |
1207 | void KUrlNavigator::setShowHiddenFolders(bool showHiddenFolders) |
1208 | { |
1209 | d->m_subfolderOptions.showHidden = showHiddenFolders; |
1210 | } |
1211 | |
1212 | bool KUrlNavigator::showHiddenFolders() const |
1213 | { |
1214 | return d->m_subfolderOptions.showHidden; |
1215 | } |
1216 | |
1217 | void KUrlNavigator::setSortHiddenFoldersLast(bool sortHiddenFoldersLast) |
1218 | { |
1219 | d->m_subfolderOptions.sortHiddenLast = sortHiddenFoldersLast; |
1220 | } |
1221 | |
1222 | bool KUrlNavigator::sortHiddenFoldersLast() const |
1223 | { |
1224 | return d->m_subfolderOptions.sortHiddenLast; |
1225 | } |
1226 | |
1227 | void KUrlNavigator::setBadgeWidget(QWidget *widget) |
1228 | { |
1229 | QWidget *oldWidget = badgeWidget(); |
1230 | if (oldWidget) { |
1231 | if (widget == oldWidget) { |
1232 | return; |
1233 | } |
1234 | d->m_badgeWidgetContainer->layout()->replaceWidget(from: oldWidget, to: widget); |
1235 | oldWidget->deleteLater(); |
1236 | } else { |
1237 | d->m_badgeWidgetContainer->layout()->addWidget(w: widget); |
1238 | } |
1239 | } |
1240 | |
1241 | QWidget *KUrlNavigator::badgeWidget() const |
1242 | { |
1243 | QLayoutItem *item = d->m_badgeWidgetContainer->layout()->itemAt(index: 0); |
1244 | if (item) { |
1245 | return item->widget(); |
1246 | } else { |
1247 | return nullptr; |
1248 | } |
1249 | } |
1250 | |
1251 | #include "moc_kurlnavigator.cpp" |
1252 | |