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