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#ifdef QT_KEYPAD_NAVIGATION
1838 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow == 0)
1839 visualRow = d->visualRow(model()->rowCount() - 1) + 1;
1840 // FIXME? visualRow = bottom + 1;
1841#endif
1842 int r = d->logicalRow(visualRow);
1843 int c = d->logicalColumn(visualColumn);
1844 if (r != -1 && d->hasSpans()) {
1845 QSpanCollection::Span span = d->span(r, c);
1846 if (span.width() > 1 || span.height() > 1)
1847 visualRow = d->visualRow(span.top());
1848 }
1849 while (visualRow >= 0) {
1850 --visualRow;
1851 r = d->logicalRow(visualRow);
1852 c = d->logicalColumn(visualColumn);
1853 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1854 break;
1855 }
1856 if (visualRow < 0)
1857 visualRow = originalRow;
1858 break;
1859 }
1860 case MoveDown: {
1861 int originalRow = visualRow;
1862 if (d->hasSpans()) {
1863 QSpanCollection::Span span = d->span(current.row(), current.column());
1864 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1865 }
1866#ifdef QT_KEYPAD_NAVIGATION
1867 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow >= bottom)
1868 visualRow = -1;
1869#endif
1870 int r = d->logicalRow(visualRow);
1871 int c = d->logicalColumn(visualColumn);
1872 if (r != -1 && d->hasSpans()) {
1873 QSpanCollection::Span span = d->span(r, c);
1874 if (span.width() > 1 || span.height() > 1)
1875 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1876 }
1877 while (visualRow <= bottom) {
1878 ++visualRow;
1879 r = d->logicalRow(visualRow);
1880 c = d->logicalColumn(visualColumn);
1881 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1882 break;
1883 }
1884 if (visualRow > bottom)
1885 visualRow = originalRow;
1886 break;
1887 }
1888 case MovePrevious:
1889 case MoveLeft: {
1890 int originalRow = visualRow;
1891 int originalColumn = visualColumn;
1892 bool firstTime = true;
1893 bool looped = false;
1894 bool wrapped = false;
1895 do {
1896 int r = d->logicalRow(visualRow);
1897 int c = d->logicalColumn(visualColumn);
1898 if (firstTime && c != -1 && d->hasSpans()) {
1899 firstTime = false;
1900 QSpanCollection::Span span = d->span(r, c);
1901 if (span.width() > 1 || span.height() > 1)
1902 visualColumn = d->visualColumn(span.left());
1903 }
1904 while (visualColumn >= 0) {
1905 --visualColumn;
1906 r = d->logicalRow(visualRow);
1907 c = d->logicalColumn(visualColumn);
1908 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1909 break;
1910 if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) {
1911 looped = true;
1912 break;
1913 }
1914 }
1915 if (cursorAction == MoveLeft || visualColumn >= 0)
1916 break;
1917 visualColumn = right + 1;
1918 if (visualRow == 0) {
1919 wrapped = true;
1920 visualRow = bottom;
1921 } else {
1922 --visualRow;
1923 }
1924 } while (!looped);
1925 if (visualColumn < 0)
1926 visualColumn = originalColumn;
1927 break;
1928 }
1929 case MoveNext:
1930 case MoveRight: {
1931 int originalRow = visualRow;
1932 int originalColumn = visualColumn;
1933 bool firstTime = true;
1934 bool looped = false;
1935 bool wrapped = false;
1936 do {
1937 int r = d->logicalRow(visualRow);
1938 int c = d->logicalColumn(visualColumn);
1939 if (firstTime && c != -1 && d->hasSpans()) {
1940 firstTime = false;
1941 QSpanCollection::Span span = d->span(r, c);
1942 if (span.width() > 1 || span.height() > 1)
1943 visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
1944 }
1945 while (visualColumn <= right) {
1946 ++visualColumn;
1947 r = d->logicalRow(visualRow);
1948 c = d->logicalColumn(visualColumn);
1949 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1950 break;
1951 if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) {
1952 looped = true;
1953 break;
1954 }
1955 }
1956 if (cursorAction == MoveRight || visualColumn <= right)
1957 break;
1958 visualColumn = -1;
1959 if (visualRow == bottom) {
1960 wrapped = true;
1961 visualRow = 0;
1962 } else {
1963 ++visualRow;
1964 }
1965 } while (!looped);
1966 if (visualColumn > right)
1967 visualColumn = originalColumn;
1968 break;
1969 }
1970 case MoveHome:
1971 visualColumn = d->nextActiveVisualColumn(visualRow, 0, right,
1972 QTableViewPrivate::SearchDirection::Increasing);
1973 if (modifiers & Qt::ControlModifier)
1974 visualRow = d->nextActiveVisualRow(0, visualColumn, bottom,
1975 QTableViewPrivate::SearchDirection::Increasing);
1976 break;
1977 case MoveEnd:
1978 visualColumn = d->nextActiveVisualColumn(visualRow, right, -1,
1979 QTableViewPrivate::SearchDirection::Decreasing);
1980 if (modifiers & Qt::ControlModifier)
1981 visualRow = d->nextActiveVisualRow(bottom, visualColumn, -1,
1982 QTableViewPrivate::SearchDirection::Decreasing);
1983 break;
1984 case MovePageUp: {
1985 int newLogicalRow = rowAt(visualRect(current).bottom() - d->viewport->height());
1986 int visualRow = (newLogicalRow == -1 ? 0 : d->visualRow(newLogicalRow));
1987 visualRow = d->nextActiveVisualRow(visualRow, current.column(), bottom,
1988 QTableViewPrivate::SearchDirection::Increasing);
1989 newLogicalRow = d->logicalRow(visualRow);
1990 return d->model->index(newLogicalRow, current.column(), d->root);
1991 }
1992 case MovePageDown: {
1993 int newLogicalRow = rowAt(visualRect(current).top() + d->viewport->height());
1994 int visualRow = (newLogicalRow == -1 ? bottom : d->visualRow(newLogicalRow));
1995 visualRow = d->nextActiveVisualRow(visualRow, current.column(), -1,
1996 QTableViewPrivate::SearchDirection::Decreasing);
1997 newLogicalRow = d->logicalRow(visualRow);
1998 return d->model->index(newLogicalRow, current.column(), d->root);
1999 }}
2000
2001 d->visualCursor = QPoint(visualColumn, visualRow);
2002 int logicalRow = d->logicalRow(visualRow);
2003 int logicalColumn = d->logicalColumn(visualColumn);
2004 if (!d->model->hasIndex(logicalRow, logicalColumn, d->root))
2005 return QModelIndex();
2006
2007 QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root);
2008 if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result)) {
2009 if (d->hasSpans()) {
2010 QSpanCollection::Span span = d->span(result.row(), result.column());
2011 if (span.width() > 1 || span.height() > 1) {
2012 result = d->model->sibling(span.top(), span.left(), result);
2013 }
2014 }
2015 return result;
2016 }
2017
2018 return QModelIndex();
2019}
2020
2021/*!
2022 \fn void QTableView::setSelection(const QRect &rect,
2023 QItemSelectionModel::SelectionFlags flags)
2024
2025 Selects the items within the given \a rect and in accordance with
2026 the specified selection \a flags.
2027*/
2028void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
2029{
2030 Q_D(QTableView);
2031 QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right())
2032 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())));
2033 QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) :
2034 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())));
2035 if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
2036 return;
2037
2038 const bool verticalMoved = verticalHeader()->sectionsMoved();
2039 const bool horizontalMoved = horizontalHeader()->sectionsMoved();
2040
2041 QItemSelection selection;
2042 int top = tl.row();
2043 int bottom = br.row();
2044 int left = tl.column();
2045 int right = br.column();
2046
2047 const auto updateVisualIndices = [&]() {
2048 if (verticalMoved && horizontalMoved) {
2049 top = d->visualRow(tl.row());
2050 bottom = d->visualRow(br.row());
2051 left = d->visualColumn(tl.column());
2052 right = d->visualColumn(br.column());
2053 } else if (horizontalMoved) {
2054 top = tl.row();
2055 bottom = br.row();
2056 left = d->visualColumn(tl.column());
2057 right = d->visualColumn(br.column());
2058 } else if (verticalMoved) {
2059 top = d->visualRow(tl.row());
2060 bottom = d->visualRow(br.row());
2061 left = tl.column();
2062 right = br.column();
2063 }
2064 };
2065
2066 if (d->hasSpans()) {
2067 bool expanded;
2068 // when the current selection does not intersect with any spans of merged cells,
2069 // the range of selected cells must be the same as if there were no merged cells
2070 bool intersectsSpan = false;
2071 top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
2072 left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2073 bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
2074 right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
2075 do {
2076 expanded = false;
2077 for (QSpanCollection::Span *it : d->spans.spans) {
2078 const QSpanCollection::Span &span = *it;
2079 const int t = d->visualRow(span.top());
2080 const int l = d->visualColumn(span.left());
2081 const int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
2082 const int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
2083 if ((t > bottom) || (l > right) || (top > b) || (left > r))
2084 continue; // no intersect
2085 intersectsSpan = true;
2086 if (t < top) {
2087 top = t;
2088 expanded = true;
2089 }
2090 if (l < left) {
2091 left = l;
2092 expanded = true;
2093 }
2094 if (b > bottom) {
2095 bottom = b;
2096 expanded = true;
2097 }
2098 if (r > right) {
2099 right = r;
2100 expanded = true;
2101 }
2102 if (expanded)
2103 break;
2104 }
2105 } while (expanded);
2106 if (!intersectsSpan) {
2107 updateVisualIndices();
2108 } else if (!verticalMoved && !horizontalMoved) {
2109 // top/left/bottom/right are visual, update indexes
2110 tl = d->model->index(top, left, d->root);
2111 br = d->model->index(bottom, right, d->root);
2112 }
2113 } else {
2114 updateVisualIndices();
2115 }
2116
2117 if (horizontalMoved && verticalMoved) {
2118 selection.reserve((right - left + 1) * (bottom - top + 1));
2119 for (int horizontal = left; horizontal <= right; ++horizontal) {
2120 int column = d->logicalColumn(horizontal);
2121 for (int vertical = top; vertical <= bottom; ++vertical) {
2122 int row = d->logicalRow(vertical);
2123 QModelIndex index = d->model->index(row, column, d->root);
2124 selection.append(QItemSelectionRange(index));
2125 }
2126 }
2127 } else if (horizontalMoved) {
2128 selection.reserve(right - left + 1);
2129 for (int visual = left; visual <= right; ++visual) {
2130 int column = d->logicalColumn(visual);
2131 QModelIndex topLeft = d->model->index(top, column, d->root);
2132 if (top == bottom)
2133 selection.append(QItemSelectionRange(topLeft));
2134 else
2135 selection.append({ topLeft, topLeft.siblingAtRow(bottom) });
2136 }
2137 } else if (verticalMoved) {
2138 selection.reserve(bottom - top + 1);
2139 for (int visual = top; visual <= bottom; ++visual) {
2140 int row = d->logicalRow(visual);
2141 QModelIndex topLeft = d->model->index(row, left, d->root);
2142 if (left == right)
2143 selection.append(QItemSelectionRange(topLeft));
2144 else
2145 selection.append({ topLeft, topLeft.siblingAtColumn(right) });
2146 }
2147 } else { // nothing moved
2148 QItemSelectionRange range(tl, br);
2149 if (!range.isEmpty())
2150 selection.append(range);
2151 }
2152
2153 d->selectionModel->select(selection, command);
2154}
2155
2156/*!
2157 \reimp
2158
2159 Returns the rectangle from the viewport of the items in the given
2160 \a selection.
2161
2162 Since 4.7, the returned region only contains rectangles intersecting
2163 (or included in) the viewport.
2164*/
2165QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const
2166{
2167 Q_D(const QTableView);
2168
2169 if (selection.isEmpty())
2170 return QRegion();
2171
2172 QRegion selectionRegion;
2173 const QRect &viewportRect = d->viewport->rect();
2174 bool verticalMoved = verticalHeader()->sectionsMoved();
2175 bool horizontalMoved = horizontalHeader()->sectionsMoved();
2176
2177 if ((verticalMoved && horizontalMoved) || (d->hasSpans() && (verticalMoved || horizontalMoved))) {
2178 for (const auto &range : selection) {
2179 if (range.parent() != d->root || !range.isValid())
2180 continue;
2181 for (int r = range.top(); r <= range.bottom(); ++r)
2182 for (int c = range.left(); c <= range.right(); ++c) {
2183 const QRect &rangeRect = visualRect(d->model->index(r, c, d->root));
2184 if (viewportRect.intersects(rangeRect))
2185 selectionRegion += rangeRect;
2186 }
2187 }
2188 } else if (horizontalMoved) {
2189 for (const auto &range : selection) {
2190 if (range.parent() != d->root || !range.isValid())
2191 continue;
2192 int top = rowViewportPosition(range.top());
2193 int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2194 if (top > bottom)
2195 qSwap<int>(top, bottom);
2196 int height = bottom - top;
2197 for (int c = range.left(); c <= range.right(); ++c) {
2198 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2199 if (viewportRect.intersects(rangeRect))
2200 selectionRegion += rangeRect;
2201 }
2202 }
2203 } else if (verticalMoved) {
2204 for (const auto &range : selection) {
2205 if (range.parent() != d->root || !range.isValid())
2206 continue;
2207 int left = columnViewportPosition(range.left());
2208 int right = columnViewportPosition(range.right()) + columnWidth(range.right());
2209 if (left > right)
2210 qSwap<int>(left, right);
2211 int width = right - left;
2212 for (int r = range.top(); r <= range.bottom(); ++r) {
2213 const QRect rangeRect(left, rowViewportPosition(r), width, rowHeight(r));
2214 if (viewportRect.intersects(rangeRect))
2215 selectionRegion += rangeRect;
2216 }
2217 }
2218 } else { // nothing moved
2219 const int gridAdjust = showGrid() ? 1 : 0;
2220 for (auto range : selection) {
2221 if (range.parent() != d->root || !range.isValid())
2222 continue;
2223 d->trimHiddenSelections(&range);
2224
2225 const int rtop = rowViewportPosition(range.top());
2226 const int rbottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2227 int rleft;
2228 int rright;
2229 if (isLeftToRight()) {
2230 rleft = columnViewportPosition(range.left());
2231 rright = columnViewportPosition(range.right()) + columnWidth(range.right());
2232 } else {
2233 rleft = columnViewportPosition(range.right());
2234 rright = columnViewportPosition(range.left()) + columnWidth(range.left());
2235 }
2236 const QRect rangeRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust));
2237 if (viewportRect.intersects(rangeRect))
2238 selectionRegion += rangeRect;
2239 if (d->hasSpans()) {
2240 const auto spansInRect = d->spans.spansInRect(range.left(), range.top(), range.width(), range.height());
2241 for (QSpanCollection::Span *s : spansInRect) {
2242 if (range.contains(s->top(), s->left(), range.parent())) {
2243 const QRect &visualSpanRect = d->visualSpanRect(*s);
2244 if (viewportRect.intersects(visualSpanRect))
2245 selectionRegion += visualSpanRect;
2246 }
2247 }
2248 }
2249 }
2250 }
2251
2252 return selectionRegion;
2253}
2254
2255
2256/*!
2257 \reimp
2258*/
2259QModelIndexList QTableView::selectedIndexes() const
2260{
2261 Q_D(const QTableView);
2262 QModelIndexList viewSelected;
2263 QModelIndexList modelSelected;
2264 if (d->selectionModel)
2265 modelSelected = d->selectionModel->selectedIndexes();
2266 for (int i = 0; i < modelSelected.size(); ++i) {
2267 QModelIndex index = modelSelected.at(i);
2268 if (!isIndexHidden(index) && index.parent() == d->root)
2269 viewSelected.append(index);
2270 }
2271 return viewSelected;
2272}
2273
2274
2275/*!
2276 This slot is called whenever rows are added or deleted. The
2277 previous number of rows is specified by \a oldCount, and the new
2278 number of rows is specified by \a newCount.
2279*/
2280void QTableView::rowCountChanged(int oldCount, int newCount )
2281{
2282 Q_D(QTableView);
2283 //when removing rows, we need to disable updates for the header until the geometries have been
2284 //updated and the offset has been adjusted, or we risk calling paintSection for all the sections
2285 if (newCount < oldCount)
2286 d->verticalHeader->setUpdatesEnabled(false);
2287 d->doDelayedItemsLayout();
2288}
2289
2290/*!
2291 This slot is called whenever columns are added or deleted. The
2292 previous number of columns is specified by \a oldCount, and the new
2293 number of columns is specified by \a newCount.
2294*/
2295void QTableView::columnCountChanged(int, int)
2296{
2297 Q_D(QTableView);
2298 updateGeometries();
2299 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
2300 d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value());
2301 else
2302 d->horizontalHeader->setOffset(horizontalScrollBar()->value());
2303 d->viewport->update();
2304}
2305
2306/*!
2307 \reimp
2308*/
2309void QTableView::updateGeometries()
2310{
2311 Q_D(QTableView);
2312 if (d->geometryRecursionBlock)
2313 return;
2314 d->geometryRecursionBlock = true;
2315
2316 int width = 0;
2317 if (!d->verticalHeader->isHidden()) {
2318 width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width());
2319 width = qMin(width, d->verticalHeader->maximumWidth());
2320 }
2321 int height = 0;
2322 if (!d->horizontalHeader->isHidden()) {
2323 height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height());
2324 height = qMin(height, d->horizontalHeader->maximumHeight());
2325 }
2326 bool reverse = isRightToLeft();
2327 if (reverse)
2328 setViewportMargins(0, height, width, 0);
2329 else
2330 setViewportMargins(width, height, 0, 0);
2331
2332 // update headers
2333
2334 QRect vg = d->viewport->geometry();
2335
2336 int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width);
2337 d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height());
2338 if (d->verticalHeader->isHidden())
2339 QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries");
2340
2341 int horizontalTop = vg.top() - height;
2342 d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height);
2343 if (d->horizontalHeader->isHidden())
2344 QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries");
2345
2346#if QT_CONFIG(abstractbutton)
2347 // update cornerWidget
2348 if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) {
2349 d->cornerWidget->setHidden(true);
2350 } else {
2351 d->cornerWidget->setHidden(false);
2352 d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height);
2353 }
2354#endif
2355
2356 // update scroll bars
2357
2358 // ### move this block into the if
2359 QSize vsize = d->viewport->size();
2360 QSize max = maximumViewportSize();
2361 const int horizontalLength = d->horizontalHeader->length();
2362 const int verticalLength = d->verticalHeader->length();
2363 if (max.width() >= horizontalLength && max.height() >= verticalLength)
2364 vsize = max;
2365
2366 // horizontal scroll bar
2367 const int columnCount = d->horizontalHeader->count();
2368 const int viewportWidth = vsize.width();
2369 int columnsInViewport = 0;
2370 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
2371 int logical = d->horizontalHeader->logicalIndex(column);
2372 if (!d->horizontalHeader->isSectionHidden(logical)) {
2373 width += d->horizontalHeader->sectionSize(logical);
2374 if (width > viewportWidth)
2375 break;
2376 ++columnsInViewport;
2377 }
2378 }
2379 columnsInViewport = qMax(columnsInViewport, 1); //there must be always at least 1 column
2380
2381 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2382 const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount();
2383 horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport);
2384 horizontalScrollBar()->setPageStep(columnsInViewport);
2385 if (columnsInViewport >= visibleColumns)
2386 d->horizontalHeader->setOffset(0);
2387 horizontalScrollBar()->setSingleStep(1);
2388 } else { // ScrollPerPixel
2389 horizontalScrollBar()->setPageStep(vsize.width());
2390 horizontalScrollBar()->setRange(0, horizontalLength - vsize.width());
2391 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2));
2392 }
2393
2394 // vertical scroll bar
2395 const int rowCount = d->verticalHeader->count();
2396 const int viewportHeight = vsize.height();
2397 int rowsInViewport = 0;
2398 for (int height = 0, row = rowCount - 1; row >= 0; --row) {
2399 int logical = d->verticalHeader->logicalIndex(row);
2400 if (!d->verticalHeader->isSectionHidden(logical)) {
2401 height += d->verticalHeader->sectionSize(logical);
2402 if (height > viewportHeight)
2403 break;
2404 ++rowsInViewport;
2405 }
2406 }
2407 rowsInViewport = qMax(rowsInViewport, 1); //there must be always at least 1 row
2408
2409 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2410 const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount();
2411 verticalScrollBar()->setRange(0, visibleRows - rowsInViewport);
2412 verticalScrollBar()->setPageStep(rowsInViewport);
2413 if (rowsInViewport >= visibleRows)
2414 d->verticalHeader->setOffset(0);
2415 verticalScrollBar()->setSingleStep(1);
2416 } else { // ScrollPerPixel
2417 verticalScrollBar()->setPageStep(vsize.height());
2418 verticalScrollBar()->setRange(0, verticalLength - vsize.height());
2419 verticalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2));
2420 }
2421 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
2422
2423 d->geometryRecursionBlock = false;
2424 QAbstractItemView::updateGeometries();
2425}
2426
2427/*!
2428 Returns the size hint for the given \a row's height or -1 if there
2429 is no model.
2430
2431 If you need to set the height of a given row to a fixed value, call
2432 QHeaderView::resizeSection() on the table's vertical header.
2433
2434 If you reimplement this function in a subclass, note that the value you
2435 return is only used when resizeRowToContents() is called. In that case,
2436 if a larger row height is required by either the vertical header or
2437 the item delegate, that width will be used instead.
2438
2439 \sa QWidget::sizeHint, verticalHeader(), QHeaderView::resizeContentsPrecision()
2440*/
2441int QTableView::sizeHintForRow(int row) const
2442{
2443 Q_D(const QTableView);
2444
2445 if (!model())
2446 return -1;
2447
2448 ensurePolished();
2449 const int maximumProcessCols = d->verticalHeader->resizeContentsPrecision();
2450
2451
2452 int left = qMax(0, d->horizontalHeader->visualIndexAt(0));
2453 int right = d->horizontalHeader->visualIndexAt(d->viewport->width());
2454 if (right == -1) // the table don't have enough columns to fill the viewport
2455 right = d->model->columnCount(d->root) - 1;
2456
2457 QStyleOptionViewItem option;
2458 initViewItemOption(&option);
2459
2460 int hint = 0;
2461 QModelIndex index;
2462 int columnsProcessed = 0;
2463 int column = left;
2464 for (; column <= right; ++column) {
2465 int logicalColumn = d->horizontalHeader->logicalIndex(column);
2466 if (d->horizontalHeader->isSectionHidden(logicalColumn))
2467 continue;
2468 index = d->model->index(row, logicalColumn, d->root);
2469 hint = d->heightHintForIndex(index, hint, option);
2470
2471 ++columnsProcessed;
2472 if (columnsProcessed == maximumProcessCols)
2473 break;
2474 }
2475
2476 const int actualRight = d->model->columnCount(d->root) - 1;
2477 int idxLeft = left;
2478 int idxRight = column - 1;
2479
2480 if (maximumProcessCols == 0 || actualRight < idxLeft)
2481 columnsProcessed = maximumProcessCols; // skip the while loop
2482
2483 while (columnsProcessed != maximumProcessCols && (idxLeft > 0 || idxRight < actualRight)) {
2484 int logicalIdx = -1;
2485
2486 if ((columnsProcessed % 2 && idxLeft > 0) || idxRight == actualRight) {
2487 while (idxLeft > 0) {
2488 --idxLeft;
2489 int logcol = d->horizontalHeader->logicalIndex(idxLeft);
2490 if (d->horizontalHeader->isSectionHidden(logcol))
2491 continue;
2492 logicalIdx = logcol;
2493 break;
2494 }
2495 } else {
2496 while (idxRight < actualRight) {
2497 ++idxRight;
2498 int logcol = d->horizontalHeader->logicalIndex(idxRight);
2499 if (d->horizontalHeader->isSectionHidden(logcol))
2500 continue;
2501 logicalIdx = logcol;
2502 break;
2503 }
2504 }
2505 if (logicalIdx >= 0) {
2506 index = d->model->index(row, logicalIdx, d->root);
2507 hint = d->heightHintForIndex(index, hint, option);
2508 }
2509 ++columnsProcessed;
2510 }
2511
2512 return d->showGrid ? hint + 1 : hint;
2513}
2514
2515/*!
2516 Returns the size hint for the given \a column's width or -1 if
2517 there is no model.
2518
2519 If you need to set the width of a given column to a fixed value, call
2520 QHeaderView::resizeSection() on the table's horizontal header.
2521
2522 If you reimplement this function in a subclass, note that the value you
2523 return will be used when resizeColumnToContents() or
2524 QHeaderView::resizeSections() is called. If a larger column width is
2525 required by either the horizontal header or the item delegate, the larger
2526 width will be used instead.
2527
2528 \sa QWidget::sizeHint, horizontalHeader(), QHeaderView::resizeContentsPrecision()
2529*/
2530int QTableView::sizeHintForColumn(int column) const
2531{
2532 Q_D(const QTableView);
2533
2534 if (!model())
2535 return -1;
2536
2537 ensurePolished();
2538 const int maximumProcessRows = d->horizontalHeader->resizeContentsPrecision();
2539
2540 int top = qMax(0, d->verticalHeader->visualIndexAt(0));
2541 int bottom = d->verticalHeader->visualIndexAt(d->viewport->height());
2542 if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport
2543 bottom = d->model->rowCount(d->root) - 1;
2544
2545 QStyleOptionViewItem option;
2546 initViewItemOption(&option);
2547
2548 int hint = 0;
2549 int rowsProcessed = 0;
2550 QModelIndex index;
2551 int row = top;
2552 for (; row <= bottom; ++row) {
2553 int logicalRow = d->verticalHeader->logicalIndex(row);
2554 if (d->verticalHeader->isSectionHidden(logicalRow))
2555 continue;
2556 index = d->model->index(logicalRow, column, d->root);
2557
2558 hint = d->widthHintForIndex(index, hint, option);
2559 ++rowsProcessed;
2560 if (rowsProcessed == maximumProcessRows)
2561 break;
2562 }
2563
2564 const int actualBottom = d->model->rowCount(d->root) - 1;
2565 int idxTop = top;
2566 int idxBottom = row - 1;
2567
2568 if (maximumProcessRows == 0 || actualBottom < idxTop)
2569 rowsProcessed = maximumProcessRows; // skip the while loop
2570
2571 while (rowsProcessed != maximumProcessRows && (idxTop > 0 || idxBottom < actualBottom)) {
2572 int logicalIdx = -1;
2573
2574 if ((rowsProcessed % 2 && idxTop > 0) || idxBottom == actualBottom) {
2575 while (idxTop > 0) {
2576 --idxTop;
2577 int logrow = d->verticalHeader->logicalIndex(idxTop);
2578 if (d->verticalHeader->isSectionHidden(logrow))
2579 continue;
2580 logicalIdx = logrow;
2581 break;
2582 }
2583 } else {
2584 while (idxBottom < actualBottom) {
2585 ++idxBottom;
2586 int logrow = d->verticalHeader->logicalIndex(idxBottom);
2587 if (d->verticalHeader->isSectionHidden(logrow))
2588 continue;
2589 logicalIdx = logrow;
2590 break;
2591 }
2592 }
2593 if (logicalIdx >= 0) {
2594 index = d->model->index(logicalIdx, column, d->root);
2595 hint = d->widthHintForIndex(index, hint, option);
2596 }
2597 ++rowsProcessed;
2598 }
2599
2600 return d->showGrid ? hint + 1 : hint;
2601}
2602
2603/*!
2604 Returns the y-coordinate in contents coordinates of the given \a
2605 row.
2606*/
2607int QTableView::rowViewportPosition(int row) const
2608{
2609 Q_D(const QTableView);
2610 return d->verticalHeader->sectionViewportPosition(row);
2611}
2612
2613/*!
2614 Returns the row in which the given y-coordinate, \a y, in contents
2615 coordinates is located.
2616
2617 \note This function returns -1 if the given coordinate is not valid
2618 (has no row).
2619
2620 \sa columnAt()
2621*/
2622int QTableView::rowAt(int y) const
2623{
2624 Q_D(const QTableView);
2625 return d->verticalHeader->logicalIndexAt(y);
2626}
2627
2628/*!
2629 Sets the height of the given \a row to be \a height.
2630*/
2631void QTableView::setRowHeight(int row, int height)
2632{
2633 Q_D(const QTableView);
2634 d->verticalHeader->resizeSection(row, height);
2635}
2636
2637/*!
2638 Returns the height of the given \a row.
2639
2640 \sa resizeRowToContents(), columnWidth()
2641*/
2642int QTableView::rowHeight(int row) const
2643{
2644 Q_D(const QTableView);
2645 return d->verticalHeader->sectionSize(row);
2646}
2647
2648/*!
2649 Returns the x-coordinate in contents coordinates of the given \a
2650 column.
2651*/
2652int QTableView::columnViewportPosition(int column) const
2653{
2654 Q_D(const QTableView);
2655 return d->horizontalHeader->sectionViewportPosition(column);
2656}
2657
2658/*!
2659 Returns the column in which the given x-coordinate, \a x, in contents
2660 coordinates is located.
2661
2662 \note This function returns -1 if the given coordinate is not valid
2663 (has no column).
2664
2665 \sa rowAt()
2666*/
2667int QTableView::columnAt(int x) const
2668{
2669 Q_D(const QTableView);
2670 return d->horizontalHeader->logicalIndexAt(x);
2671}
2672
2673/*!
2674 Sets the width of the given \a column to be \a width.
2675*/
2676void QTableView::setColumnWidth(int column, int width)
2677{
2678 Q_D(const QTableView);
2679 d->horizontalHeader->resizeSection(column, width);
2680}
2681
2682/*!
2683 Returns the width of the given \a column.
2684
2685 \sa resizeColumnToContents(), rowHeight()
2686*/
2687int QTableView::columnWidth(int column) const
2688{
2689 Q_D(const QTableView);
2690 return d->horizontalHeader->sectionSize(column);
2691}
2692
2693/*!
2694 Returns \c true if the given \a row is hidden; otherwise returns \c false.
2695
2696 \sa isColumnHidden()
2697*/
2698bool QTableView::isRowHidden(int row) const
2699{
2700 Q_D(const QTableView);
2701 return d->verticalHeader->isSectionHidden(row);
2702}
2703
2704/*!
2705 If \a hide is true \a row will be hidden, otherwise it will be shown.
2706
2707 \sa setColumnHidden()
2708*/
2709void QTableView::setRowHidden(int row, bool hide)
2710{
2711 Q_D(QTableView);
2712 if (row < 0 || row >= d->verticalHeader->count())
2713 return;
2714 d->verticalHeader->setSectionHidden(row, hide);
2715}
2716
2717/*!
2718 Returns \c true if the given \a column is hidden; otherwise returns \c false.
2719
2720 \sa isRowHidden()
2721*/
2722bool QTableView::isColumnHidden(int column) const
2723{
2724 Q_D(const QTableView);
2725 return d->horizontalHeader->isSectionHidden(column);
2726}
2727
2728/*!
2729 If \a hide is true the given \a column will be hidden; otherwise it
2730 will be shown.
2731
2732 \sa setRowHidden()
2733*/
2734void QTableView::setColumnHidden(int column, bool hide)
2735{
2736 Q_D(QTableView);
2737 if (column < 0 || column >= d->horizontalHeader->count())
2738 return;
2739 d->horizontalHeader->setSectionHidden(column, hide);
2740}
2741
2742/*!
2743 \property QTableView::sortingEnabled
2744 \brief whether sorting is enabled
2745
2746 If this property is \c true, sorting is enabled for the table. If
2747 this property is \c false, sorting is not enabled. The default value
2748 is false.
2749
2750 \note. Setting the property to true with setSortingEnabled()
2751 immediately triggers a call to sortByColumn() with the current
2752 sort section and order.
2753
2754 \sa sortByColumn()
2755*/
2756
2757/*!
2758 If \a enable is true, enables sorting for the table and immediately
2759 trigger a call to sortByColumn() with the current sort section and
2760 order
2761 */
2762void QTableView::setSortingEnabled(bool enable)
2763{
2764 Q_D(QTableView);
2765 horizontalHeader()->setSortIndicatorShown(enable);
2766 for (QMetaObject::Connection &connection : d->dynHorHeaderConnections)
2767 disconnect(connection);
2768 if (enable) {
2769 //sortByColumn has to be called before we connect or set the sortingEnabled flag
2770 // because otherwise it will not call sort on the model.
2771 sortByColumn(d->horizontalHeader->sortIndicatorSection(),
2772 d->horizontalHeader->sortIndicatorOrder());
2773 d->dynHorHeaderConnections = {
2774 QObjectPrivate::connect(d->horizontalHeader, &QHeaderView::sortIndicatorChanged,
2775 d, &QTableViewPrivate::sortIndicatorChanged)
2776 };
2777 } else {
2778 d->dynHorHeaderConnections = {
2779 connect(d->horizontalHeader, &QHeaderView::sectionPressed,
2780 this, &QTableView::selectColumn),
2781 connect(d->horizontalHeader, &QHeaderView::sectionEntered,
2782 this, [d](int column) {d->selectColumn(column, false); })
2783 };
2784 }
2785 d->sortingEnabled = enable;
2786}
2787
2788bool QTableView::isSortingEnabled() const
2789{
2790 Q_D(const QTableView);
2791 return d->sortingEnabled;
2792}
2793
2794/*!
2795 \property QTableView::showGrid
2796 \brief whether the grid is shown
2797
2798 If this property is \c true a grid is drawn for the table; if the
2799 property is \c false, no grid is drawn. The default value is true.
2800*/
2801bool QTableView::showGrid() const
2802{
2803 Q_D(const QTableView);
2804 return d->showGrid;
2805}
2806
2807void QTableView::setShowGrid(bool show)
2808{
2809 Q_D(QTableView);
2810 if (d->showGrid != show) {
2811 d->showGrid = show;
2812 d->viewport->update();
2813 }
2814}
2815
2816/*!
2817 \property QTableView::gridStyle
2818 \brief the pen style used to draw the grid.
2819
2820 This property holds the style used when drawing the grid (see \l{showGrid}).
2821*/
2822Qt::PenStyle QTableView::gridStyle() const
2823{
2824 Q_D(const QTableView);
2825 return d->gridStyle;
2826}
2827
2828void QTableView::setGridStyle(Qt::PenStyle style)
2829{
2830 Q_D(QTableView);
2831 if (d->gridStyle != style) {
2832 d->gridStyle = style;
2833 d->viewport->update();
2834 }
2835}
2836
2837/*!
2838 \property QTableView::wordWrap
2839 \brief the item text word-wrapping policy
2840
2841 If this property is \c true then the item text is wrapped where
2842 necessary at word-breaks; otherwise it is not wrapped at all.
2843 This property is \c true by default.
2844
2845 Note that even if wrapping is enabled, the cell will not be
2846 expanded to fit all text. Ellipsis will be inserted according to
2847 the current \l{QAbstractItemView::}{textElideMode}.
2848
2849*/
2850void QTableView::setWordWrap(bool on)
2851{
2852 Q_D(QTableView);
2853 if (d->wrapItemText == on)
2854 return;
2855 d->wrapItemText = on;
2856 QMetaObject::invokeMethod(d->verticalHeader, "resizeSections");
2857 QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections");
2858}
2859
2860bool QTableView::wordWrap() const
2861{
2862 Q_D(const QTableView);
2863 return d->wrapItemText;
2864}
2865
2866#if QT_CONFIG(abstractbutton)
2867/*!
2868 \property QTableView::cornerButtonEnabled
2869 \brief whether the button in the top-left corner is enabled
2870
2871 If this property is \c true then button in the top-left corner
2872 of the table view is enabled. Clicking on this button will
2873 select all the cells in the table view.
2874
2875 This property is \c true by default.
2876*/
2877void QTableView::setCornerButtonEnabled(bool enable)
2878{
2879 Q_D(QTableView);
2880 d->cornerWidget->setEnabled(enable);
2881}
2882
2883bool QTableView::isCornerButtonEnabled() const
2884{
2885 Q_D(const QTableView);
2886 return d->cornerWidget->isEnabled();
2887}
2888#endif
2889
2890/*!
2891 \reimp
2892
2893 Returns the rectangle on the viewport occupied by the given \a
2894 index.
2895 If the index is hidden in the view it will return a null QRect.
2896*/
2897QRect QTableView::visualRect(const QModelIndex &index) const
2898{
2899 Q_D(const QTableView);
2900 if (!d->isIndexValid(index) || index.parent() != d->root
2901 || (!d->hasSpans() && isIndexHidden(index)))
2902 return QRect();
2903
2904 d->executePostedLayout();
2905
2906 if (d->hasSpans()) {
2907 QSpanCollection::Span span = d->span(index.row(), index.column());
2908 return d->visualSpanRect(span);
2909 }
2910
2911 int rowp = rowViewportPosition(index.row());
2912 int rowh = rowHeight(index.row());
2913 int colp = columnViewportPosition(index.column());
2914 int colw = columnWidth(index.column());
2915
2916 const int i = showGrid() ? 1 : 0;
2917 return QRect(colp, rowp, colw - i, rowh - i);
2918}
2919
2920/*!
2921 \reimp
2922
2923 Makes sure that the given \a index is visible in the table view,
2924 scrolling if necessary.
2925*/
2926void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
2927{
2928 Q_D(QTableView);
2929
2930 // check if we really need to do anything
2931 if (!d->isIndexValid(index)
2932 || (d->model->parent(index) != d->root)
2933 || isRowHidden(index.row()) || isColumnHidden(index.column()))
2934 return;
2935
2936 QSpanCollection::Span span;
2937 if (d->hasSpans())
2938 span = d->span(index.row(), index.column());
2939
2940 // Adjust horizontal position
2941
2942 int viewportWidth = d->viewport->width();
2943 int horizontalOffset = d->horizontalHeader->offset();
2944 int horizontalPosition = d->horizontalHeader->sectionPosition(index.column());
2945 int horizontalIndex = d->horizontalHeader->visualIndex(index.column());
2946 int cellWidth = d->hasSpans()
2947 ? d->columnSpanWidth(index.column(), span.width())
2948 : d->horizontalHeader->sectionSize(index.column());
2949
2950 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2951
2952 bool positionAtLeft = (horizontalPosition - horizontalOffset < 0);
2953 bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth);
2954
2955 if (hint == PositionAtCenter || positionAtRight) {
2956 int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth);
2957 int x = cellWidth;
2958 while (horizontalIndex > 0) {
2959 x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1));
2960 if (x > w)
2961 break;
2962 --horizontalIndex;
2963 }
2964 }
2965
2966 if (positionAtRight || hint == PositionAtCenter || positionAtLeft) {
2967 int hiddenSections = 0;
2968 if (d->horizontalHeader->sectionsHidden()) {
2969 for (int s = horizontalIndex - 1; s >= 0; --s) {
2970 int column = d->horizontalHeader->logicalIndex(s);
2971 if (d->horizontalHeader->isSectionHidden(column))
2972 ++hiddenSections;
2973 }
2974 }
2975 horizontalScrollBar()->setValue(horizontalIndex - hiddenSections);
2976 }
2977
2978 } else { // ScrollPerPixel
2979 if (hint == PositionAtCenter) {
2980 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
2981 } else {
2982 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
2983 horizontalScrollBar()->setValue(horizontalPosition);
2984 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
2985 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
2986 }
2987 }
2988
2989 // Adjust vertical position
2990
2991 int viewportHeight = d->viewport->height();
2992 int verticalOffset = d->verticalHeader->offset();
2993 int verticalPosition = d->verticalHeader->sectionPosition(index.row());
2994 int verticalIndex = d->verticalHeader->visualIndex(index.row());
2995 int cellHeight = d->hasSpans()
2996 ? d->rowSpanHeight(index.row(), span.height())
2997 : d->verticalHeader->sectionSize(index.row());
2998
2999 if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) {
3000 if (hint == EnsureVisible)
3001 hint = PositionAtTop;
3002 } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) {
3003 if (hint == EnsureVisible)
3004 hint = PositionAtBottom;
3005 }
3006
3007 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
3008
3009 if (hint == PositionAtBottom || hint == PositionAtCenter) {
3010 int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight);
3011 int y = cellHeight;
3012 while (verticalIndex > 0) {
3013 int row = d->verticalHeader->logicalIndex(verticalIndex - 1);
3014 y += d->verticalHeader->sectionSize(row);
3015 if (y > h)
3016 break;
3017 --verticalIndex;
3018 }
3019 }
3020
3021 if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) {
3022 int hiddenSections = 0;
3023 if (d->verticalHeader->sectionsHidden()) {
3024 for (int s = verticalIndex - 1; s >= 0; --s) {
3025 int row = d->verticalHeader->logicalIndex(s);
3026 if (d->verticalHeader->isSectionHidden(row))
3027 ++hiddenSections;
3028 }
3029 }
3030 verticalScrollBar()->setValue(verticalIndex - hiddenSections);
3031 }
3032
3033 } else { // ScrollPerPixel
3034 if (hint == PositionAtTop) {
3035 verticalScrollBar()->setValue(verticalPosition);
3036 } else if (hint == PositionAtBottom) {
3037 verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight);
3038 } else if (hint == PositionAtCenter) {
3039 verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2));
3040 }
3041 }
3042
3043 update(index);
3044}
3045
3046/*!
3047 This slot is called to change the height of the given \a row. The
3048 old height is specified by \a oldHeight, and the new height by \a
3049 newHeight.
3050
3051 \sa columnResized()
3052*/
3053void QTableView::rowResized(int row, int, int)
3054{
3055 Q_D(QTableView);
3056 d->rowsToUpdate.append(row);
3057 if (!d->rowResizeTimer.isActive())
3058 d->rowResizeTimer.start(0ns, this);
3059}
3060
3061/*!
3062 This slot is called to change the width of the given \a column.
3063 The old width is specified by \a oldWidth, and the new width by \a
3064 newWidth.
3065
3066 \sa rowResized()
3067*/
3068void QTableView::columnResized(int column, int, int)
3069{
3070 Q_D(QTableView);
3071 d->columnsToUpdate.append(column);
3072 if (!d->columnResizeTimer.isActive())
3073 d->columnResizeTimer.start(0ns, this);
3074}
3075
3076/*!
3077 \reimp
3078 */
3079void QTableView::timerEvent(QTimerEvent *event)
3080{
3081 Q_D(QTableView);
3082
3083 if (event->id() == d->columnResizeTimer.id()) {
3084 const int oldScrollMax = horizontalScrollBar()->maximum();
3085 if (horizontalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3086 updateGeometries();
3087 d->columnResizeTimer.stop();
3088 } else {
3089 updateEditorGeometries();
3090 }
3091
3092 QRect rect;
3093 int viewportHeight = d->viewport->height();
3094 int viewportWidth = d->viewport->width();
3095 if (d->hasSpans() || horizontalScrollBar()->value() == oldScrollMax) {
3096 rect = QRect(0, 0, viewportWidth, viewportHeight);
3097 } else {
3098 for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) {
3099 int column = d->columnsToUpdate.at(i);
3100 int x = columnViewportPosition(column);
3101 if (isRightToLeft())
3102 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
3103 else
3104 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
3105 }
3106 }
3107
3108 d->viewport->update(rect.normalized());
3109 d->columnsToUpdate.clear();
3110 }
3111
3112 if (event->id() == d->rowResizeTimer.id()) {
3113 const int oldScrollMax = verticalScrollBar()->maximum();
3114 if (verticalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3115 updateGeometries();
3116 d->rowResizeTimer.stop();
3117 } else {
3118 updateEditorGeometries();
3119 }
3120
3121 int viewportHeight = d->viewport->height();
3122 int viewportWidth = d->viewport->width();
3123 int top;
3124 if (d->hasSpans() || verticalScrollBar()->value() == oldScrollMax) {
3125 top = 0;
3126 } else {
3127 top = viewportHeight;
3128 for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) {
3129 int y = rowViewportPosition(d->rowsToUpdate.at(i));
3130 top = qMin(top, y);
3131 }
3132 }
3133
3134 d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top));
3135 d->rowsToUpdate.clear();
3136 }
3137
3138 QAbstractItemView::timerEvent(event);
3139}
3140
3141#if QT_CONFIG(draganddrop)
3142/*! \reimp */
3143void QTableView::dropEvent(QDropEvent *event)
3144{
3145 Q_D(QTableView);
3146 if (event->source() == this && (event->dropAction() == Qt::MoveAction ||
3147 dragDropMode() == QAbstractItemView::InternalMove)) {
3148 QModelIndex topIndex;
3149 int col = -1;
3150 int row = -1;
3151 // check whether a subclass has already accepted the event, ie. moved the data
3152 if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && !topIndex.isValid() && col != -1) {
3153 // Drop between items (reordering) - can only happen with setDragDropOverwriteMode(false)
3154 const QModelIndexList indexes = selectedIndexes();
3155 QList<QPersistentModelIndex> persIndexes;
3156 persIndexes.reserve(indexes.size());
3157
3158 bool topIndexDropped = false;
3159 for (const auto &index : indexes) {
3160 // Reorder entire rows
3161 QPersistentModelIndex firstColIndex = index.siblingAtColumn(0);
3162 if (!persIndexes.contains(firstColIndex))
3163 persIndexes.append(firstColIndex);
3164 if (index.row() == topIndex.row()) {
3165 topIndexDropped = true;
3166 break;
3167 }
3168 }
3169 if (!topIndexDropped) {
3170 std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order.
3171
3172 QPersistentModelIndex dropRow = model()->index(row, col, topIndex);
3173
3174 int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row);
3175 bool dataMoved = false;
3176 for (const QPersistentModelIndex &pIndex : std::as_const(persIndexes)) {
3177 // only generate a move when not same row or behind itself
3178 if (r != pIndex.row() && r != pIndex.row() + 1) {
3179 // try to move (preserves selection)
3180 const bool moved = model()->moveRow(QModelIndex(), pIndex.row(), QModelIndex(), r);
3181 if (!moved)
3182 continue; // maybe it'll work for other rows
3183 dataMoved = true; // success
3184 } else {
3185 // move onto itself is blocked, don't delete anything
3186 dataMoved = true;
3187 }
3188 r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order.
3189 }
3190 if (dataMoved) {
3191 d->dropEventMoved = true;
3192 event->accept();
3193 }
3194 }
3195 }
3196 }
3197
3198 if (!event->isAccepted()) {
3199 // moveRows not implemented, fall back to default
3200 QAbstractItemView::dropEvent(event);
3201 }
3202}
3203#endif
3204
3205/*!
3206 This slot is called to change the index of the given \a row in the
3207 table view. The old index is specified by \a oldIndex, and the new
3208 index by \a newIndex.
3209
3210 \sa columnMoved()
3211*/
3212void QTableView::rowMoved(int row, int oldIndex, int newIndex)
3213{
3214 Q_UNUSED(row);
3215 Q_D(QTableView);
3216
3217 updateGeometries();
3218 int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex);
3219 int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex);
3220 if (d->hasSpans()) {
3221 d->viewport->update();
3222 } else {
3223 int oldTop = rowViewportPosition(logicalOldIndex);
3224 int newTop = rowViewportPosition(logicalNewIndex);
3225 int oldBottom = oldTop + rowHeight(logicalOldIndex);
3226 int newBottom = newTop + rowHeight(logicalNewIndex);
3227 int top = qMin(oldTop, newTop);
3228 int bottom = qMax(oldBottom, newBottom);
3229 int height = bottom - top;
3230 d->viewport->update(0, top, d->viewport->width(), height);
3231 }
3232}
3233
3234/*!
3235 This slot is called to change the index of the given \a column in
3236 the table view. The old index is specified by \a oldIndex, and
3237 the new index by \a newIndex.
3238
3239 \sa rowMoved()
3240*/
3241void QTableView::columnMoved(int column, int oldIndex, int newIndex)
3242{
3243 Q_UNUSED(column);
3244 Q_D(QTableView);
3245
3246 updateGeometries();
3247 int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex);
3248 int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex);
3249 if (d->hasSpans()) {
3250 d->viewport->update();
3251 } else {
3252 int oldLeft = columnViewportPosition(logicalOldIndex);
3253 int newLeft = columnViewportPosition(logicalNewIndex);
3254 int oldRight = oldLeft + columnWidth(logicalOldIndex);
3255 int newRight = newLeft + columnWidth(logicalNewIndex);
3256 int left = qMin(oldLeft, newLeft);
3257 int right = qMax(oldRight, newRight);
3258 int width = right - left;
3259 d->viewport->update(left, 0, width, d->viewport->height());
3260 }
3261}
3262
3263/*!
3264 Selects the given \a row in the table view if the current
3265 SelectionMode and SelectionBehavior allows rows to be selected.
3266
3267 \sa selectColumn()
3268*/
3269void QTableView::selectRow(int row)
3270{
3271 Q_D(QTableView);
3272 d->selectRow(row, true);
3273}
3274
3275/*!
3276 Selects the given \a column in the table view if the current
3277 SelectionMode and SelectionBehavior allows columns to be selected.
3278
3279 \sa selectRow()
3280*/
3281void QTableView::selectColumn(int column)
3282{
3283 Q_D(QTableView);
3284 d->selectColumn(column, true);
3285}
3286
3287/*!
3288 Hide the given \a row.
3289
3290 \sa showRow(), hideColumn()
3291*/
3292void QTableView::hideRow(int row)
3293{
3294 Q_D(QTableView);
3295 d->verticalHeader->hideSection(row);
3296}
3297
3298/*!
3299 Hide the given \a column.
3300
3301 \sa showColumn(), hideRow()
3302*/
3303void QTableView::hideColumn(int column)
3304{
3305 Q_D(QTableView);
3306 d->horizontalHeader->hideSection(column);
3307}
3308
3309/*!
3310 Show the given \a row.
3311
3312 \sa hideRow(), showColumn()
3313*/
3314void QTableView::showRow(int row)
3315{
3316 Q_D(QTableView);
3317 d->verticalHeader->showSection(row);
3318}
3319
3320/*!
3321 Show the given \a column.
3322
3323 \sa hideColumn(), showRow()
3324*/
3325void QTableView::showColumn(int column)
3326{
3327 Q_D(QTableView);
3328 d->horizontalHeader->showSection(column);
3329}
3330
3331/*!
3332 Resizes the given \a row based on the size hints of the delegate
3333 used to render each item in the row.
3334
3335 \sa resizeRowsToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3336*/
3337void QTableView::resizeRowToContents(int row)
3338{
3339 Q_D(QTableView);
3340 int content = sizeHintForRow(row);
3341 int header = d->verticalHeader->sectionSizeHint(row);
3342 d->verticalHeader->resizeSection(row, qMax(content, header));
3343}
3344
3345/*!
3346 Resizes all rows based on the size hints of the delegate
3347 used to render each item in the rows.
3348
3349 \sa resizeRowToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3350*/
3351void QTableView::resizeRowsToContents()
3352{
3353 Q_D(QTableView);
3354 d->verticalHeader->resizeSections(QHeaderView::ResizeToContents);
3355}
3356
3357/*!
3358 Resizes the given \a column based on the size hints of the delegate
3359 used to render each item in the column.
3360
3361 \note Only visible columns will be resized. Reimplement sizeHintForColumn()
3362 to resize hidden columns as well.
3363
3364 \sa resizeColumnsToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3365*/
3366void QTableView::resizeColumnToContents(int column)
3367{
3368 Q_D(QTableView);
3369 int content = sizeHintForColumn(column);
3370 int header = d->horizontalHeader->sectionSizeHint(column);
3371 d->horizontalHeader->resizeSection(column, qMax(content, header));
3372}
3373
3374/*!
3375 Resizes all columns based on the size hints of the delegate
3376 used to render each item in the columns.
3377
3378 \sa resizeColumnToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3379*/
3380void QTableView::resizeColumnsToContents()
3381{
3382 Q_D(QTableView);
3383 d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents);
3384}
3385
3386/*!
3387 Sorts the model by the values in the given \a column and \a order.
3388
3389 \a column may be -1, in which case no sort indicator will be shown
3390 and the model will return to its natural, unsorted order. Note that not
3391 all models support this and may even crash in this case.
3392
3393 \sa sortingEnabled
3394 */
3395void QTableView::sortByColumn(int column, Qt::SortOrder order)
3396{
3397 Q_D(QTableView);
3398 if (column < -1)
3399 return;
3400 d->horizontalHeader->setSortIndicator(column, order);
3401 // If sorting is not enabled or has the same order as before, force to sort now
3402 // else sorting will be trigger through sortIndicatorChanged()
3403 if (!d->sortingEnabled ||
3404 (d->horizontalHeader->sortIndicatorSection() == column && d->horizontalHeader->sortIndicatorOrder() == order))
3405 d->model->sort(column, order);
3406}
3407
3408/*!
3409 \internal
3410*/
3411void QTableView::verticalScrollbarAction(int action)
3412{
3413 QAbstractItemView::verticalScrollbarAction(action);
3414}
3415
3416/*!
3417 \internal
3418*/
3419void QTableView::horizontalScrollbarAction(int action)
3420{
3421 QAbstractItemView::horizontalScrollbarAction(action);
3422}
3423
3424/*!
3425 \reimp
3426*/
3427bool QTableView::isIndexHidden(const QModelIndex &index) const
3428{
3429 Q_D(const QTableView);
3430 Q_ASSERT(d->isIndexValid(index));
3431 if (isRowHidden(index.row()) || isColumnHidden(index.column()))
3432 return true;
3433 if (d->hasSpans()) {
3434 QSpanCollection::Span span = d->span(index.row(), index.column());
3435 return !((span.top() == index.row()) && (span.left() == index.column()));
3436 }
3437 return false;
3438}
3439
3440/*!
3441 \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
3442
3443 Sets the span of the table element at (\a row, \a column) to the number of
3444 rows and columns specified by (\a rowSpanCount, \a columnSpanCount).
3445
3446 \sa rowSpan(), columnSpan()
3447*/
3448void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan)
3449{
3450 Q_D(QTableView);
3451 if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0)
3452 return;
3453 d->setSpan(row, column, rowSpan, columnSpan);
3454 d->viewport->update();
3455}
3456
3457/*!
3458 Returns the row span of the table element at (\a row, \a column).
3459 The default is 1.
3460
3461 \sa setSpan(), columnSpan()
3462*/
3463int QTableView::rowSpan(int row, int column) const
3464{
3465 Q_D(const QTableView);
3466 return d->rowSpan(row, column);
3467}
3468
3469/*!
3470 Returns the column span of the table element at (\a row, \a
3471 column). The default is 1.
3472
3473 \sa setSpan(), rowSpan()
3474*/
3475int QTableView::columnSpan(int row, int column) const
3476{
3477 Q_D(const QTableView);
3478 return d->columnSpan(row, column);
3479}
3480
3481/*!
3482 Removes all row and column spans in the table view.
3483
3484 \sa setSpan()
3485*/
3486
3487void QTableView::clearSpans()
3488{
3489 Q_D(QTableView);
3490 d->spans.clear();
3491 d->viewport->update();
3492}
3493
3494void QTableViewPrivate::selectRow(int row, bool anchor)
3495{
3496 Q_Q(QTableView);
3497
3498 if (q->selectionBehavior() == QTableView::SelectColumns
3499 || (q->selectionMode() == QTableView::SingleSelection
3500 && q->selectionBehavior() == QTableView::SelectItems))
3501 return;
3502
3503 if (row >= 0 && row < model->rowCount(root)) {
3504 int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0);
3505 QModelIndex index = model->index(row, column, root);
3506 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3507
3508 {
3509 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3510 const auto startIndex = currentSelectionStartIndex;
3511 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3512 currentSelectionStartIndex = startIndex;
3513 }
3514
3515 if ((anchor && !(command & QItemSelectionModel::Current))
3516 || (q->selectionMode() == QTableView::SingleSelection))
3517 currentSelectionStartIndex = model->index(row, column, root);
3518
3519 if (q->selectionMode() != QTableView::SingleSelection
3520 && command.testFlag(QItemSelectionModel::Toggle)) {
3521 if (anchor)
3522 ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index)
3523 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3524 command &= ~QItemSelectionModel::Toggle;
3525 command |= ctrlDragSelectionFlag;
3526 if (!anchor)
3527 command |= QItemSelectionModel::Current;
3528 }
3529
3530 const auto rowSectionAnchor = currentSelectionStartIndex.row();
3531 QModelIndex upper = model->index(qMin(rowSectionAnchor, row), column, root);
3532 QModelIndex lower = model->index(qMax(rowSectionAnchor, row), column, root);
3533 if ((verticalHeader->sectionsMoved() && upper.row() != lower.row())) {
3534 q->setSelection(q->visualRect(upper) | q->visualRect(lower), command | QItemSelectionModel::Rows);
3535 } else {
3536 selectionModel->select(QItemSelection(upper, lower), command | QItemSelectionModel::Rows);
3537 }
3538 }
3539}
3540
3541void QTableViewPrivate::selectColumn(int column, bool anchor)
3542{
3543 Q_Q(QTableView);
3544
3545 if (q->selectionBehavior() == QTableView::SelectRows
3546 || (q->selectionMode() == QTableView::SingleSelection
3547 && q->selectionBehavior() == QTableView::SelectItems))
3548 return;
3549
3550 if (column >= 0 && column < model->columnCount(root)) {
3551 int row = verticalHeader->logicalIndexAt(0);
3552 QModelIndex index = model->index(row, column, root);
3553 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3554
3555 {
3556 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3557 const auto startIndex = currentSelectionStartIndex;
3558 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3559 currentSelectionStartIndex = startIndex;
3560 }
3561
3562 if ((anchor && !(command & QItemSelectionModel::Current))
3563 || (q->selectionMode() == QTableView::SingleSelection))
3564 currentSelectionStartIndex = model->index(row, column, root);
3565
3566 if (q->selectionMode() != QTableView::SingleSelection
3567 && command.testFlag(QItemSelectionModel::Toggle)) {
3568 if (anchor)
3569 ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns(row).contains(index)
3570 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3571 command &= ~QItemSelectionModel::Toggle;
3572 command |= ctrlDragSelectionFlag;
3573 if (!anchor)
3574 command |= QItemSelectionModel::Current;
3575 }
3576
3577 const auto columnSectionAnchor = currentSelectionStartIndex.column();
3578 QModelIndex left = model->index(row, qMin(columnSectionAnchor, column), root);
3579 QModelIndex right = model->index(row, qMax(columnSectionAnchor, column), root);
3580 if ((horizontalHeader->sectionsMoved() && left.column() != right.column())) {
3581 q->setSelection(q->visualRect(left) | q->visualRect(right), command | QItemSelectionModel::Columns);
3582 } else {
3583 selectionModel->select(QItemSelection(left, right), command | QItemSelectionModel::Columns);
3584 }
3585 }
3586}
3587
3588/*!
3589 \reimp
3590 */
3591void QTableView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3592{
3593#if QT_CONFIG(accessibility)
3594 if (QAccessible::isActive()) {
3595 if (current.isValid() && hasFocus()) {
3596 Q_D(QTableView);
3597 int entry = d->accessibleChildIndex(current);
3598 QAccessibleEvent event(this, QAccessible::Focus);
3599 event.setChild(entry);
3600 QAccessible::updateAccessibility(&event);
3601 }
3602 }
3603#endif
3604 QAbstractItemView::currentChanged(current, previous);
3605}
3606
3607/*!
3608 \reimp
3609 */
3610void QTableView::selectionChanged(const QItemSelection &selected,
3611 const QItemSelection &deselected)
3612{
3613 Q_D(QTableView);
3614 Q_UNUSED(d);
3615#if QT_CONFIG(accessibility)
3616 if (QAccessible::isActive()) {
3617 // ### does not work properly for selection ranges.
3618 QModelIndex sel = selected.indexes().value(0);
3619 if (sel.isValid()) {
3620 int entry = d->accessibleChildIndex(sel);
3621 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3622 event.setChild(entry);
3623 QAccessible::updateAccessibility(&event);
3624 }
3625 QModelIndex desel = deselected.indexes().value(0);
3626 if (desel.isValid()) {
3627 int entry = d->accessibleChildIndex(desel);
3628 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3629 event.setChild(entry);
3630 QAccessible::updateAccessibility(&event);
3631 }
3632 }
3633#endif
3634 QAbstractItemView::selectionChanged(selected, deselected);
3635}
3636
3637int QTableView::visualIndex(const QModelIndex &index) const
3638{
3639 return index.row();
3640}
3641
3642QT_END_NAMESPACE
3643
3644#include "qtableview.moc"
3645
3646#include "moc_qtableview.cpp"