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#ifdef QT_KEYPAD_NAVIGATION
1229 if (QApplicationPrivate::keypadNavigationEnabled()) {
1230 int row = d->batchStartRow() - 1;
1231 while (row >= 0 && d->isHiddenOrDisabled(row))
1232 --row;
1233 if (row >= 0)
1234 return d->model->index(row, d->column, d->root);
1235 }
1236#endif
1237 return current;
1238 }
1239 if (rect.top() < 0)
1240 rect.setTop(0);
1241 intersectVector = d->intersectingSet(rect);
1242 d->removeCurrentAndDisabled(&intersectVector, current);
1243 }
1244 return d->closestIndex(initialRect, intersectVector);
1245 case MovePageDown: {
1246 if (rect.height() >= d->viewport->height())
1247 return moveCursor(QAbstractItemView::MoveDown, modifiers);
1248
1249 rect.moveTop(rect.top() + d->viewport->height() - 1);
1250 if (rect.bottom() > contents.height() - rect.height()) {
1251 rect.setTop(contents.height() - 1);
1252 rect.setBottom(contents.height());
1253 }
1254 QModelIndex index = current;
1255 // index's bottom() - current's top() always <= (d->viewport->rect().height()
1256 while (intersectVector.isEmpty()
1257 || rectForIndex(index).bottom() >= (d->viewport->rect().height() + rectForIndex(current).top())
1258 || rect.bottom() > contents.height()) {
1259 rect.translate(0, -1);
1260 if (rect.top() >= contents.height()) {
1261 return current;
1262 }
1263 intersectVector = d->intersectingSet(rect);
1264 index = d->closestIndex(initialRect, intersectVector);
1265 }
1266 return index;
1267 }
1268 case MoveNext:
1269 case MoveDown:
1270 while (intersectVector.isEmpty()) {
1271 rect.translate(0, rect.height());
1272 if (rect.top() >= contents.height()) {
1273#ifdef QT_KEYPAD_NAVIGATION
1274 if (QApplicationPrivate::keypadNavigationEnabled()) {
1275 int rowCount = d->model->rowCount(d->root);
1276 int row = 0;
1277 while (row < rowCount && d->isHiddenOrDisabled(row))
1278 ++row;
1279 if (row < rowCount)
1280 return d->model->index(row, d->column, d->root);
1281 }
1282#endif
1283 return current;
1284 }
1285 if (rect.bottom() > contents.height())
1286 rect.setBottom(contents.height());
1287 intersectVector = d->intersectingSet(rect);
1288 d->removeCurrentAndDisabled(&intersectVector, current);
1289 }
1290 return d->closestIndex(initialRect, intersectVector);
1291 case MoveHome:
1292 return d->model->index(0, d->column, d->root);
1293 case MoveEnd:
1294 return d->model->index(d->batchStartRow() - 1, d->column, d->root);}
1295
1296 return current;
1297}
1298
1299/*!
1300 Returns the rectangle of the item at position \a index in the
1301 model. The rectangle is in contents coordinates.
1302
1303 \sa visualRect()
1304*/
1305QRect QListView::rectForIndex(const QModelIndex &index) const
1306{
1307 return d_func()->rectForIndex(index);
1308}
1309
1310/*!
1311 Sets the contents position of the item at \a index in the model to the given
1312 \a position.
1313 If the list view's movement mode is Static or its view mode is ListView,
1314 this function will have no effect.
1315*/
1316void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index)
1317{
1318 Q_D(QListView);
1319 if (d->movement == Static
1320 || !d->isIndexValid(index)
1321 || index.parent() != d->root
1322 || index.column() != d->column)
1323 return;
1324
1325 d->executePostedLayout();
1326 d->commonListView->setPositionForIndex(position, index);
1327}
1328
1329/*!
1330 \reimp
1331*/
1332void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
1333{
1334 Q_D(QListView);
1335 if (!d->selectionModel)
1336 return;
1337
1338 // if we are wrapping, we can only select inside the contents rectangle
1339 int w = qMax(d->contentsSize().width(), d->viewport->width());
1340 int h = qMax(d->contentsSize().height(), d->viewport->height());
1341 if (d->wrap && !QRect(0, 0, w, h).intersects(rect))
1342 return;
1343
1344 QItemSelection selection;
1345
1346 if (rect.width() == 1 && rect.height() == 1) {
1347 const QList<QModelIndex> intersectVector =
1348 d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset()));
1349 QModelIndex tl;
1350 if (!intersectVector.isEmpty())
1351 tl = intersectVector.last(); // special case for mouse press; only select the top item
1352 if (tl.isValid() && d->isIndexEnabled(tl))
1353 selection.select(tl, tl);
1354 } else {
1355 if (state() == DragSelectingState) { // visual selection mode (rubberband selection)
1356 selection = d->selection(rect.translated(horizontalOffset(), verticalOffset()));
1357 } else { // logical selection mode (key and mouse click selection)
1358 QModelIndex tl, br;
1359 // get the first item
1360 const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1);
1361 QList<QModelIndex> intersectVector = d->intersectingSet(topLeft);
1362 if (!intersectVector.isEmpty())
1363 tl = intersectVector.last();
1364 // get the last item
1365 const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1);
1366 intersectVector = d->intersectingSet(bottomRight);
1367 if (!intersectVector.isEmpty())
1368 br = intersectVector.last();
1369
1370 // get the ranges
1371 if (tl.isValid() && br.isValid()
1372 && d->isIndexEnabled(tl)
1373 && d->isIndexEnabled(br)) {
1374 QRect first = d->cellRectForIndex(tl);
1375 QRect last = d->cellRectForIndex(br);
1376 QRect middle;
1377 if (d->flow == LeftToRight) {
1378 QRect &top = first;
1379 QRect &bottom = last;
1380 // if bottom is above top, swap them
1381 if (top.center().y() > bottom.center().y()) {
1382 QRect tmp = top;
1383 top = bottom;
1384 bottom = tmp;
1385 }
1386 // if the rect are on different lines, expand
1387 if (top.top() != bottom.top()) {
1388 // top rectangle
1389 if (isRightToLeft())
1390 top.setLeft(0);
1391 else
1392 top.setRight(contentsSize().width());
1393 // bottom rectangle
1394 if (isRightToLeft())
1395 bottom.setRight(contentsSize().width());
1396 else
1397 bottom.setLeft(0);
1398 } else if (top.left() > bottom.right()) {
1399 if (isRightToLeft())
1400 bottom.setLeft(top.right());
1401 else
1402 bottom.setRight(top.left());
1403 } else {
1404 if (isRightToLeft())
1405 top.setLeft(bottom.right());
1406 else
1407 top.setRight(bottom.left());
1408 }
1409 // middle rectangle
1410 if (top.bottom() < bottom.top()) {
1411 if (gridSize().isValid() && !gridSize().isNull())
1412 middle.setTop(top.top() + gridSize().height());
1413 else
1414 middle.setTop(top.bottom() + 1);
1415 middle.setLeft(qMin(top.left(), bottom.left()));
1416 middle.setBottom(bottom.top() - 1);
1417 middle.setRight(qMax(top.right(), bottom.right()));
1418 }
1419 } else { // TopToBottom
1420 QRect &left = first;
1421 QRect &right = last;
1422 if (left.center().x() > right.center().x())
1423 qSwap(left, right);
1424
1425 int ch = contentsSize().height();
1426 if (left.left() != right.left()) {
1427 // left rectangle
1428 if (isRightToLeft())
1429 left.setTop(0);
1430 else
1431 left.setBottom(ch);
1432
1433 // top rectangle
1434 if (isRightToLeft())
1435 right.setBottom(ch);
1436 else
1437 right.setTop(0);
1438 // only set middle if the
1439 middle.setTop(0);
1440 middle.setBottom(ch);
1441 if (gridSize().isValid() && !gridSize().isNull())
1442 middle.setLeft(left.left() + gridSize().width());
1443 else
1444 middle.setLeft(left.right() + 1);
1445 middle.setRight(right.left() - 1);
1446 } else if (left.bottom() < right.top()) {
1447 left.setBottom(right.top() - 1);
1448 } else {
1449 right.setBottom(left.top() - 1);
1450 }
1451 }
1452
1453 // do the selections
1454 QItemSelection topSelection = d->selection(first);
1455 QItemSelection middleSelection = d->selection(middle);
1456 QItemSelection bottomSelection = d->selection(last);
1457 // merge
1458 selection.merge(topSelection, QItemSelectionModel::Select);
1459 selection.merge(middleSelection, QItemSelectionModel::Select);
1460 selection.merge(bottomSelection, QItemSelectionModel::Select);
1461 }
1462 }
1463 }
1464
1465 d->selectionModel->select(selection, command);
1466}
1467
1468/*!
1469 \reimp
1470
1471 Since 4.7, the returned region only contains rectangles intersecting
1472 (or included in) the viewport.
1473*/
1474QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const
1475{
1476 Q_D(const QListView);
1477 // ### NOTE: this is a potential bottleneck in non-static mode
1478 int c = d->column;
1479 QRegion selectionRegion;
1480 const QRect &viewportRect = d->viewport->rect();
1481 for (const auto &elem : selection) {
1482 if (!elem.isValid())
1483 continue;
1484 QModelIndex parent = elem.topLeft().parent();
1485 //we only display the children of the root in a listview
1486 //we're not interested in the other model indexes
1487 if (parent != d->root)
1488 continue;
1489 int t = elem.topLeft().row();
1490 int b = elem.bottomRight().row();
1491 if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items
1492 for (int r = t; r <= b; ++r) {
1493 const QRect &rect = visualRect(d->model->index(r, c, parent));
1494 if (viewportRect.intersects(rect))
1495 selectionRegion += rect;
1496 }
1497 } else { // in static mode, we can optimize a bit
1498 while (t <= b && d->isHidden(t)) ++t;
1499 while (b >= t && d->isHidden(b)) --b;
1500 const QModelIndex top = d->model->index(t, c, parent);
1501 const QModelIndex bottom = d->model->index(b, c, parent);
1502 QRect rect(visualRect(top).topLeft(),
1503 visualRect(bottom).bottomRight());
1504 if (viewportRect.intersects(rect))
1505 selectionRegion += rect;
1506 }
1507 }
1508
1509 return selectionRegion;
1510}
1511
1512/*!
1513 \reimp
1514*/
1515QModelIndexList QListView::selectedIndexes() const
1516{
1517 Q_D(const QListView);
1518 if (!d->selectionModel)
1519 return QModelIndexList();
1520
1521 QModelIndexList viewSelected = d->selectionModel->selectedIndexes();
1522 auto ignorable = [this, d](const QModelIndex &index) {
1523 return index.column() != d->column || index.parent() != d->root || isIndexHidden(index);
1524 };
1525 viewSelected.removeIf(ignorable);
1526 return viewSelected;
1527}
1528
1529/*!
1530 \internal
1531
1532 Layout the items according to the flow and wrapping properties.
1533*/
1534void QListView::doItemsLayout()
1535{
1536 Q_D(QListView);
1537 // showing the scroll bars will trigger a resize event,
1538 // so we set the state to expanding to avoid
1539 // triggering another layout
1540 QAbstractItemView::State oldState = state();
1541 setState(ExpandingState);
1542 if (d->model->columnCount(d->root) > 0) { // no columns means no contents
1543 d->resetBatchStartRow();
1544 if (layoutMode() == SinglePass) {
1545 d->doItemsLayout(d->model->rowCount(d->root)); // layout everything
1546 } else if (!d->batchLayoutTimer.isActive()) {
1547 if (!d->doItemsLayout(d->batchSize)) // layout is done
1548 d->batchLayoutTimer.start(0, this); // do a new batch as fast as possible
1549 }
1550 } else { // clear the QBspTree generated by the last layout
1551 d->clear();
1552 }
1553 QAbstractItemView::doItemsLayout();
1554 setState(oldState); // restoring the oldState
1555}
1556
1557/*!
1558 \reimp
1559*/
1560void QListView::updateGeometries()
1561{
1562 Q_D(QListView);
1563 if (geometry().isEmpty() || d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) {
1564 horizontalScrollBar()->setRange(0, 0);
1565 verticalScrollBar()->setRange(0, 0);
1566 } else {
1567 QModelIndex index = d->model->index(0, d->column, d->root);
1568 QStyleOptionViewItem option;
1569 initViewItemOption(&option);
1570 QSize step = d->itemSize(option, index);
1571 d->commonListView->updateHorizontalScrollBar(step);
1572 d->commonListView->updateVerticalScrollBar(step);
1573 }
1574
1575 QAbstractItemView::updateGeometries();
1576
1577 // if the scroll bars are turned off, we resize the contents to the viewport
1578 if (d->movement == Static && !d->isWrapping()) {
1579 d->layoutChildren(); // we need the viewport size to be updated
1580 if (d->flow == TopToBottom) {
1581 if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
1582 d->setContentsSize(viewport()->width(), contentsSize().height());
1583 horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway
1584 }
1585 } else { // LeftToRight
1586 if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
1587 d->setContentsSize(contentsSize().width(), viewport()->height());
1588 verticalScrollBar()->setRange(0, 0); // we see all the contents anyway
1589 }
1590 }
1591 }
1592
1593}
1594
1595/*!
1596 \reimp
1597*/
1598bool QListView::isIndexHidden(const QModelIndex &index) const
1599{
1600 Q_D(const QListView);
1601 return (d->isHidden(index.row())
1602 && (index.parent() == d->root)
1603 && index.column() == d->column);
1604}
1605
1606/*!
1607 \property QListView::modelColumn
1608 \brief the column in the model that is visible
1609
1610 By default, this property contains 0, indicating that the first
1611 column in the model will be shown.
1612*/
1613void QListView::setModelColumn(int column)
1614{
1615 Q_D(QListView);
1616 if (column < 0 || column >= d->model->columnCount(d->root))
1617 return;
1618 d->column = column;
1619 d->doDelayedItemsLayout();
1620#if QT_CONFIG(accessibility)
1621 if (QAccessible::isActive()) {
1622 QAccessibleTableModelChangeEvent event(this, QAccessibleTableModelChangeEvent::ModelReset);
1623 QAccessible::updateAccessibility(&event);
1624 }
1625#endif
1626}
1627
1628int QListView::modelColumn() const
1629{
1630 Q_D(const QListView);
1631 return d->column;
1632}
1633
1634/*!
1635 \property QListView::uniformItemSizes
1636 \brief whether all items in the listview have the same size
1637
1638 This property should only be set to true if it is guaranteed that all items
1639 in the view have the same size. This enables the view to do some
1640 optimizations for performance purposes.
1641
1642 By default, this property is \c false.
1643*/
1644void QListView::setUniformItemSizes(bool enable)
1645{
1646 Q_D(QListView);
1647 d->uniformItemSizes = enable;
1648}
1649
1650bool QListView::uniformItemSizes() const
1651{
1652 Q_D(const QListView);
1653 return d->uniformItemSizes;
1654}
1655
1656/*!
1657 \property QListView::wordWrap
1658 \brief the item text word-wrapping policy
1659
1660 If this property is \c true then the item text is wrapped where
1661 necessary at word-breaks; otherwise it is not wrapped at all.
1662 This property is \c false by default.
1663
1664 Please note that even if wrapping is enabled, the cell will not be
1665 expanded to make room for the text. It will print ellipsis for
1666 text that cannot be shown, according to the view's
1667 \l{QAbstractItemView::}{textElideMode}.
1668*/
1669void QListView::setWordWrap(bool on)
1670{
1671 Q_D(QListView);
1672 if (d->wrapItemText == on)
1673 return;
1674 d->wrapItemText = on;
1675 d->doDelayedItemsLayout();
1676}
1677
1678bool QListView::wordWrap() const
1679{
1680 Q_D(const QListView);
1681 return d->wrapItemText;
1682}
1683
1684/*!
1685 \property QListView::selectionRectVisible
1686 \brief if the selection rectangle should be visible
1687
1688 If this property is \c true then the selection rectangle is visible;
1689 otherwise it will be hidden.
1690
1691 \note The selection rectangle will only be visible if the selection mode
1692 is in a mode where more than one item can be selected; i.e., it will not
1693 draw a selection rectangle if the selection mode is
1694 QAbstractItemView::SingleSelection.
1695
1696 By default, this property is \c false.
1697*/
1698void QListView::setSelectionRectVisible(bool show)
1699{
1700 Q_D(QListView);
1701 d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible);
1702 d->setSelectionRectVisible(show);
1703}
1704
1705bool QListView::isSelectionRectVisible() const
1706{
1707 Q_D(const QListView);
1708 return d->isSelectionRectVisible();
1709}
1710
1711/*!
1712 \property QListView::itemAlignment
1713 \brief the alignment of each item in its cell
1714 \since 5.12
1715
1716 This is only supported in ListMode with TopToBottom flow
1717 and with wrapping enabled.
1718 The default alignment is 0, which means that an item fills
1719 its cell entirely.
1720*/
1721void QListView::setItemAlignment(Qt::Alignment alignment)
1722{
1723 Q_D(QListView);
1724 if (d->itemAlignment == alignment)
1725 return;
1726 d->itemAlignment = alignment;
1727 if (viewMode() == ListMode && flow() == QListView::TopToBottom && isWrapping())
1728 d->doDelayedItemsLayout();
1729}
1730
1731Qt::Alignment QListView::itemAlignment() const
1732{
1733 Q_D(const QListView);
1734 return d->itemAlignment;
1735}
1736
1737/*!
1738 \reimp
1739*/
1740bool QListView::event(QEvent *e)
1741{
1742 return QAbstractItemView::event(e);
1743}
1744
1745/*
1746 * private object implementation
1747 */
1748
1749QListViewPrivate::QListViewPrivate()
1750 : QAbstractItemViewPrivate(),
1751 commonListView(nullptr),
1752 wrap(false),
1753 space(0),
1754 flow(QListView::TopToBottom),
1755 movement(QListView::Static),
1756 resizeMode(QListView::Fixed),
1757 layoutMode(QListView::SinglePass),
1758 viewMode(QListView::ListMode),
1759 modeProperties(0),
1760 column(0),
1761 uniformItemSizes(false),
1762 batchSize(100),
1763 showElasticBand(false),
1764 itemAlignment(Qt::Alignment())
1765{
1766}
1767
1768QListViewPrivate::~QListViewPrivate()
1769{
1770 delete commonListView;
1771}
1772
1773void QListViewPrivate::clear()
1774{
1775 // initialization of data structs
1776 cachedItemSize = QSize();
1777 commonListView->clear();
1778}
1779
1780void QListViewPrivate::prepareItemsLayout()
1781{
1782 Q_Q(QListView);
1783 clear();
1784
1785 //take the size as if there were scrollbar in order to prevent scrollbar to blink
1786 layoutBounds = QRect(QPoint(), q->maximumViewportSize());
1787
1788 int frameAroundContents = 0;
1789 if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, nullptr, q)) {
1790 QStyleOption option;
1791 option.initFrom(q);
1792 frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, q) * 2;
1793 }
1794
1795 // maximumViewportSize() already takes scrollbar into account if policy is
1796 // Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy
1797 // is Qt::ScrollBarAsNeeded
1798 int verticalMargin = (vbarpolicy == Qt::ScrollBarAsNeeded) && (flow == QListView::LeftToRight || vbar->isVisible())
1799 && !q->style()->pixelMetric(QStyle::PM_ScrollView_ScrollBarOverlap, nullptr, vbar)
1800 ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, vbar) + frameAroundContents
1801 : 0;
1802 int horizontalMargin = hbarpolicy==Qt::ScrollBarAsNeeded
1803 ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, hbar) + frameAroundContents
1804 : 0;
1805
1806 layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin);
1807
1808 int rowCount = model->columnCount(root) <= 0 ? 0 : model->rowCount(root);
1809 commonListView->setRowCount(rowCount);
1810}
1811
1812/*!
1813 \internal
1814*/
1815bool QListViewPrivate::doItemsLayout(int delta)
1816{
1817 int max = model->rowCount(root) - 1;
1818 int first = batchStartRow();
1819 int last = qMin(first + delta - 1, max);
1820
1821 if (first == 0) {
1822 layoutChildren(); // make sure the viewport has the right size
1823 prepareItemsLayout();
1824 }
1825
1826 if (max < 0 || last < first) {
1827 return true; // nothing to do
1828 }
1829
1830 QListViewLayoutInfo info;
1831 info.bounds = layoutBounds;
1832 info.grid = gridSize();
1833 info.spacing = (info.grid.isValid() ? 0 : spacing());
1834 info.first = first;
1835 info.last = last;
1836 info.wrap = isWrapping();
1837 info.flow = flow;
1838 info.max = max;
1839
1840 return commonListView->doBatchedItemLayout(info, max);
1841}
1842
1843QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const
1844{
1845 if (!index.isValid() || isHidden(index.row()))
1846 return QListViewItem();
1847
1848 return commonListView->indexToListViewItem(index);
1849}
1850
1851QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const
1852{
1853 Q_Q(const QListView);
1854 if (!rect.isValid())
1855 return rect;
1856
1857 QRect result = extend ? commonListView->mapToViewport(rect) : rect;
1858 int dx = -q->horizontalOffset();
1859 int dy = -q->verticalOffset();
1860 return result.adjusted(dx, dy, dx, dy);
1861}
1862
1863QModelIndex QListViewPrivate::closestIndex(const QRect &target,
1864 const QList<QModelIndex> &candidates) const
1865{
1866 int distance = 0;
1867 int shortest = INT_MAX;
1868 QModelIndex closest;
1869 QList<QModelIndex>::const_iterator it = candidates.begin();
1870
1871 for (; it != candidates.end(); ++it) {
1872 if (!(*it).isValid())
1873 continue;
1874
1875 const QRect indexRect = indexToListViewItem(*it).rect();
1876
1877 //if the center x (or y) position of an item is included in the rect of the other item,
1878 //we define the distance between them as the difference in x (or y) of their respective center.
1879 // Otherwise, we use the nahattan length between the 2 items
1880 if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right())
1881 || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) {
1882 //one item's center is at the vertical of the other
1883 distance = qAbs(indexRect.center().y() - target.center().y());
1884 } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom())
1885 || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) {
1886 //one item's center is at the vertical of the other
1887 distance = qAbs(indexRect.center().x() - target.center().x());
1888 } else {
1889 distance = (indexRect.center() - target.center()).manhattanLength();
1890 }
1891 if (distance < shortest) {
1892 shortest = distance;
1893 closest = *it;
1894 }
1895 }
1896 return closest;
1897}
1898
1899QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
1900{
1901 Q_Q(const QListView);
1902 if (!uniformItemSizes) {
1903 const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index);
1904 return delegate ? delegate->sizeHint(option, index) : QSize();
1905 }
1906 if (!cachedItemSize.isValid()) { // the last item is probably the largest, so we use its size
1907 int row = model->rowCount(root) - 1;
1908 QModelIndex sample = model->index(row, column, root);
1909 const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(sample);
1910 cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize();
1911 }
1912 return cachedItemSize;
1913}
1914
1915QItemSelection QListViewPrivate::selection(const QRect &rect) const
1916{
1917 QItemSelection selection;
1918 QModelIndex tl, br;
1919 const QList<QModelIndex> intersectVector = intersectingSet(rect);
1920 QList<QModelIndex>::const_iterator it = intersectVector.begin();
1921 for (; it != intersectVector.end(); ++it) {
1922 if (!tl.isValid() && !br.isValid()) {
1923 tl = br = *it;
1924 } else if ((*it).row() == (tl.row() - 1)) {
1925 tl = *it; // expand current range
1926 } else if ((*it).row() == (br.row() + 1)) {
1927 br = (*it); // expand current range
1928 } else {
1929 selection.select(tl, br); // select current range
1930 tl = br = *it; // start new range
1931 }
1932 }
1933
1934 if (tl.isValid() && br.isValid())
1935 selection.select(tl, br);
1936 else if (tl.isValid())
1937 selection.select(tl, tl);
1938 else if (br.isValid())
1939 selection.select(br, br);
1940
1941 return selection;
1942}
1943
1944#if QT_CONFIG(draganddrop)
1945QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const
1946{
1947 if (viewMode == QListView::ListMode && flow == QListView::LeftToRight)
1948 return static_cast<QListModeViewBase *>(commonListView)->position(pos, rect, idx);
1949 else
1950 return QAbstractItemViewPrivate::position(pos, rect, idx);
1951}
1952
1953bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
1954{
1955 if (viewMode == QListView::ListMode && flow == QListView::LeftToRight)
1956 return static_cast<QListModeViewBase *>(commonListView)->dropOn(event, dropRow, dropCol, dropIndex);
1957 else
1958 return QAbstractItemViewPrivate::dropOn(event, dropRow, dropCol, dropIndex);
1959}
1960#endif
1961
1962#if QT_CONFIG(accessibility)
1963int QListViewPrivate::accessibleChildIndex(const QModelIndex &index) const
1964{
1965 Q_Q(const QListView);
1966 return q->visualIndex(index);
1967}
1968#endif
1969
1970void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes,
1971 const QModelIndex &current) const
1972{
1973 auto isCurrentOrDisabled = [this, current](const QModelIndex &index) {
1974 return !isIndexEnabled(index) || index == current;
1975 };
1976 indexes->removeIf(isCurrentOrDisabled);
1977}
1978
1979/*
1980 * Common ListView Implementation
1981*/
1982
1983void QCommonListViewBase::appendHiddenRow(int row)
1984{
1985 dd->hiddenRows.insert(dd->model->index(row, 0, qq->rootIndex()));
1986}
1987
1988void QCommonListViewBase::removeHiddenRow(int row)
1989{
1990 dd->hiddenRows.remove(dd->model->index(row, 0, qq->rootIndex()));
1991}
1992
1993#if QT_CONFIG(draganddrop)
1994void QCommonListViewBase::paintDragDrop(QPainter *painter)
1995{
1996 // FIXME: Until the we can provide a proper drop indicator
1997 // in IconMode, it makes no sense to show it
1998 dd->paintDropIndicator(painter);
1999}
2000#endif
2001
2002QSize QListModeViewBase::viewportSize(const QAbstractItemView *v)
2003{
2004 return v->contentsRect().marginsRemoved(v->viewportMargins()).size();
2005}
2006
2007void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step)
2008{
2009 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(step.width() + spacing());
2010 horizontalScrollBar()->setPageStep(viewport()->width());
2011
2012 // If both scroll bars are set to auto, we might end up in a situation with enough space
2013 // for the actual content. But still one of the scroll bars will become enabled due to
2014 // the other one using the space. The other one will become invisible in the same cycle.
2015 // -> Infinite loop, QTBUG-39902
2016 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2017 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2018
2019 const QSize viewportSize = QListModeViewBase::viewportSize(qq);
2020
2021 bool verticalWantsToShow = contentsSize.height() > viewportSize.height();
2022 bool horizontalWantsToShow;
2023 if (verticalWantsToShow)
2024 horizontalWantsToShow = contentsSize.width() > viewportSize.width() - qq->verticalScrollBar()->width();
2025 else
2026 horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2027
2028 if (bothScrollBarsAuto && !horizontalWantsToShow) {
2029 // break the infinite loop described above by setting the range to 0, 0.
2030 // QAbstractScrollArea will then hide the scroll bar for us
2031 horizontalScrollBar()->setRange(0, 0);
2032 } else {
2033 horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width());
2034 }
2035}
2036
2037void QCommonListViewBase::updateVerticalScrollBar(const QSize &step)
2038{
2039 verticalScrollBar()->d_func()->itemviewChangeSingleStep(step.height() + spacing());
2040 verticalScrollBar()->setPageStep(viewport()->height());
2041
2042 // If both scroll bars are set to auto, we might end up in a situation with enough space
2043 // for the actual content. But still one of the scroll bars will become enabled due to
2044 // the other one using the space. The other one will become invisible in the same cycle.
2045 // -> Infinite loop, QTBUG-39902
2046 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2047 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2048
2049 const QSize viewportSize = QListModeViewBase::viewportSize(qq);
2050
2051 bool horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2052 bool verticalWantsToShow;
2053 if (horizontalWantsToShow)
2054 verticalWantsToShow = contentsSize.height() > viewportSize.height() - qq->horizontalScrollBar()->height();
2055 else
2056 verticalWantsToShow = contentsSize.height() > viewportSize.height();
2057
2058 if (bothScrollBarsAuto && !verticalWantsToShow) {
2059 // break the infinite loop described above by setting the range to 0, 0.
2060 // QAbstractScrollArea will then hide the scroll bar for us
2061 verticalScrollBar()->setRange(0, 0);
2062 } else {
2063 verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height());
2064 }
2065}
2066
2067void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/)
2068{
2069 dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy);
2070}
2071
2072int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint,
2073 bool above, bool below, const QRect &area, const QRect &rect) const
2074{
2075 int verticalValue = verticalScrollBar()->value();
2076 QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing());
2077 if (hint == QListView::PositionAtTop || above)
2078 verticalValue += adjusted.top();
2079 else if (hint == QListView::PositionAtBottom || below)
2080 verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1);
2081 else if (hint == QListView::PositionAtCenter)
2082 verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2);
2083 return verticalValue;
2084}
2085
2086int QCommonListViewBase::horizontalOffset() const
2087{
2088 return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value());
2089}
2090
2091int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint,
2092 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2093{
2094 int horizontalValue = horizontalScrollBar()->value();
2095 if (isRightToLeft()) {
2096 if (hint == QListView::PositionAtCenter) {
2097 horizontalValue += ((area.width() - rect.width()) / 2) - rect.left();
2098 } else {
2099 if (leftOf)
2100 horizontalValue -= rect.left();
2101 else if (rightOf)
2102 horizontalValue += qMin(rect.left(), area.width() - rect.right());
2103 }
2104 } else {
2105 if (hint == QListView::PositionAtCenter) {
2106 horizontalValue += rect.left() - ((area.width()- rect.width()) / 2);
2107 } else {
2108 if (leftOf)
2109 horizontalValue += rect.left();
2110 else if (rightOf)
2111 horizontalValue += qMin(rect.left(), rect.right() - area.width());
2112 }
2113 }
2114 return horizontalValue;
2115}
2116
2117/*
2118 * ListMode ListView Implementation
2119*/
2120QListModeViewBase::QListModeViewBase(QListView *q, QListViewPrivate *d)
2121 : QCommonListViewBase(q, d)
2122{
2123#if QT_CONFIG(draganddrop)
2124 dd->defaultDropAction = Qt::CopyAction;
2125#endif
2126}
2127
2128#if QT_CONFIG(draganddrop)
2129QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const
2130{
2131 QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport;
2132 if (!dd->overwrite) {
2133 const int margin = 2;
2134 if (pos.x() - rect.left() < margin) {
2135 r = QAbstractItemView::AboveItem; // Visually, on the left
2136 } else if (rect.right() - pos.x() < margin) {
2137 r = QAbstractItemView::BelowItem; // Visually, on the right
2138 } else if (rect.contains(pos, true)) {
2139 r = QAbstractItemView::OnItem;
2140 }
2141 } else {
2142 QRect touchingRect = rect;
2143 touchingRect.adjust(-1, -1, 1, 1);
2144 if (touchingRect.contains(pos, false)) {
2145 r = QAbstractItemView::OnItem;
2146 }
2147 }
2148
2149 if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled)))
2150 r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem;
2151
2152 return r;
2153}
2154
2155void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event)
2156{
2157 if (qq->dragDropMode() == QAbstractItemView::InternalMove
2158 && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction)))
2159 return;
2160
2161 // ignore by default
2162 event->ignore();
2163
2164 // can't use indexAt, doesn't account for spacing.
2165 QPoint p = event->position().toPoint();
2166 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2167 rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
2168 const QList<QModelIndex> intersectVector = dd->intersectingSet(rect);
2169 QModelIndex index = intersectVector.size() > 0
2170 ? intersectVector.last() : QModelIndex();
2171 dd->hover = index;
2172 if (!dd->droppingOnItself(event, index)
2173 && dd->canDrop(event)) {
2174
2175 if (index.isValid() && dd->showDropIndicator) {
2176 QRect rect = qq->visualRect(index);
2177 dd->dropIndicatorPosition = position(event->position().toPoint(), rect, index);
2178 // if spacing, should try to draw between items, not just next to item.
2179 switch (dd->dropIndicatorPosition) {
2180 case QAbstractItemView::AboveItem:
2181 if (dd->isIndexDropEnabled(index.parent())) {
2182 dd->dropIndicatorRect = QRect(rect.left()-dd->spacing(), rect.top(), 0, rect.height());
2183 event->accept();
2184 } else {
2185 dd->dropIndicatorRect = QRect();
2186 }
2187 break;
2188 case QAbstractItemView::BelowItem:
2189 if (dd->isIndexDropEnabled(index.parent())) {
2190 dd->dropIndicatorRect = QRect(rect.right()+dd->spacing(), rect.top(), 0, rect.height());
2191 event->accept();
2192 } else {
2193 dd->dropIndicatorRect = QRect();
2194 }
2195 break;
2196 case QAbstractItemView::OnItem:
2197 if (dd->isIndexDropEnabled(index)) {
2198 dd->dropIndicatorRect = rect;
2199 event->accept();
2200 } else {
2201 dd->dropIndicatorRect = QRect();
2202 }
2203 break;
2204 case QAbstractItemView::OnViewport:
2205 dd->dropIndicatorRect = QRect();
2206 if (dd->isIndexDropEnabled(qq->rootIndex())) {
2207 event->accept(); // allow dropping in empty areas
2208 }
2209 break;
2210 }
2211 } else {
2212 dd->dropIndicatorRect = QRect();
2213 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2214 if (dd->isIndexDropEnabled(qq->rootIndex())) {
2215 event->accept(); // allow dropping in empty areas
2216 }
2217 }
2218 dd->viewport->update();
2219 } // can drop
2220
2221 if (dd->shouldAutoScroll(event->position().toPoint()))
2222 qq->startAutoScroll();
2223}
2224
2225/*!
2226 If the event hasn't already been accepted, determines the index to drop on.
2227
2228 if (row == -1 && col == -1)
2229 // append to this drop index
2230 else
2231 // place at row, col in drop index
2232
2233 If it returns \c true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop.
2234 \internal
2235 */
2236bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
2237{
2238 if (event->isAccepted())
2239 return false;
2240
2241 QModelIndex index;
2242 if (dd->viewport->rect().contains(event->position().toPoint())) {
2243 // can't use indexAt, doesn't account for spacing.
2244 QPoint p = event->position().toPoint();
2245 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2246 rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
2247 const QList<QModelIndex> intersectVector = dd->intersectingSet(rect);
2248 index = intersectVector.size() > 0
2249 ? intersectVector.last() : QModelIndex();
2250 if (!index.isValid())
2251 index = dd->root;
2252 }
2253
2254 // If we are allowed to do the drop
2255 if (dd->model->supportedDropActions() & event->dropAction()) {
2256 int row = -1;
2257 int col = -1;
2258 if (index != dd->root) {
2259 dd->dropIndicatorPosition = position(event->position().toPoint(), qq->visualRect(index), index);
2260 switch (dd->dropIndicatorPosition) {
2261 case QAbstractItemView::AboveItem:
2262 row = index.row();
2263 col = index.column();
2264 index = index.parent();
2265 break;
2266 case QAbstractItemView::BelowItem:
2267 row = index.row() + 1;
2268 col = index.column();
2269 index = index.parent();
2270 break;
2271 case QAbstractItemView::OnItem:
2272 case QAbstractItemView::OnViewport:
2273 break;
2274 }
2275 } else {
2276 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2277 }
2278 *dropIndex = index;
2279 *dropRow = row;
2280 *dropCol = col;
2281 if (!dd->droppingOnItself(event, index))
2282 return true;
2283 }
2284 return false;
2285}
2286
2287#endif //QT_CONFIG(draganddrop)
2288
2289void QListModeViewBase::updateVerticalScrollBar(const QSize &step)
2290{
2291 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem
2292 && ((flow() == QListView::TopToBottom && !isWrapping())
2293 || (flow() == QListView::LeftToRight && isWrapping()))) {
2294 const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).size() - 1;
2295 if (steps > 0) {
2296 const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping());
2297 verticalScrollBar()->setSingleStep(1);
2298 verticalScrollBar()->setPageStep(pageSteps);
2299 verticalScrollBar()->setRange(0, steps - pageSteps);
2300 } else {
2301 verticalScrollBar()->setRange(0, 0);
2302 }
2303 // } else if (vertical && d->isWrapping() && d->movement == Static) {
2304 // ### wrapped scrolling in flow direction
2305 } else {
2306 QCommonListViewBase::updateVerticalScrollBar(step);
2307 }
2308}
2309
2310void QListModeViewBase::updateHorizontalScrollBar(const QSize &step)
2311{
2312 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem
2313 && ((flow() == QListView::TopToBottom && isWrapping())
2314 || (flow() == QListView::LeftToRight && !isWrapping()))) {
2315 int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).size() - 1;
2316 if (steps > 0) {
2317 const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping());
2318 horizontalScrollBar()->setSingleStep(1);
2319 horizontalScrollBar()->setPageStep(pageSteps);
2320 horizontalScrollBar()->setRange(0, steps - pageSteps);
2321 } else {
2322 horizontalScrollBar()->setRange(0, 0);
2323 }
2324 } else {
2325 QCommonListViewBase::updateHorizontalScrollBar(step);
2326 }
2327}
2328
2329int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint,
2330 bool above, bool below, const QRect &area, const QRect &rect) const
2331{
2332 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2333 int value;
2334 if (scrollValueMap.isEmpty()) {
2335 value = 0;
2336 } else {
2337 int scrollBarValue = verticalScrollBar()->value();
2338 int numHidden = 0;
2339 for (const auto &idx : std::as_const(dd->hiddenRows))
2340 if (idx.row() <= scrollBarValue)
2341 ++numHidden;
2342 value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()) - numHidden, flowPositions.size() - 1);
2343 }
2344 if (above)
2345 hint = QListView::PositionAtTop;
2346 else if (below)
2347 hint = QListView::PositionAtBottom;
2348 if (hint == QListView::EnsureVisible)
2349 return value;
2350
2351 return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height());
2352 }
2353
2354 return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect);
2355}
2356
2357int QListModeViewBase::horizontalOffset() const
2358{
2359 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2360 if (isWrapping()) {
2361 if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) {
2362 const int max = segmentPositions.size() - 1;
2363 int currentValue = qBound(0, horizontalScrollBar()->value(), max);
2364 int position = segmentPositions.at(currentValue);
2365 int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max);
2366 int maximum = segmentPositions.at(maximumValue);
2367 return (isRightToLeft() ? maximum - position : position);
2368 }
2369 } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) {
2370 int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value()));
2371 int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum()));
2372 return (isRightToLeft() ? maximum - position : position);
2373 }
2374 }
2375 return QCommonListViewBase::horizontalOffset();
2376}
2377
2378int QListModeViewBase::verticalOffset() const
2379{
2380 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2381 if (isWrapping()) {
2382 if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) {
2383 int value = verticalScrollBar()->value();
2384 if (value >= segmentPositions.size())
2385 return 0;
2386 return segmentPositions.at(value) - spacing();
2387 }
2388 } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) {
2389 int value = verticalScrollBar()->value();
2390 if (value > scrollValueMap.size())
2391 return 0;
2392 return flowPositions.at(scrollValueMap.at(value)) - spacing();
2393 }
2394 }
2395 return QCommonListViewBase::verticalOffset();
2396}
2397
2398int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint,
2399 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2400{
2401 if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem)
2402 return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect);
2403
2404 int value;
2405 if (scrollValueMap.isEmpty())
2406 value = 0;
2407 else
2408 value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.size() - 1);
2409 if (leftOf)
2410 hint = QListView::PositionAtTop;
2411 else if (rightOf)
2412 hint = QListView::PositionAtBottom;
2413 if (hint == QListView::EnsureVisible)
2414 return value;
2415
2416 return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width());
2417}
2418
2419void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
2420{
2421 // ### reorder this logic
2422 const int verticalValue = verticalScrollBar()->value();
2423 const int horizontalValue = horizontalScrollBar()->value();
2424 const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem);
2425 const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem);
2426
2427 if (isWrapping()) {
2428 if (segmentPositions.isEmpty())
2429 return;
2430 const int max = segmentPositions.size() - 1;
2431 if (horizontal && flow() == QListView::TopToBottom && dx != 0) {
2432 int currentValue = qBound(0, horizontalValue, max);
2433 int previousValue = qBound(0, currentValue + dx, max);
2434 int currentCoordinate = segmentPositions.at(currentValue) - spacing();
2435 int previousCoordinate = segmentPositions.at(previousValue) - spacing();
2436 dx = previousCoordinate - currentCoordinate;
2437 } else if (vertical && flow() == QListView::LeftToRight && dy != 0) {
2438 int currentValue = qBound(0, verticalValue, max);
2439 int previousValue = qBound(0, currentValue + dy, max);
2440 int currentCoordinate = segmentPositions.at(currentValue) - spacing();
2441 int previousCoordinate = segmentPositions.at(previousValue) - spacing();
2442 dy = previousCoordinate - currentCoordinate;
2443 }
2444 } else {
2445 if (flowPositions.isEmpty())
2446 return;
2447 const int max = scrollValueMap.size() - 1;
2448 if (vertical && flow() == QListView::TopToBottom && dy != 0) {
2449 int currentValue = qBound(0, verticalValue, max);
2450 int previousValue = qBound(0, currentValue + dy, max);
2451 int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
2452 int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
2453 dy = previousCoordinate - currentCoordinate;
2454 } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) {
2455 int currentValue = qBound(0, horizontalValue, max);
2456 int previousValue = qBound(0, currentValue + dx, max);
2457 int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
2458 int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
2459 dx = previousCoordinate - currentCoordinate;
2460 }
2461 }
2462 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
2463}
2464
2465bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
2466{
2467 doStaticLayout(info);
2468 return batchStartRow > max; // returning true stops items layout
2469}
2470
2471QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const
2472{
2473 if (flowPositions.isEmpty()
2474 || segmentPositions.isEmpty()
2475 || index.row() >= flowPositions.size() - 1)
2476 return QListViewItem();
2477
2478 const int segment = qBinarySearch<int>(segmentStartRows, index.row(),
2479 0, segmentStartRows.size() - 1);
2480
2481
2482 QStyleOptionViewItem options;
2483 initViewItemOption(&options);
2484 options.rect.setSize(contentsSize);
2485 QSize size = (uniformItemSizes() && cachedItemSize().isValid())
2486 ? cachedItemSize() : itemSize(options, index);
2487 QSize cellSize = size;
2488
2489 QPoint pos;
2490 if (flow() == QListView::LeftToRight) {
2491 pos.setX(flowPositions.at(index.row()));
2492 pos.setY(segmentPositions.at(segment));
2493 } else { // TopToBottom
2494 pos.setY(flowPositions.at(index.row()));
2495 pos.setX(segmentPositions.at(segment));
2496 if (isWrapping()) { // make the items as wide as the segment
2497 int right = (segment + 1 >= segmentPositions.size()
2498 ? contentsSize.width()
2499 : segmentPositions.at(segment + 1));
2500 cellSize.setWidth(right - pos.x());
2501 } else { // make the items as wide as the viewport
2502 cellSize.setWidth(qMax(size.width(), viewport()->width() - 2 * spacing()));
2503 }
2504 }
2505
2506 if (dd->itemAlignment & Qt::AlignHorizontal_Mask) {
2507 size.setWidth(qMin(size.width(), cellSize.width()));
2508 if (dd->itemAlignment & Qt::AlignRight)
2509 pos.setX(pos.x() + cellSize.width() - size.width());
2510 if (dd->itemAlignment & Qt::AlignHCenter)
2511 pos.setX(pos.x() + (cellSize.width() - size.width()) / 2);
2512 } else {
2513 size.setWidth(cellSize.width());
2514 }
2515
2516 return QListViewItem(QRect(pos, size), index.row());
2517}
2518
2519QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info)
2520{
2521 int x, y;
2522 if (info.first == 0) {
2523 flowPositions.clear();
2524 segmentPositions.clear();
2525 segmentStartRows.clear();
2526 segmentExtents.clear();
2527 scrollValueMap.clear();
2528 x = info.bounds.left() + info.spacing;
2529 y = info.bounds.top() + info.spacing;
2530 segmentPositions.append(info.flow == QListView::LeftToRight ? y : x);
2531 segmentStartRows.append(0);
2532 } else if (info.wrap) {
2533 if (info.flow == QListView::LeftToRight) {
2534 x = batchSavedPosition;
2535 y = segmentPositions.constLast();
2536 } else { // flow == QListView::TopToBottom
2537 x = segmentPositions.constLast();
2538 y = batchSavedPosition;
2539 }
2540 } else { // not first and not wrap
2541 if (info.flow == QListView::LeftToRight) {
2542 x = batchSavedPosition;
2543 y = info.bounds.top() + info.spacing;
2544 } else { // flow == QListView::TopToBottom
2545 x = info.bounds.left() + info.spacing;
2546 y = batchSavedPosition;
2547 }
2548 }
2549 return QPoint(x, y);
2550}
2551
2552/*!
2553 \internal
2554*/
2555void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info)
2556{
2557 const bool useItemSize = !info.grid.isValid();
2558 const QPoint topLeft = initStaticLayout(info);
2559 QStyleOptionViewItem option;
2560 initViewItemOption(&option);
2561 option.rect = info.bounds;
2562 option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing);
2563
2564 // The static layout data structures are as follows:
2565 // One vector contains the coordinate in the direction of layout flow.
2566 // Another vector contains the coordinates of the segments.
2567 // A third vector contains the index (model row) of the first item
2568 // of each segment.
2569
2570 int segStartPosition;
2571 int segEndPosition;
2572 int deltaFlowPosition;
2573 int deltaSegPosition;
2574 int deltaSegHint;
2575 int flowPosition;
2576 int segPosition;
2577
2578 if (info.flow == QListView::LeftToRight) {
2579 segStartPosition = info.bounds.left();
2580 segEndPosition = info.bounds.width();
2581 flowPosition = topLeft.x();
2582 segPosition = topLeft.y();
2583 deltaFlowPosition = info.grid.width(); // dx
2584 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy
2585 deltaSegHint = info.grid.height();
2586 } else { // flow == QListView::TopToBottom
2587 segStartPosition = info.bounds.top();
2588 segEndPosition = info.bounds.height();
2589 flowPosition = topLeft.y();
2590 segPosition = topLeft.x();
2591 deltaFlowPosition = info.grid.height(); // dy
2592 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx
2593 deltaSegHint = info.grid.width();
2594 }
2595
2596 for (int row = info.first; row <= info.last; ++row) {
2597 if (isHidden(row)) { // ###
2598 flowPositions.append(flowPosition);
2599 } else {
2600 // if we are not using a grid, we need to find the deltas
2601 if (useItemSize) {
2602 QSize hint = itemSize(option, modelIndex(row));
2603 if (info.flow == QListView::LeftToRight) {
2604 deltaFlowPosition = hint.width() + info.spacing;
2605 deltaSegHint = hint.height() + info.spacing;
2606 } else { // TopToBottom
2607 deltaFlowPosition = hint.height() + info.spacing;
2608 deltaSegHint = hint.width() + info.spacing;
2609 }
2610 }
2611 // create new segment
2612 if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) {
2613 segmentExtents.append(flowPosition);
2614 flowPosition = info.spacing + segStartPosition;
2615 segPosition += info.spacing + deltaSegPosition;
2616 segmentPositions.append(segPosition);
2617 segmentStartRows.append(row);
2618 deltaSegPosition = 0;
2619 }
2620 // save the flow position of this item
2621 scrollValueMap.append(flowPositions.size());
2622 flowPositions.append(flowPosition);
2623 // prepare for the next item
2624 deltaSegPosition = qMax(deltaSegHint, deltaSegPosition);
2625 flowPosition += info.spacing + deltaFlowPosition;
2626 }
2627 }
2628 // used when laying out next batch
2629 batchSavedPosition = flowPosition;
2630 batchSavedDeltaSeg = deltaSegPosition;
2631 batchStartRow = info.last + 1;
2632 if (info.last == info.max)
2633 flowPosition -= info.spacing; // remove extra spacing
2634 // set the contents size
2635 QRect rect = info.bounds;
2636 if (info.flow == QListView::LeftToRight) {
2637 rect.setRight(segmentPositions.size() == 1 ? flowPosition : info.bounds.right());
2638 rect.setBottom(segPosition + deltaSegPosition);
2639 } else { // TopToBottom
2640 rect.setRight(segPosition + deltaSegPosition);
2641 rect.setBottom(segmentPositions.size() == 1 ? flowPosition : info.bounds.bottom());
2642 }
2643 contentsSize = QSize(rect.right(), rect.bottom());
2644 // if it is the last batch, save the end of the segments
2645 if (info.last == info.max) {
2646 segmentExtents.append(flowPosition);
2647 scrollValueMap.append(flowPositions.size());
2648 flowPositions.append(flowPosition);
2649 segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX);
2650 }
2651 // if the new items are visible, update the viewport
2652 QRect changedRect(topLeft, rect.bottomRight());
2653 if (clipRect().intersects(changedRect))
2654 viewport()->update();
2655}
2656
2657/*!
2658 \internal
2659 Finds the set of items intersecting with \a area.
2660 In this function, itemsize is counted from topleft to the start of the next item.
2661*/
2662QList<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const
2663{
2664 QList<QModelIndex> ret;
2665 int segStartPosition;
2666 int segEndPosition;
2667 int flowStartPosition;
2668 int flowEndPosition;
2669 if (flow() == QListView::LeftToRight) {
2670 segStartPosition = area.top();
2671 segEndPosition = area.bottom();
2672 flowStartPosition = area.left();
2673 flowEndPosition = area.right();
2674 } else {
2675 segStartPosition = area.left();
2676 segEndPosition = area.right();
2677 flowStartPosition = area.top();
2678 flowEndPosition = area.bottom();
2679 }
2680 if (segmentPositions.size() < 2 || flowPositions.isEmpty())
2681 return ret;
2682 // the last segment position is actually the edge of the last segment
2683 const int segLast = segmentPositions.size() - 2;
2684 int seg = qBinarySearch<int>(segmentPositions, segStartPosition, 0, segLast + 1);
2685 for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) {
2686 int first = segmentStartRows.at(seg);
2687 int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1;
2688 if (segmentExtents.at(seg) < flowStartPosition)
2689 continue;
2690 int row = qBinarySearch<int>(flowPositions, flowStartPosition, first, last);
2691 for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) {
2692 if (isHidden(row))
2693 continue;
2694 QModelIndex index = modelIndex(row);
2695 if (index.isValid()) {
2696 if (flow() == QListView::LeftToRight || dd->itemAlignment == Qt::Alignment()) {
2697 ret += index;
2698 } else {
2699 const auto viewItem = indexToListViewItem(index);
2700 const int iw = viewItem.width();
2701 const int startPos = qMax(segStartPosition, segmentPositions.at(seg));
2702 const int endPos = qMin(segmentPositions.at(seg + 1), segEndPosition);
2703 if (endPos >= viewItem.x && startPos < viewItem.x + iw)
2704 ret += index;
2705 }
2706 }
2707#if 0 // for debugging
2708 else
2709 qWarning("intersectingSet: row %d was invalid", row);
2710#endif
2711 }
2712 }
2713 return ret;
2714}
2715
2716void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &)
2717{
2718 dd->doDelayedItemsLayout();
2719}
2720
2721
2722QRect QListModeViewBase::mapToViewport(const QRect &rect) const
2723{
2724 if (isWrapping())
2725 return rect;
2726 // If the listview is in "listbox-mode", the items are as wide as the view.
2727 // But we don't shrink the items.
2728 QRect result = rect;
2729 if (flow() == QListView::TopToBottom) {
2730 result.setLeft(spacing());
2731 result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing()));
2732 } else { // LeftToRight
2733 result.setTop(spacing());
2734 result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing()));
2735 }
2736 return result;
2737}
2738
2739int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const
2740{
2741 QList<int> positions;
2742 if (wrap)
2743 positions = segmentPositions;
2744 else if (!flowPositions.isEmpty()) {
2745 positions.reserve(scrollValueMap.size());
2746 for (int itemShown : scrollValueMap)
2747 positions.append(flowPositions.at(itemShown));
2748 }
2749 if (positions.isEmpty() || bounds <= length)
2750 return positions.size();
2751 if (uniformItemSizes()) {
2752 for (int i = 1; i < positions.size(); ++i)
2753 if (positions.at(i) > 0)
2754 return length / positions.at(i);
2755 return 0; // all items had height 0
2756 }
2757 int pageSteps = 0;
2758 int steps = positions.size() - 1;
2759 int max = qMax(length, bounds);
2760 int min = qMin(length, bounds);
2761 int pos = min - (max - positions.constLast());
2762
2763 while (pos >= 0 && steps > 0) {
2764 pos -= (positions.at(steps) - positions.at(steps - 1));
2765 if (pos >= 0) //this item should be visible
2766 ++pageSteps;
2767 --steps;
2768 }
2769
2770 // at this point we know that positions has at least one entry
2771 return qMax(pageSteps, 1);
2772}
2773
2774int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize,
2775 QAbstractItemView::ScrollHint hint,
2776 Qt::Orientation orientation, bool wrap, int itemExtent) const
2777{
2778 if (index < 0)
2779 return scrollValue;
2780
2781 itemExtent += spacing();
2782 QList<int> hiddenRows = dd->hiddenRowIds();
2783 std::sort(hiddenRows.begin(), hiddenRows.end());
2784 int hiddenRowsBefore = 0;
2785 for (int i = 0; i < hiddenRows.size() - 1; ++i)
2786 if (hiddenRows.at(i) > index + hiddenRowsBefore)
2787 break;
2788 else
2789 ++hiddenRowsBefore;
2790 if (!wrap) {
2791 int topIndex = index;
2792 const int bottomIndex = topIndex;
2793 const int bottomCoordinate = flowPositions.at(index + hiddenRowsBefore);
2794 while (topIndex > 0 &&
2795 (bottomCoordinate - flowPositions.at(topIndex + hiddenRowsBefore - 1) + itemExtent) <= (viewportSize)) {
2796 topIndex--;
2797 // will the next one be a hidden row -> skip
2798 while (hiddenRowsBefore > 0 && hiddenRows.at(hiddenRowsBefore - 1) >= topIndex + hiddenRowsBefore - 1)
2799 hiddenRowsBefore--;
2800 }
2801
2802 const int itemCount = bottomIndex - topIndex + 1;
2803 switch (hint) {
2804 case QAbstractItemView::PositionAtTop:
2805 return index;
2806 case QAbstractItemView::PositionAtBottom:
2807 return index - itemCount + 1;
2808 case QAbstractItemView::PositionAtCenter:
2809 return index - (itemCount / 2);
2810 default:
2811 break;
2812 }
2813 } else { // wrapping
2814 Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight
2815 ? Qt::Horizontal : Qt::Vertical);
2816 if (flowOrientation == orientation) { // scrolling in the "flow" direction
2817 // ### wrapped scrolling in the flow direction
2818 return flowPositions.at(index + hiddenRowsBefore); // ### always pixel based for now
2819 } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction
2820 int segment = qBinarySearch<int>(segmentStartRows, index, 0, segmentStartRows.size() - 1);
2821 int leftSegment = segment;
2822 const int rightSegment = leftSegment;
2823 const int bottomCoordinate = segmentPositions.at(segment);
2824
2825 while (leftSegment > scrollValue &&
2826 (bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) {
2827 leftSegment--;
2828 }
2829
2830 const int segmentCount = rightSegment - leftSegment + 1;
2831 switch (hint) {
2832 case QAbstractItemView::PositionAtTop:
2833 return segment;
2834 case QAbstractItemView::PositionAtBottom:
2835 return segment - segmentCount + 1;
2836 case QAbstractItemView::PositionAtCenter:
2837 return segment - (segmentCount / 2);
2838 default:
2839 break;
2840 }
2841 }
2842 }
2843 return scrollValue;
2844}
2845
2846void QListModeViewBase::clear()
2847{
2848 flowPositions.clear();
2849 segmentPositions.clear();
2850 segmentStartRows.clear();
2851 segmentExtents.clear();
2852 batchSavedPosition = 0;
2853 batchStartRow = 0;
2854 batchSavedDeltaSeg = 0;
2855}
2856
2857/*
2858 * IconMode ListView Implementation
2859*/
2860
2861void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index)
2862{
2863 if (index.row() >= items.size())
2864 return;
2865 const QSize oldContents = contentsSize;
2866 qq->update(index); // update old position
2867 moveItem(index.row(), position);
2868 qq->update(index); // update new position
2869
2870 if (contentsSize != oldContents)
2871 dd->viewUpdateGeometries(); // update the scroll bars
2872}
2873
2874void QIconModeViewBase::appendHiddenRow(int row)
2875{
2876 if (row >= 0 && row < items.size()) //remove item
2877 tree.removeLeaf(items.at(row).rect(), row);
2878 QCommonListViewBase::appendHiddenRow(row);
2879}
2880
2881void QIconModeViewBase::removeHiddenRow(int row)
2882{
2883 QCommonListViewBase::removeHiddenRow(row);
2884 if (row >= 0 && row < items.size()) //insert item
2885 tree.insertLeaf(items.at(row).rect(), row);
2886}
2887
2888#if QT_CONFIG(draganddrop)
2889bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions)
2890{
2891 // This function does the same thing as in QAbstractItemView::startDrag(),
2892 // plus adding viewitems to the draggedItems list.
2893 // We need these items to draw the drag items
2894 QModelIndexList indexes = dd->selectionModel->selectedIndexes();
2895 if (indexes.size() > 0 ) {
2896 if (viewport()->acceptDrops()) {
2897 QModelIndexList::ConstIterator it = indexes.constBegin();
2898 for (; it != indexes.constEnd(); ++it)
2899 if (dd->model->flags(*it) & Qt::ItemIsDragEnabled
2900 && (*it).column() == dd->column)
2901 draggedItems.push_back(*it);
2902 }
2903
2904 QRect rect;
2905 QPixmap pixmap = dd->renderToPixmap(indexes, &rect);
2906 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
2907 QDrag *drag = new QDrag(qq);
2908 drag->setMimeData(dd->model->mimeData(indexes));
2909 drag->setPixmap(pixmap);
2910 drag->setHotSpot(dd->pressedPosition - rect.topLeft());
2911 dd->dropEventMoved = false;
2912 Qt::DropAction action = drag->exec(supportedActions, dd->defaultDropAction);
2913 draggedItems.clear();
2914 // delete item, unless it has already been moved internally (see filterDropEvent)
2915 if (action == Qt::MoveAction && !dd->dropEventMoved) {
2916 if (dd->dragDropMode != QAbstractItemView::InternalMove || drag->target() == qq->viewport())
2917 dd->clearOrRemove();
2918 }
2919 dd->dropEventMoved = false;
2920 }
2921 return true;
2922}
2923
2924bool QIconModeViewBase::filterDropEvent(QDropEvent *e)
2925{
2926 if (e->source() != qq)
2927 return false;
2928
2929 const QSize contents = contentsSize;
2930 QPoint offset(horizontalOffset(), verticalOffset());
2931 QPoint end = e->position().toPoint() + offset;
2932 if (qq->acceptDrops()) {
2933 const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled;
2934 const QList<QModelIndex> &dropIndices = intersectingSet(QRect(end, QSize(1, 1)));
2935 for (const QModelIndex &index : dropIndices)
2936 if ((index.flags() & dropableFlags) == dropableFlags)
2937 return false;
2938 }
2939 QPoint start = dd->pressedPosition;
2940 QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start);
2941 const QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes();
2942 for (const auto &index : indexes) {
2943 QRect rect = dd->rectForIndex(index);
2944 viewport()->update(dd->mapToViewport(rect, false));
2945 QPoint dest = rect.topLeft() + delta;
2946 if (qq->isRightToLeft())
2947 dest.setX(dd->flipX(dest.x()) - rect.width());
2948 moveItem(index.row(), dest);
2949 qq->update(index);
2950 }
2951 dd->stopAutoScroll();
2952 draggedItems.clear();
2953 dd->emitIndexesMoved(indexes);
2954 // do not delete item on internal move, see filterStartDrag()
2955 dd->dropEventMoved = true;
2956 e->accept(); // we have handled the event
2957 // if the size has not grown, we need to check if it has shrunk
2958 if (contentsSize != contents) {
2959 if ((contentsSize.width() <= contents.width()
2960 || contentsSize.height() <= contents.height())) {
2961 updateContentsSize();
2962 }
2963 dd->viewUpdateGeometries();
2964 }
2965 return true;
2966}
2967
2968bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e)
2969{
2970 viewport()->update(draggedItemsRect()); // erase the area
2971 draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items
2972 return QCommonListViewBase::filterDragLeaveEvent(e);
2973}
2974
2975bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e)
2976{
2977 const bool wasAccepted = e->isAccepted();
2978
2979 // ignore by default
2980 e->ignore();
2981
2982 if (e->source() != qq || !dd->canDrop(e)) {
2983 // restore previous acceptance on failure
2984 e->setAccepted(wasAccepted);
2985 return false;
2986 }
2987
2988 // get old dragged items rect
2989 QRect itemsRect = this->itemsRect(draggedItems);
2990 viewport()->update(itemsRect.translated(draggedItemsDelta()));
2991 // update position
2992 draggedItemsPos = e->position().toPoint();
2993 // get new items rect
2994 viewport()->update(itemsRect.translated(draggedItemsDelta()));
2995 // set the item under the cursor to current
2996 QModelIndex index;
2997 if (movement() == QListView::Snap) {
2998 QRect rect(snapToGrid(e->position().toPoint() + offset()), gridSize());
2999 const QList<QModelIndex> intersectVector = intersectingSet(rect);
3000 index = intersectVector.size() > 0 ? intersectVector.last() : QModelIndex();
3001 } else {
3002 index = qq->indexAt(e->position().toPoint());
3003 }
3004 // check if we allow drops here
3005 if (draggedItems.contains(index))
3006 e->accept(); // allow changing item position
3007 else if (dd->model->flags(index) & Qt::ItemIsDropEnabled)
3008 e->accept(); // allow dropping on dropenabled items
3009 else if (!index.isValid())
3010 e->accept(); // allow dropping in empty areas
3011
3012 // the event was treated. do autoscrolling
3013 if (dd->shouldAutoScroll(e->position().toPoint()))
3014 dd->startAutoScroll();
3015 return true;
3016}
3017#endif // QT_CONFIG(draganddrop)
3018
3019void QIconModeViewBase::setRowCount(int rowCount)
3020{
3021 tree.create(qMax(rowCount - hiddenCount(), 0));
3022}
3023
3024void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
3025{
3026 if (scrollElasticBand)
3027 dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy);
3028
3029 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
3030 if (!draggedItems.isEmpty())
3031 viewport()->update(draggedItemsRect().translated(dx, dy));
3032}
3033
3034void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
3035{
3036 if (column() >= topLeft.column() && column() <= bottomRight.column()) {
3037 QStyleOptionViewItem option;
3038 initViewItemOption(&option);
3039 const int bottom = qMin(items.size(), bottomRight.row() + 1);
3040 const bool useItemSize = !dd->grid.isValid();
3041 for (int row = topLeft.row(); row < bottom; ++row)
3042 {
3043 QSize s = itemSize(option, modelIndex(row));
3044 if (!useItemSize)
3045 {
3046 s.setWidth(qMin(dd->grid.width(), s.width()));
3047 s.setHeight(qMin(dd->grid.height(), s.height()));
3048 }
3049 items[row].resize(s);
3050 }
3051 }
3052}
3053
3054bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
3055{
3056 if (info.last >= items.size()) {
3057 //first we create the items
3058 QStyleOptionViewItem option;
3059 initViewItemOption(&option);
3060 for (int row = items.size(); row <= info.last; ++row) {
3061 QSize size = itemSize(option, modelIndex(row));
3062 QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos
3063 items.append(item);
3064 }
3065 doDynamicLayout(info);
3066 }
3067 return (batchStartRow > max); // done
3068}
3069
3070QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const
3071{
3072 if (index.isValid() && index.row() < items.size())
3073 return items.at(index.row());
3074 return QListViewItem();
3075}
3076
3077void QIconModeViewBase::initBspTree(const QSize &contents)
3078{
3079 // remove all items from the tree
3080 int leafCount = tree.leafCount();
3081 for (int l = 0; l < leafCount; ++l)
3082 tree.leaf(l).clear();
3083 // we have to get the bounding rect of the items before we can initialize the tree
3084 QBspTree::Node::Type type = QBspTree::Node::Both; // 2D
3085 // simple heuristics to get better bsp
3086 if (contents.height() / contents.width() >= 3)
3087 type = QBspTree::Node::HorizontalPlane;
3088 else if (contents.width() / contents.height() >= 3)
3089 type = QBspTree::Node::VerticalPlane;
3090 // build tree for the bounding rect (not just the contents rect)
3091 tree.init(QRect(0, 0, contents.width(), contents.height()), type);
3092}
3093
3094QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info)
3095{
3096 int x, y;
3097 if (info.first == 0) {
3098 x = info.bounds.x() + info.spacing;
3099 y = info.bounds.y() + info.spacing;
3100 items.reserve(rowCount() - hiddenCount());
3101 } else {
3102 int idx = info.first - 1;
3103 while (idx > 0 && !items.at(idx).isValid())
3104 --idx;
3105 const QListViewItem &item = items.at(idx);
3106 x = item.x;
3107 y = item.y;
3108 if (info.flow == QListView::LeftToRight)
3109 x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing;
3110 else
3111 y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing;
3112 }
3113 return QPoint(x, y);
3114}
3115
3116/*!
3117 \internal
3118*/
3119void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info)
3120{
3121 const bool useItemSize = !info.grid.isValid();
3122 const QPoint topLeft = initDynamicLayout(info);
3123
3124 int segStartPosition;
3125 int segEndPosition;
3126 int deltaFlowPosition;
3127 int deltaSegPosition;
3128 int deltaSegHint;
3129 int flowPosition;
3130 int segPosition;
3131
3132 if (info.flow == QListView::LeftToRight) {
3133 segStartPosition = info.bounds.left() + info.spacing;
3134 segEndPosition = info.bounds.right();
3135 deltaFlowPosition = info.grid.width(); // dx
3136 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy
3137 deltaSegHint = info.grid.height();
3138 flowPosition = topLeft.x();
3139 segPosition = topLeft.y();
3140 } else { // flow == QListView::TopToBottom
3141 segStartPosition = info.bounds.top() + info.spacing;
3142 segEndPosition = info.bounds.bottom();
3143 deltaFlowPosition = info.grid.height(); // dy
3144 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx
3145 deltaSegHint = info.grid.width();
3146 flowPosition = topLeft.y();
3147 segPosition = topLeft.x();
3148 }
3149
3150 if (moved.size() != items.size())
3151 moved.resize(items.size());
3152
3153 QRect rect(QPoint(), topLeft);
3154 QListViewItem *item = nullptr;
3155 Q_ASSERT(info.first <= info.last);
3156 for (int row = info.first; row <= info.last; ++row) {
3157 item = &items[row];
3158 if (isHidden(row)) {
3159 item->invalidate();
3160 } else {
3161 // if we are not using a grid, we need to find the deltas
3162 if (useItemSize) {
3163 if (info.flow == QListView::LeftToRight)
3164 deltaFlowPosition = item->w + info.spacing;
3165 else
3166 deltaFlowPosition = item->h + info.spacing;
3167 } else {
3168 item->w = qMin<int>(info.grid.width(), item->w);
3169 item->h = qMin<int>(info.grid.height(), item->h);
3170 }
3171
3172 // create new segment
3173 if (info.wrap
3174 && flowPosition + deltaFlowPosition > segEndPosition
3175 && flowPosition > segStartPosition) {
3176 flowPosition = segStartPosition;
3177 segPosition += deltaSegPosition;
3178 if (useItemSize)
3179 deltaSegPosition = 0;
3180 }
3181 // We must delay calculation of the seg adjustment, as this item
3182 // may have caused a wrap to occur
3183 if (useItemSize) {
3184 if (info.flow == QListView::LeftToRight)
3185 deltaSegHint = item->h + info.spacing;
3186 else
3187 deltaSegHint = item->w + info.spacing;
3188 deltaSegPosition = qMax(deltaSegPosition, deltaSegHint);
3189 }
3190
3191 // set the position of the item
3192 // ### idealy we should have some sort of alignment hint for the item
3193 // ### (normally that would be a point between the icon and the text)
3194 if (!moved.testBit(row)) {
3195 if (info.flow == QListView::LeftToRight) {
3196 if (useItemSize) {
3197 item->x = flowPosition;
3198 item->y = segPosition;
3199 } else { // use grid
3200 item->x = flowPosition + ((deltaFlowPosition - item->w) / 2);
3201 item->y = segPosition;
3202 }
3203 } else { // TopToBottom
3204 if (useItemSize) {
3205 item->y = flowPosition;
3206 item->x = segPosition;
3207 } else { // use grid
3208 item->y = flowPosition + ((deltaFlowPosition - item->h) / 2);
3209 item->x = segPosition;
3210 }
3211 }
3212 }
3213
3214 // let the contents contain the new item
3215 if (useItemSize)
3216 rect |= item->rect();
3217 else if (info.flow == QListView::LeftToRight)
3218 rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition);
3219 else // flow == TopToBottom
3220 rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition);
3221
3222 // prepare for next item
3223 flowPosition += deltaFlowPosition; // current position + item width + gap
3224 }
3225 }
3226 Q_ASSERT(item);
3227 batchSavedDeltaSeg = deltaSegPosition;
3228 batchStartRow = info.last + 1;
3229 bool done = (info.last >= rowCount() - 1);
3230 // resize the content area
3231 if (done || !info.bounds.contains(item->rect())) {
3232 contentsSize = rect.size();
3233 if (info.flow == QListView::LeftToRight)
3234 contentsSize.rheight() += info.spacing;
3235 else
3236 contentsSize.rwidth() += info.spacing;
3237 }
3238 if (rect.size().isEmpty())
3239 return;
3240 // resize tree
3241 int insertFrom = info.first;
3242 if (done || info.first == 0) {
3243 initBspTree(rect.size());
3244 insertFrom = 0;
3245 }
3246 // insert items in tree
3247 for (int row = insertFrom; row <= info.last; ++row)
3248 tree.insertLeaf(items.at(row).rect(), row);
3249 // if the new items are visible, update the viewport
3250 QRect changedRect(topLeft, rect.bottomRight());
3251 if (clipRect().intersects(changedRect))
3252 viewport()->update();
3253}
3254
3255QList<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const
3256{
3257 QIconModeViewBase *that = const_cast<QIconModeViewBase*>(this);
3258 QBspTree::Data data(static_cast<void*>(that));
3259 QList<QModelIndex> res;
3260 that->interSectingVector = &res;
3261 that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data);
3262 that->interSectingVector = nullptr;
3263 return res;
3264}
3265
3266QRect QIconModeViewBase::itemsRect(const QList<QModelIndex> &indexes) const
3267{
3268 QRect rect;
3269 for (const auto &index : indexes)
3270 rect |= viewItemRect(indexToListViewItem(index));
3271 return rect;
3272}
3273
3274int QIconModeViewBase::itemIndex(const QListViewItem &item) const
3275{
3276 if (!item.isValid())
3277 return -1;
3278 int i = item.indexHint;
3279 if (i < items.size()) {
3280 if (items.at(i) == item)
3281 return i;
3282 } else {
3283 i = items.size() - 1;
3284 }
3285
3286 int j = i;
3287 int c = items.size();
3288 bool a = true;
3289 bool b = true;
3290
3291 while (a || b) {
3292 if (a) {
3293 if (items.at(i) == item) {
3294 items.at(i).indexHint = i;
3295 return i;
3296 }
3297 a = ++i < c;
3298 }
3299 if (b) {
3300 if (items.at(j) == item) {
3301 items.at(j).indexHint = j;
3302 return j;
3303 }
3304 b = --j > -1;
3305 }
3306 }
3307 return -1;
3308}
3309
3310void QIconModeViewBase::addLeaf(QList<int> &leaf, const QRect &area, uint visited,
3311 QBspTree::Data data)
3312{
3313 QListViewItem *vi;
3314 QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr);
3315 for (int i = 0; i < leaf.size(); ++i) {
3316 int idx = leaf.at(i);
3317 if (idx < 0 || idx >= _this->items.size())
3318 continue;
3319 vi = &_this->items[idx];
3320 Q_ASSERT(vi);
3321 if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) {
3322 QModelIndex index = _this->dd->listViewItemToIndex(*vi);
3323 Q_ASSERT(index.isValid());
3324 _this->interSectingVector->append(index);
3325 vi->visited = visited;
3326 }
3327 }
3328}
3329
3330void QIconModeViewBase::moveItem(int index, const QPoint &dest)
3331{
3332 // does not impact on the bintree itself or the contents rect
3333 QListViewItem *item = &items[index];
3334 QRect rect = item->rect();
3335
3336 // move the item without removing it from the tree
3337 tree.removeLeaf(rect, index);
3338 item->move(dest);
3339 tree.insertLeaf(QRect(dest, rect.size()), index);
3340
3341 // resize the contents area
3342 contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size();
3343
3344 // mark the item as moved
3345 if (moved.size() != items.size())
3346 moved.resize(items.size());
3347 moved.setBit(index, true);
3348}
3349
3350QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const
3351{
3352 int x = pos.x() - (pos.x() % gridSize().width());
3353 int y = pos.y() - (pos.y() % gridSize().height());
3354 return QPoint(x, y);
3355}
3356
3357QPoint QIconModeViewBase::draggedItemsDelta() const
3358{
3359 if (movement() == QListView::Snap) {
3360 QPoint snapdelta = QPoint((offset().x() % gridSize().width()),
3361 (offset().y() % gridSize().height()));
3362 return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta;
3363 }
3364 return draggedItemsPos - pressedPosition();
3365}
3366
3367QRect QIconModeViewBase::draggedItemsRect() const
3368{
3369 QRect rect = itemsRect(draggedItems);
3370 rect.translate(draggedItemsDelta());
3371 return rect;
3372}
3373
3374void QListViewPrivate::scrollElasticBandBy(int dx, int dy)
3375{
3376 if (dx > 0) // right
3377 elasticBand.moveRight(elasticBand.right() + dx);
3378 else if (dx < 0) // left
3379 elasticBand.moveLeft(elasticBand.left() - dx);
3380 if (dy > 0) // down
3381 elasticBand.moveBottom(elasticBand.bottom() + dy);
3382 else if (dy < 0) // up
3383 elasticBand.moveTop(elasticBand.top() - dy);
3384}
3385
3386void QIconModeViewBase::clear()
3387{
3388 tree.destroy();
3389 items.clear();
3390 moved.clear();
3391 batchStartRow = 0;
3392 batchSavedDeltaSeg = 0;
3393}
3394
3395void QIconModeViewBase::updateContentsSize()
3396{
3397 QRect bounding;
3398 for (int i = 0; i < items.size(); ++i)
3399 bounding |= items.at(i).rect();
3400 contentsSize = bounding.size();
3401}
3402
3403/*!
3404 \reimp
3405*/
3406void QListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3407{
3408 Q_D(const QListView);
3409 QAbstractItemView::currentChanged(current, previous);
3410#if QT_CONFIG(accessibility)
3411 if (QAccessible::isActive()) {
3412 if (current.isValid() && hasFocus()) {
3413 int entry = d->accessibleChildIndex(current);
3414 QAccessibleEvent event(this, QAccessible::Focus);
3415 event.setChild(entry);
3416 QAccessible::updateAccessibility(&event);
3417 }
3418 }
3419#endif
3420}
3421
3422/*!
3423 \reimp
3424*/
3425void QListView::selectionChanged(const QItemSelection &selected,
3426 const QItemSelection &deselected)
3427{
3428#if QT_CONFIG(accessibility)
3429 Q_D(const QListView);
3430 if (QAccessible::isActive()) {
3431 // ### does not work properly for selection ranges.
3432 QModelIndex sel = selected.indexes().value(0);
3433 if (sel.isValid()) {
3434 int entry = d->accessibleChildIndex(sel);
3435 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3436 event.setChild(entry);
3437 QAccessible::updateAccessibility(&event);
3438 }
3439 QModelIndex desel = deselected.indexes().value(0);
3440 if (desel.isValid()) {
3441 int entry = d->accessibleChildIndex(desel);
3442 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3443 event.setChild(entry);
3444 QAccessible::updateAccessibility(&event);
3445 }
3446 }
3447#endif
3448 QAbstractItemView::selectionChanged(selected, deselected);
3449}
3450
3451int QListView::visualIndex(const QModelIndex &index) const
3452{
3453 Q_D(const QListView);
3454 d->executePostedLayout();
3455 QListViewItem itm = d->indexToListViewItem(index);
3456 int visualIndex = d->commonListView->itemIndex(itm);
3457 for (const auto &idx : std::as_const(d->hiddenRows)) {
3458 if (idx.row() <= index.row())
3459 --visualIndex;
3460 }
3461 return visualIndex;
3462}
3463
3464
3465/*!
3466 \since 5.2
3467 \reimp
3468*/
3469QSize QListView::viewportSizeHint() const
3470{
3471 Q_D(const QListView);
3472 // We don't have a nice simple size hint for invalid or wrapping list views.
3473 if (!d->model)
3474 return QAbstractItemView::viewportSizeHint();
3475 const int rc = d->model->rowCount();
3476 if (rc == 0 || isWrapping())
3477 return QAbstractItemView::viewportSizeHint();
3478
3479 QStyleOptionViewItem option;
3480 initViewItemOption(&option);
3481
3482 if (uniformItemSizes()) {
3483 QSize sz = d->cachedItemSize;
3484 if (!sz.isValid()) {
3485 QModelIndex idx = d->model->index(0, d->column, d->root);
3486 sz = d->itemSize(option, idx);
3487 }
3488 sz.setHeight(rc * sz.height());
3489 return sz;
3490 }
3491
3492 // Using AdjustToContents with a high number of rows will normally not make sense, so we limit
3493 // this to default 1000 (that is btw the default for QHeaderView::resizeContentsPrecision())
3494 // (By setting the property _q_resizeContentPrecision the user can however override this).
3495 int maximumRows = 1000;
3496 const QVariant userOverrideValue = property("_q_resizeContentPrecision");
3497 if (userOverrideValue.isValid() && userOverrideValue.toInt() > 0) {
3498 maximumRows = userOverrideValue.toInt();
3499 }
3500 const int rowCount = qMin(rc, maximumRows);
3501
3502 int h = 0;
3503 int w = 0;
3504
3505 for (int row = 0; row < rowCount; ++row) {
3506 QModelIndex idx = d->model->index(row, d->column, d->root);
3507 QSize itemSize = d->itemSize(option, idx);
3508 h += itemSize.height();
3509 w = qMax(w, itemSize.width());
3510 }
3511 return QSize(w, h);
3512}
3513
3514QT_END_NAMESPACE
3515
3516#include "moc_qlistview.cpp"
bool qt_sendSpontaneousEvent(QObject *, QEvent *)