• Skip to content
  • Skip to link menu
KDE 4.3 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     {}
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     rect.setLeft(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         rect.setLeft(indentSteps * tv->indentation());
00325     }
00326 
00327     QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent());
00328     Q_ASSERT(container);
00329     rect.setRight(container->viewport()->width() - 1);
00330     return rect;
00331 }
00332 
00333 
00334 QSize KExtendableItemDelegate::Private::maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
00335 {
00336     QWidget *extender = extenders.value(index);
00337     QSize size(q->QStyledItemDelegate::sizeHint(option, index));
00338     if (!extender) {
00339         return size;
00340     }
00341     //add extender height to maximum height of any column in our row
00342     int itemHeight = size.height();
00343 
00344     int row = index.row();
00345     int thisColumn = index.column();
00346 
00347     //this is quite slow, but Qt is smart about when to call sizeHint().
00348     for (int column = 0; index.model()->columnCount() < column; column++) {
00349         if (column == thisColumn) {
00350             continue;
00351         }
00352         QModelIndex neighborIndex(index.sibling(row, column));
00353         if (!neighborIndex.isValid()) {
00354             break;
00355         }
00356         itemHeight = qMax(itemHeight, q->QStyledItemDelegate::sizeHint(option, neighborIndex).height());
00357     }
00358 
00359     //we only want to reserve vertical space, the horizontal extender layout is our private business.
00360     size.rheight() = itemHeight + extender->sizeHint().height();
00361     return size;
00362 }
00363 
00364 
00365 QModelIndex KExtendableItemDelegate::Private::indexOfExtendedColumnInSameRow(const QModelIndex &index) const
00366 {
00367     const QAbstractItemModel *const model = index.model();
00368     const QModelIndex parentIndex(index.parent());
00369     const int row = index.row();
00370     const int columnCount = model->columnCount();
00371 
00372     //slow, slow, slow
00373     for (int column = 0; column < columnCount; column++) {
00374         QModelIndex indexOfExt(model->index(row, column, parentIndex));
00375         if (extenders.value(indexOfExt)) {
00376             return indexOfExt;
00377         }
00378     }
00379 
00380     return QModelIndex();
00381 }
00382 
00383 
00384 void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option,
00385                                                      const QModelIndex &index) const
00386 {
00387     Q_UNUSED(index);
00388     extender->setGeometry(option.rect);
00389 }
00390 
00391 
00392 void KExtendableItemDelegate::Private::deleteExtenders()
00393 {
00394     foreach (QWidget *ext, extenders) {
00395         ext->hide();
00396         ext->deleteLater();
00397     }
00398     deletionQueue.unite(extenderIndices);
00399     extenders.clear();
00400     extenderIndices.clear();
00401 }
00402 
00403 
00404 //make the view re-ask for sizeHint() and redisplay items with their new size
00405 //### starting from Qt 4.4 we could emit sizeHintChanged() instead
00406 void KExtendableItemDelegate::Private::scheduleUpdateViewLayout()
00407 {
00408     QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(q->parent());
00409     //prevent crashes during destruction of the view
00410     if (aiv) {
00411         //dirty hack to call aiv's protected scheduleDelayedItemsLayout()
00412         aiv->setRootIndex(aiv->rootIndex());
00413     }
00414 }
00415 
00416 
00417 void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap)
00418 {
00419     d->extendPixmap = pixmap;
00420 }
00421 
00422 
00423 void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap)
00424 {
00425     d->contractPixmap = pixmap;
00426 }
00427 
00428 
00429 QPixmap KExtendableItemDelegate::extendPixmap()
00430 {
00431     return d->extendPixmap;
00432 }
00433 
00434 
00435 QPixmap KExtendableItemDelegate::contractPixmap()
00436 {
00437     return d->contractPixmap;
00438 }
00439 
00440 #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
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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