/**************************************************************************** ** ** Copyright (C) 2009 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 #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_ACCESSIBILITY #include #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(qMin(-span->bottom(), 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()) { (*it_y).remove(-span->left()); if (it_y->isEmpty()) { it_y = index.erase(it_y) - 1; } } 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::spansInRect(int x, int y, int w, int h) const { QSet 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 visibleSpans; bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved(); if (!sectionMoved) { visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow), lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1); } else { QSet 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(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 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(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(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 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()); const int rleft = columnViewportPosition(range.left()); const int rright = columnViewportPosition(range.right()) + columnWidth(range.right()); selectionRegion += QRect(QPoint(rleft, rtop), QPoint(rright, rbottom)); 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) || isIndexHidden(index)) 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