KFile
kfileplacesview.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 Copyright (C) 2007 Kevin Ottens <ervin@kde.org> 00003 Copyright (C) 2008 Rafael Fernández López <ereslibre@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 00019 */ 00020 00021 #include "kfileplacesview.h" 00022 #include "kfileplacesview_p.h" 00023 00024 #include <QtCore/QTimeLine> 00025 #include <QtCore/QTimer> 00026 #include <QtGui/QPainter> 00027 #include <QtGui/QAbstractItemDelegate> 00028 #include <QtGui/QKeyEvent> 00029 #include <QtGui/QApplication> 00030 #include <QtGui/QScrollBar> 00031 00032 #include <kdebug.h> 00033 00034 #include <kmenu.h> 00035 #include <kcomponentdata.h> 00036 #include <kdirnotify.h> 00037 #include <kglobalsettings.h> 00038 #include <kiconloader.h> 00039 #include <klocale.h> 00040 #include <kmessagebox.h> 00041 #include <knotification.h> 00042 #include <kio/job.h> 00043 #include <kio/jobuidelegate.h> 00044 #include <kjob.h> 00045 #include <kcapacitybar.h> 00046 #include <kdiskfreespaceinfo.h> 00047 #include <solid/storageaccess.h> 00048 #include <solid/storagedrive.h> 00049 #include <solid/storagevolume.h> 00050 #include <solid/opticaldrive.h> 00051 #include <solid/opticaldisc.h> 00052 00053 #include "kfileplaceeditdialog.h" 00054 #include "kfileplacesmodel.h" 00055 00056 #define LATERAL_MARGIN 4 00057 #define CAPACITYBAR_HEIGHT 6 00058 00059 class KFilePlacesViewDelegate : public QAbstractItemDelegate 00060 { 00061 public: 00062 KFilePlacesViewDelegate(KFilePlacesView *parent); 00063 virtual ~KFilePlacesViewDelegate(); 00064 virtual QSize sizeHint(const QStyleOptionViewItem &option, 00065 const QModelIndex &index) const; 00066 virtual void paint(QPainter *painter, 00067 const QStyleOptionViewItem &option, 00068 const QModelIndex &index) const; 00069 00070 int iconSize() const; 00071 void setIconSize(int newSize); 00072 00073 void addAppearingItem(const QModelIndex &index); 00074 void setAppearingItemProgress(qreal value); 00075 void addDisappearingItem(const QModelIndex &index); 00076 void setDisappearingItemProgress(qreal value); 00077 00078 void setShowHoverIndication(bool show); 00079 00080 void addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine); 00081 void removeFadeAnimation(const QModelIndex &index); 00082 QModelIndex indexForFadeAnimation(QTimeLine *timeLine) const; 00083 QTimeLine *fadeAnimationForIndex(const QModelIndex &index) const; 00084 00085 qreal contentsOpacity(const QModelIndex &index) const; 00086 00087 private: 00088 KFilePlacesView *m_view; 00089 int m_iconSize; 00090 00091 QList<QPersistentModelIndex> m_appearingItems; 00092 int m_appearingIconSize; 00093 qreal m_appearingOpacity; 00094 00095 QList<QPersistentModelIndex> m_disappearingItems; 00096 int m_disappearingIconSize; 00097 qreal m_disappearingOpacity; 00098 00099 bool m_showHoverIndication; 00100 00101 QMap<QPersistentModelIndex, QTimeLine*> m_timeLineMap; 00102 QMap<QTimeLine*, QPersistentModelIndex> m_timeLineInverseMap; 00103 }; 00104 00105 KFilePlacesViewDelegate::KFilePlacesViewDelegate(KFilePlacesView *parent) : 00106 QAbstractItemDelegate(parent), 00107 m_view(parent), 00108 m_iconSize(48), 00109 m_appearingIconSize(0), 00110 m_appearingOpacity(0.0), 00111 m_disappearingIconSize(0), 00112 m_disappearingOpacity(0.0), 00113 m_showHoverIndication(true) 00114 { 00115 } 00116 00117 KFilePlacesViewDelegate::~KFilePlacesViewDelegate() 00118 { 00119 } 00120 00121 QSize KFilePlacesViewDelegate::sizeHint(const QStyleOptionViewItem &option, 00122 const QModelIndex &index) const 00123 { 00124 int iconSize = m_iconSize; 00125 if (m_appearingItems.contains(index)) { 00126 iconSize = m_appearingIconSize; 00127 } else if (m_disappearingItems.contains(index)) { 00128 iconSize = m_disappearingIconSize; 00129 } 00130 00131 const KFilePlacesModel *filePlacesModel = static_cast<const KFilePlacesModel*>(index.model()); 00132 Solid::Device device = filePlacesModel->deviceForIndex(index); 00133 00134 return QSize(option.rect.width(), option.fontMetrics.height() / 2 + qMax(iconSize, option.fontMetrics.height())); 00135 } 00136 00137 void KFilePlacesViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 00138 { 00139 painter->save(); 00140 00141 if (m_appearingItems.contains(index)) { 00142 painter->setOpacity(m_appearingOpacity); 00143 } else if (m_disappearingItems.contains(index)) { 00144 painter->setOpacity(m_disappearingOpacity); 00145 } 00146 00147 QStyleOptionViewItemV4 opt = option; 00148 if (!m_showHoverIndication) { 00149 opt.state &= ~QStyle::State_MouseOver; 00150 } 00151 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter); 00152 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(index.model()); 00153 00154 bool isLTR = option.direction == Qt::LeftToRight; 00155 00156 QIcon icon = index.model()->data(index, Qt::DecorationRole).value<QIcon>(); 00157 QPixmap pm = icon.pixmap(m_iconSize, m_iconSize); 00158 QPoint point(isLTR ? option.rect.left() + LATERAL_MARGIN 00159 : option.rect.right() - LATERAL_MARGIN - m_iconSize, option.rect.top() + (option.rect.height() - m_iconSize) / 2); 00160 painter->drawPixmap(point, pm); 00161 00162 if (option.state & QStyle::State_Selected) { 00163 QPalette::ColorGroup cg = QPalette::Active; 00164 if (!(option.state & QStyle::State_Enabled)) { 00165 cg = QPalette::Disabled; 00166 } else if (!(option.state & QStyle::State_Active)) { 00167 cg = QPalette::Inactive; 00168 } 00169 painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); 00170 } 00171 00172 QRect rectText; 00173 00174 QString mountPointPath = placesModel->url(index).toLocalFile(); 00175 KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(mountPointPath); 00176 bool drawCapacityBar = info.size() != 0 && 00177 placesModel->data(index, KFilePlacesModel::CapacityBarRecommendedRole).toBool(); 00178 00179 if (drawCapacityBar && contentsOpacity(index) > 0) 00180 { 00181 painter->save(); 00182 painter->setOpacity(painter->opacity() * contentsOpacity(index)); 00183 00184 int height = option.fontMetrics.height() + CAPACITYBAR_HEIGHT; 00185 rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + option.rect.left() 00186 : 0, option.rect.top() + (option.rect.height() / 2 - height / 2), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.fontMetrics.height()); 00187 painter->drawText(rectText, Qt::AlignLeft | Qt::AlignTop, option.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width())); 00188 QRect capacityRect(isLTR ? rectText.x() : LATERAL_MARGIN, rectText.bottom() - 1, rectText.width() - LATERAL_MARGIN, CAPACITYBAR_HEIGHT); 00189 KCapacityBar capacityBar(KCapacityBar::DrawTextInline); 00190 capacityBar.setValue((info.used() * 100) / info.size()); 00191 capacityBar.drawCapacityBar(painter, capacityRect); 00192 00193 painter->restore(); 00194 00195 painter->save(); 00196 painter->setOpacity(painter->opacity() * (1 - contentsOpacity(index))); 00197 } 00198 00199 rectText = QRect(isLTR ? m_iconSize + LATERAL_MARGIN * 2 + option.rect.left() 00200 : 0, option.rect.top(), option.rect.width() - m_iconSize - LATERAL_MARGIN * 2, option.rect.height()); 00201 painter->drawText(rectText, Qt::AlignLeft | Qt::AlignVCenter, option.fontMetrics.elidedText(index.model()->data(index).toString(), Qt::ElideRight, rectText.width())); 00202 00203 if (drawCapacityBar && contentsOpacity(index) > 0) { 00204 painter->restore(); 00205 } 00206 00207 painter->restore(); 00208 } 00209 00210 int KFilePlacesViewDelegate::iconSize() const 00211 { 00212 return m_iconSize; 00213 } 00214 00215 void KFilePlacesViewDelegate::setIconSize(int newSize) 00216 { 00217 m_iconSize = newSize; 00218 } 00219 00220 void KFilePlacesViewDelegate::addAppearingItem(const QModelIndex &index) 00221 { 00222 m_appearingItems << index; 00223 } 00224 00225 void KFilePlacesViewDelegate::setAppearingItemProgress(qreal value) 00226 { 00227 if (value<=0.25) { 00228 m_appearingOpacity = 0.0; 00229 m_appearingIconSize = iconSize()*value*4; 00230 00231 if (m_appearingIconSize>=m_iconSize) { 00232 m_appearingIconSize = m_iconSize; 00233 } 00234 } else { 00235 m_appearingIconSize = m_iconSize; 00236 m_appearingOpacity = (value-0.25)*4/3; 00237 00238 if (value>=1.0) { 00239 m_appearingItems.clear(); 00240 } 00241 } 00242 } 00243 00244 void KFilePlacesViewDelegate::addDisappearingItem(const QModelIndex &index) 00245 { 00246 m_disappearingItems << index; 00247 } 00248 00249 void KFilePlacesViewDelegate::setDisappearingItemProgress(qreal value) 00250 { 00251 value = 1.0 - value; 00252 00253 if (value<=0.25) { 00254 m_disappearingOpacity = 0.0; 00255 m_disappearingIconSize = iconSize()*value*4; 00256 00257 if (m_disappearingIconSize>=m_iconSize) { 00258 m_disappearingIconSize = m_iconSize; 00259 } 00260 00261 if (value<=0.0) { 00262 m_disappearingItems.clear(); 00263 } 00264 } else { 00265 m_disappearingIconSize = m_iconSize; 00266 m_disappearingOpacity = (value-0.25)*4/3; 00267 } 00268 } 00269 00270 void KFilePlacesViewDelegate::setShowHoverIndication(bool show) 00271 { 00272 m_showHoverIndication = show; 00273 } 00274 00275 void KFilePlacesViewDelegate::addFadeAnimation(const QModelIndex &index, QTimeLine *timeLine) 00276 { 00277 m_timeLineMap.insert(index, timeLine); 00278 m_timeLineInverseMap.insert(timeLine, index); 00279 } 00280 00281 void KFilePlacesViewDelegate::removeFadeAnimation(const QModelIndex &index) 00282 { 00283 QTimeLine *timeLine = m_timeLineMap.value(index, 0); 00284 m_timeLineMap.remove(index); 00285 m_timeLineInverseMap.remove(timeLine); 00286 } 00287 00288 QModelIndex KFilePlacesViewDelegate::indexForFadeAnimation(QTimeLine *timeLine) const 00289 { 00290 return m_timeLineInverseMap.value(timeLine, QModelIndex()); 00291 } 00292 00293 QTimeLine *KFilePlacesViewDelegate::fadeAnimationForIndex(const QModelIndex &index) const 00294 { 00295 return m_timeLineMap.value(index, 0); 00296 } 00297 00298 qreal KFilePlacesViewDelegate::contentsOpacity(const QModelIndex &index) const 00299 { 00300 QTimeLine *timeLine = fadeAnimationForIndex(index); 00301 if (timeLine) { 00302 return timeLine->currentValue(); 00303 } 00304 return 0; 00305 } 00306 00307 class KFilePlacesView::Private 00308 { 00309 public: 00310 Private(KFilePlacesView *parent) : q(parent), watcher(new KFilePlacesEventWatcher(q)) { } 00311 00312 enum FadeType { 00313 FadeIn = 0, 00314 FadeOut 00315 }; 00316 00317 KFilePlacesView * const q; 00318 00319 KUrl currentUrl; 00320 bool autoResizeItems; 00321 bool showAll; 00322 bool smoothItemResizing; 00323 bool dropOnPlace; 00324 bool dragging; 00325 Solid::StorageAccess *lastClickedStorage; 00326 QPersistentModelIndex lastClickedIndex; 00327 00328 QRect dropRect; 00329 00330 void setCurrentIndex(const QModelIndex &index); 00331 void adaptItemSize(); 00332 void updateHiddenRows(); 00333 bool insertAbove(const QRect &itemRect, const QPoint &pos) const; 00334 bool insertBelow(const QRect &itemRect, const QPoint &pos) const; 00335 int insertIndicatorHeight(int itemHeight) const; 00336 void fadeCapacityBar(const QModelIndex &index, FadeType fadeType); 00337 00338 void _k_placeClicked(const QModelIndex &index); 00339 void _k_placeEntered(const QModelIndex &index); 00340 void _k_placeLeft(const QModelIndex &index); 00341 void _k_storageSetupDone(const QModelIndex &index, bool success); 00342 void _k_adaptItemsUpdate(qreal value); 00343 void _k_itemAppearUpdate(qreal value); 00344 void _k_itemDisappearUpdate(qreal value); 00345 void _k_enableSmoothItemResizing(); 00346 void _k_trashUpdated(KJob *job); 00347 void _k_capacityBarFadeValueChanged(); 00348 void _k_triggerDevicePolling(); 00349 00350 QTimeLine adaptItemsTimeline; 00351 int oldSize, endSize; 00352 00353 QTimeLine itemAppearTimeline; 00354 QTimeLine itemDisappearTimeline; 00355 00356 KFilePlacesEventWatcher *const watcher; 00357 KFilePlacesViewDelegate *delegate; 00358 QTimer pollDevices; 00359 int pollingRequestCount; 00360 }; 00361 00362 KFilePlacesView::KFilePlacesView(QWidget *parent) 00363 : QListView(parent), d(new Private(this)) 00364 { 00365 d->showAll = false; 00366 d->smoothItemResizing = false; 00367 d->dropOnPlace = false; 00368 d->autoResizeItems = true; 00369 d->dragging = false; 00370 d->lastClickedStorage = 0; 00371 d->pollingRequestCount = 0; 00372 d->delegate = new KFilePlacesViewDelegate(this); 00373 00374 setSelectionRectVisible(false); 00375 setSelectionMode(SingleSelection); 00376 00377 setDragEnabled(true); 00378 setAcceptDrops(true); 00379 setMouseTracking(true); 00380 setDropIndicatorShown(false); 00381 setFrameStyle(QFrame::NoFrame); 00382 00383 setResizeMode(Adjust); 00384 setItemDelegate(d->delegate); 00385 00386 QPalette palette = viewport()->palette(); 00387 palette.setColor(viewport()->backgroundRole(), Qt::transparent); 00388 palette.setColor(viewport()->foregroundRole(), palette.color(QPalette::WindowText)); 00389 viewport()->setPalette(palette); 00390 00391 connect(this, SIGNAL(clicked(const QModelIndex&)), 00392 this, SLOT(_k_placeClicked(const QModelIndex&))); 00393 // Note: Don't connect to the activated() signal, as the behavior when it is 00394 // committed depends on the used widget style. The click behavior of 00395 // KFilePlacesView should be style independent. 00396 00397 connect(&d->adaptItemsTimeline, SIGNAL(valueChanged(qreal)), 00398 this, SLOT(_k_adaptItemsUpdate(qreal))); 00399 d->adaptItemsTimeline.setDuration(500); 00400 d->adaptItemsTimeline.setUpdateInterval(5); 00401 d->adaptItemsTimeline.setCurveShape(QTimeLine::EaseInOutCurve); 00402 00403 connect(&d->itemAppearTimeline, SIGNAL(valueChanged(qreal)), 00404 this, SLOT(_k_itemAppearUpdate(qreal))); 00405 d->itemAppearTimeline.setDuration(500); 00406 d->itemAppearTimeline.setUpdateInterval(5); 00407 d->itemAppearTimeline.setCurveShape(QTimeLine::EaseInOutCurve); 00408 00409 connect(&d->itemDisappearTimeline, SIGNAL(valueChanged(qreal)), 00410 this, SLOT(_k_itemDisappearUpdate(qreal))); 00411 d->itemDisappearTimeline.setDuration(500); 00412 d->itemDisappearTimeline.setUpdateInterval(5); 00413 d->itemDisappearTimeline.setCurveShape(QTimeLine::EaseInOutCurve); 00414 00415 viewport()->installEventFilter(d->watcher); 00416 connect(d->watcher, SIGNAL(entryEntered(const QModelIndex&)), 00417 this, SLOT(_k_placeEntered(const QModelIndex&))); 00418 connect(d->watcher, SIGNAL(entryLeft(const QModelIndex&)), 00419 this, SLOT(_k_placeLeft(const QModelIndex&))); 00420 00421 d->pollDevices.setInterval(5000); 00422 connect(&d->pollDevices, SIGNAL(timeout()), this, SLOT(_k_triggerDevicePolling())); 00423 00424 // FIXME: this is necessary to avoid flashes of black with some widget styles. 00425 // could be a bug in Qt (e.g. QAbstractScrollArea) or KFilePlacesView, but has not 00426 // yet been tracked down yet. until then, this works and is harmlessly enough. 00427 // in fact, some QStyle (Oxygen, Skulpture, others?) do this already internally. 00428 // See br #242358 for more information 00429 verticalScrollBar()->setAttribute(Qt::WA_OpaquePaintEvent, false); 00430 } 00431 00432 KFilePlacesView::~KFilePlacesView() 00433 { 00434 delete d; 00435 } 00436 00437 void KFilePlacesView::setDropOnPlaceEnabled(bool enabled) 00438 { 00439 d->dropOnPlace = enabled; 00440 } 00441 00442 bool KFilePlacesView::isDropOnPlaceEnabled() const 00443 { 00444 return d->dropOnPlace; 00445 } 00446 00447 void KFilePlacesView::setAutoResizeItemsEnabled(bool enabled) 00448 { 00449 d->autoResizeItems = enabled; 00450 } 00451 00452 bool KFilePlacesView::isAutoResizeItemsEnabled() const 00453 { 00454 return d->autoResizeItems; 00455 } 00456 00457 void KFilePlacesView::setUrl(const KUrl &url) 00458 { 00459 KUrl oldUrl = d->currentUrl; 00460 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00461 00462 if (placesModel==0) return; 00463 00464 QModelIndex index = placesModel->closestItem(url); 00465 QModelIndex current = selectionModel()->currentIndex(); 00466 00467 if (index.isValid()) { 00468 if (current!=index && placesModel->isHidden(current) && !d->showAll) { 00469 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00470 delegate->addDisappearingItem(current); 00471 00472 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) { 00473 delegate->setDisappearingItemProgress(0.0); 00474 d->itemDisappearTimeline.start(); 00475 } 00476 } 00477 00478 if (current!=index && placesModel->isHidden(index) && !d->showAll) { 00479 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00480 delegate->addAppearingItem(index); 00481 00482 if (d->itemAppearTimeline.state()!=QTimeLine::Running) { 00483 delegate->setAppearingItemProgress(0.0); 00484 d->itemAppearTimeline.start(); 00485 } 00486 00487 setRowHidden(index.row(), false); 00488 } 00489 00490 d->currentUrl = url; 00491 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); 00492 } else { 00493 d->currentUrl = KUrl(); 00494 selectionModel()->clear(); 00495 } 00496 00497 if (!current.isValid()) { 00498 d->updateHiddenRows(); 00499 } 00500 } 00501 00502 void KFilePlacesView::setShowAll(bool showAll) 00503 { 00504 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00505 00506 if (placesModel==0) return; 00507 00508 d->showAll = showAll; 00509 00510 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00511 00512 int rowCount = placesModel->rowCount(); 00513 QModelIndex current = placesModel->closestItem(d->currentUrl); 00514 00515 if (showAll) { 00516 d->updateHiddenRows(); 00517 00518 for (int i=0; i<rowCount; ++i) { 00519 QModelIndex index = placesModel->index(i, 0); 00520 if (index!=current && placesModel->isHidden(index)) { 00521 delegate->addAppearingItem(index); 00522 } 00523 } 00524 00525 if (d->itemAppearTimeline.state()!=QTimeLine::Running) { 00526 delegate->setAppearingItemProgress(0.0); 00527 d->itemAppearTimeline.start(); 00528 } 00529 } else { 00530 for (int i=0; i<rowCount; ++i) { 00531 QModelIndex index = placesModel->index(i, 0); 00532 if (index!=current && placesModel->isHidden(index)) { 00533 delegate->addDisappearingItem(index); 00534 } 00535 } 00536 00537 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) { 00538 delegate->setDisappearingItemProgress(0.0); 00539 d->itemDisappearTimeline.start(); 00540 } 00541 } 00542 } 00543 00544 void KFilePlacesView::keyPressEvent(QKeyEvent *event) 00545 { 00546 QListView::keyPressEvent(event); 00547 if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) { 00548 d->_k_placeClicked(currentIndex()); 00549 } 00550 } 00551 00552 void KFilePlacesView::contextMenuEvent(QContextMenuEvent *event) 00553 { 00554 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00555 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00556 00557 if (placesModel==0) return; 00558 00559 QModelIndex index = indexAt(event->pos()); 00560 QString label = placesModel->text(index).replace('&',"&&"); 00561 00562 KMenu menu; 00563 00564 QAction *edit = 0; 00565 QAction *hide = 0; 00566 QAction *emptyTrash = 0; 00567 QAction *eject = 0; 00568 QAction *teardown = 0; 00569 QAction *add = 0; 00570 QAction *mainSeparator = 0; 00571 00572 if (index.isValid()) { 00573 if (!placesModel->isDevice(index)) { 00574 if (placesModel->url(index) == KUrl("trash:/")) { 00575 emptyTrash = menu.addAction(KIcon("trash-empty"), i18nc("@action:inmenu", "Empty Trash")); 00576 KConfig trashConfig("trashrc", KConfig::SimpleConfig); 00577 emptyTrash->setEnabled(!trashConfig.group("Status").readEntry("Empty", true)); 00578 menu.addSeparator(); 00579 } 00580 add = menu.addAction(KIcon("document-new"), i18n("Add Entry...")); 00581 mainSeparator = menu.addSeparator(); 00582 edit = menu.addAction(KIcon("document-properties"), i18n("&Edit Entry '%1'...", label)); 00583 } else { 00584 eject = placesModel->ejectActionForIndex(index); 00585 if (eject!=0) { 00586 eject->setParent(&menu); 00587 menu.addAction(eject); 00588 } 00589 00590 teardown = placesModel->teardownActionForIndex(index); 00591 if (teardown!=0) { 00592 teardown->setParent(&menu); 00593 menu.addAction(teardown); 00594 } 00595 00596 if (teardown!=0 || eject!=0) { 00597 mainSeparator = menu.addSeparator(); 00598 } 00599 } 00600 if (add == 0) { 00601 add = menu.addAction(KIcon("document-new"), i18n("Add Entry...")); 00602 } 00603 00604 hide = menu.addAction(i18n("&Hide Entry '%1'", label)); 00605 hide->setCheckable(true); 00606 hide->setChecked(placesModel->isHidden(index)); 00607 } else { 00608 add = menu.addAction(KIcon("document-new"), i18n("Add Entry...")); 00609 } 00610 00611 QAction *showAll = 0; 00612 if (placesModel->hiddenCount()>0) { 00613 showAll = new QAction(i18n("&Show All Entries"), &menu); 00614 showAll->setCheckable(true); 00615 showAll->setChecked(d->showAll); 00616 if (mainSeparator == 0) { 00617 mainSeparator = menu.addSeparator(); 00618 } 00619 menu.insertAction(mainSeparator, showAll); 00620 } 00621 00622 QAction* remove = 0; 00623 if (index.isValid() && !placesModel->isDevice(index)) { 00624 remove = menu.addAction( KIcon("edit-delete"), i18n("&Remove Entry '%1'", label)); 00625 } 00626 00627 if (menu.isEmpty()) { 00628 return; 00629 } 00630 00631 QAction *result = menu.exec(event->globalPos()); 00632 00633 if (emptyTrash != 0 && result == emptyTrash) { 00634 const QString text = i18nc("@info", "Do you really want to empty the Trash? All items will be deleted."); 00635 const bool del = KMessageBox::warningContinueCancel(window(), 00636 text, 00637 QString(), 00638 KGuiItem(i18nc("@action:button", "Empty Trash"), 00639 KIcon("user-trash")) 00640 ) == KMessageBox::Continue; 00641 if (del) { 00642 QByteArray packedArgs; 00643 QDataStream stream(&packedArgs, QIODevice::WriteOnly); 00644 stream << int(1); 00645 KIO::Job *job = KIO::special(KUrl("trash:/"), packedArgs); 00646 KNotification::event("Trash: emptied", QString() , QPixmap() , 0, KNotification::DefaultEvent); 00647 job->ui()->setWindow(parentWidget()); 00648 connect(job, SIGNAL(result(KJob*)), SLOT(_k_trashUpdated(KJob*))); 00649 } 00650 } else if (edit != 0 && result == edit) { 00651 KBookmark bookmark = placesModel->bookmarkForIndex(index); 00652 KUrl url = bookmark.url(); 00653 QString label = bookmark.text(); 00654 QString iconName = bookmark.icon(); 00655 bool appLocal = !bookmark.metaDataItem("OnlyInApp").isEmpty(); 00656 00657 if (KFilePlaceEditDialog::getInformation(true, url, label, 00658 iconName, false, appLocal, 64, this)) 00659 { 00660 QString appName; 00661 if (appLocal) appName = KGlobal::mainComponent().componentName(); 00662 00663 placesModel->editPlace(index, label, url, iconName, appName); 00664 } 00665 00666 } else if (remove != 0 && result == remove) { 00667 placesModel->removePlace(index); 00668 } else if (hide != 0 && result == hide) { 00669 placesModel->setPlaceHidden(index, hide->isChecked()); 00670 QModelIndex current = placesModel->closestItem(d->currentUrl); 00671 00672 if (index!=current && !d->showAll && hide->isChecked()) { 00673 delegate->addDisappearingItem(index); 00674 00675 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) { 00676 delegate->setDisappearingItemProgress(0.0); 00677 d->itemDisappearTimeline.start(); 00678 } 00679 } 00680 } else if (showAll != 0 && result == showAll) { 00681 setShowAll(showAll->isChecked()); 00682 } else if (teardown != 0 && result == teardown) { 00683 placesModel->requestTeardown(index); 00684 } else if (eject != 0 && result == eject) { 00685 placesModel->requestEject(index); 00686 } else if (add != 0 && result == add) { 00687 KUrl url = d->currentUrl; 00688 QString label; 00689 QString iconName = "folder"; 00690 bool appLocal = true; 00691 if (KFilePlaceEditDialog::getInformation(true, url, label, 00692 iconName, true, appLocal, 64, this)) 00693 { 00694 QString appName; 00695 if (appLocal) appName = KGlobal::mainComponent().componentName(); 00696 00697 placesModel->addPlace(label, url, iconName, appName, index); 00698 } 00699 } 00700 00701 index = placesModel->closestItem(d->currentUrl); 00702 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); 00703 } 00704 00705 void KFilePlacesView::resizeEvent(QResizeEvent *event) 00706 { 00707 QListView::resizeEvent(event); 00708 d->adaptItemSize(); 00709 } 00710 00711 void KFilePlacesView::showEvent(QShowEvent *event) 00712 { 00713 QListView::showEvent(event); 00714 QTimer::singleShot(100, this, SLOT(_k_enableSmoothItemResizing())); 00715 } 00716 00717 void KFilePlacesView::hideEvent(QHideEvent *event) 00718 { 00719 QListView::hideEvent(event); 00720 d->smoothItemResizing = false; 00721 } 00722 00723 void KFilePlacesView::dragEnterEvent(QDragEnterEvent *event) 00724 { 00725 QListView::dragEnterEvent(event); 00726 d->dragging = true; 00727 00728 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00729 delegate->setShowHoverIndication(false); 00730 00731 d->dropRect = QRect(); 00732 } 00733 00734 void KFilePlacesView::dragLeaveEvent(QDragLeaveEvent *event) 00735 { 00736 QListView::dragLeaveEvent(event); 00737 d->dragging = false; 00738 00739 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00740 delegate->setShowHoverIndication(true); 00741 00742 setDirtyRegion(d->dropRect); 00743 } 00744 00745 void KFilePlacesView::dragMoveEvent(QDragMoveEvent *event) 00746 { 00747 QListView::dragMoveEvent(event); 00748 00749 // update the drop indicator 00750 const QPoint pos = event->pos(); 00751 const QModelIndex index = indexAt(pos); 00752 setDirtyRegion(d->dropRect); 00753 if (index.isValid()) { 00754 const QRect rect = visualRect(index); 00755 const int gap = d->insertIndicatorHeight(rect.height()); 00756 if (d->insertAbove(rect, pos)) { 00757 // indicate that the item will be inserted above the current place 00758 d->dropRect = QRect(rect.left(), rect.top() - gap / 2, 00759 rect.width(), gap); 00760 } else if (d->insertBelow(rect, pos)) { 00761 // indicate that the item will be inserted below the current place 00762 d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2, 00763 rect.width(), gap); 00764 } else { 00765 // indicate that the item be dropped above the current place 00766 d->dropRect = rect; 00767 } 00768 } 00769 00770 setDirtyRegion(d->dropRect); 00771 } 00772 00773 void KFilePlacesView::dropEvent(QDropEvent *event) 00774 { 00775 const QPoint pos = event->pos(); 00776 const QModelIndex index = indexAt(pos); 00777 if (index.isValid()) { 00778 const QRect rect = visualRect(index); 00779 if (!d->insertAbove(rect, pos) && !d->insertBelow(rect, pos)) { 00780 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00781 Q_ASSERT(placesModel != 0); 00782 emit urlsDropped(placesModel->url(index), event, this); 00783 event->acceptProposedAction(); 00784 } 00785 } 00786 00787 QListView::dropEvent(event); 00788 d->dragging = false; 00789 00790 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00791 delegate->setShowHoverIndication(true); 00792 } 00793 00794 void KFilePlacesView::paintEvent(QPaintEvent* event) 00795 { 00796 QListView::paintEvent(event); 00797 if (d->dragging && !d->dropRect.isEmpty()) { 00798 // draw drop indicator 00799 QPainter painter(viewport()); 00800 00801 const QModelIndex index = indexAt(d->dropRect.topLeft()); 00802 const QRect itemRect = visualRect(index); 00803 const bool drawInsertIndicator = !d->dropOnPlace || 00804 d->dropRect.height() <= d->insertIndicatorHeight(itemRect.height()); 00805 00806 if (drawInsertIndicator) { 00807 // draw indicator for inserting items 00808 QBrush blendedBrush = viewOptions().palette.brush(QPalette::Normal, QPalette::Highlight); 00809 QColor color = blendedBrush.color(); 00810 00811 const int y = (d->dropRect.top() + d->dropRect.bottom()) / 2; 00812 const int thickness = d->dropRect.height() / 2; 00813 Q_ASSERT(thickness >= 1); 00814 int alpha = 255; 00815 const int alphaDec = alpha / (thickness + 1); 00816 for (int i = 0; i < thickness; i++) { 00817 color.setAlpha(alpha); 00818 alpha -= alphaDec; 00819 painter.setPen(color); 00820 painter.drawLine(d->dropRect.left(), y - i, d->dropRect.right(), y - i); 00821 painter.drawLine(d->dropRect.left(), y + i, d->dropRect.right(), y + i); 00822 } 00823 } else { 00824 // draw indicator for copying/moving/linking to items 00825 QStyleOptionViewItemV4 opt; 00826 opt.initFrom(this); 00827 opt.rect = itemRect; 00828 opt.state = QStyle::State_Enabled | QStyle::State_MouseOver; 00829 style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &painter, this); 00830 } 00831 } 00832 } 00833 00834 void KFilePlacesView::setModel(QAbstractItemModel *model) 00835 { 00836 QListView::setModel(model); 00837 d->updateHiddenRows(); 00838 // Uses Qt::QueuedConnection to delay the time when the slot will be 00839 // called. In case of an item move the remove+add will be done before 00840 // we adapt the item size (otherwise we'd get it wrong as we'd execute 00841 // it after the remove only). 00842 connect(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), 00843 this, SLOT(adaptItemSize()), Qt::QueuedConnection); 00844 connect(selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), 00845 d->watcher, SLOT(currentIndexChanged(const QModelIndex&))); 00846 } 00847 00848 void KFilePlacesView::rowsInserted(const QModelIndex &parent, int start, int end) 00849 { 00850 QListView::rowsInserted(parent, start, end); 00851 setUrl(d->currentUrl); 00852 00853 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00854 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00855 00856 for (int i=start; i<=end; ++i) { 00857 QModelIndex index = placesModel->index(i, 0, parent); 00858 if (d->showAll || !placesModel->isHidden(index)) { 00859 delegate->addAppearingItem(index); 00860 } else { 00861 setRowHidden(i, true); 00862 } 00863 } 00864 00865 if (d->itemAppearTimeline.state()!=QTimeLine::Running) { 00866 delegate->setAppearingItemProgress(0.0); 00867 d->itemAppearTimeline.start(); 00868 } 00869 00870 d->adaptItemSize(); 00871 } 00872 00873 QSize KFilePlacesView::sizeHint() const 00874 { 00875 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00876 if (!placesModel) { 00877 return QListView::sizeHint(); 00878 } 00879 const int height = QListView::sizeHint().height(); 00880 QFontMetrics fm = d->q->fontMetrics(); 00881 int textWidth = 0; 00882 00883 for (int i=0; i<placesModel->rowCount(); ++i) { 00884 QModelIndex index = placesModel->index(i, 0); 00885 if (!placesModel->isHidden(index)) 00886 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString())); 00887 } 00888 00889 const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Dialog); 00890 return QSize(iconSize + textWidth + fm.height() / 2, height); 00891 } 00892 00893 void KFilePlacesView::Private::setCurrentIndex(const QModelIndex &index) 00894 { 00895 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00896 00897 if (placesModel==0) return; 00898 00899 KUrl url = placesModel->url(index); 00900 00901 if (url.isValid()) { 00902 currentUrl = url; 00903 updateHiddenRows(); 00904 emit q->urlChanged(url); 00905 if (showAll) { 00906 q->setShowAll(false); 00907 } 00908 } else { 00909 q->setUrl(currentUrl); 00910 } 00911 } 00912 00913 void KFilePlacesView::Private::adaptItemSize() 00914 { 00915 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 00916 if (!delegate) return; 00917 00918 if (!autoResizeItems) { 00919 int size = q->iconSize().width(); // Assume width == height 00920 delegate->setIconSize(size); 00921 q->scheduleDelayedItemsLayout(); 00922 return; 00923 } 00924 00925 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00926 00927 if (placesModel==0) return; 00928 00929 int rowCount = placesModel->rowCount(); 00930 00931 if (!showAll) { 00932 rowCount-= placesModel->hiddenCount(); 00933 00934 QModelIndex current = placesModel->closestItem(currentUrl); 00935 00936 if (placesModel->isHidden(current)) { 00937 rowCount++; 00938 } 00939 } 00940 00941 if (rowCount==0) return; // We've nothing to display anyway 00942 00943 const int minSize = 16; 00944 const int maxSize = 64; 00945 00946 int textWidth = 0; 00947 QFontMetrics fm = q->fontMetrics(); 00948 for (int i=0; i<placesModel->rowCount(); ++i) { 00949 QModelIndex index = placesModel->index(i, 0); 00950 00951 if (!placesModel->isHidden(index)) 00952 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString())); 00953 } 00954 00955 const int margin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, q) + 1; 00956 const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1; 00957 const int maxHeight = ((q->height() - (fm.height() / 2) * rowCount) / rowCount) - 1; 00958 00959 int size = qMin(maxHeight, maxWidth); 00960 00961 if (size<minSize) { 00962 size = minSize; 00963 } else if (size>maxSize) { 00964 size = maxSize; 00965 } else { 00966 // Make it a multiple of 16 00967 size &= ~0xf; 00968 } 00969 00970 if (size==delegate->iconSize()) return; 00971 00972 if (smoothItemResizing) { 00973 oldSize = delegate->iconSize(); 00974 endSize = size; 00975 if (adaptItemsTimeline.state()!=QTimeLine::Running) { 00976 adaptItemsTimeline.start(); 00977 } 00978 } else { 00979 delegate->setIconSize(size); 00980 q->scheduleDelayedItemsLayout(); 00981 } 00982 } 00983 00984 void KFilePlacesView::Private::updateHiddenRows() 00985 { 00986 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00987 00988 if (placesModel==0) return; 00989 00990 int rowCount = placesModel->rowCount(); 00991 QModelIndex current = placesModel->closestItem(currentUrl); 00992 00993 for (int i=0; i<rowCount; ++i) { 00994 QModelIndex index = placesModel->index(i, 0); 00995 if (index!=current && placesModel->isHidden(index) && !showAll) { 00996 q->setRowHidden(i, true); 00997 } else { 00998 q->setRowHidden(i, false); 00999 } 01000 } 01001 01002 adaptItemSize(); 01003 } 01004 01005 bool KFilePlacesView::Private::insertAbove(const QRect &itemRect, const QPoint &pos) const 01006 { 01007 if (dropOnPlace) { 01008 return pos.y() < itemRect.top() + insertIndicatorHeight(itemRect.height()) / 2; 01009 } 01010 01011 return pos.y() < itemRect.top() + (itemRect.height() / 2); 01012 } 01013 01014 bool KFilePlacesView::Private::insertBelow(const QRect &itemRect, const QPoint &pos) const 01015 { 01016 if (dropOnPlace) { 01017 return pos.y() > itemRect.bottom() - insertIndicatorHeight(itemRect.height()) / 2; 01018 } 01019 01020 return pos.y() >= itemRect.top() + (itemRect.height() / 2); 01021 } 01022 01023 int KFilePlacesView::Private::insertIndicatorHeight(int itemHeight) const 01024 { 01025 const int min = 4; 01026 const int max = 12; 01027 01028 int height = itemHeight / 4; 01029 if (height < min) { 01030 height = min; 01031 } else if (height > max) { 01032 height = max; 01033 } 01034 return height; 01035 } 01036 01037 void KFilePlacesView::Private::fadeCapacityBar(const QModelIndex &index, FadeType fadeType) 01038 { 01039 QTimeLine *timeLine = delegate->fadeAnimationForIndex(index); 01040 delete timeLine; 01041 delegate->removeFadeAnimation(index); 01042 timeLine = new QTimeLine(250, q); 01043 connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(_k_capacityBarFadeValueChanged())); 01044 if (fadeType == FadeIn) { 01045 timeLine->setDirection(QTimeLine::Forward); 01046 timeLine->setCurrentTime(0); 01047 } else { 01048 timeLine->setDirection(QTimeLine::Backward); 01049 timeLine->setCurrentTime(250); 01050 } 01051 delegate->addFadeAnimation(index, timeLine); 01052 timeLine->start(); 01053 } 01054 01055 void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) 01056 { 01057 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 01058 01059 if (placesModel==0) return; 01060 01061 lastClickedIndex = QPersistentModelIndex(); 01062 01063 if (placesModel->setupNeeded(index)) { 01064 QObject::connect(placesModel, SIGNAL(setupDone(const QModelIndex &, bool)), 01065 q, SLOT(_k_storageSetupDone(const QModelIndex &, bool))); 01066 01067 lastClickedIndex = index; 01068 placesModel->requestSetup(index); 01069 return; 01070 } 01071 01072 setCurrentIndex(index); 01073 } 01074 01075 void KFilePlacesView::Private::_k_placeEntered(const QModelIndex &index) 01076 { 01077 fadeCapacityBar(index, FadeIn); 01078 pollingRequestCount++; 01079 if (pollingRequestCount == 1) { 01080 pollDevices.start(); 01081 } 01082 } 01083 01084 void KFilePlacesView::Private::_k_placeLeft(const QModelIndex &index) 01085 { 01086 fadeCapacityBar(index, FadeOut); 01087 pollingRequestCount--; 01088 if (!pollingRequestCount) { 01089 pollDevices.stop(); 01090 } 01091 } 01092 01093 void KFilePlacesView::Private::_k_storageSetupDone(const QModelIndex &index, bool success) 01094 { 01095 if (index!=lastClickedIndex) { 01096 return; 01097 } 01098 01099 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 01100 01101 QObject::disconnect(placesModel, SIGNAL(setupDone(const QModelIndex &, bool)), 01102 q, SLOT(_k_storageSetupDone(const QModelIndex &, bool))); 01103 01104 if (success) { 01105 setCurrentIndex(lastClickedIndex); 01106 } else { 01107 q->setUrl(currentUrl); 01108 } 01109 01110 lastClickedIndex = QPersistentModelIndex(); 01111 } 01112 01113 void KFilePlacesView::Private::_k_adaptItemsUpdate(qreal value) 01114 { 01115 int add = (endSize-oldSize)*value; 01116 01117 int size = oldSize+add; 01118 01119 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01120 delegate->setIconSize(size); 01121 q->scheduleDelayedItemsLayout(); 01122 } 01123 01124 void KFilePlacesView::Private::_k_itemAppearUpdate(qreal value) 01125 { 01126 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01127 01128 delegate->setAppearingItemProgress(value); 01129 q->scheduleDelayedItemsLayout(); 01130 } 01131 01132 void KFilePlacesView::Private::_k_itemDisappearUpdate(qreal value) 01133 { 01134 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01135 01136 delegate->setDisappearingItemProgress(value); 01137 01138 if (value>=1.0) { 01139 updateHiddenRows(); 01140 } 01141 01142 q->scheduleDelayedItemsLayout(); 01143 } 01144 01145 void KFilePlacesView::Private::_k_enableSmoothItemResizing() 01146 { 01147 smoothItemResizing = true; 01148 } 01149 01150 void KFilePlacesView::Private::_k_trashUpdated(KJob *job) 01151 { 01152 if (job->error()) { 01153 static_cast<KIO::Job*>(job)->ui()->showErrorMessage(); 01154 } 01155 org::kde::KDirNotify::emitFilesAdded("trash:/"); 01156 } 01157 01158 void KFilePlacesView::Private::_k_capacityBarFadeValueChanged() 01159 { 01160 const QModelIndex index = delegate->indexForFadeAnimation(static_cast<QTimeLine*>(q->sender())); 01161 if (!index.isValid()) { 01162 return; 01163 } 01164 q->update(index); 01165 } 01166 01167 void KFilePlacesView::Private::_k_triggerDevicePolling() 01168 { 01169 const QModelIndex hoveredIndex = watcher->hoveredIndex(); 01170 if (hoveredIndex.isValid()) { 01171 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(hoveredIndex.model()); 01172 if (placesModel->isDevice(hoveredIndex)) { 01173 q->update(hoveredIndex); 01174 } 01175 } 01176 const QModelIndex focusedIndex = watcher->focusedIndex(); 01177 if (focusedIndex.isValid() && focusedIndex != hoveredIndex) { 01178 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(focusedIndex.model()); 01179 if (placesModel->isDevice(focusedIndex)) { 01180 q->update(focusedIndex); 01181 } 01182 } 01183 } 01184 01185 void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 01186 { 01187 QListView::dataChanged(topLeft, bottomRight); 01188 d->adaptItemSize(); 01189 } 01190 01191 #include "kfileplacesview.moc" 01192 #include "kfileplacesview_p.moc"
KDE 4.6 API Reference