fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kfile / kurlnavigator.cpp
blobf3c9159dfbdde6062b57824ff427e6b220edffea
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) 2007 by Kevin Ottens <ervin@kde.org> *
5 * Copyright (C) 2007 by Urs Wolfer <uwolfer @ kde.org> *
6 * *
7 * This library is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU Library General Public *
9 * License version 2 as published by the Free Software Foundation. *
10 * *
11 * This library 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 GNU *
14 * Library General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU Library General Public License *
17 * along with this library; see the file COPYING.LIB. If not, write to *
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
19 * Boston, MA 02110-1301, USA. *
20 *****************************************************************************/
22 #include "kurlnavigator.h"
24 #include "kfileplacesselector_p.h"
25 #include "kprotocolcombo_p.h"
26 #include "kurldropdownbutton_p.h"
27 #include "kurlnavigatorbutton_p.h"
28 #include "kurltogglebutton_p.h"
30 #include <kfileitem.h>
31 #include <kfileplacesmodel.h>
32 #include <kglobalsettings.h>
33 #include <kicon.h>
34 #include <klineedit.h>
35 #include <klocale.h>
36 #include <kmenu.h>
37 #include <kprotocolinfo.h>
38 #include <kurlcombobox.h>
39 #include <kurlcompletion.h>
41 #include <QtCore/QDir>
42 #include <QtCore/QLinkedList>
43 #include <QtCore/QTimer>
44 #include <QtGui/QApplication>
45 #include <QtGui/QClipboard>
46 #include <QtGui/QDropEvent>
47 #include <QtGui/QKeyEvent>
48 #include <QtGui/QBoxLayout>
49 #include <QtGui/QLabel>
51 #include <fixx11h.h>
53 /**
54 * @brief Represents the history element of an URL.
56 * A history element contains the URL and
57 * the x- and y-position of the content.
59 class HistoryElem
61 public:
62 HistoryElem();
63 HistoryElem(const KUrl& url);
64 ~HistoryElem(); // non virtual
66 const KUrl& url() const;
68 void setRootUrl(const KUrl& url);
69 const KUrl& rootUrl() const;
71 void setContentsX(int x);
72 int contentsX() const;
74 void setContentsY(int y);
75 int contentsY() const;
77 private:
78 KUrl m_url;
79 KUrl m_rootUrl;
80 int m_contentsX;
81 int m_contentsY;
84 HistoryElem::HistoryElem() :
85 m_url(),
86 m_rootUrl(),
87 m_contentsX(0),
88 m_contentsY(0)
92 HistoryElem::HistoryElem(const KUrl& url) :
93 m_url(url),
94 m_rootUrl(),
95 m_contentsX(0),
96 m_contentsY(0)
100 HistoryElem::~HistoryElem()
104 inline const KUrl& HistoryElem::url() const
106 return m_url;
109 inline void HistoryElem::setRootUrl(const KUrl& url)
111 m_rootUrl = url;
114 inline const KUrl& HistoryElem::rootUrl() const
116 return m_rootUrl;
119 inline void HistoryElem::setContentsX(int x)
121 m_contentsX = x;
124 inline int HistoryElem::contentsX() const
126 return m_contentsX;
129 inline void HistoryElem::setContentsY(int y)
131 m_contentsY = y;
134 inline int HistoryElem::contentsY() const
136 return m_contentsY;
139 ////
141 class KUrlNavigator::Private
143 public:
144 Private(KUrlNavigator* q, KFilePlacesModel* placesModel);
146 void slotReturnPressed();
147 void slotRemoteHostActivated();
148 void slotProtocolChanged(const QString&);
149 void openPathSelectorMenu();
152 * Appends the widget at the end of the URL navigator. It is assured
153 * that the filler widget remains as last widget to fill the remaining
154 * width.
156 void appendWidget(QWidget* widget, int stretch = 0);
159 * Switches the navigation bar between the breadcrumb view and the
160 * traditional view (see setUrlEditable()) and is connected to the clicked signal
161 * of the navigation bar button.
163 void switchView();
165 /** Emits the signal urlsDropped(). */
166 void dropUrls(const KUrl& destination, QDropEvent* event);
168 void updateContent();
171 * Updates all buttons to have one button for each part of the
172 * path \a path. Existing buttons, which are available by m_navButtons,
173 * are reused if possible. If the path is longer, new buttons will be
174 * created, if the path is shorter, the remaining buttons will be deleted.
175 * @param startIndex Start index of path part (/), where the buttons
176 * should be created for each following part.
178 void updateButtons(const QString& path, int startIndex);
181 * Updates the visibility state of all buttons describing the URL. If the
182 * width of the URL navigator is too small, the buttons representing the upper
183 * paths of the URL will be hidden and moved to a drop down menu.
185 void updateButtonVisibility();
187 void switchToBreadcrumbMode();
190 * Deletes all URL navigator buttons. m_navButtons is
191 * empty after this operation.
193 void deleteButtons();
196 * Retrieves the place path for the current path.
197 * E. g. for the path "fish://root@192.168.0.2/var/lib" the string
198 * "fish://root@192.168.0.2" will be returned, which leads to the
199 * navigation indication 'Custom Path > var > lib". For e. g.
200 * "settings:///System/" the path "settings://" will be returned.
202 QString retrievePlacePath() const;
205 * Returns true, if the MIME type of the path represents a
206 * compressed file like TAR or ZIP.
208 bool isCompressedPath(const KUrl& path) const;
210 void removeTrailingSlash(QString& url) const;
213 * Returns a KUrl for the typed text \a typedUrl.
214 * '\' is replaced by '/', whitespaces at the begin
215 * and end of the typed text get removed.
217 KUrl adjustedUrl(const QString& typedUrl) const;
219 bool m_editable : 1;
220 bool m_active : 1;
221 bool m_showPlacesSelector : 1;
222 bool m_showFullPath : 1;
223 int m_historyIndex;
225 QHBoxLayout* m_layout;
227 QList<HistoryElem> m_history;
228 KFilePlacesSelector* m_placesSelector;
229 KUrlComboBox* m_pathBox;
230 KProtocolCombo* m_protocols;
231 KLineEdit* m_host;
232 KUrlDropDownButton* m_dropDownButton;
233 QLinkedList<KUrlNavigatorButton*> m_navButtons;
234 KUrlButton* m_toggleEditableMode;
235 QString m_homeUrl;
236 QStringList m_customProtocols;
237 KUrlNavigator* q;
241 KUrlNavigator::Private::Private(KUrlNavigator* q, KFilePlacesModel* placesModel) :
242 m_editable(false),
243 m_active(true),
244 m_showPlacesSelector(placesModel != 0),
245 m_showFullPath(false),
246 m_historyIndex(0),
247 m_layout(new QHBoxLayout),
248 m_placesSelector(0),
249 m_pathBox(0),
250 m_protocols(0),
251 m_host(0),
252 m_dropDownButton(0),
253 m_toggleEditableMode(0),
254 m_customProtocols(QStringList()),
255 q(q)
257 m_layout->setSpacing(0);
258 m_layout->setMargin(0);
260 // initialize the places selector
261 q->setAutoFillBackground(false);
263 if (placesModel != 0) {
264 m_placesSelector = new KFilePlacesSelector(q, placesModel);
265 connect(m_placesSelector, SIGNAL(placeActivated(const KUrl&)),
266 q, SLOT(setUrl(const KUrl&)));
268 connect(placesModel, SIGNAL(rowsInserted(QModelIndex, int, int)),
269 q, SLOT(updateContent()));
270 connect(placesModel, SIGNAL(rowsRemoved(QModelIndex, int, int)),
271 q, SLOT(updateContent()));
272 connect(placesModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)),
273 q, SLOT(updateContent()));
276 // create protocol combo
277 m_protocols = new KProtocolCombo(QString(), q);
278 connect(m_protocols, SIGNAL(activated(QString)),
279 q, SLOT(slotProtocolChanged(QString)));
281 // create editor for editing the host
282 m_host = new KLineEdit(QString(), q);
283 m_host->setClearButtonShown(true);
284 connect(m_host, SIGNAL(editingFinished()),
285 q, SLOT(slotRemoteHostActivated()));
286 connect(m_host, SIGNAL(returnPressed()),
287 q, SIGNAL(returnPressed()));
289 // create drop down button for accessing all paths of the URL
290 m_dropDownButton = new KUrlDropDownButton(q);
291 connect(m_dropDownButton, SIGNAL(clicked()),
292 q, SLOT(openPathSelectorMenu()));
294 // initialize the path box of the traditional view
295 m_pathBox = new KUrlComboBox(KUrlComboBox::Both, true, q);
296 m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
297 m_pathBox->installEventFilter(q);
299 KUrlCompletion* kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
300 m_pathBox->setCompletionObject(kurlCompletion);
301 m_pathBox->setAutoDeleteCompletionObject(true);
303 connect(m_pathBox, SIGNAL(returnPressed()),
304 q, SLOT(slotReturnPressed()));
305 connect(m_pathBox, SIGNAL(urlActivated(KUrl)),
306 q, SLOT(setUrl(KUrl)));
308 m_toggleEditableMode = new KUrlToggleButton(q);
309 m_toggleEditableMode->setMinimumWidth(20);
310 connect(m_toggleEditableMode, SIGNAL(clicked()),
311 q, SLOT(switchView()));
313 if (m_placesSelector != 0) {
314 m_layout->addWidget(m_placesSelector);
316 m_layout->addWidget(m_protocols);
317 m_layout->addWidget(m_dropDownButton);
318 m_layout->addWidget(m_host);
319 m_layout->setStretchFactor(m_host, 1);
320 m_layout->addWidget(m_pathBox, 1);
321 m_layout->addWidget(m_toggleEditableMode);
324 void KUrlNavigator::Private::appendWidget(QWidget* widget, int stretch)
326 m_layout->insertWidget(m_layout->count() - 1, widget, stretch);
329 void KUrlNavigator::Private::slotReturnPressed()
331 // Parts of the following code have been taken
332 // from the class KateFileSelector located in
333 // kate/app/katefileselector.hpp of Kate.
334 // Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
335 // Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
336 // Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
338 const KUrl typedUrl = q->uncommittedUrl();
339 QStringList urls = m_pathBox->urls();
340 urls.removeAll(typedUrl.url());
341 urls.prepend(typedUrl.url());
342 m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
344 q->setUrl(typedUrl);
345 // The URL might have been adjusted by KUrlNavigator::setUrl(), hence
346 // synchronize the result in the path box.
347 m_pathBox->setUrl(q->url());
349 emit q->returnPressed();
351 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
352 // Pressing Ctrl+Return automatically switches back to the breadcrumb mode.
353 // The switch must be done asynchronously, as we are in the context of the
354 // editor.
355 QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection);
359 void KUrlNavigator::Private::slotRemoteHostActivated()
361 KUrl u = q->url();
363 KUrl n(m_protocols->currentProtocol() + "://" + m_host->text());
365 if (n.scheme() != u.scheme() ||
366 n.host() != u.host() ||
367 n.user() != u.user() ||
368 n.port() != u.port()) {
369 u.setScheme(n.scheme());
370 u.setHost(n.host());
371 u.setUser(n.user());
372 u.setPort(n.port());
374 //TODO: get rid of this HACK for file:///!
375 if (u.scheme() == "file") {
376 u.setHost("");
377 if (u.path().isEmpty()) {
378 u.setPath("/");
382 q->setUrl(u);
386 void KUrlNavigator::Private::slotProtocolChanged(const QString& protocol)
388 KUrl url;
389 url.setScheme(protocol);
390 url.setPath("/");
391 QLinkedList<KUrlNavigatorButton*>::const_iterator it = m_navButtons.begin();
392 const QLinkedList<KUrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
393 while (it != itEnd) {
394 (*it)->hide();
395 (*it)->deleteLater();
396 ++it;
398 m_navButtons.clear();
400 if (KProtocolInfo::protocolClass(protocol) == ":local") {
401 q->setUrl(url);
402 } else {
403 m_host->setText(QString());
404 m_host->show();
405 m_host->setFocus();
409 void KUrlNavigator::Private::openPathSelectorMenu()
411 if (m_navButtons.count() <= 0) {
412 return;
415 const KUrl firstVisibleUrl = q->url(m_navButtons.first()->index());
417 QString spacer;
418 KMenu* popup = new KMenu(q);
419 popup->setLayoutDirection(Qt::LeftToRight);
421 const QString placePath = retrievePlacePath();
422 int idx = placePath.count('/'); // idx points to the first directory
423 // after the place path
425 const QString path = q->url().pathOrUrl();
426 QString dirName = path.section('/', idx, idx);
427 if (dirName.isEmpty()) {
428 dirName = QChar('/');
430 do {
431 const QString text = spacer + dirName;
433 QAction* action = new QAction(text, popup);
434 const KUrl currentUrl = q->url(idx);
435 if (currentUrl == firstVisibleUrl) {
436 popup->addSeparator();
438 action->setData(QVariant(currentUrl.prettyUrl()));
439 popup->addAction(action);
441 ++idx;
442 spacer.append(" ");
443 dirName = path.section('/', idx, idx);
444 } while (!dirName.isEmpty());
446 const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight());
447 const QAction* activatedAction = popup->exec(pos);
448 if (activatedAction != 0) {
449 const KUrl url = KUrl(activatedAction->data().toString());
450 q->setUrl(url);
453 popup->deleteLater();
456 void KUrlNavigator::Private::switchView()
458 m_toggleEditableMode->setFocus();
459 m_editable = !m_editable;
460 m_toggleEditableMode->setChecked(m_editable);
461 updateContent();
462 if (q->isUrlEditable()) {
463 m_pathBox->setFocus();
466 emit q->requestActivation();
467 emit q->editableStateChanged(m_editable);
470 void KUrlNavigator::Private::dropUrls(const KUrl& destination, QDropEvent* event)
472 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
473 if (!urls.isEmpty()) {
474 emit q->urlsDropped(destination, event);
476 // KDE5: remove, as the signal has been replaced by
477 // urlsDropped(const KUrl& destination, QDropEvent* event)
478 emit q->urlsDropped(urls, destination);
482 void KUrlNavigator::Private::updateContent()
484 if (m_placesSelector != 0) {
485 m_placesSelector->updateSelection(q->url());
488 if (m_editable) {
489 m_protocols->hide();
490 m_host->hide();
491 m_dropDownButton->hide();
493 deleteButtons();
494 m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
495 q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
497 m_pathBox->show();
498 m_pathBox->setUrl(q->url());
499 } else {
500 m_dropDownButton->setVisible(!m_showFullPath);
501 m_pathBox->hide();
503 const KUrl currentUrl = q->url();
504 QString path = currentUrl.pathOrUrl();
505 removeTrailingSlash(path);
507 m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
508 q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
510 // The URL consists of a protocol, a host and a variable number of directories.
511 // The directories are mapped to URL navigator buttons represented by m_buttons.
512 // - If a part of the URL is represented by a Places bookmark, this part will
513 // be also represented as button if m_showFullPath is false.
514 // - If no part of the URL is represented by a Places bookmark, a protocols-combo
515 // and a host-editor will be shown if no subdirectories are available.
516 KUrl placeUrl;
517 if ((m_placesSelector != 0) && !m_showFullPath) {
518 placeUrl = m_placesSelector->selectedPlaceUrl();
521 QString placePath = placeUrl.isValid() ? placeUrl.pathOrUrl() : retrievePlacePath();
522 removeTrailingSlash(placePath);
524 // update the protocol-combo
525 const QString protocol = currentUrl.scheme();
526 m_protocols->setProtocol(protocol);
528 // update the host-editor
529 QString hostText = currentUrl.host();
530 if (!currentUrl.user().isEmpty()) {
531 hostText = currentUrl.user() + '@' + hostText;
533 if (currentUrl.port() != -1) {
534 hostText = hostText + ':' + QString::number(currentUrl.port());
536 m_host->setText(hostText);
538 // check whether the protocol-combo and the host-editor should be shown
539 const bool hasPlaceItem = currentUrl.isLocalFile() || placeUrl.isValid();
540 const bool isVisible = !hasPlaceItem &&
541 (placePath == path) &&
542 (KProtocolInfo::protocolClass(protocol) != ":local");
544 m_host->setVisible(isVisible);
545 m_protocols->setVisible(isVisible);
547 // calculate the start index for the directories that should be shown as buttons
548 // and create the buttons
549 int startIndex = placePath.count('/');
550 if (!isVisible && !hasPlaceItem && hostText.isEmpty()) {
551 --startIndex;
553 updateButtons(path, startIndex);
557 void KUrlNavigator::Private::updateButtons(const QString& path, int startIndex)
559 QLinkedList<KUrlNavigatorButton*>::iterator it = m_navButtons.begin();
560 const QLinkedList<KUrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
561 bool createButton = false;
562 const KUrl currentUrl = q->url();
564 int idx = startIndex;
565 bool hasNext = true;
566 do {
567 createButton = (it == itEnd);
569 const QString dirName = path.section('/', idx, idx);
570 const bool isFirstButton = (idx == startIndex);
571 hasNext = isFirstButton || !dirName.isEmpty();
572 if (hasNext) {
573 QString text;
574 if (isFirstButton) {
575 // the first URL navigator button should get the name of the
576 // place instead of the directory name
577 if ((m_placesSelector != 0) && !m_showFullPath) {
578 const KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
579 text = m_placesSelector->selectedPlaceText();
581 if (text.isEmpty()) {
582 if (currentUrl.isLocalFile()) {
583 text = m_showFullPath ? "/" : i18n("Custom Path");
584 } else if (!m_host->isVisible() && !m_host->text().isEmpty()) {
585 text = m_host->text();
586 } else {
587 // The host is already displayed by the m_host widget,
588 // no button may be added for this index.
589 ++idx;
590 continue;
595 KUrlNavigatorButton* button = 0;
596 if (createButton) {
597 button = new KUrlNavigatorButton(idx, q);
598 connect(button, SIGNAL(urlsDropped(const KUrl&, QDropEvent*)),
599 q, SLOT(dropUrls(const KUrl&, QDropEvent*)));
600 appendWidget(button);
601 } else {
602 button = *it;
603 button->setIndex(idx);
606 if (isFirstButton) {
607 button->setText(text);
610 if (createButton) {
611 m_navButtons.append(button);
612 } else {
613 ++it;
615 ++idx;
617 } while (hasNext);
619 // delete buttons which are not used anymore
620 QLinkedList<KUrlNavigatorButton*>::iterator itBegin = it;
621 while (it != itEnd) {
622 (*it)->hide();
623 (*it)->deleteLater();
624 ++it;
626 m_navButtons.erase(itBegin, m_navButtons.end());
628 updateButtonVisibility();
631 void KUrlNavigator::Private::updateButtonVisibility()
633 if (m_editable) {
634 return;
637 const int buttonsCount = m_navButtons.count();
638 if (buttonsCount == 0) {
639 m_dropDownButton->hide();
640 return;
643 // subtract all widgets from the available width, that must be shown anyway
644 int availableWidth = q->width() - m_toggleEditableMode->minimumWidth();
646 if ((m_placesSelector != 0) && m_placesSelector->isVisible()) {
647 availableWidth -= m_placesSelector->width();
650 if ((m_protocols != 0) && m_protocols->isVisible()) {
651 availableWidth -= m_protocols->width();
654 if (m_host->isVisible()) {
655 availableWidth -= m_host->width();
658 // check whether buttons must be hidden at all...
659 int requiredButtonWidth = 0;
660 foreach (KUrlNavigatorButton* button, m_navButtons) {
661 requiredButtonWidth += button->minimumWidth();
663 if (requiredButtonWidth > availableWidth) {
664 // At least one button must be hidden. This implies that the
665 // drop-down button must get visible, which again decreases the
666 // available width.
667 availableWidth -= m_dropDownButton->width();
670 // hide buttons...
671 QLinkedList<KUrlNavigatorButton*>::iterator it = m_navButtons.end();
672 const QLinkedList<KUrlNavigatorButton*>::const_iterator itBegin = m_navButtons.begin();
673 bool isLastButton = true;
674 bool hasHiddenButtons = false;
676 QLinkedList<KUrlNavigatorButton*> buttonsToShow;
677 while (it != itBegin) {
678 --it;
679 KUrlNavigatorButton* button = (*it);
680 availableWidth -= button->minimumWidth();
681 if ((availableWidth <= 0) && !isLastButton) {
682 button->hide();
683 hasHiddenButtons = true;
685 else {
686 button->setActive(isLastButton);
687 // Don't show the button immediately, as setActive()
688 // might change the size and a relayout gets triggered
689 // after showing the button. So the showing of all buttons
690 // is postponed until all buttons have the correct
691 // activation state.
692 buttonsToShow.append(button);
694 isLastButton = false;
697 // all buttons have the correct activation state and
698 // can be shown now
699 foreach (KUrlNavigatorButton* button, buttonsToShow) {
700 button->show();
703 const int startIndex = retrievePlacePath().count('/');
704 const bool showDropDownButton = hasHiddenButtons ||
705 (!hasHiddenButtons && (m_navButtons.front()->index() > startIndex));
706 m_dropDownButton->setVisible(showDropDownButton);
709 void KUrlNavigator::Private::switchToBreadcrumbMode()
711 q->setUrlEditable(false);
714 void KUrlNavigator::Private::deleteButtons()
716 foreach (KUrlNavigatorButton* button, m_navButtons) {
717 button->hide();
718 button->deleteLater();
720 m_navButtons.clear();
723 QString KUrlNavigator::Private::retrievePlacePath() const
725 const QString path = q->url().pathOrUrl();
726 int idx = path.indexOf(QLatin1String("///"));
727 if (idx >= 0) {
728 idx += 3;
729 } else {
730 idx = path.indexOf(QLatin1String("//"));
731 idx = path.indexOf(QLatin1Char('/'), (idx < 0) ? 0 : idx + 2);
734 QString placePath = (idx < 0) ? path : path.left(idx);
735 removeTrailingSlash(placePath);
736 return placePath;
739 bool KUrlNavigator::Private::isCompressedPath(const KUrl& url) const
741 const KMimeType::Ptr mime = KMimeType::findByPath(url.path(KUrl::RemoveTrailingSlash));
742 // Note: this list of MIME types depends on the protocols implemented by kio_archive
743 return mime->is("application/x-compressed-tar") ||
744 mime->is("application/x-bzip-compressed-tar") ||
745 mime->is("application/x-tar") ||
746 mime->is("application/x-tarz") ||
747 mime->is("application/x-tzo") || // (not sure KTar supports those?)
748 mime->is("application/zip") ||
749 mime->is("application/x-archive");
752 void KUrlNavigator::Private::removeTrailingSlash(QString& url) const
754 const int length = url.length();
755 if ((length > 0) && (url.at(length - 1) == QChar('/'))) {
756 url.remove(length -1, 1);
760 KUrl KUrlNavigator::Private::adjustedUrl(const QString& typedUrl) const
762 KUrl url(typedUrl.trimmed());
763 if (url.hasPass()) {
764 url.setPass(QString());
766 return url;
769 ////
771 KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel,
772 const KUrl& url,
773 QWidget* parent) :
774 QWidget(parent),
775 d(new Private(this, placesModel))
777 d->m_history.prepend(HistoryElem(url));
778 setLayoutDirection(Qt::LeftToRight);
780 const QFont font = KGlobalSettings::generalFont();
781 setFont(font);
783 const int minHeight = d->m_pathBox->sizeHint().height();
784 setMinimumHeight(minHeight);
786 setLayout(d->m_layout);
787 setMinimumWidth(100);
789 d->updateContent();
792 KUrlNavigator::~KUrlNavigator()
794 delete d;
797 const KUrl& KUrlNavigator::url() const
799 Q_ASSERT(!d->m_history.empty());
800 return d->m_history[d->m_historyIndex].url();
803 KUrl KUrlNavigator::uncommittedUrl() const
805 if (isUrlEditable()) {
806 return d->adjustedUrl(d->m_pathBox->currentText());
807 } else {
808 return KUrl(d->m_protocols->currentProtocol() + "://" + d->m_host->text());
812 KUrl KUrlNavigator::url(int index) const
814 if (index < 0) {
815 index = 0;
818 // keep scheme, hostname etc. as this is needed for e. g. browsing
819 // FTP directories
820 KUrl newUrl = url();
821 newUrl.setPath(QString());
823 QString pathOrUrl = url().pathOrUrl();
824 if (!pathOrUrl.isEmpty()) {
825 if (index == 0) {
826 // prevent the last "/" from being stripped
827 // or we end up with an empty path
828 #ifdef Q_OS_WIN
829 pathOrUrl = pathOrUrl.length() > 2 ? pathOrUrl.left(3) : QDir::rootPath();
830 #else
831 pathOrUrl = QLatin1String("/");
832 #endif
833 } else {
834 pathOrUrl = pathOrUrl.section('/', 0, index);
838 newUrl.setPath(KUrl(pathOrUrl).path());
839 return newUrl;
842 bool KUrlNavigator::goBack()
844 const int count = d->m_history.count();
845 if (d->m_historyIndex < count - 1) {
846 ++d->m_historyIndex;
847 d->updateContent();
848 emit historyChanged();
849 emit urlChanged(url());
850 return true;
853 return false;
856 bool KUrlNavigator::goForward()
858 if (d->m_historyIndex > 0) {
859 --d->m_historyIndex;
860 d->updateContent();
861 emit historyChanged();
862 emit urlChanged(url());
863 return true;
866 return false;
869 bool KUrlNavigator::goUp()
871 const KUrl& currentUrl = url();
872 const KUrl upUrl = currentUrl.upUrl();
873 if (upUrl != currentUrl) {
874 setUrl(upUrl);
875 return true;
878 return false;
881 void KUrlNavigator::goHome()
883 if (d->m_homeUrl.isEmpty()) {
884 setUrl(QDir::homePath());
885 } else {
886 setUrl(d->m_homeUrl);
890 void KUrlNavigator::setHomeUrl(const QString& homeUrl)
892 d->m_homeUrl = homeUrl;
895 void KUrlNavigator::setUrlEditable(bool editable)
897 if (d->m_editable != editable) {
898 d->switchView();
902 bool KUrlNavigator::isUrlEditable() const
904 return d->m_editable;
907 void KUrlNavigator::setShowFullPath(bool show)
909 if (d->m_showFullPath != show) {
910 d->m_showFullPath = show;
911 d->updateContent();
915 bool KUrlNavigator::showFullPath() const
917 return d->m_showFullPath;
921 void KUrlNavigator::setActive(bool active)
923 if (active != d->m_active) {
924 d->m_active = active;
925 update();
926 if (active) {
927 emit activated();
932 bool KUrlNavigator::isActive() const
934 return d->m_active;
937 void KUrlNavigator::setPlacesSelectorVisible(bool visible)
939 if (visible == d->m_showPlacesSelector) {
940 return;
943 if (visible && (d->m_placesSelector == 0)) {
944 // the places selector cannot get visible as no
945 // places model is available
946 return;
949 d->m_showPlacesSelector = visible;
950 d->m_placesSelector->setVisible(visible);
953 bool KUrlNavigator::isPlacesSelectorVisible() const
955 return d->m_showPlacesSelector;
958 void KUrlNavigator::setUrl(const KUrl& newUrl)
960 if (newUrl == url()) {
961 return;
964 QString urlStr = KUrlCompletion::replacedPath(newUrl.pathOrUrl(), true, true);
965 if ((urlStr.length() > 0) && (urlStr.at(0) == '~')) {
966 // replace '~' by the home directory
967 urlStr.remove(0, 1);
968 urlStr.insert(0, QDir::homePath());
971 if ((newUrl.protocol() == "tar") || (newUrl.protocol() == "zip")) {
972 // The URL represents a tar- or zip-file. Check whether
973 // the URL is really part of the tar- or zip-file, otherwise
974 // replace it by the local path again.
975 bool insideCompressedPath = d->isCompressedPath(newUrl);
976 if (!insideCompressedPath) {
977 KUrl prevUrl = newUrl;
978 KUrl parentUrl = newUrl.upUrl();
979 while (parentUrl != prevUrl) {
980 if (d->isCompressedPath(parentUrl)) {
981 insideCompressedPath = true;
982 break;
984 prevUrl = parentUrl;
985 parentUrl = parentUrl.upUrl();
988 if (!insideCompressedPath) {
989 // drop the tar: or zip: protocol since we are not
990 // inside the compressed path anymore
991 urlStr = newUrl.path();
995 const KUrl transformedUrl(urlStr);
997 // Check whether current history element has the same URL.
998 // If this is the case, just ignore setting the URL.
999 const HistoryElem& historyElem = d->m_history[d->m_historyIndex];
1000 const bool isUrlEqual = transformedUrl.equals(historyElem.url(), KUrl::CompareWithoutTrailingSlash) ||
1001 (!transformedUrl.isValid() && (urlStr == historyElem.url().url()));
1002 if (isUrlEqual) {
1003 return;
1006 if (d->m_historyIndex > 0) {
1007 // If an URL is set when the history index is not at the end (= 0),
1008 // then clear all previous history elements so that a new history
1009 // tree is started from the current position.
1010 QList<HistoryElem>::iterator begin = d->m_history.begin();
1011 QList<HistoryElem>::iterator end = begin + d->m_historyIndex;
1012 d->m_history.erase(begin, end);
1013 d->m_historyIndex = 0;
1016 Q_ASSERT(d->m_historyIndex == 0);
1017 d->m_history.insert(0, HistoryElem(transformedUrl));
1019 // Prevent an endless growing of the history: remembering
1020 // the last 100 Urls should be enough...
1021 const int historyMax = 100;
1022 if (d->m_history.size() > historyMax) {
1023 QList<HistoryElem>::iterator begin = d->m_history.begin() + historyMax;
1024 QList<HistoryElem>::iterator end = d->m_history.end();
1025 d->m_history.erase(begin, end);
1028 emit historyChanged();
1029 emit urlChanged(transformedUrl);
1031 d->updateContent();
1033 requestActivation();
1036 void KUrlNavigator::requestActivation()
1038 setActive(true);
1041 void KUrlNavigator::saveRootUrl(const KUrl& url)
1043 HistoryElem& hist = d->m_history[d->m_historyIndex];
1044 hist.setRootUrl(url);
1047 void KUrlNavigator::savePosition(int x, int y)
1049 HistoryElem& hist = d->m_history[d->m_historyIndex];
1050 hist.setContentsX(x);
1051 hist.setContentsY(y);
1054 void KUrlNavigator::keyReleaseEvent(QKeyEvent* event)
1056 QWidget::keyReleaseEvent(event);
1057 if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
1058 setUrlEditable(false);
1062 void KUrlNavigator::mouseReleaseEvent(QMouseEvent* event)
1064 if (event->button() == Qt::MidButton) {
1065 QClipboard* clipboard = QApplication::clipboard();
1066 const QMimeData* mimeData = clipboard->mimeData();
1067 if (mimeData->hasText()) {
1068 const QString text = mimeData->text();
1069 setUrl(KUrl(text));
1072 QWidget::mouseReleaseEvent(event);
1075 void KUrlNavigator::resizeEvent(QResizeEvent* event)
1077 QTimer::singleShot(0, this, SLOT(updateButtonVisibility()));
1078 QWidget::resizeEvent(event);
1081 bool KUrlNavigator::eventFilter(QObject* watched, QEvent* event)
1083 if ((watched == d->m_pathBox) && (event->type() == QEvent::FocusIn)) {
1084 requestActivation();
1085 setFocus();
1088 return QWidget::eventFilter(watched, event);
1091 int KUrlNavigator::historySize() const
1093 return d->m_history.count();
1096 int KUrlNavigator::historyIndex() const
1098 return d->m_historyIndex;
1101 const KUrl& KUrlNavigator::savedRootUrl() const
1103 const HistoryElem& histElem = d->m_history[d->m_historyIndex];
1104 return histElem.rootUrl();
1107 QPoint KUrlNavigator::savedPosition() const
1109 const HistoryElem& histElem = d->m_history[d->m_historyIndex];
1110 return QPoint(histElem.contentsX(), histElem.contentsY());
1113 KUrlComboBox* KUrlNavigator::editor() const
1115 return d->m_pathBox;
1118 void KUrlNavigator::setCustomProtocols(const QStringList &protocols)
1120 d->m_customProtocols = protocols;
1121 d->m_protocols->setCustomProtocols(d->m_customProtocols);
1124 QStringList KUrlNavigator::customProtocols() const
1126 return d->m_customProtocols;
1129 void KUrlNavigator::setFocus()
1131 if (isUrlEditable()) {
1132 d->m_pathBox->setFocus();
1133 } else if (d->m_host) {
1134 d->m_host->setFocus();
1135 } else {
1136 QWidget::setFocus();
1140 #include "kurlnavigator.moc"