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

KDEUI

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
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