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