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

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