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