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 | |
22 | Q_DECLARE_METATYPE(QList<QEvent::Type>) |
23 | |
24 | /** |
25 | KWidgetItemDelegatePrivate class that helps to provide binary compatibility between releases. |
26 | @internal |
27 | */ |
28 | //@cond PRIVATE |
29 | KWidgetItemDelegatePrivate::KWidgetItemDelegatePrivate(KWidgetItemDelegate *q, QObject *parent) |
30 | : QObject(parent) |
31 | , widgetPool(new KWidgetItemDelegatePool(q)) |
32 | , q(q) |
33 | { |
34 | } |
35 | |
36 | KWidgetItemDelegatePrivate::~KWidgetItemDelegatePrivate() |
37 | { |
38 | if (!viewDestroyed) { |
39 | widgetPool->fullClear(); |
40 | } |
41 | delete widgetPool; |
42 | } |
43 | |
44 | void 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 | |
52 | void KWidgetItemDelegatePrivate::_k_slotRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
53 | { |
54 | updateRowRange(parent, start, end, isRemoving: true); |
55 | } |
56 | |
57 | void 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 | |
65 | void 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 | |
75 | void 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 | |
84 | void KWidgetItemDelegatePrivate::_k_slotModelReset() |
85 | { |
86 | widgetPool->fullClear(); |
87 | QTimer::singleShot(msec: 0, receiver: this, SLOT(initializeModel())); |
88 | } |
89 | |
90 | void 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 | |
102 | void 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 | |
126 | inline 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 | |
135 | void 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 | |
158 | KWidgetItemDelegate::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 | |
178 | KWidgetItemDelegate::~KWidgetItemDelegate() = default; |
179 | |
180 | QAbstractItemView *KWidgetItemDelegate::itemView() const |
181 | { |
182 | return d->itemView; |
183 | } |
184 | |
185 | QPersistentModelIndex 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 |
197 | bool 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 | |
268 | void KWidgetItemDelegate::setBlockedEventTypes(QWidget *widget, const QList<QEvent::Type> &types) const |
269 | { |
270 | widget->setProperty(name: "goya:blockedEventTypes" , value: QVariant::fromValue(value: types)); |
271 | } |
272 | |
273 | QList<QEvent::Type> KWidgetItemDelegate::blockedEventTypes(QWidget *widget) const |
274 | { |
275 | return widget->property(name: "goya:blockedEventTypes" ).value<QList<QEvent::Type>>(); |
276 | } |
277 | |
278 | void KWidgetItemDelegate::resetModel() |
279 | { |
280 | d->_k_slotModelReset(); |
281 | } |
282 | |
283 | #include "moc_kwidgetitemdelegate.cpp" |
284 | #include "moc_kwidgetitemdelegate_p.cpp" |
285 | |