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

KDEUI

  • sources
  • kde-4.14
  • kdelibs
  • 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 
30 class KExtendableItemDelegate::Private {
31 public:
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 
71 KExtendableItemDelegate::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 
80 KExtendableItemDelegate::~KExtendableItemDelegate()
81 {
82  delete d;
83 }
84 
85 
86 void 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 
111 void 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 
130 void KExtendableItemDelegate::contractAll()
131 {
132  d->deleteExtenders();
133 }
134 
135 
136 //slot
137 void 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
157 void 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 
172 bool KExtendableItemDelegate::isExtended(const QModelIndex &index) const
173 {
174  return d->extenders.value(index);
175 }
176 
177 
178 QSize 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 
198 void 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 
314 QRect 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 
345 QSize 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 
376 QModelIndex 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 
395 void 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 
403 void 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
417 void 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 
428 void KExtendableItemDelegate::setExtendPixmap(const QPixmap &pixmap)
429 {
430  d->extendPixmap = pixmap;
431 }
432 
433 
434 void KExtendableItemDelegate::setContractPixmap(const QPixmap &pixmap)
435 {
436  d->contractPixmap = pixmap;
437 }
438 
439 
440 QPixmap KExtendableItemDelegate::extendPixmap()
441 {
442  return d->extendPixmap;
443 }
444 
445 
446 QPixmap KExtendableItemDelegate::contractPixmap()
447 {
448  return d->contractPixmap;
449 }
450 
451 #include "kextendableitemdelegate.moc"
QAbstractItemView::setRootIndex
virtual void setRootIndex(const QModelIndex &index)
QModelIndex
KExtendableItemDelegate::extendPixmap
QPixmap extendPixmap()
Return the pixmap that is displayed to extend an item.
Definition: kextendableitemdelegate.cpp:440
QWidget
QApplication::isRightToLeft
bool isRightToLeft()
QAbstractItemModel::index
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const =0
kextendableitemdelegate.h
QAbstractItemView
QStyledItemDelegate::initStyleOption
virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const
QAbstractScrollArea
KExtendableItemDelegate::contractItem
void contractItem(const QModelIndex &index)
Remove the extender of item at index from the view.
Definition: kextendableitemdelegate.cpp:111
QSize::rwidth
int & rwidth()
KExtendableItemDelegate::paint
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
Re-implemented for internal reasons.
Definition: kextendableitemdelegate.cpp:198
QPainter::save
void save()
KExtendableItemDelegate
Definition: kextendableitemdelegate.h:51
QAbstractScrollArea::viewport
QWidget * viewport() const
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
QWidget::setParent
void setParent(QWidget *parent)
QWidget::setGeometry
void setGeometry(int x, int y, int w, int h)
KExtendableItemDelegate::isExtended
bool isExtended(const QModelIndex &index) const
Return whether there is an extender that belongs to index.
Definition: kextendableitemdelegate.cpp:172
QWidget::width
width
KExtendableItemDelegate::~KExtendableItemDelegate
virtual ~KExtendableItemDelegate()
Definition: kextendableitemdelegate.cpp:80
QRect
QModelIndex::isValid
bool isValid() const
QPersistentModelIndex::isValid
bool isValid() const
KStandardShortcut::End
Definition: kstandardshortcut.h:69
QHash< QPersistentModelIndex, QWidget * >
QStyleOptionViewItem
QObject
QStyledItemDelegate::paint
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
QRect::setTop
void setTop(int y)
QPainter::drawPixmap
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QPainter
QModelIndex::row
int row() const
KExtendableItemDelegate::contractAll
void contractAll()
Close all extenders and delete all extender widgets.
Definition: kextendableitemdelegate.cpp:130
QStyledItemDelegate::sizeHint
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
QAbstractItemModel::data
virtual QVariant data(const QModelIndex &index, int role) const =0
QObject::deleteLater
void deleteLater()
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
QWidget::hide
void hide()
QModelIndex::parent
QModelIndex parent() const
QAbstractScrollArea::verticalScrollBar
QScrollBar * verticalScrollBar() const
KExtendableItemDelegate::setExtendPixmap
void setExtendPixmap(const QPixmap &pixmap)
The pixmap that is displayed to extend an item.
Definition: kextendableitemdelegate.cpp:428
QPixmap
QSize
QWidget::sizeHint
sizeHint
KExtendableItemDelegate::extendItem
void extendItem(QWidget *extender, const QModelIndex &index)
Insert the extender for item at index into the view.
Definition: kextendableitemdelegate.cpp:86
QAbstractItemView::rootIndex
QModelIndex rootIndex() const
QPainter::restore
void restore()
KExtendableItemDelegate::contractPixmap
QPixmap contractPixmap()
Return the pixmap that is displayed to contract an item.
Definition: kextendableitemdelegate.cpp:446
QRect::setRight
void setRight(int x)
QPersistentModelIndex
QModelIndex::model
const QAbstractItemModel * model() const
KExtendableItemDelegate::KExtendableItemDelegate
KExtendableItemDelegate(QAbstractItemView *parent)
Create a new KExtendableItemDelegate that belongs to parent.
Definition: kextendableitemdelegate.cpp:71
KExtendableItemDelegate::setContractPixmap
void setContractPixmap(const QPixmap &pixmap)
The pixmap that is displayed to contract an item.
Definition: kextendableitemdelegate.cpp:434
QApplication::style
QStyle * style()
QTreeView
QModelIndex::sibling
QModelIndex sibling(int row, int column) const
QAbstractItemModel::columnCount
virtual int columnCount(const QModelIndex &parent) const =0
QSize::height
int height() const
QRect::bottom
int bottom() const
QModelIndex::column
int column() const
QVariant::toBool
bool toBool() const
QStyle::drawPrimitive
virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const =0
QAbstractItemModel
QStyleOptionViewItemV4
KExtendableItemDelegate::extenderCreated
void extenderCreated(QWidget *extender, const QModelIndex &index)
This signal indicates that the item at index was extended with extender.
KExtendableItemDelegate::ShowExtensionIndicatorRole
Definition: kextendableitemdelegate.h:55
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject::parent
QObject * parent() const
KExtendableItemDelegate::sizeHint
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
Re-implemented for internal reasons.
Definition: kextendableitemdelegate.cpp:178
QRect::setLeft
void setLeft(int x)
QObject::destroyed
void destroyed(QObject *obj)
QStyledItemDelegate
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:23:59 by doxygen 1.8.7 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 API Reference

Skip menu "kdelibs 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

Search



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