diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 83b7b73b..791eb786 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,130 +1,131 @@ if(POLICY CMP0028) cmake_policy(SET CMP0028 OLD) endif() remove_definitions(-DQT_NO_CAST_FROM_ASCII) include(ECMAddTests) add_subdirectory(http) add_subdirectory(kcookiejar) find_package(Qt5Widgets REQUIRED) ########### unittests ############### find_package(Qt5Concurrent ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE) ecm_add_tests( kacltest.cpp listdirtest.cpp kmountpointtest.cpp upurltest.cpp dataprotocoltest.cpp jobtest.cpp jobremotetest.cpp kfileitemtest.cpp kprotocolinfotest.cpp ktcpsockettest.cpp globaltest.cpp mkpathjobtest.cpp threadtest.cpp udsentrytest.cpp udsentry_benchmark.cpp deletejobtest.cpp + urlutiltest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) target_link_libraries(threadtest Qt5::Concurrent) ecm_add_test( http_jobtest.cpp httpserver_p.cpp TEST_NAME http_jobtest NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) if(UNIX) ecm_add_tests( klocalsockettest.cpp klocalsocketservertest.cpp NAME_PREFIX "kiocore-" LINK_LIBRARIES KF5::KIOCore KF5::I18n Qt5::Test Qt5::Network ) endif() if (TARGET KF5::KIOGui) ecm_add_tests( favicontest.cpp NAME_PREFIX "kiogui-" LINK_LIBRARIES KF5::KIOCore KF5::KIOGui Qt5::Test ) target_link_libraries(favicontest Qt5::Concurrent) endif() if (TARGET KF5::KIOWidgets) ecm_add_tests( clipboardupdatertest.cpp dropjobtest.cpp kdynamicjobtrackernowidgetstest.cpp krununittest.cpp kdirlistertest.cpp kdirmodeltest.cpp kfileitemactionstest.cpp fileundomanagertest.cpp kurifiltertest.cpp kurlcompletiontest.cpp jobguitest.cpp pastetest.cpp accessmanagertest.cpp kurifiltersearchprovideractionstest.cpp NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(krununittest PROPERTIES COMPILE_FLAGS "-DCMAKE_INSTALL_FULL_LIBEXECDIR_KF5=\"\\\"${CMAKE_INSTALL_FULL_LIBEXECDIR_KF5}\\\"\"") # Same as accessmanagertest, but using QNetworkAccessManager, to make sure we # behave the same ecm_add_test( accessmanagertest.cpp TEST_NAME accessmanagertest-qnam NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(accessmanagertest-qnam PROPERTIES COMPILE_FLAGS "-DUSE_QNAM") # Same as kurlcompletiontest, but with immediate return, and results posted by thread later ecm_add_test( kurlcompletiontest.cpp TEST_NAME kurlcompletiontest-nowait NAME_PREFIX "kiowidgets-" LINK_LIBRARIES KF5::KIOCore KF5::KIOWidgets Qt5::Test ) set_target_properties(kurlcompletiontest-nowait PROPERTIES COMPILE_FLAGS "-DNO_WAIT") endif() if (TARGET KF5::KIOFileWidgets) find_package(KF5XmlGui ${KF5_DEP_VERSION} REQUIRED) include_directories(${CMAKE_SOURCE_DIR}/src/filewidgets ${CMAKE_BINARY_DIR}/src/filewidgets) ecm_add_tests( kurlnavigatortest.cpp kurlcomboboxtest.cpp kdiroperatortest.cpp kfilewidgettest.cpp knewfilemenutest.cpp kfilecopytomenutest.cpp kfileplacesmodeltest.cpp kurlrequestertest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n ) endif() # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) set_property(TARGET jobtest APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_BINARY_DIR}/src/core/moc_predefs.h) endif() diff --git a/autotests/urlutiltest.cpp b/autotests/urlutiltest.cpp new file mode 100644 index 00000000..910f9f70 --- /dev/null +++ b/autotests/urlutiltest.cpp @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2016 by Gregor Mi * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include + +#include "../src/filewidgets/urlutil_p.h" + +class UrlUtilTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testFirstChildUrl(); +}; + +static inline QUrl lUrl(const QString &path) { return QUrl::fromLocalFile(path); } + +void UrlUtilTest::testFirstChildUrl() +{ + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/data/documents/muh/"), lUrl("/home/test/")), lUrl("/home/test/data")); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/data/documents/muh"), lUrl("/home/test")), lUrl("/home/test/data")); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/data/documents/muh/"), lUrl("/home/test")), lUrl("/home/test/data")); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/data/documents/muh"), lUrl("/home/test/")), lUrl("/home/test/data")); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/data"), lUrl("/home/test/data/")), QUrl()); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/test/"), lUrl("/home/test/")), QUrl()); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/aaa/"), lUrl("/home/bbb/")), QUrl()); + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/aaa/"), lUrl("/home/bbb/ccc")), QUrl()); + // # is %23 in an URL, so this test could reveal path/URL confusion in the code: + QCOMPARE(KIO::UrlUtil::firstChildUrl(lUrl("/home/a#/b#"), lUrl("/home/a#")), lUrl("/home/a#/b#")); +} + +QTEST_MAIN(UrlUtilTest) + +#include "urlutiltest.moc" diff --git a/src/filewidgets/kurlnavigator.cpp b/src/filewidgets/kurlnavigator.cpp index 71a953a1..7a0c0776 100644 --- a/src/filewidgets/kurlnavigator.cpp +++ b/src/filewidgets/kurlnavigator.cpp @@ -1,1284 +1,1293 @@ /***************************************************************************** * 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 "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 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 openContextMenu(); 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 *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, SIGNAL(placeActivated(QUrl)), q, SLOT(setLocationUrl(QUrl))); 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, SIGNAL(urlActivated(QUrl)), q, SLOT(setLocationUrl(QUrl))); 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())); } 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); url.setPath((protocol == QLatin1String("file")) ? QStringLiteral("/") : QStringLiteral("//")); 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); 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(" "); dirName = path.section('/', 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(); } emit 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) { if (button & Qt::LeftButton) { q->setLocationUrl(url); } else if (button & Qt::MidButton) { emit q->tabRequested(url); } } void KUrlNavigator::Private::openContextMenu() { 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(); // 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); 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('/'); 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(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('/', 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) == QChar('/'))) { 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 + const 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")); } } // 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::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 56d70252..8b15cb7e 100644 --- a/src/filewidgets/kurlnavigator.h +++ b/src/filewidgets/kurlnavigator.h @@ -1,483 +1,495 @@ /***************************************************************************** * 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 . */ // KDE5: Think about removing the custom-protocols-property. It had been used // only by one application currently which uses a different approach now. 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) Q_DECL_OVERRIDE; /** * Reimplemented for internal purposes. */ void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; /** * Paste the clipboard content as URL, if the middle mouse * button has been clicked. * @see QWidget::mouseReleaseEvent() */ void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; bool eventFilter(QObject *watched, QEvent *event) Q_DECL_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 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/urlutil_p.h b/src/filewidgets/urlutil_p.h new file mode 100644 index 00000000..90f5a9dd --- /dev/null +++ b/src/filewidgets/urlutil_p.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2015 by Gregor Mi * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef URLUTIL_P_H +#define URLUTIL_P_H + +#include + +namespace KIO { +namespace UrlUtil { + + /** + * Given that @p lastUrl is a child of @p currentUrl + * or put in other words: + * @p currentUrl is a parent in the hierarchy of @p lastUrl, + * then an URL 'currentUrl'/'childitem' is returned where + * 'childitem' is the first item in the hierarchy down to + * @p lastUrl. An example will illustrate this: + + \verbatim + lastUrl : "/home/test/data/documents/muh/" + currentUrl : "/home/test/" + returns : "/home/test/data/" + \endverbatim + + * In case @p currentUrl is a child of @p lastUrl, an invalid + * URL is returned: + + \verbatim + lastUrl : "/home/" + currentUrl : "/home/test/" + returns : "" (invalid url) + \endverbatim + + * In case both URLs are equal, an invalid URL is returned: + + \verbatim + lastUrl : "/home/test/" + currentUrl : "/home/test/" + returns : "" (invalid url) + \endverbatim + */ + static QUrl firstChildUrl(const QUrl& lastUrl, const QUrl& currentUrl) + { + if (!currentUrl.isParentOf(lastUrl)) { + return QUrl(); + } + + const QUrl adjustedLastUrl = lastUrl.adjusted(QUrl::StripTrailingSlash); + const QUrl adjustedCurrentUrl = currentUrl.adjusted(QUrl::StripTrailingSlash); + const QString childPath = adjustedLastUrl.path(); + const QString parentPath = adjustedCurrentUrl.path(); + + // e.g. this would just be ok: + // childPath = /home/a len=7 + // parentPath = /home len=5 + Q_ASSERT(childPath.length() >= (parentPath.length() + 2)); + + const int idx2 = childPath.indexOf(QLatin1Char('/'), parentPath.length() + 2); + // parentPath = /home + // childPath = /home/a + // idx = -1 + // => len2 = 7 + // + // childPath = /homa/a/b + // idx = 7 + // => len2 = 7 + const int len2 = (idx2 < 0) ? childPath.length() : idx2; + + const QString path3 = childPath.left(len2); + + QUrl res = lastUrl; // keeps the scheme (e.g. file://) + res.setPath(path3); + return res; + } +} +} + +#endif