1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2007-2008 Rafael Fernández López <ereslibre@kde.org>
4 SPDX-FileCopyrightText: 2008 Kevin Ottens <ervin@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "kwidgetitemdelegate.h"
10#include "kwidgetitemdelegate_p.h"
11
12#include <QAbstractItemView>
13#include <QApplication>
14#include <QCursor>
15#include <QPainter>
16#include <QStyleOption>
17#include <QTimer>
18#include <QTreeView>
19
20#include "kwidgetitemdelegatepool_p.h"
21
22Q_DECLARE_METATYPE(QList<QEvent::Type>)
23
24/**
25 KWidgetItemDelegatePrivate class that helps to provide binary compatibility between releases.
26 @internal
27*/
28//@cond PRIVATE
29KWidgetItemDelegatePrivate::KWidgetItemDelegatePrivate(KWidgetItemDelegate *q, QObject *parent)
30 : QObject(parent)
31 , widgetPool(new KWidgetItemDelegatePool(q))
32 , q(q)
33{
34}
35
36KWidgetItemDelegatePrivate::~KWidgetItemDelegatePrivate()
37{
38 if (!viewDestroyed) {
39 widgetPool->fullClear();
40 }
41 delete widgetPool;
42}
43
44void KWidgetItemDelegatePrivate::_k_slotRowsInserted(const QModelIndex &parent, int start, int end)
45{
46 Q_UNUSED(end);
47 // We need to update the rows behind the inserted row as well because the widgets need to be
48 // moved to their new position
49 updateRowRange(parent, start, end: model->rowCount(parent), isRemoving: false);
50}
51
52void KWidgetItemDelegatePrivate::_k_slotRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
53{
54 updateRowRange(parent, start, end, isRemoving: true);
55}
56
57void KWidgetItemDelegatePrivate::_k_slotRowsRemoved(const QModelIndex &parent, int start, int end)
58{
59 Q_UNUSED(end);
60 // We need to update the rows that come behind the deleted rows because the widgets need to be
61 // moved to the new position
62 updateRowRange(parent, start, end: model->rowCount(parent), isRemoving: false);
63}
64
65void KWidgetItemDelegatePrivate::_k_slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
66{
67 for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
68 for (int j = topLeft.column(); j <= bottomRight.column(); ++j) {
69 const QModelIndex index = model->index(row: i, column: j, parent: topLeft.parent());
70 widgetPool->findWidgets(index, option: optionView(index));
71 }
72 }
73}
74
75void KWidgetItemDelegatePrivate::_k_slotLayoutChanged()
76{
77 const auto lst = widgetPool->invalidIndexesWidgets();
78 for (QWidget *widget : lst) {
79 widget->setVisible(false);
80 }
81 QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel()));
82}
83
84void KWidgetItemDelegatePrivate::_k_slotModelReset()
85{
86 widgetPool->fullClear();
87 QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel()));
88}
89
90void KWidgetItemDelegatePrivate::_k_slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
91{
92 const auto lstSelected = selected.indexes();
93 for (const QModelIndex &index : lstSelected) {
94 widgetPool->findWidgets(index, option: optionView(index));
95 }
96 const auto lstDeselected = deselected.indexes();
97 for (const QModelIndex &index : lstDeselected) {
98 widgetPool->findWidgets(index, option: optionView(index));
99 }
100}
101
102void KWidgetItemDelegatePrivate::updateRowRange(const QModelIndex &parent, int start, int end, bool isRemoving)
103{
104 int i = start;
105 while (i <= end) {
106 for (int j = 0; j < model->columnCount(parent); ++j) {
107 const QModelIndex index = model->index(row: i, column: j, parent);
108 const QList<QWidget *> widgetList =
109 widgetPool->findWidgets(index,
110 option: optionView(index),
111 updateWidgets: isRemoving ? KWidgetItemDelegatePool::NotUpdateWidgets : KWidgetItemDelegatePool::UpdateWidgets);
112 if (isRemoving) {
113 widgetPool->d->allocatedWidgets.removeAll(t: widgetList);
114 for (QWidget *widget : widgetList) {
115 const QModelIndex idx = widgetPool->d->widgetInIndex[widget];
116 widgetPool->d->usedWidgets.remove(key: idx);
117 widgetPool->d->widgetInIndex.remove(key: widget);
118 delete widget;
119 }
120 }
121 }
122 i++;
123 }
124}
125
126inline QStyleOptionViewItem KWidgetItemDelegatePrivate::optionView(const QModelIndex &index)
127{
128 QStyleOptionViewItem optionView;
129 optionView.initFrom(w: itemView->viewport());
130 optionView.rect = itemView->visualRect(index);
131 optionView.decorationSize = itemView->iconSize();
132 return optionView;
133}
134
135void KWidgetItemDelegatePrivate::initializeModel(const QModelIndex &parent)
136{
137 if (!model) {
138 return;
139 }
140
141 for (int i = 0; i < model->rowCount(parent); ++i) {
142 for (int j = 0; j < model->columnCount(parent); ++j) {
143 const QModelIndex index = model->index(row: i, column: j, parent);
144 if (index.isValid()) {
145 widgetPool->findWidgets(index, option: optionView(index));
146 }
147 }
148 // Check if we need to go recursively through the children of parent (if any) to initialize
149 // all possible indexes that are shown.
150 const QModelIndex index = model->index(row: i, column: 0, parent);
151 if (index.isValid() && model->hasChildren(parent: index)) {
152 initializeModel(parent: index);
153 }
154 }
155}
156//@endcond
157
158KWidgetItemDelegate::KWidgetItemDelegate(QAbstractItemView *itemView, QObject *parent)
159 : QAbstractItemDelegate(parent)
160 , d(new KWidgetItemDelegatePrivate(this))
161{
162 Q_ASSERT(itemView);
163
164 itemView->setMouseTracking(true);
165 itemView->viewport()->setAttribute(Qt::WA_Hover);
166
167 d->itemView = itemView;
168
169 itemView->viewport()->installEventFilter(filterObj: d.get()); // mouse events
170 itemView->installEventFilter(filterObj: d.get()); // keyboard events
171
172 if (qobject_cast<QTreeView *>(object: itemView)) {
173 connect(sender: itemView, SIGNAL(collapsed(QModelIndex)), receiver: d.get(), SLOT(initializeModel()));
174 connect(sender: itemView, SIGNAL(expanded(QModelIndex)), receiver: d.get(), SLOT(initializeModel()));
175 }
176}
177
178KWidgetItemDelegate::~KWidgetItemDelegate() = default;
179
180QAbstractItemView *KWidgetItemDelegate::itemView() const
181{
182 return d->itemView;
183}
184
185QPersistentModelIndex KWidgetItemDelegate::focusedIndex() const
186{
187 const QPersistentModelIndex idx = d->widgetPool->d->widgetInIndex.value(key: QApplication::focusWidget());
188 if (idx.isValid()) {
189 return idx;
190 }
191 // Use the mouse position, if the widget refused to take keyboard focus.
192 const QPoint pos = d->itemView->viewport()->mapFromGlobal(QCursor::pos());
193 return d->itemView->indexAt(point: pos);
194}
195
196//@cond PRIVATE
197bool KWidgetItemDelegatePrivate::eventFilter(QObject *watched, QEvent *event)
198{
199 if (event->type() == QEvent::Destroy) {
200 // we care for the view since it deletes the widgets (parentage).
201 // if the view hasn't been deleted, it might be that just the
202 // delegate is removed from it, in which case we need to remove the widgets
203 // manually, otherwise they still get drawn.
204 if (watched == itemView) {
205 viewDestroyed = true;
206 }
207 return false;
208 }
209
210 Q_ASSERT(itemView);
211
212 // clang-format off
213 if (model != itemView->model()) {
214 if (model) {
215 disconnect(sender: model, SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
216 disconnect(sender: model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
217 disconnect(sender: model, SIGNAL(rowsRemoved(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
218 disconnect(sender: model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
219 disconnect(sender: model, SIGNAL(layoutChanged()), receiver: q, SLOT(_k_slotLayoutChanged()));
220 disconnect(sender: model, SIGNAL(modelReset()), receiver: q, SLOT(_k_slotModelReset()));
221 }
222 model = itemView->model();
223 connect(sender: model, SIGNAL(rowsInserted(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsInserted(QModelIndex,int,int)));
224 connect(sender: model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsAboutToBeRemoved(QModelIndex,int,int)));
225 connect(sender: model, SIGNAL(rowsRemoved(QModelIndex,int,int)), receiver: q, SLOT(_k_slotRowsRemoved(QModelIndex,int,int)));
226 connect(sender: model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), receiver: q, SLOT(_k_slotDataChanged(QModelIndex,QModelIndex)));
227 connect(sender: model, SIGNAL(layoutChanged()), receiver: q, SLOT(_k_slotLayoutChanged()));
228 connect(sender: model, SIGNAL(modelReset()), receiver: q, SLOT(_k_slotModelReset()));
229 QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel()));
230 }
231
232 if (selectionModel != itemView->selectionModel()) {
233 if (selectionModel) {
234 disconnect(sender: selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), receiver: q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
235 }
236 selectionModel = itemView->selectionModel();
237 connect(sender: selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)), receiver: q, SLOT(_k_slotSelectionChanged(QItemSelection,QItemSelection)));
238 QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel()));
239 }
240 // clang-format on
241
242 switch (event->type()) {
243 case QEvent::Polish:
244 case QEvent::Resize:
245 if (!qobject_cast<QAbstractItemView *>(object: watched)) {
246 QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel()));
247 }
248 break;
249 case QEvent::FocusIn:
250 case QEvent::FocusOut:
251 if (qobject_cast<QAbstractItemView *>(object: watched)) {
252 const auto lst = selectionModel->selectedIndexes();
253 for (const QModelIndex &index : lst) {
254 if (index.isValid()) {
255 widgetPool->findWidgets(index, option: optionView(index));
256 }
257 }
258 }
259 break;
260 default:
261 break;
262 }
263
264 return QObject::eventFilter(watched, event);
265}
266//@endcond
267
268void KWidgetItemDelegate::setBlockedEventTypes(QWidget *widget, const QList<QEvent::Type> &types) const
269{
270 widget->setProperty(name: "goya:blockedEventTypes", value: QVariant::fromValue(value: types));
271}
272
273QList<QEvent::Type> KWidgetItemDelegate::blockedEventTypes(QWidget *widget) const
274{
275 return widget->property(name: "goya:blockedEventTypes").value<QList<QEvent::Type>>();
276}
277
278void KWidgetItemDelegate::resetModel()
279{
280 d->_k_slotModelReset();
281}
282
283#include "moc_kwidgetitemdelegate.cpp"
284#include "moc_kwidgetitemdelegate_p.cpp"
285

source code of kitemviews/src/kwidgetitemdelegate.cpp