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 | #ifndef QT_NO_ACCESSIBILITY |
45 | #include <qaccessible.h> |
46 | #endif |
47 | #include <qapplication.h> |
48 | #include <qpainter.h> |
49 | #include <qbitmap.h> |
50 | #include <qdebug.h> |
51 | #if QT_CONFIG(draganddrop) |
52 | #include <qdrag.h> |
53 | #endif |
54 | #include <qevent.h> |
55 | #include <qlist.h> |
56 | #if QT_CONFIG(rubberband) |
57 | #include <qrubberband.h> |
58 | #endif |
59 | #include <qscrollbar.h> |
60 | #include <qstyle.h> |
61 | #include <private/qapplication_p.h> |
62 | #include <private/qlistview_p.h> |
63 | #include <private/qscrollbar_p.h> |
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("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(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 (hint == EnsureVisible && d->viewport->rect().contains(rect)) { |
576 | d->viewport->update(rect); |
577 | return; |
578 | } |
579 | |
580 | if (d->flow == QListView::TopToBottom || d->isWrapping()) // vertical |
581 | verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint)); |
582 | |
583 | if (d->flow == QListView::LeftToRight || d->isWrapping()) // horizontal |
584 | horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint)); |
585 | } |
586 | |
587 | int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect, |
588 | QListView::ScrollHint hint) const |
589 | { |
590 | Q_Q(const QListView); |
591 | const QRect area = viewport->rect(); |
592 | const bool leftOf = q->isRightToLeft() |
593 | ? (rect.left() < area.left()) && (rect.right() < area.right()) |
594 | : rect.left() < area.left(); |
595 | const bool rightOf = q->isRightToLeft() |
596 | ? rect.right() > area.right() |
597 | : (rect.right() > area.right()) && (rect.left() > area.left()); |
598 | return commonListView->horizontalScrollToValue(q->visualIndex(index), hint, leftOf, rightOf, area, rect); |
599 | } |
600 | |
601 | int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect, |
602 | QListView::ScrollHint hint) const |
603 | { |
604 | Q_Q(const QListView); |
605 | const QRect area = viewport->rect(); |
606 | const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); |
607 | const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); |
608 | return commonListView->verticalScrollToValue(q->visualIndex(index), hint, above, below, area, rect); |
609 | } |
610 | |
611 | void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) |
612 | { |
613 | if (!selectionModel) |
614 | return; |
615 | |
616 | QItemSelection selection; |
617 | QModelIndex topLeft; |
618 | int row = 0; |
619 | const int colCount = model->columnCount(root); |
620 | for(; row < model->rowCount(root); ++row) { |
621 | if (isHidden(row)) { |
622 | //it might be the end of a selection range |
623 | if (topLeft.isValid()) { |
624 | QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); |
625 | selection.append(QItemSelectionRange(topLeft, bottomRight)); |
626 | topLeft = QModelIndex(); |
627 | } |
628 | continue; |
629 | } |
630 | |
631 | if (!topLeft.isValid()) //start of a new selection range |
632 | topLeft = model->index(row, 0, root); |
633 | } |
634 | |
635 | if (topLeft.isValid()) { |
636 | //last selected range |
637 | QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); |
638 | selection.append(QItemSelectionRange(topLeft, bottomRight)); |
639 | } |
640 | |
641 | if (!selection.isEmpty()) |
642 | selectionModel->select(selection, command); |
643 | } |
644 | |
645 | /*! |
646 | \reimp |
647 | |
648 | We have a QListView way of knowing what elements are on the viewport |
649 | through the intersectingSet function |
650 | */ |
651 | QItemViewPaintPairs QListViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const |
652 | { |
653 | Q_ASSERT(r); |
654 | Q_Q(const QListView); |
655 | QRect &rect = *r; |
656 | const QRect viewportRect = viewport->rect(); |
657 | QItemViewPaintPairs ret; |
658 | QList<QModelIndex> visibleIndexes = |
659 | intersectingSet(viewportRect.translated(q->horizontalOffset(), q->verticalOffset())); |
660 | std::sort(visibleIndexes.begin(), visibleIndexes.end()); |
661 | for (const auto &index : indexes) { |
662 | if (std::binary_search(visibleIndexes.cbegin(), visibleIndexes.cend(), index)) { |
663 | const QRect current = q->visualRect(index); |
664 | ret.append({current, index}); |
665 | rect |= current; |
666 | } |
667 | } |
668 | QRect clipped = rect & viewportRect; |
669 | rect.setLeft(clipped.left()); |
670 | rect.setRight(clipped.right()); |
671 | return ret; |
672 | } |
673 | |
674 | /*! |
675 | \internal |
676 | */ |
677 | void QListView::reset() |
678 | { |
679 | Q_D(QListView); |
680 | d->clear(); |
681 | d->hiddenRows.clear(); |
682 | QAbstractItemView::reset(); |
683 | } |
684 | |
685 | /*! |
686 | \internal |
687 | */ |
688 | void QListView::setRootIndex(const QModelIndex &index) |
689 | { |
690 | Q_D(QListView); |
691 | d->column = qBound(0, d->column, d->model->columnCount(index) - 1); |
692 | QAbstractItemView::setRootIndex(index); |
693 | // sometimes we get an update before reset() is called |
694 | d->clear(); |
695 | d->hiddenRows.clear(); |
696 | } |
697 | |
698 | /*! |
699 | \internal |
700 | |
701 | Scroll the view contents by \a dx and \a dy. |
702 | */ |
703 | |
704 | void QListView::scrollContentsBy(int dx, int dy) |
705 | { |
706 | Q_D(QListView); |
707 | d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling |
708 | d->commonListView->scrollContentsBy(dx, dy, d->state == QListView::DragSelectingState); |
709 | } |
710 | |
711 | /*! |
712 | \internal |
713 | |
714 | Resize the internal contents to \a width and \a height and set the |
715 | scroll bar ranges accordingly. |
716 | */ |
717 | void QListView::resizeContents(int width, int height) |
718 | { |
719 | Q_D(QListView); |
720 | d->setContentsSize(width, height); |
721 | } |
722 | |
723 | /*! |
724 | \internal |
725 | */ |
726 | QSize QListView::contentsSize() const |
727 | { |
728 | Q_D(const QListView); |
729 | return d->contentsSize(); |
730 | } |
731 | |
732 | /*! |
733 | \reimp |
734 | */ |
735 | void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, |
736 | const QList<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(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(e); |
786 | if (state() == DragSelectingState |
787 | && d->showElasticBand |
788 | && d->selectionMode != SingleSelection |
789 | && d->selectionMode != NoSelection) { |
790 | QRect rect(d->pressedPosition, e->position().toPoint() + QPoint(horizontalOffset(), verticalOffset())); |
791 | rect = rect.normalized(); |
792 | d->viewport->update(d->mapToViewport(rect.united(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(e); |
804 | // #### move this implementation into a dynamic class |
805 | if (d->showElasticBand && d->elasticBand.isValid()) { |
806 | d->viewport->update(d->mapToViewport(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(e->angleDelta().y()) > qAbs(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(d->hbar, &hwe); |
828 | else |
829 | QCoreApplication::sendEvent(d->hbar, &hwe); |
830 | e->setAccepted(hwe.isAccepted()); |
831 | } else { |
832 | QCoreApplication::sendEvent(d->vbar, e); |
833 | } |
834 | } else { |
835 | QCoreApplication::sendEvent(d->hbar, 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(d->batchSize)) { // layout is done |
848 | d->batchLayoutTimer.stop(); |
849 | updateGeometries(); |
850 | d->viewport->update(); |
851 | } |
852 | } |
853 | QAbstractItemView::timerEvent(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(100); // wait 1/10 sec before starting the layout |
880 | } else { |
881 | QAbstractItemView::resizeEvent(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(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(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, &col, &topIndex)) { |
925 | const QList<QModelIndex> selIndexes = selectedIndexes(); |
926 | QList<QPersistentModelIndex> persIndexes; |
927 | persIndexes.reserve(selIndexes.count()); |
928 | |
929 | for (const auto &index : selIndexes) { |
930 | persIndexes.append(index); |
931 | if (index == topIndex) { |
932 | topIndexDropped = true; |
933 | break; |
934 | } |
935 | } |
936 | |
937 | if (!topIndexDropped && !topIndex.isValid()) { |
938 | std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order. |
939 | |
940 | QPersistentModelIndex dropRow = model()->index(row, col, 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(QModelIndex(), pIndex.row(), QModelIndex(), 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 | void QListView::initViewItemOption(QStyleOptionViewItem *option) const |
985 | { |
986 | Q_D(const QListView); |
987 | QAbstractItemView::initViewItemOption(option); |
988 | if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview |
989 | int pm = (d->viewMode == QListView::ListMode |
990 | ? style()->pixelMetric(QStyle::PM_ListViewIconSize, nullptr, this) |
991 | : style()->pixelMetric(QStyle::PM_IconViewIconSize, nullptr, 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 | |
1007 | |
1008 | /*! |
1009 | \reimp |
1010 | */ |
1011 | void QListView::paintEvent(QPaintEvent *e) |
1012 | { |
1013 | Q_D(QListView); |
1014 | if (!d->itemDelegate) |
1015 | return; |
1016 | QStyleOptionViewItem option; |
1017 | initViewItemOption(&option); |
1018 | QPainter painter(d->viewport); |
1019 | |
1020 | const QList<QModelIndex> toBeRendered = |
1021 | d->intersectingSet(e->rect().translated(horizontalOffset(), verticalOffset()), 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(viewport()->size().width(), d->contentsSize().width()) - 2 * d->spacing() |
1038 | : qMax(viewport()->size().height(), d->contentsSize().height()) - 2 * d->spacing(); |
1039 | |
1040 | QList<QModelIndex>::const_iterator end = toBeRendered.constEnd(); |
1041 | for (QList<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) { |
1042 | Q_ASSERT((*it).isValid()); |
1043 | option.rect = visualRect(*it); |
1044 | |
1045 | if (flow() == TopToBottom) |
1046 | option.rect.setWidth(qMin(maxSize, option.rect.width())); |
1047 | else |
1048 | option.rect.setHeight(qMin(maxSize, option.rect.height())); |
1049 | |
1050 | option.state = state; |
1051 | if (selections && selections->isSelected(*it)) |
1052 | option.state |= QStyle::State_Selected; |
1053 | if (enabled) { |
1054 | QPalette::ColorGroup cg; |
1055 | if ((itemModel->flags(*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(QStyle::State_MouseOver, *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(previousRow + 1, 0); r < row; ++r) { |
1076 | if (!d->isHidden(r)) |
1077 | alternateBase = !alternateBase; |
1078 | } |
1079 | } else { |
1080 | alternateBase = (row & 1) != 0; |
1081 | } |
1082 | } |
1083 | option.features.setFlag(QStyleOptionViewItem::Alternate, 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(QStyle::PE_PanelItemViewRow, &option, &painter, this); |
1090 | option.state = oldState; |
1091 | |
1092 | alternateBase = !alternateBase; |
1093 | previousRow = row; |
1094 | } |
1095 | |
1096 | itemDelegateForIndex(*it)->paint(&painter, option, *it); |
1097 | } |
1098 | |
1099 | #if QT_CONFIG(draganddrop) |
1100 | d->commonListView->paintDragDrop(&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(this); |
1108 | opt.shape = QRubberBand::Rectangle; |
1109 | opt.opaque = false; |
1110 | opt.rect = d->mapToViewport(d->elasticBand, false).intersected( |
1111 | d->viewport->rect().adjusted(-16, -16, 16, 16)); |
1112 | painter.save(); |
1113 | style()->drawControl(QStyle::CE_RubberBand, &opt, &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 QList<QModelIndex> intersectVector = d->intersectingSet(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(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, d->column, 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, d->column, 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, d->column, d->root); |
1195 | } |
1196 | |
1197 | const QRect initialRect = rectForIndex(current); |
1198 | QRect rect = initialRect; |
1199 | if (rect.isEmpty()) { |
1200 | return d->model->index(0, d->column, d->root); |
1201 | } |
1202 | if (d->gridSize().isValid()) rect.setSize(d->gridSize()); |
1203 | |
1204 | QSize contents = d->contentsSize(); |
1205 | QList<QModelIndex> intersectVector; |
1206 | |
1207 | switch (cursorAction) { |
1208 | case MoveLeft: |
1209 | while (intersectVector.isEmpty()) { |
1210 | rect.translate(-rect.width(), 0); |
1211 | if (rect.right() <= 0) |
1212 | return current; |
1213 | if (rect.left() < 0) |
1214 | rect.setLeft(0); |
1215 | intersectVector = d->intersectingSet(rect); |
1216 | d->removeCurrentAndDisabled(&intersectVector, current); |
1217 | } |
1218 | return d->closestIndex(initialRect, intersectVector); |
1219 | case MoveRight: |
1220 | while (intersectVector.isEmpty()) { |
1221 | rect.translate(rect.width(), 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(rect); |
1227 | d->removeCurrentAndDisabled(&intersectVector, current); |
1228 | } |
1229 | return d->closestIndex(initialRect, 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(rect.top() - d->viewport->height() + 2 * rect.height()); |
1234 | if (rect.top() < rect.height()) |
1235 | rect.moveTop(rect.height()); |
1236 | Q_FALLTHROUGH(); |
1237 | case MovePrevious: |
1238 | case MoveUp: |
1239 | while (intersectVector.isEmpty()) { |
1240 | rect.translate(0, -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(rect); |
1256 | d->removeCurrentAndDisabled(&intersectVector, current); |
1257 | } |
1258 | return d->closestIndex(initialRect, 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(rect.top() + d->viewport->height() - 2 * rect.height()); |
1263 | if (rect.bottom() > contents.height() - rect.height()) |
1264 | rect.moveBottom(contents.height() - rect.height()); |
1265 | Q_FALLTHROUGH(); |
1266 | case MoveNext: |
1267 | case MoveDown: |
1268 | while (intersectVector.isEmpty()) { |
1269 | rect.translate(0, 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(rect); |
1286 | d->removeCurrentAndDisabled(&intersectVector, current); |
1287 | } |
1288 | return d->closestIndex(initialRect, intersectVector); |
1289 | case MoveHome: |
1290 | return d->model->index(0, d->column, d->root); |
1291 | case MoveEnd: |
1292 | return d->model->index(d->batchStartRow() - 1, d->column, 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(d->contentsSize().width(), d->viewport->width()); |
1340 | int h = qMax(d->contentsSize().height(), d->viewport->height()); |
1341 | if (d->wrap && !QRect(0, 0, w, h).intersects(rect)) |
1342 | return; |
1343 | |
1344 | QItemSelection selection; |
1345 | |
1346 | if (rect.width() == 1 && rect.height() == 1) { |
1347 | const QList<QModelIndex> intersectVector = |
1348 | d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset())); |
1349 | QModelIndex tl; |
1350 | if (!intersectVector.isEmpty()) |
1351 | tl = intersectVector.last(); // special case for mouse press; only select the top item |
1352 | if (tl.isValid() && d->isIndexEnabled(tl)) |
1353 | selection.select(tl, tl); |
1354 | } else { |
1355 | if (state() == DragSelectingState) { // visual selection mode (rubberband selection) |
1356 | selection = d->selection(rect.translated(horizontalOffset(), verticalOffset())); |
1357 | } else { // logical selection mode (key and mouse click selection) |
1358 | QModelIndex tl, br; |
1359 | // get the first item |
1360 | const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1); |
1361 | QList<QModelIndex> intersectVector = d->intersectingSet(topLeft); |
1362 | if (!intersectVector.isEmpty()) |
1363 | tl = intersectVector.last(); |
1364 | // get the last item |
1365 | const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1); |
1366 | intersectVector = d->intersectingSet(bottomRight); |
1367 | if (!intersectVector.isEmpty()) |
1368 | br = intersectVector.last(); |
1369 | |
1370 | // get the ranges |
1371 | if (tl.isValid() && br.isValid() |
1372 | && d->isIndexEnabled(tl) |
1373 | && d->isIndexEnabled(br)) { |
1374 | QRect first = d->cellRectForIndex(tl); |
1375 | QRect last = d->cellRectForIndex(br); |
1376 | QRect middle; |
1377 | if (d->flow == LeftToRight) { |
1378 | QRect &top = first; |
1379 | QRect &bottom = last; |
1380 | // if bottom is above top, swap them |
1381 | if (top.center().y() > bottom.center().y()) { |
1382 | QRect tmp = top; |
1383 | top = bottom; |
1384 | bottom = tmp; |
1385 | } |
1386 | // if the rect are on differnet lines, expand |
1387 | if (top.top() != bottom.top()) { |
1388 | // top rectangle |
1389 | if (isRightToLeft()) |
1390 | top.setLeft(0); |
1391 | else |
1392 | top.setRight(contentsSize().width()); |
1393 | // bottom rectangle |
1394 | if (isRightToLeft()) |
1395 | bottom.setRight(contentsSize().width()); |
1396 | else |
1397 | bottom.setLeft(0); |
1398 | } else if (top.left() > bottom.right()) { |
1399 | if (isRightToLeft()) |
1400 | bottom.setLeft(top.right()); |
1401 | else |
1402 | bottom.setRight(top.left()); |
1403 | } else { |
1404 | if (isRightToLeft()) |
1405 | top.setLeft(bottom.right()); |
1406 | else |
1407 | top.setRight(bottom.left()); |
1408 | } |
1409 | // middle rectangle |
1410 | if (top.bottom() < bottom.top()) { |
1411 | if (gridSize().isValid() && !gridSize().isNull()) |
1412 | middle.setTop(top.top() + gridSize().height()); |
1413 | else |
1414 | middle.setTop(top.bottom() + 1); |
1415 | middle.setLeft(qMin(top.left(), bottom.left())); |
1416 | middle.setBottom(bottom.top() - 1); |
1417 | middle.setRight(qMax(top.right(), bottom.right())); |
1418 | } |
1419 | } else { // TopToBottom |
1420 | QRect &left = first; |
1421 | QRect &right = last; |
1422 | if (left.center().x() > right.center().x()) |
1423 | qSwap(left, right); |
1424 | |
1425 | int ch = contentsSize().height(); |
1426 | if (left.left() != right.left()) { |
1427 | // left rectangle |
1428 | if (isRightToLeft()) |
1429 | left.setTop(0); |
1430 | else |
1431 | left.setBottom(ch); |
1432 | |
1433 | // top rectangle |
1434 | if (isRightToLeft()) |
1435 | right.setBottom(ch); |
1436 | else |
1437 | right.setTop(0); |
1438 | // only set middle if the |
1439 | middle.setTop(0); |
1440 | middle.setBottom(ch); |
1441 | if (gridSize().isValid() && !gridSize().isNull()) |
1442 | middle.setLeft(left.left() + gridSize().width()); |
1443 | else |
1444 | middle.setLeft(left.right() + 1); |
1445 | middle.setRight(right.left() - 1); |
1446 | } else if (left.bottom() < right.top()) { |
1447 | left.setBottom(right.top() - 1); |
1448 | } else { |
1449 | right.setBottom(left.top() - 1); |
1450 | } |
1451 | } |
1452 | |
1453 | // do the selections |
1454 | QItemSelection topSelection = d->selection(first); |
1455 | QItemSelection middleSelection = d->selection(middle); |
1456 | QItemSelection bottomSelection = d->selection(last); |
1457 | // merge |
1458 | selection.merge(topSelection, QItemSelectionModel::Select); |
1459 | selection.merge(middleSelection, QItemSelectionModel::Select); |
1460 | selection.merge(bottomSelection, QItemSelectionModel::Select); |
1461 | } |
1462 | } |
1463 | } |
1464 | |
1465 | d->selectionModel->select(selection, command); |
1466 | } |
1467 | |
1468 | /*! |
1469 | \reimp |
1470 | |
1471 | Since 4.7, the returned region only contains rectangles intersecting |
1472 | (or included in) the viewport. |
1473 | */ |
1474 | QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const |
1475 | { |
1476 | Q_D(const QListView); |
1477 | // ### NOTE: this is a potential bottleneck in non-static mode |
1478 | int c = d->column; |
1479 | QRegion selectionRegion; |
1480 | const QRect &viewportRect = d->viewport->rect(); |
1481 | for (const auto &elem : selection) { |
1482 | if (!elem.isValid()) |
1483 | continue; |
1484 | QModelIndex parent = elem.topLeft().parent(); |
1485 | //we only display the children of the root in a listview |
1486 | //we're not interested in the other model indexes |
1487 | if (parent != d->root) |
1488 | continue; |
1489 | int t = elem.topLeft().row(); |
1490 | int b = elem.bottomRight().row(); |
1491 | if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items |
1492 | for (int r = t; r <= b; ++r) { |
1493 | const QRect &rect = visualRect(d->model->index(r, c, parent)); |
1494 | if (viewportRect.intersects(rect)) |
1495 | selectionRegion += rect; |
1496 | } |
1497 | } else { // in static mode, we can optimize a bit |
1498 | while (t <= b && d->isHidden(t)) ++t; |
1499 | while (b >= t && d->isHidden(b)) --b; |
1500 | const QModelIndex top = d->model->index(t, c, parent); |
1501 | const QModelIndex bottom = d->model->index(b, c, parent); |
1502 | QRect rect(visualRect(top).topLeft(), |
1503 | visualRect(bottom).bottomRight()); |
1504 | if (viewportRect.intersects(rect)) |
1505 | selectionRegion += rect; |
1506 | } |
1507 | } |
1508 | |
1509 | return selectionRegion; |
1510 | } |
1511 | |
1512 | /*! |
1513 | \reimp |
1514 | */ |
1515 | QModelIndexList QListView::selectedIndexes() const |
1516 | { |
1517 | Q_D(const QListView); |
1518 | if (!d->selectionModel) |
1519 | return QModelIndexList(); |
1520 | |
1521 | QModelIndexList viewSelected = d->selectionModel->selectedIndexes(); |
1522 | auto ignorable = [this, d](const QModelIndex &index) { |
1523 | return index.column() != d->column || index.parent() != d->root || isIndexHidden(index); |
1524 | }; |
1525 | viewSelected.erase(std::remove_if(viewSelected.begin(), viewSelected.end(), ignorable), |
1526 | viewSelected.end()); |
1527 | return viewSelected; |
1528 | } |
1529 | |
1530 | /*! |
1531 | \internal |
1532 | |
1533 | Layout the items according to the flow and wrapping properties. |
1534 | */ |
1535 | void QListView::doItemsLayout() |
1536 | { |
1537 | Q_D(QListView); |
1538 | // showing the scroll bars will trigger a resize event, |
1539 | // so we set the state to expanding to avoid |
1540 | // triggering another layout |
1541 | QAbstractItemView::State oldState = state(); |
1542 | setState(ExpandingState); |
1543 | if (d->model->columnCount(d->root) > 0) { // no columns means no contents |
1544 | d->resetBatchStartRow(); |
1545 | if (layoutMode() == SinglePass) |
1546 | d->doItemsLayout(d->model->rowCount(d->root)); // layout everything |
1547 | else if (!d->batchLayoutTimer.isActive()) { |
1548 | if (!d->doItemsLayout(d->batchSize)) // layout is done |
1549 | d->batchLayoutTimer.start(0, this); // do a new batch as fast as possible |
1550 | } |
1551 | } |
1552 | QAbstractItemView::doItemsLayout(); |
1553 | setState(oldState); // restoring the oldState |
1554 | } |
1555 | |
1556 | /*! |
1557 | \reimp |
1558 | */ |
1559 | void QListView::updateGeometries() |
1560 | { |
1561 | Q_D(QListView); |
1562 | if (geometry().isEmpty() || d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) { |
1563 | horizontalScrollBar()->setRange(0, 0); |
1564 | verticalScrollBar()->setRange(0, 0); |
1565 | } else { |
1566 | QModelIndex index = d->model->index(0, d->column, d->root); |
1567 | QStyleOptionViewItem option; |
1568 | initViewItemOption(&option); |
1569 | QSize step = d->itemSize(option, index); |
1570 | d->commonListView->updateHorizontalScrollBar(step); |
1571 | d->commonListView->updateVerticalScrollBar(step); |
1572 | } |
1573 | |
1574 | QAbstractItemView::updateGeometries(); |
1575 | |
1576 | // if the scroll bars are turned off, we resize the contents to the viewport |
1577 | if (d->movement == Static && !d->isWrapping()) { |
1578 | d->layoutChildren(); // we need the viewport size to be updated |
1579 | if (d->flow == TopToBottom) { |
1580 | if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { |
1581 | d->setContentsSize(viewport()->width(), contentsSize().height()); |
1582 | horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway |
1583 | } |
1584 | } else { // LeftToRight |
1585 | if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { |
1586 | d->setContentsSize(contentsSize().width(), viewport()->height()); |
1587 | verticalScrollBar()->setRange(0, 0); // we see all the contents anyway |
1588 | } |
1589 | } |
1590 | } |
1591 | |
1592 | } |
1593 | |
1594 | /*! |
1595 | \reimp |
1596 | */ |
1597 | bool QListView::isIndexHidden(const QModelIndex &index) const |
1598 | { |
1599 | Q_D(const QListView); |
1600 | return (d->isHidden(index.row()) |
1601 | && (index.parent() == d->root) |
1602 | && index.column() == d->column); |
1603 | } |
1604 | |
1605 | /*! |
1606 | \property QListView::modelColumn |
1607 | \brief the column in the model that is visible |
1608 | |
1609 | By default, this property contains 0, indicating that the first |
1610 | column in the model will be shown. |
1611 | */ |
1612 | void QListView::setModelColumn(int column) |
1613 | { |
1614 | Q_D(QListView); |
1615 | if (column < 0 || column >= d->model->columnCount(d->root)) |
1616 | return; |
1617 | d->column = column; |
1618 | d->doDelayedItemsLayout(); |
1619 | } |
1620 | |
1621 | int QListView::modelColumn() const |
1622 | { |
1623 | Q_D(const QListView); |
1624 | return d->column; |
1625 | } |
1626 | |
1627 | /*! |
1628 | \property QListView::uniformItemSizes |
1629 | \brief whether all items in the listview have the same size |
1630 | \since 4.1 |
1631 | |
1632 | This property should only be set to true if it is guaranteed that all items |
1633 | in the view have the same size. This enables the view to do some |
1634 | optimizations for performance purposes. |
1635 | |
1636 | By default, this property is \c false. |
1637 | */ |
1638 | void QListView::setUniformItemSizes(bool enable) |
1639 | { |
1640 | Q_D(QListView); |
1641 | d->uniformItemSizes = enable; |
1642 | } |
1643 | |
1644 | bool QListView::uniformItemSizes() const |
1645 | { |
1646 | Q_D(const QListView); |
1647 | return d->uniformItemSizes; |
1648 | } |
1649 | |
1650 | /*! |
1651 | \property QListView::wordWrap |
1652 | \brief the item text word-wrapping policy |
1653 | \since 4.2 |
1654 | |
1655 | If this property is \c true then the item text is wrapped where |
1656 | necessary at word-breaks; otherwise it is not wrapped at all. |
1657 | This property is \c false by default. |
1658 | |
1659 | Please note that even if wrapping is enabled, the cell will not be |
1660 | expanded to make room for the text. It will print ellipsis for |
1661 | text that cannot be shown, according to the view's |
1662 | \l{QAbstractItemView::}{textElideMode}. |
1663 | */ |
1664 | void QListView::setWordWrap(bool on) |
1665 | { |
1666 | Q_D(QListView); |
1667 | if (d->wrapItemText == on) |
1668 | return; |
1669 | d->wrapItemText = on; |
1670 | d->doDelayedItemsLayout(); |
1671 | } |
1672 | |
1673 | bool QListView::wordWrap() const |
1674 | { |
1675 | Q_D(const QListView); |
1676 | return d->wrapItemText; |
1677 | } |
1678 | |
1679 | /*! |
1680 | \property QListView::selectionRectVisible |
1681 | \brief if the selection rectangle should be visible |
1682 | \since 4.3 |
1683 | |
1684 | If this property is \c true then the selection rectangle is visible; |
1685 | otherwise it will be hidden. |
1686 | |
1687 | \note The selection rectangle will only be visible if the selection mode |
1688 | is in a mode where more than one item can be selected; i.e., it will not |
1689 | draw a selection rectangle if the selection mode is |
1690 | QAbstractItemView::SingleSelection. |
1691 | |
1692 | By default, this property is \c false. |
1693 | */ |
1694 | void QListView::setSelectionRectVisible(bool show) |
1695 | { |
1696 | Q_D(QListView); |
1697 | d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible); |
1698 | d->setSelectionRectVisible(show); |
1699 | } |
1700 | |
1701 | bool QListView::isSelectionRectVisible() const |
1702 | { |
1703 | Q_D(const QListView); |
1704 | return d->isSelectionRectVisible(); |
1705 | } |
1706 | |
1707 | /*! |
1708 | \property QListView::itemAlignment |
1709 | \brief the alignment of each item in its cell |
1710 | \since 5.12 |
1711 | |
1712 | This is only supported in ListMode with TopToBottom flow |
1713 | and with wrapping enabled. |
1714 | The default alignment is 0, which means that an item fills |
1715 | its cell entirely. |
1716 | */ |
1717 | void QListView::setItemAlignment(Qt::Alignment alignment) |
1718 | { |
1719 | Q_D(QListView); |
1720 | if (d->itemAlignment == alignment) |
1721 | return; |
1722 | d->itemAlignment = alignment; |
1723 | if (viewMode() == ListMode && flow() == QListView::TopToBottom && isWrapping()) |
1724 | d->doDelayedItemsLayout(); |
1725 | } |
1726 | |
1727 | Qt::Alignment QListView::itemAlignment() const |
1728 | { |
1729 | Q_D(const QListView); |
1730 | return d->itemAlignment; |
1731 | } |
1732 | |
1733 | /*! |
1734 | \reimp |
1735 | */ |
1736 | bool QListView::event(QEvent *e) |
1737 | { |
1738 | return QAbstractItemView::event(e); |
1739 | } |
1740 | |
1741 | /* |
1742 | * private object implementation |
1743 | */ |
1744 | |
1745 | QListViewPrivate::QListViewPrivate() |
1746 | : QAbstractItemViewPrivate(), |
1747 | commonListView(nullptr), |
1748 | wrap(false), |
1749 | space(0), |
1750 | flow(QListView::TopToBottom), |
1751 | movement(QListView::Static), |
1752 | resizeMode(QListView::Fixed), |
1753 | layoutMode(QListView::SinglePass), |
1754 | viewMode(QListView::ListMode), |
1755 | modeProperties(0), |
1756 | column(0), |
1757 | uniformItemSizes(false), |
1758 | batchSize(100), |
1759 | showElasticBand(false), |
1760 | itemAlignment(Qt::Alignment()) |
1761 | { |
1762 | } |
1763 | |
1764 | QListViewPrivate::~QListViewPrivate() |
1765 | { |
1766 | delete commonListView; |
1767 | } |
1768 | |
1769 | void QListViewPrivate::clear() |
1770 | { |
1771 | // initialization of data structs |
1772 | cachedItemSize = QSize(); |
1773 | commonListView->clear(); |
1774 | } |
1775 | |
1776 | void QListViewPrivate::prepareItemsLayout() |
1777 | { |
1778 | Q_Q(QListView); |
1779 | clear(); |
1780 | |
1781 | //take the size as if there were scrollbar in order to prevent scrollbar to blink |
1782 | layoutBounds = QRect(QPoint(), q->maximumViewportSize()); |
1783 | |
1784 | int frameAroundContents = 0; |
1785 | if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) { |
1786 | QStyleOption option; |
1787 | option.initFrom(q); |
1788 | frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option) * 2; |
1789 | } |
1790 | |
1791 | // maximumViewportSize() already takes scrollbar into account if policy is |
1792 | // Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy |
1793 | // is Qt::ScrollBarAsNeeded |
1794 | int verticalMargin = vbarpolicy==Qt::ScrollBarAsNeeded |
1795 | ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, vbar) + frameAroundContents |
1796 | : 0; |
1797 | int horizontalMargin = hbarpolicy==Qt::ScrollBarAsNeeded |
1798 | ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, hbar) + frameAroundContents |
1799 | : 0; |
1800 | |
1801 | layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin); |
1802 | |
1803 | int rowCount = model->columnCount(root) <= 0 ? 0 : model->rowCount(root); |
1804 | commonListView->setRowCount(rowCount); |
1805 | } |
1806 | |
1807 | /*! |
1808 | \internal |
1809 | */ |
1810 | bool QListViewPrivate::doItemsLayout(int delta) |
1811 | { |
1812 | int max = model->rowCount(root) - 1; |
1813 | int first = batchStartRow(); |
1814 | int last = qMin(first + delta - 1, max); |
1815 | |
1816 | if (first == 0) { |
1817 | layoutChildren(); // make sure the viewport has the right size |
1818 | prepareItemsLayout(); |
1819 | } |
1820 | |
1821 | if (max < 0 || last < first) { |
1822 | return true; // nothing to do |
1823 | } |
1824 | |
1825 | QListViewLayoutInfo info; |
1826 | info.bounds = layoutBounds; |
1827 | info.grid = gridSize(); |
1828 | info.spacing = (info.grid.isValid() ? 0 : spacing()); |
1829 | info.first = first; |
1830 | info.last = last; |
1831 | info.wrap = isWrapping(); |
1832 | info.flow = flow; |
1833 | info.max = max; |
1834 | |
1835 | return commonListView->doBatchedItemLayout(info, max); |
1836 | } |
1837 | |
1838 | QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const |
1839 | { |
1840 | if (!index.isValid() || isHidden(index.row())) |
1841 | return QListViewItem(); |
1842 | |
1843 | return commonListView->indexToListViewItem(index); |
1844 | } |
1845 | |
1846 | QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const |
1847 | { |
1848 | Q_Q(const QListView); |
1849 | if (!rect.isValid()) |
1850 | return rect; |
1851 | |
1852 | QRect result = extend ? commonListView->mapToViewport(rect) : rect; |
1853 | int dx = -q->horizontalOffset(); |
1854 | int dy = -q->verticalOffset(); |
1855 | return result.adjusted(dx, dy, dx, dy); |
1856 | } |
1857 | |
1858 | QModelIndex QListViewPrivate::closestIndex(const QRect &target, |
1859 | const QList<QModelIndex> &candidates) const |
1860 | { |
1861 | int distance = 0; |
1862 | int shortest = INT_MAX; |
1863 | QModelIndex closest; |
1864 | QList<QModelIndex>::const_iterator it = candidates.begin(); |
1865 | |
1866 | for (; it != candidates.end(); ++it) { |
1867 | if (!(*it).isValid()) |
1868 | continue; |
1869 | |
1870 | const QRect indexRect = indexToListViewItem(*it).rect(); |
1871 | |
1872 | //if the center x (or y) position of an item is included in the rect of the other item, |
1873 | //we define the distance between them as the difference in x (or y) of their respective center. |
1874 | // Otherwise, we use the nahattan length between the 2 items |
1875 | if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right()) |
1876 | || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) { |
1877 | //one item's center is at the vertical of the other |
1878 | distance = qAbs(indexRect.center().y() - target.center().y()); |
1879 | } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom()) |
1880 | || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) { |
1881 | //one item's center is at the vertical of the other |
1882 | distance = qAbs(indexRect.center().x() - target.center().x()); |
1883 | } else { |
1884 | distance = (indexRect.center() - target.center()).manhattanLength(); |
1885 | } |
1886 | if (distance < shortest) { |
1887 | shortest = distance; |
1888 | closest = *it; |
1889 | } |
1890 | } |
1891 | return closest; |
1892 | } |
1893 | |
1894 | QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const |
1895 | { |
1896 | Q_Q(const QListView); |
1897 | if (!uniformItemSizes) { |
1898 | const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(index); |
1899 | return delegate ? delegate->sizeHint(option, index) : QSize(); |
1900 | } |
1901 | if (!cachedItemSize.isValid()) { // the last item is probaly the largest, so we use its size |
1902 | int row = model->rowCount(root) - 1; |
1903 | QModelIndex sample = model->index(row, column, root); |
1904 | const QAbstractItemDelegate *delegate = q->itemDelegateForIndex(sample); |
1905 | cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize(); |
1906 | } |
1907 | return cachedItemSize; |
1908 | } |
1909 | |
1910 | QItemSelection QListViewPrivate::selection(const QRect &rect) const |
1911 | { |
1912 | QItemSelection selection; |
1913 | QModelIndex tl, br; |
1914 | const QList<QModelIndex> intersectVector = intersectingSet(rect); |
1915 | QList<QModelIndex>::const_iterator it = intersectVector.begin(); |
1916 | for (; it != intersectVector.end(); ++it) { |
1917 | if (!tl.isValid() && !br.isValid()) { |
1918 | tl = br = *it; |
1919 | } else if ((*it).row() == (tl.row() - 1)) { |
1920 | tl = *it; // expand current range |
1921 | } else if ((*it).row() == (br.row() + 1)) { |
1922 | br = (*it); // expand current range |
1923 | } else { |
1924 | selection.select(tl, br); // select current range |
1925 | tl = br = *it; // start new range |
1926 | } |
1927 | } |
1928 | |
1929 | if (tl.isValid() && br.isValid()) |
1930 | selection.select(tl, br); |
1931 | else if (tl.isValid()) |
1932 | selection.select(tl, tl); |
1933 | else if (br.isValid()) |
1934 | selection.select(br, br); |
1935 | |
1936 | return selection; |
1937 | } |
1938 | |
1939 | #if QT_CONFIG(draganddrop) |
1940 | QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const |
1941 | { |
1942 | if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) |
1943 | return static_cast<QListModeViewBase *>(commonListView)->position(pos, rect, idx); |
1944 | else |
1945 | return QAbstractItemViewPrivate::position(pos, rect, idx); |
1946 | } |
1947 | |
1948 | bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) |
1949 | { |
1950 | if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) |
1951 | return static_cast<QListModeViewBase *>(commonListView)->dropOn(event, dropRow, dropCol, dropIndex); |
1952 | else |
1953 | return QAbstractItemViewPrivate::dropOn(event, dropRow, dropCol, dropIndex); |
1954 | } |
1955 | #endif |
1956 | |
1957 | void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes, |
1958 | const QModelIndex ¤t) const |
1959 | { |
1960 | auto isCurrentOrDisabled = [this, current](const QModelIndex &index) { |
1961 | return !isIndexEnabled(index) || index == current; |
1962 | }; |
1963 | indexes->erase(std::remove_if(indexes->begin(), indexes->end(), |
1964 | isCurrentOrDisabled), |
1965 | indexes->end()); |
1966 | } |
1967 | |
1968 | /* |
1969 | * Common ListView Implementation |
1970 | */ |
1971 | |
1972 | void QCommonListViewBase::appendHiddenRow(int row) |
1973 | { |
1974 | dd->hiddenRows.insert(dd->model->index(row, 0, qq->rootIndex())); |
1975 | } |
1976 | |
1977 | void QCommonListViewBase::removeHiddenRow(int row) |
1978 | { |
1979 | dd->hiddenRows.remove(dd->model->index(row, 0, qq->rootIndex())); |
1980 | } |
1981 | |
1982 | #if QT_CONFIG(draganddrop) |
1983 | void QCommonListViewBase::paintDragDrop(QPainter *painter) |
1984 | { |
1985 | // FIXME: Until the we can provide a proper drop indicator |
1986 | // in IconMode, it makes no sense to show it |
1987 | dd->paintDropIndicator(painter); |
1988 | } |
1989 | #endif |
1990 | |
1991 | QSize QListModeViewBase::viewportSize(const QAbstractItemView *v) |
1992 | { |
1993 | return v->contentsRect().marginsRemoved(v->viewportMargins()).size(); |
1994 | } |
1995 | |
1996 | void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step) |
1997 | { |
1998 | horizontalScrollBar()->d_func()->itemviewChangeSingleStep(step.width() + spacing()); |
1999 | horizontalScrollBar()->setPageStep(viewport()->width()); |
2000 | |
2001 | // If both scroll bars are set to auto, we might end up in a situation with enough space |
2002 | // for the actual content. But still one of the scroll bars will become enabled due to |
2003 | // the other one using the space. The other one will become invisible in the same cycle. |
2004 | // -> Infinite loop, QTBUG-39902 |
2005 | const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded && |
2006 | qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded; |
2007 | |
2008 | const QSize viewportSize = QListModeViewBase::viewportSize(qq); |
2009 | |
2010 | bool verticalWantsToShow = contentsSize.height() > viewportSize.height(); |
2011 | bool horizontalWantsToShow; |
2012 | if (verticalWantsToShow) |
2013 | horizontalWantsToShow = contentsSize.width() > viewportSize.width() - qq->verticalScrollBar()->width(); |
2014 | else |
2015 | horizontalWantsToShow = contentsSize.width() > viewportSize.width(); |
2016 | |
2017 | if (bothScrollBarsAuto && !horizontalWantsToShow) { |
2018 | // break the infinite loop described above by setting the range to 0, 0. |
2019 | // QAbstractScrollArea will then hide the scroll bar for us |
2020 | horizontalScrollBar()->setRange(0, 0); |
2021 | } else { |
2022 | horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width()); |
2023 | } |
2024 | } |
2025 | |
2026 | void QCommonListViewBase::updateVerticalScrollBar(const QSize &step) |
2027 | { |
2028 | verticalScrollBar()->d_func()->itemviewChangeSingleStep(step.height() + spacing()); |
2029 | verticalScrollBar()->setPageStep(viewport()->height()); |
2030 | |
2031 | // If both scroll bars are set to auto, we might end up in a situation with enough space |
2032 | // for the actual content. But still one of the scroll bars will become enabled due to |
2033 | // the other one using the space. The other one will become invisible in the same cycle. |
2034 | // -> Infinite loop, QTBUG-39902 |
2035 | const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded && |
2036 | qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded; |
2037 | |
2038 | const QSize viewportSize = QListModeViewBase::viewportSize(qq); |
2039 | |
2040 | bool horizontalWantsToShow = contentsSize.width() > viewportSize.width(); |
2041 | bool verticalWantsToShow; |
2042 | if (horizontalWantsToShow) |
2043 | verticalWantsToShow = contentsSize.height() > viewportSize.height() - qq->horizontalScrollBar()->height(); |
2044 | else |
2045 | verticalWantsToShow = contentsSize.height() > viewportSize.height(); |
2046 | |
2047 | if (bothScrollBarsAuto && !verticalWantsToShow) { |
2048 | // break the infinite loop described above by setting the range to 0, 0. |
2049 | // QAbstractScrollArea will then hide the scroll bar for us |
2050 | verticalScrollBar()->setRange(0, 0); |
2051 | } else { |
2052 | verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height()); |
2053 | } |
2054 | } |
2055 | |
2056 | void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/) |
2057 | { |
2058 | dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy); |
2059 | } |
2060 | |
2061 | int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint, |
2062 | bool above, bool below, const QRect &area, const QRect &rect) const |
2063 | { |
2064 | int verticalValue = verticalScrollBar()->value(); |
2065 | QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); |
2066 | if (hint == QListView::PositionAtTop || above) |
2067 | verticalValue += adjusted.top(); |
2068 | else if (hint == QListView::PositionAtBottom || below) |
2069 | verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); |
2070 | else if (hint == QListView::PositionAtCenter) |
2071 | verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); |
2072 | return verticalValue; |
2073 | } |
2074 | |
2075 | int QCommonListViewBase::horizontalOffset() const |
2076 | { |
2077 | return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value()); |
2078 | } |
2079 | |
2080 | int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint, |
2081 | bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const |
2082 | { |
2083 | int horizontalValue = horizontalScrollBar()->value(); |
2084 | if (isRightToLeft()) { |
2085 | if (hint == QListView::PositionAtCenter) { |
2086 | horizontalValue += ((area.width() - rect.width()) / 2) - rect.left(); |
2087 | } else { |
2088 | if (leftOf) |
2089 | horizontalValue -= rect.left(); |
2090 | else if (rightOf) |
2091 | horizontalValue += qMin(rect.left(), area.width() - rect.right()); |
2092 | } |
2093 | } else { |
2094 | if (hint == QListView::PositionAtCenter) { |
2095 | horizontalValue += rect.left() - ((area.width()- rect.width()) / 2); |
2096 | } else { |
2097 | if (leftOf) |
2098 | horizontalValue += rect.left(); |
2099 | else if (rightOf) |
2100 | horizontalValue += qMin(rect.left(), rect.right() - area.width()); |
2101 | } |
2102 | } |
2103 | return horizontalValue; |
2104 | } |
2105 | |
2106 | /* |
2107 | * ListMode ListView Implementation |
2108 | */ |
2109 | QListModeViewBase::QListModeViewBase(QListView *q, QListViewPrivate *d) |
2110 | : QCommonListViewBase(q, d) |
2111 | { |
2112 | #if QT_CONFIG(draganddrop) |
2113 | dd->defaultDropAction = Qt::CopyAction; |
2114 | #endif |
2115 | } |
2116 | |
2117 | #if QT_CONFIG(draganddrop) |
2118 | QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const |
2119 | { |
2120 | QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; |
2121 | if (!dd->overwrite) { |
2122 | const int margin = 2; |
2123 | if (pos.x() - rect.left() < margin) { |
2124 | r = QAbstractItemView::AboveItem; // Visually, on the left |
2125 | } else if (rect.right() - pos.x() < margin) { |
2126 | r = QAbstractItemView::BelowItem; // Visually, on the right |
2127 | } else if (rect.contains(pos, true)) { |
2128 | r = QAbstractItemView::OnItem; |
2129 | } |
2130 | } else { |
2131 | QRect touchingRect = rect; |
2132 | touchingRect.adjust(-1, -1, 1, 1); |
2133 | if (touchingRect.contains(pos, false)) { |
2134 | r = QAbstractItemView::OnItem; |
2135 | } |
2136 | } |
2137 | |
2138 | if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled))) |
2139 | r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; |
2140 | |
2141 | return r; |
2142 | } |
2143 | |
2144 | void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event) |
2145 | { |
2146 | if (qq->dragDropMode() == QAbstractItemView::InternalMove |
2147 | && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction))) |
2148 | return; |
2149 | |
2150 | // ignore by default |
2151 | event->ignore(); |
2152 | |
2153 | // can't use indexAt, doesn't account for spacing. |
2154 | QPoint p = event->position().toPoint(); |
2155 | QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); |
2156 | rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing()); |
2157 | const QList<QModelIndex> intersectVector = dd->intersectingSet(rect); |
2158 | QModelIndex index = intersectVector.count() > 0 |
2159 | ? intersectVector.last() : QModelIndex(); |
2160 | dd->hover = index; |
2161 | if (!dd->droppingOnItself(event, index) |
2162 | && dd->canDrop(event)) { |
2163 | |
2164 | if (index.isValid() && dd->showDropIndicator) { |
2165 | QRect rect = qq->visualRect(index); |
2166 | dd->dropIndicatorPosition = position(event->position().toPoint(), rect, index); |
2167 | // if spacing, should try to draw between items, not just next to item. |
2168 | switch (dd->dropIndicatorPosition) { |
2169 | case QAbstractItemView::AboveItem: |
2170 | if (dd->isIndexDropEnabled(index.parent())) { |
2171 | dd->dropIndicatorRect = QRect(rect.left()-dd->spacing(), rect.top(), 0, rect.height()); |
2172 | event->accept(); |
2173 | } else { |
2174 | dd->dropIndicatorRect = QRect(); |
2175 | } |
2176 | break; |
2177 | case QAbstractItemView::BelowItem: |
2178 | if (dd->isIndexDropEnabled(index.parent())) { |
2179 | dd->dropIndicatorRect = QRect(rect.right()+dd->spacing(), rect.top(), 0, rect.height()); |
2180 | event->accept(); |
2181 | } else { |
2182 | dd->dropIndicatorRect = QRect(); |
2183 | } |
2184 | break; |
2185 | case QAbstractItemView::OnItem: |
2186 | if (dd->isIndexDropEnabled(index)) { |
2187 | dd->dropIndicatorRect = rect; |
2188 | event->accept(); |
2189 | } else { |
2190 | dd->dropIndicatorRect = QRect(); |
2191 | } |
2192 | break; |
2193 | case QAbstractItemView::OnViewport: |
2194 | dd->dropIndicatorRect = QRect(); |
2195 | if (dd->isIndexDropEnabled(qq->rootIndex())) { |
2196 | event->accept(); // allow dropping in empty areas |
2197 | } |
2198 | break; |
2199 | } |
2200 | } else { |
2201 | dd->dropIndicatorRect = QRect(); |
2202 | dd->dropIndicatorPosition = QAbstractItemView::OnViewport; |
2203 | if (dd->isIndexDropEnabled(qq->rootIndex())) { |
2204 | event->accept(); // allow dropping in empty areas |
2205 | } |
2206 | } |
2207 | dd->viewport->update(); |
2208 | } // can drop |
2209 | |
2210 | if (dd->shouldAutoScroll(event->position().toPoint())) |
2211 | qq->startAutoScroll(); |
2212 | } |
2213 | |
2214 | /*! |
2215 | If the event hasn't already been accepted, determines the index to drop on. |
2216 | |
2217 | if (row == -1 && col == -1) |
2218 | // append to this drop index |
2219 | else |
2220 | // place at row, col in drop index |
2221 | |
2222 | If it returns \c true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop. |
2223 | \internal |
2224 | */ |
2225 | bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex) |
2226 | { |
2227 | if (event->isAccepted()) |
2228 | return false; |
2229 | |
2230 | QModelIndex index; |
2231 | if (dd->viewport->rect().contains(event->position().toPoint())) { |
2232 | // can't use indexAt, doesn't account for spacing. |
2233 | QPoint p = event->position().toPoint(); |
2234 | QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); |
2235 | rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing()); |
2236 | const QList<QModelIndex> intersectVector = dd->intersectingSet(rect); |
2237 | index = intersectVector.count() > 0 |
2238 | ? intersectVector.last() : QModelIndex(); |
2239 | if (!index.isValid()) |
2240 | index = dd->root; |
2241 | } |
2242 | |
2243 | // If we are allowed to do the drop |
2244 | if (dd->model->supportedDropActions() & event->dropAction()) { |
2245 | int row = -1; |
2246 | int col = -1; |
2247 | if (index != dd->root) { |
2248 | dd->dropIndicatorPosition = position(event->position().toPoint(), qq->visualRect(index), index); |
2249 | switch (dd->dropIndicatorPosition) { |
2250 | case QAbstractItemView::AboveItem: |
2251 | row = index.row(); |
2252 | col = index.column(); |
2253 | index = index.parent(); |
2254 | break; |
2255 | case QAbstractItemView::BelowItem: |
2256 | row = index.row() + 1; |
2257 | col = index.column(); |
2258 | index = index.parent(); |
2259 | break; |
2260 | case QAbstractItemView::OnItem: |
2261 | case QAbstractItemView::OnViewport: |
2262 | break; |
2263 | } |
2264 | } else { |
2265 | dd->dropIndicatorPosition = QAbstractItemView::OnViewport; |
2266 | } |
2267 | *dropIndex = index; |
2268 | *dropRow = row; |
2269 | *dropCol = col; |
2270 | if (!dd->droppingOnItself(event, index)) |
2271 | return true; |
2272 | } |
2273 | return false; |
2274 | } |
2275 | |
2276 | #endif //QT_CONFIG(draganddrop) |
2277 | |
2278 | void QListModeViewBase::updateVerticalScrollBar(const QSize &step) |
2279 | { |
2280 | if (verticalScrollMode() == QAbstractItemView::ScrollPerItem |
2281 | && ((flow() == QListView::TopToBottom && !isWrapping()) |
2282 | || (flow() == QListView::LeftToRight && isWrapping()))) { |
2283 | const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).count() - 1; |
2284 | if (steps > 0) { |
2285 | const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping()); |
2286 | verticalScrollBar()->setSingleStep(1); |
2287 | verticalScrollBar()->setPageStep(pageSteps); |
2288 | verticalScrollBar()->setRange(0, steps - pageSteps); |
2289 | } else { |
2290 | verticalScrollBar()->setRange(0, 0); |
2291 | } |
2292 | // } else if (vertical && d->isWrapping() && d->movement == Static) { |
2293 | // ### wrapped scrolling in flow direction |
2294 | } else { |
2295 | QCommonListViewBase::updateVerticalScrollBar(step); |
2296 | } |
2297 | } |
2298 | |
2299 | void QListModeViewBase::updateHorizontalScrollBar(const QSize &step) |
2300 | { |
2301 | if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem |
2302 | && ((flow() == QListView::TopToBottom && isWrapping()) |
2303 | || (flow() == QListView::LeftToRight && !isWrapping()))) { |
2304 | int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).count() - 1; |
2305 | if (steps > 0) { |
2306 | const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping()); |
2307 | horizontalScrollBar()->setSingleStep(1); |
2308 | horizontalScrollBar()->setPageStep(pageSteps); |
2309 | horizontalScrollBar()->setRange(0, steps - pageSteps); |
2310 | } else { |
2311 | horizontalScrollBar()->setRange(0, 0); |
2312 | } |
2313 | } else { |
2314 | QCommonListViewBase::updateHorizontalScrollBar(step); |
2315 | } |
2316 | } |
2317 | |
2318 | int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint, |
2319 | bool above, bool below, const QRect &area, const QRect &rect) const |
2320 | { |
2321 | if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { |
2322 | int value; |
2323 | if (scrollValueMap.isEmpty()) { |
2324 | value = 0; |
2325 | } else { |
2326 | int scrollBarValue = verticalScrollBar()->value(); |
2327 | int numHidden = 0; |
2328 | for (const auto &idx : qAsConst(dd->hiddenRows)) |
2329 | if (idx.row() <= scrollBarValue) |
2330 | ++numHidden; |
2331 | value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()) - numHidden, flowPositions.count() - 1); |
2332 | } |
2333 | if (above) |
2334 | hint = QListView::PositionAtTop; |
2335 | else if (below) |
2336 | hint = QListView::PositionAtBottom; |
2337 | if (hint == QListView::EnsureVisible) |
2338 | return value; |
2339 | |
2340 | return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height()); |
2341 | } |
2342 | |
2343 | return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect); |
2344 | } |
2345 | |
2346 | int QListModeViewBase::horizontalOffset() const |
2347 | { |
2348 | if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { |
2349 | if (isWrapping()) { |
2350 | if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) { |
2351 | const int max = segmentPositions.count() - 1; |
2352 | int currentValue = qBound(0, horizontalScrollBar()->value(), max); |
2353 | int position = segmentPositions.at(currentValue); |
2354 | int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max); |
2355 | int maximum = segmentPositions.at(maximumValue); |
2356 | return (isRightToLeft() ? maximum - position : position); |
2357 | } |
2358 | } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) { |
2359 | int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value())); |
2360 | int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum())); |
2361 | return (isRightToLeft() ? maximum - position : position); |
2362 | } |
2363 | } |
2364 | return QCommonListViewBase::horizontalOffset(); |
2365 | } |
2366 | |
2367 | int QListModeViewBase::verticalOffset() const |
2368 | { |
2369 | if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { |
2370 | if (isWrapping()) { |
2371 | if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) { |
2372 | int value = verticalScrollBar()->value(); |
2373 | if (value >= segmentPositions.count()) |
2374 | return 0; |
2375 | return segmentPositions.at(value) - spacing(); |
2376 | } |
2377 | } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) { |
2378 | int value = verticalScrollBar()->value(); |
2379 | if (value > scrollValueMap.count()) |
2380 | return 0; |
2381 | return flowPositions.at(scrollValueMap.at(value)) - spacing(); |
2382 | } |
2383 | } |
2384 | return QCommonListViewBase::verticalOffset(); |
2385 | } |
2386 | |
2387 | int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint, |
2388 | bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const |
2389 | { |
2390 | if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem) |
2391 | return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect); |
2392 | |
2393 | int value; |
2394 | if (scrollValueMap.isEmpty()) |
2395 | value = 0; |
2396 | else |
2397 | value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.count() - 1); |
2398 | if (leftOf) |
2399 | hint = QListView::PositionAtTop; |
2400 | else if (rightOf) |
2401 | hint = QListView::PositionAtBottom; |
2402 | if (hint == QListView::EnsureVisible) |
2403 | return value; |
2404 | |
2405 | return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width()); |
2406 | } |
2407 | |
2408 | void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) |
2409 | { |
2410 | // ### reorder this logic |
2411 | const int verticalValue = verticalScrollBar()->value(); |
2412 | const int horizontalValue = horizontalScrollBar()->value(); |
2413 | const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem); |
2414 | const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem); |
2415 | |
2416 | if (isWrapping()) { |
2417 | if (segmentPositions.isEmpty()) |
2418 | return; |
2419 | const int max = segmentPositions.count() - 1; |
2420 | if (horizontal && flow() == QListView::TopToBottom && dx != 0) { |
2421 | int currentValue = qBound(0, horizontalValue, max); |
2422 | int previousValue = qBound(0, currentValue + dx, max); |
2423 | int currentCoordinate = segmentPositions.at(currentValue) - spacing(); |
2424 | int previousCoordinate = segmentPositions.at(previousValue) - spacing(); |
2425 | dx = previousCoordinate - currentCoordinate; |
2426 | } else if (vertical && flow() == QListView::LeftToRight && dy != 0) { |
2427 | int currentValue = qBound(0, verticalValue, max); |
2428 | int previousValue = qBound(0, currentValue + dy, max); |
2429 | int currentCoordinate = segmentPositions.at(currentValue) - spacing(); |
2430 | int previousCoordinate = segmentPositions.at(previousValue) - spacing(); |
2431 | dy = previousCoordinate - currentCoordinate; |
2432 | } |
2433 | } else { |
2434 | if (flowPositions.isEmpty()) |
2435 | return; |
2436 | const int max = scrollValueMap.count() - 1; |
2437 | if (vertical && flow() == QListView::TopToBottom && dy != 0) { |
2438 | int currentValue = qBound(0, verticalValue, max); |
2439 | int previousValue = qBound(0, currentValue + dy, max); |
2440 | int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); |
2441 | int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); |
2442 | dy = previousCoordinate - currentCoordinate; |
2443 | } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) { |
2444 | int currentValue = qBound(0, horizontalValue, max); |
2445 | int previousValue = qBound(0, currentValue + dx, max); |
2446 | int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); |
2447 | int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); |
2448 | dx = previousCoordinate - currentCoordinate; |
2449 | } |
2450 | } |
2451 | QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); |
2452 | } |
2453 | |
2454 | bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) |
2455 | { |
2456 | doStaticLayout(info); |
2457 | return batchStartRow > max; // returning true stops items layout |
2458 | } |
2459 | |
2460 | QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const |
2461 | { |
2462 | if (flowPositions.isEmpty() |
2463 | || segmentPositions.isEmpty() |
2464 | || index.row() >= flowPositions.count() - 1) |
2465 | return QListViewItem(); |
2466 | |
2467 | const int segment = qBinarySearch<int>(segmentStartRows, index.row(), |
2468 | 0, segmentStartRows.count() - 1); |
2469 | |
2470 | |
2471 | QStyleOptionViewItem options; |
2472 | initViewItemOption(&options); |
2473 | options.rect.setSize(contentsSize); |
2474 | QSize size = (uniformItemSizes() && cachedItemSize().isValid()) |
2475 | ? cachedItemSize() : itemSize(options, index); |
2476 | QSize cellSize = size; |
2477 | |
2478 | QPoint pos; |
2479 | if (flow() == QListView::LeftToRight) { |
2480 | pos.setX(flowPositions.at(index.row())); |
2481 | pos.setY(segmentPositions.at(segment)); |
2482 | } else { // TopToBottom |
2483 | pos.setY(flowPositions.at(index.row())); |
2484 | pos.setX(segmentPositions.at(segment)); |
2485 | if (isWrapping()) { // make the items as wide as the segment |
2486 | int right = (segment + 1 >= segmentPositions.count() |
2487 | ? contentsSize.width() |
2488 | : segmentPositions.at(segment + 1)); |
2489 | cellSize.setWidth(right - pos.x()); |
2490 | } else { // make the items as wide as the viewport |
2491 | cellSize.setWidth(qMax(size.width(), viewport()->width() - 2 * spacing())); |
2492 | } |
2493 | } |
2494 | |
2495 | if (dd->itemAlignment & Qt::AlignHorizontal_Mask) { |
2496 | size.setWidth(qMin(size.width(), cellSize.width())); |
2497 | if (dd->itemAlignment & Qt::AlignRight) |
2498 | pos.setX(pos.x() + cellSize.width() - size.width()); |
2499 | if (dd->itemAlignment & Qt::AlignHCenter) |
2500 | pos.setX(pos.x() + (cellSize.width() - size.width()) / 2); |
2501 | } else { |
2502 | size.setWidth(cellSize.width()); |
2503 | } |
2504 | |
2505 | return QListViewItem(QRect(pos, size), index.row()); |
2506 | } |
2507 | |
2508 | QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info) |
2509 | { |
2510 | int x, y; |
2511 | if (info.first == 0) { |
2512 | flowPositions.clear(); |
2513 | segmentPositions.clear(); |
2514 | segmentStartRows.clear(); |
2515 | segmentExtents.clear(); |
2516 | scrollValueMap.clear(); |
2517 | x = info.bounds.left() + info.spacing; |
2518 | y = info.bounds.top() + info.spacing; |
2519 | segmentPositions.append(info.flow == QListView::LeftToRight ? y : x); |
2520 | segmentStartRows.append(0); |
2521 | } else if (info.wrap) { |
2522 | if (info.flow == QListView::LeftToRight) { |
2523 | x = batchSavedPosition; |
2524 | y = segmentPositions.constLast(); |
2525 | } else { // flow == QListView::TopToBottom |
2526 | x = segmentPositions.constLast(); |
2527 | y = batchSavedPosition; |
2528 | } |
2529 | } else { // not first and not wrap |
2530 | if (info.flow == QListView::LeftToRight) { |
2531 | x = batchSavedPosition; |
2532 | y = info.bounds.top() + info.spacing; |
2533 | } else { // flow == QListView::TopToBottom |
2534 | x = info.bounds.left() + info.spacing; |
2535 | y = batchSavedPosition; |
2536 | } |
2537 | } |
2538 | return QPoint(x, y); |
2539 | } |
2540 | |
2541 | /*! |
2542 | \internal |
2543 | */ |
2544 | void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info) |
2545 | { |
2546 | const bool useItemSize = !info.grid.isValid(); |
2547 | const QPoint topLeft = initStaticLayout(info); |
2548 | QStyleOptionViewItem option; |
2549 | initViewItemOption(&option); |
2550 | option.rect = info.bounds; |
2551 | option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing); |
2552 | |
2553 | // The static layout data structures are as follows: |
2554 | // One vector contains the coordinate in the direction of layout flow. |
2555 | // Another vector contains the coordinates of the segments. |
2556 | // A third vector contains the index (model row) of the first item |
2557 | // of each segment. |
2558 | |
2559 | int segStartPosition; |
2560 | int segEndPosition; |
2561 | int deltaFlowPosition; |
2562 | int deltaSegPosition; |
2563 | int deltaSegHint; |
2564 | int flowPosition; |
2565 | int segPosition; |
2566 | |
2567 | if (info.flow == QListView::LeftToRight) { |
2568 | segStartPosition = info.bounds.left(); |
2569 | segEndPosition = info.bounds.width(); |
2570 | flowPosition = topLeft.x(); |
2571 | segPosition = topLeft.y(); |
2572 | deltaFlowPosition = info.grid.width(); // dx |
2573 | deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy |
2574 | deltaSegHint = info.grid.height(); |
2575 | } else { // flow == QListView::TopToBottom |
2576 | segStartPosition = info.bounds.top(); |
2577 | segEndPosition = info.bounds.height(); |
2578 | flowPosition = topLeft.y(); |
2579 | segPosition = topLeft.x(); |
2580 | deltaFlowPosition = info.grid.height(); // dy |
2581 | deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx |
2582 | deltaSegHint = info.grid.width(); |
2583 | } |
2584 | |
2585 | for (int row = info.first; row <= info.last; ++row) { |
2586 | if (isHidden(row)) { // ### |
2587 | flowPositions.append(flowPosition); |
2588 | } else { |
2589 | // if we are not using a grid, we need to find the deltas |
2590 | if (useItemSize) { |
2591 | QSize hint = itemSize(option, modelIndex(row)); |
2592 | if (info.flow == QListView::LeftToRight) { |
2593 | deltaFlowPosition = hint.width() + info.spacing; |
2594 | deltaSegHint = hint.height() + info.spacing; |
2595 | } else { // TopToBottom |
2596 | deltaFlowPosition = hint.height() + info.spacing; |
2597 | deltaSegHint = hint.width() + info.spacing; |
2598 | } |
2599 | } |
2600 | // create new segment |
2601 | if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) { |
2602 | segmentExtents.append(flowPosition); |
2603 | flowPosition = info.spacing + segStartPosition; |
2604 | segPosition += info.spacing + deltaSegPosition; |
2605 | segmentPositions.append(segPosition); |
2606 | segmentStartRows.append(row); |
2607 | deltaSegPosition = 0; |
2608 | } |
2609 | // save the flow position of this item |
2610 | scrollValueMap.append(flowPositions.count()); |
2611 | flowPositions.append(flowPosition); |
2612 | // prepare for the next item |
2613 | deltaSegPosition = qMax(deltaSegHint, deltaSegPosition); |
2614 | flowPosition += info.spacing + deltaFlowPosition; |
2615 | } |
2616 | } |
2617 | // used when laying out next batch |
2618 | batchSavedPosition = flowPosition; |
2619 | batchSavedDeltaSeg = deltaSegPosition; |
2620 | batchStartRow = info.last + 1; |
2621 | if (info.last == info.max) |
2622 | flowPosition -= info.spacing; // remove extra spacing |
2623 | // set the contents size |
2624 | QRect rect = info.bounds; |
2625 | if (info.flow == QListView::LeftToRight) { |
2626 | rect.setRight(segmentPositions.count() == 1 ? flowPosition : info.bounds.right()); |
2627 | rect.setBottom(segPosition + deltaSegPosition); |
2628 | } else { // TopToBottom |
2629 | rect.setRight(segPosition + deltaSegPosition); |
2630 | rect.setBottom(segmentPositions.count() == 1 ? flowPosition : info.bounds.bottom()); |
2631 | } |
2632 | contentsSize = QSize(rect.right(), rect.bottom()); |
2633 | // if it is the last batch, save the end of the segments |
2634 | if (info.last == info.max) { |
2635 | segmentExtents.append(flowPosition); |
2636 | scrollValueMap.append(flowPositions.count()); |
2637 | flowPositions.append(flowPosition); |
2638 | segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX); |
2639 | } |
2640 | // if the new items are visble, update the viewport |
2641 | QRect changedRect(topLeft, rect.bottomRight()); |
2642 | if (clipRect().intersects(changedRect)) |
2643 | viewport()->update(); |
2644 | } |
2645 | |
2646 | /*! |
2647 | \internal |
2648 | Finds the set of items intersecting with \a area. |
2649 | In this function, itemsize is counted from topleft to the start of the next item. |
2650 | */ |
2651 | QList<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const |
2652 | { |
2653 | QList<QModelIndex> ret; |
2654 | int segStartPosition; |
2655 | int segEndPosition; |
2656 | int flowStartPosition; |
2657 | int flowEndPosition; |
2658 | if (flow() == QListView::LeftToRight) { |
2659 | segStartPosition = area.top(); |
2660 | segEndPosition = area.bottom(); |
2661 | flowStartPosition = area.left(); |
2662 | flowEndPosition = area.right(); |
2663 | } else { |
2664 | segStartPosition = area.left(); |
2665 | segEndPosition = area.right(); |
2666 | flowStartPosition = area.top(); |
2667 | flowEndPosition = area.bottom(); |
2668 | } |
2669 | if (segmentPositions.count() < 2 || flowPositions.isEmpty()) |
2670 | return ret; |
2671 | // the last segment position is actually the edge of the last segment |
2672 | const int segLast = segmentPositions.count() - 2; |
2673 | int seg = qBinarySearch<int>(segmentPositions, segStartPosition, 0, segLast + 1); |
2674 | for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) { |
2675 | int first = segmentStartRows.at(seg); |
2676 | int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1; |
2677 | if (segmentExtents.at(seg) < flowStartPosition) |
2678 | continue; |
2679 | int row = qBinarySearch<int>(flowPositions, flowStartPosition, first, last); |
2680 | for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) { |
2681 | if (isHidden(row)) |
2682 | continue; |
2683 | QModelIndex index = modelIndex(row); |
2684 | if (index.isValid()) { |
2685 | if (flow() == QListView::LeftToRight || dd->itemAlignment == Qt::Alignment()) { |
2686 | ret += index; |
2687 | } else { |
2688 | const auto viewItem = indexToListViewItem(index); |
2689 | const int iw = viewItem.width(); |
2690 | const int startPos = qMax(segStartPosition, segmentPositions.at(seg)); |
2691 | const int endPos = qMin(segmentPositions.at(seg + 1), segEndPosition); |
2692 | if (endPos >= viewItem.x && startPos < viewItem.x + iw) |
2693 | ret += index; |
2694 | } |
2695 | } |
2696 | #if 0 // for debugging |
2697 | else |
2698 | qWarning("intersectingSet: row %d was invalid" , row); |
2699 | #endif |
2700 | } |
2701 | } |
2702 | return ret; |
2703 | } |
2704 | |
2705 | void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &) |
2706 | { |
2707 | dd->doDelayedItemsLayout(); |
2708 | } |
2709 | |
2710 | |
2711 | QRect QListModeViewBase::mapToViewport(const QRect &rect) const |
2712 | { |
2713 | if (isWrapping()) |
2714 | return rect; |
2715 | // If the listview is in "listbox-mode", the items are as wide as the view. |
2716 | // But we don't shrink the items. |
2717 | QRect result = rect; |
2718 | if (flow() == QListView::TopToBottom) { |
2719 | result.setLeft(spacing()); |
2720 | result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing())); |
2721 | } else { // LeftToRight |
2722 | result.setTop(spacing()); |
2723 | result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing())); |
2724 | } |
2725 | return result; |
2726 | } |
2727 | |
2728 | int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const |
2729 | { |
2730 | QList<int> positions; |
2731 | if (wrap) |
2732 | positions = segmentPositions; |
2733 | else if (!flowPositions.isEmpty()) { |
2734 | positions.reserve(scrollValueMap.size()); |
2735 | for (int itemShown : scrollValueMap) |
2736 | positions.append(flowPositions.at(itemShown)); |
2737 | } |
2738 | if (positions.isEmpty() || bounds <= length) |
2739 | return positions.count(); |
2740 | if (uniformItemSizes()) { |
2741 | for (int i = 1; i < positions.count(); ++i) |
2742 | if (positions.at(i) > 0) |
2743 | return length / positions.at(i); |
2744 | return 0; // all items had height 0 |
2745 | } |
2746 | int pageSteps = 0; |
2747 | int steps = positions.count() - 1; |
2748 | int max = qMax(length, bounds); |
2749 | int min = qMin(length, bounds); |
2750 | int pos = min - (max - positions.constLast()); |
2751 | |
2752 | while (pos >= 0 && steps > 0) { |
2753 | pos -= (positions.at(steps) - positions.at(steps - 1)); |
2754 | if (pos >= 0) //this item should be visible |
2755 | ++pageSteps; |
2756 | --steps; |
2757 | } |
2758 | |
2759 | // at this point we know that positions has at least one entry |
2760 | return qMax(pageSteps, 1); |
2761 | } |
2762 | |
2763 | int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, |
2764 | QAbstractItemView::ScrollHint hint, |
2765 | Qt::Orientation orientation, bool wrap, int itemExtent) const |
2766 | { |
2767 | if (index < 0) |
2768 | return scrollValue; |
2769 | |
2770 | itemExtent += spacing(); |
2771 | QList<int> hiddenRows = dd->hiddenRowIds(); |
2772 | std::sort(hiddenRows.begin(), hiddenRows.end()); |
2773 | int hiddenRowsBefore = 0; |
2774 | for (int i = 0; i < hiddenRows.size() - 1; ++i) |
2775 | if (hiddenRows.at(i) > index + hiddenRowsBefore) |
2776 | break; |
2777 | else |
2778 | ++hiddenRowsBefore; |
2779 | if (!wrap) { |
2780 | int topIndex = index; |
2781 | const int bottomIndex = topIndex; |
2782 | const int bottomCoordinate = flowPositions.at(index + hiddenRowsBefore); |
2783 | while (topIndex > 0 && |
2784 | (bottomCoordinate - flowPositions.at(topIndex + hiddenRowsBefore - 1) + itemExtent) <= (viewportSize)) { |
2785 | topIndex--; |
2786 | // will the next one be a hidden row -> skip |
2787 | while (hiddenRowsBefore > 0 && hiddenRows.at(hiddenRowsBefore - 1) >= topIndex + hiddenRowsBefore - 1) |
2788 | hiddenRowsBefore--; |
2789 | } |
2790 | |
2791 | const int itemCount = bottomIndex - topIndex + 1; |
2792 | switch (hint) { |
2793 | case QAbstractItemView::PositionAtTop: |
2794 | return index; |
2795 | case QAbstractItemView::PositionAtBottom: |
2796 | return index - itemCount + 1; |
2797 | case QAbstractItemView::PositionAtCenter: |
2798 | return index - (itemCount / 2); |
2799 | default: |
2800 | break; |
2801 | } |
2802 | } else { // wrapping |
2803 | Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight |
2804 | ? Qt::Horizontal : Qt::Vertical); |
2805 | if (flowOrientation == orientation) { // scrolling in the "flow" direction |
2806 | // ### wrapped scrolling in the flow direction |
2807 | return flowPositions.at(index + hiddenRowsBefore); // ### always pixel based for now |
2808 | } else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction |
2809 | int segment = qBinarySearch<int>(segmentStartRows, index, 0, segmentStartRows.count() - 1); |
2810 | int leftSegment = segment; |
2811 | const int rightSegment = leftSegment; |
2812 | const int bottomCoordinate = segmentPositions.at(segment); |
2813 | |
2814 | while (leftSegment > scrollValue && |
2815 | (bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) { |
2816 | leftSegment--; |
2817 | } |
2818 | |
2819 | const int segmentCount = rightSegment - leftSegment + 1; |
2820 | switch (hint) { |
2821 | case QAbstractItemView::PositionAtTop: |
2822 | return segment; |
2823 | case QAbstractItemView::PositionAtBottom: |
2824 | return segment - segmentCount + 1; |
2825 | case QAbstractItemView::PositionAtCenter: |
2826 | return segment - (segmentCount / 2); |
2827 | default: |
2828 | break; |
2829 | } |
2830 | } |
2831 | } |
2832 | return scrollValue; |
2833 | } |
2834 | |
2835 | void QListModeViewBase::clear() |
2836 | { |
2837 | flowPositions.clear(); |
2838 | segmentPositions.clear(); |
2839 | segmentStartRows.clear(); |
2840 | segmentExtents.clear(); |
2841 | batchSavedPosition = 0; |
2842 | batchStartRow = 0; |
2843 | batchSavedDeltaSeg = 0; |
2844 | } |
2845 | |
2846 | /* |
2847 | * IconMode ListView Implementation |
2848 | */ |
2849 | |
2850 | void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index) |
2851 | { |
2852 | if (index.row() >= items.count()) |
2853 | return; |
2854 | const QSize oldContents = contentsSize; |
2855 | qq->update(index); // update old position |
2856 | moveItem(index.row(), position); |
2857 | qq->update(index); // update new position |
2858 | |
2859 | if (contentsSize != oldContents) |
2860 | dd->viewUpdateGeometries(); // update the scroll bars |
2861 | } |
2862 | |
2863 | void QIconModeViewBase::appendHiddenRow(int row) |
2864 | { |
2865 | if (row >= 0 && row < items.count()) //remove item |
2866 | tree.removeLeaf(items.at(row).rect(), row); |
2867 | QCommonListViewBase::appendHiddenRow(row); |
2868 | } |
2869 | |
2870 | void QIconModeViewBase::removeHiddenRow(int row) |
2871 | { |
2872 | QCommonListViewBase::removeHiddenRow(row); |
2873 | if (row >= 0 && row < items.count()) //insert item |
2874 | tree.insertLeaf(items.at(row).rect(), row); |
2875 | } |
2876 | |
2877 | #if QT_CONFIG(draganddrop) |
2878 | bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions) |
2879 | { |
2880 | // This function does the same thing as in QAbstractItemView::startDrag(), |
2881 | // plus adding viewitems to the draggedItems list. |
2882 | // We need these items to draw the drag items |
2883 | QModelIndexList indexes = dd->selectionModel->selectedIndexes(); |
2884 | if (indexes.count() > 0 ) { |
2885 | if (viewport()->acceptDrops()) { |
2886 | QModelIndexList::ConstIterator it = indexes.constBegin(); |
2887 | for (; it != indexes.constEnd(); ++it) |
2888 | if (dd->model->flags(*it) & Qt::ItemIsDragEnabled |
2889 | && (*it).column() == dd->column) |
2890 | draggedItems.push_back(*it); |
2891 | } |
2892 | |
2893 | QRect rect; |
2894 | QPixmap pixmap = dd->renderToPixmap(indexes, &rect); |
2895 | rect.adjust(horizontalOffset(), verticalOffset(), 0, 0); |
2896 | QDrag *drag = new QDrag(qq); |
2897 | drag->setMimeData(dd->model->mimeData(indexes)); |
2898 | drag->setPixmap(pixmap); |
2899 | drag->setHotSpot(dd->pressedPosition - rect.topLeft()); |
2900 | dd->dropEventMoved = false; |
2901 | Qt::DropAction action = drag->exec(supportedActions, dd->defaultDropAction); |
2902 | draggedItems.clear(); |
2903 | // delete item, unless it has already been moved internally (see filterDropEvent) |
2904 | if (action == Qt::MoveAction && !dd->dropEventMoved) |
2905 | dd->clearOrRemove(); |
2906 | dd->dropEventMoved = false; |
2907 | } |
2908 | return true; |
2909 | } |
2910 | |
2911 | bool QIconModeViewBase::filterDropEvent(QDropEvent *e) |
2912 | { |
2913 | if (e->source() != qq) |
2914 | return false; |
2915 | |
2916 | const QSize contents = contentsSize; |
2917 | QPoint offset(horizontalOffset(), verticalOffset()); |
2918 | QPoint end = e->position().toPoint() + offset; |
2919 | if (qq->acceptDrops()) { |
2920 | const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; |
2921 | const QList<QModelIndex> &dropIndices = intersectingSet(QRect(end, QSize(1, 1))); |
2922 | for (const QModelIndex &index : dropIndices) |
2923 | if ((index.flags() & dropableFlags) == dropableFlags) |
2924 | return false; |
2925 | } |
2926 | QPoint start = dd->pressedPosition; |
2927 | QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start); |
2928 | const QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes(); |
2929 | for (const auto &index : indexes) { |
2930 | QRect rect = dd->rectForIndex(index); |
2931 | viewport()->update(dd->mapToViewport(rect, false)); |
2932 | QPoint dest = rect.topLeft() + delta; |
2933 | if (qq->isRightToLeft()) |
2934 | dest.setX(dd->flipX(dest.x()) - rect.width()); |
2935 | moveItem(index.row(), dest); |
2936 | qq->update(index); |
2937 | } |
2938 | dd->stopAutoScroll(); |
2939 | draggedItems.clear(); |
2940 | dd->emitIndexesMoved(indexes); |
2941 | e->accept(); // we have handled the event |
2942 | // if the size has not grown, we need to check if it has shrinked |
2943 | if (contentsSize != contents) { |
2944 | if ((contentsSize.width() <= contents.width() |
2945 | || contentsSize.height() <= contents.height())) { |
2946 | updateContentsSize(); |
2947 | } |
2948 | dd->viewUpdateGeometries(); |
2949 | } |
2950 | return true; |
2951 | } |
2952 | |
2953 | bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e) |
2954 | { |
2955 | viewport()->update(draggedItemsRect()); // erase the area |
2956 | draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items |
2957 | return QCommonListViewBase::filterDragLeaveEvent(e); |
2958 | } |
2959 | |
2960 | bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e) |
2961 | { |
2962 | const bool wasAccepted = e->isAccepted(); |
2963 | |
2964 | // ignore by default |
2965 | e->ignore(); |
2966 | |
2967 | if (e->source() != qq || !dd->canDrop(e)) { |
2968 | // restore previous acceptance on failure |
2969 | e->setAccepted(wasAccepted); |
2970 | return false; |
2971 | } |
2972 | |
2973 | // get old dragged items rect |
2974 | QRect itemsRect = this->itemsRect(draggedItems); |
2975 | viewport()->update(itemsRect.translated(draggedItemsDelta())); |
2976 | // update position |
2977 | draggedItemsPos = e->position().toPoint(); |
2978 | // get new items rect |
2979 | viewport()->update(itemsRect.translated(draggedItemsDelta())); |
2980 | // set the item under the cursor to current |
2981 | QModelIndex index; |
2982 | if (movement() == QListView::Snap) { |
2983 | QRect rect(snapToGrid(e->position().toPoint() + offset()), gridSize()); |
2984 | const QList<QModelIndex> intersectVector = intersectingSet(rect); |
2985 | index = intersectVector.count() > 0 ? intersectVector.last() : QModelIndex(); |
2986 | } else { |
2987 | index = qq->indexAt(e->position().toPoint()); |
2988 | } |
2989 | // check if we allow drops here |
2990 | if (draggedItems.contains(index)) |
2991 | e->accept(); // allow changing item position |
2992 | else if (dd->model->flags(index) & Qt::ItemIsDropEnabled) |
2993 | e->accept(); // allow dropping on dropenabled items |
2994 | else if (!index.isValid()) |
2995 | e->accept(); // allow dropping in empty areas |
2996 | |
2997 | // the event was treated. do autoscrolling |
2998 | if (dd->shouldAutoScroll(e->position().toPoint())) |
2999 | dd->startAutoScroll(); |
3000 | return true; |
3001 | } |
3002 | #endif // QT_CONFIG(draganddrop) |
3003 | |
3004 | void QIconModeViewBase::setRowCount(int rowCount) |
3005 | { |
3006 | tree.create(qMax(rowCount - hiddenCount(), 0)); |
3007 | } |
3008 | |
3009 | void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) |
3010 | { |
3011 | if (scrollElasticBand) |
3012 | dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy); |
3013 | |
3014 | QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); |
3015 | if (!draggedItems.isEmpty()) |
3016 | viewport()->update(draggedItemsRect().translated(dx, dy)); |
3017 | } |
3018 | |
3019 | void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
3020 | { |
3021 | if (column() >= topLeft.column() && column() <= bottomRight.column()) { |
3022 | QStyleOptionViewItem option; |
3023 | initViewItemOption(&option); |
3024 | const int bottom = qMin(items.count(), bottomRight.row() + 1); |
3025 | const bool useItemSize = !dd->grid.isValid(); |
3026 | for (int row = topLeft.row(); row < bottom; ++row) |
3027 | { |
3028 | QSize s = itemSize(option, modelIndex(row)); |
3029 | if (!useItemSize) |
3030 | { |
3031 | s.setWidth(qMin(dd->grid.width(), s.width())); |
3032 | s.setHeight(qMin(dd->grid.height(), s.height())); |
3033 | } |
3034 | items[row].resize(s); |
3035 | } |
3036 | } |
3037 | } |
3038 | |
3039 | bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) |
3040 | { |
3041 | if (info.last >= items.count()) { |
3042 | //first we create the items |
3043 | QStyleOptionViewItem option; |
3044 | initViewItemOption(&option); |
3045 | for (int row = items.count(); row <= info.last; ++row) { |
3046 | QSize size = itemSize(option, modelIndex(row)); |
3047 | QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos |
3048 | items.append(item); |
3049 | } |
3050 | doDynamicLayout(info); |
3051 | } |
3052 | return (batchStartRow > max); // done |
3053 | } |
3054 | |
3055 | QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const |
3056 | { |
3057 | if (index.isValid() && index.row() < items.count()) |
3058 | return items.at(index.row()); |
3059 | return QListViewItem(); |
3060 | } |
3061 | |
3062 | void QIconModeViewBase::initBspTree(const QSize &contents) |
3063 | { |
3064 | // remove all items from the tree |
3065 | int leafCount = tree.leafCount(); |
3066 | for (int l = 0; l < leafCount; ++l) |
3067 | tree.leaf(l).clear(); |
3068 | // we have to get the bounding rect of the items before we can initialize the tree |
3069 | QBspTree::Node::Type type = QBspTree::Node::Both; // 2D |
3070 | // simple heuristics to get better bsp |
3071 | if (contents.height() / contents.width() >= 3) |
3072 | type = QBspTree::Node::HorizontalPlane; |
3073 | else if (contents.width() / contents.height() >= 3) |
3074 | type = QBspTree::Node::VerticalPlane; |
3075 | // build tree for the bounding rect (not just the contents rect) |
3076 | tree.init(QRect(0, 0, contents.width(), contents.height()), type); |
3077 | } |
3078 | |
3079 | QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info) |
3080 | { |
3081 | int x, y; |
3082 | if (info.first == 0) { |
3083 | x = info.bounds.x() + info.spacing; |
3084 | y = info.bounds.y() + info.spacing; |
3085 | items.reserve(rowCount() - hiddenCount()); |
3086 | } else { |
3087 | int idx = info.first - 1; |
3088 | while (idx > 0 && !items.at(idx).isValid()) |
3089 | --idx; |
3090 | const QListViewItem &item = items.at(idx); |
3091 | x = item.x; |
3092 | y = item.y; |
3093 | if (info.flow == QListView::LeftToRight) |
3094 | x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing; |
3095 | else |
3096 | y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing; |
3097 | } |
3098 | return QPoint(x, y); |
3099 | } |
3100 | |
3101 | /*! |
3102 | \internal |
3103 | */ |
3104 | void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info) |
3105 | { |
3106 | const bool useItemSize = !info.grid.isValid(); |
3107 | const QPoint topLeft = initDynamicLayout(info); |
3108 | |
3109 | int segStartPosition; |
3110 | int segEndPosition; |
3111 | int deltaFlowPosition; |
3112 | int deltaSegPosition; |
3113 | int deltaSegHint; |
3114 | int flowPosition; |
3115 | int segPosition; |
3116 | |
3117 | if (info.flow == QListView::LeftToRight) { |
3118 | segStartPosition = info.bounds.left() + info.spacing; |
3119 | segEndPosition = info.bounds.right(); |
3120 | deltaFlowPosition = info.grid.width(); // dx |
3121 | deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy |
3122 | deltaSegHint = info.grid.height(); |
3123 | flowPosition = topLeft.x(); |
3124 | segPosition = topLeft.y(); |
3125 | } else { // flow == QListView::TopToBottom |
3126 | segStartPosition = info.bounds.top() + info.spacing; |
3127 | segEndPosition = info.bounds.bottom(); |
3128 | deltaFlowPosition = info.grid.height(); // dy |
3129 | deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx |
3130 | deltaSegHint = info.grid.width(); |
3131 | flowPosition = topLeft.y(); |
3132 | segPosition = topLeft.x(); |
3133 | } |
3134 | |
3135 | if (moved.count() != items.count()) |
3136 | moved.resize(items.count()); |
3137 | |
3138 | QRect rect(QPoint(), topLeft); |
3139 | QListViewItem *item = nullptr; |
3140 | for (int row = info.first; row <= info.last; ++row) { |
3141 | item = &items[row]; |
3142 | if (isHidden(row)) { |
3143 | item->invalidate(); |
3144 | } else { |
3145 | // if we are not using a grid, we need to find the deltas |
3146 | if (useItemSize) { |
3147 | if (info.flow == QListView::LeftToRight) |
3148 | deltaFlowPosition = item->w + info.spacing; |
3149 | else |
3150 | deltaFlowPosition = item->h + info.spacing; |
3151 | } else { |
3152 | item->w = qMin<int>(info.grid.width(), item->w); |
3153 | item->h = qMin<int>(info.grid.height(), item->h); |
3154 | } |
3155 | |
3156 | // create new segment |
3157 | if (info.wrap |
3158 | && flowPosition + deltaFlowPosition > segEndPosition |
3159 | && flowPosition > segStartPosition) { |
3160 | flowPosition = segStartPosition; |
3161 | segPosition += deltaSegPosition; |
3162 | if (useItemSize) |
3163 | deltaSegPosition = 0; |
3164 | } |
3165 | // We must delay calculation of the seg adjustment, as this item |
3166 | // may have caused a wrap to occur |
3167 | if (useItemSize) { |
3168 | if (info.flow == QListView::LeftToRight) |
3169 | deltaSegHint = item->h + info.spacing; |
3170 | else |
3171 | deltaSegHint = item->w + info.spacing; |
3172 | deltaSegPosition = qMax(deltaSegPosition, deltaSegHint); |
3173 | } |
3174 | |
3175 | // set the position of the item |
3176 | // ### idealy we should have some sort of alignment hint for the item |
3177 | // ### (normally that would be a point between the icon and the text) |
3178 | if (!moved.testBit(row)) { |
3179 | if (info.flow == QListView::LeftToRight) { |
3180 | if (useItemSize) { |
3181 | item->x = flowPosition; |
3182 | item->y = segPosition; |
3183 | } else { // use grid |
3184 | item->x = flowPosition + ((deltaFlowPosition - item->w) / 2); |
3185 | item->y = segPosition; |
3186 | } |
3187 | } else { // TopToBottom |
3188 | if (useItemSize) { |
3189 | item->y = flowPosition; |
3190 | item->x = segPosition; |
3191 | } else { // use grid |
3192 | item->y = flowPosition + ((deltaFlowPosition - item->h) / 2); |
3193 | item->x = segPosition; |
3194 | } |
3195 | } |
3196 | } |
3197 | |
3198 | // let the contents contain the new item |
3199 | if (useItemSize) |
3200 | rect |= item->rect(); |
3201 | else if (info.flow == QListView::LeftToRight) |
3202 | rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition); |
3203 | else // flow == TopToBottom |
3204 | rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition); |
3205 | |
3206 | // prepare for next item |
3207 | flowPosition += deltaFlowPosition; // current position + item width + gap |
3208 | } |
3209 | } |
3210 | batchSavedDeltaSeg = deltaSegPosition; |
3211 | batchStartRow = info.last + 1; |
3212 | bool done = (info.last >= rowCount() - 1); |
3213 | // resize the content area |
3214 | if (done || !info.bounds.contains(item->rect())) { |
3215 | contentsSize = rect.size(); |
3216 | if (info.flow == QListView::LeftToRight) |
3217 | contentsSize.rheight() += info.spacing; |
3218 | else |
3219 | contentsSize.rwidth() += info.spacing; |
3220 | } |
3221 | if (rect.size().isEmpty()) |
3222 | return; |
3223 | // resize tree |
3224 | int insertFrom = info.first; |
3225 | if (done || info.first == 0) { |
3226 | initBspTree(rect.size()); |
3227 | insertFrom = 0; |
3228 | } |
3229 | // insert items in tree |
3230 | for (int row = insertFrom; row <= info.last; ++row) |
3231 | tree.insertLeaf(items.at(row).rect(), row); |
3232 | // if the new items are visble, update the viewport |
3233 | QRect changedRect(topLeft, rect.bottomRight()); |
3234 | if (clipRect().intersects(changedRect)) |
3235 | viewport()->update(); |
3236 | } |
3237 | |
3238 | QList<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const |
3239 | { |
3240 | QIconModeViewBase *that = const_cast<QIconModeViewBase*>(this); |
3241 | QBspTree::Data data(static_cast<void*>(that)); |
3242 | QList<QModelIndex> res; |
3243 | that->interSectingVector = &res; |
3244 | that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data); |
3245 | that->interSectingVector = nullptr; |
3246 | return res; |
3247 | } |
3248 | |
3249 | QRect QIconModeViewBase::itemsRect(const QList<QModelIndex> &indexes) const |
3250 | { |
3251 | QList<QModelIndex>::const_iterator it = indexes.begin(); |
3252 | QListViewItem item = indexToListViewItem(*it); |
3253 | QRect rect(item.x, item.y, item.w, item.h); |
3254 | for (; it != indexes.end(); ++it) { |
3255 | item = indexToListViewItem(*it); |
3256 | rect |= viewItemRect(item); |
3257 | } |
3258 | return rect; |
3259 | } |
3260 | |
3261 | int QIconModeViewBase::itemIndex(const QListViewItem &item) const |
3262 | { |
3263 | if (!item.isValid()) |
3264 | return -1; |
3265 | int i = item.indexHint; |
3266 | if (i < items.count()) { |
3267 | if (items.at(i) == item) |
3268 | return i; |
3269 | } else { |
3270 | i = items.count() - 1; |
3271 | } |
3272 | |
3273 | int j = i; |
3274 | int c = items.count(); |
3275 | bool a = true; |
3276 | bool b = true; |
3277 | |
3278 | while (a || b) { |
3279 | if (a) { |
3280 | if (items.at(i) == item) { |
3281 | items.at(i).indexHint = i; |
3282 | return i; |
3283 | } |
3284 | a = ++i < c; |
3285 | } |
3286 | if (b) { |
3287 | if (items.at(j) == item) { |
3288 | items.at(j).indexHint = j; |
3289 | return j; |
3290 | } |
3291 | b = --j > -1; |
3292 | } |
3293 | } |
3294 | return -1; |
3295 | } |
3296 | |
3297 | void QIconModeViewBase::addLeaf(QList<int> &leaf, const QRect &area, uint visited, |
3298 | QBspTree::Data data) |
3299 | { |
3300 | QListViewItem *vi; |
3301 | QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr); |
3302 | for (int i = 0; i < leaf.count(); ++i) { |
3303 | int idx = leaf.at(i); |
3304 | if (idx < 0 || idx >= _this->items.count()) |
3305 | continue; |
3306 | vi = &_this->items[idx]; |
3307 | Q_ASSERT(vi); |
3308 | if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) { |
3309 | QModelIndex index = _this->dd->listViewItemToIndex(*vi); |
3310 | Q_ASSERT(index.isValid()); |
3311 | _this->interSectingVector->append(index); |
3312 | vi->visited = visited; |
3313 | } |
3314 | } |
3315 | } |
3316 | |
3317 | void QIconModeViewBase::moveItem(int index, const QPoint &dest) |
3318 | { |
3319 | // does not impact on the bintree itself or the contents rect |
3320 | QListViewItem *item = &items[index]; |
3321 | QRect rect = item->rect(); |
3322 | |
3323 | // move the item without removing it from the tree |
3324 | tree.removeLeaf(rect, index); |
3325 | item->move(dest); |
3326 | tree.insertLeaf(QRect(dest, rect.size()), index); |
3327 | |
3328 | // resize the contents area |
3329 | contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size(); |
3330 | |
3331 | // mark the item as moved |
3332 | if (moved.count() != items.count()) |
3333 | moved.resize(items.count()); |
3334 | moved.setBit(index, true); |
3335 | } |
3336 | |
3337 | QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const |
3338 | { |
3339 | int x = pos.x() - (pos.x() % gridSize().width()); |
3340 | int y = pos.y() - (pos.y() % gridSize().height()); |
3341 | return QPoint(x, y); |
3342 | } |
3343 | |
3344 | QPoint QIconModeViewBase::draggedItemsDelta() const |
3345 | { |
3346 | if (movement() == QListView::Snap) { |
3347 | QPoint snapdelta = QPoint((offset().x() % gridSize().width()), |
3348 | (offset().y() % gridSize().height())); |
3349 | return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta; |
3350 | } |
3351 | return draggedItemsPos - pressedPosition(); |
3352 | } |
3353 | |
3354 | QRect QIconModeViewBase::draggedItemsRect() const |
3355 | { |
3356 | QRect rect = itemsRect(draggedItems); |
3357 | rect.translate(draggedItemsDelta()); |
3358 | return rect; |
3359 | } |
3360 | |
3361 | void QListViewPrivate::scrollElasticBandBy(int dx, int dy) |
3362 | { |
3363 | if (dx > 0) // right |
3364 | elasticBand.moveRight(elasticBand.right() + dx); |
3365 | else if (dx < 0) // left |
3366 | elasticBand.moveLeft(elasticBand.left() - dx); |
3367 | if (dy > 0) // down |
3368 | elasticBand.moveBottom(elasticBand.bottom() + dy); |
3369 | else if (dy < 0) // up |
3370 | elasticBand.moveTop(elasticBand.top() - dy); |
3371 | } |
3372 | |
3373 | void QIconModeViewBase::clear() |
3374 | { |
3375 | tree.destroy(); |
3376 | items.clear(); |
3377 | moved.clear(); |
3378 | batchStartRow = 0; |
3379 | batchSavedDeltaSeg = 0; |
3380 | } |
3381 | |
3382 | void QIconModeViewBase::updateContentsSize() |
3383 | { |
3384 | QRect bounding; |
3385 | for (int i = 0; i < items.count(); ++i) |
3386 | bounding |= items.at(i).rect(); |
3387 | contentsSize = bounding.size(); |
3388 | } |
3389 | |
3390 | /*! |
3391 | \reimp |
3392 | */ |
3393 | void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
3394 | { |
3395 | #ifndef QT_NO_ACCESSIBILITY |
3396 | if (QAccessible::isActive()) { |
3397 | if (current.isValid()) { |
3398 | int entry = visualIndex(current); |
3399 | QAccessibleEvent event(this, QAccessible::Focus); |
3400 | event.setChild(entry); |
3401 | QAccessible::updateAccessibility(&event); |
3402 | } |
3403 | } |
3404 | #endif |
3405 | QAbstractItemView::currentChanged(current, previous); |
3406 | } |
3407 | |
3408 | /*! |
3409 | \reimp |
3410 | */ |
3411 | void QListView::selectionChanged(const QItemSelection &selected, |
3412 | const QItemSelection &deselected) |
3413 | { |
3414 | #ifndef QT_NO_ACCESSIBILITY |
3415 | if (QAccessible::isActive()) { |
3416 | // ### does not work properly for selection ranges. |
3417 | QModelIndex sel = selected.indexes().value(0); |
3418 | if (sel.isValid()) { |
3419 | int entry = visualIndex(sel); |
3420 | QAccessibleEvent event(this, QAccessible::SelectionAdd); |
3421 | event.setChild(entry); |
3422 | QAccessible::updateAccessibility(&event); |
3423 | } |
3424 | QModelIndex desel = deselected.indexes().value(0); |
3425 | if (desel.isValid()) { |
3426 | int entry = visualIndex(desel); |
3427 | QAccessibleEvent event(this, QAccessible::SelectionRemove); |
3428 | event.setChild(entry); |
3429 | QAccessible::updateAccessibility(&event); |
3430 | } |
3431 | } |
3432 | #endif |
3433 | QAbstractItemView::selectionChanged(selected, deselected); |
3434 | } |
3435 | |
3436 | int QListView::visualIndex(const QModelIndex &index) const |
3437 | { |
3438 | Q_D(const QListView); |
3439 | d->executePostedLayout(); |
3440 | QListViewItem itm = d->indexToListViewItem(index); |
3441 | int visualIndex = d->commonListView->itemIndex(itm); |
3442 | for (const auto &idx : qAsConst(d->hiddenRows)) { |
3443 | if (idx.row() <= index.row()) |
3444 | --visualIndex; |
3445 | } |
3446 | return visualIndex; |
3447 | } |
3448 | |
3449 | |
3450 | /*! |
3451 | \since 5.2 |
3452 | \reimp |
3453 | */ |
3454 | QSize QListView::viewportSizeHint() const |
3455 | { |
3456 | return QAbstractItemView::viewportSizeHint(); |
3457 | } |
3458 | |
3459 | QT_END_NAMESPACE |
3460 | |
3461 | #include "moc_qlistview.cpp" |
3462 | |