1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2013 Samuel Gaist <samuel.gaist@deltech.ch>
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qlistview.h"
6
7#include <qabstractitemdelegate.h>
8#if QT_CONFIG(accessibility)
9#include <qaccessible.h>
10#endif
11#include <qapplication.h>
12#include <qstylepainter.h>
13#include <qbitmap.h>
14#include <qdebug.h>
15#if QT_CONFIG(draganddrop)
16#include <qdrag.h>
17#endif
18#include <qevent.h>
19#include <qlist.h>
20#if QT_CONFIG(rubberband)
21#include <qrubberband.h>
22#endif
23#include <qscrollbar.h>
24#include <qstyle.h>
25#include <private/qapplication_p.h>
26#include <private/qlistview_p.h>
27#include <private/qscrollbar_p.h>
28
29#include <algorithm>
30
31QT_BEGIN_NAMESPACE
32
33extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
34
35/*!
36 \class QListView
37
38 \brief The QListView class provides a list or icon view onto a model.
39
40 \ingroup model-view
41 \ingroup advanced
42 \inmodule QtWidgets
43
44 \image windows-listview.png
45
46 A QListView presents items stored in a model, either as a simple
47 non-hierarchical list, or as a collection of icons. This class is used
48 to provide lists and icon views that were previously provided by the
49 \c QListBox and \c QIconView classes, but using the more flexible
50 approach provided by Qt's model/view architecture.
51
52 The QListView class is one of the \l{Model/View Classes}
53 and is part of Qt's \l{Model/View Programming}{model/view framework}.
54
55 This view does not display horizontal or vertical headers; to display
56 a list of items with a horizontal header, use QTreeView instead.
57
58 QListView implements the interfaces defined by the
59 QAbstractItemView class to allow it to display data provided by
60 models derived from the QAbstractItemModel class.
61
62 Items in a list view can be displayed using one of two view modes:
63 In \l ListMode, the items are displayed in the form of a simple list;
64 in \l IconMode, the list view takes the form of an \e{icon view} in
65 which the items are displayed with icons like files in a file manager.
66 By default, the list view is in \l ListMode. To change the view mode,
67 use the setViewMode() function, and to determine the current view mode,
68 use viewMode().
69
70 Items in these views are laid out in the direction specified by the
71 flow() of the list view. The items may be fixed in place, or allowed
72 to move, depending on the view's movement() state.
73
74 If the items in the model cannot be completely laid out in the
75 direction of flow, they can be wrapped at the boundary of the view
76 widget; this depends on isWrapping(). This property is useful when the
77 items are being represented by an icon view.
78
79 The resizeMode() and layoutMode() govern how and when the items are
80 laid out. Items are spaced according to their spacing(), and can exist
81 within a notional grid of size specified by gridSize(). The items can
82 be rendered as large or small icons depending on their iconSize().
83
84 \section1 Improving Performance
85
86 It is possible to give the view hints about the data it is handling in order
87 to improve its performance when displaying large numbers of items. One approach
88 that can be taken for views that are intended to display items with equal sizes
89 is to set the \l uniformItemSizes property to true.
90
91 \sa {View Classes}, QTreeView, QTableView, QListWidget
92*/
93
94/*!
95 \enum QListView::ViewMode
96
97 \value ListMode The items are laid out using TopToBottom flow, with Small size and Static movement
98 \value IconMode The items are laid out using LeftToRight flow, with Large size and Free movement
99*/
100
101/*!
102 \enum QListView::Movement
103
104 \value Static The items cannot be moved by the user.
105 \value Free The items can be moved freely by the user.
106 \value Snap The items snap to the specified grid when moved; see
107 setGridSize().
108*/
109
110/*!
111 \enum QListView::Flow
112
113 \value LeftToRight The items are laid out in the view from the left
114 to the right.
115 \value TopToBottom The items are laid out in the view from the top
116 to the bottom.
117*/
118
119/*!
120 \enum QListView::ResizeMode
121
122 \value Fixed The items will only be laid out the first time the view is shown.
123 \value Adjust The items will be laid out every time the view is resized.
124*/
125
126/*!
127 \enum QListView::LayoutMode
128
129 \value SinglePass The items are laid out all at once.
130 \value Batched The items are laid out in batches of \l batchSize items.
131 \sa batchSize
132*/
133
134/*!
135 \since 4.2
136 \fn void QListView::indexesMoved(const QModelIndexList &indexes)
137
138 This signal is emitted when the specified \a indexes are moved in the view.
139*/
140
141/*!
142 Creates a new QListView with the given \a parent to view a model.
143 Use setModel() to set the model.
144*/
145QListView::QListView(QWidget *parent)
146 : QAbstractItemView(*new QListViewPrivate, parent)
147{
148 setViewMode(ListMode);
149 setSelectionMode(SingleSelection);
150 setAttribute(Qt::WA_MacShowFocusRect);
151 Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change
152 d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed
153}
154
155/*!
156 \internal
157*/
158QListView::QListView(QListViewPrivate &dd, QWidget *parent)
159 : QAbstractItemView(dd, parent)
160{
161 setViewMode(ListMode);
162 setSelectionMode(SingleSelection);
163 setAttribute(Qt::WA_MacShowFocusRect);
164 Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change
165 d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed
166}
167
168/*!
169 Destroys the view.
170*/
171QListView::~QListView()
172{
173}
174
175/*!
176 \property QListView::movement
177 \brief whether the items can be moved freely, are snapped to a
178 grid, or cannot be moved at all.
179
180 This property determines how the user can move the items in the
181 view. \l Static means that the items can't be moved by the user.
182 \l Free means that the user can drag and drop the items to any
183 position in the view. \l Snap means that the user can drag and
184 drop the items, but only to the positions in a notional grid
185 signified by the gridSize property.
186
187 Setting this property when the view is visible will cause the
188 items to be laid out again.
189
190 By default, this property is set to \l Static.
191
192 \sa gridSize, resizeMode, viewMode
193*/
194void QListView::setMovement(Movement movement)
195{
196 Q_D(QListView);
197 d->modeProperties |= uint(QListViewPrivate::Movement);
198 d->movement = movement;
199
200#if QT_CONFIG(draganddrop)
201 bool movable = (movement != Static);
202 setDragEnabled(movable);
203 d->viewport->setAcceptDrops(movable);
204#endif
205 d->doDelayedItemsLayout();
206}
207
208QListView::Movement QListView::movement() const
209{
210 Q_D(const QListView);
211 return d->movement;
212}
213
214/*!
215 \property QListView::flow
216 \brief which direction the items layout should flow.
217
218 If this property is \l LeftToRight, the items will be laid out left
219 to right. If the \l isWrapping property is \c true, the layout will wrap
220 when it reaches the right side of the visible area. If this
221 property is \l TopToBottom, the items will be laid out from the top
222 of the visible area, wrapping when it reaches the bottom.
223
224 Setting this property when the view is visible will cause the
225 items to be laid out again.
226
227 By default, this property is set to \l TopToBottom.
228
229 \sa viewMode
230*/
231void QListView::setFlow(Flow flow)
232{
233 Q_D(QListView);
234 d->modeProperties |= uint(QListViewPrivate::Flow);
235 d->flow = flow;
236 d->doDelayedItemsLayout();
237}
238
239QListView::Flow QListView::flow() const
240{
241 Q_D(const QListView);
242 return d->flow;
243}
244
245/*!
246 \property QListView::isWrapping
247 \brief whether the items layout should wrap.
248
249 This property holds whether the layout should wrap when there is
250 no more space in the visible area. The point at which the layout wraps
251 depends on the \l flow property.
252
253 Setting this property when the view is visible will cause the
254 items to be laid out again.
255
256 By default, this property is \c false.
257
258 \sa viewMode
259*/
260void QListView::setWrapping(bool enable)
261{
262 Q_D(QListView);
263 d->modeProperties |= uint(QListViewPrivate::Wrap);
264 d->setWrapping(enable);
265 d->doDelayedItemsLayout();
266}
267
268bool QListView::isWrapping() const
269{
270 Q_D(const QListView);
271 return d->isWrapping();
272}
273
274/*!
275 \property QListView::resizeMode
276 \brief whether the items are laid out again when the view is resized.
277
278 If this property is \l Adjust, the items will be laid out again
279 when the view is resized. If the value is \l Fixed, the items will
280 not be laid out when the view is resized.
281
282 By default, this property is set to \l Fixed.
283
284 \sa movement, gridSize, viewMode
285*/
286void QListView::setResizeMode(ResizeMode mode)
287{
288 Q_D(QListView);
289 d->modeProperties |= uint(QListViewPrivate::ResizeMode);
290 d->resizeMode = mode;
291}
292
293QListView::ResizeMode QListView::resizeMode() const
294{
295 Q_D(const QListView);
296 return d->resizeMode;
297}
298
299/*!
300 \property QListView::layoutMode
301 \brief determines whether the layout of items should happen immediately or be delayed.
302
303 This property holds the layout mode for the items. When the mode
304 is \l SinglePass (the default), the items are laid out all in one go.
305 When the mode is \l Batched, the items are laid out in batches of \l batchSize
306 items, while processing events. This makes it possible to
307 instantly view and interact with the visible items while the rest
308 are being laid out.
309
310 \sa viewMode
311*/
312void QListView::setLayoutMode(LayoutMode mode)
313{
314 Q_D(QListView);
315 d->layoutMode = mode;
316}
317
318QListView::LayoutMode QListView::layoutMode() const
319{
320 Q_D(const QListView);
321 return d->layoutMode;
322}
323
324/*!
325 \property QListView::spacing
326 \brief the space around the items in the layout
327
328 This property is the size of the empty space that is padded around
329 an item in the layout.
330
331 Setting this property when the view is visible will cause the
332 items to be laid out again.
333
334 By default, this property contains a value of 0.
335
336 \sa viewMode
337*/
338void QListView::setSpacing(int space)
339{
340 Q_D(QListView);
341 d->modeProperties |= uint(QListViewPrivate::Spacing);
342 d->setSpacing(space);
343 d->doDelayedItemsLayout();
344}
345
346int QListView::spacing() const
347{
348 Q_D(const QListView);
349 return d->spacing();
350}
351
352/*!
353 \property QListView::batchSize
354 \brief the number of items laid out in each batch if \l layoutMode is
355 set to \l Batched
356
357 The default value is 100.
358
359 \since 4.2
360*/
361
362void QListView::setBatchSize(int batchSize)
363{
364 Q_D(QListView);
365 if (Q_UNLIKELY(batchSize <= 0)) {
366 qWarning(msg: "Invalid batchSize (%d)", batchSize);
367 return;
368 }
369 d->batchSize = batchSize;
370}
371
372int QListView::batchSize() const
373{
374 Q_D(const QListView);
375 return d->batchSize;
376}
377
378/*!
379 \property QListView::gridSize
380 \brief the size of the layout grid
381
382 This property is the size of the grid in which the items are laid
383 out. The default is an empty size which means that there is no
384 grid and the layout is not done in a grid. Setting this property
385 to a non-empty size switches on the grid layout. (When a grid
386 layout is in force the \l spacing property is ignored.)
387
388 Setting this property when the view is visible will cause the
389 items to be laid out again.
390
391 \sa viewMode
392*/
393void QListView::setGridSize(const QSize &size)
394{
395 Q_D(QListView);
396 d->modeProperties |= uint(QListViewPrivate::GridSize);
397 d->setGridSize(size);
398 d->doDelayedItemsLayout();
399}
400
401QSize QListView::gridSize() const
402{
403 Q_D(const QListView);
404 return d->gridSize();
405}
406
407/*!
408 \property QListView::viewMode
409 \brief the view mode of the QListView.
410
411 This property will change the other unset properties to conform
412 with the set view mode. QListView-specific properties that have already been set
413 will not be changed, unless clearPropertyFlags() has been called.
414
415 Setting the view mode will enable or disable drag and drop based on the
416 selected movement. For ListMode, the default movement is \l Static
417 (drag and drop disabled); for IconMode, the default movement is
418 \l Free (drag and drop enabled).
419
420 \sa isWrapping, spacing, gridSize, flow, movement, resizeMode
421*/
422void QListView::setViewMode(ViewMode mode)
423{
424 Q_D(QListView);
425 if (d->commonListView && d->viewMode == mode)
426 return;
427 d->viewMode = mode;
428
429 delete d->commonListView;
430 if (mode == ListMode) {
431 d->commonListView = new QListModeViewBase(this, d);
432 if (!(d->modeProperties & QListViewPrivate::Wrap))
433 d->setWrapping(false);
434 if (!(d->modeProperties & QListViewPrivate::Spacing))
435 d->setSpacing(0);
436 if (!(d->modeProperties & QListViewPrivate::GridSize))
437 d->setGridSize(QSize());
438 if (!(d->modeProperties & QListViewPrivate::Flow))
439 d->flow = TopToBottom;
440 if (!(d->modeProperties & QListViewPrivate::Movement))
441 d->movement = Static;
442 if (!(d->modeProperties & QListViewPrivate::ResizeMode))
443 d->resizeMode = Fixed;
444 if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible))
445 d->showElasticBand = false;
446 } else {
447 d->commonListView = new QIconModeViewBase(this, d);
448 if (!(d->modeProperties & QListViewPrivate::Wrap))
449 d->setWrapping(true);
450 if (!(d->modeProperties & QListViewPrivate::Spacing))
451 d->setSpacing(0);
452 if (!(d->modeProperties & QListViewPrivate::GridSize))
453 d->setGridSize(QSize());
454 if (!(d->modeProperties & QListViewPrivate::Flow))
455 d->flow = LeftToRight;
456 if (!(d->modeProperties & QListViewPrivate::Movement))
457 d->movement = Free;
458 if (!(d->modeProperties & QListViewPrivate::ResizeMode))
459 d->resizeMode = Fixed;
460 if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible))
461 d->showElasticBand = true;
462 }
463
464#if QT_CONFIG(draganddrop)
465 bool movable = (d->movement != Static);
466 setDragEnabled(movable);
467 setAcceptDrops(movable);
468#endif
469 d->clear();
470 d->doDelayedItemsLayout();
471}
472
473QListView::ViewMode QListView::viewMode() const
474{
475 Q_D(const QListView);
476 return d->viewMode;
477}
478
479/*!
480 Clears the QListView-specific property flags. See \l{viewMode}.
481
482 Properties inherited from QAbstractItemView are not covered by the
483 property flags. Specifically, \l{QAbstractItemView::dragEnabled}
484 {dragEnabled} and \l{QAbstractItemView::acceptDrops}
485 {acceptsDrops} are computed by QListView when calling
486 setMovement() or setViewMode().
487*/
488void QListView::clearPropertyFlags()
489{
490 Q_D(QListView);
491 d->modeProperties = 0;
492}
493
494/*!
495 Returns \c true if the \a row is hidden; otherwise returns \c false.
496*/
497bool QListView::isRowHidden(int row) const
498{
499 Q_D(const QListView);
500 return d->isHidden(row);
501}
502
503/*!
504 If \a hide is true, the given \a row will be hidden; otherwise
505 the \a row will be shown.
506*/
507void QListView::setRowHidden(int row, bool hide)
508{
509 Q_D(QListView);
510 const bool hidden = d->isHidden(row);
511 if (hide && !hidden)
512 d->commonListView->appendHiddenRow(row);
513 else if (!hide && hidden)
514 d->commonListView->removeHiddenRow(row);
515 d->doDelayedItemsLayout();
516 d->viewport->update();
517}
518
519/*!
520 \reimp
521*/
522QRect QListView::visualRect(const QModelIndex &index) const
523{
524 Q_D(const QListView);
525 return d->mapToViewport(rect: rectForIndex(index));
526}
527
528/*!
529 \reimp
530*/
531void QListView::scrollTo(const QModelIndex &index, ScrollHint hint)
532{
533 Q_D(QListView);
534
535 if (index.parent() != d->root || index.column() != d->column)
536 return;
537
538 const QRect rect = visualRect(index);
539 if (!rect.isValid())
540 return;
541 if (hint == EnsureVisible && d->viewport->rect().contains(r: rect)) {
542 d->viewport->update(rect);
543 return;
544 }
545
546 if (d->flow == QListView::TopToBottom || d->isWrapping()) // vertical
547 verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint));
548
549 if (d->flow == QListView::LeftToRight || d->isWrapping()) // horizontal
550 horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint));
551}
552
553int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect,
554 QListView::ScrollHint hint) const
555{
556 Q_Q(const QListView);
557 const QRect area = viewport->rect();
558 const bool leftOf = q->isRightToLeft()
559 ? (rect.left() < area.left()) && (rect.right() < area.right())
560 : rect.left() < area.left();
561 const bool rightOf = q->isRightToLeft()
562 ? rect.right() > area.right()
563 : (rect.right() > area.right()) && (rect.left() > area.left());
564 return commonListView->horizontalScrollToValue(index: q->visualIndex(index), hint, leftOf, rightOf, area, rect);
565}
566
567int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect,
568 QListView::ScrollHint hint) const
569{
570 Q_Q(const QListView);
571 const QRect area = viewport->rect();
572 const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top());
573 const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom());
574 return commonListView->verticalScrollToValue(index: q->visualIndex(index), hint, above, below, area, rect);
575}
576
577void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command)
578{
579 if (!selectionModel)
580 return;
581
582 QItemSelection selection;
583 QModelIndex topLeft;
584 int row = 0;
585 const int colCount = model->columnCount(parent: root);
586 for(; row < model->rowCount(parent: root); ++row) {
587 if (isHidden(row)) {
588 //it might be the end of a selection range
589 if (topLeft.isValid()) {
590 QModelIndex bottomRight = model->index(row: row - 1, column: colCount - 1, parent: root);
591 selection.append(t: QItemSelectionRange(topLeft, bottomRight));
592 topLeft = QModelIndex();
593 }
594 continue;
595 }
596
597 if (!topLeft.isValid()) //start of a new selection range
598 topLeft = model->index(row, column: 0, parent: root);
599 }
600
601 if (topLeft.isValid()) {
602 //last selected range
603 QModelIndex bottomRight = model->index(row: row - 1, column: colCount - 1, parent: root);
604 selection.append(t: QItemSelectionRange(topLeft, bottomRight));
605 }
606
607 if (!selection.isEmpty())
608 selectionModel->select(selection, command);
609}
610
611/*!
612 \reimp
613
614 We have a QListView way of knowing what elements are on the viewport
615 through the intersectingSet function
616*/
617QItemViewPaintPairs QListViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
618{
619 Q_ASSERT(r);
620 Q_Q(const QListView);
621 QRect &rect = *r;
622 const QRect viewportRect = viewport->rect();
623 QItemViewPaintPairs ret;
624 QList<QModelIndex> visibleIndexes =
625 intersectingSet(area: viewportRect.translated(dx: q->horizontalOffset(), dy: q->verticalOffset()));
626 std::sort(first: visibleIndexes.begin(), last: visibleIndexes.end());
627 for (const auto &index : indexes) {
628 if (std::binary_search(first: visibleIndexes.cbegin(), last: visibleIndexes.cend(), val: index)) {
629 const QRect current = q->visualRect(index);
630 ret.append(t: {.rect: current, .index: index});
631 rect |= current;
632 }
633 }
634 QRect clipped = rect & viewportRect;
635 rect.setLeft(clipped.left());
636 rect.setRight(clipped.right());
637 return ret;
638}
639
640/*!
641 \internal
642*/
643void QListView::reset()
644{
645 Q_D(QListView);
646 d->clear();
647 d->hiddenRows.clear();
648 QAbstractItemView::reset();
649}
650
651/*!
652 \reimp
653*/
654void QListView::setRootIndex(const QModelIndex &index)
655{
656 Q_D(QListView);
657 d->column = qMax(a: 0, b: qMin(a: d->column, b: d->model->columnCount(parent: index) - 1));
658 QAbstractItemView::setRootIndex(index);
659 // sometimes we get an update before reset() is called
660 d->clear();
661 d->hiddenRows.clear();
662}
663
664/*!
665 \reimp
666
667 Scroll the view contents by \a dx and \a dy.
668*/
669
670void QListView::scrollContentsBy(int dx, int dy)
671{
672 Q_D(QListView);
673 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
674 d->commonListView->scrollContentsBy(dx, dy, scrollElasticBand: d->state == QListView::DragSelectingState);
675}
676
677/*!
678 \internal
679
680 Resize the internal contents to \a width and \a height and set the
681 scroll bar ranges accordingly.
682*/
683void QListView::resizeContents(int width, int height)
684{
685 Q_D(QListView);
686 d->setContentsSize(w: width, h: height);
687}
688
689/*!
690 \internal
691*/
692QSize QListView::contentsSize() const
693{
694 Q_D(const QListView);
695 return d->contentsSize();
696}
697
698/*!
699 \reimp
700*/
701void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
702 const QList<int> &roles)
703{
704 d_func()->commonListView->dataChanged(topLeft, bottomRight);
705 QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
706}
707
708/*!
709 \reimp
710*/
711void QListView::rowsInserted(const QModelIndex &parent, int start, int end)
712{
713 Q_D(QListView);
714 // ### be smarter about inserted items
715 d->clear();
716 d->doDelayedItemsLayout();
717 QAbstractItemView::rowsInserted(parent, start, end);
718}
719
720/*!
721 \reimp
722*/
723void QListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
724{
725 Q_D(QListView);
726 // if the parent is above d->root in the tree, nothing will happen
727 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
728 if (parent == d->root) {
729 QSet<QPersistentModelIndex>::iterator it = d->hiddenRows.begin();
730 while (it != d->hiddenRows.end()) {
731 int hiddenRow = it->row();
732 if (hiddenRow >= start && hiddenRow <= end) {
733 it = d->hiddenRows.erase(i: it);
734 } else {
735 ++it;
736 }
737 }
738 }
739 d->clear();
740 d->doDelayedItemsLayout();
741}
742
743/*!
744 \reimp
745*/
746void QListView::mouseMoveEvent(QMouseEvent *e)
747{
748 if (!isVisible())
749 return;
750 Q_D(QListView);
751 QAbstractItemView::mouseMoveEvent(event: e);
752 if (state() == DragSelectingState
753 && d->showElasticBand
754 && d->selectionMode != SingleSelection
755 && d->selectionMode != NoSelection) {
756 QRect rect(d->pressedPosition, e->position().toPoint() + QPoint(horizontalOffset(), verticalOffset()));
757 rect = rect.normalized();
758 d->viewport->update(d->mapToViewport(rect: rect.united(r: d->elasticBand)));
759 d->elasticBand = rect;
760 }
761}
762
763/*!
764 \reimp
765*/
766void QListView::mouseReleaseEvent(QMouseEvent *e)
767{
768 Q_D(QListView);
769 QAbstractItemView::mouseReleaseEvent(event: e);
770 // #### move this implementation into a dynamic class
771 if (d->showElasticBand && d->elasticBand.isValid()) {
772 d->viewport->update(d->mapToViewport(rect: d->elasticBand));
773 d->elasticBand = QRect();
774 }
775}
776
777#if QT_CONFIG(wheelevent)
778/*!
779 \reimp
780*/
781void QListView::wheelEvent(QWheelEvent *e)
782{
783 Q_D(QListView);
784 if (qAbs(t: e->angleDelta().y()) > qAbs(t: e->angleDelta().x())) {
785 if (e->angleDelta().x() == 0
786 && ((d->flow == TopToBottom && d->wrap) || (d->flow == LeftToRight && !d->wrap))
787 && d->vbar->minimum() == 0 && d->vbar->maximum() == 0) {
788 QPoint pixelDelta(e->pixelDelta().y(), e->pixelDelta().x());
789 QPoint angleDelta(e->angleDelta().y(), e->angleDelta().x());
790 QWheelEvent hwe(e->position(), e->globalPosition(), pixelDelta, angleDelta,
791 e->buttons(), e->modifiers(), e->phase(), e->inverted(), e->source());
792 if (e->spontaneous())
793 qt_sendSpontaneousEvent(receiver: d->hbar, event: &hwe);
794 else
795 QCoreApplication::sendEvent(receiver: d->hbar, event: &hwe);
796 e->setAccepted(hwe.isAccepted());
797 } else {
798 QCoreApplication::sendEvent(receiver: d->vbar, event: e);
799 }
800 } else {
801 QCoreApplication::sendEvent(receiver: d->hbar, event: e);
802 }
803}
804#endif // QT_CONFIG(wheelevent)
805
806/*!
807 \reimp
808*/
809void QListView::timerEvent(QTimerEvent *e)
810{
811 Q_D(QListView);
812 if (e->timerId() == d->batchLayoutTimer.timerId()) {
813 if (d->doItemsLayout(num: d->batchSize)) { // layout is done
814 d->batchLayoutTimer.stop();
815 updateGeometries();
816 d->viewport->update();
817 }
818 }
819 QAbstractItemView::timerEvent(event: e);
820}
821
822/*!
823 \reimp
824*/
825void QListView::resizeEvent(QResizeEvent *e)
826{
827 Q_D(QListView);
828 if (d->delayedPendingLayout)
829 return;
830
831 QSize delta = e->size() - e->oldSize();
832
833 if (delta.isNull())
834 return;
835
836 bool listWrap = (d->viewMode == ListMode) && d->wrapItemText;
837 bool flowDimensionChanged = (d->flow == LeftToRight && delta.width() != 0)
838 || (d->flow == TopToBottom && delta.height() != 0);
839
840 // We post a delayed relayout in the following cases :
841 // - we're wrapping
842 // - the state is NoState, we're adjusting and the size has changed in the flowing direction
843 if (listWrap
844 || (state() == NoState && d->resizeMode == Adjust && flowDimensionChanged)) {
845 d->doDelayedItemsLayout(delay: 100); // wait 1/10 sec before starting the layout
846 } else {
847 QAbstractItemView::resizeEvent(event: e);
848 }
849}
850
851#if QT_CONFIG(draganddrop)
852
853/*!
854 \reimp
855*/
856void QListView::dragMoveEvent(QDragMoveEvent *e)
857{
858 Q_D(QListView);
859 if (!d->commonListView->filterDragMoveEvent(e)) {
860 if (viewMode() == QListView::ListMode && flow() == QListView::LeftToRight)
861 static_cast<QListModeViewBase *>(d->commonListView)->dragMoveEvent(e);
862 else
863 QAbstractItemView::dragMoveEvent(event: e);
864 }
865}
866
867
868/*!
869 \reimp
870*/
871void QListView::dragLeaveEvent(QDragLeaveEvent *e)
872{
873 if (!d_func()->commonListView->filterDragLeaveEvent(e))
874 QAbstractItemView::dragLeaveEvent(event: e);
875}
876
877/*!
878 \reimp
879*/
880void QListView::dropEvent(QDropEvent *event)
881{
882 Q_D(QListView);
883
884 if (event->source() == this && (event->dropAction() == Qt::MoveAction ||
885 dragDropMode() == QAbstractItemView::InternalMove)) {
886 QModelIndex topIndex;
887 bool topIndexDropped = false;
888 int col = -1;
889 int row = -1;
890 // check whether a subclass has already accepted the event, ie. moved the data
891 if (!event->isAccepted() && d->dropOn(event, row: &row, col: &col, index: &topIndex)) {
892 const QList<QModelIndex> selIndexes = selectedIndexes();
893 QList<QPersistentModelIndex> persIndexes;
894 persIndexes.reserve(asize: selIndexes.size());
895
896 for (const auto &index : selIndexes) {
897 persIndexes.append(t: index);
898 if (index == topIndex) {
899 topIndexDropped = true;
900 break;
901 }
902 }
903
904 if (!topIndexDropped && !topIndex.isValid()) {
905 std::sort(first: persIndexes.begin(), last: persIndexes.end()); // The dropped items will remain in the same visual order.
906
907 QPersistentModelIndex dropRow = model()->index(row, column: col, parent: topIndex);
908
909 int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row);
910 bool dataMoved = false;
911 for (int i = 0; i < persIndexes.size(); ++i) {
912 const QPersistentModelIndex &pIndex = persIndexes.at(i);
913 // only generate a move when not same row or behind itself
914 if (r != pIndex.row() && r != pIndex.row() + 1) {
915 // try to move (preserves selection)
916 dataMoved |= model()->moveRow(sourceParent: QModelIndex(), sourceRow: pIndex.row(), destinationParent: QModelIndex(), destinationChild: r);
917 if (!dataMoved) // can't move - abort and let QAbstractItemView handle this
918 break;
919 } else {
920 // move onto itself is blocked, don't delete anything
921 dataMoved = true;
922 }
923 r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order.
924 }
925 if (dataMoved)
926 event->accept();
927 }
928 }
929
930 // either we or a subclass accepted the move event, so assume that the data was
931 // moved and that QAbstractItemView shouldn't remove the source when QDrag::exec returns
932 if (event->isAccepted())
933 d->dropEventMoved = true;
934 }
935
936 if (!d->commonListView->filterDropEvent(event) || !d->dropEventMoved) {
937 // icon view didn't move the data, and moveRows not implemented, so fall back to default
938 if (!d->dropEventMoved)
939 event->ignore();
940 QAbstractItemView::dropEvent(event);
941 }
942}
943
944/*!
945 \reimp
946*/
947void QListView::startDrag(Qt::DropActions supportedActions)
948{
949 if (!d_func()->commonListView->filterStartDrag(supportedActions))
950 QAbstractItemView::startDrag(supportedActions);
951}
952
953#endif // QT_CONFIG(draganddrop)
954
955/*!
956 \reimp
957*/
958void QListView::initViewItemOption(QStyleOptionViewItem *option) const
959{
960 Q_D(const QListView);
961 QAbstractItemView::initViewItemOption(option);
962 if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview
963 int pm = (d->viewMode == QListView::ListMode
964 ? style()->pixelMetric(metric: QStyle::PM_ListViewIconSize, option: nullptr, widget: this)
965 : style()->pixelMetric(metric: QStyle::PM_IconViewIconSize, option: nullptr, widget: this));
966 option->decorationSize = QSize(pm, pm);
967 }
968 if (d->viewMode == QListView::IconMode) {
969 option->showDecorationSelected = false;
970 option->decorationPosition = QStyleOptionViewItem::Top;
971 option->displayAlignment = Qt::AlignCenter;
972 } else {
973 option->decorationPosition = QStyleOptionViewItem::Left;
974 }
975
976 if (d->gridSize().isValid()) {
977 option->rect.setSize(d->gridSize());
978 }
979}
980
981
982/*!
983 \reimp
984*/
985void QListView::paintEvent(QPaintEvent *e)
986{
987 Q_D(QListView);
988 if (!d->itemDelegate)
989 return;
990 QStyleOptionViewItem option;
991 initViewItemOption(option: &option);
992 QStylePainter painter(d->viewport);
993
994 const QList<QModelIndex> toBeRendered =
995 d->intersectingSet(area: e->rect().translated(dx: horizontalOffset(), dy: verticalOffset()), doLayout: false);
996
997 const QModelIndex current = currentIndex();
998 const QModelIndex hover = d->hover;
999 const QAbstractItemModel *itemModel = d->model;
1000 const QItemSelectionModel *selections = d->selectionModel;
1001 const bool focus = (hasFocus() || d->viewport->hasFocus()) && current.isValid();
1002 const bool alternate = d->alternatingColors;
1003 const QStyle::State state = option.state;
1004 const QAbstractItemView::State viewState = this->state();
1005 const bool enabled = (state & QStyle::State_Enabled) != 0;
1006
1007 bool alternateBase = false;
1008 int previousRow = -2; // trigger the alternateBase adjustment on first pass
1009
1010 int maxSize = (flow() == TopToBottom)
1011 ? qMax(a: viewport()->size().width(), b: d->contentsSize().width()) - 2 * d->spacing()
1012 : qMax(a: viewport()->size().height(), b: d->contentsSize().height()) - 2 * d->spacing();
1013
1014 QList<QModelIndex>::const_iterator end = toBeRendered.constEnd();
1015 for (QList<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) {
1016 Q_ASSERT((*it).isValid());
1017 option.rect = visualRect(index: *it);
1018
1019 if (flow() == TopToBottom)
1020 option.rect.setWidth(qMin(a: maxSize, b: option.rect.width()));
1021 else
1022 option.rect.setHeight(qMin(a: maxSize, b: option.rect.height()));
1023
1024 option.state = state;
1025 if (selections && selections->isSelected(index: *it))
1026 option.state |= QStyle::State_Selected;
1027 if (enabled) {
1028 QPalette::ColorGroup cg;
1029 if ((itemModel->flags(index: *it) & Qt::ItemIsEnabled) == 0) {
1030 option.state &= ~QStyle::State_Enabled;
1031 cg = QPalette::Disabled;
1032 } else {
1033 cg = QPalette::Normal;
1034 }
1035 option.palette.setCurrentColorGroup(cg);
1036 }
1037 if (focus && current == *it) {
1038 option.state |= QStyle::State_HasFocus;
1039 if (viewState == EditingState)
1040 option.state |= QStyle::State_Editing;
1041 }
1042 option.state.setFlag(flag: QStyle::State_MouseOver, on: *it == hover);
1043
1044 if (alternate) {
1045 int row = (*it).row();
1046 if (row != previousRow + 1) {
1047 // adjust alternateBase according to rows in the "gap"
1048 if (!d->hiddenRows.isEmpty()) {
1049 for (int r = qMax(a: previousRow + 1, b: 0); r < row; ++r) {
1050 if (!d->isHidden(row: r))
1051 alternateBase = !alternateBase;
1052 }
1053 } else {
1054 alternateBase = (row & 1) != 0;
1055 }
1056 }
1057 option.features.setFlag(flag: QStyleOptionViewItem::Alternate, on: alternateBase);
1058
1059 // draw background of the item (only alternate row). rest of the background
1060 // is provided by the delegate
1061 QStyle::State oldState = option.state;
1062 option.state &= ~QStyle::State_Selected;
1063 painter.drawPrimitive(pe: QStyle::PE_PanelItemViewRow, opt: option);
1064 option.state = oldState;
1065
1066 alternateBase = !alternateBase;
1067 previousRow = row;
1068 }
1069
1070 itemDelegateForIndex(index: *it)->paint(painter: &painter, option, index: *it);
1071 }
1072
1073#if QT_CONFIG(draganddrop)
1074 d->commonListView->paintDragDrop(painter: &painter);
1075#endif
1076
1077#if QT_CONFIG(rubberband)
1078 // #### move this implementation into a dynamic class
1079 if (d->showElasticBand && d->elasticBand.isValid()) {
1080 QStyleOptionRubberBand opt;
1081 opt.initFrom(w: this);
1082 opt.shape = QRubberBand::Rectangle;
1083 opt.opaque = false;
1084 opt.rect = d->mapToViewport(rect: d->elasticBand, extend: false).intersected(
1085 other: d->viewport->rect().adjusted(xp1: -16, yp1: -16, xp2: 16, yp2: 16));
1086 painter.save();
1087 painter.drawControl(ce: QStyle::CE_RubberBand, opt);
1088 painter.restore();
1089 }
1090#endif
1091}
1092
1093/*!
1094 \reimp
1095*/
1096QModelIndex QListView::indexAt(const QPoint &p) const
1097{
1098 Q_D(const QListView);
1099 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
1100 const QList<QModelIndex> intersectVector = d->intersectingSet(area: rect);
1101 QModelIndex index = intersectVector.size() > 0
1102 ? intersectVector.last() : QModelIndex();
1103 if (index.isValid() && visualRect(index).contains(p))
1104 return index;
1105 return QModelIndex();
1106}
1107
1108/*!
1109 \reimp
1110*/
1111int QListView::horizontalOffset() const
1112{
1113 return d_func()->commonListView->horizontalOffset();
1114}
1115
1116/*!
1117 \reimp
1118*/
1119int QListView::verticalOffset() const
1120{
1121 return d_func()->commonListView->verticalOffset();
1122}
1123
1124/*!
1125 \reimp
1126*/
1127QModelIndex QListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1128{
1129 Q_D(QListView);
1130 Q_UNUSED(modifiers);
1131
1132 auto findAvailableRowBackward = [d](int row) {
1133 while (row >= 0 && d->isHiddenOrDisabled(row))
1134 --row;
1135 return row;
1136 };
1137
1138 auto findAvailableRowForward = [d](int row) {
1139 int rowCount = d->model->rowCount(parent: d->root);
1140 if (!rowCount)
1141 return -1;
1142 while (row < rowCount && d->isHiddenOrDisabled(row))
1143 ++row;
1144 if (row >= rowCount)
1145 return -1;
1146 return row;
1147 };
1148
1149 QModelIndex current = currentIndex();
1150 if (!current.isValid()) {
1151 int row = findAvailableRowForward(0);
1152 if (row == -1)
1153 return QModelIndex();
1154 return d->model->index(row, column: d->column, parent: d->root);
1155 }
1156
1157 if ((d->flow == LeftToRight && cursorAction == MoveLeft) ||
1158 (d->flow == TopToBottom && (cursorAction == MoveUp || cursorAction == MovePrevious))) {
1159 const int row = findAvailableRowBackward(current.row() - 1);
1160 if (row == -1)
1161 return current;
1162 return d->model->index(row, column: d->column, parent: d->root);
1163 } else if ((d->flow == LeftToRight && cursorAction == MoveRight) ||
1164 (d->flow == TopToBottom && (cursorAction == MoveDown || cursorAction == MoveNext))) {
1165 const int row = findAvailableRowForward(current.row() + 1);
1166 if (row == -1)
1167 return current;
1168 return d->model->index(row, column: d->column, parent: d->root);
1169 }
1170
1171 const QRect initialRect = rectForIndex(index: current);
1172 QRect rect = initialRect;
1173 if (rect.isEmpty()) {
1174 return d->model->index(row: 0, column: d->column, parent: d->root);
1175 }
1176 if (d->gridSize().isValid()) rect.setSize(d->gridSize());
1177
1178 QSize contents = d->contentsSize();
1179 QList<QModelIndex> intersectVector;
1180
1181 switch (cursorAction) {
1182 case MoveLeft:
1183 while (intersectVector.isEmpty()) {
1184 rect.translate(dx: -rect.width(), dy: 0);
1185 if (rect.right() <= 0)
1186 return current;
1187 if (rect.left() < 0)
1188 rect.setLeft(0);
1189 intersectVector = d->intersectingSet(area: rect);
1190 d->removeCurrentAndDisabled(indexes: &intersectVector, current);
1191 }
1192 return d->closestIndex(target: initialRect, candidates: intersectVector);
1193 case MoveRight:
1194 while (intersectVector.isEmpty()) {
1195 rect.translate(dx: rect.width(), dy: 0);
1196 if (rect.left() >= contents.width())
1197 return current;
1198 if (rect.right() > contents.width())
1199 rect.setRight(contents.width());
1200 intersectVector = d->intersectingSet(area: rect);
1201 d->removeCurrentAndDisabled(indexes: &intersectVector, current);
1202 }
1203 return d->closestIndex(target: initialRect, candidates: intersectVector);
1204 case MovePageUp: {
1205 if (rect.height() >= d->viewport->height())
1206 return moveCursor(cursorAction: QAbstractItemView::MoveUp, modifiers);
1207
1208 rect.moveTop(pos: rect.top() - d->viewport->height() + 1);
1209 if (rect.top() < rect.height()) {
1210 rect.setTop(0);
1211 rect.setBottom(1);
1212 }
1213 QModelIndex findindex = current;
1214 while (intersectVector.isEmpty()
1215 || rectForIndex(index: findindex).top() <= (rectForIndex(index: current).bottom() - d->viewport->rect().height())
1216 || rect.top() <= 0) {
1217 rect.translate(dx: 0, dy: 1);
1218 if (rect.bottom() <= 0) {
1219 return current;
1220 }
1221 intersectVector = d->intersectingSet(area: rect);
1222 findindex = d->closestIndex(target: initialRect, candidates: intersectVector);
1223 }
1224 return findindex;
1225 }
1226 case MovePrevious:
1227 case MoveUp:
1228 while (intersectVector.isEmpty()) {
1229 rect.translate(dx: 0, dy: -rect.height());
1230 if (rect.bottom() <= 0) {
1231#ifdef QT_KEYPAD_NAVIGATION
1232 if (QApplicationPrivate::keypadNavigationEnabled()) {
1233 int row = d->batchStartRow() - 1;
1234 while (row >= 0 && d->isHiddenOrDisabled(row))
1235 --row;
1236 if (row >= 0)
1237 return d->model->index(row, d->column, d->root);
1238 }
1239#endif
1240 return current;
1241 }
1242 if (rect.top() < 0)
1243 rect.setTop(0);
1244 intersectVector = d->intersectingSet(area: rect);
1245 d->removeCurrentAndDisabled(indexes: &intersectVector, current);
1246 }
1247 return d->closestIndex(target: initialRect, candidates: intersectVector);
1248 case MovePageDown: {
1249 if (rect.height() >= d->viewport->height())
1250 return moveCursor(cursorAction: QAbstractItemView::MoveDown, modifiers);
1251
1252 rect.moveTop(pos: rect.top() + d->viewport->height() - 1);
1253 if (rect.bottom() > contents.height() - rect.height()) {
1254 rect.setTop(contents.height() - 1);
1255 rect.setBottom(contents.height());
1256 }
1257 QModelIndex index = current;
1258 // index's bottom() - current's top() always <= (d->viewport->rect().height()
1259 while (intersectVector.isEmpty()
1260 || rectForIndex(index).bottom() >= (d->viewport->rect().height() + rectForIndex(index: current).top())
1261 || rect.bottom() > contents.height()) {
1262 rect.translate(dx: 0, dy: -1);
1263 if (rect.top() >= contents.height()) {
1264 return current;
1265 }
1266 intersectVector = d->intersectingSet(area: rect);
1267 index = d->closestIndex(target: initialRect, candidates: intersectVector);
1268 }
1269 return index;
1270 }
1271 case MoveNext:
1272 case MoveDown:
1273 while (intersectVector.isEmpty()) {
1274 rect.translate(dx: 0, dy: rect.height());
1275 if (rect.top() >= contents.height()) {
1276#ifdef QT_KEYPAD_NAVIGATION
1277 if (QApplicationPrivate::keypadNavigationEnabled()) {
1278 int rowCount = d->model->rowCount(d->root);
1279 int row = 0;
1280 while (row < rowCount && d->isHiddenOrDisabled(row))
1281 ++row;
1282 if (row < rowCount)
1283 return d->model->index(row, d->column, d->root);
1284 }
1285#endif
1286 return current;
1287 }
1288 if (rect.bottom() > contents.height())
1289 rect.setBottom(contents.height());
1290 intersectVector = d->intersectingSet(area: rect);
1291 d->removeCurrentAndDisabled(indexes: &intersectVector, current);
1292 }
1293 return d->closestIndex(target: initialRect, candidates: intersectVector);
1294 case MoveHome:
1295 return d->model->index(row: 0, column: d->column, parent: d->root);
1296 case MoveEnd:
1297 return d->model->index(row: d->batchStartRow() - 1, column: d->column, parent: d->root);}
1298
1299 return current;
1300}
1301
1302/*!
1303 Returns the rectangle of the item at position \a index in the
1304 model. The rectangle is in contents coordinates.
1305
1306 \sa visualRect()
1307*/
1308QRect QListView::rectForIndex(const QModelIndex &index) const
1309{
1310 return d_func()->rectForIndex(index);
1311}
1312
1313/*!
1314 \since 4.1
1315
1316 Sets the contents position of the item at \a index in the model to the given
1317 \a position.
1318 If the list view's movement mode is Static or its view mode is ListView,
1319 this function will have no effect.
1320*/
1321void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index)
1322{
1323 Q_D(QListView);
1324 if (d->movement == Static
1325 || !d->isIndexValid(index)
1326 || index.parent() != d->root
1327 || index.column() != d->column)
1328 return;
1329
1330 d->executePostedLayout();
1331 d->commonListView->setPositionForIndex(position, index);
1332}
1333
1334/*!
1335 \reimp
1336*/
1337void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
1338{
1339 Q_D(QListView);
1340 if (!d->selectionModel)
1341 return;
1342
1343 // if we are wrapping, we can only select inside the contents rectangle
1344 int w = qMax(a: d->contentsSize().width(), b: d->viewport->width());
1345 int h = qMax(a: d->contentsSize().height(), b: d->viewport->height());
1346 if (d->wrap && !QRect(0, 0, w, h).intersects(r: rect))
1347 return;
1348
1349 QItemSelection selection;
1350
1351 if (rect.width() == 1 && rect.height() == 1) {
1352 const QList<QModelIndex> intersectVector =
1353 d->intersectingSet(area: rect.translated(dx: horizontalOffset(), dy: verticalOffset()));
1354 QModelIndex tl;
1355 if (!intersectVector.isEmpty())
1356 tl = intersectVector.last(); // special case for mouse press; only select the top item
1357 if (tl.isValid() && d->isIndexEnabled(index: tl))
1358 selection.select(topLeft: tl, bottomRight: tl);
1359 } else {
1360 if (state() == DragSelectingState) { // visual selection mode (rubberband selection)
1361 selection = d->selection(rect: rect.translated(dx: horizontalOffset(), dy: verticalOffset()));
1362 } else { // logical selection mode (key and mouse click selection)
1363 QModelIndex tl, br;
1364 // get the first item
1365 const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1);
1366 QList<QModelIndex> intersectVector = d->intersectingSet(area: topLeft);
1367 if (!intersectVector.isEmpty())
1368 tl = intersectVector.last();
1369 // get the last item
1370 const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1);
1371 intersectVector = d->intersectingSet(area: bottomRight);
1372 if (!intersectVector.isEmpty())
1373 br = intersectVector.last();
1374
1375 // get the ranges
1376 if (tl.isValid() && br.isValid()
1377 && d->isIndexEnabled(index: tl)
1378 && d->isIndexEnabled(index: br)) {
1379 QRect first = d->cellRectForIndex(index: tl);
1380 QRect last = d->cellRectForIndex(index: br);
1381 QRect middle;
1382 if (d->flow == LeftToRight) {
1383 QRect &top = first;
1384 QRect &bottom = last;
1385 // if bottom is above top, swap them
1386 if (top.center().y() > bottom.center().y()) {
1387 QRect tmp = top;
1388 top = bottom;
1389 bottom = tmp;
1390 }
1391 // if the rect are on different lines, expand
1392 if (top.top() != bottom.top()) {
1393 // top rectangle
1394 if (isRightToLeft())
1395 top.setLeft(0);
1396 else
1397 top.setRight(contentsSize().width());
1398 // bottom rectangle
1399 if (isRightToLeft())
1400 bottom.setRight(contentsSize().width());
1401 else
1402 bottom.setLeft(0);
1403 } else if (top.left() > bottom.right()) {
1404 if (isRightToLeft())
1405 bottom.setLeft(top.right());
1406 else
1407 bottom.setRight(top.left());
1408 } else {
1409 if (isRightToLeft())
1410 top.setLeft(bottom.right());
1411 else
1412 top.setRight(bottom.left());
1413 }
1414 // middle rectangle
1415 if (top.bottom() < bottom.top()) {
1416 if (gridSize().isValid() && !gridSize().isNull())
1417 middle.setTop(top.top() + gridSize().height());
1418 else
1419 middle.setTop(top.bottom() + 1);
1420 middle.setLeft(qMin(a: top.left(), b: bottom.left()));
1421 middle.setBottom(bottom.top() - 1);
1422 middle.setRight(qMax(a: top.right(), b: bottom.right()));
1423 }
1424 } else { // TopToBottom
1425 QRect &left = first;
1426 QRect &right = last;
1427 if (left.center().x() > right.center().x())
1428 qSwap(value1&: left, value2&: right);
1429
1430 int ch = contentsSize().height();
1431 if (left.left() != right.left()) {
1432 // left rectangle
1433 if (isRightToLeft())
1434 left.setTop(0);
1435 else
1436 left.setBottom(ch);
1437
1438 // top rectangle
1439 if (isRightToLeft())
1440 right.setBottom(ch);
1441 else
1442 right.setTop(0);
1443 // only set middle if the
1444 middle.setTop(0);
1445 middle.setBottom(ch);
1446 if (gridSize().isValid() && !gridSize().isNull())
1447 middle.setLeft(left.left() + gridSize().width());
1448 else
1449 middle.setLeft(left.right() + 1);
1450 middle.setRight(right.left() - 1);
1451 } else if (left.bottom() < right.top()) {
1452 left.setBottom(right.top() - 1);
1453 } else {
1454 right.setBottom(left.top() - 1);
1455 }
1456 }
1457
1458 // do the selections
1459 QItemSelection topSelection = d->selection(rect: first);
1460 QItemSelection middleSelection = d->selection(rect: middle);
1461 QItemSelection bottomSelection = d->selection(rect: last);
1462 // merge
1463 selection.merge(other: topSelection, command: QItemSelectionModel::Select);
1464 selection.merge(other: middleSelection, command: QItemSelectionModel::Select);
1465 selection.merge(other: bottomSelection, command: QItemSelectionModel::Select);
1466 }
1467 }
1468 }
1469
1470 d->selectionModel->select(selection, command);
1471}
1472
1473/*!
1474 \reimp
1475
1476 Since 4.7, the returned region only contains rectangles intersecting
1477 (or included in) the viewport.
1478*/
1479QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const
1480{
1481 Q_D(const QListView);
1482 // ### NOTE: this is a potential bottleneck in non-static mode
1483 int c = d->column;
1484 QRegion selectionRegion;
1485 const QRect &viewportRect = d->viewport->rect();
1486 for (const auto &elem : selection) {
1487 if (!elem.isValid())
1488 continue;
1489 QModelIndex parent = elem.topLeft().parent();
1490 //we only display the children of the root in a listview
1491 //we're not interested in the other model indexes
1492 if (parent != d->root)
1493 continue;
1494 int t = elem.topLeft().row();
1495 int b = elem.bottomRight().row();
1496 if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items
1497 for (int r = t; r <= b; ++r) {
1498 const QRect &rect = visualRect(index: d->model->index(row: r, column: c, parent));
1499 if (viewportRect.intersects(r: rect))
1500 selectionRegion += rect;
1501 }
1502 } else { // in static mode, we can optimize a bit
1503 while (t <= b && d->isHidden(row: t)) ++t;
1504 while (b >= t && d->isHidden(row: b)) --b;
1505 const QModelIndex top = d->model->index(row: t, column: c, parent);
1506 const QModelIndex bottom = d->model->index(row: b, column: c, parent);
1507 QRect rect(visualRect(index: top).topLeft(),
1508 visualRect(index: bottom).bottomRight());
1509 if (viewportRect.intersects(r: rect))
1510 selectionRegion += rect;
1511 }
1512 }
1513
1514 return selectionRegion;
1515}
1516
1517/*!
1518 \reimp
1519*/
1520QModelIndexList QListView::selectedIndexes() const
1521{
1522 Q_D(const QListView);
1523 if (!d->selectionModel)
1524 return QModelIndexList();
1525
1526 QModelIndexList viewSelected = d->selectionModel->selectedIndexes();
1527 auto ignorable = [this, d](const QModelIndex &index) {
1528 return index.column() != d->column || index.parent() != d->root || isIndexHidden(index);
1529 };
1530 viewSelected.removeIf(pred: ignorable);
1531 return viewSelected;
1532}
1533
1534/*!
1535 \internal
1536
1537 Layout the items according to the flow and wrapping properties.
1538*/
1539void QListView::doItemsLayout()
1540{
1541 Q_D(QListView);
1542 // showing the scroll bars will trigger a resize event,
1543 // so we set the state to expanding to avoid
1544 // triggering another layout
1545 QAbstractItemView::State oldState = state();
1546 setState(ExpandingState);
1547 if (d->model->columnCount(parent: d->root) > 0) { // no columns means no contents
1548 d->resetBatchStartRow();
1549 if (layoutMode() == SinglePass) {
1550 d->doItemsLayout(num: d->model->rowCount(parent: d->root)); // layout everything
1551 } else if (!d->batchLayoutTimer.isActive()) {
1552 if (!d->doItemsLayout(num: d->batchSize)) // layout is done
1553 d->batchLayoutTimer.start(msec: 0, obj: this); // do a new batch as fast as possible
1554 }
1555 } else { // clear the QBspTree generated by the last layout
1556 d->clear();
1557 }
1558 QAbstractItemView::doItemsLayout();
1559 setState(oldState); // restoring the oldState
1560}
1561
1562/*!
1563 \reimp
1564*/
1565void QListView::updateGeometries()
1566{
1567 Q_D(QListView);
1568 if (geometry().isEmpty() || d->model->rowCount(parent: d->root) <= 0 || d->model->columnCount(parent: d->root) <= 0) {
1569 horizontalScrollBar()->setRange(min: 0, max: 0);
1570 verticalScrollBar()->setRange(min: 0, max: 0);
1571 } else {
1572 QModelIndex index = d->model->index(row: 0, column: d->column, parent: d->root);
1573 QStyleOptionViewItem option;
1574 initViewItemOption(option: &option);
1575 QSize step = d->itemSize(option, index);
1576 d->commonListView->updateHorizontalScrollBar(step);
1577 d->commonListView->updateVerticalScrollBar(step);
1578 }
1579
1580 QAbstractItemView::updateGeometries();
1581
1582 // if the scroll bars are turned off, we resize the contents to the viewport
1583 if (d->movement == Static && !d->isWrapping()) {
1584 d->layoutChildren(); // we need the viewport size to be updated
1585 if (d->flow == TopToBottom) {
1586 if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
1587 d->setContentsSize(w: viewport()->width(), h: contentsSize().height());
1588 horizontalScrollBar()->setRange(min: 0, max: 0); // we see all the contents anyway
1589 }
1590 } else { // LeftToRight
1591 if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
1592 d->setContentsSize(w: contentsSize().width(), h: viewport()->height());
1593 verticalScrollBar()->setRange(min: 0, max: 0); // we see all the contents anyway
1594 }
1595 }
1596 }
1597
1598}
1599
1600/*!
1601 \reimp
1602*/
1603bool QListView::isIndexHidden(const QModelIndex &index) const
1604{
1605 Q_D(const QListView);
1606 return (d->isHidden(row: index.row())
1607 && (index.parent() == d->root)
1608 && index.column() == d->column);
1609}
1610
1611/*!
1612 \property QListView::modelColumn
1613 \brief the column in the model that is visible
1614
1615 By default, this property contains 0, indicating that the first
1616 column in the model will be shown.
1617*/
1618void QListView::setModelColumn(int column)
1619{
1620 Q_D(QListView);
1621 if (column < 0 || column >= d->model->columnCount(parent: d->root))
1622 return;
1623 d->column = column;
1624 d->doDelayedItemsLayout();
1625#if QT_CONFIG(accessibility)
1626 if (QAccessible::isActive()) {
1627 QAccessibleTableModelChangeEvent event(this, QAccessibleTableModelChangeEvent::ModelReset);
1628 QAccessible::updateAccessibility(event: &event);
1629 }
1630#endif
1631}
1632
1633int QListView::modelColumn() const
1634{
1635 Q_D(const QListView);
1636 return d->column;
1637}
1638
1639/*!
1640 \property QListView::uniformItemSizes
1641 \brief whether all items in the listview have the same size
1642 \since 4.1
1643
1644 This property should only be set to true if it is guaranteed that all items
1645 in the view have the same size. This enables the view to do some
1646 optimizations for performance purposes.
1647
1648 By default, this property is \c false.
1649*/
1650void QListView::setUniformItemSizes(bool enable)
1651{
1652 Q_D(QListView);
1653 d->uniformItemSizes = enable;
1654}
1655
1656bool QListView::uniformItemSizes() const
1657{
1658 Q_D(const QListView);
1659 return d->uniformItemSizes;
1660}
1661
1662/*!
1663 \property QListView::wordWrap
1664 \brief the item text word-wrapping policy
1665 \since 4.2
1666
1667 If this property is \c true then the item text is wrapped where
1668 necessary at word-breaks; otherwise it is not wrapped at all.
1669 This property is \c false by default.
1670
1671 Please note that even if wrapping is enabled, the cell will not be
1672 expanded to make room for the text. It will print ellipsis for
1673 text that cannot be shown, according to the view's
1674 \l{QAbstractItemView::}{textElideMode}.
1675*/
1676void QListView::setWordWrap(bool on)
1677{
1678 Q_D(QListView);
1679 if (d->wrapItemText == on)
1680 return;
1681 d->wrapItemText = on;
1682 d->doDelayedItemsLayout();
1683}
1684
1685bool QListView::wordWrap() const
1686{
1687 Q_D(const QListView);
1688 return d->wrapItemText;
1689}
1690
1691/*!
1692 \property QListView::selectionRectVisible
1693 \brief if the selection rectangle should be visible
1694 \since 4.3
1695
1696 If this property is \c true then the selection rectangle is visible;
1697 otherwise it will be hidden.
1698
1699 \note The selection rectangle will only be visible if the selection mode
1700 is in a mode where more than one item can be selected; i.e., it will not
1701 draw a selection rectangle if the selection mode is
1702 QAbstractItemView::SingleSelection.
1703
1704 By default, this property is \c false.
1705*/
1706void QListView::setSelectionRectVisible(bool show)
1707{
1708 Q_D(QListView);
1709 d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible);
1710 d->setSelectionRectVisible(show);
1711}
1712
1713bool QListView::isSelectionRectVisible() const
1714{
1715 Q_D(const QListView);
1716 return d->isSelectionRectVisible();
1717}
1718
1719/*!
1720 \property QListView::itemAlignment
1721 \brief the alignment of each item in its cell
1722 \since 5.12
1723
1724 This is only supported in ListMode with TopToBottom flow
1725 and with wrapping enabled.
1726 The default alignment is 0, which means that an item fills
1727 its cell entirely.
1728*/
1729void QListView::setItemAlignment(Qt::Alignment alignment)
1730{
1731 Q_D(QListView);
1732 if (d->itemAlignment == alignment)
1733 return;
1734 d->itemAlignment = alignment;
1735 if (viewMode() == ListMode && flow() == QListView::TopToBottom && isWrapping())
1736 d->doDelayedItemsLayout();
1737}
1738
1739Qt::Alignment QListView::itemAlignment() const
1740{
1741 Q_D(const QListView);
1742 return d->itemAlignment;
1743}
1744
1745/*!
1746 \reimp
1747*/
1748bool QListView::event(QEvent *e)
1749{
1750 return QAbstractItemView::event(event: e);
1751}
1752
1753/*
1754 * private object implementation
1755 */
1756
1757QListViewPrivate::QListViewPrivate()
1758 : QAbstractItemViewPrivate(),
1759 commonListView(nullptr),
1760 wrap(false),
1761 space(0),
1762 flow(QListView::TopToBottom),
1763 movement(QListView::Static),
1764 resizeMode(QListView::Fixed),
1765 layoutMode(QListView::SinglePass),
1766 viewMode(QListView::ListMode),
1767 modeProperties(0),
1768 column(0),
1769 uniformItemSizes(false),
1770 batchSize(100),
1771 showElasticBand(false),
1772 itemAlignment(Qt::Alignment())
1773{
1774}
1775
1776QListViewPrivate::~QListViewPrivate()
1777{
1778 delete commonListView;
1779}
1780
1781void QListViewPrivate::clear()
1782{
1783 // initialization of data structs
1784 cachedItemSize = QSize();
1785 commonListView->clear();
1786}
1787
1788void QListViewPrivate::prepareItemsLayout()
1789{
1790 Q_Q(QListView);
1791 clear();
1792
1793 //take the size as if there were scrollbar in order to prevent scrollbar to blink
1794 layoutBounds = QRect(QPoint(), q->maximumViewportSize());
1795
1796 int frameAroundContents = 0;
1797 if (q->style()->styleHint(stylehint: QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
1798 QStyleOption option;
1799 option.initFrom(w: q);
1800 frameAroundContents = q->style()->pixelMetric(metric: QStyle::PM_DefaultFrameWidth, option: &option) * 2;
1801 }
1802
1803 // maximumViewportSize() already takes scrollbar into account if policy is
1804 // Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy
1805 // is Qt::ScrollBarAsNeeded
1806 int verticalMargin = (vbarpolicy == Qt::ScrollBarAsNeeded) && (flow == QListView::LeftToRight || vbar->isVisible())
1807 && !q->style()->pixelMetric(metric: QStyle::PM_ScrollView_ScrollBarOverlap, option: nullptr, widget: vbar)
1808 ? q->style()->pixelMetric(metric: QStyle::PM_ScrollBarExtent, option: nullptr, widget: vbar) + frameAroundContents
1809 : 0;
1810 int horizontalMargin = hbarpolicy==Qt::ScrollBarAsNeeded
1811 ? q->style()->pixelMetric(metric: QStyle::PM_ScrollBarExtent, option: nullptr, widget: hbar) + frameAroundContents
1812 : 0;
1813
1814 layoutBounds.adjust(dx1: 0, dy1: 0, dx2: -verticalMargin, dy2: -horizontalMargin);
1815
1816 int rowCount = model->columnCount(parent: root) <= 0 ? 0 : model->rowCount(parent: root);
1817 commonListView->setRowCount(rowCount);
1818}
1819
1820/*!
1821 \internal
1822*/
1823bool QListViewPrivate::doItemsLayout(int delta)
1824{
1825 int max = model->rowCount(parent: root) - 1;
1826 int first = batchStartRow();
1827 int last = qMin(a: first + delta - 1, b: max);
1828
1829 if (first == 0) {
1830 layoutChildren(); // make sure the viewport has the right size
1831 prepareItemsLayout();
1832 }
1833
1834 if (max < 0 || last < first) {
1835 return true; // nothing to do
1836 }
1837
1838 QListViewLayoutInfo info;
1839 info.bounds = layoutBounds;
1840 info.grid = gridSize();
1841 info.spacing = (info.grid.isValid() ? 0 : spacing());
1842 info.first = first;
1843 info.last = last;
1844 info.wrap = isWrapping();
1845 info.flow = flow;
1846 info.max = max;
1847
1848 return commonListView->doBatchedItemLayout(info, max);
1849}
1850
1851QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const
1852{
1853 if (!index.isValid() || isHidden(row: index.row()))
1854 return QListViewItem();
1855
1856 return commonListView->indexToListViewItem(index);
1857}
1858
1859QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const
1860{
1861 Q_Q(const QListView);
1862 if (!rect.isValid())
1863 return rect;
1864
1865 QRect result = extend ? commonListView->mapToViewport(rect) : rect;
1866 int dx = -q->horizontalOffset();
1867 int dy = -q->verticalOffset();
1868 return result.adjusted(xp1: dx, yp1: dy, xp2: dx, yp2: dy);
1869}
1870
1871QModelIndex QListViewPrivate::closestIndex(const QRect &target,
1872 const QList<QModelIndex> &candidates) const
1873{
1874 int distance = 0;
1875 int shortest = INT_MAX;
1876 QModelIndex closest;
1877 QList<QModelIndex>::const_iterator it = candidates.begin();
1878
1879 for (; it != candidates.end(); ++it) {
1880 if (!(*it).isValid())
1881 continue;
1882
1883 const QRect indexRect = indexToListViewItem(index: *it).rect();
1884
1885 //if the center x (or y) position of an item is included in the rect of the other item,
1886 //we define the distance between them as the difference in x (or y) of their respective center.
1887 // Otherwise, we use the nahattan length between the 2 items
1888 if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right())
1889 || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) {
1890 //one item's center is at the vertical of the other
1891 distance = qAbs(t: indexRect.center().y() - target.center().y());
1892 } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom())
1893 || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) {
1894 //one item's center is at the vertical of the other
1895 distance = qAbs(t: indexRect.center().x() - target.center().x());
1896 } else {
1897 distance = (indexRect.center() - target.center()).manhattanLength();
1898 }
1899 if (distance < shortest) {
1900 shortest = distance;
1901 closest = *it;
1902 }
1903 }
1904 return closest;
1905}
1906
1907QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
1908{
1909 Q_Q(const QListView);
1910 if (!uniformItemSizes) {
1911 const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index);
1912 return delegate ? delegate->sizeHint(option, index) : QSize();
1913 }
1914 if (!cachedItemSize.isValid()) { // the last item is probably the largest, so we use its size
1915 int row = model->rowCount(parent: root) - 1;
1916 QModelIndex sample = model->index(row, column, parent: root);
1917 const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index: sample);
1918 cachedItemSize = delegate ? delegate->sizeHint(option, index: sample) : QSize();
1919 }
1920 return cachedItemSize;
1921}
1922
1923QItemSelection QListViewPrivate::selection(const QRect &rect) const
1924{
1925 QItemSelection selection;
1926 QModelIndex tl, br;
1927 const QList<QModelIndex> intersectVector = intersectingSet(area: rect);
1928 QList<QModelIndex>::const_iterator it = intersectVector.begin();
1929 for (; it != intersectVector.end(); ++it) {
1930 if (!tl.isValid() && !br.isValid()) {
1931 tl = br = *it;
1932 } else if ((*it).row() == (tl.row() - 1)) {
1933 tl = *it; // expand current range
1934 } else if ((*it).row() == (br.row() + 1)) {
1935 br = (*it); // expand current range
1936 } else {
1937 selection.select(topLeft: tl, bottomRight: br); // select current range
1938 tl = br = *it; // start new range
1939 }
1940 }
1941
1942 if (tl.isValid() && br.isValid())
1943 selection.select(topLeft: tl, bottomRight: br);
1944 else if (tl.isValid())
1945 selection.select(topLeft: tl, bottomRight: tl);
1946 else if (br.isValid())
1947 selection.select(topLeft: br, bottomRight: br);
1948
1949 return selection;
1950}
1951
1952#if QT_CONFIG(draganddrop)
1953QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const
1954{
1955 if (viewMode == QListView::ListMode && flow == QListView::LeftToRight)
1956 return static_cast<QListModeViewBase *>(commonListView)->position(pos, rect, idx);
1957 else
1958 return QAbstractItemViewPrivate::position(pos, rect, idx);
1959}
1960
1961bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
1962{
1963 if (viewMode == QListView::ListMode && flow == QListView::LeftToRight)
1964 return static_cast<QListModeViewBase *>(commonListView)->dropOn(event, row: dropRow, col: dropCol, index: dropIndex);
1965 else
1966 return QAbstractItemViewPrivate::dropOn(event, row: dropRow, col: dropCol, index: dropIndex);
1967}
1968#endif
1969
1970void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes,
1971 const QModelIndex &current) const
1972{
1973 auto isCurrentOrDisabled = [this, current](const QModelIndex &index) {
1974 return !isIndexEnabled(index) || index == current;
1975 };
1976 indexes->removeIf(pred: isCurrentOrDisabled);
1977}
1978
1979/*
1980 * Common ListView Implementation
1981*/
1982
1983void QCommonListViewBase::appendHiddenRow(int row)
1984{
1985 dd->hiddenRows.insert(value: dd->model->index(row, column: 0, parent: qq->rootIndex()));
1986}
1987
1988void QCommonListViewBase::removeHiddenRow(int row)
1989{
1990 dd->hiddenRows.remove(value: dd->model->index(row, column: 0, parent: qq->rootIndex()));
1991}
1992
1993#if QT_CONFIG(draganddrop)
1994void QCommonListViewBase::paintDragDrop(QPainter *painter)
1995{
1996 // FIXME: Until the we can provide a proper drop indicator
1997 // in IconMode, it makes no sense to show it
1998 dd->paintDropIndicator(painter);
1999}
2000#endif
2001
2002QSize QListModeViewBase::viewportSize(const QAbstractItemView *v)
2003{
2004 return v->contentsRect().marginsRemoved(margins: v->viewportMargins()).size();
2005}
2006
2007void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step)
2008{
2009 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(step: step.width() + spacing());
2010 horizontalScrollBar()->setPageStep(viewport()->width());
2011
2012 // If both scroll bars are set to auto, we might end up in a situation with enough space
2013 // for the actual content. But still one of the scroll bars will become enabled due to
2014 // the other one using the space. The other one will become invisible in the same cycle.
2015 // -> Infinite loop, QTBUG-39902
2016 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2017 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2018
2019 const QSize viewportSize = QListModeViewBase::viewportSize(v: qq);
2020
2021 bool verticalWantsToShow = contentsSize.height() > viewportSize.height();
2022 bool horizontalWantsToShow;
2023 if (verticalWantsToShow)
2024 horizontalWantsToShow = contentsSize.width() > viewportSize.width() - qq->verticalScrollBar()->width();
2025 else
2026 horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2027
2028 if (bothScrollBarsAuto && !horizontalWantsToShow) {
2029 // break the infinite loop described above by setting the range to 0, 0.
2030 // QAbstractScrollArea will then hide the scroll bar for us
2031 horizontalScrollBar()->setRange(min: 0, max: 0);
2032 } else {
2033 horizontalScrollBar()->setRange(min: 0, max: contentsSize.width() - viewport()->width());
2034 }
2035}
2036
2037void QCommonListViewBase::updateVerticalScrollBar(const QSize &step)
2038{
2039 verticalScrollBar()->d_func()->itemviewChangeSingleStep(step: step.height() + spacing());
2040 verticalScrollBar()->setPageStep(viewport()->height());
2041
2042 // If both scroll bars are set to auto, we might end up in a situation with enough space
2043 // for the actual content. But still one of the scroll bars will become enabled due to
2044 // the other one using the space. The other one will become invisible in the same cycle.
2045 // -> Infinite loop, QTBUG-39902
2046 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2047 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2048
2049 const QSize viewportSize = QListModeViewBase::viewportSize(v: qq);
2050
2051 bool horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2052 bool verticalWantsToShow;
2053 if (horizontalWantsToShow)
2054 verticalWantsToShow = contentsSize.height() > viewportSize.height() - qq->horizontalScrollBar()->height();
2055 else
2056 verticalWantsToShow = contentsSize.height() > viewportSize.height();
2057
2058 if (bothScrollBarsAuto && !verticalWantsToShow) {
2059 // break the infinite loop described above by setting the range to 0, 0.
2060 // QAbstractScrollArea will then hide the scroll bar for us
2061 verticalScrollBar()->setRange(min: 0, max: 0);
2062 } else {
2063 verticalScrollBar()->setRange(min: 0, max: contentsSize.height() - viewport()->height());
2064 }
2065}
2066
2067void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/)
2068{
2069 dd->scrollContentsBy(dx: isRightToLeft() ? -dx : dx, dy);
2070}
2071
2072int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint,
2073 bool above, bool below, const QRect &area, const QRect &rect) const
2074{
2075 int verticalValue = verticalScrollBar()->value();
2076 QRect adjusted = rect.adjusted(xp1: -spacing(), yp1: -spacing(), xp2: spacing(), yp2: spacing());
2077 if (hint == QListView::PositionAtTop || above)
2078 verticalValue += adjusted.top();
2079 else if (hint == QListView::PositionAtBottom || below)
2080 verticalValue += qMin(a: adjusted.top(), b: adjusted.bottom() - area.height() + 1);
2081 else if (hint == QListView::PositionAtCenter)
2082 verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2);
2083 return verticalValue;
2084}
2085
2086int QCommonListViewBase::horizontalOffset() const
2087{
2088 return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value());
2089}
2090
2091int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint,
2092 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2093{
2094 int horizontalValue = horizontalScrollBar()->value();
2095 if (isRightToLeft()) {
2096 if (hint == QListView::PositionAtCenter) {
2097 horizontalValue += ((area.width() - rect.width()) / 2) - rect.left();
2098 } else {
2099 if (leftOf)
2100 horizontalValue -= rect.left();
2101 else if (rightOf)
2102 horizontalValue += qMin(a: rect.left(), b: area.width() - rect.right());
2103 }
2104 } else {
2105 if (hint == QListView::PositionAtCenter) {
2106 horizontalValue += rect.left() - ((area.width()- rect.width()) / 2);
2107 } else {
2108 if (leftOf)
2109 horizontalValue += rect.left();
2110 else if (rightOf)
2111 horizontalValue += qMin(a: rect.left(), b: rect.right() - area.width());
2112 }
2113 }
2114 return horizontalValue;
2115}
2116
2117/*
2118 * ListMode ListView Implementation
2119*/
2120QListModeViewBase::QListModeViewBase(QListView *q, QListViewPrivate *d)
2121 : QCommonListViewBase(q, d)
2122{
2123#if QT_CONFIG(draganddrop)
2124 dd->defaultDropAction = Qt::CopyAction;
2125#endif
2126}
2127
2128#if QT_CONFIG(draganddrop)
2129QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const
2130{
2131 QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport;
2132 if (!dd->overwrite) {
2133 const int margin = 2;
2134 if (pos.x() - rect.left() < margin) {
2135 r = QAbstractItemView::AboveItem; // Visually, on the left
2136 } else if (rect.right() - pos.x() < margin) {
2137 r = QAbstractItemView::BelowItem; // Visually, on the right
2138 } else if (rect.contains(p: pos, proper: true)) {
2139 r = QAbstractItemView::OnItem;
2140 }
2141 } else {
2142 QRect touchingRect = rect;
2143 touchingRect.adjust(dx1: -1, dy1: -1, dx2: 1, dy2: 1);
2144 if (touchingRect.contains(p: pos, proper: false)) {
2145 r = QAbstractItemView::OnItem;
2146 }
2147 }
2148
2149 if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled)))
2150 r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem;
2151
2152 return r;
2153}
2154
2155void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event)
2156{
2157 if (qq->dragDropMode() == QAbstractItemView::InternalMove
2158 && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction)))
2159 return;
2160
2161 // ignore by default
2162 event->ignore();
2163
2164 // can't use indexAt, doesn't account for spacing.
2165 QPoint p = event->position().toPoint();
2166 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2167 rect.adjust(dx1: -dd->spacing(), dy1: -dd->spacing(), dx2: dd->spacing(), dy2: dd->spacing());
2168 const QList<QModelIndex> intersectVector = dd->intersectingSet(area: rect);
2169 QModelIndex index = intersectVector.size() > 0
2170 ? intersectVector.last() : QModelIndex();
2171 dd->hover = index;
2172 if (!dd->droppingOnItself(event, index)
2173 && dd->canDrop(event)) {
2174
2175 if (index.isValid() && dd->showDropIndicator) {
2176 QRect rect = qq->visualRect(index);
2177 dd->dropIndicatorPosition = position(pos: event->position().toPoint(), rect, index);
2178 // if spacing, should try to draw between items, not just next to item.
2179 switch (dd->dropIndicatorPosition) {
2180 case QAbstractItemView::AboveItem:
2181 if (dd->isIndexDropEnabled(index: index.parent())) {
2182 dd->dropIndicatorRect = QRect(rect.left()-dd->spacing(), rect.top(), 0, rect.height());
2183 event->accept();
2184 } else {
2185 dd->dropIndicatorRect = QRect();
2186 }
2187 break;
2188 case QAbstractItemView::BelowItem:
2189 if (dd->isIndexDropEnabled(index: index.parent())) {
2190 dd->dropIndicatorRect = QRect(rect.right()+dd->spacing(), rect.top(), 0, rect.height());
2191 event->accept();
2192 } else {
2193 dd->dropIndicatorRect = QRect();
2194 }
2195 break;
2196 case QAbstractItemView::OnItem:
2197 if (dd->isIndexDropEnabled(index)) {
2198 dd->dropIndicatorRect = rect;
2199 event->accept();
2200 } else {
2201 dd->dropIndicatorRect = QRect();
2202 }
2203 break;
2204 case QAbstractItemView::OnViewport:
2205 dd->dropIndicatorRect = QRect();
2206 if (dd->isIndexDropEnabled(index: qq->rootIndex())) {
2207 event->accept(); // allow dropping in empty areas
2208 }
2209 break;
2210 }
2211 } else {
2212 dd->dropIndicatorRect = QRect();
2213 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2214 if (dd->isIndexDropEnabled(index: qq->rootIndex())) {
2215 event->accept(); // allow dropping in empty areas
2216 }
2217 }
2218 dd->viewport->update();
2219 } // can drop
2220
2221 if (dd->shouldAutoScroll(pos: event->position().toPoint()))
2222 qq->startAutoScroll();
2223}
2224
2225/*!
2226 If the event hasn't already been accepted, determines the index to drop on.
2227
2228 if (row == -1 && col == -1)
2229 // append to this drop index
2230 else
2231 // place at row, col in drop index
2232
2233 If it returns \c true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop.
2234 \internal
2235 */
2236bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
2237{
2238 if (event->isAccepted())
2239 return false;
2240
2241 QModelIndex index;
2242 if (dd->viewport->rect().contains(p: event->position().toPoint())) {
2243 // can't use indexAt, doesn't account for spacing.
2244 QPoint p = event->position().toPoint();
2245 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2246 rect.adjust(dx1: -dd->spacing(), dy1: -dd->spacing(), dx2: dd->spacing(), dy2: dd->spacing());
2247 const QList<QModelIndex> intersectVector = dd->intersectingSet(area: rect);
2248 index = intersectVector.size() > 0
2249 ? intersectVector.last() : QModelIndex();
2250 if (!index.isValid())
2251 index = dd->root;
2252 }
2253
2254 // If we are allowed to do the drop
2255 if (dd->model->supportedDropActions() & event->dropAction()) {
2256 int row = -1;
2257 int col = -1;
2258 if (index != dd->root) {
2259 dd->dropIndicatorPosition = position(pos: event->position().toPoint(), rect: qq->visualRect(index), index);
2260 switch (dd->dropIndicatorPosition) {
2261 case QAbstractItemView::AboveItem:
2262 row = index.row();
2263 col = index.column();
2264 index = index.parent();
2265 break;
2266 case QAbstractItemView::BelowItem:
2267 row = index.row() + 1;
2268 col = index.column();
2269 index = index.parent();
2270 break;
2271 case QAbstractItemView::OnItem:
2272 case QAbstractItemView::OnViewport:
2273 break;
2274 }
2275 } else {
2276 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2277 }
2278 *dropIndex = index;
2279 *dropRow = row;
2280 *dropCol = col;
2281 if (!dd->droppingOnItself(event, index))
2282 return true;
2283 }
2284 return false;
2285}
2286
2287#endif //QT_CONFIG(draganddrop)
2288
2289void QListModeViewBase::updateVerticalScrollBar(const QSize &step)
2290{
2291 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem
2292 && ((flow() == QListView::TopToBottom && !isWrapping())
2293 || (flow() == QListView::LeftToRight && isWrapping()))) {
2294 const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).size() - 1;
2295 if (steps > 0) {
2296 const int pageSteps = perItemScrollingPageSteps(length: viewport()->height(), bounds: contentsSize.height(), wrap: isWrapping());
2297 verticalScrollBar()->setSingleStep(1);
2298 verticalScrollBar()->setPageStep(pageSteps);
2299 verticalScrollBar()->setRange(min: 0, max: steps - pageSteps);
2300 } else {
2301 verticalScrollBar()->setRange(min: 0, max: 0);
2302 }
2303 // } else if (vertical && d->isWrapping() && d->movement == Static) {
2304 // ### wrapped scrolling in flow direction
2305 } else {
2306 QCommonListViewBase::updateVerticalScrollBar(step);
2307 }
2308}
2309
2310void QListModeViewBase::updateHorizontalScrollBar(const QSize &step)
2311{
2312 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem
2313 && ((flow() == QListView::TopToBottom && isWrapping())
2314 || (flow() == QListView::LeftToRight && !isWrapping()))) {
2315 int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).size() - 1;
2316 if (steps > 0) {
2317 const int pageSteps = perItemScrollingPageSteps(length: viewport()->width(), bounds: contentsSize.width(), wrap: isWrapping());
2318 horizontalScrollBar()->setSingleStep(1);
2319 horizontalScrollBar()->setPageStep(pageSteps);
2320 horizontalScrollBar()->setRange(min: 0, max: steps - pageSteps);
2321 } else {
2322 horizontalScrollBar()->setRange(min: 0, max: 0);
2323 }
2324 } else {
2325 QCommonListViewBase::updateHorizontalScrollBar(step);
2326 }
2327}
2328
2329int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint,
2330 bool above, bool below, const QRect &area, const QRect &rect) const
2331{
2332 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2333 int value;
2334 if (scrollValueMap.isEmpty()) {
2335 value = 0;
2336 } else {
2337 int scrollBarValue = verticalScrollBar()->value();
2338 int numHidden = 0;
2339 for (const auto &idx : std::as_const(t&: dd->hiddenRows))
2340 if (idx.row() <= scrollBarValue)
2341 ++numHidden;
2342 value = qBound(min: 0, val: scrollValueMap.at(i: verticalScrollBar()->value()) - numHidden, max: flowPositions.size() - 1);
2343 }
2344 if (above)
2345 hint = QListView::PositionAtTop;
2346 else if (below)
2347 hint = QListView::PositionAtBottom;
2348 if (hint == QListView::EnsureVisible)
2349 return value;
2350
2351 return perItemScrollToValue(index, value, height: area.height(), hint, orientation: Qt::Vertical, wrap: isWrapping(), extent: rect.height());
2352 }
2353
2354 return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect);
2355}
2356
2357int QListModeViewBase::horizontalOffset() const
2358{
2359 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2360 if (isWrapping()) {
2361 if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) {
2362 const int max = segmentPositions.size() - 1;
2363 int currentValue = qBound(min: 0, val: horizontalScrollBar()->value(), max);
2364 int position = segmentPositions.at(i: currentValue);
2365 int maximumValue = qBound(min: 0, val: horizontalScrollBar()->maximum(), max);
2366 int maximum = segmentPositions.at(i: maximumValue);
2367 return (isRightToLeft() ? maximum - position : position);
2368 }
2369 } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) {
2370 int position = flowPositions.at(i: scrollValueMap.at(i: horizontalScrollBar()->value()));
2371 int maximum = flowPositions.at(i: scrollValueMap.at(i: horizontalScrollBar()->maximum()));
2372 return (isRightToLeft() ? maximum - position : position);
2373 }
2374 }
2375 return QCommonListViewBase::horizontalOffset();
2376}
2377
2378int QListModeViewBase::verticalOffset() const
2379{
2380 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2381 if (isWrapping()) {
2382 if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) {
2383 int value = verticalScrollBar()->value();
2384 if (value >= segmentPositions.size())
2385 return 0;
2386 return segmentPositions.at(i: value) - spacing();
2387 }
2388 } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) {
2389 int value = verticalScrollBar()->value();
2390 if (value > scrollValueMap.size())
2391 return 0;
2392 return flowPositions.at(i: scrollValueMap.at(i: value)) - spacing();
2393 }
2394 }
2395 return QCommonListViewBase::verticalOffset();
2396}
2397
2398int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint,
2399 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2400{
2401 if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem)
2402 return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect);
2403
2404 int value;
2405 if (scrollValueMap.isEmpty())
2406 value = 0;
2407 else
2408 value = qBound(min: 0, val: scrollValueMap.at(i: horizontalScrollBar()->value()), max: flowPositions.size() - 1);
2409 if (leftOf)
2410 hint = QListView::PositionAtTop;
2411 else if (rightOf)
2412 hint = QListView::PositionAtBottom;
2413 if (hint == QListView::EnsureVisible)
2414 return value;
2415
2416 return perItemScrollToValue(index, value, height: area.width(), hint, orientation: Qt::Horizontal, wrap: isWrapping(), extent: rect.width());
2417}
2418
2419void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
2420{
2421 // ### reorder this logic
2422 const int verticalValue = verticalScrollBar()->value();
2423 const int horizontalValue = horizontalScrollBar()->value();
2424 const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem);
2425 const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem);
2426
2427 if (isWrapping()) {
2428 if (segmentPositions.isEmpty())
2429 return;
2430 const int max = segmentPositions.size() - 1;
2431 if (horizontal && flow() == QListView::TopToBottom && dx != 0) {
2432 int currentValue = qBound(min: 0, val: horizontalValue, max);
2433 int previousValue = qBound(min: 0, val: currentValue + dx, max);
2434 int currentCoordinate = segmentPositions.at(i: currentValue) - spacing();
2435 int previousCoordinate = segmentPositions.at(i: previousValue) - spacing();
2436 dx = previousCoordinate - currentCoordinate;
2437 } else if (vertical && flow() == QListView::LeftToRight && dy != 0) {
2438 int currentValue = qBound(min: 0, val: verticalValue, max);
2439 int previousValue = qBound(min: 0, val: currentValue + dy, max);
2440 int currentCoordinate = segmentPositions.at(i: currentValue) - spacing();
2441 int previousCoordinate = segmentPositions.at(i: previousValue) - spacing();
2442 dy = previousCoordinate - currentCoordinate;
2443 }
2444 } else {
2445 if (flowPositions.isEmpty())
2446 return;
2447 const int max = scrollValueMap.size() - 1;
2448 if (vertical && flow() == QListView::TopToBottom && dy != 0) {
2449 int currentValue = qBound(min: 0, val: verticalValue, max);
2450 int previousValue = qBound(min: 0, val: currentValue + dy, max);
2451 int currentCoordinate = flowPositions.at(i: scrollValueMap.at(i: currentValue));
2452 int previousCoordinate = flowPositions.at(i: scrollValueMap.at(i: previousValue));
2453 dy = previousCoordinate - currentCoordinate;
2454 } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) {
2455 int currentValue = qBound(min: 0, val: horizontalValue, max);
2456 int previousValue = qBound(min: 0, val: currentValue + dx, max);
2457 int currentCoordinate = flowPositions.at(i: scrollValueMap.at(i: currentValue));
2458 int previousCoordinate = flowPositions.at(i: scrollValueMap.at(i: previousValue));
2459 dx = previousCoordinate - currentCoordinate;
2460 }
2461 }
2462 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
2463}
2464
2465bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
2466{
2467 doStaticLayout(info);
2468 return batchStartRow > max; // returning true stops items layout
2469}
2470
2471QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const
2472{
2473 if (flowPositions.isEmpty()
2474 || segmentPositions.isEmpty()
2475 || index.row() >= flowPositions.size() - 1)
2476 return QListViewItem();
2477
2478 const int segment = qBinarySearch<int>(vec: segmentStartRows, item: index.row(),
2479 start: 0, end: segmentStartRows.size() - 1);
2480
2481
2482 QStyleOptionViewItem options;
2483 initViewItemOption(option: &options);
2484 options.rect.setSize(contentsSize);
2485 QSize size = (uniformItemSizes() && cachedItemSize().isValid())
2486 ? cachedItemSize() : itemSize(opt: options, idx: index);
2487 QSize cellSize = size;
2488
2489 QPoint pos;
2490 if (flow() == QListView::LeftToRight) {
2491 pos.setX(flowPositions.at(i: index.row()));
2492 pos.setY(segmentPositions.at(i: segment));
2493 } else { // TopToBottom
2494 pos.setY(flowPositions.at(i: index.row()));
2495 pos.setX(segmentPositions.at(i: segment));
2496 if (isWrapping()) { // make the items as wide as the segment
2497 int right = (segment + 1 >= segmentPositions.size()
2498 ? contentsSize.width()
2499 : segmentPositions.at(i: segment + 1));
2500 cellSize.setWidth(right - pos.x());
2501 } else { // make the items as wide as the viewport
2502 cellSize.setWidth(qMax(a: size.width(), b: viewport()->width() - 2 * spacing()));
2503 }
2504 }
2505
2506 if (dd->itemAlignment & Qt::AlignHorizontal_Mask) {
2507 size.setWidth(qMin(a: size.width(), b: cellSize.width()));
2508 if (dd->itemAlignment & Qt::AlignRight)
2509 pos.setX(pos.x() + cellSize.width() - size.width());
2510 if (dd->itemAlignment & Qt::AlignHCenter)
2511 pos.setX(pos.x() + (cellSize.width() - size.width()) / 2);
2512 } else {
2513 size.setWidth(cellSize.width());
2514 }
2515
2516 return QListViewItem(QRect(pos, size), index.row());
2517}
2518
2519QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info)
2520{
2521 int x, y;
2522 if (info.first == 0) {
2523 flowPositions.clear();
2524 segmentPositions.clear();
2525 segmentStartRows.clear();
2526 segmentExtents.clear();
2527 scrollValueMap.clear();
2528 x = info.bounds.left() + info.spacing;
2529 y = info.bounds.top() + info.spacing;
2530 segmentPositions.append(t: info.flow == QListView::LeftToRight ? y : x);
2531 segmentStartRows.append(t: 0);
2532 } else if (info.wrap) {
2533 if (info.flow == QListView::LeftToRight) {
2534 x = batchSavedPosition;
2535 y = segmentPositions.constLast();
2536 } else { // flow == QListView::TopToBottom
2537 x = segmentPositions.constLast();
2538 y = batchSavedPosition;
2539 }
2540 } else { // not first and not wrap
2541 if (info.flow == QListView::LeftToRight) {
2542 x = batchSavedPosition;
2543 y = info.bounds.top() + info.spacing;
2544 } else { // flow == QListView::TopToBottom
2545 x = info.bounds.left() + info.spacing;
2546 y = batchSavedPosition;
2547 }
2548 }
2549 return QPoint(x, y);
2550}
2551
2552/*!
2553 \internal
2554*/
2555void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info)
2556{
2557 const bool useItemSize = !info.grid.isValid();
2558 const QPoint topLeft = initStaticLayout(info);
2559 QStyleOptionViewItem option;
2560 initViewItemOption(option: &option);
2561 option.rect = info.bounds;
2562 option.rect.adjust(dx1: info.spacing, dy1: info.spacing, dx2: -info.spacing, dy2: -info.spacing);
2563
2564 // The static layout data structures are as follows:
2565 // One vector contains the coordinate in the direction of layout flow.
2566 // Another vector contains the coordinates of the segments.
2567 // A third vector contains the index (model row) of the first item
2568 // of each segment.
2569
2570 int segStartPosition;
2571 int segEndPosition;
2572 int deltaFlowPosition;
2573 int deltaSegPosition;
2574 int deltaSegHint;
2575 int flowPosition;
2576 int segPosition;
2577
2578 if (info.flow == QListView::LeftToRight) {
2579 segStartPosition = info.bounds.left();
2580 segEndPosition = info.bounds.width();
2581 flowPosition = topLeft.x();
2582 segPosition = topLeft.y();
2583 deltaFlowPosition = info.grid.width(); // dx
2584 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy
2585 deltaSegHint = info.grid.height();
2586 } else { // flow == QListView::TopToBottom
2587 segStartPosition = info.bounds.top();
2588 segEndPosition = info.bounds.height();
2589 flowPosition = topLeft.y();
2590 segPosition = topLeft.x();
2591 deltaFlowPosition = info.grid.height(); // dy
2592 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx
2593 deltaSegHint = info.grid.width();
2594 }
2595
2596 for (int row = info.first; row <= info.last; ++row) {
2597 if (isHidden(row)) { // ###
2598 flowPositions.append(t: flowPosition);
2599 } else {
2600 // if we are not using a grid, we need to find the deltas
2601 if (useItemSize) {
2602 QSize hint = itemSize(opt: option, idx: modelIndex(row));
2603 if (info.flow == QListView::LeftToRight) {
2604 deltaFlowPosition = hint.width() + info.spacing;
2605 deltaSegHint = hint.height() + info.spacing;
2606 } else { // TopToBottom
2607 deltaFlowPosition = hint.height() + info.spacing;
2608 deltaSegHint = hint.width() + info.spacing;
2609 }
2610 }
2611 // create new segment
2612 if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) {
2613 segmentExtents.append(t: flowPosition);
2614 flowPosition = info.spacing + segStartPosition;
2615 segPosition += info.spacing + deltaSegPosition;
2616 segmentPositions.append(t: segPosition);
2617 segmentStartRows.append(t: row);
2618 deltaSegPosition = 0;
2619 }
2620 // save the flow position of this item
2621 scrollValueMap.append(t: flowPositions.size());
2622 flowPositions.append(t: flowPosition);
2623 // prepare for the next item
2624 deltaSegPosition = qMax(a: deltaSegHint, b: deltaSegPosition);
2625 flowPosition += info.spacing + deltaFlowPosition;
2626 }
2627 }
2628 // used when laying out next batch
2629 batchSavedPosition = flowPosition;
2630 batchSavedDeltaSeg = deltaSegPosition;
2631 batchStartRow = info.last + 1;
2632 if (info.last == info.max)
2633 flowPosition -= info.spacing; // remove extra spacing
2634 // set the contents size
2635 QRect rect = info.bounds;
2636 if (info.flow == QListView::LeftToRight) {
2637 rect.setRight(segmentPositions.size() == 1 ? flowPosition : info.bounds.right());
2638 rect.setBottom(segPosition + deltaSegPosition);
2639 } else { // TopToBottom
2640 rect.setRight(segPosition + deltaSegPosition);
2641 rect.setBottom(segmentPositions.size() == 1 ? flowPosition : info.bounds.bottom());
2642 }
2643 contentsSize = QSize(rect.right(), rect.bottom());
2644 // if it is the last batch, save the end of the segments
2645 if (info.last == info.max) {
2646 segmentExtents.append(t: flowPosition);
2647 scrollValueMap.append(t: flowPositions.size());
2648 flowPositions.append(t: flowPosition);
2649 segmentPositions.append(t: info.wrap ? segPosition + deltaSegPosition : INT_MAX);
2650 }
2651 // if the new items are visible, update the viewport
2652 QRect changedRect(topLeft, rect.bottomRight());
2653 if (clipRect().intersects(r: changedRect))
2654 viewport()->update();
2655}
2656
2657/*!
2658 \internal
2659 Finds the set of items intersecting with \a area.
2660 In this function, itemsize is counted from topleft to the start of the next item.
2661*/
2662QList<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const
2663{
2664 QList<QModelIndex> ret;
2665 int segStartPosition;
2666 int segEndPosition;
2667 int flowStartPosition;
2668 int flowEndPosition;
2669 if (flow() == QListView::LeftToRight) {
2670 segStartPosition = area.top();
2671 segEndPosition = area.bottom();
2672 flowStartPosition = area.left();
2673 flowEndPosition = area.right();
2674 } else {
2675 segStartPosition = area.left();
2676 segEndPosition = area.right();
2677 flowStartPosition = area.top();
2678 flowEndPosition = area.bottom();
2679 }
2680 if (segmentPositions.size() < 2 || flowPositions.isEmpty())
2681 return ret;
2682 // the last segment position is actually the edge of the last segment
2683 const int segLast = segmentPositions.size() - 2;
2684 int seg = qBinarySearch<int>(vec: segmentPositions, item: segStartPosition, start: 0, end: segLast + 1);
2685 for (; seg <= segLast && segmentPositions.at(i: seg) <= segEndPosition; ++seg) {
2686 int first = segmentStartRows.at(i: seg);
2687 int last = (seg < segLast ? segmentStartRows.at(i: seg + 1) : batchStartRow) - 1;
2688 if (segmentExtents.at(i: seg) < flowStartPosition)
2689 continue;
2690 int row = qBinarySearch<int>(vec: flowPositions, item: flowStartPosition, start: first, end: last);
2691 for (; row <= last && flowPositions.at(i: row) <= flowEndPosition; ++row) {
2692 if (isHidden(row))
2693 continue;
2694 QModelIndex index = modelIndex(row);
2695 if (index.isValid()) {
2696 if (flow() == QListView::LeftToRight || dd->itemAlignment == Qt::Alignment()) {
2697 ret += index;
2698 } else {
2699 const auto viewItem = indexToListViewItem(index);
2700 const int iw = viewItem.width();
2701 const int startPos = qMax(a: segStartPosition, b: segmentPositions.at(i: seg));
2702 const int endPos = qMin(a: segmentPositions.at(i: seg + 1), b: segEndPosition);
2703 if (endPos >= viewItem.x && startPos < viewItem.x + iw)
2704 ret += index;
2705 }
2706 }
2707#if 0 // for debugging
2708 else
2709 qWarning("intersectingSet: row %d was invalid", row);
2710#endif
2711 }
2712 }
2713 return ret;
2714}
2715
2716void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &)
2717{
2718 dd->doDelayedItemsLayout();
2719}
2720
2721
2722QRect QListModeViewBase::mapToViewport(const QRect &rect) const
2723{
2724 if (isWrapping())
2725 return rect;
2726 // If the listview is in "listbox-mode", the items are as wide as the view.
2727 // But we don't shrink the items.
2728 QRect result = rect;
2729 if (flow() == QListView::TopToBottom) {
2730 result.setLeft(spacing());
2731 result.setWidth(qMax(a: rect.width(), b: qMax(a: contentsSize.width(), b: viewport()->width()) - 2 * spacing()));
2732 } else { // LeftToRight
2733 result.setTop(spacing());
2734 result.setHeight(qMax(a: rect.height(), b: qMax(a: contentsSize.height(), b: viewport()->height()) - 2 * spacing()));
2735 }
2736 return result;
2737}
2738
2739int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const
2740{
2741 QList<int> positions;
2742 if (wrap)
2743 positions = segmentPositions;
2744 else if (!flowPositions.isEmpty()) {
2745 positions.reserve(asize: scrollValueMap.size());
2746 for (int itemShown : scrollValueMap)
2747 positions.append(t: flowPositions.at(i: itemShown));
2748 }
2749 if (positions.isEmpty() || bounds <= length)
2750 return positions.size();
2751 if (uniformItemSizes()) {
2752 for (int i = 1; i < positions.size(); ++i)
2753 if (positions.at(i) > 0)
2754 return length / positions.at(i);
2755 return 0; // all items had height 0
2756 }
2757 int pageSteps = 0;
2758 int steps = positions.size() - 1;
2759 int max = qMax(a: length, b: bounds);
2760 int min = qMin(a: length, b: bounds);
2761 int pos = min - (max - positions.constLast());
2762
2763 while (pos >= 0 && steps > 0) {
2764 pos -= (positions.at(i: steps) - positions.at(i: steps - 1));
2765 if (pos >= 0) //this item should be visible
2766 ++pageSteps;
2767 --steps;
2768 }
2769
2770 // at this point we know that positions has at least one entry
2771 return qMax(a: pageSteps, b: 1);
2772}
2773
2774int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize,
2775 QAbstractItemView::ScrollHint hint,
2776 Qt::Orientation orientation, bool wrap, int itemExtent) const
2777{
2778 if (index < 0)
2779 return scrollValue;
2780
2781 itemExtent += spacing();
2782 QList<int> hiddenRows = dd->hiddenRowIds();
2783 std::sort(first: hiddenRows.begin(), last: hiddenRows.end());
2784 int hiddenRowsBefore = 0;
2785 for (int i = 0; i < hiddenRows.size() - 1; ++i)
2786 if (hiddenRows.at(i) > index + hiddenRowsBefore)
2787 break;
2788 else
2789 ++hiddenRowsBefore;
2790 if (!wrap) {
2791 int topIndex = index;
2792 const int bottomIndex = topIndex;
2793 const int bottomCoordinate = flowPositions.at(i: index + hiddenRowsBefore);
2794 while (topIndex > 0 &&
2795 (bottomCoordinate - flowPositions.at(i: topIndex + hiddenRowsBefore - 1) + itemExtent) <= (viewportSize)) {
2796 topIndex--;
2797 // will the next one be a hidden row -> skip
2798 while (hiddenRowsBefore > 0 && hiddenRows.at(i: hiddenRowsBefore - 1) >= topIndex + hiddenRowsBefore - 1)
2799 hiddenRowsBefore--;
2800 }
2801
2802 const int itemCount = bottomIndex - topIndex + 1;
2803 switch (hint) {
2804 case QAbstractItemView::PositionAtTop:
2805 return index;
2806 case QAbstractItemView::PositionAtBottom:
2807 return index - itemCount + 1;
2808 case QAbstractItemView::PositionAtCenter:
2809 return index - (itemCount / 2);
2810 default:
2811 break;
2812 }
2813 } else { // wrapping
2814 Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight
2815 ? Qt::Horizontal : Qt::Vertical);
2816 if (flowOrientation == orientation) { // scrolling in the "flow" direction
2817 // ### wrapped scrolling in the flow direction
2818 return flowPositions.at(i: index + hiddenRowsBefore); // ### always pixel based for now
2819 } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction
2820 int segment = qBinarySearch<int>(vec: segmentStartRows, item: index, start: 0, end: segmentStartRows.size() - 1);
2821 int leftSegment = segment;
2822 const int rightSegment = leftSegment;
2823 const int bottomCoordinate = segmentPositions.at(i: segment);
2824
2825 while (leftSegment > scrollValue &&
2826 (bottomCoordinate - segmentPositions.at(i: leftSegment-1) + itemExtent) <= (viewportSize)) {
2827 leftSegment--;
2828 }
2829
2830 const int segmentCount = rightSegment - leftSegment + 1;
2831 switch (hint) {
2832 case QAbstractItemView::PositionAtTop:
2833 return segment;
2834 case QAbstractItemView::PositionAtBottom:
2835 return segment - segmentCount + 1;
2836 case QAbstractItemView::PositionAtCenter:
2837 return segment - (segmentCount / 2);
2838 default:
2839 break;
2840 }
2841 }
2842 }
2843 return scrollValue;
2844}
2845
2846void QListModeViewBase::clear()
2847{
2848 flowPositions.clear();
2849 segmentPositions.clear();
2850 segmentStartRows.clear();
2851 segmentExtents.clear();
2852 batchSavedPosition = 0;
2853 batchStartRow = 0;
2854 batchSavedDeltaSeg = 0;
2855}
2856
2857/*
2858 * IconMode ListView Implementation
2859*/
2860
2861void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index)
2862{
2863 if (index.row() >= items.size())
2864 return;
2865 const QSize oldContents = contentsSize;
2866 qq->update(index); // update old position
2867 moveItem(index: index.row(), dest: position);
2868 qq->update(index); // update new position
2869
2870 if (contentsSize != oldContents)
2871 dd->viewUpdateGeometries(); // update the scroll bars
2872}
2873
2874void QIconModeViewBase::appendHiddenRow(int row)
2875{
2876 if (row >= 0 && row < items.size()) //remove item
2877 tree.removeLeaf(r: items.at(i: row).rect(), i: row);
2878 QCommonListViewBase::appendHiddenRow(row);
2879}
2880
2881void QIconModeViewBase::removeHiddenRow(int row)
2882{
2883 QCommonListViewBase::removeHiddenRow(row);
2884 if (row >= 0 && row < items.size()) //insert item
2885 tree.insertLeaf(r: items.at(i: row).rect(), i: row);
2886}
2887
2888#if QT_CONFIG(draganddrop)
2889bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions)
2890{
2891 // This function does the same thing as in QAbstractItemView::startDrag(),
2892 // plus adding viewitems to the draggedItems list.
2893 // We need these items to draw the drag items
2894 QModelIndexList indexes = dd->selectionModel->selectedIndexes();
2895 if (indexes.size() > 0 ) {
2896 if (viewport()->acceptDrops()) {
2897 QModelIndexList::ConstIterator it = indexes.constBegin();
2898 for (; it != indexes.constEnd(); ++it)
2899 if (dd->model->flags(index: *it) & Qt::ItemIsDragEnabled
2900 && (*it).column() == dd->column)
2901 draggedItems.push_back(t: *it);
2902 }
2903
2904 QRect rect;
2905 QPixmap pixmap = dd->renderToPixmap(indexes, r: &rect);
2906 rect.adjust(dx1: horizontalOffset(), dy1: verticalOffset(), dx2: 0, dy2: 0);
2907 QDrag *drag = new QDrag(qq);
2908 drag->setMimeData(dd->model->mimeData(indexes));
2909 drag->setPixmap(pixmap);
2910 drag->setHotSpot(dd->pressedPosition - rect.topLeft());
2911 dd->dropEventMoved = false;
2912 Qt::DropAction action = drag->exec(supportedActions, defaultAction: dd->defaultDropAction);
2913 draggedItems.clear();
2914 // delete item, unless it has already been moved internally (see filterDropEvent)
2915 if (action == Qt::MoveAction && !dd->dropEventMoved) {
2916 if (dd->dragDropMode != QAbstractItemView::InternalMove || drag->target() == qq->viewport())
2917 dd->clearOrRemove();
2918 }
2919 dd->dropEventMoved = false;
2920 }
2921 return true;
2922}
2923
2924bool QIconModeViewBase::filterDropEvent(QDropEvent *e)
2925{
2926 if (e->source() != qq)
2927 return false;
2928
2929 const QSize contents = contentsSize;
2930 QPoint offset(horizontalOffset(), verticalOffset());
2931 QPoint end = e->position().toPoint() + offset;
2932 if (qq->acceptDrops()) {
2933 const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled;
2934 const QList<QModelIndex> &dropIndices = intersectingSet(area: QRect(end, QSize(1, 1)));
2935 for (const QModelIndex &index : dropIndices)
2936 if ((index.flags() & dropableFlags) == dropableFlags)
2937 return false;
2938 }
2939 QPoint start = dd->pressedPosition;
2940 QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(pos: end) - snapToGrid(pos: start) : end - start);
2941 const QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes();
2942 for (const auto &index : indexes) {
2943 QRect rect = dd->rectForIndex(index);
2944 viewport()->update(dd->mapToViewport(rect, extend: false));
2945 QPoint dest = rect.topLeft() + delta;
2946 if (qq->isRightToLeft())
2947 dest.setX(dd->flipX(x: dest.x()) - rect.width());
2948 moveItem(index: index.row(), dest);
2949 qq->update(index);
2950 }
2951 dd->stopAutoScroll();
2952 draggedItems.clear();
2953 dd->emitIndexesMoved(indexes);
2954 // do not delete item on internal move, see filterStartDrag()
2955 dd->dropEventMoved = true;
2956 e->accept(); // we have handled the event
2957 // if the size has not grown, we need to check if it has shrunk
2958 if (contentsSize != contents) {
2959 if ((contentsSize.width() <= contents.width()
2960 || contentsSize.height() <= contents.height())) {
2961 updateContentsSize();
2962 }
2963 dd->viewUpdateGeometries();
2964 }
2965 return true;
2966}
2967
2968bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e)
2969{
2970 viewport()->update(draggedItemsRect()); // erase the area
2971 draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items
2972 return QCommonListViewBase::filterDragLeaveEvent(e);
2973}
2974
2975bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e)
2976{
2977 const bool wasAccepted = e->isAccepted();
2978
2979 // ignore by default
2980 e->ignore();
2981
2982 if (e->source() != qq || !dd->canDrop(event: e)) {
2983 // restore previous acceptance on failure
2984 e->setAccepted(wasAccepted);
2985 return false;
2986 }
2987
2988 // get old dragged items rect
2989 QRect itemsRect = this->itemsRect(indexes: draggedItems);
2990 viewport()->update(itemsRect.translated(p: draggedItemsDelta()));
2991 // update position
2992 draggedItemsPos = e->position().toPoint();
2993 // get new items rect
2994 viewport()->update(itemsRect.translated(p: draggedItemsDelta()));
2995 // set the item under the cursor to current
2996 QModelIndex index;
2997 if (movement() == QListView::Snap) {
2998 QRect rect(snapToGrid(pos: e->position().toPoint() + offset()), gridSize());
2999 const QList<QModelIndex> intersectVector = intersectingSet(area: rect);
3000 index = intersectVector.size() > 0 ? intersectVector.last() : QModelIndex();
3001 } else {
3002 index = qq->indexAt(p: e->position().toPoint());
3003 }
3004 // check if we allow drops here
3005 if (draggedItems.contains(t: index))
3006 e->accept(); // allow changing item position
3007 else if (dd->model->flags(index) & Qt::ItemIsDropEnabled)
3008 e->accept(); // allow dropping on dropenabled items
3009 else if (!index.isValid())
3010 e->accept(); // allow dropping in empty areas
3011
3012 // the event was treated. do autoscrolling
3013 if (dd->shouldAutoScroll(pos: e->position().toPoint()))
3014 dd->startAutoScroll();
3015 return true;
3016}
3017#endif // QT_CONFIG(draganddrop)
3018
3019void QIconModeViewBase::setRowCount(int rowCount)
3020{
3021 tree.create(n: qMax(a: rowCount - hiddenCount(), b: 0));
3022}
3023
3024void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
3025{
3026 if (scrollElasticBand)
3027 dd->scrollElasticBandBy(dx: isRightToLeft() ? -dx : dx, dy);
3028
3029 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
3030 if (!draggedItems.isEmpty())
3031 viewport()->update(draggedItemsRect().translated(dx, dy));
3032}
3033
3034void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
3035{
3036 if (column() >= topLeft.column() && column() <= bottomRight.column()) {
3037 QStyleOptionViewItem option;
3038 initViewItemOption(option: &option);
3039 const int bottom = qMin(a: items.size(), b: bottomRight.row() + 1);
3040 const bool useItemSize = !dd->grid.isValid();
3041 for (int row = topLeft.row(); row < bottom; ++row)
3042 {
3043 QSize s = itemSize(opt: option, idx: modelIndex(row));
3044 if (!useItemSize)
3045 {
3046 s.setWidth(qMin(a: dd->grid.width(), b: s.width()));
3047 s.setHeight(qMin(a: dd->grid.height(), b: s.height()));
3048 }
3049 items[row].resize(size: s);
3050 }
3051 }
3052}
3053
3054bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
3055{
3056 if (info.last >= items.size()) {
3057 //first we create the items
3058 QStyleOptionViewItem option;
3059 initViewItemOption(option: &option);
3060 for (int row = items.size(); row <= info.last; ++row) {
3061 QSize size = itemSize(opt: option, idx: modelIndex(row));
3062 QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos
3063 items.append(t: item);
3064 }
3065 doDynamicLayout(info);
3066 }
3067 return (batchStartRow > max); // done
3068}
3069
3070QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const
3071{
3072 if (index.isValid() && index.row() < items.size())
3073 return items.at(i: index.row());
3074 return QListViewItem();
3075}
3076
3077void QIconModeViewBase::initBspTree(const QSize &contents)
3078{
3079 // remove all items from the tree
3080 int leafCount = tree.leafCount();
3081 for (int l = 0; l < leafCount; ++l)
3082 tree.leaf(i: l).clear();
3083 // we have to get the bounding rect of the items before we can initialize the tree
3084 QBspTree::Node::Type type = QBspTree::Node::Both; // 2D
3085 // simple heuristics to get better bsp
3086 if (contents.height() / contents.width() >= 3)
3087 type = QBspTree::Node::HorizontalPlane;
3088 else if (contents.width() / contents.height() >= 3)
3089 type = QBspTree::Node::VerticalPlane;
3090 // build tree for the bounding rect (not just the contents rect)
3091 tree.init(area: QRect(0, 0, contents.width(), contents.height()), type);
3092}
3093
3094QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info)
3095{
3096 int x, y;
3097 if (info.first == 0) {
3098 x = info.bounds.x() + info.spacing;
3099 y = info.bounds.y() + info.spacing;
3100 items.reserve(asize: rowCount() - hiddenCount());
3101 } else {
3102 int idx = info.first - 1;
3103 while (idx > 0 && !items.at(i: idx).isValid())
3104 --idx;
3105 const QListViewItem &item = items.at(i: idx);
3106 x = item.x;
3107 y = item.y;
3108 if (info.flow == QListView::LeftToRight)
3109 x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing;
3110 else
3111 y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing;
3112 }
3113 return QPoint(x, y);
3114}
3115
3116/*!
3117 \internal
3118*/
3119void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info)
3120{
3121 const bool useItemSize = !info.grid.isValid();
3122 const QPoint topLeft = initDynamicLayout(info);
3123
3124 int segStartPosition;
3125 int segEndPosition;
3126 int deltaFlowPosition;
3127 int deltaSegPosition;
3128 int deltaSegHint;
3129 int flowPosition;
3130 int segPosition;
3131
3132 if (info.flow == QListView::LeftToRight) {
3133 segStartPosition = info.bounds.left() + info.spacing;
3134 segEndPosition = info.bounds.right();
3135 deltaFlowPosition = info.grid.width(); // dx
3136 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy
3137 deltaSegHint = info.grid.height();
3138 flowPosition = topLeft.x();
3139 segPosition = topLeft.y();
3140 } else { // flow == QListView::TopToBottom
3141 segStartPosition = info.bounds.top() + info.spacing;
3142 segEndPosition = info.bounds.bottom();
3143 deltaFlowPosition = info.grid.height(); // dy
3144 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx
3145 deltaSegHint = info.grid.width();
3146 flowPosition = topLeft.y();
3147 segPosition = topLeft.x();
3148 }
3149
3150 if (moved.size() != items.size())
3151 moved.resize(size: items.size());
3152
3153 QRect rect(QPoint(), topLeft);
3154 QListViewItem *item = nullptr;
3155 Q_ASSERT(info.first <= info.last);
3156 for (int row = info.first; row <= info.last; ++row) {
3157 item = &items[row];
3158 if (isHidden(row)) {
3159 item->invalidate();
3160 } else {
3161 // if we are not using a grid, we need to find the deltas
3162 if (useItemSize) {
3163 if (info.flow == QListView::LeftToRight)
3164 deltaFlowPosition = item->w + info.spacing;
3165 else
3166 deltaFlowPosition = item->h + info.spacing;
3167 } else {
3168 item->w = qMin<int>(a: info.grid.width(), b: item->w);
3169 item->h = qMin<int>(a: info.grid.height(), b: item->h);
3170 }
3171
3172 // create new segment
3173 if (info.wrap
3174 && flowPosition + deltaFlowPosition > segEndPosition
3175 && flowPosition > segStartPosition) {
3176 flowPosition = segStartPosition;
3177 segPosition += deltaSegPosition;
3178 if (useItemSize)
3179 deltaSegPosition = 0;
3180 }
3181 // We must delay calculation of the seg adjustment, as this item
3182 // may have caused a wrap to occur
3183 if (useItemSize) {
3184 if (info.flow == QListView::LeftToRight)
3185 deltaSegHint = item->h + info.spacing;
3186 else
3187 deltaSegHint = item->w + info.spacing;
3188 deltaSegPosition = qMax(a: deltaSegPosition, b: deltaSegHint);
3189 }
3190
3191 // set the position of the item
3192 // ### idealy we should have some sort of alignment hint for the item
3193 // ### (normally that would be a point between the icon and the text)
3194 if (!moved.testBit(i: row)) {
3195 if (info.flow == QListView::LeftToRight) {
3196 if (useItemSize) {
3197 item->x = flowPosition;
3198 item->y = segPosition;
3199 } else { // use grid
3200 item->x = flowPosition + ((deltaFlowPosition - item->w) / 2);
3201 item->y = segPosition;
3202 }
3203 } else { // TopToBottom
3204 if (useItemSize) {
3205 item->y = flowPosition;
3206 item->x = segPosition;
3207 } else { // use grid
3208 item->y = flowPosition + ((deltaFlowPosition - item->h) / 2);
3209 item->x = segPosition;
3210 }
3211 }
3212 }
3213
3214 // let the contents contain the new item
3215 if (useItemSize)
3216 rect |= item->rect();
3217 else if (info.flow == QListView::LeftToRight)
3218 rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition);
3219 else // flow == TopToBottom
3220 rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition);
3221
3222 // prepare for next item
3223 flowPosition += deltaFlowPosition; // current position + item width + gap
3224 }
3225 }
3226 Q_ASSERT(item);
3227 batchSavedDeltaSeg = deltaSegPosition;
3228 batchStartRow = info.last + 1;
3229 bool done = (info.last >= rowCount() - 1);
3230 // resize the content area
3231 if (done || !info.bounds.contains(r: item->rect())) {
3232 contentsSize = rect.size();
3233 if (info.flow == QListView::LeftToRight)
3234 contentsSize.rheight() += info.spacing;
3235 else
3236 contentsSize.rwidth() += info.spacing;
3237 }
3238 if (rect.size().isEmpty())
3239 return;
3240 // resize tree
3241 int insertFrom = info.first;
3242 if (done || info.first == 0) {
3243 initBspTree(contents: rect.size());
3244 insertFrom = 0;
3245 }
3246 // insert items in tree
3247 for (int row = insertFrom; row <= info.last; ++row)
3248 tree.insertLeaf(r: items.at(i: row).rect(), i: row);
3249 // if the new items are visible, update the viewport
3250 QRect changedRect(topLeft, rect.bottomRight());
3251 if (clipRect().intersects(r: changedRect))
3252 viewport()->update();
3253}
3254
3255QList<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const
3256{
3257 QIconModeViewBase *that = const_cast<QIconModeViewBase*>(this);
3258 QBspTree::Data data(static_cast<void*>(that));
3259 QList<QModelIndex> res;
3260 that->interSectingVector = &res;
3261 that->tree.climbTree(rect: area, function: &QIconModeViewBase::addLeaf, data);
3262 that->interSectingVector = nullptr;
3263 return res;
3264}
3265
3266QRect QIconModeViewBase::itemsRect(const QList<QModelIndex> &indexes) const
3267{
3268 QRect rect;
3269 for (const auto &index : indexes)
3270 rect |= viewItemRect(item: indexToListViewItem(index));
3271 return rect;
3272}
3273
3274int QIconModeViewBase::itemIndex(const QListViewItem &item) const
3275{
3276 if (!item.isValid())
3277 return -1;
3278 int i = item.indexHint;
3279 if (i < items.size()) {
3280 if (items.at(i) == item)
3281 return i;
3282 } else {
3283 i = items.size() - 1;
3284 }
3285
3286 int j = i;
3287 int c = items.size();
3288 bool a = true;
3289 bool b = true;
3290
3291 while (a || b) {
3292 if (a) {
3293 if (items.at(i) == item) {
3294 items.at(i).indexHint = i;
3295 return i;
3296 }
3297 a = ++i < c;
3298 }
3299 if (b) {
3300 if (items.at(i: j) == item) {
3301 items.at(i: j).indexHint = j;
3302 return j;
3303 }
3304 b = --j > -1;
3305 }
3306 }
3307 return -1;
3308}
3309
3310void QIconModeViewBase::addLeaf(QList<int> &leaf, const QRect &area, uint visited,
3311 QBspTree::Data data)
3312{
3313 QListViewItem *vi;
3314 QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr);
3315 for (int i = 0; i < leaf.size(); ++i) {
3316 int idx = leaf.at(i);
3317 if (idx < 0 || idx >= _this->items.size())
3318 continue;
3319 vi = &_this->items[idx];
3320 Q_ASSERT(vi);
3321 if (vi->isValid() && vi->rect().intersects(r: area) && vi->visited != visited) {
3322 QModelIndex index = _this->dd->listViewItemToIndex(item: *vi);
3323 Q_ASSERT(index.isValid());
3324 _this->interSectingVector->append(t: index);
3325 vi->visited = visited;
3326 }
3327 }
3328}
3329
3330void QIconModeViewBase::moveItem(int index, const QPoint &dest)
3331{
3332 // does not impact on the bintree itself or the contents rect
3333 QListViewItem *item = &items[index];
3334 QRect rect = item->rect();
3335
3336 // move the item without removing it from the tree
3337 tree.removeLeaf(r: rect, i: index);
3338 item->move(position: dest);
3339 tree.insertLeaf(r: QRect(dest, rect.size()), i: index);
3340
3341 // resize the contents area
3342 contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size();
3343
3344 // mark the item as moved
3345 if (moved.size() != items.size())
3346 moved.resize(size: items.size());
3347 moved.setBit(i: index, val: true);
3348}
3349
3350QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const
3351{
3352 int x = pos.x() - (pos.x() % gridSize().width());
3353 int y = pos.y() - (pos.y() % gridSize().height());
3354 return QPoint(x, y);
3355}
3356
3357QPoint QIconModeViewBase::draggedItemsDelta() const
3358{
3359 if (movement() == QListView::Snap) {
3360 QPoint snapdelta = QPoint((offset().x() % gridSize().width()),
3361 (offset().y() % gridSize().height()));
3362 return snapToGrid(pos: draggedItemsPos + snapdelta) - snapToGrid(pos: pressedPosition()) - snapdelta;
3363 }
3364 return draggedItemsPos - pressedPosition();
3365}
3366
3367QRect QIconModeViewBase::draggedItemsRect() const
3368{
3369 QRect rect = itemsRect(indexes: draggedItems);
3370 rect.translate(p: draggedItemsDelta());
3371 return rect;
3372}
3373
3374void QListViewPrivate::scrollElasticBandBy(int dx, int dy)
3375{
3376 if (dx > 0) // right
3377 elasticBand.moveRight(pos: elasticBand.right() + dx);
3378 else if (dx < 0) // left
3379 elasticBand.moveLeft(pos: elasticBand.left() - dx);
3380 if (dy > 0) // down
3381 elasticBand.moveBottom(pos: elasticBand.bottom() + dy);
3382 else if (dy < 0) // up
3383 elasticBand.moveTop(pos: elasticBand.top() - dy);
3384}
3385
3386void QIconModeViewBase::clear()
3387{
3388 tree.destroy();
3389 items.clear();
3390 moved.clear();
3391 batchStartRow = 0;
3392 batchSavedDeltaSeg = 0;
3393}
3394
3395void QIconModeViewBase::updateContentsSize()
3396{
3397 QRect bounding;
3398 for (int i = 0; i < items.size(); ++i)
3399 bounding |= items.at(i).rect();
3400 contentsSize = bounding.size();
3401}
3402
3403/*!
3404 \reimp
3405*/
3406void QListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3407{
3408 QAbstractItemView::currentChanged(current, previous);
3409#if QT_CONFIG(accessibility)
3410 if (QAccessible::isActive()) {
3411 if (current.isValid()) {
3412 int entry = visualIndex(index: current);
3413 QAccessibleEvent event(this, QAccessible::Focus);
3414 event.setChild(entry);
3415 QAccessible::updateAccessibility(event: &event);
3416 }
3417 }
3418#endif
3419}
3420
3421/*!
3422 \reimp
3423*/
3424void QListView::selectionChanged(const QItemSelection &selected,
3425 const QItemSelection &deselected)
3426{
3427#if QT_CONFIG(accessibility)
3428 if (QAccessible::isActive()) {
3429 // ### does not work properly for selection ranges.
3430 QModelIndex sel = selected.indexes().value(i: 0);
3431 if (sel.isValid()) {
3432 int entry = visualIndex(index: sel);
3433 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3434 event.setChild(entry);
3435 QAccessible::updateAccessibility(event: &event);
3436 }
3437 QModelIndex desel = deselected.indexes().value(i: 0);
3438 if (desel.isValid()) {
3439 int entry = visualIndex(index: desel);
3440 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3441 event.setChild(entry);
3442 QAccessible::updateAccessibility(event: &event);
3443 }
3444 }
3445#endif
3446 QAbstractItemView::selectionChanged(selected, deselected);
3447}
3448
3449int QListView::visualIndex(const QModelIndex &index) const
3450{
3451 Q_D(const QListView);
3452 d->executePostedLayout();
3453 QListViewItem itm = d->indexToListViewItem(index);
3454 int visualIndex = d->commonListView->itemIndex(item: itm);
3455 for (const auto &idx : std::as_const(t: d->hiddenRows)) {
3456 if (idx.row() <= index.row())
3457 --visualIndex;
3458 }
3459 return visualIndex;
3460}
3461
3462
3463/*!
3464 \since 5.2
3465 \reimp
3466*/
3467QSize QListView::viewportSizeHint() const
3468{
3469 Q_D(const QListView);
3470 // We don't have a nice simple size hint for invalid or wrapping list views.
3471 if (!d->model)
3472 return QAbstractItemView::viewportSizeHint();
3473 const int rc = d->model->rowCount();
3474 if (rc == 0 || isWrapping())
3475 return QAbstractItemView::viewportSizeHint();
3476
3477 QStyleOptionViewItem option;
3478 initViewItemOption(option: &option);
3479
3480 if (uniformItemSizes()) {
3481 QSize sz = d->cachedItemSize;
3482 if (!sz.isValid()) {
3483 QModelIndex idx = d->model->index(row: 0, column: d->column, parent: d->root);
3484 sz = d->itemSize(option, index: idx);
3485 }
3486 sz.setHeight(rc * sz.height());
3487 return sz;
3488 }
3489
3490 // Using AdjustToContents with a high number of rows will normally not make sense, so we limit
3491 // this to default 1000 (that is btw the default for QHeaderView::resizeContentsPrecision())
3492 // (By setting the property _q_resizeContentPrecision the user can however override this).
3493 int maximumRows = 1000;
3494 const QVariant userOverrideValue = property(name: "_q_resizeContentPrecision");
3495 if (userOverrideValue.isValid() && userOverrideValue.toInt() > 0) {
3496 maximumRows = userOverrideValue.toInt();
3497 }
3498 const int rowCount = qMin(a: rc, b: maximumRows);
3499
3500 int h = 0;
3501 int w = 0;
3502
3503 for (int row = 0; row < rowCount; ++row) {
3504 QModelIndex idx = d->model->index(row, column: d->column, parent: d->root);
3505 QSize itemSize = d->itemSize(option, index: idx);
3506 h += itemSize.height();
3507 w = qMax(a: w, b: itemSize.width());
3508 }
3509 return QSize(w, h);
3510}
3511
3512QT_END_NAMESPACE
3513
3514#include "moc_qlistview.cpp"
3515

source code of qtbase/src/widgets/itemviews/qlistview.cpp