KDEUI
kextendableitemdelegate.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com) 00003 Copyright (C) 2008 Urs Wolfer (uwolfer @ 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 as published by the Free Software Foundation; either 00008 version 2 of the License, or (at your option) any later version. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kextendableitemdelegate.h" 00022 00023 #include <QModelIndex> 00024 #include <QScrollBar> 00025 #include <QTreeView> 00026 #include <QPainter> 00027 #include <QApplication> 00028 00029 00030 class KExtendableItemDelegate::Private { 00031 public: 00032 Private(KExtendableItemDelegate *parent) : 00033 q(parent), 00034 stateTick(0), 00035 cachedStateTick(-1), 00036 cachedRow(-20), //Qt uses -1 for invalid indices 00037 extender(0), 00038 extenderHeight(0) 00039 00040 {} 00041 00042 void _k_extenderDestructionHandler(QObject *destroyed); 00043 void _k_verticalScroll(); 00044 00045 QSize maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const; 00046 QModelIndex indexOfExtendedColumnInSameRow(const QModelIndex &index) const; 00047 void scheduleUpdateViewLayout(); 00048 00049 KExtendableItemDelegate *q; 00050 00054 void deleteExtenders(); 00055 00056 //this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex 00057 QHash<QPersistentModelIndex, QWidget *> extenders; 00058 QHash<QWidget *, QPersistentModelIndex> extenderIndices; 00059 QHash<QWidget *, QPersistentModelIndex> deletionQueue; 00060 QPixmap extendPixmap; 00061 QPixmap contractPixmap; 00062 int stateTick; 00063 int cachedStateTick; 00064 int cachedRow; 00065 QModelIndex cachedParentIndex; 00066 QWidget *extender; 00067 int extenderHeight; 00068 }; 00069 00070 00071 KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView* parent) 00072 : QStyledItemDelegate(parent), 00073 d(new Private(this)) 00074 { 00075 connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)), 00076 this, SLOT(_k_verticalScroll())); 00077 } 00078 00079 00080 KExtendableItemDelegate::~KExtendableItemDelegate() 00081 { 00082 delete d; 00083 } 00084 00085 00086 void KExtendableItemDelegate::extendItem(QWidget *ext, const QModelIndex &index) 00087 { 00088 // kDebug() << "Creating extender at " << ext << " for item " << index.model()->data(index,Qt::DisplayRole).toString(); 00089 00090 if (!ext || !index.isValid()) { 00091 return; 00092 } 00093 //maintain the invariant "zero or one extender per row" 00094 d->stateTick++; 00095 contractItem(d->indexOfExtendedColumnInSameRow(index)); 00096 d->stateTick++; 00097 //reparent, as promised in the docs 00098 QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(parent()); 00099 if (!aiv) { 00100 return; 00101 } 00102 ext->setParent(aiv->viewport()); 00103 d->extenders.insert(index, ext); 00104 d->extenderIndices.insert(ext, index); 00105 connect(ext, SIGNAL(destroyed(QObject *)), this, SLOT(_k_extenderDestructionHandler(QObject *))); 00106 emit extenderCreated(ext, index); 00107 d->scheduleUpdateViewLayout(); 00108 } 00109 00110 00111 void KExtendableItemDelegate::contractItem(const QModelIndex& index) 00112 { 00113 QWidget *extender = d->extenders.value(index); 00114 if (!extender) { 00115 return; 00116 } 00117 // kDebug() << "Collapse extender at " << extender << " for item " << index.model()->data(index,Qt::DisplayRole).toString(); 00118 extender->hide(); 00119 extender->deleteLater(); 00120 00121 QPersistentModelIndex persistentIndex = d->extenderIndices.take(extender); 00122 d->extenders.remove(persistentIndex); 00123 00124 d->deletionQueue.insert(extender, persistentIndex); 00125 00126 d->scheduleUpdateViewLayout(); 00127 } 00128 00129 00130 void KExtendableItemDelegate::contractAll() 00131 { 00132 d->deleteExtenders(); 00133 } 00134 00135 00136 //slot 00137 void KExtendableItemDelegate::Private::_k_extenderDestructionHandler(QObject *destroyed) 00138 { 00139 // kDebug() << "Removing extender at " << destroyed; 00140 00141 QWidget *extender = static_cast<QWidget *>(destroyed); 00142 stateTick++; 00143 00144 QPersistentModelIndex persistentIndex = deletionQueue.take(extender); 00145 if (persistentIndex.isValid() && 00146 q->receivers(SIGNAL(extenderDestroyed(QWidget *, QModelIndex)))) { 00147 00148 QModelIndex index = persistentIndex; 00149 emit q->extenderDestroyed(extender, index); 00150 } 00151 00152 scheduleUpdateViewLayout(); 00153 } 00154 00155 00156 //slot 00157 void KExtendableItemDelegate::Private::_k_verticalScroll() 00158 { 00159 foreach (QWidget *extender, extenders) { 00160 // Fast scrolling can lead to artifacts where extenders stay in the viewport 00161 // of the parent's scroll area even though their items are scrolled out. 00162 // Therefore we hide all extenders when scrolling. 00163 // In paintEvent() show() will be called on actually visible extenders and 00164 // Qt's double buffering takes care of eliminating flicker. 00165 // ### This scales badly to many extenders. There are probably better ways to 00166 // avoid the artifacts. 00167 extender->hide(); 00168 } 00169 } 00170 00171 00172 bool KExtendableItemDelegate::isExtended(const QModelIndex &index) const 00173 { 00174 return d->extenders.value(index); 00175 } 00176 00177 00178 QSize KExtendableItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 00179 { 00180 QSize ret; 00181 00182 if (!d->extenders.isEmpty()) { 00183 ret = d->maybeExtendedSize(option, index); 00184 } else { 00185 ret = QStyledItemDelegate::sizeHint(option, index); 00186 } 00187 00188 bool showExtensionIndicator = index.model() ? 00189 index.model()->data(index, ShowExtensionIndicatorRole).toBool() : false; 00190 if (showExtensionIndicator) { 00191 ret.rwidth() += d->extendPixmap.width(); 00192 } 00193 00194 return ret; 00195 } 00196 00197 00198 void KExtendableItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 00199 { 00200 int indicatorX = 0; 00201 int indicatorY = 0; 00202 00203 QStyleOptionViewItemV4 indicatorOption(option); 00204 initStyleOption(&indicatorOption, index); 00205 if (index.column() == 0) { 00206 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::Beginning; 00207 } else if (index.column() == index.model()->columnCount() - 1) { 00208 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::End; 00209 } else { 00210 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::Middle; 00211 } 00212 00213 QStyleOptionViewItemV4 itemOption(option); 00214 initStyleOption(&itemOption, index); 00215 if (index.column() == 0) { 00216 itemOption.viewItemPosition = QStyleOptionViewItemV4::Beginning; 00217 } else if (index.column() == index.model()->columnCount() - 1) { 00218 itemOption.viewItemPosition = QStyleOptionViewItemV4::End; 00219 } else { 00220 itemOption.viewItemPosition = QStyleOptionViewItemV4::Middle; 00221 } 00222 00223 const bool showExtensionIndicator = index.model()->data(index, ShowExtensionIndicatorRole).toBool(); 00224 00225 if (showExtensionIndicator) { 00226 if (QApplication::isRightToLeft()) { 00227 indicatorX = option.rect.right() - d->extendPixmap.width(); 00228 itemOption.rect.setRight(option.rect.right() - d->extendPixmap.width()); 00229 indicatorOption.rect.setLeft(option.rect.right() - d->extendPixmap.width()); 00230 } else { 00231 indicatorX = option.rect.left(); 00232 indicatorOption.rect.setRight(option.rect.left() + d->extendPixmap.width()); 00233 itemOption.rect.setLeft(option.rect.left() + d->extendPixmap.width()); 00234 } 00235 indicatorY = option.rect.top() + ((option.rect.height() - d->extendPixmap.height()) >> 1); 00236 } 00237 00238 //fast path 00239 if (d->extenders.isEmpty()) { 00240 QStyledItemDelegate::paint(painter, itemOption, index); 00241 if (showExtensionIndicator) { 00242 painter->save(); 00243 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, 00244 painter); 00245 painter->restore(); 00246 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap); 00247 } 00248 return; 00249 } 00250 00251 int row = index.row(); 00252 QModelIndex parentIndex = index.parent(); 00253 00254 //indexOfExtendedColumnInSameRow() is very expensive, try to avoid calling it. 00255 if (row != d->cachedRow || d->cachedStateTick != d->stateTick 00256 || d->cachedParentIndex != parentIndex) { 00257 d->extender = d->extenders.value(d->indexOfExtendedColumnInSameRow(index)); 00258 d->cachedStateTick = d->stateTick; 00259 d->cachedRow = row; 00260 d->cachedParentIndex = parentIndex; 00261 if (d->extender) { 00262 d->extenderHeight = d->extender->sizeHint().height(); 00263 } 00264 } 00265 00266 if (!d->extender) { 00267 QStyledItemDelegate::paint(painter, itemOption, index); 00268 if (showExtensionIndicator) { 00269 painter->save(); 00270 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, 00271 painter); 00272 painter->restore(); 00273 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap); 00274 } 00275 return; 00276 } 00277 00278 //an extender is present - make two rectangles: one to paint the original item, one for the extender 00279 if (isExtended(index)) { 00280 QStyleOptionViewItemV4 extOption(option); 00281 initStyleOption(&extOption, index); 00282 extOption.rect = extenderRect(d->extender, option, index); 00283 updateExtenderGeometry(d->extender, extOption, index); 00284 //if we show it before, it will briefly flash in the wrong location. 00285 //the downside is, of course, that an api user effectively can't hide it. 00286 d->extender->show(); 00287 } 00288 00289 indicatorOption.rect.setHeight(option.rect.height() - d->extenderHeight); 00290 itemOption.rect.setHeight(option.rect.height() - d->extenderHeight); 00291 //tricky:make sure that the modified options' rect really has the 00292 //same height as the unchanged option.rect if no extender is present 00293 //(seems to work OK) 00294 QStyledItemDelegate::paint(painter, itemOption, index); 00295 00296 if (showExtensionIndicator) { 00297 //indicatorOption's height changed, change this too 00298 indicatorY = indicatorOption.rect.top() + ((indicatorOption.rect.height() - 00299 d->extendPixmap.height()) >> 1); 00300 painter->save(); 00301 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption, 00302 painter); 00303 painter->restore(); 00304 00305 if (d->extenders.contains(index)) { 00306 painter->drawPixmap(indicatorX, indicatorY, d->contractPixmap); 00307 } else { 00308 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap); 00309 } 00310 } 00311 } 00312 00313 00314 QRect KExtendableItemDelegate::extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const 00315 { 00316 Q_ASSERT(extender); 00317 QRect rect(option.rect); 00318 rect.setTop(rect.bottom() + 1 - extender->sizeHint().height()); 00319 00320 int indentation = 0; 00321 if (QTreeView *tv = qobject_cast<QTreeView *>(parent())) { 00322 int indentSteps = 0; 00323 for (QModelIndex idx(index.parent()); idx.isValid(); idx = idx.parent()) { 00324 indentSteps++; 00325 } 00326 if (tv->rootIsDecorated()) { 00327 indentSteps++; 00328 } 00329 indentation = indentSteps * tv->indentation(); 00330 } 00331 00332 QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent()); 00333 Q_ASSERT(container); 00334 if (qApp->isLeftToRight()) { 00335 rect.setLeft(indentation); 00336 rect.setRight(container->viewport()->width() - 1); 00337 } else { 00338 rect.setRight(container->viewport()->width() - 1 - indentation); 00339 rect.setLeft(0); 00340 } 00341 return rect; 00342 } 00343 00344 00345 QSize KExtendableItemDelegate::Private::maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const 00346 { 00347 QWidget *extender = extenders.value(index); 00348 QSize size(q->QStyledItemDelegate::sizeHint(option, index)); 00349 if (!extender) { 00350 return size; 00351 } 00352 //add extender height to maximum height of any column in our row 00353 int itemHeight = size.height(); 00354 00355 int row = index.row(); 00356 int thisColumn = index.column(); 00357 00358 //this is quite slow, but Qt is smart about when to call sizeHint(). 00359 for (int column = 0; index.model()->columnCount() < column; column++) { 00360 if (column == thisColumn) { 00361 continue; 00362 } 00363 QModelIndex neighborIndex(index.sibling(row, column)); 00364 if (!neighborIndex.isValid()) { 00365 break; 00366 } 00367 itemHeight = qMax(itemHeight, q->QStyledItemDelegate::sizeHint(option, neighborIndex).height()); 00368 } 00369 00370 //we only want to reserve vertical space, the horizontal extender layout is our private business. 00371 size.rheight() = itemHeight + extender->sizeHint().height(); 00372 return size; 00373 } 00374 00375 00376 QModelIndex KExtendableItemDelegate::Private::indexOfExtendedColumnInSameRow(const QModelIndex &index) const 00377 { 00378 const QAbstractItemModel *const model = index.model(); 00379 const QModelIndex parentIndex(index.parent()); 00380 const int row = index.row(); 00381 const int columnCount = model->columnCount(); 00382 00383 //slow, slow, slow 00384 for (int column = 0; column < columnCount; column++) { 00385 QModelIndex indexOfExt(model->index(row, column, parentIndex)); 00386 if (extenders.value(indexOfExt)) { 00387 return indexOfExt; 00388 } 00389 } 00390 00391 return QModelIndex(); 00392 } 00393 00394 00395 void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, 00396 const QModelIndex &index) const 00397 { 00398 Q_UNUSED(index); 00399 extender->setGeometry(option.rect); 00400 } 00401 00402 00403 void KExtendableItemDelegate::Private::deleteExtenders() 00404 { 00405 foreach (QWidget *ext, extenders) { 00406 ext->hide(); 00407 ext->deleteLater(); 00408 } 00409 deletionQueue.unite(extenderIndices); 00410 extenders.clear(); 00411 extenderIndices.clear(); 00412 } 00413 00414 00415 //make the view re-ask for sizeHint() and redisplay items with their new size 00416 //### starting from Qt 4.4 we could emit sizeHintChanged() instead 00417 void KExtendableItemDelegate::Private::scheduleUpdateViewLayout() 00418 { 00419 QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(q->parent()); 00420 //prevent crashes during destruction of the view 00421 if (aiv) { 00422 //dirty hack to call aiv's protected scheduleDelayedItemsLayout() 00423 aiv->setRootIndex(aiv->rootIndex()); 00424 } 00425 } 00426 00427 00428 void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap) 00429 { 00430 d->extendPixmap = pixmap; 00431 } 00432 00433 00434 void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap) 00435 { 00436 d->contractPixmap = pixmap; 00437 } 00438 00439 00440 QPixmap KExtendableItemDelegate::extendPixmap() 00441 { 00442 return d->extendPixmap; 00443 } 00444 00445 00446 QPixmap KExtendableItemDelegate::contractPixmap() 00447 { 00448 return d->contractPixmap; 00449 } 00450 00451 #include "kextendableitemdelegate.moc"
KDE 4.7 API Reference