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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/widgets/itemviews/qtableview.cpp