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