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 | #include "qabstractitemview.h" |
5 | |
6 | #include <qpointer.h> |
7 | #include <qapplication.h> |
8 | #include <qclipboard.h> |
9 | #include <qpainter.h> |
10 | #include <qstyle.h> |
11 | #if QT_CONFIG(draganddrop) |
12 | #include <qdrag.h> |
13 | #endif |
14 | #include <qevent.h> |
15 | #include <qscrollbar.h> |
16 | #if QT_CONFIG(tooltip) |
17 | #include <qtooltip.h> |
18 | #endif |
19 | #include <qdatetime.h> |
20 | #if QT_CONFIG(lineedit) |
21 | #include <qlineedit.h> |
22 | #endif |
23 | #if QT_CONFIG(spinbox) |
24 | #include <qspinbox.h> |
25 | #endif |
26 | #include <qheaderview.h> |
27 | #include <qstyleditemdelegate.h> |
28 | #include <private/qabstractitemview_p.h> |
29 | #include <private/qabstractitemmodel_p.h> |
30 | #include <private/qapplication_p.h> |
31 | #include <private/qguiapplication_p.h> |
32 | #include <private/qscrollbar_p.h> |
33 | #if QT_CONFIG(accessibility) |
34 | #include <qaccessible.h> |
35 | #endif |
36 | #if QT_CONFIG(gestures) && QT_CONFIG(scroller) |
37 | # include <qscroller.h> |
38 | #endif |
39 | |
40 | #include <algorithm> |
41 | |
42 | QT_BEGIN_NAMESPACE |
43 | |
44 | QAbstractItemViewPrivate::QAbstractItemViewPrivate() |
45 | : model(QAbstractItemModelPrivate::staticEmptyModel()), |
46 | itemDelegate(nullptr), |
47 | selectionModel(nullptr), |
48 | ctrlDragSelectionFlag(QItemSelectionModel::NoUpdate), |
49 | noSelectionOnMousePress(false), |
50 | selectionMode(QAbstractItemView::ExtendedSelection), |
51 | selectionBehavior(QAbstractItemView::SelectItems), |
52 | currentlyCommittingEditor(nullptr), |
53 | pressClosedEditor(false), |
54 | waitForIMCommit(false), |
55 | pressedModifiers(Qt::NoModifier), |
56 | pressedPosition(QPoint(-1, -1)), |
57 | pressedAlreadySelected(false), |
58 | releaseFromDoubleClick(false), |
59 | viewportEnteredNeeded(false), |
60 | state(QAbstractItemView::NoState), |
61 | stateBeforeAnimation(QAbstractItemView::NoState), |
62 | editTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed), |
63 | lastTrigger(QAbstractItemView::NoEditTriggers), |
64 | tabKeyNavigation(false), |
65 | #if QT_CONFIG(draganddrop) |
66 | showDropIndicator(true), |
67 | dragEnabled(false), |
68 | dragDropMode(QAbstractItemView::NoDragDrop), |
69 | overwrite(false), |
70 | dropEventMoved(false), |
71 | dropIndicatorPosition(QAbstractItemView::OnItem), |
72 | defaultDropAction(Qt::IgnoreAction), |
73 | #endif |
74 | autoScroll(true), |
75 | autoScrollMargin(16), |
76 | autoScrollCount(0), |
77 | shouldScrollToCurrentOnShow(false), |
78 | shouldClearStatusTip(false), |
79 | alternatingColors(false), |
80 | textElideMode(Qt::ElideRight), |
81 | verticalScrollMode(QAbstractItemView::ScrollPerItem), |
82 | horizontalScrollMode(QAbstractItemView::ScrollPerItem), |
83 | currentIndexSet(false), |
84 | wrapItemText(false), |
85 | delayedPendingLayout(true), |
86 | moveCursorUpdatedView(false), |
87 | verticalScrollModeSet(false), |
88 | horizontalScrollModeSet(false) |
89 | { |
90 | keyboardInputTime.invalidate(); |
91 | } |
92 | |
93 | QAbstractItemViewPrivate::~QAbstractItemViewPrivate() |
94 | { |
95 | } |
96 | |
97 | void QAbstractItemViewPrivate::init() |
98 | { |
99 | Q_Q(QAbstractItemView); |
100 | q->setItemDelegate(new QStyledItemDelegate(q)); |
101 | |
102 | vbar->setRange(min: 0, max: 0); |
103 | hbar->setRange(min: 0, max: 0); |
104 | |
105 | scrollbarConnections = { |
106 | QObject::connect(sender: vbar, signal: &QScrollBar::actionTriggered, |
107 | context: q, slot: &QAbstractItemView::verticalScrollbarAction), |
108 | QObject::connect(sender: hbar, signal: &QScrollBar::actionTriggered, |
109 | context: q, slot: &QAbstractItemView::horizontalScrollbarAction), |
110 | QObject::connect(sender: vbar, signal: &QScrollBar::valueChanged, |
111 | context: q, slot: &QAbstractItemView::verticalScrollbarValueChanged), |
112 | QObject::connect(sender: hbar, signal: &QScrollBar::valueChanged, |
113 | context: q, slot: &QAbstractItemView::horizontalScrollbarValueChanged) |
114 | }; |
115 | viewport->setBackgroundRole(QPalette::Base); |
116 | |
117 | q->setAttribute(Qt::WA_InputMethodEnabled); |
118 | |
119 | verticalScrollMode = static_cast<QAbstractItemView::ScrollMode>(q->style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: q, returnData: nullptr)); |
120 | horizontalScrollMode = static_cast<QAbstractItemView::ScrollMode>(q->style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: q, returnData: nullptr)); |
121 | } |
122 | |
123 | void QAbstractItemViewPrivate::setHoverIndex(const QPersistentModelIndex &index) |
124 | { |
125 | Q_Q(QAbstractItemView); |
126 | if (hover == index) |
127 | return; |
128 | |
129 | if (selectionBehavior != QAbstractItemView::SelectRows) { |
130 | q->update(index: hover); //update the old one |
131 | q->update(index); //update the new one |
132 | } else { |
133 | const QRect oldHoverRect = visualRect(index: hover); |
134 | const QRect newHoverRect = visualRect(index); |
135 | viewport->update(QRect(0, newHoverRect.y(), viewport->width(), newHoverRect.height())); |
136 | viewport->update(QRect(0, oldHoverRect.y(), viewport->width(), oldHoverRect.height())); |
137 | } |
138 | hover = index; |
139 | } |
140 | |
141 | void QAbstractItemViewPrivate::checkMouseMove(const QPersistentModelIndex &index) |
142 | { |
143 | //we take a persistent model index because the model might change by emitting signals |
144 | Q_Q(QAbstractItemView); |
145 | setHoverIndex(index); |
146 | if (viewportEnteredNeeded || enteredIndex != index) { |
147 | viewportEnteredNeeded = false; |
148 | |
149 | if (index.isValid()) { |
150 | emit q->entered(index); |
151 | #if QT_CONFIG(statustip) |
152 | QString statustip = model->data(index, role: Qt::StatusTipRole).toString(); |
153 | if (parent && (shouldClearStatusTip || !statustip.isEmpty())) { |
154 | QStatusTipEvent tip(statustip); |
155 | QCoreApplication::sendEvent(receiver: parent, event: &tip); |
156 | shouldClearStatusTip = !statustip.isEmpty(); |
157 | } |
158 | #endif |
159 | } else { |
160 | #if QT_CONFIG(statustip) |
161 | if (parent && shouldClearStatusTip) { |
162 | QString emptyString; |
163 | QStatusTipEvent tip( emptyString ); |
164 | QCoreApplication::sendEvent(receiver: parent, event: &tip); |
165 | } |
166 | #endif |
167 | emit q->viewportEntered(); |
168 | } |
169 | enteredIndex = index; |
170 | } |
171 | } |
172 | |
173 | #if QT_CONFIG(gestures) && QT_CONFIG(scroller) |
174 | |
175 | // stores and restores the selection and current item when flicking |
176 | void QAbstractItemViewPrivate::scrollerStateChanged() |
177 | { |
178 | Q_Q(QAbstractItemView); |
179 | |
180 | if (QScroller *scroller = QScroller::scroller(target: viewport)) { |
181 | switch (scroller->state()) { |
182 | case QScroller::Pressed: |
183 | // store the current selection in case we start scrolling |
184 | if (q->selectionModel()) { |
185 | oldSelection = q->selectionModel()->selection(); |
186 | oldCurrent = q->selectionModel()->currentIndex(); |
187 | } |
188 | break; |
189 | |
190 | case QScroller::Dragging: |
191 | // restore the old selection if we really start scrolling |
192 | if (q->selectionModel()) { |
193 | q->selectionModel()->select(selection: oldSelection, command: QItemSelectionModel::ClearAndSelect); |
194 | // block autoScroll logic while we are already handling scrolling |
195 | const bool wasAutoScroll = autoScroll; |
196 | autoScroll = false; |
197 | q->selectionModel()->setCurrentIndex(index: oldCurrent, command: QItemSelectionModel::NoUpdate); |
198 | autoScroll = wasAutoScroll; |
199 | } |
200 | Q_FALLTHROUGH(); |
201 | |
202 | default: |
203 | oldSelection = QItemSelection(); |
204 | oldCurrent = QModelIndex(); |
205 | break; |
206 | } |
207 | } |
208 | } |
209 | |
210 | #endif // QT_NO_GESTURES |
211 | |
212 | void QAbstractItemViewPrivate::delegateSizeHintChanged(const QModelIndex &index) |
213 | { |
214 | Q_Q(QAbstractItemView); |
215 | if (model) { |
216 | if (!model->checkIndex(index)) |
217 | qWarning(msg: "Delegate size hint changed for a model index that does not belong to this view"); |
218 | } |
219 | QMetaObject::invokeMethod(object: q, function: &QAbstractItemView::doItemsLayout, type: Qt::QueuedConnection); |
220 | } |
221 | |
222 | void QAbstractItemViewPrivate::connectDelegate(QAbstractItemDelegate *delegate) |
223 | { |
224 | if (!delegate) |
225 | return; |
226 | Q_Q(QAbstractItemView); |
227 | QObject::connect(sender: delegate, signal: &QAbstractItemDelegate::closeEditor, |
228 | context: q, slot: &QAbstractItemView::closeEditor); |
229 | QObject::connect(sender: delegate, signal: &QAbstractItemDelegate::commitData, |
230 | context: q, slot: &QAbstractItemView::commitData); |
231 | QObjectPrivate::connect(sender: delegate, signal: &QAbstractItemDelegate::sizeHintChanged, |
232 | receiverPrivate: this, slot: &QAbstractItemViewPrivate::delegateSizeHintChanged); |
233 | } |
234 | |
235 | void QAbstractItemViewPrivate::disconnectDelegate(QAbstractItemDelegate *delegate) |
236 | { |
237 | if (!delegate) |
238 | return; |
239 | Q_Q(QAbstractItemView); |
240 | QObject::disconnect(sender: delegate, signal: &QAbstractItemDelegate::closeEditor, |
241 | receiver: q, slot: &QAbstractItemView::closeEditor); |
242 | QObject::disconnect(sender: delegate, signal: &QAbstractItemDelegate::commitData, |
243 | receiver: q, slot: &QAbstractItemView::commitData); |
244 | QObjectPrivate::disconnect(sender: delegate, signal: &QAbstractItemDelegate::sizeHintChanged, |
245 | receiverPrivate: this, slot: &QAbstractItemViewPrivate::delegateSizeHintChanged); |
246 | } |
247 | |
248 | void QAbstractItemViewPrivate::disconnectAll() |
249 | { |
250 | Q_Q(QAbstractItemView); |
251 | for (const QMetaObject::Connection &connection : modelConnections) |
252 | QObject::disconnect(connection); |
253 | for (const QMetaObject::Connection &connection : scrollbarConnections) |
254 | QObject::disconnect(connection); |
255 | disconnectDelegate(delegate: itemDelegate); |
256 | for (QAbstractItemDelegate *delegate : std::as_const(t&: rowDelegates)) |
257 | disconnectDelegate(delegate); |
258 | for (QAbstractItemDelegate *delegate : std::as_const(t&: columnDelegates)) |
259 | disconnectDelegate(delegate); |
260 | if (model && selectionModel) { |
261 | QObject::disconnect(sender: model, signal: &QAbstractItemModel::destroyed, |
262 | receiver: selectionModel, slot: &QItemSelectionModel::deleteLater); |
263 | } |
264 | if (selectionModel) { |
265 | QObject::disconnect(sender: selectionModel, signal: &QItemSelectionModel::selectionChanged, |
266 | receiver: q, slot: &QAbstractItemView::selectionChanged); |
267 | QObject::disconnect(sender: selectionModel, signal: &QItemSelectionModel::currentChanged, |
268 | receiver: q, slot: &QAbstractItemView::currentChanged); |
269 | } |
270 | for (const auto &info : std::as_const(t&: indexEditorHash)) { |
271 | if (!info.isStatic && info.widget) |
272 | QObject::disconnect(sender: info.widget, signal: &QWidget::destroyed, receiver: q, slot: &QAbstractItemView::editorDestroyed); |
273 | } |
274 | #if QT_CONFIG(gestures) && QT_CONFIG(scroller) |
275 | QObject::disconnect(scollerConnection); |
276 | #endif |
277 | } |
278 | |
279 | /*! |
280 | \class QAbstractItemView |
281 | |
282 | \brief The QAbstractItemView class provides the basic functionality for |
283 | item view classes. |
284 | |
285 | \ingroup model-view |
286 | \inmodule QtWidgets |
287 | |
288 | QAbstractItemView class is the base class for every standard view |
289 | that uses a QAbstractItemModel. QAbstractItemView is an abstract |
290 | class and cannot itself be instantiated. It provides a standard |
291 | interface for interoperating with models through the signals and |
292 | slots mechanism, enabling subclasses to be kept up-to-date with |
293 | changes to their models. This class provides standard support for |
294 | keyboard and mouse navigation, viewport scrolling, item editing, |
295 | and selections. The keyboard navigation implements this |
296 | functionality: |
297 | |
298 | \table |
299 | \header |
300 | \li Keys |
301 | \li Functionality |
302 | \row |
303 | \li Arrow keys |
304 | \li Changes the current item and selects it. |
305 | \row |
306 | \li Ctrl+Arrow keys |
307 | \li Changes the current item but does not select it. |
308 | \row |
309 | \li Shift+Arrow keys |
310 | \li Changes the current item and selects it. The previously |
311 | selected item(s) is not deselected. |
312 | \row |
313 | \li Ctrl+Space |
314 | \li Toggles selection of the current item. |
315 | \row |
316 | \li Tab/Backtab |
317 | \li Changes the current item to the next/previous item. |
318 | \row |
319 | \li Home/End |
320 | \li Selects the first/last item in the model. |
321 | \row |
322 | \li Page up/Page down |
323 | \li Scrolls the rows shown up/down by the number of |
324 | visible rows in the view. |
325 | \row |
326 | \li Ctrl+A |
327 | \li Selects all items in the model. |
328 | \endtable |
329 | |
330 | Note that the above table assumes that the |
331 | \l{selectionMode}{selection mode} allows the operations. For |
332 | instance, you cannot select items if the selection mode is |
333 | QAbstractItemView::NoSelection. |
334 | |
335 | The QAbstractItemView class is one of the \l{Model/View Classes} |
336 | and is part of Qt's \l{Model/View Programming}{model/view framework}. |
337 | |
338 | The view classes that inherit QAbstractItemView only need |
339 | to implement their own view-specific functionality, such as |
340 | drawing items, returning the geometry of items, finding items, |
341 | etc. |
342 | |
343 | QAbstractItemView provides common slots such as edit() and |
344 | setCurrentIndex(). Many protected slots are also provided, including |
345 | dataChanged(), rowsInserted(), rowsAboutToBeRemoved(), selectionChanged(), |
346 | and currentChanged(). |
347 | |
348 | The root item is returned by rootIndex(), and the current item by |
349 | currentIndex(). To make sure that an item is visible use |
350 | scrollTo(). |
351 | |
352 | Some of QAbstractItemView's functions are concerned with |
353 | scrolling, for example setHorizontalScrollMode() and |
354 | setVerticalScrollMode(). To set the range of the scroll bars, you |
355 | can, for example, reimplement the view's resizeEvent() function: |
356 | |
357 | \snippet code/src_gui_itemviews_qabstractitemview.cpp 0 |
358 | |
359 | Note that the range is not updated until the widget is shown. |
360 | |
361 | Several other functions are concerned with selection control; for |
362 | example setSelectionMode(), and setSelectionBehavior(). This class |
363 | provides a default selection model to work with |
364 | (selectionModel()), but this can be replaced by using |
365 | setSelectionModel() with an instance of QItemSelectionModel. |
366 | |
367 | For complete control over the display and editing of items you can |
368 | specify a delegate with setItemDelegate(). |
369 | |
370 | QAbstractItemView provides a lot of protected functions. Some are |
371 | concerned with editing, for example, edit(), and commitData(), |
372 | whilst others are keyboard and mouse event handlers. |
373 | |
374 | \note If you inherit QAbstractItemView and intend to update the contents |
375 | of the viewport, you should use viewport->update() instead of |
376 | \l{QWidget::update()}{update()} as all painting operations take place on the |
377 | viewport. |
378 | |
379 | \sa {View Classes}, {Model/View Programming}, QAbstractItemModel |
380 | */ |
381 | |
382 | /*! |
383 | \enum QAbstractItemView::SelectionMode |
384 | |
385 | This enum indicates how the view responds to user selections: |
386 | |
387 | \value SingleSelection When the user selects an item, any already-selected |
388 | item becomes unselected. It is possible for the user to deselect the selected |
389 | item by pressing the Ctrl key when clicking the selected item. |
390 | |
391 | \value ContiguousSelection When the user selects an item in the usual way, |
392 | the selection is cleared and the new item selected. However, if the user |
393 | presses the Shift key while clicking on an item, all items between the |
394 | current item and the clicked item are selected or unselected, depending on |
395 | the state of the clicked item. |
396 | |
397 | \value ExtendedSelection When the user selects an item in the usual way, |
398 | the selection is cleared and the new item selected. However, if the user |
399 | presses the Ctrl key when clicking on an item, the clicked item gets |
400 | toggled and all other items are left untouched. If the user presses the |
401 | Shift key while clicking on an item, all items between the current item |
402 | and the clicked item are selected or unselected, depending on the state of |
403 | the clicked item. Multiple items can be selected by dragging the mouse over |
404 | them. |
405 | |
406 | \value MultiSelection When the user selects an item in the usual way, the |
407 | selection status of that item is toggled and the other items are left |
408 | alone. Multiple items can be toggled by dragging the mouse over them. |
409 | |
410 | \value NoSelection Items cannot be selected. |
411 | |
412 | The most commonly used modes are SingleSelection and ExtendedSelection. |
413 | */ |
414 | |
415 | /*! |
416 | \enum QAbstractItemView::SelectionBehavior |
417 | |
418 | \value SelectItems Selecting single items. |
419 | \value SelectRows Selecting only rows. |
420 | \value SelectColumns Selecting only columns. |
421 | */ |
422 | |
423 | /*! |
424 | \enum QAbstractItemView::ScrollHint |
425 | |
426 | \value EnsureVisible Scroll to ensure that the item is visible. |
427 | \value PositionAtTop Scroll to position the item at the top of the |
428 | viewport. |
429 | \value PositionAtBottom Scroll to position the item at the bottom of the |
430 | viewport. |
431 | \value PositionAtCenter Scroll to position the item at the center of the |
432 | viewport. |
433 | */ |
434 | |
435 | |
436 | /*! |
437 | \enum QAbstractItemView::EditTrigger |
438 | |
439 | This enum describes actions which will initiate item editing. |
440 | |
441 | \value NoEditTriggers No editing possible. |
442 | \value CurrentChanged Editing start whenever current item changes. |
443 | \value DoubleClicked Editing starts when an item is double clicked. |
444 | \value SelectedClicked Editing starts when clicking on an already selected |
445 | item. |
446 | \value EditKeyPressed Editing starts when the platform edit key has been |
447 | pressed over an item. |
448 | \value AnyKeyPressed Editing starts when any key is pressed over an item. |
449 | \value AllEditTriggers Editing starts for all above actions. |
450 | */ |
451 | |
452 | /*! |
453 | \enum QAbstractItemView::CursorAction |
454 | |
455 | This enum describes the different ways to navigate between items, |
456 | \sa moveCursor() |
457 | |
458 | \value MoveUp Move to the item above the current item. |
459 | \value MoveDown Move to the item below the current item. |
460 | \value MoveLeft Move to the item left of the current item. |
461 | \value MoveRight Move to the item right of the current item. |
462 | \value MoveHome Move to the top-left corner item. |
463 | \value MoveEnd Move to the bottom-right corner item. |
464 | \value MovePageUp Move one page up above the current item. |
465 | \value MovePageDown Move one page down below the current item. |
466 | \value MoveNext Move to the item after the current item. |
467 | \value MovePrevious Move to the item before the current item. |
468 | */ |
469 | |
470 | /*! |
471 | \enum QAbstractItemView::State |
472 | |
473 | Describes the different states the view can be in. This is usually |
474 | only interesting when reimplementing your own view. |
475 | |
476 | \value NoState The is the default state. |
477 | \value DraggingState The user is dragging items. |
478 | \value DragSelectingState The user is selecting items. |
479 | \value EditingState The user is editing an item in a widget editor. |
480 | \value ExpandingState The user is opening a branch of items. |
481 | \value CollapsingState The user is closing a branch of items. |
482 | \value AnimatingState The item view is performing an animation. |
483 | */ |
484 | |
485 | /*! |
486 | \since 4.2 |
487 | \enum QAbstractItemView::ScrollMode |
488 | |
489 | Describes how the scrollbar should behave. When setting the scroll mode |
490 | to ScrollPerPixel the single step size will adjust automatically unless |
491 | it was set explicitly using \l{QAbstractSlider::}{setSingleStep()}. |
492 | The automatic adjustment can be restored by setting the single step size to -1. |
493 | |
494 | \value ScrollPerItem The view will scroll the contents one item at a time. |
495 | \value ScrollPerPixel The view will scroll the contents one pixel at a time. |
496 | */ |
497 | |
498 | /*! |
499 | \fn QRect QAbstractItemView::visualRect(const QModelIndex &index) const = 0 |
500 | Returns the rectangle on the viewport occupied by the item at \a index. |
501 | |
502 | If your item is displayed in several areas then visualRect should return |
503 | the primary area that contains index and not the complete area that index |
504 | might encompasses, touch or cause drawing. |
505 | |
506 | In the base class this is a pure virtual function. |
507 | |
508 | \sa indexAt(), visualRegionForSelection() |
509 | */ |
510 | |
511 | /*! |
512 | \fn void QAbstractItemView::scrollTo(const QModelIndex &index, ScrollHint hint) = 0 |
513 | |
514 | Scrolls the view if necessary to ensure that the item at \a index |
515 | is visible. The view will try to position the item according to the given \a hint. |
516 | |
517 | In the base class this is a pure virtual function. |
518 | */ |
519 | |
520 | /*! |
521 | \fn QModelIndex QAbstractItemView::indexAt(const QPoint &point) const = 0 |
522 | |
523 | Returns the model index of the item at the viewport coordinates \a point. |
524 | |
525 | In the base class this is a pure virtual function. |
526 | |
527 | \sa visualRect() |
528 | */ |
529 | |
530 | /*! |
531 | \fn void QAbstractItemView::activated(const QModelIndex &index) |
532 | |
533 | This signal is emitted when the item specified by \a index is |
534 | activated by the user. How to activate items depends on the |
535 | platform; e.g., by single- or double-clicking the item, or by |
536 | pressing the Return or Enter key when the item is current. |
537 | |
538 | \sa clicked(), doubleClicked(), entered(), pressed() |
539 | */ |
540 | |
541 | /*! |
542 | \fn void QAbstractItemView::entered(const QModelIndex &index) |
543 | |
544 | This signal is emitted when the mouse cursor enters the item |
545 | specified by \a index. |
546 | Mouse tracking needs to be enabled for this feature to work. |
547 | |
548 | \sa viewportEntered(), activated(), clicked(), doubleClicked(), pressed() |
549 | */ |
550 | |
551 | /*! |
552 | \fn void QAbstractItemView::viewportEntered() |
553 | |
554 | This signal is emitted when the mouse cursor enters the viewport. |
555 | Mouse tracking needs to be enabled for this feature to work. |
556 | |
557 | \sa entered() |
558 | */ |
559 | |
560 | /*! |
561 | \fn void QAbstractItemView::pressed(const QModelIndex &index) |
562 | |
563 | This signal is emitted when a mouse button is pressed. The item |
564 | the mouse was pressed on is specified by \a index. The signal is |
565 | only emitted when the index is valid. |
566 | |
567 | Use the QGuiApplication::mouseButtons() function to get the state |
568 | of the mouse buttons. |
569 | |
570 | \sa activated(), clicked(), doubleClicked(), entered() |
571 | */ |
572 | |
573 | /*! |
574 | \fn void QAbstractItemView::clicked(const QModelIndex &index) |
575 | |
576 | This signal is emitted when a mouse button is left-clicked. The item |
577 | the mouse was clicked on is specified by \a index. The signal is |
578 | only emitted when the index is valid. |
579 | |
580 | \sa activated(), doubleClicked(), entered(), pressed() |
581 | */ |
582 | |
583 | /*! |
584 | \fn void QAbstractItemView::doubleClicked(const QModelIndex &index) |
585 | |
586 | This signal is emitted when a mouse button is double-clicked. The |
587 | item the mouse was double-clicked on is specified by \a index. |
588 | The signal is only emitted when the index is valid. |
589 | |
590 | \sa clicked(), activated() |
591 | */ |
592 | |
593 | /*! |
594 | \fn QModelIndex QAbstractItemView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) = 0 |
595 | |
596 | Returns a QModelIndex object pointing to the next object in the view, |
597 | based on the given \a cursorAction and keyboard modifiers specified |
598 | by \a modifiers. |
599 | |
600 | In the base class this is a pure virtual function. |
601 | */ |
602 | |
603 | /*! |
604 | \fn int QAbstractItemView::horizontalOffset() const = 0 |
605 | |
606 | Returns the horizontal offset of the view. |
607 | |
608 | In the base class this is a pure virtual function. |
609 | |
610 | \sa verticalOffset() |
611 | */ |
612 | |
613 | /*! |
614 | \fn int QAbstractItemView::verticalOffset() const = 0 |
615 | |
616 | Returns the vertical offset of the view. |
617 | |
618 | In the base class this is a pure virtual function. |
619 | |
620 | \sa horizontalOffset() |
621 | */ |
622 | |
623 | /*! |
624 | \fn bool QAbstractItemView::isIndexHidden(const QModelIndex &index) const |
625 | |
626 | Returns \c true if the item referred to by the given \a index is hidden in the view, |
627 | otherwise returns \c false. |
628 | |
629 | Hiding is a view specific feature. For example in TableView a column can be marked |
630 | as hidden or a row in the TreeView. |
631 | |
632 | In the base class this is a pure virtual function. |
633 | */ |
634 | |
635 | /*! |
636 | \fn void QAbstractItemView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) |
637 | |
638 | Applies the selection \a flags to the items in or touched by the |
639 | rectangle, \a rect. |
640 | |
641 | When implementing your own itemview setSelection should call |
642 | selectionModel()->select(selection, flags) where selection |
643 | is either an empty QModelIndex or a QItemSelection that contains |
644 | all items that are contained in \a rect. |
645 | |
646 | \sa selectionCommand(), selectedIndexes() |
647 | */ |
648 | |
649 | /*! |
650 | \fn QRegion QAbstractItemView::visualRegionForSelection(const QItemSelection &selection) const = 0 |
651 | |
652 | Returns the region from the viewport of the items in the given |
653 | \a selection. |
654 | |
655 | In the base class this is a pure virtual function. |
656 | |
657 | \sa visualRect(), selectedIndexes() |
658 | */ |
659 | |
660 | /*! |
661 | Constructs an abstract item view with the given \a parent. |
662 | */ |
663 | QAbstractItemView::QAbstractItemView(QWidget *parent) |
664 | : QAbstractScrollArea(*(new QAbstractItemViewPrivate), parent) |
665 | { |
666 | d_func()->init(); |
667 | } |
668 | |
669 | /*! |
670 | \internal |
671 | */ |
672 | QAbstractItemView::QAbstractItemView(QAbstractItemViewPrivate &dd, QWidget *parent) |
673 | : QAbstractScrollArea(dd, parent) |
674 | { |
675 | d_func()->init(); |
676 | } |
677 | |
678 | /*! |
679 | Destroys the view. |
680 | */ |
681 | QAbstractItemView::~QAbstractItemView() |
682 | { |
683 | Q_D(QAbstractItemView); |
684 | // stop these timers here before ~QObject |
685 | d->delayedReset.stop(); |
686 | d->updateTimer.stop(); |
687 | d->delayedEditing.stop(); |
688 | d->delayedAutoScroll.stop(); |
689 | d->autoScrollTimer.stop(); |
690 | d->delayedLayout.stop(); |
691 | d->fetchMoreTimer.stop(); |
692 | d->disconnectAll(); |
693 | } |
694 | |
695 | /*! |
696 | Sets the \a model for the view to present. |
697 | |
698 | This function will create and set a new selection model, replacing any |
699 | model that was previously set with setSelectionModel(). However, the old |
700 | selection model will not be deleted as it may be shared between several |
701 | views. We recommend that you delete the old selection model if it is no |
702 | longer required. This is done with the following code: |
703 | |
704 | \snippet code/src_gui_itemviews_qabstractitemview.cpp 2 |
705 | |
706 | If both the old model and the old selection model do not have parents, or |
707 | if their parents are long-lived objects, it may be preferable to call their |
708 | deleteLater() functions to explicitly delete them. |
709 | |
710 | The view \e{does not} take ownership of the model unless it is the model's |
711 | parent object because the model may be shared between many different views. |
712 | |
713 | \sa selectionModel(), setSelectionModel() |
714 | */ |
715 | void QAbstractItemView::setModel(QAbstractItemModel *model) |
716 | { |
717 | Q_D(QAbstractItemView); |
718 | if (model == d->model) |
719 | return; |
720 | if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) { |
721 | for (const QMetaObject::Connection &connection : d->modelConnections) |
722 | disconnect(connection); |
723 | } |
724 | d->model = (model ? model : QAbstractItemModelPrivate::staticEmptyModel()); |
725 | |
726 | if (d->model != QAbstractItemModelPrivate::staticEmptyModel()) { |
727 | d->modelConnections = { |
728 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::destroyed, |
729 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::modelDestroyed), |
730 | QObject::connect(sender: d->model, signal: &QAbstractItemModel::dataChanged, |
731 | context: this, slot: &QAbstractItemView::dataChanged), |
732 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::headerDataChanged, |
733 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::headerDataChanged), |
734 | QObject::connect(sender: d->model, signal: &QAbstractItemModel::rowsInserted, |
735 | context: this, slot: &QAbstractItemView::rowsInserted), |
736 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::rowsInserted, |
737 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::rowsInserted), |
738 | QObject::connect(sender: d->model, signal: &QAbstractItemModel::rowsAboutToBeRemoved, |
739 | context: this, slot: &QAbstractItemView::rowsAboutToBeRemoved), |
740 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::rowsRemoved, |
741 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::rowsRemoved), |
742 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::rowsMoved, |
743 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::rowsMoved), |
744 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::columnsAboutToBeRemoved, |
745 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::columnsAboutToBeRemoved), |
746 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::columnsRemoved, |
747 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::columnsRemoved), |
748 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::columnsInserted, |
749 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::columnsInserted), |
750 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::columnsMoved, |
751 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::columnsMoved), |
752 | QObject::connect(sender: d->model, signal: &QAbstractItemModel::modelReset, |
753 | context: this, slot: &QAbstractItemView::reset), |
754 | QObjectPrivate::connect(sender: d->model, signal: &QAbstractItemModel::layoutChanged, |
755 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::layoutChanged), |
756 | }; |
757 | } |
758 | |
759 | QItemSelectionModel *selection_model = new QItemSelectionModel(d->model, this); |
760 | connect(sender: d->model, signal: &QAbstractItemModel::destroyed, |
761 | context: selection_model, slot: &QItemSelectionModel::deleteLater); |
762 | setSelectionModel(selection_model); |
763 | |
764 | reset(); // kill editors, set new root and do layout |
765 | } |
766 | |
767 | /*! |
768 | Returns the model that this view is presenting. |
769 | */ |
770 | QAbstractItemModel *QAbstractItemView::model() const |
771 | { |
772 | Q_D(const QAbstractItemView); |
773 | return (d->model == QAbstractItemModelPrivate::staticEmptyModel() ? nullptr : d->model); |
774 | } |
775 | |
776 | /*! |
777 | Sets the current selection model to the given \a selectionModel. |
778 | |
779 | Note that, if you call setModel() after this function, the given \a selectionModel |
780 | will be replaced by one created by the view. |
781 | |
782 | \note It is up to the application to delete the old selection model if it is no |
783 | longer needed; i.e., if it is not being used by other views. This will happen |
784 | automatically when its parent object is deleted. However, if it does not have a |
785 | parent, or if the parent is a long-lived object, it may be preferable to call its |
786 | deleteLater() function to explicitly delete it. |
787 | |
788 | \sa selectionModel(), setModel(), clearSelection() |
789 | */ |
790 | void QAbstractItemView::setSelectionModel(QItemSelectionModel *selectionModel) |
791 | { |
792 | // ### if the given model is null, we should use the original selection model |
793 | Q_ASSERT(selectionModel); |
794 | Q_D(QAbstractItemView); |
795 | |
796 | if (Q_UNLIKELY(selectionModel->model() != d->model)) { |
797 | qWarning(msg: "QAbstractItemView::setSelectionModel() failed: " |
798 | "Trying to set a selection model, which works on " |
799 | "a different model than the view."); |
800 | return; |
801 | } |
802 | |
803 | QItemSelection oldSelection; |
804 | QModelIndex oldCurrentIndex; |
805 | |
806 | if (d->selectionModel) { |
807 | if (d->selectionModel->model() == selectionModel->model()) { |
808 | oldSelection = d->selectionModel->selection(); |
809 | oldCurrentIndex = d->selectionModel->currentIndex(); |
810 | } |
811 | disconnect(sender: d->selectionModel, signal: &QItemSelectionModel::selectionChanged, |
812 | receiver: this, slot: &QAbstractItemView::selectionChanged); |
813 | disconnect(sender: d->selectionModel, signal: &QItemSelectionModel::currentChanged, |
814 | receiver: this, slot: &QAbstractItemView::currentChanged); |
815 | } |
816 | |
817 | d->selectionModel = selectionModel; |
818 | |
819 | if (d->selectionModel) { |
820 | connect(sender: d->selectionModel, signal: &QItemSelectionModel::selectionChanged, |
821 | context: this, slot: &QAbstractItemView::selectionChanged); |
822 | connect(sender: d->selectionModel, signal: &QItemSelectionModel::currentChanged, |
823 | context: this, slot: &QAbstractItemView::currentChanged); |
824 | |
825 | selectionChanged(selected: d->selectionModel->selection(), deselected: oldSelection); |
826 | currentChanged(current: d->selectionModel->currentIndex(), previous: oldCurrentIndex); |
827 | } |
828 | } |
829 | |
830 | /*! |
831 | Returns the current selection model. |
832 | |
833 | \sa setSelectionModel(), selectedIndexes() |
834 | */ |
835 | QItemSelectionModel* QAbstractItemView::selectionModel() const |
836 | { |
837 | Q_D(const QAbstractItemView); |
838 | return d->selectionModel; |
839 | } |
840 | |
841 | /*! |
842 | Sets the item delegate for this view and its model to \a delegate. |
843 | This is useful if you want complete control over the editing and |
844 | display of items. |
845 | |
846 | Any existing delegate will be removed, but not deleted. QAbstractItemView |
847 | does not take ownership of \a delegate. |
848 | |
849 | \warning You should not share the same instance of a delegate between views. |
850 | Doing so can cause incorrect or unintuitive editing behavior since each |
851 | view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} |
852 | signal, and attempt to access, modify or close an editor that has already been closed. |
853 | |
854 | \sa itemDelegate() |
855 | */ |
856 | void QAbstractItemView::setItemDelegate(QAbstractItemDelegate *delegate) |
857 | { |
858 | Q_D(QAbstractItemView); |
859 | if (delegate == d->itemDelegate) |
860 | return; |
861 | |
862 | if (d->itemDelegate) { |
863 | if (d->delegateRefCount(delegate: d->itemDelegate) == 1) |
864 | d->disconnectDelegate(delegate); |
865 | } |
866 | |
867 | if (delegate) { |
868 | if (d->delegateRefCount(delegate) == 0) |
869 | d->connectDelegate(delegate); |
870 | } |
871 | d->itemDelegate = delegate; |
872 | viewport()->update(); |
873 | d->doDelayedItemsLayout(); |
874 | } |
875 | |
876 | /*! |
877 | Returns the item delegate used by this view and model. This is |
878 | either one set with setItemDelegate(), or the default one. |
879 | |
880 | \sa setItemDelegate() |
881 | */ |
882 | QAbstractItemDelegate *QAbstractItemView::itemDelegate() const |
883 | { |
884 | return d_func()->itemDelegate; |
885 | } |
886 | |
887 | /*! |
888 | \reimp |
889 | */ |
890 | QVariant QAbstractItemView::inputMethodQuery(Qt::InputMethodQuery query) const |
891 | { |
892 | Q_D(const QAbstractItemView); |
893 | const QModelIndex current = currentIndex(); |
894 | QVariant result; |
895 | if (current.isValid()) { |
896 | if (QWidget *currentEditor; |
897 | d->waitForIMCommit && (currentEditor = d->editorForIndex(index: current).widget)) { |
898 | // An editor is open but the initial preedit is still ongoing. Delegate |
899 | // queries to the editor and map coordinates from editor to this view. |
900 | result = currentEditor->inputMethodQuery(query); |
901 | if (result.typeId() == QMetaType::QRect) { |
902 | const QRect editorRect = result.value<QRect>(); |
903 | result = QRect(currentEditor->mapTo(this, editorRect.topLeft()), editorRect.size()); |
904 | } |
905 | } else if (query == Qt::ImCursorRectangle) { |
906 | result = visualRect(index: current); |
907 | } |
908 | } |
909 | if (!result.isValid()) |
910 | result = QAbstractScrollArea::inputMethodQuery(query); |
911 | return result; |
912 | } |
913 | |
914 | /*! |
915 | \since 4.2 |
916 | |
917 | Sets the given item \a delegate used by this view and model for the given |
918 | \a row. All items on \a row will be drawn and managed by \a delegate |
919 | instead of using the default delegate (i.e., itemDelegate()). |
920 | |
921 | Any existing row delegate for \a row will be removed, but not |
922 | deleted. QAbstractItemView does not take ownership of \a delegate. |
923 | |
924 | \note If a delegate has been assigned to both a row and a column, the row |
925 | delegate (i.e., this delegate) will take precedence and manage the |
926 | intersecting cell index. |
927 | |
928 | \warning You should not share the same instance of a delegate between views. |
929 | Doing so can cause incorrect or unintuitive editing behavior since each |
930 | view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} |
931 | signal, and attempt to access, modify or close an editor that has already been closed. |
932 | |
933 | \sa itemDelegateForRow(), setItemDelegateForColumn(), itemDelegate() |
934 | */ |
935 | void QAbstractItemView::setItemDelegateForRow(int row, QAbstractItemDelegate *delegate) |
936 | { |
937 | Q_D(QAbstractItemView); |
938 | if (QAbstractItemDelegate *rowDelegate = d->rowDelegates.value(key: row, defaultValue: nullptr)) { |
939 | if (d->delegateRefCount(delegate: rowDelegate) == 1) |
940 | d->disconnectDelegate(delegate: rowDelegate); |
941 | d->rowDelegates.remove(key: row); |
942 | } |
943 | if (delegate) { |
944 | if (d->delegateRefCount(delegate) == 0) |
945 | d->connectDelegate(delegate); |
946 | d->rowDelegates.insert(key: row, value: delegate); |
947 | } |
948 | viewport()->update(); |
949 | d->doDelayedItemsLayout(); |
950 | } |
951 | |
952 | /*! |
953 | \since 4.2 |
954 | |
955 | Returns the item delegate used by this view and model for the given \a row, |
956 | or \nullptr if no delegate has been assigned. You can call itemDelegate() |
957 | to get a pointer to the current delegate for a given index. |
958 | |
959 | \sa setItemDelegateForRow(), itemDelegateForColumn(), setItemDelegate() |
960 | */ |
961 | QAbstractItemDelegate *QAbstractItemView::itemDelegateForRow(int row) const |
962 | { |
963 | Q_D(const QAbstractItemView); |
964 | return d->rowDelegates.value(key: row, defaultValue: nullptr); |
965 | } |
966 | |
967 | /*! |
968 | \since 4.2 |
969 | |
970 | Sets the given item \a delegate used by this view and model for the given |
971 | \a column. All items on \a column will be drawn and managed by \a delegate |
972 | instead of using the default delegate (i.e., itemDelegate()). |
973 | |
974 | Any existing column delegate for \a column will be removed, but not |
975 | deleted. QAbstractItemView does not take ownership of \a delegate. |
976 | |
977 | \note If a delegate has been assigned to both a row and a column, the row |
978 | delegate will take precedence and manage the intersecting cell index. |
979 | |
980 | \warning You should not share the same instance of a delegate between views. |
981 | Doing so can cause incorrect or unintuitive editing behavior since each |
982 | view connected to a given delegate may receive the \l{QAbstractItemDelegate::}{closeEditor()} |
983 | signal, and attempt to access, modify or close an editor that has already been closed. |
984 | |
985 | \sa itemDelegateForColumn(), setItemDelegateForRow(), itemDelegate() |
986 | */ |
987 | void QAbstractItemView::setItemDelegateForColumn(int column, QAbstractItemDelegate *delegate) |
988 | { |
989 | Q_D(QAbstractItemView); |
990 | if (QAbstractItemDelegate *columnDelegate = d->columnDelegates.value(key: column, defaultValue: nullptr)) { |
991 | if (d->delegateRefCount(delegate: columnDelegate) == 1) |
992 | d->disconnectDelegate(delegate: columnDelegate); |
993 | d->columnDelegates.remove(key: column); |
994 | } |
995 | if (delegate) { |
996 | if (d->delegateRefCount(delegate) == 0) |
997 | d->connectDelegate(delegate); |
998 | d->columnDelegates.insert(key: column, value: delegate); |
999 | } |
1000 | viewport()->update(); |
1001 | d->doDelayedItemsLayout(); |
1002 | } |
1003 | |
1004 | /*! |
1005 | \since 4.2 |
1006 | |
1007 | Returns the item delegate used by this view and model for the given \a |
1008 | column. You can call itemDelegate() to get a pointer to the current delegate |
1009 | for a given index. |
1010 | |
1011 | \sa setItemDelegateForColumn(), itemDelegateForRow(), itemDelegate() |
1012 | */ |
1013 | QAbstractItemDelegate *QAbstractItemView::itemDelegateForColumn(int column) const |
1014 | { |
1015 | Q_D(const QAbstractItemView); |
1016 | return d->columnDelegates.value(key: column, defaultValue: nullptr); |
1017 | } |
1018 | |
1019 | /*! |
1020 | \fn QAbstractItemDelegate *QAbstractItemView::itemDelegate(const QModelIndex &index) const |
1021 | \deprecated Use itemDelegateForIndex() instead. |
1022 | Returns the item delegate used by this view and model for |
1023 | the given \a index. |
1024 | */ |
1025 | |
1026 | /*! |
1027 | \since 6.0 |
1028 | |
1029 | Returns the item delegate used by this view and model for |
1030 | the given \a index. |
1031 | |
1032 | \sa setItemDelegate(), setItemDelegateForRow(), setItemDelegateForColumn() |
1033 | */ |
1034 | QAbstractItemDelegate *QAbstractItemView::itemDelegateForIndex(const QModelIndex &index) const |
1035 | { |
1036 | Q_D(const QAbstractItemView); |
1037 | return d->delegateForIndex(index); |
1038 | } |
1039 | |
1040 | /*! |
1041 | \property QAbstractItemView::selectionMode |
1042 | \brief which selection mode the view operates in |
1043 | |
1044 | This property controls whether the user can select one or many items |
1045 | and, in many-item selections, whether the selection must be a |
1046 | continuous range of items. |
1047 | |
1048 | \sa SelectionMode, SelectionBehavior |
1049 | */ |
1050 | void QAbstractItemView::setSelectionMode(SelectionMode mode) |
1051 | { |
1052 | Q_D(QAbstractItemView); |
1053 | d->selectionMode = mode; |
1054 | } |
1055 | |
1056 | QAbstractItemView::SelectionMode QAbstractItemView::selectionMode() const |
1057 | { |
1058 | Q_D(const QAbstractItemView); |
1059 | return d->selectionMode; |
1060 | } |
1061 | |
1062 | /*! |
1063 | \property QAbstractItemView::selectionBehavior |
1064 | \brief which selection behavior the view uses |
1065 | |
1066 | This property holds whether selections are done |
1067 | in terms of single items, rows or columns. |
1068 | |
1069 | \sa SelectionMode, SelectionBehavior |
1070 | */ |
1071 | |
1072 | void QAbstractItemView::setSelectionBehavior(QAbstractItemView::SelectionBehavior behavior) |
1073 | { |
1074 | Q_D(QAbstractItemView); |
1075 | d->selectionBehavior = behavior; |
1076 | } |
1077 | |
1078 | QAbstractItemView::SelectionBehavior QAbstractItemView::selectionBehavior() const |
1079 | { |
1080 | Q_D(const QAbstractItemView); |
1081 | return d->selectionBehavior; |
1082 | } |
1083 | |
1084 | /*! |
1085 | Sets the current item to be the item at \a index. |
1086 | |
1087 | Unless the current selection mode is |
1088 | \l{QAbstractItemView::}{NoSelection}, the item is also selected. |
1089 | Note that this function also updates the starting position for any |
1090 | new selections the user performs. |
1091 | |
1092 | To set an item as the current item without selecting it, call |
1093 | |
1094 | \c{selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);} |
1095 | |
1096 | \sa currentIndex(), currentChanged(), selectionMode |
1097 | */ |
1098 | void QAbstractItemView::setCurrentIndex(const QModelIndex &index) |
1099 | { |
1100 | Q_D(QAbstractItemView); |
1101 | if (d->selectionModel && (!index.isValid() || d->isIndexEnabled(index))) { |
1102 | QItemSelectionModel::SelectionFlags command = selectionCommand(index, event: nullptr); |
1103 | d->selectionModel->setCurrentIndex(index, command); |
1104 | d->currentIndexSet = true; |
1105 | } |
1106 | } |
1107 | |
1108 | /*! |
1109 | Returns the model index of the current item. |
1110 | |
1111 | \sa setCurrentIndex() |
1112 | */ |
1113 | QModelIndex QAbstractItemView::currentIndex() const |
1114 | { |
1115 | Q_D(const QAbstractItemView); |
1116 | return d->selectionModel ? d->selectionModel->currentIndex() : QModelIndex(); |
1117 | } |
1118 | |
1119 | |
1120 | /*! |
1121 | Reset the internal state of the view. |
1122 | |
1123 | \warning This function will reset open editors, scroll bar positions, |
1124 | selections, etc. Existing changes will not be committed. If you would like |
1125 | to save your changes when resetting the view, you can reimplement this |
1126 | function, commit your changes, and then call the superclass' |
1127 | implementation. |
1128 | */ |
1129 | void QAbstractItemView::reset() |
1130 | { |
1131 | Q_D(QAbstractItemView); |
1132 | d->delayedReset.stop(); //make sure we stop the timer |
1133 | // Taking a copy because releaseEditor() eventurally calls deleteLater() on the |
1134 | // editor, which calls QCoreApplication::postEvent(), the latter may invoke unknown |
1135 | // code that may modify d->indexEditorHash. |
1136 | const auto copy = d->indexEditorHash; |
1137 | for (const auto &[index, info] : copy.asKeyValueRange()) { |
1138 | if (info.widget) |
1139 | d->releaseEditor(editor: info.widget.data(), index: d->indexForEditor(editor: info.widget.data())); |
1140 | } |
1141 | d->editorIndexHash.clear(); |
1142 | d->indexEditorHash.clear(); |
1143 | d->persistent.clear(); |
1144 | d->currentIndexSet = false; |
1145 | setState(NoState); |
1146 | setRootIndex(QModelIndex()); |
1147 | if (d->selectionModel) |
1148 | d->selectionModel->reset(); |
1149 | #if QT_CONFIG(accessibility) |
1150 | if (QAccessible::isActive()) { |
1151 | QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::ModelReset); |
1152 | QAccessible::updateAccessibility(event: &accessibleEvent); |
1153 | } |
1154 | #endif |
1155 | d->updateGeometry(); |
1156 | } |
1157 | |
1158 | /*! |
1159 | Sets the root item to the item at the given \a index. |
1160 | |
1161 | \sa rootIndex() |
1162 | */ |
1163 | void QAbstractItemView::setRootIndex(const QModelIndex &index) |
1164 | { |
1165 | Q_D(QAbstractItemView); |
1166 | if (Q_UNLIKELY(index.isValid() && index.model() != d->model)) { |
1167 | qWarning(msg: "QAbstractItemView::setRootIndex failed : index must be from the currently set model"); |
1168 | return; |
1169 | } |
1170 | d->root = index; |
1171 | #if QT_CONFIG(accessibility) |
1172 | if (QAccessible::isActive()) { |
1173 | QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::ModelReset); |
1174 | QAccessible::updateAccessibility(event: &accessibleEvent); |
1175 | } |
1176 | #endif |
1177 | d->doDelayedItemsLayout(); |
1178 | d->updateGeometry(); |
1179 | } |
1180 | |
1181 | /*! |
1182 | Returns the model index of the model's root item. The root item is |
1183 | the parent item to the view's toplevel items. The root can be invalid. |
1184 | |
1185 | \sa setRootIndex() |
1186 | */ |
1187 | QModelIndex QAbstractItemView::rootIndex() const |
1188 | { |
1189 | return QModelIndex(d_func()->root); |
1190 | } |
1191 | |
1192 | /*! |
1193 | Selects all items in the view. |
1194 | This function will use the selection behavior |
1195 | set on the view when selecting. |
1196 | |
1197 | \sa setSelection(), selectedIndexes(), clearSelection() |
1198 | */ |
1199 | void QAbstractItemView::selectAll() |
1200 | { |
1201 | Q_D(QAbstractItemView); |
1202 | const SelectionMode mode = d->selectionMode; |
1203 | switch (mode) { |
1204 | case MultiSelection: |
1205 | case ExtendedSelection: |
1206 | d->selectAll(command: QItemSelectionModel::ClearAndSelect |
1207 | | d->selectionBehaviorFlags()); |
1208 | break; |
1209 | case NoSelection: |
1210 | case ContiguousSelection: |
1211 | if (d->model->hasChildren(parent: d->root)) |
1212 | d->selectAll(command: selectionCommand(index: d->model->index(row: 0, column: 0, parent: d->root))); |
1213 | break; |
1214 | case SingleSelection: |
1215 | break; |
1216 | } |
1217 | } |
1218 | |
1219 | /*! |
1220 | Starts editing the item corresponding to the given \a index if it is |
1221 | editable. |
1222 | |
1223 | Note that this function does not change the current index. Since the current |
1224 | index defines the next and previous items to edit, users may find that |
1225 | keyboard navigation does not work as expected. To provide consistent navigation |
1226 | behavior, call setCurrentIndex() before this function with the same model |
1227 | index. |
1228 | |
1229 | \sa QModelIndex::flags() |
1230 | */ |
1231 | void QAbstractItemView::edit(const QModelIndex &index) |
1232 | { |
1233 | Q_D(QAbstractItemView); |
1234 | if (Q_UNLIKELY(!d->isIndexValid(index))) |
1235 | qWarning(msg: "edit: index was invalid"); |
1236 | if (Q_UNLIKELY(!edit(index, AllEditTriggers, nullptr))) |
1237 | qWarning(msg: "edit: editing failed"); |
1238 | } |
1239 | |
1240 | /*! |
1241 | Deselects all selected items. The current index will not be changed. |
1242 | |
1243 | \sa setSelection(), selectAll() |
1244 | */ |
1245 | void QAbstractItemView::clearSelection() |
1246 | { |
1247 | Q_D(QAbstractItemView); |
1248 | if (d->selectionModel) |
1249 | d->selectionModel->clearSelection(); |
1250 | } |
1251 | |
1252 | /*! |
1253 | \internal |
1254 | |
1255 | This function is intended to lay out the items in the view. |
1256 | The default implementation just calls updateGeometries() and updates the viewport. |
1257 | */ |
1258 | void QAbstractItemView::doItemsLayout() |
1259 | { |
1260 | Q_D(QAbstractItemView); |
1261 | d->interruptDelayedItemsLayout(); |
1262 | updateGeometries(); |
1263 | d->viewport->update(); |
1264 | } |
1265 | |
1266 | /*! |
1267 | \property QAbstractItemView::editTriggers |
1268 | \brief which actions will initiate item editing |
1269 | |
1270 | This property is a selection of flags defined by |
1271 | \l{EditTrigger}, combined using the OR |
1272 | operator. The view will only initiate the editing of an item if the |
1273 | action performed is set in this property. |
1274 | */ |
1275 | void QAbstractItemView::setEditTriggers(EditTriggers actions) |
1276 | { |
1277 | Q_D(QAbstractItemView); |
1278 | d->editTriggers = actions; |
1279 | } |
1280 | |
1281 | QAbstractItemView::EditTriggers QAbstractItemView::editTriggers() const |
1282 | { |
1283 | Q_D(const QAbstractItemView); |
1284 | return d->editTriggers; |
1285 | } |
1286 | |
1287 | /*! |
1288 | \since 4.2 |
1289 | \property QAbstractItemView::verticalScrollMode |
1290 | \brief how the view scrolls its contents in the vertical direction |
1291 | |
1292 | This property controls how the view scroll its contents vertically. |
1293 | Scrolling can be done either per pixel or per item. Its default value |
1294 | comes from the style via the QStyle::SH_ItemView_ScrollMode style hint. |
1295 | */ |
1296 | |
1297 | void QAbstractItemView::setVerticalScrollMode(ScrollMode mode) |
1298 | { |
1299 | Q_D(QAbstractItemView); |
1300 | d->verticalScrollModeSet = true; |
1301 | if (mode == d->verticalScrollMode) |
1302 | return; |
1303 | QModelIndex topLeft = indexAt(point: QPoint(0, 0)); |
1304 | d->verticalScrollMode = mode; |
1305 | if (mode == ScrollPerItem) |
1306 | verticalScrollBar()->d_func()->itemviewChangeSingleStep(step: 1); // setSingleStep(-1) => step with 1 |
1307 | else |
1308 | verticalScrollBar()->setSingleStep(-1); // Ensure that the view can update single step |
1309 | updateGeometries(); // update the scroll bars |
1310 | scrollTo(index: topLeft, hint: QAbstractItemView::PositionAtTop); |
1311 | } |
1312 | |
1313 | QAbstractItemView::ScrollMode QAbstractItemView::verticalScrollMode() const |
1314 | { |
1315 | Q_D(const QAbstractItemView); |
1316 | return d->verticalScrollMode; |
1317 | } |
1318 | |
1319 | void QAbstractItemView::resetVerticalScrollMode() |
1320 | { |
1321 | auto sm = static_cast<ScrollMode>(style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: this, returnData: nullptr)); |
1322 | setVerticalScrollMode(sm); |
1323 | d_func()->verticalScrollModeSet = false; |
1324 | } |
1325 | |
1326 | /*! |
1327 | \since 4.2 |
1328 | \property QAbstractItemView::horizontalScrollMode |
1329 | \brief how the view scrolls its contents in the horizontal direction |
1330 | |
1331 | This property controls how the view scroll its contents horizontally. |
1332 | Scrolling can be done either per pixel or per item. Its default value |
1333 | comes from the style via the QStyle::SH_ItemView_ScrollMode style hint. |
1334 | */ |
1335 | |
1336 | void QAbstractItemView::setHorizontalScrollMode(ScrollMode mode) |
1337 | { |
1338 | Q_D(QAbstractItemView); |
1339 | d->horizontalScrollModeSet = true; |
1340 | if (mode == d->horizontalScrollMode) |
1341 | return; |
1342 | d->horizontalScrollMode = mode; |
1343 | if (mode == ScrollPerItem) |
1344 | horizontalScrollBar()->d_func()->itemviewChangeSingleStep(step: 1); // setSingleStep(-1) => step with 1 |
1345 | else |
1346 | horizontalScrollBar()->setSingleStep(-1); // Ensure that the view can update single step |
1347 | updateGeometries(); // update the scroll bars |
1348 | } |
1349 | |
1350 | QAbstractItemView::ScrollMode QAbstractItemView::horizontalScrollMode() const |
1351 | { |
1352 | Q_D(const QAbstractItemView); |
1353 | return d->horizontalScrollMode; |
1354 | } |
1355 | |
1356 | void QAbstractItemView::resetHorizontalScrollMode() |
1357 | { |
1358 | auto sm = static_cast<ScrollMode>(style()->styleHint(stylehint: QStyle::SH_ItemView_ScrollMode, opt: nullptr, widget: this, returnData: nullptr)); |
1359 | setHorizontalScrollMode(sm); |
1360 | d_func()->horizontalScrollModeSet = false; |
1361 | } |
1362 | |
1363 | #if QT_CONFIG(draganddrop) |
1364 | /*! |
1365 | \since 4.2 |
1366 | \property QAbstractItemView::dragDropOverwriteMode |
1367 | \brief the view's drag and drop behavior |
1368 | |
1369 | If its value is \c true, the selected data will overwrite the |
1370 | existing item data when dropped, while moving the data will clear |
1371 | the item. If its value is \c false, the selected data will be |
1372 | inserted as a new item when the data is dropped. When the data is |
1373 | moved, the item is removed as well. |
1374 | |
1375 | The default value is \c false, as in the QListView and QTreeView |
1376 | subclasses. In the QTableView subclass, on the other hand, the |
1377 | property has been set to \c true. |
1378 | |
1379 | Note: This is not intended to prevent overwriting of items. |
1380 | The model's implementation of flags() should do that by not |
1381 | returning Qt::ItemIsDropEnabled. |
1382 | |
1383 | \sa dragDropMode |
1384 | */ |
1385 | void QAbstractItemView::setDragDropOverwriteMode(bool overwrite) |
1386 | { |
1387 | Q_D(QAbstractItemView); |
1388 | d->overwrite = overwrite; |
1389 | } |
1390 | |
1391 | bool QAbstractItemView::dragDropOverwriteMode() const |
1392 | { |
1393 | Q_D(const QAbstractItemView); |
1394 | return d->overwrite; |
1395 | } |
1396 | #endif |
1397 | |
1398 | /*! |
1399 | \property QAbstractItemView::autoScroll |
1400 | \brief whether autoscrolling in drag move events is enabled |
1401 | |
1402 | If this property is set to true (the default), the |
1403 | QAbstractItemView automatically scrolls the contents of the view |
1404 | if the user drags within 16 pixels of the viewport edge. If the current |
1405 | item changes, then the view will scroll automatically to ensure that the |
1406 | current item is fully visible. |
1407 | |
1408 | This property only works if the viewport accepts drops. Autoscroll is |
1409 | switched off by setting this property to false. |
1410 | */ |
1411 | |
1412 | void QAbstractItemView::setAutoScroll(bool enable) |
1413 | { |
1414 | Q_D(QAbstractItemView); |
1415 | d->autoScroll = enable; |
1416 | } |
1417 | |
1418 | bool QAbstractItemView::hasAutoScroll() const |
1419 | { |
1420 | Q_D(const QAbstractItemView); |
1421 | return d->autoScroll; |
1422 | } |
1423 | |
1424 | /*! |
1425 | \since 4.4 |
1426 | \property QAbstractItemView::autoScrollMargin |
1427 | \brief the size of the area when auto scrolling is triggered |
1428 | |
1429 | This property controls the size of the area at the edge of the viewport that |
1430 | triggers autoscrolling. The default value is 16 pixels. |
1431 | */ |
1432 | void QAbstractItemView::setAutoScrollMargin(int margin) |
1433 | { |
1434 | Q_D(QAbstractItemView); |
1435 | d->autoScrollMargin = margin; |
1436 | } |
1437 | |
1438 | int QAbstractItemView::autoScrollMargin() const |
1439 | { |
1440 | Q_D(const QAbstractItemView); |
1441 | return d->autoScrollMargin; |
1442 | } |
1443 | |
1444 | /*! |
1445 | \property QAbstractItemView::tabKeyNavigation |
1446 | \brief whether item navigation with tab and backtab is enabled. |
1447 | */ |
1448 | |
1449 | void QAbstractItemView::setTabKeyNavigation(bool enable) |
1450 | { |
1451 | Q_D(QAbstractItemView); |
1452 | d->tabKeyNavigation = enable; |
1453 | } |
1454 | |
1455 | bool QAbstractItemView::tabKeyNavigation() const |
1456 | { |
1457 | Q_D(const QAbstractItemView); |
1458 | return d->tabKeyNavigation; |
1459 | } |
1460 | |
1461 | /*! |
1462 | \since 5.2 |
1463 | \reimp |
1464 | */ |
1465 | QSize QAbstractItemView::viewportSizeHint() const |
1466 | { |
1467 | return QAbstractScrollArea::viewportSizeHint(); |
1468 | } |
1469 | |
1470 | #if QT_CONFIG(draganddrop) |
1471 | /*! |
1472 | \property QAbstractItemView::showDropIndicator |
1473 | \brief whether the drop indicator is shown when dragging items and dropping. |
1474 | |
1475 | \sa dragEnabled, DragDropMode, dragDropOverwriteMode, acceptDrops |
1476 | */ |
1477 | |
1478 | void QAbstractItemView::setDropIndicatorShown(bool enable) |
1479 | { |
1480 | Q_D(QAbstractItemView); |
1481 | d->showDropIndicator = enable; |
1482 | } |
1483 | |
1484 | bool QAbstractItemView::showDropIndicator() const |
1485 | { |
1486 | Q_D(const QAbstractItemView); |
1487 | return d->showDropIndicator; |
1488 | } |
1489 | |
1490 | /*! |
1491 | \property QAbstractItemView::dragEnabled |
1492 | \brief whether the view supports dragging of its own items |
1493 | |
1494 | \sa showDropIndicator, DragDropMode, dragDropOverwriteMode, acceptDrops |
1495 | */ |
1496 | |
1497 | void QAbstractItemView::setDragEnabled(bool enable) |
1498 | { |
1499 | Q_D(QAbstractItemView); |
1500 | d->dragEnabled = enable; |
1501 | } |
1502 | |
1503 | bool QAbstractItemView::dragEnabled() const |
1504 | { |
1505 | Q_D(const QAbstractItemView); |
1506 | return d->dragEnabled; |
1507 | } |
1508 | |
1509 | /*! |
1510 | \since 4.2 |
1511 | \enum QAbstractItemView::DragDropMode |
1512 | |
1513 | Describes the various drag and drop events the view can act upon. |
1514 | By default the view does not support dragging or dropping (\c |
1515 | NoDragDrop). |
1516 | |
1517 | \value NoDragDrop Does not support dragging or dropping. |
1518 | \value DragOnly The view supports dragging of its own items |
1519 | \value DropOnly The view accepts drops |
1520 | \value DragDrop The view supports both dragging and dropping |
1521 | \value InternalMove The view accepts move (\b{not copy}) operations only |
1522 | from itself. |
1523 | |
1524 | Note that the model used needs to provide support for drag and drop operations. |
1525 | |
1526 | \sa setDragDropMode(), {Using drag and drop with item views} |
1527 | */ |
1528 | |
1529 | /*! |
1530 | \property QAbstractItemView::dragDropMode |
1531 | \brief the drag and drop event the view will act upon |
1532 | |
1533 | \since 4.2 |
1534 | \sa showDropIndicator, dragDropOverwriteMode |
1535 | */ |
1536 | void QAbstractItemView::setDragDropMode(DragDropMode behavior) |
1537 | { |
1538 | Q_D(QAbstractItemView); |
1539 | d->dragDropMode = behavior; |
1540 | setDragEnabled(behavior == DragOnly || behavior == DragDrop || behavior == InternalMove); |
1541 | setAcceptDrops(behavior == DropOnly || behavior == DragDrop || behavior == InternalMove); |
1542 | } |
1543 | |
1544 | QAbstractItemView::DragDropMode QAbstractItemView::dragDropMode() const |
1545 | { |
1546 | Q_D(const QAbstractItemView); |
1547 | DragDropMode setBehavior = d->dragDropMode; |
1548 | if (!dragEnabled() && !acceptDrops()) |
1549 | return NoDragDrop; |
1550 | |
1551 | if (dragEnabled() && !acceptDrops()) |
1552 | return DragOnly; |
1553 | |
1554 | if (!dragEnabled() && acceptDrops()) |
1555 | return DropOnly; |
1556 | |
1557 | if (dragEnabled() && acceptDrops()) { |
1558 | if (setBehavior == InternalMove) |
1559 | return setBehavior; |
1560 | else |
1561 | return DragDrop; |
1562 | } |
1563 | |
1564 | return NoDragDrop; |
1565 | } |
1566 | |
1567 | /*! |
1568 | \property QAbstractItemView::defaultDropAction |
1569 | \brief the drop action that will be used by default in QAbstractItemView::drag(). |
1570 | |
1571 | If the property is not set, the drop action is CopyAction when the supported |
1572 | actions support CopyAction. |
1573 | |
1574 | \since 4.6 |
1575 | \sa showDropIndicator, dragDropOverwriteMode |
1576 | */ |
1577 | void QAbstractItemView::setDefaultDropAction(Qt::DropAction dropAction) |
1578 | { |
1579 | Q_D(QAbstractItemView); |
1580 | d->defaultDropAction = dropAction; |
1581 | } |
1582 | |
1583 | Qt::DropAction QAbstractItemView::defaultDropAction() const |
1584 | { |
1585 | Q_D(const QAbstractItemView); |
1586 | return d->defaultDropAction; |
1587 | } |
1588 | |
1589 | #endif // QT_CONFIG(draganddrop) |
1590 | |
1591 | /*! |
1592 | \property QAbstractItemView::alternatingRowColors |
1593 | \brief whether to draw the background using alternating colors |
1594 | |
1595 | If this property is \c true, the item background will be drawn using |
1596 | QPalette::Base and QPalette::AlternateBase; otherwise the background |
1597 | will be drawn using the QPalette::Base color. |
1598 | |
1599 | By default, this property is \c false. |
1600 | */ |
1601 | void QAbstractItemView::setAlternatingRowColors(bool enable) |
1602 | { |
1603 | Q_D(QAbstractItemView); |
1604 | d->alternatingColors = enable; |
1605 | if (isVisible()) |
1606 | d->viewport->update(); |
1607 | } |
1608 | |
1609 | bool QAbstractItemView::alternatingRowColors() const |
1610 | { |
1611 | Q_D(const QAbstractItemView); |
1612 | return d->alternatingColors; |
1613 | } |
1614 | |
1615 | /*! |
1616 | \property QAbstractItemView::iconSize |
1617 | \brief the size of items' icons |
1618 | |
1619 | Setting this property when the view is visible will cause the |
1620 | items to be laid out again. |
1621 | */ |
1622 | void QAbstractItemView::setIconSize(const QSize &size) |
1623 | { |
1624 | Q_D(QAbstractItemView); |
1625 | if (size == d->iconSize) |
1626 | return; |
1627 | d->iconSize = size; |
1628 | d->doDelayedItemsLayout(); |
1629 | emit iconSizeChanged(size); |
1630 | } |
1631 | |
1632 | QSize QAbstractItemView::iconSize() const |
1633 | { |
1634 | Q_D(const QAbstractItemView); |
1635 | return d->iconSize; |
1636 | } |
1637 | |
1638 | /*! |
1639 | \property QAbstractItemView::textElideMode |
1640 | |
1641 | \brief the position of the "..." in elided text. |
1642 | |
1643 | The default value for all item views is Qt::ElideRight. |
1644 | */ |
1645 | void QAbstractItemView::setTextElideMode(Qt::TextElideMode mode) |
1646 | { |
1647 | Q_D(QAbstractItemView); |
1648 | d->textElideMode = mode; |
1649 | } |
1650 | |
1651 | Qt::TextElideMode QAbstractItemView::textElideMode() const |
1652 | { |
1653 | return d_func()->textElideMode; |
1654 | } |
1655 | |
1656 | /*! |
1657 | \reimp |
1658 | */ |
1659 | bool QAbstractItemView::focusNextPrevChild(bool next) |
1660 | { |
1661 | Q_D(QAbstractItemView); |
1662 | if (d->tabKeyNavigation && isVisible() && isEnabled() && d->viewport->isEnabled()) { |
1663 | QKeyEvent event(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); |
1664 | keyPressEvent(event: &event); |
1665 | if (event.isAccepted()) |
1666 | return true; |
1667 | } |
1668 | return QAbstractScrollArea::focusNextPrevChild(next); |
1669 | } |
1670 | |
1671 | /*! |
1672 | \reimp |
1673 | */ |
1674 | bool QAbstractItemView::event(QEvent *event) |
1675 | { |
1676 | Q_D(QAbstractItemView); |
1677 | switch (event->type()) { |
1678 | case QEvent::Paint: |
1679 | //we call this here because the scrollbars' visibility might be altered |
1680 | //so this can't be done in the paintEvent method |
1681 | d->executePostedLayout(); //make sure we set the layout properly |
1682 | break; |
1683 | case QEvent::Show: |
1684 | d->executePostedLayout(); //make sure we set the layout properly |
1685 | if (d->shouldScrollToCurrentOnShow) { |
1686 | d->shouldScrollToCurrentOnShow = false; |
1687 | const QModelIndex current = currentIndex(); |
1688 | if (current.isValid() && (d->state == QAbstractItemView::EditingState || d->autoScroll)) |
1689 | scrollTo(index: current); |
1690 | } |
1691 | break; |
1692 | case QEvent::LocaleChange: |
1693 | viewport()->update(); |
1694 | break; |
1695 | case QEvent::LayoutDirectionChange: |
1696 | case QEvent::ApplicationLayoutDirectionChange: |
1697 | updateGeometries(); |
1698 | break; |
1699 | case QEvent::StyleChange: |
1700 | doItemsLayout(); |
1701 | if (!d->verticalScrollModeSet) |
1702 | resetVerticalScrollMode(); |
1703 | if (!d->horizontalScrollModeSet) |
1704 | resetHorizontalScrollMode(); |
1705 | break; |
1706 | case QEvent::FocusOut: |
1707 | d->checkPersistentEditorFocus(); |
1708 | break; |
1709 | case QEvent::FontChange: |
1710 | d->doDelayedItemsLayout(); // the size of the items will change |
1711 | break; |
1712 | default: |
1713 | break; |
1714 | } |
1715 | return QAbstractScrollArea::event(event); |
1716 | } |
1717 | |
1718 | /*! |
1719 | \fn bool QAbstractItemView::viewportEvent(QEvent *event) |
1720 | |
1721 | This function is used to handle tool tips, and What's |
1722 | This? mode, if the given \a event is a QEvent::ToolTip,or a |
1723 | QEvent::WhatsThis. It passes all other |
1724 | events on to its base class viewportEvent() handler. |
1725 | |
1726 | Returns \c true if \a event has been recognized and processed; otherwise, |
1727 | returns \c false. |
1728 | */ |
1729 | bool QAbstractItemView::viewportEvent(QEvent *event) |
1730 | { |
1731 | Q_D(QAbstractItemView); |
1732 | switch (event->type()) { |
1733 | case QEvent::Paint: |
1734 | // Similar to pre-painting in QAbstractItemView::event to update scrollbar |
1735 | // visibility, make sure that all pending layout requests have been executed |
1736 | // so that the view's data structures are up-to-date before rendering. |
1737 | d->executePostedLayout(); |
1738 | break; |
1739 | case QEvent::HoverMove: |
1740 | case QEvent::HoverEnter: |
1741 | d->setHoverIndex(indexAt(point: static_cast<QHoverEvent*>(event)->position().toPoint())); |
1742 | break; |
1743 | case QEvent::HoverLeave: |
1744 | d->setHoverIndex(QModelIndex()); |
1745 | break; |
1746 | case QEvent::Enter: |
1747 | d->viewportEnteredNeeded = true; |
1748 | break; |
1749 | case QEvent::Leave: |
1750 | d->setHoverIndex(QModelIndex()); // If we've left, no hover should be needed anymore |
1751 | #if QT_CONFIG(statustip) |
1752 | if (d->shouldClearStatusTip && d->parent) { |
1753 | QString empty; |
1754 | QStatusTipEvent tip(empty); |
1755 | QCoreApplication::sendEvent(receiver: d->parent, event: &tip); |
1756 | d->shouldClearStatusTip = false; |
1757 | } |
1758 | #endif |
1759 | d->enteredIndex = QModelIndex(); |
1760 | break; |
1761 | case QEvent::ToolTip: |
1762 | case QEvent::QueryWhatsThis: |
1763 | case QEvent::WhatsThis: { |
1764 | QHelpEvent *he = static_cast<QHelpEvent*>(event); |
1765 | const QModelIndex index = indexAt(point: he->pos()); |
1766 | QStyleOptionViewItem option; |
1767 | initViewItemOption(option: &option); |
1768 | option.rect = visualRect(index); |
1769 | option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); |
1770 | |
1771 | QAbstractItemDelegate *delegate = itemDelegateForIndex(index); |
1772 | if (!delegate) |
1773 | return false; |
1774 | return delegate->helpEvent(event: he, view: this, option, index); |
1775 | } |
1776 | case QEvent::FontChange: |
1777 | d->doDelayedItemsLayout(); // the size of the items will change |
1778 | break; |
1779 | case QEvent::WindowActivate: |
1780 | case QEvent::WindowDeactivate: |
1781 | d->viewport->update(); |
1782 | break; |
1783 | case QEvent::ScrollPrepare: |
1784 | executeDelayedItemsLayout(); |
1785 | #if QT_CONFIG(gestures) && QT_CONFIG(scroller) |
1786 | d->scollerConnection = QObjectPrivate::connect( |
1787 | sender: QScroller::scroller(target: d->viewport), signal: &QScroller::stateChanged, |
1788 | receiverPrivate: d, slot: &QAbstractItemViewPrivate::scrollerStateChanged, |
1789 | type: Qt::UniqueConnection); |
1790 | #endif |
1791 | break; |
1792 | |
1793 | default: |
1794 | break; |
1795 | } |
1796 | return QAbstractScrollArea::viewportEvent(event); |
1797 | } |
1798 | |
1799 | /*! |
1800 | This function is called with the given \a event when a mouse button is pressed |
1801 | while the cursor is inside the widget. If a valid item is pressed on it is made |
1802 | into the current item. This function emits the pressed() signal. |
1803 | */ |
1804 | void QAbstractItemView::mousePressEvent(QMouseEvent *event) |
1805 | { |
1806 | Q_D(QAbstractItemView); |
1807 | d->releaseFromDoubleClick = false; |
1808 | d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling |
1809 | QPoint pos = event->position().toPoint(); |
1810 | QPersistentModelIndex index = indexAt(point: pos); |
1811 | |
1812 | // this is the mouse press event that closed the last editor (via focus event) |
1813 | d->pressClosedEditor = d->pressClosedEditorWatcher.isActive() && d->lastEditedIndex == index; |
1814 | |
1815 | if (!d->selectionModel || (d->state == EditingState && d->hasEditor(index))) |
1816 | return; |
1817 | |
1818 | d->pressedAlreadySelected = d->selectionModel->isSelected(index); |
1819 | d->pressedIndex = index; |
1820 | d->pressedModifiers = event->modifiers(); |
1821 | QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); |
1822 | d->noSelectionOnMousePress = command == QItemSelectionModel::NoUpdate || !index.isValid(); |
1823 | QPoint offset = d->offset(); |
1824 | d->draggedPosition = pos + offset; |
1825 | |
1826 | #if QT_CONFIG(draganddrop) |
1827 | // update the pressed position when drag was enable |
1828 | if (d->dragEnabled) |
1829 | d->pressedPosition = d->draggedPosition; |
1830 | #endif |
1831 | |
1832 | if (!(command & QItemSelectionModel::Current)) { |
1833 | d->pressedPosition = pos + offset; |
1834 | d->currentSelectionStartIndex = index; |
1835 | } |
1836 | else if (!d->currentSelectionStartIndex.isValid()) |
1837 | d->currentSelectionStartIndex = currentIndex(); |
1838 | |
1839 | if (edit(index, trigger: NoEditTriggers, event)) |
1840 | return; |
1841 | |
1842 | if (index.isValid() && d->isIndexEnabled(index)) { |
1843 | // we disable scrollTo for mouse press so the item doesn't change position |
1844 | // when the user is interacting with it (ie. clicking on it) |
1845 | bool autoScroll = d->autoScroll; |
1846 | d->autoScroll = false; |
1847 | d->selectionModel->setCurrentIndex(index, command: QItemSelectionModel::NoUpdate); |
1848 | d->autoScroll = autoScroll; |
1849 | if (command.testFlag(flag: QItemSelectionModel::Toggle)) { |
1850 | command &= ~QItemSelectionModel::Toggle; |
1851 | d->ctrlDragSelectionFlag = d->selectionModel->isSelected(index) ? QItemSelectionModel::Deselect : QItemSelectionModel::Select; |
1852 | command |= d->ctrlDragSelectionFlag; |
1853 | } |
1854 | |
1855 | if (!(command & QItemSelectionModel::Current)) { |
1856 | setSelection(rect: QRect(pos, QSize(1, 1)), command); |
1857 | } else { |
1858 | QRect rect(visualRect(index: d->currentSelectionStartIndex).center(), pos); |
1859 | setSelection(rect, command); |
1860 | } |
1861 | |
1862 | // signal handlers may change the model |
1863 | emit pressed(index); |
1864 | if (d->autoScroll) { |
1865 | //we delay the autoscrolling to filter out double click event |
1866 | //100 is to be sure that there won't be a double-click misinterpreted as a 2 single clicks |
1867 | d->delayedAutoScroll.start(msec: QApplication::doubleClickInterval()+100, obj: this); |
1868 | } |
1869 | |
1870 | } else { |
1871 | // Forces a finalize() even if mouse is pressed, but not on a item |
1872 | d->selectionModel->select(index: QModelIndex(), command: QItemSelectionModel::Select); |
1873 | } |
1874 | } |
1875 | |
1876 | /*! |
1877 | This function is called with the given \a event when a mouse move event is |
1878 | sent to the widget. If a selection is in progress and new items are moved |
1879 | over the selection is extended; if a drag is in progress it is continued. |
1880 | */ |
1881 | void QAbstractItemView::mouseMoveEvent(QMouseEvent *event) |
1882 | { |
1883 | Q_D(QAbstractItemView); |
1884 | QPoint bottomRight = event->position().toPoint(); |
1885 | |
1886 | d->draggedPosition = bottomRight + d->offset(); |
1887 | |
1888 | if (state() == ExpandingState || state() == CollapsingState) |
1889 | return; |
1890 | |
1891 | #if QT_CONFIG(draganddrop) |
1892 | if (state() == DraggingState) { |
1893 | d->maybeStartDrag(eventPoint: bottomRight); |
1894 | return; |
1895 | } |
1896 | #endif // QT_CONFIG(draganddrop) |
1897 | |
1898 | QPersistentModelIndex index = indexAt(point: bottomRight); |
1899 | QModelIndex buddy = d->model->buddy(index: d->pressedIndex); |
1900 | if ((state() == EditingState && d->hasEditor(index: buddy)) |
1901 | || edit(index, trigger: NoEditTriggers, event)) |
1902 | return; |
1903 | |
1904 | const QPoint topLeft = |
1905 | d->selectionMode != SingleSelection ? d->pressedPosition - d->offset() : bottomRight; |
1906 | |
1907 | d->checkMouseMove(index); |
1908 | |
1909 | #if QT_CONFIG(draganddrop) |
1910 | if (d->pressedIndex.isValid() |
1911 | && d->dragEnabled |
1912 | && (state() != DragSelectingState) |
1913 | && (event->buttons() != Qt::NoButton) |
1914 | && !d->selectedDraggableIndexes().isEmpty()) { |
1915 | setState(DraggingState); |
1916 | d->maybeStartDrag(eventPoint: bottomRight); |
1917 | return; |
1918 | } |
1919 | #endif |
1920 | |
1921 | if ((event->buttons() & Qt::LeftButton) && d->selectionAllowed(index) && d->selectionModel) { |
1922 | setState(DragSelectingState); |
1923 | QItemSelectionModel::SelectionFlags command = selectionCommand(index, event); |
1924 | if (d->ctrlDragSelectionFlag != QItemSelectionModel::NoUpdate && command.testFlag(flag: QItemSelectionModel::Toggle)) { |
1925 | command &= ~QItemSelectionModel::Toggle; |
1926 | command |= d->ctrlDragSelectionFlag; |
1927 | } |
1928 | |
1929 | // Do the normalize ourselves, since QRect::normalized() is flawed |
1930 | QRect selectionRect = QRect(topLeft, bottomRight); |
1931 | setSelection(rect: selectionRect, command); |
1932 | |
1933 | // set at the end because it might scroll the view |
1934 | if (index.isValid() && (index != d->selectionModel->currentIndex()) && d->isIndexEnabled(index)) |
1935 | d->selectionModel->setCurrentIndex(index, command: QItemSelectionModel::NoUpdate); |
1936 | else if (d->shouldAutoScroll(pos: event->pos()) && !d->autoScrollTimer.isActive()) |
1937 | startAutoScroll(); |
1938 | } |
1939 | } |
1940 | |
1941 | /*! |
1942 | This function is called with the given \a event when a mouse button is released, |
1943 | after a mouse press event on the widget. If a user presses the mouse inside your |
1944 | widget and then drags the mouse to another location before releasing the mouse button, |
1945 | your widget receives the release event. The function will emit the clicked() signal if an |
1946 | item was being pressed. |
1947 | */ |
1948 | void QAbstractItemView::mouseReleaseEvent(QMouseEvent *event) |
1949 | { |
1950 | Q_D(QAbstractItemView); |
1951 | const bool releaseFromDoubleClick = d->releaseFromDoubleClick; |
1952 | d->releaseFromDoubleClick = false; |
1953 | |
1954 | QPoint pos = event->position().toPoint(); |
1955 | QPersistentModelIndex index = indexAt(point: pos); |
1956 | |
1957 | if (state() == EditingState) { |
1958 | if (d->isIndexValid(index) |
1959 | && d->isIndexEnabled(index) |
1960 | && d->sendDelegateEvent(index, event)) |
1961 | update(index); |
1962 | return; |
1963 | } |
1964 | |
1965 | bool click = (index == d->pressedIndex && index.isValid() && !releaseFromDoubleClick); |
1966 | bool selectedClicked = click && d->pressedAlreadySelected |
1967 | && (event->button() == Qt::LeftButton) |
1968 | && (event->modifiers() == Qt::NoModifier); |
1969 | EditTrigger trigger = (selectedClicked ? SelectedClicked : NoEditTriggers); |
1970 | const bool edited = click && !d->pressClosedEditor ? edit(index, trigger, event) : false; |
1971 | |
1972 | d->ctrlDragSelectionFlag = QItemSelectionModel::NoUpdate; |
1973 | |
1974 | if (d->selectionModel && d->noSelectionOnMousePress) { |
1975 | d->noSelectionOnMousePress = false; |
1976 | if (!d->pressClosedEditor) |
1977 | d->selectionModel->select(index, command: selectionCommand(index, event)); |
1978 | } |
1979 | |
1980 | d->pressClosedEditor = false; |
1981 | setState(NoState); |
1982 | |
1983 | if (click) { |
1984 | if (event->button() == Qt::LeftButton) |
1985 | emit clicked(index); |
1986 | if (edited) |
1987 | return; |
1988 | QStyleOptionViewItem option; |
1989 | initViewItemOption(option: &option); |
1990 | if (d->pressedAlreadySelected) |
1991 | option.state |= QStyle::State_Selected; |
1992 | if ((d->model->flags(index) & Qt::ItemIsEnabled) |
1993 | && style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick, opt: &option, widget: this)) |
1994 | emit activated(index); |
1995 | } |
1996 | } |
1997 | |
1998 | /*! |
1999 | This function is called with the given \a event when a mouse button is |
2000 | double clicked inside the widget. If the double-click is on a valid item it |
2001 | emits the doubleClicked() signal and calls edit() on the item. |
2002 | */ |
2003 | void QAbstractItemView::mouseDoubleClickEvent(QMouseEvent *event) |
2004 | { |
2005 | Q_D(QAbstractItemView); |
2006 | |
2007 | QModelIndex index = indexAt(point: event->position().toPoint()); |
2008 | if (!index.isValid() |
2009 | || !d->isIndexEnabled(index) |
2010 | || (d->pressedIndex != index)) { |
2011 | QMouseEvent me(QEvent::MouseButtonPress, |
2012 | event->position(), event->scenePosition(), event->globalPosition(), |
2013 | event->button(), event->buttons(), event->modifiers(), |
2014 | event->source(), event->pointingDevice()); |
2015 | mousePressEvent(event: &me); |
2016 | return; |
2017 | } |
2018 | // signal handlers may change the model |
2019 | QPersistentModelIndex persistent = index; |
2020 | emit doubleClicked(index: persistent); |
2021 | if ((event->button() == Qt::LeftButton) && !edit(index: persistent, trigger: DoubleClicked, event) |
2022 | && !style()->styleHint(stylehint: QStyle::SH_ItemView_ActivateItemOnSingleClick, opt: nullptr, widget: this)) |
2023 | emit activated(index: persistent); |
2024 | d->releaseFromDoubleClick = true; |
2025 | } |
2026 | |
2027 | #if QT_CONFIG(draganddrop) |
2028 | |
2029 | /*! |
2030 | This function is called with the given \a event when a drag and drop operation enters |
2031 | the widget. If the drag is over a valid dropping place (e.g. over an item that |
2032 | accepts drops), the event is accepted; otherwise it is ignored. |
2033 | |
2034 | \sa dropEvent(), startDrag() |
2035 | */ |
2036 | void QAbstractItemView::dragEnterEvent(QDragEnterEvent *event) |
2037 | { |
2038 | if (dragDropMode() == InternalMove |
2039 | && (event->source() != this|| !(event->possibleActions() & Qt::MoveAction))) |
2040 | return; |
2041 | |
2042 | if (d_func()->canDrop(event)) { |
2043 | event->accept(); |
2044 | setState(DraggingState); |
2045 | } else { |
2046 | event->ignore(); |
2047 | } |
2048 | } |
2049 | |
2050 | /*! |
2051 | This function is called continuously with the given \a event during a drag and |
2052 | drop operation over the widget. It can cause the view to scroll if, for example, |
2053 | the user drags a selection to view's right or bottom edge. In this case, the |
2054 | event will be accepted; otherwise it will be ignored. |
2055 | |
2056 | \sa dropEvent(), startDrag() |
2057 | */ |
2058 | void QAbstractItemView::dragMoveEvent(QDragMoveEvent *event) |
2059 | { |
2060 | Q_D(QAbstractItemView); |
2061 | d->draggedPosition = event->position().toPoint() + d->offset(); |
2062 | if (dragDropMode() == InternalMove |
2063 | && (event->source() != this || !(event->possibleActions() & Qt::MoveAction))) |
2064 | return; |
2065 | |
2066 | // ignore by default |
2067 | event->ignore(); |
2068 | |
2069 | QModelIndex index = indexAt(point: event->position().toPoint()); |
2070 | d->hover = index; |
2071 | if (!d->droppingOnItself(event, index) |
2072 | && d->canDrop(event)) { |
2073 | |
2074 | if (index.isValid() && d->showDropIndicator) { |
2075 | QRect rect = visualRect(index); |
2076 | d->dropIndicatorPosition = d->position(pos: event->position().toPoint(), rect, idx: index); |
2077 | if (d->selectionBehavior == QAbstractItemView::SelectRows |
2078 | && d->dropIndicatorPosition != OnViewport |
2079 | && (d->dropIndicatorPosition != OnItem || event->source() == this)) { |
2080 | if (index.column() > 0) |
2081 | rect = visualRect(index: index.siblingAtColumn(acolumn: 0)); |
2082 | rect.setWidth(viewport()->width() - 1 - rect.x()); |
2083 | } |
2084 | switch (d->dropIndicatorPosition) { |
2085 | case AboveItem: |
2086 | if (d->isIndexDropEnabled(index: index.parent())) { |
2087 | d->dropIndicatorRect = QRect(rect.left(), rect.top(), rect.width(), 0); |
2088 | event->acceptProposedAction(); |
2089 | } else { |
2090 | d->dropIndicatorRect = QRect(); |
2091 | } |
2092 | break; |
2093 | case BelowItem: |
2094 | if (d->isIndexDropEnabled(index: index.parent())) { |
2095 | d->dropIndicatorRect = QRect(rect.left(), rect.bottom(), rect.width(), 0); |
2096 | event->acceptProposedAction(); |
2097 | } else { |
2098 | d->dropIndicatorRect = QRect(); |
2099 | } |
2100 | break; |
2101 | case OnItem: |
2102 | if (d->isIndexDropEnabled(index)) { |
2103 | d->dropIndicatorRect = rect; |
2104 | event->acceptProposedAction(); |
2105 | } else { |
2106 | d->dropIndicatorRect = QRect(); |
2107 | } |
2108 | break; |
2109 | case OnViewport: |
2110 | d->dropIndicatorRect = QRect(); |
2111 | if (d->isIndexDropEnabled(index: rootIndex())) { |
2112 | event->acceptProposedAction(); // allow dropping in empty areas |
2113 | } |
2114 | break; |
2115 | } |
2116 | } else { |
2117 | d->dropIndicatorRect = QRect(); |
2118 | d->dropIndicatorPosition = OnViewport; |
2119 | if (d->isIndexDropEnabled(index: rootIndex())) { |
2120 | event->acceptProposedAction(); // allow dropping in empty areas |
2121 | } |
2122 | } |
2123 | d->viewport->update(); |
2124 | } // can drop |
2125 | |
2126 | if (d->shouldAutoScroll(pos: event->position().toPoint())) |
2127 | startAutoScroll(); |
2128 | } |
2129 | |
2130 | /*! |
2131 | \internal |
2132 | Return true if this is a move from ourself and \a index is a child of the selection that |
2133 | is being moved. |
2134 | */ |
2135 | bool QAbstractItemViewPrivate::droppingOnItself(QDropEvent *event, const QModelIndex &index) |
2136 | { |
2137 | Q_Q(QAbstractItemView); |
2138 | Qt::DropAction dropAction = event->dropAction(); |
2139 | if (q->dragDropMode() == QAbstractItemView::InternalMove) |
2140 | dropAction = Qt::MoveAction; |
2141 | if (event->source() == q |
2142 | && event->possibleActions() & Qt::MoveAction |
2143 | && dropAction == Qt::MoveAction) { |
2144 | QModelIndexList selectedIndexes = q->selectedIndexes(); |
2145 | QModelIndex child = index; |
2146 | while (child.isValid() && child != root) { |
2147 | if (selectedIndexes.contains(t: child)) |
2148 | return true; |
2149 | child = child.parent(); |
2150 | } |
2151 | } |
2152 | return false; |
2153 | } |
2154 | |
2155 | /*! |
2156 | \fn void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *event) |
2157 | |
2158 | This function is called when the item being dragged leaves the view. |
2159 | The \a event describes the state of the drag and drop operation. |
2160 | */ |
2161 | void QAbstractItemView::dragLeaveEvent(QDragLeaveEvent *) |
2162 | { |
2163 | Q_D(QAbstractItemView); |
2164 | stopAutoScroll(); |
2165 | setState(NoState); |
2166 | d->hover = QModelIndex(); |
2167 | d->viewport->update(); |
2168 | } |
2169 | |
2170 | /*! |
2171 | This function is called with the given \a event when a drop event occurs over |
2172 | the widget. If the model accepts the even position the drop event is accepted; |
2173 | otherwise it is ignored. |
2174 | |
2175 | \sa startDrag() |
2176 | */ |
2177 | void QAbstractItemView::dropEvent(QDropEvent *event) |
2178 | { |
2179 | Q_D(QAbstractItemView); |
2180 | if (dragDropMode() == InternalMove) { |
2181 | if (event->source() != this || !(event->possibleActions() & Qt::MoveAction)) |
2182 | return; |
2183 | } |
2184 | |
2185 | QModelIndex index; |
2186 | int col = -1; |
2187 | int row = -1; |
2188 | if (d->dropOn(event, row: &row, col: &col, index: &index)) { |
2189 | const Qt::DropAction action = dragDropMode() == InternalMove ? Qt::MoveAction : event->dropAction(); |
2190 | if (d->model->dropMimeData(data: event->mimeData(), action, row, column: col, parent: index)) { |
2191 | if (action != event->dropAction()) { |
2192 | event->setDropAction(action); |
2193 | event->accept(); |
2194 | } else { |
2195 | event->acceptProposedAction(); |
2196 | } |
2197 | } |
2198 | } |
2199 | stopAutoScroll(); |
2200 | setState(NoState); |
2201 | d->viewport->update(); |
2202 | } |
2203 | |
2204 | /*! |
2205 | If the event hasn't already been accepted, determines the index to drop on. |
2206 | |
2207 | if (row == -1 && col == -1) |
2208 | // append to this drop index |
2209 | else |
2210 | // place at row, col in drop index |
2211 | |
2212 | If it returns \c true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop. |
2213 | \internal |
2214 | */ |
2215 | bool QAbstractItemViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) |
2216 | { |
2217 | Q_Q(QAbstractItemView); |
2218 | if (event->isAccepted()) |
2219 | return false; |
2220 | |
2221 | QModelIndex index; |
2222 | // rootIndex() (i.e. the viewport) might be a valid index |
2223 | if (viewport->rect().contains(p: event->position().toPoint())) { |
2224 | index = q->indexAt(point: event->position().toPoint()); |
2225 | if (!index.isValid()) |
2226 | index = root; |
2227 | } |
2228 | |
2229 | // If we are allowed to do the drop |
2230 | if (model->supportedDropActions() & event->dropAction()) { |
2231 | int row = -1; |
2232 | int col = -1; |
2233 | if (index != root) { |
2234 | dropIndicatorPosition = position(pos: event->position().toPoint(), rect: q->visualRect(index), idx: index); |
2235 | switch (dropIndicatorPosition) { |
2236 | case QAbstractItemView::AboveItem: |
2237 | row = index.row(); |
2238 | col = index.column(); |
2239 | index = index.parent(); |
2240 | break; |
2241 | case QAbstractItemView::BelowItem: |
2242 | row = index.row() + 1; |
2243 | col = index.column(); |
2244 | index = index.parent(); |
2245 | break; |
2246 | case QAbstractItemView::OnItem: |
2247 | case QAbstractItemView::OnViewport: |
2248 | break; |
2249 | } |
2250 | } else { |
2251 | dropIndicatorPosition = QAbstractItemView::OnViewport; |
2252 | } |
2253 | *dropIndex = index; |
2254 | *dropRow = row; |
2255 | *dropCol = col; |
2256 | if (!droppingOnItself(event, index)) |
2257 | return true; |
2258 | } |
2259 | return false; |
2260 | } |
2261 | |
2262 | QAbstractItemView::DropIndicatorPosition |
2263 | QAbstractItemViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const |
2264 | { |
2265 | QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; |
2266 | if (!overwrite) { |
2267 | const int margin = qBound(min: 2, val: qRound(d: qreal(rect.height()) / 5.5), max: 12); |
2268 | if (pos.y() - rect.top() < margin) { |
2269 | r = QAbstractItemView::AboveItem; |
2270 | } else if (rect.bottom() - pos.y() < margin) { |
2271 | r = QAbstractItemView::BelowItem; |
2272 | } else if (rect.contains(p: pos, proper: true)) { |
2273 | r = QAbstractItemView::OnItem; |
2274 | } |
2275 | } else { |
2276 | QRect touchingRect = rect; |
2277 | touchingRect.adjust(dx1: -1, dy1: -1, dx2: 1, dy2: 1); |
2278 | if (touchingRect.contains(p: pos, proper: false)) { |
2279 | r = QAbstractItemView::OnItem; |
2280 | } |
2281 | } |
2282 | |
2283 | if (r == QAbstractItemView::OnItem && (!(model->flags(index) & Qt::ItemIsDropEnabled))) |
2284 | r = pos.y() < rect.center().y() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; |
2285 | |
2286 | return r; |
2287 | } |
2288 | |
2289 | #endif // QT_CONFIG(draganddrop) |
2290 | |
2291 | /*! |
2292 | This function is called with the given \a event when the widget obtains the focus. |
2293 | By default, the event is ignored. |
2294 | |
2295 | \sa setFocus(), focusOutEvent() |
2296 | */ |
2297 | void QAbstractItemView::focusInEvent(QFocusEvent *event) |
2298 | { |
2299 | Q_D(QAbstractItemView); |
2300 | QAbstractScrollArea::focusInEvent(event); |
2301 | |
2302 | const QItemSelectionModel* model = selectionModel(); |
2303 | bool currentIndexValid = currentIndex().isValid(); |
2304 | |
2305 | if (model |
2306 | && !d->currentIndexSet |
2307 | && !currentIndexValid) { |
2308 | bool autoScroll = d->autoScroll; |
2309 | d->autoScroll = false; |
2310 | QModelIndex index = moveCursor(cursorAction: MoveNext, modifiers: Qt::NoModifier); // first visible index |
2311 | if (index.isValid() && d->isIndexEnabled(index) && event->reason() != Qt::MouseFocusReason) { |
2312 | selectionModel()->setCurrentIndex(index, command: QItemSelectionModel::NoUpdate); |
2313 | currentIndexValid = true; |
2314 | } |
2315 | d->autoScroll = autoScroll; |
2316 | } |
2317 | |
2318 | if (model && currentIndexValid) |
2319 | setAttribute(Qt::WA_InputMethodEnabled, on: (currentIndex().flags() & Qt::ItemIsEditable)); |
2320 | else if (!currentIndexValid) |
2321 | setAttribute(Qt::WA_InputMethodEnabled, on: false); |
2322 | |
2323 | d->viewport->update(); |
2324 | } |
2325 | |
2326 | /*! |
2327 | This function is called with the given \a event when the widget |
2328 | loses the focus. By default, the event is ignored. |
2329 | |
2330 | \sa clearFocus(), focusInEvent() |
2331 | */ |
2332 | void QAbstractItemView::focusOutEvent(QFocusEvent *event) |
2333 | { |
2334 | Q_D(QAbstractItemView); |
2335 | QAbstractScrollArea::focusOutEvent(event); |
2336 | d->viewport->update(); |
2337 | } |
2338 | |
2339 | /*! |
2340 | This function is called with the given \a event when a key event is sent to |
2341 | the widget. The default implementation handles basic cursor movement, e.g. Up, |
2342 | Down, Left, Right, Home, PageUp, and PageDown; the activated() signal is |
2343 | emitted if the current index is valid and the activation key is pressed |
2344 | (e.g. Enter or Return, depending on the platform). |
2345 | This function is where editing is initiated by key press, e.g. if F2 is |
2346 | pressed. |
2347 | |
2348 | \sa edit(), moveCursor(), keyboardSearch(), tabKeyNavigation |
2349 | */ |
2350 | void QAbstractItemView::keyPressEvent(QKeyEvent *event) |
2351 | { |
2352 | Q_D(QAbstractItemView); |
2353 | d->delayedAutoScroll.stop(); //any interaction with the view cancel the auto scrolling |
2354 | |
2355 | #ifdef QT_KEYPAD_NAVIGATION |
2356 | switch (event->key()) { |
2357 | case Qt::Key_Select: |
2358 | if (QApplicationPrivate::keypadNavigationEnabled()) { |
2359 | if (!hasEditFocus()) { |
2360 | setEditFocus(true); |
2361 | return; |
2362 | } |
2363 | } |
2364 | break; |
2365 | case Qt::Key_Back: |
2366 | if (QApplicationPrivate::keypadNavigationEnabled() && hasEditFocus()) { |
2367 | setEditFocus(false); |
2368 | } else { |
2369 | event->ignore(); |
2370 | } |
2371 | return; |
2372 | case Qt::Key_Down: |
2373 | case Qt::Key_Up: |
2374 | // Let's ignore vertical navigation events, only if there is no other widget |
2375 | // what can take the focus in vertical direction. This means widget can handle navigation events |
2376 | // even the widget don't have edit focus, and there is no other widget in requested direction. |
2377 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus() |
2378 | && QWidgetPrivate::canKeypadNavigate(Qt::Vertical)) { |
2379 | event->ignore(); |
2380 | return; |
2381 | } |
2382 | break; |
2383 | case Qt::Key_Left: |
2384 | case Qt::Key_Right: |
2385 | // Similar logic as in up and down events |
2386 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus() |
2387 | && (QWidgetPrivate::canKeypadNavigate(Qt::Horizontal) || QWidgetPrivate::inTabWidget(this))) { |
2388 | event->ignore(); |
2389 | return; |
2390 | } |
2391 | break; |
2392 | default: |
2393 | if (QApplicationPrivate::keypadNavigationEnabled() && !hasEditFocus()) { |
2394 | event->ignore(); |
2395 | return; |
2396 | } |
2397 | } |
2398 | #endif |
2399 | |
2400 | #if !defined(QT_NO_CLIPBOARD) && !defined(QT_NO_SHORTCUT) |
2401 | if (event == QKeySequence::Copy) { |
2402 | const QModelIndex index = currentIndex(); |
2403 | if (index.isValid() && d->model) { |
2404 | const QVariant variant = d->model->data(index, role: Qt::DisplayRole); |
2405 | if (variant.canConvert<QString>()) |
2406 | QGuiApplication::clipboard()->setText(variant.toString()); |
2407 | } |
2408 | event->accept(); |
2409 | } |
2410 | #endif |
2411 | |
2412 | QPersistentModelIndex newCurrent; |
2413 | d->moveCursorUpdatedView = false; |
2414 | switch (event->key()) { |
2415 | case Qt::Key_Down: |
2416 | newCurrent = moveCursor(cursorAction: MoveDown, modifiers: event->modifiers()); |
2417 | break; |
2418 | case Qt::Key_Up: |
2419 | newCurrent = moveCursor(cursorAction: MoveUp, modifiers: event->modifiers()); |
2420 | break; |
2421 | case Qt::Key_Left: |
2422 | newCurrent = moveCursor(cursorAction: MoveLeft, modifiers: event->modifiers()); |
2423 | break; |
2424 | case Qt::Key_Right: |
2425 | newCurrent = moveCursor(cursorAction: MoveRight, modifiers: event->modifiers()); |
2426 | break; |
2427 | case Qt::Key_Home: |
2428 | newCurrent = moveCursor(cursorAction: MoveHome, modifiers: event->modifiers()); |
2429 | break; |
2430 | case Qt::Key_End: |
2431 | newCurrent = moveCursor(cursorAction: MoveEnd, modifiers: event->modifiers()); |
2432 | break; |
2433 | case Qt::Key_PageUp: |
2434 | newCurrent = moveCursor(cursorAction: MovePageUp, modifiers: event->modifiers()); |
2435 | break; |
2436 | case Qt::Key_PageDown: |
2437 | newCurrent = moveCursor(cursorAction: MovePageDown, modifiers: event->modifiers()); |
2438 | break; |
2439 | case Qt::Key_Tab: |
2440 | if (d->tabKeyNavigation) |
2441 | newCurrent = moveCursor(cursorAction: MoveNext, modifiers: event->modifiers()); |
2442 | break; |
2443 | case Qt::Key_Backtab: |
2444 | if (d->tabKeyNavigation) |
2445 | newCurrent = moveCursor(cursorAction: MovePrevious, modifiers: event->modifiers()); |
2446 | break; |
2447 | } |
2448 | |
2449 | QPersistentModelIndex oldCurrent = currentIndex(); |
2450 | if (newCurrent != oldCurrent && newCurrent.isValid() && d->isIndexEnabled(index: newCurrent)) { |
2451 | if (!hasFocus() && QApplication::focusWidget() == indexWidget(index: oldCurrent)) |
2452 | setFocus(); |
2453 | QItemSelectionModel::SelectionFlags command = selectionCommand(index: newCurrent, event); |
2454 | if (command != QItemSelectionModel::NoUpdate |
2455 | || style()->styleHint(stylehint: QStyle::SH_ItemView_MovementWithoutUpdatingSelection, opt: nullptr, widget: this)) { |
2456 | // note that we don't check if the new current index is enabled because moveCursor() makes sure it is |
2457 | if (command & QItemSelectionModel::Current) { |
2458 | d->selectionModel->setCurrentIndex(index: newCurrent, command: QItemSelectionModel::NoUpdate); |
2459 | if (!d->currentSelectionStartIndex.isValid()) |
2460 | d->currentSelectionStartIndex = oldCurrent; |
2461 | QRect rect(visualRect(index: d->currentSelectionStartIndex).center(), visualRect(index: newCurrent).center()); |
2462 | setSelection(rect, command); |
2463 | } else { |
2464 | d->selectionModel->setCurrentIndex(index: newCurrent, command); |
2465 | d->currentSelectionStartIndex = newCurrent; |
2466 | if (newCurrent.isValid()) { |
2467 | // We copy the same behaviour as for mousePressEvent(). |
2468 | QRect rect(visualRect(index: newCurrent).center(), QSize(1, 1)); |
2469 | setSelection(rect, command); |
2470 | } |
2471 | } |
2472 | event->accept(); |
2473 | return; |
2474 | } |
2475 | } |
2476 | |
2477 | switch (event->key()) { |
2478 | // ignored keys |
2479 | case Qt::Key_Down: |
2480 | case Qt::Key_Up: |
2481 | #ifdef QT_KEYPAD_NAVIGATION |
2482 | if (QApplicationPrivate::keypadNavigationEnabled() |
2483 | && QWidgetPrivate::canKeypadNavigate(Qt::Vertical)) { |
2484 | event->accept(); // don't change focus |
2485 | break; |
2486 | } |
2487 | #endif |
2488 | case Qt::Key_Left: |
2489 | case Qt::Key_Right: |
2490 | #ifdef QT_KEYPAD_NAVIGATION |
2491 | if (QApplication::navigationMode() == Qt::NavigationModeKeypadDirectional |
2492 | && (QWidgetPrivate::canKeypadNavigate(Qt::Horizontal) |
2493 | || (QWidgetPrivate::inTabWidget(this) && d->model->columnCount(d->root) > 1))) { |
2494 | event->accept(); // don't change focus |
2495 | break; |
2496 | } |
2497 | #endif // QT_KEYPAD_NAVIGATION |
2498 | case Qt::Key_Home: |
2499 | case Qt::Key_End: |
2500 | case Qt::Key_PageUp: |
2501 | case Qt::Key_PageDown: |
2502 | case Qt::Key_Escape: |
2503 | case Qt::Key_Shift: |
2504 | case Qt::Key_Control: |
2505 | case Qt::Key_Delete: |
2506 | case Qt::Key_Backspace: |
2507 | event->ignore(); |
2508 | break; |
2509 | case Qt::Key_Space: |
2510 | case Qt::Key_Select: |
2511 | if (!edit(index: currentIndex(), trigger: AnyKeyPressed, event)) { |
2512 | if (d->selectionModel) |
2513 | d->selectionModel->select(index: currentIndex(), command: selectionCommand(index: currentIndex(), event)); |
2514 | if (event->key() == Qt::Key_Space) { |
2515 | keyboardSearch(search: event->text()); |
2516 | event->accept(); |
2517 | } |
2518 | } |
2519 | #ifdef QT_KEYPAD_NAVIGATION |
2520 | if ( event->key()==Qt::Key_Select ) { |
2521 | // Also do Key_Enter action. |
2522 | if (currentIndex().isValid()) { |
2523 | if (state() != EditingState) |
2524 | emit activated(currentIndex()); |
2525 | } else { |
2526 | event->ignore(); |
2527 | } |
2528 | } |
2529 | #endif |
2530 | break; |
2531 | #ifdef Q_OS_MACOS |
2532 | case Qt::Key_Enter: |
2533 | case Qt::Key_Return: |
2534 | // Propagate the enter if you couldn't edit the item and there are no |
2535 | // current editors (if there are editors, the event was most likely propagated from it). |
2536 | if (!edit(currentIndex(), EditKeyPressed, event) && d->editorIndexHash.isEmpty()) |
2537 | event->ignore(); |
2538 | break; |
2539 | #else |
2540 | case Qt::Key_F2: |
2541 | if (!edit(index: currentIndex(), trigger: EditKeyPressed, event)) |
2542 | event->ignore(); |
2543 | break; |
2544 | case Qt::Key_Enter: |
2545 | case Qt::Key_Return: |
2546 | // ### we can't open the editor on enter, because |
2547 | // some widgets will forward the enter event back |
2548 | // to the viewport, starting an endless loop |
2549 | if (state() != EditingState || hasFocus()) { |
2550 | if (currentIndex().isValid()) |
2551 | emit activated(index: currentIndex()); |
2552 | event->ignore(); |
2553 | } |
2554 | break; |
2555 | #endif |
2556 | default: { |
2557 | #ifndef QT_NO_SHORTCUT |
2558 | if (event == QKeySequence::SelectAll && selectionMode() != NoSelection) { |
2559 | selectAll(); |
2560 | break; |
2561 | } |
2562 | #endif |
2563 | #ifdef Q_OS_MACOS |
2564 | if (event->key() == Qt::Key_O && event->modifiers() & Qt::ControlModifier && currentIndex().isValid()) { |
2565 | emit activated(currentIndex()); |
2566 | break; |
2567 | } |
2568 | #endif |
2569 | bool modified = (event->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)); |
2570 | if (!event->text().isEmpty() && !modified && !edit(index: currentIndex(), trigger: AnyKeyPressed, event)) { |
2571 | keyboardSearch(search: event->text()); |
2572 | event->accept(); |
2573 | } else { |
2574 | event->ignore(); |
2575 | } |
2576 | break; } |
2577 | } |
2578 | if (d->moveCursorUpdatedView) |
2579 | event->accept(); |
2580 | } |
2581 | |
2582 | /*! |
2583 | This function is called with the given \a event when a resize event is sent to |
2584 | the widget. |
2585 | |
2586 | \sa QWidget::resizeEvent() |
2587 | */ |
2588 | void QAbstractItemView::resizeEvent(QResizeEvent *event) |
2589 | { |
2590 | QAbstractScrollArea::resizeEvent(event); |
2591 | updateGeometries(); |
2592 | } |
2593 | |
2594 | /*! |
2595 | This function is called with the given \a event when a timer event is sent |
2596 | to the widget. |
2597 | |
2598 | \sa QObject::timerEvent() |
2599 | */ |
2600 | void QAbstractItemView::timerEvent(QTimerEvent *event) |
2601 | { |
2602 | Q_D(QAbstractItemView); |
2603 | if (event->timerId() == d->fetchMoreTimer.timerId()) |
2604 | d->fetchMore(); |
2605 | else if (event->timerId() == d->delayedReset.timerId()) |
2606 | reset(); |
2607 | else if (event->timerId() == d->autoScrollTimer.timerId()) |
2608 | doAutoScroll(); |
2609 | else if (event->timerId() == d->updateTimer.timerId()) |
2610 | d->updateDirtyRegion(); |
2611 | else if (event->timerId() == d->delayedEditing.timerId()) { |
2612 | d->delayedEditing.stop(); |
2613 | edit(index: currentIndex()); |
2614 | } else if (event->timerId() == d->delayedLayout.timerId()) { |
2615 | d->delayedLayout.stop(); |
2616 | if (isVisible()) { |
2617 | d->interruptDelayedItemsLayout(); |
2618 | doItemsLayout(); |
2619 | const QModelIndex current = currentIndex(); |
2620 | if (current.isValid() && d->state == QAbstractItemView::EditingState) |
2621 | scrollTo(index: current); |
2622 | } |
2623 | } else if (event->timerId() == d->delayedAutoScroll.timerId()) { |
2624 | d->delayedAutoScroll.stop(); |
2625 | //end of the timer: if the current item is still the same as the one when the mouse press occurred |
2626 | //we only get here if there was no double click |
2627 | if (d->pressedIndex.isValid() && d->pressedIndex == currentIndex()) |
2628 | scrollTo(index: d->pressedIndex); |
2629 | } else if (event->timerId() == d->pressClosedEditorWatcher.timerId()) { |
2630 | d->pressClosedEditorWatcher.stop(); |
2631 | } |
2632 | } |
2633 | |
2634 | /*! |
2635 | \reimp |
2636 | */ |
2637 | void QAbstractItemView::inputMethodEvent(QInputMethodEvent *event) |
2638 | { |
2639 | Q_D(QAbstractItemView); |
2640 | // When QAbstractItemView::AnyKeyPressed is used, a new IM composition might |
2641 | // start before the editor widget acquires focus. Changing focus would interrupt |
2642 | // the composition, so we keep focus on the view until that first composition |
2643 | // is complete, and pass QInputMethoEvents on to the editor widget so that the |
2644 | // user gets the expected feedback. See also inputMethodQuery, which redirects |
2645 | // calls to the editor widget during that period. |
2646 | bool forwardEventToEditor = false; |
2647 | const bool commit = !event->commitString().isEmpty(); |
2648 | const bool preediting = !event->preeditString().isEmpty(); |
2649 | if (QWidget *currentEditor = d->editorForIndex(index: currentIndex()).widget) { |
2650 | if (d->waitForIMCommit) { |
2651 | if (commit || !preediting) { |
2652 | // commit or cancel |
2653 | d->waitForIMCommit = false; |
2654 | QApplication::sendEvent(receiver: currentEditor, event); |
2655 | if (!commit) { |
2656 | QAbstractItemDelegate *delegate = itemDelegateForIndex(index: currentIndex()); |
2657 | if (delegate) |
2658 | delegate->setEditorData(editor: currentEditor, index: currentIndex()); |
2659 | d->selectAllInEditor(w: currentEditor); |
2660 | } |
2661 | if (currentEditor->focusPolicy() != Qt::NoFocus) |
2662 | currentEditor->setFocus(); |
2663 | } else { |
2664 | // more pre-editing |
2665 | QApplication::sendEvent(receiver: currentEditor, event); |
2666 | } |
2667 | return; |
2668 | } |
2669 | } else if (preediting) { |
2670 | // don't set focus when the editor opens |
2671 | d->waitForIMCommit = true; |
2672 | // but pass preedit on to editor |
2673 | forwardEventToEditor = true; |
2674 | } else if (!commit) { |
2675 | event->ignore(); |
2676 | return; |
2677 | } |
2678 | if (!edit(index: currentIndex(), trigger: AnyKeyPressed, event)) { |
2679 | d->waitForIMCommit = false; |
2680 | if (commit) |
2681 | keyboardSearch(search: event->commitString()); |
2682 | event->ignore(); |
2683 | } else if (QWidget *currentEditor; forwardEventToEditor |
2684 | && (currentEditor = d->editorForIndex(index: currentIndex()).widget)) { |
2685 | QApplication::sendEvent(receiver: currentEditor, event); |
2686 | } |
2687 | } |
2688 | |
2689 | #if QT_CONFIG(draganddrop) |
2690 | /*! |
2691 | \enum QAbstractItemView::DropIndicatorPosition |
2692 | |
2693 | This enum indicates the position of the drop indicator in |
2694 | relation to the index at the current mouse position: |
2695 | |
2696 | \value OnItem The item will be dropped on the index. |
2697 | |
2698 | \value AboveItem The item will be dropped above the index. |
2699 | |
2700 | \value BelowItem The item will be dropped below the index. |
2701 | |
2702 | \value OnViewport The item will be dropped onto a region of the viewport with |
2703 | no items. The way each view handles items dropped onto the viewport depends on |
2704 | the behavior of the underlying model in use. |
2705 | */ |
2706 | |
2707 | |
2708 | /*! |
2709 | \since 4.1 |
2710 | |
2711 | Returns the position of the drop indicator in relation to the closest item. |
2712 | */ |
2713 | QAbstractItemView::DropIndicatorPosition QAbstractItemView::dropIndicatorPosition() const |
2714 | { |
2715 | Q_D(const QAbstractItemView); |
2716 | return d->dropIndicatorPosition; |
2717 | } |
2718 | #endif |
2719 | |
2720 | /*! |
2721 | This convenience function returns a list of all selected and |
2722 | non-hidden item indexes in the view. The list contains no |
2723 | duplicates, and is not sorted. |
2724 | |
2725 | \sa QItemSelectionModel::selectedIndexes() |
2726 | */ |
2727 | QModelIndexList QAbstractItemView::selectedIndexes() const |
2728 | { |
2729 | Q_D(const QAbstractItemView); |
2730 | QModelIndexList indexes; |
2731 | if (d->selectionModel) { |
2732 | indexes = d->selectionModel->selectedIndexes(); |
2733 | auto isHidden = [this](const QModelIndex &idx) { |
2734 | return isIndexHidden(index: idx); |
2735 | }; |
2736 | indexes.removeIf(pred: isHidden); |
2737 | } |
2738 | return indexes; |
2739 | } |
2740 | |
2741 | /*! |
2742 | Starts editing the item at \a index, creating an editor if |
2743 | necessary, and returns \c true if the view's \l{State} is now |
2744 | EditingState; otherwise returns \c false. |
2745 | |
2746 | The action that caused the editing process is described by |
2747 | \a trigger, and the associated event is specified by \a event. |
2748 | |
2749 | Editing can be forced by specifying the \a trigger to be |
2750 | QAbstractItemView::AllEditTriggers. |
2751 | |
2752 | \sa closeEditor() |
2753 | */ |
2754 | bool QAbstractItemView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) |
2755 | { |
2756 | Q_D(QAbstractItemView); |
2757 | |
2758 | if (!d->isIndexValid(index)) |
2759 | return false; |
2760 | |
2761 | if (QWidget *w = (d->persistent.isEmpty() ? static_cast<QWidget*>(nullptr) : d->editorForIndex(index).widget.data())) { |
2762 | if (w->focusPolicy() == Qt::NoFocus) |
2763 | return false; |
2764 | if (!d->waitForIMCommit) |
2765 | w->setFocus(); |
2766 | else |
2767 | updateMicroFocus(); |
2768 | return true; |
2769 | } |
2770 | |
2771 | if (trigger == DoubleClicked) { |
2772 | d->delayedEditing.stop(); |
2773 | d->delayedAutoScroll.stop(); |
2774 | } else if (trigger == CurrentChanged) { |
2775 | d->delayedEditing.stop(); |
2776 | } |
2777 | |
2778 | // in case e.g. setData() triggers a reset() |
2779 | QPersistentModelIndex safeIndex(index); |
2780 | |
2781 | if (d->sendDelegateEvent(index, event)) { |
2782 | update(index: safeIndex); |
2783 | return true; |
2784 | } |
2785 | |
2786 | if (!safeIndex.isValid()) { |
2787 | return false; |
2788 | } |
2789 | |
2790 | // save the previous trigger before updating |
2791 | EditTriggers lastTrigger = d->lastTrigger; |
2792 | d->lastTrigger = trigger; |
2793 | |
2794 | if (!d->shouldEdit(trigger, index: d->model->buddy(index: safeIndex))) |
2795 | return false; |
2796 | |
2797 | if (d->delayedEditing.isActive()) |
2798 | return false; |
2799 | |
2800 | // we will receive a mouseButtonReleaseEvent after a |
2801 | // mouseDoubleClickEvent, so we need to check the previous trigger |
2802 | if (lastTrigger == DoubleClicked && trigger == SelectedClicked) |
2803 | return false; |
2804 | |
2805 | // we may get a double click event later |
2806 | if (trigger == SelectedClicked) |
2807 | d->delayedEditing.start(msec: QApplication::doubleClickInterval(), obj: this); |
2808 | else |
2809 | d->openEditor(index: safeIndex, event: d->shouldForwardEvent(trigger, event) ? event : nullptr); |
2810 | |
2811 | return true; |
2812 | } |
2813 | |
2814 | /*! |
2815 | \internal |
2816 | Updates the data shown in the open editor widgets in the view. |
2817 | */ |
2818 | void QAbstractItemView::updateEditorData() |
2819 | { |
2820 | Q_D(QAbstractItemView); |
2821 | d->updateEditorData(topLeft: QModelIndex(), bottomRight: QModelIndex()); |
2822 | } |
2823 | |
2824 | /*! |
2825 | \internal |
2826 | Updates the geometry of the open editor widgets in the view. |
2827 | */ |
2828 | void QAbstractItemView::updateEditorGeometries() |
2829 | { |
2830 | Q_D(QAbstractItemView); |
2831 | if (d->editorIndexHash.isEmpty()) |
2832 | return; |
2833 | if (d->delayedPendingLayout) { |
2834 | // doItemsLayout() will end up calling this function again |
2835 | d->executePostedLayout(); |
2836 | return; |
2837 | } |
2838 | QStyleOptionViewItem option; |
2839 | initViewItemOption(option: &option); |
2840 | QEditorIndexHash::iterator it = d->editorIndexHash.begin(); |
2841 | QWidgetList editorsToRelease; |
2842 | QWidgetList editorsToHide; |
2843 | while (it != d->editorIndexHash.end()) { |
2844 | QModelIndex index = it.value(); |
2845 | QWidget *editor = it.key(); |
2846 | if (index.isValid() && editor) { |
2847 | option.rect = visualRect(index); |
2848 | if (option.rect.isValid()) { |
2849 | editor->show(); |
2850 | QAbstractItemDelegate *delegate = itemDelegateForIndex(index); |
2851 | if (delegate) |
2852 | delegate->updateEditorGeometry(editor, option, index); |
2853 | } else { |
2854 | editorsToHide << editor; |
2855 | } |
2856 | ++it; |
2857 | } else { |
2858 | d->indexEditorHash.remove(key: it.value()); |
2859 | it = d->editorIndexHash.erase(it); |
2860 | editorsToRelease << editor; |
2861 | } |
2862 | } |
2863 | |
2864 | //we hide and release the editor outside of the loop because it might change the focus and try |
2865 | //to change the editors hashes. |
2866 | for (int i = 0; i < editorsToHide.size(); ++i) { |
2867 | editorsToHide.at(i)->hide(); |
2868 | } |
2869 | for (int i = 0; i < editorsToRelease.size(); ++i) { |
2870 | d->releaseEditor(editor: editorsToRelease.at(i)); |
2871 | } |
2872 | } |
2873 | |
2874 | /*! |
2875 | \since 4.4 |
2876 | |
2877 | Updates the geometry of the child widgets of the view. |
2878 | */ |
2879 | void QAbstractItemView::updateGeometries() |
2880 | { |
2881 | Q_D(QAbstractItemView); |
2882 | updateEditorGeometries(); |
2883 | d->fetchMoreTimer.start(msec: 0, obj: this); //fetch more later |
2884 | d->updateGeometry(); |
2885 | } |
2886 | |
2887 | /*! |
2888 | \internal |
2889 | */ |
2890 | void QAbstractItemView::verticalScrollbarValueChanged(int value) |
2891 | { |
2892 | Q_D(QAbstractItemView); |
2893 | if (verticalScrollBar()->maximum() == value && d->model->canFetchMore(parent: d->root)) |
2894 | d->model->fetchMore(parent: d->root); |
2895 | QPoint posInVp = viewport()->mapFromGlobal(QCursor::pos()); |
2896 | if (viewport()->rect().contains(p: posInVp)) |
2897 | d->checkMouseMove(pos: posInVp); |
2898 | } |
2899 | |
2900 | /*! |
2901 | \internal |
2902 | */ |
2903 | void QAbstractItemView::horizontalScrollbarValueChanged(int value) |
2904 | { |
2905 | Q_D(QAbstractItemView); |
2906 | if (horizontalScrollBar()->maximum() == value && d->model->canFetchMore(parent: d->root)) |
2907 | d->model->fetchMore(parent: d->root); |
2908 | QPoint posInVp = viewport()->mapFromGlobal(QCursor::pos()); |
2909 | if (viewport()->rect().contains(p: posInVp)) |
2910 | d->checkMouseMove(pos: posInVp); |
2911 | } |
2912 | |
2913 | /*! |
2914 | \internal |
2915 | */ |
2916 | void QAbstractItemView::verticalScrollbarAction(int) |
2917 | { |
2918 | //do nothing |
2919 | } |
2920 | |
2921 | /*! |
2922 | \internal |
2923 | */ |
2924 | void QAbstractItemView::horizontalScrollbarAction(int) |
2925 | { |
2926 | //do nothing |
2927 | } |
2928 | |
2929 | /*! |
2930 | Closes the given \a editor, and releases it. The \a hint is |
2931 | used to specify how the view should respond to the end of the editing |
2932 | operation. For example, the hint may indicate that the next item in |
2933 | the view should be opened for editing. |
2934 | |
2935 | \sa edit(), commitData() |
2936 | */ |
2937 | |
2938 | void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint) |
2939 | { |
2940 | Q_D(QAbstractItemView); |
2941 | |
2942 | // Close the editor |
2943 | if (editor) { |
2944 | const bool isPersistent = d->persistent.contains(value: editor); |
2945 | const QModelIndex index = d->indexForEditor(editor); |
2946 | if (!index.isValid()) { |
2947 | if (!editor->isVisible()) { |
2948 | // The commit might have removed the index (e.g. it might get filtered), in |
2949 | // which case the editor is already hidden and scheduled for deletion. We |
2950 | // don't have to do anything, except reset the state, and continue with |
2951 | // EndEditHint processing. |
2952 | if (!isPersistent) |
2953 | setState(NoState); |
2954 | } else { |
2955 | qWarning(msg: "QAbstractItemView::closeEditor called with an editor that does not belong to this view"); |
2956 | return; |
2957 | } |
2958 | } else { |
2959 | const bool hadFocus = editor->hasFocus(); |
2960 | // start a timer that expires immediately when we return to the event loop |
2961 | // to identify whether this close was triggered by a mousepress-initiated |
2962 | // focus event |
2963 | d->pressClosedEditorWatcher.start(msec: 0, obj: this); |
2964 | d->lastEditedIndex = index; |
2965 | |
2966 | if (!isPersistent) { |
2967 | setState(NoState); |
2968 | QModelIndex index = d->indexForEditor(editor); |
2969 | editor->removeEventFilter(obj: itemDelegateForIndex(index)); |
2970 | d->removeEditor(editor); |
2971 | } |
2972 | if (hadFocus) { |
2973 | if (focusPolicy() != Qt::NoFocus) |
2974 | setFocus(); // this will send a focusLost event to the editor |
2975 | else |
2976 | editor->clearFocus(); |
2977 | } else { |
2978 | d->checkPersistentEditorFocus(); |
2979 | } |
2980 | |
2981 | QPointer<QWidget> ed = editor; |
2982 | QCoreApplication::sendPostedEvents(receiver: editor, event_type: 0); |
2983 | editor = ed; |
2984 | |
2985 | if (!isPersistent && editor) |
2986 | d->releaseEditor(editor, index); |
2987 | } |
2988 | } |
2989 | |
2990 | // The EndEditHint part |
2991 | QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::NoUpdate; |
2992 | if (d->selectionMode != NoSelection) |
2993 | flags = QItemSelectionModel::ClearAndSelect | d->selectionBehaviorFlags(); |
2994 | switch (hint) { |
2995 | case QAbstractItemDelegate::EditNextItem: { |
2996 | QModelIndex index = moveCursor(cursorAction: MoveNext, modifiers: Qt::NoModifier); |
2997 | if (index.isValid()) { |
2998 | QPersistentModelIndex persistent(index); |
2999 | d->selectionModel->setCurrentIndex(index: persistent, command: flags); |
3000 | // currentChanged signal would have already started editing |
3001 | if (index.flags() & Qt::ItemIsEditable |
3002 | && (!(editTriggers() & QAbstractItemView::CurrentChanged))) |
3003 | edit(index: persistent); |
3004 | } break; } |
3005 | case QAbstractItemDelegate::EditPreviousItem: { |
3006 | QModelIndex index = moveCursor(cursorAction: MovePrevious, modifiers: Qt::NoModifier); |
3007 | if (index.isValid()) { |
3008 | QPersistentModelIndex persistent(index); |
3009 | d->selectionModel->setCurrentIndex(index: persistent, command: flags); |
3010 | // currentChanged signal would have already started editing |
3011 | if (index.flags() & Qt::ItemIsEditable |
3012 | && (!(editTriggers() & QAbstractItemView::CurrentChanged))) |
3013 | edit(index: persistent); |
3014 | } break; } |
3015 | case QAbstractItemDelegate::SubmitModelCache: |
3016 | d->model->submit(); |
3017 | break; |
3018 | case QAbstractItemDelegate::RevertModelCache: |
3019 | d->model->revert(); |
3020 | break; |
3021 | default: |
3022 | break; |
3023 | } |
3024 | } |
3025 | |
3026 | /*! |
3027 | Commit the data in the \a editor to the model. |
3028 | |
3029 | \sa closeEditor() |
3030 | */ |
3031 | void QAbstractItemView::commitData(QWidget *editor) |
3032 | { |
3033 | Q_D(QAbstractItemView); |
3034 | if (!editor || !d->itemDelegate || d->currentlyCommittingEditor) |
3035 | return; |
3036 | QModelIndex index = d->indexForEditor(editor); |
3037 | if (!index.isValid()) { |
3038 | qWarning(msg: "QAbstractItemView::commitData called with an editor that does not belong to this view"); |
3039 | return; |
3040 | } |
3041 | d->currentlyCommittingEditor = editor; |
3042 | QAbstractItemDelegate *delegate = itemDelegateForIndex(index); |
3043 | editor->removeEventFilter(obj: delegate); |
3044 | delegate->setModelData(editor, model: d->model, index); |
3045 | editor->installEventFilter(filterObj: delegate); |
3046 | d->currentlyCommittingEditor = nullptr; |
3047 | } |
3048 | |
3049 | /*! |
3050 | This function is called when the given \a editor has been destroyed. |
3051 | |
3052 | \sa closeEditor() |
3053 | */ |
3054 | void QAbstractItemView::editorDestroyed(QObject *editor) |
3055 | { |
3056 | Q_D(QAbstractItemView); |
3057 | QWidget *w = qobject_cast<QWidget*>(o: editor); |
3058 | d->removeEditor(editor: w); |
3059 | d->persistent.remove(value: w); |
3060 | if (state() == EditingState) |
3061 | setState(NoState); |
3062 | } |
3063 | |
3064 | |
3065 | |
3066 | /*! |
3067 | Moves to and selects the item best matching the string \a search. |
3068 | If no item is found nothing happens. |
3069 | |
3070 | In the default implementation, the search is reset if \a search is empty, or |
3071 | the time interval since the last search has exceeded |
3072 | QApplication::keyboardInputInterval(). |
3073 | */ |
3074 | void QAbstractItemView::keyboardSearch(const QString &search) |
3075 | { |
3076 | Q_D(QAbstractItemView); |
3077 | if (!d->model->rowCount(parent: d->root) || !d->model->columnCount(parent: d->root)) |
3078 | return; |
3079 | |
3080 | QModelIndex start = currentIndex().isValid() ? currentIndex() |
3081 | : d->model->index(row: 0, column: 0, parent: d->root); |
3082 | bool skipRow = false; |
3083 | bool keyboardTimeWasValid = d->keyboardInputTime.isValid(); |
3084 | qint64 keyboardInputTimeElapsed; |
3085 | if (keyboardTimeWasValid) |
3086 | keyboardInputTimeElapsed = d->keyboardInputTime.restart(); |
3087 | else |
3088 | d->keyboardInputTime.start(); |
3089 | if (search.isEmpty() || !keyboardTimeWasValid |
3090 | || keyboardInputTimeElapsed > QApplication::keyboardInputInterval()) { |
3091 | d->keyboardInput = search; |
3092 | skipRow = currentIndex().isValid(); //if it is not valid we should really start at QModelIndex(0,0) |
3093 | } else { |
3094 | d->keyboardInput += search; |
3095 | } |
3096 | |
3097 | // special case for searches with same key like 'aaaaa' |
3098 | bool sameKey = false; |
3099 | if (d->keyboardInput.size() > 1) { |
3100 | int c = d->keyboardInput.count(c: d->keyboardInput.at(i: d->keyboardInput.size() - 1)); |
3101 | sameKey = (c == d->keyboardInput.size()); |
3102 | if (sameKey) |
3103 | skipRow = true; |
3104 | } |
3105 | |
3106 | // skip if we are searching for the same key or a new search started |
3107 | if (skipRow) { |
3108 | QModelIndex parent = start.parent(); |
3109 | int newRow = (start.row() < d->model->rowCount(parent) - 1) ? start.row() + 1 : 0; |
3110 | start = d->model->index(row: newRow, column: start.column(), parent); |
3111 | } |
3112 | |
3113 | // search from start with wraparound |
3114 | QModelIndex current = start; |
3115 | QModelIndexList match; |
3116 | QModelIndex firstMatch; |
3117 | QModelIndex startMatch; |
3118 | QModelIndexList previous; |
3119 | do { |
3120 | match = d->model->match(start: current, role: Qt::DisplayRole, value: d->keyboardInput); |
3121 | if (match == previous) |
3122 | break; |
3123 | firstMatch = match.value(i: 0); |
3124 | previous = match; |
3125 | if (firstMatch.isValid()) { |
3126 | if (d->isIndexEnabled(index: firstMatch)) { |
3127 | setCurrentIndex(firstMatch); |
3128 | break; |
3129 | } |
3130 | int row = firstMatch.row() + 1; |
3131 | if (row >= d->model->rowCount(parent: firstMatch.parent())) |
3132 | row = 0; |
3133 | current = firstMatch.sibling(arow: row, acolumn: firstMatch.column()); |
3134 | |
3135 | //avoid infinite loop if all the matching items are disabled. |
3136 | if (!startMatch.isValid()) |
3137 | startMatch = firstMatch; |
3138 | else if (startMatch == firstMatch) |
3139 | break; |
3140 | } |
3141 | } while (current != start && firstMatch.isValid()); |
3142 | } |
3143 | |
3144 | /*! |
3145 | Returns the size hint for the item with the specified \a index or |
3146 | an invalid size for invalid indexes. |
3147 | |
3148 | \sa sizeHintForRow(), sizeHintForColumn() |
3149 | */ |
3150 | QSize QAbstractItemView::sizeHintForIndex(const QModelIndex &index) const |
3151 | { |
3152 | Q_D(const QAbstractItemView); |
3153 | if (!d->isIndexValid(index)) |
3154 | return QSize(); |
3155 | const auto delegate = itemDelegateForIndex(index); |
3156 | QStyleOptionViewItem option; |
3157 | initViewItemOption(option: &option); |
3158 | return delegate ? delegate->sizeHint(option, index) : QSize(); |
3159 | } |
3160 | |
3161 | /*! |
3162 | Returns the height size hint for the specified \a row or -1 if |
3163 | there is no model. |
3164 | |
3165 | The returned height is calculated using the size hints of the |
3166 | given \a row's items, i.e. the returned value is the maximum |
3167 | height among the items. Note that to control the height of a row, |
3168 | you must reimplement the QAbstractItemDelegate::sizeHint() |
3169 | function. |
3170 | |
3171 | This function is used in views with a vertical header to find the |
3172 | size hint for a header section based on the contents of the given |
3173 | \a row. |
3174 | |
3175 | \sa sizeHintForColumn() |
3176 | */ |
3177 | int QAbstractItemView::sizeHintForRow(int row) const |
3178 | { |
3179 | Q_D(const QAbstractItemView); |
3180 | |
3181 | if (row < 0 || row >= d->model->rowCount(parent: d->root)) |
3182 | return -1; |
3183 | |
3184 | ensurePolished(); |
3185 | |
3186 | QStyleOptionViewItem option; |
3187 | initViewItemOption(option: &option); |
3188 | int height = 0; |
3189 | int colCount = d->model->columnCount(parent: d->root); |
3190 | for (int c = 0; c < colCount; ++c) { |
3191 | const QModelIndex index = d->model->index(row, column: c, parent: d->root); |
3192 | if (QWidget *editor = d->editorForIndex(index).widget.data()) |
3193 | height = qMax(a: height, b: editor->height()); |
3194 | if (const QAbstractItemDelegate *delegate = itemDelegateForIndex(index)) |
3195 | height = qMax(a: height, b: delegate->sizeHint(option, index).height()); |
3196 | } |
3197 | return height; |
3198 | } |
3199 | |
3200 | /*! |
3201 | Returns the width size hint for the specified \a column or -1 if there is no model. |
3202 | |
3203 | This function is used in views with a horizontal header to find the size hint for |
3204 | a header section based on the contents of the given \a column. |
3205 | |
3206 | \sa sizeHintForRow() |
3207 | */ |
3208 | int QAbstractItemView::sizeHintForColumn(int column) const |
3209 | { |
3210 | Q_D(const QAbstractItemView); |
3211 | |
3212 | if (column < 0 || column >= d->model->columnCount(parent: d->root)) |
3213 | return -1; |
3214 | |
3215 | ensurePolished(); |
3216 | |
3217 | QStyleOptionViewItem option; |
3218 | initViewItemOption(option: &option); |
3219 | int width = 0; |
3220 | int rows = d->model->rowCount(parent: d->root); |
3221 | for (int r = 0; r < rows; ++r) { |
3222 | const QModelIndex index = d->model->index(row: r, column, parent: d->root); |
3223 | if (QWidget *editor = d->editorForIndex(index).widget.data()) |
3224 | width = qMax(a: width, b: editor->sizeHint().width()); |
3225 | if (const QAbstractItemDelegate *delegate = itemDelegateForIndex(index)) |
3226 | width = qMax(a: width, b: delegate->sizeHint(option, index).width()); |
3227 | } |
3228 | return width; |
3229 | } |
3230 | |
3231 | /*! |
3232 | Opens a persistent editor on the item at the given \a index. |
3233 | If no editor exists, the delegate will create a new editor. |
3234 | |
3235 | \sa closePersistentEditor(), isPersistentEditorOpen() |
3236 | */ |
3237 | void QAbstractItemView::openPersistentEditor(const QModelIndex &index) |
3238 | { |
3239 | Q_D(QAbstractItemView); |
3240 | QStyleOptionViewItem options; |
3241 | initViewItemOption(option: &options); |
3242 | options.rect = visualRect(index); |
3243 | options.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); |
3244 | |
3245 | QWidget *editor = d->editor(index, options); |
3246 | if (editor) { |
3247 | editor->show(); |
3248 | d->persistent.insert(value: editor); |
3249 | } |
3250 | } |
3251 | |
3252 | /*! |
3253 | Closes the persistent editor for the item at the given \a index. |
3254 | |
3255 | \sa openPersistentEditor(), isPersistentEditorOpen() |
3256 | */ |
3257 | void QAbstractItemView::closePersistentEditor(const QModelIndex &index) |
3258 | { |
3259 | Q_D(QAbstractItemView); |
3260 | if (QWidget *editor = d->editorForIndex(index).widget.data()) { |
3261 | if (index == selectionModel()->currentIndex()) |
3262 | closeEditor(editor, hint: QAbstractItemDelegate::RevertModelCache); |
3263 | d->persistent.remove(value: editor); |
3264 | d->removeEditor(editor); |
3265 | d->releaseEditor(editor, index); |
3266 | } |
3267 | } |
3268 | |
3269 | /*! |
3270 | \since 5.10 |
3271 | |
3272 | Returns whether a persistent editor is open for the item at index \a index. |
3273 | |
3274 | \sa openPersistentEditor(), closePersistentEditor() |
3275 | */ |
3276 | bool QAbstractItemView::isPersistentEditorOpen(const QModelIndex &index) const |
3277 | { |
3278 | Q_D(const QAbstractItemView); |
3279 | return d->editorForIndex(index).widget; |
3280 | } |
3281 | |
3282 | /*! |
3283 | \since 4.1 |
3284 | |
3285 | Sets the given \a widget on the item at the given \a index, passing the |
3286 | ownership of the widget to the viewport. |
3287 | |
3288 | If \a index is invalid (e.g., if you pass the root index), this function |
3289 | will do nothing. |
3290 | |
3291 | The given \a widget's \l{QWidget}{autoFillBackground} property must be set |
3292 | to true, otherwise the widget's background will be transparent, showing |
3293 | both the model data and the item at the given \a index. |
3294 | |
3295 | \note The view takes ownership of the \a widget. |
3296 | This means if index widget A is replaced with index widget B, index widget A will be |
3297 | deleted. For example, in the code snippet below, the QLineEdit object will |
3298 | be deleted. |
3299 | |
3300 | \snippet code/src_gui_itemviews_qabstractitemview.cpp 1 |
3301 | |
3302 | This function should only be used to display static content within the |
3303 | visible area corresponding to an item of data. If you want to display |
3304 | custom dynamic content or implement a custom editor widget, subclass |
3305 | QStyledItemDelegate instead. |
3306 | |
3307 | \sa {Delegate Classes} |
3308 | */ |
3309 | void QAbstractItemView::setIndexWidget(const QModelIndex &index, QWidget *widget) |
3310 | { |
3311 | Q_D(QAbstractItemView); |
3312 | if (!d->isIndexValid(index)) |
3313 | return; |
3314 | if (indexWidget(index) == widget) |
3315 | return; |
3316 | if (QWidget *oldWidget = indexWidget(index)) { |
3317 | d->persistent.remove(value: oldWidget); |
3318 | d->removeEditor(editor: oldWidget); |
3319 | oldWidget->removeEventFilter(obj: this); |
3320 | oldWidget->deleteLater(); |
3321 | } |
3322 | if (widget) { |
3323 | widget->setParent(viewport()); |
3324 | d->persistent.insert(value: widget); |
3325 | d->addEditor(index, editor: widget, isStatic: true); |
3326 | widget->installEventFilter(filterObj: this); |
3327 | widget->show(); |
3328 | dataChanged(topLeft: index, bottomRight: index); // update the geometry |
3329 | if (!d->delayedPendingLayout) { |
3330 | widget->setGeometry(visualRect(index)); |
3331 | d->doDelayedItemsLayout(); // relayout due to updated geometry |
3332 | } |
3333 | } |
3334 | } |
3335 | |
3336 | /*! |
3337 | \since 4.1 |
3338 | |
3339 | Returns the widget for the item at the given \a index. |
3340 | */ |
3341 | QWidget* QAbstractItemView::indexWidget(const QModelIndex &index) const |
3342 | { |
3343 | Q_D(const QAbstractItemView); |
3344 | if (d->isIndexValid(index)) |
3345 | if (QWidget *editor = d->editorForIndex(index).widget.data()) |
3346 | return editor; |
3347 | |
3348 | return nullptr; |
3349 | } |
3350 | |
3351 | /*! |
3352 | \since 4.1 |
3353 | |
3354 | Scrolls the view to the top. |
3355 | |
3356 | \sa scrollTo(), scrollToBottom() |
3357 | */ |
3358 | void QAbstractItemView::scrollToTop() |
3359 | { |
3360 | verticalScrollBar()->setValue(verticalScrollBar()->minimum()); |
3361 | } |
3362 | |
3363 | /*! |
3364 | \since 4.1 |
3365 | |
3366 | Scrolls the view to the bottom. |
3367 | |
3368 | \sa scrollTo(), scrollToTop() |
3369 | */ |
3370 | void QAbstractItemView::scrollToBottom() |
3371 | { |
3372 | Q_D(QAbstractItemView); |
3373 | if (d->delayedPendingLayout) { |
3374 | d->executePostedLayout(); |
3375 | updateGeometries(); |
3376 | } |
3377 | verticalScrollBar()->setValue(verticalScrollBar()->maximum()); |
3378 | } |
3379 | |
3380 | /*! |
3381 | \since 4.3 |
3382 | |
3383 | Updates the area occupied by the given \a index. |
3384 | |
3385 | */ |
3386 | void QAbstractItemView::update(const QModelIndex &index) |
3387 | { |
3388 | Q_D(QAbstractItemView); |
3389 | if (index.isValid()) { |
3390 | const QRect rect = d->visualRect(index); |
3391 | //this test is important for performance reason |
3392 | //For example in dataChanged we simply update all the cells without checking |
3393 | //it can be a major bottleneck to update rects that aren't even part of the viewport |
3394 | if (d->viewport->rect().intersects(r: rect)) |
3395 | d->viewport->update(rect); |
3396 | } |
3397 | } |
3398 | |
3399 | /*! |
3400 | This slot is called when items with the given \a roles are changed in the |
3401 | model. The changed items are those from \a topLeft to \a bottomRight |
3402 | inclusive. If just one item is changed \a topLeft == \a bottomRight. |
3403 | |
3404 | The \a roles which have been changed can either be an empty container (meaning everything |
3405 | has changed), or a non-empty container with the subset of roles which have changed. |
3406 | |
3407 | \note: Qt::ToolTipRole is not honored by dataChanged() in the views provided by Qt. |
3408 | */ |
3409 | void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, |
3410 | const QList<int> &roles) |
3411 | { |
3412 | Q_UNUSED(roles); |
3413 | // Single item changed |
3414 | Q_D(QAbstractItemView); |
3415 | if (topLeft == bottomRight && topLeft.isValid()) { |
3416 | const QEditorInfo &editorInfo = d->editorForIndex(index: topLeft); |
3417 | //we don't update the edit data if it is static |
3418 | if (!editorInfo.isStatic && editorInfo.widget) { |
3419 | QAbstractItemDelegate *delegate = itemDelegateForIndex(index: topLeft); |
3420 | if (delegate) { |
3421 | delegate->setEditorData(editor: editorInfo.widget.data(), index: topLeft); |
3422 | } |
3423 | } |
3424 | if (isVisible() && !d->delayedPendingLayout) { |
3425 | // otherwise the items will be updated later anyway |
3426 | update(index: topLeft); |
3427 | } |
3428 | } else { |
3429 | d->updateEditorData(topLeft, bottomRight); |
3430 | if (isVisible() && !d->delayedPendingLayout) { |
3431 | if (!topLeft.isValid() || |
3432 | topLeft.parent() != bottomRight.parent() || |
3433 | topLeft.row() > bottomRight.row() || |
3434 | topLeft.column() > bottomRight.column()) { |
3435 | // invalid parameter - call update() to redraw all |
3436 | d->viewport->update(); |
3437 | } else { |
3438 | const QRect updateRect = d->intersectedRect(rect: d->viewport->rect(), topLeft, bottomRight); |
3439 | if (!updateRect.isEmpty()) |
3440 | d->viewport->update(updateRect); |
3441 | } |
3442 | } |
3443 | } |
3444 | |
3445 | #if QT_CONFIG(accessibility) |
3446 | if (QAccessible::isActive()) { |
3447 | QAccessibleTableModelChangeEvent accessibleEvent(this, QAccessibleTableModelChangeEvent::DataChanged); |
3448 | accessibleEvent.setFirstRow(topLeft.row()); |
3449 | accessibleEvent.setFirstColumn(topLeft.column()); |
3450 | accessibleEvent.setLastRow(bottomRight.row()); |
3451 | accessibleEvent.setLastColumn(bottomRight.column()); |
3452 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3453 | } |
3454 | #endif |
3455 | d->updateGeometry(); |
3456 | } |
3457 | |
3458 | /*! |
3459 | This slot is called when rows are inserted. The new rows are those |
3460 | under the given \a parent from \a start to \a end inclusive. The |
3461 | base class implementation calls fetchMore() on the model to check |
3462 | for more data. |
3463 | |
3464 | \sa rowsAboutToBeRemoved() |
3465 | */ |
3466 | void QAbstractItemView::rowsInserted(const QModelIndex &, int, int) |
3467 | { |
3468 | if (!isVisible()) |
3469 | d_func()->fetchMoreTimer.start(msec: 0, obj: this); //fetch more later |
3470 | else |
3471 | updateEditorGeometries(); |
3472 | } |
3473 | |
3474 | /*! |
3475 | This slot is called when rows are about to be removed. The deleted rows are |
3476 | those under the given \a parent from \a start to \a end inclusive. |
3477 | |
3478 | \sa rowsInserted() |
3479 | */ |
3480 | void QAbstractItemView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
3481 | { |
3482 | Q_D(QAbstractItemView); |
3483 | |
3484 | setState(CollapsingState); |
3485 | |
3486 | // Ensure one selected item in single selection mode. |
3487 | QModelIndex current = currentIndex(); |
3488 | if (d->selectionMode == SingleSelection |
3489 | && current.isValid() |
3490 | && current.row() >= start |
3491 | && current.row() <= end |
3492 | && current.parent() == parent) { |
3493 | int totalToRemove = end - start + 1; |
3494 | if (d->model->rowCount(parent) <= totalToRemove) { // no more children |
3495 | QModelIndex index = parent; |
3496 | while (index != d->root && !d->isIndexEnabled(index)) |
3497 | index = index.parent(); |
3498 | if (index != d->root) |
3499 | setCurrentIndex(index); |
3500 | } else { |
3501 | int row = end + 1; |
3502 | QModelIndex next; |
3503 | const int rowCount = d->model->rowCount(parent); |
3504 | bool found = false; |
3505 | // find the next visible and enabled item |
3506 | while (row < rowCount && !found) { |
3507 | next = d->model->index(row: row++, column: current.column(), parent: current.parent()); |
3508 | #ifdef QT_DEBUG |
3509 | if (!next.isValid()) { |
3510 | qWarning(msg: "Model unexpectedly returned an invalid index"); |
3511 | break; |
3512 | } |
3513 | #endif |
3514 | if (!isIndexHidden(index: next) && d->isIndexEnabled(index: next)) { |
3515 | found = true; |
3516 | break; |
3517 | } |
3518 | } |
3519 | |
3520 | if (!found) { |
3521 | row = start - 1; |
3522 | // find the previous visible and enabled item |
3523 | while (row >= 0) { |
3524 | next = d->model->index(row: row--, column: current.column(), parent: current.parent()); |
3525 | #ifdef QT_DEBUG |
3526 | if (!next.isValid()) { |
3527 | qWarning(msg: "Model unexpectedly returned an invalid index"); |
3528 | break; |
3529 | } |
3530 | #endif |
3531 | if (!isIndexHidden(index: next) && d->isIndexEnabled(index: next)) |
3532 | break; |
3533 | } |
3534 | } |
3535 | |
3536 | setCurrentIndex(next); |
3537 | } |
3538 | } |
3539 | |
3540 | // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes |
3541 | const auto findDirectChildOf = [](const QModelIndex &parent, QModelIndex child) |
3542 | { |
3543 | while (child.isValid()) { |
3544 | const auto parentIndex = child.parent(); |
3545 | if (parentIndex == parent) |
3546 | return child; |
3547 | child = parentIndex; |
3548 | } |
3549 | return QModelIndex(); |
3550 | }; |
3551 | QEditorIndexHash::iterator i = d->editorIndexHash.begin(); |
3552 | while (i != d->editorIndexHash.end()) { |
3553 | const QModelIndex index = i.value(); |
3554 | const QModelIndex directChild = findDirectChildOf(parent, index); |
3555 | if (directChild.isValid() && directChild.row() >= start && directChild.row() <= end) { |
3556 | QWidget *editor = i.key(); |
3557 | QEditorInfo info = d->indexEditorHash.take(key: index); |
3558 | i = d->editorIndexHash.erase(it: i); |
3559 | if (info.widget) |
3560 | d->releaseEditor(editor, index); |
3561 | } else { |
3562 | ++i; |
3563 | } |
3564 | } |
3565 | } |
3566 | |
3567 | /*! |
3568 | \internal |
3569 | |
3570 | This slot is called when rows have been removed. The deleted |
3571 | rows are those under the given \a parent from \a start to \a end |
3572 | inclusive. |
3573 | */ |
3574 | void QAbstractItemViewPrivate::rowsRemoved(const QModelIndex &index, int start, int end) |
3575 | { |
3576 | Q_UNUSED(index); |
3577 | Q_UNUSED(start); |
3578 | Q_UNUSED(end); |
3579 | |
3580 | Q_Q(QAbstractItemView); |
3581 | if (q->isVisible()) |
3582 | q->updateEditorGeometries(); |
3583 | q->setState(QAbstractItemView::NoState); |
3584 | #if QT_CONFIG(accessibility) |
3585 | if (QAccessible::isActive()) { |
3586 | QAccessibleTableModelChangeEvent accessibleEvent(q, QAccessibleTableModelChangeEvent::RowsRemoved); |
3587 | accessibleEvent.setFirstRow(start); |
3588 | accessibleEvent.setLastRow(end); |
3589 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3590 | } |
3591 | #endif |
3592 | updateGeometry(); |
3593 | } |
3594 | |
3595 | /*! |
3596 | \internal |
3597 | |
3598 | This slot is called when columns are about to be removed. The deleted |
3599 | columns are those under the given \a parent from \a start to \a end |
3600 | inclusive. |
3601 | */ |
3602 | void QAbstractItemViewPrivate::columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
3603 | { |
3604 | Q_Q(QAbstractItemView); |
3605 | |
3606 | q->setState(QAbstractItemView::CollapsingState); |
3607 | |
3608 | // Ensure one selected item in single selection mode. |
3609 | QModelIndex current = q->currentIndex(); |
3610 | if (current.isValid() |
3611 | && selectionMode == QAbstractItemView::SingleSelection |
3612 | && current.column() >= start |
3613 | && current.column() <= end) { |
3614 | int totalToRemove = end - start + 1; |
3615 | if (model->columnCount(parent) < totalToRemove) { // no more columns |
3616 | QModelIndex index = parent; |
3617 | while (index.isValid() && !isIndexEnabled(index)) |
3618 | index = index.parent(); |
3619 | if (index.isValid()) |
3620 | q->setCurrentIndex(index); |
3621 | } else { |
3622 | int column = end; |
3623 | QModelIndex next; |
3624 | const int columnCount = model->columnCount(parent: current.parent()); |
3625 | // find the next visible and enabled item |
3626 | while (column < columnCount) { |
3627 | next = model->index(row: current.row(), column: column++, parent: current.parent()); |
3628 | #ifdef QT_DEBUG |
3629 | if (!next.isValid()) { |
3630 | qWarning(msg: "Model unexpectedly returned an invalid index"); |
3631 | break; |
3632 | } |
3633 | #endif |
3634 | if (!q->isIndexHidden(index: next) && isIndexEnabled(index: next)) |
3635 | break; |
3636 | } |
3637 | q->setCurrentIndex(next); |
3638 | } |
3639 | } |
3640 | |
3641 | // Remove all affected editors; this is more efficient than waiting for updateGeometries() to clean out editors for invalid indexes |
3642 | QEditorIndexHash::iterator it = editorIndexHash.begin(); |
3643 | while (it != editorIndexHash.end()) { |
3644 | QModelIndex index = it.value(); |
3645 | if (index.column() <= start && index.column() >= end && model->parent(child: index) == parent) { |
3646 | QWidget *editor = it.key(); |
3647 | QEditorInfo info = indexEditorHash.take(key: it.value()); |
3648 | it = editorIndexHash.erase(it); |
3649 | if (info.widget) |
3650 | releaseEditor(editor, index); |
3651 | } else { |
3652 | ++it; |
3653 | } |
3654 | } |
3655 | |
3656 | } |
3657 | |
3658 | /*! |
3659 | \internal |
3660 | |
3661 | This slot is called when columns have been removed. The deleted |
3662 | rows are those under the given \a parent from \a start to \a end |
3663 | inclusive. |
3664 | */ |
3665 | void QAbstractItemViewPrivate::columnsRemoved(const QModelIndex &index, int start, int end) |
3666 | { |
3667 | Q_UNUSED(index); |
3668 | Q_UNUSED(start); |
3669 | Q_UNUSED(end); |
3670 | |
3671 | Q_Q(QAbstractItemView); |
3672 | if (q->isVisible()) |
3673 | q->updateEditorGeometries(); |
3674 | q->setState(QAbstractItemView::NoState); |
3675 | #if QT_CONFIG(accessibility) |
3676 | if (QAccessible::isActive()) { |
3677 | QAccessibleTableModelChangeEvent accessibleEvent(q, QAccessibleTableModelChangeEvent::ColumnsRemoved); |
3678 | accessibleEvent.setFirstColumn(start); |
3679 | accessibleEvent.setLastColumn(end); |
3680 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3681 | } |
3682 | #endif |
3683 | updateGeometry(); |
3684 | } |
3685 | |
3686 | |
3687 | /*! |
3688 | \internal |
3689 | |
3690 | This slot is called when rows have been inserted. |
3691 | */ |
3692 | void QAbstractItemViewPrivate::rowsInserted(const QModelIndex &index, int start, int end) |
3693 | { |
3694 | Q_UNUSED(index); |
3695 | Q_UNUSED(start); |
3696 | Q_UNUSED(end); |
3697 | |
3698 | #if QT_CONFIG(accessibility) |
3699 | Q_Q(QAbstractItemView); |
3700 | if (QAccessible::isActive()) { |
3701 | QAccessibleTableModelChangeEvent accessibleEvent(q, QAccessibleTableModelChangeEvent::RowsInserted); |
3702 | accessibleEvent.setFirstRow(start); |
3703 | accessibleEvent.setLastRow(end); |
3704 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3705 | } |
3706 | #endif |
3707 | updateGeometry(); |
3708 | } |
3709 | |
3710 | /*! |
3711 | \internal |
3712 | |
3713 | This slot is called when columns have been inserted. |
3714 | */ |
3715 | void QAbstractItemViewPrivate::columnsInserted(const QModelIndex &index, int start, int end) |
3716 | { |
3717 | Q_UNUSED(index); |
3718 | Q_UNUSED(start); |
3719 | Q_UNUSED(end); |
3720 | |
3721 | Q_Q(QAbstractItemView); |
3722 | if (q->isVisible()) |
3723 | q->updateEditorGeometries(); |
3724 | #if QT_CONFIG(accessibility) |
3725 | if (QAccessible::isActive()) { |
3726 | QAccessibleTableModelChangeEvent accessibleEvent(q, QAccessibleTableModelChangeEvent::ColumnsInserted); |
3727 | accessibleEvent.setFirstColumn(start); |
3728 | accessibleEvent.setLastColumn(end); |
3729 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3730 | } |
3731 | #endif |
3732 | updateGeometry(); |
3733 | } |
3734 | |
3735 | /*! |
3736 | \internal |
3737 | */ |
3738 | void QAbstractItemViewPrivate::modelDestroyed() |
3739 | { |
3740 | model = QAbstractItemModelPrivate::staticEmptyModel(); |
3741 | doDelayedReset(); |
3742 | } |
3743 | |
3744 | /*! |
3745 | \internal |
3746 | |
3747 | This slot is called when the layout is changed. |
3748 | */ |
3749 | void QAbstractItemViewPrivate::layoutChanged() |
3750 | { |
3751 | doDelayedItemsLayout(); |
3752 | #if QT_CONFIG(accessibility) |
3753 | Q_Q(QAbstractItemView); |
3754 | if (QAccessible::isActive()) { |
3755 | QAccessibleTableModelChangeEvent accessibleEvent(q, QAccessibleTableModelChangeEvent::ModelReset); |
3756 | QAccessible::updateAccessibility(event: &accessibleEvent); |
3757 | } |
3758 | #endif |
3759 | } |
3760 | |
3761 | void QAbstractItemViewPrivate::rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int) |
3762 | { |
3763 | layoutChanged(); |
3764 | } |
3765 | |
3766 | void QAbstractItemViewPrivate::columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int) |
3767 | { |
3768 | layoutChanged(); |
3769 | } |
3770 | |
3771 | QRect QAbstractItemViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const |
3772 | { |
3773 | Q_Q(const QAbstractItemView); |
3774 | |
3775 | const auto parentIdx = topLeft.parent(); |
3776 | QRect updateRect; |
3777 | for (int r = topLeft.row(); r <= bottomRight.row(); ++r) { |
3778 | for (int c = topLeft.column(); c <= bottomRight.column(); ++c) |
3779 | updateRect |= q->visualRect(index: model->index(row: r, column: c, parent: parentIdx)); |
3780 | } |
3781 | return rect.intersected(other: updateRect); |
3782 | } |
3783 | |
3784 | /*! |
3785 | This slot is called when the selection is changed. The previous |
3786 | selection (which may be empty), is specified by \a deselected, and the |
3787 | new selection by \a selected. |
3788 | |
3789 | \sa setSelection() |
3790 | */ |
3791 | void QAbstractItemView::selectionChanged(const QItemSelection &selected, |
3792 | const QItemSelection &deselected) |
3793 | { |
3794 | Q_D(QAbstractItemView); |
3795 | if (isVisible() && updatesEnabled()) { |
3796 | d->viewport->update(visualRegionForSelection(selection: deselected) | visualRegionForSelection(selection: selected)); |
3797 | } |
3798 | } |
3799 | |
3800 | /*! |
3801 | This slot is called when a new item becomes the current item. |
3802 | The previous current item is specified by the \a previous index, and the new |
3803 | item by the \a current index. |
3804 | |
3805 | If you want to know about changes to items see the |
3806 | dataChanged() signal. |
3807 | */ |
3808 | void QAbstractItemView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
3809 | { |
3810 | Q_D(QAbstractItemView); |
3811 | Q_ASSERT(d->model); |
3812 | |
3813 | QPersistentModelIndex persistentCurrent(current); // in case commitData() moves things around (QTBUG-127852) |
3814 | |
3815 | if (previous.isValid()) { |
3816 | QModelIndex buddy = d->model->buddy(index: previous); |
3817 | QWidget *editor = d->editorForIndex(index: buddy).widget.data(); |
3818 | if (isVisible()) { |
3819 | update(index: previous); |
3820 | } |
3821 | if (editor && !d->persistent.contains(value: editor)) { |
3822 | const bool rowChanged = current.row() != previous.row(); |
3823 | commitData(editor); // might invalidate previous, don't use after this line (QTBUG-127852) |
3824 | if (rowChanged) |
3825 | closeEditor(editor, hint: QAbstractItemDelegate::SubmitModelCache); |
3826 | else |
3827 | closeEditor(editor, hint: QAbstractItemDelegate::NoHint); |
3828 | } |
3829 | } |
3830 | |
3831 | const QModelIndex newCurrent = persistentCurrent; |
3832 | |
3833 | QItemSelectionModel::SelectionFlags command = selectionCommand(index: newCurrent, event: nullptr); |
3834 | if ((command & QItemSelectionModel::Current) == 0) |
3835 | d->currentSelectionStartIndex = newCurrent; |
3836 | |
3837 | if (newCurrent.isValid() && !d->autoScrollTimer.isActive()) { |
3838 | if (isVisible()) { |
3839 | if (d->autoScroll) |
3840 | scrollTo(index: newCurrent); |
3841 | update(index: newCurrent); |
3842 | edit(index: newCurrent, trigger: CurrentChanged, event: nullptr); |
3843 | if (newCurrent.row() == (d->model->rowCount(parent: d->root) - 1)) |
3844 | d->fetchMore(); |
3845 | } else { |
3846 | d->shouldScrollToCurrentOnShow = d->autoScroll; |
3847 | } |
3848 | } |
3849 | setAttribute(Qt::WA_InputMethodEnabled, on: (newCurrent.isValid() && (newCurrent.flags() & Qt::ItemIsEditable))); |
3850 | } |
3851 | |
3852 | #if QT_CONFIG(draganddrop) |
3853 | /*! |
3854 | Starts a drag by calling drag->exec() using the given \a supportedActions. |
3855 | */ |
3856 | void QAbstractItemView::startDrag(Qt::DropActions supportedActions) |
3857 | { |
3858 | Q_D(QAbstractItemView); |
3859 | QModelIndexList indexes = d->selectedDraggableIndexes(); |
3860 | if (indexes.size() > 0) { |
3861 | QMimeData *data = d->model->mimeData(indexes); |
3862 | if (!data) |
3863 | return; |
3864 | QRect rect; |
3865 | QPixmap pixmap = d->renderToPixmap(indexes, r: &rect); |
3866 | rect.adjust(dx1: horizontalOffset(), dy1: verticalOffset(), dx2: 0, dy2: 0); |
3867 | QDrag *drag = new QDrag(this); |
3868 | drag->setPixmap(pixmap); |
3869 | drag->setMimeData(data); |
3870 | drag->setHotSpot(d->pressedPosition - rect.topLeft()); |
3871 | Qt::DropAction defaultDropAction = Qt::IgnoreAction; |
3872 | if (dragDropMode() == InternalMove) |
3873 | supportedActions &= ~Qt::CopyAction; |
3874 | if (d->defaultDropAction != Qt::IgnoreAction && (supportedActions & d->defaultDropAction)) |
3875 | defaultDropAction = d->defaultDropAction; |
3876 | else if (supportedActions & Qt::CopyAction && dragDropMode() != QAbstractItemView::InternalMove) |
3877 | defaultDropAction = Qt::CopyAction; |
3878 | d->dropEventMoved = false; |
3879 | if (drag->exec(supportedActions, defaultAction: defaultDropAction) == Qt::MoveAction && !d->dropEventMoved) { |
3880 | if (dragDropMode() != InternalMove || drag->target() == viewport()) |
3881 | d->clearOrRemove(); |
3882 | } |
3883 | d->dropEventMoved = false; |
3884 | // Reset the drop indicator |
3885 | d->dropIndicatorRect = QRect(); |
3886 | d->dropIndicatorPosition = OnItem; |
3887 | } |
3888 | } |
3889 | #endif // QT_CONFIG(draganddrop) |
3890 | |
3891 | /*! |
3892 | \since 6.0 |
3893 | |
3894 | Initialize the \a option structure with the view's palette, font, state, |
3895 | alignments etc. |
3896 | |
3897 | \note Implementations of this methods should check the \l{QStyleOption::}{version} |
3898 | of the structure received, populate all members the implementation is familiar with, |
3899 | and set the version member to the one supported by the implementation before returning. |
3900 | */ |
3901 | void QAbstractItemView::initViewItemOption(QStyleOptionViewItem *option) const |
3902 | { |
3903 | Q_D(const QAbstractItemView); |
3904 | option->initFrom(w: this); |
3905 | option->state &= ~QStyle::State_MouseOver; |
3906 | option->font = font(); |
3907 | |
3908 | // On mac the focus appearance follows window activation |
3909 | // not widget activation |
3910 | if (!hasFocus()) |
3911 | option->state &= ~QStyle::State_Active; |
3912 | |
3913 | option->state &= ~QStyle::State_HasFocus; |
3914 | if (d->iconSize.isValid()) { |
3915 | option->decorationSize = d->iconSize; |
3916 | } else { |
3917 | int pm = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: this); |
3918 | option->decorationSize = QSize(pm, pm); |
3919 | } |
3920 | option->decorationPosition = QStyleOptionViewItem::Left; |
3921 | option->decorationAlignment = Qt::AlignCenter; |
3922 | option->displayAlignment = Qt::AlignLeft|Qt::AlignVCenter; |
3923 | option->textElideMode = d->textElideMode; |
3924 | option->rect = QRect(); |
3925 | option->showDecorationSelected = style()->styleHint(stylehint: QStyle::SH_ItemView_ShowDecorationSelected, opt: nullptr, widget: this); |
3926 | if (d->wrapItemText) |
3927 | option->features = QStyleOptionViewItem::WrapText; |
3928 | option->locale = locale(); |
3929 | option->locale.setNumberOptions(QLocale::OmitGroupSeparator); |
3930 | option->widget = this; |
3931 | } |
3932 | |
3933 | /*! |
3934 | Returns the item view's state. |
3935 | |
3936 | \sa setState() |
3937 | */ |
3938 | QAbstractItemView::State QAbstractItemView::state() const |
3939 | { |
3940 | Q_D(const QAbstractItemView); |
3941 | return d->state; |
3942 | } |
3943 | |
3944 | /*! |
3945 | Sets the item view's state to the given \a state. |
3946 | |
3947 | \sa state() |
3948 | */ |
3949 | void QAbstractItemView::setState(State state) |
3950 | { |
3951 | Q_D(QAbstractItemView); |
3952 | d->state = state; |
3953 | } |
3954 | |
3955 | /*! |
3956 | Schedules a layout of the items in the view to be executed when the |
3957 | event processing starts. |
3958 | |
3959 | Even if scheduleDelayedItemsLayout() is called multiple times before |
3960 | events are processed, the view will only do the layout once. |
3961 | |
3962 | \sa executeDelayedItemsLayout() |
3963 | */ |
3964 | void QAbstractItemView::scheduleDelayedItemsLayout() |
3965 | { |
3966 | Q_D(QAbstractItemView); |
3967 | d->doDelayedItemsLayout(); |
3968 | } |
3969 | |
3970 | /*! |
3971 | Executes the scheduled layouts without waiting for the event processing |
3972 | to begin. |
3973 | |
3974 | \sa scheduleDelayedItemsLayout() |
3975 | */ |
3976 | void QAbstractItemView::executeDelayedItemsLayout() |
3977 | { |
3978 | Q_D(QAbstractItemView); |
3979 | d->executePostedLayout(); |
3980 | } |
3981 | |
3982 | /*! |
3983 | \since 4.1 |
3984 | |
3985 | Marks the given \a region as dirty and schedules it to be updated. |
3986 | You only need to call this function if you are implementing |
3987 | your own view subclass. |
3988 | |
3989 | \sa scrollDirtyRegion(), dirtyRegionOffset() |
3990 | */ |
3991 | |
3992 | void QAbstractItemView::setDirtyRegion(const QRegion ®ion) |
3993 | { |
3994 | Q_D(QAbstractItemView); |
3995 | d->setDirtyRegion(region); |
3996 | } |
3997 | |
3998 | /*! |
3999 | Prepares the view for scrolling by (\a{dx},\a{dy}) pixels by moving the dirty regions in the |
4000 | opposite direction. You only need to call this function if you are implementing a scrolling |
4001 | viewport in your view subclass. |
4002 | |
4003 | If you implement scrollContentsBy() in a subclass of QAbstractItemView, call this function |
4004 | before you call QWidget::scroll() on the viewport. Alternatively, just call update(). |
4005 | |
4006 | \sa scrollContentsBy(), dirtyRegionOffset(), setDirtyRegion() |
4007 | */ |
4008 | void QAbstractItemView::scrollDirtyRegion(int dx, int dy) |
4009 | { |
4010 | Q_D(QAbstractItemView); |
4011 | d->scrollDirtyRegion(dx, dy); |
4012 | } |
4013 | |
4014 | /*! |
4015 | Returns the offset of the dirty regions in the view. |
4016 | |
4017 | If you use scrollDirtyRegion() and implement a paintEvent() in a subclass of |
4018 | QAbstractItemView, you should translate the area given by the paint event with |
4019 | the offset returned from this function. |
4020 | |
4021 | \sa scrollDirtyRegion(), setDirtyRegion() |
4022 | */ |
4023 | QPoint QAbstractItemView::dirtyRegionOffset() const |
4024 | { |
4025 | Q_D(const QAbstractItemView); |
4026 | return d->scrollDelayOffset; |
4027 | } |
4028 | |
4029 | /*! |
4030 | \internal |
4031 | */ |
4032 | void QAbstractItemView::startAutoScroll() |
4033 | { |
4034 | d_func()->startAutoScroll(); |
4035 | } |
4036 | |
4037 | /*! |
4038 | \internal |
4039 | */ |
4040 | void QAbstractItemView::stopAutoScroll() |
4041 | { |
4042 | d_func()->stopAutoScroll(); |
4043 | } |
4044 | |
4045 | /*! |
4046 | \internal |
4047 | */ |
4048 | void QAbstractItemView::doAutoScroll() |
4049 | { |
4050 | // find how much we should scroll with |
4051 | Q_D(QAbstractItemView); |
4052 | QScrollBar *verticalScroll = verticalScrollBar(); |
4053 | QScrollBar *horizontalScroll = horizontalScrollBar(); |
4054 | |
4055 | // QHeaderView does not (normally) have scrollbars |
4056 | // It needs to use its parents scroll instead |
4057 | QHeaderView *hv = qobject_cast<QHeaderView*>(object: this); |
4058 | if (hv) { |
4059 | QAbstractScrollArea *parent = qobject_cast<QAbstractScrollArea*>(object: parentWidget()); |
4060 | if (parent) { |
4061 | if (hv->orientation() == Qt::Horizontal) { |
4062 | if (!hv->horizontalScrollBar() || !hv->horizontalScrollBar()->isVisible()) |
4063 | horizontalScroll = parent->horizontalScrollBar(); |
4064 | } else { |
4065 | if (!hv->verticalScrollBar() || !hv->verticalScrollBar()->isVisible()) |
4066 | verticalScroll = parent->verticalScrollBar(); |
4067 | } |
4068 | } |
4069 | } |
4070 | |
4071 | const int verticalStep = verticalScroll->pageStep(); |
4072 | const int horizontalStep = horizontalScroll->pageStep(); |
4073 | if (d->autoScrollCount < qMax(a: verticalStep, b: horizontalStep)) |
4074 | ++d->autoScrollCount; |
4075 | |
4076 | const int margin = d->autoScrollMargin; |
4077 | const int verticalValue = verticalScroll->value(); |
4078 | const int horizontalValue = horizontalScroll->value(); |
4079 | |
4080 | const QPoint pos = d->draggedPosition - d->offset(); |
4081 | const QRect area = QWidgetPrivate::get(w: d->viewport)->clipRect(); |
4082 | |
4083 | // do the scrolling if we are in the scroll margins |
4084 | if (pos.y() - area.top() < margin) |
4085 | verticalScroll->setValue(verticalValue - d->autoScrollCount); |
4086 | else if (area.bottom() - pos.y() < margin) |
4087 | verticalScroll->setValue(verticalValue + d->autoScrollCount); |
4088 | if (pos.x() - area.left() < margin) |
4089 | horizontalScroll->setValue(horizontalValue - d->autoScrollCount); |
4090 | else if (area.right() - pos.x() < margin) |
4091 | horizontalScroll->setValue(horizontalValue + d->autoScrollCount); |
4092 | // if nothing changed, stop scrolling |
4093 | const bool verticalUnchanged = (verticalValue == verticalScroll->value()); |
4094 | const bool horizontalUnchanged = (horizontalValue == horizontalScroll->value()); |
4095 | if (verticalUnchanged && horizontalUnchanged) { |
4096 | stopAutoScroll(); |
4097 | } else { |
4098 | #if QT_CONFIG(draganddrop) |
4099 | d->dropIndicatorRect = QRect(); |
4100 | d->dropIndicatorPosition = QAbstractItemView::OnViewport; |
4101 | #endif |
4102 | switch (state()) { |
4103 | case QAbstractItemView::DragSelectingState: { |
4104 | // mouseMoveEvent updates the drag-selection rectangle, so fake an event. This also |
4105 | // updates draggedPosition taking the now scrolled viewport into account. |
4106 | const QPoint globalPos = d->viewport->mapToGlobal(pos); |
4107 | const QPoint windowPos = window()->mapFromGlobal(globalPos); |
4108 | QMouseEvent mm(QEvent::MouseMove, pos, windowPos, globalPos, |
4109 | Qt::NoButton, Qt::LeftButton, d->pressedModifiers, |
4110 | Qt::MouseEventSynthesizedByQt); |
4111 | QApplication::sendEvent(receiver: viewport(), event: &mm); |
4112 | break; |
4113 | } |
4114 | case QAbstractItemView::DraggingState: { |
4115 | // we can't simulate mouse (it would throw off the drag'n'drop state logic) or drag |
4116 | // (we don't have the mime data or the actions) move events during drag'n'drop, so |
4117 | // update our dragged position manually after the scroll. "pos" is the old |
4118 | // draggedPosition - d->offset(), and d->offset() is now updated after scrolling, so |
4119 | // pos + d->offset() gives us the new position. |
4120 | d->draggedPosition = pos + d->offset(); |
4121 | break; |
4122 | } |
4123 | default: |
4124 | break; |
4125 | } |
4126 | d->viewport->update(); |
4127 | } |
4128 | } |
4129 | |
4130 | /*! |
4131 | Returns the SelectionFlags to be used when updating a selection model |
4132 | for the specified \a index. The result depends on the current |
4133 | selectionMode(), and on the user input event \a event, which can be |
4134 | \nullptr. |
4135 | |
4136 | Reimplement this function to define your own selection behavior. |
4137 | |
4138 | \sa setSelection() |
4139 | */ |
4140 | QItemSelectionModel::SelectionFlags QAbstractItemView::selectionCommand(const QModelIndex &index, |
4141 | const QEvent *event) const |
4142 | { |
4143 | Q_D(const QAbstractItemView); |
4144 | Qt::KeyboardModifiers keyModifiers = event && event->isInputEvent() |
4145 | ? static_cast<const QInputEvent*>(event)->modifiers() |
4146 | : Qt::NoModifier; |
4147 | switch (d->selectionMode) { |
4148 | case NoSelection: // Never update selection model |
4149 | return QItemSelectionModel::NoUpdate; |
4150 | case SingleSelection: // ClearAndSelect on valid index otherwise NoUpdate |
4151 | if (event) { |
4152 | switch (event->type()) { |
4153 | case QEvent::MouseButtonPress: |
4154 | // press with any modifiers on a selected item does nothing |
4155 | if (d->pressedAlreadySelected) |
4156 | return QItemSelectionModel::NoUpdate; |
4157 | break; |
4158 | case QEvent::MouseButtonRelease: |
4159 | // clicking into area with no items does nothing |
4160 | if (!index.isValid()) |
4161 | return QItemSelectionModel::NoUpdate; |
4162 | Q_FALLTHROUGH(); |
4163 | case QEvent::KeyPress: |
4164 | // ctrl-release on selected item deselects |
4165 | if ((keyModifiers & Qt::ControlModifier) && d->selectionModel->isSelected(index)) |
4166 | return QItemSelectionModel::Deselect | d->selectionBehaviorFlags(); |
4167 | break; |
4168 | default: |
4169 | break; |
4170 | } |
4171 | } |
4172 | return QItemSelectionModel::ClearAndSelect | d->selectionBehaviorFlags(); |
4173 | case MultiSelection: |
4174 | return d->multiSelectionCommand(index, event); |
4175 | case ExtendedSelection: |
4176 | return d->extendedSelectionCommand(index, event); |
4177 | case ContiguousSelection: |
4178 | return d->contiguousSelectionCommand(index, event); |
4179 | } |
4180 | return QItemSelectionModel::NoUpdate; |
4181 | } |
4182 | |
4183 | QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::multiSelectionCommand( |
4184 | const QModelIndex &index, const QEvent *event) const |
4185 | { |
4186 | Q_UNUSED(index); |
4187 | |
4188 | if (event) { |
4189 | switch (event->type()) { |
4190 | case QEvent::KeyPress: |
4191 | if (static_cast<const QKeyEvent*>(event)->key() == Qt::Key_Space |
4192 | || static_cast<const QKeyEvent*>(event)->key() == Qt::Key_Select) |
4193 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4194 | break; |
4195 | case QEvent::MouseButtonPress: |
4196 | if (static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton) { |
4197 | // since the press might start a drag, deselect only on release |
4198 | if (!pressedAlreadySelected |
4199 | #if QT_CONFIG(draganddrop) |
4200 | || !dragEnabled || !isIndexDragEnabled(index) |
4201 | #endif |
4202 | ) |
4203 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); // toggle |
4204 | } |
4205 | break; |
4206 | case QEvent::MouseButtonRelease: |
4207 | if (static_cast<const QMouseEvent*>(event)->button() == Qt::LeftButton) { |
4208 | if (pressedAlreadySelected |
4209 | #if QT_CONFIG(draganddrop) |
4210 | && dragEnabled && isIndexDragEnabled(index) |
4211 | #endif |
4212 | && index == pressedIndex) |
4213 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4214 | return QItemSelectionModel::NoUpdate|selectionBehaviorFlags(); // finalize |
4215 | } |
4216 | break; |
4217 | case QEvent::MouseMove: |
4218 | if (static_cast<const QMouseEvent*>(event)->buttons() & Qt::LeftButton) |
4219 | return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); // toggle drag select |
4220 | break; |
4221 | default: |
4222 | break; |
4223 | } |
4224 | return QItemSelectionModel::NoUpdate; |
4225 | } |
4226 | |
4227 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4228 | } |
4229 | |
4230 | QItemSelectionModel::SelectionFlags QAbstractItemViewPrivate::extendedSelectionCommand( |
4231 | const QModelIndex &index, const QEvent *event) const |
4232 | { |
4233 | Qt::KeyboardModifiers modifiers = event && event->isInputEvent() |
4234 | ? static_cast<const QInputEvent*>(event)->modifiers() |
4235 | : QGuiApplication::keyboardModifiers(); |
4236 | if (event) { |
4237 | switch (event->type()) { |
4238 | case QEvent::MouseMove: { |
4239 | // Toggle on MouseMove |
4240 | if (modifiers & Qt::ControlModifier) |
4241 | return QItemSelectionModel::ToggleCurrent|selectionBehaviorFlags(); |
4242 | break; |
4243 | } |
4244 | case QEvent::MouseButtonPress: { |
4245 | const Qt::MouseButton button = static_cast<const QMouseEvent*>(event)->button(); |
4246 | const bool rightButtonPressed = button & Qt::RightButton; |
4247 | const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; |
4248 | const bool controlKeyPressed = modifiers & Qt::ControlModifier; |
4249 | const bool indexIsSelected = selectionModel->isSelected(index); |
4250 | if ((shiftKeyPressed || controlKeyPressed) && rightButtonPressed) |
4251 | return QItemSelectionModel::NoUpdate; |
4252 | if (!shiftKeyPressed && !controlKeyPressed && indexIsSelected) |
4253 | return QItemSelectionModel::NoUpdate; |
4254 | if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed) |
4255 | return QItemSelectionModel::Clear; |
4256 | if (!index.isValid()) |
4257 | return QItemSelectionModel::NoUpdate; |
4258 | // since the press might start a drag, deselect only on release |
4259 | if (controlKeyPressed && !rightButtonPressed && pressedAlreadySelected |
4260 | #if QT_CONFIG(draganddrop) |
4261 | && dragEnabled && isIndexDragEnabled(index) |
4262 | #endif |
4263 | ) { |
4264 | return QItemSelectionModel::NoUpdate; |
4265 | } |
4266 | break; |
4267 | } |
4268 | case QEvent::MouseButtonRelease: { |
4269 | // ClearAndSelect on MouseButtonRelease if MouseButtonPress on selected item or empty area |
4270 | const Qt::MouseButton button = static_cast<const QMouseEvent*>(event)->button(); |
4271 | const bool rightButtonPressed = button & Qt::RightButton; |
4272 | const bool shiftKeyPressed = modifiers & Qt::ShiftModifier; |
4273 | const bool controlKeyPressed = modifiers & Qt::ControlModifier; |
4274 | if (((index == pressedIndex && selectionModel->isSelected(index)) |
4275 | || !index.isValid()) && state != QAbstractItemView::DragSelectingState |
4276 | && !shiftKeyPressed && !controlKeyPressed && (!rightButtonPressed || !index.isValid())) |
4277 | return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); |
4278 | if (index == pressedIndex && controlKeyPressed && !rightButtonPressed |
4279 | #if QT_CONFIG(draganddrop) |
4280 | && dragEnabled && isIndexDragEnabled(index) |
4281 | #endif |
4282 | ) { |
4283 | break; |
4284 | } |
4285 | return QItemSelectionModel::NoUpdate; |
4286 | } |
4287 | case QEvent::KeyPress: { |
4288 | // NoUpdate on Key movement and Ctrl |
4289 | switch (static_cast<const QKeyEvent*>(event)->key()) { |
4290 | case Qt::Key_Backtab: |
4291 | modifiers = modifiers & ~Qt::ShiftModifier; // special case for backtab |
4292 | Q_FALLTHROUGH(); |
4293 | case Qt::Key_Down: |
4294 | case Qt::Key_Up: |
4295 | case Qt::Key_Left: |
4296 | case Qt::Key_Right: |
4297 | case Qt::Key_Home: |
4298 | case Qt::Key_End: |
4299 | case Qt::Key_PageUp: |
4300 | case Qt::Key_PageDown: |
4301 | case Qt::Key_Tab: |
4302 | if (modifiers & Qt::ControlModifier |
4303 | #ifdef QT_KEYPAD_NAVIGATION |
4304 | // Preserve historical tab order navigation behavior |
4305 | || QApplication::navigationMode() == Qt::NavigationModeKeypadTabOrder |
4306 | #endif |
4307 | ) |
4308 | return QItemSelectionModel::NoUpdate; |
4309 | break; |
4310 | case Qt::Key_Select: |
4311 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4312 | case Qt::Key_Space:// Toggle on Ctrl-Qt::Key_Space, Select on Space |
4313 | if (modifiers & Qt::ControlModifier) |
4314 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4315 | return QItemSelectionModel::Select|selectionBehaviorFlags(); |
4316 | default: |
4317 | break; |
4318 | } |
4319 | break; |
4320 | } |
4321 | default: |
4322 | break; |
4323 | } |
4324 | } |
4325 | |
4326 | if (modifiers & Qt::ShiftModifier) |
4327 | return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); |
4328 | if (modifiers & Qt::ControlModifier) |
4329 | return QItemSelectionModel::Toggle|selectionBehaviorFlags(); |
4330 | if (state == QAbstractItemView::DragSelectingState) { |
4331 | //when drag-selecting we need to clear any previous selection and select the current one |
4332 | return QItemSelectionModel::Clear|QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); |
4333 | } |
4334 | |
4335 | return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); |
4336 | } |
4337 | |
4338 | QItemSelectionModel::SelectionFlags |
4339 | QAbstractItemViewPrivate::contiguousSelectionCommand(const QModelIndex &index, |
4340 | const QEvent *event) const |
4341 | { |
4342 | QItemSelectionModel::SelectionFlags flags = extendedSelectionCommand(index, event); |
4343 | const int Mask = QItemSelectionModel::Clear | QItemSelectionModel::Select |
4344 | | QItemSelectionModel::Deselect | QItemSelectionModel::Toggle |
4345 | | QItemSelectionModel::Current; |
4346 | |
4347 | switch (flags & Mask) { |
4348 | case QItemSelectionModel::Clear: |
4349 | case QItemSelectionModel::ClearAndSelect: |
4350 | case QItemSelectionModel::SelectCurrent: |
4351 | return flags; |
4352 | case QItemSelectionModel::NoUpdate: |
4353 | if (event && |
4354 | (event->type() == QEvent::MouseButtonPress |
4355 | || event->type() == QEvent::MouseButtonRelease)) |
4356 | return flags; |
4357 | return QItemSelectionModel::ClearAndSelect|selectionBehaviorFlags(); |
4358 | default: |
4359 | return QItemSelectionModel::SelectCurrent|selectionBehaviorFlags(); |
4360 | } |
4361 | } |
4362 | |
4363 | void QAbstractItemViewPrivate::fetchMore() |
4364 | { |
4365 | fetchMoreTimer.stop(); |
4366 | if (!model->canFetchMore(parent: root)) |
4367 | return; |
4368 | int last = model->rowCount(parent: root) - 1; |
4369 | if (last < 0) { |
4370 | model->fetchMore(parent: root); |
4371 | return; |
4372 | } |
4373 | |
4374 | QModelIndex index = model->index(row: last, column: 0, parent: root); |
4375 | QRect rect = q_func()->visualRect(index); |
4376 | if (viewport->rect().intersects(r: rect)) |
4377 | model->fetchMore(parent: root); |
4378 | } |
4379 | |
4380 | bool QAbstractItemViewPrivate::shouldEdit(QAbstractItemView::EditTrigger trigger, |
4381 | const QModelIndex &index) const |
4382 | { |
4383 | if (!index.isValid()) |
4384 | return false; |
4385 | Qt::ItemFlags flags = model->flags(index); |
4386 | if (((flags & Qt::ItemIsEditable) == 0) || ((flags & Qt::ItemIsEnabled) == 0)) |
4387 | return false; |
4388 | if (state == QAbstractItemView::EditingState) |
4389 | return false; |
4390 | if (hasEditor(index)) |
4391 | return false; |
4392 | if (trigger == QAbstractItemView::AllEditTriggers) // force editing |
4393 | return true; |
4394 | if ((trigger & editTriggers) == QAbstractItemView::SelectedClicked |
4395 | && !selectionModel->isSelected(index)) |
4396 | return false; |
4397 | return (trigger & editTriggers); |
4398 | } |
4399 | |
4400 | bool QAbstractItemViewPrivate::shouldForwardEvent(QAbstractItemView::EditTrigger trigger, |
4401 | const QEvent *event) const |
4402 | { |
4403 | if (!event || (trigger & editTriggers) != QAbstractItemView::AnyKeyPressed) |
4404 | return false; |
4405 | |
4406 | switch (event->type()) { |
4407 | case QEvent::KeyPress: |
4408 | case QEvent::MouseButtonDblClick: |
4409 | case QEvent::MouseButtonPress: |
4410 | case QEvent::MouseButtonRelease: |
4411 | case QEvent::MouseMove: |
4412 | return true; |
4413 | default: |
4414 | break; |
4415 | }; |
4416 | |
4417 | return false; |
4418 | } |
4419 | |
4420 | bool QAbstractItemViewPrivate::shouldAutoScroll(const QPoint &pos) const |
4421 | { |
4422 | if (!autoScroll) |
4423 | return false; |
4424 | QRect area = static_cast<QAbstractItemView*>(viewport)->d_func()->clipRect(); // access QWidget private by bending C++ rules |
4425 | return (pos.y() - area.top() < autoScrollMargin) |
4426 | || (area.bottom() - pos.y() < autoScrollMargin) |
4427 | || (pos.x() - area.left() < autoScrollMargin) |
4428 | || (area.right() - pos.x() < autoScrollMargin); |
4429 | } |
4430 | |
4431 | void QAbstractItemViewPrivate::doDelayedItemsLayout(int delay) |
4432 | { |
4433 | if (!delayedPendingLayout) { |
4434 | delayedPendingLayout = true; |
4435 | delayedLayout.start(msec: delay, obj: q_func()); |
4436 | } |
4437 | } |
4438 | |
4439 | void QAbstractItemViewPrivate::interruptDelayedItemsLayout() const |
4440 | { |
4441 | delayedLayout.stop(); |
4442 | delayedPendingLayout = false; |
4443 | } |
4444 | |
4445 | void QAbstractItemViewPrivate::updateGeometry() |
4446 | { |
4447 | Q_Q(QAbstractItemView); |
4448 | if (sizeAdjustPolicy == QAbstractScrollArea::AdjustIgnored) |
4449 | return; |
4450 | if (sizeAdjustPolicy == QAbstractScrollArea::AdjustToContents || !shownOnce) |
4451 | q->updateGeometry(); |
4452 | } |
4453 | |
4454 | /* |
4455 | Handles selection of content for some editors containing QLineEdit. |
4456 | |
4457 | ### Qt 7 This should be done by a virtual method in QAbstractItemDelegate. |
4458 | */ |
4459 | void QAbstractItemViewPrivate::selectAllInEditor(QWidget *editor) |
4460 | { |
4461 | while (QWidget *fp = editor->focusProxy()) |
4462 | editor = fp; |
4463 | |
4464 | #if QT_CONFIG(lineedit) |
4465 | if (QLineEdit *le = qobject_cast<QLineEdit*>(object: editor)) |
4466 | le->selectAll(); |
4467 | #endif |
4468 | #if QT_CONFIG(spinbox) |
4469 | if (QSpinBox *sb = qobject_cast<QSpinBox*>(object: editor)) |
4470 | sb->selectAll(); |
4471 | else if (QDoubleSpinBox *dsb = qobject_cast<QDoubleSpinBox*>(object: editor)) |
4472 | dsb->selectAll(); |
4473 | #endif |
4474 | } |
4475 | |
4476 | QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index, |
4477 | const QStyleOptionViewItem &options) |
4478 | { |
4479 | Q_Q(QAbstractItemView); |
4480 | QWidget *w = editorForIndex(index).widget.data(); |
4481 | if (!w) { |
4482 | QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index); |
4483 | if (!delegate) |
4484 | return nullptr; |
4485 | w = delegate->createEditor(parent: viewport, option: options, index); |
4486 | if (w) { |
4487 | w->installEventFilter(filterObj: delegate); |
4488 | QObject::connect(sender: w, signal: &QWidget::destroyed, context: q, slot: &QAbstractItemView::editorDestroyed); |
4489 | delegate->updateEditorGeometry(editor: w, option: options, index); |
4490 | delegate->setEditorData(editor: w, index); |
4491 | addEditor(index, editor: w, isStatic: false); |
4492 | if (w->parent() == viewport) |
4493 | QWidget::setTabOrder(q, w); |
4494 | |
4495 | selectAllInEditor(editor: w); |
4496 | } |
4497 | } |
4498 | |
4499 | return w; |
4500 | } |
4501 | |
4502 | void QAbstractItemViewPrivate::updateEditorData(const QModelIndex &tl, const QModelIndex &br) |
4503 | { |
4504 | Q_Q(QAbstractItemView); |
4505 | // we are counting on having relatively few editors |
4506 | const bool checkIndexes = tl.isValid() && br.isValid(); |
4507 | const QModelIndex parent = tl.parent(); |
4508 | // QTBUG-25370: We need to copy the indexEditorHash, because while we're |
4509 | // iterating over it, we are calling methods which can allow user code to |
4510 | // call a method on *this which can modify the member indexEditorHash. |
4511 | const QIndexEditorHash indexEditorHashCopy = indexEditorHash; |
4512 | QIndexEditorHash::const_iterator it = indexEditorHashCopy.constBegin(); |
4513 | for (; it != indexEditorHashCopy.constEnd(); ++it) { |
4514 | QWidget *editor = it.value().widget.data(); |
4515 | const QModelIndex index = it.key(); |
4516 | if (it.value().isStatic || !editor || !index.isValid() || |
4517 | (checkIndexes |
4518 | && (index.row() < tl.row() || index.row() > br.row() |
4519 | || index.column() < tl.column() || index.column() > br.column() |
4520 | || index.parent() != parent))) |
4521 | continue; |
4522 | |
4523 | QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index); |
4524 | if (delegate) { |
4525 | delegate->setEditorData(editor, index); |
4526 | } |
4527 | } |
4528 | } |
4529 | |
4530 | /*! |
4531 | \internal |
4532 | |
4533 | In DND if something has been moved then this is called. |
4534 | Typically this means you should "remove" the selected item or row, |
4535 | but the behavior is view-dependent (table just clears the selected indexes for example). |
4536 | |
4537 | Either remove the selected rows or clear them |
4538 | */ |
4539 | void QAbstractItemViewPrivate::clearOrRemove() |
4540 | { |
4541 | #if QT_CONFIG(draganddrop) |
4542 | const QItemSelection selection = selectionModel->selection(); |
4543 | QList<QItemSelectionRange>::const_iterator it = selection.constBegin(); |
4544 | |
4545 | if (!overwrite) { |
4546 | for (; it != selection.constEnd(); ++it) { |
4547 | QModelIndex parent = (*it).parent(); |
4548 | if ((*it).left() != 0) |
4549 | continue; |
4550 | if ((*it).right() != (model->columnCount(parent) - 1)) |
4551 | continue; |
4552 | int count = (*it).bottom() - (*it).top() + 1; |
4553 | model->removeRows(row: (*it).top(), count, parent); |
4554 | } |
4555 | } else { |
4556 | // we can't remove the rows so reset the items (i.e. the view is like a table) |
4557 | QModelIndexList list = selection.indexes(); |
4558 | for (int i=0; i < list.size(); ++i) { |
4559 | QModelIndex index = list.at(i); |
4560 | QMap<int, QVariant> roles = model->itemData(index); |
4561 | for (QMap<int, QVariant>::Iterator it = roles.begin(); it != roles.end(); ++it) |
4562 | it.value() = QVariant(); |
4563 | model->setItemData(index, roles); |
4564 | } |
4565 | } |
4566 | #endif |
4567 | } |
4568 | |
4569 | /*! |
4570 | \internal |
4571 | |
4572 | When persistent aeditor gets/loses focus, we need to check |
4573 | and setcorrectly the current index. |
4574 | */ |
4575 | void QAbstractItemViewPrivate::checkPersistentEditorFocus() |
4576 | { |
4577 | Q_Q(QAbstractItemView); |
4578 | if (QWidget *widget = QApplication::focusWidget()) { |
4579 | if (persistent.contains(value: widget)) { |
4580 | //a persistent editor has gained the focus |
4581 | QModelIndex index = indexForEditor(editor: widget); |
4582 | if (selectionModel->currentIndex() != index) |
4583 | q->setCurrentIndex(index); |
4584 | } |
4585 | } |
4586 | } |
4587 | |
4588 | |
4589 | const QEditorInfo & QAbstractItemViewPrivate::editorForIndex(const QModelIndex &index) const |
4590 | { |
4591 | static QEditorInfo nullInfo; |
4592 | |
4593 | // do not try to search to avoid slow implicit cast from QModelIndex to QPersistentModelIndex |
4594 | if (indexEditorHash.isEmpty()) |
4595 | return nullInfo; |
4596 | |
4597 | QIndexEditorHash::const_iterator it = indexEditorHash.find(key: index); |
4598 | if (it == indexEditorHash.end()) |
4599 | return nullInfo; |
4600 | |
4601 | return it.value(); |
4602 | } |
4603 | |
4604 | bool QAbstractItemViewPrivate::hasEditor(const QModelIndex &index) const |
4605 | { |
4606 | // Search's implicit cast (QModelIndex to QPersistentModelIndex) is slow; use cheap pre-test to avoid when we can. |
4607 | return !indexEditorHash.isEmpty() && indexEditorHash.contains(key: index); |
4608 | } |
4609 | |
4610 | QModelIndex QAbstractItemViewPrivate::indexForEditor(QWidget *editor) const |
4611 | { |
4612 | // do not try to search to avoid slow implicit cast from QModelIndex to QPersistentModelIndex |
4613 | if (indexEditorHash.isEmpty()) |
4614 | return QModelIndex(); |
4615 | |
4616 | QEditorIndexHash::const_iterator it = editorIndexHash.find(key: editor); |
4617 | if (it == editorIndexHash.end()) |
4618 | return QModelIndex(); |
4619 | |
4620 | return it.value(); |
4621 | } |
4622 | |
4623 | void QAbstractItemViewPrivate::removeEditor(QWidget *editor) |
4624 | { |
4625 | Q_Q(QAbstractItemView); |
4626 | if (editor) |
4627 | QObject::disconnect(sender: editor, signal: &QWidget::destroyed, receiver: q, slot: &QAbstractItemView::editorDestroyed); |
4628 | const auto it = editorIndexHash.constFind(key: editor); |
4629 | if (it != editorIndexHash.cend()) { |
4630 | indexEditorHash.remove(key: it.value()); |
4631 | editorIndexHash.erase(it); |
4632 | } |
4633 | } |
4634 | |
4635 | void QAbstractItemViewPrivate::addEditor(const QModelIndex &index, QWidget *editor, bool isStatic) |
4636 | { |
4637 | editorIndexHash.insert(key: editor, value: index); |
4638 | indexEditorHash.insert(key: index, value: QEditorInfo(editor, isStatic)); |
4639 | } |
4640 | |
4641 | bool QAbstractItemViewPrivate::sendDelegateEvent(const QModelIndex &index, QEvent *event) const |
4642 | { |
4643 | Q_Q(const QAbstractItemView); |
4644 | QModelIndex buddy = model->buddy(index); |
4645 | QStyleOptionViewItem options; |
4646 | q->initViewItemOption(option: &options); |
4647 | options.rect = q->visualRect(index: buddy); |
4648 | options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); |
4649 | QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index); |
4650 | return (event && delegate && delegate->editorEvent(event, model, option: options, index: buddy)); |
4651 | } |
4652 | |
4653 | bool QAbstractItemViewPrivate::openEditor(const QModelIndex &index, QEvent *event) |
4654 | { |
4655 | Q_Q(QAbstractItemView); |
4656 | |
4657 | QModelIndex buddy = model->buddy(index); |
4658 | QStyleOptionViewItem options; |
4659 | q->initViewItemOption(option: &options); |
4660 | options.rect = q->visualRect(index: buddy); |
4661 | options.state |= (buddy == q->currentIndex() ? QStyle::State_HasFocus : QStyle::State_None); |
4662 | |
4663 | QWidget *w = editor(index: buddy, options); |
4664 | if (!w) |
4665 | return false; |
4666 | |
4667 | q->setState(QAbstractItemView::EditingState); |
4668 | w->show(); |
4669 | if (!waitForIMCommit) |
4670 | w->setFocus(); |
4671 | else |
4672 | q->updateMicroFocus(); |
4673 | |
4674 | if (event) |
4675 | QCoreApplication::sendEvent(receiver: w->focusProxy() ? w->focusProxy() : w, event); |
4676 | |
4677 | return true; |
4678 | } |
4679 | |
4680 | /* |
4681 | \internal |
4682 | |
4683 | returns the pair QRect/QModelIndex that should be painted on the viewports's rect |
4684 | */ |
4685 | |
4686 | QItemViewPaintPairs QAbstractItemViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const |
4687 | { |
4688 | Q_ASSERT(r); |
4689 | Q_Q(const QAbstractItemView); |
4690 | QRect &rect = *r; |
4691 | const QRect viewportRect = viewport->rect(); |
4692 | QItemViewPaintPairs ret; |
4693 | for (const auto &index : indexes) { |
4694 | const QRect current = q->visualRect(index); |
4695 | if (current.intersects(r: viewportRect)) { |
4696 | ret.append(t: {.rect: current, .index: index}); |
4697 | rect |= current; |
4698 | } |
4699 | } |
4700 | QRect clipped = rect & viewportRect; |
4701 | rect.setLeft(clipped.left()); |
4702 | rect.setRight(clipped.right()); |
4703 | return ret; |
4704 | } |
4705 | |
4706 | QPixmap QAbstractItemViewPrivate::renderToPixmap(const QModelIndexList &indexes, QRect *r) const |
4707 | { |
4708 | Q_Q(const QAbstractItemView); |
4709 | Q_ASSERT(r); |
4710 | QItemViewPaintPairs paintPairs = draggablePaintPairs(indexes, r); |
4711 | if (paintPairs.isEmpty()) |
4712 | return QPixmap(); |
4713 | |
4714 | QWindow *window = windowHandle(mode: WindowHandleMode::Closest); |
4715 | const qreal scale = window ? window->devicePixelRatio() : qreal(1); |
4716 | |
4717 | QPixmap pixmap(r->size() * scale); |
4718 | pixmap.setDevicePixelRatio(scale); |
4719 | |
4720 | pixmap.fill(fillColor: Qt::transparent); |
4721 | QPainter painter(&pixmap); |
4722 | QStyleOptionViewItem option; |
4723 | q->initViewItemOption(option: &option); |
4724 | option.state |= QStyle::State_Selected; |
4725 | for (int j = 0; j < paintPairs.size(); ++j) { |
4726 | option.rect = paintPairs.at(i: j).rect.translated(p: -r->topLeft()); |
4727 | const QModelIndex ¤t = paintPairs.at(i: j).index; |
4728 | adjustViewOptionsForIndex(&option, current); |
4729 | q->itemDelegateForIndex(index: current)->paint(painter: &painter, option, index: current); |
4730 | } |
4731 | return pixmap; |
4732 | } |
4733 | |
4734 | void QAbstractItemViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) |
4735 | { |
4736 | if (!selectionModel) |
4737 | return; |
4738 | if (!model->hasChildren(parent: root)) |
4739 | return; |
4740 | |
4741 | QItemSelection selection; |
4742 | QModelIndex tl = model->index(row: 0, column: 0, parent: root); |
4743 | QModelIndex br = model->index(row: model->rowCount(parent: root) - 1, |
4744 | column: model->columnCount(parent: root) - 1, |
4745 | parent: root); |
4746 | selection.append(t: QItemSelectionRange(tl, br)); |
4747 | selectionModel->select(selection, command); |
4748 | } |
4749 | |
4750 | #if QT_CONFIG(draganddrop) |
4751 | QModelIndexList QAbstractItemViewPrivate::selectedDraggableIndexes() const |
4752 | { |
4753 | Q_Q(const QAbstractItemView); |
4754 | QModelIndexList indexes = q->selectedIndexes(); |
4755 | auto isNotDragEnabled = [this](const QModelIndex &index) { |
4756 | return !isIndexDragEnabled(index); |
4757 | }; |
4758 | indexes.removeIf(pred: isNotDragEnabled); |
4759 | return indexes; |
4760 | } |
4761 | |
4762 | void QAbstractItemViewPrivate::maybeStartDrag(QPoint eventPosition) |
4763 | { |
4764 | Q_Q(QAbstractItemView); |
4765 | |
4766 | const QPoint topLeft = pressedPosition - offset(); |
4767 | if ((topLeft - eventPosition).manhattanLength() > QApplication::startDragDistance()) { |
4768 | pressedIndex = QModelIndex(); |
4769 | q->startDrag(supportedActions: model->supportedDragActions()); |
4770 | q->setState(QAbstractItemView::NoState); // the startDrag will return when the dnd operation |
4771 | // is done |
4772 | q->stopAutoScroll(); |
4773 | } |
4774 | } |
4775 | #endif |
4776 | |
4777 | /*! |
4778 | \reimp |
4779 | */ |
4780 | |
4781 | bool QAbstractItemView::eventFilter(QObject *object, QEvent *event) |
4782 | { |
4783 | Q_D(QAbstractItemView); |
4784 | if (object == this || object == viewport() || event->type() != QEvent::FocusIn) |
4785 | return QAbstractScrollArea::eventFilter(object, event); |
4786 | QWidget *widget = qobject_cast<QWidget *>(o: object); |
4787 | // If it is not a persistent widget then we did not install |
4788 | // the event filter on it, so assume a base implementation is |
4789 | // filtering |
4790 | if (!widget || !d->persistent.contains(value: widget)) |
4791 | return QAbstractScrollArea::eventFilter(object, event); |
4792 | setCurrentIndex(d->indexForEditor(editor: widget)); |
4793 | return false; |
4794 | } |
4795 | |
4796 | QT_END_NAMESPACE |
4797 | |
4798 | #include "moc_qabstractitemview.cpp" |
4799 |
Definitions
- QAbstractItemViewPrivate
- ~QAbstractItemViewPrivate
- init
- setHoverIndex
- checkMouseMove
- scrollerStateChanged
- delegateSizeHintChanged
- connectDelegate
- disconnectDelegate
- disconnectAll
- QAbstractItemView
- QAbstractItemView
- ~QAbstractItemView
- setModel
- model
- setSelectionModel
- selectionModel
- setItemDelegate
- itemDelegate
- inputMethodQuery
- setItemDelegateForRow
- itemDelegateForRow
- setItemDelegateForColumn
- itemDelegateForColumn
- itemDelegateForIndex
- setSelectionMode
- selectionMode
- setSelectionBehavior
- selectionBehavior
- setCurrentIndex
- currentIndex
- reset
- setRootIndex
- rootIndex
- selectAll
- edit
- clearSelection
- doItemsLayout
- setEditTriggers
- editTriggers
- setVerticalScrollMode
- verticalScrollMode
- resetVerticalScrollMode
- setHorizontalScrollMode
- horizontalScrollMode
- resetHorizontalScrollMode
- setDragDropOverwriteMode
- dragDropOverwriteMode
- setAutoScroll
- hasAutoScroll
- setAutoScrollMargin
- autoScrollMargin
- setTabKeyNavigation
- tabKeyNavigation
- viewportSizeHint
- setDropIndicatorShown
- showDropIndicator
- setDragEnabled
- dragEnabled
- setDragDropMode
- dragDropMode
- setDefaultDropAction
- defaultDropAction
- setAlternatingRowColors
- alternatingRowColors
- setIconSize
- iconSize
- setTextElideMode
- textElideMode
- focusNextPrevChild
- event
- viewportEvent
- mousePressEvent
- mouseMoveEvent
- mouseReleaseEvent
- mouseDoubleClickEvent
- dragEnterEvent
- dragMoveEvent
- droppingOnItself
- dragLeaveEvent
- dropEvent
- dropOn
- position
- focusInEvent
- focusOutEvent
- keyPressEvent
- resizeEvent
- timerEvent
- inputMethodEvent
- dropIndicatorPosition
- selectedIndexes
- edit
- updateEditorData
- updateEditorGeometries
- updateGeometries
- verticalScrollbarValueChanged
- horizontalScrollbarValueChanged
- verticalScrollbarAction
- horizontalScrollbarAction
- closeEditor
- commitData
- editorDestroyed
- keyboardSearch
- sizeHintForIndex
- sizeHintForRow
- sizeHintForColumn
- openPersistentEditor
- closePersistentEditor
- isPersistentEditorOpen
- setIndexWidget
- indexWidget
- scrollToTop
- scrollToBottom
- update
- dataChanged
- rowsInserted
- rowsAboutToBeRemoved
- rowsRemoved
- columnsAboutToBeRemoved
- columnsRemoved
- rowsInserted
- columnsInserted
- modelDestroyed
- layoutChanged
- rowsMoved
- columnsMoved
- intersectedRect
- selectionChanged
- currentChanged
- startDrag
- initViewItemOption
- state
- setState
- scheduleDelayedItemsLayout
- executeDelayedItemsLayout
- setDirtyRegion
- scrollDirtyRegion
- dirtyRegionOffset
- startAutoScroll
- stopAutoScroll
- doAutoScroll
- selectionCommand
- multiSelectionCommand
- extendedSelectionCommand
- contiguousSelectionCommand
- fetchMore
- shouldEdit
- shouldForwardEvent
- shouldAutoScroll
- doDelayedItemsLayout
- interruptDelayedItemsLayout
- updateGeometry
- selectAllInEditor
- editor
- updateEditorData
- clearOrRemove
- checkPersistentEditorFocus
- editorForIndex
- hasEditor
- indexForEditor
- removeEditor
- addEditor
- sendDelegateEvent
- openEditor
- draggablePaintPairs
- renderToPixmap
- selectAll
- selectedDraggableIndexes
- maybeStartDrag
Start learning QML with our Intro Training
Find out more