Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtableview.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include "qtableview.h"
6
7#include <qheaderview.h>
8#include <qabstractitemdelegate.h>
9#include <qapplication.h>
10#include <qpainter.h>
11#include <qstyle.h>
12#include <qsize.h>
13#include <qevent.h>
14#include <qbitarray.h>
15#include <qscrollbar.h>
16#if QT_CONFIG(abstractbutton)
17#include <qabstractbutton.h>
18#endif
19#include <private/qapplication_p.h>
20#include <private/qtableview_p.h>
21#include <private/qheaderview_p.h>
22#include <private/qscrollbar_p.h>
23#if QT_CONFIG(accessibility)
24#include <qaccessible.h>
25#endif
26
27#include <algorithm>
28
29using namespace std::chrono_literals;
30
31QT_BEGIN_NAMESPACE
32
33/** \internal
34 Add a span to the collection. the collection takes the ownership.
35 */
36void QSpanCollection::addSpan(QSpanCollection::Span *span)
37{
38 spans.push_back(span);
39 Index::iterator it_y = index.lowerBound(-span->top());
40 if (it_y == index.end() || it_y.key() != -span->top()) {
41 //there is no spans that starts with the row in the index, so create a sublist for it.
42 SubIndex sub_index;
43 if (it_y != index.end()) {
44 //the previouslist is the list of spans that sarts _before_ the row of the span.
45 // and which may intersect this row.
46 const SubIndex previousList = it_y.value();
47 for (Span *s : previousList) {
48 //If a subspans intersect the row, we need to split it into subspans
49 if (s->bottom() >= span->top())
50 sub_index.insert(-s->left(), s);
51 }
52 }
53 it_y = index.insert(-span->top(), sub_index);
54 //we will insert span to *it_y in the later loop
55 }
56
57 //insert the span as supspan in all the lists that intesects the span
58 while(-it_y.key() <= span->bottom()) {
59 (*it_y).insert(-span->left(), span);
60 if (it_y == index.begin())
61 break;
62 --it_y;
63 }
64}
65
66
67/** \internal
68* Has to be called after the height and width of a span is changed.
69*
70* old_height is the height before the change
71*
72* if the size of the span is now 0x0 the span will be deleted.
73*/
74void QSpanCollection::updateSpan(QSpanCollection::Span *span, int old_height)
75{
76 if (old_height < span->height()) {
77 //add the span as subspan in all the lists that intersect the new covered columns
78 Index::iterator it_y = index.lowerBound(-(span->top() + old_height - 1));
79 Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
80 while (-it_y.key() <= span->bottom()) {
81 (*it_y).insert(-span->left(), span);
82 if (it_y == index.begin())
83 break;
84 --it_y;
85 }
86 } else if (old_height > span->height()) {
87 //remove the span from all the subspans lists that intersect the columns not covered anymore
88 Index::iterator it_y = index.lowerBound(-qMax(span->bottom(), span->top())); //qMax useful if height is 0
89 Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
90 while (-it_y.key() <= span->top() + old_height -1) {
91 if (-it_y.key() > span->bottom()) {
92 int removed = (*it_y).remove(-span->left());
93 Q_ASSERT(removed == 1);
94 Q_UNUSED(removed);
95 if (it_y->isEmpty()) {
96 it_y = index.erase(it_y);
97 }
98 }
99 if (it_y == index.begin())
100 break;
101 --it_y;
102 }
103 }
104
105 if (span->width() == 0 && span->height() == 0) {
106 spans.remove(span);
107 delete span;
108 }
109}
110
111/** \internal
112 * \return a spans that spans over cell x,y (column,row)
113 * or \nullptr if there is none.
114 */
115QSpanCollection::Span *QSpanCollection::spanAt(int x, int y) const
116{
117 Index::const_iterator it_y = index.lowerBound(-y);
118 if (it_y == index.end())
119 return nullptr;
120 SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
121 if (it_x == (*it_y).end())
122 return nullptr;
123 Span *span = *it_x;
124 if (span->right() >= x && span->bottom() >= y)
125 return span;
126 return nullptr;
127}
128
129
130/** \internal
131* remove and deletes all spans inside the collection
132*/
133void QSpanCollection::clear()
134{
135 qDeleteAll(spans);
136 index.clear();
137 spans.clear();
138}
139
140/** \internal
141 * return a list to all the spans that spans over cells in the given rectangle
142 */
143QSet<QSpanCollection::Span *> QSpanCollection::spansInRect(int x, int y, int w, int h) const
144{
145 QSet<Span *> list;
146 Index::const_iterator it_y = index.lowerBound(-y);
147 if (it_y == index.end())
148 --it_y;
149 while(-it_y.key() <= y + h) {
150 SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
151 if (it_x == (*it_y).end())
152 --it_x;
153 while(-it_x.key() <= x + w) {
154 Span *s = *it_x;
155 if (s->bottom() >= y && s->right() >= x)
156 list << s;
157 if (it_x == (*it_y).begin())
158 break;
159 --it_x;
160 }
161 if (it_y == index.begin())
162 break;
163 --it_y;
164 }
165 return list;
166}
167
168#undef DEBUG_SPAN_UPDATE
169
170#ifdef DEBUG_SPAN_UPDATE
171QDebug operator<<(QDebug str, const QSpanCollection::Span &span)
172{
173 str << '(' << span.top() << ',' << span.left() << ',' << span.bottom() << ',' << span.right() << ')';
174 return str;
175}
176
177QDebug operator<<(QDebug debug, const QSpanCollection::SpanList &spans)
178{
179 for (const auto *span : spans)
180 debug << span << *span;
181 return debug;
182}
183#endif
184
185/** \internal
186* Updates the span collection after row insertion.
187*/
188void QSpanCollection::updateInsertedRows(int start, int end)
189{
190#ifdef DEBUG_SPAN_UPDATE
191 qDebug() << start << end << Qt::endl << index;
192#endif
193 if (spans.empty())
194 return;
195
196 int delta = end - start + 1;
197#ifdef DEBUG_SPAN_UPDATE
198 qDebug("Before");
199#endif
200 for (Span *span : spans) {
201#ifdef DEBUG_SPAN_UPDATE
202 qDebug() << span << *span;
203#endif
204 if (span->m_bottom < start)
205 continue;
206 if (span->m_top >= start)
207 span->m_top += delta;
208 span->m_bottom += delta;
209 }
210
211#ifdef DEBUG_SPAN_UPDATE
212 qDebug("After");
213 qDebug() << spans;
214#endif
215
216 for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
217 int y = -it_y.key();
218 if (y < start) {
219 ++it_y;
220 continue;
221 }
222
223 index.insert(-y - delta, it_y.value());
224 it_y = index.erase(it_y);
225 }
226#ifdef DEBUG_SPAN_UPDATE
227 qDebug() << index;
228#endif
229}
230
231/** \internal
232* Updates the span collection after column insertion.
233*/
234void QSpanCollection::updateInsertedColumns(int start, int end)
235{
236#ifdef DEBUG_SPAN_UPDATE
237 qDebug() << start << end << Qt::endl << index;
238#endif
239 if (spans.empty())
240 return;
241
242 int delta = end - start + 1;
243#ifdef DEBUG_SPAN_UPDATE
244 qDebug("Before");
245#endif
246 for (Span *span : spans) {
247#ifdef DEBUG_SPAN_UPDATE
248 qDebug() << span << *span;
249#endif
250 if (span->m_right < start)
251 continue;
252 if (span->m_left >= start)
253 span->m_left += delta;
254 span->m_right += delta;
255 }
256
257#ifdef DEBUG_SPAN_UPDATE
258 qDebug("After");
259 qDebug() << spans;
260#endif
261
262 for (Index::iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
263 SubIndex &subindex = it_y.value();
264 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
265 int x = -it.key();
266 if (x < start) {
267 ++it;
268 continue;
269 }
270 subindex.insert(-x - delta, it.value());
271 it = subindex.erase(it);
272 }
273 }
274#ifdef DEBUG_SPAN_UPDATE
275 qDebug() << index;
276#endif
277}
278
279/** \internal
280* Cleans a subindex from to be deleted spans. The update argument is used
281* to move the spans inside the subindex, in case their anchor changed.
282* \return true if no span in this subindex starts at y, and should thus be deleted.
283*/
284bool QSpanCollection::cleanSpanSubIndex(QSpanCollection::SubIndex &subindex, int y, bool update)
285{
286 if (subindex.isEmpty())
287 return true;
288
289 bool should_be_deleted = true;
290 SubIndex::iterator it = subindex.end();
291 do {
292 --it;
293 int x = -it.key();
294 Span *span = it.value();
295 if (span->will_be_deleted) {
296 it = subindex.erase(it);
297 continue;
298 }
299 if (update && span->m_left != x) {
300 subindex.insert(-span->m_left, span);
301 it = subindex.erase(it);
302 }
303 if (should_be_deleted && span->m_top == y)
304 should_be_deleted = false;
305 } while (it != subindex.begin());
306
307 return should_be_deleted;
308}
309
310/** \internal
311* Updates the span collection after row removal.
312*/
313void QSpanCollection::updateRemovedRows(int start, int end)
314{
315#ifdef DEBUG_SPAN_UPDATE
316 qDebug() << start << end << Qt::endl << index;
317#endif
318 if (spans.empty())
319 return;
320
321 SpanList spansToBeDeleted;
322 int delta = end - start + 1;
323#ifdef DEBUG_SPAN_UPDATE
324 qDebug("Before");
325#endif
326 for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
327 Span *span = *it;
328#ifdef DEBUG_SPAN_UPDATE
329 qDebug() << span << *span;
330#endif
331 if (span->m_bottom < start) {
332 ++it;
333 continue;
334 }
335 if (span->m_top < start) {
336 if (span->m_bottom <= end)
337 span->m_bottom = start - 1;
338 else
339 span->m_bottom -= delta;
340 } else {
341 if (span->m_bottom > end) {
342 if (span->m_top <= end)
343 span->m_top = start;
344 else
345 span->m_top -= delta;
346 span->m_bottom -= delta;
347 } else {
348 span->will_be_deleted = true;
349 }
350 }
351 if (span->m_top == span->m_bottom && span->m_left == span->m_right)
352 span->will_be_deleted = true;
353 if (span->will_be_deleted) {
354 spansToBeDeleted.push_back(span);
355 it = spans.erase(it);
356 } else {
357 ++it;
358 }
359 }
360
361#ifdef DEBUG_SPAN_UPDATE
362 qDebug("After");
363 qDebug() << spans;
364#endif
365 if (spans.empty()) {
366 qDeleteAll(spansToBeDeleted);
367 index.clear();
368 return;
369 }
370
371 Index::iterator it_y = index.end();
372 do {
373 --it_y;
374 int y = -it_y.key();
375 SubIndex &subindex = it_y.value();
376 if (y < start) {
377 if (cleanSpanSubIndex(subindex, y))
378 it_y = index.erase(it_y);
379 } else if (y >= start && y <= end) {
380 bool span_at_start = false;
381 SubIndex spansToBeMoved;
382 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ++it) {
383 Span *span = it.value();
384 if (span->will_be_deleted)
385 continue;
386 if (!span_at_start && span->m_top == start)
387 span_at_start = true;
388 spansToBeMoved.insert(it.key(), span);
389 }
390
391 if (y == start && span_at_start)
392 subindex.clear();
393 else
394 it_y = index.erase(it_y);
395
396 if (span_at_start) {
397 Index::iterator it_start;
398 if (y == start)
399 it_start = it_y;
400 else {
401 it_start = index.find(-start);
402 if (it_start == index.end())
403 it_start = index.insert(-start, SubIndex());
404 }
405 SubIndex &start_subindex = it_start.value();
406 for (SubIndex::iterator it = spansToBeMoved.begin(); it != spansToBeMoved.end(); ++it)
407 start_subindex.insert(it.key(), it.value());
408 }
409 } else {
410 if (y == end + 1) {
411 Index::iterator it_top = index.find(-y + delta);
412 if (it_top == index.end())
413 it_top = index.insert(-y + delta, SubIndex());
414 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
415 Span *span = it.value();
416 if (!span->will_be_deleted)
417 it_top.value().insert(it.key(), span);
418 ++it;
419 }
420 } else {
421 index.insert(-y + delta, subindex);
422 }
423 it_y = index.erase(it_y);
424 }
425 } while (it_y != index.begin());
426
427#ifdef DEBUG_SPAN_UPDATE
428 qDebug() << index;
429 qDebug("Deleted");
430 qDebug() << spansToBeDeleted;
431#endif
432 qDeleteAll(spansToBeDeleted);
433}
434
435/** \internal
436* Updates the span collection after column removal.
437*/
438void QSpanCollection::updateRemovedColumns(int start, int end)
439{
440#ifdef DEBUG_SPAN_UPDATE
441 qDebug() << start << end << Qt::endl << index;
442#endif
443 if (spans.empty())
444 return;
445
446 SpanList toBeDeleted;
447 int delta = end - start + 1;
448#ifdef DEBUG_SPAN_UPDATE
449 qDebug("Before");
450#endif
451 for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
452 Span *span = *it;
453#ifdef DEBUG_SPAN_UPDATE
454 qDebug() << span << *span;
455#endif
456 if (span->m_right < start) {
457 ++it;
458 continue;
459 }
460 if (span->m_left < start) {
461 if (span->m_right <= end)
462 span->m_right = start - 1;
463 else
464 span->m_right -= delta;
465 } else {
466 if (span->m_right > end) {
467 if (span->m_left <= end)
468 span->m_left = start;
469 else
470 span->m_left -= delta;
471 span->m_right -= delta;
472 } else {
473 span->will_be_deleted = true;
474 }
475 }
476 if (span->m_top == span->m_bottom && span->m_left == span->m_right)
477 span->will_be_deleted = true;
478 if (span->will_be_deleted) {
479 toBeDeleted.push_back(span);
480 it = spans.erase(it);
481 } else {
482 ++it;
483 }
484 }
485
486#ifdef DEBUG_SPAN_UPDATE
487 qDebug("After");
488 qDebug() << spans;
489#endif
490 if (spans.empty()) {
491 qDeleteAll(toBeDeleted);
492 index.clear();
493 return;
494 }
495
496 for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
497 int y = -it_y.key();
498 if (cleanSpanSubIndex(it_y.value(), y, true))
499 it_y = index.erase(it_y);
500 else
501 ++it_y;
502 }
503
504#ifdef DEBUG_SPAN_UPDATE
505 qDebug() << index;
506 qDebug("Deleted");
507 qDebug() << toBeDeleted;
508#endif
509 qDeleteAll(toBeDeleted);
510}
511
512#ifdef QT_BUILD_INTERNAL
513/*!
514 \internal
515 Checks whether the span index structure is self-consistent, and consistent with the spans list.
516*/
517bool QSpanCollection::checkConsistency() const
518{
519 for (Index::const_iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
520 int y = -it_y.key();
521 const SubIndex &subIndex = it_y.value();
522 for (SubIndex::const_iterator it = subIndex.begin(); it != subIndex.end(); ++it) {
523 int x = -it.key();
524 Span *span = it.value();
525 const bool contains = std::find(spans.begin(), spans.end(), span) != spans.end();
526 if (!contains || span->left() != x || y < span->top() || y > span->bottom())
527 return false;
528 }
529 }
530
531 for (const Span *span : spans) {
532 if (span->width() < 1 || span->height() < 1
533 || (span->width() == 1 && span->height() == 1))
534 return false;
535 for (int y = span->top(); y <= span->bottom(); ++y) {
536 Index::const_iterator it_y = index.find(-y);
537 if (it_y == index.end()) {
538 if (y == span->top())
539 return false;
540 else
541 continue;
542 }
543 const SubIndex &subIndex = it_y.value();
544 SubIndex::const_iterator it = subIndex.find(-span->left());
545 if (it == subIndex.end() || it.value() != span)
546 return false;
547 }
548 }
549 return true;
550}
551#endif
552
553#if QT_CONFIG(abstractbutton)
554class QTableCornerButton : public QAbstractButton
555{
556 Q_OBJECT
557public:
558 QTableCornerButton(QWidget *parent) : QAbstractButton(parent) {}
559 void paintEvent(QPaintEvent*) override {
560 QStyleOptionHeader opt;
561 opt.initFrom(this);
562 QStyle::State state = QStyle::State_None;
563 if (isEnabled())
564 state |= QStyle::State_Enabled;
565 if (isActiveWindow())
566 state |= QStyle::State_Active;
567 if (isDown())
568 state |= QStyle::State_Sunken;
569 opt.state = state;
570 opt.rect = rect();
571 opt.position = QStyleOptionHeader::OnlyOneSection;
572 QPainter painter(this);
573 style()->drawControl(QStyle::CE_Header, &opt, &painter, this);
574 }
575};
576#endif
577
578void QTableViewPrivate::init()
579{
580 Q_Q(QTableView);
581
582 q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed);
583
584 QHeaderView *vertical = new QHeaderView(Qt::Vertical, q);
585 vertical->setSectionsClickable(true);
586 vertical->setHighlightSections(true);
587 q->setVerticalHeader(vertical);
588
589 QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q);
590 horizontal->setSectionsClickable(true);
591 horizontal->setHighlightSections(true);
592 q->setHorizontalHeader(horizontal);
593
594 tabKeyNavigation = true;
595
596#if QT_CONFIG(abstractbutton)
597 cornerWidget = new QTableCornerButton(q);
598 cornerWidget->setFocusPolicy(Qt::NoFocus);
599 cornerWidgetConnection = QObject::connect(
600 cornerWidget, &QTableCornerButton::clicked,
601 q, &QTableView::selectAll);
602#endif
603}
604
605void QTableViewPrivate::clearConnections()
606{
607 for (const QMetaObject::Connection &connection : modelConnections)
608 QObject::disconnect(connection);
609 for (const QMetaObject::Connection &connection : verHeaderConnections)
610 QObject::disconnect(connection);
611 for (const QMetaObject::Connection &connection : horHeaderConnections)
612 QObject::disconnect(connection);
613 for (const QMetaObject::Connection &connection : dynHorHeaderConnections)
614 QObject::disconnect(connection);
615 QObject::disconnect(selectionmodelConnection);
616#if QT_CONFIG(abstractbutton)
617 QObject::disconnect(cornerWidgetConnection);
618#endif
619}
620
621/*!
622 \internal
623 Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections.
624*/
625void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const
626{
627 Q_ASSERT(range && range->isValid());
628
629 int top = range->top();
630 int left = range->left();
631 int bottom = range->bottom();
632 int right = range->right();
633
634 while (bottom >= top && verticalHeader->isSectionHidden(bottom))
635 --bottom;
636 while (right >= left && horizontalHeader->isSectionHidden(right))
637 --right;
638
639 if (top > bottom || left > right) { // everything is hidden
640 *range = QItemSelectionRange();
641 return;
642 }
643
644 while (verticalHeader->isSectionHidden(top) && top <= bottom)
645 ++top;
646 while (horizontalHeader->isSectionHidden(left) && left <= right)
647 ++left;
648
649 if (top > bottom || left > right) { // everything is hidden
650 *range = QItemSelectionRange();
651 return;
652 }
653
654 QModelIndex bottomRight = model->index(bottom, right, range->parent());
655 QModelIndex topLeft = model->index(top, left, range->parent());
656 *range = QItemSelectionRange(topLeft, bottomRight);
657}
658
659QRect QTableViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const
660{
661 Q_Q(const QTableView);
662
663 using minMaxPair = std::pair<int, int>;
664 const auto calcMinMax = [q](QHeaderView *hdr, int startIdx, int endIdx, minMaxPair bounds) -> minMaxPair
665 {
666 minMaxPair ret(std::numeric_limits<int>::max(), std::numeric_limits<int>::min());
667 if (hdr->sectionsMoved()) {
668 for (int i = startIdx; i <= endIdx; ++i) {
669 const int start = hdr->sectionViewportPosition(i);
670 const int end = start + hdr->sectionSize(i);
671 ret.first = std::min(start, ret.first);
672 ret.second = std::max(end, ret.second);
673 if (ret.first <= bounds.first && ret.second >= bounds.second)
674 break;
675 }
676 } else {
677 if (q->isRightToLeft() && q->horizontalHeader() == hdr)
678 std::swap(startIdx, endIdx);
679 ret.first = hdr->sectionViewportPosition(startIdx);
680 ret.second = hdr->sectionViewportPosition(endIdx) +
681 hdr->sectionSize(endIdx);
682 }
683 return ret;
684 };
685
686 const auto yVals = calcMinMax(verticalHeader, topLeft.row(), bottomRight.row(),
687 minMaxPair(rect.top(), rect.bottom()));
688 if (yVals.first == yVals.second) // all affected rows are hidden
689 return QRect();
690
691 // short circuit: check if no row is inside rect
692 const QRect colRect(QPoint(rect.left(), yVals.first),
693 QPoint(rect.right(), yVals.second));
694 const QRect intersected = rect.intersected(colRect);
695 if (intersected.isNull())
696 return QRect();
697
698 const auto xVals = calcMinMax(horizontalHeader, topLeft.column(), bottomRight.column(),
699 minMaxPair(rect.left(), rect.right()));
700 const QRect updateRect(QPoint(xVals.first, yVals.first),
701 QPoint(xVals.second, yVals.second));
702 return rect.intersected(updateRect);
703}
704
705/*!
706 \internal
707 Sets the span for the cell at (\a row, \a column).
708*/
709void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan)
710{
711 if (Q_UNLIKELY(row < 0 || column < 0 || rowSpan <= 0 || columnSpan <= 0)) {
712 qWarning("QTableView::setSpan: invalid span given: (%d, %d, %d, %d)",
713 row, column, rowSpan, columnSpan);
714 return;
715 }
716 QSpanCollection::Span *sp = spans.spanAt(column, row);
717 if (sp) {
718 if (sp->top() != row || sp->left() != column) {
719 qWarning("QTableView::setSpan: span cannot overlap");
720 return;
721 }
722 if (rowSpan == 1 && columnSpan == 1) {
723 rowSpan = columnSpan = 0;
724 }
725 const int old_height = sp->height();
726 sp->m_bottom = row + rowSpan - 1;
727 sp->m_right = column + columnSpan - 1;
728 spans.updateSpan(sp, old_height);
729 return;
730 } else if (Q_UNLIKELY(rowSpan == 1 && columnSpan == 1)) {
731 qWarning("QTableView::setSpan: single cell span won't be added");
732 return;
733 }
734 sp = new QSpanCollection::Span(row, column, rowSpan, columnSpan);
735 spans.addSpan(sp);
736}
737
738/*!
739 \internal
740 Gets the span information for the cell at (\a row, \a column).
741*/
742QSpanCollection::Span QTableViewPrivate::span(int row, int column) const
743{
744 QSpanCollection::Span *sp = spans.spanAt(column, row);
745 if (sp)
746 return *sp;
747
748 return QSpanCollection::Span(row, column, 1, 1);
749}
750
751/*!
752 \internal
753 Returns the logical index of the last section that's part of the span.
754*/
755int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const
756{
757 int visual = header->visualIndex(logical);
758 for (int i = 1; i < span; ) {
759 if (++visual >= header->count())
760 break;
761 logical = header->logicalIndex(visual);
762 ++i;
763 }
764 return logical;
765}
766
767/*!
768 \internal
769 Returns the size of the span starting at logical index \a logical
770 and spanning \a span sections.
771*/
772int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const
773{
774 int endLogical = sectionSpanEndLogical(header, logical, span);
775 return header->sectionPosition(endLogical)
776 - header->sectionPosition(logical)
777 + header->sectionSize(endLogical);
778}
779
780/*!
781 \internal
782 Returns \c true if the section at logical index \a logical is part of the span
783 starting at logical index \a spanLogical and spanning \a span sections;
784 otherwise, returns \c false.
785*/
786bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const
787{
788 if (logical == spanLogical)
789 return true; // it's the start of the span
790 int visual = header->visualIndex(spanLogical);
791 for (int i = 1; i < span; ) {
792 if (++visual >= header->count())
793 break;
794 spanLogical = header->logicalIndex(visual);
795 if (logical == spanLogical)
796 return true;
797 ++i;
798 }
799 return false;
800}
801
802/*!
803 \internal
804 Searches for the next cell which is available for e.g. keyboard navigation
805 The search is done by row
806*/
807int QTableViewPrivate::nextActiveVisualRow(int rowToStart, int column, int limit,
808 SearchDirection searchDirection) const
809{
810 const int lc = logicalColumn(column);
811 int visualRow = rowToStart;
812 const auto isCellActive = [this](int vr, int lc)
813 {
814 const int lr = logicalRow(vr);
815 return !isRowHidden(lr) && isCellEnabled(lr, lc);
816 };
817 switch (searchDirection) {
818 case SearchDirection::Increasing:
819 if (visualRow < limit) {
820 while (!isCellActive(visualRow, lc)) {
821 if (++visualRow == limit)
822 return rowToStart;
823 }
824 }
825 break;
826 case SearchDirection::Decreasing:
827 while (visualRow > limit && !isCellActive(visualRow, lc))
828 --visualRow;
829 break;
830 }
831 return visualRow;
832}
833
834/*!
835 \internal
836 Searches for the next cell which is available for e.g. keyboard navigation
837 The search is done by column
838*/
839int QTableViewPrivate::nextActiveVisualColumn(int row, int columnToStart, int limit,
840 SearchDirection searchDirection) const
841{
842 const int lr = logicalRow(row);
843 int visualColumn = columnToStart;
844 const auto isCellActive = [this](int lr, int vc)
845 {
846 const int lc = logicalColumn(vc);
847 return !isColumnHidden(lc) && isCellEnabled(lr, lc);
848 };
849 switch (searchDirection) {
850 case SearchDirection::Increasing:
851 while (visualColumn < limit && !isCellActive(lr, visualColumn))
852 ++visualColumn;
853 break;
854 case SearchDirection::Decreasing:
855 while (visualColumn > limit && !isCellActive(lr, visualColumn))
856 --visualColumn;
857 break;
858 }
859 return visualColumn;
860}
861
862/*!
863 \internal
864 Returns the visual rect for the given \a span.
865*/
866QRect QTableViewPrivate::visualSpanRect(const QSpanCollection::Span &span) const
867{
868 Q_Q(const QTableView);
869 // vertical
870 int row = span.top();
871 int rowp = verticalHeader->sectionViewportPosition(row);
872 int rowh = rowSpanHeight(row, span.height());
873 // horizontal
874 int column = span.left();
875 int colw = columnSpanWidth(column, span.width());
876 if (q->isRightToLeft())
877 column = span.right();
878 int colp = horizontalHeader->sectionViewportPosition(column);
879
880 const int i = showGrid ? 1 : 0;
881 if (q->isRightToLeft())
882 return QRect(colp + i, rowp, colw - i, rowh - i);
883 return QRect(colp, rowp, colw - i, rowh - i);
884}
885
886/*!
887 \internal
888 Draws the spanning cells within rect \a area, and clips them off as
889 preparation for the main drawing loop.
890 \a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn
891*/
892void QTableViewPrivate::drawAndClipSpans(const QRegion &area, QPainter *painter,
893 const QStyleOptionViewItem &option, QBitArray *drawn,
894 int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn)
895{
896 Q_Q(const QTableView);
897 bool alternateBase = false;
898 QRegion region = viewport->rect();
899
900 QSet<QSpanCollection::Span *> visibleSpans;
901 bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved();
902
903 if (!sectionMoved) {
904 visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow),
905 lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1);
906 } else {
907 // Any cell outside the viewport, on the top or left, can still end up visible inside the
908 // viewport if is has a span. Calculating if a spanned cell overlaps with the viewport is
909 // "easy" enough when the columns (or rows) in the view are aligned with the columns
910 // in the model; In that case you know that if a column is outside the viewport on the
911 // right, it cannot affect the drawing of the cells inside the viewport, even with a span.
912 // And under that assumption, the spansInRect() function can be used (which is optimized
913 // to only iterate the spans that are close to the viewport).
914 // But when the view has rearranged the columns (or rows), this is no longer true. In that
915 // case, even if a column, according to the model, is outside the viewport on the right, it
916 // can still overlap with the viewport. This can happen if it was moved to the left of the
917 // viewport and one of its cells has a span. In that case we need to take the theoretically
918 // slower route and iterate through all the spans, and check if any of them overlaps with
919 // the viewport.
920 const auto spanList = spans.spans;
921 for (QSpanCollection::Span *span : spanList) {
922 const int spanVisualLeft = visualColumn(span->left());
923 const int spanVisualTop = visualRow(span->top());
924 const int spanVisualRight = spanVisualLeft + span->width() - 1;
925 const int spanVisualBottom = spanVisualTop + span->height() - 1;
926 if ((spanVisualLeft <= lastVisualColumn && spanVisualRight >= firstVisualColumn)
927 && (spanVisualTop <= lastVisualRow && spanVisualBottom >= firstVisualRow))
928 visibleSpans.insert(span);
929 }
930 }
931
932 for (QSpanCollection::Span *span : std::as_const(visibleSpans)) {
933 int row = span->top();
934 int col = span->left();
935 QModelIndex index = model->index(row, col, root);
936 if (!index.isValid())
937 continue;
938 QRect rect = visualSpanRect(*span);
939 rect.translate(scrollDelayOffset);
940 if (!area.intersects(rect))
941 continue;
942 QStyleOptionViewItem opt = option;
943 opt.rect = rect;
944 alternateBase = alternatingColors && (span->top() & 1);
945 opt.features.setFlag(QStyleOptionViewItem::Alternate, alternateBase);
946 drawCell(painter, opt, index);
947 if (showGrid) {
948 // adjust the clip rect to be able to paint the top & left grid lines
949 // if the headers are not visible, see paintEvent()
950 if (horizontalHeader->visualIndex(row) == 0)
951 rect.setTop(rect.top() + 1);
952 if (verticalHeader->visualIndex(row) == 0) {
953 if (q->isLeftToRight())
954 rect.setLeft(rect.left() + 1);
955 else
956 rect.setRight(rect.right() - 1);
957 }
958 }
959 region -= rect;
960 for (int r = span->top(); r <= span->bottom(); ++r) {
961 const int vr = visualRow(r);
962 if (vr < firstVisualRow || vr > lastVisualRow)
963 continue;
964 for (int c = span->left(); c <= span->right(); ++c) {
965 const int vc = visualColumn(c);
966 if (vc < firstVisualColumn || vc > lastVisualColumn)
967 continue;
968 drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
969 + vc - firstVisualColumn);
970 }
971 }
972
973 }
974 painter->setClipRegion(region);
975}
976
977/*!
978 \internal
979 Updates spans after row insertion.
980*/
981void QTableViewPrivate::updateSpanInsertedRows(const QModelIndex &parent, int start, int end)
982{
983 Q_UNUSED(parent);
984 spans.updateInsertedRows(start, end);
985}
986
987/*!
988 \internal
989 Updates spans after column insertion.
990*/
991void QTableViewPrivate::updateSpanInsertedColumns(const QModelIndex &parent, int start, int end)
992{
993 Q_UNUSED(parent);
994 spans.updateInsertedColumns(start, end);
995}
996
997/*!
998 \internal
999 Updates spans after row removal.
1000*/
1001void QTableViewPrivate::updateSpanRemovedRows(const QModelIndex &parent, int start, int end)
1002{
1003 Q_UNUSED(parent);
1004 spans.updateRemovedRows(start, end);
1005}
1006
1007/*!
1008 \internal
1009 Updates spans after column removal.
1010*/
1011void QTableViewPrivate::updateSpanRemovedColumns(const QModelIndex &parent, int start, int end)
1012{
1013 Q_UNUSED(parent);
1014 spans.updateRemovedColumns(start, end);
1015}
1016
1017/*!
1018 \internal
1019 Sort the model when the header sort indicator changed
1020*/
1021void QTableViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
1022{
1023 model->sort(column, order);
1024}
1025
1026QStyleOptionViewItem::ViewItemPosition QTableViewPrivate::viewItemPosition(
1027 const QModelIndex &index) const
1028{
1029 int visualColumn = horizontalHeader->visualIndex(index.column());
1030 int count = horizontalHeader->count();
1031
1032 if (count <= 0 || visualColumn < 0 || visualColumn >= count)
1033 return QStyleOptionViewItem::Invalid;
1034
1035 if (count == 1 && visualColumn == 0)
1036 return QStyleOptionViewItem::OnlyOne;
1037 else if (visualColumn == 0)
1038 return QStyleOptionViewItem::Beginning;
1039 else if (visualColumn == count - 1)
1040 return QStyleOptionViewItem::End;
1041 else
1042 return QStyleOptionViewItem::Middle;
1043}
1044
1045/*!
1046 \internal
1047 Draws a table cell.
1048*/
1049void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
1050{
1051 Q_Q(QTableView);
1052 QStyleOptionViewItem opt = option;
1053
1054 if (selectionModel && selectionModel->isSelected(index))
1055 opt.state |= QStyle::State_Selected;
1056 if (index == hover)
1057 opt.state |= QStyle::State_MouseOver;
1058 if (option.state & QStyle::State_Enabled) {
1059 QPalette::ColorGroup cg;
1060 if ((model->flags(index) & Qt::ItemIsEnabled) == 0) {
1061 opt.state &= ~QStyle::State_Enabled;
1062 cg = QPalette::Disabled;
1063 } else {
1064 cg = QPalette::Normal;
1065 }
1066 opt.palette.setCurrentColorGroup(cg);
1067 }
1068 opt.viewItemPosition = viewItemPosition(index);
1069
1070 if (index == q->currentIndex()) {
1071 const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid();
1072 if (focus)
1073 opt.state |= QStyle::State_HasFocus;
1074 }
1075
1076 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);
1077
1078 q->itemDelegateForIndex(index)->paint(painter, opt, index);
1079}
1080
1081/*!
1082 \internal
1083 Get sizeHint width for single Index (providing existing hint and style option)
1084*/
1085int QTableViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option) const
1086{
1087 Q_Q(const QTableView);
1088 const int oldHint = hint;
1089 QWidget *editor = editorForIndex(index).widget.data();
1090 if (editor && persistent.contains(editor)) {
1091 hint = qMax(hint, editor->sizeHint().width());
1092 int min = editor->minimumSize().width();
1093 int max = editor->maximumSize().width();
1094 hint = qBound(min, hint, max);
1095 }
1096 hint = qMax(hint, q->itemDelegateForIndex(index)->sizeHint(option, index).width());
1097
1098 if (hasSpans()) {
1099 auto span = spans.spanAt(index.column(), index.row());
1100 if (span && span->m_left == index.column() && span->m_top == index.row()) {
1101 // spans are screwed up when sections are moved
1102 const auto left = logicalColumn(span->m_left);
1103 for (int i = 1; i <= span->width(); ++i)
1104 hint -= q->columnWidth(visualColumn(left + i));
1105 }
1106 hint = std::max(hint, oldHint);
1107 }
1108 return hint;
1109}
1110
1111/*!
1112 \internal
1113 Get sizeHint height for single Index (providing existing hint and style option)
1114*/
1115int QTableViewPrivate::heightHintForIndex(const QModelIndex &index, int hint, QStyleOptionViewItem &option) const
1116{
1117 Q_Q(const QTableView);
1118 QWidget *editor = editorForIndex(index).widget.data();
1119 if (editor && persistent.contains(editor)) {
1120 hint = qMax(hint, editor->sizeHint().height());
1121 int min = editor->minimumSize().height();
1122 int max = editor->maximumSize().height();
1123 hint = qBound(min, hint, max);
1124 }
1125
1126 if (wrapItemText) {// for wrapping boundaries
1127 option.rect.setY(q->rowViewportPosition(index.row()));
1128 int height = q->rowHeight(index.row());
1129 // if the option.height == 0 then q->itemDelegateForIndex(index)->sizeHint(option, index) will be wrong.
1130 // The option.height == 0 is used to conclude that the text is not wrapped, and hence it will
1131 // (exactly like widthHintForIndex) return a QSize with a long width (that we don't use) -
1132 // and the height of the text if it was/is on one line.
1133 // What we want is a height hint for the current width (and we know that this section is not hidden)
1134 // Therefore we catch this special situation with:
1135 if (height == 0)
1136 height = 1;
1137 option.rect.setHeight(height);
1138 option.rect.setX(q->columnViewportPosition(index.column()));
1139 option.rect.setWidth(q->columnWidth(index.column()));
1140 if (hasSpans()) {
1141 auto span = spans.spanAt(index.column(), index.row());
1142 if (span && span->m_left == index.column() && span->m_top == index.row())
1143 option.rect.setWidth(std::max(option.rect.width(), visualSpanRect(*span).width()));
1144 }
1145 // 1px less space when grid is shown (see drawCell)
1146 if (showGrid)
1147 option.rect.setWidth(option.rect.width() - 1);
1148 }
1149 hint = qMax(hint, q->itemDelegateForIndex(index)->sizeHint(option, index).height());
1150 return hint;
1151}
1152
1153
1154/*!
1155 \class QTableView
1156
1157 \brief The QTableView class provides a default model/view
1158 implementation of a table view.
1159
1160 \ingroup model-view
1161 \ingroup advanced
1162 \inmodule QtWidgets
1163
1164 \image fusion-tableview.png {Table of months and amounts}
1165
1166 A QTableView implements a table view that displays items from a
1167 model. This class is used to provide standard tables that were
1168 previously provided by the QTable class, but using the more
1169 flexible approach provided by Qt's model/view architecture.
1170
1171 The QTableView class is one of the \l{Model/View Classes}
1172 and is part of Qt's \l{Model/View Programming}{model/view framework}.
1173
1174 QTableView implements the interfaces defined by the
1175 QAbstractItemView class to allow it to display data provided by
1176 models derived from the QAbstractItemModel class.
1177
1178 \section1 Navigation
1179
1180 You can navigate the cells in the table by clicking on a cell with the
1181 mouse, or by using the arrow keys. Because QTableView enables
1182 \l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you
1183 can also hit Tab and Backtab to move from cell to cell.
1184
1185 \section1 Visual Appearance
1186
1187 The table has a vertical header that can be obtained using the
1188 verticalHeader() function, and a horizontal header that is available
1189 through the horizontalHeader() function. The height of each row in the
1190 table can be found by using rowHeight(); similarly, the width of
1191 columns can be found using columnWidth(). Since both of these are plain
1192 widgets, you can hide either of them using their hide() functions.
1193 Each header is configured with its \l{QHeaderView::}{highlightSections}
1194 and \l{QHeaderView::}{sectionsClickable} properties set to \c true.
1195
1196 Rows and columns can be hidden and shown with hideRow(), hideColumn(),
1197 showRow(), and showColumn(). They can be selected with selectRow()
1198 and selectColumn(). The table will show a grid depending on the
1199 \l showGrid property.
1200
1201 The items shown in a table view, like those in the other item views, are
1202 rendered and edited using standard \l{QStyledItemDelegate}{delegates}. However,
1203 for some tasks it is sometimes useful to be able to insert widgets in a
1204 table instead. Widgets are set for particular indexes with the
1205 \l{QAbstractItemView::}{setIndexWidget()} function, and
1206 later retrieved with \l{QAbstractItemView::}{indexWidget()}.
1207
1208 \table
1209 \row \li \inlineimage qtableview-resized.png
1210 {Table of names, addresses, and quantity}
1211 \li By default, the cells in a table do not expand to fill the available space.
1212
1213 You can make the cells fill the available space by stretching the last
1214 header section. Access the relevant header using horizontalHeader()
1215 or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection}
1216 property.
1217
1218 To distribute the available space according to the space requirement of
1219 each column or row, call the view's resizeColumnsToContents() or
1220 resizeRowsToContents() functions.
1221 \endtable
1222
1223 \section1 Coordinate Systems
1224
1225 For some specialized forms of tables it is useful to be able to
1226 convert between row and column indexes and widget coordinates.
1227 The rowAt() function provides the y-coordinate within the view of the
1228 specified row; the row index can be used to obtain a corresponding
1229 y-coordinate with rowViewportPosition(). The columnAt() and
1230 columnViewportPosition() functions provide the equivalent conversion
1231 operations between x-coordinates and column indexes.
1232
1233 \sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
1234 {Table Model Example}
1235*/
1236
1237/*!
1238 Constructs a table view with a \a parent to represent the data.
1239
1240 \sa QAbstractItemModel
1241*/
1242
1243QTableView::QTableView(QWidget *parent)
1244 : QAbstractItemView(*new QTableViewPrivate, parent)
1245{
1246 Q_D(QTableView);
1247 d->init();
1248}
1249
1250/*!
1251 \internal
1252*/
1253QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent)
1254 : QAbstractItemView(dd, parent)
1255{
1256 Q_D(QTableView);
1257 d->init();
1258}
1259
1260/*!
1261 Destroys the table view.
1262*/
1263QTableView::~QTableView()
1264{
1265 Q_D(QTableView);
1266 d->clearConnections();
1267}
1268
1269/*!
1270 \reimp
1271*/
1272QSize QTableView::viewportSizeHint() const
1273{
1274 Q_D(const QTableView);
1275 QSize result( (d->verticalHeader->isHidden() ? 0 : d->verticalHeader->width()) + d->horizontalHeader->length(),
1276 (d->horizontalHeader->isHidden() ? 0 : d->horizontalHeader->height()) + d->verticalHeader->length());
1277 return result;
1278}
1279
1280/*!
1281 \reimp
1282*/
1283void QTableView::setModel(QAbstractItemModel *model)
1284{
1285 Q_D(QTableView);
1286 if (model == d->model)
1287 return;
1288 //let's disconnect from the old model
1289 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
1290 for (const QMetaObject::Connection &connection : d->modelConnections)
1291 disconnect(connection);
1292 }
1293 if (d->selectionModel) { // support row editing
1294 disconnect(d->selectionmodelConnection);
1295 }
1296 if (model) { //and connect to the new one
1297 d->modelConnections = {
1298 QObjectPrivate::connect(model, &QAbstractItemModel::rowsInserted,
1299 d, &QTableViewPrivate::updateSpanInsertedRows),
1300 QObjectPrivate::connect(model, &QAbstractItemModel::columnsInserted,
1301 d, &QTableViewPrivate::updateSpanInsertedColumns),
1302 QObjectPrivate::connect(model, &QAbstractItemModel::rowsRemoved,
1303 d, &QTableViewPrivate::updateSpanRemovedRows),
1304 QObjectPrivate::connect(model, &QAbstractItemModel::columnsRemoved,
1305 d, &QTableViewPrivate::updateSpanRemovedColumns)
1306 };
1307 }
1308 d->verticalHeader->setModel(model);
1309 d->horizontalHeader->setModel(model);
1310 QAbstractItemView::setModel(model);
1311}
1312
1313/*!
1314 \reimp
1315*/
1316void QTableView::setRootIndex(const QModelIndex &index)
1317{
1318 Q_D(QTableView);
1319 if (index == d->root) {
1320 viewport()->update();
1321 return;
1322 }
1323 d->verticalHeader->setRootIndex(index);
1324 d->horizontalHeader->setRootIndex(index);
1325 QAbstractItemView::setRootIndex(index);
1326}
1327
1328/*!
1329 \internal
1330*/
1331void QTableView::doItemsLayout()
1332{
1333 Q_D(QTableView);
1334 QAbstractItemView::doItemsLayout();
1335 if (!d->verticalHeader->updatesEnabled())
1336 d->verticalHeader->setUpdatesEnabled(true);
1337}
1338
1339/*!
1340 \reimp
1341*/
1342void QTableView::setSelectionModel(QItemSelectionModel *selectionModel)
1343{
1344 Q_D(QTableView);
1345 Q_ASSERT(selectionModel);
1346 if (d->selectionModel) {
1347 // support row editing
1348 disconnect(d->selectionmodelConnection);
1349 }
1350
1351 d->verticalHeader->setSelectionModel(selectionModel);
1352 d->horizontalHeader->setSelectionModel(selectionModel);
1353 QAbstractItemView::setSelectionModel(selectionModel);
1354
1355 if (d->selectionModel) {
1356 // support row editing
1357 d->selectionmodelConnection =
1358 connect(d->selectionModel, &QItemSelectionModel::currentRowChanged,
1359 d->model, &QAbstractItemModel::submit);
1360 }
1361}
1362
1363/*!
1364 Returns the table view's horizontal header.
1365
1366 \sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData()
1367*/
1368QHeaderView *QTableView::horizontalHeader() const
1369{
1370 Q_D(const QTableView);
1371 return d->horizontalHeader;
1372}
1373
1374/*!
1375 Returns the table view's vertical header.
1376
1377 \sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData()
1378*/
1379QHeaderView *QTableView::verticalHeader() const
1380{
1381 Q_D(const QTableView);
1382 return d->verticalHeader;
1383}
1384
1385/*!
1386 Sets the widget to use for the horizontal header to \a header.
1387
1388 \sa horizontalHeader(), setVerticalHeader()
1389*/
1390void QTableView::setHorizontalHeader(QHeaderView *header)
1391{
1392 Q_D(QTableView);
1393
1394 if (!header || header == d->horizontalHeader)
1395 return;
1396 for (const QMetaObject::Connection &connection : d->horHeaderConnections)
1397 disconnect(connection);
1398 if (d->horizontalHeader && d->horizontalHeader->parent() == this)
1399 delete d->horizontalHeader;
1400 d->horizontalHeader = header;
1401 d->horizontalHeader->setParent(this);
1402 d->horizontalHeader->setFirstSectionMovable(true);
1403 if (!d->horizontalHeader->model()) {
1404 d->horizontalHeader->setModel(d->model);
1405 if (d->selectionModel)
1406 d->horizontalHeader->setSelectionModel(d->selectionModel);
1407 }
1408
1409 d->horHeaderConnections = {
1410 connect(d->horizontalHeader,&QHeaderView::sectionResized,
1411 this, &QTableView::columnResized),
1412 connect(d->horizontalHeader, &QHeaderView::sectionMoved,
1413 this, &QTableView::columnMoved),
1414 connect(d->horizontalHeader, &QHeaderView::sectionCountChanged,
1415 this, &QTableView::columnCountChanged),
1416 connect(d->horizontalHeader, &QHeaderView::sectionHandleDoubleClicked,
1417 this, &QTableView::resizeColumnToContents),
1418 connect(d->horizontalHeader, &QHeaderView::geometriesChanged,
1419 this, &QTableView::updateGeometries),
1420 };
1421 //update the sorting enabled states on the new header
1422 setSortingEnabled(d->sortingEnabled);
1423}
1424
1425/*!
1426 Sets the widget to use for the vertical header to \a header.
1427
1428 \sa verticalHeader(), setHorizontalHeader()
1429*/
1430void QTableView::setVerticalHeader(QHeaderView *header)
1431{
1432 Q_D(QTableView);
1433
1434 if (!header || header == d->verticalHeader)
1435 return;
1436 for (const QMetaObject::Connection &connection : d->verHeaderConnections)
1437 disconnect(connection);
1438 if (d->verticalHeader && d->verticalHeader->parent() == this)
1439 delete d->verticalHeader;
1440 d->verticalHeader = header;
1441 d->verticalHeader->setParent(this);
1442 d->verticalHeader->setFirstSectionMovable(true);
1443 if (!d->verticalHeader->model()) {
1444 d->verticalHeader->setModel(d->model);
1445 if (d->selectionModel)
1446 d->verticalHeader->setSelectionModel(d->selectionModel);
1447 }
1448
1449 d->verHeaderConnections = {
1450 connect(d->verticalHeader, &QHeaderView::sectionResized,
1451 this, &QTableView::rowResized),
1452 connect(d->verticalHeader, &QHeaderView::sectionMoved,
1453 this, &QTableView::rowMoved),
1454 connect(d->verticalHeader, &QHeaderView::sectionCountChanged,
1455 this, &QTableView::rowCountChanged),
1456 connect(d->verticalHeader, &QHeaderView::sectionPressed,
1457 this, &QTableView::selectRow),
1458 connect(d->verticalHeader, &QHeaderView::sectionHandleDoubleClicked,
1459 this, &QTableView::resizeRowToContents),
1460 connect(d->verticalHeader, &QHeaderView::geometriesChanged,
1461 this, &QTableView::updateGeometries),
1462 connect(d->verticalHeader, &QHeaderView::sectionEntered,
1463 this, [d](int row) { d->selectRow(row, false); })
1464 };
1465}
1466
1467/*!
1468 \reimp
1469
1470 Scroll the contents of the table view by (\a dx, \a dy).
1471*/
1472void QTableView::scrollContentsBy(int dx, int dy)
1473{
1474 Q_D(QTableView);
1475
1476 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
1477
1478 dx = isRightToLeft() ? -dx : dx;
1479 if (dx) {
1480 int oldOffset = d->horizontalHeader->offset();
1481 d->horizontalHeader->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
1482 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
1483 int newOffset = d->horizontalHeader->offset();
1484 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
1485 }
1486 }
1487 if (dy) {
1488 int oldOffset = d->verticalHeader->offset();
1489 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
1490 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1491 int newOffset = d->verticalHeader->offset();
1492 dy = oldOffset - newOffset;
1493 }
1494 }
1495 d->scrollContentsBy(dx, dy);
1496
1497 if (d->showGrid) {
1498 //we need to update the first line of the previous top item in the view
1499 //because it has the grid drawn if the header is invisible.
1500 //It is strictly related to what's done at then end of the paintEvent
1501 if (dy > 0 && d->horizontalHeader->isHidden()) {
1502 d->viewport->update(0, dy, d->viewport->width(), dy);
1503 }
1504 if (dx > 0 && d->verticalHeader->isHidden()) {
1505 d->viewport->update(dx, 0, dx, d->viewport->height());
1506 }
1507 }
1508}
1509
1510/*!
1511 \reimp
1512*/
1513void QTableView::initViewItemOption(QStyleOptionViewItem *option) const
1514{
1515 QAbstractItemView::initViewItemOption(option);
1516 option->showDecorationSelected = true;
1517}
1518
1519/*!
1520 Paints the table on receipt of the given paint event \a event.
1521*/
1522void QTableView::paintEvent(QPaintEvent *event)
1523{
1524 Q_D(QTableView);
1525 // setup temp variables for the painting
1526 QStyleOptionViewItem option;
1527 initViewItemOption(&option);
1528 const QPoint offset = d->scrollDelayOffset;
1529 const bool showGrid = d->showGrid;
1530 const int gridSize = showGrid ? 1 : 0;
1531 const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
1532 const QColor gridColor = QColor::fromRgba(static_cast<QRgb>(gridHint));
1533 const QPen gridPen = QPen(gridColor, 1, d->gridStyle);
1534 const QHeaderView *verticalHeader = d->verticalHeader;
1535 const QHeaderView *horizontalHeader = d->horizontalHeader;
1536 const bool alternate = d->alternatingColors;
1537 const bool rightToLeft = isRightToLeft();
1538
1539 QPainter painter(d->viewport);
1540
1541 // if there's nothing to do, clear the area and return
1542 if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)
1543 return;
1544
1545 const int x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
1546 const int y = verticalHeader->length() - verticalHeader->offset() - 1;
1547
1548 //firstVisualRow is the visual index of the first visible row. lastVisualRow is the visual index of the last visible Row.
1549 //same goes for ...VisualColumn
1550 int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0);
1551 int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->height());
1552 if (lastVisualRow == -1)
1553 lastVisualRow = d->model->rowCount(d->root) - 1;
1554
1555 int firstVisualColumn = horizontalHeader->visualIndexAt(0);
1556 int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->width());
1557 if (rightToLeft)
1558 qSwap(firstVisualColumn, lastVisualColumn);
1559 if (firstVisualColumn == -1)
1560 firstVisualColumn = 0;
1561 if (lastVisualColumn == -1)
1562 lastVisualColumn = horizontalHeader->count() - 1;
1563
1564 QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1));
1565
1566 const QRegion region = event->region().translated(offset);
1567
1568 if (d->hasSpans()) {
1569 d->drawAndClipSpans(region, &painter, option, &drawn,
1570 firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn);
1571 }
1572
1573 for (QRect dirtyArea : region) {
1574 dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));
1575 if (rightToLeft) {
1576 dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));
1577 } else {
1578 dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));
1579 }
1580 // dirtyArea may be invalid when the horizontal header is not stretched
1581 if (!dirtyArea.isValid())
1582 continue;
1583
1584 // get the horizontal start and end visual sections
1585 int left = horizontalHeader->visualIndexAt(dirtyArea.left());
1586 int right = horizontalHeader->visualIndexAt(dirtyArea.right());
1587 if (rightToLeft)
1588 qSwap(left, right);
1589 if (left == -1) left = 0;
1590 if (right == -1) right = horizontalHeader->count() - 1;
1591
1592 // get the vertical start and end visual sections and if alternate color
1593 int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
1594 if (bottom == -1) bottom = verticalHeader->count() - 1;
1595 int top = 0;
1596 bool alternateBase = false;
1597 if (alternate && verticalHeader->sectionsHidden()) {
1598 const int verticalOffset = verticalHeader->offset();
1599 int row = verticalHeader->logicalIndex(top);
1600 for (int y = 0;
1601 ((y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);
1602 ++top) {
1603 row = verticalHeader->logicalIndex(top);
1604 if (alternate && !verticalHeader->isSectionHidden(row))
1605 alternateBase = !alternateBase;
1606 }
1607 } else {
1608 top = verticalHeader->visualIndexAt(dirtyArea.top());
1609 alternateBase = (top & 1) && alternate;
1610 }
1611 if (top == -1 || top > bottom)
1612 continue;
1613
1614 // Paint each row item
1615 for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {
1616 int row = verticalHeader->logicalIndex(visualRowIndex);
1617 if (verticalHeader->isSectionHidden(row))
1618 continue;
1619 int rowY = rowViewportPosition(row);
1620 rowY += offset.y();
1621 int rowh = rowHeight(row) - gridSize;
1622
1623 // Paint each column item
1624 for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {
1625 int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
1626 + visualColumnIndex - firstVisualColumn;
1627
1628 if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))
1629 continue;
1630 drawn.setBit(currentBit);
1631
1632 int col = horizontalHeader->logicalIndex(visualColumnIndex);
1633 if (horizontalHeader->isSectionHidden(col))
1634 continue;
1635 int colp = columnViewportPosition(col);
1636 colp += offset.x();
1637 int colw = columnWidth(col) - gridSize;
1638
1639 const QModelIndex index = d->model->index(row, col, d->root);
1640 if (index.isValid()) {
1641 option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);
1642 if (alternate) {
1643 if (alternateBase)
1644 option.features |= QStyleOptionViewItem::Alternate;
1645 else
1646 option.features &= ~QStyleOptionViewItem::Alternate;
1647 }
1648 d->drawCell(&painter, option, index);
1649 }
1650 }
1651 alternateBase = !alternateBase && alternate;
1652 }
1653
1654 if (showGrid) {
1655 // Find the bottom right (the last rows/columns might be hidden)
1656 while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;
1657 QPen old = painter.pen();
1658 painter.setPen(gridPen);
1659 // Paint each row
1660 for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {
1661 int row = verticalHeader->logicalIndex(visualIndex);
1662 if (verticalHeader->isSectionHidden(row))
1663 continue;
1664 int rowY = rowViewportPosition(row);
1665 rowY += offset.y();
1666 int rowh = rowHeight(row) - gridSize;
1667 QLineF line(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);
1668 painter.drawLine(line.translated(0.5, 0.5));
1669 }
1670
1671 // Paint each column
1672 for (int h = left; h <= right; ++h) {
1673 int col = horizontalHeader->logicalIndex(h);
1674 if (horizontalHeader->isSectionHidden(col))
1675 continue;
1676 int colp = columnViewportPosition(col);
1677 colp += offset.x();
1678 if (!rightToLeft)
1679 colp += columnWidth(col) - gridSize;
1680 QLineF line(colp, dirtyArea.top(), colp, dirtyArea.bottom());
1681 painter.drawLine(line.translated(0.5, 0.5));
1682 }
1683 const bool drawWhenHidden = style()->styleHint(QStyle::SH_Table_AlwaysDrawLeftTopGridLines,
1684 &option, this);
1685 if (drawWhenHidden && horizontalHeader->isHidden()) {
1686 const int row = verticalHeader->logicalIndex(top);
1687 if (!verticalHeader->isSectionHidden(row)) {
1688 const int rowY = rowViewportPosition(row) + offset.y();
1689 if (rowY == dirtyArea.top())
1690 painter.drawLine(dirtyArea.left(), rowY, dirtyArea.right(), rowY);
1691 }
1692 }
1693 if (drawWhenHidden && verticalHeader->isHidden()) {
1694 const int col = horizontalHeader->logicalIndex(left);
1695 if (!horizontalHeader->isSectionHidden(col)) {
1696 int colX = columnViewportPosition(col) + offset.x();
1697 if (!isLeftToRight())
1698 colX += columnWidth(left) - 1;
1699 if (isLeftToRight() && colX == dirtyArea.left())
1700 painter.drawLine(colX, dirtyArea.top(), colX, dirtyArea.bottom());
1701 if (!isLeftToRight() && colX == dirtyArea.right())
1702 painter.drawLine(colX, dirtyArea.top(), colX, dirtyArea.bottom());
1703 }
1704 }
1705 painter.setPen(old);
1706 }
1707 }
1708
1709#if QT_CONFIG(draganddrop)
1710 // Paint the dropIndicator
1711 d->paintDropIndicator(&painter);
1712#endif
1713}
1714
1715/*!
1716 Returns the index position of the model item corresponding to the
1717 table item at position \a pos in contents coordinates.
1718*/
1719QModelIndex QTableView::indexAt(const QPoint &pos) const
1720{
1721 Q_D(const QTableView);
1722 d->executePostedLayout();
1723 int r = rowAt(pos.y());
1724 int c = columnAt(pos.x());
1725 if (r >= 0 && c >= 0) {
1726 if (d->hasSpans()) {
1727 QSpanCollection::Span span = d->span(r, c);
1728 r = span.top();
1729 c = span.left();
1730 }
1731 return d->model->index(r, c, d->root);
1732 }
1733 return QModelIndex();
1734}
1735
1736/*!
1737 Returns the horizontal offset of the items in the table view.
1738
1739 Note that the table view uses the horizontal header section
1740 positions to determine the positions of columns in the view.
1741
1742 \sa verticalOffset()
1743*/
1744int QTableView::horizontalOffset() const
1745{
1746 Q_D(const QTableView);
1747 return d->horizontalHeader->offset();
1748}
1749
1750/*!
1751 Returns the vertical offset of the items in the table view.
1752
1753 Note that the table view uses the vertical header section
1754 positions to determine the positions of rows in the view.
1755
1756 \sa horizontalOffset()
1757*/
1758int QTableView::verticalOffset() const
1759{
1760 Q_D(const QTableView);
1761 return d->verticalHeader->offset();
1762}
1763
1764/*!
1765 \fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1766
1767 Moves the cursor in accordance with the given \a cursorAction, using the
1768 information provided by the \a modifiers.
1769
1770 \sa QAbstractItemView::CursorAction
1771*/
1772QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1773{
1774 Q_D(QTableView);
1775 Q_UNUSED(modifiers);
1776
1777 int bottom = d->model->rowCount(d->root) - 1;
1778 // make sure that bottom is the bottommost *visible* row
1779 while (bottom >= 0 && isRowHidden(d->logicalRow(bottom)))
1780 --bottom;
1781
1782 int right = d->model->columnCount(d->root) - 1;
1783
1784 while (right >= 0 && isColumnHidden(d->logicalColumn(right)))
1785 --right;
1786
1787 if (bottom == -1 || right == -1)
1788 return QModelIndex(); // model is empty
1789
1790 QModelIndex current = currentIndex();
1791
1792 if (!current.isValid()) {
1793 int row = 0;
1794 int column = 0;
1795 while (column < right && isColumnHidden(d->logicalColumn(column)))
1796 ++column;
1797 while (isRowHidden(d->logicalRow(row)) && row < bottom)
1798 ++row;
1799 d->visualCursor = QPoint(column, row);
1800 return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root);
1801 }
1802
1803 // Update visual cursor if current index has changed.
1804 QPoint visualCurrent(d->visualColumn(current.column()), d->visualRow(current.row()));
1805 if (visualCurrent != d->visualCursor) {
1806 if (d->hasSpans()) {
1807 QSpanCollection::Span span = d->span(current.row(), current.column());
1808 if (span.top() > d->visualCursor.y() || d->visualCursor.y() > span.bottom()
1809 || span.left() > d->visualCursor.x() || d->visualCursor.x() > span.right())
1810 d->visualCursor = visualCurrent;
1811 } else {
1812 d->visualCursor = visualCurrent;
1813 }
1814 }
1815
1816 int visualRow = d->visualCursor.y();
1817 if (visualRow > bottom)
1818 visualRow = bottom;
1819 Q_ASSERT(visualRow != -1);
1820 int visualColumn = d->visualCursor.x();
1821 if (visualColumn > right)
1822 visualColumn = right;
1823 Q_ASSERT(visualColumn != -1);
1824
1825 if (isRightToLeft()) {
1826 if (cursorAction == MoveLeft)
1827 cursorAction = MoveRight;
1828 else if (cursorAction == MoveRight)
1829 cursorAction = MoveLeft;
1830 }
1831
1832 switch (cursorAction) {
1833 case MoveUp: {
1834 int originalRow = visualRow;
1835#ifdef QT_KEYPAD_NAVIGATION
1836 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow == 0)
1837 visualRow = d->visualRow(model()->rowCount() - 1) + 1;
1838 // FIXME? visualRow = bottom + 1;
1839#endif
1840 int r = d->logicalRow(visualRow);
1841 int c = d->logicalColumn(visualColumn);
1842 if (r != -1 && d->hasSpans()) {
1843 QSpanCollection::Span span = d->span(r, c);
1844 if (span.width() > 1 || span.height() > 1)
1845 visualRow = d->visualRow(span.top());
1846 }
1847 while (visualRow >= 0) {
1848 --visualRow;
1849 r = d->logicalRow(visualRow);
1850 c = d->logicalColumn(visualColumn);
1851 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1852 break;
1853 }
1854 if (visualRow < 0)
1855 visualRow = originalRow;
1856 break;
1857 }
1858 case MoveDown: {
1859 int originalRow = visualRow;
1860 if (d->hasSpans()) {
1861 QSpanCollection::Span span = d->span(current.row(), current.column());
1862 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1863 }
1864#ifdef QT_KEYPAD_NAVIGATION
1865 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow >= bottom)
1866 visualRow = -1;
1867#endif
1868 int r = d->logicalRow(visualRow);
1869 int c = d->logicalColumn(visualColumn);
1870 if (r != -1 && d->hasSpans()) {
1871 QSpanCollection::Span span = d->span(r, c);
1872 if (span.width() > 1 || span.height() > 1)
1873 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1874 }
1875 while (visualRow <= bottom) {
1876 ++visualRow;
1877 r = d->logicalRow(visualRow);
1878 c = d->logicalColumn(visualColumn);
1879 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1880 break;
1881 }
1882 if (visualRow > bottom)
1883 visualRow = originalRow;
1884 break;
1885 }
1886 case MovePrevious:
1887 case MoveLeft: {
1888 int originalRow = visualRow;
1889 int originalColumn = visualColumn;
1890 bool firstTime = true;
1891 bool looped = false;
1892 bool wrapped = false;
1893 do {
1894 int r = d->logicalRow(visualRow);
1895 int c = d->logicalColumn(visualColumn);
1896 if (firstTime && c != -1 && d->hasSpans()) {
1897 firstTime = false;
1898 QSpanCollection::Span span = d->span(r, c);
1899 if (span.width() > 1 || span.height() > 1)
1900 visualColumn = d->visualColumn(span.left());
1901 }
1902 while (visualColumn >= 0) {
1903 --visualColumn;
1904 r = d->logicalRow(visualRow);
1905 c = d->logicalColumn(visualColumn);
1906 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1907 break;
1908 if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) {
1909 looped = true;
1910 break;
1911 }
1912 }
1913 if (cursorAction == MoveLeft || visualColumn >= 0)
1914 break;
1915 visualColumn = right + 1;
1916 if (visualRow == 0) {
1917 wrapped = true;
1918 visualRow = bottom;
1919 } else {
1920 --visualRow;
1921 }
1922 } while (!looped);
1923 if (visualColumn < 0)
1924 visualColumn = originalColumn;
1925 break;
1926 }
1927 case MoveNext:
1928 case MoveRight: {
1929 int originalRow = visualRow;
1930 int originalColumn = visualColumn;
1931 bool firstTime = true;
1932 bool looped = false;
1933 bool wrapped = false;
1934 do {
1935 int r = d->logicalRow(visualRow);
1936 int c = d->logicalColumn(visualColumn);
1937 if (firstTime && c != -1 && d->hasSpans()) {
1938 firstTime = false;
1939 QSpanCollection::Span span = d->span(r, c);
1940 if (span.width() > 1 || span.height() > 1)
1941 visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
1942 }
1943 while (visualColumn <= right) {
1944 ++visualColumn;
1945 r = d->logicalRow(visualRow);
1946 c = d->logicalColumn(visualColumn);
1947 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1948 break;
1949 if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) {
1950 looped = true;
1951 break;
1952 }
1953 }
1954 if (cursorAction == MoveRight || visualColumn <= right)
1955 break;
1956 visualColumn = -1;
1957 if (visualRow == bottom) {
1958 wrapped = true;
1959 visualRow = 0;
1960 } else {
1961 ++visualRow;
1962 }
1963 } while (!looped);
1964 if (visualColumn > right)
1965 visualColumn = originalColumn;
1966 break;
1967 }
1968 case MoveHome:
1969 visualColumn = d->nextActiveVisualColumn(visualRow, 0, right,
1970 QTableViewPrivate::SearchDirection::Increasing);
1971 if (modifiers & Qt::ControlModifier)
1972 visualRow = d->nextActiveVisualRow(0, visualColumn, bottom,
1973 QTableViewPrivate::SearchDirection::Increasing);
1974 break;
1975 case MoveEnd:
1976 visualColumn = d->nextActiveVisualColumn(visualRow, right, -1,
1977 QTableViewPrivate::SearchDirection::Decreasing);
1978 if (modifiers & Qt::ControlModifier)
1979 visualRow = d->nextActiveVisualRow(bottom, visualColumn, -1,
1980 QTableViewPrivate::SearchDirection::Decreasing);
1981 break;
1982 case MovePageUp: {
1983 int newLogicalRow = rowAt(visualRect(current).bottom() - d->viewport->height());
1984 int visualRow = (newLogicalRow == -1 ? 0 : d->visualRow(newLogicalRow));
1985 visualRow = d->nextActiveVisualRow(visualRow, current.column(), bottom,
1986 QTableViewPrivate::SearchDirection::Increasing);
1987 newLogicalRow = d->logicalRow(visualRow);
1988 return d->model->index(newLogicalRow, current.column(), d->root);
1989 }
1990 case MovePageDown: {
1991 int newLogicalRow = rowAt(visualRect(current).top() + d->viewport->height());
1992 int visualRow = (newLogicalRow == -1 ? bottom : d->visualRow(newLogicalRow));
1993 visualRow = d->nextActiveVisualRow(visualRow, current.column(), -1,
1994 QTableViewPrivate::SearchDirection::Decreasing);
1995 newLogicalRow = d->logicalRow(visualRow);
1996 return d->model->index(newLogicalRow, current.column(), d->root);
1997 }}
1998
1999 d->visualCursor = QPoint(visualColumn, visualRow);
2000 int logicalRow = d->logicalRow(visualRow);
2001 int logicalColumn = d->logicalColumn(visualColumn);
2002 if (!d->model->hasIndex(logicalRow, logicalColumn, d->root))
2003 return QModelIndex();
2004
2005 QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root);
2006 if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result)) {
2007 if (d->hasSpans()) {
2008 QSpanCollection::Span span = d->span(result.row(), result.column());
2009 if (span.width() > 1 || span.height() > 1) {
2010 result = d->model->sibling(span.top(), span.left(), result);
2011 }
2012 }
2013 return result;
2014 }
2015
2016 return QModelIndex();
2017}
2018
2019/*!
2020 \fn void QTableView::setSelection(const QRect &rect,
2021 QItemSelectionModel::SelectionFlags flags)
2022
2023 Selects the items within the given \a rect and in accordance with
2024 the specified selection \a flags.
2025*/
2026void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2027{
2028 Q_D(QTableView);
2029 QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right())
2030 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())));
2031 QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2032 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())));
2033 if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
2034 return;
2035
2036 const bool verticalMoved = verticalHeader()->sectionsMoved();
2037 const bool horizontalMoved = horizontalHeader()->sectionsMoved();
2038
2039 QItemSelection selection;
2040 int top = tl.row();
2041 int bottom = br.row();
2042 int left = tl.column();
2043 int right = br.column();
2044
2045 if (d->hasSpans()) {
2046 bool expanded;
2047 // when the current selection does not intersect with any spans of merged cells,
2048 // the range of selected cells must be the same as if there were no merged cells
2049 bool intersectsSpan = false;
2050 top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
2051 left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2052 bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
2053 right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2054 do {
2055 expanded = false;
2056 for (QSpanCollection::Span *it : d->spans.spans) {
2057 const QSpanCollection::Span &span = *it;
2058 const int t = d->visualRow(span.top());
2059 const int l = d->visualColumn(span.left());
2060 const int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
2061 const int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
2062 if ((t > bottom) || (l > right) || (top > b) || (left > r))
2063 continue; // no intersect
2064 intersectsSpan = true;
2065 if (t < top) {
2066 top = t;
2067 expanded = true;
2068 }
2069 if (l < left) {
2070 left = l;
2071 expanded = true;
2072 }
2073 if (b > bottom) {
2074 bottom = b;
2075 expanded = true;
2076 }
2077 if (r > right) {
2078 right = r;
2079 expanded = true;
2080 }
2081 if (expanded)
2082 break;
2083 }
2084 } while (expanded);
2085 if (!intersectsSpan) {
2086 top = tl.row();
2087 bottom = br.row();
2088 left = tl.column();
2089 right = br.column();
2090 } else if (!verticalMoved && !horizontalMoved) {
2091 // top/left/bottom/right are visual, update indexes
2092 tl = d->model->index(top, left, d->root);
2093 br = d->model->index(bottom, right, d->root);
2094 }
2095 } else if (verticalMoved && horizontalMoved) {
2096 top = d->visualRow(tl.row());
2097 bottom = d->visualRow(br.row());
2098 left = d->visualColumn(tl.column());
2099 right = d->visualColumn(br.column());
2100 } else if (horizontalMoved) {
2101 top = tl.row();
2102 bottom = br.row();
2103 left = d->visualColumn(tl.column());
2104 right = d->visualColumn(br.column());
2105 } else if (verticalMoved) {
2106 top = d->visualRow(tl.row());
2107 bottom = d->visualRow(br.row());
2108 left = tl.column();
2109 right = br.column();
2110 }
2111
2112 if (horizontalMoved && verticalMoved) {
2113 selection.reserve((right - left + 1) * (bottom - top + 1));
2114 for (int horizontal = left; horizontal <= right; ++horizontal) {
2115 int column = d->logicalColumn(horizontal);
2116 for (int vertical = top; vertical <= bottom; ++vertical) {
2117 int row = d->logicalRow(vertical);
2118 QModelIndex index = d->model->index(row, column, d->root);
2119 selection.append(QItemSelectionRange(index));
2120 }
2121 }
2122 } else if (horizontalMoved) {
2123 selection.reserve(right - left + 1);
2124 for (int visual = left; visual <= right; ++visual) {
2125 int column = d->logicalColumn(visual);
2126 QModelIndex topLeft = d->model->index(top, column, d->root);
2127 QModelIndex bottomRight = d->model->index(bottom, column, d->root);
2128 selection.append(QItemSelectionRange(topLeft, bottomRight));
2129 }
2130 } else if (verticalMoved) {
2131 selection.reserve(bottom - top + 1);
2132 for (int visual = top; visual <= bottom; ++visual) {
2133 int row = d->logicalRow(visual);
2134 QModelIndex topLeft = d->model->index(row, left, d->root);
2135 QModelIndex bottomRight = d->model->index(row, right, d->root);
2136 selection.append(QItemSelectionRange(topLeft, bottomRight));
2137 }
2138 } else { // nothing moved
2139 QItemSelectionRange range(tl, br);
2140 if (!range.isEmpty())
2141 selection.append(range);
2142 }
2143
2144 d->selectionModel->select(selection, command);
2145}
2146
2147/*!
2148 \reimp
2149
2150 Returns the rectangle from the viewport of the items in the given
2151 \a selection.
2152
2153 Since 4.7, the returned region only contains rectangles intersecting
2154 (or included in) the viewport.
2155*/
2156QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const
2157{
2158 Q_D(const QTableView);
2159
2160 if (selection.isEmpty())
2161 return QRegion();
2162
2163 QRegion selectionRegion;
2164 const QRect &viewportRect = d->viewport->rect();
2165 bool verticalMoved = verticalHeader()->sectionsMoved();
2166 bool horizontalMoved = horizontalHeader()->sectionsMoved();
2167
2168 if ((verticalMoved && horizontalMoved) || (d->hasSpans() && (verticalMoved || horizontalMoved))) {
2169 for (const auto &range : selection) {
2170 if (range.parent() != d->root || !range.isValid())
2171 continue;
2172 for (int r = range.top(); r <= range.bottom(); ++r)
2173 for (int c = range.left(); c <= range.right(); ++c) {
2174 const QRect &rangeRect = visualRect(d->model->index(r, c, d->root));
2175 if (viewportRect.intersects(rangeRect))
2176 selectionRegion += rangeRect;
2177 }
2178 }
2179 } else if (horizontalMoved) {
2180 for (const auto &range : selection) {
2181 if (range.parent() != d->root || !range.isValid())
2182 continue;
2183 int top = rowViewportPosition(range.top());
2184 int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2185 if (top > bottom)
2186 qSwap<int>(top, bottom);
2187 int height = bottom - top;
2188 for (int c = range.left(); c <= range.right(); ++c) {
2189 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2190 if (viewportRect.intersects(rangeRect))
2191 selectionRegion += rangeRect;
2192 }
2193 }
2194 } else if (verticalMoved) {
2195 for (const auto &range : selection) {
2196 if (range.parent() != d->root || !range.isValid())
2197 continue;
2198 int left = columnViewportPosition(range.left());
2199 int right = columnViewportPosition(range.right()) + columnWidth(range.right());
2200 if (left > right)
2201 qSwap<int>(left, right);
2202 int width = right - left;
2203 for (int r = range.top(); r <= range.bottom(); ++r) {
2204 const QRect rangeRect(left, rowViewportPosition(r), width, rowHeight(r));
2205 if (viewportRect.intersects(rangeRect))
2206 selectionRegion += rangeRect;
2207 }
2208 }
2209 } else { // nothing moved
2210 const int gridAdjust = showGrid() ? 1 : 0;
2211 for (auto range : selection) {
2212 if (range.parent() != d->root || !range.isValid())
2213 continue;
2214 d->trimHiddenSelections(&range);
2215
2216 const int rtop = rowViewportPosition(range.top());
2217 const int rbottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2218 int rleft;
2219 int rright;
2220 if (isLeftToRight()) {
2221 rleft = columnViewportPosition(range.left());
2222 rright = columnViewportPosition(range.right()) + columnWidth(range.right());
2223 } else {
2224 rleft = columnViewportPosition(range.right());
2225 rright = columnViewportPosition(range.left()) + columnWidth(range.left());
2226 }
2227 const QRect rangeRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust));
2228 if (viewportRect.intersects(rangeRect))
2229 selectionRegion += rangeRect;
2230 if (d->hasSpans()) {
2231 const auto spansInRect = d->spans.spansInRect(range.left(), range.top(), range.width(), range.height());
2232 for (QSpanCollection::Span *s : spansInRect) {
2233 if (range.contains(s->top(), s->left(), range.parent())) {
2234 const QRect &visualSpanRect = d->visualSpanRect(*s);
2235 if (viewportRect.intersects(visualSpanRect))
2236 selectionRegion += visualSpanRect;
2237 }
2238 }
2239 }
2240 }
2241 }
2242
2243 return selectionRegion;
2244}
2245
2246
2247/*!
2248 \reimp
2249*/
2250QModelIndexList QTableView::selectedIndexes() const
2251{
2252 Q_D(const QTableView);
2253 QModelIndexList viewSelected;
2254 QModelIndexList modelSelected;
2255 if (d->selectionModel)
2256 modelSelected = d->selectionModel->selectedIndexes();
2257 for (int i = 0; i < modelSelected.size(); ++i) {
2258 QModelIndex index = modelSelected.at(i);
2259 if (!isIndexHidden(index) && index.parent() == d->root)
2260 viewSelected.append(index);
2261 }
2262 return viewSelected;
2263}
2264
2265
2266/*!
2267 This slot is called whenever rows are added or deleted. The
2268 previous number of rows is specified by \a oldCount, and the new
2269 number of rows is specified by \a newCount.
2270*/
2271void QTableView::rowCountChanged(int oldCount, int newCount )
2272{
2273 Q_D(QTableView);
2274 //when removing rows, we need to disable updates for the header until the geometries have been
2275 //updated and the offset has been adjusted, or we risk calling paintSection for all the sections
2276 if (newCount < oldCount)
2277 d->verticalHeader->setUpdatesEnabled(false);
2278 d->doDelayedItemsLayout();
2279}
2280
2281/*!
2282 This slot is called whenever columns are added or deleted. The
2283 previous number of columns is specified by \a oldCount, and the new
2284 number of columns is specified by \a newCount.
2285*/
2286void QTableView::columnCountChanged(int, int)
2287{
2288 Q_D(QTableView);
2289 updateGeometries();
2290 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
2291 d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value());
2292 else
2293 d->horizontalHeader->setOffset(horizontalScrollBar()->value());
2294 d->viewport->update();
2295}
2296
2297/*!
2298 \reimp
2299*/
2300void QTableView::updateGeometries()
2301{
2302 Q_D(QTableView);
2303 if (d->geometryRecursionBlock)
2304 return;
2305 d->geometryRecursionBlock = true;
2306
2307 int width = 0;
2308 if (!d->verticalHeader->isHidden()) {
2309 width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width());
2310 width = qMin(width, d->verticalHeader->maximumWidth());
2311 }
2312 int height = 0;
2313 if (!d->horizontalHeader->isHidden()) {
2314 height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height());
2315 height = qMin(height, d->horizontalHeader->maximumHeight());
2316 }
2317 bool reverse = isRightToLeft();
2318 if (reverse)
2319 setViewportMargins(0, height, width, 0);
2320 else
2321 setViewportMargins(width, height, 0, 0);
2322
2323 // update headers
2324
2325 QRect vg = d->viewport->geometry();
2326
2327 int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width);
2328 d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height());
2329 if (d->verticalHeader->isHidden())
2330 QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries");
2331
2332 int horizontalTop = vg.top() - height;
2333 d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height);
2334 if (d->horizontalHeader->isHidden())
2335 QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries");
2336
2337#if QT_CONFIG(abstractbutton)
2338 // update cornerWidget
2339 if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) {
2340 d->cornerWidget->setHidden(true);
2341 } else {
2342 d->cornerWidget->setHidden(false);
2343 d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height);
2344 }
2345#endif
2346
2347 // update scroll bars
2348
2349 // ### move this block into the if
2350 QSize vsize = d->viewport->size();
2351 QSize max = maximumViewportSize();
2352 const int horizontalLength = d->horizontalHeader->length();
2353 const int verticalLength = d->verticalHeader->length();
2354 if (max.width() >= horizontalLength && max.height() >= verticalLength)
2355 vsize = max;
2356
2357 // horizontal scroll bar
2358 const int columnCount = d->horizontalHeader->count();
2359 const int viewportWidth = vsize.width();
2360 int columnsInViewport = 0;
2361 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
2362 int logical = d->horizontalHeader->logicalIndex(column);
2363 if (!d->horizontalHeader->isSectionHidden(logical)) {
2364 width += d->horizontalHeader->sectionSize(logical);
2365 if (width > viewportWidth)
2366 break;
2367 ++columnsInViewport;
2368 }
2369 }
2370 columnsInViewport = qMax(columnsInViewport, 1); //there must be always at least 1 column
2371
2372 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2373 const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount();
2374 horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport);
2375 horizontalScrollBar()->setPageStep(columnsInViewport);
2376 if (columnsInViewport >= visibleColumns)
2377 d->horizontalHeader->setOffset(0);
2378 horizontalScrollBar()->setSingleStep(1);
2379 } else { // ScrollPerPixel
2380 horizontalScrollBar()->setPageStep(vsize.width());
2381 horizontalScrollBar()->setRange(0, horizontalLength - vsize.width());
2382 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2));
2383 }
2384
2385 // vertical scroll bar
2386 const int rowCount = d->verticalHeader->count();
2387 const int viewportHeight = vsize.height();
2388 int rowsInViewport = 0;
2389 for (int height = 0, row = rowCount - 1; row >= 0; --row) {
2390 int logical = d->verticalHeader->logicalIndex(row);
2391 if (!d->verticalHeader->isSectionHidden(logical)) {
2392 height += d->verticalHeader->sectionSize(logical);
2393 if (height > viewportHeight)
2394 break;
2395 ++rowsInViewport;
2396 }
2397 }
2398 rowsInViewport = qMax(rowsInViewport, 1); //there must be always at least 1 row
2399
2400 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2401 const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount();
2402 verticalScrollBar()->setRange(0, visibleRows - rowsInViewport);
2403 verticalScrollBar()->setPageStep(rowsInViewport);
2404 if (rowsInViewport >= visibleRows)
2405 d->verticalHeader->setOffset(0);
2406 verticalScrollBar()->setSingleStep(1);
2407 } else { // ScrollPerPixel
2408 verticalScrollBar()->setPageStep(vsize.height());
2409 verticalScrollBar()->setRange(0, verticalLength - vsize.height());
2410 verticalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2));
2411 }
2412 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
2413
2414 d->geometryRecursionBlock = false;
2415 QAbstractItemView::updateGeometries();
2416}
2417
2418/*!
2419 Returns the size hint for the given \a row's height or -1 if there
2420 is no model.
2421
2422 If you need to set the height of a given row to a fixed value, call
2423 QHeaderView::resizeSection() on the table's vertical header.
2424
2425 If you reimplement this function in a subclass, note that the value you
2426 return is only used when resizeRowToContents() is called. In that case,
2427 if a larger row height is required by either the vertical header or
2428 the item delegate, that width will be used instead.
2429
2430 \sa QWidget::sizeHint, verticalHeader(), QHeaderView::resizeContentsPrecision()
2431*/
2432int QTableView::sizeHintForRow(int row) const
2433{
2434 Q_D(const QTableView);
2435
2436 if (!model())
2437 return -1;
2438
2439 ensurePolished();
2440 const int maximumProcessCols = d->verticalHeader->resizeContentsPrecision();
2441
2442
2443 int left = qMax(0, d->horizontalHeader->visualIndexAt(0));
2444 int right = d->horizontalHeader->visualIndexAt(d->viewport->width());
2445 if (right == -1) // the table don't have enough columns to fill the viewport
2446 right = d->model->columnCount(d->root) - 1;
2447
2448 QStyleOptionViewItem option;
2449 initViewItemOption(&option);
2450
2451 int hint = 0;
2452 QModelIndex index;
2453 int columnsProcessed = 0;
2454 int column = left;
2455 for (; column <= right; ++column) {
2456 int logicalColumn = d->horizontalHeader->logicalIndex(column);
2457 if (d->horizontalHeader->isSectionHidden(logicalColumn))
2458 continue;
2459 index = d->model->index(row, logicalColumn, d->root);
2460 hint = d->heightHintForIndex(index, hint, option);
2461
2462 ++columnsProcessed;
2463 if (columnsProcessed == maximumProcessCols)
2464 break;
2465 }
2466
2467 const int actualRight = d->model->columnCount(d->root) - 1;
2468 int idxLeft = left;
2469 int idxRight = column - 1;
2470
2471 if (maximumProcessCols == 0 || actualRight < idxLeft)
2472 columnsProcessed = maximumProcessCols; // skip the while loop
2473
2474 while (columnsProcessed != maximumProcessCols && (idxLeft > 0 || idxRight < actualRight)) {
2475 int logicalIdx = -1;
2476
2477 if ((columnsProcessed % 2 && idxLeft > 0) || idxRight == actualRight) {
2478 while (idxLeft > 0) {
2479 --idxLeft;
2480 int logcol = d->horizontalHeader->logicalIndex(idxLeft);
2481 if (d->horizontalHeader->isSectionHidden(logcol))
2482 continue;
2483 logicalIdx = logcol;
2484 break;
2485 }
2486 } else {
2487 while (idxRight < actualRight) {
2488 ++idxRight;
2489 int logcol = d->horizontalHeader->logicalIndex(idxRight);
2490 if (d->horizontalHeader->isSectionHidden(logcol))
2491 continue;
2492 logicalIdx = logcol;
2493 break;
2494 }
2495 }
2496 if (logicalIdx >= 0) {
2497 index = d->model->index(row, logicalIdx, d->root);
2498 hint = d->heightHintForIndex(index, hint, option);
2499 }
2500 ++columnsProcessed;
2501 }
2502
2503 return d->showGrid ? hint + 1 : hint;
2504}
2505
2506/*!
2507 Returns the size hint for the given \a column's width or -1 if
2508 there is no model.
2509
2510 If you need to set the width of a given column to a fixed value, call
2511 QHeaderView::resizeSection() on the table's horizontal header.
2512
2513 If you reimplement this function in a subclass, note that the value you
2514 return will be used when resizeColumnToContents() or
2515 QHeaderView::resizeSections() is called. If a larger column width is
2516 required by either the horizontal header or the item delegate, the larger
2517 width will be used instead.
2518
2519 \sa QWidget::sizeHint, horizontalHeader(), QHeaderView::resizeContentsPrecision()
2520*/
2521int QTableView::sizeHintForColumn(int column) const
2522{
2523 Q_D(const QTableView);
2524
2525 if (!model())
2526 return -1;
2527
2528 ensurePolished();
2529 const int maximumProcessRows = d->horizontalHeader->resizeContentsPrecision();
2530
2531 int top = qMax(0, d->verticalHeader->visualIndexAt(0));
2532 int bottom = d->verticalHeader->visualIndexAt(d->viewport->height());
2533 if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport
2534 bottom = d->model->rowCount(d->root) - 1;
2535
2536 QStyleOptionViewItem option;
2537 initViewItemOption(&option);
2538
2539 int hint = 0;
2540 int rowsProcessed = 0;
2541 QModelIndex index;
2542 int row = top;
2543 for (; row <= bottom; ++row) {
2544 int logicalRow = d->verticalHeader->logicalIndex(row);
2545 if (d->verticalHeader->isSectionHidden(logicalRow))
2546 continue;
2547 index = d->model->index(logicalRow, column, d->root);
2548
2549 hint = d->widthHintForIndex(index, hint, option);
2550 ++rowsProcessed;
2551 if (rowsProcessed == maximumProcessRows)
2552 break;
2553 }
2554
2555 const int actualBottom = d->model->rowCount(d->root) - 1;
2556 int idxTop = top;
2557 int idxBottom = row - 1;
2558
2559 if (maximumProcessRows == 0 || actualBottom < idxTop)
2560 rowsProcessed = maximumProcessRows; // skip the while loop
2561
2562 while (rowsProcessed != maximumProcessRows && (idxTop > 0 || idxBottom < actualBottom)) {
2563 int logicalIdx = -1;
2564
2565 if ((rowsProcessed % 2 && idxTop > 0) || idxBottom == actualBottom) {
2566 while (idxTop > 0) {
2567 --idxTop;
2568 int logrow = d->verticalHeader->logicalIndex(idxTop);
2569 if (d->verticalHeader->isSectionHidden(logrow))
2570 continue;
2571 logicalIdx = logrow;
2572 break;
2573 }
2574 } else {
2575 while (idxBottom < actualBottom) {
2576 ++idxBottom;
2577 int logrow = d->verticalHeader->logicalIndex(idxBottom);
2578 if (d->verticalHeader->isSectionHidden(logrow))
2579 continue;
2580 logicalIdx = logrow;
2581 break;
2582 }
2583 }
2584 if (logicalIdx >= 0) {
2585 index = d->model->index(logicalIdx, column, d->root);
2586 hint = d->widthHintForIndex(index, hint, option);
2587 }
2588 ++rowsProcessed;
2589 }
2590
2591 return d->showGrid ? hint + 1 : hint;
2592}
2593
2594/*!
2595 Returns the y-coordinate in contents coordinates of the given \a
2596 row.
2597*/
2598int QTableView::rowViewportPosition(int row) const
2599{
2600 Q_D(const QTableView);
2601 return d->verticalHeader->sectionViewportPosition(row);
2602}
2603
2604/*!
2605 Returns the row in which the given y-coordinate, \a y, in contents
2606 coordinates is located.
2607
2608 \note This function returns -1 if the given coordinate is not valid
2609 (has no row).
2610
2611 \sa columnAt()
2612*/
2613int QTableView::rowAt(int y) const
2614{
2615 Q_D(const QTableView);
2616 return d->verticalHeader->logicalIndexAt(y);
2617}
2618
2619/*!
2620 Sets the height of the given \a row to be \a height.
2621*/
2622void QTableView::setRowHeight(int row, int height)
2623{
2624 Q_D(const QTableView);
2625 d->verticalHeader->resizeSection(row, height);
2626}
2627
2628/*!
2629 Returns the height of the given \a row.
2630
2631 \sa resizeRowToContents(), columnWidth()
2632*/
2633int QTableView::rowHeight(int row) const
2634{
2635 Q_D(const QTableView);
2636 return d->verticalHeader->sectionSize(row);
2637}
2638
2639/*!
2640 Returns the x-coordinate in contents coordinates of the given \a
2641 column.
2642*/
2643int QTableView::columnViewportPosition(int column) const
2644{
2645 Q_D(const QTableView);
2646 return d->horizontalHeader->sectionViewportPosition(column);
2647}
2648
2649/*!
2650 Returns the column in which the given x-coordinate, \a x, in contents
2651 coordinates is located.
2652
2653 \note This function returns -1 if the given coordinate is not valid
2654 (has no column).
2655
2656 \sa rowAt()
2657*/
2658int QTableView::columnAt(int x) const
2659{
2660 Q_D(const QTableView);
2661 return d->horizontalHeader->logicalIndexAt(x);
2662}
2663
2664/*!
2665 Sets the width of the given \a column to be \a width.
2666*/
2667void QTableView::setColumnWidth(int column, int width)
2668{
2669 Q_D(const QTableView);
2670 d->horizontalHeader->resizeSection(column, width);
2671}
2672
2673/*!
2674 Returns the width of the given \a column.
2675
2676 \sa resizeColumnToContents(), rowHeight()
2677*/
2678int QTableView::columnWidth(int column) const
2679{
2680 Q_D(const QTableView);
2681 return d->horizontalHeader->sectionSize(column);
2682}
2683
2684/*!
2685 Returns \c true if the given \a row is hidden; otherwise returns \c false.
2686
2687 \sa isColumnHidden()
2688*/
2689bool QTableView::isRowHidden(int row) const
2690{
2691 Q_D(const QTableView);
2692 return d->verticalHeader->isSectionHidden(row);
2693}
2694
2695/*!
2696 If \a hide is true \a row will be hidden, otherwise it will be shown.
2697
2698 \sa setColumnHidden()
2699*/
2700void QTableView::setRowHidden(int row, bool hide)
2701{
2702 Q_D(QTableView);
2703 if (row < 0 || row >= d->verticalHeader->count())
2704 return;
2705 d->verticalHeader->setSectionHidden(row, hide);
2706}
2707
2708/*!
2709 Returns \c true if the given \a column is hidden; otherwise returns \c false.
2710
2711 \sa isRowHidden()
2712*/
2713bool QTableView::isColumnHidden(int column) const
2714{
2715 Q_D(const QTableView);
2716 return d->horizontalHeader->isSectionHidden(column);
2717}
2718
2719/*!
2720 If \a hide is true the given \a column will be hidden; otherwise it
2721 will be shown.
2722
2723 \sa setRowHidden()
2724*/
2725void QTableView::setColumnHidden(int column, bool hide)
2726{
2727 Q_D(QTableView);
2728 if (column < 0 || column >= d->horizontalHeader->count())
2729 return;
2730 d->horizontalHeader->setSectionHidden(column, hide);
2731}
2732
2733/*!
2734 \property QTableView::sortingEnabled
2735 \brief whether sorting is enabled
2736
2737 If this property is \c true, sorting is enabled for the table. If
2738 this property is \c false, sorting is not enabled. The default value
2739 is false.
2740
2741 \note. Setting the property to true with setSortingEnabled()
2742 immediately triggers a call to sortByColumn() with the current
2743 sort section and order.
2744
2745 \sa sortByColumn()
2746*/
2747
2748/*!
2749 If \a enable is true, enables sorting for the table and immediately
2750 trigger a call to sortByColumn() with the current sort section and
2751 order
2752 */
2753void QTableView::setSortingEnabled(bool enable)
2754{
2755 Q_D(QTableView);
2756 horizontalHeader()->setSortIndicatorShown(enable);
2757 for (const QMetaObject::Connection &connection : d->dynHorHeaderConnections)
2758 disconnect(connection);
2759 d->dynHorHeaderConnections.clear();
2760 if (enable) {
2761 //sortByColumn has to be called before we connect or set the sortingEnabled flag
2762 // because otherwise it will not call sort on the model.
2763 sortByColumn(d->horizontalHeader->sortIndicatorSection(),
2764 d->horizontalHeader->sortIndicatorOrder());
2765 d->dynHorHeaderConnections = {
2766 QObjectPrivate::connect(d->horizontalHeader, &QHeaderView::sortIndicatorChanged,
2767 d, &QTableViewPrivate::sortIndicatorChanged)
2768 };
2769 } else {
2770 d->dynHorHeaderConnections = {
2771 connect(d->horizontalHeader, &QHeaderView::sectionPressed,
2772 this, &QTableView::selectColumn),
2773 connect(d->horizontalHeader, &QHeaderView::sectionEntered,
2774 this, [d](int column) {d->selectColumn(column, false); })
2775 };
2776 }
2777 d->sortingEnabled = enable;
2778}
2779
2780bool QTableView::isSortingEnabled() const
2781{
2782 Q_D(const QTableView);
2783 return d->sortingEnabled;
2784}
2785
2786/*!
2787 \property QTableView::showGrid
2788 \brief whether the grid is shown
2789
2790 If this property is \c true a grid is drawn for the table; if the
2791 property is \c false, no grid is drawn. The default value is true.
2792*/
2793bool QTableView::showGrid() const
2794{
2795 Q_D(const QTableView);
2796 return d->showGrid;
2797}
2798
2799void QTableView::setShowGrid(bool show)
2800{
2801 Q_D(QTableView);
2802 if (d->showGrid != show) {
2803 d->showGrid = show;
2804 d->viewport->update();
2805 }
2806}
2807
2808/*!
2809 \property QTableView::gridStyle
2810 \brief the pen style used to draw the grid.
2811
2812 This property holds the style used when drawing the grid (see \l{showGrid}).
2813*/
2814Qt::PenStyle QTableView::gridStyle() const
2815{
2816 Q_D(const QTableView);
2817 return d->gridStyle;
2818}
2819
2820void QTableView::setGridStyle(Qt::PenStyle style)
2821{
2822 Q_D(QTableView);
2823 if (d->gridStyle != style) {
2824 d->gridStyle = style;
2825 d->viewport->update();
2826 }
2827}
2828
2829/*!
2830 \property QTableView::wordWrap
2831 \brief the item text word-wrapping policy
2832
2833 If this property is \c true then the item text is wrapped where
2834 necessary at word-breaks; otherwise it is not wrapped at all.
2835 This property is \c true by default.
2836
2837 Note that even if wrapping is enabled, the cell will not be
2838 expanded to fit all text. Ellipsis will be inserted according to
2839 the current \l{QAbstractItemView::}{textElideMode}.
2840
2841*/
2842void QTableView::setWordWrap(bool on)
2843{
2844 Q_D(QTableView);
2845 if (d->wrapItemText == on)
2846 return;
2847 d->wrapItemText = on;
2848 QMetaObject::invokeMethod(d->verticalHeader, "resizeSections");
2849 QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections");
2850}
2851
2852bool QTableView::wordWrap() const
2853{
2854 Q_D(const QTableView);
2855 return d->wrapItemText;
2856}
2857
2858#if QT_CONFIG(abstractbutton)
2859/*!
2860 \property QTableView::cornerButtonEnabled
2861 \brief whether the button in the top-left corner is enabled
2862
2863 If this property is \c true then button in the top-left corner
2864 of the table view is enabled. Clicking on this button will
2865 select all the cells in the table view.
2866
2867 This property is \c true by default.
2868*/
2869void QTableView::setCornerButtonEnabled(bool enable)
2870{
2871 Q_D(QTableView);
2872 d->cornerWidget->setEnabled(enable);
2873}
2874
2875bool QTableView::isCornerButtonEnabled() const
2876{
2877 Q_D(const QTableView);
2878 return d->cornerWidget->isEnabled();
2879}
2880#endif
2881
2882/*!
2883 \reimp
2884
2885 Returns the rectangle on the viewport occupied by the given \a
2886 index.
2887 If the index is hidden in the view it will return a null QRect.
2888*/
2889QRect QTableView::visualRect(const QModelIndex &index) const
2890{
2891 Q_D(const QTableView);
2892 if (!d->isIndexValid(index) || index.parent() != d->root
2893 || (!d->hasSpans() && isIndexHidden(index)))
2894 return QRect();
2895
2896 d->executePostedLayout();
2897
2898 if (d->hasSpans()) {
2899 QSpanCollection::Span span = d->span(index.row(), index.column());
2900 return d->visualSpanRect(span);
2901 }
2902
2903 int rowp = rowViewportPosition(index.row());
2904 int rowh = rowHeight(index.row());
2905 int colp = columnViewportPosition(index.column());
2906 int colw = columnWidth(index.column());
2907
2908 const int i = showGrid() ? 1 : 0;
2909 return QRect(colp, rowp, colw - i, rowh - i);
2910}
2911
2912/*!
2913 \reimp
2914
2915 Makes sure that the given \a index is visible in the table view,
2916 scrolling if necessary.
2917*/
2918void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
2919{
2920 Q_D(QTableView);
2921
2922 // check if we really need to do anything
2923 if (!d->isIndexValid(index)
2924 || (d->model->parent(index) != d->root)
2925 || isRowHidden(index.row()) || isColumnHidden(index.column()))
2926 return;
2927
2928 QSpanCollection::Span span;
2929 if (d->hasSpans())
2930 span = d->span(index.row(), index.column());
2931
2932 // Adjust horizontal position
2933
2934 int viewportWidth = d->viewport->width();
2935 int horizontalOffset = d->horizontalHeader->offset();
2936 int horizontalPosition = d->horizontalHeader->sectionPosition(index.column());
2937 int horizontalIndex = d->horizontalHeader->visualIndex(index.column());
2938 int cellWidth = d->hasSpans()
2939 ? d->columnSpanWidth(index.column(), span.width())
2940 : d->horizontalHeader->sectionSize(index.column());
2941
2942 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2943
2944 bool positionAtLeft = (horizontalPosition - horizontalOffset < 0);
2945 bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth);
2946
2947 if (hint == PositionAtCenter || positionAtRight) {
2948 int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth);
2949 int x = cellWidth;
2950 while (horizontalIndex > 0) {
2951 x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1));
2952 if (x > w)
2953 break;
2954 --horizontalIndex;
2955 }
2956 }
2957
2958 if (positionAtRight || hint == PositionAtCenter || positionAtLeft) {
2959 int hiddenSections = 0;
2960 if (d->horizontalHeader->sectionsHidden()) {
2961 for (int s = horizontalIndex - 1; s >= 0; --s) {
2962 int column = d->horizontalHeader->logicalIndex(s);
2963 if (d->horizontalHeader->isSectionHidden(column))
2964 ++hiddenSections;
2965 }
2966 }
2967 horizontalScrollBar()->setValue(horizontalIndex - hiddenSections);
2968 }
2969
2970 } else { // ScrollPerPixel
2971 if (hint == PositionAtCenter) {
2972 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
2973 } else {
2974 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
2975 horizontalScrollBar()->setValue(horizontalPosition);
2976 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
2977 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
2978 }
2979 }
2980
2981 // Adjust vertical position
2982
2983 int viewportHeight = d->viewport->height();
2984 int verticalOffset = d->verticalHeader->offset();
2985 int verticalPosition = d->verticalHeader->sectionPosition(index.row());
2986 int verticalIndex = d->verticalHeader->visualIndex(index.row());
2987 int cellHeight = d->hasSpans()
2988 ? d->rowSpanHeight(index.row(), span.height())
2989 : d->verticalHeader->sectionSize(index.row());
2990
2991 if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) {
2992 if (hint == EnsureVisible)
2993 hint = PositionAtTop;
2994 } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) {
2995 if (hint == EnsureVisible)
2996 hint = PositionAtBottom;
2997 }
2998
2999 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
3000
3001 if (hint == PositionAtBottom || hint == PositionAtCenter) {
3002 int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight);
3003 int y = cellHeight;
3004 while (verticalIndex > 0) {
3005 int row = d->verticalHeader->logicalIndex(verticalIndex - 1);
3006 y += d->verticalHeader->sectionSize(row);
3007 if (y > h)
3008 break;
3009 --verticalIndex;
3010 }
3011 }
3012
3013 if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) {
3014 int hiddenSections = 0;
3015 if (d->verticalHeader->sectionsHidden()) {
3016 for (int s = verticalIndex - 1; s >= 0; --s) {
3017 int row = d->verticalHeader->logicalIndex(s);
3018 if (d->verticalHeader->isSectionHidden(row))
3019 ++hiddenSections;
3020 }
3021 }
3022 verticalScrollBar()->setValue(verticalIndex - hiddenSections);
3023 }
3024
3025 } else { // ScrollPerPixel
3026 if (hint == PositionAtTop) {
3027 verticalScrollBar()->setValue(verticalPosition);
3028 } else if (hint == PositionAtBottom) {
3029 verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight);
3030 } else if (hint == PositionAtCenter) {
3031 verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2));
3032 }
3033 }
3034
3035 update(index);
3036}
3037
3038/*!
3039 This slot is called to change the height of the given \a row. The
3040 old height is specified by \a oldHeight, and the new height by \a
3041 newHeight.
3042
3043 \sa columnResized()
3044*/
3045void QTableView::rowResized(int row, int, int)
3046{
3047 Q_D(QTableView);
3048 d->rowsToUpdate.append(row);
3049 if (!d->rowResizeTimer.isActive())
3050 d->rowResizeTimer.start(0ns, this);
3051}
3052
3053/*!
3054 This slot is called to change the width of the given \a column.
3055 The old width is specified by \a oldWidth, and the new width by \a
3056 newWidth.
3057
3058 \sa rowResized()
3059*/
3060void QTableView::columnResized(int column, int, int)
3061{
3062 Q_D(QTableView);
3063 d->columnsToUpdate.append(column);
3064 if (!d->columnResizeTimer.isActive())
3065 d->columnResizeTimer.start(0ns, this);
3066}
3067
3068/*!
3069 \reimp
3070 */
3071void QTableView::timerEvent(QTimerEvent *event)
3072{
3073 Q_D(QTableView);
3074
3075 if (event->id() == d->columnResizeTimer.id()) {
3076 const int oldScrollMax = horizontalScrollBar()->maximum();
3077 if (horizontalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3078 updateGeometries();
3079 d->columnResizeTimer.stop();
3080 } else {
3081 updateEditorGeometries();
3082 }
3083
3084 QRect rect;
3085 int viewportHeight = d->viewport->height();
3086 int viewportWidth = d->viewport->width();
3087 if (d->hasSpans() || horizontalScrollBar()->value() == oldScrollMax) {
3088 rect = QRect(0, 0, viewportWidth, viewportHeight);
3089 } else {
3090 for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) {
3091 int column = d->columnsToUpdate.at(i);
3092 int x = columnViewportPosition(column);
3093 if (isRightToLeft())
3094 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
3095 else
3096 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
3097 }
3098 }
3099
3100 d->viewport->update(rect.normalized());
3101 d->columnsToUpdate.clear();
3102 }
3103
3104 if (event->id() == d->rowResizeTimer.id()) {
3105 const int oldScrollMax = verticalScrollBar()->maximum();
3106 if (verticalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3107 updateGeometries();
3108 d->rowResizeTimer.stop();
3109 } else {
3110 updateEditorGeometries();
3111 }
3112
3113 int viewportHeight = d->viewport->height();
3114 int viewportWidth = d->viewport->width();
3115 int top;
3116 if (d->hasSpans() || verticalScrollBar()->value() == oldScrollMax) {
3117 top = 0;
3118 } else {
3119 top = viewportHeight;
3120 for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) {
3121 int y = rowViewportPosition(d->rowsToUpdate.at(i));
3122 top = qMin(top, y);
3123 }
3124 }
3125
3126 d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top));
3127 d->rowsToUpdate.clear();
3128 }
3129
3130 QAbstractItemView::timerEvent(event);
3131}
3132
3133#if QT_CONFIG(draganddrop)
3134/*! \reimp */
3135void QTableView::dropEvent(QDropEvent *event)
3136{
3137 Q_D(QTableView);
3138 if (event->source() == this && (event->dropAction() == Qt::MoveAction ||
3139 dragDropMode() == QAbstractItemView::InternalMove)) {
3140 QModelIndex topIndex;
3141 int col = -1;
3142 int row = -1;
3143 // check whether a subclass has already accepted the event, ie. moved the data
3144 if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && !topIndex.isValid() && col != -1) {
3145 // Drop between items (reordering) - can only happen with setDragDropOverwriteMode(false)
3146 const QModelIndexList indexes = selectedIndexes();
3147 QList<QPersistentModelIndex> persIndexes;
3148 persIndexes.reserve(indexes.size());
3149
3150 bool topIndexDropped = false;
3151 for (const auto &index : indexes) {
3152 // Reorder entire rows
3153 QPersistentModelIndex firstColIndex = index.siblingAtColumn(0);
3154 if (!persIndexes.contains(firstColIndex))
3155 persIndexes.append(firstColIndex);
3156 if (index.row() == topIndex.row()) {
3157 topIndexDropped = true;
3158 break;
3159 }
3160 }
3161 if (!topIndexDropped) {
3162 std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order.
3163
3164 QPersistentModelIndex dropRow = model()->index(row, col, topIndex);
3165
3166 int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row);
3167 bool dataMoved = false;
3168 for (const QPersistentModelIndex &pIndex : std::as_const(persIndexes)) {
3169 // only generate a move when not same row or behind itself
3170 if (r != pIndex.row() && r != pIndex.row() + 1) {
3171 // try to move (preserves selection)
3172 const bool moved = model()->moveRow(QModelIndex(), pIndex.row(), QModelIndex(), r);
3173 if (!moved)
3174 continue; // maybe it'll work for other rows
3175 dataMoved = true; // success
3176 } else {
3177 // move onto itself is blocked, don't delete anything
3178 dataMoved = true;
3179 }
3180 r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order.
3181 }
3182 if (dataMoved) {
3183 d->dropEventMoved = true;
3184 event->accept();
3185 }
3186 }
3187 }
3188 }
3189
3190 if (!event->isAccepted()) {
3191 // moveRows not implemented, fall back to default
3192 QAbstractItemView::dropEvent(event);
3193 }
3194}
3195#endif
3196
3197/*!
3198 This slot is called to change the index of the given \a row in the
3199 table view. The old index is specified by \a oldIndex, and the new
3200 index by \a newIndex.
3201
3202 \sa columnMoved()
3203*/
3204void QTableView::rowMoved(int row, int oldIndex, int newIndex)
3205{
3206 Q_UNUSED(row);
3207 Q_D(QTableView);
3208
3209 updateGeometries();
3210 int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex);
3211 int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex);
3212 if (d->hasSpans()) {
3213 d->viewport->update();
3214 } else {
3215 int oldTop = rowViewportPosition(logicalOldIndex);
3216 int newTop = rowViewportPosition(logicalNewIndex);
3217 int oldBottom = oldTop + rowHeight(logicalOldIndex);
3218 int newBottom = newTop + rowHeight(logicalNewIndex);
3219 int top = qMin(oldTop, newTop);
3220 int bottom = qMax(oldBottom, newBottom);
3221 int height = bottom - top;
3222 d->viewport->update(0, top, d->viewport->width(), height);
3223 }
3224}
3225
3226/*!
3227 This slot is called to change the index of the given \a column in
3228 the table view. The old index is specified by \a oldIndex, and
3229 the new index by \a newIndex.
3230
3231 \sa rowMoved()
3232*/
3233void QTableView::columnMoved(int column, int oldIndex, int newIndex)
3234{
3235 Q_UNUSED(column);
3236 Q_D(QTableView);
3237
3238 updateGeometries();
3239 int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex);
3240 int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex);
3241 if (d->hasSpans()) {
3242 d->viewport->update();
3243 } else {
3244 int oldLeft = columnViewportPosition(logicalOldIndex);
3245 int newLeft = columnViewportPosition(logicalNewIndex);
3246 int oldRight = oldLeft + columnWidth(logicalOldIndex);
3247 int newRight = newLeft + columnWidth(logicalNewIndex);
3248 int left = qMin(oldLeft, newLeft);
3249 int right = qMax(oldRight, newRight);
3250 int width = right - left;
3251 d->viewport->update(left, 0, width, d->viewport->height());
3252 }
3253}
3254
3255/*!
3256 Selects the given \a row in the table view if the current
3257 SelectionMode and SelectionBehavior allows rows to be selected.
3258
3259 \sa selectColumn()
3260*/
3261void QTableView::selectRow(int row)
3262{
3263 Q_D(QTableView);
3264 d->selectRow(row, true);
3265}
3266
3267/*!
3268 Selects the given \a column in the table view if the current
3269 SelectionMode and SelectionBehavior allows columns to be selected.
3270
3271 \sa selectRow()
3272*/
3273void QTableView::selectColumn(int column)
3274{
3275 Q_D(QTableView);
3276 d->selectColumn(column, true);
3277}
3278
3279/*!
3280 Hide the given \a row.
3281
3282 \sa showRow(), hideColumn()
3283*/
3284void QTableView::hideRow(int row)
3285{
3286 Q_D(QTableView);
3287 d->verticalHeader->hideSection(row);
3288}
3289
3290/*!
3291 Hide the given \a column.
3292
3293 \sa showColumn(), hideRow()
3294*/
3295void QTableView::hideColumn(int column)
3296{
3297 Q_D(QTableView);
3298 d->horizontalHeader->hideSection(column);
3299}
3300
3301/*!
3302 Show the given \a row.
3303
3304 \sa hideRow(), showColumn()
3305*/
3306void QTableView::showRow(int row)
3307{
3308 Q_D(QTableView);
3309 d->verticalHeader->showSection(row);
3310}
3311
3312/*!
3313 Show the given \a column.
3314
3315 \sa hideColumn(), showRow()
3316*/
3317void QTableView::showColumn(int column)
3318{
3319 Q_D(QTableView);
3320 d->horizontalHeader->showSection(column);
3321}
3322
3323/*!
3324 Resizes the given \a row based on the size hints of the delegate
3325 used to render each item in the row.
3326
3327 \sa resizeRowsToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3328*/
3329void QTableView::resizeRowToContents(int row)
3330{
3331 Q_D(QTableView);
3332 int content = sizeHintForRow(row);
3333 int header = d->verticalHeader->sectionSizeHint(row);
3334 d->verticalHeader->resizeSection(row, qMax(content, header));
3335}
3336
3337/*!
3338 Resizes all rows based on the size hints of the delegate
3339 used to render each item in the rows.
3340
3341 \sa resizeRowToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3342*/
3343void QTableView::resizeRowsToContents()
3344{
3345 Q_D(QTableView);
3346 d->verticalHeader->resizeSections(QHeaderView::ResizeToContents);
3347}
3348
3349/*!
3350 Resizes the given \a column based on the size hints of the delegate
3351 used to render each item in the column.
3352
3353 \note Only visible columns will be resized. Reimplement sizeHintForColumn()
3354 to resize hidden columns as well.
3355
3356 \sa resizeColumnsToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3357*/
3358void QTableView::resizeColumnToContents(int column)
3359{
3360 Q_D(QTableView);
3361 int content = sizeHintForColumn(column);
3362 int header = d->horizontalHeader->sectionSizeHint(column);
3363 d->horizontalHeader->resizeSection(column, qMax(content, header));
3364}
3365
3366/*!
3367 Resizes all columns based on the size hints of the delegate
3368 used to render each item in the columns.
3369
3370 \sa resizeColumnToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3371*/
3372void QTableView::resizeColumnsToContents()
3373{
3374 Q_D(QTableView);
3375 d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents);
3376}
3377
3378/*!
3379 Sorts the model by the values in the given \a column and \a order.
3380
3381 \a column may be -1, in which case no sort indicator will be shown
3382 and the model will return to its natural, unsorted order. Note that not
3383 all models support this and may even crash in this case.
3384
3385 \sa sortingEnabled
3386 */
3387void QTableView::sortByColumn(int column, Qt::SortOrder order)
3388{
3389 Q_D(QTableView);
3390 if (column < -1)
3391 return;
3392 d->horizontalHeader->setSortIndicator(column, order);
3393 // If sorting is not enabled or has the same order as before, force to sort now
3394 // else sorting will be trigger through sortIndicatorChanged()
3395 if (!d->sortingEnabled ||
3396 (d->horizontalHeader->sortIndicatorSection() == column && d->horizontalHeader->sortIndicatorOrder() == order))
3397 d->model->sort(column, order);
3398}
3399
3400/*!
3401 \internal
3402*/
3403void QTableView::verticalScrollbarAction(int action)
3404{
3405 QAbstractItemView::verticalScrollbarAction(action);
3406}
3407
3408/*!
3409 \internal
3410*/
3411void QTableView::horizontalScrollbarAction(int action)
3412{
3413 QAbstractItemView::horizontalScrollbarAction(action);
3414}
3415
3416/*!
3417 \reimp
3418*/
3419bool QTableView::isIndexHidden(const QModelIndex &index) const
3420{
3421 Q_D(const QTableView);
3422 Q_ASSERT(d->isIndexValid(index));
3423 if (isRowHidden(index.row()) || isColumnHidden(index.column()))
3424 return true;
3425 if (d->hasSpans()) {
3426 QSpanCollection::Span span = d->span(index.row(), index.column());
3427 return !((span.top() == index.row()) && (span.left() == index.column()));
3428 }
3429 return false;
3430}
3431
3432/*!
3433 \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
3434
3435 Sets the span of the table element at (\a row, \a column) to the number of
3436 rows and columns specified by (\a rowSpanCount, \a columnSpanCount).
3437
3438 \sa rowSpan(), columnSpan()
3439*/
3440void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan)
3441{
3442 Q_D(QTableView);
3443 if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0)
3444 return;
3445 d->setSpan(row, column, rowSpan, columnSpan);
3446 d->viewport->update();
3447}
3448
3449/*!
3450 Returns the row span of the table element at (\a row, \a column).
3451 The default is 1.
3452
3453 \sa setSpan(), columnSpan()
3454*/
3455int QTableView::rowSpan(int row, int column) const
3456{
3457 Q_D(const QTableView);
3458 return d->rowSpan(row, column);
3459}
3460
3461/*!
3462 Returns the column span of the table element at (\a row, \a
3463 column). The default is 1.
3464
3465 \sa setSpan(), rowSpan()
3466*/
3467int QTableView::columnSpan(int row, int column) const
3468{
3469 Q_D(const QTableView);
3470 return d->columnSpan(row, column);
3471}
3472
3473/*!
3474 Removes all row and column spans in the table view.
3475
3476 \sa setSpan()
3477*/
3478
3479void QTableView::clearSpans()
3480{
3481 Q_D(QTableView);
3482 d->spans.clear();
3483 d->viewport->update();
3484}
3485
3486void QTableViewPrivate::selectRow(int row, bool anchor)
3487{
3488 Q_Q(QTableView);
3489
3490 if (q->selectionBehavior() == QTableView::SelectColumns
3491 || (q->selectionMode() == QTableView::SingleSelection
3492 && q->selectionBehavior() == QTableView::SelectItems))
3493 return;
3494
3495 if (row >= 0 && row < model->rowCount(root)) {
3496 int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0);
3497 QModelIndex index = model->index(row, column, root);
3498 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3499
3500 {
3501 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3502 const auto startIndex = currentSelectionStartIndex;
3503 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3504 currentSelectionStartIndex = startIndex;
3505 }
3506
3507 if ((anchor && !(command & QItemSelectionModel::Current))
3508 || (q->selectionMode() == QTableView::SingleSelection))
3509 currentSelectionStartIndex = model->index(row, column, root);
3510
3511 if (q->selectionMode() != QTableView::SingleSelection
3512 && command.testFlag(QItemSelectionModel::Toggle)) {
3513 if (anchor)
3514 ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index)
3515 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3516 command &= ~QItemSelectionModel::Toggle;
3517 command |= ctrlDragSelectionFlag;
3518 if (!anchor)
3519 command |= QItemSelectionModel::Current;
3520 }
3521
3522 const auto rowSectionAnchor = currentSelectionStartIndex.row();
3523 QModelIndex upper = model->index(qMin(rowSectionAnchor, row), column, root);
3524 QModelIndex lower = model->index(qMax(rowSectionAnchor, row), column, root);
3525 if ((verticalHeader->sectionsMoved() && upper.row() != lower.row())) {
3526 q->setSelection(q->visualRect(upper) | q->visualRect(lower), command | QItemSelectionModel::Rows);
3527 } else {
3528 selectionModel->select(QItemSelection(upper, lower), command | QItemSelectionModel::Rows);
3529 }
3530 }
3531}
3532
3533void QTableViewPrivate::selectColumn(int column, bool anchor)
3534{
3535 Q_Q(QTableView);
3536
3537 if (q->selectionBehavior() == QTableView::SelectRows
3538 || (q->selectionMode() == QTableView::SingleSelection
3539 && q->selectionBehavior() == QTableView::SelectItems))
3540 return;
3541
3542 if (column >= 0 && column < model->columnCount(root)) {
3543 int row = verticalHeader->logicalIndexAt(0);
3544 QModelIndex index = model->index(row, column, root);
3545 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3546
3547 {
3548 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3549 const auto startIndex = currentSelectionStartIndex;
3550 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3551 currentSelectionStartIndex = startIndex;
3552 }
3553
3554 if ((anchor && !(command & QItemSelectionModel::Current))
3555 || (q->selectionMode() == QTableView::SingleSelection))
3556 currentSelectionStartIndex = model->index(row, column, root);
3557
3558 if (q->selectionMode() != QTableView::SingleSelection
3559 && command.testFlag(QItemSelectionModel::Toggle)) {
3560 if (anchor)
3561 ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns(row).contains(index)
3562 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3563 command &= ~QItemSelectionModel::Toggle;
3564 command |= ctrlDragSelectionFlag;
3565 if (!anchor)
3566 command |= QItemSelectionModel::Current;
3567 }
3568
3569 const auto columnSectionAnchor = currentSelectionStartIndex.column();
3570 QModelIndex left = model->index(row, qMin(columnSectionAnchor, column), root);
3571 QModelIndex right = model->index(row, qMax(columnSectionAnchor, column), root);
3572 if ((horizontalHeader->sectionsMoved() && left.column() != right.column())) {
3573 q->setSelection(q->visualRect(left) | q->visualRect(right), command | QItemSelectionModel::Columns);
3574 } else {
3575 selectionModel->select(QItemSelection(left, right), command | QItemSelectionModel::Columns);
3576 }
3577 }
3578}
3579
3580/*!
3581 \reimp
3582 */
3583void QTableView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3584{
3585#if QT_CONFIG(accessibility)
3586 if (QAccessible::isActive()) {
3587 if (current.isValid() && hasFocus()) {
3588 Q_D(QTableView);
3589 int entry = d->accessibleTable2Index(current);
3590 QAccessibleEvent event(this, QAccessible::Focus);
3591 event.setChild(entry);
3592 QAccessible::updateAccessibility(&event);
3593 }
3594 }
3595#endif
3596 QAbstractItemView::currentChanged(current, previous);
3597}
3598
3599/*!
3600 \reimp
3601 */
3602void QTableView::selectionChanged(const QItemSelection &selected,
3603 const QItemSelection &deselected)
3604{
3605 Q_D(QTableView);
3606 Q_UNUSED(d);
3607#if QT_CONFIG(accessibility)
3608 if (QAccessible::isActive()) {
3609 // ### does not work properly for selection ranges.
3610 QModelIndex sel = selected.indexes().value(0);
3611 if (sel.isValid()) {
3612 int entry = d->accessibleTable2Index(sel);
3613 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3614 event.setChild(entry);
3615 QAccessible::updateAccessibility(&event);
3616 }
3617 QModelIndex desel = deselected.indexes().value(0);
3618 if (desel.isValid()) {
3619 int entry = d->accessibleTable2Index(desel);
3620 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3621 event.setChild(entry);
3622 QAccessible::updateAccessibility(&event);
3623 }
3624 }
3625#endif
3626 QAbstractItemView::selectionChanged(selected, deselected);
3627}
3628
3629int QTableView::visualIndex(const QModelIndex &index) const
3630{
3631 return index.row();
3632}
3633
3634QT_END_NAMESPACE
3635
3636#include "qtableview.moc"
3637
3638#include "moc_qtableview.cpp"