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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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