source: trunk/src/gui/itemviews/qlistview.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

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