1/*****************************************************************************
2 * Copyright (C) 2006-2010 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 as published by the Free Software Foundation; either *
10 * version 2 of the License, or (at your option) any later version. *
11 * *
12 * This library 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 GNU *
15 * Library General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU Library General Public License *
18 * along with this library; see the file COPYING.LIB. If not, write to *
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
20 * Boston, MA 02110-1301, USA. *
21 *****************************************************************************/
22
23#include "kurlnavigator.h"
24
25#include "kurlnavigatorplacesselector_p.h"
26#include "kurlnavigatorprotocolcombo_p.h"
27#include "kurlnavigatordropdownbutton_p.h"
28#include "kurlnavigatorbutton_p.h"
29#include "kurlnavigatortogglebutton_p.h"
30
31#include <kfileitem.h>
32#include <kfileplacesmodel.h>
33#include <kglobalsettings.h>
34#include <kicon.h>
35#include <klocale.h>
36#include <kmenu.h>
37#include <kprotocolinfo.h>
38#include <kurlcombobox.h>
39#include <kurlcompletion.h>
40#include <kurifilter.h>
41
42#include <QtCore/QDir>
43#include <QtCore/QLinkedList>
44#include <QtCore/QTimer>
45#include <QtGui/QApplication>
46#include <QtGui/QBoxLayout>
47#include <QtGui/QClipboard>
48#include <QtGui/QDropEvent>
49#include <QtGui/QKeyEvent>
50#include <QtGui/QLabel>
51#include <QtGui/QPainter>
52#include <QtGui/QStyleOption>
53
54#include <fixx11h.h>
55
56using namespace KDEPrivate;
57
58struct LocationData
59{
60 KUrl url;
61#ifndef KDE_NO_DEPRECATED
62 KUrl rootUrl; // KDE5: remove after the deprecated methods have been removed
63 QPoint pos; // KDE5: remove after the deprecated methods have been removed
64#endif
65 QByteArray state;
66};
67
68class KUrlNavigator::Private
69{
70public:
71 Private(KUrlNavigator* q, KFilePlacesModel* placesModel);
72
73 void initialize(const KUrl& url);
74
75 void slotReturnPressed();
76 void slotProtocolChanged(const QString&);
77 void openPathSelectorMenu();
78
79 /**
80 * Appends the widget at the end of the URL navigator. It is assured
81 * that the filler widget remains as last widget to fill the remaining
82 * width.
83 */
84 void appendWidget(QWidget* widget, int stretch = 0);
85
86 /**
87 * Switches the navigation bar between the breadcrumb view and the
88 * traditional view (see setUrlEditable()) and is connected to the clicked signal
89 * of the navigation bar button.
90 */
91 void switchView();
92
93 /** Emits the signal urlsDropped(). */
94 void dropUrls(const KUrl& destination, QDropEvent* event);
95
96 /**
97 * Is invoked when a navigator button has been clicked. Changes the URL
98 * of the navigator if the left mouse button has been used. If the middle
99 * mouse button has been used, the signal tabRequested() will be emitted.
100 */
101 void slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button);
102
103 void openContextMenu();
104
105 void slotPathBoxChanged(const QString& text);
106
107 void updateContent();
108
109 /**
110 * Updates all buttons to have one button for each part of the
111 * current URL. Existing buttons, which are available by m_navButtons,
112 * are reused if possible. If the URL is longer, new buttons will be
113 * created, if the URL is shorter, the remaining buttons will be deleted.
114 * @param startIndex Start index of URL part (/), where the buttons
115 * should be created for each following part.
116 */
117 void updateButtons(int startIndex);
118
119 /**
120 * Updates the visibility state of all buttons describing the URL. If the
121 * width of the URL navigator is too small, the buttons representing the upper
122 * paths of the URL will be hidden and moved to a drop down menu.
123 */
124 void updateButtonVisibility();
125
126 /**
127 * @return Text for the first button of the URL navigator.
128 */
129 QString firstButtonText() const;
130
131 /**
132 * Returns the URL that should be applied for the button with the index \a index.
133 */
134 KUrl buttonUrl(int index) const;
135
136 void switchToBreadcrumbMode();
137
138 /**
139 * Deletes all URL navigator buttons. m_navButtons is
140 * empty after this operation.
141 */
142 void deleteButtons();
143
144 /**
145 * Retrieves the place path for the current path.
146 * E. g. for the path "fish://root@192.168.0.2/var/lib" the string
147 * "fish://root@192.168.0.2" will be returned, which leads to the
148 * navigation indication 'Custom Path > var > lib". For e. g.
149 * "settings:///System/" the path "settings://" will be returned.
150 */
151 QString retrievePlacePath() const;
152
153 /**
154 * Returns true, if the MIME type of the path represents a
155 * compressed file like TAR or ZIP.
156 */
157 bool isCompressedPath(const KUrl& path) const;
158
159 void removeTrailingSlash(QString& url) const;
160
161 /**
162 * Returns the current history index, if \a historyIndex is
163 * smaller than 0. If \a historyIndex is greater or equal than
164 * the number of available history items, the largest possible
165 * history index is returned. For the other cases just \a historyIndex
166 * is returned.
167 */
168 int adjustedHistoryIndex(int historyIndex) const;
169
170 bool m_editable : 1;
171 bool m_active : 1;
172 bool m_showPlacesSelector : 1;
173 bool m_showFullPath : 1;
174 int m_historyIndex;
175
176 QHBoxLayout* m_layout;
177
178 QList<LocationData> m_history;
179 KUrlNavigatorPlacesSelector* m_placesSelector;
180 KUrlComboBox* m_pathBox;
181 KUrlNavigatorProtocolCombo* m_protocols;
182 KUrlNavigatorDropDownButton* m_dropDownButton;
183 QList<KUrlNavigatorButton*> m_navButtons;
184 KUrlNavigatorButtonBase* m_toggleEditableMode;
185 KUrl m_homeUrl;
186 QStringList m_customProtocols;
187 KUrlNavigator* q;
188};
189
190
191KUrlNavigator::Private::Private(KUrlNavigator* q, KFilePlacesModel* placesModel) :
192 m_editable(false),
193 m_active(true),
194 m_showPlacesSelector(placesModel != 0),
195 m_showFullPath(false),
196 m_historyIndex(0),
197 m_layout(new QHBoxLayout),
198 m_placesSelector(0),
199 m_pathBox(0),
200 m_protocols(0),
201 m_dropDownButton(0),
202 m_navButtons(),
203 m_toggleEditableMode(0),
204 m_homeUrl(),
205 m_customProtocols(QStringList()),
206 q(q)
207{
208 m_layout->setSpacing(0);
209 m_layout->setMargin(0);
210
211 // initialize the places selector
212 q->setAutoFillBackground(false);
213
214 if (placesModel != 0) {
215 m_placesSelector = new KUrlNavigatorPlacesSelector(q, placesModel);
216 connect(m_placesSelector, SIGNAL(placeActivated(KUrl)),
217 q, SLOT(setLocationUrl(KUrl)));
218
219 connect(placesModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
220 q, SLOT(updateContent()));
221 connect(placesModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
222 q, SLOT(updateContent()));
223 connect(placesModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
224 q, SLOT(updateContent()));
225 }
226
227 // create protocol combo
228 m_protocols = new KUrlNavigatorProtocolCombo(QString(), q);
229 connect(m_protocols, SIGNAL(activated(QString)),
230 q, SLOT(slotProtocolChanged(QString)));
231
232 // create drop down button for accessing all paths of the URL
233 m_dropDownButton = new KUrlNavigatorDropDownButton(q);
234 m_dropDownButton->setForegroundRole(QPalette::WindowText);
235 m_dropDownButton->installEventFilter(q);
236 connect(m_dropDownButton, SIGNAL(clicked()),
237 q, SLOT(openPathSelectorMenu()));
238
239 // initialize the path box of the traditional view
240 m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, q);
241 m_pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
242 m_pathBox->installEventFilter(q);
243
244 KUrlCompletion* kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
245 m_pathBox->setCompletionObject(kurlCompletion);
246 m_pathBox->setAutoDeleteCompletionObject(true);
247
248 connect(m_pathBox, SIGNAL(returnPressed()),
249 q, SLOT(slotReturnPressed()));
250 connect(m_pathBox, SIGNAL(urlActivated(KUrl)),
251 q, SLOT(setLocationUrl(KUrl)));
252 connect(m_pathBox, SIGNAL(editTextChanged(QString)),
253 q, SLOT(slotPathBoxChanged(QString)));
254
255 // create toggle button which allows to switch between
256 // the breadcrumb and traditional view
257 m_toggleEditableMode = new KUrlNavigatorToggleButton(q);
258 m_toggleEditableMode->installEventFilter(q);
259 m_toggleEditableMode->setMinimumWidth(20);
260 connect(m_toggleEditableMode, SIGNAL(clicked()),
261 q, SLOT(switchView()));
262
263 if (m_placesSelector != 0) {
264 m_layout->addWidget(m_placesSelector);
265 }
266 m_layout->addWidget(m_protocols);
267 m_layout->addWidget(m_dropDownButton);
268 m_layout->addWidget(m_pathBox, 1);
269 m_layout->addWidget(m_toggleEditableMode);
270
271 q->setContextMenuPolicy(Qt::CustomContextMenu);
272 connect(q, SIGNAL(customContextMenuRequested(QPoint)),
273 q, SLOT(openContextMenu()));
274}
275
276void KUrlNavigator::Private::initialize(const KUrl& url)
277{
278 LocationData data;
279 data.url = url;
280 m_history.prepend(data);
281
282 q->setLayoutDirection(Qt::LeftToRight);
283
284 const int minHeight = m_pathBox->sizeHint().height();
285 q->setMinimumHeight(minHeight);
286
287 q->setLayout(m_layout);
288 q->setMinimumWidth(100);
289
290 updateContent();
291}
292
293void KUrlNavigator::Private::appendWidget(QWidget* widget, int stretch)
294{
295 m_layout->insertWidget(m_layout->count() - 1, widget, stretch);
296}
297
298void KUrlNavigator::Private::slotReturnPressed()
299{
300 // Parts of the following code have been taken
301 // from the class KateFileSelector located in
302 // kate/app/katefileselector.hpp of Kate.
303 // Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
304 // Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
305 // Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
306
307 const KUrl typedUrl = q->uncommittedUrl();
308 QStringList urls = m_pathBox->urls();
309 urls.removeAll(typedUrl.url());
310 urls.prepend(typedUrl.url());
311 m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
312
313 q->setLocationUrl(typedUrl);
314 // The URL might have been adjusted by KUrlNavigator::setUrl(), hence
315 // synchronize the result in the path box.
316 const KUrl currentUrl = q->locationUrl();
317 m_pathBox->setUrl(currentUrl);
318
319 emit q->returnPressed();
320
321 if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
322 // Pressing Ctrl+Return automatically switches back to the breadcrumb mode.
323 // The switch must be done asynchronously, as we are in the context of the
324 // editor.
325 QMetaObject::invokeMethod(q, "switchToBreadcrumbMode", Qt::QueuedConnection);
326 }
327}
328
329void KUrlNavigator::Private::slotProtocolChanged(const QString& protocol)
330{
331 Q_ASSERT(m_editable);
332
333 KUrl url;
334 url.setProtocol(protocol);
335 url.setPath((protocol == QLatin1String("file")) ? QLatin1String("/") : QLatin1String("//"));
336
337 m_pathBox->setEditUrl(url);
338}
339
340void KUrlNavigator::Private::openPathSelectorMenu()
341{
342 if (m_navButtons.count() <= 0) {
343 return;
344 }
345
346 const KUrl firstVisibleUrl = m_navButtons.first()->url();
347
348 QString spacer;
349 QPointer<KMenu> popup = new KMenu(q);
350 popup->setLayoutDirection(Qt::LeftToRight);
351
352 const QString placePath = retrievePlacePath();
353 int idx = placePath.count(QLatin1Char('/')); // idx points to the first directory
354 // after the place path
355
356 const QString path = m_history[m_historyIndex].url.pathOrUrl();
357 QString dirName = path.section(QLatin1Char('/'), idx, idx);
358 if (dirName.isEmpty()) {
359 dirName = QLatin1Char('/');
360 }
361 do {
362 const QString text = spacer + dirName;
363
364 QAction* action = new QAction(text, popup);
365 const KUrl currentUrl = buttonUrl(idx);
366 if (currentUrl == firstVisibleUrl) {
367 popup->addSeparator();
368 }
369 action->setData(QVariant(currentUrl.prettyUrl()));
370 popup->addAction(action);
371
372 ++idx;
373 spacer.append(" ");
374 dirName = path.section('/', idx, idx);
375 } while (!dirName.isEmpty());
376
377 const QPoint pos = q->mapToGlobal(m_dropDownButton->geometry().bottomRight());
378 const QAction* activatedAction = popup->exec(pos);
379 if (activatedAction != 0) {
380 const KUrl url = KUrl(activatedAction->data().toString());
381 q->setLocationUrl(url);
382 }
383
384 // Delete the menu, unless it has been deleted in its own nested event loop already.
385 if (popup) {
386 popup->deleteLater();
387 }
388}
389
390void KUrlNavigator::Private::switchView()
391{
392 m_toggleEditableMode->setFocus();
393 m_editable = !m_editable;
394 m_toggleEditableMode->setChecked(m_editable);
395 updateContent();
396 if (q->isUrlEditable()) {
397 m_pathBox->setFocus();
398 }
399
400 emit q->requestActivation();
401 emit q->editableStateChanged(m_editable);
402}
403
404void KUrlNavigator::Private::dropUrls(const KUrl& destination, QDropEvent* event)
405{
406 const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
407 if (!urls.isEmpty()) {
408 emit q->urlsDropped(destination, event);
409
410#ifndef KDE_NO_DEPRECATED
411 // KDE5: remove, as the signal has been replaced by
412 // urlsDropped(const KUrl& destination, QDropEvent* event)
413 emit q->urlsDropped(urls, destination);
414#endif
415 }
416}
417
418void KUrlNavigator::Private::slotNavigatorButtonClicked(const KUrl& url, Qt::MouseButton button)
419{
420 if (button & Qt::LeftButton) {
421 q->setLocationUrl(url);
422 } else if (button & Qt::MidButton) {
423 emit q->tabRequested(url);
424 }
425}
426
427void KUrlNavigator::Private::openContextMenu()
428{
429 q->setActive(true);
430
431 QPointer<KMenu> popup = new KMenu(q);
432
433 // provide 'Copy' action, which copies the current URL of
434 // the URL navigator into the clipboard
435 QAction* copyAction = popup->addAction(KIcon("edit-copy"), i18n("Copy"));
436
437 // provide 'Paste' action, which copies the current clipboard text
438 // into the URL navigator
439 QAction* pasteAction = popup->addAction(KIcon("edit-paste"), i18n("Paste"));
440 QClipboard* clipboard = QApplication::clipboard();
441 pasteAction->setEnabled(!clipboard->text().isEmpty());
442
443 popup->addSeparator();
444
445 // provide radiobuttons for toggling between the edit and the navigation mode
446 QAction* editAction = popup->addAction(i18n("Edit"));
447 editAction->setCheckable(true);
448
449 QAction* navigateAction = popup->addAction(i18n("Navigate"));
450 navigateAction->setCheckable(true);
451
452 QActionGroup* modeGroup = new QActionGroup(popup);
453 modeGroup->addAction(editAction);
454 modeGroup->addAction(navigateAction);
455 if (q->isUrlEditable()) {
456 editAction->setChecked(true);
457 } else {
458 navigateAction->setChecked(true);
459 }
460
461 popup->addSeparator();
462
463 // allow showing of the full path
464 QAction* showFullPathAction = popup->addAction(i18n("Show Full Path"));
465 showFullPathAction->setCheckable(true);
466 showFullPathAction->setChecked(q->showFullPath());
467
468 QAction* activatedAction = popup->exec(QCursor::pos());
469 if (activatedAction == copyAction) {
470 QMimeData* mimeData = new QMimeData();
471 mimeData->setText(q->locationUrl().pathOrUrl());
472 clipboard->setMimeData(mimeData);
473 } else if (activatedAction == pasteAction) {
474 q->setLocationUrl(KUrl(clipboard->text()));
475 } else if (activatedAction == editAction) {
476 q->setUrlEditable(true);
477 } else if (activatedAction == navigateAction) {
478 q->setUrlEditable(false);
479 } else if (activatedAction == showFullPathAction) {
480 q->setShowFullPath(showFullPathAction->isChecked());
481 }
482
483 // Delete the menu, unless it has been deleted in its own nested event loop already.
484 if (popup) {
485 popup->deleteLater();
486 }
487}
488
489void KUrlNavigator::Private::slotPathBoxChanged(const QString& text)
490{
491 if (text.isEmpty()) {
492 const QString protocol = q->locationUrl().protocol();
493 m_protocols->setProtocol(protocol);
494 m_protocols->show();
495 } else {
496 m_protocols->hide();
497 }
498}
499
500void KUrlNavigator::Private::updateContent()
501{
502 const KUrl currentUrl = q->locationUrl();
503 if (m_placesSelector != 0) {
504 m_placesSelector->updateSelection(currentUrl);
505 }
506
507 if (m_editable) {
508 m_protocols->hide();
509 m_dropDownButton->hide();
510
511 deleteButtons();
512 m_toggleEditableMode->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
513 q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
514
515 m_pathBox->show();
516 m_pathBox->setUrl(currentUrl);
517 } else {
518 m_pathBox->hide();
519
520 m_protocols->hide();
521
522 m_toggleEditableMode->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
523 q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
524
525 // Calculate the start index for the directories that should be shown as buttons
526 // and create the buttons
527 KUrl placeUrl;
528 if ((m_placesSelector != 0) && !m_showFullPath) {
529 placeUrl = m_placesSelector->selectedPlaceUrl();
530 }
531
532 QString placePath = placeUrl.isValid() ? placeUrl.pathOrUrl() : retrievePlacePath();
533 removeTrailingSlash(placePath);
534
535 const int startIndex = placePath.count('/');
536 updateButtons(startIndex);
537 }
538}
539
540void KUrlNavigator::Private::updateButtons(int startIndex)
541{
542 KUrl currentUrl = q->locationUrl();
543
544 const QString path = currentUrl.pathOrUrl();
545
546 bool createButton = false;
547 const int oldButtonCount = m_navButtons.count();
548
549 int idx = startIndex;
550 bool hasNext = true;
551 do {
552 createButton = (idx - startIndex >= oldButtonCount);
553 const bool isFirstButton = (idx == startIndex);
554 const QString dirName = path.section(QLatin1Char('/'), idx, idx);
555 hasNext = isFirstButton || !dirName.isEmpty();
556 if (hasNext) {
557 KUrlNavigatorButton* button = 0;
558 if (createButton) {
559 button = new KUrlNavigatorButton(buttonUrl(idx), q);
560 button->installEventFilter(q);
561 button->setForegroundRole(QPalette::WindowText);
562 connect(button, SIGNAL(urlsDropped(KUrl,QDropEvent*)),
563 q, SLOT(dropUrls(KUrl,QDropEvent*)));
564 connect(button, SIGNAL(clicked(KUrl,Qt::MouseButton)),
565 q, SLOT(slotNavigatorButtonClicked(KUrl,Qt::MouseButton)));
566 connect(button, SIGNAL(finishedTextResolving()),
567 q, SLOT(updateButtonVisibility()));
568 appendWidget(button);
569 } else {
570 button = m_navButtons[idx - startIndex];
571 button->setUrl(buttonUrl(idx));
572 }
573
574 if (isFirstButton) {
575 button->setText(firstButtonText());
576 }
577 button->setActive(q->isActive());
578
579 if (createButton) {
580 if (!isFirstButton) {
581 setTabOrder(m_navButtons.last(), button);
582 }
583 m_navButtons.append(button);
584 }
585
586 ++idx;
587 button->setActiveSubDirectory(path.section(QLatin1Char('/'), idx, idx));
588 }
589 } while (hasNext);
590
591 // delete buttons which are not used anymore
592 const int newButtonCount = idx - startIndex;
593 if (newButtonCount < oldButtonCount) {
594 const QList<KUrlNavigatorButton*>::iterator itBegin = m_navButtons.begin() + newButtonCount;
595 const QList<KUrlNavigatorButton*>::iterator itEnd = m_navButtons.end();
596 QList<KUrlNavigatorButton*>::iterator it = itBegin;
597 while (it != itEnd) {
598 (*it)->hide();
599 (*it)->deleteLater();
600 ++it;
601 }
602 m_navButtons.erase(itBegin, itEnd);
603 }
604
605 setTabOrder(m_dropDownButton, m_navButtons.first());
606 setTabOrder(m_navButtons.last(), m_toggleEditableMode);
607
608 updateButtonVisibility();
609}
610
611void KUrlNavigator::Private::updateButtonVisibility()
612{
613 if (m_editable) {
614 return;
615 }
616
617 const int buttonsCount = m_navButtons.count();
618 if (buttonsCount == 0) {
619 m_dropDownButton->hide();
620 return;
621 }
622
623 // Subtract all widgets from the available width, that must be shown anyway
624 int availableWidth = q->width() - m_toggleEditableMode->minimumWidth();
625
626 if ((m_placesSelector != 0) && m_placesSelector->isVisible()) {
627 availableWidth -= m_placesSelector->width();
628 }
629
630 if ((m_protocols != 0) && m_protocols->isVisible()) {
631 availableWidth -= m_protocols->width();
632 }
633
634 // Check whether buttons must be hidden at all...
635 int requiredButtonWidth = 0;
636 foreach (const KUrlNavigatorButton* button, m_navButtons) {
637 requiredButtonWidth += button->minimumWidth();
638 }
639
640 if (requiredButtonWidth > availableWidth) {
641 // At least one button must be hidden. This implies that the
642 // drop-down button must get visible, which again decreases the
643 // available width.
644 availableWidth -= m_dropDownButton->width();
645 }
646
647 // Hide buttons...
648 QList<KUrlNavigatorButton*>::const_iterator it = m_navButtons.constEnd();
649 const QList<KUrlNavigatorButton*>::const_iterator itBegin = m_navButtons.constBegin();
650 bool isLastButton = true;
651 bool hasHiddenButtons = false;
652
653 QLinkedList<KUrlNavigatorButton*> buttonsToShow;
654 while (it != itBegin) {
655 --it;
656 KUrlNavigatorButton* button = (*it);
657 availableWidth -= button->minimumWidth();
658 if ((availableWidth <= 0) && !isLastButton) {
659 button->hide();
660 hasHiddenButtons = true;
661 }
662 else {
663 // Don't show the button immediately, as setActive()
664 // might change the size and a relayout gets triggered
665 // after showing the button. So the showing of all buttons
666 // is postponed until all buttons have the correct
667 // activation state.
668 buttonsToShow.append(button);
669 }
670 isLastButton = false;
671 }
672
673 // All buttons have the correct activation state and
674 // can be shown now
675 foreach (KUrlNavigatorButton* button, buttonsToShow) {
676 button->show();
677 }
678
679 if (hasHiddenButtons) {
680 m_dropDownButton->show();
681 } else {
682 // Check whether going upwards is possible. If this is the case, show the drop-down button.
683 KUrl url = m_navButtons.front()->url();
684 url.adjustPath(KUrl::AddTrailingSlash);
685 const bool visible = !url.equals(url.upUrl()) && (url.protocol() != "nepomuksearch");
686 m_dropDownButton->setVisible(visible);
687 }
688}
689
690QString KUrlNavigator::Private::firstButtonText() const
691{
692 QString text;
693
694 // The first URL navigator button should get the name of the
695 // place instead of the directory name
696 if ((m_placesSelector != 0) && !m_showFullPath) {
697 const KUrl placeUrl = m_placesSelector->selectedPlaceUrl();
698 text = m_placesSelector->selectedPlaceText();
699 }
700
701 if (text.isEmpty()) {
702 const KUrl currentUrl = q->locationUrl();
703 if (currentUrl.isLocalFile()) {
704#ifdef Q_OS_WIN
705 text = currentUrl.path().length() > 1 ? currentUrl.path().left(2) : QDir::rootPath();
706#else
707 text = m_showFullPath ? QLatin1String("/") : i18n("Custom Path");
708#endif
709 } else {
710 text = currentUrl.protocol() + QLatin1Char(':');
711 if (!currentUrl.host().isEmpty()) {
712 text += QLatin1Char(' ') + currentUrl.host();
713 }
714 }
715 }
716
717 return text;
718}
719
720KUrl KUrlNavigator::Private::buttonUrl(int index) const
721{
722 if (index < 0) {
723 index = 0;
724 }
725
726 // Keep scheme, hostname etc. as this is needed for e. g. browsing
727 // FTP directories
728 const KUrl currentUrl = q->locationUrl();
729 KUrl newUrl = currentUrl;
730 newUrl.setPath(QString());
731
732 QString pathOrUrl = currentUrl.pathOrUrl();
733 if (!pathOrUrl.isEmpty()) {
734 if (index == 0) {
735 // prevent the last "/" from being stripped
736 // or we end up with an empty path
737#ifdef Q_OS_WIN
738 pathOrUrl = pathOrUrl.length() > 1 ? pathOrUrl.left(2) : QDir::rootPath();
739#else
740 pathOrUrl = QLatin1String("/");
741#endif
742 } else {
743 pathOrUrl = pathOrUrl.section('/', 0, index);
744 }
745 }
746
747 newUrl.setPath(KUrl(pathOrUrl).path());
748 return newUrl;
749}
750
751void KUrlNavigator::Private::switchToBreadcrumbMode()
752{
753 q->setUrlEditable(false);
754}
755
756void KUrlNavigator::Private::deleteButtons()
757{
758 foreach (KUrlNavigatorButton* button, m_navButtons) {
759 button->hide();
760 button->deleteLater();
761 }
762 m_navButtons.clear();
763}
764
765QString KUrlNavigator::Private::retrievePlacePath() const
766{
767 const KUrl currentUrl = q->locationUrl();
768 const QString path = currentUrl.pathOrUrl();
769 int idx = path.indexOf(QLatin1String("///"));
770 if (idx >= 0) {
771 idx += 3;
772 } else {
773 idx = path.indexOf(QLatin1String("//"));
774 idx = path.indexOf(QLatin1Char('/'), (idx < 0) ? 0 : idx + 2);
775 }
776
777 QString placePath = (idx < 0) ? path : path.left(idx);
778 removeTrailingSlash(placePath);
779 return placePath;
780}
781
782bool KUrlNavigator::Private::isCompressedPath(const KUrl& url) const
783{
784 const KMimeType::Ptr mime = KMimeType::findByPath(url.path(KUrl::RemoveTrailingSlash));
785 // Note: this list of MIME types depends on the protocols implemented by kio_archive
786 return mime->is("application/x-compressed-tar") ||
787 mime->is("application/x-bzip-compressed-tar") ||
788 mime->is("application/x-lzma-compressed-tar") ||
789 mime->is("application/x-xz-compressed-tar") ||
790 mime->is("application/x-tar") ||
791 mime->is("application/x-tarz") ||
792 mime->is("application/x-tzo") || // (not sure KTar supports those?)
793 mime->is("application/zip") ||
794 mime->is("application/x-archive");
795}
796
797void KUrlNavigator::Private::removeTrailingSlash(QString& url) const
798{
799 const int length = url.length();
800 if ((length > 0) && (url.at(length - 1) == QChar('/'))) {
801 url.remove(length - 1, 1);
802 }
803}
804
805int KUrlNavigator::Private::adjustedHistoryIndex(int historyIndex) const
806{
807 if (historyIndex < 0) {
808 historyIndex = m_historyIndex;
809 } else if (historyIndex >= m_history.size()) {
810 historyIndex = m_history.size() - 1;
811 Q_ASSERT(historyIndex >= 0); // m_history.size() must always be > 0
812 }
813 return historyIndex;
814}
815
816// ------------------------------------------------------------------------------------------------
817
818KUrlNavigator::KUrlNavigator(QWidget* parent) :
819 QWidget(parent),
820 d(new Private(this, 0))
821{
822 d->initialize(KUrl());
823}
824
825KUrlNavigator::KUrlNavigator(KFilePlacesModel* placesModel,
826 const KUrl& url,
827 QWidget* parent) :
828 QWidget(parent),
829 d(new Private(this, placesModel))
830{
831 d->initialize(url);
832}
833
834KUrlNavigator::~KUrlNavigator()
835{
836 delete d;
837}
838
839KUrl KUrlNavigator::locationUrl(int historyIndex) const
840{
841 historyIndex = d->adjustedHistoryIndex(historyIndex);
842 return d->m_history[historyIndex].url;
843}
844
845void KUrlNavigator::saveLocationState(const QByteArray& state)
846{
847 d->m_history[d->m_historyIndex].state = state;
848}
849
850QByteArray KUrlNavigator::locationState(int historyIndex) const
851{
852 historyIndex = d->adjustedHistoryIndex(historyIndex);
853 return d->m_history[historyIndex].state;
854}
855
856bool KUrlNavigator::goBack()
857{
858 const int count = d->m_history.count();
859 if (d->m_historyIndex < count - 1) {
860 const KUrl newUrl = locationUrl(d->m_historyIndex + 1);
861 emit urlAboutToBeChanged(newUrl);
862
863 ++d->m_historyIndex;
864 d->updateContent();
865
866 emit historyChanged();
867 emit urlChanged(locationUrl());
868 return true;
869 }
870
871 return false;
872}
873
874bool KUrlNavigator::goForward()
875{
876 if (d->m_historyIndex > 0) {
877 const KUrl newUrl = locationUrl(d->m_historyIndex - 1);
878 emit urlAboutToBeChanged(newUrl);
879
880 --d->m_historyIndex;
881 d->updateContent();
882
883 emit historyChanged();
884 emit urlChanged(locationUrl());
885 return true;
886 }
887
888 return false;
889}
890
891bool KUrlNavigator::goUp()
892{
893 const KUrl currentUrl = locationUrl();
894 const KUrl upUrl = currentUrl.upUrl();
895 if (upUrl != currentUrl) {
896 setLocationUrl(upUrl);
897 return true;
898 }
899
900 return false;
901}
902
903void KUrlNavigator::goHome()
904{
905 if (d->m_homeUrl.isEmpty() || !d->m_homeUrl.isValid()) {
906 setLocationUrl(KUrl(QDir::homePath()));
907 } else {
908 setLocationUrl(d->m_homeUrl);
909 }
910}
911
912void KUrlNavigator::setHomeUrl(const KUrl& url)
913{
914 d->m_homeUrl = url;
915}
916
917KUrl KUrlNavigator::homeUrl() const
918{
919 return d->m_homeUrl;
920}
921
922void KUrlNavigator::setUrlEditable(bool editable)
923{
924 if (d->m_editable != editable) {
925 d->switchView();
926 }
927}
928
929bool KUrlNavigator::isUrlEditable() const
930{
931 return d->m_editable;
932}
933
934void KUrlNavigator::setShowFullPath(bool show)
935{
936 if (d->m_showFullPath != show) {
937 d->m_showFullPath = show;
938 d->updateContent();
939 }
940}
941
942bool KUrlNavigator::showFullPath() const
943{
944 return d->m_showFullPath;
945}
946
947
948void KUrlNavigator::setActive(bool active)
949{
950 if (active != d->m_active) {
951 d->m_active = active;
952
953 d->m_dropDownButton->setActive(active);
954 foreach(KUrlNavigatorButton* button, d->m_navButtons) {
955 button->setActive(active);
956 }
957
958 update();
959 if (active) {
960 emit activated();
961 }
962 }
963}
964
965bool KUrlNavigator::isActive() const
966{
967 return d->m_active;
968}
969
970void KUrlNavigator::setPlacesSelectorVisible(bool visible)
971{
972 if (visible == d->m_showPlacesSelector) {
973 return;
974 }
975
976 if (visible && (d->m_placesSelector == 0)) {
977 // the places selector cannot get visible as no
978 // places model is available
979 return;
980 }
981
982 d->m_showPlacesSelector = visible;
983 d->m_placesSelector->setVisible(visible);
984}
985
986bool KUrlNavigator::isPlacesSelectorVisible() const
987{
988 return d->m_showPlacesSelector;
989}
990
991KUrl KUrlNavigator::uncommittedUrl() const
992{
993 KUriFilterData filteredData(d->m_pathBox->currentText().trimmed());
994 filteredData.setCheckForExecutables(false);
995 if (KUriFilter::self()->filterUri(filteredData, QStringList() << "kshorturifilter" << "kurisearchfilter")) {
996 return filteredData.uri();
997 }
998 else {
999 return KUrl(filteredData.typedString());
1000 }
1001}
1002
1003void KUrlNavigator::setLocationUrl(const KUrl& newUrl)
1004{
1005 if (newUrl == locationUrl()) {
1006 return;
1007 }
1008
1009 KUrl url = newUrl;
1010 url.cleanPath();
1011
1012 if ((url.protocol() == QLatin1String("tar")) || (url.protocol() == QLatin1String("zip"))) {
1013 // The URL represents a tar- or zip-file. Check whether
1014 // the URL is really part of the tar- or zip-file, otherwise
1015 // replace it by the local path again.
1016 bool insideCompressedPath = d->isCompressedPath(url);
1017 if (!insideCompressedPath) {
1018 KUrl prevUrl = url;
1019 KUrl parentUrl = url.upUrl();
1020 while (parentUrl != prevUrl) {
1021 if (d->isCompressedPath(parentUrl)) {
1022 insideCompressedPath = true;
1023 break;
1024 }
1025 prevUrl = parentUrl;
1026 parentUrl = parentUrl.upUrl();
1027 }
1028 }
1029 if (!insideCompressedPath) {
1030 // drop the tar: or zip: protocol since we are not
1031 // inside the compressed path
1032 url.setProtocol("file");
1033 }
1034 }
1035
1036 // Check whether current history element has the same URL.
1037 // If this is the case, just ignore setting the URL.
1038 const LocationData& data = d->m_history[d->m_historyIndex];
1039 const bool isUrlEqual = url.equals(locationUrl(), KUrl::CompareWithoutTrailingSlash) ||
1040 (!url.isValid() && url.equals(data.url, KUrl::CompareWithoutTrailingSlash));
1041 if (isUrlEqual) {
1042 return;
1043 }
1044
1045 emit urlAboutToBeChanged(url);
1046
1047 if (d->m_historyIndex > 0) {
1048 // If an URL is set when the history index is not at the end (= 0),
1049 // then clear all previous history elements so that a new history
1050 // tree is started from the current position.
1051 QList<LocationData>::iterator begin = d->m_history.begin();
1052 QList<LocationData>::iterator end = begin + d->m_historyIndex;
1053 d->m_history.erase(begin, end);
1054 d->m_historyIndex = 0;
1055 }
1056
1057 Q_ASSERT(d->m_historyIndex == 0);
1058 LocationData newData;
1059 newData.url = url;
1060 d->m_history.insert(0, newData);
1061
1062 // Prevent an endless growing of the history: remembering
1063 // the last 100 Urls should be enough...
1064 const int historyMax = 100;
1065 if (d->m_history.size() > historyMax) {
1066 QList<LocationData>::iterator begin = d->m_history.begin() + historyMax;
1067 QList<LocationData>::iterator end = d->m_history.end();
1068 d->m_history.erase(begin, end);
1069 }
1070
1071 emit historyChanged();
1072 emit urlChanged(url);
1073
1074 d->updateContent();
1075
1076 requestActivation();
1077}
1078
1079void KUrlNavigator::requestActivation()
1080{
1081 setActive(true);
1082}
1083
1084void KUrlNavigator::setFocus()
1085{
1086 if (isUrlEditable()) {
1087 d->m_pathBox->setFocus();
1088 } else {
1089 QWidget::setFocus();
1090 }
1091}
1092
1093#ifndef KDE_NO_DEPRECATED
1094void KUrlNavigator::setUrl(const KUrl& url)
1095{
1096 // deprecated
1097 setLocationUrl(url);
1098}
1099#endif
1100
1101#ifndef KDE_NO_DEPRECATED
1102void KUrlNavigator::saveRootUrl(const KUrl& url)
1103{
1104 // deprecated
1105 d->m_history[d->m_historyIndex].rootUrl = url;
1106}
1107#endif
1108
1109#ifndef KDE_NO_DEPRECATED
1110void KUrlNavigator::savePosition(int x, int y)
1111{
1112 // deprecated
1113 d->m_history[d->m_historyIndex].pos = QPoint(x, y);
1114}
1115#endif
1116
1117void KUrlNavigator::keyPressEvent(QKeyEvent* event)
1118{
1119 if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
1120 setUrlEditable(false);
1121 } else {
1122 QWidget::keyPressEvent(event);
1123 }
1124}
1125
1126void KUrlNavigator::keyReleaseEvent(QKeyEvent* event)
1127{
1128 QWidget::keyReleaseEvent(event);
1129}
1130
1131void KUrlNavigator::mouseReleaseEvent(QMouseEvent* event)
1132{
1133 if (event->button() == Qt::MidButton) {
1134 const QRect bounds = d->m_toggleEditableMode->geometry();
1135 if (bounds.contains(event->pos())) {
1136 // The middle mouse button has been clicked above the
1137 // toggle-editable-mode-button. Paste the clipboard content
1138 // as location URL.
1139 QClipboard* clipboard = QApplication::clipboard();
1140 const QMimeData* mimeData = clipboard->mimeData();
1141 if (mimeData->hasText()) {
1142 const QString text = mimeData->text();
1143 setLocationUrl(KUrl(text));
1144 }
1145 }
1146 }
1147 QWidget::mouseReleaseEvent(event);
1148}
1149
1150void KUrlNavigator::resizeEvent(QResizeEvent* event)
1151{
1152 QTimer::singleShot(0, this, SLOT(updateButtonVisibility()));
1153 QWidget::resizeEvent(event);
1154}
1155
1156void KUrlNavigator::wheelEvent(QWheelEvent* event)
1157{
1158 setActive(true);
1159 QWidget::wheelEvent(event);
1160}
1161
1162bool KUrlNavigator::eventFilter(QObject* watched, QEvent* event)
1163{
1164 switch (event->type()) {
1165 case QEvent::FocusIn:
1166 if (watched == d->m_pathBox) {
1167 requestActivation();
1168 setFocus();
1169 }
1170 foreach (KUrlNavigatorButton* button, d->m_navButtons) {
1171 button->setShowMnemonic(true);
1172 }
1173 break;
1174
1175 case QEvent::FocusOut:
1176 foreach (KUrlNavigatorButton* button, d->m_navButtons) {
1177 button->setShowMnemonic(false);
1178 }
1179 break;
1180
1181 default:
1182 break;
1183 }
1184
1185 return QWidget::eventFilter(watched, event);
1186}
1187
1188int KUrlNavigator::historySize() const
1189{
1190 return d->m_history.count();
1191}
1192
1193int KUrlNavigator::historyIndex() const
1194{
1195 return d->m_historyIndex;
1196}
1197
1198KUrlComboBox* KUrlNavigator::editor() const
1199{
1200 return d->m_pathBox;
1201}
1202
1203void KUrlNavigator::setCustomProtocols(const QStringList &protocols)
1204{
1205 d->m_customProtocols = protocols;
1206 d->m_protocols->setCustomProtocols(d->m_customProtocols);
1207}
1208
1209QStringList KUrlNavigator::customProtocols() const
1210{
1211 return d->m_customProtocols;
1212}
1213
1214#ifndef KDE_NO_DEPRECATED
1215const KUrl& KUrlNavigator::url() const
1216{
1217 // deprecated
1218
1219 // Workaround required because of flawed interface ('const KUrl&' is returned
1220 // instead of 'KUrl'): remember the URL to prevent a dangling pointer
1221 static KUrl url;
1222 url = locationUrl();
1223 return url;
1224}
1225#endif
1226
1227#ifndef KDE_NO_DEPRECATED
1228KUrl KUrlNavigator::url(int index) const
1229{
1230 // deprecated
1231 return d->buttonUrl(index);
1232}
1233#endif
1234
1235#ifndef KDE_NO_DEPRECATED
1236KUrl KUrlNavigator::historyUrl(int historyIndex) const
1237{
1238 // deprecated
1239 return locationUrl(historyIndex);
1240}
1241#endif
1242
1243#ifndef KDE_NO_DEPRECATED
1244const KUrl& KUrlNavigator::savedRootUrl() const
1245{
1246 // deprecated
1247
1248 // Workaround required because of flawed interface ('const KUrl&' is returned
1249 // instead of 'KUrl'): remember the root URL to prevent a dangling pointer
1250 static KUrl rootUrl;
1251 rootUrl = d->m_history[d->m_historyIndex].rootUrl;
1252 return rootUrl;
1253}
1254#endif
1255
1256#ifndef KDE_NO_DEPRECATED
1257QPoint KUrlNavigator::savedPosition() const
1258{
1259 // deprecated
1260 return d->m_history[d->m_historyIndex].pos;
1261}
1262#endif
1263
1264#ifndef KDE_NO_DEPRECATED
1265void KUrlNavigator::setHomeUrl(const QString& homeUrl)
1266{
1267 // deprecated
1268 setLocationUrl(KUrl(homeUrl));
1269}
1270#endif
1271
1272#include "kurlnavigator.moc"
1273