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

source code of kitemviews/src/kwidgetitemdelegate.cpp