1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtableview.h"
41
42#include <qheaderview.h>
43#include <qitemdelegate.h>
44#include <qapplication.h>
45#include <qpainter.h>
46#include <qstyle.h>
47#include <qsize.h>
48#include <qevent.h>
49#include <qbitarray.h>
50#include <qscrollbar.h>
51#if QT_CONFIG(abstractbutton)
52#include <qabstractbutton.h>
53#endif
54#include <private/qapplication_p.h>
55#include <private/qtableview_p.h>
56#include <private/qheaderview_p.h>
57#include <private/qscrollbar_p.h>
58#ifndef QT_NO_ACCESSIBILITY
59#include <qaccessible.h>
60#endif
61
62#include <algorithm>
63
64QT_BEGIN_NAMESPACE
65
66/** \internal
67 Add a span to the collection. the collection takes the ownership.
68 */
69void QSpanCollection::addSpan(QSpanCollection::Span *span)
70{
71 spans.push_back(span);
72 Index::iterator it_y = index.lowerBound(-span->top());
73 if (it_y == index.end() || it_y.key() != -span->top()) {
74 //there is no spans that starts with the row in the index, so create a sublist for it.
75 SubIndex sub_index;
76 if (it_y != index.end()) {
77 //the previouslist is the list of spans that sarts _before_ the row of the span.
78 // and which may intersect this row.
79 const SubIndex previousList = it_y.value();
80 for (Span *s : previousList) {
81 //If a subspans intersect the row, we need to split it into subspans
82 if(s->bottom() >= span->top())
83 sub_index.insert(-s->left(), s);
84 }
85 }
86 it_y = index.insert(-span->top(), sub_index);
87 //we will insert span to *it_y in the later loop
88 }
89
90 //insert the span as supspan in all the lists that intesects the span
91 while(-it_y.key() <= span->bottom()) {
92 (*it_y).insert(-span->left(), span);
93 if(it_y == index.begin())
94 break;
95 --it_y;
96 }
97}
98
99
100/** \internal
101* Has to be called after the height and width of a span is changed.
102*
103* old_height is the height before the change
104*
105* if the size of the span is now 0x0 the span will be deleted.
106*/
107void QSpanCollection::updateSpan(QSpanCollection::Span *span, int old_height)
108{
109 if (old_height < span->height()) {
110 //add the span as subspan in all the lists that intersect the new covered columns
111 Index::iterator it_y = index.lowerBound(-(span->top() + old_height - 1));
112 Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
113 while (-it_y.key() <= span->bottom()) {
114 (*it_y).insert(-span->left(), span);
115 if(it_y == index.begin())
116 break;
117 --it_y;
118 }
119 } else if (old_height > span->height()) {
120 //remove the span from all the subspans lists that intersect the columns not covered anymore
121 Index::iterator it_y = index.lowerBound(-qMax(span->bottom(), span->top())); //qMax useful if height is 0
122 Q_ASSERT(it_y != index.end()); //it_y must exist since the span is in the list
123 while (-it_y.key() <= span->top() + old_height -1) {
124 if (-it_y.key() > span->bottom()) {
125 int removed = (*it_y).remove(-span->left());
126 Q_ASSERT(removed == 1); Q_UNUSED(removed);
127 if (it_y->isEmpty()) {
128 it_y = index.erase(it_y);
129 }
130 }
131 if(it_y == index.begin())
132 break;
133 --it_y;
134 }
135 }
136
137 if (span->width() == 0 && span->height() == 0) {
138 spans.remove(span);
139 delete span;
140 }
141}
142
143/** \internal
144 * \return a spans that spans over cell x,y (column,row)
145 * or \nullptr if there is none.
146 */
147QSpanCollection::Span *QSpanCollection::spanAt(int x, int y) const
148{
149 Index::const_iterator it_y = index.lowerBound(-y);
150 if (it_y == index.end())
151 return nullptr;
152 SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
153 if (it_x == (*it_y).end())
154 return nullptr;
155 Span *span = *it_x;
156 if (span->right() >= x && span->bottom() >= y)
157 return span;
158 return nullptr;
159}
160
161
162/** \internal
163* remove and deletes all spans inside the collection
164*/
165void QSpanCollection::clear()
166{
167 qDeleteAll(spans);
168 index.clear();
169 spans.clear();
170}
171
172/** \internal
173 * return a list to all the spans that spans over cells in the given rectangle
174 */
175QSet<QSpanCollection::Span *> QSpanCollection::spansInRect(int x, int y, int w, int h) const
176{
177 QSet<Span *> list;
178 Index::const_iterator it_y = index.lowerBound(-y);
179 if(it_y == index.end())
180 --it_y;
181 while(-it_y.key() <= y + h) {
182 SubIndex::const_iterator it_x = (*it_y).lowerBound(-x);
183 if (it_x == (*it_y).end())
184 --it_x;
185 while(-it_x.key() <= x + w) {
186 Span *s = *it_x;
187 if (s->bottom() >= y && s->right() >= x)
188 list << s;
189 if (it_x == (*it_y).begin())
190 break;
191 --it_x;
192 }
193 if(it_y == index.begin())
194 break;
195 --it_y;
196 }
197 return list;
198}
199
200#undef DEBUG_SPAN_UPDATE
201
202#ifdef DEBUG_SPAN_UPDATE
203QDebug operator<<(QDebug str, const QSpanCollection::Span &span)
204{
205 str << '(' << span.top() << ',' << span.left() << ',' << span.bottom() << ',' << span.right() << ')';
206 return str;
207}
208#endif
209
210/** \internal
211* Updates the span collection after row insertion.
212*/
213void QSpanCollection::updateInsertedRows(int start, int end)
214{
215#ifdef DEBUG_SPAN_UPDATE
216 qDebug() << start << end << Qt::endl << index;
217#endif
218 if (spans.empty())
219 return;
220
221 int delta = end - start + 1;
222#ifdef DEBUG_SPAN_UPDATE
223 qDebug("Before");
224#endif
225 for (Span *span : spans) {
226#ifdef DEBUG_SPAN_UPDATE
227 qDebug() << span << *span;
228#endif
229 if (span->m_bottom < start)
230 continue;
231 if (span->m_top >= start)
232 span->m_top += delta;
233 span->m_bottom += delta;
234 }
235
236#ifdef DEBUG_SPAN_UPDATE
237 qDebug("After");
238 foreach (QSpanCollection::Span *span, spans)
239 qDebug() << span << *span;
240#endif
241
242 for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
243 int y = -it_y.key();
244 if (y < start) {
245 ++it_y;
246 continue;
247 }
248
249 index.insert(-y - delta, it_y.value());
250 it_y = index.erase(it_y);
251 }
252#ifdef DEBUG_SPAN_UPDATE
253 qDebug() << index;
254#endif
255}
256
257/** \internal
258* Updates the span collection after column insertion.
259*/
260void QSpanCollection::updateInsertedColumns(int start, int end)
261{
262#ifdef DEBUG_SPAN_UPDATE
263 qDebug() << start << end << Qt::endl << index;
264#endif
265 if (spans.empty())
266 return;
267
268 int delta = end - start + 1;
269#ifdef DEBUG_SPAN_UPDATE
270 qDebug("Before");
271#endif
272 for (Span *span : spans) {
273#ifdef DEBUG_SPAN_UPDATE
274 qDebug() << span << *span;
275#endif
276 if (span->m_right < start)
277 continue;
278 if (span->m_left >= start)
279 span->m_left += delta;
280 span->m_right += delta;
281 }
282
283#ifdef DEBUG_SPAN_UPDATE
284 qDebug("After");
285 foreach (QSpanCollection::Span *span, spans)
286 qDebug() << span << *span;
287#endif
288
289 for (Index::iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
290 SubIndex &subindex = it_y.value();
291 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
292 int x = -it.key();
293 if (x < start) {
294 ++it;
295 continue;
296 }
297 subindex.insert(-x - delta, it.value());
298 it = subindex.erase(it);
299 }
300 }
301#ifdef DEBUG_SPAN_UPDATE
302 qDebug() << index;
303#endif
304}
305
306/** \internal
307* Cleans a subindex from to be deleted spans. The update argument is used
308* to move the spans inside the subindex, in case their anchor changed.
309* \return true if no span in this subindex starts at y, and should thus be deleted.
310*/
311bool QSpanCollection::cleanSpanSubIndex(QSpanCollection::SubIndex &subindex, int y, bool update)
312{
313 if (subindex.isEmpty())
314 return true;
315
316 bool should_be_deleted = true;
317 SubIndex::iterator it = subindex.end();
318 do {
319 --it;
320 int x = -it.key();
321 Span *span = it.value();
322 if (span->will_be_deleted) {
323 it = subindex.erase(it);
324 continue;
325 }
326 if (update && span->m_left != x) {
327 subindex.insert(-span->m_left, span);
328 it = subindex.erase(it);
329 }
330 if (should_be_deleted && span->m_top == y)
331 should_be_deleted = false;
332 } while (it != subindex.begin());
333
334 return should_be_deleted;
335}
336
337/** \internal
338* Updates the span collection after row removal.
339*/
340void QSpanCollection::updateRemovedRows(int start, int end)
341{
342#ifdef DEBUG_SPAN_UPDATE
343 qDebug() << start << end << Qt::endl << index;
344#endif
345 if (spans.empty())
346 return;
347
348 SpanList spansToBeDeleted;
349 int delta = end - start + 1;
350#ifdef DEBUG_SPAN_UPDATE
351 qDebug("Before");
352#endif
353 for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
354 Span *span = *it;
355#ifdef DEBUG_SPAN_UPDATE
356 qDebug() << span << *span;
357#endif
358 if (span->m_bottom < start) {
359 ++it;
360 continue;
361 }
362 if (span->m_top < start) {
363 if (span->m_bottom <= end)
364 span->m_bottom = start - 1;
365 else
366 span->m_bottom -= delta;
367 } else {
368 if (span->m_bottom > end) {
369 if (span->m_top <= end)
370 span->m_top = start;
371 else
372 span->m_top -= delta;
373 span->m_bottom -= delta;
374 } else {
375 span->will_be_deleted = true;
376 }
377 }
378 if (span->m_top == span->m_bottom && span->m_left == span->m_right)
379 span->will_be_deleted = true;
380 if (span->will_be_deleted) {
381 spansToBeDeleted.push_back(span);
382 it = spans.erase(it);
383 } else {
384 ++it;
385 }
386 }
387
388#ifdef DEBUG_SPAN_UPDATE
389 qDebug("After");
390 foreach (QSpanCollection::Span *span, spans)
391 qDebug() << span << *span;
392#endif
393 if (spans.empty()) {
394 qDeleteAll(spansToBeDeleted);
395 index.clear();
396 return;
397 }
398
399 Index::iterator it_y = index.end();
400 do {
401 --it_y;
402 int y = -it_y.key();
403 SubIndex &subindex = it_y.value();
404 if (y < start) {
405 if (cleanSpanSubIndex(subindex, y))
406 it_y = index.erase(it_y);
407 } else if (y >= start && y <= end) {
408 bool span_at_start = false;
409 SubIndex spansToBeMoved;
410 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ++it) {
411 Span *span = it.value();
412 if (span->will_be_deleted)
413 continue;
414 if (!span_at_start && span->m_top == start)
415 span_at_start = true;
416 spansToBeMoved.insert(it.key(), span);
417 }
418
419 if (y == start && span_at_start)
420 subindex.clear();
421 else
422 it_y = index.erase(it_y);
423
424 if (span_at_start) {
425 Index::iterator it_start;
426 if (y == start)
427 it_start = it_y;
428 else {
429 it_start = index.find(-start);
430 if (it_start == index.end())
431 it_start = index.insert(-start, SubIndex());
432 }
433 SubIndex &start_subindex = it_start.value();
434 for (SubIndex::iterator it = spansToBeMoved.begin(); it != spansToBeMoved.end(); ++it)
435 start_subindex.insert(it.key(), it.value());
436 }
437 } else {
438 if (y == end + 1) {
439 Index::iterator it_top = index.find(-y + delta);
440 if (it_top == index.end())
441 it_top = index.insert(-y + delta, SubIndex());
442 for (SubIndex::iterator it = subindex.begin(); it != subindex.end(); ) {
443 Span *span = it.value();
444 if (!span->will_be_deleted)
445 it_top.value().insert(it.key(), span);
446 ++it;
447 }
448 } else {
449 index.insert(-y + delta, subindex);
450 }
451 it_y = index.erase(it_y);
452 }
453 } while (it_y != index.begin());
454
455#ifdef DEBUG_SPAN_UPDATE
456 qDebug() << index;
457 qDebug("Deleted");
458 foreach (QSpanCollection::Span *span, spansToBeDeleted)
459 qDebug() << span << *span;
460#endif
461 qDeleteAll(spansToBeDeleted);
462}
463
464/** \internal
465* Updates the span collection after column removal.
466*/
467void QSpanCollection::updateRemovedColumns(int start, int end)
468{
469#ifdef DEBUG_SPAN_UPDATE
470 qDebug() << start << end << Qt::endl << index;
471#endif
472 if (spans.empty())
473 return;
474
475 SpanList toBeDeleted;
476 int delta = end - start + 1;
477#ifdef DEBUG_SPAN_UPDATE
478 qDebug("Before");
479#endif
480 for (SpanList::iterator it = spans.begin(); it != spans.end(); ) {
481 Span *span = *it;
482#ifdef DEBUG_SPAN_UPDATE
483 qDebug() << span << *span;
484#endif
485 if (span->m_right < start) {
486 ++it;
487 continue;
488 }
489 if (span->m_left < start) {
490 if (span->m_right <= end)
491 span->m_right = start - 1;
492 else
493 span->m_right -= delta;
494 } else {
495 if (span->m_right > end) {
496 if (span->m_left <= end)
497 span->m_left = start;
498 else
499 span->m_left -= delta;
500 span->m_right -= delta;
501 } else {
502 span->will_be_deleted = true;
503 }
504 }
505 if (span->m_top == span->m_bottom && span->m_left == span->m_right)
506 span->will_be_deleted = true;
507 if (span->will_be_deleted) {
508 toBeDeleted.push_back(span);
509 it = spans.erase(it);
510 } else {
511 ++it;
512 }
513 }
514
515#ifdef DEBUG_SPAN_UPDATE
516 qDebug("After");
517 foreach (QSpanCollection::Span *span, spans)
518 qDebug() << span << *span;
519#endif
520 if (spans.empty()) {
521 qDeleteAll(toBeDeleted);
522 index.clear();
523 return;
524 }
525
526 for (Index::iterator it_y = index.begin(); it_y != index.end(); ) {
527 int y = -it_y.key();
528 if (cleanSpanSubIndex(it_y.value(), y, true))
529 it_y = index.erase(it_y);
530 else
531 ++it_y;
532 }
533
534#ifdef DEBUG_SPAN_UPDATE
535 qDebug() << index;
536 qDebug("Deleted");
537 foreach (QSpanCollection::Span *span, toBeDeleted)
538 qDebug() << span << *span;
539#endif
540 qDeleteAll(toBeDeleted);
541}
542
543#ifdef QT_BUILD_INTERNAL
544/*!
545 \internal
546 Checks whether the span index structure is self-consistent, and consistent with the spans list.
547*/
548bool QSpanCollection::checkConsistency() const
549{
550 for (Index::const_iterator it_y = index.begin(); it_y != index.end(); ++it_y) {
551 int y = -it_y.key();
552 const SubIndex &subIndex = it_y.value();
553 for (SubIndex::const_iterator it = subIndex.begin(); it != subIndex.end(); ++it) {
554 int x = -it.key();
555 Span *span = it.value();
556 const bool contains = std::find(spans.begin(), spans.end(), span) != spans.end();
557 if (!contains || span->left() != x || y < span->top() || y > span->bottom())
558 return false;
559 }
560 }
561
562 for (const Span *span : spans) {
563 if (span->width() < 1 || span->height() < 1
564 || (span->width() == 1 && span->height() == 1))
565 return false;
566 for (int y = span->top(); y <= span->bottom(); ++y) {
567 Index::const_iterator it_y = index.find(-y);
568 if (it_y == index.end()) {
569 if (y == span->top())
570 return false;
571 else
572 continue;
573 }
574 const SubIndex &subIndex = it_y.value();
575 SubIndex::const_iterator it = subIndex.find(-span->left());
576 if (it == subIndex.end() || it.value() != span)
577 return false;
578 }
579 }
580 return true;
581}
582#endif
583
584#if QT_CONFIG(abstractbutton)
585class QTableCornerButton : public QAbstractButton
586{
587 Q_OBJECT
588public:
589 QTableCornerButton(QWidget *parent) : QAbstractButton(parent) {}
590 void paintEvent(QPaintEvent*) override {
591 QStyleOptionHeader opt;
592 opt.init(this);
593 QStyle::State state = QStyle::State_None;
594 if (isEnabled())
595 state |= QStyle::State_Enabled;
596 if (isActiveWindow())
597 state |= QStyle::State_Active;
598 if (isDown())
599 state |= QStyle::State_Sunken;
600 opt.state = state;
601 opt.rect = rect();
602 opt.position = QStyleOptionHeader::OnlyOneSection;
603 QPainter painter(this);
604 style()->drawControl(QStyle::CE_Header, &opt, &painter, this);
605 }
606};
607#endif
608
609void QTableViewPrivate::init()
610{
611 Q_Q(QTableView);
612
613 q->setEditTriggers(editTriggers|QAbstractItemView::AnyKeyPressed);
614
615 QHeaderView *vertical = new QHeaderView(Qt::Vertical, q);
616 vertical->setSectionsClickable(true);
617 vertical->setHighlightSections(true);
618 q->setVerticalHeader(vertical);
619
620 QHeaderView *horizontal = new QHeaderView(Qt::Horizontal, q);
621 horizontal->setSectionsClickable(true);
622 horizontal->setHighlightSections(true);
623 q->setHorizontalHeader(horizontal);
624
625 tabKeyNavigation = true;
626
627#if QT_CONFIG(abstractbutton)
628 cornerWidget = new QTableCornerButton(q);
629 cornerWidget->setFocusPolicy(Qt::NoFocus);
630 QObject::connect(cornerWidget, SIGNAL(clicked()), q, SLOT(selectAll()));
631#endif
632}
633
634/*!
635 \internal
636 Trims away indices that are hidden in the treeview due to hidden horizontal or vertical sections.
637*/
638void QTableViewPrivate::trimHiddenSelections(QItemSelectionRange *range) const
639{
640 Q_ASSERT(range && range->isValid());
641
642 int top = range->top();
643 int left = range->left();
644 int bottom = range->bottom();
645 int right = range->right();
646
647 while (bottom >= top && verticalHeader->isSectionHidden(bottom))
648 --bottom;
649 while (right >= left && horizontalHeader->isSectionHidden(right))
650 --right;
651
652 if (top > bottom || left > right) { // everything is hidden
653 *range = QItemSelectionRange();
654 return;
655 }
656
657 while (verticalHeader->isSectionHidden(top) && top <= bottom)
658 ++top;
659 while (horizontalHeader->isSectionHidden(left) && left <= right)
660 ++left;
661
662 if (top > bottom || left > right) { // everything is hidden
663 *range = QItemSelectionRange();
664 return;
665 }
666
667 QModelIndex bottomRight = model->index(bottom, right, range->parent());
668 QModelIndex topLeft = model->index(top, left, range->parent());
669 *range = QItemSelectionRange(topLeft, bottomRight);
670}
671
672/*!
673 \internal
674 Sets the span for the cell at (\a row, \a column).
675*/
676void QTableViewPrivate::setSpan(int row, int column, int rowSpan, int columnSpan)
677{
678 if (Q_UNLIKELY(row < 0 || column < 0 || rowSpan <= 0 || columnSpan <= 0)) {
679 qWarning("QTableView::setSpan: invalid span given: (%d, %d, %d, %d)",
680 row, column, rowSpan, columnSpan);
681 return;
682 }
683 QSpanCollection::Span *sp = spans.spanAt(column, row);
684 if (sp) {
685 if (sp->top() != row || sp->left() != column) {
686 qWarning("QTableView::setSpan: span cannot overlap");
687 return;
688 }
689 if (rowSpan == 1 && columnSpan == 1) {
690 rowSpan = columnSpan = 0;
691 }
692 const int old_height = sp->height();
693 sp->m_bottom = row + rowSpan - 1;
694 sp->m_right = column + columnSpan - 1;
695 spans.updateSpan(sp, old_height);
696 return;
697 } else if (Q_UNLIKELY(rowSpan == 1 && columnSpan == 1)) {
698 qWarning("QTableView::setSpan: single cell span won't be added");
699 return;
700 }
701 sp = new QSpanCollection::Span(row, column, rowSpan, columnSpan);
702 spans.addSpan(sp);
703}
704
705/*!
706 \internal
707 Gets the span information for the cell at (\a row, \a column).
708*/
709QSpanCollection::Span QTableViewPrivate::span(int row, int column) const
710{
711 QSpanCollection::Span *sp = spans.spanAt(column, row);
712 if (sp)
713 return *sp;
714
715 return QSpanCollection::Span(row, column, 1, 1);
716}
717
718/*!
719 \internal
720 Returns the logical index of the last section that's part of the span.
721*/
722int QTableViewPrivate::sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const
723{
724 int visual = header->visualIndex(logical);
725 for (int i = 1; i < span; ) {
726 if (++visual >= header->count())
727 break;
728 logical = header->logicalIndex(visual);
729 ++i;
730 }
731 return logical;
732}
733
734/*!
735 \internal
736 Returns the size of the span starting at logical index \a logical
737 and spanning \a span sections.
738*/
739int QTableViewPrivate::sectionSpanSize(const QHeaderView *header, int logical, int span) const
740{
741 int endLogical = sectionSpanEndLogical(header, logical, span);
742 return header->sectionPosition(endLogical)
743 - header->sectionPosition(logical)
744 + header->sectionSize(endLogical);
745}
746
747/*!
748 \internal
749 Returns \c true if the section at logical index \a logical is part of the span
750 starting at logical index \a spanLogical and spanning \a span sections;
751 otherwise, returns \c false.
752*/
753bool QTableViewPrivate::spanContainsSection(const QHeaderView *header, int logical, int spanLogical, int span) const
754{
755 if (logical == spanLogical)
756 return true; // it's the start of the span
757 int visual = header->visualIndex(spanLogical);
758 for (int i = 1; i < span; ) {
759 if (++visual >= header->count())
760 break;
761 spanLogical = header->logicalIndex(visual);
762 if (logical == spanLogical)
763 return true;
764 ++i;
765 }
766 return false;
767}
768
769/*!
770 \internal
771 Searches for the next cell which is available for e.g. keyboard navigation
772 The search is done by row
773*/
774int QTableViewPrivate::nextActiveVisualRow(int rowToStart, int column, int limit,
775 SearchDirection searchDirection) const
776{
777 const int lc = logicalColumn(column);
778 int visualRow = rowToStart;
779 const auto isCellActive = [this](int vr, int lc)
780 {
781 const int lr = logicalRow(vr);
782 return !isRowHidden(lr) && isCellEnabled(lr, lc);
783 };
784 switch (searchDirection) {
785 case SearchDirection::Increasing:
786 if (visualRow < limit) {
787 while (!isCellActive(visualRow, lc)) {
788 if (++visualRow == limit)
789 return rowToStart;
790 }
791 }
792 break;
793 case SearchDirection::Decreasing:
794 while (visualRow > limit && !isCellActive(visualRow, lc))
795 --visualRow;
796 break;
797 }
798 return visualRow;
799}
800
801/*!
802 \internal
803 Searches for the next cell which is available for e.g. keyboard navigation
804 The search is done by column
805*/
806int QTableViewPrivate::nextActiveVisualColumn(int row, int columnToStart, int limit,
807 SearchDirection searchDirection) const
808{
809 const int lr = logicalRow(row);
810 int visualColumn = columnToStart;
811 const auto isCellActive = [this](int lr, int vc)
812 {
813 const int lc = logicalColumn(vc);
814 return !isColumnHidden(lc) && isCellEnabled(lr, lc);
815 };
816 switch (searchDirection) {
817 case SearchDirection::Increasing:
818 while (visualColumn < limit && !isCellActive(lr, visualColumn))
819 ++visualColumn;
820 break;
821 case SearchDirection::Decreasing:
822 while (visualColumn > limit && !isCellActive(lr, visualColumn))
823 --visualColumn;
824 break;
825 }
826 return visualColumn;
827}
828
829/*!
830 \internal
831 Returns the visual rect for the given \a span.
832*/
833QRect QTableViewPrivate::visualSpanRect(const QSpanCollection::Span &span) const
834{
835 Q_Q(const QTableView);
836 // vertical
837 int row = span.top();
838 int rowp = verticalHeader->sectionViewportPosition(row);
839 int rowh = rowSpanHeight(row, span.height());
840 // horizontal
841 int column = span.left();
842 int colw = columnSpanWidth(column, span.width());
843 if (q->isRightToLeft())
844 column = span.right();
845 int colp = horizontalHeader->sectionViewportPosition(column);
846
847 const int i = showGrid ? 1 : 0;
848 if (q->isRightToLeft())
849 return QRect(colp + i, rowp, colw - i, rowh - i);
850 return QRect(colp, rowp, colw - i, rowh - i);
851}
852
853/*!
854 \internal
855 Draws the spanning cells within rect \a area, and clips them off as
856 preparation for the main drawing loop.
857 \a drawn is a QBitArray of visualRowCountxvisualCoulumnCount which say if particular cell has been drawn
858*/
859void QTableViewPrivate::drawAndClipSpans(const QRegion &area, QPainter *painter,
860 const QStyleOptionViewItem &option, QBitArray *drawn,
861 int firstVisualRow, int lastVisualRow, int firstVisualColumn, int lastVisualColumn)
862{
863 Q_Q(const QTableView);
864 bool alternateBase = false;
865 QRegion region = viewport->rect();
866
867 QSet<QSpanCollection::Span *> visibleSpans;
868 bool sectionMoved = verticalHeader->sectionsMoved() || horizontalHeader->sectionsMoved();
869
870 if (!sectionMoved) {
871 visibleSpans = spans.spansInRect(logicalColumn(firstVisualColumn), logicalRow(firstVisualRow),
872 lastVisualColumn - firstVisualColumn + 1, lastVisualRow - firstVisualRow + 1);
873 } else {
874 for(int x = firstVisualColumn; x <= lastVisualColumn; x++)
875 for(int y = firstVisualRow; y <= lastVisualRow; y++)
876 visibleSpans.insert(spans.spanAt(x,y));
877 visibleSpans.remove(nullptr);
878 }
879
880 for (QSpanCollection::Span *span : qAsConst(visibleSpans)) {
881 int row = span->top();
882 int col = span->left();
883 QModelIndex index = model->index(row, col, root);
884 if (!index.isValid())
885 continue;
886 QRect rect = visualSpanRect(*span);
887 rect.translate(scrollDelayOffset);
888 if (!area.intersects(rect))
889 continue;
890 QStyleOptionViewItem opt = option;
891 opt.rect = rect;
892 alternateBase = alternatingColors && (span->top() & 1);
893 opt.features.setFlag(QStyleOptionViewItem::Alternate, alternateBase);
894 drawCell(painter, opt, index);
895 if (showGrid) {
896 // adjust the clip rect to be able to paint the top & left grid lines
897 // if the headers are not visible, see paintEvent()
898 if (horizontalHeader->visualIndex(row) == 0)
899 rect.setTop(rect.top() + 1);
900 if (verticalHeader->visualIndex(row) == 0) {
901 if (q->isLeftToRight())
902 rect.setLeft(rect.left() + 1);
903 else
904 rect.setRight(rect.right() - 1);
905 }
906 }
907 region -= rect;
908 for (int r = span->top(); r <= span->bottom(); ++r) {
909 const int vr = visualRow(r);
910 if (vr < firstVisualRow || vr > lastVisualRow)
911 continue;
912 for (int c = span->left(); c <= span->right(); ++c) {
913 const int vc = visualColumn(c);
914 if (vc < firstVisualColumn || vc > lastVisualColumn)
915 continue;
916 drawn->setBit((vr - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
917 + vc - firstVisualColumn);
918 }
919 }
920
921 }
922 painter->setClipRegion(region);
923}
924
925/*!
926 \internal
927 Updates spans after row insertion.
928*/
929void QTableViewPrivate::_q_updateSpanInsertedRows(const QModelIndex &parent, int start, int end)
930{
931 Q_UNUSED(parent)
932 spans.updateInsertedRows(start, end);
933}
934
935/*!
936 \internal
937 Updates spans after column insertion.
938*/
939void QTableViewPrivate::_q_updateSpanInsertedColumns(const QModelIndex &parent, int start, int end)
940{
941 Q_UNUSED(parent)
942 spans.updateInsertedColumns(start, end);
943}
944
945/*!
946 \internal
947 Updates spans after row removal.
948*/
949void QTableViewPrivate::_q_updateSpanRemovedRows(const QModelIndex &parent, int start, int end)
950{
951 Q_UNUSED(parent)
952 spans.updateRemovedRows(start, end);
953}
954
955/*!
956 \internal
957 Updates spans after column removal.
958*/
959void QTableViewPrivate::_q_updateSpanRemovedColumns(const QModelIndex &parent, int start, int end)
960{
961 Q_UNUSED(parent)
962 spans.updateRemovedColumns(start, end);
963}
964
965/*!
966 \internal
967 Sort the model when the header sort indicator changed
968*/
969void QTableViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order)
970{
971 model->sort(column, order);
972}
973
974/*!
975 \internal
976 Draws a table cell.
977*/
978void QTableViewPrivate::drawCell(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
979{
980 Q_Q(QTableView);
981 QStyleOptionViewItem opt = option;
982
983 if (selectionModel && selectionModel->isSelected(index))
984 opt.state |= QStyle::State_Selected;
985 if (index == hover)
986 opt.state |= QStyle::State_MouseOver;
987 if (option.state & QStyle::State_Enabled) {
988 QPalette::ColorGroup cg;
989 if ((model->flags(index) & Qt::ItemIsEnabled) == 0) {
990 opt.state &= ~QStyle::State_Enabled;
991 cg = QPalette::Disabled;
992 } else {
993 cg = QPalette::Normal;
994 }
995 opt.palette.setCurrentColorGroup(cg);
996 }
997
998 if (index == q->currentIndex()) {
999 const bool focus = (q->hasFocus() || viewport->hasFocus()) && q->currentIndex().isValid();
1000 if (focus)
1001 opt.state |= QStyle::State_HasFocus;
1002 }
1003
1004 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, q);
1005
1006 q->itemDelegate(index)->paint(painter, opt, index);
1007}
1008
1009/*!
1010 \internal
1011 Get sizeHint width for single Index (providing existing hint and style option)
1012*/
1013int QTableViewPrivate::widthHintForIndex(const QModelIndex &index, int hint, const QStyleOptionViewItem &option) const
1014{
1015 Q_Q(const QTableView);
1016 QWidget *editor = editorForIndex(index).widget.data();
1017 if (editor && persistent.contains(editor)) {
1018 hint = qMax(hint, editor->sizeHint().width());
1019 int min = editor->minimumSize().width();
1020 int max = editor->maximumSize().width();
1021 hint = qBound(min, hint, max);
1022 }
1023 hint = qMax(hint, q->itemDelegate(index)->sizeHint(option, index).width());
1024 return hint;
1025}
1026
1027/*!
1028 \internal
1029 Get sizeHint height for single Index (providing existing hint and style option)
1030*/
1031int QTableViewPrivate::heightHintForIndex(const QModelIndex &index, int hint, QStyleOptionViewItem &option) const
1032{
1033 Q_Q(const QTableView);
1034 QWidget *editor = editorForIndex(index).widget.data();
1035 if (editor && persistent.contains(editor)) {
1036 hint = qMax(hint, editor->sizeHint().height());
1037 int min = editor->minimumSize().height();
1038 int max = editor->maximumSize().height();
1039 hint = qBound(min, hint, max);
1040 }
1041
1042 if (wrapItemText) {// for wrapping boundaries
1043 option.rect.setY(q->rowViewportPosition(index.row()));
1044 int height = q->rowHeight(index.row());
1045 // if the option.height == 0 then q->itemDelegate(index)->sizeHint(option, index) will be wrong.
1046 // The option.height == 0 is used to conclude that the text is not wrapped, and hence it will
1047 // (exactly like widthHintForIndex) return a QSize with a long width (that we don't use) -
1048 // and the height of the text if it was/is on one line.
1049 // What we want is a height hint for the current width (and we know that this section is not hidden)
1050 // Therefore we catch this special situation with:
1051 if (height == 0)
1052 height = 1;
1053 option.rect.setHeight(height);
1054 option.rect.setX(q->columnViewportPosition(index.column()));
1055 option.rect.setWidth(q->columnWidth(index.column()));
1056 // 1px less space when grid is shown (see drawCell)
1057 if (showGrid)
1058 option.rect.setWidth(option.rect.width() - 1);
1059 }
1060 hint = qMax(hint, q->itemDelegate(index)->sizeHint(option, index).height());
1061 return hint;
1062}
1063
1064
1065/*!
1066 \class QTableView
1067
1068 \brief The QTableView class provides a default model/view
1069 implementation of a table view.
1070
1071 \ingroup model-view
1072 \ingroup advanced
1073 \inmodule QtWidgets
1074
1075 \image windows-tableview.png
1076
1077 A QTableView implements a table view that displays items from a
1078 model. This class is used to provide standard tables that were
1079 previously provided by the QTable class, but using the more
1080 flexible approach provided by Qt's model/view architecture.
1081
1082 The QTableView class is one of the \l{Model/View Classes}
1083 and is part of Qt's \l{Model/View Programming}{model/view framework}.
1084
1085 QTableView implements the interfaces defined by the
1086 QAbstractItemView class to allow it to display data provided by
1087 models derived from the QAbstractItemModel class.
1088
1089 \section1 Navigation
1090
1091 You can navigate the cells in the table by clicking on a cell with the
1092 mouse, or by using the arrow keys. Because QTableView enables
1093 \l{QAbstractItemView::tabKeyNavigation}{tabKeyNavigation} by default, you
1094 can also hit Tab and Backtab to move from cell to cell.
1095
1096 \section1 Visual Appearance
1097
1098 The table has a vertical header that can be obtained using the
1099 verticalHeader() function, and a horizontal header that is available
1100 through the horizontalHeader() function. The height of each row in the
1101 table can be found by using rowHeight(); similarly, the width of
1102 columns can be found using columnWidth(). Since both of these are plain
1103 widgets, you can hide either of them using their hide() functions.
1104
1105 Rows and columns can be hidden and shown with hideRow(), hideColumn(),
1106 showRow(), and showColumn(). They can be selected with selectRow()
1107 and selectColumn(). The table will show a grid depending on the
1108 \l showGrid property.
1109
1110 The items shown in a table view, like those in the other item views, are
1111 rendered and edited using standard \l{QStyledItemDelegate}{delegates}. However,
1112 for some tasks it is sometimes useful to be able to insert widgets in a
1113 table instead. Widgets are set for particular indexes with the
1114 \l{QAbstractItemView::}{setIndexWidget()} function, and
1115 later retrieved with \l{QAbstractItemView::}{indexWidget()}.
1116
1117 \table
1118 \row \li \inlineimage qtableview-resized.png
1119 \li By default, the cells in a table do not expand to fill the available space.
1120
1121 You can make the cells fill the available space by stretching the last
1122 header section. Access the relevant header using horizontalHeader()
1123 or verticalHeader() and set the header's \l{QHeaderView::}{stretchLastSection}
1124 property.
1125
1126 To distribute the available space according to the space requirement of
1127 each column or row, call the view's resizeColumnsToContents() or
1128 resizeRowsToContents() functions.
1129 \endtable
1130
1131 \section1 Coordinate Systems
1132
1133 For some specialized forms of tables it is useful to be able to
1134 convert between row and column indexes and widget coordinates.
1135 The rowAt() function provides the y-coordinate within the view of the
1136 specified row; the row index can be used to obtain a corresponding
1137 y-coordinate with rowViewportPosition(). The columnAt() and
1138 columnViewportPosition() functions provide the equivalent conversion
1139 operations between x-coordinates and column indexes.
1140
1141 \sa QTableWidget, {View Classes}, QAbstractItemModel, QAbstractItemView,
1142 {Chart Example}, {Pixelator Example}, {Table Model Example}
1143*/
1144
1145/*!
1146 Constructs a table view with a \a parent to represent the data.
1147
1148 \sa QAbstractItemModel
1149*/
1150
1151QTableView::QTableView(QWidget *parent)
1152 : QAbstractItemView(*new QTableViewPrivate, parent)
1153{
1154 Q_D(QTableView);
1155 d->init();
1156}
1157
1158/*!
1159 \internal
1160*/
1161QTableView::QTableView(QTableViewPrivate &dd, QWidget *parent)
1162 : QAbstractItemView(dd, parent)
1163{
1164 Q_D(QTableView);
1165 d->init();
1166}
1167
1168/*!
1169 Destroys the table view.
1170*/
1171QTableView::~QTableView()
1172{
1173}
1174
1175/*!
1176 \reimp
1177*/
1178QSize QTableView::viewportSizeHint() const
1179{
1180 Q_D(const QTableView);
1181 QSize result( (d->verticalHeader->isHidden() ? 0 : d->verticalHeader->width()) + d->horizontalHeader->length(),
1182 (d->horizontalHeader->isHidden() ? 0 : d->horizontalHeader->height()) + d->verticalHeader->length());
1183 return result;
1184}
1185
1186/*!
1187 \reimp
1188*/
1189void QTableView::setModel(QAbstractItemModel *model)
1190{
1191 Q_D(QTableView);
1192 if (model == d->model)
1193 return;
1194 //let's disconnect from the old model
1195 if (d->model && d->model != QAbstractItemModelPrivate::staticEmptyModel()) {
1196 disconnect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)),
1197 this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int)));
1198 disconnect(d->model, SIGNAL(columnsInserted(QModelIndex,int,int)),
1199 this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int)));
1200 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
1201 this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int)));
1202 disconnect(d->model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
1203 this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int)));
1204 }
1205 if (d->selectionModel) { // support row editing
1206 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
1207 d->model, SLOT(submit()));
1208 }
1209 if (model) { //and connect to the new one
1210 connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)),
1211 this, SLOT(_q_updateSpanInsertedRows(QModelIndex,int,int)));
1212 connect(model, SIGNAL(columnsInserted(QModelIndex,int,int)),
1213 this, SLOT(_q_updateSpanInsertedColumns(QModelIndex,int,int)));
1214 connect(model, SIGNAL(rowsRemoved(QModelIndex,int,int)),
1215 this, SLOT(_q_updateSpanRemovedRows(QModelIndex,int,int)));
1216 connect(model, SIGNAL(columnsRemoved(QModelIndex,int,int)),
1217 this, SLOT(_q_updateSpanRemovedColumns(QModelIndex,int,int)));
1218 }
1219 d->verticalHeader->setModel(model);
1220 d->horizontalHeader->setModel(model);
1221 QAbstractItemView::setModel(model);
1222}
1223
1224/*!
1225 \reimp
1226*/
1227void QTableView::setRootIndex(const QModelIndex &index)
1228{
1229 Q_D(QTableView);
1230 if (index == d->root) {
1231 viewport()->update();
1232 return;
1233 }
1234 d->verticalHeader->setRootIndex(index);
1235 d->horizontalHeader->setRootIndex(index);
1236 QAbstractItemView::setRootIndex(index);
1237}
1238
1239/*!
1240 \internal
1241*/
1242void QTableView::doItemsLayout()
1243{
1244 Q_D(QTableView);
1245 QAbstractItemView::doItemsLayout();
1246 if (!d->verticalHeader->updatesEnabled())
1247 d->verticalHeader->setUpdatesEnabled(true);
1248}
1249
1250/*!
1251 \reimp
1252*/
1253void QTableView::setSelectionModel(QItemSelectionModel *selectionModel)
1254{
1255 Q_D(QTableView);
1256 Q_ASSERT(selectionModel);
1257 if (d->selectionModel) {
1258 // support row editing
1259 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
1260 d->model, SLOT(submit()));
1261 }
1262
1263 d->verticalHeader->setSelectionModel(selectionModel);
1264 d->horizontalHeader->setSelectionModel(selectionModel);
1265 QAbstractItemView::setSelectionModel(selectionModel);
1266
1267 if (d->selectionModel) {
1268 // support row editing
1269 connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
1270 d->model, SLOT(submit()));
1271 }
1272}
1273
1274/*!
1275 Returns the table view's horizontal header.
1276
1277 \sa setHorizontalHeader(), verticalHeader(), QAbstractItemModel::headerData()
1278*/
1279QHeaderView *QTableView::horizontalHeader() const
1280{
1281 Q_D(const QTableView);
1282 return d->horizontalHeader;
1283}
1284
1285/*!
1286 Returns the table view's vertical header.
1287
1288 \sa setVerticalHeader(), horizontalHeader(), QAbstractItemModel::headerData()
1289*/
1290QHeaderView *QTableView::verticalHeader() const
1291{
1292 Q_D(const QTableView);
1293 return d->verticalHeader;
1294}
1295
1296/*!
1297 Sets the widget to use for the horizontal header to \a header.
1298
1299 \sa horizontalHeader(), setVerticalHeader()
1300*/
1301void QTableView::setHorizontalHeader(QHeaderView *header)
1302{
1303 Q_D(QTableView);
1304
1305 if (!header || header == d->horizontalHeader)
1306 return;
1307 if (d->horizontalHeader && d->horizontalHeader->parent() == this)
1308 delete d->horizontalHeader;
1309 d->horizontalHeader = header;
1310 d->horizontalHeader->setParent(this);
1311 d->horizontalHeader->setFirstSectionMovable(true);
1312 if (!d->horizontalHeader->model()) {
1313 d->horizontalHeader->setModel(d->model);
1314 if (d->selectionModel)
1315 d->horizontalHeader->setSelectionModel(d->selectionModel);
1316 }
1317
1318 connect(d->horizontalHeader,SIGNAL(sectionResized(int,int,int)),
1319 this, SLOT(columnResized(int,int,int)));
1320 connect(d->horizontalHeader, SIGNAL(sectionMoved(int,int,int)),
1321 this, SLOT(columnMoved(int,int,int)));
1322 connect(d->horizontalHeader, SIGNAL(sectionCountChanged(int,int)),
1323 this, SLOT(columnCountChanged(int,int)));
1324 connect(d->horizontalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectColumn(int)));
1325 connect(d->horizontalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectColumn(int)));
1326 connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)),
1327 this, SLOT(resizeColumnToContents(int)));
1328 connect(d->horizontalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries()));
1329
1330 //update the sorting enabled states on the new header
1331 setSortingEnabled(d->sortingEnabled);
1332}
1333
1334/*!
1335 Sets the widget to use for the vertical header to \a header.
1336
1337 \sa verticalHeader(), setHorizontalHeader()
1338*/
1339void QTableView::setVerticalHeader(QHeaderView *header)
1340{
1341 Q_D(QTableView);
1342
1343 if (!header || header == d->verticalHeader)
1344 return;
1345 if (d->verticalHeader && d->verticalHeader->parent() == this)
1346 delete d->verticalHeader;
1347 d->verticalHeader = header;
1348 d->verticalHeader->setParent(this);
1349 d->verticalHeader->setFirstSectionMovable(true);
1350 if (!d->verticalHeader->model()) {
1351 d->verticalHeader->setModel(d->model);
1352 if (d->selectionModel)
1353 d->verticalHeader->setSelectionModel(d->selectionModel);
1354 }
1355
1356 connect(d->verticalHeader, SIGNAL(sectionResized(int,int,int)),
1357 this, SLOT(rowResized(int,int,int)));
1358 connect(d->verticalHeader, SIGNAL(sectionMoved(int,int,int)),
1359 this, SLOT(rowMoved(int,int,int)));
1360 connect(d->verticalHeader, SIGNAL(sectionCountChanged(int,int)),
1361 this, SLOT(rowCountChanged(int,int)));
1362 connect(d->verticalHeader, SIGNAL(sectionPressed(int)), this, SLOT(selectRow(int)));
1363 connect(d->verticalHeader, SIGNAL(sectionEntered(int)), this, SLOT(_q_selectRow(int)));
1364 connect(d->verticalHeader, SIGNAL(sectionHandleDoubleClicked(int)),
1365 this, SLOT(resizeRowToContents(int)));
1366 connect(d->verticalHeader, SIGNAL(geometriesChanged()), this, SLOT(updateGeometries()));
1367}
1368
1369/*!
1370 \internal
1371
1372 Scroll the contents of the table view by (\a dx, \a dy).
1373*/
1374void QTableView::scrollContentsBy(int dx, int dy)
1375{
1376 Q_D(QTableView);
1377
1378 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
1379
1380 dx = isRightToLeft() ? -dx : dx;
1381 if (dx) {
1382 int oldOffset = d->horizontalHeader->offset();
1383 d->horizontalHeader->d_func()->setScrollOffset(horizontalScrollBar(), horizontalScrollMode());
1384 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
1385 int newOffset = d->horizontalHeader->offset();
1386 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset;
1387 }
1388 }
1389 if (dy) {
1390 int oldOffset = d->verticalHeader->offset();
1391 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
1392 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
1393 int newOffset = d->verticalHeader->offset();
1394 dy = oldOffset - newOffset;
1395 }
1396 }
1397 d->scrollContentsBy(dx, dy);
1398
1399 if (d->showGrid) {
1400 //we need to update the first line of the previous top item in the view
1401 //because it has the grid drawn if the header is invisible.
1402 //It is strictly related to what's done at then end of the paintEvent
1403 if (dy > 0 && d->horizontalHeader->isHidden()) {
1404 d->viewport->update(0, dy, d->viewport->width(), dy);
1405 }
1406 if (dx > 0 && d->verticalHeader->isHidden()) {
1407 d->viewport->update(dx, 0, dx, d->viewport->height());
1408 }
1409 }
1410}
1411
1412/*!
1413 \reimp
1414*/
1415QStyleOptionViewItem QTableView::viewOptions() const
1416{
1417 QStyleOptionViewItem option = QAbstractItemView::viewOptions();
1418 option.showDecorationSelected = true;
1419 return option;
1420}
1421
1422/*!
1423 Paints the table on receipt of the given paint event \a event.
1424*/
1425void QTableView::paintEvent(QPaintEvent *event)
1426{
1427 Q_D(QTableView);
1428 // setup temp variables for the painting
1429 QStyleOptionViewItem option = d->viewOptionsV1();
1430 const QPoint offset = d->scrollDelayOffset;
1431 const bool showGrid = d->showGrid;
1432 const int gridSize = showGrid ? 1 : 0;
1433 const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
1434 const QColor gridColor = static_cast<QRgb>(gridHint);
1435 const QPen gridPen = QPen(gridColor, 0, d->gridStyle);
1436 const QHeaderView *verticalHeader = d->verticalHeader;
1437 const QHeaderView *horizontalHeader = d->horizontalHeader;
1438 const bool alternate = d->alternatingColors;
1439 const bool rightToLeft = isRightToLeft();
1440
1441 QPainter painter(d->viewport);
1442
1443 // if there's nothing to do, clear the area and return
1444 if (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)
1445 return;
1446
1447 const int x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);
1448 const int y = verticalHeader->length() - verticalHeader->offset() - 1;
1449
1450 //firstVisualRow is the visual index of the first visible row. lastVisualRow is the visual index of the last visible Row.
1451 //same goes for ...VisualColumn
1452 int firstVisualRow = qMax(verticalHeader->visualIndexAt(0),0);
1453 int lastVisualRow = verticalHeader->visualIndexAt(verticalHeader->height());
1454 if (lastVisualRow == -1)
1455 lastVisualRow = d->model->rowCount(d->root) - 1;
1456
1457 int firstVisualColumn = horizontalHeader->visualIndexAt(0);
1458 int lastVisualColumn = horizontalHeader->visualIndexAt(horizontalHeader->width());
1459 if (rightToLeft)
1460 qSwap(firstVisualColumn, lastVisualColumn);
1461 if (firstVisualColumn == -1)
1462 firstVisualColumn = 0;
1463 if (lastVisualColumn == -1)
1464 lastVisualColumn = horizontalHeader->count() - 1;
1465
1466 QBitArray drawn((lastVisualRow - firstVisualRow + 1) * (lastVisualColumn - firstVisualColumn + 1));
1467
1468 const QRegion region = event->region().translated(offset);
1469
1470 if (d->hasSpans()) {
1471 d->drawAndClipSpans(region, &painter, option, &drawn,
1472 firstVisualRow, lastVisualRow, firstVisualColumn, lastVisualColumn);
1473 }
1474
1475 for (QRect dirtyArea : region) {
1476 dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));
1477 if (rightToLeft) {
1478 dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));
1479 } else {
1480 dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));
1481 }
1482 // dirtyArea may be invalid when the horizontal header is not stretched
1483 if (!dirtyArea.isValid())
1484 continue;
1485
1486 // get the horizontal start and end visual sections
1487 int left = horizontalHeader->visualIndexAt(dirtyArea.left());
1488 int right = horizontalHeader->visualIndexAt(dirtyArea.right());
1489 if (rightToLeft)
1490 qSwap(left, right);
1491 if (left == -1) left = 0;
1492 if (right == -1) right = horizontalHeader->count() - 1;
1493
1494 // get the vertical start and end visual sections and if alternate color
1495 int bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());
1496 if (bottom == -1) bottom = verticalHeader->count() - 1;
1497 int top = 0;
1498 bool alternateBase = false;
1499 if (alternate && verticalHeader->sectionsHidden()) {
1500 const int verticalOffset = verticalHeader->offset();
1501 int row = verticalHeader->logicalIndex(top);
1502 for (int y = 0;
1503 ((y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);
1504 ++top) {
1505 row = verticalHeader->logicalIndex(top);
1506 if (alternate && !verticalHeader->isSectionHidden(row))
1507 alternateBase = !alternateBase;
1508 }
1509 } else {
1510 top = verticalHeader->visualIndexAt(dirtyArea.top());
1511 alternateBase = (top & 1) && alternate;
1512 }
1513 if (top == -1 || top > bottom)
1514 continue;
1515
1516 // Paint each row item
1517 for (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {
1518 int row = verticalHeader->logicalIndex(visualRowIndex);
1519 if (verticalHeader->isSectionHidden(row))
1520 continue;
1521 int rowY = rowViewportPosition(row);
1522 rowY += offset.y();
1523 int rowh = rowHeight(row) - gridSize;
1524
1525 // Paint each column item
1526 for (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {
1527 int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)
1528 + visualColumnIndex - firstVisualColumn;
1529
1530 if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))
1531 continue;
1532 drawn.setBit(currentBit);
1533
1534 int col = horizontalHeader->logicalIndex(visualColumnIndex);
1535 if (horizontalHeader->isSectionHidden(col))
1536 continue;
1537 int colp = columnViewportPosition(col);
1538 colp += offset.x();
1539 int colw = columnWidth(col) - gridSize;
1540
1541 const QModelIndex index = d->model->index(row, col, d->root);
1542 if (index.isValid()) {
1543 option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);
1544 if (alternate) {
1545 if (alternateBase)
1546 option.features |= QStyleOptionViewItem::Alternate;
1547 else
1548 option.features &= ~QStyleOptionViewItem::Alternate;
1549 }
1550 d->drawCell(&painter, option, index);
1551 }
1552 }
1553 alternateBase = !alternateBase && alternate;
1554 }
1555
1556 if (showGrid) {
1557 // Find the bottom right (the last rows/columns might be hidden)
1558 while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;
1559 QPen old = painter.pen();
1560 painter.setPen(gridPen);
1561 // Paint each row
1562 for (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {
1563 int row = verticalHeader->logicalIndex(visualIndex);
1564 if (verticalHeader->isSectionHidden(row))
1565 continue;
1566 int rowY = rowViewportPosition(row);
1567 rowY += offset.y();
1568 int rowh = rowHeight(row) - gridSize;
1569 painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);
1570 }
1571
1572 // Paint each column
1573 for (int h = left; h <= right; ++h) {
1574 int col = horizontalHeader->logicalIndex(h);
1575 if (horizontalHeader->isSectionHidden(col))
1576 continue;
1577 int colp = columnViewportPosition(col);
1578 colp += offset.x();
1579 if (!rightToLeft)
1580 colp += columnWidth(col) - gridSize;
1581 painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom());
1582 }
1583 painter.setPen(old);
1584 }
1585 }
1586
1587#if QT_CONFIG(draganddrop)
1588 // Paint the dropIndicator
1589 d->paintDropIndicator(&painter);
1590#endif
1591}
1592
1593/*!
1594 Returns the index position of the model item corresponding to the
1595 table item at position \a pos in contents coordinates.
1596*/
1597QModelIndex QTableView::indexAt(const QPoint &pos) const
1598{
1599 Q_D(const QTableView);
1600 d->executePostedLayout();
1601 int r = rowAt(pos.y());
1602 int c = columnAt(pos.x());
1603 if (r >= 0 && c >= 0) {
1604 if (d->hasSpans()) {
1605 QSpanCollection::Span span = d->span(r, c);
1606 r = span.top();
1607 c = span.left();
1608 }
1609 return d->model->index(r, c, d->root);
1610 }
1611 return QModelIndex();
1612}
1613
1614/*!
1615 Returns the horizontal offset of the items in the table view.
1616
1617 Note that the table view uses the horizontal header section
1618 positions to determine the positions of columns in the view.
1619
1620 \sa verticalOffset()
1621*/
1622int QTableView::horizontalOffset() const
1623{
1624 Q_D(const QTableView);
1625 return d->horizontalHeader->offset();
1626}
1627
1628/*!
1629 Returns the vertical offset of the items in the table view.
1630
1631 Note that the table view uses the vertical header section
1632 positions to determine the positions of rows in the view.
1633
1634 \sa horizontalOffset()
1635*/
1636int QTableView::verticalOffset() const
1637{
1638 Q_D(const QTableView);
1639 return d->verticalHeader->offset();
1640}
1641
1642/*!
1643 \fn QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1644
1645 Moves the cursor in accordance with the given \a cursorAction, using the
1646 information provided by the \a modifiers.
1647
1648 \sa QAbstractItemView::CursorAction
1649*/
1650QModelIndex QTableView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
1651{
1652 Q_D(QTableView);
1653 Q_UNUSED(modifiers);
1654
1655 int bottom = d->model->rowCount(d->root) - 1;
1656 // make sure that bottom is the bottommost *visible* row
1657 while (bottom >= 0 && isRowHidden(d->logicalRow(bottom)))
1658 --bottom;
1659
1660 int right = d->model->columnCount(d->root) - 1;
1661
1662 while (right >= 0 && isColumnHidden(d->logicalColumn(right)))
1663 --right;
1664
1665 if (bottom == -1 || right == -1)
1666 return QModelIndex(); // model is empty
1667
1668 QModelIndex current = currentIndex();
1669
1670 if (!current.isValid()) {
1671 int row = 0;
1672 int column = 0;
1673 while (column < right && isColumnHidden(d->logicalColumn(column)))
1674 ++column;
1675 while (isRowHidden(d->logicalRow(row)) && row < bottom)
1676 ++row;
1677 d->visualCursor = QPoint(column, row);
1678 return d->model->index(d->logicalRow(row), d->logicalColumn(column), d->root);
1679 }
1680
1681 // Update visual cursor if current index has changed.
1682 QPoint visualCurrent(d->visualColumn(current.column()), d->visualRow(current.row()));
1683 if (visualCurrent != d->visualCursor) {
1684 if (d->hasSpans()) {
1685 QSpanCollection::Span span = d->span(current.row(), current.column());
1686 if (span.top() > d->visualCursor.y() || d->visualCursor.y() > span.bottom()
1687 || span.left() > d->visualCursor.x() || d->visualCursor.x() > span.right())
1688 d->visualCursor = visualCurrent;
1689 } else {
1690 d->visualCursor = visualCurrent;
1691 }
1692 }
1693
1694 int visualRow = d->visualCursor.y();
1695 if (visualRow > bottom)
1696 visualRow = bottom;
1697 Q_ASSERT(visualRow != -1);
1698 int visualColumn = d->visualCursor.x();
1699 if (visualColumn > right)
1700 visualColumn = right;
1701 Q_ASSERT(visualColumn != -1);
1702
1703 if (isRightToLeft()) {
1704 if (cursorAction == MoveLeft)
1705 cursorAction = MoveRight;
1706 else if (cursorAction == MoveRight)
1707 cursorAction = MoveLeft;
1708 }
1709
1710 switch (cursorAction) {
1711 case MoveUp: {
1712 int originalRow = visualRow;
1713#ifdef QT_KEYPAD_NAVIGATION
1714 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow == 0)
1715 visualRow = d->visualRow(model()->rowCount() - 1) + 1;
1716 // FIXME? visualRow = bottom + 1;
1717#endif
1718 int r = d->logicalRow(visualRow);
1719 int c = d->logicalColumn(visualColumn);
1720 if (r != -1 && d->hasSpans()) {
1721 QSpanCollection::Span span = d->span(r, c);
1722 if (span.width() > 1 || span.height() > 1)
1723 visualRow = d->visualRow(span.top());
1724 }
1725 while (visualRow >= 0) {
1726 --visualRow;
1727 r = d->logicalRow(visualRow);
1728 c = d->logicalColumn(visualColumn);
1729 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1730 break;
1731 }
1732 if (visualRow < 0)
1733 visualRow = originalRow;
1734 break;
1735 }
1736 case MoveDown: {
1737 int originalRow = visualRow;
1738 if (d->hasSpans()) {
1739 QSpanCollection::Span span = d->span(current.row(), current.column());
1740 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1741 }
1742#ifdef QT_KEYPAD_NAVIGATION
1743 if (QApplicationPrivate::keypadNavigationEnabled() && visualRow >= bottom)
1744 visualRow = -1;
1745#endif
1746 int r = d->logicalRow(visualRow);
1747 int c = d->logicalColumn(visualColumn);
1748 if (r != -1 && d->hasSpans()) {
1749 QSpanCollection::Span span = d->span(r, c);
1750 if (span.width() > 1 || span.height() > 1)
1751 visualRow = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1752 }
1753 while (visualRow <= bottom) {
1754 ++visualRow;
1755 r = d->logicalRow(visualRow);
1756 c = d->logicalColumn(visualColumn);
1757 if (r == -1 || (!isRowHidden(r) && d->isCellEnabled(r, c)))
1758 break;
1759 }
1760 if (visualRow > bottom)
1761 visualRow = originalRow;
1762 break;
1763 }
1764 case MovePrevious:
1765 case MoveLeft: {
1766 int originalRow = visualRow;
1767 int originalColumn = visualColumn;
1768 bool firstTime = true;
1769 bool looped = false;
1770 bool wrapped = false;
1771 do {
1772 int r = d->logicalRow(visualRow);
1773 int c = d->logicalColumn(visualColumn);
1774 if (firstTime && c != -1 && d->hasSpans()) {
1775 firstTime = false;
1776 QSpanCollection::Span span = d->span(r, c);
1777 if (span.width() > 1 || span.height() > 1)
1778 visualColumn = d->visualColumn(span.left());
1779 }
1780 while (visualColumn >= 0) {
1781 --visualColumn;
1782 r = d->logicalRow(visualRow);
1783 c = d->logicalColumn(visualColumn);
1784 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1785 break;
1786 if (wrapped && (originalRow < visualRow || (originalRow == visualRow && originalColumn <= visualColumn))) {
1787 looped = true;
1788 break;
1789 }
1790 }
1791 if (cursorAction == MoveLeft || visualColumn >= 0)
1792 break;
1793 visualColumn = right + 1;
1794 if (visualRow == 0) {
1795 wrapped = true;
1796 visualRow = bottom;
1797 } else {
1798 --visualRow;
1799 }
1800 } while (!looped);
1801 if (visualColumn < 0)
1802 visualColumn = originalColumn;
1803 break;
1804 }
1805 case MoveNext:
1806 case MoveRight: {
1807 int originalRow = visualRow;
1808 int originalColumn = visualColumn;
1809 bool firstTime = true;
1810 bool looped = false;
1811 bool wrapped = false;
1812 do {
1813 int r = d->logicalRow(visualRow);
1814 int c = d->logicalColumn(visualColumn);
1815 if (firstTime && c != -1 && d->hasSpans()) {
1816 firstTime = false;
1817 QSpanCollection::Span span = d->span(r, c);
1818 if (span.width() > 1 || span.height() > 1)
1819 visualColumn = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
1820 }
1821 while (visualColumn <= right) {
1822 ++visualColumn;
1823 r = d->logicalRow(visualRow);
1824 c = d->logicalColumn(visualColumn);
1825 if (r == -1 || c == -1 || (!isRowHidden(r) && !isColumnHidden(c) && d->isCellEnabled(r, c)))
1826 break;
1827 if (wrapped && (originalRow > visualRow || (originalRow == visualRow && originalColumn >= visualColumn))) {
1828 looped = true;
1829 break;
1830 }
1831 }
1832 if (cursorAction == MoveRight || visualColumn <= right)
1833 break;
1834 visualColumn = -1;
1835 if (visualRow == bottom) {
1836 wrapped = true;
1837 visualRow = 0;
1838 } else {
1839 ++visualRow;
1840 }
1841 } while (!looped);
1842 if (visualColumn > right)
1843 visualColumn = originalColumn;
1844 break;
1845 }
1846 case MoveHome:
1847 visualColumn = d->nextActiveVisualColumn(visualRow, 0, right,
1848 QTableViewPrivate::SearchDirection::Increasing);
1849 if (modifiers & Qt::ControlModifier)
1850 visualRow = d->nextActiveVisualRow(0, visualColumn, bottom,
1851 QTableViewPrivate::SearchDirection::Increasing);
1852 break;
1853 case MoveEnd:
1854 visualColumn = d->nextActiveVisualColumn(visualRow, right, -1,
1855 QTableViewPrivate::SearchDirection::Decreasing);
1856 if (modifiers & Qt::ControlModifier)
1857 visualRow = d->nextActiveVisualRow(bottom, visualColumn, -1,
1858 QTableViewPrivate::SearchDirection::Decreasing);
1859 break;
1860 case MovePageUp: {
1861 int newLogicalRow = rowAt(visualRect(current).bottom() - d->viewport->height());
1862 int visualRow = (newLogicalRow == -1 ? 0 : d->visualRow(newLogicalRow));
1863 visualRow = d->nextActiveVisualRow(visualRow, current.column(), bottom,
1864 QTableViewPrivate::SearchDirection::Increasing);
1865 newLogicalRow = d->logicalRow(visualRow);
1866 return d->model->index(newLogicalRow, current.column(), d->root);
1867 }
1868 case MovePageDown: {
1869 int newLogicalRow = rowAt(visualRect(current).top() + d->viewport->height());
1870 int visualRow = (newLogicalRow == -1 ? bottom : d->visualRow(newLogicalRow));
1871 visualRow = d->nextActiveVisualRow(visualRow, current.column(), -1,
1872 QTableViewPrivate::SearchDirection::Decreasing);
1873 newLogicalRow = d->logicalRow(visualRow);
1874 return d->model->index(newLogicalRow, current.column(), d->root);
1875 }}
1876
1877 d->visualCursor = QPoint(visualColumn, visualRow);
1878 int logicalRow = d->logicalRow(visualRow);
1879 int logicalColumn = d->logicalColumn(visualColumn);
1880 if (!d->model->hasIndex(logicalRow, logicalColumn, d->root))
1881 return QModelIndex();
1882
1883 QModelIndex result = d->model->index(logicalRow, logicalColumn, d->root);
1884 if (!d->isRowHidden(logicalRow) && !d->isColumnHidden(logicalColumn) && d->isIndexEnabled(result)) {
1885 if (d->hasSpans()) {
1886 QSpanCollection::Span span = d->span(result.row(), result.column());
1887 if (span.width() > 1 || span.height() > 1) {
1888 result = d->model->sibling(span.top(), span.left(), result);
1889 }
1890 }
1891 return result;
1892 }
1893
1894 return QModelIndex();
1895}
1896
1897/*!
1898 \fn void QTableView::setSelection(const QRect &rect,
1899 QItemSelectionModel::SelectionFlags flags)
1900
1901 Selects the items within the given \a rect and in accordance with
1902 the specified selection \a flags.
1903*/
1904void QTableView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
1905{
1906 Q_D(QTableView);
1907 QModelIndex tl = indexAt(QPoint(isRightToLeft() ? qMax(rect.left(), rect.right())
1908 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())));
1909 QModelIndex br = indexAt(QPoint(isRightToLeft() ? qMin(rect.left(), rect.right()) :
1910 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())));
1911 if (!d->selectionModel || !tl.isValid() || !br.isValid() || !d->isIndexEnabled(tl) || !d->isIndexEnabled(br))
1912 return;
1913
1914 bool verticalMoved = verticalHeader()->sectionsMoved();
1915 bool horizontalMoved = horizontalHeader()->sectionsMoved();
1916
1917 QItemSelection selection;
1918
1919 if (d->hasSpans()) {
1920 bool expanded;
1921 int top = qMin(d->visualRow(tl.row()), d->visualRow(br.row()));
1922 int left = qMin(d->visualColumn(tl.column()), d->visualColumn(br.column()));
1923 int bottom = qMax(d->visualRow(tl.row()), d->visualRow(br.row()));
1924 int right = qMax(d->visualColumn(tl.column()), d->visualColumn(br.column()));
1925 do {
1926 expanded = false;
1927 for (QSpanCollection::Span *it : d->spans.spans) {
1928 const QSpanCollection::Span &span = *it;
1929 int t = d->visualRow(span.top());
1930 int l = d->visualColumn(span.left());
1931 int b = d->visualRow(d->rowSpanEndLogical(span.top(), span.height()));
1932 int r = d->visualColumn(d->columnSpanEndLogical(span.left(), span.width()));
1933 if ((t > bottom) || (l > right) || (top > b) || (left > r))
1934 continue; // no intersect
1935 if (t < top) {
1936 top = t;
1937 expanded = true;
1938 }
1939 if (l < left) {
1940 left = l;
1941 expanded = true;
1942 }
1943 if (b > bottom) {
1944 bottom = b;
1945 expanded = true;
1946 }
1947 if (r > right) {
1948 right = r;
1949 expanded = true;
1950 }
1951 if (expanded)
1952 break;
1953 }
1954 } while (expanded);
1955 selection.reserve((right - left + 1) * (bottom - top + 1));
1956 for (int horizontal = left; horizontal <= right; ++horizontal) {
1957 int column = d->logicalColumn(horizontal);
1958 for (int vertical = top; vertical <= bottom; ++vertical) {
1959 int row = d->logicalRow(vertical);
1960 QModelIndex index = d->model->index(row, column, d->root);
1961 selection.append(QItemSelectionRange(index));
1962 }
1963 }
1964 } else if (verticalMoved && horizontalMoved) {
1965 int top = d->visualRow(tl.row());
1966 int left = d->visualColumn(tl.column());
1967 int bottom = d->visualRow(br.row());
1968 int right = d->visualColumn(br.column());
1969 selection.reserve((right - left + 1) * (bottom - top + 1));
1970 for (int horizontal = left; horizontal <= right; ++horizontal) {
1971 int column = d->logicalColumn(horizontal);
1972 for (int vertical = top; vertical <= bottom; ++vertical) {
1973 int row = d->logicalRow(vertical);
1974 QModelIndex index = d->model->index(row, column, d->root);
1975 selection.append(QItemSelectionRange(index));
1976 }
1977 }
1978 } else if (horizontalMoved) {
1979 int left = d->visualColumn(tl.column());
1980 int right = d->visualColumn(br.column());
1981 selection.reserve(right - left + 1);
1982 for (int visual = left; visual <= right; ++visual) {
1983 int column = d->logicalColumn(visual);
1984 QModelIndex topLeft = d->model->index(tl.row(), column, d->root);
1985 QModelIndex bottomRight = d->model->index(br.row(), column, d->root);
1986 selection.append(QItemSelectionRange(topLeft, bottomRight));
1987 }
1988 } else if (verticalMoved) {
1989 int top = d->visualRow(tl.row());
1990 int bottom = d->visualRow(br.row());
1991 selection.reserve(bottom - top + 1);
1992 for (int visual = top; visual <= bottom; ++visual) {
1993 int row = d->logicalRow(visual);
1994 QModelIndex topLeft = d->model->index(row, tl.column(), d->root);
1995 QModelIndex bottomRight = d->model->index(row, br.column(), d->root);
1996 selection.append(QItemSelectionRange(topLeft, bottomRight));
1997 }
1998 } else { // nothing moved
1999 QItemSelectionRange range(tl, br);
2000 if (!range.isEmpty())
2001 selection.append(range);
2002 }
2003
2004 d->selectionModel->select(selection, command);
2005}
2006
2007/*!
2008 \internal
2009
2010 Returns the rectangle from the viewport of the items in the given
2011 \a selection.
2012
2013 Since 4.7, the returned region only contains rectangles intersecting
2014 (or included in) the viewport.
2015*/
2016QRegion QTableView::visualRegionForSelection(const QItemSelection &selection) const
2017{
2018 Q_D(const QTableView);
2019
2020 if (selection.isEmpty())
2021 return QRegion();
2022
2023 QRegion selectionRegion;
2024 const QRect &viewportRect = d->viewport->rect();
2025 bool verticalMoved = verticalHeader()->sectionsMoved();
2026 bool horizontalMoved = horizontalHeader()->sectionsMoved();
2027
2028 if ((verticalMoved && horizontalMoved) || (d->hasSpans() && (verticalMoved || horizontalMoved))) {
2029 for (const auto &range : selection) {
2030 if (range.parent() != d->root || !range.isValid())
2031 continue;
2032 for (int r = range.top(); r <= range.bottom(); ++r)
2033 for (int c = range.left(); c <= range.right(); ++c) {
2034 const QRect &rangeRect = visualRect(d->model->index(r, c, d->root));
2035 if (viewportRect.intersects(rangeRect))
2036 selectionRegion += rangeRect;
2037 }
2038 }
2039 } else if (horizontalMoved) {
2040 for (const auto &range : selection) {
2041 if (range.parent() != d->root || !range.isValid())
2042 continue;
2043 int top = rowViewportPosition(range.top());
2044 int bottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2045 if (top > bottom)
2046 qSwap<int>(top, bottom);
2047 int height = bottom - top;
2048 for (int c = range.left(); c <= range.right(); ++c) {
2049 const QRect rangeRect(columnViewportPosition(c), top, columnWidth(c), height);
2050 if (viewportRect.intersects(rangeRect))
2051 selectionRegion += rangeRect;
2052 }
2053 }
2054 } else if (verticalMoved) {
2055 for (const auto &range : selection) {
2056 if (range.parent() != d->root || !range.isValid())
2057 continue;
2058 int left = columnViewportPosition(range.left());
2059 int right = columnViewportPosition(range.right()) + columnWidth(range.right());
2060 if (left > right)
2061 qSwap<int>(left, right);
2062 int width = right - left;
2063 for (int r = range.top(); r <= range.bottom(); ++r) {
2064 const QRect rangeRect(left, rowViewportPosition(r), width, rowHeight(r));
2065 if (viewportRect.intersects(rangeRect))
2066 selectionRegion += rangeRect;
2067 }
2068 }
2069 } else { // nothing moved
2070 const int gridAdjust = showGrid() ? 1 : 0;
2071 for (auto range : selection) {
2072 if (range.parent() != d->root || !range.isValid())
2073 continue;
2074 d->trimHiddenSelections(&range);
2075
2076 const int rtop = rowViewportPosition(range.top());
2077 const int rbottom = rowViewportPosition(range.bottom()) + rowHeight(range.bottom());
2078 int rleft;
2079 int rright;
2080 if (isLeftToRight()) {
2081 rleft = columnViewportPosition(range.left());
2082 rright = columnViewportPosition(range.right()) + columnWidth(range.right());
2083 } else {
2084 rleft = columnViewportPosition(range.right());
2085 rright = columnViewportPosition(range.left()) + columnWidth(range.left());
2086 }
2087 const QRect rangeRect(QPoint(rleft, rtop), QPoint(rright - 1 - gridAdjust, rbottom - 1 - gridAdjust));
2088 if (viewportRect.intersects(rangeRect))
2089 selectionRegion += rangeRect;
2090 if (d->hasSpans()) {
2091 const auto spansInRect = d->spans.spansInRect(range.left(), range.top(), range.width(), range.height());
2092 for (QSpanCollection::Span *s : spansInRect) {
2093 if (range.contains(s->top(), s->left(), range.parent())) {
2094 const QRect &visualSpanRect = d->visualSpanRect(*s);
2095 if (viewportRect.intersects(visualSpanRect))
2096 selectionRegion += visualSpanRect;
2097 }
2098 }
2099 }
2100 }
2101 }
2102
2103 return selectionRegion;
2104}
2105
2106
2107/*!
2108 \reimp
2109*/
2110QModelIndexList QTableView::selectedIndexes() const
2111{
2112 Q_D(const QTableView);
2113 QModelIndexList viewSelected;
2114 QModelIndexList modelSelected;
2115 if (d->selectionModel)
2116 modelSelected = d->selectionModel->selectedIndexes();
2117 for (int i = 0; i < modelSelected.count(); ++i) {
2118 QModelIndex index = modelSelected.at(i);
2119 if (!isIndexHidden(index) && index.parent() == d->root)
2120 viewSelected.append(index);
2121 }
2122 return viewSelected;
2123}
2124
2125
2126/*!
2127 This slot is called whenever rows are added or deleted. The
2128 previous number of rows is specified by \a oldCount, and the new
2129 number of rows is specified by \a newCount.
2130*/
2131void QTableView::rowCountChanged(int oldCount, int newCount )
2132{
2133 Q_D(QTableView);
2134 //when removing rows, we need to disable updates for the header until the geometries have been
2135 //updated and the offset has been adjusted, or we risk calling paintSection for all the sections
2136 if (newCount < oldCount)
2137 d->verticalHeader->setUpdatesEnabled(false);
2138 d->doDelayedItemsLayout();
2139}
2140
2141/*!
2142 This slot is called whenever columns are added or deleted. The
2143 previous number of columns is specified by \a oldCount, and the new
2144 number of columns is specified by \a newCount.
2145*/
2146void QTableView::columnCountChanged(int, int)
2147{
2148 Q_D(QTableView);
2149 updateGeometries();
2150 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
2151 d->horizontalHeader->setOffsetToSectionPosition(horizontalScrollBar()->value());
2152 else
2153 d->horizontalHeader->setOffset(horizontalScrollBar()->value());
2154 d->viewport->update();
2155}
2156
2157/*!
2158 \reimp
2159*/
2160void QTableView::updateGeometries()
2161{
2162 Q_D(QTableView);
2163 if (d->geometryRecursionBlock)
2164 return;
2165 d->geometryRecursionBlock = true;
2166
2167 int width = 0;
2168 if (!d->verticalHeader->isHidden()) {
2169 width = qMax(d->verticalHeader->minimumWidth(), d->verticalHeader->sizeHint().width());
2170 width = qMin(width, d->verticalHeader->maximumWidth());
2171 }
2172 int height = 0;
2173 if (!d->horizontalHeader->isHidden()) {
2174 height = qMax(d->horizontalHeader->minimumHeight(), d->horizontalHeader->sizeHint().height());
2175 height = qMin(height, d->horizontalHeader->maximumHeight());
2176 }
2177 bool reverse = isRightToLeft();
2178 if (reverse)
2179 setViewportMargins(0, height, width, 0);
2180 else
2181 setViewportMargins(width, height, 0, 0);
2182
2183 // update headers
2184
2185 QRect vg = d->viewport->geometry();
2186
2187 int verticalLeft = reverse ? vg.right() + 1 : (vg.left() - width);
2188 d->verticalHeader->setGeometry(verticalLeft, vg.top(), width, vg.height());
2189 if (d->verticalHeader->isHidden())
2190 QMetaObject::invokeMethod(d->verticalHeader, "updateGeometries");
2191
2192 int horizontalTop = vg.top() - height;
2193 d->horizontalHeader->setGeometry(vg.left(), horizontalTop, vg.width(), height);
2194 if (d->horizontalHeader->isHidden())
2195 QMetaObject::invokeMethod(d->horizontalHeader, "updateGeometries");
2196
2197#if QT_CONFIG(abstractbutton)
2198 // update cornerWidget
2199 if (d->horizontalHeader->isHidden() || d->verticalHeader->isHidden()) {
2200 d->cornerWidget->setHidden(true);
2201 } else {
2202 d->cornerWidget->setHidden(false);
2203 d->cornerWidget->setGeometry(verticalLeft, horizontalTop, width, height);
2204 }
2205#endif
2206
2207 // update scroll bars
2208
2209 // ### move this block into the if
2210 QSize vsize = d->viewport->size();
2211 QSize max = maximumViewportSize();
2212 const int horizontalLength = d->horizontalHeader->length();
2213 const int verticalLength = d->verticalHeader->length();
2214 if (max.width() >= horizontalLength && max.height() >= verticalLength)
2215 vsize = max;
2216
2217 // horizontal scroll bar
2218 const int columnCount = d->horizontalHeader->count();
2219 const int viewportWidth = vsize.width();
2220 int columnsInViewport = 0;
2221 for (int width = 0, column = columnCount - 1; column >= 0; --column) {
2222 int logical = d->horizontalHeader->logicalIndex(column);
2223 if (!d->horizontalHeader->isSectionHidden(logical)) {
2224 width += d->horizontalHeader->sectionSize(logical);
2225 if (width > viewportWidth)
2226 break;
2227 ++columnsInViewport;
2228 }
2229 }
2230 columnsInViewport = qMax(columnsInViewport, 1); //there must be always at least 1 column
2231
2232 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
2233 const int visibleColumns = columnCount - d->horizontalHeader->hiddenSectionCount();
2234 horizontalScrollBar()->setRange(0, visibleColumns - columnsInViewport);
2235 horizontalScrollBar()->setPageStep(columnsInViewport);
2236 if (columnsInViewport >= visibleColumns)
2237 d->horizontalHeader->setOffset(0);
2238 horizontalScrollBar()->setSingleStep(1);
2239 } else { // ScrollPerPixel
2240 horizontalScrollBar()->setPageStep(vsize.width());
2241 horizontalScrollBar()->setRange(0, horizontalLength - vsize.width());
2242 horizontalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.width() / (columnsInViewport + 1), 2));
2243 }
2244
2245 // vertical scroll bar
2246 const int rowCount = d->verticalHeader->count();
2247 const int viewportHeight = vsize.height();
2248 int rowsInViewport = 0;
2249 for (int height = 0, row = rowCount - 1; row >= 0; --row) {
2250 int logical = d->verticalHeader->logicalIndex(row);
2251 if (!d->verticalHeader->isSectionHidden(logical)) {
2252 height += d->verticalHeader->sectionSize(logical);
2253 if (height > viewportHeight)
2254 break;
2255 ++rowsInViewport;
2256 }
2257 }
2258 rowsInViewport = qMax(rowsInViewport, 1); //there must be always at least 1 row
2259
2260 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
2261 const int visibleRows = rowCount - d->verticalHeader->hiddenSectionCount();
2262 verticalScrollBar()->setRange(0, visibleRows - rowsInViewport);
2263 verticalScrollBar()->setPageStep(rowsInViewport);
2264 if (rowsInViewport >= visibleRows)
2265 d->verticalHeader->setOffset(0);
2266 verticalScrollBar()->setSingleStep(1);
2267 } else { // ScrollPerPixel
2268 verticalScrollBar()->setPageStep(vsize.height());
2269 verticalScrollBar()->setRange(0, verticalLength - vsize.height());
2270 verticalScrollBar()->d_func()->itemviewChangeSingleStep(qMax(vsize.height() / (rowsInViewport + 1), 2));
2271 }
2272 d->verticalHeader->d_func()->setScrollOffset(verticalScrollBar(), verticalScrollMode());
2273
2274 d->geometryRecursionBlock = false;
2275 QAbstractItemView::updateGeometries();
2276}
2277
2278/*!
2279 Returns the size hint for the given \a row's height or -1 if there
2280 is no model.
2281
2282 If you need to set the height of a given row to a fixed value, call
2283 QHeaderView::resizeSection() on the table's vertical header.
2284
2285 If you reimplement this function in a subclass, note that the value you
2286 return is only used when resizeRowToContents() is called. In that case,
2287 if a larger row height is required by either the vertical header or
2288 the item delegate, that width will be used instead.
2289
2290 \sa QWidget::sizeHint, verticalHeader(), QHeaderView::resizeContentsPrecision()
2291*/
2292int QTableView::sizeHintForRow(int row) const
2293{
2294 Q_D(const QTableView);
2295
2296 if (!model())
2297 return -1;
2298
2299 ensurePolished();
2300 const int maximumProcessCols = d->verticalHeader->resizeContentsPrecision();
2301
2302
2303 int left = qMax(0, d->horizontalHeader->visualIndexAt(0));
2304 int right = d->horizontalHeader->visualIndexAt(d->viewport->width());
2305 if (right == -1) // the table don't have enough columns to fill the viewport
2306 right = d->model->columnCount(d->root) - 1;
2307
2308 QStyleOptionViewItem option = d->viewOptionsV1();
2309
2310 int hint = 0;
2311 QModelIndex index;
2312 int columnsProcessed = 0;
2313 int column = left;
2314 for (; column <= right; ++column) {
2315 int logicalColumn = d->horizontalHeader->logicalIndex(column);
2316 if (d->horizontalHeader->isSectionHidden(logicalColumn))
2317 continue;
2318 index = d->model->index(row, logicalColumn, d->root);
2319 hint = d->heightHintForIndex(index, hint, option);
2320
2321 ++columnsProcessed;
2322 if (columnsProcessed == maximumProcessCols)
2323 break;
2324 }
2325
2326 int actualRight = d->model->columnCount(d->root) - 1;
2327 int idxLeft = left;
2328 int idxRight = column - 1;
2329
2330 if (maximumProcessCols == 0)
2331 columnsProcessed = 0; // skip the while loop
2332
2333 while (columnsProcessed != maximumProcessCols && (idxLeft > 0 || idxRight < actualRight)) {
2334 int logicalIdx = -1;
2335
2336 if ((columnsProcessed % 2 && idxLeft > 0) || idxRight == actualRight) {
2337 while (idxLeft > 0) {
2338 --idxLeft;
2339 int logcol = d->horizontalHeader->logicalIndex(idxLeft);
2340 if (d->horizontalHeader->isSectionHidden(logcol))
2341 continue;
2342 logicalIdx = logcol;
2343 break;
2344 }
2345 } else {
2346 while (idxRight < actualRight) {
2347 ++idxRight;
2348 int logcol = d->horizontalHeader->logicalIndex(idxRight);
2349 if (d->horizontalHeader->isSectionHidden(logcol))
2350 continue;
2351 logicalIdx = logcol;
2352 break;
2353 }
2354 }
2355 if (logicalIdx < 0)
2356 continue;
2357
2358 index = d->model->index(row, logicalIdx, d->root);
2359 hint = d->heightHintForIndex(index, hint, option);
2360 ++columnsProcessed;
2361 }
2362
2363 return d->showGrid ? hint + 1 : hint;
2364}
2365
2366/*!
2367 Returns the size hint for the given \a column's width or -1 if
2368 there is no model.
2369
2370 If you need to set the width of a given column to a fixed value, call
2371 QHeaderView::resizeSection() on the table's horizontal header.
2372
2373 If you reimplement this function in a subclass, note that the value you
2374 return will be used when resizeColumnToContents() or
2375 QHeaderView::resizeSections() is called. If a larger column width is
2376 required by either the horizontal header or the item delegate, the larger
2377 width will be used instead.
2378
2379 \sa QWidget::sizeHint, horizontalHeader(), QHeaderView::resizeContentsPrecision()
2380*/
2381int QTableView::sizeHintForColumn(int column) const
2382{
2383 Q_D(const QTableView);
2384
2385 if (!model())
2386 return -1;
2387
2388 ensurePolished();
2389 const int maximumProcessRows = d->horizontalHeader->resizeContentsPrecision();
2390
2391 int top = qMax(0, d->verticalHeader->visualIndexAt(0));
2392 int bottom = d->verticalHeader->visualIndexAt(d->viewport->height());
2393 if (!isVisible() || bottom == -1) // the table don't have enough rows to fill the viewport
2394 bottom = d->model->rowCount(d->root) - 1;
2395
2396 QStyleOptionViewItem option = d->viewOptionsV1();
2397
2398 int hint = 0;
2399 int rowsProcessed = 0;
2400 QModelIndex index;
2401 int row = top;
2402 for (; row <= bottom; ++row) {
2403 int logicalRow = d->verticalHeader->logicalIndex(row);
2404 if (d->verticalHeader->isSectionHidden(logicalRow))
2405 continue;
2406 index = d->model->index(logicalRow, column, d->root);
2407
2408 hint = d->widthHintForIndex(index, hint, option);
2409 ++rowsProcessed;
2410 if (rowsProcessed == maximumProcessRows)
2411 break;
2412 }
2413
2414 int actualBottom = d->model->rowCount(d->root) - 1;
2415 int idxTop = top;
2416 int idxBottom = row - 1;
2417
2418 if (maximumProcessRows == 0)
2419 rowsProcessed = 0; // skip the while loop
2420
2421 while (rowsProcessed != maximumProcessRows && (idxTop > 0 || idxBottom < actualBottom)) {
2422 int logicalIdx = -1;
2423
2424 if ((rowsProcessed % 2 && idxTop > 0) || idxBottom == actualBottom) {
2425 while (idxTop > 0) {
2426 --idxTop;
2427 int logrow = d->verticalHeader->logicalIndex(idxTop);
2428 if (d->verticalHeader->isSectionHidden(logrow))
2429 continue;
2430 logicalIdx = logrow;
2431 break;
2432 }
2433 } else {
2434 while (idxBottom < actualBottom) {
2435 ++idxBottom;
2436 int logrow = d->verticalHeader->logicalIndex(idxBottom);
2437 if (d->verticalHeader->isSectionHidden(logrow))
2438 continue;
2439 logicalIdx = logrow;
2440 break;
2441 }
2442 }
2443 if (logicalIdx < 0)
2444 continue;
2445
2446 index = d->model->index(logicalIdx, column, d->root);
2447 hint = d->widthHintForIndex(index, hint, option);
2448 ++rowsProcessed;
2449 }
2450
2451 return d->showGrid ? hint + 1 : hint;
2452}
2453
2454/*!
2455 Returns the y-coordinate in contents coordinates of the given \a
2456 row.
2457*/
2458int QTableView::rowViewportPosition(int row) const
2459{
2460 Q_D(const QTableView);
2461 return d->verticalHeader->sectionViewportPosition(row);
2462}
2463
2464/*!
2465 Returns the row in which the given y-coordinate, \a y, in contents
2466 coordinates is located.
2467
2468 \note This function returns -1 if the given coordinate is not valid
2469 (has no row).
2470
2471 \sa columnAt()
2472*/
2473int QTableView::rowAt(int y) const
2474{
2475 Q_D(const QTableView);
2476 return d->verticalHeader->logicalIndexAt(y);
2477}
2478
2479/*!
2480 \since 4.1
2481
2482 Sets the height of the given \a row to be \a height.
2483*/
2484void QTableView::setRowHeight(int row, int height)
2485{
2486 Q_D(const QTableView);
2487 d->verticalHeader->resizeSection(row, height);
2488}
2489
2490/*!
2491 Returns the height of the given \a row.
2492
2493 \sa resizeRowToContents(), columnWidth()
2494*/
2495int QTableView::rowHeight(int row) const
2496{
2497 Q_D(const QTableView);
2498 return d->verticalHeader->sectionSize(row);
2499}
2500
2501/*!
2502 Returns the x-coordinate in contents coordinates of the given \a
2503 column.
2504*/
2505int QTableView::columnViewportPosition(int column) const
2506{
2507 Q_D(const QTableView);
2508 return d->horizontalHeader->sectionViewportPosition(column);
2509}
2510
2511/*!
2512 Returns the column in which the given x-coordinate, \a x, in contents
2513 coordinates is located.
2514
2515 \note This function returns -1 if the given coordinate is not valid
2516 (has no column).
2517
2518 \sa rowAt()
2519*/
2520int QTableView::columnAt(int x) const
2521{
2522 Q_D(const QTableView);
2523 return d->horizontalHeader->logicalIndexAt(x);
2524}
2525
2526/*!
2527 \since 4.1
2528
2529 Sets the width of the given \a column to be \a width.
2530*/
2531void QTableView::setColumnWidth(int column, int width)
2532{
2533 Q_D(const QTableView);
2534 d->horizontalHeader->resizeSection(column, width);
2535}
2536
2537/*!
2538 Returns the width of the given \a column.
2539
2540 \sa resizeColumnToContents(), rowHeight()
2541*/
2542int QTableView::columnWidth(int column) const
2543{
2544 Q_D(const QTableView);
2545 return d->horizontalHeader->sectionSize(column);
2546}
2547
2548/*!
2549 Returns \c true if the given \a row is hidden; otherwise returns \c false.
2550
2551 \sa isColumnHidden()
2552*/
2553bool QTableView::isRowHidden(int row) const
2554{
2555 Q_D(const QTableView);
2556 return d->verticalHeader->isSectionHidden(row);
2557}
2558
2559/*!
2560 If \a hide is true \a row will be hidden, otherwise it will be shown.
2561
2562 \sa setColumnHidden()
2563*/
2564void QTableView::setRowHidden(int row, bool hide)
2565{
2566</