diff --git a/src/filewidgets/kurlnavigator.cpp b/src/filewidgets/kurlnavigator.cpp index 24e9ef74..26ba586f 100644 --- a/src/filewidgets/kurlnavigator.cpp +++ b/src/filewidgets/kurlnavigator.cpp @@ -1,1318 +1,1330 @@ /***************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * Copyright (C) 2007 by Kevin Ottens * * Copyright (C) 2007 by Urs Wolfer * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #include "kurlnavigator.h" #include "kurlnavigatorplacesselector_p.h" #include "kurlnavigatorprotocolcombo_p.h" #include "kurlnavigatordropdownbutton_p.h" #include "kurlnavigatorbutton_p.h" #include "kurlnavigatortogglebutton_p.h" #include "kurlnavigatorpathselectoreventfilter_p.h" #include "urlutil_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDEPrivate; struct LocationData { QUrl url; #ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl rootUrl; // KDE5: remove after the deprecated methods have been removed QPoint pos; // KDE5: remove after the deprecated methods have been removed #endif QByteArray state; }; class Q_DECL_HIDDEN KUrlNavigator::Private { public: Private(KUrlNavigator *q, KFilePlacesModel *placesModel); void initialize(const QUrl &url); /** Applies the edited URL in m_pathBox to the URL navigator */ void applyUncommittedUrl(); void slotReturnPressed(); void slotProtocolChanged(const QString &); void openPathSelectorMenu(); /** * Appends the widget at the end of the URL navigator. It is assured * that the filler widget remains as last widget to fill the remaining * width. */ void appendWidget(QWidget *widget, int stretch = 0); /** * This slot is connected to the clicked signal of the navigation bar button. It calls switchView(). * Moreover, if switching from "editable" mode to the breadcrumb view, it calls applyUncommittedUrl(). */ void slotToggleEditableButtonPressed(); /** * Switches the navigation bar between the breadcrumb view and the * traditional view (see setUrlEditable()). */ void switchView(); /** Emits the signal urlsDropped(). */ void dropUrls(const QUrl &destination, QDropEvent *event); /** * Is invoked when a navigator button has been clicked. Changes the URL * of the navigator if the left mouse button has been used. If the middle * mouse button has been used, the signal tabRequested() will be emitted. */ - void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button); + void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); - void openContextMenu(); + void openContextMenu(const QPoint &p); void slotPathBoxChanged(const QString &text); void updateContent(); /** * Updates all buttons to have one button for each part of the * current URL. Existing buttons, which are available by m_navButtons, * are reused if possible. If the URL is longer, new buttons will be * created, if the URL is shorter, the remaining buttons will be deleted. * @param startIndex Start index of URL part (/), where the buttons * should be created for each following part. */ void updateButtons(int startIndex); /** * Updates the visibility state of all buttons describing the URL. If the * width of the URL navigator is too small, the buttons representing the upper * paths of the URL will be hidden and moved to a drop down menu. */ void updateButtonVisibility(); /** * @return Text for the first button of the URL navigator. */ QString firstButtonText() const; /** * Returns the URL that should be applied for the button with the index \a index. */ QUrl buttonUrl(int index) const; void switchToBreadcrumbMode(); /** * Deletes all URL navigator buttons. m_navButtons is * empty after this operation. */ void deleteButtons(); /** * Retrieves the place url for the current url. * E. g. for the path "fish://root@192.168.0.2/var/lib" the string * "fish://root@192.168.0.2" will be returned, which leads to the * navigation indication 'Custom Path > var > lib". For e. g. * "settings:///System/" the path "settings://" will be returned. */ QUrl retrievePlaceUrl() const; /** * Returns true, if the MIME type of the path represents a * compressed file like TAR or ZIP. */ bool isCompressedPath(const QUrl &path) const; void removeTrailingSlash(QString &url) const; /** * Returns the current history index, if \a historyIndex is * smaller than 0. If \a historyIndex is greater or equal than * the number of available history items, the largest possible * history index is returned. For the other cases just \a historyIndex * is returned. */ int adjustedHistoryIndex(int historyIndex) const; bool m_editable : 1; bool m_active : 1; bool m_showPlacesSelector : 1; bool m_showFullPath : 1; int m_historyIndex; QHBoxLayout *m_layout; QList m_history; KUrlNavigatorPlacesSelector *m_placesSelector; KUrlComboBox *m_pathBox; KUrlNavigatorProtocolCombo *m_protocols; KUrlNavigatorDropDownButton *m_dropDownButton; QList m_navButtons; KUrlNavigatorButtonBase *m_toggleEditableMode; QUrl m_homeUrl; QStringList m_customProtocols; QWidget *m_dropWidget; KUrlNavigator * const q; }; KUrlNavigator::Private::Private(KUrlNavigator *q, KFilePlacesModel *placesModel) : m_editable(false), m_active(true), m_showPlacesSelector(placesModel != nullptr), m_showFullPath(false), m_historyIndex(0), m_layout(new QHBoxLayout), m_placesSelector(nullptr), m_pathBox(nullptr), m_protocols(nullptr), m_dropDownButton(nullptr), m_navButtons(), m_toggleEditableMode(nullptr), m_homeUrl(), m_customProtocols(QStringList()), m_dropWidget(nullptr), q(q) { m_layout->setSpacing(0); m_layout->setMargin(0); // initialize the places selector q->setAutoFillBackground(false); if (placesModel != nullptr) { m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel); connect(m_placesSelector, &KUrlNavigatorPlacesSelector::placeActivated, q, &KUrlNavigator::setLocationUrl); connect(m_placesSelector, &KUrlNavigatorPlacesSelector::tabRequested, q, &KUrlNavigator::tabRequested); connect(placesModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(updateContent())); connect(placesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(updateContent())); connect(placesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), q, SLOT(updateContent())); } // create protocol combo m_protocols = new KUrlNavigatorProtocolCombo(QString(), q); connect(m_protocols, SIGNAL(activated(QString)), q, SLOT(slotProtocolChanged(QString))); // create drop down button for accessing all paths of the URL m_dropDownButton = new KUrlNavigatorDropDownButton(q); m_dropDownButton->setForegroundRole(QPalette::WindowText); m_dropDownButton->installEventFilter(q); connect(m_dropDownButton, SIGNAL(clicked()), q, SLOT(openPathSelectorMenu())); // initialize the path box of the traditional view m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q); m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); m_pathBox->installEventFilter(q); KUrlCompletion *kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion); m_pathBox->setCompletionObject(kurlCompletion); m_pathBox->setAutoDeleteCompletionObject(true); connect(m_pathBox, SIGNAL(returnPressed()), q, SLOT(slotReturnPressed())); connect(m_pathBox, &KUrlComboBox::urlActivated, q, &KUrlNavigator::setLocationUrl); connect(m_pathBox, SIGNAL(editTextChanged(QString)), q, SLOT(slotPathBoxChanged(QString))); // create toggle button which allows to switch between // the breadcrumb and traditional view m_toggleEditableMode = new KUrlNavigatorToggleButton(q); m_toggleEditableMode->installEventFilter(q); m_toggleEditableMode->setMinimumWidth(20); connect(m_toggleEditableMode, SIGNAL(clicked()), q, SLOT(slotToggleEditableButtonPressed())); if (m_placesSelector != nullptr) { m_layout->addWidget(m_placesSelector); } m_layout->addWidget(m_protocols); m_layout->addWidget(m_dropDownButton); m_layout->addWidget(m_pathBox, 1); m_layout->addWidget(m_toggleEditableMode); q->setContextMenuPolicy(Qt::CustomContextMenu); connect(q, SIGNAL(customContextMenuRequested(QPoint)), - q, SLOT(openContextMenu())); + q, SLOT(openContextMenu(QPoint))); } void KUrlNavigator::Private::initialize(const QUrl &url) { LocationData data; data.url = url.adjusted(QUrl::NormalizePathSegments); m_history.prepend(data); q->setLayoutDirection(Qt::LeftToRight); const int minHeight = m_pathBox->sizeHint().height(); q->setMinimumHeight(minHeight); q->setLayout(m_layout); q->setMinimumWidth(100); updateContent(); } void KUrlNavigator::Private::appendWidget(QWidget *widget, int stretch) { m_layout->insertWidget(m_layout->count() - 1, widget, stretch); } void KUrlNavigator::Private::applyUncommittedUrl() { // Parts of the following code have been taken // from the class KateFileSelector located in // kate/app/katefileselector.hpp of Kate. // Copyright (C) 2001 Christoph Cullmann // Copyright (C) 2001 Joseph Wenninger // Copyright (C) 2001 Anders Lund const QUrl typedUrl = q->uncommittedUrl(); QStringList urls = m_pathBox->urls(); urls.removeAll(typedUrl.toString()); urls.prepend(typedUrl.toString()); m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom); q->setLocationUrl(typedUrl); // The URL might have been adjusted by KUrlNavigator::setUrl(), hence // synchronize the result in the path box. const QUrl currentUrl = q->locationUrl(); m_pathBox->setUrl(currentUrl); } void KUrlNavigator::Private::slotReturnPressed() { applyUncommittedUrl(); emit q->returnPressed(); if (QApplication::keyboardModifiers() & Qt::ControlModifier) { // Pressing Ctrl+Return automatically switches back to the breadcrumb mode. // The switch must be done asynchronously, as we are in the context of the // editor. QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection); } } void KUrlNavigator::Private::slotProtocolChanged(const QString &protocol) { Q_ASSERT(m_editable); QUrl url; url.setScheme(protocol); if (protocol == QLatin1String("file")) { url.setPath(QStringLiteral("/")); } else { // With no authority set we'll get e.g. "ftp:" instead of "ftp://". // We want the latter, so let's set an empty authority. url.setAuthority(QString()); } m_pathBox->setEditUrl(url); } void KUrlNavigator::Private::openPathSelectorMenu() { if (m_navButtons.count() <= 0) { return; } const QUrl firstVisibleUrl = m_navButtons.first()->url(); QString spacer; QPointer popup = new QMenu(q); auto *popupFilter = new KUrlNavigatorPathSelectorEventFilter(popup.data()); connect(popupFilter, &KUrlNavigatorPathSelectorEventFilter::tabRequested, q, &KUrlNavigator::tabRequested); popup->installEventFilter(popupFilter); popup->setLayoutDirection(Qt::LeftToRight); const QUrl placeUrl = retrievePlaceUrl(); int idx = placeUrl.path().count(QLatin1Char('/')); // idx points to the first directory // after the place path const QString path = m_history[m_historyIndex].url.path(); QString dirName = path.section(QLatin1Char('/'), idx, idx); if (dirName.isEmpty()) { if (placeUrl.isLocalFile()) { dirName = QStringLiteral("/"); } else { dirName = placeUrl.toDisplayString(); } } do { const QString text = spacer + dirName; QAction *action = new QAction(text, popup); const QUrl currentUrl = buttonUrl(idx); if (currentUrl == firstVisibleUrl) { popup->addSeparator(); } action->setData(QVariant(currentUrl.toString())); popup->addAction(action); ++idx; spacer.append(QLatin1String(" ")); dirName = path.section(QLatin1Char('/'), idx, idx); } while (!dirName.isEmpty()); const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight()); const QAction *activatedAction = popup->exec(pos); if (activatedAction != nullptr) { const QUrl url(activatedAction->data().toString()); q->setLocationUrl(url); } // Delete the menu, unless it has been deleted in its own nested event loop already. if (popup) { popup->deleteLater(); } } void KUrlNavigator::Private::slotToggleEditableButtonPressed() { if (m_editable) { applyUncommittedUrl(); } switchView(); } void KUrlNavigator::Private::switchView() { m_toggleEditableMode->setFocus(); m_editable = !m_editable; m_toggleEditableMode->setChecked(m_editable); updateContent(); if (q->isUrlEditable()) { m_pathBox->setFocus(); } q->requestActivation(); emit q->editableStateChanged(m_editable); } void KUrlNavigator::Private::dropUrls(const QUrl &destination, QDropEvent *event) { if (event->mimeData()->hasUrls()) { m_dropWidget = qobject_cast(q->sender()); emit q->urlsDropped(destination, event); } } -void KUrlNavigator::Private::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button) +void KUrlNavigator::Private::slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) { - if (button & Qt::LeftButton) { - q->setLocationUrl(url); - } else if (button & Qt::MidButton) { + if (button & Qt::MidButton || (button & Qt::LeftButton && modifiers & Qt::ControlModifier)) { emit q->tabRequested(url); + } else if (button & Qt::LeftButton) { + q->setLocationUrl(url); } } -void KUrlNavigator::Private::openContextMenu() +void KUrlNavigator::Private::openContextMenu(const QPoint &p) { q->setActive(true); QPointer popup = new QMenu(q); // provide 'Copy' action, which copies the current URL of // the URL navigator into the clipboard QAction *copyAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy")); // provide 'Paste' action, which copies the current clipboard text // into the URL navigator QAction *pasteAction = popup->addAction(QIcon::fromTheme(QStringLiteral("edit-paste")), i18n("Paste")); QClipboard *clipboard = QApplication::clipboard(); pasteAction->setEnabled(!clipboard->text().isEmpty()); popup->addSeparator(); + //We are checking for receivers because it's odd to have a tab entry even if it's not supported, like in the case of the open dialog + if (q->receivers(SIGNAL(tabRequested(QUrl))) > 0) { + for (auto button : qAsConst(m_navButtons)) { + if (button->geometry().contains(p)) { + const auto url = button->url(); + QAction* openInTab = popup->addAction(QIcon::fromTheme(QStringLiteral("tab-new")), i18n("Open %1 in tab", button->text())); + q->connect(openInTab, &QAction::triggered, q, [this, url](){ Q_EMIT q->tabRequested(url); }); + break; + } + } + } + // provide radiobuttons for toggling between the edit and the navigation mode QAction *editAction = popup->addAction(i18n("Edit")); editAction->setCheckable(true); QAction *navigateAction = popup->addAction(i18n("Navigate")); navigateAction->setCheckable(true); QActionGroup *modeGroup = new QActionGroup(popup); modeGroup->addAction(editAction); modeGroup->addAction(navigateAction); if (q->isUrlEditable()) { editAction->setChecked(true); } else { navigateAction->setChecked(true); } popup->addSeparator(); // allow showing of the full path QAction *showFullPathAction = popup->addAction(i18n("Show Full Path")); showFullPathAction->setCheckable(true); showFullPathAction->setChecked(q->showFullPath()); QAction *activatedAction = popup->exec(QCursor::pos()); if (activatedAction == copyAction) { QMimeData *mimeData = new QMimeData(); mimeData->setText(q->locationUrl().toDisplayString(QUrl::PreferLocalFile)); clipboard->setMimeData(mimeData); } else if (activatedAction == pasteAction) { q->setLocationUrl(QUrl::fromUserInput(clipboard->text())); } else if (activatedAction == editAction) { q->setUrlEditable(true); } else if (activatedAction == navigateAction) { q->setUrlEditable(false); } else if (activatedAction == showFullPathAction) { q->setShowFullPath(showFullPathAction->isChecked()); } // Delete the menu, unless it has been deleted in its own nested event loop already. if (popup) { popup->deleteLater(); } } void KUrlNavigator::Private::slotPathBoxChanged(const QString &text) { if (text.isEmpty()) { const QString protocol = q->locationUrl().scheme(); m_protocols->setProtocol(protocol); if (m_customProtocols.count() != 1) { m_protocols->show(); } } else { m_protocols->hide(); } } void KUrlNavigator::Private::updateContent() { const QUrl currentUrl = q->locationUrl(); if (m_placesSelector != nullptr) { m_placesSelector->updateSelection(currentUrl); } if (m_editable) { m_protocols->hide(); m_dropDownButton->hide(); deleteButtons(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); m_pathBox->show(); m_pathBox->setUrl(currentUrl); } else { m_pathBox->hide(); m_protocols->hide(); m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // Calculate the start index for the directories that should be shown as buttons // and create the buttons QUrl placeUrl; if ((m_placesSelector != nullptr) && !m_showFullPath) { placeUrl = m_placesSelector->selectedPlaceUrl(); } if (!placeUrl.isValid()) { placeUrl = retrievePlaceUrl(); } QString placePath = placeUrl.path(); removeTrailingSlash(placePath); const int startIndex = placePath.count(QLatin1Char('/')); updateButtons(startIndex); } } void KUrlNavigator::Private::updateButtons(int startIndex) { QUrl currentUrl = q->locationUrl(); if (!currentUrl.isValid()) { // QFileDialog::setDirectory not called yet return; } const QString path = currentUrl.path(); bool createButton = false; const int oldButtonCount = m_navButtons.count(); int idx = startIndex; bool hasNext = true; do { createButton = (idx - startIndex >= oldButtonCount); const bool isFirstButton = (idx == startIndex); const QString dirName = path.section(QLatin1Char('/'), idx, idx); hasNext = isFirstButton || !dirName.isEmpty(); if (hasNext) { KUrlNavigatorButton *button = nullptr; if (createButton) { button = new KUrlNavigatorButton(buttonUrl(idx), q); button->installEventFilter(q); button->setForegroundRole(QPalette::WindowText); connect(button, SIGNAL(urlsDropped(QUrl,QDropEvent*)), q, SLOT(dropUrls(QUrl,QDropEvent*))); - connect(button, SIGNAL(clicked(QUrl,Qt::MouseButton)), - q, SLOT(slotNavigatorButtonClicked(QUrl,Qt::MouseButton))); + connect(button, SIGNAL(clicked(QUrl,Qt::MouseButton,Qt::KeyboardModifiers)), + q, SLOT(slotNavigatorButtonClicked(QUrl,Qt::MouseButton,Qt::KeyboardModifiers))); connect(button, SIGNAL(finishedTextResolving()), q, SLOT(updateButtonVisibility())); appendWidget(button); } else { button = m_navButtons[idx - startIndex]; button->setUrl(buttonUrl(idx)); } if (isFirstButton) { button->setText(firstButtonText()); } button->setActive(q->isActive()); if (createButton) { if (!isFirstButton) { setTabOrder(m_navButtons.last(), button); } m_navButtons.append(button); } ++idx; button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx)); } } while (hasNext); // delete buttons which are not used anymore const int newButtonCount = idx - startIndex; if (newButtonCount < oldButtonCount) { const QList::iterator itBegin = m_navButtons.begin() + newButtonCount; const QList::iterator itEnd = m_navButtons.end(); QList::iterator it = itBegin; while (it != itEnd) { (*it)->hide(); (*it)->deleteLater(); ++it; } m_navButtons.erase(itBegin, itEnd); } setTabOrder(m_dropDownButton, m_navButtons.first()); setTabOrder(m_navButtons.last(), m_toggleEditableMode); updateButtonVisibility(); } void KUrlNavigator::Private::updateButtonVisibility() { if (m_editable) { return; } const int buttonsCount = m_navButtons.count(); if (buttonsCount == 0) { m_dropDownButton->hide(); return; } // Subtract all widgets from the available width, that must be shown anyway int availableWidth = q->width() - m_toggleEditableMode->minimumWidth(); if ((m_placesSelector != nullptr) && m_placesSelector->isVisible()) { availableWidth -= m_placesSelector->width(); } if ((m_protocols != nullptr) && m_protocols->isVisible()) { availableWidth -= m_protocols->width(); } // Check whether buttons must be hidden at all... int requiredButtonWidth = 0; foreach (const KUrlNavigatorButton *button, m_navButtons) { requiredButtonWidth += button->minimumWidth(); } if (requiredButtonWidth > availableWidth) { // At least one button must be hidden. This implies that the // drop-down button must get visible, which again decreases the // available width. availableWidth -= m_dropDownButton->width(); } // Hide buttons... QList::const_iterator it = m_navButtons.constEnd(); const QList::const_iterator itBegin = m_navButtons.constBegin(); bool isLastButton = true; bool hasHiddenButtons = false; QLinkedList buttonsToShow; while (it != itBegin) { --it; KUrlNavigatorButton *button = (*it); availableWidth -= button->minimumWidth(); if ((availableWidth <= 0) && !isLastButton) { button->hide(); hasHiddenButtons = true; } else { // Don't show the button immediately, as setActive() // might change the size and a relayout gets triggered // after showing the button. So the showing of all buttons // is postponed until all buttons have the correct // activation state. buttonsToShow.append(button); } isLastButton = false; } // All buttons have the correct activation state and // can be shown now foreach (KUrlNavigatorButton *button, buttonsToShow) { button->show(); } if (hasHiddenButtons) { m_dropDownButton->show(); } else { // Check whether going upwards is possible. If this is the case, show the drop-down button. QUrl url(m_navButtons.front()->url()); const bool visible = !url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash) && (url.scheme() != QLatin1String("nepomuksearch")); m_dropDownButton->setVisible(visible); } } QString KUrlNavigator::Private::firstButtonText() const { QString text; // The first URL navigator button should get the name of the // place instead of the directory name if ((m_placesSelector != nullptr) && !m_showFullPath) { text = m_placesSelector->selectedPlaceText(); } if (text.isEmpty()) { const QUrl currentUrl = q->locationUrl(); if (currentUrl.isLocalFile()) { #ifdef Q_OS_WIN text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath(); #else text = m_showFullPath ? QStringLiteral("/") : i18n("Custom Path"); #endif } else { text = currentUrl.scheme() + QLatin1Char(':'); if (!currentUrl.host().isEmpty()) { text += QLatin1Char(' ') + currentUrl.host(); } } } return text; } QUrl KUrlNavigator::Private::buttonUrl(int index) const { if (index < 0) { index = 0; } // Keep scheme, hostname etc. as this is needed for e. g. browsing // FTP directories QUrl url = q->locationUrl(); QString path = url.path(); if (!path.isEmpty()) { if (index == 0) { // prevent the last "/" from being stripped // or we end up with an empty path #ifdef Q_OS_WIN path = path.length() > 1 ? path.left(2) : QDir::rootPath(); #else path = QStringLiteral("/"); #endif } else { path = path.section(QLatin1Char('/'), 0, index); } } url.setPath(path); return url; } void KUrlNavigator::Private::switchToBreadcrumbMode() { q->setUrlEditable(false); } void KUrlNavigator::Private::deleteButtons() { foreach (KUrlNavigatorButton *button, m_navButtons) { button->hide(); button->deleteLater(); } m_navButtons.clear(); } QUrl KUrlNavigator::Private::retrievePlaceUrl() const { QUrl currentUrl = q->locationUrl(); currentUrl.setPath(QString()); return currentUrl; } bool KUrlNavigator::Private::isCompressedPath(const QUrl &url) const { QMimeDatabase db; const QMimeType mime = db.mimeTypeForUrl(QUrl(url.toString(QUrl::StripTrailingSlash))); // Note: this list of MIME types depends on the protocols implemented by kio_archive return mime.inherits(QStringLiteral("application/x-compressed-tar")) || mime.inherits(QStringLiteral("application/x-bzip-compressed-tar")) || mime.inherits(QStringLiteral("application/x-lzma-compressed-tar")) || mime.inherits(QStringLiteral("application/x-xz-compressed-tar")) || mime.inherits(QStringLiteral("application/x-tar")) || mime.inherits(QStringLiteral("application/x-tarz")) || mime.inherits(QStringLiteral("application/x-tzo")) || // (not sure KTar supports those?) mime.inherits(QStringLiteral("application/zip")) || mime.inherits(QStringLiteral("application/x-archive")); } void KUrlNavigator::Private::removeTrailingSlash(QString &url) const { const int length = url.length(); if ((length > 0) && (url.at(length - 1) == QLatin1Char('/'))) { url.remove(length - 1, 1); } } int KUrlNavigator::Private::adjustedHistoryIndex(int historyIndex) const { if (historyIndex < 0) { historyIndex = m_historyIndex; } else if (historyIndex >= m_history.size()) { historyIndex = m_history.size() - 1; Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0 } return historyIndex; } // ------------------------------------------------------------------------------------------------ KUrlNavigator::KUrlNavigator(QWidget *parent) : QWidget(parent), d(new Private(this, nullptr)) { d->initialize(QUrl()); } KUrlNavigator::KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent) : QWidget(parent), d(new Private(this, placesModel)) { d->initialize(url); } KUrlNavigator::~KUrlNavigator() { delete d; } QUrl KUrlNavigator::locationUrl(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].url; } void KUrlNavigator::saveLocationState(const QByteArray &state) { d->m_history[d->m_historyIndex].state = state; } QByteArray KUrlNavigator::locationState(int historyIndex) const { historyIndex = d->adjustedHistoryIndex(historyIndex); return d->m_history[historyIndex].state; } bool KUrlNavigator::goBack() { const int count = d->m_history.count(); if (d->m_historyIndex < count - 1) { const QUrl newUrl = locationUrl(d->m_historyIndex + 1); emit urlAboutToBeChanged(newUrl); ++d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goForward() { if (d->m_historyIndex > 0) { const QUrl newUrl = locationUrl(d->m_historyIndex - 1); emit urlAboutToBeChanged(newUrl); --d->m_historyIndex; d->updateContent(); emit historyChanged(); emit urlChanged(locationUrl()); return true; } return false; } bool KUrlNavigator::goUp() { const QUrl currentUrl = locationUrl(); const QUrl upUrl = KIO::upUrl(currentUrl); if (upUrl != currentUrl) { // TODO use url.matches(KIO::upUrl(url), QUrl::StripTrailingSlash) setLocationUrl(upUrl); return true; } return false; } void KUrlNavigator::goHome() { if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) { setLocationUrl(QUrl::fromLocalFile(QDir::homePath())); } else { setLocationUrl(d->m_homeUrl); } } void KUrlNavigator::setHomeUrl(const QUrl &url) { d->m_homeUrl = url; } QUrl KUrlNavigator::homeUrl() const { return d->m_homeUrl; } void KUrlNavigator::setUrlEditable(bool editable) { if (d->m_editable != editable) { d->switchView(); } } bool KUrlNavigator::isUrlEditable() const { return d->m_editable; } void KUrlNavigator::setShowFullPath(bool show) { if (d->m_showFullPath != show) { d->m_showFullPath = show; d->updateContent(); } } bool KUrlNavigator::showFullPath() const { return d->m_showFullPath; } void KUrlNavigator::setActive(bool active) { if (active != d->m_active) { d->m_active = active; d->m_dropDownButton->setActive(active); foreach (KUrlNavigatorButton *button, d->m_navButtons) { button->setActive(active); } update(); if (active) { emit activated(); } } } bool KUrlNavigator::isActive() const { return d->m_active; } void KUrlNavigator::setPlacesSelectorVisible(bool visible) { if (visible == d->m_showPlacesSelector) { return; } if (visible && (d->m_placesSelector == nullptr)) { // the places selector cannot get visible as no // places model is available return; } d->m_showPlacesSelector = visible; d->m_placesSelector->setVisible(visible); } bool KUrlNavigator::isPlacesSelectorVisible() const { return d->m_showPlacesSelector; } QUrl KUrlNavigator::uncommittedUrl() const { KUriFilterData filteredData(d->m_pathBox->currentText().trimmed()); filteredData.setCheckForExecutables(false); if (KUriFilter::self()->filterUri(filteredData, QStringList{ QStringLiteral("kshorturifilter"), QStringLiteral("kurisearchfilter")})) { return filteredData.uri(); } else { return QUrl::fromUserInput(filteredData.typedString()); } } void KUrlNavigator::setLocationUrl(const QUrl &newUrl) { if (newUrl == locationUrl()) { return; } QUrl url = newUrl.adjusted(QUrl::NormalizePathSegments); // This will be used below; we define it here because in the lower part of the // code locationUrl() and url become the same URLs QUrl firstChildUrl = KIO::UrlUtil::firstChildUrl(locationUrl(), url); if ((url.scheme() == QLatin1String("tar")) || (url.scheme() == QLatin1String("zip"))) { // The URL represents a tar- or zip-file. Check whether // the URL is really part of the tar- or zip-file, otherwise // replace it by the local path again. bool insideCompressedPath = d->isCompressedPath(url); if (!insideCompressedPath) { QUrl prevUrl = url; QUrl parentUrl = KIO::upUrl(url); while (parentUrl != prevUrl) { if (d->isCompressedPath(parentUrl)) { insideCompressedPath = true; break; } prevUrl = parentUrl; parentUrl = KIO::upUrl(parentUrl); } } if (!insideCompressedPath) { // drop the tar: or zip: protocol since we are not // inside the compressed path url.setScheme(QStringLiteral("file")); firstChildUrl.setScheme(QStringLiteral("file")); } } // Check whether current history element has the same URL. // If this is the case, just ignore setting the URL. const LocationData &data = d->m_history[d->m_historyIndex]; const bool isUrlEqual = url.matches(locationUrl(), QUrl::StripTrailingSlash) || (!url.isValid() && url.matches(data.url, QUrl::StripTrailingSlash)); if (isUrlEqual) { return; } emit urlAboutToBeChanged(url); if (d->m_historyIndex > 0) { // If an URL is set when the history index is not at the end (= 0), // then clear all previous history elements so that a new history // tree is started from the current position. QList::iterator begin = d->m_history.begin(); QList::iterator end = begin + d->m_historyIndex; d->m_history.erase(begin, end); d->m_historyIndex = 0; } Q_ASSERT(d->m_historyIndex == 0); LocationData newData; newData.url = url; d->m_history.insert(0, newData); // Prevent an endless growing of the history: remembering // the last 100 Urls should be enough... const int historyMax = 100; if (d->m_history.size() > historyMax) { QList::iterator begin = d->m_history.begin() + historyMax; QList::iterator end = d->m_history.end(); d->m_history.erase(begin, end); } emit historyChanged(); emit urlChanged(url); if (firstChildUrl.isValid()) { emit urlSelectionRequested(firstChildUrl); } d->updateContent(); requestActivation(); } void KUrlNavigator::requestActivation() { setActive(true); } void KUrlNavigator::setFocus() { if (isUrlEditable()) { d->m_pathBox->setFocus(); } else { QWidget::setFocus(); } } #ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::setUrl(const QUrl &url) { // deprecated setLocationUrl(url); } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::saveRootUrl(const QUrl &url) { // deprecated d->m_history[d->m_historyIndex].rootUrl = url; } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::savePosition(int x, int y) { // deprecated d->m_history[d->m_historyIndex].pos = QPoint(x, y); } #endif void KUrlNavigator::keyPressEvent(QKeyEvent *event) { if (isUrlEditable() && (event->key() == Qt::Key_Escape)) { setUrlEditable(false); } else { QWidget::keyPressEvent(event); } } void KUrlNavigator::keyReleaseEvent(QKeyEvent *event) { QWidget::keyReleaseEvent(event); } void KUrlNavigator::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::MiddleButton) { requestActivation(); } QWidget::mousePressEvent(event); } void KUrlNavigator::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::MidButton) { const QRect bounds = d->m_toggleEditableMode->geometry(); if (bounds.contains(event->pos())) { // The middle mouse button has been clicked above the // toggle-editable-mode-button. Paste the clipboard content // as location URL. QClipboard *clipboard = QApplication::clipboard(); const QMimeData *mimeData = clipboard->mimeData(); if (mimeData->hasText()) { const QString text = mimeData->text(); setLocationUrl(QUrl::fromUserInput(text)); } } } QWidget::mouseReleaseEvent(event); } void KUrlNavigator::resizeEvent(QResizeEvent *event) { QTimer::singleShot(0, this, SLOT(updateButtonVisibility())); QWidget::resizeEvent(event); } void KUrlNavigator::wheelEvent(QWheelEvent *event) { setActive(true); QWidget::wheelEvent(event); } bool KUrlNavigator::eventFilter(QObject *watched, QEvent *event) { switch (event->type()) { case QEvent::FocusIn: if (watched == d->m_pathBox) { requestActivation(); setFocus(); } foreach (KUrlNavigatorButton *button, d->m_navButtons) { button->setShowMnemonic(true); } break; case QEvent::FocusOut: foreach (KUrlNavigatorButton *button, d->m_navButtons) { button->setShowMnemonic(false); } break; default: break; } return QWidget::eventFilter(watched, event); } int KUrlNavigator::historySize() const { return d->m_history.count(); } int KUrlNavigator::historyIndex() const { return d->m_historyIndex; } KUrlComboBox *KUrlNavigator::editor() const { return d->m_pathBox; } void KUrlNavigator::setCustomProtocols(const QStringList &protocols) { d->m_customProtocols = protocols; d->m_protocols->setCustomProtocols(d->m_customProtocols); } QStringList KUrlNavigator::customProtocols() const { return d->m_customProtocols; } QWidget *KUrlNavigator::dropWidget() const { return d->m_dropWidget; } #ifndef KIOFILEWIDGETS_NO_DEPRECATED const QUrl &KUrlNavigator::url() const { // deprecated // Workaround required because of flawed interface ('const QUrl&' is returned // instead of 'QUrl'): remember the URL to prevent a dangling pointer static QUrl url; url = locationUrl(); return url; } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl KUrlNavigator::url(int index) const { // deprecated return d->buttonUrl(index); } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED QUrl KUrlNavigator::historyUrl(int historyIndex) const { // deprecated return locationUrl(historyIndex); } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED const QUrl &KUrlNavigator::savedRootUrl() const { // deprecated // Workaround required because of flawed interface ('const QUrl&' is returned // instead of 'QUrl'): remember the root URL to prevent a dangling pointer static QUrl rootUrl; rootUrl = d->m_history[d->m_historyIndex].rootUrl; return rootUrl; } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED QPoint KUrlNavigator::savedPosition() const { // deprecated return d->m_history[d->m_historyIndex].pos; } #endif #ifndef KIOFILEWIDGETS_NO_DEPRECATED void KUrlNavigator::setHomeUrl(const QString &homeUrl) { // deprecated setLocationUrl(QUrl::fromUserInput(homeUrl)); } #endif #include "moc_kurlnavigator.cpp" diff --git a/src/filewidgets/kurlnavigator.h b/src/filewidgets/kurlnavigator.h index c7b24e82..14d294dd 100644 --- a/src/filewidgets/kurlnavigator.h +++ b/src/filewidgets/kurlnavigator.h @@ -1,499 +1,499 @@ /***************************************************************************** * Copyright (C) 2006-2010 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * Copyright (C) 2007 by Kevin Ottens * * Copyright (C) 2007 by Urs Wolfer * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #ifndef KURLNAVIGATOR_H #define KURLNAVIGATOR_H #include "kiofilewidgets_export.h" #include #include #include class KFilePlacesModel; class KUrlComboBox; class QMouseEvent; /** * @class KUrlNavigator kurlnavigator.h * * @brief Widget that allows to navigate through the paths of an URL. * * The URL navigator offers two modes: * - Editable: The URL of the location is editable inside an editor. * By pressing RETURN the URL will get activated. * - Non editable ("breadcrumb view"): The URL of the location is represented by * a number of buttons, where each button represents a path * of the URL. By clicking on a button the path will get * activated. This mode also supports drag and drop of items. * * The mode can be changed by clicking on the empty area of the URL navigator. * It is recommended that the application remembers the setting * or allows to configure the default mode (see KUrlNavigator::setUrlEditable()). * * The URL navigator remembers the URL history during navigation and allows to go * back and forward within this history. * * In the non editable mode ("breadcrumb view") it can be configured whether * the full path should be shown. It is recommended that the application * remembers the setting or allows to configure the default mode (see * KUrlNavigator::setShowFullPath()). * * The typical usage of the KUrlNavigator is: * - Create an instance providing a places model and an URL. * - Create an instance of QAbstractItemView which shows the content of the URL * given by the URL navigator. * - Connect to the signal KUrlNavigator::urlChanged() and synchronize the content of * QAbstractItemView with the URL given by the URL navigator. * * It is recommended, that the application remembers the state of the QAbstractItemView * when the URL has been changed. This allows to restore the view state when going back in history. * KUrlNavigator offers support for remembering the view state: * - The signal urlAboutToBeChanged() will be emitted before the URL change takes places. * This allows the application to store the view state by KUrlNavigator::saveLocationState(). * - The signal urlChanged() will be emitted after the URL change took place. This allows * the application to restore the view state by getting the values from * KUrlNavigator::locationState(). */ class KIOFILEWIDGETS_EXPORT KUrlNavigator : public QWidget { Q_OBJECT public: /** @since 4.5 */ KUrlNavigator(QWidget *parent = nullptr); /** * @param placesModel Model for the places which are selectable inside a * menu. A place can be a bookmark or a device. If it is 0, * no places selector is displayed. * @param url URL which is used for the navigation or editing. * @param parent Parent widget. */ KUrlNavigator(KFilePlacesModel *placesModel, const QUrl &url, QWidget *parent); virtual ~KUrlNavigator(); /** * @return URL of the location given by the \a historyIndex. If \a historyIndex * is smaller than 0, the URL of the current location is returned. * @since 4.5 */ QUrl locationUrl(int historyIndex = -1) const; /** * Saves the location state described by \a state for the current location. It is recommended * that at least the scroll position of a view is remembered and restored when traversing * through the history. Saving the location state should be done when the signal * KUrlNavigator::urlAboutToBeChanged() has been emitted. Restoring the location state (see * KUrlNavigator::locationState()) should be done when the signal KUrlNavigator::urlChanged() * has been emitted. * * Example: * \code * QByteArray state; * QDataStream data(&state, QIODevice::WriteOnly); * data << QPoint(x, y); * data << ...; * ... * urlNavigator->saveLocationState(state); * \endcode * * @since 4.5 */ void saveLocationState(const QByteArray &state); /** * @return Location state given by \a historyIndex. If \a historyIndex * is smaller than 0, the state of the current location is returned. * @see KUrlNavigator::saveLocationState() * @since 4.5 */ QByteArray locationState(int historyIndex = -1) const; /** * Goes back one step in the URL history. The signals * KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and * KUrlNavigator::historyChanged() are emitted if true is returned. False is returned * if the beginning of the history has already been reached and hence going back was * not possible. The history index (see KUrlNavigator::historyIndex()) is * increased by one if the operation was successful. */ bool goBack(); /** * Goes forward one step in the URL history. The signals * KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() and * KUrlNavigator::historyChanged() are emitted if true is returned. False is returned * if the end of the history has already been reached and hence going forward * was not possible. The history index (see KUrlNavigator::historyIndex()) is * decreased by one if the operation was successful. */ bool goForward(); /** * Goes up one step of the URL path and remembers the old path * in the history. The signals KUrlNavigator::urlAboutToBeChanged(), * KUrlNavigator::urlChanged() and KUrlNavigator::historyChanged() are * emitted if true is returned. False is returned if going up was not * possible as the root has been reached. */ bool goUp(); /** * Goes to the home URL and remembers the old URL in the history. * The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() * and KUrlNavigator::historyChanged() are emitted. * * @see KUrlNavigator::setHomeUrl() */ // KDE5: Remove the home-property. It is sufficient to invoke // KUrlNavigator::setLocationUrl(homeUrl) on application-side. void goHome(); /** * Sets the home URL used by KUrlNavigator::goHome(). If no * home URL is set, the default home path of the user is used. * @since 4.5 */ // KDE5: Remove the home-property. It is sufficient to invoke // KUrlNavigator::setLocationUrl(homeUrl) on application-side. void setHomeUrl(const QUrl &url); QUrl homeUrl() const; /** * Allows to edit the URL of the navigation bar if \a editable * is true, and sets the focus accordingly. * If \a editable is false, each part of * the URL is presented by a button for a fast navigation ("breadcrumb view"). */ void setUrlEditable(bool editable); /** * @return True, if the URL is editable within a line editor. * If false is returned, each part of the URL is presented by a button * for fast navigation ("breadcrumb view"). */ bool isUrlEditable() const; /** * Shows the full path of the URL even if a place represents a part of the URL. * Assuming that a place called "Pictures" uses the URL /home/user/Pictures. * An URL like /home/user/Pictures/2008 is shown as [Pictures] > [2008] * in the breadcrumb view, if showing the full path is turned off. If * showing the full path is turned on, the URL is shown * as [/] > [home] > [Pictures] > [2008]. * @since 4.2 */ void setShowFullPath(bool show); /** * @return True, if the full path of the URL should be shown in the breadcrumb view. * @since 4.2 */ bool showFullPath() const; /** * Set the URL navigator to the active mode, if \a active * is true. The active mode is default. The inactive mode only differs * visually from the active mode, no change of the behavior is given. * * Using the URL navigator in the inactive mode is useful when having split views, * where the inactive view is indicated by an inactive URL * navigator visually. */ void setActive(bool active); /** * @return True, if the URL navigator is in the active mode. * @see KUrlNavigator::setActive() */ bool isActive() const; /** * Sets the places selector visible, if \a visible is true. * The places selector allows to select the places provided * by the places model passed in the constructor. Per default * the places selector is visible. */ void setPlacesSelectorVisible(bool visible); /** @return True, if the places selector is visible. */ bool isPlacesSelectorVisible() const; /** * @return The currently entered, but not accepted URL. * It is possible that the returned URL is not valid. */ QUrl uncommittedUrl() const; /** * @return The amount of locations in the history. The data for each * location can be retrieved by KUrlNavigator::locationUrl() and * KUrlNavigator::locationState(). */ int historySize() const; /** * @return The history index of the current location, where * 0 <= history index < KUrlNavigator::historySize(). 0 is the most * recent history entry. */ int historyIndex() const; /** * @return The used editor when the navigator is in the edit mode * @see KUrlNavigator::setUrlEditable() */ KUrlComboBox *editor() const; /** * If an application supports only some special protocols, they can be set * with \a protocols . */ // TODO KF6 rename to setSupportedSchemes to match KDirOperator and KFileWidget void setCustomProtocols(const QStringList &protocols); /** * @return The custom protocols if they are set, QStringList() otherwise. */ QStringList customProtocols() const; /** * The child widget that received the QDropEvent when dropping on the URL * navigator. You can pass this widget to KJobWidgets::setWindow() * if you need to show a drop menu with KIO::drop(). * @return Child widget that has received the last drop event, or nullptr if * nothing has been dropped yet on the URL navigator. * @since 5.37 * @see KIO::drop() */ QWidget *dropWidget() const; #if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) /** * @return The current URL of the location. * @deprecated Use KUrlNavigator::locationUrl() instead. */ KIOFILEWIDGETS_DEPRECATED const QUrl &url() const; /** * @return The portion of the current URL up to the path part given * by \a index. Assuming that the current URL is /home/peter/Documents/Music, * then the following URLs are returned for an index: * - index <= 0: /home * - index is 1: /home/peter * - index is 2: /home/peter/Documents * - index >= 3: /home/peter/Documents/Music * @deprecated It should not be necessary for a client of KUrlNavigator to query this information. */ KIOFILEWIDGETS_DEPRECATED QUrl url(int index) const; /** * @return URL for the history element with the index \a historyIndex. * The history index 0 represents the most recent URL. * @since 4.3 * @deprecated Use KUrlNavigator::locationUrl(historyIndex) instead. */ KIOFILEWIDGETS_DEPRECATED QUrl historyUrl(int historyIndex) const; /** * @return The saved root URL for the current URL (see KUrlNavigator::saveRootUrl()). * @deprecated Use KUrlNavigator::locationState() instead. */ KIOFILEWIDGETS_DEPRECATED const QUrl &savedRootUrl() const; /** * @return The saved contents position of the upper left corner * for the current URL. * @deprecated Use KUrlNavigator::locationState() instead. */ KIOFILEWIDGETS_DEPRECATED QPoint savedPosition() const; /** @deprecated Use setHomeUrl(const QUrl& url) instead. */ KIOFILEWIDGETS_DEPRECATED void setHomeUrl(const QString &homeUrl); #endif public Q_SLOTS: /** * Sets the location to \a url. The old URL is added to the history. * The signals KUrlNavigator::urlAboutToBeChanged(), KUrlNavigator::urlChanged() * and KUrlNavigator::historyChanged() are emitted. Use * KUrlNavigator::locationUrl() to read the location. * @since 4.5 */ void setLocationUrl(const QUrl &url); /** * Activates the URL navigator (KUrlNavigator::isActive() will return true) * and emits the signal KUrlNavigator::activated(). * @see KUrlNavigator::setActive() */ void requestActivation(); #if !defined(DOXYGEN_SHOULD_SKIP_THIS) // KDE5: Remove and listen for focus-signal instead void setFocus(); #endif #if !defined(KIOFILEWIDGETS_NO_DEPRECATED) && !defined(DOXYGEN_SHOULD_SKIP_THIS) /** * Sets the location to \a url. * @deprecated Use KUrlNavigator::setLocationUrl(url). */ KIOFILEWIDGETS_DEPRECATED void setUrl(const QUrl &url); /** * Saves the used root URL of the content for the current history element. * @deprecated Use KUrlNavigator::saveLocationState() instead. */ KIOFILEWIDGETS_DEPRECATED void saveRootUrl(const QUrl &url); /** * Saves the coordinates of the contents for the current history element. * @deprecated Use KUrlNavigator::saveLocationState() instead. */ KIOFILEWIDGETS_DEPRECATED void savePosition(int x, int y); #endif Q_SIGNALS: /** * Is emitted, if the URL navigator has been activated by * an user interaction * @see KUrlNavigator::setActive() */ void activated(); /** * Is emitted, if the location URL has been changed e. g. by * the user. * @see KUrlNavigator::setUrl() */ void urlChanged(const QUrl &url); /** * Is emitted, before the location URL is going to be changed to \a newUrl. * The signal KUrlNavigator::urlChanged() will be emitted after the change * has been done. Connecting to this signal is useful to save the state * of a view with KUrlNavigator::saveLocationState(). * @since 4.5 */ void urlAboutToBeChanged(const QUrl &newUrl); /** * Is emitted, if the editable state for the URL has been changed * (see KUrlNavigator::setUrlEditable()). */ void editableStateChanged(bool editable); /** * Is emitted, if the history has been changed. Usually * the history is changed if a new URL has been selected. */ void historyChanged(); /** * Is emitted if a dropping has been done above the destination * \a destination. The receiver must accept the drop event if * the dropped data can be handled. * @since 4.2 */ void urlsDropped(const QUrl &destination, QDropEvent *event); /** * This signal is emitted when the Return or Enter key is pressed. */ void returnPressed(); /** * Is emitted if the URL \a url should be opened in a new tab because * the user clicked on a breadcrumb with the middle mouse button. * @since 4.5 */ void tabRequested(const QUrl &url); /** * When the URL is changed and the new URL (e.g. /home/user1/) * is a parent of the previous URL (e.g. /home/user1/data/stuff), * then this signal is emitted and \a url is set to the child * directory of the new URL which is an ancestor of the old URL * (in the example paths this would be /home/user1/data/). * This signal allows file managers to pre-select the directory * that the user is navigating up from. * @since 5.37.0 */ void urlSelectionRequested(const QUrl &url); protected: #if !defined(DOXYGEN_SHOULD_SKIP_THIS) /** * If the Escape key is pressed, the navigation bar should switch * to the breadcrumb view. * @see QWidget::keyPressEvent() */ void keyPressEvent(QKeyEvent *event) override; /** * Reimplemented for internal purposes. */ void keyReleaseEvent(QKeyEvent *event) override; /** * Paste the clipboard content as URL, if the middle mouse * button has been clicked. * @see QWidget::mouseReleaseEvent() */ void mouseReleaseEvent(QMouseEvent *event) override; /** * Reimplemented to activate on middle mousse button click */ void mousePressEvent(QMouseEvent *event) override; void resizeEvent(QResizeEvent *event) override; void wheelEvent(QWheelEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; #endif private: Q_PRIVATE_SLOT(d, void slotReturnPressed()) Q_PRIVATE_SLOT(d, void slotProtocolChanged(const QString &protocol)) Q_PRIVATE_SLOT(d, void slotToggleEditableButtonPressed()) Q_PRIVATE_SLOT(d, void dropUrls(const QUrl &destination, QDropEvent *)) - Q_PRIVATE_SLOT(d, void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button)) - Q_PRIVATE_SLOT(d, void openContextMenu()) + Q_PRIVATE_SLOT(d, void slotNavigatorButtonClicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers)) + Q_PRIVATE_SLOT(d, void openContextMenu(QPoint)) Q_PRIVATE_SLOT(d, void openPathSelectorMenu()) Q_PRIVATE_SLOT(d, void updateButtonVisibility()) Q_PRIVATE_SLOT(d, void switchToBreadcrumbMode()) Q_PRIVATE_SLOT(d, void slotPathBoxChanged(const QString &text)) Q_PRIVATE_SLOT(d, void updateContent()) private: class Private; Private *const d; Q_DISABLE_COPY(KUrlNavigator) }; #endif diff --git a/src/filewidgets/kurlnavigatorbutton.cpp b/src/filewidgets/kurlnavigatorbutton.cpp index 8c7a1d78..beb7bbfe 100644 --- a/src/filewidgets/kurlnavigatorbutton.cpp +++ b/src/filewidgets/kurlnavigatorbutton.cpp @@ -1,702 +1,702 @@ /***************************************************************************** * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #include "kurlnavigatorbutton_p.h" #include "kurlnavigator.h" #include "kurlnavigatormenu_p.h" #include "kdirsortfilterproxymodel.h" #include "../pathhelpers_p.h" #include #include #include #include #include #include #include #include #include namespace KDEPrivate { QPointer KUrlNavigatorButton::m_subDirsMenu; KUrlNavigatorButton::KUrlNavigatorButton(const QUrl &url, QWidget *parent) : KUrlNavigatorButtonBase(parent), m_hoverArrow(false), m_pendingTextChange(false), m_replaceButton(false), m_showMnemonic(false), m_wheelSteps(0), m_url(url), m_subDir(), m_openSubDirsTimer(nullptr), m_subDirsJob(nullptr) { setAcceptDrops(true); setUrl(url); setMouseTracking(true); m_openSubDirsTimer = new QTimer(this); m_openSubDirsTimer->setSingleShot(true); m_openSubDirsTimer->setInterval(300); connect(m_openSubDirsTimer, &QTimer::timeout, this, &KUrlNavigatorButton::startSubDirsJob); connect(this, &QAbstractButton::pressed, this, &KUrlNavigatorButton::requestSubDirs); } KUrlNavigatorButton::~KUrlNavigatorButton() { } void KUrlNavigatorButton::setUrl(const QUrl &url) { m_url = url; // Doing a text-resolving with KIO::stat() for all non-local // URLs leads to problems for protocols where a limit is given for // the number of parallel connections. A black-list // is given where KIO::stat() should not be used: static const QSet protocolBlacklist = QSet{ QStringLiteral("nfs"), QStringLiteral("fish"), QStringLiteral("ftp"), QStringLiteral("sftp"), QStringLiteral("smb"), QStringLiteral("webdav"), QStringLiteral("mtp"), }; const bool startTextResolving = m_url.isValid() && !m_url.isLocalFile() && !protocolBlacklist.contains(m_url.scheme()); if (startTextResolving) { m_pendingTextChange = true; KIO::StatJob *job = KIO::stat(m_url, KIO::HideProgressInfo); connect(job, &KJob::result, this, &KUrlNavigatorButton::statFinished); emit startedTextResolving(); } else { setText(m_url.fileName().replace(QLatin1Char('&'), QLatin1String("&&"))); } } QUrl KUrlNavigatorButton::url() const { return m_url; } void KUrlNavigatorButton::setText(const QString &text) { QString adjustedText = text; if (adjustedText.isEmpty()) { adjustedText = m_url.scheme(); } // Assure that the button always consists of one line adjustedText.remove(QLatin1Char('\n')); KUrlNavigatorButtonBase::setText(adjustedText); updateMinimumWidth(); // Assure that statFinished() does not overwrite a text that has been // set by a client of the URL navigator button m_pendingTextChange = false; } void KUrlNavigatorButton::setActiveSubDirectory(const QString &subDir) { m_subDir = subDir; // We use a different (bold) font on active, so the size hint changes updateGeometry(); update(); } QString KUrlNavigatorButton::activeSubDirectory() const { return m_subDir; } QSize KUrlNavigatorButton::sizeHint() const { QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); // the minimum size is textWidth + arrowWidth() + 2 * BorderWidth; for the // preferred size we add the BorderWidth 2 times again for having an uncluttered look const int width = QFontMetrics(adjustedFont).width(plainText()) + arrowWidth() + 4 * BorderWidth; return QSize(width, KUrlNavigatorButtonBase::sizeHint().height()); } void KUrlNavigatorButton::setShowMnemonic(bool show) { if (m_showMnemonic != show) { m_showMnemonic = show; update(); } } bool KUrlNavigatorButton::showMnemonic() const { return m_showMnemonic; } void KUrlNavigatorButton::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); painter.setFont(adjustedFont); int buttonWidth = width(); int preferredWidth = sizeHint().width(); if (preferredWidth < minimumWidth()) { preferredWidth = minimumWidth(); } if (buttonWidth > preferredWidth) { buttonWidth = preferredWidth; } const int buttonHeight = height(); const QColor fgColor = foregroundColor(); drawHoverBackground(&painter); int textLeft = 0; int textWidth = buttonWidth; const bool leftToRight = (layoutDirection() == Qt::LeftToRight); if (!m_subDir.isEmpty()) { // draw arrow const int arrowSize = arrowWidth(); const int arrowX = leftToRight ? (buttonWidth - arrowSize) - BorderWidth : BorderWidth; const int arrowY = (buttonHeight - arrowSize) / 2; QStyleOption option; option.initFrom(this); option.rect = QRect(arrowX, arrowY, arrowSize, arrowSize); option.palette = palette(); option.palette.setColor(QPalette::Text, fgColor); option.palette.setColor(QPalette::WindowText, fgColor); option.palette.setColor(QPalette::ButtonText, fgColor); if (m_hoverArrow) { // highlight the background of the arrow to indicate that the directories // popup can be opened by a mouse click QColor hoverColor = palette().color(QPalette::HighlightedText); hoverColor.setAlpha(96); painter.setPen(Qt::NoPen); painter.setBrush(hoverColor); int hoverX = arrowX; if (!leftToRight) { hoverX -= BorderWidth; } painter.drawRect(QRect(hoverX, 0, arrowSize + BorderWidth, buttonHeight)); } if (leftToRight) { style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &painter, this); } else { style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &painter, this); textLeft += arrowSize + 2 * BorderWidth; } textWidth -= arrowSize + 2 * BorderWidth; } painter.setPen(fgColor); const bool clipped = isTextClipped(); const QRect textRect(textLeft, 0, textWidth, buttonHeight); if (clipped) { QColor bgColor = fgColor; bgColor.setAlpha(0); QLinearGradient gradient(textRect.topLeft(), textRect.topRight()); if (leftToRight) { gradient.setColorAt(0.8, fgColor); gradient.setColorAt(1.0, bgColor); } else { gradient.setColorAt(0.0, bgColor); gradient.setColorAt(0.2, fgColor); } QPen pen; pen.setBrush(QBrush(gradient)); painter.setPen(pen); } int textFlags = clipped ? Qt::AlignVCenter : Qt::AlignCenter; if (m_showMnemonic) { textFlags |= Qt::TextShowMnemonic; painter.drawText(textRect, textFlags, text()); } else { painter.drawText(textRect, textFlags, plainText()); } } void KUrlNavigatorButton::enterEvent(QEvent *event) { KUrlNavigatorButtonBase::enterEvent(event); // if the text is clipped due to a small window width, the text should // be shown as tooltip if (isTextClipped()) { setToolTip(plainText()); } } void KUrlNavigatorButton::leaveEvent(QEvent *event) { KUrlNavigatorButtonBase::leaveEvent(event); setToolTip(QString()); if (m_hoverArrow) { m_hoverArrow = false; update(); } } void KUrlNavigatorButton::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Enter: case Qt::Key_Return: - emit clicked(m_url, Qt::LeftButton); + emit clicked(m_url, Qt::LeftButton, event->modifiers()); break; case Qt::Key_Down: case Qt::Key_Space: startSubDirsJob(); break; default: KUrlNavigatorButtonBase::keyPressEvent(event); } } void KUrlNavigatorButton::dropEvent(QDropEvent *event) { if (event->mimeData()->hasUrls()) { setDisplayHintEnabled(DraggedHint, true); emit urlsDropped(m_url, event); setDisplayHintEnabled(DraggedHint, false); update(); } } void KUrlNavigatorButton::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { setDisplayHintEnabled(DraggedHint, true); event->acceptProposedAction(); update(); } } void KUrlNavigatorButton::dragMoveEvent(QDragMoveEvent *event) { QRect rect = event->answerRect(); if (isAboveArrow(rect.center().x())) { m_hoverArrow = true; update(); if (m_subDirsMenu == nullptr) { requestSubDirs(); } else if (m_subDirsMenu->parent() != this) { m_subDirsMenu->close(); m_subDirsMenu->deleteLater(); m_subDirsMenu = nullptr; requestSubDirs(); } } else { if (m_openSubDirsTimer->isActive()) { cancelSubDirsRequest(); } delete m_subDirsMenu; m_subDirsMenu = nullptr; m_hoverArrow = false; update(); } } void KUrlNavigatorButton::dragLeaveEvent(QDragLeaveEvent *event) { KUrlNavigatorButtonBase::dragLeaveEvent(event); m_hoverArrow = false; setDisplayHintEnabled(DraggedHint, false); update(); } void KUrlNavigatorButton::mousePressEvent(QMouseEvent *event) { if (isAboveArrow(event->x()) && (event->button() == Qt::LeftButton)) { // the mouse is pressed above the [>] button startSubDirsJob(); } KUrlNavigatorButtonBase::mousePressEvent(event); } void KUrlNavigatorButton::mouseReleaseEvent(QMouseEvent *event) { if (!isAboveArrow(event->x()) || (event->button() != Qt::LeftButton)) { // the mouse has been released above the text area and not // above the [>] button - emit clicked(m_url, event->button()); + emit clicked(m_url, event->button(), event->modifiers()); cancelSubDirsRequest(); } KUrlNavigatorButtonBase::mouseReleaseEvent(event); } void KUrlNavigatorButton::mouseMoveEvent(QMouseEvent *event) { KUrlNavigatorButtonBase::mouseMoveEvent(event); const bool hoverArrow = isAboveArrow(event->x()); if (hoverArrow != m_hoverArrow) { m_hoverArrow = hoverArrow; update(); } } void KUrlNavigatorButton::wheelEvent(QWheelEvent *event) { if (event->orientation() == Qt::Vertical) { m_wheelSteps = event->delta() / 120; m_replaceButton = true; startSubDirsJob(); } KUrlNavigatorButtonBase::wheelEvent(event); } void KUrlNavigatorButton::requestSubDirs() { if (!m_openSubDirsTimer->isActive() && (m_subDirsJob == nullptr)) { m_openSubDirsTimer->start(); } } void KUrlNavigatorButton::startSubDirsJob() { if (m_subDirsJob != nullptr) { return; } const QUrl url = m_replaceButton ? KIO::upUrl(m_url) : m_url; m_subDirsJob = KIO::listDir(url, KIO::HideProgressInfo, false /*no hidden files*/); m_subDirs.clear(); // just to be ++safe connect(m_subDirsJob, &KIO::ListJob::entries, this, &KUrlNavigatorButton::addEntriesToSubDirs); if (m_replaceButton) { connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::replaceButton); } else { connect(m_subDirsJob, &KJob::result, this, &KUrlNavigatorButton::openSubDirsMenu); } } void KUrlNavigatorButton::addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries) { Q_ASSERT(job == m_subDirsJob); Q_UNUSED(job); foreach (const KIO::UDSEntry &entry, entries) { if (entry.isDir()) { const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME); QString displayName = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (displayName.isEmpty()) { displayName = name; } if ((name != QLatin1String(".")) && (name != QLatin1String(".."))) { m_subDirs.append(qMakePair(name, displayName)); } } } } void KUrlNavigatorButton::urlsDropped(QAction *action, QDropEvent *event) { const int result = action->data().toInt(); QUrl url(m_url); url.setPath(concatPaths(url.path(), m_subDirs.at(result).first)); emit urlsDropped(url, event); } void KUrlNavigatorButton::slotMenuActionClicked(QAction *action, Qt::MouseButton button) { const int result = action->data().toInt(); QUrl url(m_url); url.setPath(concatPaths(url.path(), m_subDirs.at(result).first)); - emit clicked(url, button); + emit clicked(url, button, Qt::NoModifier); } void KUrlNavigatorButton::statFinished(KJob *job) { if (m_pendingTextChange) { m_pendingTextChange = false; const KIO::UDSEntry entry = static_cast(job)->statResult(); QString name = entry.stringValue(KIO::UDSEntry::UDS_DISPLAY_NAME); if (name.isEmpty()) { name = m_url.fileName(); } setText(name); emit finishedTextResolving(); } } /** * Helper class for openSubDirsMenu */ class NaturalLessThan { public: NaturalLessThan() { m_collator.setCaseSensitivity(Qt::CaseInsensitive); m_collator.setNumericMode(true); } bool operator()(const QPair &s1, const QPair &s2) { return m_collator.compare(s1.first, s2.first) < 0; } private: QCollator m_collator; }; void KUrlNavigatorButton::openSubDirsMenu(KJob *job) { Q_ASSERT(job == m_subDirsJob); m_subDirsJob = nullptr; if (job->error() || m_subDirs.isEmpty()) { // clear listing return; } NaturalLessThan nlt; std::sort(m_subDirs.begin(), m_subDirs.end(), nlt); setDisplayHintEnabled(PopupActiveHint, true); update(); // ensure the button is drawn highlighted if (m_subDirsMenu != nullptr) { m_subDirsMenu->close(); m_subDirsMenu->deleteLater(); m_subDirsMenu = nullptr; } m_subDirsMenu = new KUrlNavigatorMenu(this); initMenu(m_subDirsMenu, 0); const bool leftToRight = (layoutDirection() == Qt::LeftToRight); const int popupX = leftToRight ? width() - arrowWidth() - BorderWidth : 0; const QPoint popupPos = parentWidget()->mapToGlobal(geometry().bottomLeft() + QPoint(popupX, 0)); QPointer guard(this); m_subDirsMenu->exec(popupPos); // If 'this' has been deleted in the menu's nested event loop, we have to return // immediately because any access to a member variable might cause a crash. if (!guard) { return; } m_subDirs.clear(); delete m_subDirsMenu; m_subDirsMenu = nullptr; setDisplayHintEnabled(PopupActiveHint, false); } void KUrlNavigatorButton::replaceButton(KJob *job) { Q_ASSERT(job == m_subDirsJob); m_subDirsJob = nullptr; m_replaceButton = false; if (job->error() || m_subDirs.isEmpty()) { return; } NaturalLessThan nlt; std::sort(m_subDirs.begin(), m_subDirs.end(), nlt); // Get index of the directory that is shown currently in the button const QString currentDir = m_url.fileName(); int currentIndex = 0; const int subDirsCount = m_subDirs.count(); while (currentIndex < subDirsCount) { if (m_subDirs[currentIndex].first == currentDir) { break; } ++currentIndex; } // Adjust the index by respecting the wheel steps and // trigger a replacing of the button content int targetIndex = currentIndex - m_wheelSteps; if (targetIndex < 0) { targetIndex = 0; } else if (targetIndex >= subDirsCount) { targetIndex = subDirsCount - 1; } QUrl url(KIO::upUrl(m_url)); url.setPath(concatPaths(url.path(), m_subDirs[targetIndex].first)); - emit clicked(url, Qt::LeftButton); + emit clicked(url, Qt::LeftButton, Qt::NoModifier); m_subDirs.clear(); } void KUrlNavigatorButton::cancelSubDirsRequest() { m_openSubDirsTimer->stop(); if (m_subDirsJob != nullptr) { m_subDirsJob->kill(); m_subDirsJob = nullptr; } } QString KUrlNavigatorButton::plainText() const { // Replace all "&&" by '&' and remove all single // '&' characters const QString source = text(); const int sourceLength = source.length(); QString dest; dest.reserve(sourceLength); int sourceIndex = 0; int destIndex = 0; while (sourceIndex < sourceLength) { if (source.at(sourceIndex) == QLatin1Char('&')) { ++sourceIndex; if (sourceIndex >= sourceLength) { break; } } dest[destIndex] = source.at(sourceIndex); ++sourceIndex; ++destIndex; } return dest; } int KUrlNavigatorButton::arrowWidth() const { // if there isn't arrow then return 0 int width = 0; if (!m_subDir.isEmpty()) { width = height() / 2; if (width < 4) { width = 4; } } return width; } bool KUrlNavigatorButton::isAboveArrow(int x) const { const bool leftToRight = (layoutDirection() == Qt::LeftToRight); return leftToRight ? (x >= width() - arrowWidth()) : (x < arrowWidth()); } bool KUrlNavigatorButton::isTextClipped() const { int availableWidth = width() - 2 * BorderWidth; if (!m_subDir.isEmpty()) { availableWidth -= arrowWidth() - BorderWidth; } QFont adjustedFont(font()); adjustedFont.setBold(m_subDir.isEmpty()); return QFontMetrics(adjustedFont).width(plainText()) >= availableWidth; } void KUrlNavigatorButton::updateMinimumWidth() { const int oldMinWidth = minimumWidth(); int minWidth = sizeHint().width(); if (minWidth < 40) { minWidth = 40; } else if (minWidth > 150) { // don't let an overlong path name waste all the URL navigator space minWidth = 150; } if (oldMinWidth != minWidth) { setMinimumWidth(minWidth); } } void KUrlNavigatorButton::initMenu(KUrlNavigatorMenu *menu, int startIndex) { connect(menu, &KUrlNavigatorMenu::mouseButtonClicked, this, &KUrlNavigatorButton::slotMenuActionClicked); connect(menu, &KUrlNavigatorMenu::urlsDropped, this, QOverload::of(&KUrlNavigatorButton::urlsDropped)); menu->setLayoutDirection(Qt::LeftToRight); const int maxIndex = startIndex + 30; // Don't show more than 30 items in a menu const int lastIndex = qMin(m_subDirs.count() - 1, maxIndex); for (int i = startIndex; i <= lastIndex; ++i) { const QString subDirName = m_subDirs[i].first; const QString subDirDisplayName = m_subDirs[i].second; QString text = KStringHandler::csqueeze(subDirDisplayName, 60); text.replace(QLatin1Char('&'), QLatin1String("&&")); QAction *action = new QAction(text, this); if (m_subDir == subDirName) { QFont font(action->font()); font.setBold(true); action->setFont(font); } action->setData(i); menu->addAction(action); } if (m_subDirs.count() > maxIndex) { // If too much items are shown, move them into a sub menu menu->addSeparator(); KUrlNavigatorMenu *subDirsMenu = new KUrlNavigatorMenu(menu); subDirsMenu->setTitle(i18nc("@action:inmenu", "More")); initMenu(subDirsMenu, maxIndex); menu->addMenu(subDirsMenu); } } } // namespace KDEPrivate #include "moc_kurlnavigatorbutton_p.cpp" diff --git a/src/filewidgets/kurlnavigatorbutton_p.h b/src/filewidgets/kurlnavigatorbutton_p.h index 17ae5577..869efe72 100644 --- a/src/filewidgets/kurlnavigatorbutton_p.h +++ b/src/filewidgets/kurlnavigatorbutton_p.h @@ -1,204 +1,204 @@ /***************************************************************************** * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Aaron J. Seigo * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *****************************************************************************/ #ifndef KURLNAVIGATORBUTTON_P_H #define KURLNAVIGATORBUTTON_P_H #include "kurlnavigatorbuttonbase_p.h" #include "kurlnavigatormenu_p.h" #include #include #include #include class KJob; class QDropEvent; class QPaintEvent; namespace KIO { class ListJob; class Job; } namespace KDEPrivate { /** * @brief Button of the URL navigator which contains one part of an URL. * * It is possible to drop a various number of items to an UrlNavigatorButton. In this case * a context menu is opened where the user must select whether he wants * to copy, move or link the dropped items to the URL part indicated by * the button. */ class KUrlNavigatorButton : public KUrlNavigatorButtonBase { Q_OBJECT public: explicit KUrlNavigatorButton(const QUrl &url, QWidget *parent); virtual ~KUrlNavigatorButton(); void setUrl(const QUrl &url); QUrl url() const; /* Implementation note: QAbstractButton::setText() is not virtual, * but KUrlNavigatorButton needs to adjust the minimum size when * the text has been changed. KUrlNavigatorButton::setText() hides * QAbstractButton::setText() which is not nice, but sufficient for * the usage in KUrlNavigator. */ void setText(const QString &text); /** * Sets the name of the sub directory that should be marked when * opening the sub directories popup. */ void setActiveSubDirectory(const QString &subDir); QString activeSubDirectory() const; /** @see QWidget::sizeHint() */ QSize sizeHint() const override; void setShowMnemonic(bool show); bool showMnemonic() const; Q_SIGNALS: /** * Is emitted if URLs have been dropped * to the destination \a destination. */ void urlsDropped(const QUrl &destination, QDropEvent *event); - void clicked(const QUrl &url, Qt::MouseButton button); + void clicked(const QUrl &url, Qt::MouseButton button, Qt::KeyboardModifiers modifiers); /** * Is emitted, if KUrlNavigatorButton::setUrl() cannot resolve * the text synchronously and KUrlNavigator::text() will return * an empty string in this case. The signal finishedTextResolving() is * emitted, as soon as the text has been resolved. */ void startedTextResolving(); /** * Is emitted, if the asynchronous resolving of the text has * been finished (see startTextResolving()). * KUrlNavigatorButton::text() contains the resolved text. */ void finishedTextResolving(); protected: void paintEvent(QPaintEvent *event) override; void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void dropEvent(QDropEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; private Q_SLOTS: /** * Requests to load the sub-directories after a short delay. * startSubDirsJob() is invoked if the delay is exceeded. */ void requestSubDirs(); /** * Starts to load the sub directories asynchronously. The directories * are stored in m_subDirs by addEntriesToSubDirs(). */ void startSubDirsJob(); /** * Adds the entries from the sub-directories job to m_subDirs. The entries * will be shown if the job has been finished in openSubDirsMenu() or * replaceButton(). */ void addEntriesToSubDirs(KIO::Job *job, const KIO::UDSEntryList &entries); /** * Is called after the sub-directories job has been finished and opens a menu * showing all sub directories. */ void openSubDirsMenu(KJob *job); /** * Is called after the sub-directories job has been finished and replaces * the button content by the current sub directory (triggered by * the scroll wheel). */ void replaceButton(KJob *job); void urlsDropped(QAction *action, QDropEvent *event); /** * Is called, if an action of a sub-menu has been triggered by * a click. */ void slotMenuActionClicked(QAction *action, Qt::MouseButton button); void statFinished(KJob *); private: /** * Cancels any request done by requestSubDirs(). */ void cancelSubDirsRequest(); /** * @return Text without mnemonic characters. */ QString plainText() const; int arrowWidth() const; bool isAboveArrow(int x) const; bool isTextClipped() const; void updateMinimumWidth(); void initMenu(KUrlNavigatorMenu *menu, int startIndex); private: bool m_hoverArrow; bool m_pendingTextChange; bool m_replaceButton; bool m_showMnemonic; int m_wheelSteps; QUrl m_url; QString m_subDir; QTimer *m_openSubDirsTimer; KIO::ListJob *m_subDirsJob; /// pair of name and display name QList > m_subDirs; static QPointer m_subDirsMenu; }; } // namespace KDEPrivate #endif