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