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