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