| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). |
| ** All rights reserved. |
| ** Contact: Nokia Corporation (qt-info@nokia.com) |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** GNU Lesser General Public License Usage |
| ** This file may be used under the terms of the GNU Lesser General Public |
| ** License version 2.1 as published by the Free Software Foundation and |
| ** appearing in the file LICENSE.LGPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU Lesser |
| ** General Public License version 2.1 requirements will be met: |
| ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| ** |
| ** In addition, as a special exception, Nokia gives you certain additional |
| ** rights. These rights are described in the Nokia Qt LGPL Exception |
| ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU General |
| ** Public License version 3.0 as published by the Free Software Foundation |
| ** and appearing in the file LICENSE.GPL included in the packaging of this |
| ** file. Please review the following information to ensure the GNU General |
| ** Public License version 3.0 requirements will be met: |
| ** http://www.gnu.org/copyleft/gpl.html. |
| ** |
| ** Other Usage |
| ** Alternatively, this file may be used in accordance with the terms and |
| ** conditions contained in a signed written agreement between you and Nokia. |
| ** |
| ** |
| ** |
| ** |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "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 useful 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/columns 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 |
| QItemSelectionRange range(tl, br); |
| if (!range.isEmpty()) |
| selection.append(range); |
| } |
| |
| d->selectionModel->select(selection, command); |
| } |
| |
| /*! |
| \internal |
| |
| Returns the rectangle from the viewport of the items in the given |
| \a selection. |
| |
| Since 4.7, the returned region only contains rectangles intersecting |
| (or included in) the viewport. |
| */ |
| QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const |
| { |
| Q_D(const QTableView); |
| |
| if (selection.isEmpty()) |
| return QRegion(); |
| |
| QRegion selectionRegion; |
| const QRect &viewportRect = d->viewport->rect(); |
| 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) { |
| const QRect &rangeRect = visualRect(d->model->index(r, c, d->root)); |
| if (viewportRect.intersects(rangeRect)) |
| selectionRegion += rangeRect; |
| } |
| } |
| } 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) { |
| const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height); |
| if (viewportRect.intersects(rangeRect)) |
| selectionRegion += rangeRect; |
| } |
| } |
| } 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) { |
| const QRect rangeRect(left, rowViewportPosition(r), width, rowHeight(r)); |
| if (viewportRect.intersects(rangeRect)) |
| selectionRegion += rangeRect; |
| } |
| } |
| } 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()); |
| } |
| const QRect rangeRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust)); |
| if (viewportRect.intersects(rangeRect)) |
| selectionRegion += rangeRect; |
| 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())) { |
| const QRect &visualSpanRect = d->visualSpanRect(*s); |
| if (viewportRect.intersects(visualSpanRect)) |
| selectionRegion += visualSpanRect; |
| } |
| } |
| } |
| } |
| } |
| |
| 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); |
| d->doDelayedItemsLayout(); |
| } |
| |
| /*! |
| 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, d->horizontalHeader->visualIndexAt(0)); |
| int right = d->horizontalHeader->visualIndexAt(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, d->verticalHeader->visualIndexAt(0)); |
| int bottom = d->verticalHeader->visualIndexAt(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 |