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

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