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 (const QMetaObject::Connection &connection : modelConnections)
610 QObject::disconnect(connection);
611 for (const QMetaObject::Connection &connection : verHeaderConnections)
612 QObject::disconnect(connection);
613 for (const QMetaObject::Connection &connection : horHeaderConnections)
614 QObject::disconnect(connection);
615 for (const 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 (const 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 (const 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 (const 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 (const QMetaObject::Connection &connection : d->dynHorHeaderConnections)
2767 disconnect(connection);
2768 d->dynHorHeaderConnections.clear();
2769 if (enable) {
2770 //sortByColumn has to be called before we connect or set the sortingEnabled flag
2771 // because otherwise it will not call sort on the model.
2772 sortByColumn(d->horizontalHeader->sortIndicatorSection(),
2773 d->horizontalHeader->sortIndicatorOrder());
2774 d->dynHorHeaderConnections = {
2775 QObjectPrivate::connect(d->horizontalHeader, &QHeaderView::sortIndicatorChanged,
2776 d, &QTableViewPrivate::sortIndicatorChanged)
2777 };
2778 } else {
2779 d->dynHorHeaderConnections = {
2780 connect(d->horizontalHeader, &QHeaderView::sectionPressed,
2781 this, &QTableView::selectColumn),
2782 connect(d->horizontalHeader, &QHeaderView::sectionEntered,
2783 this, [d](int column) {d->selectColumn(column, false); })
2784 };
2785 }
2786 d->sortingEnabled = enable;
2787}
2788
2789bool QTableView::isSortingEnabled() const
2790{
2791 Q_D(const QTableView);
2792 return d->sortingEnabled;
2793}
2794
2795/*!
2796 \property QTableView::showGrid
2797 \brief whether the grid is shown
2798
2799 If this property is \c true a grid is drawn for the table; if the
2800 property is \c false, no grid is drawn. The default value is true.
2801*/
2802bool QTableView::showGrid() const
2803{
2804 Q_D(const QTableView);
2805 return d->showGrid;
2806}
2807
2808void QTableView::setShowGrid(bool show)
2809{
2810 Q_D(QTableView);
2811 if (d->showGrid != show) {
2812 d->showGrid = show;
2813 d->viewport->update();
2814 }
2815}
2816
2817/*!
2818 \property QTableView::gridStyle
2819 \brief the pen style used to draw the grid.
2820
2821 This property holds the style used when drawing the grid (see \l{showGrid}).
2822*/
2823Qt::PenStyle QTableView::gridStyle() const
2824{
2825 Q_D(const QTableView);
2826 return d->gridStyle;
2827}
2828
2829void QTableView::setGridStyle(Qt::PenStyle style)
2830{
2831 Q_D(QTableView);
2832 if (d->gridStyle != style) {
2833 d->gridStyle = style;
2834 d->viewport->update();
2835 }
2836}
2837
2838/*!
2839 \property QTableView::wordWrap
2840 \brief the item text word-wrapping policy
2841
2842 If this property is \c true then the item text is wrapped where
2843 necessary at word-breaks; otherwise it is not wrapped at all.
2844 This property is \c true by default.
2845
2846 Note that even if wrapping is enabled, the cell will not be
2847 expanded to fit all text. Ellipsis will be inserted according to
2848 the current \l{QAbstractItemView::}{textElideMode}.
2849
2850*/
2851void QTableView::setWordWrap(bool on)
2852{
2853 Q_D(QTableView);
2854 if (d->wrapItemText == on)
2855 return;
2856 d->wrapItemText = on;
2857 QMetaObject::invokeMethod(d->verticalHeader, "resizeSections");
2858 QMetaObject::invokeMethod(d->horizontalHeader, "resizeSections");
2859}
2860
2861bool QTableView::wordWrap() const
2862{
2863 Q_D(const QTableView);
2864 return d->wrapItemText;
2865}
2866
2867#if QT_CONFIG(abstractbutton)
2868/*!
2869 \property QTableView::cornerButtonEnabled
2870 \brief whether the button in the top-left corner is enabled
2871
2872 If this property is \c true then button in the top-left corner
2873 of the table view is enabled. Clicking on this button will
2874 select all the cells in the table view.
2875
2876 This property is \c true by default.
2877*/
2878void QTableView::setCornerButtonEnabled(bool enable)
2879{
2880 Q_D(QTableView);
2881 d->cornerWidget->setEnabled(enable);
2882}
2883
2884bool QTableView::isCornerButtonEnabled() const
2885{
2886 Q_D(const QTableView);
2887 return d->cornerWidget->isEnabled();
2888}
2889#endif
2890
2891/*!
2892 \reimp
2893
2894 Returns the rectangle on the viewport occupied by the given \a
2895 index.
2896 If the index is hidden in the view it will return a null QRect.
2897*/
2898QRect QTableView::visualRect(const QModelIndex &index) const
2899{
2900 Q_D(const QTableView);
2901 if (!d->isIndexValid(index) || index.parent() != d->root
2902 || (!d->hasSpans() && isIndexHidden(index)))
2903 return QRect();
2904
2905 d->executePostedLayout();
2906
2907 if (d->hasSpans()) {
2908 QSpanCollection::Span span = d->span(index.row(), index.column());
2909 return d->visualSpanRect(span);
2910 }
2911
2912 int rowp = rowViewportPosition(index.row());
2913 int rowh = rowHeight(index.row());
2914 int colp = columnViewportPosition(index.column());
2915 int colw = columnWidth(index.column());
2916
2917 const int i = showGrid() ? 1 : 0;
2918 return QRect(colp, rowp, colw - i, rowh - i);
2919}
2920
2921/*!
2922 \reimp
2923
2924 Makes sure that the given \a index is visible in the table view,
2925 scrolling if necessary.
2926*/
2927void QTableView::scrollTo(const QModelIndex &index, ScrollHint hint)
2928{
2929 Q_D(QTableView);
2930
2931 // check if we really need to do anything
2932 if (!d->isIndexValid(index)
2933 || (d->model->parent(index) != d->root)
2934 || isRowHidden(index.row()) || isColumnHidden(index.column()))
2935 return;
2936
2937 QSpanCollection::Span span;
2938 if (d->hasSpans())
2939 span = d->span(index.row(), index.column());
2940
2941 // Adjust horizontal position
2942
2943 int viewportWidth = d->viewport->width();
2944 int horizontalOffset = d->horizontalHeader->offset();
2945 int horizontalPosition = d->horizontalHeader->sectionPosition(index.column());
2946 int horizontalIndex = d->horizontalHeader->visualIndex(index.column());
2947 int cellWidth = d->hasSpans()
2948 ? d->columnSpanWidth(index.column(), span.width())
2949 : d->horizontalHeader->sectionSize(index.column());
2950
2951 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2952
2953 bool positionAtLeft = (horizontalPosition - horizontalOffset < 0);
2954 bool positionAtRight = (horizontalPosition - horizontalOffset + cellWidth > viewportWidth);
2955
2956 if (hint == PositionAtCenter || positionAtRight) {
2957 int w = (hint == PositionAtCenter ? viewportWidth / 2 : viewportWidth);
2958 int x = cellWidth;
2959 while (horizontalIndex > 0) {
2960 x += columnWidth(d->horizontalHeader->logicalIndex(horizontalIndex-1));
2961 if (x > w)
2962 break;
2963 --horizontalIndex;
2964 }
2965 }
2966
2967 if (positionAtRight || hint == PositionAtCenter || positionAtLeft) {
2968 int hiddenSections = 0;
2969 if (d->horizontalHeader->sectionsHidden()) {
2970 for (int s = horizontalIndex - 1; s >= 0; --s) {
2971 int column = d->horizontalHeader->logicalIndex(s);
2972 if (d->horizontalHeader->isSectionHidden(column))
2973 ++hiddenSections;
2974 }
2975 }
2976 horizontalScrollBar()->setValue(horizontalIndex - hiddenSections);
2977 }
2978
2979 } else { // ScrollPerPixel
2980 if (hint == PositionAtCenter) {
2981 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2));
2982 } else {
2983 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth)
2984 horizontalScrollBar()->setValue(horizontalPosition);
2985 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth)
2986 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth);
2987 }
2988 }
2989
2990 // Adjust vertical position
2991
2992 int viewportHeight = d->viewport->height();
2993 int verticalOffset = d->verticalHeader->offset();
2994 int verticalPosition = d->verticalHeader->sectionPosition(index.row());
2995 int verticalIndex = d->verticalHeader->visualIndex(index.row());
2996 int cellHeight = d->hasSpans()
2997 ? d->rowSpanHeight(index.row(), span.height())
2998 : d->verticalHeader->sectionSize(index.row());
2999
3000 if (verticalPosition - verticalOffset < 0 || cellHeight > viewportHeight) {
3001 if (hint == EnsureVisible)
3002 hint = PositionAtTop;
3003 } else if (verticalPosition - verticalOffset + cellHeight > viewportHeight) {
3004 if (hint == EnsureVisible)
3005 hint = PositionAtBottom;
3006 }
3007
3008 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
3009
3010 if (hint == PositionAtBottom || hint == PositionAtCenter) {
3011 int h = (hint == PositionAtCenter ? viewportHeight / 2 : viewportHeight);
3012 int y = cellHeight;
3013 while (verticalIndex > 0) {
3014 int row = d->verticalHeader->logicalIndex(verticalIndex - 1);
3015 y += d->verticalHeader->sectionSize(row);
3016 if (y > h)
3017 break;
3018 --verticalIndex;
3019 }
3020 }
3021
3022 if (hint == PositionAtBottom || hint == PositionAtCenter || hint == PositionAtTop) {
3023 int hiddenSections = 0;
3024 if (d->verticalHeader->sectionsHidden()) {
3025 for (int s = verticalIndex - 1; s >= 0; --s) {
3026 int row = d->verticalHeader->logicalIndex(s);
3027 if (d->verticalHeader->isSectionHidden(row))
3028 ++hiddenSections;
3029 }
3030 }
3031 verticalScrollBar()->setValue(verticalIndex - hiddenSections);
3032 }
3033
3034 } else { // ScrollPerPixel
3035 if (hint == PositionAtTop) {
3036 verticalScrollBar()->setValue(verticalPosition);
3037 } else if (hint == PositionAtBottom) {
3038 verticalScrollBar()->setValue(verticalPosition - viewportHeight + cellHeight);
3039 } else if (hint == PositionAtCenter) {
3040 verticalScrollBar()->setValue(verticalPosition - ((viewportHeight - cellHeight) / 2));
3041 }
3042 }
3043
3044 update(index);
3045}
3046
3047/*!
3048 This slot is called to change the height of the given \a row. The
3049 old height is specified by \a oldHeight, and the new height by \a
3050 newHeight.
3051
3052 \sa columnResized()
3053*/
3054void QTableView::rowResized(int row, int, int)
3055{
3056 Q_D(QTableView);
3057 d->rowsToUpdate.append(row);
3058 if (!d->rowResizeTimer.isActive())
3059 d->rowResizeTimer.start(0ns, this);
3060}
3061
3062/*!
3063 This slot is called to change the width of the given \a column.
3064 The old width is specified by \a oldWidth, and the new width by \a
3065 newWidth.
3066
3067 \sa rowResized()
3068*/
3069void QTableView::columnResized(int column, int, int)
3070{
3071 Q_D(QTableView);
3072 d->columnsToUpdate.append(column);
3073 if (!d->columnResizeTimer.isActive())
3074 d->columnResizeTimer.start(0ns, this);
3075}
3076
3077/*!
3078 \reimp
3079 */
3080void QTableView::timerEvent(QTimerEvent *event)
3081{
3082 Q_D(QTableView);
3083
3084 if (event->id() == d->columnResizeTimer.id()) {
3085 const int oldScrollMax = horizontalScrollBar()->maximum();
3086 if (horizontalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3087 updateGeometries();
3088 d->columnResizeTimer.stop();
3089 } else {
3090 updateEditorGeometries();
3091 }
3092
3093 QRect rect;
3094 int viewportHeight = d->viewport->height();
3095 int viewportWidth = d->viewport->width();
3096 if (d->hasSpans() || horizontalScrollBar()->value() == oldScrollMax) {
3097 rect = QRect(0, 0, viewportWidth, viewportHeight);
3098 } else {
3099 for (int i = d->columnsToUpdate.size()-1; i >= 0; --i) {
3100 int column = d->columnsToUpdate.at(i);
3101 int x = columnViewportPosition(column);
3102 if (isRightToLeft())
3103 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight);
3104 else
3105 rect |= QRect(x, 0, viewportWidth - x, viewportHeight);
3106 }
3107 }
3108
3109 d->viewport->update(rect.normalized());
3110 d->columnsToUpdate.clear();
3111 }
3112
3113 if (event->id() == d->rowResizeTimer.id()) {
3114 const int oldScrollMax = verticalScrollBar()->maximum();
3115 if (verticalHeader()->d_func()->state != QHeaderViewPrivate::ResizeSection) {
3116 updateGeometries();
3117 d->rowResizeTimer.stop();
3118 } else {
3119 updateEditorGeometries();
3120 }
3121
3122 int viewportHeight = d->viewport->height();
3123 int viewportWidth = d->viewport->width();
3124 int top;
3125 if (d->hasSpans() || verticalScrollBar()->value() == oldScrollMax) {
3126 top = 0;
3127 } else {
3128 top = viewportHeight;
3129 for (int i = d->rowsToUpdate.size()-1; i >= 0; --i) {
3130 int y = rowViewportPosition(d->rowsToUpdate.at(i));
3131 top = qMin(top, y);
3132 }
3133 }
3134
3135 d->viewport->update(QRect(0, top, viewportWidth, viewportHeight - top));
3136 d->rowsToUpdate.clear();
3137 }
3138
3139 QAbstractItemView::timerEvent(event);
3140}
3141
3142#if QT_CONFIG(draganddrop)
3143/*! \reimp */
3144void QTableView::dropEvent(QDropEvent *event)
3145{
3146 Q_D(QTableView);
3147 if (event->source() == this && (event->dropAction() == Qt::MoveAction ||
3148 dragDropMode() == QAbstractItemView::InternalMove)) {
3149 QModelIndex topIndex;
3150 int col = -1;
3151 int row = -1;
3152 // check whether a subclass has already accepted the event, ie. moved the data
3153 if (!event->isAccepted() && d->dropOn(event, &row, &col, &topIndex) && !topIndex.isValid() && col != -1) {
3154 // Drop between items (reordering) - can only happen with setDragDropOverwriteMode(false)
3155 const QModelIndexList indexes = selectedIndexes();
3156 QList<QPersistentModelIndex> persIndexes;
3157 persIndexes.reserve(indexes.size());
3158
3159 bool topIndexDropped = false;
3160 for (const auto &index : indexes) {
3161 // Reorder entire rows
3162 QPersistentModelIndex firstColIndex = index.siblingAtColumn(0);
3163 if (!persIndexes.contains(firstColIndex))
3164 persIndexes.append(firstColIndex);
3165 if (index.row() == topIndex.row()) {
3166 topIndexDropped = true;
3167 break;
3168 }
3169 }
3170 if (!topIndexDropped) {
3171 std::sort(persIndexes.begin(), persIndexes.end()); // The dropped items will remain in the same visual order.
3172
3173 QPersistentModelIndex dropRow = model()->index(row, col, topIndex);
3174
3175 int r = row == -1 ? model()->rowCount() : (dropRow.row() >= 0 ? dropRow.row() : row);
3176 bool dataMoved = false;
3177 for (const QPersistentModelIndex &pIndex : std::as_const(persIndexes)) {
3178 // only generate a move when not same row or behind itself
3179 if (r != pIndex.row() && r != pIndex.row() + 1) {
3180 // try to move (preserves selection)
3181 const bool moved = model()->moveRow(QModelIndex(), pIndex.row(), QModelIndex(), r);
3182 if (!moved)
3183 continue; // maybe it'll work for other rows
3184 dataMoved = true; // success
3185 } else {
3186 // move onto itself is blocked, don't delete anything
3187 dataMoved = true;
3188 }
3189 r = pIndex.row() + 1; // Dropped items are inserted contiguously and in the right order.
3190 }
3191 if (dataMoved) {
3192 d->dropEventMoved = true;
3193 event->accept();
3194 }
3195 }
3196 }
3197 }
3198
3199 if (!event->isAccepted()) {
3200 // moveRows not implemented, fall back to default
3201 QAbstractItemView::dropEvent(event);
3202 }
3203}
3204#endif
3205
3206/*!
3207 This slot is called to change the index of the given \a row in the
3208 table view. The old index is specified by \a oldIndex, and the new
3209 index by \a newIndex.
3210
3211 \sa columnMoved()
3212*/
3213void QTableView::rowMoved(int row, int oldIndex, int newIndex)
3214{
3215 Q_UNUSED(row);
3216 Q_D(QTableView);
3217
3218 updateGeometries();
3219 int logicalOldIndex = d->verticalHeader->logicalIndex(oldIndex);
3220 int logicalNewIndex = d->verticalHeader->logicalIndex(newIndex);
3221 if (d->hasSpans()) {
3222 d->viewport->update();
3223 } else {
3224 int oldTop = rowViewportPosition(logicalOldIndex);
3225 int newTop = rowViewportPosition(logicalNewIndex);
3226 int oldBottom = oldTop + rowHeight(logicalOldIndex);
3227 int newBottom = newTop + rowHeight(logicalNewIndex);
3228 int top = qMin(oldTop, newTop);
3229 int bottom = qMax(oldBottom, newBottom);
3230 int height = bottom - top;
3231 d->viewport->update(0, top, d->viewport->width(), height);
3232 }
3233}
3234
3235/*!
3236 This slot is called to change the index of the given \a column in
3237 the table view. The old index is specified by \a oldIndex, and
3238 the new index by \a newIndex.
3239
3240 \sa rowMoved()
3241*/
3242void QTableView::columnMoved(int column, int oldIndex, int newIndex)
3243{
3244 Q_UNUSED(column);
3245 Q_D(QTableView);
3246
3247 updateGeometries();
3248 int logicalOldIndex = d->horizontalHeader->logicalIndex(oldIndex);
3249 int logicalNewIndex = d->horizontalHeader->logicalIndex(newIndex);
3250 if (d->hasSpans()) {
3251 d->viewport->update();
3252 } else {
3253 int oldLeft = columnViewportPosition(logicalOldIndex);
3254 int newLeft = columnViewportPosition(logicalNewIndex);
3255 int oldRight = oldLeft + columnWidth(logicalOldIndex);
3256 int newRight = newLeft + columnWidth(logicalNewIndex);
3257 int left = qMin(oldLeft, newLeft);
3258 int right = qMax(oldRight, newRight);
3259 int width = right - left;
3260 d->viewport->update(left, 0, width, d->viewport->height());
3261 }
3262}
3263
3264/*!
3265 Selects the given \a row in the table view if the current
3266 SelectionMode and SelectionBehavior allows rows to be selected.
3267
3268 \sa selectColumn()
3269*/
3270void QTableView::selectRow(int row)
3271{
3272 Q_D(QTableView);
3273 d->selectRow(row, true);
3274}
3275
3276/*!
3277 Selects the given \a column in the table view if the current
3278 SelectionMode and SelectionBehavior allows columns to be selected.
3279
3280 \sa selectRow()
3281*/
3282void QTableView::selectColumn(int column)
3283{
3284 Q_D(QTableView);
3285 d->selectColumn(column, true);
3286}
3287
3288/*!
3289 Hide the given \a row.
3290
3291 \sa showRow(), hideColumn()
3292*/
3293void QTableView::hideRow(int row)
3294{
3295 Q_D(QTableView);
3296 d->verticalHeader->hideSection(row);
3297}
3298
3299/*!
3300 Hide the given \a column.
3301
3302 \sa showColumn(), hideRow()
3303*/
3304void QTableView::hideColumn(int column)
3305{
3306 Q_D(QTableView);
3307 d->horizontalHeader->hideSection(column);
3308}
3309
3310/*!
3311 Show the given \a row.
3312
3313 \sa hideRow(), showColumn()
3314*/
3315void QTableView::showRow(int row)
3316{
3317 Q_D(QTableView);
3318 d->verticalHeader->showSection(row);
3319}
3320
3321/*!
3322 Show the given \a column.
3323
3324 \sa hideColumn(), showRow()
3325*/
3326void QTableView::showColumn(int column)
3327{
3328 Q_D(QTableView);
3329 d->horizontalHeader->showSection(column);
3330}
3331
3332/*!
3333 Resizes the given \a row based on the size hints of the delegate
3334 used to render each item in the row.
3335
3336 \sa resizeRowsToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3337*/
3338void QTableView::resizeRowToContents(int row)
3339{
3340 Q_D(QTableView);
3341 int content = sizeHintForRow(row);
3342 int header = d->verticalHeader->sectionSizeHint(row);
3343 d->verticalHeader->resizeSection(row, qMax(content, header));
3344}
3345
3346/*!
3347 Resizes all rows based on the size hints of the delegate
3348 used to render each item in the rows.
3349
3350 \sa resizeRowToContents(), sizeHintForRow(), QHeaderView::resizeContentsPrecision()
3351*/
3352void QTableView::resizeRowsToContents()
3353{
3354 Q_D(QTableView);
3355 d->verticalHeader->resizeSections(QHeaderView::ResizeToContents);
3356}
3357
3358/*!
3359 Resizes the given \a column based on the size hints of the delegate
3360 used to render each item in the column.
3361
3362 \note Only visible columns will be resized. Reimplement sizeHintForColumn()
3363 to resize hidden columns as well.
3364
3365 \sa resizeColumnsToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3366*/
3367void QTableView::resizeColumnToContents(int column)
3368{
3369 Q_D(QTableView);
3370 int content = sizeHintForColumn(column);
3371 int header = d->horizontalHeader->sectionSizeHint(column);
3372 d->horizontalHeader->resizeSection(column, qMax(content, header));
3373}
3374
3375/*!
3376 Resizes all columns based on the size hints of the delegate
3377 used to render each item in the columns.
3378
3379 \sa resizeColumnToContents(), sizeHintForColumn(), QHeaderView::resizeContentsPrecision()
3380*/
3381void QTableView::resizeColumnsToContents()
3382{
3383 Q_D(QTableView);
3384 d->horizontalHeader->resizeSections(QHeaderView::ResizeToContents);
3385}
3386
3387/*!
3388 Sorts the model by the values in the given \a column and \a order.
3389
3390 \a column may be -1, in which case no sort indicator will be shown
3391 and the model will return to its natural, unsorted order. Note that not
3392 all models support this and may even crash in this case.
3393
3394 \sa sortingEnabled
3395 */
3396void QTableView::sortByColumn(int column, Qt::SortOrder order)
3397{
3398 Q_D(QTableView);
3399 if (column < -1)
3400 return;
3401 d->horizontalHeader->setSortIndicator(column, order);
3402 // If sorting is not enabled or has the same order as before, force to sort now
3403 // else sorting will be trigger through sortIndicatorChanged()
3404 if (!d->sortingEnabled ||
3405 (d->horizontalHeader->sortIndicatorSection() == column && d->horizontalHeader->sortIndicatorOrder() == order))
3406 d->model->sort(column, order);
3407}
3408
3409/*!
3410 \internal
3411*/
3412void QTableView::verticalScrollbarAction(int action)
3413{
3414 QAbstractItemView::verticalScrollbarAction(action);
3415}
3416
3417/*!
3418 \internal
3419*/
3420void QTableView::horizontalScrollbarAction(int action)
3421{
3422 QAbstractItemView::horizontalScrollbarAction(action);
3423}
3424
3425/*!
3426 \reimp
3427*/
3428bool QTableView::isIndexHidden(const QModelIndex &index) const
3429{
3430 Q_D(const QTableView);
3431 Q_ASSERT(d->isIndexValid(index));
3432 if (isRowHidden(index.row()) || isColumnHidden(index.column()))
3433 return true;
3434 if (d->hasSpans()) {
3435 QSpanCollection::Span span = d->span(index.row(), index.column());
3436 return !((span.top() == index.row()) && (span.left() == index.column()));
3437 }
3438 return false;
3439}
3440
3441/*!
3442 \fn void QTableView::setSpan(int row, int column, int rowSpanCount, int columnSpanCount)
3443
3444 Sets the span of the table element at (\a row, \a column) to the number of
3445 rows and columns specified by (\a rowSpanCount, \a columnSpanCount).
3446
3447 \sa rowSpan(), columnSpan()
3448*/
3449void QTableView::setSpan(int row, int column, int rowSpan, int columnSpan)
3450{
3451 Q_D(QTableView);
3452 if (row < 0 || column < 0 || rowSpan < 0 || columnSpan < 0)
3453 return;
3454 d->setSpan(row, column, rowSpan, columnSpan);
3455 d->viewport->update();
3456}
3457
3458/*!
3459 Returns the row span of the table element at (\a row, \a column).
3460 The default is 1.
3461
3462 \sa setSpan(), columnSpan()
3463*/
3464int QTableView::rowSpan(int row, int column) const
3465{
3466 Q_D(const QTableView);
3467 return d->rowSpan(row, column);
3468}
3469
3470/*!
3471 Returns the column span of the table element at (\a row, \a
3472 column). The default is 1.
3473
3474 \sa setSpan(), rowSpan()
3475*/
3476int QTableView::columnSpan(int row, int column) const
3477{
3478 Q_D(const QTableView);
3479 return d->columnSpan(row, column);
3480}
3481
3482/*!
3483 Removes all row and column spans in the table view.
3484
3485 \sa setSpan()
3486*/
3487
3488void QTableView::clearSpans()
3489{
3490 Q_D(QTableView);
3491 d->spans.clear();
3492 d->viewport->update();
3493}
3494
3495void QTableViewPrivate::selectRow(int row, bool anchor)
3496{
3497 Q_Q(QTableView);
3498
3499 if (q->selectionBehavior() == QTableView::SelectColumns
3500 || (q->selectionMode() == QTableView::SingleSelection
3501 && q->selectionBehavior() == QTableView::SelectItems))
3502 return;
3503
3504 if (row >= 0 && row < model->rowCount(root)) {
3505 int column = horizontalHeader->logicalIndexAt(q->isRightToLeft() ? viewport->width() : 0);
3506 QModelIndex index = model->index(row, column, root);
3507 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3508
3509 {
3510 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3511 const auto startIndex = currentSelectionStartIndex;
3512 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3513 currentSelectionStartIndex = startIndex;
3514 }
3515
3516 if ((anchor && !(command & QItemSelectionModel::Current))
3517 || (q->selectionMode() == QTableView::SingleSelection))
3518 currentSelectionStartIndex = model->index(row, column, root);
3519
3520 if (q->selectionMode() != QTableView::SingleSelection
3521 && command.testFlag(QItemSelectionModel::Toggle)) {
3522 if (anchor)
3523 ctrlDragSelectionFlag = verticalHeader->selectionModel()->selectedRows(column).contains(index)
3524 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3525 command &= ~QItemSelectionModel::Toggle;
3526 command |= ctrlDragSelectionFlag;
3527 if (!anchor)
3528 command |= QItemSelectionModel::Current;
3529 }
3530
3531 const auto rowSectionAnchor = currentSelectionStartIndex.row();
3532 QModelIndex upper = model->index(qMin(rowSectionAnchor, row), column, root);
3533 QModelIndex lower = model->index(qMax(rowSectionAnchor, row), column, root);
3534 if ((verticalHeader->sectionsMoved() && upper.row() != lower.row())) {
3535 q->setSelection(q->visualRect(upper) | q->visualRect(lower), command | QItemSelectionModel::Rows);
3536 } else {
3537 selectionModel->select(QItemSelection(upper, lower), command | QItemSelectionModel::Rows);
3538 }
3539 }
3540}
3541
3542void QTableViewPrivate::selectColumn(int column, bool anchor)
3543{
3544 Q_Q(QTableView);
3545
3546 if (q->selectionBehavior() == QTableView::SelectRows
3547 || (q->selectionMode() == QTableView::SingleSelection
3548 && q->selectionBehavior() == QTableView::SelectItems))
3549 return;
3550
3551 if (column >= 0 && column < model->columnCount(root)) {
3552 int row = verticalHeader->logicalIndexAt(0);
3553 QModelIndex index = model->index(row, column, root);
3554 QItemSelectionModel::SelectionFlags command = q->selectionCommand(index);
3555
3556 {
3557 // currentSelectionStartIndex gets modified inside QAbstractItemView::currentChanged()
3558 const auto startIndex = currentSelectionStartIndex;
3559 selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
3560 currentSelectionStartIndex = startIndex;
3561 }
3562
3563 if ((anchor && !(command & QItemSelectionModel::Current))
3564 || (q->selectionMode() == QTableView::SingleSelection))
3565 currentSelectionStartIndex = model->index(row, column, root);
3566
3567 if (q->selectionMode() != QTableView::SingleSelection
3568 && command.testFlag(QItemSelectionModel::Toggle)) {
3569 if (anchor)
3570 ctrlDragSelectionFlag = horizontalHeader->selectionModel()->selectedColumns(row).contains(index)
3571 ? QItemSelectionModel::Deselect : QItemSelectionModel::Select;
3572 command &= ~QItemSelectionModel::Toggle;
3573 command |= ctrlDragSelectionFlag;
3574 if (!anchor)
3575 command |= QItemSelectionModel::Current;
3576 }
3577
3578 const auto columnSectionAnchor = currentSelectionStartIndex.column();
3579 QModelIndex left = model->index(row, qMin(columnSectionAnchor, column), root);
3580 QModelIndex right = model->index(row, qMax(columnSectionAnchor, column), root);
3581 if ((horizontalHeader->sectionsMoved() && left.column() != right.column())) {
3582 q->setSelection(q->visualRect(left) | q->visualRect(right), command | QItemSelectionModel::Columns);
3583 } else {
3584 selectionModel->select(QItemSelection(left, right), command | QItemSelectionModel::Columns);
3585 }
3586 }
3587}
3588
3589/*!
3590 \reimp
3591 */
3592void QTableView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
3593{
3594#if QT_CONFIG(accessibility)
3595 if (QAccessible::isActive()) {
3596 if (current.isValid() && hasFocus()) {
3597 Q_D(QTableView);
3598 int entry = d->accessibleChildIndex(current);
3599 QAccessibleEvent event(this, QAccessible::Focus);
3600 event.setChild(entry);
3601 QAccessible::updateAccessibility(&event);
3602 }
3603 }
3604#endif
3605 QAbstractItemView::currentChanged(current, previous);
3606}
3607
3608/*!
3609 \reimp
3610 */
3611void QTableView::selectionChanged(const QItemSelection &selected,
3612 const QItemSelection &deselected)
3613{
3614 Q_D(QTableView);
3615 Q_UNUSED(d);
3616#if QT_CONFIG(accessibility)
3617 if (QAccessible::isActive()) {
3618 // ### does not work properly for selection ranges.
3619 QModelIndex sel = selected.indexes().value(0);
3620 if (sel.isValid()) {
3621 int entry = d->accessibleChildIndex(sel);
3622 QAccessibleEvent event(this, QAccessible::SelectionAdd);
3623 event.setChild(entry);
3624 QAccessible::updateAccessibility(&event);
3625 }
3626 QModelIndex desel = deselected.indexes().value(0);
3627 if (desel.isValid()) {
3628 int entry = d->accessibleChildIndex(desel);
3629 QAccessibleEvent event(this, QAccessible::SelectionRemove);
3630 event.setChild(entry);
3631 QAccessible::updateAccessibility(&event);
3632 }
3633 }
3634#endif
3635 QAbstractItemView::selectionChanged(selected, deselected);
3636}
3637
3638int QTableView::visualIndex(const QModelIndex &index) const
3639{
3640 return index.row();
3641}
3642
3643QT_END_NAMESPACE
3644
3645#include "qtableview.moc"
3646
3647#include "moc_qtableview.cpp"