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