mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00

the fix is only for non-local file URLs, KUrl already does the correct thing by stat()-ing local file URLs (see KUrl::upUrl()) trailing slash is added to the URL by KUrlNavigator::Private::updateButtonVisibility() too before checking if it is possible to go up which essentially means that KUrlNavigator was not ment to be for files (it does not autocomplete files either) even tho it is possible to open file by adding file name manually to the URL (via dolphin anyway, not via KFileDialog tho) Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
1124 lines
33 KiB
C++
1124 lines
33 KiB
C++
/*****************************************************************************
|
|
* Copyright (C) 2006-2010 by Peter Penz <peter.penz@gmx.at> *
|
|
* Copyright (C) 2006 by Aaron J. Seigo <aseigo@kde.org> *
|
|
* Copyright (C) 2007 by Kevin Ottens <ervin@kde.org> *
|
|
* Copyright (C) 2007 by Urs Wolfer <uwolfer @ kde.org> *
|
|
* *
|
|
* 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 "kfileitem.h"
|
|
#include "kfileplacesmodel.h"
|
|
#include "kglobalsettings.h"
|
|
#include "kicon.h"
|
|
#include "klocale.h"
|
|
#include "kmenu.h"
|
|
#include "kprotocolinfo.h"
|
|
#include "kurlcombobox.h"
|
|
#include "kurlcompletion.h"
|
|
#include "kurifilter.h"
|
|
|
|
#include <QDir>
|
|
#include <QList>
|
|
#include <QTimer>
|
|
#include <QApplication>
|
|
#include <QBoxLayout>
|
|
#include <QClipboard>
|
|
#include <QDropEvent>
|
|
#include <QLabel>
|
|
#include <QPainter>
|
|
#include <QStyleOption>
|
|
|
|
using namespace KDEPrivate;
|
|
|
|
struct LocationData
|
|
{
|
|
KUrl url;
|
|
QByteArray state;
|
|
};
|
|
|
|
class KUrlNavigator::Private
|
|
{
|
|
public:
|
|
Private(KUrlNavigator* q, KFilePlacesModel* placesModel);
|
|
|
|
void initialize(const KUrl& url);
|
|
|
|
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);
|
|
|
|
/**
|
|
* Switches the navigation bar between the breadcrumb view and the
|
|
* traditional view (see setUrlEditable()) and is connected to the clicked signal
|
|
* of the navigation bar button.
|
|
*/
|
|
void switchView();
|
|
|
|
/** Emits the signal urlsDropped(). */
|
|
void dropUrls(const KUrl& 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 KUrl& 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.
|
|
*/
|
|
KUrl buttonUrl(int index) const;
|
|
|
|
void switchToBreadcrumbMode();
|
|
|
|
/**
|
|
* Deletes all URL navigator buttons. m_navButtons is
|
|
* empty after this operation.
|
|
*/
|
|
void deleteButtons();
|
|
|
|
/**
|
|
* Retrieves the place path for the current path.
|
|
* 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.
|
|
*/
|
|
QString retrievePlacePath() 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<LocationData> m_history;
|
|
KUrlNavigatorPlacesSelector* m_placesSelector;
|
|
KUrlComboBox* m_pathBox;
|
|
KUrlNavigatorProtocolCombo* m_protocols;
|
|
KUrlNavigatorDropDownButton* m_dropDownButton;
|
|
QList<KUrlNavigatorButton*> m_navButtons;
|
|
KUrlNavigatorButtonBase* m_toggleEditableMode;
|
|
KUrl m_homeUrl;
|
|
QStringList m_customProtocols;
|
|
KUrlNavigator* q;
|
|
};
|
|
|
|
|
|
KUrlNavigator::Private::Private(KUrlNavigator* q, KFilePlacesModel* placesModel) :
|
|
m_editable(false),
|
|
m_active(true),
|
|
m_showPlacesSelector(placesModel != 0),
|
|
m_showFullPath(false),
|
|
m_historyIndex(0),
|
|
m_layout(new QHBoxLayout),
|
|
m_placesSelector(0),
|
|
m_pathBox(0),
|
|
m_protocols(0),
|
|
m_dropDownButton(0),
|
|
m_navButtons(),
|
|
m_toggleEditableMode(0),
|
|
m_homeUrl(),
|
|
m_customProtocols(QStringList()),
|
|
q(q)
|
|
{
|
|
m_layout->setSpacing(0);
|
|
m_layout->setMargin(0);
|
|
|
|
// initialize the places selector
|
|
q->setAutoFillBackground(false);
|
|
|
|
if (placesModel != 0) {
|
|
m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel);
|
|
connect(m_placesSelector, SIGNAL(placeActivated(KUrl)),
|
|
q, SLOT(setLocationUrl(KUrl)));
|
|
|
|
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(KUrl)),
|
|
q, SLOT(setLocationUrl(KUrl)));
|
|
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(switchView()));
|
|
|
|
if (m_placesSelector != 0) {
|
|
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 KUrl& url)
|
|
{
|
|
LocationData data;
|
|
data.url = url;
|
|
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::slotReturnPressed()
|
|
{
|
|
// 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 <cullmann@kde.org>
|
|
// Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
|
|
// Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
|
|
|
|
const KUrl typedUrl = q->uncommittedUrl();
|
|
QStringList urls = m_pathBox->urls();
|
|
urls.removeAll(typedUrl.url());
|
|
urls.prepend(typedUrl.url());
|
|
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 KUrl currentUrl = q->locationUrl();
|
|
m_pathBox->setUrl(currentUrl);
|
|
|
|
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);
|
|
|
|
KUrl url;
|
|
url.setScheme(protocol);
|
|
url.setPath((protocol == QLatin1String("file")) ? QLatin1String("/") : QLatin1String("//"));
|
|
|
|
m_pathBox->setEditUrl(url);
|
|
}
|
|
|
|
void KUrlNavigator::Private::openPathSelectorMenu()
|
|
{
|
|
if (m_navButtons.count() <= 0) {
|
|
return;
|
|
}
|
|
|
|
const KUrl firstVisibleUrl = m_navButtons.first()->url();
|
|
|
|
QString spacer;
|
|
QPointer<KMenu> popup = new KMenu(q);
|
|
popup->setLayoutDirection(Qt::LeftToRight);
|
|
|
|
const QString placePath = retrievePlacePath();
|
|
int idx = placePath.count(QLatin1Char('/')); // idx points to the first directory
|
|
// after the place path
|
|
|
|
const QString path = m_history[m_historyIndex].url.pathOrUrl();
|
|
QString dirName = path.section(QLatin1Char('/'), idx, idx);
|
|
if (dirName.isEmpty()) {
|
|
dirName = QLatin1Char('/');
|
|
}
|
|
do {
|
|
const QString text = spacer + dirName;
|
|
|
|
QAction* action = new QAction(text, popup);
|
|
const KUrl currentUrl = buttonUrl(idx);
|
|
if (currentUrl == firstVisibleUrl) {
|
|
popup->addSeparator();
|
|
}
|
|
action->setData(QVariant(currentUrl.prettyUrl()));
|
|
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 != 0) {
|
|
const KUrl url = KUrl(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::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 KUrl& destination, QDropEvent* event)
|
|
{
|
|
const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
|
|
if (!urls.isEmpty()) {
|
|
emit q->urlsDropped(destination, event);
|
|
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::Private::slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button)
|
|
{
|
|
if (button & Qt::LeftButton) {
|
|
q->setLocationUrl(url);
|
|
} else if (button & Qt::MiddleButton) {
|
|
emit q->tabRequested(url);
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::Private::openContextMenu()
|
|
{
|
|
q->setActive(true);
|
|
|
|
QPointer<KMenu> popup = new KMenu(q);
|
|
|
|
// provide 'Copy' action, which copies the current URL of
|
|
// the URL navigator into the clipboard
|
|
QAction* copyAction = popup->addAction(KIcon("edit-copy"), i18n("Copy"));
|
|
|
|
// provide 'Paste' action, which copies the current clipboard text
|
|
// into the URL navigator
|
|
QAction* pasteAction = popup->addAction(KIcon("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().pathOrUrl());
|
|
clipboard->setMimeData(mimeData);
|
|
} else if (activatedAction == pasteAction) {
|
|
q->setLocationUrl(KUrl(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().protocol();
|
|
m_protocols->setProtocol(protocol);
|
|
m_protocols->show();
|
|
} else {
|
|
m_protocols->hide();
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::Private::updateContent()
|
|
{
|
|
const KUrl currentUrl = q->locationUrl();
|
|
if (m_placesSelector != 0) {
|
|
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
|
|
KUrl placeUrl;
|
|
if ((m_placesSelector != 0) && !m_showFullPath) {
|
|
placeUrl = m_placesSelector->selectedPlaceUrl();
|
|
}
|
|
|
|
QString placePath = placeUrl.isValid() ? placeUrl.pathOrUrl() : retrievePlacePath();
|
|
removeTrailingSlash(placePath);
|
|
|
|
const int startIndex = placePath.count('/');
|
|
updateButtons(startIndex);
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::Private::updateButtons(int startIndex)
|
|
{
|
|
KUrl currentUrl = q->locationUrl();
|
|
|
|
const QString path = currentUrl.pathOrUrl();
|
|
|
|
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 = 0;
|
|
if (createButton) {
|
|
button = new KUrlNavigatorButton(buttonUrl(idx), q);
|
|
button->installEventFilter(q);
|
|
button->setForegroundRole(QPalette::WindowText);
|
|
connect(button, SIGNAL(urlsDropped(KUrl,QDropEvent*)),
|
|
q, SLOT(dropUrls(KUrl,QDropEvent*)));
|
|
connect(button, SIGNAL(clicked(KUrl,Qt::MouseButton)),
|
|
q, SLOT(slotNavigatorButtonClicked(KUrl,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<KUrlNavigatorButton*>::iterator itBegin = m_navButtons.begin() + newButtonCount;
|
|
const QList<KUrlNavigatorButton*>::iterator itEnd = m_navButtons.end();
|
|
QList<KUrlNavigatorButton*>::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 != 0) && m_placesSelector->isVisible()) {
|
|
availableWidth -= m_placesSelector->width();
|
|
}
|
|
|
|
if ((m_protocols != 0) && 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<KUrlNavigatorButton*>::const_iterator it = m_navButtons.constEnd();
|
|
const QList<KUrlNavigatorButton*>::const_iterator itBegin = m_navButtons.constBegin();
|
|
bool isLastButton = true;
|
|
bool hasHiddenButtons = false;
|
|
|
|
QList<KUrlNavigatorButton*> 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.
|
|
KUrl url = m_navButtons.front()->url();
|
|
url.adjustPath(KUrl::AddTrailingSlash);
|
|
const bool visible = !url.equals(url.upUrl());
|
|
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 != 0) && !m_showFullPath) {
|
|
const KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
|
|
text = m_placesSelector->selectedPlaceText();
|
|
}
|
|
|
|
if (text.isEmpty()) {
|
|
const KUrl currentUrl = q->locationUrl();
|
|
if (currentUrl.isLocalFile()) {
|
|
text = m_showFullPath ? QLatin1String("/") : i18n("Custom Path");
|
|
} else {
|
|
text = currentUrl.protocol() + QLatin1Char(':');
|
|
if (!currentUrl.host().isEmpty()) {
|
|
text += QLatin1Char(' ') + currentUrl.host();
|
|
}
|
|
}
|
|
}
|
|
|
|
return text;
|
|
}
|
|
|
|
KUrl 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
|
|
const KUrl currentUrl = q->locationUrl();
|
|
KUrl newUrl = currentUrl;
|
|
newUrl.setPath(QString());
|
|
|
|
QString pathOrUrl = currentUrl.pathOrUrl();
|
|
if (!pathOrUrl.isEmpty()) {
|
|
if (index == 0) {
|
|
// prevent the last "/" from being stripped
|
|
// or we end up with an empty path
|
|
pathOrUrl = QLatin1String("/");
|
|
} else {
|
|
pathOrUrl = pathOrUrl.section('/', 0, index);
|
|
}
|
|
}
|
|
|
|
newUrl.setPath(KUrl(pathOrUrl).path());
|
|
return newUrl;
|
|
}
|
|
|
|
void KUrlNavigator::Private::switchToBreadcrumbMode()
|
|
{
|
|
q->setUrlEditable(false);
|
|
}
|
|
|
|
void KUrlNavigator::Private::deleteButtons()
|
|
{
|
|
foreach (KUrlNavigatorButton* button, m_navButtons) {
|
|
button->hide();
|
|
button->deleteLater();
|
|
}
|
|
m_navButtons.clear();
|
|
}
|
|
|
|
QString KUrlNavigator::Private::retrievePlacePath() const
|
|
{
|
|
const KUrl currentUrl = q->locationUrl();
|
|
const QString path = currentUrl.pathOrUrl();
|
|
int idx = path.indexOf(QLatin1String("///"));
|
|
if (idx >= 0) {
|
|
idx += 3;
|
|
} else {
|
|
idx = path.indexOf(QLatin1String("//"));
|
|
idx = path.indexOf(QLatin1Char('/'), (idx < 0) ? 0 : idx + 2);
|
|
}
|
|
|
|
QString placePath = (idx < 0) ? path : path.left(idx);
|
|
removeTrailingSlash(placePath);
|
|
return placePath;
|
|
}
|
|
|
|
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, 0))
|
|
{
|
|
d->initialize(KUrl());
|
|
}
|
|
|
|
KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel,
|
|
const KUrl& url,
|
|
QWidget* parent) :
|
|
QWidget(parent),
|
|
d(new Private(this, placesModel))
|
|
{
|
|
d->initialize(url);
|
|
}
|
|
|
|
KUrlNavigator::~KUrlNavigator()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
KUrl 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 KUrl 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 KUrl 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 KUrl currentUrl = locationUrl();
|
|
KUrl upUrl = currentUrl;
|
|
upUrl.adjustPath(KUrl::AddTrailingSlash);
|
|
upUrl = upUrl.upUrl();
|
|
if (upUrl != currentUrl) {
|
|
setLocationUrl(upUrl);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void KUrlNavigator::goHome()
|
|
{
|
|
if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) {
|
|
setLocationUrl(KUrl(QDir::homePath()));
|
|
} else {
|
|
setLocationUrl(d->m_homeUrl);
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::setHomeUrl(const KUrl& url)
|
|
{
|
|
d->m_homeUrl = url;
|
|
}
|
|
|
|
KUrl 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 == 0)) {
|
|
// 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;
|
|
}
|
|
|
|
KUrl KUrlNavigator::uncommittedUrl() const
|
|
{
|
|
KUriFilterData filteredData(d->m_pathBox->currentText().trimmed());
|
|
filteredData.setCheckForExecutables(false);
|
|
if (KUriFilter::self()->filterUri(filteredData, QStringList() << "kshorturifilter" << "kurisearchfilter")) {
|
|
return filteredData.uri();
|
|
}
|
|
else {
|
|
return KUrl(filteredData.typedString());
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::setLocationUrl(const KUrl& newUrl)
|
|
{
|
|
if (newUrl == locationUrl()) {
|
|
return;
|
|
}
|
|
|
|
KUrl url = newUrl;
|
|
url.cleanPath();
|
|
|
|
// 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.equals(locationUrl(), KUrl::RemoveTrailingSlash) ||
|
|
(!url.isValid() && url.equals(data.url, KUrl::RemoveTrailingSlash));
|
|
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<LocationData>::iterator begin = d->m_history.begin();
|
|
QList<LocationData>::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<LocationData>::iterator begin = d->m_history.begin() + historyMax;
|
|
QList<LocationData>::iterator end = d->m_history.end();
|
|
d->m_history.erase(begin, end);
|
|
}
|
|
|
|
emit historyChanged();
|
|
emit urlChanged(url);
|
|
|
|
d->updateContent();
|
|
|
|
requestActivation();
|
|
}
|
|
|
|
void KUrlNavigator::requestActivation()
|
|
{
|
|
setActive(true);
|
|
}
|
|
|
|
void KUrlNavigator::setFocus()
|
|
{
|
|
if (isUrlEditable()) {
|
|
d->m_pathBox->setFocus();
|
|
} else {
|
|
QWidget::setFocus();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void KUrlNavigator::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
|
|
setUrlEditable(false);
|
|
} else {
|
|
QWidget::keyPressEvent(event);
|
|
}
|
|
}
|
|
|
|
void KUrlNavigator::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
if (event->button() == Qt::MiddleButton) {
|
|
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(KUrl(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;
|
|
}
|
|
|
|
#include "moc_kurlnavigator.cpp"
|