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