1 /***************************************************************************
2 * Copyright (C) 2006 by Peter Penz (<peter.penz@gmx.at>) *
3 * Copyright (C) 2006 by Aaron J. Seigo (<aseigo@kde.org>) *
4 * Copyright (C) 2006 by Patrice Tremblay *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the *
18 * Free Software Foundation, Inc., *
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
20 ***************************************************************************/
22 #include "urlnavigator.h"
24 #include "bookmarkselector.h"
25 #include "protocolcombo.h"
26 #include "urlnavigatorbutton.h"
30 #include <kfileitem.h>
33 #include <kprotocolinfo.h>
34 #include <kurlcombobox.h>
35 #include <kurlcompletion.h>
37 #include <QApplication>
40 #include <QHBoxLayout>
43 #include <QLinkedList>
44 #include <QMouseEvent>
45 #include <QToolButton>
48 * @brief Represents the history element of an URL.
50 * A history element contains the URL, the name of the current file
51 * (the 'current file' is the file where the cursor is located) and
52 * the x- and y-position of the content.
57 HistoryElem(const KUrl
& url
);
58 ~HistoryElem(); // non virtual
60 const KUrl
& url() const { return m_url
; }
62 void setCurrentFileName(const QString
& name
) { m_currentFileName
= name
; }
63 const QString
& currentFileName() const { return m_currentFileName
; }
65 void setContentsX(int x
) { m_contentsX
= x
; }
66 int contentsX() const { return m_contentsX
; }
68 void setContentsY(int y
) { m_contentsY
= y
; }
69 int contentsY() const { return m_contentsY
; }
73 QString m_currentFileName
;
78 HistoryElem::HistoryElem() :
86 HistoryElem::HistoryElem(const KUrl
& url
) :
94 HistoryElem::~HistoryElem()
98 class UrlNavigator::Private
101 Private(UrlNavigator
* q
, KBookmarkManager
* bookmarkManager
);
103 void slotReturnPressed(const QString
&);
104 void slotRemoteHostActivated();
105 void slotProtocolChanged(const QString
&);
108 * Appends the widget at the end of the URL navigator. It is assured
109 * that the filler widget remains as last widget to fill the remaining
112 void appendWidget(QWidget
* widget
);
115 * Switches the navigation bar between the breadcrumb view and the
116 * traditional view (see setUrlEditable()) and is connected to the clicked signal
117 * of the navigation bar button.
122 * Updates the history element with the current file item
123 * and the contents position.
125 void updateHistoryElem();
126 void updateContent();
129 * Updates all buttons to have one button for each part of the
130 * path \a path. Existing buttons, which are available by m_navButtons,
131 * are reused if possible. If the path is longer, new buttons will be
132 * created, if the path is shorter, the remaining buttons will be deleted.
133 * @param startIndex Start index of path part (/), where the buttons
134 * should be created for each following part.
136 void updateButtons(const QString
& path
, int startIndex
);
139 * Deletes all URL navigator buttons. m_navButtons is
140 * empty after this operation.
142 void deleteButtons();
146 bool m_showHiddenFiles
;
149 QHBoxLayout
* m_layout
;
151 QList
<HistoryElem
> m_history
;
152 QToolButton
* m_toggleButton
;
153 BookmarkSelector
* m_bookmarkSelector
;
154 KUrlComboBox
* m_pathBox
;
155 ProtocolCombo
* m_protocols
;
156 QLabel
* m_protocolSeparator
;
158 QLinkedList
<UrlNavigatorButton
*> m_navButtons
;
165 UrlNavigator::Private::Private(UrlNavigator
* q
, KBookmarkManager
* bookmarkManager
)
168 m_showHiddenFiles(false),
170 m_layout(new QHBoxLayout
),
172 m_protocolSeparator(0),
177 m_layout
->setSpacing(0);
178 m_layout
->setMargin(0);
180 // initialize toggle button which switches between the breadcrumb view
181 // and the traditional view
182 m_toggleButton
= new QToolButton();
183 m_toggleButton
->setCheckable(true);
184 m_toggleButton
->setAutoRaise(true);
185 m_toggleButton
->setIcon(KIcon("editinput")); // TODO: is just a placeholder icon (?)
186 m_toggleButton
->setFocusPolicy(Qt::NoFocus
);
187 m_toggleButton
->setMinimumHeight(q
->minimumHeight());
188 connect(m_toggleButton
, SIGNAL(clicked()),
189 q
, SLOT(switchView()));
191 // initialize the bookmark selector
192 m_bookmarkSelector
= new BookmarkSelector(q
, bookmarkManager
);
193 connect(m_bookmarkSelector
, SIGNAL(bookmarkActivated(const KUrl
&)),
194 q
, SLOT(setUrl(const KUrl
&)));
196 // initialize the path box of the traditional view
197 m_pathBox
= new KUrlComboBox(KUrlComboBox::Directories
, true, q
);
199 KUrlCompletion
* kurlCompletion
= new KUrlCompletion(KUrlCompletion::DirCompletion
);
200 m_pathBox
->setCompletionObject(kurlCompletion
);
201 m_pathBox
->setAutoDeleteCompletionObject(true);
203 connect(m_pathBox
, SIGNAL(returnPressed(QString
)),
204 q
, SLOT(slotReturnPressed(QString
)));
205 connect(m_pathBox
, SIGNAL(urlActivated(KUrl
)),
206 q
, SLOT(setUrl(KUrl
)));
208 // Append a filler widget at the end, which automatically resizes to the
209 // maximum available width. This assures that the URL navigator uses the
210 // whole width, so that the clipboard content can be dropped.
211 m_filler
= new QWidget();
212 m_filler
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Preferred
);
214 m_layout
->addWidget(m_toggleButton
);
215 m_layout
->addWidget(m_bookmarkSelector
);
216 m_layout
->addWidget(m_pathBox
);
217 m_layout
->addWidget(m_filler
);
220 void UrlNavigator::Private::appendWidget(QWidget
* widget
)
222 m_layout
->insertWidget(m_layout
->count() - 1, widget
);
225 void UrlNavigator::Private::slotReturnPressed(const QString
& text
)
227 // Parts of the following code have been taken
228 // from the class KateFileSelector located in
229 // kate/app/katefileselector.hpp of Kate.
230 // Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
231 // Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
232 // Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
235 if (typedUrl
.hasPass()) {
236 typedUrl
.setPass(QString());
239 QStringList urls
= m_pathBox
->urls();
240 urls
.removeAll(typedUrl
.url());
241 urls
.prepend(typedUrl
.url());
242 m_pathBox
->setUrls(urls
, KUrlComboBox::RemoveBottom
);
245 // The URL might have been adjusted by UrlNavigator::setUrl(), hence
246 // synchronize the result in the path box.
247 m_pathBox
->setUrl(q
->url());
250 void UrlNavigator::Private::slotRemoteHostActivated()
254 QString host
= m_host
->text();
257 int marker
= host
.indexOf("@");
260 user
= host
.left(marker
);
262 host
= host
.right(host
.length() - marker
- 1);
265 marker
= host
.indexOf("/");
268 u
.setPath(host
.right(host
.length() - marker
));
269 host
.truncate(marker
);
276 if (m_protocols
->currentProtocol() != u
.protocol() ||
280 u
.setProtocol(m_protocols
->currentProtocol());
281 u
.setHost(m_host
->text());
283 //TODO: get rid of this HACK for file:///!
284 if (u
.protocol() == "file")
287 if (u
.path().isEmpty())
297 void UrlNavigator::Private::slotProtocolChanged(const QString
& protocol
)
300 url
.setProtocol(protocol
);
301 //url.setPath(KProtocolInfo::protocolClass(protocol) == ":local" ? "/" : "");
303 QLinkedList
<UrlNavigatorButton
*>::const_iterator it
= m_navButtons
.begin();
304 const QLinkedList
<UrlNavigatorButton
*>::const_iterator itEnd
= m_navButtons
.end();
305 while (it
!= itEnd
) {
307 (*it
)->deleteLater();
310 m_navButtons
.clear();
312 if (KProtocolInfo::protocolClass(protocol
) == ":local") {
317 m_protocolSeparator
= new QLabel("://", q
);
318 appendWidget(m_protocolSeparator
);
319 m_host
= new QLineEdit(q
);
320 appendWidget(m_host
);
322 connect(m_host
, SIGNAL(lostFocus()),
323 q
, SLOT(slotRemoteHostActivated()));
324 connect(m_host
, SIGNAL(returnPressed()),
325 q
, SLOT(slotRemoteHostActivated()));
330 m_protocolSeparator
->show();
337 void UrlNavigator::slotRedirection(const KUrl
& oldUrl
, const KUrl
& newUrl
)
339 // kDebug() << "received redirection to " << newUrl << endl;
340 kDebug() << "received redirection from " << oldUrl
<< " to " << newUrl
<< endl
;
341 /* UrlStack::iterator it = m_urls.find(oldUrl);
342 if (it != m_urls.end())
344 m_urls.erase(++it, m_urls.end());
347 m_urls.append(newUrl);*/
351 void UrlNavigator::Private::switchView()
354 if (q
->isUrlEditable()) {
355 m_pathBox
->setFocus();
357 q
->setUrl(m_pathBox
->currentText());
359 emit q
->requestActivation();
362 void UrlNavigator::Private::updateHistoryElem()
364 assert(m_historyIndex
>= 0);
365 const KFileItem
* item
= 0; // TODO: m_dolphinView->currentFileItem();
367 HistoryElem
& hist
= m_history
[m_historyIndex
];
368 hist
.setCurrentFileName(item
->name());
372 void UrlNavigator::Private::updateContent()
374 m_bookmarkSelector
->updateSelection(q
->url());
376 m_toggleButton
->setToolTip(QString());
377 QString
path(q
->url().pathOrUrl());
379 // TODO: prevent accessing the DolphinMainWindow out from this scope
380 //const QAction* action = dolphinView()->mainWindow()->actionCollection()->action("editable_location");
381 // TODO: registry of default shortcuts
382 //QString shortcut = action? action->shortcut().toString() : "Ctrl+L";
383 const QString shortcut
= "Ctrl+L";
385 if (m_toggleButton
->isChecked()) {
386 delete m_protocols
; m_protocols
= 0;
387 delete m_protocolSeparator
; m_protocolSeparator
= 0;
388 delete m_host
; m_host
= 0;
392 m_toggleButton
->setToolTip(i18n("Browse (%1, Escape)", shortcut
));
394 q
->setSizePolicy(QSizePolicy::Minimum
, QSizePolicy::Fixed
);
396 m_pathBox
->setUrl(q
->url());
399 m_toggleButton
->setToolTip(i18n("Edit location (%1)", shortcut
));
401 q
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
405 // get the data from the currently selected bookmark
406 KBookmark bookmark
= m_bookmarkSelector
->selectedBookmark();
408 QString bookmarkPath
;
409 if (bookmark
.isNull()) {
410 // No bookmark is a part of the current Url.
411 // The following code tries to guess the bookmark
412 // path. E. g. "fish://root@192.168.0.2/var/lib" writes
413 // "fish://root@192.168.0.2" to 'bookmarkPath', which leads to the
414 // navigation indication 'Custom Path > var > lib".
415 int idx
= path
.indexOf(QString("//"));
416 idx
= path
.indexOf("/", (idx
< 0) ? 0 : idx
+ 2);
417 bookmarkPath
= (idx
< 0) ? path
: path
.left(idx
);
420 bookmarkPath
= bookmark
.url().pathOrUrl();
422 const uint len
= bookmarkPath
.length();
424 // calculate the start point for the URL navigator buttons by counting
425 // the slashs inside the bookmark URL
427 for (uint i
= 0; i
< len
; ++i
) {
428 if (bookmarkPath
.at(i
) == QChar('/')) {
432 if ((len
> 0) && bookmarkPath
.at(len
- 1) == QChar('/')) {
433 assert(slashCount
> 0);
437 const KUrl currentUrl
= q
->url();
438 if (!currentUrl
.isLocalFile() && bookmark
.isNull()) {
439 QString protocol
= currentUrl
.protocol();
442 m_protocols
= new ProtocolCombo(protocol
, q
);
443 appendWidget(m_protocols
);
444 connect(m_protocols
, SIGNAL(activated(QString
)),
445 q
, SLOT(slotProtocolChanged(QString
)));
448 m_protocols
->setProtocol(protocol
);
452 if (KProtocolInfo::protocolClass(protocol
) != ":local") {
453 QString hostText
= currentUrl
.host();
455 if (!currentUrl
.user().isEmpty()) {
456 hostText
= currentUrl
.user() + '@' + hostText
;
460 // ######### TODO: this code is duplicated from slotProtocolChanged!
461 m_protocolSeparator
= new QLabel("://", q
);
462 appendWidget(m_protocolSeparator
);
463 m_host
= new QLineEdit(hostText
, q
);
464 appendWidget(m_host
);
466 connect(m_host
, SIGNAL(lostFocus()),
467 q
, SLOT(slotRemoteHostActivated()));
468 connect(m_host
, SIGNAL(returnPressed()),
469 q
, SLOT(slotRemoteHostActivated()));
472 m_host
->setText(hostText
);
474 m_protocolSeparator
->show();
478 delete m_protocolSeparator
; m_protocolSeparator
= 0;
479 delete m_host
; m_host
= 0;
482 else if (m_protocols
) {
486 m_protocolSeparator
->hide();
491 updateButtons(path
, slashCount
);
495 void UrlNavigator::Private::updateButtons(const QString
& path
, int startIndex
)
497 QLinkedList
<UrlNavigatorButton
*>::iterator it
= m_navButtons
.begin();
498 const QLinkedList
<UrlNavigatorButton
*>::const_iterator itEnd
= m_navButtons
.end();
499 bool createButton
= false;
500 const KUrl currentUrl
= q
->url();
502 int idx
= startIndex
;
505 createButton
= (it
== itEnd
);
507 const QString dirName
= path
.section('/', idx
, idx
);
508 const bool isFirstButton
= (idx
== startIndex
);
509 hasNext
= isFirstButton
|| !dirName
.isEmpty();
513 // the first URL navigator button should get the name of the
514 // bookmark instead of the directory name
515 const KBookmark bookmark
= m_bookmarkSelector
->selectedBookmark();
516 text
= bookmark
.text();
517 if (text
.isEmpty()) {
518 if (currentUrl
.isLocalFile()) {
519 text
= i18n("Custom Path");
528 UrlNavigatorButton
* button
= 0;
530 button
= new UrlNavigatorButton(idx
, q
);
531 appendWidget(button
);
535 button
->setIndex(idx
);
539 button
->setText(text
);
544 m_navButtons
.append(button
);
553 // delete buttons which are not used anymore
554 QLinkedList
<UrlNavigatorButton
*>::iterator itBegin
= it
;
555 while (it
!= itEnd
) {
557 (*it
)->deleteLater();
560 m_navButtons
.erase(itBegin
, m_navButtons
.end());
563 void UrlNavigator::Private::deleteButtons()
565 QLinkedList
<UrlNavigatorButton
*>::iterator itBegin
= m_navButtons
.begin();
566 QLinkedList
<UrlNavigatorButton
*>::iterator itEnd
= m_navButtons
.end();
567 QLinkedList
<UrlNavigatorButton
*>::iterator it
= itBegin
;
568 while (it
!= itEnd
) {
570 (*it
)->deleteLater();
573 m_navButtons
.erase(itBegin
, itEnd
);
579 UrlNavigator::UrlNavigator(KBookmarkManager
* bookmarkManager
,
583 d( new Private(this, bookmarkManager
) )
585 d
->m_history
.prepend(HistoryElem(url
));
587 QFontMetrics
fontMetrics(font());
588 setMinimumHeight(fontMetrics
.height() + 10);
590 setLayout(d
->m_layout
);
595 UrlNavigator::~UrlNavigator()
600 const KUrl
& UrlNavigator::url() const
602 assert(!d
->m_history
.empty());
603 return d
->m_history
[d
->m_historyIndex
].url();
606 KUrl
UrlNavigator::url(int index
) const
609 // keep scheme, hostname etc. maybe we will need this in the future
610 // for e.g. browsing ftp repositories.
612 newurl
.setPath(QString());
613 QString
path(url().path());
615 if (!path
.isEmpty()) {
616 if (index
== 0) //prevent the last "/" from being stripped
617 path
= "/"; //or we end up with an empty path
619 path
= path
.section('/', 0, index
);
622 newurl
.setPath(path
);
626 QPoint
UrlNavigator::savedPosition() const
628 const HistoryElem
& histElem
= d
->m_history
[d
->m_historyIndex
];
629 return QPoint( histElem
.contentsX(), histElem
.contentsY() );
632 int UrlNavigator::historySize() const
634 return d
->m_history
.count();
637 void UrlNavigator::goBack()
639 d
->updateHistoryElem();
641 const int count
= d
->m_history
.count();
642 if (d
->m_historyIndex
< count
- 1) {
645 emit
urlChanged(url());
646 emit
historyChanged();
650 void UrlNavigator::goForward()
652 if (d
->m_historyIndex
> 0) {
655 emit
urlChanged(url());
656 emit
historyChanged();
660 void UrlNavigator::goUp()
662 setUrl(url().upUrl());
665 void UrlNavigator::goHome()
667 if (d
->m_homeUrl
.isEmpty())
668 setUrl(QDir::homePath());
670 setUrl(d
->m_homeUrl
);
673 bool UrlNavigator::isUrlEditable() const
675 return d
->m_toggleButton
->isChecked();
678 void UrlNavigator::setUrlEditable(bool editable
)
680 if (isUrlEditable() != editable
) {
681 d
->m_toggleButton
->toggle();
686 void UrlNavigator::setActive(bool active
)
688 if (active
!= d
->m_active
) {
689 d
->m_active
= active
;
697 void UrlNavigator::setShowHiddenFiles( bool show
)
699 d
->m_showHiddenFiles
= show
;
702 void UrlNavigator::dropUrls(const KUrl::List
& urls
,
703 const KUrl
& destination
)
705 emit
urlsDropped(urls
, destination
);
708 void UrlNavigator::setUrl(const KUrl
& url
)
710 QString
urlStr(url
.pathOrUrl());
712 // TODO: a patch has been submitted by Filip Brcic which adjusts
713 // the URL for tar and zip files. See https://bugs.kde.org/show_bug.cgi?id=142781
714 // for details. The URL navigator part of the patch has not been committed yet,
715 // as the URL navigator will be subject of change and
716 // we might think of a more generic approach to check the protocol + MIME type for
719 //kDebug() << "setUrl(" << url << ")" << endl;
720 if ( urlStr
.length() > 0 && urlStr
.at(0) == '~') {
721 // replace '~' by the home directory
723 urlStr
.insert(0, QDir::homePath());
726 const KUrl
transformedUrl(urlStr
);
728 if (d
->m_historyIndex
> 0) {
729 // Check whether the previous element of the history has the same Url.
730 // If yes, just go forward instead of inserting a duplicate history
732 HistoryElem
& prevHistoryElem
= d
->m_history
[d
->m_historyIndex
- 1];
733 if (transformedUrl
== prevHistoryElem
.url()) {
735 // kDebug() << "goin' forward in history" << endl;
740 if (this->url() == transformedUrl
) {
741 // don't insert duplicate history elements
742 // kDebug() << "current url == transformedUrl" << endl;
746 d
->updateHistoryElem();
747 d
->m_history
.insert(d
->m_historyIndex
, HistoryElem(transformedUrl
));
751 emit
urlChanged(transformedUrl
);
752 emit
historyChanged();
754 // Prevent an endless growing of the history: remembering
755 // the last 100 Urls should be enough...
756 if (d
->m_historyIndex
> 100) {
757 d
->m_history
.removeFirst();
761 /* kDebug() << "history starting ====================" << endl;
763 for (QValueListIterator<UrlNavigator::HistoryElem> it = d->m_history.begin();
764 it != d->m_history.end();
767 kDebug() << i << ": " << (*it).url() << endl;
769 kDebug() << "history done ========================" << endl;*/
774 void UrlNavigator::requestActivation()
779 void UrlNavigator::storeContentsPosition(int x
, int y
)
781 HistoryElem
& hist
= d
->m_history
[d
->m_historyIndex
];
782 hist
.setContentsX(x
);
783 hist
.setContentsY(y
);
786 void UrlNavigator::keyReleaseEvent(QKeyEvent
* event
)
788 QWidget::keyReleaseEvent(event
);
789 if (isUrlEditable() && (event
->key() == Qt::Key_Escape
)) {
790 setUrlEditable(false);
794 void UrlNavigator::mouseReleaseEvent(QMouseEvent
* event
)
796 if (event
->button() == Qt::MidButton
) {
797 QClipboard
* clipboard
= QApplication::clipboard();
798 const QMimeData
* mimeData
= clipboard
->mimeData();
799 if (mimeData
->hasText()) {
800 const QString text
= mimeData
->text();
804 QWidget::mouseReleaseEvent(event
);
807 bool UrlNavigator::isActive() const
812 bool UrlNavigator::showHiddenFiles() const
814 return d
->m_showHiddenFiles
;
817 void UrlNavigator::setHomeUrl(const QString
& homeUrl
)
819 d
->m_homeUrl
= homeUrl
;
822 #include "urlnavigator.moc"