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