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

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