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

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