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 *
5 * Copyright (C) 2007 by Kevin Ottens (ervin@kde.org) *
7 * This program is free software; you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation; either version 2 of the License, or *
10 * (at your option) any later version. *
12 * This program is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
17 * You should have received a copy of the GNU General Public License *
18 * along with this program; if not, write to the *
19 * Free Software Foundation, Inc., *
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
21 ***************************************************************************/
23 #include "kurlnavigator.h"
25 #include "kfileplacesselector_p.h"
26 #include "kprotocolcombo_p.h"
27 #include "kurlnavigatorbutton_p.h"
31 #include <kfileitem.h>
34 #include <kprotocolinfo.h>
35 #include <kurlcombobox.h>
36 #include <kurlcompletion.h>
38 #include <QApplication>
41 #include <QHBoxLayout>
44 #include <QLinkedList>
45 #include <QMouseEvent>
46 #include <QToolButton>
49 * @brief Represents the history element of an URL.
51 * A history element contains the URL, the name of the current file
52 * (the 'current file' is the file where the cursor is located) and
53 * the x- and y-position of the content.
58 HistoryElem(const KUrl
& url
);
59 ~HistoryElem(); // non virtual
61 const KUrl
& url() const { return m_url
; }
63 void setCurrentFileName(const QString
& name
) { m_currentFileName
= name
; }
64 const QString
& currentFileName() const { return m_currentFileName
; }
66 void setContentsX(int x
) { m_contentsX
= x
; }
67 int contentsX() const { return m_contentsX
; }
69 void setContentsY(int y
) { m_contentsY
= y
; }
70 int contentsY() const { return m_contentsY
; }
74 QString m_currentFileName
;
79 HistoryElem::HistoryElem() :
87 HistoryElem::HistoryElem(const KUrl
& url
) :
95 HistoryElem::~HistoryElem()
99 class KUrlNavigator::Private
102 Private(KUrlNavigator
* q
, KFilePlacesModel
* placesModel
);
104 void slotReturnPressed(const QString
&);
105 void slotRemoteHostActivated();
106 void slotProtocolChanged(const QString
&);
109 * Appends the widget at the end of the URL navigator. It is assured
110 * that the filler widget remains as last widget to fill the remaining
113 void appendWidget(QWidget
* widget
);
116 * Switches the navigation bar between the breadcrumb view and the
117 * traditional view (see setUrlEditable()) and is connected to the clicked signal
118 * of the navigation bar button.
123 * Updates the history element with the current file item
124 * and the contents position.
126 void updateHistoryElem();
127 void updateContent();
130 * Updates all buttons to have one button for each part of the
131 * path \a path. Existing buttons, which are available by m_navButtons,
132 * are reused if possible. If the path is longer, new buttons will be
133 * created, if the path is shorter, the remaining buttons will be deleted.
134 * @param startIndex Start index of path part (/), where the buttons
135 * should be created for each following part.
137 void updateButtons(const QString
& path
, int startIndex
);
140 * Deletes all URL navigator buttons. m_navButtons is
141 * empty after this operation.
143 void deleteButtons();
147 bool m_showHiddenFiles
;
150 QHBoxLayout
* m_layout
;
152 QList
<HistoryElem
> m_history
;
153 QToolButton
* m_toggleButton
;
154 KFilePlacesSelector
* m_placesSelector
;
155 KUrlComboBox
* m_pathBox
;
156 KProtocolCombo
* m_protocols
;
157 QLabel
* m_protocolSeparator
;
159 QLinkedList
<KUrlNavigatorButton
*> m_navButtons
;
166 KUrlNavigator::Private::Private(KUrlNavigator
* q
, KFilePlacesModel
* placesModel
)
169 m_showHiddenFiles(false),
171 m_layout(new QHBoxLayout
),
173 m_protocolSeparator(0),
178 m_layout
->setSpacing(0);
179 m_layout
->setMargin(0);
181 // initialize toggle button which switches between the breadcrumb view
182 // and the traditional view
183 m_toggleButton
= new QToolButton();
184 m_toggleButton
->setCheckable(true);
185 m_toggleButton
->setAutoRaise(true);
186 m_toggleButton
->setIcon(KIcon("editinput")); // TODO: is just a placeholder icon (?)
187 m_toggleButton
->setFocusPolicy(Qt::NoFocus
);
188 m_toggleButton
->setMinimumHeight(q
->minimumHeight());
189 connect(m_toggleButton
, SIGNAL(clicked()),
190 q
, SLOT(switchView()));
192 // initialize the places selector
193 m_placesSelector
= new KFilePlacesSelector(q
, placesModel
);
194 connect(m_placesSelector
, SIGNAL(placeActivated(const KUrl
&)),
195 q
, SLOT(setUrl(const KUrl
&)));
197 // initialize the path box of the traditional view
198 m_pathBox
= new KUrlComboBox(KUrlComboBox::Directories
, true, q
);
200 KUrlCompletion
* kurlCompletion
= new KUrlCompletion(KUrlCompletion::DirCompletion
);
201 m_pathBox
->setCompletionObject(kurlCompletion
);
202 m_pathBox
->setAutoDeleteCompletionObject(true);
204 connect(m_pathBox
, SIGNAL(returnPressed(QString
)),
205 q
, SLOT(slotReturnPressed(QString
)));
206 connect(m_pathBox
, SIGNAL(urlActivated(KUrl
)),
207 q
, SLOT(setUrl(KUrl
)));
209 // Append a filler widget at the end, which automatically resizes to the
210 // maximum available width. This assures that the URL navigator uses the
211 // whole width, so that the clipboard content can be dropped.
212 m_filler
= new QWidget();
213 m_filler
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Preferred
);
215 m_layout
->addWidget(m_toggleButton
);
216 m_layout
->addWidget(m_placesSelector
);
217 m_layout
->addWidget(m_pathBox
);
218 m_layout
->addWidget(m_filler
);
221 void KUrlNavigator::Private::appendWidget(QWidget
* widget
)
223 m_layout
->insertWidget(m_layout
->count() - 1, widget
);
226 void KUrlNavigator::Private::slotReturnPressed(const QString
& text
)
228 // Parts of the following code have been taken
229 // from the class KateFileSelector located in
230 // kate/app/katefileselector.hpp of Kate.
231 // Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
232 // Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
233 // Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
236 if (typedUrl
.hasPass()) {
237 typedUrl
.setPass(QString());
240 QStringList urls
= m_pathBox
->urls();
241 urls
.removeAll(typedUrl
.url());
242 urls
.prepend(typedUrl
.url());
243 m_pathBox
->setUrls(urls
, KUrlComboBox::RemoveBottom
);
246 // The URL might have been adjusted by KUrlNavigator::setUrl(), hence
247 // synchronize the result in the path box.
248 m_pathBox
->setUrl(q
->url());
251 void KUrlNavigator::Private::slotRemoteHostActivated()
255 QString host
= m_host
->text();
258 int marker
= host
.indexOf("@");
261 user
= host
.left(marker
);
263 host
= host
.right(host
.length() - marker
- 1);
266 marker
= host
.indexOf("/");
269 u
.setPath(host
.right(host
.length() - marker
));
270 host
.truncate(marker
);
277 if (m_protocols
->currentProtocol() != u
.protocol() ||
281 u
.setProtocol(m_protocols
->currentProtocol());
282 u
.setHost(m_host
->text());
284 //TODO: get rid of this HACK for file:///!
285 if (u
.protocol() == "file")
288 if (u
.path().isEmpty())
298 void KUrlNavigator::Private::slotProtocolChanged(const QString
& protocol
)
301 url
.setProtocol(protocol
);
302 //url.setPath(KProtocolInfo::protocolClass(protocol) == ":local" ? "/" : "");
304 QLinkedList
<KUrlNavigatorButton
*>::const_iterator it
= m_navButtons
.begin();
305 const QLinkedList
<KUrlNavigatorButton
*>::const_iterator itEnd
= m_navButtons
.end();
306 while (it
!= itEnd
) {
308 (*it
)->deleteLater();
311 m_navButtons
.clear();
313 if (KProtocolInfo::protocolClass(protocol
) == ":local") {
318 m_protocolSeparator
= new QLabel("://", q
);
319 appendWidget(m_protocolSeparator
);
320 m_host
= new QLineEdit(q
);
321 appendWidget(m_host
);
323 connect(m_host
, SIGNAL(lostFocus()),
324 q
, SLOT(slotRemoteHostActivated()));
325 connect(m_host
, SIGNAL(returnPressed()),
326 q
, SLOT(slotRemoteHostActivated()));
331 m_protocolSeparator
->show();
338 void KUrlNavigator::slotRedirection(const KUrl
& oldUrl
, const KUrl
& newUrl
)
340 // kDebug() << "received redirection to " << newUrl << endl;
341 kDebug() << "received redirection from " << oldUrl
<< " to " << newUrl
<< endl
;
342 /* UrlStack::iterator it = m_urls.find(oldUrl);
343 if (it != m_urls.end())
345 m_urls.erase(++it, m_urls.end());
348 m_urls.append(newUrl);*/
352 void KUrlNavigator::Private::switchView()
355 if (q
->isUrlEditable()) {
356 m_pathBox
->setFocus();
358 q
->setUrl(m_pathBox
->currentText());
360 emit q
->requestActivation();
363 void KUrlNavigator::Private::updateHistoryElem()
365 assert(m_historyIndex
>= 0);
366 const KFileItem
* item
= 0; // TODO: m_dolphinView->currentFileItem();
368 HistoryElem
& hist
= m_history
[m_historyIndex
];
369 hist
.setCurrentFileName(item
->name());
373 void KUrlNavigator::Private::updateContent()
375 m_placesSelector
->updateSelection(q
->url());
377 m_toggleButton
->setToolTip(QString());
378 QString
path(q
->url().pathOrUrl());
380 // TODO: prevent accessing the DolphinMainWindow out from this scope
381 //const QAction* action = dolphinView()->mainWindow()->actionCollection()->action("editable_location");
382 // TODO: registry of default shortcuts
383 //QString shortcut = action? action->shortcut().toString() : "Ctrl+L";
384 const QString shortcut
= "Ctrl+L";
386 if (m_toggleButton
->isChecked()) {
387 delete m_protocols
; m_protocols
= 0;
388 delete m_protocolSeparator
; m_protocolSeparator
= 0;
389 delete m_host
; m_host
= 0;
393 m_toggleButton
->setToolTip(i18n("Browse (%1, Escape)", shortcut
));
395 q
->setSizePolicy(QSizePolicy::Minimum
, QSizePolicy::Fixed
);
397 m_pathBox
->setUrl(q
->url());
400 m_toggleButton
->setToolTip(i18n("Edit location (%1)", shortcut
));
402 q
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
406 // get the data from the currently selected place
407 KUrl placeUrl
= m_placesSelector
->selectedPlaceUrl();
410 if (!placeUrl
.isValid()) {
411 // No place is a part of the current Url.
412 // The following code tries to guess the place
413 // path. E. g. "fish://root@192.168.0.2/var/lib" writes
414 // "fish://root@192.168.0.2" to 'placePath', which leads to the
415 // navigation indication 'Custom Path > var > lib".
416 int idx
= path
.indexOf(QString("//"));
417 idx
= path
.indexOf("/", (idx
< 0) ? 0 : idx
+ 2);
418 placePath
= (idx
< 0) ? path
: path
.left(idx
);
421 placePath
= placeUrl
.pathOrUrl();
423 const uint len
= placePath
.length();
425 // calculate the start point for the URL navigator buttons by counting
426 // the slashs inside the place URL
428 for (uint i
= 0; i
< len
; ++i
) {
429 if (placePath
.at(i
) == QChar('/')) {
433 if ((len
> 0) && placePath
.at(len
- 1) == QChar('/')) {
434 assert(slashCount
> 0);
438 const KUrl currentUrl
= q
->url();
439 if (!currentUrl
.isLocalFile() && !placeUrl
.isValid()) {
440 QString protocol
= currentUrl
.protocol();
443 m_protocols
= new KProtocolCombo(protocol
, q
);
444 appendWidget(m_protocols
);
445 connect(m_protocols
, SIGNAL(activated(QString
)),
446 q
, SLOT(slotProtocolChanged(QString
)));
449 m_protocols
->setProtocol(protocol
);
453 if (KProtocolInfo::protocolClass(protocol
) != ":local") {
454 QString hostText
= currentUrl
.host();
456 if (!currentUrl
.user().isEmpty()) {
457 hostText
= currentUrl
.user() + '@' + hostText
;
461 // ######### TODO: this code is duplicated from slotProtocolChanged!
462 m_protocolSeparator
= new QLabel("://", q
);
463 appendWidget(m_protocolSeparator
);
464 m_host
= new QLineEdit(hostText
, q
);
465 appendWidget(m_host
);
467 connect(m_host
, SIGNAL(lostFocus()),
468 q
, SLOT(slotRemoteHostActivated()));
469 connect(m_host
, SIGNAL(returnPressed()),
470 q
, SLOT(slotRemoteHostActivated()));
473 m_host
->setText(hostText
);
475 m_protocolSeparator
->show();
479 delete m_protocolSeparator
; m_protocolSeparator
= 0;
480 delete m_host
; m_host
= 0;
483 else if (m_protocols
) {
487 m_protocolSeparator
->hide();
492 updateButtons(path
, slashCount
);
496 void KUrlNavigator::Private::updateButtons(const QString
& path
, int startIndex
)
498 QLinkedList
<KUrlNavigatorButton
*>::iterator it
= m_navButtons
.begin();
499 const QLinkedList
<KUrlNavigatorButton
*>::const_iterator itEnd
= m_navButtons
.end();
500 bool createButton
= false;
501 const KUrl currentUrl
= q
->url();
503 int idx
= startIndex
;
506 createButton
= (it
== itEnd
);
508 const QString dirName
= path
.section('/', idx
, idx
);
509 const bool isFirstButton
= (idx
== startIndex
);
510 hasNext
= isFirstButton
|| !dirName
.isEmpty();
514 // the first URL navigator button should get the name of the
515 // place instead of the directory name
516 const KUrl placeUrl
= m_placesSelector
->selectedPlaceUrl();
517 text
= m_placesSelector
->selectedPlaceText();
518 if (text
.isEmpty()) {
519 if (currentUrl
.isLocalFile()) {
520 text
= i18n("Custom Path");
529 KUrlNavigatorButton
* button
= 0;
531 button
= new KUrlNavigatorButton(idx
, q
);
532 appendWidget(button
);
536 button
->setIndex(idx
);
540 button
->setText(text
);
545 m_navButtons
.append(button
);
554 // delete buttons which are not used anymore
555 QLinkedList
<KUrlNavigatorButton
*>::iterator itBegin
= it
;
556 while (it
!= itEnd
) {
558 (*it
)->deleteLater();
561 m_navButtons
.erase(itBegin
, m_navButtons
.end());
564 void KUrlNavigator::Private::deleteButtons()
566 QLinkedList
<KUrlNavigatorButton
*>::iterator itBegin
= m_navButtons
.begin();
567 QLinkedList
<KUrlNavigatorButton
*>::iterator itEnd
= m_navButtons
.end();
568 QLinkedList
<KUrlNavigatorButton
*>::iterator it
= itBegin
;
569 while (it
!= itEnd
) {
571 (*it
)->deleteLater();
574 m_navButtons
.erase(itBegin
, itEnd
);
580 KUrlNavigator::KUrlNavigator(KFilePlacesModel
* placesModel
,
584 d( new Private(this, placesModel
) )
586 d
->m_history
.prepend(HistoryElem(url
));
588 QFontMetrics
fontMetrics(font());
589 setMinimumHeight(fontMetrics
.height() + 10);
591 setLayout(d
->m_layout
);
596 KUrlNavigator::~KUrlNavigator()
601 const KUrl
& KUrlNavigator::url() const
603 assert(!d
->m_history
.empty());
604 return d
->m_history
[d
->m_historyIndex
].url();
607 KUrl
KUrlNavigator::url(int index
) const
610 // keep scheme, hostname etc. maybe we will need this in the future
611 // for e.g. browsing ftp repositories.
613 newurl
.setPath(QString());
614 QString
path(url().path());
616 if (!path
.isEmpty()) {
617 if (index
== 0) //prevent the last "/" from being stripped
618 path
= "/"; //or we end up with an empty path
620 path
= path
.section('/', 0, index
);
623 newurl
.setPath(path
);
627 QPoint
KUrlNavigator::savedPosition() const
629 const HistoryElem
& histElem
= d
->m_history
[d
->m_historyIndex
];
630 return QPoint( histElem
.contentsX(), histElem
.contentsY() );
633 int KUrlNavigator::historySize() const
635 return d
->m_history
.count();
638 void KUrlNavigator::goBack()
640 d
->updateHistoryElem();
642 const int count
= d
->m_history
.count();
643 if (d
->m_historyIndex
< count
- 1) {
646 emit
urlChanged(url());
647 emit
historyChanged();
651 void KUrlNavigator::goForward()
653 if (d
->m_historyIndex
> 0) {
656 emit
urlChanged(url());
657 emit
historyChanged();
661 void KUrlNavigator::goUp()
663 setUrl(url().upUrl());
666 void KUrlNavigator::goHome()
668 if (d
->m_homeUrl
.isEmpty())
669 setUrl(QDir::homePath());
671 setUrl(d
->m_homeUrl
);
674 bool KUrlNavigator::isUrlEditable() const
676 return d
->m_toggleButton
->isChecked();
679 void KUrlNavigator::setUrlEditable(bool editable
)
681 if (isUrlEditable() != editable
) {
682 d
->m_toggleButton
->toggle();
687 void KUrlNavigator::setActive(bool active
)
689 if (active
!= d
->m_active
) {
690 d
->m_active
= active
;
698 void KUrlNavigator::setShowHiddenFiles( bool show
)
700 d
->m_showHiddenFiles
= show
;
703 void KUrlNavigator::dropUrls(const KUrl::List
& urls
,
704 const KUrl
& destination
)
706 emit
urlsDropped(urls
, destination
);
709 void KUrlNavigator::setUrl(const KUrl
& url
)
711 QString
urlStr(url
.pathOrUrl());
713 // TODO: a patch has been submitted by Filip Brcic which adjusts
714 // the URL for tar and zip files. See https://bugs.kde.org/show_bug.cgi?id=142781
715 // for details. The URL navigator part of the patch has not been committed yet,
716 // as the URL navigator will be subject of change and
717 // we might think of a more generic approach to check the protocol + MIME type for
720 //kDebug() << "setUrl(" << url << ")" << endl;
721 if ( urlStr
.length() > 0 && urlStr
.at(0) == '~') {
722 // replace '~' by the home directory
724 urlStr
.insert(0, QDir::homePath());
727 const KUrl
transformedUrl(urlStr
);
729 if (d
->m_historyIndex
> 0) {
730 // Check whether the previous element of the history has the same Url.
731 // If yes, just go forward instead of inserting a duplicate history
733 HistoryElem
& prevHistoryElem
= d
->m_history
[d
->m_historyIndex
- 1];
734 if (transformedUrl
== prevHistoryElem
.url()) {
736 // kDebug() << "goin' forward in history" << endl;
741 if (this->url() == transformedUrl
) {
742 // don't insert duplicate history elements
743 // kDebug() << "current url == transformedUrl" << endl;
747 d
->updateHistoryElem();
748 d
->m_history
.insert(d
->m_historyIndex
, HistoryElem(transformedUrl
));
752 emit
urlChanged(transformedUrl
);
753 emit
historyChanged();
755 // Prevent an endless growing of the history: remembering
756 // the last 100 Urls should be enough...
757 if (d
->m_historyIndex
> 100) {
758 d
->m_history
.removeFirst();
762 /* kDebug() << "history starting ====================" << endl;
764 for (QValueListIterator<KUrlNavigator::HistoryElem> it = d->m_history.begin();
765 it != d->m_history.end();
768 kDebug() << i << ": " << (*it).url() << endl;
770 kDebug() << "history done ========================" << endl;*/
775 void KUrlNavigator::requestActivation()
780 void KUrlNavigator::storeContentsPosition(int x
, int y
)
782 HistoryElem
& hist
= d
->m_history
[d
->m_historyIndex
];
783 hist
.setContentsX(x
);
784 hist
.setContentsY(y
);
787 void KUrlNavigator::keyReleaseEvent(QKeyEvent
* event
)
789 QWidget::keyReleaseEvent(event
);
790 if (isUrlEditable() && (event
->key() == Qt::Key_Escape
)) {
791 setUrlEditable(false);
795 void KUrlNavigator::mouseReleaseEvent(QMouseEvent
* event
)
797 if (event
->button() == Qt::MidButton
) {
798 QClipboard
* clipboard
= QApplication::clipboard();
799 const QMimeData
* mimeData
= clipboard
->mimeData();
800 if (mimeData
->hasText()) {
801 const QString text
= mimeData
->text();
805 QWidget::mouseReleaseEvent(event
);
808 bool KUrlNavigator::isActive() const
813 bool KUrlNavigator::showHiddenFiles() const
815 return d
->m_showHiddenFiles
;
818 void KUrlNavigator::setHomeUrl(const QString
& homeUrl
)
820 d
->m_homeUrl
= homeUrl
;
823 #include "kurlnavigator.moc"