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 Q_ASSERT(index.isValid());
1967 return q->visualIndex(index);
1968}
1969#endif
1970
1971void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes,
1972 const QModelIndex &current) const
1973{
1974 auto isCurrentOrDisabled = [this, current](const QModelIndex &index) {
1975 return !isIndexEnabled(index) || index == current;
1976 };
1977 indexes->removeIf(isCurrentOrDisabled);
1978}
1979
1980/*
1981 * Common ListView Implementation
1982*/
1983
1984void QCommonListViewBase::appendHiddenRow(int row)
1985{
1986 dd->hiddenRows.insert(dd->model->index(row, 0, qq->rootIndex()));
1987}
1988
1989void QCommonListViewBase::removeHiddenRow(int row)
1990{
1991 dd->hiddenRows.remove(dd->model->index(row, 0, qq->rootIndex()));
1992}
1993
1994#if QT_CONFIG(draganddrop)
1995void QCommonListViewBase::paintDragDrop(QPainter *painter)
1996{
1997 // FIXME: Until the we can provide a proper drop indicator
1998 // in IconMode, it makes no sense to show it
1999 dd->paintDropIndicator(painter);
2000}
2001#endif
2002
2003QSize QListModeViewBase::viewportSize(const QAbstractItemView *v)
2004{
2005 return v->contentsRect().marginsRemoved(v->viewportMargins()).size();
2006}
2007
2008void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step)
2009{
2010 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(step.width() + spacing());
2011 horizontalScrollBar()->setPageStep(viewport()->width());
2012
2013 // If both scroll bars are set to auto, we might end up in a situation with enough space
2014 // for the actual content. But still one of the scroll bars will become enabled due to
2015 // the other one using the space. The other one will become invisible in the same cycle.
2016 // -> Infinite loop, QTBUG-39902
2017 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2018 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2019
2020 const QSize viewportSize = QListModeViewBase::viewportSize(qq);
2021
2022 bool verticalWantsToShow = contentsSize.height() > viewportSize.height();
2023 bool horizontalWantsToShow;
2024 if (verticalWantsToShow)
2025 horizontalWantsToShow = contentsSize.width() > viewportSize.width() - qq->verticalScrollBar()->width();
2026 else
2027 horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2028
2029 if (bothScrollBarsAuto && !horizontalWantsToShow) {
2030 // break the infinite loop described above by setting the range to 0, 0.
2031 // QAbstractScrollArea will then hide the scroll bar for us
2032 horizontalScrollBar()->setRange(0, 0);
2033 } else {
2034 horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width());
2035 }
2036}
2037
2038void QCommonListViewBase::updateVerticalScrollBar(const QSize &step)
2039{
2040 verticalScrollBar()->d_func()->itemviewChangeSingleStep(step.height() + spacing());
2041 verticalScrollBar()->setPageStep(viewport()->height());
2042
2043 // If both scroll bars are set to auto, we might end up in a situation with enough space
2044 // for the actual content. But still one of the scroll bars will become enabled due to
2045 // the other one using the space. The other one will become invisible in the same cycle.
2046 // -> Infinite loop, QTBUG-39902
2047 const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
2048 qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;
2049
2050 const QSize viewportSize = QListModeViewBase::viewportSize(qq);
2051
2052 bool horizontalWantsToShow = contentsSize.width() > viewportSize.width();
2053 bool verticalWantsToShow;
2054 if (horizontalWantsToShow)
2055 verticalWantsToShow = contentsSize.height() > viewportSize.height() - qq->horizontalScrollBar()->height();
2056 else
2057 verticalWantsToShow = contentsSize.height() > viewportSize.height();
2058
2059 if (bothScrollBarsAuto && !verticalWantsToShow) {
2060 // break the infinite loop described above by setting the range to 0, 0.
2061 // QAbstractScrollArea will then hide the scroll bar for us
2062 verticalScrollBar()->setRange(0, 0);
2063 } else {
2064 verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height());
2065 }
2066}
2067
2068void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/)
2069{
2070 dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy);
2071}
2072
2073int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint,
2074 bool above, bool below, const QRect &area, const QRect &rect) const
2075{
2076 int verticalValue = verticalScrollBar()->value();
2077 QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing());
2078 if (hint == QListView::PositionAtTop || above)
2079 verticalValue += adjusted.top();
2080 else if (hint == QListView::PositionAtBottom || below)
2081 verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1);
2082 else if (hint == QListView::PositionAtCenter)
2083 verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2);
2084 return verticalValue;
2085}
2086
2087int QCommonListViewBase::horizontalOffset() const
2088{
2089 return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value());
2090}
2091
2092int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint,
2093 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2094{
2095 int horizontalValue = horizontalScrollBar()->value();
2096 if (isRightToLeft()) {
2097 if (hint == QListView::PositionAtCenter) {
2098 horizontalValue += ((area.width() - rect.width()) / 2) - rect.left();
2099 } else {
2100 if (leftOf)
2101 horizontalValue -= rect.left();
2102 else if (rightOf)
2103 horizontalValue += qMin(rect.left(), area.width() - rect.right());
2104 }
2105 } else {
2106 if (hint == QListView::PositionAtCenter) {
2107 horizontalValue += rect.left() - ((area.width()- rect.width()) / 2);
2108 } else {
2109 if (leftOf)
2110 horizontalValue += rect.left();
2111 else if (rightOf)
2112 horizontalValue += qMin(rect.left(), rect.right() - area.width());
2113 }
2114 }
2115 return horizontalValue;
2116}
2117
2118/*
2119 * ListMode ListView Implementation
2120*/
2121QListModeViewBase::QListModeViewBase(QListView *q, QListViewPrivate *d)
2122 : QCommonListViewBase(q, d)
2123{
2124#if QT_CONFIG(draganddrop)
2125 dd->defaultDropAction = Qt::CopyAction;
2126#endif
2127}
2128
2129#if QT_CONFIG(draganddrop)
2130QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const
2131{
2132 QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport;
2133 if (!dd->overwrite) {
2134 const int margin = 2;
2135 if (pos.x() - rect.left() < margin) {
2136 r = QAbstractItemView::AboveItem; // Visually, on the left
2137 } else if (rect.right() - pos.x() < margin) {
2138 r = QAbstractItemView::BelowItem; // Visually, on the right
2139 } else if (rect.contains(pos, true)) {
2140 r = QAbstractItemView::OnItem;
2141 }
2142 } else {
2143 QRect touchingRect = rect;
2144 touchingRect.adjust(-1, -1, 1, 1);
2145 if (touchingRect.contains(pos, false)) {
2146 r = QAbstractItemView::OnItem;
2147 }
2148 }
2149
2150 if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled)))
2151 r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem;
2152
2153 return r;
2154}
2155
2156void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event)
2157{
2158 if (qq->dragDropMode() == QAbstractItemView::InternalMove
2159 && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction)))
2160 return;
2161
2162 // ignore by default
2163 event->ignore();
2164
2165 // can't use indexAt, doesn't account for spacing.
2166 QPoint p = event->position().toPoint();
2167 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2168 rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
2169 const QList<QModelIndex> intersectVector = dd->intersectingSet(rect);
2170 QModelIndex index = intersectVector.size() > 0
2171 ? intersectVector.last() : QModelIndex();
2172 dd->hover = index;
2173 if (!dd->droppingOnItself(event, index)
2174 && dd->canDrop(event)) {
2175
2176 if (index.isValid() && dd->showDropIndicator) {
2177 QRect rect = qq->visualRect(index);
2178 dd->dropIndicatorPosition = position(event->position().toPoint(), rect, index);
2179 // if spacing, should try to draw between items, not just next to item.
2180 switch (dd->dropIndicatorPosition) {
2181 case QAbstractItemView::AboveItem:
2182 if (dd->isIndexDropEnabled(index.parent())) {
2183 dd->dropIndicatorRect = QRect(rect.left()-dd->spacing(), rect.top(), 0, rect.height());
2184 event->accept();
2185 } else {
2186 dd->dropIndicatorRect = QRect();
2187 }
2188 break;
2189 case QAbstractItemView::BelowItem:
2190 if (dd->isIndexDropEnabled(index.parent())) {
2191 dd->dropIndicatorRect = QRect(rect.right()+dd->spacing(), rect.top(), 0, rect.height());
2192 event->accept();
2193 } else {
2194 dd->dropIndicatorRect = QRect();
2195 }
2196 break;
2197 case QAbstractItemView::OnItem:
2198 if (dd->isIndexDropEnabled(index)) {
2199 dd->dropIndicatorRect = rect;
2200 event->accept();
2201 } else {
2202 dd->dropIndicatorRect = QRect();
2203 }
2204 break;
2205 case QAbstractItemView::OnViewport:
2206 dd->dropIndicatorRect = QRect();
2207 if (dd->isIndexDropEnabled(qq->rootIndex())) {
2208 event->accept(); // allow dropping in empty areas
2209 }
2210 break;
2211 }
2212 } else {
2213 dd->dropIndicatorRect = QRect();
2214 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2215 if (dd->isIndexDropEnabled(qq->rootIndex())) {
2216 event->accept(); // allow dropping in empty areas
2217 }
2218 }
2219 dd->viewport->update();
2220 } // can drop
2221
2222 if (dd->shouldAutoScroll(event->position().toPoint()))
2223 qq->startAutoScroll();
2224}
2225
2226/*!
2227 If the event hasn't already been accepted, determines the index to drop on.
2228
2229 if (row == -1 && col == -1)
2230 // append to this drop index
2231 else
2232 // place at row, col in drop index
2233
2234 If it returns \c true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop.
2235 \internal
2236 */
2237bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
2238{
2239 if (event->isAccepted())
2240 return false;
2241
2242 QModelIndex index;
2243 if (dd->viewport->rect().contains(event->position().toPoint())) {
2244 // can't use indexAt, doesn't account for spacing.
2245 QPoint p = event->position().toPoint();
2246 QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
2247 rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
2248 const QList<QModelIndex> intersectVector = dd->intersectingSet(rect);
2249 index = intersectVector.size() > 0
2250 ? intersectVector.last() : QModelIndex();
2251 if (!index.isValid())
2252 index = dd->root;
2253 }
2254
2255 // If we are allowed to do the drop
2256 if (dd->model->supportedDropActions() & event->dropAction()) {
2257 int row = -1;
2258 int col = -1;
2259 if (index != dd->root) {
2260 dd->dropIndicatorPosition = position(event->position().toPoint(), qq->visualRect(index), index);
2261 switch (dd->dropIndicatorPosition) {
2262 case QAbstractItemView::AboveItem:
2263 row = index.row();
2264 col = index.column();
2265 index = index.parent();
2266 break;
2267 case QAbstractItemView::BelowItem:
2268 row = index.row() + 1;
2269 col = index.column();
2270 index = index.parent();
2271 break;
2272 case QAbstractItemView::OnItem:
2273 case QAbstractItemView::OnViewport:
2274 break;
2275 }
2276 } else {
2277 dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
2278 }
2279 *dropIndex = index;
2280 *dropRow = row;
2281 *dropCol = col;
2282 if (!dd->droppingOnItself(event, index))
2283 return true;
2284 }
2285 return false;
2286}
2287
2288#endif //QT_CONFIG(draganddrop)
2289
2290void QListModeViewBase::updateVerticalScrollBar(const QSize &step)
2291{
2292 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem
2293 && ((flow() == QListView::TopToBottom && !isWrapping())
2294 || (flow() == QListView::LeftToRight && isWrapping()))) {
2295 const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).size() - 1;
2296 if (steps > 0) {
2297 const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping());
2298 verticalScrollBar()->setSingleStep(1);
2299 verticalScrollBar()->setPageStep(pageSteps);
2300 verticalScrollBar()->setRange(0, steps - pageSteps);
2301 } else {
2302 verticalScrollBar()->setRange(0, 0);
2303 }
2304 // } else if (vertical && d->isWrapping() && d->movement == Static) {
2305 // ### wrapped scrolling in flow direction
2306 } else {
2307 QCommonListViewBase::updateVerticalScrollBar(step);
2308 }
2309}
2310
2311void QListModeViewBase::updateHorizontalScrollBar(const QSize &step)
2312{
2313 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem
2314 && ((flow() == QListView::TopToBottom && isWrapping())
2315 || (flow() == QListView::LeftToRight && !isWrapping()))) {
2316 int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).size() - 1;
2317 if (steps > 0) {
2318 const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping());
2319 horizontalScrollBar()->setSingleStep(1);
2320 horizontalScrollBar()->setPageStep(pageSteps);
2321 horizontalScrollBar()->setRange(0, steps - pageSteps);
2322 } else {
2323 horizontalScrollBar()->setRange(0, 0);
2324 }
2325 } else {
2326 QCommonListViewBase::updateHorizontalScrollBar(step);
2327 }
2328}
2329
2330int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint,
2331 bool above, bool below, const QRect &area, const QRect &rect) const
2332{
2333 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2334 int value;
2335 if (scrollValueMap.isEmpty()) {
2336 value = 0;
2337 } else {
2338 int scrollBarValue = verticalScrollBar()->value();
2339 int numHidden = 0;
2340 for (const auto &idx : std::as_const(dd->hiddenRows))
2341 if (idx.row() <= scrollBarValue)
2342 ++numHidden;
2343 value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()) - numHidden, flowPositions.size() - 1);
2344 }
2345 if (above)
2346 hint = QListView::PositionAtTop;
2347 else if (below)
2348 hint = QListView::PositionAtBottom;
2349 if (hint == QListView::EnsureVisible)
2350 return value;
2351
2352 return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height());
2353 }
2354
2355 return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect);
2356}
2357
2358int QListModeViewBase::horizontalOffset() const
2359{
2360 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2361 if (isWrapping()) {
2362 if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) {
2363 const int max = segmentPositions.size() - 1;
2364 int currentValue = qBound(0, horizontalScrollBar()->value(), max);
2365 int position = segmentPositions.at(currentValue);
2366 int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max);
2367 int maximum = segmentPositions.at(maximumValue);
2368 return (isRightToLeft() ? maximum - position : position);
2369 }
2370 } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) {
2371 int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value()));
2372 int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum()));
2373 return (isRightToLeft() ? maximum - position : position);
2374 }
2375 }
2376 return QCommonListViewBase::horizontalOffset();
2377}
2378
2379int QListModeViewBase::verticalOffset() const
2380{
2381 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2382 if (isWrapping()) {
2383 if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) {
2384 int value = verticalScrollBar()->value();
2385 if (value >= segmentPositions.size())
2386 return 0;
2387 return segmentPositions.at(value) - spacing();
2388 }
2389 } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) {
2390 int value = verticalScrollBar()->value();
2391 if (value > scrollValueMap.size())
2392 return 0;
2393 return flowPositions.at(scrollValueMap.at(value)) - spacing();
2394 }
2395 }
2396 return QCommonListViewBase::verticalOffset();
2397}
2398
2399int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint,
2400 bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
2401{
2402 if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem)
2403 return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect);
2404
2405 int value;
2406 if (scrollValueMap.isEmpty())
2407 value = 0;
2408 else
2409 value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.size() - 1);
2410 if (leftOf)
2411 hint = QListView::PositionAtTop;
2412 else if (rightOf)
2413 hint = QListView::PositionAtBottom;
2414 if (hint == QListView::EnsureVisible)
2415 return value;
2416
2417 return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width());
2418}
2419
2420void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
2421{
2422 // ### reorder this logic
2423 const int verticalValue = verticalScrollBar()->value();
2424 const int horizontalValue = horizontalScrollBar()->value();
2425 const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem);
2426 const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem);
2427
2428 if (isWrapping()) {
2429 if (segmentPositions.isEmpty())
2430 return;
2431 const int max = segmentPositions.size() - 1;
2432 if (horizontal && flow() == QListView::TopToBottom && dx != 0) {
2433 int currentValue = qBound(0, horizontalValue, max);
2434 int previousValue = qBound(0, currentValue + dx, max);
2435 int currentCoordinate = segmentPositions.at(currentValue) - spacing();
2436 int previousCoordinate = segmentPositions.at(previousValue) - spacing();
2437 dx = previousCoordinate - currentCoordinate;
2438 } else if (vertical && flow() == QListView::LeftToRight && dy != 0) {
2439 int currentValue = qBound(0, verticalValue, max);
2440 int previousValue = qBound(0, currentValue + dy, max);
2441 int currentCoordinate = segmentPositions.at(currentValue) - spacing();
2442 int previousCoordinate = segmentPositions.at(previousValue) - spacing();
2443 dy = previousCoordinate - currentCoordinate;
2444 }
2445 } else {
2446 if (flowPositions.isEmpty())
2447 return;
2448 const int max = scrollValueMap.size() - 1;
2449 if (vertical && flow() == QListView::TopToBottom && dy != 0) {
2450 int currentValue = qBound(0, verticalValue, max);
2451 int previousValue = qBound(0, currentValue + dy, max);
2452 int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
2453 int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
2454 dy = previousCoordinate - currentCoordinate;
2455 } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) {
2456 int currentValue = qBound(0, horizontalValue, max);
2457 int previousValue = qBound(0, currentValue + dx, max);
2458 int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
2459 int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
2460 dx = previousCoordinate - currentCoordinate;
2461 }
2462 }
2463 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
2464}
2465
2466bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
2467{
2468 doStaticLayout(info);
2469 return batchStartRow > max; // returning true stops items layout
2470}
2471
2472QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const
2473{
2474 if (flowPositions.isEmpty()
2475 || segmentPositions.isEmpty()
2476 || index.row() >= flowPositions.size() - 1)
2477 return QListViewItem();
2478
2479 const int segment = qBinarySearch<int>(segmentStartRows, index.row(),
2480 0, segmentStartRows.size() - 1);
2481
2482
2483 QStyleOptionViewItem options;
2484 initViewItemOption(&options);
2485 options.rect.setSize(contentsSize);
2486 QSize size = (uniformItemSizes() && cachedItemSize().isValid())
2487 ? cachedItemSize() : itemSize(options, index);
2488 QSize cellSize = size;
2489
2490 QPoint pos;
2491 if (flow() == QListView::LeftToRight) {
2492 pos.setX(flowPositions.at(index.row()));
2493 pos.setY(segmentPositions.at(segment));
2494 } else { // TopToBottom
2495 pos.setY(flowPositions.at(index.row()));
2496 pos.setX(segmentPositions.at(segment));
2497 if (isWrapping()) { // make the items as wide as the segment
2498 int right = (segment + 1 >= segmentPositions.size()
2499 ? contentsSize.width()
2500 : segmentPositions.at(segment + 1));
2501 cellSize.setWidth(right - pos.x());
2502 } else { // make the items as wide as the viewport
2503 cellSize.setWidth(qMax(size.width(), viewport()->width() - 2 * spacing()));
2504 }
2505 }
2506
2507 if (dd->itemAlignment & Qt::AlignHorizontal_Mask) {
2508 size.setWidth(qMin(size.width(), cellSize.width()));
2509 if (dd->itemAlignment & Qt::AlignRight)
2510 pos.setX(pos.x() + cellSize.width() - size.width());
2511 if (dd->itemAlignment & Qt::AlignHCenter)
2512 pos.setX(pos.x() + (cellSize.width() - size.width()) / 2);
2513 } else {
2514 size.setWidth(cellSize.width());
2515 }
2516
2517 return QListViewItem(QRect(pos, size), index.row());
2518}
2519
2520QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info)
2521{
2522 int x, y;
2523 if (info.first == 0) {
2524 flowPositions.clear();
2525 segmentPositions.clear();
2526 segmentStartRows.clear();
2527 segmentExtents.clear();
2528 scrollValueMap.clear();
2529 x = info.bounds.left() + info.spacing;
2530 y = info.bounds.top() + info.spacing;
2531 segmentPositions.append(info.flow == QListView::LeftToRight ? y : x);
2532 segmentStartRows.append(0);
2533 } else if (info.wrap) {
2534 if (info.flow == QListView::LeftToRight) {
2535 x = batchSavedPosition;
2536 y = segmentPositions.constLast();
2537 } else { // flow == QListView::TopToBottom
2538 x = segmentPositions.constLast();
2539 y = batchSavedPosition;
2540 }
2541 } else { // not first and not wrap
2542 if (info.flow == QListView::LeftToRight) {
2543 x = batchSavedPosition;
2544 y = info.bounds.top() + info.spacing;
2545 } else { // flow == QListView::TopToBottom
2546 x = info.bounds.left() + info.spacing;
2547 y = batchSavedPosition;
2548 }
2549 }
2550 return QPoint(x, y);
2551}
2552
2553/*!
2554 \internal
2555*/
2556void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info)
2557{
2558 const bool useItemSize = !info.grid.isValid();
2559 const QPoint topLeft = initStaticLayout(info);
2560 QStyleOptionViewItem option;
2561 initViewItemOption(&option);
2562 option.rect = info.bounds;
2563 option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing);
2564
2565 // The static layout data structures are as follows:
2566 // One vector contains the coordinate in the direction of layout flow.
2567 // Another vector contains the coordinates of the segments.
2568 // A third vector contains the index (model row) of the first item
2569 // of each segment.
2570
2571 int segStartPosition;
2572 int segEndPosition;
2573 int deltaFlowPosition;
2574 int deltaSegPosition;
2575 int deltaSegHint;
2576 int flowPosition;
2577 int segPosition;
2578
2579 if (info.flow == QListView::LeftToRight) {
2580 segStartPosition = info.bounds.left();
2581 segEndPosition = info.bounds.width();
2582 flowPosition = topLeft.x();
2583 segPosition = topLeft.y();
2584 deltaFlowPosition = info.grid.width(); // dx
2585 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy
2586 deltaSegHint = info.grid.height();
2587 } else { // flow == QListView::TopToBottom
2588 segStartPosition = info.bounds.top();
2589 segEndPosition = info.bounds.height();
2590 flowPosition = topLeft.y();
2591 segPosition = topLeft.x();
2592 deltaFlowPosition = info.grid.height(); // dy
2593 deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx
2594 deltaSegHint = info.grid.width();
2595 }
2596
2597 for (int row = info.first; row <= info.last; ++row) {
2598 if (isHidden(row)) { // ###
2599 flowPositions.append(flowPosition);
2600 } else {
2601 // if we are not using a grid, we need to find the deltas
2602 if (useItemSize) {
2603 QSize hint = itemSize(option, modelIndex(row));
2604 if (info.flow == QListView::LeftToRight) {
2605 deltaFlowPosition = hint.width() + info.spacing;
2606 deltaSegHint = hint.height() + info.spacing;
2607 } else { // TopToBottom
2608 deltaFlowPosition = hint.height() + info.spacing;
2609 deltaSegHint = hint.width() + info.spacing;
2610 }
2611 }
2612 // create new segment
2613 if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) {
2614 segmentExtents.append(flowPosition);
2615 flowPosition = info.spacing + segStartPosition;
2616 segPosition += info.spacing + deltaSegPosition;
2617 segmentPositions.append(segPosition);
2618 segmentStartRows.append(row);
2619 deltaSegPosition = 0;
2620 }
2621 // save the flow position of this item
2622 scrollValueMap.append(flowPositions.size());
2623 flowPositions.append(flowPosition);
2624 // prepare for the next item
2625 deltaSegPosition = qMax(deltaSegHint, deltaSegPosition);
2626 flowPosition += info.spacing + deltaFlowPosition;
2627 }
2628 }
2629 // used when laying out next batch
2630 batchSavedPosition = flowPosition;
2631 batchSavedDeltaSeg = deltaSegPosition;
2632 batchStartRow = info.last + 1;
2633 if (info.last == info.max)
2634 flowPosition -= info.spacing; // remove extra spacing
2635 // set the contents size
2636 QRect rect = info.bounds;
2637 if (info.flow == QListView::LeftToRight) {
2638 rect.setRight(segmentPositions.size() == 1 ? flowPosition : info.bounds.right());
2639 rect.setBottom(segPosition + deltaSegPosition);
2640 } else { // TopToBottom
2641 rect.setRight(segPosition + deltaSegPosition);
2642 rect.setBottom(segmentPositions.size() == 1 ? flowPosition : info.bounds.bottom());
2643 }
2644 contentsSize = QSize(rect.right(), rect.bottom());
2645 // if it is the last batch, save the end of the segments
2646 if (info.last == info.max) {
2647 segmentExtents.append(flowPosition);
2648 scrollValueMap.append(flowPositions.size());
2649 flowPositions.append(flowPosition);
2650 segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX);
2651 }
2652 // if the new items are visible, update the viewport
2653 QRect changedRect(topLeft, rect.bottomRight());
2654 if (clipRect().intersects(changedRect))
2655 viewport()->update();
2656}
2657
2658/*!
2659 \internal
2660 Finds the set of items intersecting with \a area.
2661 In this function, itemsize is counted from topleft to the start of the next item.
2662*/
2663QList<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const
2664{
2665 QList<QModelIndex> ret;
2666 int segStartPosition;
2667 int segEndPosition;
2668 int flowStartPosition;
2669 int flowEndPosition;
2670 if (flow() == QListView::LeftToRight) {
2671 segStartPosition = area.top();
2672 segEndPosition = area.bottom();
2673 flowStartPosition = area.left();
2674 flowEndPosition = area.right();
2675 } else {
2676 segStartPosition = area.left();
2677 segEndPosition = area.right();
2678 flowStartPosition = area.top();
2679 flowEndPosition = area.bottom();
2680 }
2681 if (segmentPositions.size() < 2 || flowPositions.isEmpty())
2682 return ret;
2683 // the last segment position is actually the edge of the last segment
2684 const int segLast = segmentPositions.size() - 2;
2685 int seg = qBinarySearch<int>(segmentPositions, segStartPosition, 0, segLast + 1);
2686 for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) {
2687 int first = segmentStartRows.at(seg);
2688 int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1;
2689 if (segmentExtents.at(seg) < flowStartPosition)
2690 continue;
2691 int row = qBinarySearch<int>(flowPositions, flowStartPosition, first, last);
2692 for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) {
2693 if (isHidden(row))
2694 continue;
2695 QModelIndex index = modelIndex(row);
2696 if (index.isValid()) {
2697 if (flow() == QListView::LeftToRight || dd->itemAlignment == Qt::Alignment()) {
2698 ret += index;
2699 } else {
2700 const auto viewItem = indexToListViewItem(index);
2701 const int iw = viewItem.width();
2702 const int startPos = qMax(segStartPosition, segmentPositions.at(seg));
2703 const int endPos = qMin(segmentPositions.at(seg + 1), segEndPosition);
2704 if (endPos >= viewItem.x && startPos < viewItem.x + iw)
2705 ret += index;
2706 }
2707 }
2708#if 0 // for debugging
2709 else
2710 qWarning("intersectingSet: row %d was invalid", row);
2711#endif
2712 }
2713 }
2714 return ret;
2715}
2716
2717void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &)
2718{
2719 dd->doDelayedItemsLayout();
2720}
2721
2722
2723QRect QListModeViewBase::mapToViewport(const QRect &rect) const
2724{
2725 if (isWrapping())
2726 return rect;
2727 // If the listview is in "listbox-mode", the items are as wide as the view.
2728 // But we don't shrink the items.
2729 QRect result = rect;
2730 if (flow() == QListView::TopToBottom) {
2731 result.setLeft(spacing());
2732 result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing()));
2733 } else { // LeftToRight
2734 result.setTop(spacing());
2735 result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing()));
2736 }
2737 return result;
2738}
2739
2740int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const
2741{
2742 QList<int> positions;
2743 if (wrap)
2744 positions = segmentPositions;
2745 else if (!flowPositions.isEmpty()) {
2746 positions.reserve(scrollValueMap.size());
2747 for (int itemShown : scrollValueMap)
2748 positions.append(flowPositions.at(itemShown));
2749 }
2750 if (positions.isEmpty() || bounds <= length)
2751 return positions.size();
2752 if (uniformItemSizes()) {
2753 for (int i = 1; i < positions.size(); ++i)
2754 if (positions.at(i) > 0)
2755 return length / positions.at(i);
2756 return 0; // all items had height 0
2757 }
2758 int pageSteps = 0;
2759 int steps = positions.size() - 1;
2760 int max = qMax(length, bounds);
2761 int min = qMin(length, bounds);
2762 int pos = min - (max - positions.constLast());
2763
2764 while (pos >= 0 && steps > 0) {
2765 pos -= (positions.at(steps) - positions.at(steps - 1));
2766 if (pos >= 0) //this item should be visible
2767 ++pageSteps;
2768 --steps;
2769 }
2770
2771 // at this point we know that positions has at least one entry
2772 return qMax(pageSteps, 1);
2773}
2774
2775int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize,
2776 QAbstractItemView::ScrollHint hint,
2777 Qt::Orientation orientation, bool wrap, int itemExtent) const
2778{
2779 if (index < 0)
2780 return scrollValue;
2781
2782 itemExtent += spacing();
2783 QList<int> hiddenRows = dd->hiddenRowIds();
2784 std::sort(hiddenRows.begin(), hiddenRows.end());
2785 int hiddenRowsBefore = 0;
2786 for (int i = 0; i < hiddenRows.size() - 1; ++i)
2787 if (hiddenRows.at(i) > index + hiddenRowsBefore)
2788 break;
2789 else
2790 ++hiddenRowsBefore;
2791 if (!wrap) {
2792 int topIndex = index;
2793 const int bottomIndex = topIndex;
2794 const int bottomCoordinate = flowPositions.at(index + hiddenRowsBefore);
2795 while (topIndex > 0 &&
2796 (bottomCoordinate - flowPositions.at(topIndex + hiddenRowsBefore - 1) + itemExtent) <= (viewportSize)) {
2797 topIndex--;
2798 // will the next one be a hidden row -> skip
2799 while (hiddenRowsBefore > 0 && hiddenRows.at(hiddenRowsBefore - 1) >= topIndex + hiddenRowsBefore - 1)
2800 hiddenRowsBefore--;
2801 }
2802
2803 const int itemCount = bottomIndex - topIndex + 1;
2804 switch (hint) {
2805 case QAbstractItemView::PositionAtTop:
2806 return index;
2807 case QAbstractItemView::PositionAtBottom:
2808 return index - itemCount + 1;
2809 case QAbstractItemView::PositionAtCenter:
2810 return index - (itemCount / 2);
2811 default:
2812 break;
2813 }
2814 } else { // wrapping
2815 Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight
2816 ? Qt::Horizontal : Qt::Vertical);
2817 if (flowOrientation == orientation) { // scrolling in the "flow" direction
2818 // ### wrapped scrolling in the flow direction
2819 return flowPositions.at(index + hiddenRowsBefore); // ### always pixel based for now
2820 } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction
2821 int segment = qBinarySearch<int>(segmentStartRows, index, 0, segmentStartRows.size() - 1);
2822 int leftSegment = segment;
2823 const int rightSegment = leftSegment;
2824 const int bottomCoordinate = segmentPositions.at(segment);
2825
2826 while (leftSegment > scrollValue &&
2827 (bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) {
2828 leftSegment--;
2829 }
2830
2831 const int segmentCount = rightSegment - leftSegment + 1;
2832 switch (hint) {
2833 case QAbstractItemView::PositionAtTop:
2834 return segment;
2835 case QAbstractItemView::PositionAtBottom:
2836 return segment - segmentCount + 1;
2837 case QAbstractItemView::PositionAtCenter:
2838 return segment - (segmentCount / 2);
2839 default:
2840 break;
2841 }
2842 }
2843 }
2844 return scrollValue;
2845}
2846
2847void QListModeViewBase::clear()
2848{
2849 flowPositions.clear();
2850 segmentPositions.clear();
2851 segmentStartRows.clear();
2852 segmentExtents.clear();
2853 batchSavedPosition = 0;
2854 batchStartRow = 0;
2855 batchSavedDeltaSeg = 0;
2856}
2857
2858/*
2859 * IconMode ListView Implementation
2860*/
2861
2862void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index)
2863{
2864 if (index.row() >= items.size())
2865 return;
2866 const QSize oldContents = contentsSize;
2867 qq->update(index); // update old position
2868 moveItem(index.row(), position);
2869 qq->update(index); // update new position
2870
2871 if (contentsSize != oldContents)
2872 dd->viewUpdateGeometries(); // update the scroll bars
2873}
2874
2875void QIconModeViewBase::appendHiddenRow(int row)
2876{
2877 if (row >= 0 && row < items.size()) //remove item
2878 tree.removeLeaf(items.at(row).rect(), row);
2879 QCommonListViewBase::appendHiddenRow(row);
2880}
2881
2882void QIconModeViewBase::removeHiddenRow(int row)
2883{
2884 QCommonListViewBase::removeHiddenRow(row);
2885 if (row >= 0 && row < items.size()) //insert item
2886 tree.insertLeaf(items.at(row).rect(), row);
2887}
2888
2889#if QT_CONFIG(draganddrop)
2890bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions)
2891{
2892 // This function does the same thing as in QAbstractItemView::startDrag(),
2893 // plus adding viewitems to the draggedItems list.
2894 // We need these items to draw the drag items
2895 QModelIndexList indexes = dd->selectionModel->selectedIndexes();
2896 if (indexes.size() > 0 ) {
2897 if (viewport()->acceptDrops()) {
2898 QModelIndexList::ConstIterator it = indexes.constBegin();
2899 for (; it != indexes.constEnd(); ++it)
2900 if (dd->model->flags(*it) & Qt::ItemIsDragEnabled
2901 && (*it).column() == dd->column)
2902 draggedItems.push_back(*it);
2903 }
2904
2905 QRect rect;
2906 QPixmap pixmap = dd->renderToPixmap(indexes, &rect);
2907 rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);
2908 QDrag *drag = new QDrag(qq);
2909 drag->setMimeData(dd->model->mimeData(indexes));
2910 drag->setPixmap(pixmap);
2911 drag->setHotSpot(dd->pressedPosition - rect.topLeft());
2912 dd->dropEventMoved = false;
2913 Qt::DropAction action = drag->exec(supportedActions, dd->defaultDropAction);
2914 draggedItems.clear();
2915 // delete item, unless it has already been moved internally (see filterDropEvent)
2916 if (action == Qt::MoveAction && !dd->dropEventMoved) {
2917 if (dd->dragDropMode != QAbstractItemView::InternalMove || drag->target() == qq->viewport())
2918 dd->clearOrRemove();
2919 }
2920 dd->dropEventMoved = false;
2921 }
2922 return true;
2923}
2924
2925bool QIconModeViewBase::filterDropEvent(QDropEvent *e)
2926{
2927 if (e->source() != qq)
2928 return false;
2929
2930 const QSize contents = contentsSize;
2931 QPoint offset(horizontalOffset(), verticalOffset());
2932 QPoint end = e->position().toPoint() + offset;
2933 if (qq->acceptDrops()) {
2934 const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled;
2935 const QList<QModelIndex> &dropIndices = intersectingSet(QRect(end, QSize(1, 1)));
2936 for (const QModelIndex &index : dropIndices)
2937 if ((index.flags() & dropableFlags) == dropableFlags)
2938 return false;
2939 }
2940 QPoint start = dd->pressedPosition;
2941 QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start);
2942 const QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes();
2943 for (const auto &index : indexes) {
2944 QRect rect = dd->rectForIndex(index);
2945 viewport()->update(dd->mapToViewport(rect, false));
2946 QPoint dest = rect.topLeft() + delta;
2947 if (qq->isRightToLeft())
2948 dest.setX(dd->flipX(dest.x()) - rect.width());
2949 moveItem(index.row(), dest);
2950 qq->update(index);
2951 }
2952 dd->stopAutoScroll();
2953 draggedItems.clear();
2954 dd->emitIndexesMoved(indexes);
2955 // do not delete item on internal move, see filterStartDrag()
2956 dd->dropEventMoved = true;
2957 e->accept(); // we have handled the event
2958 // if the size has not grown, we need to check if it has shrunk
2959 if (contentsSize != contents) {
2960 if ((contentsSize.width() <= contents.width()
2961 || contentsSize.height() <= contents.height())) {
2962 updateContentsSize();
2963 }
2964 dd->viewUpdateGeometries();
2965 }
2966 return true;
2967}
2968
2969bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e)
2970{
2971 viewport()->update(draggedItemsRect()); // erase the area
2972 draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items
2973 return QCommonListViewBase::filterDragLeaveEvent(e);
2974}
2975
2976bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e)
2977{
2978 const bool wasAccepted = e->isAccepted();
2979
2980 // ignore by default
2981 e->ignore();
2982
2983 if (e->source() != qq || !dd->canDrop(e)) {
2984 // restore previous acceptance on failure
2985 e->setAccepted(wasAccepted);
2986 return false;
2987 }
2988
2989 // get old dragged items rect
2990 QRect itemsRect = this->itemsRect(draggedItems);
2991 viewport()->update(itemsRect.translated(draggedItemsDelta()));
2992 // update position
2993 draggedItemsPos = e->position().toPoint();
2994 // get new items rect
2995 viewport()->update(itemsRect.translated(draggedItemsDelta()));
2996 // set the item under the cursor to current
2997 QModelIndex index;
2998 if (movement() == QListView::Snap) {
2999 QRect rect(snapToGrid(e->position().toPoint() + offset()), gridSize());
3000 const QList<QModelIndex> intersectVector = intersectingSet(rect);
3001 index = intersectVector.size() > 0 ? intersectVector.last() : QModelIndex();
3002 } else {
3003 index = qq->indexAt(e->position().toPoint());
3004 }
3005 // check if we allow drops here
3006 if (draggedItems.contains(index))
3007 e->accept(); // allow changing item position
3008 else if (dd->model->flags(index) & Qt::ItemIsDropEnabled)
3009 e->accept(); // allow dropping on dropenabled items
3010 else if (!index.isValid())
3011 e->accept(); // allow dropping in empty areas
3012
3013 // the event was treated. do autoscrolling
3014 if (dd->shouldAutoScroll(e->position().toPoint()))
3015 dd->startAutoScroll();
3016 return true;
3017}
3018#endif // QT_CONFIG(draganddrop)
3019
3020void QIconModeViewBase::setRowCount(int rowCount)
3021{
3022 tree.create(qMax(rowCount - hiddenCount(), 0));
3023}
3024
3025void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
3026{
3027 if (scrollElasticBand)
3028 dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy);
3029
3030 QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
3031 if (!draggedItems.isEmpty())
3032 viewport()->update(draggedItemsRect().translated(dx, dy));
3033}
3034
3035void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
3036{
3037 if (column() >= topLeft.column() && column() <= bottomRight.column()) {
3038 QStyleOptionViewItem option;
3039 initViewItemOption(&option);
3040 const int bottom = qMin(items.size(), bottomRight.row() + 1);
3041 const bool useItemSize = !dd->grid.isValid();
3042 for (int row = topLeft.row(); row < bottom; ++row)
3043 {
3044 QSize s = itemSize(option, modelIndex(row));
3045 if (!useItemSize)
3046 {
3047 s.setWidth(qMin(dd->grid.width(), s.width()));
3048 s.setHeight(qMin(dd->grid.height(), s.height()));
3049 }
3050 items[row].resize(s);
3051 }
3052 }
3053}
3054
3055bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
3056{
3057 if (info.last >= items.size()) {
3058 //first we create the items
3059 QStyleOptionViewItem option;
3060 initViewItemOption(&option);
3061 for (int row = items.size(); row <= info.last; ++row) {
3062 QSize size = itemSize(option, modelIndex(row));
3063 QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos
3064 items.append(item);
3065 }
3066 doDynamicLayout(info);
3067 }
3068 return (batchStartRow > max); // done
3069}
3070
3071QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const
3072{
3073 if (index.isValid() && index.row() < items.size())
3074 return items.at(index.row());
3075 return QListViewItem();
3076}
3077
3078void QIconModeViewBase::initBspTree(const QSize &contents)
3079{
3080 // remove all items from the tree
3081 int leafCount = tree.leafCount();
3082 for (int l = 0; l < leafCount; ++l)
3083 tree.leaf(l).clear();
3084 // we have to get the bounding rect of the items before we can initialize the tree
3085 QBspTree::Node::Type type = QBspTree::Node::Both; // 2D
3086 // simple heuristics to get better bsp
3087 if (contents.height() / contents.width() >= 3)
3088 type = QBspTree::Node::HorizontalPlane;
3089 else if (contents.width() / contents.height() >= 3)
3090 type = QBspTree::Node::VerticalPlane;
3091 // build tree for the bounding rect (not just the contents rect)
3092 tree.init(QRect(0, 0, contents.width(), contents.height()), type);
3093}
3094
3095QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info)
3096{
3097 int x, y;
3098 if (info.first == 0) {
3099 x = info.bounds.x() + info.spacing;
3100 y = info.bounds.y() + info.spacing;
3101 items.reserve(rowCount() - hiddenCount());
3102 } else {
3103 int idx = info.first - 1;
3104 while (idx > 0 && !items.at(idx).isValid())
3105 --idx;
3106 const QListViewItem &item = items.at(idx);
3107 x = item.x;
3108 y = item.y;
3109 if (info.flow == QListView::LeftToRight)
3110 x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing;
3111 else
3112 y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing;
3113 }
3114 return QPoint(x, y);
3115}
3116
3117/*!
3118 \internal
3119*/
3120void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info)
3121{
3122 const bool useItemSize = !info.grid.isValid();
3123 const QPoint topLeft = initDynamicLayout(info);
3124
3125 int segStartPosition;
3126 int segEndPosition;
3127 int deltaFlowPosition;
3128 int deltaSegPosition;
3129 int deltaSegHint;
3130 int flowPosition;
3131 int segPosition;
3132
3133 if (info.flow == QListView::LeftToRight) {
3134 segStartPosition = info.bounds.left() + info.spacing;
3135 segEndPosition = info.bounds.right();
3136 deltaFlowPosition = info.grid.width(); // dx
3137 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy
3138 deltaSegHint = info.grid.height();
3139 flowPosition = topLeft.x();
3140 segPosition = topLeft.y();
3141 } else { // flow == QListView::TopToBottom
3142 segStartPosition = info.bounds.top() + info.spacing;
3143 segEndPosition = info.bounds.bottom();
3144 deltaFlowPosition = info.grid.height(); // dy
3145 deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx
3146 deltaSegHint = info.grid.width();
3147 flowPosition = topLeft.y();
3148 segPosition = topLeft.x();
3149 }
3150
3151 if (moved.size() != items.size())
3152 moved.resize(items.size());
3153
3154 QRect rect(QPoint(), topLeft);
3155 QListViewItem *item = nullptr;
3156 Q_ASSERT(info.first <= info.last);
3157 for (int row = info.first; row <= info.last; ++row) {
3158 item = &items[row];
3159 if (isHidden(row)) {
3160 item->invalidate();
3161 } else {
3162 // if we are not using a grid, we need to find the deltas
3163 if (useItemSize) {
3164 if (info.flow == QListView::LeftToRight)
3165 deltaFlowPosition = item->w + info.spacing;
3166 else
3167 deltaFlowPosition = item->h + info.spacing;
3168 } else {
3169 item->w = qMin<int>(info.grid.width(), item->w);
3170 item->h = qMin<int>(info.grid.height(), item->h);
3171 }
3172
3173 // create new segment
3174 if (info.wrap
3175 && flowPosition + deltaFlowPosition > segEndPosition
3176 && flowPosition > segStartPosition) {
3177 flowPosition = segStartPosition;
3178 segPosition += deltaSegPosition;
3179 if (useItemSize)
3180 deltaSegPosition = 0;
3181 }
3182 // We must delay calculation of the seg adjustment, as this item
3183 // may have caused a wrap to occur
3184 if (useItemSize) {
3185 if (info.flow == QListView::LeftToRight)
3186 deltaSegHint = item->h + info.spacing;
3187 else
3188 deltaSegHint = item->w + info.spacing;
3189 deltaSegPosition = qMax(deltaSegPosition, deltaSegHint);
3190 }
3191
3192 // set the position of the item
3193 // ### idealy we should have some sort of alignment hint for the item
3194 // ### (normally that would be a point between the icon and the text)
3195 if (!moved.testBit(row)) {
3196 if (info.flow == QListView::LeftToRight) {
3197 if (useItemSize) {
3198 item->x = flowPosition;
3199 item->y = segPosition;
3200 } else { // use grid
3201 item->x = flowPosition + ((deltaFlowPosition - item->w) / 2);
3202 item->y = segPosition;
3203 }
3204 } else { // TopToBottom
3205 if (useItemSize) {
3206 item->y = flowPosition;
3207 item->x = segPosition;
3208 } else { // use grid
3209 item->y = flowPosition + ((deltaFlowPosition - item->h) / 2);
3210 item->x = segPosition;
3211 }
3212 }
3213 }
3214
3215 // let the contents contain the new item
3216 if (useItemSize)
3217 rect |= item->rect();
3218 else if (info.flow == QListView::LeftToRight)
3219 rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition);
3220 else // flow == TopToBottom
3221 rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition);
3222
3223 // prepare for next item
3224 flowPosition += deltaFlowPosition; // current position + item width + gap
3225 }
3226 }
3227 Q_ASSERT(item);
3228 batchSavedDeltaSeg = deltaSegPosition;
3229 batchStartRow = info.last + 1;
3230 bool done = (info.last >= rowCount() - 1);
3231 // resize the content area
3232 if (done || !info.bounds.contains(item->rect())) {
3233 contentsSize = rect.size();
3234 if (info.flow == QListView::LeftToRight)
3235 contentsSize.rheight() += info.spacing;
3236 else
3237 contentsSize.rwidth() += info.spacing;
3238 }
3239 if (rect.size().isEmpty())
3240 return;
3241 // resize tree
3242 int insertFrom = info.first;
3243 if (done || info.first == 0) {
3244 initBspTree(rect.size());
3245 insertFrom = 0;
3246 }
3247 // insert items in tree
3248 for (int row = insertFrom; row <= info.last; ++row)
3249 tree.insertLeaf(items.at(row).rect(), row);
3250 // if the new items are visible, update the viewport
3251 QRect changedRect(topLeft, rect.bottomRight());
3252 if (clipRect().intersects(changedRect))
3253 viewport()->update();
3254}
3255
3256QList<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const
3257{
3258 QIconModeViewBase *that = const_cast<QIconModeViewBase*>(this);
3259 QBspTree::Data data(static_cast<void*>(that));
3260 QList<QModelIndex> res;
3261 that->interSectingVector = &res;
3262 that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data);
3263 that->interSectingVector = nullptr;
3264 return res;
3265}
3266
3267QRect QIconModeViewBase::itemsRect(const QList<QModelIndex> &indexes) const
3268{
3269 QRect rect;
3270 for (const auto &index : indexes)
3271 rect |= viewItemRect(indexToListViewItem(index));
3272 return rect;
3273}
3274
3275int QIconModeViewBase::itemIndex(const QListViewItem &item) const
3276{
3277 if (!item.isValid())
3278 return -1;
3279 int i = item.indexHint;
3280 if (i < items.size()) {
3281 if (items.at(i) == item)
3282 return i;
3283 } else {
3284 i = items.size() - 1;
3285 }
3286
3287 int j = i;
3288 int c = items.size();
3289 bool a = true;
3290 bool b = true;
3291
3292 while (a || b) {
3293 if (a) {
3294 if (items.at(i) == item) {
3295 items.at(i).indexHint = i;
3296 return i;
3297 }
3298 a = ++i < c;
3299 }
3300 if (b) {
3301 if (items.at(j) == item) {
3302 items.at(j).indexHint = j;
3303 return j;
3304 }
3305 b = --j > -1;
3306 }
3307 }
3308 return -1;
3309}
3310
3311void QIconModeViewBase::addLeaf(QList<int> &leaf, const QRect &area, uint visited,
3312 QBspTree::Data data)
3313{
3314 QListViewItem *vi;
3315 QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr);
3316 for (int i = 0; i < leaf.size(); ++i) {
3317 int idx = leaf.at(i);
3318 if (idx < 0 || idx >= _this->items.size())
3319 continue;
3320 vi = &_this->items[idx];
3321 Q_ASSERT(vi);
3322 if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) {
3323 QModelIndex index = _this->dd->listViewItemToIndex(*vi);
3324 Q_ASSERT(index.isValid());
3325 _this->interSectingVector->append(index);
3326 vi->visited = visited;
3327 }
3328 }
3329}
3330
3331void QIconModeViewBase::moveItem(int index, const QPoint &dest)
3332{
3333 // does not impact on the bintree itself or the contents rect
3334 QListViewItem *item = &items[index];
3335 QRect rect = item->rect();
3336
3337 // move the item without removing it from the tree
3338 tree.removeLeaf(rect, index);
3339 item->move(dest);
3340 tree.insertLeaf(QRect(dest, rect.size()), index);
3341
3342 // resize the contents area
3343 contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size();
3344
3345 // mark the item as moved
3346 if (moved.size() != items.size())
3347 moved.resize(items.size());
3348 moved.setBit(index, true);
3349}
3350
3351QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const
3352{
3353 int x = pos.x() - (pos.x() % gridSize().width());
3354 int y = pos.y() - (pos.y() % gridSize().height());
3355 return QPoint(x, y);
3356}
3357
3358QPoint QIconModeViewBase::draggedItemsDelta() const
3359{
3360 if (movement() == QListView::Snap) {
3361 QPoint snapdelta = QPoint((offset().x() % gridSize().width()),
3362 (offset().y() % gridSize().height()));
3363 return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta;
3364 }
3365 return draggedItemsPos - pressedPosition();
3366}
3367
3368QRect QIconModeViewBase::draggedItemsRect() const
3369{
3370 QRect rect = itemsRect(draggedItems);
3371 rect.translate(draggedItemsDelta());
3372 return rect;
3373}
3374
3375void QListViewPrivate::scrollElasticBandBy(int dx, int dy)
3376{
3377 if (dx > 0) // right
3378 elasticBand.moveRight(elasticBand.right() + dx);
3379 else if (dx < 0) // left
3380 elasticBand.moveLeft(elasticBand.left() - dx);
3381 if (dy > 0) // down
3382 elasticBand.moveBottom(elasticBand.bottom() + dy);
3383 else if (dy < 0) // up
3384 elasticBand.moveTop(elasticBand.top() - dy);
3385}
3386
3387void QIconModeViewBase::clear()
3388{
3389 tree.destroy();
3390 items.clear();
3391 moved.clear();
3392 batchStartRow = 0;
3393 batchSavedDeltaSeg = 0;
3394}
3395
3396void QIconModeViewBase::updateContentsSize()
3397{
3398 QRect bounding;
3399 for (int i = 0; i < items.size(); ++i)
3400 bounding |= items.at(i).rect();
3401 contentsSize = bounding.size();
3402}
3403
3404/*!
3405 \reimp
3406*/
3407void QListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3408{
3409 Q_D(const QListView);
3410 QAbstractItemView::currentChanged(current, previous);
3411#if QT_CONFIG(accessibility)
3412 if (QAccessible::isActive()) {
3413 if (current.isValid() && hasFocus()) {
3414 int entry = d->accessibleChildIndex(current);
3415 QAccessibleEvent event(this, QAccessible::Focus);
3416 event.setChild(entry);
3417 QAccessible::updateAccessibility(&event);
3418 }
3419 }
3420#endif
3421}
3422
3423/*!
3424 \reimp
3425*/
3426void QListView::selectionChanged(const QItemSelection &selected,
3427 const QItemSelection &deselected)
3428{
3429#if QT_CONFIG(accessibility)
3430 Q_D(const QListView);
3431 if (QAccessible::isActive()) {
3432 // ### does not work properly for selection ranges.
3433 QModelIndex sel = selected.indexes().value(0);
3434 if (sel.isValid()) {
3435 int entry = d->accessibleChildIndex(sel);
3436 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3437 event.setChild(entry);
3438 QAccessible::updateAccessibility(&event);
3439 }
3440 QModelIndex desel = deselected.indexes().value(0);
3441 if (desel.isValid()) {
3442 int entry = d->accessibleChildIndex(desel);
3443 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3444 event.setChild(entry);
3445 QAccessible::updateAccessibility(&event);
3446 }
3447 }
3448#endif
3449 QAbstractItemView::selectionChanged(selected, deselected);
3450}
3451
3452int QListView::visualIndex(const QModelIndex &index) const
3453{
3454 Q_D(const QListView);
3455 d->executePostedLayout();
3456 QListViewItem itm = d->indexToListViewItem(index);
3457 int visualIndex = d->commonListView->itemIndex(itm);
3458 for (const auto &idx : std::as_const(d->hiddenRows)) {
3459 if (idx.row() <= index.row())
3460 --visualIndex;
3461 }
3462 return visualIndex;
3463}
3464
3465
3466/*!
3467 \since 5.2
3468 \reimp
3469*/
3470QSize QListView::viewportSizeHint() const
3471{
3472 Q_D(const QListView);
3473 // We don't have a nice simple size hint for invalid or wrapping list views.
3474 if (!d->model)
3475 return QAbstractItemView::viewportSizeHint();
3476 const int rc = d->model->rowCount();
3477 if (rc == 0 || isWrapping())
3478 return QAbstractItemView::viewportSizeHint();
3479
3480 QStyleOptionViewItem option;
3481 initViewItemOption(&option);
3482
3483 if (uniformItemSizes()) {
3484 QSize sz = d->cachedItemSize;
3485 if (!sz.isValid()) {
3486 QModelIndex idx = d->model->index(0, d->column, d->root);
3487 sz = d->itemSize(option, idx);
3488 }
3489 sz.setHeight(rc * sz.height());
3490 return sz;
3491 }
3492
3493 // Using AdjustToContents with a high number of rows will normally not make sense, so we limit
3494 // this to default 1000 (that is btw the default for QHeaderView::resizeContentsPrecision())
3495 // (By setting the property _q_resizeContentPrecision the user can however override this).
3496 int maximumRows = 1000;
3497 const QVariant userOverrideValue = property("_q_resizeContentPrecision");
3498 if (userOverrideValue.isValid() && userOverrideValue.toInt() > 0) {
3499 maximumRows = userOverrideValue.toInt();
3500 }
3501 const int rowCount = qMin(rc, maximumRows);
3502
3503 int h = 0;
3504 int w = 0;
3505
3506 for (int row = 0; row < rowCount; ++row) {
3507 QModelIndex idx = d->model->index(row, d->column, d->root);
3508 QSize itemSize = d->itemSize(option, idx);
3509 h += itemSize.height();
3510 w = qMax(w, itemSize.width());
3511 }
3512 return QSize(w, h);
3513}
3514
3515QT_END_NAMESPACE
3516
3517#include "moc_qlistview.cpp"
bool qt_sendSpontaneousEvent(QObject *, QEvent *)