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

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