-/***************************************************************************
- * Copyright (C) 2006 by Peter Penz (<peter.penz@gmx.at>) *
- * Copyright (C) 2006 by Aaron J. Seigo (<aseigo@kde.org>) *
- * Copyright (C) 2006 by Patrice Tremblay *
- * Copyright (C) 2007 by Kevin Ottens (ervin@kde.org) *
- * *
- * 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 "kurlnavigator.h"
-
-#include "kfileplacesselector_p.h"
-#include "kprotocolcombo_p.h"
-#include "kurlnavigatorbutton_p.h"
-
-#include <assert.h>
-
-#include <kfileitem.h>
-#include <kicon.h>
-#include <klocale.h>
-#include <kprotocolinfo.h>
-#include <kurlcombobox.h>
-#include <kurlcompletion.h>
-
-#include <QApplication>
-#include <QClipboard>
-#include <QDir>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
-#include <QLinkedList>
-#include <QMouseEvent>
-#include <QToolButton>
-
-/**
- * @brief Represents the history element of an URL.
- *
- * A history element contains the URL, the name of the current file
- * (the 'current file' is the file where the cursor is located) and
- * the x- and y-position of the content.
- */
-class HistoryElem {
-public:
- HistoryElem();
- HistoryElem(const KUrl& url);
- ~HistoryElem(); // non virtual
-
- const KUrl& url() const { return m_url; }
-
- void setCurrentFileName(const QString& name) { m_currentFileName = name; }
- const QString& currentFileName() const { return m_currentFileName; }
-
- void setContentsX(int x) { m_contentsX = x; }
- int contentsX() const { return m_contentsX; }
-
- void setContentsY(int y) { m_contentsY = y; }
- int contentsY() const { return m_contentsY; }
-
-private:
- KUrl m_url;
- QString m_currentFileName;
- int m_contentsX;
- int m_contentsY;
-};
-
-HistoryElem::HistoryElem() :
- m_url(),
- m_currentFileName(),
- m_contentsX(0),
- m_contentsY(0)
-{
-}
-
-HistoryElem::HistoryElem(const KUrl& url) :
- m_url(url),
- m_currentFileName(),
- m_contentsX(0),
- m_contentsY(0)
-{
-}
-
-HistoryElem::~HistoryElem()
-{
-}
-
-class KUrlNavigator::Private
-{
-public:
- Private(KUrlNavigator* q, KFilePlacesModel* placesModel);
-
- void slotReturnPressed(const QString&);
- void slotRemoteHostActivated();
- void slotProtocolChanged(const QString&);
-
- /**
- * 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);
-
- /**
- * 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();
-
- /**
- * Updates the history element with the current file item
- * and the contents position.
- */
- void updateHistoryElem();
- void updateContent();
-
- /**
- * Updates all buttons to have one button for each part of the
- * path \a path. Existing buttons, which are available by m_navButtons,
- * are reused if possible. If the path is longer, new buttons will be
- * created, if the path is shorter, the remaining buttons will be deleted.
- * @param startIndex Start index of path part (/), where the buttons
- * should be created for each following part.
- */
- void updateButtons(const QString& path, int startIndex);
-
- /**
- * Deletes all URL navigator buttons. m_navButtons is
- * empty after this operation.
- */
- void deleteButtons();
-
-
- bool m_active;
- bool m_showHiddenFiles;
- int m_historyIndex;
-
- QHBoxLayout* m_layout;
-
- QList<HistoryElem> m_history;
- QToolButton* m_toggleButton;
- KFilePlacesSelector* m_placesSelector;
- KUrlComboBox* m_pathBox;
- KProtocolCombo* m_protocols;
- QLabel* m_protocolSeparator;
- QLineEdit* m_host;
- QLinkedList<KUrlNavigatorButton*> m_navButtons;
- QWidget* m_filler;
- QString m_homeUrl;
- KUrlNavigator* q;
-};
-
-
-KUrlNavigator::Private::Private(KUrlNavigator* q, KFilePlacesModel* placesModel)
- :
- m_active(true),
- m_showHiddenFiles(false),
- m_historyIndex(0),
- m_layout(new QHBoxLayout),
- m_protocols(0),
- m_protocolSeparator(0),
- m_host(0),
- m_filler(0),
- q(q)
-{
- m_layout->setSpacing(0);
- m_layout->setMargin(0);
-
- // initialize toggle button which switches between the breadcrumb view
- // and the traditional view
- m_toggleButton = new QToolButton();
- m_toggleButton->setCheckable(true);
- m_toggleButton->setAutoRaise(true);
- m_toggleButton->setIcon(KIcon("editinput")); // TODO: is just a placeholder icon (?)
- m_toggleButton->setFocusPolicy(Qt::NoFocus);
- m_toggleButton->setMinimumHeight(q->minimumHeight());
- connect(m_toggleButton, SIGNAL(clicked()),
- q, SLOT(switchView()));
-
- // initialize the places selector
- m_placesSelector = new KFilePlacesSelector(q, placesModel);
- connect(m_placesSelector, SIGNAL(placeActivated(const KUrl&)),
- q, SLOT(setUrl(const KUrl&)));
-
- // initialize the path box of the traditional view
- m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q);
-
- KUrlCompletion* kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
- m_pathBox->setCompletionObject(kurlCompletion);
- m_pathBox->setAutoDeleteCompletionObject(true);
-
- connect(m_pathBox, SIGNAL(returnPressed(QString)),
- q, SLOT(slotReturnPressed(QString)));
- connect(m_pathBox, SIGNAL(urlActivated(KUrl)),
- q, SLOT(setUrl(KUrl)));
-
- // Append a filler widget at the end, which automatically resizes to the
- // maximum available width. This assures that the URL navigator uses the
- // whole width, so that the clipboard content can be dropped.
- m_filler = new QWidget();
- m_filler->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
-
- m_layout->addWidget(m_toggleButton);
- m_layout->addWidget(m_placesSelector);
- m_layout->addWidget(m_pathBox);
- m_layout->addWidget(m_filler);
-}
-
-void KUrlNavigator::Private::appendWidget(QWidget* widget)
-{
- m_layout->insertWidget(m_layout->count() - 1, widget);
-}
-
-void KUrlNavigator::Private::slotReturnPressed(const QString& text)
-{
- // 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>
-
- KUrl typedUrl(text);
- if (typedUrl.hasPass()) {
- typedUrl.setPass(QString());
- }
-
- QStringList urls = m_pathBox->urls();
- urls.removeAll(typedUrl.url());
- urls.prepend(typedUrl.url());
- m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
-
- q->setUrl(typedUrl);
- // The URL might have been adjusted by KUrlNavigator::setUrl(), hence
- // synchronize the result in the path box.
- m_pathBox->setUrl(q->url());
-}
-
-void KUrlNavigator::Private::slotRemoteHostActivated()
-{
- KUrl u = q->url();
-
- QString host = m_host->text();
- QString user;
-
- int marker = host.indexOf("@");
- if (marker != -1)
- {
- user = host.left(marker);
- u.setUser(user);
- host = host.right(host.length() - marker - 1);
- }
-
- marker = host.indexOf("/");
- if (marker != -1)
- {
- u.setPath(host.right(host.length() - marker));
- host.truncate(marker);
- }
- else
- {
- u.setPath("");
- }
-
- if (m_protocols->currentProtocol() != u.protocol() ||
- host != u.host() ||
- user != u.user())
- {
- u.setProtocol(m_protocols->currentProtocol());
- u.setHost(m_host->text());
-
- //TODO: get rid of this HACK for file:///!
- if (u.protocol() == "file")
- {
- u.setHost("");
- if (u.path().isEmpty())
- {
- u.setPath("/");
- }
- }
-
- q->setUrl(u);
- }
-}
-
-void KUrlNavigator::Private::slotProtocolChanged(const QString& protocol)
-{
- KUrl url;
- url.setProtocol(protocol);
- //url.setPath(KProtocolInfo::protocolClass(protocol) == ":local" ? "/" : "");
- url.setPath("/");
- QLinkedList<KUrlNavigatorButton*>::const_iterator it = m_navButtons.begin();
- const QLinkedList<KUrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
- while (it != itEnd) {
- (*it)->close();
- (*it)->deleteLater();
- ++it;
- }
- m_navButtons.clear();
-
- if (KProtocolInfo::protocolClass(protocol) == ":local") {
- q->setUrl(url);
- }
- else {
- if (!m_host) {
- m_protocolSeparator = new QLabel("://", q);
- appendWidget(m_protocolSeparator);
- m_host = new QLineEdit(q);
- appendWidget(m_host);
-
- connect(m_host, SIGNAL(lostFocus()),
- q, SLOT(slotRemoteHostActivated()));
- connect(m_host, SIGNAL(returnPressed()),
- q, SLOT(slotRemoteHostActivated()));
- }
- else {
- m_host->setText("");
- }
- m_protocolSeparator->show();
- m_host->show();
- m_host->setFocus();
- }
-}
-
-#if 0
-void KUrlNavigator::slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
-{
-// kDebug() << "received redirection to " << newUrl << endl;
-kDebug() << "received redirection from " << oldUrl << " to " << newUrl << endl;
-/* UrlStack::iterator it = m_urls.find(oldUrl);
- if (it != m_urls.end())
- {
- m_urls.erase(++it, m_urls.end());
- }
-
- m_urls.append(newUrl);*/
-}
-#endif
-
-void KUrlNavigator::Private::switchView()
-{
- updateContent();
- if (q->isUrlEditable()) {
- m_pathBox->setFocus();
- } else {
- q->setUrl(m_pathBox->currentText());
- }
- emit q->requestActivation();
-}
-
-void KUrlNavigator::Private::updateHistoryElem()
-{
- assert(m_historyIndex >= 0);
- const KFileItem* item = 0; // TODO: m_dolphinView->currentFileItem();
- if (item != 0) {
- HistoryElem& hist = m_history[m_historyIndex];
- hist.setCurrentFileName(item->name());
- }
-}
-
-void KUrlNavigator::Private::updateContent()
-{
- m_placesSelector->updateSelection(q->url());
-
- m_toggleButton->setToolTip(QString());
- QString path(q->url().pathOrUrl());
-
- // TODO: prevent accessing the DolphinMainWindow out from this scope
- //const QAction* action = dolphinView()->mainWindow()->actionCollection()->action("editable_location");
- // TODO: registry of default shortcuts
- //QString shortcut = action? action->shortcut().toString() : "Ctrl+L";
- const QString shortcut = "Ctrl+L";
-
- if (m_toggleButton->isChecked()) {
- delete m_protocols; m_protocols = 0;
- delete m_protocolSeparator; m_protocolSeparator = 0;
- delete m_host; m_host = 0;
- deleteButtons();
- m_filler->hide();
-
- m_toggleButton->setToolTip(i18n("Browse (%1, Escape)", shortcut));
-
- q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
- m_pathBox->show();
- m_pathBox->setUrl(q->url());
- }
- else {
- m_toggleButton->setToolTip(i18n("Edit location (%1)", shortcut));
-
- q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
- m_pathBox->hide();
- m_filler->show();
-
- // get the data from the currently selected place
- KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
-
- QString placePath;
- if (!placeUrl.isValid()) {
- // No place is a part of the current Url.
- // The following code tries to guess the place
- // path. E. g. "fish://root@192.168.0.2/var/lib" writes
- // "fish://root@192.168.0.2" to 'placePath', which leads to the
- // navigation indication 'Custom Path > var > lib".
- int idx = path.indexOf(QString("//"));
- idx = path.indexOf("/", (idx < 0) ? 0 : idx + 2);
- placePath = (idx < 0) ? path : path.left(idx);
- }
- else {
- placePath = placeUrl.pathOrUrl();
- }
- const uint len = placePath.length();
-
- // calculate the start point for the URL navigator buttons by counting
- // the slashs inside the place URL
- int slashCount = 0;
- for (uint i = 0; i < len; ++i) {
- if (placePath.at(i) == QChar('/')) {
- ++slashCount;
- }
- }
- if ((len > 0) && placePath.at(len - 1) == QChar('/')) {
- assert(slashCount > 0);
- --slashCount;
- }
-
- const KUrl currentUrl = q->url();
- if (!currentUrl.isLocalFile() && !placeUrl.isValid()) {
- QString protocol = currentUrl.protocol();
- if (!m_protocols) {
- deleteButtons();
- m_protocols = new KProtocolCombo(protocol, q);
- appendWidget(m_protocols);
- connect(m_protocols, SIGNAL(activated(QString)),
- q, SLOT(slotProtocolChanged(QString)));
- }
- else {
- m_protocols->setProtocol(protocol);
- }
- m_protocols->show();
-
- if (KProtocolInfo::protocolClass(protocol) != ":local") {
- QString hostText = currentUrl.host();
-
- if (!currentUrl.user().isEmpty()) {
- hostText = currentUrl.user() + '@' + hostText;
- }
-
- if (!m_host) {
- // ######### TODO: this code is duplicated from slotProtocolChanged!
- m_protocolSeparator = new QLabel("://", q);
- appendWidget(m_protocolSeparator);
- m_host = new QLineEdit(hostText, q);
- appendWidget(m_host);
-
- connect(m_host, SIGNAL(lostFocus()),
- q, SLOT(slotRemoteHostActivated()));
- connect(m_host, SIGNAL(returnPressed()),
- q, SLOT(slotRemoteHostActivated()));
- }
- else {
- m_host->setText(hostText);
- }
- m_protocolSeparator->show();
- m_host->show();
- }
- else {
- delete m_protocolSeparator; m_protocolSeparator = 0;
- delete m_host; m_host = 0;
- }
- }
- else if (m_protocols) {
- m_protocols->hide();
-
- if (m_host) {
- m_protocolSeparator->hide();
- m_host->hide();
- }
- }
-
- updateButtons(path, slashCount);
- }
-}
-
-void KUrlNavigator::Private::updateButtons(const QString& path, int startIndex)
-{
- QLinkedList<KUrlNavigatorButton*>::iterator it = m_navButtons.begin();
- const QLinkedList<KUrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
- bool createButton = false;
- const KUrl currentUrl = q->url();
-
- int idx = startIndex;
- bool hasNext = true;
- do {
- createButton = (it == itEnd);
-
- const QString dirName = path.section('/', idx, idx);
- const bool isFirstButton = (idx == startIndex);
- hasNext = isFirstButton || !dirName.isEmpty();
- if (hasNext) {
- QString text;
- if (isFirstButton) {
- // the first URL navigator button should get the name of the
- // place instead of the directory name
- const KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
- text = m_placesSelector->selectedPlaceText();
- if (text.isEmpty()) {
- if (currentUrl.isLocalFile()) {
- text = i18n("Custom Path");
- }
- else {
- ++idx;
- continue;
- }
- }
- }
-
- KUrlNavigatorButton* button = 0;
- if (createButton) {
- button = new KUrlNavigatorButton(idx, q);
- appendWidget(button);
- }
- else {
- button = *it;
- button->setIndex(idx);
- }
-
- if (isFirstButton) {
- button->setText(text);
- }
-
- if (createButton) {
- button->show();
- m_navButtons.append(button);
- }
- else {
- ++it;
- }
- ++idx;
- }
- } while (hasNext);
-
- // delete buttons which are not used anymore
- QLinkedList<KUrlNavigatorButton*>::iterator itBegin = it;
- while (it != itEnd) {
- (*it)->close();
- (*it)->deleteLater();
- ++it;
- }
- m_navButtons.erase(itBegin, m_navButtons.end());
-}
-
-void KUrlNavigator::Private::deleteButtons()
-{
- QLinkedList<KUrlNavigatorButton*>::iterator itBegin = m_navButtons.begin();
- QLinkedList<KUrlNavigatorButton*>::iterator itEnd = m_navButtons.end();
- QLinkedList<KUrlNavigatorButton*>::iterator it = itBegin;
- while (it != itEnd) {
- (*it)->close();
- (*it)->deleteLater();
- ++it;
- }
- m_navButtons.erase(itBegin, itEnd);
-}
-
-////
-
-
-KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel,
- const KUrl& url,
- QWidget* parent) :
- QWidget(parent),
- d( new Private(this, placesModel) )
-{
- d->m_history.prepend(HistoryElem(url));
-
- QFontMetrics fontMetrics(font());
- setMinimumHeight(fontMetrics.height() + 10);
-
- setLayout(d->m_layout);
-
- d->updateContent();
-}
-
-KUrlNavigator::~KUrlNavigator()
-{
- delete d;
-}
-
-const KUrl& KUrlNavigator::url() const
-{
- assert(!d->m_history.empty());
- return d->m_history[d->m_historyIndex].url();
-}
-
-KUrl KUrlNavigator::url(int index) const
-{
- assert(index >= 0);
- // keep scheme, hostname etc. maybe we will need this in the future
- // for e.g. browsing ftp repositories.
- KUrl newurl(url());
- newurl.setPath(QString());
- QString path(url().path());
-
- if (!path.isEmpty()) {
- if (index == 0) //prevent the last "/" from being stripped
- path = "/"; //or we end up with an empty path
- else
- path = path.section('/', 0, index);
- }
-
- newurl.setPath(path);
- return newurl;
-}
-
-QPoint KUrlNavigator::savedPosition() const
-{
- const HistoryElem& histElem = d->m_history[d->m_historyIndex];
- return QPoint( histElem.contentsX(), histElem.contentsY() );
-}
-
-int KUrlNavigator::historySize() const
-{
- return d->m_history.count();
-}
-
-void KUrlNavigator::goBack()
-{
- d->updateHistoryElem();
-
- const int count = d->m_history.count();
- if (d->m_historyIndex < count - 1) {
- ++d->m_historyIndex;
- d->updateContent();
- emit urlChanged(url());
- emit historyChanged();
- }
-}
-
-void KUrlNavigator::goForward()
-{
- if (d->m_historyIndex > 0) {
- --d->m_historyIndex;
- d->updateContent();
- emit urlChanged(url());
- emit historyChanged();
- }
-}
-
-void KUrlNavigator::goUp()
-{
- setUrl(url().upUrl());
-}
-
-void KUrlNavigator::goHome()
-{
- if (d->m_homeUrl.isEmpty())
- setUrl(QDir::homePath());
- else
- setUrl(d->m_homeUrl);
-}
-
-bool KUrlNavigator::isUrlEditable() const
-{
- return d->m_toggleButton->isChecked();
-}
-
-void KUrlNavigator::setUrlEditable(bool editable)
-{
- if (isUrlEditable() != editable) {
- d->m_toggleButton->toggle();
- d->switchView();
- }
-}
-
-void KUrlNavigator::setActive(bool active)
-{
- if (active != d->m_active) {
- d->m_active = active;
- update();
- if (active) {
- emit activated();
- }
- }
-}
-
-void KUrlNavigator::setShowHiddenFiles( bool show )
-{
- d->m_showHiddenFiles = show;
-}
-
-void KUrlNavigator::dropUrls(const KUrl::List& urls,
- const KUrl& destination)
-{
- emit urlsDropped(urls, destination);
-}
-
-void KUrlNavigator::setUrl(const KUrl& url)
-{
- QString urlStr(url.pathOrUrl());
-
- // TODO: a patch has been submitted by Filip Brcic which adjusts
- // the URL for tar and zip files. See https://bugs.kde.org/show_bug.cgi?id=142781
- // for details. The URL navigator part of the patch has not been committed yet,
- // as the URL navigator will be subject of change and
- // we might think of a more generic approach to check the protocol + MIME type for
- // this use case.
-
- //kDebug() << "setUrl(" << url << ")" << endl;
- if ( urlStr.length() > 0 && urlStr.at(0) == '~') {
- // replace '~' by the home directory
- urlStr.remove(0, 1);
- urlStr.insert(0, QDir::homePath());
- }
-
- const KUrl transformedUrl(urlStr);
-
- if (d->m_historyIndex > 0) {
- // Check whether the previous element of the history has the same Url.
- // If yes, just go forward instead of inserting a duplicate history
- // element.
- HistoryElem& prevHistoryElem = d->m_history[d->m_historyIndex - 1];
- if (transformedUrl == prevHistoryElem.url()) {
- goForward();
-// kDebug() << "goin' forward in history" << endl;
- return;
- }
- }
-
- if (this->url() == transformedUrl) {
- // don't insert duplicate history elements
-// kDebug() << "current url == transformedUrl" << endl;
- return;
- }
-
- d->updateHistoryElem();
- d->m_history.insert(d->m_historyIndex, HistoryElem(transformedUrl));
-
- d->updateContent();
-
- emit urlChanged(transformedUrl);
- emit historyChanged();
-
- // Prevent an endless growing of the history: remembering
- // the last 100 Urls should be enough...
- if (d->m_historyIndex > 100) {
- d->m_history.removeFirst();
- --d->m_historyIndex;
- }
-
-/* kDebug() << "history starting ====================" << endl;
- int i = 0;
- for (QValueListIterator<KUrlNavigator::HistoryElem> it = d->m_history.begin();
- it != d->m_history.end();
- ++it, ++i)
- {
- kDebug() << i << ": " << (*it).url() << endl;
- }
- kDebug() << "history done ========================" << endl;*/
-
- requestActivation();
-}
-
-void KUrlNavigator::requestActivation()
-{
- setActive(true);
-}
-
-void KUrlNavigator::storeContentsPosition(int x, int y)
-{
- HistoryElem& hist = d->m_history[d->m_historyIndex];
- hist.setContentsX(x);
- hist.setContentsY(y);
-}
-
-void KUrlNavigator::keyReleaseEvent(QKeyEvent* event)
-{
- QWidget::keyReleaseEvent(event);
- if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
- setUrlEditable(false);
- }
-}
-
-void KUrlNavigator::mouseReleaseEvent(QMouseEvent* event)
-{
- if (event->button() == Qt::MidButton) {
- QClipboard* clipboard = QApplication::clipboard();
- const QMimeData* mimeData = clipboard->mimeData();
- if (mimeData->hasText()) {
- const QString text = mimeData->text();
- setUrl(KUrl(text));
- }
- }
- QWidget::mouseReleaseEvent(event);
-}
-
-bool KUrlNavigator::isActive() const
-{
- return d->m_active;
-}
-
-bool KUrlNavigator::showHiddenFiles() const
-{
- return d->m_showHiddenFiles;
-}
-
-void KUrlNavigator::setHomeUrl(const QString& homeUrl)
-{
- d->m_homeUrl = homeUrl;
-}
-
-#include "kurlnavigator.moc"