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