]> cloud.milkyroute.net Git - dolphin.git/blob - src/urlnavigator.cpp
Avoid to use QUrl::toString, as per Davids suggestion.
[dolphin.git] / src / urlnavigator.cpp
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 * *
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. *
10 * *
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. *
15 * *
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 ***************************************************************************/
21
22 #include "urlnavigator.h"
23
24 #include "bookmarkselector.h"
25 #include "dolphinsettings.h"
26 #include "dolphin_generalsettings.h"
27 #include "protocolcombo.h"
28 #include "urlnavigatorbutton.h"
29
30 #include <assert.h>
31
32 #include <kfileitem.h>
33 #include <kicon.h>
34 #include <klocale.h>
35 #include <kprotocolinfo.h>
36 #include <kurlcombobox.h>
37 #include <kurlcompletion.h>
38
39 #include <QApplication>
40 #include <QClipboard>
41 #include <QDir>
42 #include <QHBoxLayout>
43 #include <QLabel>
44 #include <QLineEdit>
45 #include <QMouseEvent>
46 #include <QToolButton>
47
48 UrlNavigator::HistoryElem::HistoryElem() :
49 m_url(),
50 m_currentFileName(),
51 m_contentsX(0),
52 m_contentsY(0)
53 {
54 }
55
56 UrlNavigator::HistoryElem::HistoryElem(const KUrl& url) :
57 m_url(url),
58 m_currentFileName(),
59 m_contentsX(0),
60 m_contentsY(0)
61 {
62 }
63
64 UrlNavigator::HistoryElem::~HistoryElem()
65 {
66 }
67
68 UrlNavigator::UrlNavigator(const KUrl& url,
69 QWidget* parent) :
70 QWidget(parent),
71 m_active(true),
72 m_showHiddenFiles(false),
73 m_historyIndex(0),
74 m_layout(0),
75 m_protocols(0),
76 m_protocolSeparator(0),
77 m_host(0),
78 m_filler(0)
79 {
80 m_layout = new QHBoxLayout();
81 m_layout->setSpacing(0);
82 m_layout->setMargin(0);
83
84 m_history.prepend(HistoryElem(url));
85
86 QFontMetrics fontMetrics(font());
87 setMinimumHeight(fontMetrics.height() + 10);
88
89 // intialize toggle button which switches between the breadcrumb view
90 // and the traditional view
91 m_toggleButton = new QToolButton();
92 m_toggleButton->setCheckable(true);
93 m_toggleButton->setAutoRaise(true);
94 m_toggleButton->setIcon(KIcon("editinput")); // TODO: is just a placeholder icon (?)
95 m_toggleButton->setFocusPolicy(Qt::NoFocus);
96 m_toggleButton->setMinimumHeight(minimumHeight());
97 connect(m_toggleButton, SIGNAL(clicked()),
98 this, SLOT(switchView()));
99 if (DolphinSettings::instance().generalSettings()->editableUrl()) {
100 m_toggleButton->toggle();
101 }
102
103 // initialize the bookmark selector
104 m_bookmarkSelector = new BookmarkSelector(this);
105 connect(m_bookmarkSelector, SIGNAL(bookmarkActivated(const KUrl&)),
106 this, SLOT(setUrl(const KUrl&)));
107
108 // initialize the path box of the traditional view
109 m_pathBox = new KUrlComboBox(KUrlComboBox::Directories, true, this);
110
111 KUrlCompletion* kurlCompletion = new KUrlCompletion(KUrlCompletion::DirCompletion);
112 m_pathBox->setCompletionObject(kurlCompletion);
113 m_pathBox->setAutoDeleteCompletionObject(true);
114
115 connect(m_pathBox, SIGNAL(returnPressed(const QString&)),
116 this, SLOT(slotReturnPressed(const QString&)));
117 connect(m_pathBox, SIGNAL(urlActivated(const KUrl&)),
118 this, SLOT(slotUrlActivated(const KUrl&)));
119
120 //connect(dolphinView, SIGNAL(redirection(const KUrl&, const KUrl&)),
121 // this, SLOT(slotRedirection(const KUrl&, const KUrl&)));
122
123 // Append a filler widget at the end, which automatically resizes to the
124 // maximum available width. This assures that the URL navigator uses the
125 // whole width, so that the clipboard content can be dropped.
126 m_filler = new QWidget();
127 m_filler->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
128
129 m_layout->addWidget(m_toggleButton);
130 m_layout->addWidget(m_bookmarkSelector);
131 m_layout->addWidget(m_pathBox);
132 m_layout->addWidget(m_filler);
133 setLayout(m_layout);
134
135 updateContent();
136 }
137
138 UrlNavigator::~UrlNavigator()
139 {
140 }
141
142 const KUrl& UrlNavigator::url() const
143 {
144 assert(!m_history.empty());
145 QLinkedList<HistoryElem>::const_iterator it = m_history.begin();
146 it += m_historyIndex;
147 return (*it).url();
148 }
149
150 KUrl UrlNavigator::url(int index) const
151 {
152 assert(index >= 0);
153 // keep scheme, hostname etc. maybe we will need this in the future
154 // for e.g. browsing ftp repositories.
155 KUrl newurl(url());
156 newurl.setPath(QString());
157 QString path(url().path());
158
159 if (!path.isEmpty()) {
160 if (index == 0) //prevent the last "/" from being stripped
161 path = "/"; //or we end up with an empty path
162 else
163 path = path.section('/', 0, index);
164 }
165
166 newurl.setPath(path);
167 return newurl;
168 }
169
170 const QLinkedList<UrlNavigator::HistoryElem>& UrlNavigator::history(int& index) const
171 {
172 index = m_historyIndex;
173 return m_history;
174 }
175
176 void UrlNavigator::goBack()
177 {
178 updateHistoryElem();
179
180 const int count = m_history.count();
181 if (m_historyIndex < count - 1) {
182 ++m_historyIndex;
183 updateContent();
184 emit urlChanged(url());
185 emit historyChanged();
186 }
187 }
188
189 void UrlNavigator::goForward()
190 {
191 if (m_historyIndex > 0) {
192 --m_historyIndex;
193 updateContent();
194 emit urlChanged(url());
195 emit historyChanged();
196 }
197 }
198
199 void UrlNavigator::goUp()
200 {
201 setUrl(url().upUrl());
202 }
203
204 void UrlNavigator::goHome()
205 {
206 setUrl(DolphinSettings::instance().generalSettings()->homeUrl());
207 }
208
209 void UrlNavigator::setUrlEditable(bool editable)
210 {
211 if (isUrlEditable() != editable) {
212 m_toggleButton->toggle();
213 switchView();
214 }
215 }
216
217 bool UrlNavigator::isUrlEditable() const
218 {
219 return m_toggleButton->isChecked();
220 }
221
222 void UrlNavigator::editUrl(bool editOrBrowse)
223 {
224 setUrlEditable(editOrBrowse);
225 if (editOrBrowse) {
226 m_pathBox->setFocus();
227 }
228 }
229
230 void UrlNavigator::setActive(bool active)
231 {
232 if (active != m_active) {
233 m_active = active;
234 update();
235 if (active) {
236 emit activated();
237 }
238 }
239 }
240
241 void UrlNavigator::setShowHiddenFiles( bool show )
242 {
243 m_showHiddenFiles = show;
244 }
245
246 void UrlNavigator::dropUrls(const KUrl::List& urls,
247 const KUrl& destination)
248 {
249 emit urlsDropped(urls, destination);
250 }
251
252 void UrlNavigator::setUrl(const KUrl& url)
253 {
254 QString urlStr(url.pathOrUrl());
255
256 // TODO: a patch has been submitted by Filip Brcic which adjusts
257 // the URL for tar and zip files. See https://bugs.kde.org/show_bug.cgi?id=142781
258 // for details. The URL navigator part of the patch has not been committed yet,
259 // as the URL navigator will be subject of change and
260 // we might think of a more generic approach to check the protocol + MIME type for
261 // this use case.
262
263 //kDebug() << "setUrl(" << url << ")" << endl;
264 if ( urlStr.length() > 0 && urlStr.at(0) == '~') {
265 // replace '~' by the home directory
266 urlStr.remove(0, 1);
267 urlStr.insert(0, QDir::home().path());
268 }
269
270 const KUrl transformedUrl(urlStr);
271
272 if (m_historyIndex > 0) {
273 // Check whether the previous element of the history has the same Url.
274 // If yes, just go forward instead of inserting a duplicate history
275 // element.
276 QLinkedList<HistoryElem>::const_iterator it = m_history.begin();
277 it += m_historyIndex - 1;
278 const KUrl& nextUrl = (*it).url();
279 if (transformedUrl == nextUrl) {
280 goForward();
281 // kDebug() << "goin' forward in history" << endl;
282 return;
283 }
284 }
285
286 QLinkedList<HistoryElem>::iterator it = m_history.begin() + m_historyIndex;
287 const KUrl& currUrl = (*it).url();
288 if (currUrl == transformedUrl) {
289 // don't insert duplicate history elements
290 // kDebug() << "currUrl == transformedUrl" << endl;
291 return;
292 }
293
294 updateHistoryElem();
295 m_history.insert(it, HistoryElem(transformedUrl));
296
297 updateContent();
298
299 emit urlChanged(transformedUrl);
300 emit historyChanged();
301
302 // Prevent an endless growing of the history: remembering
303 // the last 100 Urls should be enough...
304 if (m_historyIndex > 100) {
305 m_history.erase(m_history.begin());
306 --m_historyIndex;
307 }
308
309 /* kDebug() << "history starting ====================" << endl;
310 int i = 0;
311 for (QValueListIterator<UrlNavigator::HistoryElem> it = m_history.begin();
312 it != m_history.end();
313 ++it, ++i)
314 {
315 kDebug() << i << ": " << (*it).url() << endl;
316 }
317 kDebug() << "history done ========================" << endl;*/
318
319 requestActivation();
320 }
321
322 void UrlNavigator::requestActivation()
323 {
324 setActive(true);
325 }
326
327 void UrlNavigator::storeContentsPosition(int x, int y)
328 {
329 QLinkedList<HistoryElem>::iterator it = m_history.begin() + m_historyIndex;
330 (*it).setContentsX(x);
331 (*it).setContentsY(y);
332 }
333
334 void UrlNavigator::keyReleaseEvent(QKeyEvent* event)
335 {
336 QWidget::keyReleaseEvent(event);
337 if (isUrlEditable() && (event->key() == Qt::Key_Escape)) {
338 setUrlEditable(false);
339 }
340 }
341
342 void UrlNavigator::mouseReleaseEvent(QMouseEvent* event)
343 {
344 if (event->button() == Qt::MidButton) {
345 QClipboard* clipboard = QApplication::clipboard();
346 const QMimeData* mimeData = clipboard->mimeData();
347 if (mimeData->hasText()) {
348 const QString text = mimeData->text();
349 setUrl(KUrl(text));
350 }
351 }
352 QWidget::mouseReleaseEvent(event);
353 }
354
355 void UrlNavigator::slotReturnPressed(const QString& text)
356 {
357 // Parts of the following code have been taken
358 // from the class KateFileSelector located in
359 // kate/app/katefileselector.hpp of Kate.
360 // Copyright (C) 2001 Christoph Cullmann <cullmann@kde.org>
361 // Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
362 // Copyright (C) 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
363
364 KUrl typedUrl(text);
365 if (typedUrl.hasPass()) {
366 typedUrl.setPass(QString());
367 }
368
369 QStringList urls = m_pathBox->urls();
370 urls.removeAll(typedUrl.url());
371 urls.prepend(typedUrl.url());
372 m_pathBox->setUrls(urls, KUrlComboBox::RemoveBottom);
373
374 setUrl(typedUrl);
375 // The URL might have been adjusted by UrlNavigator::setUrl(), hence
376 // synchronize the result in the path box.
377 m_pathBox->setUrl(url());
378 }
379
380 void UrlNavigator::slotUrlActivated(const KUrl& url)
381 {
382 setUrl(url);
383 }
384
385 void UrlNavigator::slotRemoteHostActivated()
386 {
387 KUrl u = url();
388
389 QString host = m_host->text();
390 QString user;
391
392 int marker = host.indexOf("@");
393 if (marker != -1)
394 {
395 user = host.left(marker);
396 u.setUser(user);
397 host = host.right(host.length() - marker - 1);
398 }
399
400 marker = host.indexOf("/");
401 if (marker != -1)
402 {
403 u.setPath(host.right(host.length() - marker));
404 host.truncate(marker);
405 }
406 else
407 {
408 u.setPath("");
409 }
410
411 if (m_protocols->currentProtocol() != u.protocol() ||
412 host != u.host() ||
413 user != u.user())
414 {
415 u.setProtocol(m_protocols->currentProtocol());
416 u.setHost(m_host->text());
417
418 //TODO: get rid of this HACK for file:///!
419 if (u.protocol() == "file")
420 {
421 u.setHost("");
422 if (u.path().isEmpty())
423 {
424 u.setPath("/");
425 }
426 }
427
428 setUrl(u);
429 }
430 }
431
432 void UrlNavigator::slotProtocolChanged(const QString& protocol)
433 {
434 KUrl url;
435 url.setProtocol(protocol);
436 //url.setPath(KProtocolInfo::protocolClass(protocol) == ":local" ? "/" : "");
437 url.setPath("/");
438 QLinkedList<UrlNavigatorButton*>::const_iterator it = m_navButtons.begin();
439 const QLinkedList<UrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
440 while (it != itEnd) {
441 (*it)->close();
442 (*it)->deleteLater();
443 ++it;
444 }
445 m_navButtons.clear();
446
447 if (KProtocolInfo::protocolClass(protocol) == ":local") {
448 setUrl(url);
449 }
450 else {
451 if (!m_host) {
452 m_protocolSeparator = new QLabel("://", this);
453 appendWidget(m_protocolSeparator);
454 m_host = new QLineEdit(this);
455 appendWidget(m_host);
456
457 connect(m_host, SIGNAL(lostFocus()),
458 this, SLOT(slotRemoteHostActivated()));
459 connect(m_host, SIGNAL(returnPressed()),
460 this, SLOT(slotRemoteHostActivated()));
461 }
462 else {
463 m_host->setText("");
464 }
465 m_protocolSeparator->show();
466 m_host->show();
467 m_host->setFocus();
468 }
469 }
470
471 void UrlNavigator::slotRedirection(const KUrl& oldUrl, const KUrl& newUrl)
472 {
473 // kDebug() << "received redirection to " << newUrl << endl;
474 kDebug() << "received redirection from " << oldUrl << " to " << newUrl << endl;
475 /* UrlStack::iterator it = m_urls.find(oldUrl);
476 if (it != m_urls.end())
477 {
478 m_urls.erase(++it, m_urls.end());
479 }
480
481 m_urls.append(newUrl);*/
482 }
483
484 void UrlNavigator::switchView()
485 {
486 updateContent();
487 if (isUrlEditable()) {
488 m_pathBox->setFocus();
489 }
490 else {
491 setUrl(m_pathBox->currentText());
492 }
493 emit requestActivation();
494 }
495
496 void UrlNavigator::updateHistoryElem()
497 {
498 assert(m_historyIndex >= 0);
499 const KFileItem* item = 0; // TODO: m_dolphinView->currentFileItem();
500 if (item != 0) {
501 QLinkedList<HistoryElem>::iterator it = m_history.begin() + m_historyIndex;
502 (*it).setCurrentFileName(item->name());
503 }
504 }
505
506 void UrlNavigator::updateContent()
507 {
508 m_bookmarkSelector->updateSelection(url());
509
510 m_toggleButton->setToolTip(QString());
511 QString path(url().pathOrUrl());
512
513 // TODO: prevent accessing the DolphinMainWindow out from this scope
514 //const QAction* action = dolphinView()->mainWindow()->actionCollection()->action("editable_location");
515 // TODO: registry of default shortcuts
516 //QString shortcut = action? action->shortcut().toString() : "Ctrl+L";
517 const QString shortcut = "Ctrl+L";
518
519 if (m_toggleButton->isChecked()) {
520 delete m_protocols; m_protocols = 0;
521 delete m_protocolSeparator; m_protocolSeparator = 0;
522 delete m_host; m_host = 0;
523 deleteButtons();
524 m_filler->hide();
525
526 m_toggleButton->setToolTip(i18n("Browse (%1, Escape)", shortcut));
527
528 setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
529 m_pathBox->show();
530 m_pathBox->setUrl(url());
531 }
532 else {
533 m_toggleButton->setToolTip(i18n("Edit location (%1)", shortcut));
534
535 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
536 m_pathBox->hide();
537 m_filler->show();
538
539 // get the data from the currently selected bookmark
540 KBookmark bookmark = m_bookmarkSelector->selectedBookmark();
541 //int bookmarkIndex = m_bookmarkSelector->selectedIndex();
542
543 QString bookmarkPath;
544 if (bookmark.isNull()) {
545 // No bookmark is a part of the current Url.
546 // The following code tries to guess the bookmark
547 // path. E. g. "fish://root@192.168.0.2/var/lib" writes
548 // "fish://root@192.168.0.2" to 'bookmarkPath', which leads to the
549 // navigation indication 'Custom Path > var > lib".
550 int idx = path.indexOf(QString("//"));
551 idx = path.indexOf("/", (idx < 0) ? 0 : idx + 2);
552 bookmarkPath = (idx < 0) ? path : path.left(idx);
553 }
554 else {
555 bookmarkPath = bookmark.url().pathOrUrl();
556 }
557 const uint len = bookmarkPath.length();
558
559 // calculate the start point for the URL navigator buttons by counting
560 // the slashs inside the bookmark URL
561 int slashCount = 0;
562 for (uint i = 0; i < len; ++i) {
563 if (bookmarkPath.at(i) == QChar('/')) {
564 ++slashCount;
565 }
566 }
567 if ((len > 0) && bookmarkPath.at(len - 1) == QChar('/')) {
568 assert(slashCount > 0);
569 --slashCount;
570 }
571
572 if (!url().isLocalFile() && bookmark.isNull()) {
573 QString protocol = url().protocol();
574 if (!m_protocols) {
575 deleteButtons();
576 m_protocols = new ProtocolCombo(protocol, this);
577 appendWidget(m_protocols);
578 connect(m_protocols, SIGNAL(activated(const QString&)),
579 this, SLOT(slotProtocolChanged(const QString&)));
580 }
581 else {
582 m_protocols->setProtocol(protocol);
583 }
584 m_protocols->show();
585
586 if (KProtocolInfo::protocolClass(protocol) != ":local") {
587 QString hostText = url().host();
588
589 if (!url().user().isEmpty()) {
590 hostText = url().user() + '@' + hostText;
591 }
592
593 if (!m_host) {
594 m_protocolSeparator = new QLabel("://", this);
595 appendWidget(m_protocolSeparator);
596 m_host = new QLineEdit(hostText, this);
597 appendWidget(m_host);
598
599 connect(m_host, SIGNAL(lostFocus()),
600 this, SLOT(slotRemoteHostActivated()));
601 connect(m_host, SIGNAL(returnPressed()),
602 this, SLOT(slotRemoteHostActivated()));
603 }
604 else {
605 m_host->setText(hostText);
606 }
607 m_protocolSeparator->show();
608 m_host->show();
609 }
610 else {
611 delete m_protocolSeparator; m_protocolSeparator = 0;
612 delete m_host; m_host = 0;
613 }
614 }
615 else if (m_protocols) {
616 m_protocols->hide();
617
618 if (m_host) {
619 m_protocolSeparator->hide();
620 m_host->hide();
621 }
622 }
623
624 updateButtons(path, slashCount);
625 }
626 }
627
628 void UrlNavigator::updateButtons(const QString& path, int startIndex)
629 {
630 QLinkedList<UrlNavigatorButton*>::iterator it = m_navButtons.begin();
631 const QLinkedList<UrlNavigatorButton*>::const_iterator itEnd = m_navButtons.end();
632 bool createButton = false;
633
634 int idx = startIndex;
635 bool hasNext = true;
636 do {
637 createButton = (it == itEnd);
638
639 const QString dirName = path.section('/', idx, idx);
640 const bool isFirstButton = (idx == startIndex);
641 hasNext = isFirstButton || !dirName.isEmpty();
642 if (hasNext) {
643 QString text;
644 if (isFirstButton) {
645 // the first URL navigator button should get the name of the
646 // bookmark instead of the directory name
647 const KBookmark bookmark = m_bookmarkSelector->selectedBookmark();
648 text = bookmark.text();
649 if (text.isEmpty()) {
650 if (url().isLocalFile()) {
651 text = i18n("Custom Path");
652 }
653 else {
654 ++idx;
655 continue;
656 }
657 }
658 }
659
660 UrlNavigatorButton* button = 0;
661 if (createButton) {
662 button = new UrlNavigatorButton(idx, this);
663 appendWidget(button);
664 }
665 else {
666 button = *it;
667 button->setIndex(idx);
668 }
669
670 if (isFirstButton) {
671 button->setText(text);
672 }
673
674 if (createButton) {
675 button->show();
676 m_navButtons.append(button);
677 }
678 else {
679 ++it;
680 }
681 ++idx;
682 }
683 } while (hasNext);
684
685 // delete buttons which are not used anymore
686 QLinkedList<UrlNavigatorButton*>::iterator itBegin = it;
687 while (it != itEnd) {
688 (*it)->close();
689 (*it)->deleteLater();
690 ++it;
691 }
692 m_navButtons.erase(itBegin, m_navButtons.end());
693 }
694
695 void UrlNavigator::deleteButtons()
696 {
697 QLinkedList<UrlNavigatorButton*>::iterator itBegin = m_navButtons.begin();
698 QLinkedList<UrlNavigatorButton*>::iterator itEnd = m_navButtons.end();
699 QLinkedList<UrlNavigatorButton*>::iterator it = itBegin;
700 while (it != itEnd) {
701 (*it)->close();
702 (*it)->deleteLater();
703 ++it;
704 }
705 m_navButtons.erase(itBegin, itEnd);
706 }
707
708 void UrlNavigator::appendWidget(QWidget* widget)
709 {
710 m_layout->insertWidget(m_layout->count() - 1, widget);
711 }
712
713 #include "urlnavigator.moc"