1/* This file is part of the KDE project
2 Copyright (C) 2007 Kevin Ottens <ervin@kde.org>
3 Copyright (C) 2008 Rafael Fernández López <ereslibre@kde.org>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18
19*/
20
21#include "kfileplacesview.h"
22#include "kfileplacesview_p.h"
23
24#include <QtCore/QTimeLine>
25#include <QtCore/QTimer>
26#include <QtGui/QPainter>
27#include <QtGui/QAbstractItemDelegate>
28#include <QtGui/QKeyEvent>
29#include <QtGui/QApplication>
30#include <QtGui/QScrollBar>
31
32#include <kdebug.h>
33
34#include <kmenu.h>
35#include <kcomponentdata.h>
36#include <kdirnotify.h>
37#include <kglobalsettings.h>
38#include <kiconloader.h>
39#include <klocale.h>
40#include <kmessagebox.h>
41#include <knotification.h>
42#include <kio/job.h>
43#include <kio/jobuidelegate.h>
44#include <kjob.h>
45#include <kcapacitybar.h>
46#include <kdiskfreespaceinfo.h>
47#include <solid/storageaccess.h>
48#include <solid/storagedrive.h>
49#include <solid/storagevolume.h>
50#include <solid/opticaldrive.h>
51#include <solid/opticaldisc.h>
52
53#include "kfileplaceeditdialog.h"
54#include "kfileplacesmodel.h"
55
56#define LATERAL_MARGIN 4
57#define CAPACITYBAR_HEIGHT 6
58
59class KFilePlacesViewDelegate : public QAbstractItemDelegate
60{
61public:
62 KFilePlacesViewDelegate(KFilePlacesView *parent);
63 virtual ~KFilePlacesViewDelegate();
64 virtual QSize sizeHint(const QStyleOptionViewItem &option,
65 const QModelIndex &index) const;
66 virtual void paint(QPainter *painter,
67 const QStyleOptionViewItem &option,
68 const QModelIndex &index) const;
69
70 int iconSize() const;
71 void setIconSize(int newSize);
72
73 void addAppearingItem(const QModelIndex &index);
74 void setAppearingItemProgress(qreal value);
75 void addDisappearingItem(const QModelIndex &index);
76 void setDisappearingItemProgress(qreal value);
77
78 void setShowHoverIndication(bool show);
79
80 void addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine);
81 void removeFadeAnimation(const QModelIndex &index);
82 QModelIndex indexForFadeAnimation(QTimeLine *timeLine) const;
83 QTimeLine *fadeAnimationForIndex(const QModelIndex &index) const;
84
85 qreal contentsOpacity(const QModelIndex &index) const;
86
87private:
88 KFilePlacesView *m_view;
89 int m_iconSize;
90
91 QList<QPersistentModelIndex> m_appearingItems;
92 int m_appearingIconSize;
93 qreal m_appearingOpacity;
94
95 QList<QPersistentModelIndex> m_disappearingItems;
96 int m_disappearingIconSize;
97 qreal m_disappearingOpacity;
98
99 bool m_showHoverIndication;
100
101 QMap<QPersistentModelIndex, QTimeLine*> m_timeLineMap;
102 QMap<QTimeLine*, QPersistentModelIndex> m_timeLineInverseMap;
103};
104
105KFilePlacesViewDelegate::KFilePlacesViewDelegate(KFilePlacesView *parent) :
106 QAbstractItemDelegate(parent),
107 m_view(parent),
108 m_iconSize(48),
109 m_appearingIconSize(0),
110 m_appearingOpacity(0.0),
111 m_disappearingIconSize(0),
112 m_disappearingOpacity(0.0),
113 m_showHoverIndication(true)
114{
115}
116
117KFilePlacesViewDelegate::~KFilePlacesViewDelegate()
118{
119}
120
121QSize KFilePlacesViewDelegate::sizeHint(const QStyleOptionViewItem &option,
122 const QModelIndex &index) const
123{
124 int iconSize = m_iconSize;
125 if (m_appearingItems.contains(index)) {
126 iconSize = m_appearingIconSize;
127 } else if (m_disappearingItems.contains(index)) {
128 iconSize = m_disappearingIconSize;
129 }
130
131 const KFilePlacesModel *filePlacesModel = static_cast<const KFilePlacesModel*>(index.model());
132 Solid::Device device = filePlacesModel->deviceForIndex(index);
133
134 return QSize(option.rect.width(), option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height()));
135}
136
137void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
138{
139 painter->save();
140
141 if (m_appearingItems.contains(index)) {
142 painter->setOpacity(m_appearingOpacity);
143 } else if (m_disappearingItems.contains(index)) {
144 painter->setOpacity(m_disappearingOpacity);
145 }
146
147 QStyleOptionViewItemV4 opt = option;
148 if (!m_showHoverIndication) {
149 opt.state &= ~QStyle::State_MouseOver;
150 }
151 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter);
152 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(index.model());
153
154 bool isLTR = option.direction == Qt::LeftToRight;
155
156 QIcon icon = index.model()->data(index, Qt::DecorationRole).value<QIcon>();
157 QPixmap pm = icon.pixmap(m_iconSize, m_iconSize);
158 QPoint point(isLTR ? option.rect.left() + LATERAL_MARGIN
159 : option.rect.right() - LATERAL_MARGIN - m_iconSize, option.rect.top() + (option.rect.height() - m_iconSize) / 2);
160 painter->drawPixmap(point, pm);
161
162 if (option.state & QStyle::State_Selected) {
163 QPalette::ColorGroup cg = QPalette::Active;
164 if (!(option.state & QStyle::State_Enabled)) {
165 cg = QPalette::Disabled;
166 } else if (!(option.state & QStyle::State_Active)) {
167 cg = QPalette::Inactive;
168 }
169 painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
170 }
171
172 QRect rectText;
173
174 const KUrl url = placesModel->url(index);
175 bool drawCapacityBar = false;
176 if (url.isLocalFile()) {
177 const QString mountPointPath = placesModel->url(index).toLocalFile();
178 const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(mountPointPath);
179 drawCapacityBar = info.size() != 0 &&
180 placesModel->data(index, KFilePlacesModel::CapacityBarRecommendedRole).toBool();
181
182 if (drawCapacityBar && contentsOpacity(index) > 0)
183 {
184 painter->save();
185 painter->setOpacity(painter->opacity() * contentsOpacity(index));
186
187 int height = option.fontMetrics.height() + CAPACITYBAR_HEIGHT;
188 rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + option.rect.left()
189 : 0, option.rect.top() + (option.rect.height() / 2 - height / 2), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.fontMetrics.height());
190 painter->drawText(rectText, Qt::AlignLeft | Qt::AlignTop, option.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width()));
191 QRect capacityRect(isLTR ? rectText.x() : LATERAL_MARGIN, rectText.bottom() - 1, rectText.width() - LATERAL_MARGIN, CAPACITYBAR_HEIGHT);
192 KCapacityBar capacityBar(KCapacityBar::DrawTextInline);
193 capacityBar.setValue((info.used() * 100) / info.size());
194 capacityBar.drawCapacityBar(painter, capacityRect);
195
196 painter->restore();
197
198 painter->save();
199 painter->setOpacity(painter->opacity() * (1 - contentsOpacity(index)));
200 }
201 }
202
203 rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + option.rect.left()
204 : 0, option.rect.top(), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.rect.height());
205 painter->drawText(rectText, Qt::AlignLeft | Qt::AlignVCenter, option.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width()));
206
207 if (drawCapacityBar && contentsOpacity(index) > 0) {
208 painter->restore();
209 }
210
211 painter->restore();
212}
213
214int KFilePlacesViewDelegate::iconSize() const
215{
216 return m_iconSize;
217}
218
219void KFilePlacesViewDelegate::setIconSize(int newSize)
220{
221 m_iconSize = newSize;
222}
223
224void KFilePlacesViewDelegate::addAppearingItem(const QModelIndex &index)
225{
226 m_appearingItems << index;
227}
228
229void KFilePlacesViewDelegate::setAppearingItemProgress(qreal value)
230{
231 if (value<=0.25) {
232 m_appearingOpacity = 0.0;
233 m_appearingIconSize = iconSize()*value*4;
234
235 if (m_appearingIconSize>=m_iconSize) {
236 m_appearingIconSize = m_iconSize;
237 }
238 } else {
239 m_appearingIconSize = m_iconSize;
240 m_appearingOpacity = (value-0.25)*4/3;
241
242 if (value>=1.0) {
243 m_appearingItems.clear();
244 }
245 }
246}
247
248void KFilePlacesViewDelegate::addDisappearingItem(const QModelIndex &index)
249{
250 m_disappearingItems << index;
251}
252
253void KFilePlacesViewDelegate::setDisappearingItemProgress(qreal value)
254{
255 value = 1.0 - value;
256
257 if (value<=0.25) {
258 m_disappearingOpacity = 0.0;
259 m_disappearingIconSize = iconSize()*value*4;
260
261 if (m_disappearingIconSize>=m_iconSize) {
262 m_disappearingIconSize = m_iconSize;
263 }
264
265 if (value<=0.0) {
266 m_disappearingItems.clear();
267 }
268 } else {
269 m_disappearingIconSize = m_iconSize;
270 m_disappearingOpacity = (value-0.25)*4/3;
271 }
272}
273
274void KFilePlacesViewDelegate::setShowHoverIndication(bool show)
275{
276 m_showHoverIndication = show;
277}
278
279void KFilePlacesViewDelegate::addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine)
280{
281 m_timeLineMap.insert(index, timeLine);
282 m_timeLineInverseMap.insert(timeLine, index);
283}
284
285void KFilePlacesViewDelegate::removeFadeAnimation(const QModelIndex &index)
286{
287 QTimeLine *timeLine = m_timeLineMap.value(index, 0);
288 m_timeLineMap.remove(index);
289 m_timeLineInverseMap.remove(timeLine);
290}
291
292QModelIndex KFilePlacesViewDelegate::indexForFadeAnimation(QTimeLine *timeLine) const
293{
294 return m_timeLineInverseMap.value(timeLine, QModelIndex());
295}
296
297QTimeLine *KFilePlacesViewDelegate::fadeAnimationForIndex(const QModelIndex &index) const
298{
299 return m_timeLineMap.value(index, 0);
300}
301
302qreal KFilePlacesViewDelegate::contentsOpacity(const QModelIndex &index) const
303{
304 QTimeLine *timeLine = fadeAnimationForIndex(index);
305 if (timeLine) {
306 return timeLine->currentValue();
307 }
308 return 0;
309}
310
311class KFilePlacesView::Private
312{
313public:
314 Private(KFilePlacesView *parent) : q(parent), watcher(new KFilePlacesEventWatcher(q)) { }
315
316 enum FadeType {
317 FadeIn = 0,
318 FadeOut
319 };
320
321 KFilePlacesView * const q;
322
323 KUrl currentUrl;
324 bool autoResizeItems;
325 bool showAll;
326 bool smoothItemResizing;
327 bool dropOnPlace;
328 bool dragging;
329 Solid::StorageAccess *lastClickedStorage;
330 QPersistentModelIndex lastClickedIndex;
331
332 QRect dropRect;
333
334 void setCurrentIndex(const QModelIndex &index);
335 void adaptItemSize();
336 void updateHiddenRows();
337 bool insertAbove(const QRect &itemRect, const QPoint &pos) const;
338 bool insertBelow(const QRect &itemRect, const QPoint &pos) const;
339 int insertIndicatorHeight(int itemHeight) const;
340 void fadeCapacityBar(const QModelIndex &index, FadeType fadeType);
341
342 void _k_placeClicked(const QModelIndex &index);
343 void _k_placeEntered(const QModelIndex &index);
344 void _k_placeLeft(const QModelIndex &index);
345 void _k_storageSetupDone(const QModelIndex &index, bool success);
346 void _k_adaptItemsUpdate(qreal value);
347 void _k_itemAppearUpdate(qreal value);
348 void _k_itemDisappearUpdate(qreal value);
349 void _k_enableSmoothItemResizing();
350 void _k_trashUpdated(KJob *job);
351 void _k_capacityBarFadeValueChanged();
352 void _k_triggerDevicePolling();
353
354 QTimeLine adaptItemsTimeline;
355 int oldSize, endSize;
356
357 QTimeLine itemAppearTimeline;
358 QTimeLine itemDisappearTimeline;
359
360 KFilePlacesEventWatcher *const watcher;
361 KFilePlacesViewDelegate *delegate;
362 QTimer pollDevices;
363 int pollingRequestCount;
364};
365
366KFilePlacesView::KFilePlacesView(QWidget *parent)
367 : QListView(parent), d(new Private(this))
368{
369 d->showAll = false;
370 d->smoothItemResizing = false;
371 d->dropOnPlace = false;
372 d->autoResizeItems = true;
373 d->dragging = false;
374 d->lastClickedStorage = 0;
375 d->pollingRequestCount = 0;
376 d->delegate = new KFilePlacesViewDelegate(this);
377
378 setSelectionRectVisible(false);
379 setSelectionMode(SingleSelection);
380
381 setDragEnabled(true);
382 setAcceptDrops(true);
383 setMouseTracking(true);
384 setDropIndicatorShown(false);
385 setFrameStyle(QFrame::NoFrame);
386
387 setResizeMode(Adjust);
388 setItemDelegate(d->delegate);
389
390 QPalette palette = viewport()->palette();
391 palette.setColor(viewport()->backgroundRole(), Qt::transparent);
392 palette.setColor(viewport()->foregroundRole(), palette.color(QPalette::WindowText));
393 viewport()->setPalette(palette);
394
395 connect(this, SIGNAL(clicked(QModelIndex)),
396 this, SLOT(_k_placeClicked(QModelIndex)));
397 // Note: Don't connect to the activated() signal, as the behavior when it is
398 // committed depends on the used widget style. The click behavior of
399 // KFilePlacesView should be style independent.
400
401 connect(&d->adaptItemsTimeline, SIGNAL(valueChanged(qreal)),
402 this, SLOT(_k_adaptItemsUpdate(qreal)));
403 d->adaptItemsTimeline.setDuration(500);
404 d->adaptItemsTimeline.setUpdateInterval(5);
405 d->adaptItemsTimeline.setCurveShape(QTimeLine::EaseInOutCurve);
406
407 connect(&d->itemAppearTimeline, SIGNAL(valueChanged(qreal)),
408 this, SLOT(_k_itemAppearUpdate(qreal)));
409 d->itemAppearTimeline.setDuration(500);
410 d->itemAppearTimeline.setUpdateInterval(5);
411 d->itemAppearTimeline.setCurveShape(QTimeLine::EaseInOutCurve);
412
413 connect(&d->itemDisappearTimeline, SIGNAL(valueChanged(qreal)),
414 this, SLOT(_k_itemDisappearUpdate(qreal)));
415 d->itemDisappearTimeline.setDuration(500);
416 d->itemDisappearTimeline.setUpdateInterval(5);
417 d->itemDisappearTimeline.setCurveShape(QTimeLine::EaseInOutCurve);
418
419 viewport()->installEventFilter(d->watcher);
420 connect(d->watcher, SIGNAL(entryEntered(QModelIndex)),
421 this, SLOT(_k_placeEntered(QModelIndex)));
422 connect(d->watcher, SIGNAL(entryLeft(QModelIndex)),
423 this, SLOT(_k_placeLeft(QModelIndex)));
424
425 d->pollDevices.setInterval(5000);
426 connect(&d->pollDevices, SIGNAL(timeout()), this, SLOT(_k_triggerDevicePolling()));
427
428 // FIXME: this is necessary to avoid flashes of black with some widget styles.
429 // could be a bug in Qt (e.g. QAbstractScrollArea) or KFilePlacesView, but has not
430 // yet been tracked down yet. until then, this works and is harmlessly enough.
431 // in fact, some QStyle (Oxygen, Skulpture, others?) do this already internally.
432 // See br #242358 for more information
433 verticalScrollBar()->setAttribute(Qt::WA_OpaquePaintEvent, false);
434}
435
436KFilePlacesView::~KFilePlacesView()
437{
438 delete d;
439}
440
441void KFilePlacesView::setDropOnPlaceEnabled(bool enabled)
442{
443 d->dropOnPlace = enabled;
444}
445
446bool KFilePlacesView::isDropOnPlaceEnabled() const
447{
448 return d->dropOnPlace;
449}
450
451void KFilePlacesView::setAutoResizeItemsEnabled(bool enabled)
452{
453 d->autoResizeItems = enabled;
454}
455
456bool KFilePlacesView::isAutoResizeItemsEnabled() const
457{
458 return d->autoResizeItems;
459}
460
461void KFilePlacesView::setUrl(const KUrl &url)
462{
463 KUrl oldUrl = d->currentUrl;
464 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
465
466 if (placesModel==0) return;
467
468 QModelIndex index = placesModel->closestItem(url);
469 QModelIndex current = selectionModel()->currentIndex();
470
471 if (index.isValid()) {
472 if (current!=index && placesModel->isHidden(current) && !d->showAll) {
473 KFilePlacesViewDelegate *delegate = static_cast<KFilePlacesViewDelegate*>(itemDelegate());
474 delegate->addDisappearingItem(current);
475
476 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
477 delegate->setDisappearingItemProgress(0.0);
478 d->itemDisappearTimeline.start();
479 }
480 }
481
482 if (current!=index && placesModel->isHidden(index) && !d->showAll) {
483 KFilePlacesViewDelegate *delegate = static_cast<KFilePlacesViewDelegate*>(itemDelegate());
484 delegate->addAppearingItem(index);
485
486 if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
487 delegate->setAppearingItemProgress(0.0);
488 d->itemAppearTimeline.start();
489 }
490
491 setRowHidden(index.row(), false);
492 }
493
494 d->currentUrl = url;
495 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
496 } else {
497 d->currentUrl = KUrl();
498 selectionModel()->clear();
499 }
500
501 if (!current.isValid()) {
502 d->updateHiddenRows();
503 }
504}
505
506void KFilePlacesView::setShowAll(bool showAll)
507{
508 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
509
510 if (placesModel==0) return;
511
512 d->showAll = showAll;
513
514 KFilePlacesViewDelegate *delegate = static_cast<KFilePlacesViewDelegate*>(itemDelegate());
515
516 int rowCount = placesModel->rowCount();
517 QModelIndex current = placesModel->closestItem(d->currentUrl);
518
519 if (showAll) {
520 d->updateHiddenRows();
521
522 for (int i=0; i<rowCount; ++i) {
523 QModelIndex index = placesModel->index(i, 0);
524 if (index!=current && placesModel->isHidden(index)) {
525 delegate->addAppearingItem(index);
526 }
527 }
528
529 if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
530 delegate->setAppearingItemProgress(0.0);
531 d->itemAppearTimeline.start();
532 }
533 } else {
534 for (int i=0; i<rowCount; ++i) {
535 QModelIndex index = placesModel->index(i, 0);
536 if (index!=current && placesModel->isHidden(index)) {
537 delegate->addDisappearingItem(index);
538 }
539 }
540
541 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
542 delegate->setDisappearingItemProgress(0.0);
543 d->itemDisappearTimeline.start();
544 }
545 }
546}
547
548void KFilePlacesView::keyPressEvent(QKeyEvent *event)
549{
550 QListView::keyPressEvent(event);
551 if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) {
552 d->_k_placeClicked(currentIndex());
553 }
554}
555
556void KFilePlacesView::contextMenuEvent(QContextMenuEvent *event)
557{
558 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
559 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate());
560
561 if (placesModel==0) return;
562
563 QModelIndex index = indexAt(event->pos());
564 QString label = placesModel->text(index).replace('&',"&&");
565
566 KMenu menu;
567
568 QAction *edit = 0;
569 QAction *hide = 0;
570 QAction *emptyTrash = 0;
571 QAction *eject = 0;
572 QAction *teardown = 0;
573 QAction *add = 0;
574 QAction *mainSeparator = 0;
575
576 if (index.isValid()) {
577 if (!placesModel->isDevice(index)) {
578 if (placesModel->url(index) == KUrl("trash:/")) {
579 emptyTrash = menu.addAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash"));
580 KConfig trashConfig("trashrc", KConfig::SimpleConfig);
581 emptyTrash->setEnabled(!trashConfig.group("Status").readEntry("Empty", true));
582 menu.addSeparator();
583 }
584 add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
585 mainSeparator = menu.addSeparator();
586 edit = menu.addAction(KIcon("document-properties"), i18n("&Edit Entry '%1'...", label));
587 } else {
588 eject = placesModel->ejectActionForIndex(index);
589 if (eject!=0) {
590 eject->setParent(&menu);
591 menu.addAction(eject);
592 }
593
594 teardown = placesModel->teardownActionForIndex(index);
595 if (teardown!=0) {
596 teardown->setParent(&menu);
597 menu.addAction(teardown);
598 }
599
600 if (teardown!=0 || eject!=0) {
601 mainSeparator = menu.addSeparator();
602 }
603 }
604 if (add == 0) {
605 add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
606 }
607
608 hide = menu.addAction(i18n("&Hide Entry '%1'", label));
609 hide->setCheckable(true);
610 hide->setChecked(placesModel->isHidden(index));
611 } else {
612 add = menu.addAction(KIcon("document-new"), i18n("Add Entry..."));
613 }
614
615 QAction *showAll = 0;
616 if (placesModel->hiddenCount()>0) {
617 showAll = new QAction(i18n("&Show All Entries"), &menu);
618 showAll->setCheckable(true);
619 showAll->setChecked(d->showAll);
620 if (mainSeparator == 0) {
621 mainSeparator = menu.addSeparator();
622 }
623 menu.insertAction(mainSeparator, showAll);
624 }
625
626 QAction* remove = 0;
627 if (index.isValid() && !placesModel->isDevice(index)) {
628 remove = menu.addAction( KIcon("edit-delete"), i18n("&Remove Entry '%1'", label));
629 }
630
631 menu.addActions(actions());
632
633 if (menu.isEmpty()) {
634 return;
635 }
636
637 QAction *result = menu.exec(event->globalPos());
638
639 if (emptyTrash != 0 && result == emptyTrash) {
640 const QString text = i18nc("@info", "Do you really want to empty the Trash? All items will be deleted.");
641 const bool del = KMessageBox::warningContinueCancel(window(),
642 text,
643 QString(),
644 KGuiItem(i18nc("@action:button", "Empty Trash"),
645 KIcon("user-trash"))
646 ) == KMessageBox::Continue;
647 if (del) {
648 QByteArray packedArgs;
649 QDataStream stream(&packedArgs, QIODevice::WriteOnly);
650 stream << int(1);
651 KIO::Job *job = KIO::special(KUrl("trash:/"), packedArgs);
652 KNotification::event("Trash: emptied", QString() , QPixmap() , 0, KNotification::DefaultEvent);
653 job->ui()->setWindow(parentWidget());
654 connect(job, SIGNAL(result(KJob*)), SLOT(_k_trashUpdated(KJob*)));
655 }
656 } else if (edit != 0 && result == edit) {
657 KBookmark bookmark = placesModel->bookmarkForIndex(index);
658 KUrl url = bookmark.url();
659 QString label = bookmark.text();
660 QString iconName = bookmark.icon();
661 bool appLocal = !bookmark.metaDataItem("OnlyInApp").isEmpty();
662
663 if (KFilePlaceEditDialog::getInformation(true, url, label,
664 iconName, false, appLocal, 64, this))
665 {
666 QString appName;
667 if (appLocal) appName = KGlobal::mainComponent().componentName();
668
669 placesModel->editPlace(index, label, url, iconName, appName);
670 }
671
672 } else if (remove != 0 && result == remove) {
673 placesModel->removePlace(index);
674 } else if (hide != 0 && result == hide) {
675 placesModel->setPlaceHidden(index, hide->isChecked());
676 QModelIndex current = placesModel->closestItem(d->currentUrl);
677
678 if (index!=current && !d->showAll && hide->isChecked()) {
679 delegate->addDisappearingItem(index);
680
681 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) {
682 delegate->setDisappearingItemProgress(0.0);
683 d->itemDisappearTimeline.start();
684 }
685 }
686 } else if (showAll != 0 && result == showAll) {
687 setShowAll(showAll->isChecked());
688 } else if (teardown != 0 && result == teardown) {
689 placesModel->requestTeardown(index);
690 } else if (eject != 0 && result == eject) {
691 placesModel->requestEject(index);
692 } else if (add != 0 && result == add) {
693 KUrl url = d->currentUrl;
694 QString label;
695 QString iconName = "folder";
696 bool appLocal = true;
697 if (KFilePlaceEditDialog::getInformation(true, url, label,
698 iconName, true, appLocal, 64, this))
699 {
700 QString appName;
701 if (appLocal) appName = KGlobal::mainComponent().componentName();
702
703 placesModel->addPlace(label, url, iconName, appName, index);
704 }
705 }
706
707 index = placesModel->closestItem(d->currentUrl);
708 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect);
709}
710
711void KFilePlacesView::resizeEvent(QResizeEvent *event)
712{
713 QListView::resizeEvent(event);
714 d->adaptItemSize();
715}
716
717void KFilePlacesView::showEvent(QShowEvent *event)
718{
719 QListView::showEvent(event);
720 QTimer::singleShot(100, this, SLOT(_k_enableSmoothItemResizing()));
721}
722
723void KFilePlacesView::hideEvent(QHideEvent *event)
724{
725 QListView::hideEvent(event);
726 d->smoothItemResizing = false;
727}
728
729void KFilePlacesView::dragEnterEvent(QDragEnterEvent *event)
730{
731 QListView::dragEnterEvent(event);
732 d->dragging = true;
733
734 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate());
735 delegate->setShowHoverIndication(false);
736
737 d->dropRect = QRect();
738}
739
740void KFilePlacesView::dragLeaveEvent(QDragLeaveEvent *event)
741{
742 QListView::dragLeaveEvent(event);
743 d->dragging = false;
744
745 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate());
746 delegate->setShowHoverIndication(true);
747
748 setDirtyRegion(d->dropRect);
749}
750
751void KFilePlacesView::dragMoveEvent(QDragMoveEvent *event)
752{
753 QListView::dragMoveEvent(event);
754
755 // update the drop indicator
756 const QPoint pos = event->pos();
757 const QModelIndex index = indexAt(pos);
758 setDirtyRegion(d->dropRect);
759 if (index.isValid()) {
760 const QRect rect = visualRect(index);
761 const int gap = d->insertIndicatorHeight(rect.height());
762 if (d->insertAbove(rect, pos)) {
763 // indicate that the item will be inserted above the current place
764 d->dropRect = QRect(rect.left(), rect.top() - gap / 2,
765 rect.width(), gap);
766 } else if (d->insertBelow(rect, pos)) {
767 // indicate that the item will be inserted below the current place
768 d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2,
769 rect.width(), gap);
770 } else {
771 // indicate that the item be dropped above the current place
772 d->dropRect = rect;
773 }
774 }
775
776 setDirtyRegion(d->dropRect);
777}
778
779void KFilePlacesView::dropEvent(QDropEvent *event)
780{
781 const QPoint pos = event->pos();
782 const QModelIndex index = indexAt(pos);
783 if (index.isValid()) {
784 const QRect rect = visualRect(index);
785 if (!d->insertAbove(rect, pos) && !d->insertBelow(rect, pos)) {
786 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
787 Q_ASSERT(placesModel != 0);
788 emit urlsDropped(placesModel->url(index), event, this);
789 event->acceptProposedAction();
790 }
791 }
792
793 QListView::dropEvent(event);
794 d->dragging = false;
795
796 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate());
797 delegate->setShowHoverIndication(true);
798}
799
800void KFilePlacesView::paintEvent(QPaintEvent* event)
801{
802 QListView::paintEvent(event);
803 if (d->dragging && !d->dropRect.isEmpty()) {
804 // draw drop indicator
805 QPainter painter(viewport());
806
807 const QModelIndex index = indexAt(d->dropRect.topLeft());
808 const QRect itemRect = visualRect(index);
809 const bool drawInsertIndicator = !d->dropOnPlace ||
810 d->dropRect.height() <= d->insertIndicatorHeight(itemRect.height());
811
812 if (drawInsertIndicator) {
813 // draw indicator for inserting items
814 QBrush blendedBrush = viewOptions().palette.brush(QPalette::Normal, QPalette::Highlight);
815 QColor color = blendedBrush.color();
816
817 const int y = (d->dropRect.top() + d->dropRect.bottom()) / 2;
818 const int thickness = d->dropRect.height() / 2;
819 Q_ASSERT(thickness >= 1);
820 int alpha = 255;
821 const int alphaDec = alpha / (thickness + 1);
822 for (int i = 0; i < thickness; i++) {
823 color.setAlpha(alpha);
824 alpha -= alphaDec;
825 painter.setPen(color);
826 painter.drawLine(d->dropRect.left(), y - i, d->dropRect.right(), y - i);
827 painter.drawLine(d->dropRect.left(), y + i, d->dropRect.right(), y + i);
828 }
829 } else {
830 // draw indicator for copying/moving/linking to items
831 QStyleOptionViewItemV4 opt;
832 opt.initFrom(this);
833 opt.rect = itemRect;
834 opt.state = QStyle::State_Enabled | QStyle::State_MouseOver;
835 style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &painter, this);
836 }
837 }
838}
839
840void KFilePlacesView::setModel(QAbstractItemModel *model)
841{
842 QListView::setModel(model);
843 d->updateHiddenRows();
844 // Uses Qt::QueuedConnection to delay the time when the slot will be
845 // called. In case of an item move the remove+add will be done before
846 // we adapt the item size (otherwise we'd get it wrong as we'd execute
847 // it after the remove only).
848 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
849 this, SLOT(adaptItemSize()), Qt::QueuedConnection);
850 connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
851 d->watcher, SLOT(currentIndexChanged(QModelIndex)));
852}
853
854void KFilePlacesView::rowsInserted(const QModelIndex &parent, int start, int end)
855{
856 QListView::rowsInserted(parent, start, end);
857 setUrl(d->currentUrl);
858
859 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate());
860 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
861
862 for (int i=start; i<=end; ++i) {
863 QModelIndex index = placesModel->index(i, 0, parent);
864 if (d->showAll || !placesModel->isHidden(index)) {
865 delegate->addAppearingItem(index);
866 } else {
867 setRowHidden(i, true);
868 }
869 }
870
871 if (d->itemAppearTimeline.state()!=QTimeLine::Running) {
872 delegate->setAppearingItemProgress(0.0);
873 d->itemAppearTimeline.start();
874 }
875
876 d->adaptItemSize();
877}
878
879QSize KFilePlacesView::sizeHint() const
880{
881 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model());
882 if (!placesModel) {
883 return QListView::sizeHint();
884 }
885 const int height = QListView::sizeHint().height();
886 QFontMetrics fm = d->q->fontMetrics();
887 int textWidth = 0;
888
889 for (int i=0; i<placesModel->rowCount(); ++i) {
890 QModelIndex index = placesModel->index(i, 0);
891 if (!placesModel->isHidden(index))
892 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString()));
893 }
894
895 const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small) + 3 * LATERAL_MARGIN;
896 return QSize(iconSize + textWidth + fm.height() / 2, height);
897}
898
899void KFilePlacesView::Private::setCurrentIndex(const QModelIndex &index)
900{
901 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
902
903 if (placesModel==0) return;
904
905 KUrl url = placesModel->url(index);
906
907 if (url.isValid()) {
908 currentUrl = url;
909 updateHiddenRows();
910 emit q->urlChanged(url);
911 if (showAll) {
912 q->setShowAll(false);
913 }
914 } else {
915 q->setUrl(currentUrl);
916 }
917}
918
919void KFilePlacesView::Private::adaptItemSize()
920{
921 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
922 if (!delegate) return;
923
924 if (!autoResizeItems) {
925 int size = q->iconSize().width(); // Assume width == height
926 delegate->setIconSize(size);
927 q->scheduleDelayedItemsLayout();
928 return;
929 }
930
931 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
932
933 if (placesModel==0) return;
934
935 int rowCount = placesModel->rowCount();
936
937 if (!showAll) {
938 rowCount-= placesModel->hiddenCount();
939
940 QModelIndex current = placesModel->closestItem(currentUrl);
941
942 if (placesModel->isHidden(current)) {
943 rowCount++;
944 }
945 }
946
947 if (rowCount==0) return; // We've nothing to display anyway
948
949 const int minSize = IconSize(KIconLoader::Small);
950 const int maxSize = 64;
951
952 int textWidth = 0;
953 QFontMetrics fm = q->fontMetrics();
954 for (int i=0; i<placesModel->rowCount(); ++i) {
955 QModelIndex index = placesModel->index(i, 0);
956
957 if (!placesModel->isHidden(index))
958 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString()));
959 }
960
961 const int margin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, q) + 1;
962 const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1;
963 const int maxHeight = ((q->height() - (fm.height() / 2) * rowCount) / rowCount) - 1;
964
965 int size = qMin(maxHeight, maxWidth);
966
967 if (size<minSize) {
968 size = minSize;
969 } else if (size>maxSize) {
970 size = maxSize;
971 } else {
972 // Make it a multiple of 16
973 size &= ~0xf;
974 }
975
976 if (size==delegate->iconSize()) return;
977
978 if (smoothItemResizing) {
979 oldSize = delegate->iconSize();
980 endSize = size;
981 if (adaptItemsTimeline.state()!=QTimeLine::Running) {
982 adaptItemsTimeline.start();
983 }
984 } else {
985 delegate->setIconSize(size);
986 q->scheduleDelayedItemsLayout();
987 }
988}
989
990void KFilePlacesView::Private::updateHiddenRows()
991{
992 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
993
994 if (placesModel==0) return;
995
996 int rowCount = placesModel->rowCount();
997 QModelIndex current = placesModel->closestItem(currentUrl);
998
999 for (int i=0; i<rowCount; ++i) {
1000 QModelIndex index = placesModel->index(i, 0);
1001 if (index!=current && placesModel->isHidden(index) && !showAll) {
1002 q->setRowHidden(i, true);
1003 } else {
1004 q->setRowHidden(i, false);
1005 }
1006 }
1007
1008 adaptItemSize();
1009}
1010
1011bool KFilePlacesView::Private::insertAbove(const QRect &itemRect, const QPoint &pos) const
1012{
1013 if (dropOnPlace) {
1014 return pos.y() < itemRect.top() + insertIndicatorHeight(itemRect.height()) / 2;
1015 }
1016
1017 return pos.y() < itemRect.top() + (itemRect.height() / 2);
1018}
1019
1020bool KFilePlacesView::Private::insertBelow(const QRect &itemRect, const QPoint &pos) const
1021{
1022 if (dropOnPlace) {
1023 return pos.y() > itemRect.bottom() - insertIndicatorHeight(itemRect.height()) / 2;
1024 }
1025
1026 return pos.y() >= itemRect.top() + (itemRect.height() / 2);
1027}
1028
1029int KFilePlacesView::Private::insertIndicatorHeight(int itemHeight) const
1030{
1031 const int min = 4;
1032 const int max = 12;
1033
1034 int height = itemHeight / 4;
1035 if (height < min) {
1036 height = min;
1037 } else if (height > max) {
1038 height = max;
1039 }
1040 return height;
1041}
1042
1043void KFilePlacesView::Private::fadeCapacityBar(const QModelIndex &index, FadeType fadeType)
1044{
1045 QTimeLine *timeLine = delegate->fadeAnimationForIndex(index);
1046 delete timeLine;
1047 delegate->removeFadeAnimation(index);
1048 timeLine = new QTimeLine(250, q);
1049 connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(_k_capacityBarFadeValueChanged()));
1050 if (fadeType == FadeIn) {
1051 timeLine->setDirection(QTimeLine::Forward);
1052 timeLine->setCurrentTime(0);
1053 } else {
1054 timeLine->setDirection(QTimeLine::Backward);
1055 timeLine->setCurrentTime(250);
1056 }
1057 delegate->addFadeAnimation(index, timeLine);
1058 timeLine->start();
1059}
1060
1061void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index)
1062{
1063 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
1064
1065 if (placesModel==0) return;
1066
1067 lastClickedIndex = QPersistentModelIndex();
1068
1069 if (placesModel->setupNeeded(index)) {
1070 QObject::connect(placesModel, SIGNAL(setupDone(QModelIndex,bool)),
1071 q, SLOT(_k_storageSetupDone(QModelIndex,bool)));
1072
1073 lastClickedIndex = index;
1074 placesModel->requestSetup(index);
1075 return;
1076 }
1077
1078 setCurrentIndex(index);
1079}
1080
1081void KFilePlacesView::Private::_k_placeEntered(const QModelIndex &index)
1082{
1083 fadeCapacityBar(index, FadeIn);
1084 pollingRequestCount++;
1085 if (pollingRequestCount == 1) {
1086 pollDevices.start();
1087 }
1088}
1089
1090void KFilePlacesView::Private::_k_placeLeft(const QModelIndex &index)
1091{
1092 fadeCapacityBar(index, FadeOut);
1093 pollingRequestCount--;
1094 if (!pollingRequestCount) {
1095 pollDevices.stop();
1096 }
1097}
1098
1099void KFilePlacesView::Private::_k_storageSetupDone(const QModelIndex &index, bool success)
1100{
1101 if (index!=lastClickedIndex) {
1102 return;
1103 }
1104
1105 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model());
1106
1107 QObject::disconnect(placesModel, SIGNAL(setupDone(QModelIndex,bool)),
1108 q, SLOT(_k_storageSetupDone(QModelIndex,bool)));
1109
1110 if (success) {
1111 setCurrentIndex(lastClickedIndex);
1112 } else {
1113 q->setUrl(currentUrl);
1114 }
1115
1116 lastClickedIndex = QPersistentModelIndex();
1117}
1118
1119void KFilePlacesView::Private::_k_adaptItemsUpdate(qreal value)
1120{
1121 int add = (endSize-oldSize)*value;
1122
1123 int size = oldSize+add;
1124
1125 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
1126 delegate->setIconSize(size);
1127 q->scheduleDelayedItemsLayout();
1128}
1129
1130void KFilePlacesView::Private::_k_itemAppearUpdate(qreal value)
1131{
1132 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
1133
1134 delegate->setAppearingItemProgress(value);
1135 q->scheduleDelayedItemsLayout();
1136}
1137
1138void KFilePlacesView::Private::_k_itemDisappearUpdate(qreal value)
1139{
1140 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate());
1141
1142 delegate->setDisappearingItemProgress(value);
1143
1144 if (value>=1.0) {
1145 updateHiddenRows();
1146 }
1147
1148 q->scheduleDelayedItemsLayout();
1149}
1150
1151void KFilePlacesView::Private::_k_enableSmoothItemResizing()
1152{
1153 smoothItemResizing = true;
1154}
1155
1156void KFilePlacesView::Private::_k_trashUpdated(KJob *job)
1157{
1158 if (job->error()) {
1159 static_cast<KIO::Job*>(job)->ui()->showErrorMessage();
1160 }
1161 org::kde::KDirNotify::emitFilesAdded("trash:/");
1162}
1163
1164void KFilePlacesView::Private::_k_capacityBarFadeValueChanged()
1165{
1166 const QModelIndex index = delegate->indexForFadeAnimation(static_cast<QTimeLine*>(q->sender()));
1167 if (!index.isValid()) {
1168 return;
1169 }
1170 q->update(index);
1171}
1172
1173void KFilePlacesView::Private::_k_triggerDevicePolling()
1174{
1175 const QModelIndex hoveredIndex = watcher->hoveredIndex();
1176 if (hoveredIndex.isValid()) {
1177 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(hoveredIndex.model());
1178 if (placesModel->isDevice(hoveredIndex)) {
1179 q->update(hoveredIndex);
1180 }
1181 }
1182 const QModelIndex focusedIndex = watcher->focusedIndex();
1183 if (focusedIndex.isValid() && focusedIndex != hoveredIndex) {
1184 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(focusedIndex.model());
1185 if (placesModel->isDevice(focusedIndex)) {
1186 q->update(focusedIndex);
1187 }
1188 }
1189}
1190
1191void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
1192{
1193 QListView::dataChanged(topLeft, bottomRight);
1194 d->adaptItemSize();
1195}
1196
1197#include "kfileplacesview.moc"
1198#include "kfileplacesview_p.moc"
1199