/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qlistview.h" | |
#ifndef QT_NO_LISTVIEW | |
#include <qabstractitemdelegate.h> | |
#include <qapplication.h> | |
#include <qpainter.h> | |
#include <qbitmap.h> | |
#include <qvector.h> | |
#include <qstyle.h> | |
#include <qevent.h> | |
#include <qscrollbar.h> | |
#include <qrubberband.h> | |
#include <private/qlistview_p.h> | |
#include <qdebug.h> | |
#ifndef QT_NO_ACCESSIBILITY | |
#include <qaccessible.h> | |
#endif | |
QT_BEGIN_NAMESPACE | |
/*! | |
\class QListView | |
\brief The QListView class provides a list or icon view onto a model. | |
\ingroup model-view | |
\ingroup advanced | |
A QListView presents items stored in a model, either as a simple | |
non-hierarchical list, or as a collection of icons. This class is used | |
to provide lists and icon views that were previously provided by the | |
\c QListBox and \c QIconView classes, but using the more flexible | |
approach provided by Qt's model/view architecture. | |
The QListView class is one of the \l{Model/View Classes} | |
and is part of Qt's \l{Model/View Programming}{model/view framework}. | |
This view does not display horizontal or vertical headers; to display | |
a list of items with a horizontal header, use QTreeView instead. | |
QListView implements the interfaces defined by the | |
QAbstractItemView class to allow it to display data provided by | |
models derived from the QAbstractItemModel class. | |
Items in a list view can be displayed using one of two view modes: | |
In \l ListMode, the items are displayed in the form of a simple list; | |
in \l IconMode, the list view takes the form of an \e{icon view} in | |
which the items are displayed with icons like files in a file manager. | |
By default, the list view is in \l ListMode. To change the view mode, | |
use the setViewMode() function, and to determine the current view mode, | |
use viewMode(). | |
Items in these views are laid out in the direction specified by the | |
flow() of the list view. The items may be fixed in place, or allowed | |
to move, depending on the view's movement() state. | |
If the items in the model cannot be completely laid out in the | |
direction of flow, they can be wrapped at the boundary of the view | |
widget; this depends on isWrapping(). This property is useful when the | |
items are being represented by an icon view. | |
The resizeMode() and layoutMode() govern how and when the items are | |
laid out. Items are spaced according to their spacing(), and can exist | |
within a notional grid of size specified by gridSize(). The items can | |
be rendered as large or small icons depending on their iconSize(). | |
\table 100% | |
\row \o \inlineimage windowsxp-listview.png Screenshot of a Windows XP style list view | |
\o \inlineimage macintosh-listview.png Screenshot of a Macintosh style table view | |
\o \inlineimage plastique-listview.png Screenshot of a Plastique style table view | |
\row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} list view. | |
\o A \l{Macintosh Style Widget Gallery}{Macintosh style} list view. | |
\o A \l{Plastique Style Widget Gallery}{Plastique style} list view. | |
\endtable | |
\section1 Improving Performance | |
It is possible to give the view hints about the data it is handling in order | |
to improve its performance when displaying large numbers of items. One approach | |
that can be taken for views that are intended to display items with equal sizes | |
is to set the \l uniformItemSizes property to true. | |
\sa {View Classes}, QTreeView, QTableView, QListWidget | |
*/ | |
/*! | |
\enum QListView::ViewMode | |
\value ListMode The items are laid out using TopToBottom flow, with Small size and Static movement | |
\value IconMode The items are laid out using LeftToRight flow, with Large size and Free movement | |
*/ | |
/*! | |
\enum QListView::Movement | |
\value Static The items cannot be moved by the user. | |
\value Free The items can be moved freely by the user. | |
\value Snap The items snap to the specified grid when moved; see | |
setGridSize(). | |
*/ | |
/*! | |
\enum QListView::Flow | |
\value LeftToRight The items are laid out in the view from the left | |
to the right. | |
\value TopToBottom The items are laid out in the view from the top | |
to the bottom. | |
*/ | |
/*! | |
\enum QListView::ResizeMode | |
\value Fixed The items will only be laid out the first time the view is shown. | |
\value Adjust The items will be laid out every time the view is resized. | |
*/ | |
/*! | |
\enum QListView::LayoutMode | |
\value SinglePass The items are laid out all at once. | |
\value Batched The items are laid out in batches of \l batchSize items. | |
\sa batchSize | |
*/ | |
/*! | |
\since 4.2 | |
\fn void QListView::indexesMoved(const QModelIndexList &indexes) | |
This signal is emitted when the specified \a indexes are moved in the view. | |
*/ | |
/*! | |
Creates a new QListView with the given \a parent to view a model. | |
Use setModel() to set the model. | |
*/ | |
QListView::QListView(QWidget *parent) | |
: QAbstractItemView(*new QListViewPrivate, parent) | |
{ | |
setViewMode(ListMode); | |
setSelectionMode(SingleSelection); | |
setAttribute(Qt::WA_MacShowFocusRect); | |
Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change | |
d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed | |
} | |
/*! | |
\internal | |
*/ | |
QListView::QListView(QListViewPrivate &dd, QWidget *parent) | |
: QAbstractItemView(dd, parent) | |
{ | |
setViewMode(ListMode); | |
setSelectionMode(SingleSelection); | |
setAttribute(Qt::WA_MacShowFocusRect); | |
Q_D(QListView); // We rely on a qobject_cast for PM_DefaultFrameWidth to change | |
d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed | |
} | |
/*! | |
Destroys the view. | |
*/ | |
QListView::~QListView() | |
{ | |
} | |
/*! | |
\property QListView::movement | |
\brief whether the items can be moved freely, are snapped to a | |
grid, or cannot be moved at all. | |
This property determines how the user can move the items in the | |
view. \l Static means that the items can't be moved the user. \l | |
Free means that the user can drag and drop the items to any | |
position in the view. \l Snap means that the user can drag and | |
drop the items, but only to the positions in a notional grid | |
signified by the gridSize property. | |
Setting this property when the view is visible will cause the | |
items to be laid out again. | |
By default, this property is set to \l Static. | |
\sa gridSize, resizeMode, viewMode | |
*/ | |
void QListView::setMovement(Movement movement) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::Movement); | |
d->movement = movement; | |
#ifndef QT_NO_DRAGANDDROP | |
bool movable = (movement != Static); | |
setDragEnabled(movable); | |
d->viewport->setAcceptDrops(movable); | |
#endif | |
d->doDelayedItemsLayout(); | |
} | |
QListView::Movement QListView::movement() const | |
{ | |
Q_D(const QListView); | |
return d->movement; | |
} | |
/*! | |
\property QListView::flow | |
\brief which direction the items layout should flow. | |
If this property is \l LeftToRight, the items will be laid out left | |
to right. If the \l isWrapping property is true, the layout will wrap | |
when it reaches the right side of the visible area. If this | |
property is \l TopToBottom, the items will be laid out from the top | |
of the visible area, wrapping when it reaches the bottom. | |
Setting this property when the view is visible will cause the | |
items to be laid out again. | |
By default, this property is set to \l TopToBottom. | |
\sa viewMode | |
*/ | |
void QListView::setFlow(Flow flow) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::Flow); | |
d->flow = flow; | |
d->doDelayedItemsLayout(); | |
} | |
QListView::Flow QListView::flow() const | |
{ | |
Q_D(const QListView); | |
return d->flow; | |
} | |
/*! | |
\property QListView::isWrapping | |
\brief whether the items layout should wrap. | |
This property holds whether the layout should wrap when there is | |
no more space in the visible area. The point at which the layout wraps | |
depends on the \l flow property. | |
Setting this property when the view is visible will cause the | |
items to be laid out again. | |
By default, this property is false. | |
\sa viewMode | |
*/ | |
void QListView::setWrapping(bool enable) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::Wrap); | |
d->setWrapping(enable); | |
d->doDelayedItemsLayout(); | |
} | |
bool QListView::isWrapping() const | |
{ | |
Q_D(const QListView); | |
return d->isWrapping(); | |
} | |
/*! | |
\property QListView::resizeMode | |
\brief whether the items are laid out again when the view is resized. | |
If this property is \l Adjust, the items will be laid out again | |
when the view is resized. If the value is \l Fixed, the items will | |
not be laid out when the view is resized. | |
By default, this property is set to \l Fixed. | |
\sa movement, gridSize, viewMode | |
*/ | |
void QListView::setResizeMode(ResizeMode mode) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::ResizeMode); | |
d->resizeMode = mode; | |
} | |
QListView::ResizeMode QListView::resizeMode() const | |
{ | |
Q_D(const QListView); | |
return d->resizeMode; | |
} | |
/*! | |
\property QListView::layoutMode | |
\brief determines whether the layout of items should happen immediately or be delayed. | |
This property holds the layout mode for the items. When the mode | |
is \l SinglePass (the default), the items are laid out all in one go. | |
When the mode is \l Batched, the items are laid out in batches of \l batchSize | |
items, while processing events. This makes it possible to | |
instantly view and interact with the visible items while the rest | |
are being laid out. | |
\sa viewMode | |
*/ | |
void QListView::setLayoutMode(LayoutMode mode) | |
{ | |
Q_D(QListView); | |
d->layoutMode = mode; | |
} | |
QListView::LayoutMode QListView::layoutMode() const | |
{ | |
Q_D(const QListView); | |
return d->layoutMode; | |
} | |
/*! | |
\property QListView::spacing | |
\brief the space around the items in the layout | |
This property is the size of the empty space that is padded around | |
an item in the layout. | |
Setting this property when the view is visible will cause the | |
items to be laid out again. | |
By default, this property contains a value of 0. | |
\sa viewMode | |
*/ | |
// ### Qt5: Use same semantic as layouts (spacing is the size of space | |
// *between* items) | |
void QListView::setSpacing(int space) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::Spacing); | |
d->setSpacing(space); | |
d->doDelayedItemsLayout(); | |
} | |
int QListView::spacing() const | |
{ | |
Q_D(const QListView); | |
return d->spacing(); | |
} | |
/*! | |
\property QListView::batchSize | |
\brief the number of items laid out in each batch if \l layoutMode is | |
set to \l Batched | |
The default value is 100. | |
\since 4.2 | |
*/ | |
void QListView::setBatchSize(int batchSize) | |
{ | |
Q_D(QListView); | |
if (batchSize <= 0) { | |
qWarning("Invalid batchSize (%d)", batchSize); | |
return; | |
} | |
d->batchSize = batchSize; | |
} | |
int QListView::batchSize() const | |
{ | |
Q_D(const QListView); | |
return d->batchSize; | |
} | |
/*! | |
\property QListView::gridSize | |
\brief the size of the layout grid | |
This property is the size of the grid in which the items are laid | |
out. The default is an empty size which means that there is no | |
grid and the layout is not done in a grid. Setting this property | |
to a non-empty size switches on the grid layout. (When a grid | |
layout is in force the \l spacing property is ignored.) | |
Setting this property when the view is visible will cause the | |
items to be laid out again. | |
\sa viewMode | |
*/ | |
void QListView::setGridSize(const QSize &size) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::GridSize); | |
d->setGridSize(size); | |
d->doDelayedItemsLayout(); | |
} | |
QSize QListView::gridSize() const | |
{ | |
Q_D(const QListView); | |
return d->gridSize(); | |
} | |
/*! | |
\property QListView::viewMode | |
\brief the view mode of the QListView. | |
This property will change the other unset properties to conform | |
with the set view mode. QListView-specific properties that have already been set | |
will not be changed, unless clearPropertyFlags() has been called. | |
Setting the view mode will enable or disable drag and drop based on the | |
selected movement. For ListMode, the default movement is \l Static | |
(drag and drop disabled); for IconMode, the default movement is | |
\l Free (drag and drop enabled). | |
\sa isWrapping, spacing, gridSize, flow, movement, resizeMode | |
*/ | |
void QListView::setViewMode(ViewMode mode) | |
{ | |
Q_D(QListView); | |
if (d->commonListView && d->viewMode == mode) | |
return; | |
d->viewMode = mode; | |
delete d->commonListView; | |
if (mode == ListMode) { | |
d->commonListView = new QListModeViewBase(this, d); | |
if (!(d->modeProperties & QListViewPrivate::Wrap)) | |
d->setWrapping(false); | |
if (!(d->modeProperties & QListViewPrivate::Spacing)) | |
d->setSpacing(0); | |
if (!(d->modeProperties & QListViewPrivate::GridSize)) | |
d->setGridSize(QSize()); | |
if (!(d->modeProperties & QListViewPrivate::Flow)) | |
d->flow = TopToBottom; | |
if (!(d->modeProperties & QListViewPrivate::Movement)) | |
d->movement = Static; | |
if (!(d->modeProperties & QListViewPrivate::ResizeMode)) | |
d->resizeMode = Fixed; | |
if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) | |
d->showElasticBand = false; | |
} else { | |
d->commonListView = new QIconModeViewBase(this, d); | |
if (!(d->modeProperties & QListViewPrivate::Wrap)) | |
d->setWrapping(true); | |
if (!(d->modeProperties & QListViewPrivate::Spacing)) | |
d->setSpacing(0); | |
if (!(d->modeProperties & QListViewPrivate::GridSize)) | |
d->setGridSize(QSize()); | |
if (!(d->modeProperties & QListViewPrivate::Flow)) | |
d->flow = LeftToRight; | |
if (!(d->modeProperties & QListViewPrivate::Movement)) | |
d->movement = Free; | |
if (!(d->modeProperties & QListViewPrivate::ResizeMode)) | |
d->resizeMode = Fixed; | |
if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) | |
d->showElasticBand = true; | |
} | |
#ifndef QT_NO_DRAGANDDROP | |
bool movable = (d->movement != Static); | |
setDragEnabled(movable); | |
setAcceptDrops(movable); | |
#endif | |
d->clear(); | |
d->doDelayedItemsLayout(); | |
} | |
QListView::ViewMode QListView::viewMode() const | |
{ | |
Q_D(const QListView); | |
return d->viewMode; | |
} | |
/*! | |
Clears the QListView-specific property flags. See \l{viewMode}. | |
Properties inherited from QAbstractItemView are not covered by the | |
property flags. Specifically, \l{QAbstractItemView::dragEnabled} | |
{dragEnabled} and \l{QAbstractItemView::acceptDrops} | |
{acceptsDrops} are computed by QListView when calling | |
setMovement() or setViewMode(). | |
*/ | |
void QListView::clearPropertyFlags() | |
{ | |
Q_D(QListView); | |
d->modeProperties = 0; | |
} | |
/*! | |
Returns true if the \a row is hidden; otherwise returns false. | |
*/ | |
bool QListView::isRowHidden(int row) const | |
{ | |
Q_D(const QListView); | |
return d->isHidden(row); | |
} | |
/*! | |
If \a hide is true, the given \a row will be hidden; otherwise | |
the \a row will be shown. | |
*/ | |
void QListView::setRowHidden(int row, bool hide) | |
{ | |
Q_D(QListView); | |
const bool hidden = d->isHidden(row); | |
if (hide && !hidden) | |
d->commonListView->appendHiddenRow(row); | |
else if (!hide && hidden) | |
d->commonListView->removeHiddenRow(row); | |
d->doDelayedItemsLayout(); | |
d->viewport->update(); | |
} | |
/*! | |
\reimp | |
*/ | |
QRect QListView::visualRect(const QModelIndex &index) const | |
{ | |
Q_D(const QListView); | |
return d->mapToViewport(rectForIndex(index)); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::scrollTo(const QModelIndex &index, ScrollHint hint) | |
{ | |
Q_D(QListView); | |
if (index.parent() != d->root || index.column() != d->column) | |
return; | |
const QRect rect = visualRect(index); | |
if (hint == EnsureVisible && d->viewport->rect().contains(rect)) { | |
d->viewport->update(rect); | |
return; | |
} | |
if (d->flow == QListView::TopToBottom || d->isWrapping()) // vertical | |
verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint)); | |
if (d->flow == QListView::LeftToRight || d->isWrapping()) // horizontal | |
horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint)); | |
} | |
int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect, | |
QListView::ScrollHint hint) const | |
{ | |
Q_Q(const QListView); | |
const QRect area = viewport->rect(); | |
const bool leftOf = q->isRightToLeft() | |
? (rect.left() < area.left()) && (rect.right() < area.right()) | |
: rect.left() < area.left(); | |
const bool rightOf = q->isRightToLeft() | |
? rect.right() > area.right() | |
: (rect.right() > area.right()) && (rect.left() > area.left()); | |
return commonListView->horizontalScrollToValue(q->visualIndex(index), hint, leftOf, rightOf, area, rect); | |
} | |
int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect, | |
QListView::ScrollHint hint) const | |
{ | |
Q_Q(const QListView); | |
const QRect area = viewport->rect(); | |
const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top()); | |
const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom()); | |
return commonListView->verticalScrollToValue(q->visualIndex(index), hint, above, below, area, rect); | |
} | |
void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command) | |
{ | |
if (!selectionModel) | |
return; | |
QItemSelection selection; | |
QModelIndex topLeft; | |
int row = 0; | |
const int colCount = model->columnCount(root); | |
for(; row < model->rowCount(root); ++row) { | |
if (isHidden(row)) { | |
//it might be the end of a selection range | |
if (topLeft.isValid()) { | |
QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); | |
selection.append(QItemSelectionRange(topLeft, bottomRight)); | |
topLeft = QModelIndex(); | |
} | |
continue; | |
} | |
if (!topLeft.isValid()) //start of a new selection range | |
topLeft = model->index(row, 0, root); | |
} | |
if (topLeft.isValid()) { | |
//last selected range | |
QModelIndex bottomRight = model->index(row - 1, colCount - 1, root); | |
selection.append(QItemSelectionRange(topLeft, bottomRight)); | |
} | |
if (!selection.isEmpty()) | |
selectionModel->select(selection, command); | |
} | |
/*! | |
\reimp | |
We have a QListView way of knowing what elements are on the viewport | |
through the intersectingSet function | |
*/ | |
QItemViewPaintPairs QListViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const | |
{ | |
Q_ASSERT(r); | |
Q_Q(const QListView); | |
QRect &rect = *r; | |
const QRect viewportRect = viewport->rect(); | |
QItemViewPaintPairs ret; | |
const QSet<QModelIndex> visibleIndexes = intersectingSet(viewportRect).toList().toSet(); | |
for (int i = 0; i < indexes.count(); ++i) { | |
const QModelIndex &index = indexes.at(i); | |
if (visibleIndexes.contains(index)) { | |
const QRect current = q->visualRect(index); | |
ret += qMakePair(current, index); | |
rect |= current; | |
} | |
} | |
rect &= viewportRect; | |
return ret; | |
} | |
/*! | |
\internal | |
*/ | |
void QListView::reset() | |
{ | |
Q_D(QListView); | |
d->clear(); | |
d->hiddenRows.clear(); | |
QAbstractItemView::reset(); | |
} | |
/*! | |
\internal | |
*/ | |
void QListView::setRootIndex(const QModelIndex &index) | |
{ | |
Q_D(QListView); | |
d->column = qBound(0, d->column, d->model->columnCount(index) - 1); | |
QAbstractItemView::setRootIndex(index); | |
// sometimes we get an update before reset() is called | |
d->clear(); | |
d->hiddenRows.clear(); | |
} | |
/*! | |
\internal | |
Scroll the view contents by \a dx and \a dy. | |
*/ | |
void QListView::scrollContentsBy(int dx, int dy) | |
{ | |
Q_D(QListView); | |
d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling | |
d->commonListView->scrollContentsBy(dx, dy, d->state == QListView::DragSelectingState); | |
} | |
/*! | |
\internal | |
Resize the internal contents to \a width and \a height and set the | |
scroll bar ranges accordingly. | |
*/ | |
void QListView::resizeContents(int width, int height) | |
{ | |
Q_D(QListView); | |
d->setContentsSize(width, height); | |
} | |
/*! | |
\internal | |
*/ | |
QSize QListView::contentsSize() const | |
{ | |
Q_D(const QListView); | |
return d->contentsSize(); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) | |
{ | |
d_func()->commonListView->dataChanged(topLeft, bottomRight); | |
QAbstractItemView::dataChanged(topLeft, bottomRight); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::rowsInserted(const QModelIndex &parent, int start, int end) | |
{ | |
Q_D(QListView); | |
// ### be smarter about inserted items | |
d->clear(); | |
d->doDelayedItemsLayout(); | |
QAbstractItemView::rowsInserted(parent, start, end); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) | |
{ | |
Q_D(QListView); | |
// if the parent is above d->root in the tree, nothing will happen | |
QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); | |
if (parent == d->root) { | |
for (int i = d->hiddenRows.count() - 1; i >= 0; --i) { | |
int hiddenRow = d->hiddenRows.at(i).row(); | |
if (hiddenRow >= start && hiddenRow <= end) { | |
d->hiddenRows.remove(i); | |
} | |
} | |
} | |
d->clear(); | |
d->doDelayedItemsLayout(); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::mouseMoveEvent(QMouseEvent *e) | |
{ | |
if (!isVisible()) | |
return; | |
Q_D(QListView); | |
QAbstractItemView::mouseMoveEvent(e); | |
if (state() == DragSelectingState | |
&& d->showElasticBand | |
&& d->selectionMode != SingleSelection | |
&& d->selectionMode != NoSelection) { | |
QRect rect(d->pressedPosition, e->pos() + QPoint(horizontalOffset(), verticalOffset())); | |
rect = rect.normalized(); | |
d->viewport->update(d->mapToViewport(rect.united(d->elasticBand))); | |
d->elasticBand = rect; | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::mouseReleaseEvent(QMouseEvent *e) | |
{ | |
Q_D(QListView); | |
QAbstractItemView::mouseReleaseEvent(e); | |
// #### move this implementation into a dynamic class | |
if (d->showElasticBand && d->elasticBand.isValid()) { | |
d->viewport->update(d->mapToViewport(d->elasticBand)); | |
d->elasticBand = QRect(); | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::timerEvent(QTimerEvent *e) | |
{ | |
Q_D(QListView); | |
if (e->timerId() == d->batchLayoutTimer.timerId()) { | |
if (d->doItemsLayout(d->batchSize)) { // layout is done | |
d->batchLayoutTimer.stop(); | |
updateGeometries(); | |
d->viewport->update(); | |
} | |
} | |
QAbstractItemView::timerEvent(e); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::resizeEvent(QResizeEvent *e) | |
{ | |
Q_D(QListView); | |
if (d->delayedPendingLayout) | |
return; | |
QSize delta = e->size() - e->oldSize(); | |
if (delta.isNull()) | |
return; | |
bool listWrap = (d->viewMode == ListMode) && d->wrapItemText; | |
bool flowDimensionChanged = (d->flow == LeftToRight && delta.width() != 0) | |
|| (d->flow == TopToBottom && delta.height() != 0); | |
// We post a delayed relayout in the following cases : | |
// - we're wrapping | |
// - the state is NoState, we're adjusting and the size has changed in the flowing direction | |
if (listWrap | |
|| (state() == NoState && d->resizeMode == Adjust && flowDimensionChanged)) { | |
d->doDelayedItemsLayout(100); // wait 1/10 sec before starting the layout | |
} else { | |
QAbstractItemView::resizeEvent(e); | |
} | |
} | |
#ifndef QT_NO_DRAGANDDROP | |
/*! | |
\reimp | |
*/ | |
void QListView::dragMoveEvent(QDragMoveEvent *e) | |
{ | |
Q_D(QListView); | |
if (!d->commonListView->filterDragMoveEvent(e)) { | |
if (viewMode() == QListView::ListMode && flow() == QListView::LeftToRight) | |
static_cast<QListModeViewBase *>(d->commonListView)->dragMoveEvent(e); | |
else | |
QAbstractItemView::dragMoveEvent(e); | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::dragLeaveEvent(QDragLeaveEvent *e) | |
{ | |
if (!d_func()->commonListView->filterDragLeaveEvent(e)) | |
QAbstractItemView::dragLeaveEvent(e); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::dropEvent(QDropEvent *e) | |
{ | |
if (!d_func()->commonListView->filterDropEvent(e)) | |
QAbstractItemView::dropEvent(e); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::startDrag(Qt::DropActions supportedActions) | |
{ | |
if (!d_func()->commonListView->filterStartDrag(supportedActions)) | |
QAbstractItemView::startDrag(supportedActions); | |
} | |
/*! | |
\internal | |
Called whenever items from the view is dropped on the viewport. | |
The \a event provides additional information. | |
*/ | |
void QListView::internalDrop(QDropEvent *event) | |
{ | |
// ### Qt5: remove that function | |
Q_UNUSED(event); | |
} | |
/*! | |
\internal | |
Called whenever the user starts dragging items and the items are movable, | |
enabling internal dragging and dropping of items. | |
*/ | |
void QListView::internalDrag(Qt::DropActions supportedActions) | |
{ | |
// ### Qt5: remove that function | |
Q_UNUSED(supportedActions); | |
} | |
#endif // QT_NO_DRAGANDDROP | |
/*! | |
\reimp | |
*/ | |
QStyleOptionViewItem QListView::viewOptions() const | |
{ | |
Q_D(const QListView); | |
QStyleOptionViewItem option = QAbstractItemView::viewOptions(); | |
if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview | |
int pm = (d->viewMode == ListMode | |
? style()->pixelMetric(QStyle::PM_ListViewIconSize, 0, this) | |
: style()->pixelMetric(QStyle::PM_IconViewIconSize, 0, this)); | |
option.decorationSize = QSize(pm, pm); | |
} | |
if (d->viewMode == IconMode) { | |
option.showDecorationSelected = false; | |
option.decorationPosition = QStyleOptionViewItem::Top; | |
option.displayAlignment = Qt::AlignCenter; | |
} else { | |
option.decorationPosition = QStyleOptionViewItem::Left; | |
} | |
return option; | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::paintEvent(QPaintEvent *e) | |
{ | |
Q_D(QListView); | |
if (!d->itemDelegate) | |
return; | |
QStyleOptionViewItemV4 option = d->viewOptionsV4(); | |
QPainter painter(d->viewport); | |
const QVector<QModelIndex> toBeRendered = d->intersectingSet(e->rect().translated(horizontalOffset(), verticalOffset()), false); | |
const QModelIndex current = currentIndex(); | |
const QModelIndex hover = d->hover; | |
const QAbstractItemModel *itemModel = d->model; | |
const QItemSelectionModel *selections = d->selectionModel; | |
const bool focus = (hasFocus() || d->viewport->hasFocus()) && current.isValid(); | |
const bool alternate = d->alternatingColors; | |
const QStyle::State state = option.state; | |
const QAbstractItemView::State viewState = this->state(); | |
const bool enabled = (state & QStyle::State_Enabled) != 0; | |
bool alternateBase = false; | |
int previousRow = -2; // trigger the alternateBase adjustment on first pass | |
int maxSize = (flow() == TopToBottom) | |
? qMax(viewport()->size().width(), d->contentsSize().width()) - 2 * d->spacing() | |
: qMax(viewport()->size().height(), d->contentsSize().height()) - 2 * d->spacing(); | |
QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd(); | |
for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) { | |
Q_ASSERT((*it).isValid()); | |
option.rect = visualRect(*it); | |
if (flow() == TopToBottom) | |
option.rect.setWidth(qMin(maxSize, option.rect.width())); | |
else | |
option.rect.setHeight(qMin(maxSize, option.rect.height())); | |
option.state = state; | |
if (selections && selections->isSelected(*it)) | |
option.state |= QStyle::State_Selected; | |
if (enabled) { | |
QPalette::ColorGroup cg; | |
if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) { | |
option.state &= ~QStyle::State_Enabled; | |
cg = QPalette::Disabled; | |
} else { | |
cg = QPalette::Normal; | |
} | |
option.palette.setCurrentColorGroup(cg); | |
} | |
if (focus && current == *it) { | |
option.state |= QStyle::State_HasFocus; | |
if (viewState == EditingState) | |
option.state |= QStyle::State_Editing; | |
} | |
if (*it == hover) | |
option.state |= QStyle::State_MouseOver; | |
else | |
option.state &= ~QStyle::State_MouseOver; | |
if (alternate) { | |
int row = (*it).row(); | |
if (row != previousRow + 1) { | |
// adjust alternateBase according to rows in the "gap" | |
if (!d->hiddenRows.isEmpty()) { | |
for (int r = qMax(previousRow + 1, 0); r < row; ++r) { | |
if (!d->isHidden(r)) | |
alternateBase = !alternateBase; | |
} | |
} else { | |
alternateBase = (row & 1) != 0; | |
} | |
} | |
if (alternateBase) { | |
option.features |= QStyleOptionViewItemV2::Alternate; | |
} else { | |
option.features &= ~QStyleOptionViewItemV2::Alternate; | |
} | |
// draw background of the item (only alternate row). rest of the background | |
// is provided by the delegate | |
QStyle::State oldState = option.state; | |
option.state &= ~QStyle::State_Selected; | |
style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, &painter, this); | |
option.state = oldState; | |
alternateBase = !alternateBase; | |
previousRow = row; | |
} | |
if (const QWidget *widget = d->editorForIndex(*it).editor) { | |
QRegion itemGeometry(option.rect); | |
QRegion widgetGeometry(widget->geometry()); | |
painter.save(); | |
painter.setClipRegion(itemGeometry.subtracted(widgetGeometry)); | |
d->delegateForIndex(*it)->paint(&painter, option, *it); | |
painter.restore(); | |
} else { | |
d->delegateForIndex(*it)->paint(&painter, option, *it); | |
} | |
} | |
#ifndef QT_NO_DRAGANDDROP | |
d->commonListView->paintDragDrop(&painter); | |
#endif | |
#ifndef QT_NO_RUBBERBAND | |
// #### move this implementation into a dynamic class | |
if (d->showElasticBand && d->elasticBand.isValid()) { | |
QStyleOptionRubberBand opt; | |
opt.initFrom(this); | |
opt.shape = QRubberBand::Rectangle; | |
opt.opaque = false; | |
opt.rect = d->mapToViewport(d->elasticBand, false).intersected( | |
d->viewport->rect().adjusted(-16, -16, 16, 16)); | |
painter.save(); | |
style()->drawControl(QStyle::CE_RubberBand, &opt, &painter); | |
painter.restore(); | |
} | |
#endif | |
} | |
/*! | |
\reimp | |
*/ | |
QModelIndex QListView::indexAt(const QPoint &p) const | |
{ | |
Q_D(const QListView); | |
QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1); | |
const QVector<QModelIndex> intersectVector = d->intersectingSet(rect); | |
QModelIndex index = intersectVector.count() > 0 | |
? intersectVector.last() : QModelIndex(); | |
if (index.isValid() && visualRect(index).contains(p)) | |
return index; | |
return QModelIndex(); | |
} | |
/*! | |
\reimp | |
*/ | |
int QListView::horizontalOffset() const | |
{ | |
return d_func()->commonListView->horizontalOffset(); | |
} | |
/*! | |
\reimp | |
*/ | |
int QListView::verticalOffset() const | |
{ | |
return d_func()->commonListView->verticalOffset(); | |
} | |
/*! | |
\reimp | |
*/ | |
QModelIndex QListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) | |
{ | |
Q_D(QListView); | |
Q_UNUSED(modifiers); | |
QModelIndex current = currentIndex(); | |
if (!current.isValid()) { | |
int rowCount = d->model->rowCount(d->root); | |
if (!rowCount) | |
return QModelIndex(); | |
int row = 0; | |
while (row < rowCount && d->isHiddenOrDisabled(row)) | |
++row; | |
if (row >= rowCount) | |
return QModelIndex(); | |
return d->model->index(row, d->column, d->root); | |
} | |
const QRect initialRect = rectForIndex(current); | |
QRect rect = initialRect; | |
if (rect.isEmpty()) { | |
return d->model->index(0, d->column, d->root); | |
} | |
if (d->gridSize().isValid()) rect.setSize(d->gridSize()); | |
QSize contents = d->contentsSize(); | |
QVector<QModelIndex> intersectVector; | |
switch (cursorAction) { | |
case MoveLeft: | |
while (intersectVector.isEmpty()) { | |
rect.translate(-rect.width(), 0); | |
if (rect.right() <= 0) | |
return current; | |
if (rect.left() < 0) | |
rect.setLeft(0); | |
intersectVector = d->intersectingSet(rect); | |
d->removeCurrentAndDisabled(&intersectVector, current); | |
} | |
return d->closestIndex(initialRect, intersectVector); | |
case MoveRight: | |
while (intersectVector.isEmpty()) { | |
rect.translate(rect.width(), 0); | |
if (rect.left() >= contents.width()) | |
return current; | |
if (rect.right() > contents.width()) | |
rect.setRight(contents.width()); | |
intersectVector = d->intersectingSet(rect); | |
d->removeCurrentAndDisabled(&intersectVector, current); | |
} | |
return d->closestIndex(initialRect, intersectVector); | |
case MovePageUp: | |
// move current by (visibileRowCount - 1) items. | |
// rect.translate(0, -rect.height()); will happen in the switch fallthrough for MoveUp. | |
rect.moveTop(rect.top() - d->viewport->height() + 2 * rect.height()); | |
if (rect.top() < rect.height()) | |
rect.moveTop(rect.height()); | |
case MovePrevious: | |
case MoveUp: | |
while (intersectVector.isEmpty()) { | |
rect.translate(0, -rect.height()); | |
if (rect.bottom() <= 0) { | |
#ifdef QT_KEYPAD_NAVIGATION | |
if (QApplication::keypadNavigationEnabled()) { | |
int row = d->batchStartRow() - 1; | |
while (row >= 0 && d->isHiddenOrDisabled(row)) | |
--row; | |
if (row >= 0) | |
return d->model->index(row, d->column, d->root); | |
} | |
#endif | |
return current; | |
} | |
if (rect.top() < 0) | |
rect.setTop(0); | |
intersectVector = d->intersectingSet(rect); | |
d->removeCurrentAndDisabled(&intersectVector, current); | |
} | |
return d->closestIndex(initialRect, intersectVector); | |
case MovePageDown: | |
// move current by (visibileRowCount - 1) items. | |
// rect.translate(0, rect.height()); will happen in the switch fallthrough for MoveDown. | |
rect.moveTop(rect.top() + d->viewport->height() - 2 * rect.height()); | |
if (rect.bottom() > contents.height() - rect.height()) | |
rect.moveBottom(contents.height() - rect.height()); | |
case MoveNext: | |
case MoveDown: | |
while (intersectVector.isEmpty()) { | |
rect.translate(0, rect.height()); | |
if (rect.top() >= contents.height()) { | |
#ifdef QT_KEYPAD_NAVIGATION | |
if (QApplication::keypadNavigationEnabled()) { | |
int rowCount = d->model->rowCount(d->root); | |
int row = 0; | |
while (row < rowCount && d->isHiddenOrDisabled(row)) | |
++row; | |
if (row < rowCount) | |
return d->model->index(row, d->column, d->root); | |
} | |
#endif | |
return current; | |
} | |
if (rect.bottom() > contents.height()) | |
rect.setBottom(contents.height()); | |
intersectVector = d->intersectingSet(rect); | |
d->removeCurrentAndDisabled(&intersectVector, current); | |
} | |
return d->closestIndex(initialRect, intersectVector); | |
case MoveHome: | |
return d->model->index(0, d->column, d->root); | |
case MoveEnd: | |
return d->model->index(d->batchStartRow() - 1, d->column, d->root);} | |
return current; | |
} | |
/*! | |
Returns the rectangle of the item at position \a index in the | |
model. The rectangle is in contents coordinates. | |
\sa visualRect() | |
*/ | |
QRect QListView::rectForIndex(const QModelIndex &index) const | |
{ | |
return d_func()->rectForIndex(index); | |
} | |
/*! | |
\since 4.1 | |
Sets the contents position of the item at \a index in the model to the given | |
\a position. | |
If the list view's movement mode is Static or its view mode is ListView, | |
this function will have no effect. | |
*/ | |
void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index) | |
{ | |
Q_D(QListView); | |
if (d->movement == Static | |
|| !d->isIndexValid(index) | |
|| index.parent() != d->root | |
|| index.column() != d->column) | |
return; | |
d->executePostedLayout(); | |
d->commonListView->setPositionForIndex(position, index); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) | |
{ | |
Q_D(QListView); | |
if (!d->selectionModel) | |
return; | |
// if we are wrapping, we can only selecte inside the contents rectangle | |
int w = qMax(d->contentsSize().width(), d->viewport->width()); | |
int h = qMax(d->contentsSize().height(), d->viewport->height()); | |
if (d->wrap && !QRect(0, 0, w, h).intersects(rect)) | |
return; | |
QItemSelection selection; | |
if (rect.width() == 1 && rect.height() == 1) { | |
const QVector<QModelIndex> intersectVector = d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset())); | |
QModelIndex tl; | |
if (!intersectVector.isEmpty()) | |
tl = intersectVector.last(); // special case for mouse press; only select the top item | |
if (tl.isValid() && d->isIndexEnabled(tl)) | |
selection.select(tl, tl); | |
} else { | |
if (state() == DragSelectingState) { // visual selection mode (rubberband selection) | |
selection = d->selection(rect.translated(horizontalOffset(), verticalOffset())); | |
} else { // logical selection mode (key and mouse click selection) | |
QModelIndex tl, br; | |
// get the first item | |
const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1); | |
QVector<QModelIndex> intersectVector = d->intersectingSet(topLeft); | |
if (!intersectVector.isEmpty()) | |
tl = intersectVector.last(); | |
// get the last item | |
const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1); | |
intersectVector = d->intersectingSet(bottomRight); | |
if (!intersectVector.isEmpty()) | |
br = intersectVector.last(); | |
// get the ranges | |
if (tl.isValid() && br.isValid() | |
&& d->isIndexEnabled(tl) | |
&& d->isIndexEnabled(br)) { | |
QRect first = rectForIndex(tl); | |
QRect last = rectForIndex(br); | |
QRect middle; | |
if (d->flow == LeftToRight) { | |
QRect &top = first; | |
QRect &bottom = last; | |
// if bottom is above top, swap them | |
if (top.center().y() > bottom.center().y()) { | |
QRect tmp = top; | |
top = bottom; | |
bottom = tmp; | |
} | |
// if the rect are on differnet lines, expand | |
if (top.top() != bottom.top()) { | |
// top rectangle | |
if (isRightToLeft()) | |
top.setLeft(0); | |
else | |
top.setRight(contentsSize().width()); | |
// bottom rectangle | |
if (isRightToLeft()) | |
bottom.setRight(contentsSize().width()); | |
else | |
bottom.setLeft(0); | |
} else if (top.left() > bottom.right()) { | |
if (isRightToLeft()) | |
bottom.setLeft(top.right()); | |
else | |
bottom.setRight(top.left()); | |
} else { | |
if (isRightToLeft()) | |
top.setLeft(bottom.right()); | |
else | |
top.setRight(bottom.left()); | |
} | |
// middle rectangle | |
if (top.bottom() < bottom.top()) { | |
if (gridSize().isValid() && !gridSize().isNull()) | |
middle.setTop(top.top() + gridSize().height()); | |
else | |
middle.setTop(top.bottom() + 1); | |
middle.setLeft(qMin(top.left(), bottom.left())); | |
middle.setBottom(bottom.top() - 1); | |
middle.setRight(qMax(top.right(), bottom.right())); | |
} | |
} else { // TopToBottom | |
QRect &left = first; | |
QRect &right = last; | |
if (left.center().x() > right.center().x()) | |
qSwap(left, right); | |
int ch = contentsSize().height(); | |
if (left.left() != right.left()) { | |
// left rectangle | |
if (isRightToLeft()) | |
left.setTop(0); | |
else | |
left.setBottom(ch); | |
// top rectangle | |
if (isRightToLeft()) | |
right.setBottom(ch); | |
else | |
right.setTop(0); | |
// only set middle if the | |
middle.setTop(0); | |
middle.setBottom(ch); | |
if (gridSize().isValid() && !gridSize().isNull()) | |
middle.setLeft(left.left() + gridSize().width()); | |
else | |
middle.setLeft(left.right() + 1); | |
middle.setRight(right.left() - 1); | |
} else if (left.bottom() < right.top()) { | |
left.setBottom(right.top() - 1); | |
} else { | |
right.setBottom(left.top() - 1); | |
} | |
} | |
// do the selections | |
QItemSelection topSelection = d->selection(first); | |
QItemSelection middleSelection = d->selection(middle); | |
QItemSelection bottomSelection = d->selection(last); | |
// merge | |
selection.merge(topSelection, QItemSelectionModel::Select); | |
selection.merge(middleSelection, QItemSelectionModel::Select); | |
selection.merge(bottomSelection, QItemSelectionModel::Select); | |
} | |
} | |
} | |
d->selectionModel->select(selection, command); | |
} | |
/*! | |
\reimp | |
Since 4.7, the returned region only contains rectangles intersecting | |
(or included in) the viewport. | |
*/ | |
QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const | |
{ | |
Q_D(const QListView); | |
// ### NOTE: this is a potential bottleneck in non-static mode | |
int c = d->column; | |
QRegion selectionRegion; | |
const QRect &viewportRect = d->viewport->rect(); | |
for (int i = 0; i < selection.count(); ++i) { | |
if (!selection.at(i).isValid()) | |
continue; | |
QModelIndex parent = selection.at(i).topLeft().parent(); | |
//we only display the children of the root in a listview | |
//we're not interested in the other model indexes | |
if (parent != d->root) | |
continue; | |
int t = selection.at(i).topLeft().row(); | |
int b = selection.at(i).bottomRight().row(); | |
if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items | |
for (int r = t; r <= b; ++r) { | |
const QRect &rect = visualRect(d->model->index(r, c, parent)); | |
if (viewportRect.intersects(rect)) | |
selectionRegion += rect; | |
} | |
} else { // in static mode, we can optimize a bit | |
while (t <= b && d->isHidden(t)) ++t; | |
while (b >= t && d->isHidden(b)) --b; | |
const QModelIndex top = d->model->index(t, c, parent); | |
const QModelIndex bottom = d->model->index(b, c, parent); | |
QRect rect(visualRect(top).topLeft(), | |
visualRect(bottom).bottomRight()); | |
if (viewportRect.intersects(rect)) | |
selectionRegion += rect; | |
} | |
} | |
return selectionRegion; | |
} | |
/*! | |
\reimp | |
*/ | |
QModelIndexList QListView::selectedIndexes() const | |
{ | |
Q_D(const QListView); | |
if (!d->selectionModel) | |
return QModelIndexList(); | |
QModelIndexList viewSelected = d->selectionModel->selectedIndexes(); | |
for (int i = 0; i < viewSelected.count(); ++i) { | |
const QModelIndex &index = viewSelected.at(i); | |
if (!isIndexHidden(index) && index.parent() == d->root && index.column() == d->column) | |
++i; | |
else | |
viewSelected.removeAt(i); | |
} | |
return viewSelected; | |
} | |
/*! | |
\internal | |
Layout the items according to the flow and wrapping properties. | |
*/ | |
void QListView::doItemsLayout() | |
{ | |
Q_D(QListView); | |
// showing the scroll bars will trigger a resize event, | |
// so we set the state to expanding to avoid | |
// triggering another layout | |
QAbstractItemView::State oldState = state(); | |
setState(ExpandingState); | |
if (d->model->columnCount(d->root) > 0) { // no columns means no contents | |
d->resetBatchStartRow(); | |
if (layoutMode() == SinglePass) | |
d->doItemsLayout(d->model->rowCount(d->root)); // layout everything | |
else if (!d->batchLayoutTimer.isActive()) { | |
if (!d->doItemsLayout(d->batchSize)) // layout is done | |
d->batchLayoutTimer.start(0, this); // do a new batch as fast as possible | |
} | |
} | |
QAbstractItemView::doItemsLayout(); | |
setState(oldState); // restoring the oldState | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::updateGeometries() | |
{ | |
Q_D(QListView); | |
if (d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) { | |
horizontalScrollBar()->setRange(0, 0); | |
verticalScrollBar()->setRange(0, 0); | |
} else { | |
QModelIndex index = d->model->index(0, d->column, d->root); | |
QStyleOptionViewItemV4 option = d->viewOptionsV4(); | |
QSize step = d->itemSize(option, index); | |
d->commonListView->updateHorizontalScrollBar(step); | |
d->commonListView->updateVerticalScrollBar(step); | |
} | |
QAbstractItemView::updateGeometries(); | |
// if the scroll bars are turned off, we resize the contents to the viewport | |
if (d->movement == Static && !d->isWrapping()) { | |
d->layoutChildren(); // we need the viewport size to be updated | |
if (d->flow == TopToBottom) { | |
if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { | |
d->setContentsSize(viewport()->width(), contentsSize().height()); | |
horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway | |
} | |
} else { // LeftToRight | |
if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) { | |
d->setContentsSize(contentsSize().width(), viewport()->height()); | |
verticalScrollBar()->setRange(0, 0); // we see all the contents anyway | |
} | |
} | |
} | |
} | |
/*! | |
\reimp | |
*/ | |
bool QListView::isIndexHidden(const QModelIndex &index) const | |
{ | |
Q_D(const QListView); | |
return (d->isHidden(index.row()) | |
&& (index.parent() == d->root) | |
&& index.column() == d->column); | |
} | |
/*! | |
\property QListView::modelColumn | |
\brief the column in the model that is visible | |
By default, this property contains 0, indicating that the first | |
column in the model will be shown. | |
*/ | |
void QListView::setModelColumn(int column) | |
{ | |
Q_D(QListView); | |
if (column < 0 || column >= d->model->columnCount(d->root)) | |
return; | |
d->column = column; | |
d->doDelayedItemsLayout(); | |
} | |
int QListView::modelColumn() const | |
{ | |
Q_D(const QListView); | |
return d->column; | |
} | |
/*! | |
\property QListView::uniformItemSizes | |
\brief whether all items in the listview have the same size | |
\since 4.1 | |
This property should only be set to true if it is guaranteed that all items | |
in the view have the same size. This enables the view to do some | |
optimizations for performance purposes. | |
By default, this property is false. | |
*/ | |
void QListView::setUniformItemSizes(bool enable) | |
{ | |
Q_D(QListView); | |
d->uniformItemSizes = enable; | |
} | |
bool QListView::uniformItemSizes() const | |
{ | |
Q_D(const QListView); | |
return d->uniformItemSizes; | |
} | |
/*! | |
\property QListView::wordWrap | |
\brief the item text word-wrapping policy | |
\since 4.2 | |
If this property is true then the item text is wrapped where | |
necessary at word-breaks; otherwise it is not wrapped at all. | |
This property is false by default. | |
Please note that even if wrapping is enabled, the cell will not be | |
expanded to make room for the text. It will print ellipsis for | |
text that cannot be shown, according to the view's | |
\l{QAbstractItemView::}{textElideMode}. | |
*/ | |
void QListView::setWordWrap(bool on) | |
{ | |
Q_D(QListView); | |
if (d->wrapItemText == on) | |
return; | |
d->wrapItemText = on; | |
d->doDelayedItemsLayout(); | |
} | |
bool QListView::wordWrap() const | |
{ | |
Q_D(const QListView); | |
return d->wrapItemText; | |
} | |
/*! | |
\property QListView::selectionRectVisible | |
\brief if the selection rectangle should be visible | |
\since 4.3 | |
If this property is true then the selection rectangle is visible; | |
otherwise it will be hidden. | |
\note The selection rectangle will only be visible if the selection mode | |
is in a mode where more than one item can be selected; i.e., it will not | |
draw a selection rectangle if the selection mode is | |
QAbstractItemView::SingleSelection. | |
By default, this property is false. | |
*/ | |
void QListView::setSelectionRectVisible(bool show) | |
{ | |
Q_D(QListView); | |
d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible); | |
d->setSelectionRectVisible(show); | |
} | |
bool QListView::isSelectionRectVisible() const | |
{ | |
Q_D(const QListView); | |
return d->isSelectionRectVisible(); | |
} | |
/*! | |
\reimp | |
*/ | |
bool QListView::event(QEvent *e) | |
{ | |
return QAbstractItemView::event(e); | |
} | |
/* | |
* private object implementation | |
*/ | |
QListViewPrivate::QListViewPrivate() | |
: QAbstractItemViewPrivate(), | |
commonListView(0), | |
wrap(false), | |
space(0), | |
flow(QListView::TopToBottom), | |
movement(QListView::Static), | |
resizeMode(QListView::Fixed), | |
layoutMode(QListView::SinglePass), | |
viewMode(QListView::ListMode), | |
modeProperties(0), | |
column(0), | |
uniformItemSizes(false), | |
batchSize(100), | |
showElasticBand(false) | |
{ | |
} | |
QListViewPrivate::~QListViewPrivate() | |
{ | |
delete commonListView; | |
} | |
void QListViewPrivate::clear() | |
{ | |
// initialization of data structs | |
cachedItemSize = QSize(); | |
commonListView->clear(); | |
} | |
void QListViewPrivate::prepareItemsLayout() | |
{ | |
Q_Q(QListView); | |
clear(); | |
//take the size as if there were scrollbar in order to prevent scrollbar to blink | |
layoutBounds = QRect(QPoint(), q->maximumViewportSize()); | |
int frameAroundContents = 0; | |
if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) | |
frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2; | |
// maximumViewportSize() already takes scrollbar into account if policy is | |
// Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy | |
// is Qt::ScrollBarAsNeeded | |
int verticalMargin = vbarpolicy==Qt::ScrollBarAsNeeded | |
? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, vbar) + frameAroundContents | |
: 0; | |
int horizontalMargin = hbarpolicy==Qt::ScrollBarAsNeeded | |
? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, hbar) + frameAroundContents | |
: 0; | |
layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin); | |
int rowCount = model->columnCount(root) <= 0 ? 0 : model->rowCount(root); | |
commonListView->setRowCount(rowCount); | |
} | |
/*! | |
\internal | |
*/ | |
bool QListViewPrivate::doItemsLayout(int delta) | |
{ | |
int max = model->rowCount(root) - 1; | |
int first = batchStartRow(); | |
int last = qMin(first + delta - 1, max); | |
if (first == 0) { | |
layoutChildren(); // make sure the viewport has the right size | |
prepareItemsLayout(); | |
} | |
if (max < 0 || last < first) { | |
return true; // nothing to do | |
} | |
QListViewLayoutInfo info; | |
info.bounds = layoutBounds; | |
info.grid = gridSize(); | |
info.spacing = (info.grid.isValid() ? 0 : spacing()); | |
info.first = first; | |
info.last = last; | |
info.wrap = isWrapping(); | |
info.flow = flow; | |
info.max = max; | |
return commonListView->doBatchedItemLayout(info, max); | |
} | |
QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const | |
{ | |
if (!index.isValid() || isHidden(index.row())) | |
return QListViewItem(); | |
return commonListView->indexToListViewItem(index); | |
} | |
QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const | |
{ | |
Q_Q(const QListView); | |
if (!rect.isValid()) | |
return rect; | |
QRect result = extend ? commonListView->mapToViewport(rect) : rect; | |
int dx = -q->horizontalOffset(); | |
int dy = -q->verticalOffset(); | |
return result.adjusted(dx, dy, dx, dy); | |
} | |
QModelIndex QListViewPrivate::closestIndex(const QRect &target, | |
const QVector<QModelIndex> &candidates) const | |
{ | |
int distance = 0; | |
int shortest = INT_MAX; | |
QModelIndex closest; | |
QVector<QModelIndex>::const_iterator it = candidates.begin(); | |
for (; it != candidates.end(); ++it) { | |
if (!(*it).isValid()) | |
continue; | |
const QRect indexRect = indexToListViewItem(*it).rect(); | |
//if the center x (or y) position of an item is included in the rect of the other item, | |
//we define the distance between them as the difference in x (or y) of their respective center. | |
// Otherwise, we use the nahattan length between the 2 items | |
if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right()) | |
|| (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) { | |
//one item's center is at the vertical of the other | |
distance = qAbs(indexRect.center().y() - target.center().y()); | |
} else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom()) | |
|| (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) { | |
//one item's center is at the vertical of the other | |
distance = qAbs(indexRect.center().x() - target.center().x()); | |
} else { | |
distance = (indexRect.center() - target.center()).manhattanLength(); | |
} | |
if (distance < shortest) { | |
shortest = distance; | |
closest = *it; | |
} | |
} | |
return closest; | |
} | |
QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const | |
{ | |
if (!uniformItemSizes) { | |
const QAbstractItemDelegate *delegate = delegateForIndex(index); | |
return delegate ? delegate->sizeHint(option, index) : QSize(); | |
} | |
if (!cachedItemSize.isValid()) { // the last item is probaly the largest, so we use its size | |
int row = model->rowCount(root) - 1; | |
QModelIndex sample = model->index(row, column, root); | |
const QAbstractItemDelegate *delegate = delegateForIndex(sample); | |
cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize(); | |
} | |
return cachedItemSize; | |
} | |
QItemSelection QListViewPrivate::selection(const QRect &rect) const | |
{ | |
QItemSelection selection; | |
QModelIndex tl, br; | |
const QVector<QModelIndex> intersectVector = intersectingSet(rect); | |
QVector<QModelIndex>::const_iterator it = intersectVector.begin(); | |
for (; it != intersectVector.end(); ++it) { | |
if (!tl.isValid() && !br.isValid()) { | |
tl = br = *it; | |
} else if ((*it).row() == (tl.row() - 1)) { | |
tl = *it; // expand current range | |
} else if ((*it).row() == (br.row() + 1)) { | |
br = (*it); // expand current range | |
} else { | |
selection.select(tl, br); // select current range | |
tl = br = *it; // start new range | |
} | |
} | |
if (tl.isValid() && br.isValid()) | |
selection.select(tl, br); | |
else if (tl.isValid()) | |
selection.select(tl, tl); | |
else if (br.isValid()) | |
selection.select(br, br); | |
return selection; | |
} | |
#ifndef QT_NO_DRAGANDDROP | |
QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect, const QModelIndex &idx) const | |
{ | |
if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) | |
return static_cast<QListModeViewBase *>(commonListView)->position(pos, rect, idx); | |
else | |
return QAbstractItemViewPrivate::position(pos, rect, idx); | |
} | |
#endif | |
/* | |
* Common ListView Implementation | |
*/ | |
void QCommonListViewBase::appendHiddenRow(int row) | |
{ | |
dd->hiddenRows.append(dd->model->index(row, 0, qq->rootIndex())); | |
} | |
void QCommonListViewBase::removeHiddenRow(int row) | |
{ | |
dd->hiddenRows.remove(dd->hiddenRows.indexOf(dd->model->index(row, 0, qq->rootIndex()))); | |
} | |
void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step) | |
{ | |
horizontalScrollBar()->setSingleStep(step.width() + spacing()); | |
horizontalScrollBar()->setPageStep(viewport()->width()); | |
horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width()); | |
} | |
void QCommonListViewBase::updateVerticalScrollBar(const QSize &step) | |
{ | |
verticalScrollBar()->setSingleStep(step.height() + spacing()); | |
verticalScrollBar()->setPageStep(viewport()->height()); | |
verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height()); | |
} | |
void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/) | |
{ | |
dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy); | |
} | |
int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint, | |
bool above, bool below, const QRect &area, const QRect &rect) const | |
{ | |
int verticalValue = verticalScrollBar()->value(); | |
QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing()); | |
if (hint == QListView::PositionAtTop || above) | |
verticalValue += adjusted.top(); | |
else if (hint == QListView::PositionAtBottom || below) | |
verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1); | |
else if (hint == QListView::PositionAtCenter) | |
verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2); | |
return verticalValue; | |
} | |
int QCommonListViewBase::horizontalOffset() const | |
{ | |
return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() : horizontalScrollBar()->value()); | |
} | |
int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint, | |
bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const | |
{ | |
int horizontalValue = horizontalScrollBar()->value(); | |
if (isRightToLeft()) { | |
if (hint == QListView::PositionAtCenter) { | |
horizontalValue += ((area.width() - rect.width()) / 2) - rect.left(); | |
} else { | |
if (leftOf) | |
horizontalValue -= rect.left(); | |
else if (rightOf) | |
horizontalValue += qMin(rect.left(), area.width() - rect.right()); | |
} | |
} else { | |
if (hint == QListView::PositionAtCenter) { | |
horizontalValue += rect.left() - ((area.width()- rect.width()) / 2); | |
} else { | |
if (leftOf) | |
horizontalValue += rect.left(); | |
else if (rightOf) | |
horizontalValue += qMin(rect.left(), rect.right() - area.width()); | |
} | |
} | |
return horizontalValue; | |
} | |
/* | |
* ListMode ListView Implementation | |
*/ | |
#ifndef QT_NO_DRAGANDDROP | |
void QListModeViewBase::paintDragDrop(QPainter *painter) | |
{ | |
// FIXME: Until the we can provide a proper drop indicator | |
// in IconMode, it makes no sense to show it | |
dd->paintDropIndicator(painter); | |
} | |
QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect, const QModelIndex &index) const | |
{ | |
QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport; | |
if (!dd->overwrite) { | |
const int margin = 2; | |
if (pos.x() - rect.left() < margin) { | |
r = QAbstractItemView::AboveItem; // Visually, on the left | |
} else if (rect.right() - pos.x() < margin) { | |
r = QAbstractItemView::BelowItem; // Visually, on the right | |
} else if (rect.contains(pos, true)) { | |
r = QAbstractItemView::OnItem; | |
} | |
} else { | |
QRect touchingRect = rect; | |
touchingRect.adjust(-1, -1, 1, 1); | |
if (touchingRect.contains(pos, false)) { | |
r = QAbstractItemView::OnItem; | |
} | |
} | |
if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled))) | |
r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem; | |
return r; | |
} | |
void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event) | |
{ | |
if (qq->dragDropMode() == QAbstractItemView::InternalMove | |
&& (event->source() != qq || !(event->possibleActions() & Qt::MoveAction))) | |
return; | |
// ignore by default | |
event->ignore(); | |
QModelIndex index = qq->indexAt(event->pos()); | |
dd->hover = index; | |
if (!dd->droppingOnItself(event, index) | |
&& dd->canDecode(event)) { | |
if (index.isValid() && dd->showDropIndicator) { | |
QRect rect = qq->visualRect(index); | |
dd->dropIndicatorPosition = position(event->pos(), rect, index); | |
switch (dd->dropIndicatorPosition) { | |
case QAbstractItemView::AboveItem: | |
if (dd->isIndexDropEnabled(index.parent())) { | |
dd->dropIndicatorRect = QRect(rect.left(), rect.top(), 0, rect.height()); | |
event->accept(); | |
} else { | |
dd->dropIndicatorRect = QRect(); | |
} | |
break; | |
case QAbstractItemView::BelowItem: | |
if (dd->isIndexDropEnabled(index.parent())) { | |
dd->dropIndicatorRect = QRect(rect.right(), rect.top(), 0, rect.height()); | |
event->accept(); | |
} else { | |
dd->dropIndicatorRect = QRect(); | |
} | |
break; | |
case QAbstractItemView::OnItem: | |
if (dd->isIndexDropEnabled(index)) { | |
dd->dropIndicatorRect = rect; | |
event->accept(); | |
} else { | |
dd->dropIndicatorRect = QRect(); | |
} | |
break; | |
case QAbstractItemView::OnViewport: | |
dd->dropIndicatorRect = QRect(); | |
if (dd->isIndexDropEnabled(qq->rootIndex())) { | |
event->accept(); // allow dropping in empty areas | |
} | |
break; | |
} | |
} else { | |
dd->dropIndicatorRect = QRect(); | |
dd->dropIndicatorPosition = QAbstractItemView::OnViewport; | |
if (dd->isIndexDropEnabled(qq->rootIndex())) { | |
event->accept(); // allow dropping in empty areas | |
} | |
} | |
dd->viewport->update(); | |
} // can decode | |
if (dd->shouldAutoScroll(event->pos())) | |
qq->startAutoScroll(); | |
} | |
#endif //QT_NO_DRAGANDDROP | |
void QListModeViewBase::updateVerticalScrollBar(const QSize &step) | |
{ | |
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem | |
&& ((flow() == QListView::TopToBottom && !isWrapping()) | |
|| (flow() == QListView::LeftToRight && isWrapping()))) { | |
const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).count() - 1; | |
if (steps > 0) { | |
const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping()); | |
verticalScrollBar()->setSingleStep(1); | |
verticalScrollBar()->setPageStep(pageSteps); | |
verticalScrollBar()->setRange(0, steps - pageSteps); | |
} else { | |
verticalScrollBar()->setRange(0, 0); | |
} | |
// } else if (vertical && d->isWrapping() && d->movement == Static) { | |
// ### wrapped scrolling in flow direction | |
} else { | |
QCommonListViewBase::updateVerticalScrollBar(step); | |
} | |
} | |
void QListModeViewBase::updateHorizontalScrollBar(const QSize &step) | |
{ | |
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem | |
&& ((flow() == QListView::TopToBottom && isWrapping()) | |
|| (flow() == QListView::LeftToRight && !isWrapping()))) { | |
int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).count() - 1; | |
if (steps > 0) { | |
const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping()); | |
horizontalScrollBar()->setSingleStep(1); | |
horizontalScrollBar()->setPageStep(pageSteps); | |
horizontalScrollBar()->setRange(0, steps - pageSteps); | |
} else { | |
horizontalScrollBar()->setRange(0, 0); | |
} | |
} else { | |
QCommonListViewBase::updateHorizontalScrollBar(step); | |
} | |
} | |
int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint, | |
bool above, bool below, const QRect &area, const QRect &rect) const | |
{ | |
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
int value; | |
if (scrollValueMap.isEmpty()) | |
value = 0; | |
else | |
value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()), flowPositions.count() - 1); | |
if (above) | |
hint = QListView::PositionAtTop; | |
else if (below) | |
hint = QListView::PositionAtBottom; | |
if (hint == QListView::EnsureVisible) | |
return value; | |
return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height()); | |
} | |
return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect); | |
} | |
int QListModeViewBase::horizontalOffset() const | |
{ | |
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
if (isWrapping()) { | |
if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) { | |
const int max = segmentPositions.count() - 1; | |
int currentValue = qBound(0, horizontalScrollBar()->value(), max); | |
int position = segmentPositions.at(currentValue); | |
int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max); | |
int maximum = segmentPositions.at(maximumValue); | |
return (isRightToLeft() ? maximum - position : position); | |
} | |
} else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) { | |
int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value())); | |
int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum())); | |
return (isRightToLeft() ? maximum - position : position); | |
} | |
} | |
return QCommonListViewBase::horizontalOffset(); | |
} | |
int QListModeViewBase::verticalOffset() const | |
{ | |
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { | |
if (isWrapping()) { | |
if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) { | |
int value = verticalScrollBar()->value(); | |
if (value >= segmentPositions.count()) | |
return 0; | |
return segmentPositions.at(value); | |
} | |
} else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) { | |
int value = verticalScrollBar()->value(); | |
if (value > scrollValueMap.count()) | |
return 0; | |
return flowPositions.at(scrollValueMap.at(value)) - spacing(); | |
} | |
} | |
return QCommonListViewBase::verticalOffset(); | |
} | |
int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint, | |
bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const | |
{ | |
if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem) | |
return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect); | |
int value; | |
if (scrollValueMap.isEmpty()) | |
value = 0; | |
else | |
value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.count() - 1); | |
if (leftOf) | |
hint = QListView::PositionAtTop; | |
else if (rightOf) | |
hint = QListView::PositionAtBottom; | |
if (hint == QListView::EnsureVisible) | |
return value; | |
return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width()); | |
} | |
void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) | |
{ | |
// ### reorder this logic | |
const int verticalValue = verticalScrollBar()->value(); | |
const int horizontalValue = horizontalScrollBar()->value(); | |
const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem); | |
const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem); | |
if (isWrapping()) { | |
if (segmentPositions.isEmpty()) | |
return; | |
const int max = segmentPositions.count() - 1; | |
if (horizontal && flow() == QListView::TopToBottom && dx != 0) { | |
int currentValue = qBound(0, horizontalValue, max); | |
int previousValue = qBound(0, currentValue + dx, max); | |
int currentCoordinate = segmentPositions.at(currentValue); | |
int previousCoordinate = segmentPositions.at(previousValue); | |
dx = previousCoordinate - currentCoordinate; | |
} else if (vertical && flow() == QListView::LeftToRight && dy != 0) { | |
int currentValue = qBound(0, verticalValue, max); | |
int previousValue = qBound(0, currentValue + dy, max); | |
int currentCoordinate = segmentPositions.at(currentValue); | |
int previousCoordinate = segmentPositions.at(previousValue); | |
dy = previousCoordinate - currentCoordinate; | |
} | |
} else { | |
if (flowPositions.isEmpty()) | |
return; | |
const int max = scrollValueMap.count() - 1; | |
if (vertical && flow() == QListView::TopToBottom && dy != 0) { | |
int currentValue = qBound(0, verticalValue, max); | |
int previousValue = qBound(0, currentValue + dy, max); | |
int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); | |
int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); | |
dy = previousCoordinate - currentCoordinate; | |
} else if (horizontal && flow() == QListView::LeftToRight && dx != 0) { | |
int currentValue = qBound(0, horizontalValue, max); | |
int previousValue = qBound(0, currentValue + dx, max); | |
int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue)); | |
int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue)); | |
dx = previousCoordinate - currentCoordinate; | |
} | |
} | |
QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); | |
} | |
bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) | |
{ | |
doStaticLayout(info); | |
if (batchStartRow > max) { // stop items layout | |
flowPositions.resize(flowPositions.count()); | |
segmentPositions.resize(segmentPositions.count()); | |
segmentStartRows.resize(segmentStartRows.count()); | |
return true; // done | |
} | |
return false; // not done | |
} | |
QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const | |
{ | |
if (flowPositions.isEmpty() | |
|| segmentPositions.isEmpty() | |
|| index.row() >= flowPositions.count()) | |
return QListViewItem(); | |
const int segment = qBinarySearch<int>(segmentStartRows, index.row(), | |
0, segmentStartRows.count() - 1); | |
QStyleOptionViewItemV4 options = viewOptions(); | |
options.rect.setSize(contentsSize); | |
QSize size = (uniformItemSizes() && cachedItemSize().isValid()) | |
? cachedItemSize() : itemSize(options, index); | |
QPoint pos; | |
if (flow() == QListView::LeftToRight) { | |
pos.setX(flowPositions.at(index.row())); | |
pos.setY(segmentPositions.at(segment)); | |
} else { // TopToBottom | |
pos.setY(flowPositions.at(index.row())); | |
pos.setX(segmentPositions.at(segment)); | |
if (isWrapping()) { // make the items as wide as the segment | |
int right = (segment + 1 >= segmentPositions.count() | |
? contentsSize.width() | |
: segmentPositions.at(segment + 1)); | |
size.setWidth(right - pos.x()); | |
} else { // make the items as wide as the viewport | |
size.setWidth(qMax(size.width(), viewport()->width())); | |
} | |
} | |
return QListViewItem(QRect(pos, size), index.row()); | |
} | |
QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info) | |
{ | |
int x, y; | |
if (info.first == 0) { | |
flowPositions.clear(); | |
segmentPositions.clear(); | |
segmentStartRows.clear(); | |
segmentExtents.clear(); | |
scrollValueMap.clear(); | |
x = info.bounds.left() + info.spacing; | |
y = info.bounds.top() + info.spacing; | |
segmentPositions.append(info.flow == QListView::LeftToRight ? y : x); | |
segmentStartRows.append(0); | |
} else if (info.wrap) { | |
if (info.flow == QListView::LeftToRight) { | |
x = batchSavedPosition; | |
y = segmentPositions.last(); | |
} else { // flow == QListView::TopToBottom | |
x = segmentPositions.last(); | |
y = batchSavedPosition; | |
} | |
} else { // not first and not wrap | |
if (info.flow == QListView::LeftToRight) { | |
x = batchSavedPosition; | |
y = info.bounds.top() + info.spacing; | |
} else { // flow == QListView::TopToBottom | |
x = info.bounds.left() + info.spacing; | |
y = batchSavedPosition; | |
} | |
} | |
return QPoint(x, y); | |
} | |
/*! | |
\internal | |
*/ | |
void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info) | |
{ | |
const bool useItemSize = !info.grid.isValid(); | |
const QPoint topLeft = initStaticLayout(info); | |
QStyleOptionViewItemV4 option = viewOptions(); | |
option.rect = info.bounds; | |
option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing); | |
// The static layout data structures are as follows: | |
// One vector contains the coordinate in the direction of layout flow. | |
// Another vector contains the coordinates of the segments. | |
// A third vector contains the index (model row) of the first item | |
// of each segment. | |
int segStartPosition; | |
int segEndPosition; | |
int deltaFlowPosition; | |
int deltaSegPosition; | |
int deltaSegHint; | |
int flowPosition; | |
int segPosition; | |
if (info.flow == QListView::LeftToRight) { | |
segStartPosition = info.bounds.left(); | |
segEndPosition = info.bounds.width(); | |
flowPosition = topLeft.x(); | |
segPosition = topLeft.y(); | |
deltaFlowPosition = info.grid.width(); // dx | |
deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy | |
deltaSegHint = info.grid.height(); | |
} else { // flow == QListView::TopToBottom | |
segStartPosition = info.bounds.top(); | |
segEndPosition = info.bounds.height(); | |
flowPosition = topLeft.y(); | |
segPosition = topLeft.x(); | |
deltaFlowPosition = info.grid.height(); // dy | |
deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx | |
deltaSegHint = info.grid.width(); | |
} | |
for (int row = info.first; row <= info.last; ++row) { | |
if (isHidden(row)) { // ### | |
flowPositions.append(flowPosition); | |
} else { | |
// if we are not using a grid, we need to find the deltas | |
if (useItemSize) { | |
QSize hint = itemSize(option, modelIndex(row)); | |
if (info.flow == QListView::LeftToRight) { | |
deltaFlowPosition = hint.width() + info.spacing; | |
deltaSegHint = hint.height() + info.spacing; | |
} else { // TopToBottom | |
deltaFlowPosition = hint.height() + info.spacing; | |
deltaSegHint = hint.width() + info.spacing; | |
} | |
} | |
// create new segment | |
if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) { | |
segmentExtents.append(flowPosition); | |
flowPosition = info.spacing + segStartPosition; | |
segPosition += deltaSegPosition; | |
segmentPositions.append(segPosition); | |
segmentStartRows.append(row); | |
deltaSegPosition = 0; | |
} | |
// save the flow position of this item | |
scrollValueMap.append(flowPositions.count()); | |
flowPositions.append(flowPosition); | |
// prepare for the next item | |
deltaSegPosition = qMax(deltaSegHint, deltaSegPosition); | |
flowPosition += info.spacing + deltaFlowPosition; | |
} | |
} | |
// used when laying out next batch | |
batchSavedPosition = flowPosition; | |
batchSavedDeltaSeg = deltaSegPosition; | |
batchStartRow = info.last + 1; | |
if (info.last == info.max) | |
flowPosition -= info.spacing; // remove extra spacing | |
// set the contents size | |
QRect rect = info.bounds; | |
if (info.flow == QListView::LeftToRight) { | |
rect.setRight(segmentPositions.count() == 1 ? flowPosition : info.bounds.right()); | |
rect.setBottom(segPosition + deltaSegPosition); | |
} else { // TopToBottom | |
rect.setRight(segPosition + deltaSegPosition); | |
rect.setBottom(segmentPositions.count() == 1 ? flowPosition : info.bounds.bottom()); | |
} | |
contentsSize = QSize(rect.right(), rect.bottom()); | |
// if it is the last batch, save the end of the segments | |
if (info.last == info.max) { | |
segmentExtents.append(flowPosition); | |
scrollValueMap.append(flowPositions.count()); | |
flowPositions.append(flowPosition); | |
segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX); | |
} | |
// if the new items are visble, update the viewport | |
QRect changedRect(topLeft, rect.bottomRight()); | |
if (clipRect().intersects(changedRect)) | |
viewport()->update(); | |
} | |
/*! | |
\internal | |
Finds the set of items intersecting with \a area. | |
In this function, itemsize is counted from topleft to the start of the next item. | |
*/ | |
QVector<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const | |
{ | |
QVector<QModelIndex> ret; | |
int segStartPosition; | |
int segEndPosition; | |
int flowStartPosition; | |
int flowEndPosition; | |
if (flow() == QListView::LeftToRight) { | |
segStartPosition = area.top(); | |
segEndPosition = area.bottom(); | |
flowStartPosition = area.left(); | |
flowEndPosition = area.right(); | |
} else { | |
segStartPosition = area.left(); | |
segEndPosition = area.right(); | |
flowStartPosition = area.top(); | |
flowEndPosition = area.bottom(); | |
} | |
if (segmentPositions.count() < 2 || flowPositions.isEmpty()) | |
return ret; | |
// the last segment position is actually the edge of the last segment | |
const int segLast = segmentPositions.count() - 2; | |
int seg = qBinarySearch<int>(segmentPositions, segStartPosition, 0, segLast + 1); | |
for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) { | |
int first = segmentStartRows.at(seg); | |
int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1; | |
if (segmentExtents.at(seg) < flowStartPosition) | |
continue; | |
int row = qBinarySearch<int>(flowPositions, flowStartPosition, first, last); | |
for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) { | |
if (isHidden(row)) | |
continue; | |
QModelIndex index = modelIndex(row); | |
if (index.isValid()) | |
ret += index; | |
#if 0 // for debugging | |
else | |
qWarning("intersectingSet: row %d was invalid", row); | |
#endif | |
} | |
} | |
return ret; | |
} | |
void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &) | |
{ | |
dd->doDelayedItemsLayout(); | |
} | |
QRect QListModeViewBase::mapToViewport(const QRect &rect) const | |
{ | |
if (isWrapping()) | |
return rect; | |
// If the listview is in "listbox-mode", the items are as wide as the view. | |
// But we don't shrink the items. | |
QRect result = rect; | |
if (flow() == QListView::TopToBottom) { | |
result.setLeft(spacing()); | |
result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing())); | |
} else { // LeftToRight | |
result.setTop(spacing()); | |
result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing())); | |
} | |
return result; | |
} | |
int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const | |
{ | |
QVector<int> positions; | |
if (wrap) | |
positions = segmentPositions; | |
else if (!flowPositions.isEmpty()) { | |
positions.reserve(scrollValueMap.size()); | |
foreach (int itemShown, scrollValueMap) | |
positions.append(flowPositions.at(itemShown)); | |
} | |
if (positions.isEmpty() || bounds <= length) | |
return positions.count(); | |
if (uniformItemSizes()) { | |
for (int i = 1; i < positions.count(); ++i) | |
if (positions.at(i) > 0) | |
return length / positions.at(i); | |
return 0; // all items had height 0 | |
} | |
int pageSteps = 0; | |
int steps = positions.count() - 1; | |
int max = qMax(length, bounds); | |
int min = qMin(length, bounds); | |
int pos = min - (max - positions.last()); | |
while (pos >= 0 && steps > 0) { | |
pos -= (positions.at(steps) - positions.at(steps - 1)); | |
if (pos >= 0) //this item should be visible | |
++pageSteps; | |
--steps; | |
} | |
// at this point we know that positions has at least one entry | |
return qMax(pageSteps, 1); | |
} | |
int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize, | |
QAbstractItemView::ScrollHint hint, | |
Qt::Orientation orientation, bool wrap, int itemExtent) const | |
{ | |
if (index < 0) | |
return scrollValue; | |
if (!wrap) { | |
int topIndex = index; | |
const int bottomIndex = topIndex; | |
const int bottomCoordinate = flowPositions.at(index); | |
while (topIndex > 0 && | |
(bottomCoordinate - flowPositions.at(topIndex-1) + itemExtent) <= (viewportSize)) { | |
topIndex--; | |
} | |
const int itemCount = bottomIndex - topIndex + 1; | |
switch (hint) { | |
case QAbstractItemView::PositionAtTop: | |
return index; | |
case QAbstractItemView::PositionAtBottom: | |
return index - itemCount + 1; | |
case QAbstractItemView::PositionAtCenter: | |
return index - (itemCount / 2); | |
default: | |
break; | |
} | |
} else { // wrapping | |
Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight | |
? Qt::Horizontal : Qt::Vertical); | |
if (flowOrientation == orientation) { // scrolling in the "flow" direction | |
// ### wrapped scrolling in the flow direction | |
return flowPositions.at(index); // ### always pixel based for now | |
} else if (!segmentStartRows.isEmpty()) { // we are scrolling in the "segment" direction | |
int segment = qBinarySearch<int>(segmentStartRows, index, 0, segmentStartRows.count() - 1); | |
int leftSegment = segment; | |
const int rightSegment = leftSegment; | |
const int bottomCoordinate = segmentPositions.at(segment); | |
while (leftSegment > scrollValue && | |
(bottomCoordinate - segmentPositions.at(leftSegment-1) + itemExtent) <= (viewportSize)) { | |
leftSegment--; | |
} | |
const int segmentCount = rightSegment - leftSegment + 1; | |
switch (hint) { | |
case QAbstractItemView::PositionAtTop: | |
return segment; | |
case QAbstractItemView::PositionAtBottom: | |
return segment - segmentCount + 1; | |
case QAbstractItemView::PositionAtCenter: | |
return segment - (segmentCount / 2); | |
default: | |
break; | |
} | |
} | |
} | |
return scrollValue; | |
} | |
void QListModeViewBase::clear() | |
{ | |
flowPositions.clear(); | |
segmentPositions.clear(); | |
segmentStartRows.clear(); | |
segmentExtents.clear(); | |
batchSavedPosition = 0; | |
batchStartRow = 0; | |
batchSavedDeltaSeg = 0; | |
} | |
/* | |
* IconMode ListView Implementation | |
*/ | |
void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index) | |
{ | |
if (index.row() >= items.count()) | |
return; | |
const QSize oldContents = contentsSize; | |
qq->update(index); // update old position | |
moveItem(index.row(), position); | |
qq->update(index); // update new position | |
if (contentsSize != oldContents) | |
dd->viewUpdateGeometries(); // update the scroll bars | |
} | |
void QIconModeViewBase::appendHiddenRow(int row) | |
{ | |
if (row >= 0 && row < items.count()) //remove item | |
tree.removeLeaf(items.at(row).rect(), row); | |
QCommonListViewBase::appendHiddenRow(row); | |
} | |
void QIconModeViewBase::removeHiddenRow(int row) | |
{ | |
QCommonListViewBase::removeHiddenRow(row); | |
if (row >= 0 && row < items.count()) //insert item | |
tree.insertLeaf(items.at(row).rect(), row); | |
} | |
#ifndef QT_NO_DRAGANDDROP | |
void QIconModeViewBase::paintDragDrop(QPainter *painter) | |
{ | |
if (!draggedItems.isEmpty() && viewport()->rect().contains(draggedItemsPos)) { | |
//we need to draw the items that arre dragged | |
painter->translate(draggedItemsDelta()); | |
QStyleOptionViewItemV4 option = viewOptions(); | |
option.state &= ~QStyle::State_MouseOver; | |
QVector<QModelIndex>::const_iterator it = draggedItems.begin(); | |
QListViewItem item = indexToListViewItem(*it); | |
for (; it != draggedItems.end(); ++it) { | |
item = indexToListViewItem(*it); | |
option.rect = viewItemRect(item); | |
delegate(*it)->paint(painter, option, *it); | |
} | |
} | |
} | |
bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions) | |
{ | |
// This function does the same thing as in QAbstractItemView::startDrag(), | |
// plus adding viewitems to the draggedItems list. | |
// We need these items to draw the drag items | |
QModelIndexList indexes = dd->selectionModel->selectedIndexes(); | |
if (indexes.count() > 0 ) { | |
if (viewport()->acceptDrops()) { | |
QModelIndexList::ConstIterator it = indexes.constBegin(); | |
for (; it != indexes.constEnd(); ++it) | |
if (dd->model->flags(*it) & Qt::ItemIsDragEnabled | |
&& (*it).column() == dd->column) | |
draggedItems.push_back(*it); | |
} | |
QDrag *drag = new QDrag(qq); | |
drag->setMimeData(dd->model->mimeData(indexes)); | |
Qt::DropAction action = drag->exec(supportedActions, Qt::CopyAction); | |
draggedItems.clear(); | |
if (action == Qt::MoveAction) | |
dd->clearOrRemove(); | |
} | |
return true; | |
} | |
bool QIconModeViewBase::filterDropEvent(QDropEvent *e) | |
{ | |
if (e->source() != qq) | |
return false; | |
const QSize contents = contentsSize; | |
QPoint offset(horizontalOffset(), verticalOffset()); | |
QPoint end = e->pos() + offset; | |
if (qq->acceptDrops()) { | |
const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; | |
const QVector<QModelIndex> &dropIndices = intersectingSet(QRect(end, QSize(1, 1))); | |
foreach (const QModelIndex &index, dropIndices) | |
if ((index.flags() & dropableFlags) == dropableFlags) | |
return false; | |
} | |
QPoint start = dd->pressedPosition; | |
QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start); | |
QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes(); | |
for (int i = 0; i < indexes.count(); ++i) { | |
QModelIndex index = indexes.at(i); | |
QRect rect = dd->rectForIndex(index); | |
viewport()->update(dd->mapToViewport(rect, false)); | |
QPoint dest = rect.topLeft() + delta; | |
if (qq->isRightToLeft()) | |
dest.setX(dd->flipX(dest.x()) - rect.width()); | |
moveItem(index.row(), dest); | |
qq->update(index); | |
} | |
dd->stopAutoScroll(); | |
draggedItems.clear(); | |
dd->emitIndexesMoved(indexes); | |
e->accept(); // we have handled the event | |
// if the size has not grown, we need to check if it has shrinked | |
if (contentsSize != contents) { | |
if ((contentsSize.width() <= contents.width() | |
|| contentsSize.height() <= contents.height())) { | |
updateContentsSize(); | |
} | |
dd->viewUpdateGeometries(); | |
} | |
return true; | |
} | |
bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e) | |
{ | |
viewport()->update(draggedItemsRect()); // erase the area | |
draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items | |
return QCommonListViewBase::filterDragLeaveEvent(e); | |
} | |
bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e) | |
{ | |
if (e->source() != qq || !dd->canDecode(e)) | |
return false; | |
// ignore by default | |
e->ignore(); | |
// get old dragged items rect | |
QRect itemsRect = this->itemsRect(draggedItems); | |
viewport()->update(itemsRect.translated(draggedItemsDelta())); | |
// update position | |
draggedItemsPos = e->pos(); | |
// get new items rect | |
viewport()->update(itemsRect.translated(draggedItemsDelta())); | |
// set the item under the cursor to current | |
QModelIndex index; | |
if (movement() == QListView::Snap) { | |
QRect rect(snapToGrid(e->pos() + offset()), gridSize()); | |
const QVector<QModelIndex> intersectVector = intersectingSet(rect); | |
index = intersectVector.count() > 0 ? intersectVector.last() : QModelIndex(); | |
} else { | |
index = qq->indexAt(e->pos()); | |
} | |
// check if we allow drops here | |
if (draggedItems.contains(index)) | |
e->accept(); // allow changing item position | |
else if (dd->model->flags(index) & Qt::ItemIsDropEnabled) | |
e->accept(); // allow dropping on dropenabled items | |
else if (!index.isValid()) | |
e->accept(); // allow dropping in empty areas | |
// the event was treated. do autoscrolling | |
if (dd->shouldAutoScroll(e->pos())) | |
dd->startAutoScroll(); | |
return true; | |
} | |
#endif // QT_NO_DRAGANDDROP | |
void QIconModeViewBase::setRowCount(int rowCount) | |
{ | |
tree.create(qMax(rowCount - hiddenCount(), 0)); | |
} | |
void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand) | |
{ | |
if (scrollElasticBand) | |
dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy); | |
QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand); | |
if (!draggedItems.isEmpty()) | |
viewport()->update(draggedItemsRect().translated(dx, dy)); | |
} | |
void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) | |
{ | |
if (column() >= topLeft.column() && column() <= bottomRight.column()) { | |
QStyleOptionViewItemV4 option = viewOptions(); | |
int bottom = qMin(items.count(), bottomRight.row() + 1); | |
for (int row = topLeft.row(); row < bottom; ++row) | |
items[row].resize(itemSize(option, modelIndex(row))); | |
} | |
} | |
bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max) | |
{ | |
if (info.last >= items.count()) { | |
//first we create the items | |
QStyleOptionViewItemV4 option = viewOptions(); | |
for (int row = items.count(); row <= info.last; ++row) { | |
QSize size = itemSize(option, modelIndex(row)); | |
QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos | |
items.append(item); | |
} | |
doDynamicLayout(info); | |
} | |
return (batchStartRow > max); // done | |
} | |
QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const | |
{ | |
if (index.isValid() && index.row() < items.count()) | |
return items.at(index.row()); | |
return QListViewItem(); | |
} | |
void QIconModeViewBase::initBspTree(const QSize &contents) | |
{ | |
// remove all items from the tree | |
int leafCount = tree.leafCount(); | |
for (int l = 0; l < leafCount; ++l) | |
tree.leaf(l).clear(); | |
// we have to get the bounding rect of the items before we can initialize the tree | |
QBspTree::Node::Type type = QBspTree::Node::Both; // 2D | |
// simple heuristics to get better bsp | |
if (contents.height() / contents.width() >= 3) | |
type = QBspTree::Node::HorizontalPlane; | |
else if (contents.width() / contents.height() >= 3) | |
type = QBspTree::Node::VerticalPlane; | |
// build tree for the bounding rect (not just the contents rect) | |
tree.init(QRect(0, 0, contents.width(), contents.height()), type); | |
} | |
QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info) | |
{ | |
int x, y; | |
if (info.first == 0) { | |
x = info.bounds.x() + info.spacing; | |
y = info.bounds.y() + info.spacing; | |
items.reserve(rowCount() - hiddenCount()); | |
} else { | |
int idx = info.first - 1; | |
while (idx > 0 && !items.at(idx).isValid()) | |
--idx; | |
const QListViewItem &item = items.at(idx); | |
x = item.x; | |
y = item.y; | |
if (info.flow == QListView::LeftToRight) | |
x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing; | |
else | |
y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing; | |
} | |
return QPoint(x, y); | |
} | |
/*! | |
\internal | |
*/ | |
void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info) | |
{ | |
const bool useItemSize = !info.grid.isValid(); | |
const QPoint topLeft = initDynamicLayout(info); | |
int segStartPosition; | |
int segEndPosition; | |
int deltaFlowPosition; | |
int deltaSegPosition; | |
int deltaSegHint; | |
int flowPosition; | |
int segPosition; | |
if (info.flow == QListView::LeftToRight) { | |
segStartPosition = info.bounds.left() + info.spacing; | |
segEndPosition = info.bounds.right(); | |
deltaFlowPosition = info.grid.width(); // dx | |
deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy | |
deltaSegHint = info.grid.height(); | |
flowPosition = topLeft.x(); | |
segPosition = topLeft.y(); | |
} else { // flow == QListView::TopToBottom | |
segStartPosition = info.bounds.top() + info.spacing; | |
segEndPosition = info.bounds.bottom(); | |
deltaFlowPosition = info.grid.height(); // dy | |
deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx | |
deltaSegHint = info.grid.width(); | |
flowPosition = topLeft.y(); | |
segPosition = topLeft.x(); | |
} | |
if (moved.count() != items.count()) | |
moved.resize(items.count()); | |
QRect rect(QPoint(), topLeft); | |
QListViewItem *item = 0; | |
for (int row = info.first; row <= info.last; ++row) { | |
item = &items[row]; | |
if (isHidden(row)) { | |
item->invalidate(); | |
} else { | |
// if we are not using a grid, we need to find the deltas | |
if (useItemSize) { | |
if (info.flow == QListView::LeftToRight) | |
deltaFlowPosition = item->w + info.spacing; | |
else | |
deltaFlowPosition = item->h + info.spacing; | |
} else { | |
item->w = qMin<int>(info.grid.width(), item->w); | |
item->h = qMin<int>(info.grid.height(), item->h); | |
} | |
// create new segment | |
if (info.wrap | |
&& flowPosition + deltaFlowPosition > segEndPosition | |
&& flowPosition > segStartPosition) { | |
flowPosition = segStartPosition; | |
segPosition += deltaSegPosition; | |
if (useItemSize) | |
deltaSegPosition = 0; | |
} | |
// We must delay calculation of the seg adjustment, as this item | |
// may have caused a wrap to occur | |
if (useItemSize) { | |
if (info.flow == QListView::LeftToRight) | |
deltaSegHint = item->h + info.spacing; | |
else | |
deltaSegHint = item->w + info.spacing; | |
deltaSegPosition = qMax(deltaSegPosition, deltaSegHint); | |
} | |
// set the position of the item | |
// ### idealy we should have some sort of alignment hint for the item | |
// ### (normally that would be a point between the icon and the text) | |
if (!moved.testBit(row)) { | |
if (info.flow == QListView::LeftToRight) { | |
if (useItemSize) { | |
item->x = flowPosition; | |
item->y = segPosition; | |
} else { // use grid | |
item->x = flowPosition + ((deltaFlowPosition - item->w) / 2); | |
item->y = segPosition; | |
} | |
} else { // TopToBottom | |
if (useItemSize) { | |
item->y = flowPosition; | |
item->x = segPosition; | |
} else { // use grid | |
item->y = flowPosition + ((deltaFlowPosition - item->h) / 2); | |
item->x = segPosition; | |
} | |
} | |
} | |
// let the contents contain the new item | |
if (useItemSize) | |
rect |= item->rect(); | |
else if (info.flow == QListView::LeftToRight) | |
rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition); | |
else // flow == TopToBottom | |
rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition); | |
// prepare for next item | |
flowPosition += deltaFlowPosition; // current position + item width + gap | |
} | |
} | |
batchSavedDeltaSeg = deltaSegPosition; | |
batchStartRow = info.last + 1; | |
bool done = (info.last >= rowCount() - 1); | |
// resize the content area | |
if (done || !info.bounds.contains(item->rect())) { | |
contentsSize = rect.size(); | |
if (info.flow == QListView::LeftToRight) | |
contentsSize.rheight() += info.spacing; | |
else | |
contentsSize.rwidth() += info.spacing; | |
} | |
if (rect.size().isEmpty()) | |
return; | |
// resize tree | |
int insertFrom = info.first; | |
if (done || info.first == 0) { | |
initBspTree(rect.size()); | |
insertFrom = 0; | |
} | |
// insert items in tree | |
for (int row = insertFrom; row <= info.last; ++row) | |
tree.insertLeaf(items.at(row).rect(), row); | |
// if the new items are visble, update the viewport | |
QRect changedRect(topLeft, rect.bottomRight()); | |
if (clipRect().intersects(changedRect)) | |
viewport()->update(); | |
} | |
QVector<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const | |
{ | |
QIconModeViewBase *that = const_cast<QIconModeViewBase*>(this); | |
QBspTree::Data data(static_cast<void*>(that)); | |
QVector<QModelIndex> res; | |
that->interSectingVector = &res; | |
that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data); | |
that->interSectingVector = 0; | |
return res; | |
} | |
QRect QIconModeViewBase::itemsRect(const QVector<QModelIndex> &indexes) const | |
{ | |
QVector<QModelIndex>::const_iterator it = indexes.begin(); | |
QListViewItem item = indexToListViewItem(*it); | |
QRect rect(item.x, item.y, item.w, item.h); | |
for (; it != indexes.end(); ++it) { | |
item = indexToListViewItem(*it); | |
rect |= viewItemRect(item); | |
} | |
return rect; | |
} | |
int QIconModeViewBase::itemIndex(const QListViewItem &item) const | |
{ | |
if (!item.isValid()) | |
return -1; | |
int i = item.indexHint; | |
if (i < items.count()) { | |
if (items.at(i) == item) | |
return i; | |
} else { | |
i = items.count() - 1; | |
} | |
int j = i; | |
int c = items.count(); | |
bool a = true; | |
bool b = true; | |
while (a || b) { | |
if (a) { | |
if (items.at(i) == item) { | |
items.at(i).indexHint = i; | |
return i; | |
} | |
a = ++i < c; | |
} | |
if (b) { | |
if (items.at(j) == item) { | |
items.at(j).indexHint = j; | |
return j; | |
} | |
b = --j > -1; | |
} | |
} | |
return -1; | |
} | |
void QIconModeViewBase::addLeaf(QVector<int> &leaf, const QRect &area, | |
uint visited, QBspTree::Data data) | |
{ | |
QListViewItem *vi; | |
QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr); | |
for (int i = 0; i < leaf.count(); ++i) { | |
int idx = leaf.at(i); | |
if (idx < 0 || idx >= _this->items.count()) | |
continue; | |
vi = &_this->items[idx]; | |
Q_ASSERT(vi); | |
if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) { | |
QModelIndex index = _this->dd->listViewItemToIndex(*vi); | |
Q_ASSERT(index.isValid()); | |
_this->interSectingVector->append(index); | |
vi->visited = visited; | |
} | |
} | |
} | |
void QIconModeViewBase::moveItem(int index, const QPoint &dest) | |
{ | |
// does not impact on the bintree itself or the contents rect | |
QListViewItem *item = &items[index]; | |
QRect rect = item->rect(); | |
// move the item without removing it from the tree | |
tree.removeLeaf(rect, index); | |
item->move(dest); | |
tree.insertLeaf(QRect(dest, rect.size()), index); | |
// resize the contents area | |
contentsSize = (QRect(QPoint(0, 0), contentsSize)|QRect(dest, rect.size())).size(); | |
// mark the item as moved | |
if (moved.count() != items.count()) | |
moved.resize(items.count()); | |
moved.setBit(index, true); | |
} | |
QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const | |
{ | |
int x = pos.x() - (pos.x() % gridSize().width()); | |
int y = pos.y() - (pos.y() % gridSize().height()); | |
return QPoint(x, y); | |
} | |
QPoint QIconModeViewBase::draggedItemsDelta() const | |
{ | |
if (movement() == QListView::Snap) { | |
QPoint snapdelta = QPoint((offset().x() % gridSize().width()), | |
(offset().y() % gridSize().height())); | |
return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta; | |
} | |
return draggedItemsPos - pressedPosition(); | |
} | |
QRect QIconModeViewBase::draggedItemsRect() const | |
{ | |
QRect rect = itemsRect(draggedItems); | |
rect.translate(draggedItemsDelta()); | |
return rect; | |
} | |
void QListViewPrivate::scrollElasticBandBy(int dx, int dy) | |
{ | |
if (dx > 0) // right | |
elasticBand.moveRight(elasticBand.right() + dx); | |
else if (dx < 0) // left | |
elasticBand.moveLeft(elasticBand.left() - dx); | |
if (dy > 0) // down | |
elasticBand.moveBottom(elasticBand.bottom() + dy); | |
else if (dy < 0) // up | |
elasticBand.moveTop(elasticBand.top() - dy); | |
} | |
void QIconModeViewBase::clear() | |
{ | |
tree.destroy(); | |
items.clear(); | |
moved.clear(); | |
batchStartRow = 0; | |
batchSavedDeltaSeg = 0; | |
} | |
void QIconModeViewBase::updateContentsSize() | |
{ | |
QRect bounding; | |
for (int i = 0; i < items.count(); ++i) | |
bounding |= items.at(i).rect(); | |
contentsSize = bounding.size(); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) | |
{ | |
#ifndef QT_NO_ACCESSIBILITY | |
if (QAccessible::isActive()) { | |
if (current.isValid()) { | |
int entry = visualIndex(current) + 1; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); | |
} | |
} | |
#endif | |
QAbstractItemView::currentChanged(current, previous); | |
} | |
/*! | |
\reimp | |
*/ | |
void QListView::selectionChanged(const QItemSelection &selected, | |
const QItemSelection &deselected) | |
{ | |
#ifndef QT_NO_ACCESSIBILITY | |
if (QAccessible::isActive()) { | |
// ### does not work properly for selection ranges. | |
QModelIndex sel = selected.indexes().value(0); | |
if (sel.isValid()) { | |
int entry = visualIndex(sel) + 1; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); | |
} | |
QModelIndex desel = deselected.indexes().value(0); | |
if (desel.isValid()) { | |
int entry = visualIndex(desel) + 1; | |
QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); | |
} | |
} | |
#endif | |
QAbstractItemView::selectionChanged(selected, deselected); | |
} | |
int QListView::visualIndex(const QModelIndex &index) const | |
{ | |
Q_D(const QListView); | |
d->executePostedLayout(); | |
QListViewItem itm = d->indexToListViewItem(index); | |
return d->commonListView->itemIndex(itm); | |
} | |
QT_END_NAMESPACE | |
#endif // QT_NO_LISTVIEW |