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