• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KDEUI

  • kdeui
  • itemviews
kextendableitemdelegate.cpp
Go to the documentation of this file.
1/* This file is part of the KDE libraries
2 Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com)
3 Copyright (C) 2008 Urs Wolfer (uwolfer @ kde.org)
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "kextendableitemdelegate.h"
22
23#include <QModelIndex>
24#include <QScrollBar>
25#include <QTreeView>
26#include <QPainter>
27#include <QApplication>
28
29
30class KExtendableItemDelegate::Private {
31public:
32 Private(KExtendableItemDelegate *parent) :
33 q(parent),
34 stateTick(0),
35 cachedStateTick(-1),
36 cachedRow(-20), //Qt uses -1 for invalid indices
37 extender(0),
38 extenderHeight(0)
39
40 {}
41
42 void _k_extenderDestructionHandler(QObject *destroyed);
43 void _k_verticalScroll();
44
45 QSize maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const;
46 QModelIndex indexOfExtendedColumnInSameRow(const QModelIndex &index) const;
47 void scheduleUpdateViewLayout();
48
49 KExtendableItemDelegate *q;
50
54 void deleteExtenders();
55
56 //this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex
57 QHash<QPersistentModelIndex, QWidget *> extenders;
58 QHash<QWidget *, QPersistentModelIndex> extenderIndices;
59 QHash<QWidget *, QPersistentModelIndex> deletionQueue;
60 QPixmap extendPixmap;
61 QPixmap contractPixmap;
62 int stateTick;
63 int cachedStateTick;
64 int cachedRow;
65 QModelIndex cachedParentIndex;
66 QWidget *extender;
67 int extenderHeight;
68};
69
70
71KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView* parent)
72 : QStyledItemDelegate(parent),
73 d(new Private(this))
74{
75 connect(parent->verticalScrollBar(), SIGNAL(valueChanged(int)),
76 this, SLOT(_k_verticalScroll()));
77}
78
79
80KExtendableItemDelegate::~KExtendableItemDelegate()
81{
82 delete d;
83}
84
85
86void KExtendableItemDelegate::extendItem(QWidget *ext, const QModelIndex &index)
87{
88 // kDebug() << "Creating extender at " << ext << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
89
90 if (!ext || !index.isValid()) {
91 return;
92 }
93 //maintain the invariant "zero or one extender per row"
94 d->stateTick++;
95 contractItem(d->indexOfExtendedColumnInSameRow(index));
96 d->stateTick++;
97 //reparent, as promised in the docs
98 QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(parent());
99 if (!aiv) {
100 return;
101 }
102 ext->setParent(aiv->viewport());
103 d->extenders.insert(index, ext);
104 d->extenderIndices.insert(ext, index);
105 connect(ext, SIGNAL(destroyed(QObject*)), this, SLOT(_k_extenderDestructionHandler(QObject*)));
106 emit extenderCreated(ext, index);
107 d->scheduleUpdateViewLayout();
108}
109
110
111void KExtendableItemDelegate::contractItem(const QModelIndex& index)
112{
113 QWidget *extender = d->extenders.value(index);
114 if (!extender) {
115 return;
116 }
117 // kDebug() << "Collapse extender at " << extender << " for item " << index.model()->data(index,Qt::DisplayRole).toString();
118 extender->hide();
119 extender->deleteLater();
120
121 QPersistentModelIndex persistentIndex = d->extenderIndices.take(extender);
122 d->extenders.remove(persistentIndex);
123
124 d->deletionQueue.insert(extender, persistentIndex);
125
126 d->scheduleUpdateViewLayout();
127}
128
129
130void KExtendableItemDelegate::contractAll()
131{
132 d->deleteExtenders();
133}
134
135
136//slot
137void KExtendableItemDelegate::Private::_k_extenderDestructionHandler(QObject *destroyed)
138{
139 // kDebug() << "Removing extender at " << destroyed;
140
141 QWidget *extender = static_cast<QWidget *>(destroyed);
142 stateTick++;
143
144 QPersistentModelIndex persistentIndex = deletionQueue.take(extender);
145 if (persistentIndex.isValid() &&
146 q->receivers(SIGNAL(extenderDestroyed(QWidget*,QModelIndex)))) {
147
148 QModelIndex index = persistentIndex;
149 emit q->extenderDestroyed(extender, index);
150 }
151
152 scheduleUpdateViewLayout();
153}
154
155
156//slot
157void KExtendableItemDelegate::Private::_k_verticalScroll()
158{
159 foreach (QWidget *extender, extenders) {
160 // Fast scrolling can lead to artifacts where extenders stay in the viewport
161 // of the parent's scroll area even though their items are scrolled out.
162 // Therefore we hide all extenders when scrolling.
163 // In paintEvent() show() will be called on actually visible extenders and
164 // Qt's double buffering takes care of eliminating flicker.
165 // ### This scales badly to many extenders. There are probably better ways to
166 // avoid the artifacts.
167 extender->hide();
168 }
169}
170
171
172bool KExtendableItemDelegate::isExtended(const QModelIndex &index) const
173{
174 return d->extenders.value(index);
175}
176
177
178QSize KExtendableItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
179{
180 QSize ret;
181
182 if (!d->extenders.isEmpty()) {
183 ret = d->maybeExtendedSize(option, index);
184 } else {
185 ret = QStyledItemDelegate::sizeHint(option, index);
186 }
187
188 bool showExtensionIndicator = index.model() ?
189 index.model()->data(index, ShowExtensionIndicatorRole).toBool() : false;
190 if (showExtensionIndicator) {
191 ret.rwidth() += d->extendPixmap.width();
192 }
193
194 return ret;
195}
196
197
198void KExtendableItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
199{
200 int indicatorX = 0;
201 int indicatorY = 0;
202
203 QStyleOptionViewItemV4 indicatorOption(option);
204 initStyleOption(&indicatorOption, index);
205 if (index.column() == 0) {
206 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::Beginning;
207 } else if (index.column() == index.model()->columnCount() - 1) {
208 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::End;
209 } else {
210 indicatorOption.viewItemPosition = QStyleOptionViewItemV4::Middle;
211 }
212
213 QStyleOptionViewItemV4 itemOption(option);
214 initStyleOption(&itemOption, index);
215 if (index.column() == 0) {
216 itemOption.viewItemPosition = QStyleOptionViewItemV4::Beginning;
217 } else if (index.column() == index.model()->columnCount() - 1) {
218 itemOption.viewItemPosition = QStyleOptionViewItemV4::End;
219 } else {
220 itemOption.viewItemPosition = QStyleOptionViewItemV4::Middle;
221 }
222
223 const bool showExtensionIndicator = index.model()->data(index, ShowExtensionIndicatorRole).toBool();
224
225 if (showExtensionIndicator) {
226 if (QApplication::isRightToLeft()) {
227 indicatorX = option.rect.right() - d->extendPixmap.width();
228 itemOption.rect.setRight(option.rect.right() - d->extendPixmap.width());
229 indicatorOption.rect.setLeft(option.rect.right() - d->extendPixmap.width());
230 } else {
231 indicatorX = option.rect.left();
232 indicatorOption.rect.setRight(option.rect.left() + d->extendPixmap.width());
233 itemOption.rect.setLeft(option.rect.left() + d->extendPixmap.width());
234 }
235 indicatorY = option.rect.top() + ((option.rect.height() - d->extendPixmap.height()) >> 1);
236 }
237
238 //fast path
239 if (d->extenders.isEmpty()) {
240 QStyledItemDelegate::paint(painter, itemOption, index);
241 if (showExtensionIndicator) {
242 painter->save();
243 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption,
244 painter);
245 painter->restore();
246 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
247 }
248 return;
249 }
250
251 int row = index.row();
252 QModelIndex parentIndex = index.parent();
253
254 //indexOfExtendedColumnInSameRow() is very expensive, try to avoid calling it.
255 if (row != d->cachedRow || d->cachedStateTick != d->stateTick
256 || d->cachedParentIndex != parentIndex) {
257 d->extender = d->extenders.value(d->indexOfExtendedColumnInSameRow(index));
258 d->cachedStateTick = d->stateTick;
259 d->cachedRow = row;
260 d->cachedParentIndex = parentIndex;
261 if (d->extender) {
262 d->extenderHeight = d->extender->sizeHint().height();
263 }
264 }
265
266 if (!d->extender) {
267 QStyledItemDelegate::paint(painter, itemOption, index);
268 if (showExtensionIndicator) {
269 painter->save();
270 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption,
271 painter);
272 painter->restore();
273 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
274 }
275 return;
276 }
277
278 //an extender is present - make two rectangles: one to paint the original item, one for the extender
279 if (isExtended(index)) {
280 QStyleOptionViewItemV4 extOption(option);
281 initStyleOption(&extOption, index);
282 extOption.rect = extenderRect(d->extender, option, index);
283 updateExtenderGeometry(d->extender, extOption, index);
284 //if we show it before, it will briefly flash in the wrong location.
285 //the downside is, of course, that an api user effectively can't hide it.
286 d->extender->show();
287 }
288
289 indicatorOption.rect.setHeight(option.rect.height() - d->extenderHeight);
290 itemOption.rect.setHeight(option.rect.height() - d->extenderHeight);
291 //tricky:make sure that the modified options' rect really has the
292 //same height as the unchanged option.rect if no extender is present
293 //(seems to work OK)
294 QStyledItemDelegate::paint(painter, itemOption, index);
295
296 if (showExtensionIndicator) {
297 //indicatorOption's height changed, change this too
298 indicatorY = indicatorOption.rect.top() + ((indicatorOption.rect.height() -
299 d->extendPixmap.height()) >> 1);
300 painter->save();
301 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &indicatorOption,
302 painter);
303 painter->restore();
304
305 if (d->extenders.contains(index)) {
306 painter->drawPixmap(indicatorX, indicatorY, d->contractPixmap);
307 } else {
308 painter->drawPixmap(indicatorX, indicatorY, d->extendPixmap);
309 }
310 }
311}
312
313
314QRect KExtendableItemDelegate::extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
315{
316 Q_ASSERT(extender);
317 QRect rect(option.rect);
318 rect.setTop(rect.bottom() + 1 - extender->sizeHint().height());
319
320 int indentation = 0;
321 if (QTreeView *tv = qobject_cast<QTreeView *>(parent())) {
322 int indentSteps = 0;
323 for (QModelIndex idx(index.parent()); idx.isValid(); idx = idx.parent()) {
324 indentSteps++;
325 }
326 if (tv->rootIsDecorated()) {
327 indentSteps++;
328 }
329 indentation = indentSteps * tv->indentation();
330 }
331
332 QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent());
333 Q_ASSERT(container);
334 if (qApp->isLeftToRight()) {
335 rect.setLeft(indentation);
336 rect.setRight(container->viewport()->width() - 1);
337 } else {
338 rect.setRight(container->viewport()->width() - 1 - indentation);
339 rect.setLeft(0);
340 }
341 return rect;
342}
343
344
345QSize KExtendableItemDelegate::Private::maybeExtendedSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
346{
347 QWidget *extender = extenders.value(index);
348 QSize size(q->QStyledItemDelegate::sizeHint(option, index));
349 if (!extender) {
350 return size;
351 }
352 //add extender height to maximum height of any column in our row
353 int itemHeight = size.height();
354
355 int row = index.row();
356 int thisColumn = index.column();
357
358 //this is quite slow, but Qt is smart about when to call sizeHint().
359 for (int column = 0; index.model()->columnCount() < column; column++) {
360 if (column == thisColumn) {
361 continue;
362 }
363 QModelIndex neighborIndex(index.sibling(row, column));
364 if (!neighborIndex.isValid()) {
365 break;
366 }
367 itemHeight = qMax(itemHeight, q->QStyledItemDelegate::sizeHint(option, neighborIndex).height());
368 }
369
370 //we only want to reserve vertical space, the horizontal extender layout is our private business.
371 size.rheight() = itemHeight + extender->sizeHint().height();
372 return size;
373}
374
375
376QModelIndex KExtendableItemDelegate::Private::indexOfExtendedColumnInSameRow(const QModelIndex &index) const
377{
378 const QAbstractItemModel *const model = index.model();
379 const QModelIndex parentIndex(index.parent());
380 const int row = index.row();
381 const int columnCount = model->columnCount();
382
383 //slow, slow, slow
384 for (int column = 0; column < columnCount; column++) {
385 QModelIndex indexOfExt(model->index(row, column, parentIndex));
386 if (extenders.value(indexOfExt)) {
387 return indexOfExt;
388 }
389 }
390
391 return QModelIndex();
392}
393
394
395void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option,
396 const QModelIndex &index) const
397{
398 Q_UNUSED(index);
399 extender->setGeometry(option.rect);
400}
401
402
403void KExtendableItemDelegate::Private::deleteExtenders()
404{
405 foreach (QWidget *ext, extenders) {
406 ext->hide();
407 ext->deleteLater();
408 }
409 deletionQueue.unite(extenderIndices);
410 extenders.clear();
411 extenderIndices.clear();
412}
413
414
415//make the view re-ask for sizeHint() and redisplay items with their new size
416//### starting from Qt 4.4 we could emit sizeHintChanged() instead
417void KExtendableItemDelegate::Private::scheduleUpdateViewLayout()
418{
419 QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(q->parent());
420 //prevent crashes during destruction of the view
421 if (aiv) {
422 //dirty hack to call aiv's protected scheduleDelayedItemsLayout()
423 aiv->setRootIndex(aiv->rootIndex());
424 }
425}
426
427
428void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap)
429{
430 d->extendPixmap = pixmap;
431}
432
433
434void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap)
435{
436 d->contractPixmap = pixmap;
437}
438
439
440QPixmap KExtendableItemDelegate::extendPixmap()
441{
442 return d->extendPixmap;
443}
444
445
446QPixmap KExtendableItemDelegate::contractPixmap()
447{
448 return d->contractPixmap;
449}
450
451#include "kextendableitemdelegate.moc"
KExtendableItemDelegate
Definition: kextendableitemdelegate.h:51
KExtendableItemDelegate::contractPixmap
QPixmap contractPixmap()
Return the pixmap that is displayed to contract an item.
Definition: kextendableitemdelegate.cpp:446
KExtendableItemDelegate::contractItem
void contractItem(const QModelIndex &index)
Remove the extender of item at index from the view.
Definition: kextendableitemdelegate.cpp:111
KExtendableItemDelegate::contractAll
void contractAll()
Close all extenders and delete all extender widgets.
Definition: kextendableitemdelegate.cpp:130
KExtendableItemDelegate::extendPixmap
QPixmap extendPixmap()
Return the pixmap that is displayed to extend an item.
Definition: kextendableitemdelegate.cpp:440
KExtendableItemDelegate::setExtendPixmap
void setExtendPixmap(const QPixmap &pixmap)
The pixmap that is displayed to extend an item.
Definition: kextendableitemdelegate.cpp:428
KExtendableItemDelegate::setContractPixmap
void setContractPixmap(const QPixmap &pixmap)
The pixmap that is displayed to contract an item.
Definition: kextendableitemdelegate.cpp:434
KExtendableItemDelegate::~KExtendableItemDelegate
virtual ~KExtendableItemDelegate()
Definition: kextendableitemdelegate.cpp:80
KExtendableItemDelegate::extenderRect
QRect extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
Reimplement this function to fine-tune the position of the extender.
Definition: kextendableitemdelegate.cpp:314
KExtendableItemDelegate::isExtended
bool isExtended(const QModelIndex &index) const
Return whether there is an extender that belongs to index.
Definition: kextendableitemdelegate.cpp:172
KExtendableItemDelegate::KExtendableItemDelegate
KExtendableItemDelegate(QAbstractItemView *parent)
Create a new KExtendableItemDelegate that belongs to parent.
Definition: kextendableitemdelegate.cpp:71
KExtendableItemDelegate::updateExtenderGeometry
virtual void updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
Reimplement this function to adjust the internal geometry of the extender.
Definition: kextendableitemdelegate.cpp:395
KExtendableItemDelegate::sizeHint
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
Re-implemented for internal reasons.
Definition: kextendableitemdelegate.cpp:178
KExtendableItemDelegate::extendItem
void extendItem(QWidget *extender, const QModelIndex &index)
Insert the extender for item at index into the view.
Definition: kextendableitemdelegate.cpp:86
KExtendableItemDelegate::paint
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
Re-implemented for internal reasons.
Definition: kextendableitemdelegate.cpp:198
KExtendableItemDelegate::ShowExtensionIndicatorRole
@ ShowExtensionIndicatorRole
Definition: kextendableitemdelegate.h:55
KExtendableItemDelegate::extenderCreated
void extenderCreated(QWidget *extender, const QModelIndex &index)
This signal indicates that the item at index was extended with extender.
QAbstractItemModel
QHash
QObject
QWidget
kextendableitemdelegate.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

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-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal