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