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