1 /* This file is part of the KDE libraries
2 Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com)
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
21 #include "kextendableitemdelegate.h"
22 #include <QModelIndex>
23 #include <QPersistentModelIndex>
27 #include <QMouseEvent>
28 #include <QApplication>
32 //TODO:listen for removal of rows and columns to clean out dead persistent indexes/editors.
33 //(not needed ATM, the only user is KShortcutsEditor)
35 class KExtendableItemDelegatePrivate
{
37 //this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex
38 QHash
<QPersistentModelIndex
, QWidget
*> extenders
;
39 QHash
<QWidget
*, QPersistentModelIndex
> extenderIndices
;
42 //mostly for quick startup - don't look for extenders while the view
49 KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView
* parent
)
50 : QItemDelegate(parent
),
51 d(new KExtendableItemDelegatePrivate
)
53 d
->hasExtenders
= false;
55 //parent->installEventFilter(this); //not sure if this is good
59 KExtendableItemDelegate::~KExtendableItemDelegate()
65 void KExtendableItemDelegate::extendItem(QWidget
*ext
, const QModelIndex
&index
)
67 if (!ext
|| !index
.isValid())
69 //maintain the invariant "zero or one extender per row"
71 contractItem(indexOfExtendedColumnInSameRow(index
));
73 //reparent, as promised in the docs
74 QAbstractItemView
*aiv
= qobject_cast
<QAbstractItemView
*>(parent());
77 ext
->setParent(aiv
->viewport());
78 d
->extenders
.insert(index
, ext
);
79 d
->extenderIndices
.insert(ext
, index
);
80 d
->hasExtenders
= true;
81 connect(ext
, SIGNAL(destroyed(QObject
*)), this, SLOT(extenderDestructionHandler(QObject
*)));
82 emit
extenderCreated(ext
, index
);
83 scheduleUpdateViewLayout();
87 void KExtendableItemDelegate::contractItem(const QModelIndex
& index
)
89 QWidget
*extender
= d
->extenders
.value(index
);
94 extender
->deleteLater();
96 scheduleUpdateViewLayout();
101 void KExtendableItemDelegate::extenderDestructionHandler(QObject
*destroyed
)
103 QWidget
*extender
= static_cast<QWidget
*>(destroyed
);
106 if(d
->extenderIndices
.value(extender
).isValid()) {
107 if (receivers(SIGNAL(extenderDestroyed(QWidget
*, QModelIndex
)))) {
108 QPersistentModelIndex persistentIndex
= d
->extenderIndices
.take(extender
);
109 QModelIndex index
= persistentIndex
;
110 emit
extenderDestroyed(extender
, index
);
111 d
->extenders
.remove(persistentIndex
);
113 d
->extenders
.remove(d
->extenderIndices
.take(extender
));
115 if (d
->extenders
.isEmpty())
116 d
->hasExtenders
= false;
118 scheduleUpdateViewLayout();
123 bool KExtendableItemDelegate::isExtended(const QModelIndex
&index
) const {
124 return d
->extenders
.value(index
);
128 QSize
KExtendableItemDelegate::sizeHint(const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
133 ret
= maybeExtendedSize(option
, index
);
135 ret
= QItemDelegate::sizeHint(option
, index
);
137 bool showExtensionIndicator
= index
.model()->data(index
, ShowExtensionIndicatorRole
).toBool();
138 if (showExtensionIndicator
)
139 ret
.rwidth() += d
->extendIcon
.width();
145 void KExtendableItemDelegate::paint(QPainter
*painter
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
150 QStyleOptionViewItem
indicatorOption(option
);
151 QStyleOptionViewItem
itemOption(option
);
152 bool showExtensionIndicator
= index
.model()->data(index
, ShowExtensionIndicatorRole
).toBool();
154 if (showExtensionIndicator
) {
155 if (QApplication::isRightToLeft()) {
156 indicatorX
= option
.rect
.right() - d
->extendIcon
.width();
157 itemOption
.rect
.setRight(option
.rect
.right() - d
->extendIcon
.width());
158 indicatorOption
.rect
.setLeft(option
.rect
.right() - d
->extendIcon
.width());
161 indicatorX
= option
.rect
.left();
162 indicatorOption
.rect
.setRight(option
.rect
.left() + d
->extendIcon
.width());
163 itemOption
.rect
.setLeft(option
.rect
.left() + d
->extendIcon
.width());
165 indicatorY
= option
.rect
.top() + ((option
.rect
.height() - d
->extendIcon
.height()) >> 1);
169 if (!d
->hasExtenders
) {
170 QItemDelegate::paint(painter
, itemOption
, index
);
171 if (showExtensionIndicator
) {
172 QItemDelegate::drawBackground(painter
, indicatorOption
, index
);
173 painter
->drawPixmap(indicatorX
, indicatorY
, d
->extendIcon
);
178 //indexOfExtendedColumnInSameRow() is very expensive, try to avoid calling it.
179 static int cachedStateTick
= -1;
180 static int cachedRow
= -20; //Qt uses -1 for invalid indices
181 static QModelIndex cachedParentIndex
;
182 static QWidget
*extender
= 0;
183 static int extenderHeight
;
184 int row
= index
.row();
185 QModelIndex parentIndex
= index
.parent();
187 if (row
!= cachedRow
|| cachedStateTick
!= d
->stateTick
188 || cachedParentIndex
!= parentIndex
) {
189 extender
= d
->extenders
.value(indexOfExtendedColumnInSameRow(index
));
190 cachedStateTick
= d
->stateTick
;
192 cachedParentIndex
= parentIndex
;
194 extenderHeight
= extender
->sizeHint().height();
199 QItemDelegate::paint(painter
, itemOption
, index
);
200 if (showExtensionIndicator
) {
201 QItemDelegate::drawBackground(painter
, indicatorOption
, index
);
202 painter
->drawPixmap(indicatorX
, indicatorY
, d
->extendIcon
);
207 //an extender is present - make two rectangles: one to paint the original item, one for the extender
208 if (isExtended(index
)) {
209 QStyleOptionViewItem
extOption(option
);
210 extOption
.rect
= extenderRect(extender
, option
, index
);
211 updateExtenderGeometry(extender
, extOption
, index
);
212 //if we show it before, it will briefly flash in the wrong location.
213 //the downside is, of course, that an api user effectively can't hide it.
217 indicatorOption
.rect
.setHeight(option
.rect
.height() - extenderHeight
);
218 itemOption
.rect
.setHeight(option
.rect
.height() - extenderHeight
);
219 //tricky:make sure that the modified options' rect really has the
220 //same height as the unchanged option.rect if no extender is present
222 QItemDelegate::paint(painter
, itemOption
, index
);
224 if (showExtensionIndicator
) {
225 //indicatorOption's height changed, change this too
226 indicatorY
= indicatorOption
.rect
.top() + ((indicatorOption
.rect
.height() - d
->extendIcon
.height()) >> 1);
227 QItemDelegate::drawBackground(painter
, indicatorOption
, index
);
229 if (d
->extenders
.contains(index
))
230 painter
->drawPixmap(indicatorX
, indicatorY
, d
->contractIcon
);
232 painter
->drawPixmap(indicatorX
, indicatorY
, d
->extendIcon
);
237 QRect
KExtendableItemDelegate::extenderRect(QWidget
*extender
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
240 QRect
rect(option
.rect
);
241 rect
.setTop(rect
.bottom() + 1 - extender
->sizeHint().height());
244 QTreeView
*tv
= qobject_cast
<QTreeView
*>(parent());
246 for (QModelIndex
idx(index
.parent()); idx
.isValid(); idx
= idx
.parent())
247 rect
.translate(tv
->indentation(), 0);
249 QAbstractScrollArea
*container
= qobject_cast
<QAbstractScrollArea
*>(parent());
251 rect
.setRight(container
->viewport()->width() - 1);
256 QSize
KExtendableItemDelegate::maybeExtendedSize(const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
258 QWidget
*extender
= d
->extenders
.value(index
);
259 QSize
size(QItemDelegate::sizeHint(option
, index
));
263 //add extender height to maximum height of any column in our row
264 int itemHeight
= size
.height();
266 int row
= index
.row();
267 int thisColumn
= index
.column();
268 QModelIndex
parentIndex(index
.parent());
270 //this is quite slow, but Qt is smart about when to call sizeHint().
271 for (int column
=0;column
<6; column
++) {
272 if (column
== thisColumn
)
275 QModelIndex
neighborIndex(index
.sibling(row
, column
));
276 if (!neighborIndex
.isValid())
278 itemHeight
= qMax(itemHeight
, QItemDelegate::sizeHint(option
, neighborIndex
).height());
281 //we only want to reserve vertical space
282 size
.rheight() = itemHeight
+ extender
->sizeHint().height();
287 QModelIndex
KExtendableItemDelegate::indexOfExtendedColumnInSameRow(const QModelIndex
&index
) const
289 const QAbstractItemModel
*const model
= index
.model();
290 const QModelIndex
parentIndex(index
.parent());
291 const int row
= index
.row();
292 const int columnCount
= model
->columnCount();
295 for (int column
= 0; column
< columnCount
; column
++) {
296 QModelIndex
indexOfExt(model
->index(row
, column
, parentIndex
));
297 if (d
->extenders
.value(indexOfExt
))
301 return QModelIndex();
305 void KExtendableItemDelegate::updateExtenderGeometry(QWidget
*extender
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
308 extender
->setGeometry(option
.rect
);
312 //make the view re-ask for sizeHint() and redisplay items with their new size
313 void KExtendableItemDelegate::scheduleUpdateViewLayout() const
315 QAbstractItemView
*aiv
= qobject_cast
<QAbstractItemView
*>(parent());
316 //prevent crashes during destruction of the view
318 //dirty hack to call aiv's protected scheduleDelayedItemsLayout()
319 aiv
->setRootIndex(aiv
->rootIndex());
323 void KExtendableItemDelegate::setExtendIcon(const QPixmap
&icon
)
325 d
->extendIcon
= icon
;
329 void KExtendableItemDelegate::setContractIcon(const QPixmap
&icon
)
331 d
->contractIcon
= icon
;
335 QPixmap
KExtendableItemDelegate::extendIcon()
337 return d
->extendIcon
;
341 QPixmap
KExtendableItemDelegate::contractIcon()
343 return d
->contractIcon
;
346 #include "kextendableitemdelegate.moc"