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