1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QABSTRACTITEMVIEW_P_H
5#define QABSTRACTITEMVIEW_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtWidgets/private/qtwidgetsglobal_p.h>
19#include "private/qabstractscrollarea_p.h"
20#include "private/qabstractitemmodel_p.h"
21#include "QtWidgets/qapplication.h"
22#include "QtGui/qevent.h"
23#include "QtCore/qmimedata.h"
24#include "QtGui/qpainter.h"
25#include "QtCore/qpair.h"
26#include "QtGui/qregion.h"
27#include "QtCore/qdebug.h"
28#include "QtCore/qbasictimer.h"
29#include "QtCore/qelapsedtimer.h"
30
31QT_REQUIRE_CONFIG(itemviews);
32
33QT_BEGIN_NAMESPACE
34
35struct QEditorInfo {
36 QEditorInfo(QWidget *e, bool s): widget(QPointer<QWidget>(e)), isStatic(s) {}
37 QEditorInfo(): isStatic(false) {}
38
39 QPointer<QWidget> widget;
40 bool isStatic;
41};
42
43// Fast associativity between Persistent editors and indices.
44typedef QHash<QWidget *, QPersistentModelIndex> QEditorIndexHash;
45typedef QHash<QPersistentModelIndex, QEditorInfo> QIndexEditorHash;
46
47struct QItemViewPaintPair {
48 QRect rect;
49 QModelIndex index;
50};
51template <>
52class QTypeInfo<QItemViewPaintPair> : public QTypeInfoMerger<QItemViewPaintPair, QRect, QModelIndex> {};
53
54typedef QList<QItemViewPaintPair> QItemViewPaintPairs;
55
56class Q_AUTOTEST_EXPORT QAbstractItemViewPrivate : public QAbstractScrollAreaPrivate
57{
58 Q_DECLARE_PUBLIC(QAbstractItemView)
59
60public:
61 QAbstractItemViewPrivate();
62 virtual ~QAbstractItemViewPrivate();
63
64 void init();
65
66 virtual void _q_rowsRemoved(const QModelIndex &parent, int start, int end);
67 virtual void _q_rowsInserted(const QModelIndex &parent, int start, int end);
68 virtual void _q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end);
69 virtual void _q_columnsRemoved(const QModelIndex &parent, int start, int end);
70 virtual void _q_columnsInserted(const QModelIndex &parent, int start, int end);
71 virtual void _q_modelDestroyed();
72 virtual void _q_layoutChanged();
73 virtual void _q_rowsMoved(const QModelIndex &source, int sourceStart, int sourceEnd, const QModelIndex &destination, int destinationStart);
74 virtual void _q_columnsMoved(const QModelIndex &source, int sourceStart, int sourceEnd, const QModelIndex &destination, int destinationStart);
75 virtual QRect intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const;
76
77 void _q_headerDataChanged() { doDelayedItemsLayout(); }
78 void _q_scrollerStateChanged();
79 void _q_delegateSizeHintChanged(const QModelIndex &index);
80
81 void fetchMore();
82
83 bool shouldEdit(QAbstractItemView::EditTrigger trigger, const QModelIndex &index) const;
84 bool shouldForwardEvent(QAbstractItemView::EditTrigger trigger, const QEvent *event) const;
85 bool shouldAutoScroll(const QPoint &pos) const;
86 void doDelayedItemsLayout(int delay = 0);
87 void interruptDelayedItemsLayout() const;
88
89 void updateGeometry();
90
91 void startAutoScroll()
92 { // ### it would be nice to make this into a style hint one day
93 int scrollInterval = (verticalScrollMode == QAbstractItemView::ScrollPerItem) ? 150 : 50;
94 autoScrollTimer.start(msec: scrollInterval, obj: q_func());
95 autoScrollCount = 0;
96 }
97 void stopAutoScroll() { autoScrollTimer.stop(); autoScrollCount = 0;}
98
99#if QT_CONFIG(draganddrop)
100 virtual bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index);
101#endif
102 bool droppingOnItself(QDropEvent *event, const QModelIndex &index);
103
104 QWidget *editor(const QModelIndex &index, const QStyleOptionViewItem &options);
105 bool sendDelegateEvent(const QModelIndex &index, QEvent *event) const;
106 bool openEditor(const QModelIndex &index, QEvent *event);
107 void updateEditorData(const QModelIndex &topLeft, const QModelIndex &bottomRight);
108 void selectAllInEditor(QWidget *w);
109
110 QItemSelectionModel::SelectionFlags multiSelectionCommand(const QModelIndex &index,
111 const QEvent *event) const;
112 QItemSelectionModel::SelectionFlags extendedSelectionCommand(const QModelIndex &index,
113 const QEvent *event) const;
114 QItemSelectionModel::SelectionFlags contiguousSelectionCommand(const QModelIndex &index,
115 const QEvent *event) const;
116 virtual void selectAll(QItemSelectionModel::SelectionFlags command);
117
118 void setHoverIndex(const QPersistentModelIndex &index);
119
120 void checkMouseMove(const QPersistentModelIndex &index);
121 inline void checkMouseMove(const QPoint &pos) { checkMouseMove(index: q_func()->indexAt(point: pos)); }
122
123 inline QItemSelectionModel::SelectionFlags selectionBehaviorFlags() const
124 {
125 switch (selectionBehavior) {
126 case QAbstractItemView::SelectRows: return QItemSelectionModel::Rows;
127 case QAbstractItemView::SelectColumns: return QItemSelectionModel::Columns;
128 case QAbstractItemView::SelectItems: default: return QItemSelectionModel::NoUpdate;
129 }
130 }
131
132#if QT_CONFIG(draganddrop)
133 virtual QAbstractItemView::DropIndicatorPosition position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const;
134
135 inline bool canDrop(QDropEvent *event) {
136 const QMimeData *mime = event->mimeData();
137
138 // Drag enter event shall always be accepted, if mime type and action match.
139 // Whether the data can actually be dropped will be checked in drag move.
140 if (event->type() == QEvent::DragEnter && (event->dropAction() & model->supportedDropActions())) {
141 const QStringList modelTypes = model->mimeTypes();
142 for (const auto &modelType : modelTypes) {
143 if (mime->hasFormat(mimetype: modelType))
144 return true;
145 }
146 }
147
148 QModelIndex index;
149 int col = -1;
150 int row = -1;
151 if (dropOn(event, row: &row, col: &col, index: &index)) {
152 return model->canDropMimeData(data: mime,
153 action: dragDropMode == QAbstractItemView::InternalMove ? Qt::MoveAction : event->dropAction(),
154 row, column: col, parent: index);
155 }
156 return false;
157 }
158
159 inline void paintDropIndicator(QPainter *painter)
160 {
161 if (showDropIndicator && state == QAbstractItemView::DraggingState
162#ifndef QT_NO_CURSOR
163 && viewport->cursor().shape() != Qt::ForbiddenCursor
164#endif
165 ) {
166 QStyleOption opt;
167 opt.initFrom(w: q_func());
168 opt.rect = dropIndicatorRect;
169 q_func()->style()->drawPrimitive(pe: QStyle::PE_IndicatorItemViewItemDrop, opt: &opt, p: painter, w: q_func());
170 }
171 }
172
173#endif
174 virtual QItemViewPaintPairs draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const;
175 // reimplemented in subclasses
176 virtual void adjustViewOptionsForIndex(QStyleOptionViewItem*, const QModelIndex&) const {}
177
178 inline void releaseEditor(QWidget *editor, const QModelIndex &index = QModelIndex()) const {
179 if (editor) {
180 Q_Q(const QAbstractItemView);
181 QObject::disconnect(sender: editor, SIGNAL(destroyed(QObject*)),
182 receiver: q_func(), SLOT(editorDestroyed(QObject*)));
183 editor->removeEventFilter(obj: itemDelegate);
184 editor->hide();
185 QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index);
186
187 if (delegate)
188 delegate->destroyEditor(editor, index);
189 else
190 editor->deleteLater();
191 }
192 }
193
194 inline void executePostedLayout() const {
195 if (delayedPendingLayout && state != QAbstractItemView::CollapsingState) {
196 interruptDelayedItemsLayout();
197 const_cast<QAbstractItemView*>(q_func())->doItemsLayout();
198 }
199 }
200
201 inline void setDirtyRegion(const QRegion &visualRegion) {
202 updateRegion += visualRegion;
203 if (!updateTimer.isActive())
204 updateTimer.start(msec: 0, obj: q_func());
205 }
206
207 inline void scrollDirtyRegion(int dx, int dy) {
208 scrollDelayOffset = QPoint(-dx, -dy);
209 updateDirtyRegion();
210 scrollDelayOffset = QPoint(0, 0);
211 }
212
213 inline void scrollContentsBy(int dx, int dy) {
214 scrollDirtyRegion(dx, dy);
215 viewport->scroll(dx, dy);
216 }
217
218 void updateDirtyRegion() {
219 updateTimer.stop();
220 viewport->update(updateRegion);
221 updateRegion = QRegion();
222 }
223
224 void clearOrRemove();
225 void checkPersistentEditorFocus();
226
227 QPixmap renderToPixmap(const QModelIndexList &indexes, QRect *r) const;
228
229 inline QPoint offset() const {
230 const Q_Q(QAbstractItemView);
231 return QPoint(q->isRightToLeft() ? -q->horizontalOffset()
232 : q->horizontalOffset(), q->verticalOffset());
233 }
234
235 const QEditorInfo &editorForIndex(const QModelIndex &index) const;
236 bool hasEditor(const QModelIndex &index) const;
237
238 QModelIndex indexForEditor(QWidget *editor) const;
239 void addEditor(const QModelIndex &index, QWidget *editor, bool isStatic);
240 void removeEditor(QWidget *editor);
241
242 inline bool isAnimating() const {
243 return state == QAbstractItemView::AnimatingState;
244 }
245
246 inline bool isIndexValid(const QModelIndex &index) const {
247 return (index.row() >= 0) && (index.column() >= 0) && (index.model() == model);
248 }
249 inline bool isIndexSelectable(const QModelIndex &index) const {
250 return (model->flags(index) & Qt::ItemIsSelectable);
251 }
252 inline bool isIndexEnabled(const QModelIndex &index) const {
253 return (model->flags(index) & Qt::ItemIsEnabled);
254 }
255#if QT_CONFIG(draganddrop)
256 inline bool isIndexDropEnabled(const QModelIndex &index) const {
257 return (model->flags(index) & Qt::ItemIsDropEnabled);
258 }
259 inline bool isIndexDragEnabled(const QModelIndex &index) const {
260 return (model->flags(index) & Qt::ItemIsDragEnabled);
261 }
262#endif
263
264 virtual bool selectionAllowed(const QModelIndex &index) const {
265 // in some views we want to go ahead with selections, even if the index is invalid
266 return isIndexValid(index) && isIndexSelectable(index);
267 }
268
269 // reimplemented from QAbstractScrollAreaPrivate
270 QPoint contentsOffset() const override {
271 Q_Q(const QAbstractItemView);
272 return QPoint(q->horizontalOffset(), q->verticalOffset());
273 }
274
275 /**
276 * For now, assume that we have few editors, if we need a more efficient implementation
277 * we should add a QMap<QAbstractItemDelegate*, int> member.
278 */
279 int delegateRefCount(const QAbstractItemDelegate *delegate) const
280 {
281 int ref = 0;
282 if (itemDelegate == delegate)
283 ++ref;
284
285 for (int maps = 0; maps < 2; ++maps) {
286 const QMap<int, QPointer<QAbstractItemDelegate> > *delegates = maps ? &columnDelegates : &rowDelegates;
287 for (QMap<int, QPointer<QAbstractItemDelegate> >::const_iterator it = delegates->begin();
288 it != delegates->end(); ++it) {
289 if (it.value() == delegate) {
290 ++ref;
291 // optimization, we are only interested in the ref count values 0, 1 or >=2
292 if (ref >= 2) {
293 return ref;
294 }
295 }
296 }
297 }
298 return ref;
299 }
300
301 /**
302 * return true if the index is registered as a QPersistentModelIndex
303 */
304 inline bool isPersistent(const QModelIndex &index) const
305 {
306 return static_cast<QAbstractItemModelPrivate *>(model->d_ptr.data())->persistent.indexes.contains(key: index);
307 }
308
309#if QT_CONFIG(draganddrop)
310 QModelIndexList selectedDraggableIndexes() const;
311#endif
312
313 void doDelayedReset()
314 {
315 //we delay the reset of the timer because some views (QTableView)
316 //with headers can't handle the fact that the model has been destroyed
317 //all _q_modelDestroyed slots must have been called
318 if (!delayedReset.isActive())
319 delayedReset.start(msec: 0, obj: q_func());
320 }
321
322 QAbstractItemModel *model;
323 QPointer<QAbstractItemDelegate> itemDelegate;
324 QMap<int, QPointer<QAbstractItemDelegate> > rowDelegates;
325 QMap<int, QPointer<QAbstractItemDelegate> > columnDelegates;
326 QPointer<QItemSelectionModel> selectionModel;
327 QItemSelectionModel::SelectionFlag ctrlDragSelectionFlag;
328 bool noSelectionOnMousePress;
329
330 QAbstractItemView::SelectionMode selectionMode;
331 QAbstractItemView::SelectionBehavior selectionBehavior;
332
333 QEditorIndexHash editorIndexHash;
334 QIndexEditorHash indexEditorHash;
335 QSet<QWidget*> persistent;
336 QWidget *currentlyCommittingEditor;
337 QBasicTimer pressClosedEditorWatcher;
338 QPersistentModelIndex lastEditedIndex;
339 bool pressClosedEditor;
340 bool waitForIMCommit;
341
342 QPersistentModelIndex enteredIndex;
343 QPersistentModelIndex pressedIndex;
344 QPersistentModelIndex currentSelectionStartIndex;
345 Qt::KeyboardModifiers pressedModifiers;
346 QPoint pressedPosition;
347 QPoint draggedPosition;
348 bool pressedAlreadySelected;
349 bool releaseFromDoubleClick;
350
351 //forces the next mouseMoveEvent to send the viewportEntered signal
352 //if the mouse is over the viewport and not over an item
353 bool viewportEnteredNeeded;
354
355 QAbstractItemView::State state;
356 QAbstractItemView::State stateBeforeAnimation;
357 QAbstractItemView::EditTriggers editTriggers;
358 QAbstractItemView::EditTrigger lastTrigger;
359
360 QPersistentModelIndex root;
361 QPersistentModelIndex hover;
362
363 bool tabKeyNavigation;
364
365#if QT_CONFIG(draganddrop)
366 bool showDropIndicator;
367 QRect dropIndicatorRect;
368 bool dragEnabled;
369 QAbstractItemView::DragDropMode dragDropMode;
370 bool overwrite;
371 bool dropEventMoved;
372 QAbstractItemView::DropIndicatorPosition dropIndicatorPosition;
373 Qt::DropAction defaultDropAction;
374#endif
375
376 QString keyboardInput;
377 QElapsedTimer keyboardInputTime;
378
379 bool autoScroll;
380 QBasicTimer autoScrollTimer;
381 int autoScrollMargin;
382 int autoScrollCount;
383 bool shouldScrollToCurrentOnShow; //used to know if we should scroll to current on show event
384 bool shouldClearStatusTip; //if there is a statustip currently shown that need to be cleared when leaving.
385
386 bool alternatingColors;
387
388 QSize iconSize;
389 Qt::TextElideMode textElideMode;
390
391 QRegion updateRegion; // used for the internal update system
392 QPoint scrollDelayOffset;
393
394 QBasicTimer updateTimer;
395 QBasicTimer delayedEditing;
396 QBasicTimer delayedAutoScroll; //used when an item is clicked
397 QBasicTimer delayedReset;
398
399 QAbstractItemView::ScrollMode verticalScrollMode;
400 QAbstractItemView::ScrollMode horizontalScrollMode;
401
402#ifndef QT_NO_GESTURES
403 // the selection before the last mouse down. In case we have to restore it for scrolling
404 QItemSelection oldSelection;
405 QModelIndex oldCurrent;
406#endif
407
408 bool currentIndexSet;
409
410 bool wrapItemText;
411 mutable bool delayedPendingLayout;
412 bool moveCursorUpdatedView;
413
414 // Whether scroll mode has been explicitly set or its value come from SH_ItemView_ScrollMode
415 bool verticalScrollModeSet;
416 bool horizontalScrollModeSet;
417
418 virtual QRect visualRect(const QModelIndex &index) const { return q_func()->visualRect(index); }
419
420private:
421 inline QAbstractItemDelegate *delegateForIndex(const QModelIndex &index) const {
422 QMap<int, QPointer<QAbstractItemDelegate> >::ConstIterator it;
423
424 it = rowDelegates.find(key: index.row());
425 if (it != rowDelegates.end())
426 return it.value();
427
428 it = columnDelegates.find(key: index.column());
429 if (it != columnDelegates.end())
430 return it.value();
431
432 return itemDelegate;
433 }
434
435 mutable QBasicTimer delayedLayout;
436 mutable QBasicTimer fetchMoreTimer;
437};
438
439QT_BEGIN_INCLUDE_NAMESPACE
440#include <qlist.h>
441QT_END_INCLUDE_NAMESPACE
442
443template<typename T>
444inline int qBinarySearch(const QList<T> &vec, const T &item, int start, int end)
445{
446 int i = (start + end + 1) >> 1;
447 while (end - start > 0) {
448 if (vec.at(i) > item)
449 end = i - 1;
450 else
451 start = i;
452 i = (start + end + 1) >> 1;
453 }
454 return i;
455}
456
457QT_END_NAMESPACE
458
459#endif // QABSTRACTITEMVIEW_P_H
460

source code of qtbase/src/widgets/itemviews/qabstractitemview_p.h