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