Relicense all GPLv2 only code to GPLv2+.
[kdenetwork.git] / kget / ui / kextendableitemdelegate.cpp
blobe3ba96b2e100f8d636fa6dfd21e3936548da219d
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>
24 #include <QTreeView>
25 #include <QPainter>
26 #include <QEvent>
27 #include <QMouseEvent>
28 #include <QApplication>
30 #include <kdebug.h>
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 {
36 public:
37 //this will trigger a lot of auto-casting QModelIndex <-> QPersistentModelIndex
38 QHash<QPersistentModelIndex, QWidget *> extenders;
39 QHash<QWidget *, QPersistentModelIndex> extenderIndices;
40 QPixmap extendIcon;
41 QPixmap contractIcon;
42 //mostly for quick startup - don't look for extenders while the view
43 //is being populated.
44 int stateTick;
45 bool hasExtenders;
49 KExtendableItemDelegate::KExtendableItemDelegate(QAbstractItemView* parent)
50 : QItemDelegate(parent),
51 d(new KExtendableItemDelegatePrivate)
53 d->hasExtenders = false;
54 d->stateTick = 0;
55 //parent->installEventFilter(this); //not sure if this is good
59 KExtendableItemDelegate::~KExtendableItemDelegate()
61 delete d;
65 void KExtendableItemDelegate::extendItem(QWidget *ext, const QModelIndex &index)
67 if (!ext || !index.isValid())
68 return;
69 //maintain the invariant "zero or one extender per row"
70 d->stateTick++;
71 contractItem(indexOfExtendedColumnInSameRow(index));
72 d->stateTick++;
73 //reparent, as promised in the docs
74 QAbstractItemView *aiv = qobject_cast<QAbstractItemView *>(parent());
75 if (!aiv)
76 return;
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);
90 if (!extender)
91 return;
93 extender->hide();
94 extender->deleteLater();
96 scheduleUpdateViewLayout();
100 //slot
101 void KExtendableItemDelegate::extenderDestructionHandler(QObject *destroyed)
103 QWidget *extender = static_cast<QWidget *>(destroyed);
104 d->stateTick++;
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);
112 } else
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
130 QSize ret;
132 if (d->hasExtenders)
133 ret = maybeExtendedSize(option, index);
134 else
135 ret = QItemDelegate::sizeHint(option, index);
137 bool showExtensionIndicator = index.model()->data(index, ShowExtensionIndicatorRole).toBool();
138 if (showExtensionIndicator)
139 ret.rwidth() += d->extendIcon.width();
141 return ret;
145 void KExtendableItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
147 int indicatorX = 0;
148 int indicatorY = 0;
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());
160 else {
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);
168 //fast path
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);
175 return;
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;
191 cachedRow = row;
192 cachedParentIndex = parentIndex;
193 if (extender) {
194 extenderHeight = extender->sizeHint().height();
198 if (!extender) {
199 QItemDelegate::paint(painter, itemOption, index);
200 if (showExtensionIndicator) {
201 QItemDelegate::drawBackground(painter, indicatorOption, index);
202 painter->drawPixmap(indicatorX, indicatorY, d->extendIcon);
204 return;
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.
214 extender->show();
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
221 //(seems to work OK)
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);
231 else
232 painter->drawPixmap(indicatorX, indicatorY, d->extendIcon);
237 QRect KExtendableItemDelegate::extenderRect(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
239 Q_ASSERT(extender);
240 QRect rect(option.rect);
241 rect.setTop(rect.bottom() + 1 - extender->sizeHint().height());
243 rect.setLeft(0);
244 QTreeView *tv = qobject_cast<QTreeView *>(parent());
245 if (tv)
246 for (QModelIndex idx(index.parent()); idx.isValid(); idx = idx.parent())
247 rect.translate(tv->indentation(), 0);
249 QAbstractScrollArea *container = qobject_cast<QAbstractScrollArea *>(parent());
250 Q_ASSERT(container);
251 rect.setRight(container->viewport()->width() - 1);
252 return rect;
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));
260 if (!extender)
261 return size;
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)
273 continue;
275 QModelIndex neighborIndex(index.sibling(row, column));
276 if (!neighborIndex.isValid())
277 break;
278 itemHeight = qMax(itemHeight, QItemDelegate::sizeHint(option, neighborIndex).height());
281 //we only want to reserve vertical space
282 size.rheight() = itemHeight + extender->sizeHint().height();
283 return size;
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();
294 //slow, slow, slow
295 for (int column = 0; column < columnCount; column++) {
296 QModelIndex indexOfExt(model->index(row, column, parentIndex));
297 if (d->extenders.value(indexOfExt))
298 return indexOfExt;
301 return QModelIndex();
305 void KExtendableItemDelegate::updateExtenderGeometry(QWidget *extender, const QStyleOptionViewItem &option, const QModelIndex &index) const
307 Q_UNUSED(index);
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
317 if (aiv)
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"