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