• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

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

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.6
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal