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 menu.addActions(actions()); 00628 00629 if (menu.isEmpty()) { 00630 return; 00631 } 00632 00633 QAction *result = menu.exec(event->globalPos()); 00634 00635 if (emptyTrash != 0 && result == emptyTrash) { 00636 const QString text = i18nc("@info", "Do you really want to empty the Trash? All items will be deleted."); 00637 const bool del = KMessageBox::warningContinueCancel(window(), 00638 text, 00639 QString(), 00640 KGuiItem(i18nc("@action:button", "Empty Trash"), 00641 KIcon("user-trash")) 00642 ) == KMessageBox::Continue; 00643 if (del) { 00644 QByteArray packedArgs; 00645 QDataStream stream(&packedArgs, QIODevice::WriteOnly); 00646 stream << int(1); 00647 KIO::Job *job = KIO::special(KUrl("trash:/"), packedArgs); 00648 KNotification::event("Trash: emptied", QString() , QPixmap() , 0, KNotification::DefaultEvent); 00649 job->ui()->setWindow(parentWidget()); 00650 connect(job, SIGNAL(result(KJob*)), SLOT(_k_trashUpdated(KJob*))); 00651 } 00652 } else if (edit != 0 && result == edit) { 00653 KBookmark bookmark = placesModel->bookmarkForIndex(index); 00654 KUrl url = bookmark.url(); 00655 QString label = bookmark.text(); 00656 QString iconName = bookmark.icon(); 00657 bool appLocal = !bookmark.metaDataItem("OnlyInApp").isEmpty(); 00658 00659 if (KFilePlaceEditDialog::getInformation(true, url, label, 00660 iconName, false, appLocal, 64, this)) 00661 { 00662 QString appName; 00663 if (appLocal) appName = KGlobal::mainComponent().componentName(); 00664 00665 placesModel->editPlace(index, label, url, iconName, appName); 00666 } 00667 00668 } else if (remove != 0 && result == remove) { 00669 placesModel->removePlace(index); 00670 } else if (hide != 0 && result == hide) { 00671 placesModel->setPlaceHidden(index, hide->isChecked()); 00672 QModelIndex current = placesModel->closestItem(d->currentUrl); 00673 00674 if (index!=current && !d->showAll && hide->isChecked()) { 00675 delegate->addDisappearingItem(index); 00676 00677 if (d->itemDisappearTimeline.state()!=QTimeLine::Running) { 00678 delegate->setDisappearingItemProgress(0.0); 00679 d->itemDisappearTimeline.start(); 00680 } 00681 } 00682 } else if (showAll != 0 && result == showAll) { 00683 setShowAll(showAll->isChecked()); 00684 } else if (teardown != 0 && result == teardown) { 00685 placesModel->requestTeardown(index); 00686 } else if (eject != 0 && result == eject) { 00687 placesModel->requestEject(index); 00688 } else if (add != 0 && result == add) { 00689 KUrl url = d->currentUrl; 00690 QString label; 00691 QString iconName = "folder"; 00692 bool appLocal = true; 00693 if (KFilePlaceEditDialog::getInformation(true, url, label, 00694 iconName, true, appLocal, 64, this)) 00695 { 00696 QString appName; 00697 if (appLocal) appName = KGlobal::mainComponent().componentName(); 00698 00699 placesModel->addPlace(label, url, iconName, appName, index); 00700 } 00701 } 00702 00703 index = placesModel->closestItem(d->currentUrl); 00704 selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); 00705 } 00706 00707 void KFilePlacesView::resizeEvent(QResizeEvent *event) 00708 { 00709 QListView::resizeEvent(event); 00710 d->adaptItemSize(); 00711 } 00712 00713 void KFilePlacesView::showEvent(QShowEvent *event) 00714 { 00715 QListView::showEvent(event); 00716 QTimer::singleShot(100, this, SLOT(_k_enableSmoothItemResizing())); 00717 } 00718 00719 void KFilePlacesView::hideEvent(QHideEvent *event) 00720 { 00721 QListView::hideEvent(event); 00722 d->smoothItemResizing = false; 00723 } 00724 00725 void KFilePlacesView::dragEnterEvent(QDragEnterEvent *event) 00726 { 00727 QListView::dragEnterEvent(event); 00728 d->dragging = true; 00729 00730 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00731 delegate->setShowHoverIndication(false); 00732 00733 d->dropRect = QRect(); 00734 } 00735 00736 void KFilePlacesView::dragLeaveEvent(QDragLeaveEvent *event) 00737 { 00738 QListView::dragLeaveEvent(event); 00739 d->dragging = false; 00740 00741 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00742 delegate->setShowHoverIndication(true); 00743 00744 setDirtyRegion(d->dropRect); 00745 } 00746 00747 void KFilePlacesView::dragMoveEvent(QDragMoveEvent *event) 00748 { 00749 QListView::dragMoveEvent(event); 00750 00751 // update the drop indicator 00752 const QPoint pos = event->pos(); 00753 const QModelIndex index = indexAt(pos); 00754 setDirtyRegion(d->dropRect); 00755 if (index.isValid()) { 00756 const QRect rect = visualRect(index); 00757 const int gap = d->insertIndicatorHeight(rect.height()); 00758 if (d->insertAbove(rect, pos)) { 00759 // indicate that the item will be inserted above the current place 00760 d->dropRect = QRect(rect.left(), rect.top() - gap / 2, 00761 rect.width(), gap); 00762 } else if (d->insertBelow(rect, pos)) { 00763 // indicate that the item will be inserted below the current place 00764 d->dropRect = QRect(rect.left(), rect.bottom() + 1 - gap / 2, 00765 rect.width(), gap); 00766 } else { 00767 // indicate that the item be dropped above the current place 00768 d->dropRect = rect; 00769 } 00770 } 00771 00772 setDirtyRegion(d->dropRect); 00773 } 00774 00775 void KFilePlacesView::dropEvent(QDropEvent *event) 00776 { 00777 const QPoint pos = event->pos(); 00778 const QModelIndex index = indexAt(pos); 00779 if (index.isValid()) { 00780 const QRect rect = visualRect(index); 00781 if (!d->insertAbove(rect, pos) && !d->insertBelow(rect, pos)) { 00782 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00783 Q_ASSERT(placesModel != 0); 00784 emit urlsDropped(placesModel->url(index), event, this); 00785 event->acceptProposedAction(); 00786 } 00787 } 00788 00789 QListView::dropEvent(event); 00790 d->dragging = false; 00791 00792 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00793 delegate->setShowHoverIndication(true); 00794 } 00795 00796 void KFilePlacesView::paintEvent(QPaintEvent* event) 00797 { 00798 QListView::paintEvent(event); 00799 if (d->dragging && !d->dropRect.isEmpty()) { 00800 // draw drop indicator 00801 QPainter painter(viewport()); 00802 00803 const QModelIndex index = indexAt(d->dropRect.topLeft()); 00804 const QRect itemRect = visualRect(index); 00805 const bool drawInsertIndicator = !d->dropOnPlace || 00806 d->dropRect.height() <= d->insertIndicatorHeight(itemRect.height()); 00807 00808 if (drawInsertIndicator) { 00809 // draw indicator for inserting items 00810 QBrush blendedBrush = viewOptions().palette.brush(QPalette::Normal, QPalette::Highlight); 00811 QColor color = blendedBrush.color(); 00812 00813 const int y = (d->dropRect.top() + d->dropRect.bottom()) / 2; 00814 const int thickness = d->dropRect.height() / 2; 00815 Q_ASSERT(thickness >= 1); 00816 int alpha = 255; 00817 const int alphaDec = alpha / (thickness + 1); 00818 for (int i = 0; i < thickness; i++) { 00819 color.setAlpha(alpha); 00820 alpha -= alphaDec; 00821 painter.setPen(color); 00822 painter.drawLine(d->dropRect.left(), y - i, d->dropRect.right(), y - i); 00823 painter.drawLine(d->dropRect.left(), y + i, d->dropRect.right(), y + i); 00824 } 00825 } else { 00826 // draw indicator for copying/moving/linking to items 00827 QStyleOptionViewItemV4 opt; 00828 opt.initFrom(this); 00829 opt.rect = itemRect; 00830 opt.state = QStyle::State_Enabled | QStyle::State_MouseOver; 00831 style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, &painter, this); 00832 } 00833 } 00834 } 00835 00836 void KFilePlacesView::setModel(QAbstractItemModel *model) 00837 { 00838 QListView::setModel(model); 00839 d->updateHiddenRows(); 00840 // Uses Qt::QueuedConnection to delay the time when the slot will be 00841 // called. In case of an item move the remove+add will be done before 00842 // we adapt the item size (otherwise we'd get it wrong as we'd execute 00843 // it after the remove only). 00844 connect(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), 00845 this, SLOT(adaptItemSize()), Qt::QueuedConnection); 00846 connect(selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), 00847 d->watcher, SLOT(currentIndexChanged(const QModelIndex&))); 00848 } 00849 00850 void KFilePlacesView::rowsInserted(const QModelIndex &parent, int start, int end) 00851 { 00852 QListView::rowsInserted(parent, start, end); 00853 setUrl(d->currentUrl); 00854 00855 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(itemDelegate()); 00856 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00857 00858 for (int i=start; i<=end; ++i) { 00859 QModelIndex index = placesModel->index(i, 0, parent); 00860 if (d->showAll || !placesModel->isHidden(index)) { 00861 delegate->addAppearingItem(index); 00862 } else { 00863 setRowHidden(i, true); 00864 } 00865 } 00866 00867 if (d->itemAppearTimeline.state()!=QTimeLine::Running) { 00868 delegate->setAppearingItemProgress(0.0); 00869 d->itemAppearTimeline.start(); 00870 } 00871 00872 d->adaptItemSize(); 00873 } 00874 00875 QSize KFilePlacesView::sizeHint() const 00876 { 00877 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(model()); 00878 if (!placesModel) { 00879 return QListView::sizeHint(); 00880 } 00881 const int height = QListView::sizeHint().height(); 00882 QFontMetrics fm = d->q->fontMetrics(); 00883 int textWidth = 0; 00884 00885 for (int i=0; i<placesModel->rowCount(); ++i) { 00886 QModelIndex index = placesModel->index(i, 0); 00887 if (!placesModel->isHidden(index)) 00888 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString())); 00889 } 00890 00891 const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Dialog); 00892 return QSize(iconSize + textWidth + fm.height() / 2, height); 00893 } 00894 00895 void KFilePlacesView::Private::setCurrentIndex(const QModelIndex &index) 00896 { 00897 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00898 00899 if (placesModel==0) return; 00900 00901 KUrl url = placesModel->url(index); 00902 00903 if (url.isValid()) { 00904 currentUrl = url; 00905 updateHiddenRows(); 00906 emit q->urlChanged(url); 00907 if (showAll) { 00908 q->setShowAll(false); 00909 } 00910 } else { 00911 q->setUrl(currentUrl); 00912 } 00913 } 00914 00915 void KFilePlacesView::Private::adaptItemSize() 00916 { 00917 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 00918 if (!delegate) return; 00919 00920 if (!autoResizeItems) { 00921 int size = q->iconSize().width(); // Assume width == height 00922 delegate->setIconSize(size); 00923 q->scheduleDelayedItemsLayout(); 00924 return; 00925 } 00926 00927 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00928 00929 if (placesModel==0) return; 00930 00931 int rowCount = placesModel->rowCount(); 00932 00933 if (!showAll) { 00934 rowCount-= placesModel->hiddenCount(); 00935 00936 QModelIndex current = placesModel->closestItem(currentUrl); 00937 00938 if (placesModel->isHidden(current)) { 00939 rowCount++; 00940 } 00941 } 00942 00943 if (rowCount==0) return; // We've nothing to display anyway 00944 00945 const int minSize = 16; 00946 const int maxSize = 64; 00947 00948 int textWidth = 0; 00949 QFontMetrics fm = q->fontMetrics(); 00950 for (int i=0; i<placesModel->rowCount(); ++i) { 00951 QModelIndex index = placesModel->index(i, 0); 00952 00953 if (!placesModel->isHidden(index)) 00954 textWidth = qMax(textWidth,fm.width(index.data(Qt::DisplayRole).toString())); 00955 } 00956 00957 const int margin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, q) + 1; 00958 const int maxWidth = q->viewport()->width() - textWidth - 4 * margin - 1; 00959 const int maxHeight = ((q->height() - (fm.height() / 2) * rowCount) / rowCount) - 1; 00960 00961 int size = qMin(maxHeight, maxWidth); 00962 00963 if (size<minSize) { 00964 size = minSize; 00965 } else if (size>maxSize) { 00966 size = maxSize; 00967 } else { 00968 // Make it a multiple of 16 00969 size &= ~0xf; 00970 } 00971 00972 if (size==delegate->iconSize()) return; 00973 00974 if (smoothItemResizing) { 00975 oldSize = delegate->iconSize(); 00976 endSize = size; 00977 if (adaptItemsTimeline.state()!=QTimeLine::Running) { 00978 adaptItemsTimeline.start(); 00979 } 00980 } else { 00981 delegate->setIconSize(size); 00982 q->scheduleDelayedItemsLayout(); 00983 } 00984 } 00985 00986 void KFilePlacesView::Private::updateHiddenRows() 00987 { 00988 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 00989 00990 if (placesModel==0) return; 00991 00992 int rowCount = placesModel->rowCount(); 00993 QModelIndex current = placesModel->closestItem(currentUrl); 00994 00995 for (int i=0; i<rowCount; ++i) { 00996 QModelIndex index = placesModel->index(i, 0); 00997 if (index!=current && placesModel->isHidden(index) && !showAll) { 00998 q->setRowHidden(i, true); 00999 } else { 01000 q->setRowHidden(i, false); 01001 } 01002 } 01003 01004 adaptItemSize(); 01005 } 01006 01007 bool KFilePlacesView::Private::insertAbove(const QRect &itemRect, const QPoint &pos) const 01008 { 01009 if (dropOnPlace) { 01010 return pos.y() < itemRect.top() + insertIndicatorHeight(itemRect.height()) / 2; 01011 } 01012 01013 return pos.y() < itemRect.top() + (itemRect.height() / 2); 01014 } 01015 01016 bool KFilePlacesView::Private::insertBelow(const QRect &itemRect, const QPoint &pos) const 01017 { 01018 if (dropOnPlace) { 01019 return pos.y() > itemRect.bottom() - insertIndicatorHeight(itemRect.height()) / 2; 01020 } 01021 01022 return pos.y() >= itemRect.top() + (itemRect.height() / 2); 01023 } 01024 01025 int KFilePlacesView::Private::insertIndicatorHeight(int itemHeight) const 01026 { 01027 const int min = 4; 01028 const int max = 12; 01029 01030 int height = itemHeight / 4; 01031 if (height < min) { 01032 height = min; 01033 } else if (height > max) { 01034 height = max; 01035 } 01036 return height; 01037 } 01038 01039 void KFilePlacesView::Private::fadeCapacityBar(const QModelIndex &index, FadeType fadeType) 01040 { 01041 QTimeLine *timeLine = delegate->fadeAnimationForIndex(index); 01042 delete timeLine; 01043 delegate->removeFadeAnimation(index); 01044 timeLine = new QTimeLine(250, q); 01045 connect(timeLine, SIGNAL(valueChanged(qreal)), q, SLOT(_k_capacityBarFadeValueChanged())); 01046 if (fadeType == FadeIn) { 01047 timeLine->setDirection(QTimeLine::Forward); 01048 timeLine->setCurrentTime(0); 01049 } else { 01050 timeLine->setDirection(QTimeLine::Backward); 01051 timeLine->setCurrentTime(250); 01052 } 01053 delegate->addFadeAnimation(index, timeLine); 01054 timeLine->start(); 01055 } 01056 01057 void KFilePlacesView::Private::_k_placeClicked(const QModelIndex &index) 01058 { 01059 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 01060 01061 if (placesModel==0) return; 01062 01063 lastClickedIndex = QPersistentModelIndex(); 01064 01065 if (placesModel->setupNeeded(index)) { 01066 QObject::connect(placesModel, SIGNAL(setupDone(const QModelIndex &, bool)), 01067 q, SLOT(_k_storageSetupDone(const QModelIndex &, bool))); 01068 01069 lastClickedIndex = index; 01070 placesModel->requestSetup(index); 01071 return; 01072 } 01073 01074 setCurrentIndex(index); 01075 } 01076 01077 void KFilePlacesView::Private::_k_placeEntered(const QModelIndex &index) 01078 { 01079 fadeCapacityBar(index, FadeIn); 01080 pollingRequestCount++; 01081 if (pollingRequestCount == 1) { 01082 pollDevices.start(); 01083 } 01084 } 01085 01086 void KFilePlacesView::Private::_k_placeLeft(const QModelIndex &index) 01087 { 01088 fadeCapacityBar(index, FadeOut); 01089 pollingRequestCount--; 01090 if (!pollingRequestCount) { 01091 pollDevices.stop(); 01092 } 01093 } 01094 01095 void KFilePlacesView::Private::_k_storageSetupDone(const QModelIndex &index, bool success) 01096 { 01097 if (index!=lastClickedIndex) { 01098 return; 01099 } 01100 01101 KFilePlacesModel *placesModel = qobject_cast<KFilePlacesModel*>(q->model()); 01102 01103 QObject::disconnect(placesModel, SIGNAL(setupDone(const QModelIndex &, bool)), 01104 q, SLOT(_k_storageSetupDone(const QModelIndex &, bool))); 01105 01106 if (success) { 01107 setCurrentIndex(lastClickedIndex); 01108 } else { 01109 q->setUrl(currentUrl); 01110 } 01111 01112 lastClickedIndex = QPersistentModelIndex(); 01113 } 01114 01115 void KFilePlacesView::Private::_k_adaptItemsUpdate(qreal value) 01116 { 01117 int add = (endSize-oldSize)*value; 01118 01119 int size = oldSize+add; 01120 01121 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01122 delegate->setIconSize(size); 01123 q->scheduleDelayedItemsLayout(); 01124 } 01125 01126 void KFilePlacesView::Private::_k_itemAppearUpdate(qreal value) 01127 { 01128 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01129 01130 delegate->setAppearingItemProgress(value); 01131 q->scheduleDelayedItemsLayout(); 01132 } 01133 01134 void KFilePlacesView::Private::_k_itemDisappearUpdate(qreal value) 01135 { 01136 KFilePlacesViewDelegate *delegate = dynamic_cast<KFilePlacesViewDelegate*>(q->itemDelegate()); 01137 01138 delegate->setDisappearingItemProgress(value); 01139 01140 if (value>=1.0) { 01141 updateHiddenRows(); 01142 } 01143 01144 q->scheduleDelayedItemsLayout(); 01145 } 01146 01147 void KFilePlacesView::Private::_k_enableSmoothItemResizing() 01148 { 01149 smoothItemResizing = true; 01150 } 01151 01152 void KFilePlacesView::Private::_k_trashUpdated(KJob *job) 01153 { 01154 if (job->error()) { 01155 static_cast<KIO::Job*>(job)->ui()->showErrorMessage(); 01156 } 01157 org::kde::KDirNotify::emitFilesAdded("trash:/"); 01158 } 01159 01160 void KFilePlacesView::Private::_k_capacityBarFadeValueChanged() 01161 { 01162 const QModelIndex index = delegate->indexForFadeAnimation(static_cast<QTimeLine*>(q->sender())); 01163 if (!index.isValid()) { 01164 return; 01165 } 01166 q->update(index); 01167 } 01168 01169 void KFilePlacesView::Private::_k_triggerDevicePolling() 01170 { 01171 const QModelIndex hoveredIndex = watcher->hoveredIndex(); 01172 if (hoveredIndex.isValid()) { 01173 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(hoveredIndex.model()); 01174 if (placesModel->isDevice(hoveredIndex)) { 01175 q->update(hoveredIndex); 01176 } 01177 } 01178 const QModelIndex focusedIndex = watcher->focusedIndex(); 01179 if (focusedIndex.isValid() && focusedIndex != hoveredIndex) { 01180 const KFilePlacesModel *placesModel = static_cast<const KFilePlacesModel*>(focusedIndex.model()); 01181 if (placesModel->isDevice(focusedIndex)) { 01182 q->update(focusedIndex); 01183 } 01184 } 01185 } 01186 01187 void KFilePlacesView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) 01188 { 01189 QListView::dataChanged(topLeft, bottomRight); 01190 d->adaptItemSize(); 01191 } 01192 01193 #include "kfileplacesview.moc" 01194 #include "kfileplacesview_p.moc"
KDE 4.7 API Reference