/****************************************************************************
**
** Copyright (C) 2010 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$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, 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.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qtableview.h"
#ifndef QT_NO_TABLEVIEW
#include <qheaderview.h>
#include <qitemdelegate.h>
#include <qapplication.h>
#include <qpainter.h>
#include <qstyle.h>
#include <qsize.h>
#include <qevent.h>
#include <qbitarray.h>
#include <qscrollbar.h>
#include <qabstractbutton.h>
#include <private/qtableview_p.h>
#ifndef QT_NO_ACCESSIBILITY
#include <qaccessible.h>
#endif
QT_BEGIN_NAMESPACE
/** \internal
Add a span to the collection. the collection takes the ownership.
*/
void QSpanCollection::addSpan(QSpanCollection::Span *span)
{
spans.append(span);
Index::iterator it_y = index.lowerBound(-span->top());
if (it_y == index.end() || it_y.key() != -span->top()) {
//there is no spans that starts with the row in the index, so create a sublist for it.
SubIndex sub_index;
if (it_y != index.end()) {
//the previouslist is the list of spans that sarts _before_ the row of the span.
// and which may intersect this row.
const SubIndex previousList = it_y.value();
foreach(Span *s, previousList) {
//If a subspans intersect the row, we need to split it into subspans
if(s->bottom() >= span->top())
sub_index.insert(-s->left(), s);
}
}
it_y = index.insert(-span->top(), sub_index);
//we will insert span to *it_y in the later loop
}
//insert the span as supspan in all the lists that intesects the span
while(-it_y.key() <= span->bottom()) {
(*it_y).insert(-span->left(), span);
if(it_y == index.begin())
break;
--it_y;
}
}
/** \internal
* Has to be called after the height and width of a span is changed.
*
* old_height is the height before the change
*
* if the size of the span is now 0x0 the span will be deleted.
*/
void QSpanCollection::updateSpan(QSpanCollection::Span *span, int old_height)
{
if (old_height < span->height()) {
//add the span as subspan in all the lists that intersect the new covered columns
Index::iterator it_y = index.lowerBound(-(span->top() + old_height - 1));
Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
while (-it_y.key() <= span->bottom()) {
(*it_y).insert(-span->left(), span);
if(it_y == index.begin())
break;
--it_y;
}
} else if (old_height > span->height()) {
//remove the span from all the subspans lists that intersect the columns not covered anymore
Index::iterator it_y = index.lowerBound(-qMax(span->bottom(), span->top())); //qMax usefull if height is 0
Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
while (-it_y.key() <= span->top() + old_height -1) {
if (-it_y.key() > span->bottom()) {
int removed = (*it_y).remove(-span->left());
Q_ASSERT(removed == 1); Q_UNUSED(removed);
if (it_y->isEmpty()) {
it_y = index.erase(it_y);
}
}
if(it_y == index.begin())
break;
--it_y;
}
}
if (span->width() == 0 && span->height() == 0) {
spans.removeOne(span);
delete span;
}
}
/** \internal
* \return a spans that spans over cell x,y (column,row) or 0 if there is none.
*/
QSpanCollection::Span *QSpanCollection::spanAt(int x, int y) const
{
Index::const_iterator it_y = index.lowerBound(-y);
if (it_y == index.end())
return 0;
SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
if (it_x == (*it_y).end())
return 0;
Span *span = *it_x;
if (span->right() >= x && span->bottom() >= y)
return span;
return 0;
}
/** \internal
* remove and deletes all spans inside the collection
*/
void QSpanCollection::clear()
{
qDeleteAll(spans);
index.clear();
spans.clear();
}
/** \internal
* return a list to all the spans that spans over cells in the given rectangle
*/
QList<QSpanCollection::Span *> QSpanCollection::spansInRect(int x, int y, int w, int h) const
{
QSet<Span *> list;
Index::const_iterator it_y = index.lowerBound(-y);
if(it_y == index.end())
--it_y;
while(-it_y.key() <= y + h) {
SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
if (it_x == (*it_y).end())
--it_x;
while(-it_x.key() <= x + w) {
Span *s = *it_x;
if (s->bottom() >= y && s->right() >= x)
list << s;
if (it_x == (*it_y).begin())
break;
--it_x;
}
if(it_y == index.begin())
break;
--it_y;
}
return list.toList();
}
#undef DEBUG_SPAN_UPDATE
#ifdef DEBUG_SPAN_UPDATE
QDebug operator<<(QDebug str, const QSpanCollection::Span &span)
{
str << "(" << span.top() << "," << span.left() << "," << span.bottom() << "," << span.right() << ")";
return str;
}
#endif
/** \internal
* Updates the span collection after row insertion.
*/
void QSpanCollection::updateInsertedRows(int start, int end)
{
#ifdef DEBUG_SPAN_UPDATE
qDebug() << Q_FUNC_INFO;
qDebug() << start << end;
qDebug() << index;
#endif
if (spans.isEmpty())
return;
int delta = end - start + 1;
#ifdef DEBUG_SPAN_UPDATE
qDebug("Before");
#endif
for (SpanList::iterator it = spans.begin(); it != spans.end(); ++it) {
Span *span = *it;
#ifdef DEBUG_SPAN_UPDATE
qDebug() << span << *span;
#endif
if (span->m_bottom < start)
continue;
if (span->m_top >= start)
span->m_top += delta;
span->m_bottom += delta;
}
#ifdef DEBUG_SPAN_UPDATE
qDebug("After");
foreach (QSpanCollection::Span *span, spans)
qDebug() << span << *span;
#endif
for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
int y = -it_y.key();
if (y < start) {
++it_y;
continue;
}
index.insert(-y - delta, it_y.value());
it_y = index.erase(it_y);
}
#ifdef DEBUG_SPAN_UPDATE
qDebug() << index;
#endif
}
/** \internal
* Updates the span collection after column insertion.
*/
void QSpanCollection::updateInsertedColumns(int start, int end)
{
#ifdef DEBUG_SPAN_UPDATE
qDebug() << Q_FUNC_INFO;
qDebug() << start << end;
qDebug() << index;
#endif
if (spans.isEmpty())
return;
int delta = end - start + 1;
#ifdef DEBUG_SPAN_UPDATE
qDebug("Before");
#endif
for (SpanList::iterator it = spans.begin(); it != spans.end(); ++it) {
Span *span = *it;
#ifdef DEBUG_SPAN_UPDATE
qDebug() << span << *span;
#endif
if (span->m_right < start)
continue;
if (span->m_left >= start)
span->m_left += delta;
span->m_right += delta;
}
#ifdef DEBUG_SPAN_UPDATE
qDebug("After");
foreach (QSpanCollection::Span *span, spans)
qDebug() << span << *span;
#endif
for (Index::iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
SubIndex &subindex = it_y.value();
for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
int x = -it.key();
if (x < start) {
++it;
continue;
}
subindex.insert(-x - delta, it.value());
it = subindex.erase(it);
}
}
#ifdef DEBUG_SPAN_UPDATE
qDebug() << index;
#endif
}
/** \internal
* Cleans a subindex from to be deleted spans. The update argument is used
* to move the spans inside the subindex, in case their anchor changed.
* \return true if no span in this subindex starts at y, and should thus be deleted.
*/
bool QSpanCollection::cleanSpanSubIndex(QSpanCollection::SubIndex &subindex, int y, bool update)
{
if (subindex.isEmpty())
return true;
bool should_be_deleted = true;
SubIndex::iterator it = subindex.end();
do {
--it;
int x = -it.key();
Span *span = it.value();
if (span->will_be_deleted) {
it = subindex.erase(it);
continue;
}
if (update && span->m_left != x) {
subindex.insert(-span->m_left, span);
it = subindex.erase(it);
}
if (should_be_deleted && span->m_top == y)
should_be_deleted = false;
} while (it != subindex.begin());
return should_be_deleted;
}
/** \internal
* Updates the span collection after row removal.
*/
void QSpanCollection::updateRemovedRows(int start, int end)
{
#ifdef DEBUG_SPAN_UPDATE
qDebug() << Q_FUNC_INFO;
qDebug() << start << end;
qDebug() << index;
#endif
if (spans.isEmpty())
return;
SpanList spansToBeDeleted;
int delta = end - start + 1;
#ifdef DEBUG_SPAN_UPDATE
qDebug("Before");
#endif
for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
Span *span = *it;
#ifdef DEBUG_SPAN_UPDATE
qDebug() << span << *span;
#endif
if (span->m_bottom < start) {
++it;
continue;
}
if (span->m_top < start) {
if (span->m_bottom <= end)
span->m_bottom = start - 1;
else
span->m_bottom -= delta;
} else {
if (span->m_bottom > end) {
if (span->m_top <= end)
span->m_top = start;
else
span->m_top -= delta;
span->m_bottom -= delta;
} else {
span->will_be_deleted = true;
}
}
if (span->m_top == span->m_bottom && span->m_left == span->m_right)
span->will_be_deleted = true;
if (span->will_be_deleted) {
spansToBeDeleted.append(span);
it = spans.erase(it);
} else {
++it;
}
}
#ifdef DEBUG_SPAN_UPDATE
qDebug("After");
foreach (QSpanCollection::Span *span, spans)
qDebug() << span << *span;
#endif
if (spans.isEmpty()) {
qDeleteAll(spansToBeDeleted);
index.clear();
return;
}
Index::iterator it_y = index.end();
do {
--it_y;
int y = -it_y.key();
SubIndex &subindex = it_y.value();
if (y < start) {
if (cleanSpanSubIndex(subindex, y))
it_y = index.erase(it_y);
} else if (y >= start && y <= end) {
bool span_at_start = false;
SubIndex spansToBeMoved;
for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ++it) {
Span *span = it.value();
if (span->will_be_deleted)
continue;
if (!span_at_start && span->m_top == start)
span_at_start = true;
spansToBeMoved.insert(it.key(), span);
}
if (y == start && span_at_start)
subindex.clear();
else
it_y = index.erase(it_y);
if (span_at_start) {
Index::iterator it_start;
if (y == start)
it_start = it_y;
else {
it_start = index.find(-start);
if (it_start == index.end())
it_start = index.insert(-start, SubIndex());
}
SubIndex &start_subindex = it_start.value();
for (SubIndex::iterator it = spansToBeMoved.begin(); it != spansToBeMoved.end(); ++it)
start_subindex.insert(it.key(), it.value());
}
} else {
if (y == end + 1) {
Index::iterator it_top = index.find(-y + delta);
if (it_top == index.end())
it_top = index.insert(-y + delta, SubIndex());
for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
Span *span = it.value();
if (!span->will_be_deleted)
it_top.value().insert(it.key(), span);
++it;
}
} else {
index.insert(-y + delta, subindex);
}
it_y = index.erase(it_y);
}
} while (it_y != index.begin());
#ifdef DEBUG_SPAN_UPDATE
qDebug() << index;
qDebug("Deleted");
foreach (QSpanCollection::Span *span, spansToBeDeleted)
qDebug() << span << *span;
#endif
qDeleteAll(spansToBeDeleted);
}
/** \internal
* Updates the span collection after column removal.
*/
void QSpanCollection::updateRemovedColumns(int start, int end)
{
#ifdef DEBUG_SPAN_UPDATE
qDebug() << Q_FUNC_INFO;
qDebug() << start << end;
qDebug() << index;
#endif
if (spans.isEmpty())
return;
SpanList toBeDeleted;
int delta = end - start + 1;
#ifdef DEBUG_SPAN_UPDATE
qDebug("Before");
#endif
for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
Span *span = *it;
#ifdef DEBUG_SPAN_UPDATE
qDebug() << span << *span;
#endif
if (span->m_right < start) {
++it;
continue;
}
if (span->m_left < start) {
if (span->m_right <= end)
span->m_right = start - 1;
else
span->m_right -= delta;
} else {
if (span->m_right > end) {
if (span->m_left <= end)
span->m_left = start;
else
span->m_left -= delta;
span->m_right -= delta;
} else {
span->will_be_deleted = true;
}
}
if (span->m_top == span->m_bottom && span->m_left == span->m_right)
span->will_be_deleted = true;
if (span->will_be_deleted) {
toBeDeleted.append(span);
it = spans.erase(it);
} else {
++it;
}
}
#ifdef DEBUG_SPAN_UPDATE
qDebug("After");
foreach (QSpanCollection::Span *span, spans)
qDebug() << span << *span;
#endif
if (spans.isEmpty()) {
qDeleteAll(toBeDeleted);
index.clear();
return;
}
for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
int y = -it_y.key();
if (cleanSpanSubIndex(it_y.value(), y, true))
it_y = index.erase(it_y);
else
++it_y;
}
#ifdef DEBUG_SPAN_UPDATE
qDebug() << index;
qDebug("Deleted");
foreach (QSpanCollection::Span *span, toBeDeleted)
qDebug() << span << *span;
#endif
qDeleteAll(toBeDeleted);
}
#ifdef QT_BUILD_INTERNAL
/*!
\internal
Checks whether the span index structure is self-consistent, and consistent with the spans list.
*/
bool QSpanCollection::checkConsistency() const
{
for (Index::const_iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
int y = -it_y.key();
const SubIndex &subIndex = it_y.value();
for (SubIndex::const_iterator it = subIndex.begin(); it != subIndex.end(); ++it) {
int x = -it.key();
Span *span = it.value();
if (!spans.contains(span) || span->left() != x
|| y < span->top() || y > span->bottom())
return false;
}
}
foreach (const Span *span, spans) {
if (span->width() < 1 || span->height() < 1
|| (span->width() == 1 && span->height() == 1))
return false;
for (int y = span->top(); y <= span->bottom(); ++y) {
Index::const_iterator it_y = index.find(-y);
if (it_y == index.end()) {
if (y == span->top())
return false;
else
continue;
}
const SubIndex &subIndex = it_y.value();
SubIndex::const_iterator it = subIndex.find(-span->left());
if (it == subIndex.end() || it.value() != span)
return false;
}
}
return true;
}
#endif
class QTableCornerButton : public QAbstractButton
{
Q_OBJECT
public:
QTableCornerButton(QWidget *parent) : QAbstractButton(parent) {}
void paintEvent(QPaintEvent*) {
QStyleOptionHeader opt;
opt.init(this);
QStyle::State state = QStyle::State_None;
if (isEnabled())
state |= QStyle::State_Enabled;
if (isActiveWindow())
state |= QStyle::State_Active;
if (isDown())
state |= QStyle::State_Sunken;
opt.state = state;
opt.rect = rect();
opt.position = QStyleOptionHeader::OnlyOneSection;
QPainter painter(this);
style()->drawControl(QStyle::CE_Header, &opt, &painter, this);
}
};
void QTableViewPrivate::init()
{
Q_Q(QTableView);
q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed);
QHeaderView *vertical = new QHeaderView(Qt::Vertical, q);
vertical->setClickable(true);
vertical->setHighlightSections(true);
q->setVerticalHeader(vertical);
QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q);
horizontal->setClickable(true);
horizontal->setHighlightSections(true);
q->setHorizontalHeader(horizontal);
tabKeyNavigation = true;
cornerWidget = new QTableCornerButton(q);
cornerWidget->setFocusPolicy(Qt::NoFocus);
QObject::connect(cornerWidget, SIGNAL(clicked()), q, SLOT(selectAll()));
}
/*!
\internal
Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections.
*/
void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const
{
Q_ASSERT(range && range->isValid());
int top = range->top();
int left = range->left();
int bottom = range->bottom();
int right = range->right();
while (bottom >= top && verticalHeader->isSectionHidden(bottom))
--bottom;
while (right >= left && horizontalHeader->isSectionHidden(right))
--right;
if (top > bottom || left > right) { // everything is hidden
*range = QItemSelectionRange();
return;
}
while (verticalHeader->isSectionHidden(top) && top <= bottom)
++top;
while (horizontalHeader->isSectionHidden(left) && left <= right)
++left;
if (top > bottom || left > right) { // everything is hidden
*range = QItemSelectionRange();
return;
}
QModelIndex bottomRight = model->index(bottom, right, range->parent());
QModelIndex topLeft = model->index(top, left, range->parent());
*range = QItemSelectionRange(topLeft, bottomRight);
}
/*!
\internal
Sets the span for the cell at (\a row, \a column).
*/
void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan)
{
if (row < 0 || column < 0 || rowSpan <= 0 || columnSpan <= 0) {
qWarning() << "QTableView::setSpan: invalid span given: (" << row << ',' << column << ',' << rowSpan << ',' << columnSpan << ')';
return;
}
QSpanCollection::Span *sp = spans.spanAt(column, row);
if (sp) {
if (sp->top() != row || sp->left() != column) {
qWarning() << "QTableView::setSpan: span cannot overlap";
return;
}
if (rowSpan == 1 && columnSpan == 1) {
rowSpan = columnSpan = 0;
}
const int old_height = sp->height();
sp->m_bottom = row + rowSpan - 1;
sp->m_right = column + columnSpan - 1;
spans.updateSpan(sp, old_height);
return;
} else if (rowSpan == 1 && columnSpan == 1) {
qWarning() << "QTableView::setSpan: single cell span won't be added";
return;
}
sp = new QSpanCollection::Span(row, column, rowSpan, columnSpan);
spans.addSpan(sp);
}
/*!
\internal
Gets the span information for the cell at (\a row, \a column).
*/
QSpanCollection::Span QTableViewPrivate::span(int row, int column) const
{
QSpanCollection::Span *sp = spans.spanAt(column, row);
if (sp)
return *sp;
return QSpanCollection::Span(row, column, 1, 1);
}
/*!
\internal
Returns the logical index of the last section that's part of the span.
*/
int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const
{
int visual = header->visualIndex(logical);
for (int i = 1; i < span; ) {
if (++visual >= header->count())
break;
logical = header->logicalIndex(visual);
++i;
}
return logical;
}
/*!
\internal
Returns the size of the span starting at logical index \a logical
and spanning \a span sections.
*/
int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const
{
int endLogical = sectionSpanEndLogical(header, logical, span);
return header->sectionPosition(endLogical)
- header->sectionPosition(logical)
+ header->sectionSize(endLogical);
}
/*!
\internal
Returns true if the section at logical index \a logical is part of the span
starting at logical index \a spanLogical and spanning \a span sections;
otherwise, returns false.
*/
bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const
{
if (logical == spanLogical)
return true; // it's the start of the span
int visual = header->visualIndex(spanLogical);
for (int i = 1; i < span; ) {
if (++visual >= header->count())
break;
spanLogical = header->logicalIndex(visual);
if (logical == spanLogical)
return true;
++i;
}
return false;
}
/*!
\internal
Returns the visual rect for the given \a span.
*/
QRect QTableViewPrivate::visualSpanRect(const QSpanCollection::Span &span) const
{
Q_Q(const QTableView);
// vertical
int row = span.top();
int rowp = verticalHeader->sectionViewportPosition(row);
int rowh = rowSpanHeight(row, span.height());
// horizontal
int column = span.left();
int colw = columnSpanWidth(column, span.width());
if (q->isRightToLeft())
column = span.right();
int colp = horizontalHeader->sectionViewportPosition(column);
const int i = showGrid ? 1 : 0;
if (q->isRightToLeft())
return QRect(colp + i, rowp, colw - i, rowh - i);
return QRect(colp, rowp, colw - i, rowh - i);
}
/*!
\internal
Draws the spanning cells within rect \a area, and clips them off as
preparation for the main drawing loop.
\a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn
*/
void QTableViewPrivate::drawAndClipSpans(const QRegion &area, QPainter *painter,
const QStyleOptionViewItemV4 &option, QBitArray *drawn,
int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn)
{
bool alternateBase = false;
QRegion region = viewport->rect();
QList<QSpanCollection::Span *> visibleSpans;
bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved();
if (!sectionMoved) {
visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow),
lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1);
} else {
QSet<QSpanCollection::Span *> set;
for(int x = firstVisualColumn; x <= lastVisualColumn; x++)
for(int y = firstVisualRow; y <= lastVisualRow; y++)
set.insert(spans.spanAt(x,y));
set.remove(0);
visibleSpans = set.toList();
}
foreach (QSpanCollection::Span *span, visibleSpans) {
int row = span->top();
int col = span->left();
QModelIndex index = model->index(row, col, root);
if (!index.isValid())
continue;
QRect rect = visualSpanRect(*span);
rect.translate(scrollDelayOffset);
if (!area.intersects(rect))
continue;
QStyleOptionViewItemV4 opt = option;
opt.rect = rect;
alternateBase = alternatingColors && (span->top() & 1);
if (alternateBase)
opt.features |= QStyleOptionViewItemV2::Alternate;
else
opt.features &= ~QStyleOptionViewItemV2::Alternate;
drawCell(painter, opt, index);
region -= rect;
for (int r = span->top(); r <= span->bottom(); ++r) {
const int vr = visualRow(r);
if (vr < firstVisualRow || vr > lastVisualRow)
continue;
for (int c = span->left(); c <= span->right(); ++c) {
const int vc = visualColumn(c);
if (vc < firstVisualColumn || vc > lastVisualColumn)
continue;
drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
+ vc - firstVisualColumn);
}
}
}
painter->setClipRegion(region);
}
/*!
\internal
Updates spans after row insertion.
*/
void QTableViewPrivate::_q_updateSpanInsertedRows(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
spans.updateInsertedRows(start, end);
}
/*!
\internal
Updates spans after column insertion.
*/
void QTableViewPrivate::_q_updateSpanInsertedColumns(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
spans.updateInsertedColumns(start, end);
}
/*!
\internal
Updates spans after row removal.
*/
void QTableViewPrivate::_q_updateSpanRemovedRows(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
spans.updateRemovedRows(start, end);
}
/*!
\internal
Updates spans after column removal.
*/
void QTableViewPrivate::_q_updateSpanRemovedColumns(const QModelIndex &parent, int start, int end)
{
Q_UNUSED(parent)
spans.updateRemovedColumns(start, end);
}
/*!
\internal
Draws a table cell.
*/
void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItemV4 &option, const QModelIndex &index)
{
Q_Q(QTableView);
QStyleOptionViewItemV4 opt = option;
if (selectionModel && selectionModel->isSelected(index))
opt.state |= QStyle::State_Selected;
if (index == hover)
opt.state |= QStyle::State_MouseOver;
if (option.state & QStyle::State_Enabled) {
QPalette::ColorGroup cg;
if ((model->flags(index) & Qt::ItemIsEnabled) == 0) {
opt.state &= ~QStyle::State_Enabled;
cg = QPalette::Disabled;
} else {
cg = QPalette::Normal;
}
opt.palette.setCurrentColorGroup(cg);
}
if (index == q->currentIndex()) {
const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid();
if (focus)
opt.state |= QStyle::State_HasFocus;
}
q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);
if (const QWidget *widget = editorForIndex(index).editor) {
painter->save();
painter->setClipRect(widget->geometry());
q->itemDelegate(index)->paint(painter, opt, index);
painter->restore();
} else {
q->itemDelegate(index)->paint(painter, opt, index);
}
}
/*!
\class QTableView
\brief The QTableView class provides a default model/view
implementation of a table view.
\ingroup model-view
\ingroup advanced
A QTableView implements a table view that displays items from a
model. This class is used to provide standard tables that were
previously provided by the QTable class, but using the more
flexible approach provided by Qt's model/view architecture.
The QTableView class is one of the \l{Model/View Classes}
and is part of Qt's \l{Model/View Programming}{model/view framework}.
QTableView implements the interfaces defined by the
QAbstractItemView class to allow it to display data provided by
models derived from the QAbstractItemModel class.
\section1 Navigation
You can navigate the cells in the table by clicking on a cell with the
mouse, or by using the arrow keys. Because QTableView enables
\l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you
can also hit Tab and Backtab to move from cell to cell.
\section1 Visual Appearance
The table has a vertical header that can be obtained using the
verticalHeader() function, and a horizontal header that is available
through the horizontalHeader() function. The height of each row in the
table can be found by using rowHeight(); similarly, the width of
columns can be found using columnWidth(). Since both of these are plain
widgets, you can hide either of them using their hide() functions.
Rows and columns can be hidden and shown with hideRow(), hideColumn(),
showRow(), and showColumn(). They can be selected with selectRow()
and selectColumn(). The table will show a grid depending on the
\l showGrid property.
The items shown in a table view, like those in the other item views, are
rendered and edited using standard \l{QItemDelegate}{delegates}. However,
for some tasks it is sometimes useful to be able to insert widgets in a
table instead. Widgets are set for particular indexes with the
\l{QAbstractItemView::}{setIndexWidget()} function, and
later retrieved with \l{QAbstractItemView::}{indexWidget()}.
\table
\row \o \inlineimage qtableview-resized.png
\o By default, the cells in a table do not expand to fill the available space.
You can make the cells fill the available space by stretching the last
header section. Access the relevant header using horizontalHeader()
or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection}
property.
To distribute the available space according to the space requirement of
each column or row, call the view's resizeColumnsToContents() or
resizeRowsToContents() functions.
\endtable
\section1 Coordinate Systems
For some specialized forms of tables it is useful to be able to
convert between row and column indexes and widget coordinates.
The rowAt() function provides the y-coordinate within the view of the
specified row; the row index can be used to obtain a corresponding
y-coordinate with rowViewportPosition(). The columnAt() and
columnViewportPosition() functions provide the equivalent conversion
operations between x-coordinates and column indexes.
\section1 Styles
QTableView is styled appropriately for each platform. The following images show
how it looks on three different platforms. Go to the \l{Qt Widget Gallery} to see
its appearance in other styles.
\table 100%
\row \o \inlineimage windowsxp-tableview.png Screenshot of a Windows XP style table view
\o \inlineimage macintosh-tableview.png Screenshot of a Macintosh style table view
\o \inlineimage plastique-tableview.png Screenshot of a Plastique style table view
\row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} table view.
\o A \l{Macintosh Style Widget Gallery}{Macintosh style} table view.
\o A \l{Plastique Style Widget Gallery}{Plastique style} table view.
\endtable
\sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
{Chart Example}, {Pixelator Example}, {Table Model Example}
*/
/*!
Constructs a table view with a \a parent to represent the data.
\sa QAbstractItemModel
*/
QTableView::QTableView(QWidget *parent)
: QAbstractItemView(*new QTableViewPrivate, parent)
{
Q_D(QTableView);
d->init();
}
/*!
\internal
*/
QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent)
: QAbstractItemView(dd, parent)
{
Q_D(QTableView);
d->init();
}
/*!
Destroys the table view.
*/
QTableView::~QTableView()
{
}
/*!
\reimp
*/
void QTableView::setModel(QAbstractItemModel *model)
{
Q_D(QTableView);
if (model == d->model)
return;
//let's disconnect from the old model
if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int)));
disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int)));
disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int)));
disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int)));
}
if (model) { //and connect to the new one
connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int)));
connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int)));
connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int)));
connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int)));
}
d->verticalHeader->setModel(model);
d->horizontalHeader->setModel(model);
QAbstractItemView::setModel(model);
}
/*!
\reimp
*/
void QTableView::setRootIndex(const QModelIndex &index)
{
Q_D(QTableView);
if (index == d->root) {
viewport()->update();
return;
}
d->verticalHeader->setRootIndex(index);
d->horizontalHeader->setRootIndex(index);
QAbstractItemView::setRootIndex(index);
}
/*!
\reimp
*/
void QTableView::setSelectionModel(QItemSelectionModel *selectionModel)
{
Q_D(QTableView);
Q_ASSERT(selectionModel);
d->verticalHeader->setSelectionModel(selectionModel);
d->horizontalHeader->setSelectionModel(selectionModel);
QAbstractItemView::setSelectionModel(selectionModel);
}
/*!
Returns the table view's horizontal header.
\sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData()
*/
QHeaderView *QTableView::horizontalHeader() const
{
Q_D(const QTableView);
return d->horizontalHeader;
}
/*!
Returns the table view's vertical header.
\sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData()
*/
QHeaderView *QTableView::verticalHeader() const
{
Q_D(const QTableView);
return d->verticalHeader;
}
/*!
Sets the widget to use for the horizontal header to \a header.
\sa horizontalHeader() setVerticalHeader()
*/
void QTableView::setHorizontalHeader(QHeaderView *header)
{
Q_D(QTableView);
if (!header || header == d->horizontalHeader)
return;
if (d->horizontalHeader && d->horizontalHeader->parent() == this)
delete d->horizontalHeader;
d->horizontalHeader = header;
d->horizontalHeader->setParent(this);
if (!d->horizontalHeader->model()) {
d->horizontalHeader->setModel(d->model);
if (d->selectionModel)
d->horizontalHeader->setSelectionModel(d->selectionModel);
}
connect(d->horizontalHeader,SIGNAL(sectionResized(int,int,int)),
this, SLOT(columnResized(int,int,int)));
connect(d->horizontalHeader, SIGNAL(sectionMoved(int,int,int)),
this, SLOT(columnMoved(int,int,int)));
connect(d->horizontalHeader, SIGNAL(sectionCountChanged(int,int)),
this, SLOT(columnCountChanged(int,int)));
connect(d->horizontalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectColumn(int)));
connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectColumn(int)));
connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)),
this, SLOT(resizeColumnToContents(int)));
connect(d->horizontalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries()));
//update the sorting enabled states on the new header
setSortingEnabled(d->sortingEnabled);
}
/*!
Sets the widget to use for the vertical header to \a header.
\sa verticalHeader() setHorizontalHeader()
*/
void QTableView::setVerticalHeader(QHeaderView *header)
{
Q_D(QTableView);
if (!header || header == d->verticalHeader)
return;
if (d->verticalHeader && d->verticalHeader->parent() == this)
delete d->verticalHeader;
d->verticalHeader = header;
d->verticalHeader->setParent(this);
if (!d->verticalHeader->model()) {
d->verticalHeader->setModel(d->model);
if (d->selectionModel)
d->verticalHeader->setSelectionModel(d->selectionModel);
}
connect(d->verticalHeader, SIGNAL(sectionResized(int,int,int)),
this, SLOT(rowResized(int,int,int)));
connect(d->verticalHeader, SIGNAL(sectionMoved(int,int,int)),
this, SLOT(rowMoved(int,int,int)));
connect(d->verticalHeader, SIGNAL(sectionCountChanged(int,int)),
this, SLOT(rowCountChanged(int,int)));
connect(d->verticalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectRow(int)));
connect(d->verticalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectRow(int)));
connect(d->verticalHeader, SIGNAL(sectionHandleDoubleClicked(int)),
this, SLOT(resizeRowToContents(int)));
connect(d->verticalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries()));
}
/*!
\internal
Scroll the contents of the table view by (\a dx, \a dy).
*/
void QTableView::scrollContentsBy(int dx, int dy)
{
Q_D(QTableView);
d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
dx = isRightToLeft() ? -dx : dx;
if (dx) {
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
int oldOffset = d->horizontalHeader->offset();
if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum())
d->horizontalHeader->setOffsetToLastSection();
else
d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value());
int newOffset = d->horizontalHeader->offset();
dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
} else {
d->horizontalHeader->setOffset(horizontalScrollBar()->value());
}
}
if (dy) {
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
int oldOffset = d->verticalHeader->offset();
if (verticalScrollBar()->value() == verticalScrollBar()->maximum())
d->verticalHeader->setOffsetToLastSection();
else
d->verticalHeader->setOffsetToSectionPosition(verticalScrollBar()->value());
int newOffset = d->verticalHeader->offset();
dy = oldOffset - newOffset;
} else {
d->verticalHeader->setOffset(verticalScrollBar()->value());
}
}
d->scrollContentsBy(dx, dy);
if (d->showGrid) {
//we need to update the first line of the previous top item in the view
//because it has the grid drawn if the header is invisible.
//It is strictly related to what's done at then end of the paintEvent
if (dy > 0 && d->horizontalHeader->isHidden() && d->verticalScrollMode == ScrollPerItem) {
d->viewport->update(0, dy, d->viewport->width(), dy);
}
if (dx > 0 && d->verticalHeader->isHidden() && d->horizontalScrollMode == ScrollPerItem) {
d->viewport->update(dx, 0, dx, d->viewport->height());
}
}
}
/*!
\reimp
*/
QStyleOptionViewItem QTableView::viewOptions() const
{
QStyleOptionViewItem option = QAbstractItemView::viewOptions();
option.showDecorationSelected = true;
return option;
}
/*!
Paints the table on receipt of the given paint event \a event.
*/
void QTableView::paintEvent(QPaintEvent *event)
{
Q_D(QTableView);
// setup temp variables for the painting
QStyleOptionViewItemV4 option = d->viewOptionsV4();
const QPoint offset = d->scrollDelayOffset;
const bool showGrid = d->showGrid;
const int gridSize = showGrid ? 1 : 0;
const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
const QColor gridColor = static_cast<QRgb>(gridHint);
const QPen gridPen = QPen(gridColor, 0, d->gridStyle);
const QHeaderView *verticalHeader = d->verticalHeader;
const QHeaderView *horizontalHeader = d->horizontalHeader;
const QStyle::State state = option.state;
const bool alternate = d->alternatingColors;
const bool rightToLeft = isRightToLeft();
QPainter painter(d->viewport);
// if there's nothing to do, clear the area and return
if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)
return;
uint x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
uint y = verticalHeader->length() - verticalHeader->offset() - 1;
const QRegion region = event->region().translated(offset);
const QVector<QRect> rects = region.rects();
//firstVisualRow is the visual index of the first visible row. lastVisualRow is the visual index of the last visible Row.
//same goes for ...VisualColumn
int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0);
int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->viewport()->height());
if (lastVisualRow == -1)
lastVisualRow = d->model->rowCount(d->root) - 1;
int firstVisualColumn = horizontalHeader->visualIndexAt(0);
int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->viewport()->width());
if (rightToLeft)
qSwap(firstVisualColumn, lastVisualColumn);
if (firstVisualColumn == -1)
firstVisualColumn = 0;
if (lastVisualColumn == -1)
lastVisualColumn = horizontalHeader->count() - 1;
QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1));
if (d->hasSpans()) {
d->drawAndClipSpans(region, &painter, option, &drawn,
firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn);
}
for (int i = 0; i < rects.size(); ++i) {
QRect dirtyArea = rects.at(i);
dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));
if (rightToLeft) {
dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));
} else {
dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));
}
// get the horizontal start and end visual sections
int left = horizontalHeader->visualIndexAt(dirtyArea.left());
int right = horizontalHeader->visualIndexAt(dirtyArea.right());
if (rightToLeft)
qSwap(left, right);
if (left == -1) left = 0;
if (right == -1) right = horizontalHeader->count() - 1;
// get the vertical start and end visual sections and if alternate color
int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
if (bottom == -1) bottom = verticalHeader->count() - 1;
int top = 0;
bool alternateBase = false;
if (alternate && verticalHeader->sectionsHidden()) {
uint verticalOffset = verticalHeader->offset();
int row = verticalHeader->logicalIndex(top);
for (int y = 0;
((uint)(y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);
++top) {
row = verticalHeader->logicalIndex(top);
if (alternate && !verticalHeader->isSectionHidden(row))
alternateBase = !alternateBase;
}
} else {
top = verticalHeader->visualIndexAt(dirtyArea.top());
alternateBase = (top & 1) && alternate;
}
if (top == -1 || top > bottom)
continue;
// Paint each row item
for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {
int row = verticalHeader->logicalIndex(visualRowIndex);
if (verticalHeader->isSectionHidden(row))
continue;
int rowY = rowViewportPosition(row);
rowY += offset.y();
int rowh = rowHeight(row) - gridSize;
// Paint each column item
for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {
int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
+ visualColumnIndex - firstVisualColumn;
if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))
continue;
drawn.setBit(currentBit);
int col = horizontalHeader->logicalIndex(visualColumnIndex);
if (horizontalHeader->isSectionHidden(col))
continue;
int colp = columnViewportPosition(col);
colp += offset.x();
int colw = columnWidth(col) - gridSize;
const QModelIndex index = d->model->index(row, col, d->root);
if (index.isValid()) {
option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);
if (alternate) {
if (alternateBase)
option.features |= QStyleOptionViewItemV2::Alternate;
else
option.features &= ~QStyleOptionViewItemV2::Alternate;
}
d->drawCell(&painter, option, index);
}
}
alternateBase = !alternateBase && alternate;
}
if (showGrid) {
// Find the bottom right (the last rows/coloumns might be hidden)
while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;
QPen old = painter.pen();
painter.setPen(gridPen);
// Paint each row
for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {
int row = verticalHeader->logicalIndex(visualIndex);
if (verticalHeader->isSectionHidden(row))
continue;
int rowY = rowViewportPosition(row);
rowY += offset.y();
int rowh = rowHeight(row) - gridSize;
painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);
}
// Paint each column
for (int h = left; h <= right; ++h) {
int col = horizontalHeader->logicalIndex(h);
if (horizontalHeader->isSectionHidden(col))
continue;
int colp = columnViewportPosition(col);
colp += offset.x();
if (!rightToLeft)
colp += columnWidth(col) - gridSize;
painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom());
}
//draw the top & left grid lines if the headers are not visible.
//We do update this line when subsequent scroll happen (see scrollContentsBy)
if (horizontalHeader->isHidden() && verticalScrollMode() == ScrollPerItem)
painter.drawLine(dirtyArea.left(), 0, dirtyArea.right(), 0);
if (verticalHeader->isHidden() && horizontalScrollMode() == ScrollPerItem)
painter.drawLine(0, dirtyArea.top(), 0, dirtyArea.bottom());
painter.setPen(old);
}
}
#ifndef QT_NO_DRAGANDDROP
// Paint the dropIndicator
d->paintDropIndicator(&painter);
#endif
}
/*!
Returns the index position of the model item corresponding to the
table item at position \a pos in contents coordinates.
*/
QModelIndex QTableView::indexAt(const QPoint &pos) const
{
Q_D(const QTableView);
d->executePostedLayout();
int r = rowAt(pos.y());
int c = columnAt(pos.x());
if (r >= 0 && c >= 0) {
if (d->hasSpans()) {
QSpanCollection::Span span = d->span(r, c);
r = span.top();
c = span.left();
}
return d->model->index(r, c, d->root);
}
return QModelIndex();
}
/*!
Returns the horizontal offset of the items in the table view.
Note that the table view uses the horizontal header section
positions to determine the positions of columns in the view.
\sa verticalOffset()
*/
int QTableView::horizontalOffset() const
{
Q_D(const QTableView);
return d->horizontalHeader->offset();
}
/*!
Returns the vertical offset of the items in the table view.
Note that the table view uses the vertical header section
positions to determine the positions of rows in the view.
\sa horizontalOffset()
*/
int QTableView::verticalOffset() const
{
Q_D(const QTableView);
return d->verticalHeader->offset();
}
/*!
\fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
Moves the cursor in accordance with the given \a cursorAction, using the
information provided by the \a modifiers.
\sa QAbstractItemView::CursorAction
*/
QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
Q_D(QTableView);
Q_UNUSED(modifiers);
int bottom = d->model->rowCount(d->root) - 1;
// make sure that bottom is the bottommost *visible* row
while (bottom >= 0 && isRowHidden(d->logicalRow(bottom)))
--bottom;
int right = d->model->columnCount(d->root) - 1;
while (right >= 0 && isColumnHidden(d->logicalColumn(right)))
--right;
if (bottom == -1 || right == -1)
return QModelIndex(); // model is empty
QModelIndex current = currentIndex();
if (!current.isValid()) {
int row = 0;
int column = 0;
while (column < right && isColumnHidden(d->logicalColumn(column)))
++column;
while (isRowHidden(d->logicalRow(row)) && row < bottom)
++row;
d->visualCursor = QPoint(column, row);
return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root);
}
// Update visual cursor if current index has changed.
QPoint visualCurrent(d->visualColumn(current.column()), d->visualRow(current.row()));
if (visualCurrent != d->visualCursor) {
if (d->hasSpans()) {
QSpanCollection::Span span = d->span(current.row(), current.column());
if (span.top() > d->visualCursor.y() || d->visualCursor.y() > span.bottom()
|| span.left() > d->visualCursor.x() || d->visualCursor.x() > span.right())
d->visualCursor = visualCurrent;
} else {
d->visualCursor = visualCurrent;
}
}
int visualRow = d->visualCursor.y();
if (visualRow > bottom)
visualRow = bottom;
Q_ASSERT(visualRow != -1);
int visualColumn = d->visualCursor.x();
if (visualColumn > right)
visualColumn = right;
Q_ASSERT(visualColumn != -1);
if (isRightToLeft()) {
if (cursorAction == MoveLeft)
cursorAction = MoveRight;
else if (cursorAction == MoveRight)
cursorAction = MoveLeft;
}
switch (cursorAction) {
case MoveUp: {
int originalRow = visualRow;
#ifdef QT_KEYPAD_NAVIGATION
if (QApplication::keypadNavigationEnabled() && visualRow == 0)
visualRow = d->visualRow(model()->rowCount() - 1) + 1;
// FIXME? visualRow = bottom + 1;
#endif
int r = d->logicalRow(visualRow);
int c = d->logicalColumn(visualColumn);
if (r != -1 && d->hasSpans()) {
QSpanCollection::Span span = d->span(r, c);
if (span.width() > 1 || span.height() > 1)
visualRow = d->visualRow(span.top());
}
while (visualRow >= 0) {
--visualRow;
r = d->logicalRow(visualRow);
c = d->logicalColumn(visualColumn);
if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
break;
}
if (visualRow < 0)
visualRow = originalRow;
break;
}
case MoveDown: {
int originalRow = visualRow;
if (d->hasSpans()) {
QSpanCollection::Span span = d->span(current.row(), current.column());
visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
}
#ifdef QT_KEYPAD_NAVIGATION
if (QApplication::keypadNavigationEnabled() && visualRow >= bottom)
visualRow = -1;
#endif
int r = d->logicalRow(visualRow);
int c = d->logicalColumn(visualColumn);
if (r != -1 && d->hasSpans()) {
QSpanCollection::Span span = d->span(r, c);
if (span.width() > 1 || span.height() > 1)
visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
}
while (visualRow <= bottom) {
++visualRow;
r = d->logicalRow(visualRow);
c = d->logicalColumn(visualColumn);
if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
break;
}
if (visualRow > bottom)
visualRow = originalRow;
break;
}
case MovePrevious:
case MoveLeft: {
int originalRow = visualRow;
int originalColumn = visualColumn;
bool firstTime = true;
bool looped = false;
bool wrapped = false;
do {
int r = d->logicalRow(visualRow);
int c = d->logicalColumn(visualColumn);
if (firstTime && c != -1 && d->hasSpans()) {
firstTime = false;
QSpanCollection::Span span = d->span(r, c);
if (span.width() > 1 || span.height() > 1)
visualColumn = d->visualColumn(span.left());
}
while (visualColumn >= 0) {
--visualColumn;
r = d->logicalRow(visualRow);
c = d->logicalColumn(visualColumn);
if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
break;
if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) {
looped = true;
break;
}
}
if (cursorAction == MoveLeft || visualColumn >= 0)
break;
visualColumn = right + 1;
if (visualRow == 0) {
wrapped = true;
visualRow = bottom;
} else {
--visualRow;
}
} while (!looped);
if (visualColumn < 0)
visualColumn = originalColumn;
break;
}
case MoveNext:
case MoveRight: {
int originalRow = visualRow;
int originalColumn = visualColumn;
bool firstTime = true;
bool looped = false;
bool wrapped = false;
do {
int r = d->logicalRow(visualRow);
int c = d->logicalColumn(visualColumn);
if (firstTime && c != -1 && d->hasSpans()) {
firstTime = false;
QSpanCollection::Span span = d->span(r, c);
if (span.width() > 1 || span.height() > 1)
visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
}
while (visualColumn <= right) {
++visualColumn;
r = d->logicalRow(visualRow);
c = d->logicalColumn(visualColumn);
if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
break;
if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) {
looped = true;
break;
}
}
if (cursorAction == MoveRight || visualColumn <= right)
break;
visualColumn = -1;
if (visualRow == bottom) {
wrapped = true;
visualRow = 0;
} else {
++visualRow;
}
} while (!looped);
if (visualColumn > right)
visualColumn = originalColumn;
break;
}
case MoveHome:
visualColumn = 0;
while (visualColumn < right && d->isVisualColumnHiddenOrDisabled(visualRow, visualColumn))
++visualColumn;
if (modifiers & Qt::ControlModifier) {
visualRow = 0;
while (visualRow < bottom && d->isVisualRowHiddenOrDisabled(visualRow, visualColumn))
++visualRow;
}
break;
case MoveEnd:
visualColumn = right;
if (modifiers & Qt::ControlModifier)
visualRow = bottom;
break;
case MovePageUp: {
int newRow = rowAt(visualRect(current).top() - d->viewport->height());
if (newRow == -1)
newRow = d->logicalRow(0);
return d->model->index(newRow, current.column(), d->root);
}
case MovePageDown: {
int newRow = rowAt(visualRect(current).bottom() + d->viewport->height());
if (newRow == -1)
newRow = d->logicalRow(bottom);
return d->model->index(newRow, current.column(), d->root);
}}
d->visualCursor = QPoint(visualColumn, visualRow);
int logicalRow = d->logicalRow(visualRow);
int logicalColumn = d->logicalColumn(visualColumn);
if (!d->model->hasIndex(logicalRow, logicalColumn, d->root))
return QModelIndex();
QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root);
if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result))
return result;
return QModelIndex();
}
/*!
\fn void QTableView::setSelection(const QRect &rect,
QItemSelectionModel::SelectionFlags flags)
Selects the items within the given \a rect and in accordance with
the specified selection \a flags.
*/
void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
Q_D(QTableView);
QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right())
: qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())));
QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) :
qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())));
if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
return;
bool verticalMoved = verticalHeader()->sectionsMoved();
bool horizontalMoved = horizontalHeader()->sectionsMoved();
QItemSelection selection;
if (d->hasSpans()) {
bool expanded;
int top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
int left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
int bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
int right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
do {
expanded = false;
foreach (QSpanCollection::Span *it, d->spans.spans) {
const QSpanCollection::Span &span = *it;
int t = d->visualRow(span.top());
int l = d->visualColumn(span.left());
int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
if ((t > bottom) || (l > right) || (top > b) || (left > r))
continue; // no intersect
if (t < top) {
top = t;
expanded = true;
}
if (l < left) {
left = l;
expanded = true;
}
if (b > bottom) {
bottom = b;
expanded = true;
}
if (r > right) {
right = r;
expanded = true;
}
if (expanded)
break;
}
} while (expanded);
for (int horizontal = left; horizontal <= right; ++horizontal) {
int column = d->logicalColumn(horizontal);
for (int vertical = top; vertical <= bottom; ++vertical) {
int row = d->logicalRow(vertical);
QModelIndex index = d->model->index(row, column, d->root);
selection.append(QItemSelectionRange(index));
}
}
} else if (verticalMoved && horizontalMoved) {
int top = d->visualRow(tl.row());
int left = d->visualColumn(tl.column());
int bottom = d->visualRow(br.row());
int right = d->visualColumn(br.column());
for (int horizontal = left; horizontal <= right; ++horizontal) {
int column = d->logicalColumn(horizontal);
for (int vertical = top; vertical <= bottom; ++vertical) {
int row = d->logicalRow(vertical);
QModelIndex index = d->model->index(row, column, d->root);
selection.append(QItemSelectionRange(index));
}
}
} else if (horizontalMoved) {
int left = d->visualColumn(tl.column());
int right = d->visualColumn(br.column());
for (int visual = left; visual <= right; ++visual) {
int column = d->logicalColumn(visual);
QModelIndex topLeft = d->model->index(tl.row(), column, d->root);
QModelIndex bottomRight = d->model->index(br.row(), column, d->root);
selection.append(QItemSelectionRange(topLeft, bottomRight));
}
} else if (verticalMoved) {
int top = d->visualRow(tl.row());
int bottom = d->visualRow(br.row());
for (int visual = top; visual <= bottom; ++visual) {
int row = d->logicalRow(visual);
QModelIndex topLeft = d->model->index(row, tl.column(), d->root);
QModelIndex bottomRight = d->model->index(row, br.column(), d->root);
selection.append(QItemSelectionRange(topLeft, bottomRight));
}
} else { // nothing moved
selection.append(QItemSelectionRange(tl, br));
}
d->selectionModel->select(selection, command);
}
/*!
\internal
Returns the rectangle from the viewport of the items in the given
\a selection.
*/
QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const
{
Q_D(const QTableView);
if (selection.isEmpty())
return QRegion();
QRegion selectionRegion;
bool verticalMoved = verticalHeader()->sectionsMoved();
bool horizontalMoved = horizontalHeader()->sectionsMoved();
if ((verticalMoved && horizontalMoved) || (d->hasSpans() && (verticalMoved || horizontalMoved))) {
for (int i = 0; i < selection.count(); ++i) {
QItemSelectionRange range = selection.at(i);
if (range.parent() != d->root || !range.isValid())
continue;
for (int r = range.top(); r <= range.bottom(); ++r)
for (int c = range.left(); c <= range.right(); ++c)
selectionRegion += QRegion(visualRect(d->model->index(r, c, d->root)));
}
} else if (horizontalMoved) {
for (int i = 0; i < selection.count(); ++i) {
QItemSelectionRange range = selection.at(i);
if (range.parent() != d->root || !range.isValid())
continue;
int top = rowViewportPosition(range.top());
int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
if (top > bottom)
qSwap<int>(top, bottom);
int height = bottom - top;
for (int c = range.left(); c <= range.right(); ++c)
selectionRegion += QRegion(QRect(columnViewportPosition(c), top,
columnWidth(c), height));
}
} else if (verticalMoved) {
for (int i = 0; i < selection.count(); ++i) {
QItemSelectionRange range = selection.at(i);
if (range.parent() != d->root || !range.isValid())
continue;
int left = columnViewportPosition(range.left());
int right = columnViewportPosition(range.right()) + columnWidth(range.right());
if (left > right)
qSwap<int>(left, right);
int width = right - left;
for (int r = range.top(); r <= range.bottom(); ++r)
selectionRegion += QRegion(QRect(left, rowViewportPosition(r),
width, rowHeight(r)));
}
} else { // nothing moved
const int gridAdjust = showGrid() ? 1 : 0;
for (int i = 0; i < selection.count(); ++i) {
QItemSelectionRange range = selection.at(i);
if (range.parent() != d->root || !range.isValid())
continue;
d->trimHiddenSelections(&range);
const int rtop = rowViewportPosition(range.top());
const int rbottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
int rleft;
int rright;
if (isLeftToRight()) {
rleft = columnViewportPosition(range.left());
rright = columnViewportPosition(range.right()) + columnWidth(range.right());
} else {
rleft = columnViewportPosition(range.right());
rright = columnViewportPosition(range.left()) + columnWidth(range.left());
}
selectionRegion += QRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust));
if (d->hasSpans()) {
foreach (QSpanCollection::Span *s,
d->spans.spansInRect(range.left(), range.top(), range.width(), range.height())) {
if (range.contains(s->top(), s->left(), range.parent()))
selectionRegion += d->visualSpanRect(*s);
}
}
}
}
return selectionRegion;
}
/*!
\reimp
*/
QModelIndexList QTableView::selectedIndexes() const
{
Q_D(const QTableView);
QModelIndexList viewSelected;
QModelIndexList modelSelected;
if (d->selectionModel)
modelSelected = d->selectionModel->selectedIndexes();
for (int i = 0; i < modelSelected.count(); ++i) {
QModelIndex index = modelSelected.at(i);
if (!isIndexHidden(index) && index.parent() == d->root)
viewSelected.append(index);
}
return viewSelected;
}
/*!
This slot is called whenever rows are added or deleted. The
previous number of rows is specified by \a oldCount, and the new
number of rows is specified by \a newCount.
*/
void QTableView::rowCountChanged(int /*oldCount*/, int /*newCount*/ )
{
Q_D(QTableView);
updateGeometries();
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem)
d->verticalHeader->setOffsetToSectionPosition(verticalScrollBar()->value());
else
d->verticalHeader->setOffset(verticalScrollBar()->value());
d->viewport->update();
}
/*!
This slot is called whenever columns are added or deleted. The
previous number of columns is specified by \a oldCount, and the new
number of columns is specified by \a newCount.
*/
void QTableView::columnCountChanged(int, int)
{
Q_D(QTableView);
updateGeometries();
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value());
else
d->horizontalHeader->setOffset(horizontalScrollBar()->value());
d->viewport->update();
}
/*!
\reimp
*/
void QTableView::updateGeometries()
{
Q_D(QTableView);
if (d->geometryRecursionBlock)
return;
d->geometryRecursionBlock = true;
int width = 0;
if (!d->verticalHeader->isHidden()) {
width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width());
width = qMin(width, d->verticalHeader->maximumWidth());
}
int height = 0;
if (!d->horizontalHeader->isHidden()) {
height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height());
height = qMin(height, d->horizontalHeader->maximumHeight());
}
bool reverse = isRightToLeft();
if (reverse)
setViewportMargins(0, height, width, 0);
else
setViewportMargins(width, height, 0, 0);
// update headers
QRect vg = d->viewport->geometry();
int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width);
d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height());
if (d->verticalHeader->isHidden())
QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries");
int horizontalTop = vg.top() - height;
d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height);
if (d->horizontalHeader->isHidden())
QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries");
// update cornerWidget
if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) {
d->cornerWidget->setHidden(true);
} else {
d->cornerWidget->setHidden(false);
d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height);
}
// update scroll bars
// ### move this block into the if
QSize vsize = d->viewport->size();
QSize max = maximumViewportSize();
uint horizontalLength = d->horizontalHeader->length();
uint verticalLength = d->verticalHeader->length();
if ((uint)max.width() >= horizontalLength && (uint)max.height() >= verticalLength)
vsize = max;
// horizontal scroll bar
const int columnCount = d->horizontalHeader->count();
const int viewportWidth = vsize.width();
int columnsInViewport = 0;
for (int width = 0, column = columnCount - 1; column >= 0; --column) {
int logical = d->horizontalHeader->logicalIndex(column);
if (!d->horizontalHeader->isSectionHidden(logical)) {
width += d->horizontalHeader->sectionSize(logical);
if (width > viewportWidth)
break;
++columnsInViewport;
}
}
columnsInViewport = qMax(columnsInViewport, 1); //there must be always at least 1 column
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount();
horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport);
horizontalScrollBar()->setPageStep(columnsInViewport);
if (columnsInViewport >= visibleColumns)
d->horizontalHeader->setOffset(0);
horizontalScrollBar()->setSingleStep(1);
} else { // ScrollPerPixel
horizontalScrollBar()->setPageStep(vsize.width());
horizontalScrollBar()->setRange(0, horizontalLength - vsize.width());
horizontalScrollBar()->setSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2));
}
// vertical scroll bar
const int rowCount = d->verticalHeader->count();
const int viewportHeight = vsize.height();
int rowsInViewport = 0;
for (int height = 0, row = rowCount - 1; row >= 0; --row) {
int logical = d->verticalHeader->logicalIndex(row);
if (!d->verticalHeader->isSectionHidden(logical)) {
height += d->verticalHeader->sectionSize(logical);
if (height > viewportHeight)
break;
++rowsInViewport;
}
}
rowsInViewport = qMax(rowsInViewport, 1); //there must be always at least 1 row
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount();
verticalScrollBar()->setRange(0, visibleRows - rowsInViewport);
verticalScrollBar()->setPageStep(rowsInViewport);
if (rowsInViewport >= visibleRows)
d->verticalHeader->setOffset(0);
verticalScrollBar()->setSingleStep(1);
} else { // ScrollPerPixel
verticalScrollBar()->setPageStep(vsize.height());
verticalScrollBar()->setRange(0, verticalLength - vsize.height());
verticalScrollBar()->setSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2));
}
d->geometryRecursionBlock = false;
QAbstractItemView::updateGeometries();
}
/*!
Returns the size hint for the given \a row's height or -1 if there
is no model.
If you need to set the height of a given row to a fixed value, call
QHeaderView::resizeSection() on the table's vertical header.
If you reimplement this function in a subclass, note that the value you
return is only used when resizeRowToContents() is called. In that case,
if a larger row height is required by either the vertical header or
the item delegate, that width will be used instead.
\sa QWidget::sizeHint, verticalHeader()
*/
int QTableView::sizeHintForRow(int row) const
{
Q_D(const QTableView);
if (!model())
return -1;
ensurePolished();
int left = qMax(0, columnAt(0));
int right = columnAt(d->viewport->width());
if (right == -1) // the table don't have enough columns to fill the viewport
right = d->model->columnCount(d->root) - 1;
QStyleOptionViewItemV4 option = d->viewOptionsV4();
int hint = 0;
QModelIndex index;
for (int column = left; column <= right; ++column) {
int logicalColumn = d->horizontalHeader->logicalIndex(column);
if (d->horizontalHeader->isSectionHidden(logicalColumn))
continue;
index = d->model->index(row, logicalColumn, d->root);
if (d->wrapItemText) {// for wrapping boundaries
option.rect.setY(rowViewportPosition(index.row()));
option.rect.setHeight(rowHeight(index.row()));
option.rect.setX(columnViewportPosition(index.column()));
option.rect.setWidth(columnWidth(index.column()));
}
QWidget *editor = d->editorForIndex(index).editor;
if (editor && d->persistent.contains(editor)) {
hint = qMax(hint, editor->sizeHint().height());
int min = editor->minimumSize().height();
int max = editor->maximumSize().height();
hint = qBound(min, hint, max);
}
hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).height());
}
return d->showGrid ? hint + 1 : hint;
}
/*!
Returns the size hint for the given \a column's width or -1 if
there is no model.
If you need to set the width of a given column to a fixed value, call
QHeaderView::resizeSection() on the table's horizontal header.
If you reimplement this function in a subclass, note that the value you
return will be used when resizeColumnToContents() or
QHeaderView::resizeSections() is called. If a larger column width is
required by either the horizontal header or the item delegate, the larger
width will be used instead.
\sa QWidget::sizeHint, horizontalHeader()
*/
int QTableView::sizeHintForColumn(int column) const
{
Q_D(const QTableView);
if (!model())
return -1;
ensurePolished();
int top = qMax(0, rowAt(0));
int bottom = rowAt(d->viewport->height());
if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport
bottom = d->model->rowCount(d->root) - 1;
QStyleOptionViewItemV4 option = d->viewOptionsV4();
int hint = 0;
QModelIndex index;
for (int row = top; row <= bottom; ++row) {
int logicalRow = d->verticalHeader->logicalIndex(row);
if (d->verticalHeader->isSectionHidden(logicalRow))
continue;
index = d->model->index(logicalRow, column, d->root);
QWidget *editor = d->editorForIndex(index).editor;
if (editor && d->persistent.contains(editor)) {
hint = qMax(hint, editor->sizeHint().width());
int min = editor->minimumSize().width();
int max = editor->maximumSize().width();
hint = qBound(min, hint, max);
}
hint = qMax(hint, itemDelegate(index)->sizeHint(option, index).width());
}
return d->showGrid ? hint + 1 : hint;
}
/*!
Returns the y-coordinate in contents coordinates of the given \a
row.
*/
int QTableView::rowViewportPosition(int row) const
{
Q_D(const QTableView);
return d->verticalHeader->sectionViewportPosition(row);
}
/*!
Returns the row in which the given y-coordinate, \a y, in contents
coordinates is located.
\note This function returns -1 if the given coordinate is not valid
(has no row).
\sa columnAt()
*/
int QTableView::rowAt(int y) const
{
Q_D(const QTableView);
return d->verticalHeader->logicalIndexAt(y);
}
/*!
\since 4.1
Sets the height of the given \a row to be \a height.
*/
void QTableView::setRowHeight(int row, int height)
{
Q_D(const QTableView);
d->verticalHeader->resizeSection(row, height);
}
/*!
Returns the height of the given \a row.
\sa resizeRowToContents(), columnWidth()
*/
int QTableView::rowHeight(int row) const
{
Q_D(const QTableView);
return d->verticalHeader->sectionSize(row);
}
/*!
Returns the x-coordinate in contents coordinates of the given \a
column.
*/
int QTableView::columnViewportPosition(int column) const
{
Q_D(const QTableView);
return d->horizontalHeader->sectionViewportPosition(column);
}
/*!
Returns the column in which the given x-coordinate, \a x, in contents
coordinates is located.
\note This function returns -1 if the given coordinate is not valid
(has no column).
\sa rowAt()
*/
int QTableView::columnAt(int x) const
{
Q_D(const QTableView);
return d->horizontalHeader->logicalIndexAt(x);
}
/*!
\since 4.1
Sets the width of the given \a column to be \a width.
*/
void QTableView::setColumnWidth(int column, int width)
{
Q_D(const QTableView);
d->horizontalHeader->resizeSection(column, width);
}
/*!
Returns the width of the given \a column.
\sa resizeColumnToContents(), rowHeight()
*/
int QTableView::columnWidth(int column) const
{
Q_D(const QTableView);
return d->horizontalHeader->sectionSize(column);
}
/*!
Returns true if the given \a row is hidden; otherwise returns false.
\sa isColumnHidden()
*/
bool QTableView::isRowHidden(int row) const
{
Q_D(const QTableView);
return d->verticalHeader->isSectionHidden(row);
}
/*!
If \a hide is true \a row will be hidden, otherwise it will be shown.
\sa setColumnHidden()
*/
void QTableView::setRowHidden(int row, bool hide)
{
Q_D(QTableView);
if (row < 0 || row >= d->verticalHeader->count())
return;
d->verticalHeader->setSectionHidden(row, hide);
}
/*!
Returns true if the given \a column is hidden; otherwise returns false.
\sa isRowHidden()
*/
bool QTableView::isColumnHidden(int column) const
{
Q_D(const QTableView);
return d->horizontalHeader->isSectionHidden(column);
}
/*!
If \a hide is true the given \a column will be hidden; otherwise it
will be shown.
\sa setRowHidden()
*/
void QTableView::setColumnHidden(int column, bool hide)
{
Q_D(QTableView);
if (column < 0 || column >= d->horizontalHeader->count())
return;
d->horizontalHeader->setSectionHidden(column, hide);
}
/*!
\since 4.2
\property QTableView::sortingEnabled
\brief whether sorting is enabled
If this property is true, sorting is enabled for the table. If
this property is false, sorting is not enabled. The default value
is false.
\note. Setting the property to true with setSortingEnabled()
immediately triggers a call to sortByColumn() with the current
sort section and order.
\sa sortByColumn()
*/
/*!
If \a enabled true enables sorting for the table and immediately
trigger a call to sortByColumn() with the current sort section and
order
*/
void QTableView::setSortingEnabled(bool enable)
{
Q_D(QTableView);
d->sortingEnabled = enable;
horizontalHeader()->setSortIndicatorShown(enable);
if (enable) {
disconnect(d->horizontalHeader, SIGNAL(sectionEntered(int)),
this, SLOT(_q_selectColumn(int)));
disconnect(horizontalHeader(), SIGNAL(sectionPressed(int)),
this, SLOT(selectColumn(int)));
connect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
this, SLOT(sortByColumn(int)), Qt::UniqueConnection);
sortByColumn(horizontalHeader()->sortIndicatorSection(),
horizontalHeader()->sortIndicatorOrder());
} else {
connect(d->horizontalHeader, SIGNAL(sectionEntered(int)),
this, SLOT(_q_selectColumn(int)), Qt::UniqueConnection);
connect(horizontalHeader(), SIGNAL(sectionPressed(int)),
this, SLOT(selectColumn(int)), Qt::UniqueConnection);
disconnect(horizontalHeader(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)),
this, SLOT(sortByColumn(int)));
}
}
bool QTableView::isSortingEnabled() const
{
Q_D(const QTableView);
return d->sortingEnabled;
}
/*!
\property QTableView::showGrid
\brief whether the grid is shown
If this property is true a grid is drawn for the table; if the
property is false, no grid is drawn. The default value is true.
*/
bool QTableView::showGrid() const
{
Q_D(const QTableView);
return d->showGrid;
}
void QTableView::setShowGrid(bool show)
{
Q_D(QTableView);
if (d->showGrid != show) {
d->showGrid = show;
d->viewport->update();
}
}
/*!
\property QTableView::gridStyle
\brief the pen style used to draw the grid.
This property holds the style used when drawing the grid (see \l{showGrid}).
*/
Qt::PenStyle QTableView::gridStyle() const
{
Q_D(const QTableView);
return d->gridStyle;
}
void QTableView::setGridStyle(Qt::PenStyle style)
{
Q_D(QTableView);
if (d->gridStyle != style) {
d->gridStyle = style;
d->viewport->update();
}
}
/*!
\property QTableView::wordWrap
\brief the item text word-wrapping policy
\since 4.3
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 true by default.
Note that even of wrapping is enabled, the cell will not be
expanded to fit all text. Ellipsis will be inserted according to
the current \l{QAbstractItemView::}{textElideMode}.
*/
void QTableView::setWordWrap(bool on)
{
Q_D(QTableView);
if (d->wrapItemText == on)
return;
d->wrapItemText = on;
QMetaObject::invokeMethod(d->verticalHeader, "resizeSections");
QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections");
}
bool QTableView::wordWrap() const
{
Q_D(const QTableView);
return d->wrapItemText;
}
/*!
\property QTableView::cornerButtonEnabled
\brief whether the button in the top-left corner is enabled
\since 4.3
If this property is true then button in the top-left corner
of the table view is enabled. Clicking on this button will
select all the cells in the table view.
This property is true by default.
*/
void QTableView::setCornerButtonEnabled(bool enable)
{
Q_D(QTableView);
d->cornerWidget->setEnabled(enable);
}
bool QTableView::isCornerButtonEnabled() const
{
Q_D(const QTableView);
return d->cornerWidget->isEnabled();
}
/*!
\internal
Returns the rectangle on the viewport occupied by the given \a
index.
If the index is hidden in the view it will return a null QRect.
*/
QRect QTableView::visualRect(const QModelIndex &index) const
{
Q_D(const QTableView);
if (!d->isIndexValid(index) || index.parent() != d->root
|| (!d->hasSpans() && isIndexHidden(index)))
return QRect();
d->executePostedLayout();
if (d->hasSpans()) {
QSpanCollection::Span span = d->span(index.row(), index.column());
return d->visualSpanRect(span);
}
int rowp = rowViewportPosition(index.row());
int rowh = rowHeight(index.row());
int colp = columnViewportPosition(index.column());
int colw = columnWidth(index.column());
const int i = showGrid() ? 1 : 0;
return QRect(colp, rowp, colw - i, rowh - i);
}
/*!
\internal
Makes sure that the given \a item is visible in the table view,
scrolling if necessary.
*/
void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
{
Q_D(QTableView);
// check if we really need to do anything
if (!d->isIndexValid(index)
|| (d->model->parent(index) != d->root)
|| isRowHidden(index.row()) || isColumnHidden(index.column()))
return;
QSpanCollection::Span span;
if (d->hasSpans())
span = d->span(index.row(), index.column());
// Adjust horizontal position
int viewportWidth = d->viewport->width();
int horizontalOffset = d->horizontalHeader->offset();
int horizontalPosition = d->horizontalHeader->sectionPosition(index.column());
int horizontalIndex = d->horizontalHeader->visualIndex(index.column());
int cellWidth = d->hasSpans()
? d->columnSpanWidth(index.column(), span.width())
: d->horizontalHeader->sectionSize(index.column());
if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
bool positionAtLeft = (horizontalPosition - horizontalOffset < 0);
bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth);
if (hint == PositionAtCenter || positionAtRight) {
int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth);
int x = cellWidth;
while (horizontalIndex > 0) {
x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1));
if (x > w)
break;
--horizontalIndex;
}
}
if (positionAtRight || hint == PositionAtCenter || positionAtLeft) {
int hiddenSections = 0;
if (d->horizontalHeader->sectionsHidden()) {
for (int s = horizontalIndex - 1; s >= 0; --s) {
int column = d->horizontalHeader->logicalIndex(s);
if (d->horizontalHeader->isSectionHidden(column))
++hiddenSections;
}
}
horizontalScrollBar()->setValue(horizontalIndex - hiddenSections);
}
} else { // ScrollPerPixel
if (hint == PositionAtCenter) {
horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
} else {
if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
horizontalScrollBar()->setValue(horizontalPosition);
else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
}
}
// Adjust vertical position
int viewportHeight = d->viewport->height();
int verticalOffset = d->verticalHeader->offset();
int verticalPosition = d->verticalHeader->sectionPosition(index.row());
int verticalIndex = d->verticalHeader->visualIndex(index.row());
int cellHeight = d->hasSpans()
? d->rowSpanHeight(index.row(), span.height())
: d->verticalHeader->sectionSize(index.row());
if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) {
if (hint == EnsureVisible)
hint = PositionAtTop;
} else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) {
if (hint == EnsureVisible)
hint = PositionAtBottom;
}
if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
if (hint == PositionAtBottom || hint == PositionAtCenter) {
int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight);
int y = cellHeight;
while (verticalIndex > 0) {
int row = d->verticalHeader->logicalIndex(verticalIndex - 1);
y += d->verticalHeader->sectionSize(row);
if (y > h)
break;
--verticalIndex;
}
}
if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) {
int hiddenSections = 0;
if (d->verticalHeader->sectionsHidden()) {
for (int s = verticalIndex - 1; s >= 0; --s) {
int row = d->verticalHeader->logicalIndex(s);
if (d->verticalHeader->isSectionHidden(row))
++hiddenSections;
}
}
verticalScrollBar()->setValue(verticalIndex - hiddenSections);
}
} else { // ScrollPerPixel
if (hint == PositionAtTop) {
verticalScrollBar()->setValue(verticalPosition);
} else if (hint == PositionAtBottom) {
verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight);
} else if (hint == PositionAtCenter) {
verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2));
}
}
update(index);
}
/*!
This slot is called to change the height of the given \a row. The
old height is specified by \a oldHeight, and the new height by \a
newHeight.
\sa columnResized()
*/
void QTableView::rowResized(int row, int, int)
{
Q_D(QTableView);
d->rowsToUpdate.append(row);
if (d->rowResizeTimerID == 0)
d->rowResizeTimerID = startTimer(0);
}
/*!
This slot is called to change the width of the given \a column.
The old width is specified by \a oldWidth, and the new width by \a
newWidth.
\sa rowResized()
*/
void QTableView::columnResized(int column, int, int)
{
Q_D(QTableView);
d->columnsToUpdate.append(column);
if (d->columnResizeTimerID == 0)
d->columnResizeTimerID = startTimer(0);
}
/*!
\reimp
*/
void QTableView::timerEvent(QTimerEvent *event)
{
Q_D(QTableView);
if (event->timerId() == d->columnResizeTimerID) {
updateGeometries();
killTimer(d->columnResizeTimerID);
d->columnResizeTimerID = 0;
QRect rect;
int viewportHeight = d->viewport->height();
int viewportWidth = d->viewport->width();
if (d->hasSpans()) {
rect = QRect(0, 0, viewportWidth, viewportHeight);
} else {
for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) {
int column = d->columnsToUpdate.at(i);
int x = columnViewportPosition(column);
if (isRightToLeft())
rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
else
rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
}
}
d->viewport->update(rect.normalized());
d->columnsToUpdate.clear();
}
if (event->timerId() == d->rowResizeTimerID) {
updateGeometries();
killTimer(d->rowResizeTimerID);
d->rowResizeTimerID = 0;
int viewportHeight = d->viewport->height();
int viewportWidth = d->viewport->width();
int top;
if (d->hasSpans()) {
top = 0;
} else {
top = viewportHeight;
for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) {
int y = rowViewportPosition(d->rowsToUpdate.at(i));
top = qMin(top, y);
}
}
d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top));
d->rowsToUpdate.clear();
}
QAbstractItemView::timerEvent(event);
}
/*!
This slot is called to change the index of the given \a row in the
table view. The old index is specified by \a oldIndex, and the new
index by \a newIndex.
\sa columnMoved()
*/
void QTableView::rowMoved(int, int oldIndex, int newIndex)
{
Q_D(QTableView);
updateGeometries();
int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex);
int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex);
if (d->hasSpans()) {
d->viewport->update();
} else {
int oldTop = rowViewportPosition(logicalOldIndex);
int newTop = rowViewportPosition(logicalNewIndex);
int oldBottom = oldTop + rowHeight(logicalOldIndex);
int newBottom = newTop + rowHeight(logicalNewIndex);
int top = qMin(oldTop, newTop);
int bottom = qMax(oldBottom, newBottom);
int height = bottom - top;
d->viewport->update(0, top, d->viewport->width(), height);
}
}
/*!
This slot is called to change the index of the given \a column in
the table view. The old index is specified by \a oldIndex, and
the new index by \a newIndex.
\sa rowMoved()
*/
void QTableView::columnMoved(int, int oldIndex, int newIndex)
{
Q_D(QTableView);
updateGeometries();
int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex);
int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex);
if (d->hasSpans()) {
d->viewport->update();
} else {
int oldLeft = columnViewportPosition(logicalOldIndex);
int newLeft = columnViewportPosition(logicalNewIndex);
int oldRight = oldLeft + columnWidth(logicalOldIndex);
int newRight = newLeft + columnWidth(logicalNewIndex);
int left = qMin(oldLeft, newLeft);
int right = qMax(oldRight, newRight);
int width = right - left;
d->viewport->update(left, 0, width, d->viewport->height());
}
}
/*!
Selects the given \a row in the table view if the current
SelectionMode and SelectionBehavior allows rows to be selected.
\sa selectColumn()
*/
void QTableView::selectRow(int row)
{
Q_D(QTableView);
d->selectRow(row, true);
}
/*!
Selects the given \a column in the table view if the current
SelectionMode and SelectionBehavior allows columns to be selected.
\sa selectRow()
*/
void QTableView::selectColumn(int column)
{
Q_D(QTableView);
d->selectColumn(column, true);
}
/*!
Hide the given \a row.
\sa showRow() hideColumn()
*/
void QTableView::hideRow(int row)
{
Q_D(QTableView);
d->verticalHeader->hideSection(row);
}
/*!
Hide the given \a column.
\sa showColumn() hideRow()
*/
void QTableView::hideColumn(int column)
{
Q_D(QTableView);
d->horizontalHeader->hideSection(column);
}
/*!
Show the given \a row.
\sa hideRow() showColumn()
*/
void QTableView::showRow(int row)
{
Q_D(QTableView);
d->verticalHeader->showSection(row);
}
/*!
Show the given \a column.
\sa hideColumn() showRow()
*/
void QTableView::showColumn(int column)
{
Q_D(QTableView);
d->horizontalHeader->showSection(column);
}
/*!
Resizes the given \a row based on the size hints of the delegate
used to render each item in the row.
*/
void QTableView::resizeRowToContents(int row)
{
Q_D(QTableView);
int content = sizeHintForRow(row);
int header = d->verticalHeader->sectionSizeHint(row);
d->verticalHeader->resizeSection(row, qMax(content, header));
}
/*!
Resizes all rows based on the size hints of the delegate
used to render each item in the rows.
*/
void QTableView::resizeRowsToContents()
{
Q_D(QTableView);
d->verticalHeader->resizeSections(QHeaderView::ResizeToContents);
}
/*!
Resizes the given \a column based on the size hints of the delegate
used to render each item in the column.
\note Only visible columns will be resized. Reimplement sizeHintForColumn()
to resize hidden columns as well.
*/
void QTableView::resizeColumnToContents(int column)
{
Q_D(QTableView);
int content = sizeHintForColumn(column);
int header = d->horizontalHeader->sectionSizeHint(column);
d->horizontalHeader->resizeSection(column, qMax(content, header));
}
/*!
Resizes all columns based on the size hints of the delegate
used to render each item in the columns.
*/
void QTableView::resizeColumnsToContents()
{
Q_D(QTableView);
d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents);
}
/*!
\obsolete
\overload
Sorts the model by the values in the given \a column.
*/
void QTableView::sortByColumn(int column)
{
Q_D(QTableView);
if (column == -1)
return;
d->model->sort(column, d->horizontalHeader->sortIndicatorOrder());
}
/*!
\since 4.2
Sorts the model by the values in the given \a column in the given \a order.
\sa sortingEnabled
*/
void QTableView::sortByColumn(int column, Qt::SortOrder order)
{
Q_D(QTableView);
d->horizontalHeader->setSortIndicator(column, order);
sortByColumn(column);
}
/*!
\internal
*/
void QTableView::verticalScrollbarAction(int action)
{
QAbstractItemView::verticalScrollbarAction(action);
}
/*!
\internal
*/
void QTableView::horizontalScrollbarAction(int action)
{
QAbstractItemView::horizontalScrollbarAction(action);
}
/*!
\reimp
*/
bool QTableView::isIndexHidden(const QModelIndex &index) const
{
Q_D(const QTableView);
Q_ASSERT(d->isIndexValid(index));
if (isRowHidden(index.row()) || isColumnHidden(index.column()))
return true;
if (d->hasSpans()) {
QSpanCollection::Span span = d->span(index.row(), index.column());
return !((span.top() == index.row()) && (span.left() == index.column()));
}
return false;
}
/*!
\fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
\since 4.2
Sets the span of the table element at (\a row, \a column) to the number of
rows and columns specified by (\a rowSpanCount, \a columnSpanCount).
\sa rowSpan(), columnSpan()
*/
void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan)
{
Q_D(QTableView);
if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0)
return;
d->setSpan(row, column, rowSpan, columnSpan);
d->viewport->update();
}
/*!
\since 4.2
Returns the row span of the table element at (\a row, \a column).
The default is 1.
\sa setSpan(), columnSpan()
*/
int QTableView::rowSpan(int row, int column) const
{
Q_D(const QTableView);
return d->rowSpan(row, column);
}
/*!
\since 4.2
Returns the column span of the table element at (\a row, \a
column). The default is 1.
\sa setSpan(), rowSpan()
*/
int QTableView::columnSpan(int row, int column) const
{
Q_D(const QTableView);
return d->columnSpan(row, column);
}
/*!
\since 4.4
Removes all row and column spans in the table view.
\sa setSpan()
*/
void QTableView::clearSpans()
{
Q_D(QTableView);
d->spans.clear();
d->viewport->update();
}
void QTableViewPrivate::_q_selectRow(int row)
{
selectRow(row, false);
}
void QTableViewPrivate::_q_selectColumn(int column)
{
selectColumn(column, false);
}
void QTableViewPrivate::selectRow(int row, bool anchor)
{
Q_Q(QTableView);
if (q->selectionBehavior() == QTableView::SelectColumns
|| (q->selectionMode() == QTableView::SingleSelection
&& q->selectionBehavior() == QTableView::SelectItems))
return;
if (row >= 0 && row < model->rowCount(root)) {
int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0);
QModelIndex index = model->index(row, column, root);
QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
if ((anchor && !(command & QItemSelectionModel::Current))
|| (q->selectionMode() == QTableView::SingleSelection))
rowSectionAnchor = row;
if (q->selectionMode() != QTableView::SingleSelection
&& command.testFlag(QItemSelectionModel::Toggle)) {
if (anchor)
ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows().contains(index)
? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
command &= ~QItemSelectionModel::Toggle;
command |= ctrlDragSelectionFlag;
if (!anchor)
command |= QItemSelectionModel::Current;
}
QModelIndex tl = model->index(qMin(rowSectionAnchor, row), 0, root);
QModelIndex br = model->index(qMax(rowSectionAnchor, row), model->columnCount(root) - 1, root);
if (verticalHeader->sectionsMoved() && tl.row() != br.row())
q->setSelection(q->visualRect(tl)|q->visualRect(br), command);
else
selectionModel->select(QItemSelection(tl, br), command);
}
}
void QTableViewPrivate::selectColumn(int column, bool anchor)
{
Q_Q(QTableView);
if (q->selectionBehavior() == QTableView::SelectRows
|| (q->selectionMode() == QTableView::SingleSelection
&& q->selectionBehavior() == QTableView::SelectItems))
return;
if (column >= 0 && column < model->columnCount(root)) {
int row = verticalHeader->logicalIndexAt(0);
QModelIndex index = model->index(row, column, root);
QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
if ((anchor && !(command & QItemSelectionModel::Current))
|| (q->selectionMode() == QTableView::SingleSelection))
columnSectionAnchor = column;
if (q->selectionMode() != QTableView::SingleSelection
&& command.testFlag(QItemSelectionModel::Toggle)) {
if (anchor)
ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns().contains(index)
? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
command &= ~QItemSelectionModel::Toggle;
command |= ctrlDragSelectionFlag;
if (!anchor)
command |= QItemSelectionModel::Current;
}
QModelIndex tl = model->index(0, qMin(columnSectionAnchor, column), root);
QModelIndex br = model->index(model->rowCount(root) - 1,
qMax(columnSectionAnchor, column), root);
if (horizontalHeader->sectionsMoved() && tl.column() != br.column())
q->setSelection(q->visualRect(tl)|q->visualRect(br), command);
else
selectionModel->select(QItemSelection(tl, br), command);
}
}
/*!
\reimp
*/
void QTableView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous)
{
#ifndef QT_NO_ACCESSIBILITY
if (QAccessible::isActive()) {
if (current.isValid()) {
int entry = visualIndex(current) + 1;
if (horizontalHeader())
++entry;
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus);
}
}
#endif
QAbstractItemView::currentChanged(current, previous);
}
/*!
\reimp
*/
void QTableView::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);
if (horizontalHeader())
++entry;
QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection);
}
QModelIndex desel = deselected.indexes().value(0);
if (desel.isValid()) {
int entry = visualIndex(sel);
if (horizontalHeader())
++entry;
QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove);
}
}
#endif
QAbstractItemView::selectionChanged(selected, deselected);
}
int QTableView::visualIndex(const QModelIndex &index) const
{
return index.row();
}
QT_END_NAMESPACE
#include "qtableview.moc"
#include "moc_qtableview.cpp"
#endif // QT_NO_TABLEVIEW