File indexing completed on 2024-04-21 03:55:25
0001 /* 0002 SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at> 0003 SPDX-FileCopyrightText: 2006 Aaron J. Seigo <aseigo@kde.org> 0004 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org> 0005 SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer @ kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kurlnavigator.h" 0011 #include "kcoreurlnavigator.h" 0012 0013 #include "../utils_p.h" 0014 #include "kurlnavigatorbutton_p.h" 0015 #include "kurlnavigatordropdownbutton_p.h" 0016 #include "kurlnavigatorpathselectoreventfilter_p.h" 0017 #include "kurlnavigatorplacesselector_p.h" 0018 #include "kurlnavigatorschemecombo_p.h" 0019 #include "kurlnavigatortogglebutton_p.h" 0020 0021 #include <KIO/StatJob> 0022 #include <KLocalizedString> 0023 #include <kfileitem.h> 0024 #include <kfileplacesmodel.h> 0025 #include <kprotocolinfo.h> 0026 #include <kurifilter.h> 0027 #include <kurlcombobox.h> 0028 #include <kurlcompletion.h> 0029 0030 #include <QActionGroup> 0031 #include <QApplication> 0032 #include <QClipboard> 0033 #include <QDir> 0034 #include <QDropEvent> 0035 #include <QHBoxLayout> 0036 #include <QKeyEvent> 0037 #include <QMenu> 0038 #include <QMetaMethod> 0039 #include <QMimeData> 0040 #include <QMimeDatabase> 0041 #include <QTimer> 0042 #include <QUrlQuery> 0043 0044 #include <algorithm> 0045 #include <numeric> 0046 0047 using namespace KDEPrivate; 0048 0049 struct KUrlNavigatorData { 0050 QByteArray state; 0051 }; 0052 Q_DECLARE_METATYPE(KUrlNavigatorData) 0053 0054 class KUrlNavigatorPrivate 0055 { 0056 public: 0057 KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, KFilePlacesModel *placesModel); 0058 0059 ~KUrlNavigatorPrivate() 0060 { 0061 m_dropDownButton->removeEventFilter(q); 0062 m_pathBox->removeEventFilter(q); 0063 m_toggleEditableMode->removeEventFilter(q); 0064 0065 for (KUrlNavigatorButton *button : std::as_const(m_navButtons)) { 0066 button->removeEventFilter(q); 0067 } 0068 } 0069 0070 /** Applies the edited URL in m_pathBox to the URL navigator */ 0071 void applyUncommittedUrl(); 0072 void slotApplyUrl(QUrl url); 0073 // Returns true if "text" matched a URI filter (i.e. was fitlered), 0074 // otherwise returns false 0075 bool slotCheckFilters(const QString &text); 0076 0077 void slotReturnPressed(); 0078 void slotSchemeChanged(const QString &); 0079 void openPathSelectorMenu(); 0080 0081 /** 0082 * Appends the widget at the end of the URL navigator. It is assured 0083 * that the filler widget remains as last widget to fill the remaining 0084 * width. 0085 */ 0086 void appendWidget(QWidget *widget, int stretch = 0); 0087 0088 /** 0089 * This slot is connected to the clicked signal of the navigation bar button. It calls switchView(). 0090 * Moreover, if switching from "editable" mode to the breadcrumb view, it calls applyUncommittedUrl(). 0091 */ 0092 void slotToggleEditableButtonPressed(); 0093 0094 /** 0095 * Switches the navigation bar between the breadcrumb view and the 0096 * traditional view (see setUrlEditable()). 0097 */ 0098 void switchView(); 0099 0100 /** Emits the signal urlsDropped(). */ 0101 void dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton); 0102 0103 /** 0104 * Is invoked when a navigator button has been clicked. 0105 * Different combinations of mouse clicks and keyboard modifiers have different effects on how 0106 * the url is opened. The behaviours are the following: 0107 * - shift+middle-click or ctrl+shift+left-click => activeTabRequested() signal is emitted 0108 * - ctrl+left-click or middle-click => tabRequested() signal is emitted 0109 * - shift+left-click => newWindowRequested() signal is emitted 0110 * - left-click => open the new url in-place 0111 */ 0112 void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); 0113 0114 void openContextMenu(const QPoint &p); 0115 0116 void slotPathBoxChanged(const QString &text); 0117 0118 void updateContent(); 0119 0120 /** 0121 * Updates all buttons to have one button for each part of the 0122 * current URL. Existing buttons, which are available by m_navButtons, 0123 * are reused if possible. If the URL is longer, new buttons will be 0124 * created, if the URL is shorter, the remaining buttons will be deleted. 0125 * @param startIndex Start index of URL part (/), where the buttons 0126 * should be created for each following part. 0127 */ 0128 void updateButtons(int startIndex); 0129 0130 /** 0131 * Updates the visibility state of all buttons describing the URL. If the 0132 * width of the URL navigator is too small, the buttons representing the upper 0133 * paths of the URL will be hidden and moved to a drop down menu. 0134 */ 0135 void updateButtonVisibility(); 0136 0137 /** 0138 * @return Text for the first button of the URL navigator. 0139 */ 0140 QString firstButtonText() const; 0141 0142 /** 0143 * Returns the URL that should be applied for the button with the index \a index. 0144 */ 0145 QUrl buttonUrl(int index) const; 0146 0147 void switchToBreadcrumbMode(); 0148 0149 /** 0150 * Deletes all URL navigator buttons. m_navButtons is 0151 * empty after this operation. 0152 */ 0153 void deleteButtons(); 0154 0155 /** 0156 * Retrieves the place url for the current url. 0157 * E. g. for the path "fish://root@192.168.0.2/var/lib" the string 0158 * "fish://root@192.168.0.2" will be returned, which leads to the 0159 * navigation indication 'Custom Path > var > lib". For e. g. 0160 * "settings:///System/" the path "settings://" will be returned. 0161 */ 0162 QUrl retrievePlaceUrl() const; 0163 0164 KUrlNavigator *const q; 0165 0166 QHBoxLayout *m_layout = new QHBoxLayout(q); 0167 KCoreUrlNavigator *m_coreUrlNavigator = nullptr; 0168 QList<KUrlNavigatorButton *> m_navButtons; 0169 QStringList m_supportedSchemes; 0170 QUrl m_homeUrl; 0171 KUrlNavigatorPlacesSelector *m_placesSelector = nullptr; 0172 KUrlComboBox *m_pathBox = nullptr; 0173 KUrlNavigatorSchemeCombo *m_schemes = nullptr; 0174 KUrlNavigatorDropDownButton *m_dropDownButton = nullptr; 0175 KUrlNavigatorButtonBase *m_toggleEditableMode = nullptr; 0176 QWidget *m_dropWidget = nullptr; 0177 0178 bool m_editable = false; 0179 bool m_active = true; 0180 bool m_showPlacesSelector = false; 0181 bool m_showFullPath = false; 0182 0183 struct { 0184 bool showHidden = false; 0185 bool sortHiddenLast = false; 0186 } m_subfolderOptions; 0187 }; 0188 0189 KUrlNavigatorPrivate::KUrlNavigatorPrivate(const QUrl &url, KUrlNavigator *qq, KFilePlacesModel *placesModel) 0190 : q(qq) 0191 , m_coreUrlNavigator(new KCoreUrlNavigator(url, qq)) 0192 , m_showPlacesSelector(placesModel != nullptr) 0193 { 0194 m_layout->setSpacing(0); 0195 m_layout->setContentsMargins(0, 0, 0, 0); 0196 0197 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::currentLocationUrlChanged, q, [this]() { 0198 Q_EMIT q->urlChanged(m_coreUrlNavigator->currentLocationUrl()); 0199 }); 0200 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::currentUrlAboutToChange, q, [this](const QUrl &url) { 0201 Q_EMIT q->urlAboutToBeChanged(url); 0202 }); 0203 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::historySizeChanged, q, [this]() { 0204 Q_EMIT q->historyChanged(); 0205 }); 0206 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::historyIndexChanged, q, [this]() { 0207 Q_EMIT q->historyChanged(); 0208 }); 0209 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::historyChanged, q, [this]() { 0210 Q_EMIT q->historyChanged(); 0211 }); 0212 q->connect(m_coreUrlNavigator, &KCoreUrlNavigator::urlSelectionRequested, q, [this](const QUrl &url) { 0213 Q_EMIT q->urlSelectionRequested(url); 0214 }); 0215 0216 // initialize the places selector 0217 q->setAutoFillBackground(false); 0218 0219 if (placesModel != nullptr) { 0220 m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel); 0221 q->connect(m_placesSelector, &KUrlNavigatorPlacesSelector::placeActivated, q, &KUrlNavigator::setLocationUrl); 0222 q->connect(m_placesSelector, &KUrlNavigatorPlacesSelector::tabRequested, q, &KUrlNavigator::tabRequested); 0223 0224 auto updateContentFunc = [this]() { 0225 updateContent(); 0226 }; 0227 q->connect(placesModel, &KFilePlacesModel::rowsInserted, q, updateContentFunc); 0228 q->connect(placesModel, &KFilePlacesModel::rowsRemoved, q, updateContentFunc); 0229 q->connect(placesModel, &KFilePlacesModel::dataChanged, q, updateContentFunc); 0230 } 0231 0232 // create scheme combo 0233 m_schemes = new KUrlNavigatorSchemeCombo(QString(), q); 0234 q->connect(m_schemes, &KUrlNavigatorSchemeCombo::activated, q, [this](const QString &schene) { 0235 slotSchemeChanged(schene); 0236 }); 0237 0238 // create drop down button for accessing all paths of the URL 0239 m_dropDownButton = new KUrlNavigatorDropDownButton(q); 0240 m_dropDownButton->setForegroundRole(QPalette::WindowText); 0241 m_dropDownButton->installEventFilter(q); 0242 q->connect(m_dropDownButton, &KUrlNavigatorDropDownButton::clicked, q, [this]() { 0243 openPathSelectorMenu(); 0244 }); 0245 0246 // initialize the path box of the traditional view 0247 m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q); 0248 m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); 0249 m_pathBox->installEventFilter(q); 0250 0251 KUrlCompletion *kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion); 0252 m_pathBox->setCompletionObject(kurlCompletion); 0253 m_pathBox->setAutoDeleteCompletionObject(true); 0254 0255 // TODO KF6: remove this QOverload, only KUrlComboBox::returnPressed(const QString &) will remain 0256 q->connect(m_pathBox, &KUrlComboBox::returnPressed, q, [this]() { 0257 slotReturnPressed(); 0258 }); 0259 q->connect(m_pathBox, &KUrlComboBox::urlActivated, q, &KUrlNavigator::setLocationUrl); 0260 q->connect(m_pathBox, &QComboBox::editTextChanged, q, [this](const QString &text) { 0261 slotPathBoxChanged(text); 0262 }); 0263 0264 // create toggle button which allows to switch between 0265 // the breadcrumb and traditional view 0266 m_toggleEditableMode = new KUrlNavigatorToggleButton(q); 0267 m_toggleEditableMode->installEventFilter(q); 0268 m_toggleEditableMode->setMinimumWidth(20); 0269 q->connect(m_toggleEditableMode, &KUrlNavigatorToggleButton::clicked, q, [this]() { 0270 slotToggleEditableButtonPressed(); 0271 }); 0272 0273 if (m_placesSelector != nullptr) { 0274 m_layout->addWidget(m_placesSelector); 0275 } 0276 m_layout->addWidget(m_schemes); 0277 m_layout->addWidget(m_dropDownButton); 0278 m_layout->addWidget(m_pathBox, 1); 0279 m_layout->addWidget(m_toggleEditableMode); 0280 0281 q->setContextMenuPolicy(Qt::CustomContextMenu); 0282 q->connect(q, &QWidget::customContextMenuRequested, q, [this](const QPoint &pos) { 0283 openContextMenu(pos); 0284 }); 0285 } 0286 0287 void KUrlNavigatorPrivate::appendWidget(QWidget *widget, int stretch) 0288 { 0289 m_layout->insertWidget(m_layout->count() - 1, widget, stretch); 0290 } 0291 0292 void KUrlNavigatorPrivate::slotApplyUrl(QUrl url) 0293 { 0294 // Parts of the following code have been taken from the class KateFileSelector 0295 // located in kate/app/katefileselector.hpp of Kate. 0296 // SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org> 0297 // SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org> 0298 // SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk> 0299 0300 // For example "desktop:/" _not_ "desktop:", see the comment in slotSchemeChanged() 0301 if (!url.isEmpty() && url.path().isEmpty() && KProtocolInfo::protocolClass(url.scheme()) == QLatin1String(":local")) { 0302 url.setPath(QStringLiteral("/")); 0303 } 0304 0305 const auto urlStr = url.toString(); 0306 QStringList urls = m_pathBox->urls(); 0307 urls.removeAll(urlStr); 0308 urls.prepend(urlStr); 0309 m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom); 0310 0311 q->setLocationUrl(url); 0312 // The URL might have been adjusted by KUrlNavigator::setUrl(), hence 0313 // synchronize the result in the path box. 0314 m_pathBox->setUrl(q->locationUrl()); 0315 } 0316 0317 bool KUrlNavigatorPrivate::slotCheckFilters(const QString &text) 0318 { 0319 KUriFilterData filteredData(text); 0320 filteredData.setCheckForExecutables(false); 0321 // Using kshorturifilter to fix up e.g. "ftp.kde.org" ---> "ftp://ftp.kde.org" 0322 const auto filtersList = QStringList{QStringLiteral("kshorturifilter")}; 0323 const bool wasFiltered = KUriFilter::self()->filterUri(filteredData, filtersList); 0324 if (wasFiltered) { 0325 slotApplyUrl(filteredData.uri()); // The text was filtered 0326 } 0327 return wasFiltered; 0328 } 0329 0330 void KUrlNavigatorPrivate::applyUncommittedUrl() 0331 { 0332 const QString text = m_pathBox->currentText().trimmed(); 0333 QUrl url = q->locationUrl(); 0334 0335 // Using the stat job below, check if the url and text match a local dir; but first 0336 // handle a special case where "url" is empty in the unittests which use 0337 // KUrlNavigator::setLocationUrl(QUrl()); in practice (e.g. in Dolphin, or KFileWidget), 0338 // locationUrl() is never empty 0339 if (url.isEmpty() && !text.isEmpty()) { 0340 if (slotCheckFilters(text)) { 0341 return; 0342 } 0343 } 0344 0345 // Treat absolute paths as absolute paths. 0346 // Relative paths get appended to the current path. 0347 if (text.startsWith(QLatin1Char('/'))) { 0348 url.setPath(text); 0349 } else { 0350 url.setPath(Utils::concatPaths(url.path(), text)); 0351 } 0352 0353 // Dirs and symlinks to dirs 0354 constexpr auto details = KIO::StatBasic | KIO::StatResolveSymlink; 0355 auto *job = KIO::stat(url, KIO::StatJob::DestinationSide, details, KIO::HideProgressInfo); 0356 q->connect(job, &KJob::result, q, [this, job, text]() { 0357 // If there is a dir matching "text" relative to the current url, use that, e.g.: 0358 // - typing "bar" while at "/path/to/foo" ---> "/path/to/foo/bar/" 0359 // - typing ".config" while at "/home/foo" ---> "/home/foo/.config" 0360 if (!job->error() && job->statResult().isDir()) { 0361 slotApplyUrl(job->url()); 0362 return; 0363 } 0364 0365 // Check if text matches a URI filter 0366 if (slotCheckFilters(text)) { 0367 return; 0368 } 0369 0370 // ... otherwise fallback to whatever QUrl::fromUserInput() returns 0371 slotApplyUrl(QUrl::fromUserInput(text)); 0372 }); 0373 } 0374 0375 void KUrlNavigatorPrivate::slotReturnPressed() 0376 { 0377 applyUncommittedUrl(); 0378 0379 Q_EMIT q->returnPressed(); 0380 0381 if (QApplication::keyboardModifiers() & Qt::ControlModifier) { 0382 // Pressing Ctrl+Return automatically switches back to the breadcrumb mode. 0383 // The switch must be done asynchronously, as we are in the context of the 0384 // editor. 0385 auto switchModeFunc = [this]() { 0386 switchToBreadcrumbMode(); 0387 }; 0388 QMetaObject::invokeMethod(q, switchModeFunc, Qt::QueuedConnection); 0389 } 0390 } 0391 0392 void KUrlNavigatorPrivate::slotSchemeChanged(const QString &scheme) 0393 { 0394 Q_ASSERT(m_editable); 0395 0396 QUrl url; 0397 url.setScheme(scheme); 0398 if (KProtocolInfo::protocolClass(scheme) == QLatin1String(":local")) { 0399 // E.g. "file:/" or "desktop:/", _not_ "file:" or "desktop:" respectively. 0400 // This is the more expected behaviour, "file:somedir" treats somedir as 0401 // a path relative to current dir; file:/somedir is an absolute path to /somedir. 0402 url.setPath(QStringLiteral("/")); 0403 } else { 0404 // With no authority set we'll get e.g. "ftp:" instead of "ftp://". 0405 // We want the latter, so let's set an empty authority. 0406 url.setAuthority(QString()); 0407 } 0408 0409 m_pathBox->setEditUrl(url); 0410 } 0411 0412 void KUrlNavigatorPrivate::openPathSelectorMenu() 0413 { 0414 if (m_navButtons.count() <= 0) { 0415 return; 0416 } 0417 0418 const QUrl firstVisibleUrl = m_navButtons.constFirst()->url(); 0419 0420 QString spacer; 0421 QPointer<QMenu> popup = new QMenu(q); 0422 0423 auto *popupFilter = new KUrlNavigatorPathSelectorEventFilter(popup.data()); 0424 q->connect(popupFilter, &KUrlNavigatorPathSelectorEventFilter::tabRequested, q, &KUrlNavigator::tabRequested); 0425 popup->installEventFilter(popupFilter); 0426 0427 const QUrl placeUrl = retrievePlaceUrl(); 0428 int idx = placeUrl.path().count(QLatin1Char('/')); // idx points to the first directory 0429 // after the place path 0430 0431 const QString path = m_coreUrlNavigator->locationUrl(m_coreUrlNavigator->historyIndex()).path(); 0432 QString dirName = path.section(QLatin1Char('/'), idx, idx); 0433 if (dirName.isEmpty()) { 0434 if (placeUrl.isLocalFile()) { 0435 dirName = QStringLiteral("/"); 0436 } else { 0437 dirName = placeUrl.toDisplayString(); 0438 } 0439 } 0440 do { 0441 const QString text = spacer + dirName; 0442 0443 QAction *action = new QAction(text, popup); 0444 const QUrl currentUrl = buttonUrl(idx); 0445 if (currentUrl == firstVisibleUrl) { 0446 popup->addSeparator(); 0447 } 0448 action->setData(QVariant(currentUrl.toString())); 0449 popup->addAction(action); 0450 0451 ++idx; 0452 spacer.append(QLatin1String(" ")); 0453 dirName = path.section(QLatin1Char('/'), idx, idx); 0454 } while (!dirName.isEmpty()); 0455 0456 const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight()); 0457 const QAction *activatedAction = popup->exec(pos); 0458 if (activatedAction != nullptr) { 0459 const QUrl url(activatedAction->data().toString()); 0460 q->setLocationUrl(url); 0461 } 0462 0463 // Delete the menu, unless it has been deleted in its own nested event loop already. 0464 if (popup) { 0465 popup->deleteLater(); 0466 } 0467 } 0468 0469 void KUrlNavigatorPrivate::slotToggleEditableButtonPressed() 0470 { 0471 if (m_editable) { 0472 applyUncommittedUrl(); 0473 } 0474 0475 switchView(); 0476 } 0477 0478 void KUrlNavigatorPrivate::switchView() 0479 { 0480 m_toggleEditableMode->setFocus(); 0481 m_editable = !m_editable; 0482 m_toggleEditableMode->setChecked(m_editable); 0483 updateContent(); 0484 if (q->isUrlEditable()) { 0485 m_pathBox->setFocus(); 0486 } 0487 0488 q->requestActivation(); 0489 Q_EMIT q->editableStateChanged(m_editable); 0490 } 0491 0492 void KUrlNavigatorPrivate::dropUrls(const QUrl &destination, QDropEvent *event, KUrlNavigatorButton *dropButton) 0493 { 0494 if (event->mimeData()->hasUrls()) { 0495 m_dropWidget = qobject_cast<QWidget *>(dropButton); 0496 Q_EMIT q->urlsDropped(destination, event); 0497 } 0498 } 0499 0500 void KUrlNavigatorPrivate::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) 0501 { 0502 if ((button & Qt::MiddleButton && modifiers & Qt::ShiftModifier) || (button & Qt::LeftButton && modifiers & (Qt::ControlModifier | Qt::ShiftModifier))) { 0503 Q_EMIT q->activeTabRequested(url); 0504 } else if (button & Qt::MiddleButton || (button & Qt::LeftButton && modifiers & Qt::ControlModifier)) { 0505 Q_EMIT q->tabRequested(url); 0506 } else if (button & Qt::LeftButton && modifiers & Qt::ShiftModifier) { 0507 Q_EMIT q->newWindowRequested(url); 0508 } else if (button & Qt::LeftButton) { 0509 q->setLocationUrl(url); 0510 } 0511 } 0512 0513 void KUrlNavigatorPrivate::openContextMenu(const QPoint &p) 0514 { 0515 q->setActive(true); 0516 0517 QPointer<QMenu> popup = new QMenu(q); 0518 0519 // provide 'Copy' action, which copies the current URL of 0520 // the URL navigator into the clipboard 0521 QAction *copyAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); 0522 0523 // provide 'Paste' action, which copies the current clipboard text 0524 // into the URL navigator 0525 QAction *pasteAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste")); 0526 QClipboard *clipboard = QApplication::clipboard(); 0527 pasteAction->setEnabled(!clipboard->text().isEmpty()); 0528 0529 popup->addSeparator(); 0530 0531 // We are checking whether the signal is connected because it's odd to have a tab entry even 0532 // if it's not supported, like in the case of the open dialog 0533 const bool isTabSignal = q->isSignalConnected(QMetaMethod::fromSignal(&KUrlNavigator::tabRequested)); 0534 const bool isWindowSignal = q->isSignalConnected(QMetaMethod::fromSignal(&KUrlNavigator::newWindowRequested)); 0535 if (isTabSignal || isWindowSignal) { 0536 auto it = std::find_if(m_navButtons.cbegin(), m_navButtons.cend(), [&p](const KUrlNavigatorButton *button) { 0537 return button->geometry().contains(p); 0538 }); 0539 if (it != m_navButtons.cend()) { 0540 const auto *button = *it; 0541 const QUrl url = button->url(); 0542 const QString text = button->text(); 0543 0544 if (isTabSignal) { 0545 QAction *openInTab = popup->addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18nc("@item:inmenu", "Open \"%1\" in New Tab", text)); 0546 q->connect(openInTab, &QAction::triggered, q, [this, url]() { 0547 Q_EMIT q->tabRequested(url); 0548 }); 0549 } 0550 0551 if (isWindowSignal) { 0552 QAction *openInWindow = 0553 popup->addAction(QIcon::fromTheme(QStringLiteral("window-new")), i18nc("@item:inmenu", "Open \"%1\" in New Window", text)); 0554 q->connect(openInWindow, &QAction::triggered, q, [this, url]() { 0555 Q_EMIT q->newWindowRequested(url); 0556 }); 0557 } 0558 } 0559 } 0560 0561 // provide radiobuttons for toggling between the edit and the navigation mode 0562 QAction *editAction = popup->addAction(i18n("Edit")); 0563 editAction->setCheckable(true); 0564 0565 QAction *navigateAction = popup->addAction(i18n("Navigate")); 0566 navigateAction->setCheckable(true); 0567 0568 QActionGroup *modeGroup = new QActionGroup(popup); 0569 modeGroup->addAction(editAction); 0570 modeGroup->addAction(navigateAction); 0571 if (q->isUrlEditable()) { 0572 editAction->setChecked(true); 0573 } else { 0574 navigateAction->setChecked(true); 0575 } 0576 0577 popup->addSeparator(); 0578 0579 // allow showing of the full path 0580 QAction *showFullPathAction = popup->addAction(i18n("Show Full Path")); 0581 showFullPathAction->setCheckable(true); 0582 showFullPathAction->setChecked(q->showFullPath()); 0583 0584 QAction *activatedAction = popup->exec(QCursor::pos()); 0585 if (activatedAction == copyAction) { 0586 QMimeData *mimeData = new QMimeData(); 0587 mimeData->setText(q->locationUrl().toDisplayString(QUrl::PreferLocalFile)); 0588 clipboard->setMimeData(mimeData); 0589 } else if (activatedAction == pasteAction) { 0590 q->setLocationUrl(QUrl::fromUserInput(clipboard->text())); 0591 } else if (activatedAction == editAction) { 0592 q->setUrlEditable(true); 0593 } else if (activatedAction == navigateAction) { 0594 q->setUrlEditable(false); 0595 } else if (activatedAction == showFullPathAction) { 0596 q->setShowFullPath(showFullPathAction->isChecked()); 0597 } 0598 0599 // Delete the menu, unless it has been deleted in its own nested event loop already. 0600 if (popup) { 0601 popup->deleteLater(); 0602 } 0603 } 0604 0605 void KUrlNavigatorPrivate::slotPathBoxChanged(const QString &text) 0606 { 0607 if (text.isEmpty()) { 0608 const QString scheme = q->locationUrl().scheme(); 0609 m_schemes->setScheme(scheme); 0610 if (m_supportedSchemes.count() != 1) { 0611 m_schemes->show(); 0612 } 0613 } else { 0614 m_schemes->hide(); 0615 } 0616 } 0617 0618 void KUrlNavigatorPrivate::updateContent() 0619 { 0620 const QUrl currentUrl = q->locationUrl(); 0621 if (m_placesSelector != nullptr) { 0622 m_placesSelector->updateSelection(currentUrl); 0623 } 0624 0625 if (m_editable) { 0626 m_schemes->hide(); 0627 m_dropDownButton->hide(); 0628 0629 deleteButtons(); 0630 m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); 0631 q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); 0632 0633 m_pathBox->show(); 0634 m_pathBox->setUrl(currentUrl); 0635 } else { 0636 m_pathBox->hide(); 0637 0638 m_schemes->hide(); 0639 0640 m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 0641 q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 0642 0643 // Calculate the start index for the directories that should be shown as buttons 0644 // and create the buttons 0645 QUrl placeUrl; 0646 if ((m_placesSelector != nullptr) && !m_showFullPath) { 0647 placeUrl = m_placesSelector->selectedPlaceUrl(); 0648 } 0649 0650 if (!placeUrl.isValid()) { 0651 placeUrl = retrievePlaceUrl(); 0652 } 0653 QString placePath = Utils::trailingSlashRemoved(placeUrl.path()); 0654 0655 const int startIndex = placePath.count(QLatin1Char('/')); 0656 updateButtons(startIndex); 0657 } 0658 } 0659 0660 void KUrlNavigatorPrivate::updateButtons(int startIndex) 0661 { 0662 QUrl currentUrl = q->locationUrl(); 0663 if (!currentUrl.isValid()) { // QFileDialog::setDirectory not called yet 0664 return; 0665 } 0666 0667 const QString path = currentUrl.path(); 0668 0669 const int oldButtonCount = m_navButtons.count(); 0670 0671 int idx = startIndex; 0672 bool hasNext = true; 0673 do { 0674 const bool createButton = (idx - startIndex) >= oldButtonCount; 0675 const bool isFirstButton = (idx == startIndex); 0676 const QString dirName = path.section(QLatin1Char('/'), idx, idx); 0677 hasNext = isFirstButton || !dirName.isEmpty(); 0678 if (hasNext) { 0679 KUrlNavigatorButton *button = nullptr; 0680 if (createButton) { 0681 button = new KUrlNavigatorButton(buttonUrl(idx), q); 0682 button->installEventFilter(q); 0683 button->setForegroundRole(QPalette::WindowText); 0684 q->connect(button, &KUrlNavigatorButton::urlsDroppedOnNavButton, q, [this, button](const QUrl &destination, QDropEvent *event) { 0685 dropUrls(destination, event, button); 0686 }); 0687 0688 auto activatedFunc = [this](const QUrl &url, Qt::MouseButton btn, Qt::KeyboardModifiers modifiers) { 0689 slotNavigatorButtonClicked(url, btn, modifiers); 0690 }; 0691 q->connect(button, &KUrlNavigatorButton::navigatorButtonActivated, q, activatedFunc); 0692 0693 q->connect(button, &KUrlNavigatorButton::finishedTextResolving, q, [this]() { 0694 updateButtonVisibility(); 0695 }); 0696 0697 appendWidget(button); 0698 } else { 0699 button = m_navButtons[idx - startIndex]; 0700 button->setUrl(buttonUrl(idx)); 0701 } 0702 0703 if (isFirstButton) { 0704 button->setText(firstButtonText()); 0705 } 0706 button->setActive(q->isActive()); 0707 0708 if (createButton) { 0709 if (!isFirstButton) { 0710 q->setTabOrder(m_navButtons.constLast(), button); 0711 } 0712 m_navButtons.append(button); 0713 } 0714 0715 ++idx; 0716 button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx)); 0717 } 0718 } while (hasNext); 0719 0720 // delete buttons which are not used anymore 0721 const int newButtonCount = idx - startIndex; 0722 if (newButtonCount < oldButtonCount) { 0723 const auto itBegin = m_navButtons.begin() + newButtonCount; 0724 const auto itEnd = m_navButtons.end(); 0725 for (auto it = itBegin; it != itEnd; ++it) { 0726 auto *navBtn = *it; 0727 navBtn->hide(); 0728 navBtn->deleteLater(); 0729 } 0730 m_navButtons.erase(itBegin, itEnd); 0731 } 0732 0733 q->setTabOrder(m_dropDownButton, m_navButtons.constFirst()); 0734 q->setTabOrder(m_navButtons.constLast(), m_toggleEditableMode); 0735 0736 updateButtonVisibility(); 0737 } 0738 0739 void KUrlNavigatorPrivate::updateButtonVisibility() 0740 { 0741 if (m_editable) { 0742 return; 0743 } 0744 0745 const int buttonsCount = m_navButtons.count(); 0746 if (buttonsCount == 0) { 0747 m_dropDownButton->hide(); 0748 return; 0749 } 0750 0751 // Subtract all widgets from the available width, that must be shown anyway 0752 int availableWidth = q->width() - m_toggleEditableMode->minimumWidth(); 0753 0754 if ((m_placesSelector != nullptr) && m_placesSelector->isVisible()) { 0755 availableWidth -= m_placesSelector->width(); 0756 } 0757 0758 if ((m_schemes != nullptr) && m_schemes->isVisible()) { 0759 availableWidth -= m_schemes->width(); 0760 } 0761 0762 // Check whether buttons must be hidden at all... 0763 int requiredButtonWidth = 0; 0764 for (const auto *button : std::as_const(m_navButtons)) { 0765 requiredButtonWidth += button->minimumWidth(); 0766 } 0767 0768 if (requiredButtonWidth > availableWidth) { 0769 // At least one button must be hidden. This implies that the 0770 // drop-down button must get visible, which again decreases the 0771 // available width. 0772 availableWidth -= m_dropDownButton->width(); 0773 } 0774 0775 // Hide buttons... 0776 bool isLastButton = true; 0777 bool hasHiddenButtons = false; 0778 QList<KUrlNavigatorButton *> buttonsToShow; 0779 for (auto it = m_navButtons.crbegin(); it != m_navButtons.crend(); ++it) { 0780 KUrlNavigatorButton *button = *it; 0781 availableWidth -= button->minimumWidth(); 0782 if ((availableWidth <= 0) && !isLastButton) { 0783 button->hide(); 0784 hasHiddenButtons = true; 0785 } else { 0786 // Don't show the button immediately, as setActive() 0787 // might change the size and a relayout gets triggered 0788 // after showing the button. So the showing of all buttons 0789 // is postponed until all buttons have the correct 0790 // activation state. 0791 buttonsToShow.append(button); 0792 } 0793 isLastButton = false; 0794 } 0795 0796 // All buttons have the correct activation state and 0797 // can be shown now 0798 for (KUrlNavigatorButton *button : std::as_const(buttonsToShow)) { 0799 button->show(); 0800 } 0801 0802 if (hasHiddenButtons) { 0803 m_dropDownButton->show(); 0804 } else { 0805 // Check whether going upwards is possible. If this is the case, show the drop-down button. 0806 QUrl url(m_navButtons.front()->url()); 0807 const bool visible = !url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash) // 0808 && url.scheme() != QLatin1String("baloosearch") // 0809 && url.scheme() != QLatin1String("filenamesearch"); 0810 m_dropDownButton->setVisible(visible); 0811 } 0812 } 0813 0814 QString KUrlNavigatorPrivate::firstButtonText() const 0815 { 0816 QString text; 0817 0818 // The first URL navigator button should get the name of the 0819 // place instead of the directory name 0820 if ((m_placesSelector != nullptr) && !m_showFullPath) { 0821 text = m_placesSelector->selectedPlaceText(); 0822 } 0823 0824 const QUrl currentUrl = q->locationUrl(); 0825 0826 if (text.isEmpty()) { 0827 if (currentUrl.isLocalFile()) { 0828 #ifdef Q_OS_WIN 0829 text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath(); 0830 #else 0831 text = QStringLiteral("/"); 0832 #endif 0833 } 0834 } 0835 0836 if (text.isEmpty()) { 0837 if (currentUrl.path().isEmpty() || currentUrl.path() == QLatin1Char('/')) { 0838 QUrlQuery query(currentUrl); 0839 text = query.queryItemValue(QStringLiteral("title")); 0840 } 0841 } 0842 0843 if (text.isEmpty()) { 0844 text = currentUrl.scheme() + QLatin1Char(':'); 0845 if (!currentUrl.host().isEmpty()) { 0846 text += QLatin1Char(' ') + currentUrl.host(); 0847 } 0848 } 0849 0850 return text; 0851 } 0852 0853 QUrl KUrlNavigatorPrivate::buttonUrl(int index) const 0854 { 0855 if (index < 0) { 0856 index = 0; 0857 } 0858 0859 // Keep scheme, hostname etc. as this is needed for e. g. browsing 0860 // FTP directories 0861 QUrl url = q->locationUrl(); 0862 QString path = url.path(); 0863 0864 if (!path.isEmpty()) { 0865 if (index == 0) { 0866 // prevent the last "/" from being stripped 0867 // or we end up with an empty path 0868 #ifdef Q_OS_WIN 0869 path = path.length() > 1 ? path.left(2) : QDir::rootPath(); 0870 #else 0871 path = QStringLiteral("/"); 0872 #endif 0873 } else { 0874 path = path.section(QLatin1Char('/'), 0, index); 0875 } 0876 } 0877 0878 url.setPath(path); 0879 return url; 0880 } 0881 0882 void KUrlNavigatorPrivate::switchToBreadcrumbMode() 0883 { 0884 q->setUrlEditable(false); 0885 } 0886 0887 void KUrlNavigatorPrivate::deleteButtons() 0888 { 0889 for (KUrlNavigatorButton *button : std::as_const(m_navButtons)) { 0890 button->hide(); 0891 button->deleteLater(); 0892 } 0893 m_navButtons.clear(); 0894 } 0895 0896 QUrl KUrlNavigatorPrivate::retrievePlaceUrl() const 0897 { 0898 QUrl currentUrl = q->locationUrl(); 0899 currentUrl.setPath(QString()); 0900 return currentUrl; 0901 } 0902 0903 // ------------------------------------------------------------------------------------------------ 0904 0905 KUrlNavigator::KUrlNavigator(QWidget *parent) 0906 : KUrlNavigator(nullptr, QUrl{}, parent) 0907 { 0908 } 0909 0910 KUrlNavigator::KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent) 0911 : QWidget(parent) 0912 , d(new KUrlNavigatorPrivate(url, this, placesModel)) 0913 { 0914 const int minHeight = d->m_pathBox->sizeHint().height(); 0915 setMinimumHeight(minHeight); 0916 0917 setMinimumWidth(100); 0918 0919 d->updateContent(); 0920 } 0921 0922 KUrlNavigator::~KUrlNavigator() 0923 { 0924 d->m_dropDownButton->removeEventFilter(this); 0925 d->m_pathBox->removeEventFilter(this); 0926 for (auto *button : std::as_const(d->m_navButtons)) { 0927 button->removeEventFilter(this); 0928 } 0929 } 0930 0931 QUrl KUrlNavigator::locationUrl(int historyIndex) const 0932 { 0933 return d->m_coreUrlNavigator->locationUrl(historyIndex); 0934 } 0935 0936 void KUrlNavigator::saveLocationState(const QByteArray &state) 0937 { 0938 auto current = d->m_coreUrlNavigator->locationState().value<KUrlNavigatorData>(); 0939 current.state = state; 0940 d->m_coreUrlNavigator->saveLocationState(QVariant::fromValue(current)); 0941 } 0942 0943 QByteArray KUrlNavigator::locationState(int historyIndex) const 0944 { 0945 return d->m_coreUrlNavigator->locationState(historyIndex).value<KUrlNavigatorData>().state; 0946 } 0947 0948 bool KUrlNavigator::goBack() 0949 { 0950 return d->m_coreUrlNavigator->goBack(); 0951 } 0952 0953 bool KUrlNavigator::goForward() 0954 { 0955 return d->m_coreUrlNavigator->goForward(); 0956 } 0957 0958 bool KUrlNavigator::goUp() 0959 { 0960 return d->m_coreUrlNavigator->goUp(); 0961 } 0962 0963 void KUrlNavigator::goHome() 0964 { 0965 if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) { 0966 setLocationUrl(QUrl::fromLocalFile(QDir::homePath())); 0967 } else { 0968 setLocationUrl(d->m_homeUrl); 0969 } 0970 } 0971 0972 void KUrlNavigator::setHomeUrl(const QUrl &url) 0973 { 0974 d->m_homeUrl = url; 0975 } 0976 0977 QUrl KUrlNavigator::homeUrl() const 0978 { 0979 return d->m_homeUrl; 0980 } 0981 0982 void KUrlNavigator::setUrlEditable(bool editable) 0983 { 0984 if (d->m_editable != editable) { 0985 d->switchView(); 0986 } 0987 } 0988 0989 bool KUrlNavigator::isUrlEditable() const 0990 { 0991 return d->m_editable; 0992 } 0993 0994 void KUrlNavigator::setShowFullPath(bool show) 0995 { 0996 if (d->m_showFullPath != show) { 0997 d->m_showFullPath = show; 0998 d->updateContent(); 0999 } 1000 } 1001 1002 bool KUrlNavigator::showFullPath() const 1003 { 1004 return d->m_showFullPath; 1005 } 1006 1007 void KUrlNavigator::setActive(bool active) 1008 { 1009 if (active != d->m_active) { 1010 d->m_active = active; 1011 1012 d->m_dropDownButton->setActive(active); 1013 for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) { 1014 button->setActive(active); 1015 } 1016 1017 update(); 1018 if (active) { 1019 Q_EMIT activated(); 1020 } 1021 } 1022 } 1023 1024 bool KUrlNavigator::isActive() const 1025 { 1026 return d->m_active; 1027 } 1028 1029 void KUrlNavigator::setPlacesSelectorVisible(bool visible) 1030 { 1031 if (visible == d->m_showPlacesSelector) { 1032 return; 1033 } 1034 1035 if (visible && (d->m_placesSelector == nullptr)) { 1036 // the places selector cannot get visible as no 1037 // places model is available 1038 return; 1039 } 1040 1041 d->m_showPlacesSelector = visible; 1042 d->m_placesSelector->setVisible(visible); 1043 } 1044 1045 bool KUrlNavigator::isPlacesSelectorVisible() const 1046 { 1047 return d->m_showPlacesSelector; 1048 } 1049 1050 QUrl KUrlNavigator::uncommittedUrl() const 1051 { 1052 KUriFilterData filteredData(d->m_pathBox->currentText().trimmed()); 1053 filteredData.setCheckForExecutables(false); 1054 if (KUriFilter::self()->filterUri(filteredData, QStringList{QStringLiteral("kshorturifilter")})) { 1055 return filteredData.uri(); 1056 } else { 1057 return QUrl::fromUserInput(filteredData.typedString()); 1058 } 1059 } 1060 1061 void KUrlNavigator::setLocationUrl(const QUrl &newUrl) 1062 { 1063 d->m_coreUrlNavigator->setCurrentLocationUrl(newUrl); 1064 1065 d->updateContent(); 1066 1067 requestActivation(); 1068 } 1069 1070 void KUrlNavigator::requestActivation() 1071 { 1072 setActive(true); 1073 } 1074 1075 void KUrlNavigator::setFocus() 1076 { 1077 if (isUrlEditable()) { 1078 d->m_pathBox->setFocus(); 1079 } else { 1080 QWidget::setFocus(); 1081 } 1082 } 1083 1084 void KUrlNavigator::keyPressEvent(QKeyEvent *event) 1085 { 1086 if (isUrlEditable() && (event->key() == Qt::Key_Escape)) { 1087 setUrlEditable(false); 1088 } else { 1089 QWidget::keyPressEvent(event); 1090 } 1091 } 1092 1093 void KUrlNavigator::keyReleaseEvent(QKeyEvent *event) 1094 { 1095 QWidget::keyReleaseEvent(event); 1096 } 1097 1098 void KUrlNavigator::mousePressEvent(QMouseEvent *event) 1099 { 1100 if (event->button() == Qt::MiddleButton) { 1101 requestActivation(); 1102 } 1103 QWidget::mousePressEvent(event); 1104 } 1105 1106 void KUrlNavigator::mouseReleaseEvent(QMouseEvent *event) 1107 { 1108 if (event->button() == Qt::MiddleButton) { 1109 const QRect bounds = d->m_toggleEditableMode->geometry(); 1110 if (bounds.contains(event->pos())) { 1111 // The middle mouse button has been clicked above the 1112 // toggle-editable-mode-button. Paste the clipboard content 1113 // as location URL. 1114 QClipboard *clipboard = QApplication::clipboard(); 1115 const QMimeData *mimeData = clipboard->mimeData(); 1116 if (mimeData->hasText()) { 1117 const QString text = mimeData->text(); 1118 setLocationUrl(QUrl::fromUserInput(text)); 1119 } 1120 } 1121 } 1122 QWidget::mouseReleaseEvent(event); 1123 } 1124 1125 void KUrlNavigator::resizeEvent(QResizeEvent *event) 1126 { 1127 QTimer::singleShot(0, this, [this]() { 1128 d->updateButtonVisibility(); 1129 }); 1130 QWidget::resizeEvent(event); 1131 } 1132 1133 void KUrlNavigator::wheelEvent(QWheelEvent *event) 1134 { 1135 setActive(true); 1136 QWidget::wheelEvent(event); 1137 } 1138 1139 bool KUrlNavigator::eventFilter(QObject *watched, QEvent *event) 1140 { 1141 switch (event->type()) { 1142 case QEvent::FocusIn: 1143 if (watched == d->m_pathBox) { 1144 requestActivation(); 1145 setFocus(); 1146 } 1147 for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) { 1148 button->setShowMnemonic(true); 1149 } 1150 break; 1151 1152 case QEvent::FocusOut: 1153 for (KUrlNavigatorButton *button : std::as_const(d->m_navButtons)) { 1154 button->setShowMnemonic(false); 1155 } 1156 break; 1157 1158 default: 1159 break; 1160 } 1161 1162 return QWidget::eventFilter(watched, event); 1163 } 1164 1165 int KUrlNavigator::historySize() const 1166 { 1167 return d->m_coreUrlNavigator->historySize(); 1168 } 1169 1170 int KUrlNavigator::historyIndex() const 1171 { 1172 return d->m_coreUrlNavigator->historyIndex(); 1173 } 1174 1175 KUrlComboBox *KUrlNavigator::editor() const 1176 { 1177 return d->m_pathBox; 1178 } 1179 1180 void KUrlNavigator::setSupportedSchemes(const QStringList &schemes) 1181 { 1182 d->m_supportedSchemes = schemes; 1183 d->m_schemes->setSupportedSchemes(d->m_supportedSchemes); 1184 } 1185 1186 QStringList KUrlNavigator::supportedSchemes() const 1187 { 1188 return d->m_supportedSchemes; 1189 } 1190 1191 QWidget *KUrlNavigator::dropWidget() const 1192 { 1193 return d->m_dropWidget; 1194 } 1195 1196 void KUrlNavigator::setShowHiddenFolders(bool showHiddenFolders) 1197 { 1198 d->m_subfolderOptions.showHidden = showHiddenFolders; 1199 } 1200 1201 bool KUrlNavigator::showHiddenFolders() const 1202 { 1203 return d->m_subfolderOptions.showHidden; 1204 } 1205 1206 void KUrlNavigator::setSortHiddenFoldersLast(bool sortHiddenFoldersLast) 1207 { 1208 d->m_subfolderOptions.sortHiddenLast = sortHiddenFoldersLast; 1209 } 1210 1211 bool KUrlNavigator::sortHiddenFoldersLast() const 1212 { 1213 return d->m_subfolderOptions.sortHiddenLast; 1214 } 1215 1216 #include "moc_kurlnavigator.cpp"