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 setObjectName(QLatin1String("qt_tableview_cornerbutton"));
560 }
561 void paintEvent(QPaintEvent*) override {
562 QStyleOptionHeader opt;
563 opt.initFrom(this);
564 QStyle::State state = QStyle::State_None;
565 if (isEnabled())
566 state |= QStyle::State_Enabled;
567 if (isActiveWindow())
568 state |= QStyle::State_Active;
569 if (isDown())
570 state |= QStyle::State_Sunken;
571 opt.state = state;
572 opt.rect = rect();
573 opt.position = QStyleOptionHeader::OnlyOneSection;
574 QPainter painter(this);
575 style()->drawControl(QStyle::CE_Header, &opt, &painter, this);
576 }
577};
578#endif
579
580void QTableViewPrivate::init()
581{
582 Q_Q(QTableView);
583
584 q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed);
585
586 QHeaderView *vertical = new QHeaderView(Qt::Vertical, q);
587 vertical->setSectionsClickable(true);
588 vertical->setHighlightSections(true);
589 q->setVerticalHeader(vertical);
590
591 QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q);
592 horizontal->setSectionsClickable(true);
593 horizontal->setHighlightSections(true);
594 q->setHorizontalHeader(horizontal);
595
596 tabKeyNavigation = true;
597
598#if QT_CONFIG(abstractbutton)
599 cornerWidget = new QTableCornerButton(q);
600 cornerWidget->setFocusPolicy(Qt::NoFocus);
601 cornerWidgetConnection = QObject::connect(
602 cornerWidget, &QTableCornerButton::clicked,
603 q, &QTableView::selectAll);
604#endif
605}
606
607void QTableViewPrivate::clearConnections()
608{
609 for (QMetaObject::Connection &connection : modelConnections)
610 QObject::disconnect(connection);
611 for (QMetaObject::Connection &connection : verHeaderConnections)
612 QObject::disconnect(connection);
613 for (QMetaObject::Connection &connection : horHeaderConnections)
614 QObject::disconnect(connection);
615 for (QMetaObject::Connection &connection : dynHorHeaderConnections)
616 QObject::disconnect(connection);
617 QObject::disconnect(selectionmodelConnection);
618#if QT_CONFIG(abstractbutton)
619 QObject::disconnect(cornerWidgetConnection);
620#endif
621}
622
623/*!
624 \internal
625 Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections.
626*/
627void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const
628{
629 Q_ASSERT(range && range->isValid());
630
631 int top = range->top();
632 int left = range->left();
633 int bottom = range->bottom();
634 int right = range->right();
635
636 while (bottom >= top && verticalHeader->isSectionHidden(bottom))
637 --bottom;
638 while (right >= left && horizontalHeader->isSectionHidden(right))
639 --right;
640
641 if (top > bottom || left > right) { // everything is hidden
642 *range = QItemSelectionRange();
643 return;
644 }
645
646 while (verticalHeader->isSectionHidden(top) && top <= bottom)
647 ++top;
648 while (horizontalHeader->isSectionHidden(left) && left <= right)
649 ++left;
650
651 if (top > bottom || left > right) { // everything is hidden
652 *range = QItemSelectionRange();
653 return;
654 }
655
656 QModelIndex bottomRight = model->index(bottom, right, range->parent());
657 QModelIndex topLeft = model->index(top, left, range->parent());
658 *range = QItemSelectionRange(topLeft, bottomRight);
659}
660
661QRect QTableViewPrivate::intersectedRect(const QRect rect, const QModelIndex &topLeft, const QModelIndex &bottomRight) const
662{
663 Q_Q(const QTableView);
664
665 using minMaxPair = std::pair<int, int>;
666 const auto calcMinMax = [q](QHeaderView *hdr, int startIdx, int endIdx, minMaxPair bounds) -> minMaxPair
667 {
668 minMaxPair ret(std::numeric_limits<int>::max(), std::numeric_limits<int>::min());
669 if (hdr->sectionsMoved()) {
670 for (int i = startIdx; i <= endIdx; ++i) {
671 const int start = hdr->sectionViewportPosition(i);
672 const int end = start + hdr->sectionSize(i);
673 ret.first = std::min(start, ret.first);
674 ret.second = std::max(end, ret.second);
675 if (ret.first <= bounds.first && ret.second >= bounds.second)
676 break;
677 }
678 } else {
679 if (q->isRightToLeft() && q->horizontalHeader() == hdr)
680 std::swap(startIdx, endIdx);
681 ret.first = hdr->sectionViewportPosition(startIdx);
682 ret.second = hdr->sectionViewportPosition(endIdx) +
683 hdr->sectionSize(endIdx);
684 }
685 return ret;
686 };
687
688 const auto yVals = calcMinMax(verticalHeader, topLeft.row(), bottomRight.row(),
689 minMaxPair(rect.top(), rect.bottom()));
690 if (yVals.first == yVals.second) // all affected rows are hidden
691 return QRect();
692
693 // short circuit: check if no row is inside rect
694 const QRect colRect(QPoint(rect.left(), yVals.first),
695 QPoint(rect.right(), yVals.second));
696 const QRect intersected = rect.intersected(colRect);
697 if (intersected.isNull())
698 return QRect();
699
700 const auto xVals = calcMinMax(horizontalHeader, topLeft.column(), bottomRight.column(),
701 minMaxPair(rect.left(), rect.right()));
702 const QRect updateRect(QPoint(xVals.first, yVals.first),
703 QPoint(xVals.second, yVals.second));
704 return rect.intersected(updateRect);
705}
706
707/*!
708 \internal
709 Sets the span for the cell at (\a row, \a column).
710*/
711void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan)
712{
713 if (Q_UNLIKELY(row < 0 || column < 0 || rowSpan <= 0 || columnSpan <= 0)) {
714 qWarning("QTableView::setSpan: invalid span given: (%d, %d, %d, %d)",
715 row, column, rowSpan, columnSpan);
716 return;
717 }
718 QSpanCollection::Span *sp = spans.spanAt(column, row);
719 if (sp) {
720 if (sp->top() != row || sp->left() != column) {
721 qWarning("QTableView::setSpan: span cannot overlap");
722 return;
723 }
724 if (rowSpan == 1 && columnSpan == 1) {
725 rowSpan = columnSpan = 0;
726 }
727 const int old_height = sp->height();
728 sp->m_bottom = row + rowSpan - 1;
729 sp->m_right = column + columnSpan - 1;
730 spans.updateSpan(sp, old_height);
731 return;
732 } else if (Q_UNLIKELY(rowSpan == 1 && columnSpan == 1)) {
733 qWarning("QTableView::setSpan: single cell span won't be added");
734 return;
735 }
736 sp = new QSpanCollection::Span(row, column, rowSpan, columnSpan);
737 spans.addSpan(sp);
738}
739
740/*!
741 \internal
742 Gets the span information for the cell at (\a row, \a column).
743*/
744QSpanCollection::Span QTableViewPrivate::span(int row, int column) const
745{
746 QSpanCollection::Span *sp = spans.spanAt(column, row);
747 if (sp)
748 return *sp;
749
750 return QSpanCollection::Span(row, column, 1, 1);
751}
752
753/*!
754 \internal
755 Returns the logical index of the last section that's part of the span.
756*/
757int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const
758{
759 int visual = header->visualIndex(logical);
760 for (int i = 1; i < span; ) {
761 if (++visual >= header->count())
762 break;
763 logical = header->logicalIndex(visual);
764 ++i;
765 }
766 return logical;
767}
768
769/*!
770 \internal
771 Returns the size of the span starting at logical index \a logical
772 and spanning \a span sections.
773*/
774int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const
775{
776 int endLogical = sectionSpanEndLogical(header, logical, span);
777 return header->sectionPosition(endLogical)
778 - header->sectionPosition(logical)
779 + header->sectionSize(endLogical);
780}
781
782/*!
783 \internal
784 Returns \c true if the section at logical index \a logical is part of the span
785 starting at logical index \a spanLogical and spanning \a span sections;
786 otherwise, returns \c false.
787*/
788bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const
789{
790 if (logical == spanLogical)
791 return true; // it's the start of the span
792 int visual = header->visualIndex(spanLogical);
793 for (int i = 1; i < span; ) {
794 if (++visual >= header->count())
795 break;
796 spanLogical = header->logicalIndex(visual);
797 if (logical == spanLogical)
798 return true;
799 ++i;
800 }
801 return false;
802}
803
804/*!
805 \internal
806 Searches for the next cell which is available for e.g. keyboard navigation
807 The search is done by row
808*/
809int QTableViewPrivate::nextActiveVisualRow(int rowToStart, int column, int limit,
810 SearchDirection searchDirection) const
811{
812 const int lc = logicalColumn(column);
813 int visualRow = rowToStart;
814 const auto isCellActive = [this](int vr, int lc)
815 {
816 const int lr = logicalRow(vr);
817 return !isRowHidden(lr) && isCellEnabled(lr, lc);
818 };
819 switch (searchDirection) {
820 case SearchDirection::Increasing:
821 if (visualRow < limit) {
822 while (!isCellActive(visualRow, lc)) {
823 if (++visualRow == limit)
824 return rowToStart;
825 }
826 }
827 break;
828 case SearchDirection::Decreasing:
829 while (visualRow > limit && !isCellActive(visualRow, lc))
830 --visualRow;
831 break;
832 }
833 return visualRow;
834}
835
836/*!
837 \internal
838 Searches for the next cell which is available for e.g. keyboard navigation
839 The search is done by column
840*/
841int QTableViewPrivate::nextActiveVisualColumn(int row, int columnToStart, int limit,
842 SearchDirection searchDirection) const
843{
844 const int lr = logicalRow(row);
845 int visualColumn = columnToStart;
846 const auto isCellActive = [this](int lr, int vc)
847 {
848 const int lc = logicalColumn(vc);
849 return !isColumnHidden(lc) && isCellEnabled(lr, lc);
850 };
851 switch (searchDirection) {
852 case SearchDirection::Increasing:
853 while (visualColumn < limit && !isCellActive(lr, visualColumn))
854 ++visualColumn;
855 break;
856 case SearchDirection::Decreasing:
857 while (visualColumn > limit && !isCellActive(lr, visualColumn))
858 --visualColumn;
859 break;
860 }
861 return visualColumn;
862}
863
864/*!
865 \internal
866 Returns the visual rect for the given \a span.
867*/
868QRect QTableViewPrivate::visualSpanRect(const QSpanCollection::Span &span) const
869{
870 Q_Q(const QTableView);
871 // vertical
872 int row = span.top();
873 int rowp = verticalHeader->sectionViewportPosition(row);
874 int rowh = rowSpanHeight(row, span.height());
875 // horizontal
876 int column = span.left();
877 int colw = columnSpanWidth(column, span.width());
878 if (q->isRightToLeft())
879 column = span.right();
880 int colp = horizontalHeader->sectionViewportPosition(column);
881
882 const int i = showGrid ? 1 : 0;
883 if (q->isRightToLeft())
884 return QRect(colp + i, rowp, colw - i, rowh - i);
885 return QRect(colp, rowp, colw - i, rowh - i);
886}
887
888/*!
889 \internal
890 Draws the spanning cells within rect \a area, and clips them off as
891 preparation for the main drawing loop.
892 \a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn
893*/
894void QTableViewPrivate::drawAndClipSpans(const QRegion &area, QPainter *painter,
895 const QStyleOptionViewItem &option, QBitArray *drawn,
896 int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn)
897{
898 Q_Q(const QTableView);
899 bool alternateBase = false;
900 QRegion region = viewport->rect();
901
902 QSet<QSpanCollection::Span *> visibleSpans;
903 bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved();
904
905 if (!sectionMoved) {
906 visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow),
907 lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1);
908 } else {
909 // Any cell outside the viewport, on the top or left, can still end up visible inside the
910 // viewport if is has a span. Calculating if a spanned cell overlaps with the viewport is
911 // "easy" enough when the columns (or rows) in the view are aligned with the columns
912 // in the model; In that case you know that if a column is outside the viewport on the
913 // right, it cannot affect the drawing of the cells inside the viewport, even with a span.
914 // And under that assumption, the spansInRect() function can be used (which is optimized
915 // to only iterate the spans that are close to the viewport).
916 // But when the view has rearranged the columns (or rows), this is no longer true. In that
917 // case, even if a column, according to the model, is outside the viewport on the right, it
918 // can still overlap with the viewport. This can happen if it was moved to the left of the
919 // viewport and one of its cells has a span. In that case we need to take the theoretically
920 // slower route and iterate through all the spans, and check if any of them overlaps with
921 // the viewport.
922 const auto spanList = spans.spans;
923 for (QSpanCollection::Span *span : spanList) {
924 const int spanVisualLeft = visualColumn(span->left());
925 const int spanVisualTop = visualRow(span->top());
926 const int spanVisualRight = spanVisualLeft + span->width() - 1;
927 const int spanVisualBottom = spanVisualTop + span->height() - 1;
928 if ((spanVisualLeft <= lastVisualColumn && spanVisualRight >= firstVisualColumn)
929 && (spanVisualTop <= lastVisualRow && spanVisualBottom >= firstVisualRow))
930 visibleSpans.insert(span);
931 }
932 }
933
934 for (QSpanCollection::Span *span : std::as_const(visibleSpans)) {
935 int row = span->top();
936 int col = span->left();
937 QModelIndex index = model->index(row, col, root);
938 if (!index.isValid())
939 continue;
940 QRect rect = visualSpanRect(*span);
941 rect.translate(scrollDelayOffset);
942 if (!area.intersects(rect))
943 continue;
944 QStyleOptionViewItem opt = option;
945 opt.rect = rect;
946 alternateBase = alternatingColors && (span->top() & 1);
947 opt.features.setFlag(QStyleOptionViewItem::Alternate, alternateBase);
948 drawCell(painter, opt, index);
949 if (showGrid) {
950 // adjust the clip rect to be able to paint the top & left grid lines
951 // if the headers are not visible, see paintEvent()
952 if (horizontalHeader->visualIndex(row) == 0)
953 rect.setTop(rect.top() + 1);
954 if (verticalHeader->visualIndex(row) == 0) {
955 if (q->isLeftToRight())
956 rect.setLeft(rect.left() + 1);
957 else
958 rect.setRight(rect.right() - 1);
959 }
960 }
961 region -= rect;
962 for (int r = span->top(); r <= span->bottom(); ++r) {
963 const int vr = visualRow(r);
964 if (vr < firstVisualRow || vr > lastVisualRow)
965 continue;
966 for (int c = span->left(); c <= span->right(); ++c) {
967 const int vc = visualColumn(c);
968 if (vc < firstVisualColumn || vc > lastVisualColumn)
969 continue;
970 drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
971 + vc - firstVisualColumn);
972 }
973 }
974
975 }
976 painter->setClipRegion(region);
977}
978
979/*!
980 \internal
981 Updates spans after row insertion.
982*/
983void QTableViewPrivate::updateSpanInsertedRows(const QModelIndex &parent, int start, int end)
984{
985 Q_UNUSED(parent);
986 spans.updateInsertedRows(start, end);
987}
988
989/*!
990 \internal
991 Updates spans after column insertion.
992*/
993void QTableViewPrivate::updateSpanInsertedColumns(const QModelIndex &parent, int start, int end)
994{
995 Q_UNUSED(parent);
996 spans.updateInsertedColumns(start, end);
997}
998
999/*!
1000 \internal
1001 Updates spans after row removal.
1002*/
1003void QTableViewPrivate::updateSpanRemovedRows(const QModelIndex &parent, int start, int end)
1004{
1005 Q_UNUSED(parent);
1006 spans.updateRemovedRows(start, end);
1007}
1008
1009/*!
1010 \internal
1011 Updates spans after column removal.
1012*/
1013void QTableViewPrivate::updateSpanRemovedColumns(const QModelIndex &parent, int start, int end)
1014{
1015 Q_UNUSED(parent);
1016 spans.updateRemovedColumns(start, end);
1017}
1018
1019/*!
1020 \internal
1021 Sort the model when the header sort indicator changed
1022*/
1023void QTableViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
1024{
1025 model->sort(column, order);
1026}
1027
1028QStyleOptionViewItem::ViewItemPosition QTableViewPrivate::viewItemPosition(
1029 const QModelIndex &index) const
1030{
1031 int visualColumn = horizontalHeader->visualIndex(index.column());
1032 int count = horizontalHeader->count();
1033
1034 if (count <= 0 || visualColumn < 0 || visualColumn >= count)
1035 return QStyleOptionViewItem::Invalid;
1036
1037 if (count == 1 && visualColumn == 0)
1038 return QStyleOptionViewItem::OnlyOne;
1039 else if (visualColumn == 0)
1040 return QStyleOptionViewItem::Beginning;
1041 else if (visualColumn == count - 1)
1042 return QStyleOptionViewItem::End;
1043 else
1044 return QStyleOptionViewItem::Middle;
1045}
1046
1047/*!
1048 \internal
1049 Draws a table cell.
1050*/
1051void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
1052{
1053 Q_Q(QTableView);
1054 QStyleOptionViewItem opt = option;
1055
1056 if (selectionModel && selectionModel->isSelected(index))
1057 opt.state |= QStyle::State_Selected;
1058 if (index == hover)
1059 opt.state |= QStyle::State_MouseOver;
1060 if (option.state & QStyle::State_Enabled) {
1061 QPalette::ColorGroup cg;
1062 if ((model->flags(index) & Qt::ItemIsEnabled) == 0) {
1063 opt.state &= ~QStyle::State_Enabled;
1064 cg = QPalette::Disabled;
1065 } else {
1066 cg = QPalette::Normal;
1067 }
1068 opt.palette.setCurrentColorGroup(cg);
1069 }
1070 opt.viewItemPosition = viewItemPosition(index);
1071
1072 if (index == q->currentIndex()) {
1073 const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid();
1074 if (focus)
1075 opt.state |= QStyle::State_HasFocus;
1076 }
1077
1078 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);
1079
1080 q->itemDelegateForIndex(index)->paint(painter, opt, index);
1081}
1082
1083/*!
1084 \internal
1085 Get sizeHint width for single Index (providing existing hint and style option)
1086*/
1087int QTableViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option) const
1088{
1089 Q_Q(const QTableView);
1090 const int oldHint = hint;
1091 QWidget *editor = editorForIndex(index).widget.data();
1092 if (editor && persistent.contains(editor)) {
1093 hint = qMax(hint, editor->sizeHint().width());
1094 int min = editor->minimumSize().width();
1095 int max = editor->maximumSize().width();
1096 hint = qBound(min, hint, max);
1097 }
1098 hint = qMax(hint, q->itemDelegateForIndex(index)->sizeHint(option, index).width());
1099
1100 if (hasSpans()) {
1101 auto span = spans.spanAt(index.column(), index.row());
1102 if (span && span->m_left == index.column() && span->m_top == index.row()) {
1103 // spans are screwed up when sections are moved
1104 const auto left = logicalColumn(span->m_left);
1105 for (int i = 1; i <= span->width(); ++i)
1106 hint -= q->columnWidth(visualColumn(left + i));
1107 }
1108 hint = std::max(hint, oldHint);
1109 }
1110 return hint;
1111}
1112
1113/*!
1114 \internal
1115 Get sizeHint height for single Index (providing existing hint and style option)
1116*/
1117int QTableViewPrivate::heightHintForIndex(const QModelIndex &index, int hint, QStyleOptionViewItem &option) const
1118{
1119 Q_Q(const QTableView);
1120 QWidget *editor = editorForIndex(index).widget.data();
1121 if (editor && persistent.contains(editor)) {
1122 hint = qMax(hint, editor->sizeHint().height());
1123 int min = editor->minimumSize().height();
1124 int max = editor->maximumSize().height();
1125 hint = qBound(min, hint, max);
1126 }
1127
1128 if (wrapItemText) {// for wrapping boundaries
1129 option.rect.setY(q->rowViewportPosition(index.row()));
1130 int height = q->rowHeight(index.row());
1131 // if the option.height == 0 then q->itemDelegateForIndex(index)->sizeHint(option, index) will be wrong.
1132 // The option.height == 0 is used to conclude that the text is not wrapped, and hence it will
1133 // (exactly like widthHintForIndex) return a QSize with a long width (that we don't use) -
1134 // and the height of the text if it was/is on one line.
1135 // What we want is a height hint for the current width (and we know that this section is not hidden)
1136 // Therefore we catch this special situation with:
1137 if (height == 0)
1138 height = 1;
1139 option.rect.setHeight(height);
1140 option.rect.setX(q->columnViewportPosition(index.column()));
1141 option.rect.setWidth(q->columnWidth(index.column()));
1142 if (hasSpans()) {
1143 auto span = spans.spanAt(index.column(), index.row());
1144 if (span && span->m_left == index.column() && span->m_top == index.row())
1145 option.rect.setWidth(std::max(option.rect.width(), visualSpanRect(*span).width()));
1146 }
1147 // 1px less space when grid is shown (see drawCell)
1148 if (showGrid)
1149 option.rect.setWidth(option.rect.width() - 1);
1150 }
1151 hint = qMax(hint, q->itemDelegateForIndex(index)->sizeHint(option, index).height());
1152 return hint;
1153}
1154
1155
1156/*!
1157 \class QTableView
1158
1159 \brief The QTableView class provides a default model/view
1160 implementation of a table view.
1161
1162 \ingroup model-view
1163 \ingroup advanced
1164 \inmodule QtWidgets
1165
1166 \image fusion-tableview.png {Table of months and amounts}
1167
1168 A QTableView implements a table view that displays items from a
1169 model. This class is used to provide standard tables that were
1170 previously provided by the QTable class, but using the more
1171 flexible approach provided by Qt's model/view architecture.
1172
1173 The QTableView class is one of the \l{Model/View Classes}
1174 and is part of Qt's \l{Model/View Programming}{model/view framework}.
1175
1176 QTableView implements the interfaces defined by the
1177 QAbstractItemView class to allow it to display data provided by
1178 models derived from the QAbstractItemModel class.
1179
1180 \section1 Navigation
1181
1182 You can navigate the cells in the table by clicking on a cell with the
1183 mouse, or by using the arrow keys. Because QTableView enables
1184 \l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you
1185 can also hit Tab and Backtab to move from cell to cell.
1186
1187 \section1 Visual Appearance
1188
1189 The table has a vertical header that can be obtained using the
1190 verticalHeader() function, and a horizontal header that is available
1191 through the horizontalHeader() function. The height of each row in the
1192 table can be found by using rowHeight(); similarly, the width of
1193 columns can be found using columnWidth(). Since both of these are plain
1194 widgets, you can hide either of them using their hide() functions.
1195 Each header is configured with its \l{QHeaderView::}{highlightSections}
1196 and \l{QHeaderView::}{sectionsClickable} properties set to \c true.
1197
1198 Rows and columns can be hidden and shown with hideRow(), hideColumn(),
1199 showRow(), and showColumn(). They can be selected with selectRow()
1200 and selectColumn(). The table will show a grid depending on the
1201 \l showGrid property.
1202
1203 The items shown in a table view, like those in the other item views, are
1204 rendered and edited using standard \l{QStyledItemDelegate}{delegates}. However,
1205 for some tasks it is sometimes useful to be able to insert widgets in a
1206 table instead. Widgets are set for particular indexes with the
1207 \l{QAbstractItemView::}{setIndexWidget()} function, and
1208 later retrieved with \l{QAbstractItemView::}{indexWidget()}.
1209
1210 \table
1211 \row \li \inlineimage qtableview-resized.png
1212 {Table of names, addresses, and quantity}
1213 \li By default, the cells in a table do not expand to fill the available space.
1214
1215 You can make the cells fill the available space by stretching the last
1216 header section. Access the relevant header using horizontalHeader()
1217 or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection}
1218 property.
1219
1220 To distribute the available space according to the space requirement of
1221 each column or row, call the view's resizeColumnsToContents() or
1222 resizeRowsToContents() functions.
1223 \endtable
1224
1225 \section1 Coordinate Systems
1226
1227 For some specialized forms of tables it is useful to be able to
1228 convert between row and column indexes and widget coordinates.
1229 The rowAt() function provides the y-coordinate within the view of the
1230 specified row; the row index can be used to obtain a corresponding
1231 y-coordinate with rowViewportPosition(). The columnAt() and
1232 columnViewportPosition() functions provide the equivalent conversion
1233 operations between x-coordinates and column indexes.
1234
1235 \sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
1236 {Table Model Example}
1237*/
1238
1239/*!
1240 Constructs a table view with a \a parent to represent the data.
1241
1242 \sa QAbstractItemModel
1243*/
1244
1245QTableView::QTableView(QWidget *parent)
1246 : QAbstractItemView(*new QTableViewPrivate, parent)
1247{
1248 Q_D(QTableView);
1249 d->init();
1250}
1251
1252/*!
1253 \internal
1254*/
1255QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent)
1256 : QAbstractItemView(dd, parent)
1257{
1258 Q_D(QTableView);
1259 d->init();
1260}
1261
1262/*!
1263 Destroys the table view.
1264*/
1265QTableView::~QTableView()
1266{
1267 Q_D(QTableView);
1268 d->clearConnections();
1269}
1270
1271/*!
1272 \reimp
1273*/
1274QSize QTableView::viewportSizeHint() const
1275{
1276 Q_D(const QTableView);
1277 QSize result( (d->verticalHeader->isHidden() ? 0 : d->verticalHeader->width()) + d->horizontalHeader->length(),
1278 (d->horizontalHeader->isHidden() ? 0 : d->horizontalHeader->height()) + d->verticalHeader->length());
1279 return result;
1280}
1281
1282/*!
1283 \reimp
1284*/
1285void QTableView::setModel(QAbstractItemModel *model)
1286{
1287 Q_D(QTableView);
1288 if (model == d->model)
1289 return;
1290 //let's disconnect from the old model
1291 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
1292 for (QMetaObject::Connection &connection : d->modelConnections)
1293 disconnect(connection);
1294 }
1295 if (d->selectionModel) { // support row editing
1296 disconnect(d->selectionmodelConnection);
1297 }
1298 if (model) { //and connect to the new one
1299 d->modelConnections = {
1300 QObjectPrivate::connect(model, &QAbstractItemModel::rowsInserted,
1301 d, &QTableViewPrivate::updateSpanInsertedRows),
1302 QObjectPrivate::connect(model, &QAbstractItemModel::columnsInserted,
1303 d, &QTableViewPrivate::updateSpanInsertedColumns),
1304 QObjectPrivate::connect(model, &QAbstractItemModel::rowsRemoved,
1305 d, &QTableViewPrivate::updateSpanRemovedRows),
1306 QObjectPrivate::connect(model, &QAbstractItemModel::columnsRemoved,
1307 d, &QTableViewPrivate::updateSpanRemovedColumns)
1308 };
1309 }
1310 d->verticalHeader->setModel(model);
1311 d->horizontalHeader->setModel(model);
1312 QAbstractItemView::setModel(model);
1313}
1314
1315/*!
1316 \reimp
1317*/
1318void QTableView::setRootIndex(const QModelIndex &index)
1319{
1320 Q_D(QTableView);
1321 if (index == d->root) {
1322 viewport()->update();
1323 return;
1324 }
1325 d->verticalHeader->setRootIndex(index);
1326 d->horizontalHeader->setRootIndex(index);
1327 QAbstractItemView::setRootIndex(index);
1328}
1329
1330/*!
1331 \internal
1332*/
1333void QTableView::doItemsLayout()
1334{
1335 Q_D(QTableView);
1336 QAbstractItemView::doItemsLayout();
1337 if (!d->verticalHeader->updatesEnabled())
1338 d->verticalHeader->setUpdatesEnabled(true);
1339}
1340
1341/*!
1342 \reimp
1343*/
1344void QTableView::setSelectionModel(QItemSelectionModel *selectionModel)
1345{
1346 Q_D(QTableView);
1347 Q_ASSERT(selectionModel);
1348 if (d->selectionModel) {
1349 // support row editing
1350 disconnect(d->selectionmodelConnection);
1351 }
1352
1353 d->verticalHeader->setSelectionModel(selectionModel);
1354 d->horizontalHeader->setSelectionModel(selectionModel);
1355 QAbstractItemView::setSelectionModel(selectionModel);
1356
1357 if (d->selectionModel) {
1358 // support row editing
1359 d->selectionmodelConnection =
1360 connect(d->selectionModel, &QItemSelectionModel::currentRowChanged,
1361 d->model, &QAbstractItemModel::submit);
1362 }
1363}
1364
1365/*!
1366 Returns the table view's horizontal header.
1367
1368 \sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData()
1369*/
1370QHeaderView *QTableView::horizontalHeader() const
1371{
1372 Q_D(const QTableView);
1373 return d->horizontalHeader;
1374}
1375
1376/*!
1377 Returns the table view's vertical header.
1378
1379 \sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData()
1380*/
1381QHeaderView *QTableView::verticalHeader() const
1382{
1383 Q_D(const QTableView);
1384 return d->verticalHeader;
1385}
1386
1387/*!
1388 Sets the widget to use for the horizontal header to \a header.
1389
1390 \sa horizontalHeader(), setVerticalHeader()
1391*/
1392void QTableView::setHorizontalHeader(QHeaderView *header)
1393{
1394 Q_D(QTableView);
1395
1396 if (!header || header == d->horizontalHeader)
1397 return;
1398 for (QMetaObject::Connection &connection : d->horHeaderConnections)
1399 disconnect(connection);
1400 if (d->horizontalHeader && d->horizontalHeader->parent() == this)
1401 delete d->horizontalHeader;
1402 d->horizontalHeader = header;
1403 d->horizontalHeader->setParent(this);
1404 d->horizontalHeader->setFirstSectionMovable(true);
1405 if (!d->horizontalHeader->model()) {
1406 d->horizontalHeader->setModel(d->model);
1407 if (d->selectionModel)
1408 d->horizontalHeader->setSelectionModel(d->selectionModel);
1409 }
1410
1411 d->horHeaderConnections = {
1412 connect(d->horizontalHeader,&QHeaderView::sectionResized,
1413 this, &QTableView::columnResized),
1414 connect(d->horizontalHeader, &QHeaderView::sectionMoved,
1415 this, &QTableView::columnMoved),
1416 connect(d->horizontalHeader, &QHeaderView::sectionCountChanged,
1417 this, &QTableView::columnCountChanged),
1418 connect(d->horizontalHeader, &QHeaderView::sectionHandleDoubleClicked,
1419 this, &QTableView::resizeColumnToContents),
1420 connect(d->horizontalHeader, &QHeaderView::geometriesChanged,
1421 this, &QTableView::updateGeometries),
1422 };
1423 //update the sorting enabled states on the new header
1424 setSortingEnabled(d->sortingEnabled);
1425}
1426
1427/*!
1428 Sets the widget to use for the vertical header to \a header.
1429
1430 \sa verticalHeader(), setHorizontalHeader()
1431*/
1432void QTableView::setVerticalHeader(QHeaderView *header)
1433{
1434 Q_D(QTableView);
1435
1436 if (!header || header == d->verticalHeader)
1437 return;
1438 for (QMetaObject::Connection &connection : d->verHeaderConnections)
1439 disconnect(connection);
1440 if (d->verticalHeader && d->verticalHeader->parent() == this)
1441 delete d->verticalHeader;
1442 d->verticalHeader = header;
1443 d->verticalHeader->setParent(this);
1444 d->verticalHeader->setFirstSectionMovable(true);
1445 if (!d->verticalHeader->model()) {
1446 d->verticalHeader->setModel(d->model);
1447 if (d->selectionModel)
1448 d->verticalHeader->setSelectionModel(d->selectionModel);
1449 }
1450
1451 d->verHeaderConnections = {
1452 connect(d->verticalHeader, &QHeaderView::sectionResized,
1453 this, &QTableView::rowResized),
1454 connect(d->verticalHeader, &QHeaderView::sectionMoved,
1455 this, &QTableView::rowMoved),
1456 connect(d->verticalHeader, &QHeaderView::sectionCountChanged,
1457 this, &QTableView::rowCountChanged),
1458 connect(d->verticalHeader, &QHeaderView::sectionPressed,
1459 this, &QTableView::selectRow),
1460 connect(d->verticalHeader, &QHeaderView::sectionHandleDoubleClicked,
1461 this, &QTableView::resizeRowToContents),
1462 connect(d->verticalHeader, &QHeaderView::geometriesChanged,
1463 this, &QTableView::updateGeometries),
1464 connect(d->verticalHeader, &QHeaderView::sectionEntered,
1465 this, [d](int row) { d->selectRow(row, false); })
1466 };
1467}
1468
1469/*!
1470 \reimp
1471
1472 Scroll the contents of the table view by (\a dx, \a dy).
1473*/
1474void QTableView::scrollContentsBy(int dx, int dy)
1475{
1476 Q_D(QTableView);
1477
1478 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
1479
1480 dx = isRightToLeft() ? -dx : dx;
1481 if (dx) {
1482 int oldOffset = d->horizontalHeader->offset();
1483 d->horizontalHeader->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
1484 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
1485 int newOffset = d->horizontalHeader->offset();
1486 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
1487 }
1488 }
1489 if (dy) {
1490 int oldOffset = d->verticalHeader->offset();
1491 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
1492 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1493 int newOffset = d->verticalHeader->offset();
1494 dy = oldOffset - newOffset;
1495 }
1496 }
1497 d->scrollContentsBy(dx, dy);
1498
1499 if (d->showGrid) {
1500 //we need to update the first line of the previous top item in the view
1501 //because it has the grid drawn if the header is invisible.
1502 //It is strictly related to what's done at then end of the paintEvent
1503 if (dy > 0 && d->horizontalHeader->isHidden()) {
1504 d->viewport->update(0, dy, d->viewport->width(), dy);
1505 }
1506 if (dx > 0 && d->verticalHeader->isHidden()) {
1507 d->viewport->update(dx, 0, dx, d->viewport->height());
1508 }
1509 }
1510}
1511
1512/*!
1513 \reimp
1514*/
1515void QTableView::initViewItemOption(QStyleOptionViewItem *option) const
1516{
1517 QAbstractItemView::initViewItemOption(option);
1518 option->showDecorationSelected = true;
1519}
1520
1521/*!
1522 Paints the table on receipt of the given paint event \a event.
1523*/
1524void QTableView::paintEvent(QPaintEvent *event)
1525{
1526 Q_D(QTableView);
1527 // setup temp variables for the painting
1528 QStyleOptionViewItem option;
1529 initViewItemOption(&option);
1530 const QPoint offset = d->scrollDelayOffset;
1531 const bool showGrid = d->showGrid;
1532 const int gridSize = showGrid ? 1 : 0;
1533 const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
1534 const QColor gridColor = QColor::fromRgba(static_cast<QRgb>(gridHint));
1535 const QPen gridPen = QPen(gridColor, 1, d->gridStyle);
1536 const QHeaderView *verticalHeader = d->verticalHeader;
1537 const QHeaderView *horizontalHeader = d->horizontalHeader;
1538 const bool alternate = d->alternatingColors;
1539 const bool rightToLeft = isRightToLeft();
1540
1541 QPainter painter(d->viewport);
1542
1543 // if there's nothing to do, clear the area and return
1544 if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)
1545 return;
1546
1547 const int x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
1548 const int y = verticalHeader->length() - verticalHeader->offset() - 1;
1549
1550 //firstVisualRow is the visual index of the first visible row. lastVisualRow is the visual index of the last visible Row.
1551 //same goes for ...VisualColumn
1552 int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0);
1553 int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->height());
1554 if (lastVisualRow == -1)
1555 lastVisualRow = d->model->rowCount(d->root) - 1;
1556
1557 int firstVisualColumn = horizontalHeader->visualIndexAt(0);
1558 int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->width());
1559 if (rightToLeft)
1560 qSwap(firstVisualColumn, lastVisualColumn);
1561 if (firstVisualColumn == -1)
1562 firstVisualColumn = 0;
1563 if (lastVisualColumn == -1)
1564 lastVisualColumn = horizontalHeader->count() - 1;
1565
1566 QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1));
1567
1568 const QRegion region = event->region().translated(offset);
1569
1570 if (d->hasSpans()) {
1571 d->drawAndClipSpans(region, &painter, option, &drawn,
1572 firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn);
1573 }
1574
1575 for (QRect dirtyArea : region) {
1576 dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));
1577 if (rightToLeft) {
1578 dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));
1579 } else {
1580 dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));
1581 }
1582 // dirtyArea may be invalid when the horizontal header is not stretched
1583 if (!dirtyArea.isValid())
1584 continue;
1585
1586 // get the horizontal start and end visual sections
1587 int left = horizontalHeader->visualIndexAt(dirtyArea.left());
1588 int right = horizontalHeader->visualIndexAt(dirtyArea.right());
1589 if (rightToLeft)
1590 qSwap(left, right);
1591 if (left == -1) left = 0;
1592 if (right == -1) right = horizontalHeader->count() - 1;
1593
1594 // get the vertical start and end visual sections and if alternate color
1595 int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
1596 if (bottom == -1) bottom = verticalHeader->count() - 1;
1597 int top = 0;
1598 bool alternateBase = false;
1599 if (alternate && verticalHeader->sectionsHidden()) {
1600 const int verticalOffset = verticalHeader->offset();
1601 int row = verticalHeader->logicalIndex(top);
1602 for (int y = 0;
1603 ((y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);
1604 ++top) {
1605 row = verticalHeader->logicalIndex(top);
1606 if (alternate && !verticalHeader->isSectionHidden(row))
1607 alternateBase = !alternateBase;
1608 }
1609 } else {
1610 top = verticalHeader->visualIndexAt(dirtyArea.top());
1611 alternateBase = (top & 1) && alternate;
1612 }
1613 if (top == -1 || top > bottom)
1614 continue;
1615
1616 // Paint each row item
1617 for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {
1618 int row = verticalHeader->logicalIndex(visualRowIndex);
1619 if (verticalHeader->isSectionHidden(row))
1620 continue;
1621 int rowY = rowViewportPosition(row);
1622 rowY += offset.y();
1623 int rowh = rowHeight(row) - gridSize;
1624
1625 // Paint each column item
1626 for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {
1627 int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
1628 + visualColumnIndex - firstVisualColumn;
1629
1630 if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))
1631 continue;
1632 drawn.setBit(currentBit);
1633
1634 int col = horizontalHeader->logicalIndex(visualColumnIndex);
1635 if (horizontalHeader->isSectionHidden(col))
1636 continue;
1637 int colp = columnViewportPosition(col);
1638 colp += offset.x();
1639 int colw = columnWidth(col) - gridSize;
1640
1641 const QModelIndex index = d->model->index(row, col, d->root);
1642 if (index.isValid()) {
1643 option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);
1644 if (alternate) {
1645 if (alternateBase)
1646 option.features |= QStyleOptionViewItem::Alternate;
1647 else
1648 option.features &= ~QStyleOptionViewItem::Alternate;
1649 }
1650 d->drawCell(&painter, option, index);
1651 }
1652 }
1653 alternateBase = !alternateBase && alternate;
1654 }
1655
1656 if (showGrid) {
1657 // Find the bottom right (the last rows/columns might be hidden)
1658 while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;
1659 QPen old = painter.pen();
1660 painter.setPen(gridPen);
1661 // Paint each row
1662 for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {
1663 int row = verticalHeader->logicalIndex(visualIndex);
1664 if (verticalHeader->isSectionHidden(row))
1665 continue;
1666 int rowY = rowViewportPosition(row);
1667 rowY += offset.y();
1668 int rowh = rowHeight(row) - gridSize;
1669 QLineF line(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);
1670 painter.drawLine(line.translated(0.5, 0.5));
1671 }
1672
1673 // Paint each column
1674 for (int h = left; h <= right; ++h) {
1675 int col = horizontalHeader->logicalIndex(h);
1676 if (horizontalHeader->isSectionHidden(col))
1677 continue;
1678 int colp = columnViewportPosition(col);
1679 colp += offset.x();
1680 if (!rightToLeft)
1681 colp += columnWidth(col) - gridSize;
1682 QLineF line(colp, dirtyArea.top(), colp, dirtyArea.bottom());
1683 painter.drawLine(line.translated(0.5, 0.5));
1684 }
1685 const bool drawWhenHidden = style()->styleHint(QStyle::SH_Table_AlwaysDrawLeftTopGridLines,
1686 &option, this);
1687 if (drawWhenHidden && horizontalHeader->isHidden()) {
1688 const int row = verticalHeader->logicalIndex(top);
1689 if (!verticalHeader->isSectionHidden(row)) {
1690 const int rowY = rowViewportPosition(row) + offset.y();
1691 if (rowY == dirtyArea.top())
1692 painter.drawLine(dirtyArea.left(), rowY, dirtyArea.right(), rowY);
1693 }
1694 }
1695 if (drawWhenHidden && verticalHeader->isHidden()) {
1696 const int col = horizontalHeader->logicalIndex(left);
1697 if (!horizontalHeader->isSectionHidden(col)) {
1698 int colX = columnViewportPosition(col) + offset.x();
1699 if (!isLeftToRight())
1700 colX += columnWidth(left) - 1;
1701 if (isLeftToRight() && colX == dirtyArea.left())
1702 painter.drawLine(colX, dirtyArea.top(), colX, dirtyArea.bottom());
1703 if (!isLeftToRight() && colX == dirtyArea.right())
1704 painter.drawLine(colX, dirtyArea.top(), colX, dirtyArea.bottom());
1705 }
1706 }
1707 painter.setPen(old);
1708 }
1709 }
1710
1711#if QT_CONFIG(draganddrop)
1712 // Paint the dropIndicator
1713 d->paintDropIndicator(&painter);
1714#endif
1715}
1716
1717/*!
1718 Returns the index position of the model item corresponding to the
1719 table item at position \a pos in contents coordinates.
1720*/
1721QModelIndex QTableView::indexAt(const QPoint &pos) const
1722{
1723 Q_D(const QTableView);
1724 d->executePostedLayout();
1725 int r = rowAt(pos.y());
1726 int c = columnAt(pos.x());
1727 if (r >= 0 && c >= 0) {
1728 if (d->hasSpans()) {
1729 QSpanCollection::Span span = d->span(r, c);
1730 r = span.top();
1731 c = span.left();
1732 }
1733 return d->model->index(r, c, d->root);
1734 }
1735 return QModelIndex();
1736}
1737
1738/*!
1739 Returns the horizontal offset of the items in the table view.
1740
1741 Note that the table view uses the horizontal header section
1742 positions to determine the positions of columns in the view.
1743
1744 \sa verticalOffset()
1745*/
1746int QTableView::horizontalOffset() const
1747{
1748 Q_D(const QTableView);
1749 return d->horizontalHeader->offset();
1750}
1751
1752/*!
1753 Returns the vertical offset of the items in the table view.
1754
1755 Note that the table view uses the vertical header section
1756 positions to determine the positions of rows in the view.
1757
1758 \sa horizontalOffset()
1759*/
1760int QTableView::verticalOffset() const
1761{
1762 Q_D(const QTableView);
1763 return d->verticalHeader->offset();
1764}
1765
1766/*!
1767 \fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1768
1769 Moves the cursor in accordance with the given \a cursorAction, using the
1770 information provided by the \a modifiers.
1771
1772 \sa QAbstractItemView::CursorAction
1773*/
1774QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1775{
1776 Q_D(QTableView);
1777 Q_UNUSED(modifiers);
1778
1779 int bottom = d->model->rowCount(d->root) - 1;
1780 // make sure that bottom is the bottommost *visible* row
1781 while (bottom >= 0 && isRowHidden(d->logicalRow(bottom)))
1782 --bottom;
1783
1784 int right = d->model->columnCount(d->root) - 1;
1785
1786 while (right >= 0 && isColumnHidden(d->logicalColumn(right)))
1787 --right;
1788
1789 if (bottom == -1 || right == -1)
1790 return QModelIndex(); // model is empty
1791
1792 QModelIndex current = currentIndex();
1793
1794 if (!current.isValid()) {
1795 int row = 0;
1796 int column = 0;
1797 while (column < right && isColumnHidden(d->logicalColumn(column)))
1798 ++column;
1799 while (isRowHidden(d->logicalRow(row)) && row < bottom)
1800 ++row;
1801 d->visualCursor = QPoint(column, row);
1802 return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root);
1803 }
1804
1805 // Update visual cursor if current index has changed.
1806 QPoint visualCurrent(d->visualColumn(current.column()), d->visualRow(current.row()));
1807 if (visualCurrent != d->visualCursor) {
1808 if (d->hasSpans()) {
1809 QSpanCollection::Span span = d->span(current.row(), current.column());
1810 if (span.top() > d->visualCursor.y() || d->visualCursor.y() > span.bottom()
1811 || span.left() > d->visualCursor.x() || d->visualCursor.x() > span.right())
1812 d->visualCursor = visualCurrent;
1813 } else {
1814 d->visualCursor = visualCurrent;
1815 }
1816 }
1817
1818 int visualRow = d->visualCursor.y();
1819 if (visualRow > bottom)
1820 visualRow = bottom;
1821 Q_ASSERT(visualRow != -1);
1822 int visualColumn = d->visualCursor.x();
1823 if (visualColumn > right)
1824 visualColumn = right;
1825 Q_ASSERT(visualColumn != -1);
1826
1827 if (isRightToLeft()) {
1828 if (cursorAction == MoveLeft)
1829 cursorAction = MoveRight;
1830 else if (cursorAction == MoveRight)
1831 cursorAction = MoveLeft;
1832 }
1833
1834 switch (cursorAction) {
1835 case MoveUp: {
1836 int originalRow = visualRow;
1837 int r = d->logicalRow(visualRow);
1838 int c = d->logicalColumn(visualColumn);
1839 if (r != -1 && d->hasSpans()) {
1840 QSpanCollection::Span span = d->span(r, c);
1841 if (span.width() > 1 || span.height() > 1)
1842 visualRow = d->visualRow(span.top());
1843 }
1844 while (visualRow >= 0) {
1845 --visualRow;
1846 r = d->logicalRow(visualRow);
1847 c = d->logicalColumn(visualColumn);
1848 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1849 break;
1850 }
1851 if (visualRow < 0)
1852 visualRow = originalRow;
1853 break;
1854 }
1855 case MoveDown: {
1856 int originalRow = visualRow;
1857 if (d->hasSpans()) {
1858 QSpanCollection::Span span = d->span(current.row(), current.column());
1859 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1860 }
1861 int r = d->logicalRow(visualRow);
1862 int c = d->logicalColumn(visualColumn);
1863 if (r != -1 && d->hasSpans()) {
1864 QSpanCollection::Span span = d->span(r, c);
1865 if (span.width() > 1 || span.height() > 1)
1866 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1867 }
1868 while (visualRow <= bottom) {
1869 ++visualRow;
1870 r = d->logicalRow(visualRow);
1871 c = d->logicalColumn(visualColumn);
1872 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1873 break;
1874 }
1875 if (visualRow > bottom)
1876 visualRow = originalRow;
1877 break;
1878 }
1879 case MovePrevious:
1880 case MoveLeft: {
1881 int originalRow = visualRow;
1882 int originalColumn = visualColumn;
1883 bool firstTime = true;
1884 bool looped = false;
1885 bool wrapped = false;
1886 do {
1887 int r = d->logicalRow(visualRow);
1888 int c = d->logicalColumn(visualColumn);
1889 if (firstTime && c != -1 && d->hasSpans()) {
1890 firstTime = false;
1891 QSpanCollection::Span span = d->span(r, c);
1892 if (span.width() > 1 || span.height() > 1)
1893 visualColumn = d->visualColumn(span.left());
1894 }
1895 while (visualColumn >= 0) {
1896 --visualColumn;
1897 r = d->logicalRow(visualRow);
1898 c = d->logicalColumn(visualColumn);
1899 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1900 break;
1901 if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) {
1902 looped = true;
1903 break;
1904 }
1905 }
1906 if (cursorAction == MoveLeft || visualColumn >= 0)
1907 break;
1908 visualColumn = right + 1;
1909 if (visualRow == 0) {
1910 wrapped = true;
1911 visualRow = bottom;
1912 } else {
1913 --visualRow;
1914 }
1915 } while (!looped);
1916 if (visualColumn < 0)
1917 visualColumn = originalColumn;
1918 break;
1919 }
1920 case MoveNext:
1921 case MoveRight: {
1922 int originalRow = visualRow;
1923 int originalColumn = visualColumn;
1924 bool firstTime = true;
1925 bool looped = false;
1926 bool wrapped = false;
1927 do {
1928 int r = d->logicalRow(visualRow);
1929 int c = d->logicalColumn(visualColumn);
1930 if (firstTime && c != -1 && d->hasSpans()) {
1931 firstTime = false;
1932 QSpanCollection::Span span = d->span(r, c);
1933 if (span.width() > 1 || span.height() > 1)
1934 visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
1935 }
1936 while (visualColumn <= right) {
1937 ++visualColumn;
1938 r = d->logicalRow(visualRow);
1939 c = d->logicalColumn(visualColumn);
1940 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1941 break;
1942 if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) {
1943 looped = true;
1944 break;
1945 }
1946 }
1947 if (cursorAction == MoveRight || visualColumn <= right)
1948 break;
1949 visualColumn = -1;
1950 if (visualRow == bottom) {
1951 wrapped = true;
1952 visualRow = 0;
1953 } else {
1954 ++visualRow;
1955 }
1956 } while (!looped);
1957 if (visualColumn > right)
1958 visualColumn = originalColumn;
1959 break;
1960 }
1961 case MoveHome:
1962 visualColumn = d->nextActiveVisualColumn(visualRow, 0, right,
1963 QTableViewPrivate::SearchDirection::Increasing);
1964 if (modifiers & Qt::ControlModifier)
1965 visualRow = d->nextActiveVisualRow(0, visualColumn, bottom,
1966 QTableViewPrivate::SearchDirection::Increasing);
1967 break;
1968 case MoveEnd:
1969 visualColumn = d->nextActiveVisualColumn(visualRow, right, -1,
1970 QTableViewPrivate::SearchDirection::Decreasing);
1971 if (modifiers & Qt::ControlModifier)
1972 visualRow = d->nextActiveVisualRow(bottom, visualColumn, -1,
1973 QTableViewPrivate::SearchDirection::Decreasing);
1974 break;
1975 case MovePageUp: {
1976 int newLogicalRow = rowAt(visualRect(current).bottom() - d->viewport->height());
1977 int visualRow = (newLogicalRow == -1 ? 0 : d->visualRow(newLogicalRow));
1978 visualRow = d->nextActiveVisualRow(visualRow, current.column(), bottom,
1979 QTableViewPrivate::SearchDirection::Increasing);
1980 newLogicalRow = d->logicalRow(visualRow);
1981 return d->model->index(newLogicalRow, current.column(), d->root);
1982 }
1983 case MovePageDown: {
1984 int newLogicalRow = rowAt(visualRect(current).top() + d->viewport->height());
1985 int visualRow = (newLogicalRow == -1 ? bottom : d->visualRow(newLogicalRow));
1986 visualRow = d->nextActiveVisualRow(visualRow, current.column(), -1,
1987 QTableViewPrivate::SearchDirection::Decreasing);
1988 newLogicalRow = d->logicalRow(visualRow);
1989 return d->model->index(newLogicalRow, current.column(), d->root);
1990 }}
1991
1992 d->visualCursor = QPoint(visualColumn, visualRow);
1993 int logicalRow = d->logicalRow(visualRow);
1994 int logicalColumn = d->logicalColumn(visualColumn);
1995 if (!d->model->hasIndex(logicalRow, logicalColumn, d->root))
1996 return QModelIndex();
1997
1998 QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root);
1999 if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result)) {
2000 if (d->hasSpans()) {
2001 QSpanCollection::Span span = d->span(result.row(), result.column());
2002 if (span.width() > 1 || span.height() > 1) {
2003 result = d->model->sibling(span.top(), span.left(), result);
2004 }
2005 }
2006 return result;
2007 }
2008
2009 return QModelIndex();
2010}
2011
2012/*!
2013 \fn void QTableView::setSelection(const QRect &rect,
2014 QItemSelectionModel::SelectionFlags flags)
2015
2016 Selects the items within the given \a rect and in accordance with
2017 the specified selection \a flags.
2018*/
2019void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2020{
2021 Q_D(QTableView);
2022 QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right())
2023 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())));
2024 QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2025 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())));
2026 if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
2027 return;
2028
2029 const bool verticalMoved = verticalHeader()->sectionsMoved();
2030 const bool horizontalMoved = horizontalHeader()->sectionsMoved();
2031
2032 QItemSelection selection;
2033 int top = tl.row();
2034 int bottom = br.row();
2035 int left = tl.column();
2036 int right = br.column();
2037
2038 const auto updateVisualIndices = [&]() {
2039 if (verticalMoved && horizontalMoved) {
2040 top = d->visualRow(tl.row());
2041 bottom = d->visualRow(br.row());
2042 left = d->visualColumn(tl.column());
2043 right = d->visualColumn(br.column());
2044 } else if (horizontalMoved) {
2045 top = tl.row();
2046 bottom = br.row();
2047 left = d->visualColumn(tl.column());
2048 right = d->visualColumn(br.column());
2049 } else if (verticalMoved) {
2050 top = d->visualRow(tl.row());
2051 bottom = d->visualRow(br.row());
2052 left = tl.column();
2053 right = br.column();
2054 }
2055 };
2056
2057 if (d->hasSpans()) {
2058 bool expanded;
2059 // when the current selection does not intersect with any spans of merged cells,
2060 // the range of selected cells must be the same as if there were no merged cells
2061 bool intersectsSpan = false;
2062 top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
2063 left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2064 bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
2065 right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2066 do {
2067 expanded = false;
2068 for (QSpanCollection::Span *it : d->spans.spans) {
2069 const QSpanCollection::Span &span = *it;
2070 const int t = d->visualRow(span.top());
2071 const int l = d->visualColumn(span.left());
2072 const int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
2073 const int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
2074 if ((t > bottom) || (l > right) || (top > b) || (left > r))
2075 continue; // no intersect
2076 intersectsSpan = true;
2077 if (t < top) {
2078 top = t;
2079 expanded = true;
2080 }
2081 if (l < left) {
2082 left = l;
2083 expanded = true;
2084 }
2085 if (b > bottom) {
2086 bottom = b;
2087 expanded = true;
2088 }
2089 if (r > right) {
2090 right = r;
2091 expanded = true;
2092 }
2093 if (expanded)
2094 break;
2095 }
2096 } while (expanded);
2097 if (!intersectsSpan) {
2098 updateVisualIndices();
2099 } else if (!verticalMoved && !horizontalMoved) {
2100 // top/left/bottom/right are visual, update indexes
2101 tl = d->model->index(top, left, d->root);
2102 br = d->model->index(bottom, right, d->root);
2103 }
2104 } else {
2105 updateVisualIndices();
2106 }
2107
2108 if (horizontalMoved && verticalMoved) {
2109 selection.reserve((right - left + 1) * (bottom - top + 1));
2110 for (int horizontal = left; horizontal <= right; ++horizontal) {
2111 int column = d->logicalColumn(horizontal);
2112 for (int vertical = top; vertical <= bottom; ++vertical) {
2113 int row = d->logicalRow(vertical);
2114 QModelIndex index = d->model->index(row, column, d->root);
2115 selection.append(QItemSelectionRange(index));
2116 }
2117 }
2118 } else if (horizontalMoved) {
2119 selection.reserve(right - left + 1);
2120 for (int visual = left; visual <= right; ++visual) {
2121 int column = d->logicalColumn(visual);
2122 QModelIndex topLeft = d->model->index(top, column, d->root);
2123 if (top == bottom)
2124 selection.append(QItemSelectionRange(topLeft));
2125 else
2126 selection.append({ topLeft, topLeft.siblingAtRow(bottom) });
2127 }
2128 } else if (verticalMoved) {
2129 selection.reserve(bottom - top + 1);
2130 for (int visual = top; visual <= bottom; ++visual) {
2131 int row = d->logicalRow(visual);
2132 QModelIndex topLeft = d->model->index(row, left, d->root);
2133 if (left == right)
2134 selection.append(QItemSelectionRange(topLeft));
2135 else
2136 selection.append({ topLeft, topLeft.siblingAtColumn(right) });
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 (QMetaObject::Connection &connection : d->dynHorHeaderConnections)
2758 disconnect(connection);
2759 if (enable) {
2760 //sortByColumn has to be called before we connect or set the sortingEnabled flag
2761 // because otherwise it will not call sort on the model.
2762 sortByColumn(d->horizontalHeader->sortIndicatorSection(),
2763 d->horizontalHeader->sortIndicatorOrder());
2764 d->dynHorHeaderConnections = {
2765 QObjectPrivate::connect(d->horizontalHeader, &QHeaderView::sortIndicatorChanged,
2766 d, &QTableViewPrivate::sortIndicatorChanged)
2767 };
2768 } else {
2769 d->dynHorHeaderConnections = {
2770 connect(d->horizontalHeader, &QHeaderView::sectionPressed,
2771 this, &QTableView::selectColumn),
2772 connect(d->horizontalHeader, &QHeaderView::sectionEntered,
2773 this, [d](int column) {d->selectColumn(column, false); })
2774 };
2775 }
2776 d->sortingEnabled = enable;
2777}
2778
2779bool QTableView::isSortingEnabled() const
2780{
2781 Q_D(const QTableView);
2782 return d->sortingEnabled;
2783}
2784
2785/*!
2786 \property QTableView::showGrid
2787 \brief whether the grid is shown
2788
2789 If this property is \c true a grid is drawn for the table; if the
2790 property is \c false, no grid is drawn. The default value is true.
2791*/
2792bool QTableView::showGrid() const
2793{
2794 Q_D(const QTableView);
2795 return d->showGrid;
2796}
2797
2798void QTableView::setShowGrid(bool show)
2799{
2800 Q_D(QTableView);
2801 if (d->showGrid != show) {
2802 d->showGrid = show;
2803 d->viewport->update();
2804 }
2805}
2806
2807/*!
2808 \property QTableView::gridStyle
2809 \brief the pen style used to draw the grid.
2810
2811 This property holds the style used when drawing the grid (see \l{showGrid}).
2812*/
2813Qt::PenStyle QTableView::gridStyle() const
2814{
2815 Q_D(const QTableView);
2816 return d->gridStyle;
2817}
2818
2819void QTableView::setGridStyle(Qt::PenStyle style)
2820{
2821 Q_D(QTableView);
2822 if (d->gridStyle != style) {
2823 d->gridStyle = style;
2824 d->viewport->update();
2825 }
2826}
2827
2828/*!
2829 \property QTableView::wordWrap
2830 \brief the item text word-wrapping policy
2831
2832 If this property is \c true then the item text is wrapped where
2833 necessary at word-breaks; otherwise it is not wrapped at all.
2834 This property is \c true by default.
2835
2836 Note that even if wrapping is enabled, the cell will not be
2837 expanded to fit all text. Ellipsis will be inserted according to
2838 the current \l{QAbstractItemView::}{textElideMode}.
2839
2840*/
2841void QTableView::setWordWrap(bool on)
2842{
2843 Q_D(QTableView);
2844 if (d->wrapItemText == on)
2845 return;
2846 d->wrapItemText = on;
2847 QMetaObject::invokeMethod(d->verticalHeader, "resizeSections");
2848 QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections");
2849}
2850
2851bool QTableView::wordWrap() const
2852{
2853 Q_D(const QTableView);
2854 return d->wrapItemText;
2855}
2856
2857#if QT_CONFIG(abstractbutton)
2858/*!
2859 \property QTableView::cornerButtonEnabled
2860 \brief whether the button in the top-left corner is enabled
2861
2862 If this property is \c true then button in the top-left corner
2863 of the table view is enabled. Clicking on this button will
2864 select all the cells in the table view.
2865
2866 This property is \c true by default.
2867*/
2868void QTableView::setCornerButtonEnabled(bool enable)
2869{
2870 Q_D(QTableView);
2871 d->cornerWidget->setEnabled(enable);
2872}
2873
2874bool QTableView::isCornerButtonEnabled() const
2875{
2876 Q_D(const QTableView);
2877 return d->cornerWidget->isEnabled();
2878}
2879#endif
2880
2881/*!
2882 \reimp
2883
2884 Returns the rectangle on the viewport occupied by the given \a
2885 index.
2886 If the index is hidden in the view it will return a null QRect.
2887*/
2888QRect QTableView::visualRect(const QModelIndex &index) const
2889{
2890 Q_D(const QTableView);
2891 if (!d->isIndexValid(index) || index.parent() != d->root
2892 || (!d->hasSpans() && isIndexHidden(index)))
2893 return QRect();
2894
2895 d->executePostedLayout();
2896
2897 if (d->hasSpans()) {
2898 QSpanCollection::Span span = d->span(index.row(), index.column());
2899 return d->visualSpanRect(span);
2900 }
2901
2902 int rowp = rowViewportPosition(index.row());
2903 int rowh = rowHeight(index.row());
2904 int colp = columnViewportPosition(index.column());
2905 int colw = columnWidth(index.column());
2906
2907 const int i = showGrid() ? 1 : 0;
2908 return QRect(colp, rowp, colw - i, rowh - i);
2909}
2910
2911/*!
2912 \reimp
2913
2914 Makes sure that the given \a index is visible in the table view,
2915 scrolling if necessary.
2916*/
2917void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
2918{
2919 Q_D(QTableView);
2920
2921 // check if we really need to do anything
2922 if (!d->isIndexValid(index)
2923 || (d->model->parent(index) != d->root)
2924 || isRowHidden(index.row()) || isColumnHidden(index.column()))
2925 return;
2926
2927 QSpanCollection::Span span;
2928 if (d->hasSpans())
2929 span = d->span(index.row(), index.column());
2930
2931 // Adjust horizontal position
2932
2933 int viewportWidth = d->viewport->width();
2934 int horizontalOffset = d->horizontalHeader->offset();
2935 int horizontalPosition = d->horizontalHeader->sectionPosition(index.column());
2936 int horizontalIndex = d->horizontalHeader->visualIndex(index.column());
2937 int cellWidth = d->hasSpans()
2938 ? d->columnSpanWidth(index.column(), span.width())
2939 : d->horizontalHeader->sectionSize(index.column());
2940
2941 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2942
2943 bool positionAtLeft = (horizontalPosition - horizontalOffset < 0);
2944 bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth);
2945
2946 if (hint == PositionAtCenter || positionAtRight) {
2947 int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth);
2948 int x = cellWidth;
2949 while (horizontalIndex > 0) {
2950 x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1));
2951 if (x > w)
2952 break;
2953 --horizontalIndex;
2954 }
2955 }
2956
2957 if (positionAtRight || hint == PositionAtCenter || positionAtLeft) {
2958 int hiddenSections = 0;
2959 if (d->horizontalHeader->sectionsHidden()) {
2960 for (int s = horizontalIndex - 1; s >= 0; --s) {
2961 int column = d->horizontalHeader->logicalIndex(s);
2962 if (d->horizontalHeader->isSectionHidden(column))
2963 ++hiddenSections;
2964 }
2965 }
2966 horizontalScrollBar()->setValue(horizontalIndex - hiddenSections);
2967 }
2968
2969 } else { // ScrollPerPixel
2970 if (hint == PositionAtCenter) {
2971 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
2972 } else {
2973 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
2974 horizontalScrollBar()->setValue(horizontalPosition);
2975 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
2976 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
2977 }
2978 }
2979
2980 // Adjust vertical position
2981
2982 int viewportHeight = d->viewport->height();
2983 int verticalOffset = d->verticalHeader->offset();
2984 int verticalPosition = d->verticalHeader->sectionPosition(index.row());
2985 int verticalIndex = d->verticalHeader->visualIndex(index.row());
2986 int cellHeight = d->hasSpans()
2987 ? d->rowSpanHeight(index.row(), span.height())
2988 : d->verticalHeader->sectionSize(index.row());
2989
2990 if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) {
2991 if (hint == EnsureVisible)
2992 hint = PositionAtTop;
2993 } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) {
2994 if (hint == EnsureVisible)
2995 hint = PositionAtBottom;
2996 }
2997
2998 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2999
3000 if (hint == PositionAtBottom || hint == PositionAtCenter) {
3001 int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight);
3002 int y = cellHeight;
3003 while (verticalIndex > 0) {
3004 int row = d->verticalHeader->logicalIndex(verticalIndex - 1);
3005 y += d->verticalHeader->sectionSize(row);
3006 if (y > h)
3007 break;
3008 --verticalIndex;
3009 }
3010 }
3011
3012 if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) {
3013 int hiddenSections = 0;
3014 if (d->verticalHeader->sectionsHidden()) {
3015 for (int s = verticalIndex - 1; s >= 0; --s) {
3016 int row = d->verticalHeader->logicalIndex(s);
3017 if (d->verticalHeader->isSectionHidden(row))
3018 ++hiddenSections;
3019 }
3020 }
3021 verticalScrollBar()->setValue(verticalIndex - hiddenSections);
3022 }
3023
3024 } else { // ScrollPerPixel
3025 if (hint == PositionAtTop) {
3026 verticalScrollBar()->setValue(verticalPosition);
3027 } else if (hint == PositionAtBottom) {
3028 verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight);
3029 } else if (hint == PositionAtCenter) {
3030 verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2));
3031 }
3032 }
3033
3034 update(index);
3035}
3036
3037/*!
3038 This slot is called to change the height of the given \a row. The
3039 old height is specified by \a oldHeight, and the new height by \a
3040 newHeight.
3041
3042 \sa columnResized()
3043*/
3044void QTableView::rowResized(int row, int, int)
3045{
3046 Q_D(QTableView);
3047 d->rowsToUpdate.append(row);
3048 if (!d->rowResizeTimer.isActive())
3049 d->rowResizeTimer.start(0ns, this);
3050}
3051
3052/*!
3053 This slot is called to change the width of the given \a column.
3054 The old width is specified by \a oldWidth, and the new width by \a
3055 newWidth.
3056
3057 \sa rowResized()
3058*/
3059void QTableView::columnResized(int column, int, int)
3060{
3061 Q_D(QTableView);
3062 d->columnsToUpdate.append(column);
3063 if (!d->columnResizeTimer.isActive())
3064 d->columnResizeTimer.start(0ns, this);
3065}
3066
3067/*!
3068 \reimp
3069 */
3070void QTableView::timerEvent(QTimerEvent *event)
3071{
3072 Q_D(QTableView);
3073
3074 if (event->id() == d->columnResizeTimer.id()) {
3075 const int oldScrollMax = horizontalScrollBar()->maximum();
3076 if (horizontalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3077 updateGeometries();
3078 d->columnResizeTimer.stop();
3079 } else {
3080 updateEditorGeometries();
3081 }
3082
3083 QRect rect;
3084 int viewportHeight = d->viewport->height();
3085 int viewportWidth = d->viewport->width();
3086 if (d->hasSpans() || horizontalScrollBar()->value() == oldScrollMax) {
3087 rect = QRect(0, 0, viewportWidth, viewportHeight);
3088 } else {
3089 for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) {
3090 int column = d->columnsToUpdate.at(i);
3091 int x = columnViewportPosition(column);
3092 if (isRightToLeft())
3093 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
3094 else
3095 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
3096 }
3097 }
3098
3099 d->viewport->update(rect.normalized());
3100 d->columnsToUpdate.clear();
3101 }
3102
3103 if (event->id() == d->rowResizeTimer.id()) {
3104 const int oldScrollMax = verticalScrollBar()->maximum();
3105 if (verticalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3106 updateGeometries();
3107 d->rowResizeTimer.stop();
3108 } else {
3109 updateEditorGeometries();
3110 }
3111
3112 int viewportHeight = d->viewport->height();
3113 int viewportWidth = d->viewport->width();
3114 int top;
3115 if (d->hasSpans() || verticalScrollBar()->value() == oldScrollMax) {
3116 top = 0;
3117 } else {
3118 top = viewportHeight;
3119 for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) {
3120 int y = rowViewportPosition(d->rowsToUpdate.at(i));
3121 top = qMin(top, y);
3122 }
3123 }
3124
3125 d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top));
3126 d->rowsToUpdate.clear();
3127 }
3128
3129 QAbstractItemView::timerEvent(event);
3130}
3131
3132#if QT_CONFIG(draganddrop)
3133/*! \reimp */
3134void QTableView::dropEvent(QDropEvent *event)
3135{
3136 Q_D(QTableView);
3137 if (event->source() == this && (event->dropAction() == Qt::MoveAction ||
3138 dragDropMode() == QAbstractItemView::InternalMove)) {
3139 QModelIndex topIndex;
3140 int col = -1;
3141 int row = -1;
3142 // check whether a subclass has already accepted the event, ie. moved the data
3143 if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && !topIndex.isValid() && col != -1) {
3144 // Drop between items (reordering) - can only happen with setDragDropOverwriteMode(false)
3145 const QModelIndexList indexes = selectedIndexes();
3146 QList<QPersistentModelIndex> persIndexes;
3147 persIndexes.reserve(indexes.size());
3148
3149 bool topIndexDropped = false;
3150 for (const auto &index : indexes) {
3151 // Reorder entire rows
3152 QPersistentModelIndex firstColIndex = index.siblingAtColumn(0);
3153 if (!persIndexes.contains(firstColIndex))
3154 persIndexes.append(firstColIndex);
3155 if (index.row() == topIndex.row()) {
3156 topIndexDropped = true;
3157 break;
3158 }
3159 }
3160 if (!topIndexDropped) {
3161 std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order.
3162
3163 QPersistentModelIndex dropRow = model()->index(row, col, topIndex);
3164
3165 int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row);
3166 bool dataMoved = false;
3167 for (const QPersistentModelIndex &pIndex : std::as_const(persIndexes)) {
3168 // only generate a move when not same row or behind itself
3169 if (r != pIndex.row() && r != pIndex.row() + 1) {
3170 // try to move (preserves selection)
3171 const bool moved = model()->moveRow(QModelIndex(), pIndex.row(), QModelIndex(), r);
3172 if (!moved)
3173 continue; // maybe it'll work for other rows
3174 dataMoved = true; // success
3175 } else {
3176 // move onto itself is blocked, don't delete anything
3177 dataMoved = true;
3178 }
3179 r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order.
3180 }
3181 if (dataMoved) {
3182 d->dropEventMoved = true;
3183 event->accept();
3184 }
3185 }
3186 }
3187 }
3188
3189 if (!event->isAccepted()) {
3190 // moveRows not implemented, fall back to default
3191 QAbstractItemView::dropEvent(event);
3192 }
3193}
3194#endif
3195
3196/*!
3197 This slot is called to change the index of the given \a row in the
3198 table view. The old index is specified by \a oldIndex, and the new
3199 index by \a newIndex.
3200
3201 \sa columnMoved()
3202*/
3203void QTableView::rowMoved(int row, int oldIndex, int newIndex)
3204{
3205 Q_UNUSED(row);
3206 Q_D(QTableView);
3207
3208 updateGeometries();
3209 int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex);
3210 int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex);
3211 if (d->hasSpans()) {
3212 d->viewport->update();
3213 } else {
3214 int oldTop = rowViewportPosition(logicalOldIndex);
3215 int newTop = rowViewportPosition(logicalNewIndex);
3216 int oldBottom = oldTop + rowHeight(logicalOldIndex);
3217 int newBottom = newTop + rowHeight(logicalNewIndex);
3218 int top = qMin(oldTop, newTop);
3219 int bottom = qMax(oldBottom, newBottom);
3220 int height = bottom - top;
3221 d->viewport->update(0, top, d->viewport->width(), height);
3222 }
3223}
3224
3225/*!
3226 This slot is called to change the index of the given \a column in
3227 the table view. The old index is specified by \a oldIndex, and
3228 the new index by \a newIndex.
3229
3230 \sa rowMoved()
3231*/
3232void QTableView::columnMoved(int column, int oldIndex, int newIndex)
3233{
3234 Q_UNUSED(column);
3235 Q_D(QTableView);
3236
3237 updateGeometries();
3238 int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex);
3239 int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex);
3240 if (d->hasSpans()) {
3241 d->viewport->update();
3242 } else {
3243 int oldLeft = columnViewportPosition(logicalOldIndex);
3244 int newLeft = columnViewportPosition(logicalNewIndex);
3245 int oldRight = oldLeft + columnWidth(logicalOldIndex);
3246 int newRight = newLeft + columnWidth(logicalNewIndex);
3247 int left = qMin(oldLeft, newLeft);
3248 int right = qMax(oldRight, newRight);
3249 int width = right - left;
3250 d->viewport->update(left, 0, width, d->viewport->height());
3251 }
3252}
3253
3254/*!
3255 Selects the given \a row in the table view if the current
3256 SelectionMode and SelectionBehavior allows rows to be selected.
3257
3258 \sa selectColumn()
3259*/
3260void QTableView::selectRow(int row)
3261{
3262 Q_D(QTableView);
3263 d->selectRow(row, true);
3264}
3265
3266/*!
3267 Selects the given \a column in the table view if the current
3268 SelectionMode and SelectionBehavior allows columns to be selected.
3269
3270 \sa selectRow()
3271*/
3272void QTableView::selectColumn(int column)
3273{
3274 Q_D(QTableView);
3275 d->selectColumn(column, true);
3276}
3277
3278/*!
3279 Hide the given \a row.
3280
3281 \sa showRow(), hideColumn()
3282*/
3283void QTableView::hideRow(int row)
3284{
3285 Q_D(QTableView);
3286 d->verticalHeader->hideSection(row);
3287}
3288
3289/*!
3290 Hide the given \a column.
3291
3292 \sa showColumn(), hideRow()
3293*/
3294void QTableView::hideColumn(int column)
3295{
3296 Q_D(QTableView);
3297 d->horizontalHeader->hideSection(column);
3298}
3299
3300/*!
3301 Show the given \a row.
3302
3303 \sa hideRow(), showColumn()
3304*/
3305void QTableView::showRow(int row)
3306{
3307 Q_D(QTableView);
3308 d->verticalHeader->showSection(row);
3309}
3310
3311/*!
3312 Show the given \a column.
3313
3314 \sa hideColumn(), showRow()
3315*/
3316void QTableView::showColumn(int column)
3317{
3318 Q_D(QTableView);
3319 d->horizontalHeader->showSection(column);
3320}
3321
3322/*!
3323 Resizes the given \a row based on the size hints of the delegate
3324 used to render each item in the row.
3325
3326 \sa resizeRowsToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3327*/
3328void QTableView::resizeRowToContents(int row)
3329{
3330 Q_D(QTableView);
3331 int content = sizeHintForRow(row);
3332 int header = d->verticalHeader->sectionSizeHint(row);
3333 d->verticalHeader->resizeSection(row, qMax(content, header));
3334}
3335
3336/*!
3337 Resizes all rows based on the size hints of the delegate
3338 used to render each item in the rows.
3339
3340 \sa resizeRowToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3341*/
3342void QTableView::resizeRowsToContents()
3343{
3344 Q_D(QTableView);
3345 d->verticalHeader->resizeSections(QHeaderView::ResizeToContents);
3346}
3347
3348/*!
3349 Resizes the given \a column based on the size hints of the delegate
3350 used to render each item in the column.
3351
3352 \note Only visible columns will be resized. Reimplement sizeHintForColumn()
3353 to resize hidden columns as well.
3354
3355 \sa resizeColumnsToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3356*/
3357void QTableView::resizeColumnToContents(int column)
3358{
3359 Q_D(QTableView);
3360 int content = sizeHintForColumn(column);
3361 int header = d->horizontalHeader->sectionSizeHint(column);
3362 d->horizontalHeader->resizeSection(column, qMax(content, header));
3363}
3364
3365/*!
3366 Resizes all columns based on the size hints of the delegate
3367 used to render each item in the columns.
3368
3369 \sa resizeColumnToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3370*/
3371void QTableView::resizeColumnsToContents()
3372{
3373 Q_D(QTableView);
3374 d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents);
3375}
3376
3377/*!
3378 Sorts the model by the values in the given \a column and \a order.
3379
3380 \a column may be -1, in which case no sort indicator will be shown
3381 and the model will return to its natural, unsorted order. Note that not
3382 all models support this and may even crash in this case.
3383
3384 \sa sortingEnabled
3385 */
3386void QTableView::sortByColumn(int column, Qt::SortOrder order)
3387{
3388 Q_D(QTableView);
3389 if (column < -1)
3390 return;
3391 d->horizontalHeader->setSortIndicator(column, order);
3392 // If sorting is not enabled or has the same order as before, force to sort now
3393 // else sorting will be trigger through sortIndicatorChanged()
3394 if (!d->sortingEnabled ||
3395 (d->horizontalHeader->sortIndicatorSection() == column && d->horizontalHeader->sortIndicatorOrder() == order))
3396 d->model->sort(column, order);
3397}
3398
3399/*!
3400 \internal
3401*/
3402void QTableView::verticalScrollbarAction(int action)
3403{
3404 QAbstractItemView::verticalScrollbarAction(action);
3405}
3406
3407/*!
3408 \internal
3409*/
3410void QTableView::horizontalScrollbarAction(int action)
3411{
3412 QAbstractItemView::horizontalScrollbarAction(action);
3413}
3414
3415/*!
3416 \reimp
3417*/
3418bool QTableView::isIndexHidden(const QModelIndex &index) const
3419{
3420 Q_D(const QTableView);
3421 Q_ASSERT(d->isIndexValid(index));
3422 if (isRowHidden(index.row()) || isColumnHidden(index.column()))
3423 return true;
3424 if (d->hasSpans()) {
3425 QSpanCollection::Span span = d->span(index.row(), index.column());
3426 return !((span.top() == index.row()) && (span.left() == index.column()));
3427 }
3428 return false;
3429}
3430
3431/*!
3432 \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
3433
3434 Sets the span of the table element at (\a row, \a column) to the number of
3435 rows and columns specified by (\a rowSpanCount, \a columnSpanCount).
3436
3437 \sa rowSpan(), columnSpan()
3438*/
3439void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan)
3440{
3441 Q_D(QTableView);
3442 if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0)
3443 return;
3444 d->setSpan(row, column, rowSpan, columnSpan);
3445 d->viewport->update();
3446}
3447
3448/*!
3449 Returns the row span of the table element at (\a row, \a column).
3450 The default is 1.
3451
3452 \sa setSpan(), columnSpan()
3453*/
3454int QTableView::rowSpan(int row, int column) const
3455{
3456 Q_D(const QTableView);
3457 return d->rowSpan(row, column);
3458}
3459
3460/*!
3461 Returns the column span of the table element at (\a row, \a
3462 column). The default is 1.
3463
3464 \sa setSpan(), rowSpan()
3465*/
3466int QTableView::columnSpan(int row, int column) const
3467{
3468 Q_D(const QTableView);
3469 return d->columnSpan(row, column);
3470}
3471
3472/*!
3473 Removes all row and column spans in the table view.
3474
3475 \sa setSpan()
3476*/
3477
3478void QTableView::clearSpans()
3479{
3480 Q_D(QTableView);
3481 d->spans.clear();
3482 d->viewport->update();
3483}
3484
3485void QTableViewPrivate::selectRow(int row, bool anchor)
3486{
3487 Q_Q(QTableView);
3488
3489 if (q->selectionBehavior() == QTableView::SelectColumns
3490 || (q->selectionMode() == QTableView::SingleSelection
3491 && q->selectionBehavior() == QTableView::SelectItems))
3492 return;
3493
3494 if (row >= 0 && row < model->rowCount(root)) {
3495 int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0);
3496 QModelIndex index = model->index(row, column, root);
3497 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3498
3499 {
3500 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3501 const auto startIndex = currentSelectionStartIndex;
3502 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3503 currentSelectionStartIndex = startIndex;
3504 }
3505
3506 if ((anchor && !(command & QItemSelectionModel::Current))
3507 || (q->selectionMode() == QTableView::SingleSelection))
3508 currentSelectionStartIndex = model->index(row, column, root);
3509
3510 if (q->selectionMode() != QTableView::SingleSelection
3511 && command.testFlag(QItemSelectionModel::Toggle)) {
3512 if (anchor)
3513 ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index)
3514 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3515 command &= ~QItemSelectionModel::Toggle;
3516 command |= ctrlDragSelectionFlag;
3517 if (!anchor)
3518 command |= QItemSelectionModel::Current;
3519 }
3520
3521 const auto rowSectionAnchor = currentSelectionStartIndex.row();
3522 QModelIndex upper = model->index(qMin(rowSectionAnchor, row), column, root);
3523 QModelIndex lower = model->index(qMax(rowSectionAnchor, row), column, root);
3524 if ((verticalHeader->sectionsMoved() && upper.row() != lower.row())) {
3525 q->setSelection(q->visualRect(upper) | q->visualRect(lower), command | QItemSelectionModel::Rows);
3526 } else {
3527 selectionModel->select(QItemSelection(upper, lower), command | QItemSelectionModel::Rows);
3528 }
3529 }
3530}
3531
3532void QTableViewPrivate::selectColumn(int column, bool anchor)
3533{
3534 Q_Q(QTableView);
3535
3536 if (q->selectionBehavior() == QTableView::SelectRows
3537 || (q->selectionMode() == QTableView::SingleSelection
3538 && q->selectionBehavior() == QTableView::SelectItems))
3539 return;
3540
3541 if (column >= 0 && column < model->columnCount(root)) {
3542 int row = verticalHeader->logicalIndexAt(0);
3543 QModelIndex index = model->index(row, column, root);
3544 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3545
3546 {
3547 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3548 const auto startIndex = currentSelectionStartIndex;
3549 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3550 currentSelectionStartIndex = startIndex;
3551 }
3552
3553 if ((anchor && !(command & QItemSelectionModel::Current))
3554 || (q->selectionMode() == QTableView::SingleSelection))
3555 currentSelectionStartIndex = model->index(row, column, root);
3556
3557 if (q->selectionMode() != QTableView::SingleSelection
3558 && command.testFlag(QItemSelectionModel::Toggle)) {
3559 if (anchor)
3560 ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns(row).contains(index)
3561 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3562 command &= ~QItemSelectionModel::Toggle;
3563 command |= ctrlDragSelectionFlag;
3564 if (!anchor)
3565 command |= QItemSelectionModel::Current;
3566 }
3567
3568 const auto columnSectionAnchor = currentSelectionStartIndex.column();
3569 QModelIndex left = model->index(row, qMin(columnSectionAnchor, column), root);
3570 QModelIndex right = model->index(row, qMax(columnSectionAnchor, column), root);
3571 if ((horizontalHeader->sectionsMoved() && left.column() != right.column())) {
3572 q->setSelection(q->visualRect(left) | q->visualRect(right), command | QItemSelectionModel::Columns);
3573 } else {
3574 selectionModel->select(QItemSelection(left, right), command | QItemSelectionModel::Columns);
3575 }
3576 }
3577}
3578
3579/*!
3580 \reimp
3581 */
3582void QTableView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3583{
3584#if QT_CONFIG(accessibility)
3585 if (QAccessible::isActive()) {
3586 if (current.isValid() && hasFocus()) {
3587 Q_D(QTableView);
3588 int entry = d->accessibleChildIndex(current);
3589 QAccessibleEvent event(this, QAccessible::Focus);
3590 event.setChild(entry);
3591 QAccessible::updateAccessibility(&event);
3592 }
3593 }
3594#endif
3595 QAbstractItemView::currentChanged(current, previous);
3596}
3597
3598/*!
3599 \reimp
3600 */
3601void QTableView::selectionChanged(const QItemSelection &selected,
3602 const QItemSelection &deselected)
3603{
3604 Q_D(QTableView);
3605 Q_UNUSED(d);
3606#if QT_CONFIG(accessibility)
3607 if (QAccessible::isActive()) {
3608 // ### does not work properly for selection ranges.
3609 QModelIndex sel = selected.indexes().value(0);
3610 if (sel.isValid()) {
3611 int entry = d->accessibleChildIndex(sel);
3612 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3613 event.setChild(entry);
3614 QAccessible::updateAccessibility(&event);
3615 }
3616 QModelIndex desel = deselected.indexes().value(0);
3617 if (desel.isValid()) {
3618 int entry = d->accessibleChildIndex(desel);
3619 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3620 event.setChild(entry);
3621 QAccessible::updateAccessibility(&event);
3622 }
3623 }
3624#endif
3625 QAbstractItemView::selectionChanged(selected, deselected);
3626}
3627
3628int QTableView::visualIndex(const QModelIndex &index) const
3629{
3630 return index.row();
3631}
3632
3633QT_END_NAMESPACE
3634
3635#include "qtableview.moc"
3636
3637#include "moc_qtableview.cpp"