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