1// Copyright (C) 2020 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 <qglobal.h>
5#include "qcolumnview.h"
6
7#if QT_CONFIG(columnview)
8
9#include "qcolumnview_p.h"
10#include "qcolumnviewgrip_p.h"
11
12#include <qlistview.h>
13#include <qabstractitemdelegate.h>
14#include <qscrollbar.h>
15#include <qpainter.h>
16#include <qdebug.h>
17
18QT_BEGIN_NAMESPACE
19
20/*!
21 \since 4.3
22 \class QColumnView
23 \brief The QColumnView class provides a model/view implementation of a column view.
24 \ingroup model-view
25 \ingroup advanced
26 \inmodule QtWidgets
27
28 QColumnView displays a model in a number of QListViews, one for each
29 hierarchy in the tree. This is sometimes referred to as a cascading list.
30
31 The QColumnView class is one of the \l{Model/View Classes}
32 and is part of Qt's \l{Model/View Programming}{model/view framework}.
33
34 QColumnView implements the interfaces defined by the
35 QAbstractItemView class to allow it to display data provided by
36 models derived from the QAbstractItemModel class.
37
38 \image qcolumnview.png
39
40 \sa {Model/View Programming}
41*/
42
43/*!
44 Constructs a column view with a \a parent to represent a model's
45 data. Use setModel() to set the model.
46
47 \sa QAbstractItemModel
48*/
49QColumnView::QColumnView(QWidget * parent)
50: QAbstractItemView(*new QColumnViewPrivate, parent)
51{
52 Q_D(QColumnView);
53 d->initialize();
54}
55
56/*!
57 \internal
58*/
59QColumnView::QColumnView(QColumnViewPrivate & dd, QWidget * parent)
60: QAbstractItemView(dd, parent)
61{
62 Q_D(QColumnView);
63 d->initialize();
64}
65
66void QColumnViewPrivate::initialize()
67{
68 Q_Q(QColumnView);
69 q->setTextElideMode(Qt::ElideMiddle);
70#if QT_CONFIG(animation)
71 QObject::connect(sender: &currentAnimation, SIGNAL(finished()), receiver: q, SLOT(_q_changeCurrentColumn()));
72 currentAnimation.setTargetObject(hbar);
73 currentAnimation.setPropertyName("value");
74 currentAnimation.setEasingCurve(QEasingCurve::InOutQuad);
75#endif // animation
76 delete itemDelegate;
77 q->setItemDelegate(new QColumnViewDelegate(q));
78}
79
80/*!
81 Destroys the column view.
82*/
83QColumnView::~QColumnView()
84{
85}
86
87/*!
88 \property QColumnView::resizeGripsVisible
89 \brief the way to specify if the list views gets resize grips or not
90
91 By default, \c visible is set to true
92
93 \sa setRootIndex()
94*/
95void QColumnView::setResizeGripsVisible(bool visible)
96{
97 Q_D(QColumnView);
98 if (d->showResizeGrips == visible)
99 return;
100 d->showResizeGrips = visible;
101 for (int i = 0; i < d->columns.size(); ++i) {
102 QAbstractItemView *view = d->columns[i];
103 if (visible) {
104 QColumnViewGrip *grip = new QColumnViewGrip(view);
105 view->setCornerWidget(grip);
106 connect(sender: grip, SIGNAL(gripMoved(int)), receiver: this, SLOT(_q_gripMoved(int)));
107 } else {
108 QWidget *widget = view->cornerWidget();
109 view->setCornerWidget(nullptr);
110 widget->deleteLater();
111 }
112 }
113}
114
115bool QColumnView::resizeGripsVisible() const
116{
117 Q_D(const QColumnView);
118 return d->showResizeGrips;
119}
120
121/*!
122 \reimp
123*/
124void QColumnView::setModel(QAbstractItemModel *model)
125{
126 Q_D(QColumnView);
127 if (model == d->model)
128 return;
129 d->closeColumns();
130 QAbstractItemView::setModel(model);
131}
132
133/*!
134 \reimp
135*/
136void QColumnView::setRootIndex(const QModelIndex &index)
137{
138 Q_D(QColumnView);
139 if (!model())
140 return;
141
142 d->closeColumns();
143 Q_ASSERT(d->columns.size() == 0);
144
145 QAbstractItemView *view = d->createColumn(index, show: true);
146 if (view->selectionModel())
147 view->selectionModel()->deleteLater();
148 if (view->model())
149 view->setSelectionModel(selectionModel());
150
151 QAbstractItemView::setRootIndex(index);
152 d->updateScrollbars();
153}
154
155/*!
156 \reimp
157*/
158bool QColumnView::isIndexHidden(const QModelIndex &index) const
159{
160 Q_UNUSED(index);
161 return false;
162}
163
164/*!
165 \reimp
166*/
167QModelIndex QColumnView::indexAt(const QPoint &point) const
168{
169 Q_D(const QColumnView);
170 for (int i = 0; i < d->columns.size(); ++i) {
171 QPoint topLeft = d->columns.at(i)->frameGeometry().topLeft();
172 QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
173 QModelIndex index = d->columns.at(i)->indexAt(point: adjustedPoint);
174 if (index.isValid())
175 return index;
176 }
177 return QModelIndex();
178}
179
180/*!
181 \reimp
182*/
183QRect QColumnView::visualRect(const QModelIndex &index) const
184{
185 if (!index.isValid())
186 return QRect();
187
188 Q_D(const QColumnView);
189 for (int i = 0; i < d->columns.size(); ++i) {
190 QRect rect = d->columns.at(i)->visualRect(index);
191 if (!rect.isNull()) {
192 rect.translate(p: d->columns.at(i)->frameGeometry().topLeft());
193 return rect;
194 }
195 }
196 return QRect();
197}
198
199/*!
200 \reimp
201 */
202void QColumnView::scrollContentsBy(int dx, int dy)
203{
204 Q_D(QColumnView);
205 if (d->columns.isEmpty() || dx == 0)
206 return;
207
208 dx = isRightToLeft() ? -dx : dx;
209 for (int i = 0; i < d->columns.size(); ++i)
210 d->columns.at(i)->move(ax: d->columns.at(i)->x() + dx, ay: 0);
211 d->offset += dx;
212 QAbstractItemView::scrollContentsBy(dx, dy);
213}
214
215/*!
216 \reimp
217*/
218void QColumnView::scrollTo(const QModelIndex &index, ScrollHint hint)
219{
220 Q_D(QColumnView);
221 Q_UNUSED(hint);
222 if (!index.isValid() || d->columns.isEmpty())
223 return;
224
225#if QT_CONFIG(animation)
226 if (d->currentAnimation.state() == QPropertyAnimation::Running)
227 return;
228
229 d->currentAnimation.stop();
230#endif // animation
231
232 // Fill up what is needed to get to index
233 d->closeColumns(parent: index, build: true);
234
235 QModelIndex indexParent = index.parent();
236 // Find the left edge of the column that contains index
237 int currentColumn = 0;
238 int leftEdge = 0;
239 while (currentColumn < d->columns.size()) {
240 if (indexParent == d->columns.at(i: currentColumn)->rootIndex())
241 break;
242 leftEdge += d->columns.at(i: currentColumn)->width();
243 ++currentColumn;
244 }
245
246 // Don't let us scroll above the root index
247 if (currentColumn == d->columns.size())
248 return;
249
250 int indexColumn = currentColumn;
251 // Find the width of what we want to show (i.e. the right edge)
252 int visibleWidth = d->columns.at(i: currentColumn)->width();
253 // We want to always try to show two columns
254 if (currentColumn + 1 < d->columns.size()) {
255 ++currentColumn;
256 visibleWidth += d->columns.at(i: currentColumn)->width();
257 }
258
259 int rightEdge = leftEdge + visibleWidth;
260 if (isRightToLeft()) {
261 leftEdge = viewport()->width() - leftEdge;
262 rightEdge = leftEdge - visibleWidth;
263 qSwap(value1&: rightEdge, value2&: leftEdge);
264 }
265
266 // If it is already visible don't animate
267 if (leftEdge > -horizontalOffset()
268 && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) {
269 d->columns.at(i: indexColumn)->scrollTo(index);
270 d->_q_changeCurrentColumn();
271 return;
272 }
273
274 int newScrollbarValue = 0;
275 if (isRightToLeft()) {
276 if (leftEdge < 0) {
277 // scroll to the right
278 newScrollbarValue = viewport()->size().width() - leftEdge;
279 } else {
280 // scroll to the left
281 newScrollbarValue = rightEdge + horizontalOffset();
282 }
283 } else {
284 if (leftEdge > -horizontalOffset()) {
285 // scroll to the right
286 newScrollbarValue = rightEdge - viewport()->size().width();
287 } else {
288 // scroll to the left
289 newScrollbarValue = leftEdge;
290 }
291 }
292
293#if QT_CONFIG(animation)
294 if (const int animationDuration = style()->styleHint(stylehint: QStyle::SH_Widget_Animation_Duration, opt: nullptr, widget: this)) {
295 d->currentAnimation.setDuration(animationDuration);
296 d->currentAnimation.setEndValue(newScrollbarValue);
297 d->currentAnimation.start();
298 } else
299#endif // animation
300 {
301 horizontalScrollBar()->setValue(newScrollbarValue);
302 }
303}
304
305/*!
306 \reimp
307 Move left should go to the parent index
308 Move right should go to the child index or down if there is no child
309*/
310QModelIndex QColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
311{
312 // the child views which have focus get to deal with this first and if
313 // they don't accept it then it comes up this view and we only grip left/right
314 Q_UNUSED(modifiers);
315 if (!model())
316 return QModelIndex();
317
318 QModelIndex current = currentIndex();
319 if (isRightToLeft()) {
320 if (cursorAction == MoveLeft)
321 cursorAction = MoveRight;
322 else if (cursorAction == MoveRight)
323 cursorAction = MoveLeft;
324 }
325 switch (cursorAction) {
326 case MoveLeft:
327 if (current.parent().isValid() && current.parent() != rootIndex())
328 return (current.parent());
329 else
330 return current;
331
332 case MoveRight:
333 if (model()->hasChildren(parent: current))
334 return model()->index(row: 0, column: 0, parent: current);
335 else
336 return current.sibling(arow: current.row() + 1, acolumn: current.column());
337
338 default:
339 break;
340 }
341
342 return QModelIndex();
343}
344
345/*!
346 \reimp
347*/
348void QColumnView::resizeEvent(QResizeEvent *event)
349{
350 Q_D(QColumnView);
351 d->doLayout();
352 d->updateScrollbars();
353 if (!isRightToLeft()) {
354 int diff = event->oldSize().width() - event->size().width();
355 if (diff < 0 && horizontalScrollBar()->isVisible()
356 && horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) {
357 horizontalScrollBar()->setMaximum(horizontalScrollBar()->maximum() + diff);
358 }
359 }
360 QAbstractItemView::resizeEvent(event);
361}
362
363/*!
364 \internal
365*/
366void QColumnViewPrivate::updateScrollbars()
367{
368 Q_Q(QColumnView);
369#if QT_CONFIG(animation)
370 if (currentAnimation.state() == QPropertyAnimation::Running)
371 return;
372#endif // animation
373
374 // find the total horizontal length of the laid out columns
375 int horizontalLength = 0;
376 if (!columns.isEmpty()) {
377 horizontalLength = (columns.constLast()->x() + columns.constLast()->width()) - columns.constFirst()->x();
378 if (horizontalLength <= 0) // reverse mode
379 horizontalLength = (columns.constFirst()->x() + columns.constFirst()->width()) - columns.constLast()->x();
380 }
381
382 QSize viewportSize = viewport->size();
383 if (horizontalLength < viewportSize.width() && hbar->value() == 0) {
384 hbar->setRange(min: 0, max: 0);
385 } else {
386 int visibleLength = qMin(a: horizontalLength + q->horizontalOffset(), b: viewportSize.width());
387 int hiddenLength = horizontalLength - visibleLength;
388 if (hiddenLength != hbar->maximum())
389 hbar->setRange(min: 0, max: hiddenLength);
390 }
391 if (!columns.isEmpty()) {
392 int pageStepSize = columns.at(i: 0)->width();
393 if (pageStepSize != hbar->pageStep())
394 hbar->setPageStep(pageStepSize);
395 }
396 bool visible = (hbar->maximum() > 0);
397 if (visible != hbar->isVisible())
398 hbar->setVisible(visible);
399}
400
401/*!
402 \reimp
403*/
404int QColumnView::horizontalOffset() const
405{
406 Q_D(const QColumnView);
407 return d->offset;
408}
409
410/*!
411 \reimp
412*/
413int QColumnView::verticalOffset() const
414{
415 return 0;
416}
417
418/*!
419 \reimp
420*/
421QRegion QColumnView::visualRegionForSelection(const QItemSelection &selection) const
422{
423 int ranges = selection.size();
424
425 if (ranges == 0)
426 return QRect();
427
428 // Note that we use the top and bottom functions of the selection range
429 // since the data is stored in rows.
430 int firstRow = selection.at(i: 0).top();
431 int lastRow = selection.at(i: 0).top();
432 for (int i = 0; i < ranges; ++i) {
433 firstRow = qMin(a: firstRow, b: selection.at(i).top());
434 lastRow = qMax(a: lastRow, b: selection.at(i).bottom());
435 }
436
437 QModelIndex firstIdx = model()->index(row: qMin(a: firstRow, b: lastRow), column: 0, parent: rootIndex());
438 QModelIndex lastIdx = model()->index(row: qMax(a: firstRow, b: lastRow), column: 0, parent: rootIndex());
439
440 if (firstIdx == lastIdx)
441 return visualRect(index: firstIdx);
442
443 QRegion firstRegion = visualRect(index: firstIdx);
444 QRegion lastRegion = visualRect(index: lastIdx);
445 return firstRegion.united(r: lastRegion);
446}
447
448/*!
449 \reimp
450*/
451void QColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
452{
453 Q_UNUSED(rect);
454 Q_UNUSED(command);
455}
456
457/*!
458 \reimp
459*/
460void QColumnView::setSelectionModel(QItemSelectionModel *newSelectionModel)
461{
462 Q_D(const QColumnView);
463 for (int i = 0; i < d->columns.size(); ++i) {
464 if (d->columns.at(i)->selectionModel() == selectionModel()) {
465 d->columns.at(i)->setSelectionModel(newSelectionModel);
466 break;
467 }
468 }
469 QAbstractItemView::setSelectionModel(newSelectionModel);
470}
471
472/*!
473 \reimp
474*/
475QSize QColumnView::sizeHint() const
476{
477 Q_D(const QColumnView);
478 QSize sizeHint;
479 for (int i = 0; i < d->columns.size(); ++i) {
480 sizeHint += d->columns.at(i)->sizeHint();
481 }
482 return sizeHint.expandedTo(otherSize: QAbstractItemView::sizeHint());
483}
484
485/*!
486 \internal
487 Move all widgets from the corner grip and to the right
488 */
489void QColumnViewPrivate::_q_gripMoved(int offset)
490{
491 Q_Q(QColumnView);
492
493 QObject *grip = q->sender();
494 Q_ASSERT(grip);
495
496 if (q->isRightToLeft())
497 offset = -1 * offset;
498
499 bool found = false;
500 for (int i = 0; i < columns.size(); ++i) {
501 if (!found && columns.at(i)->cornerWidget() == grip) {
502 found = true;
503 columnSizes[i] = columns.at(i)->width();
504 if (q->isRightToLeft())
505 columns.at(i)->move(ax: columns.at(i)->x() + offset, ay: 0);
506 continue;
507 }
508 if (!found)
509 continue;
510
511 int currentX = columns.at(i)->x();
512 columns.at(i)->move(ax: currentX + offset, ay: 0);
513 }
514
515 updateScrollbars();
516}
517
518/*!
519 \internal
520
521 Find where the current columns intersect parent's columns
522
523 Delete any extra columns and insert any needed columns.
524 */
525void QColumnViewPrivate::closeColumns(const QModelIndex &parent, bool build)
526{
527 if (columns.isEmpty())
528 return;
529
530 bool clearAll = !parent.isValid();
531 bool passThroughRoot = false;
532
533 QList<QModelIndex> dirsToAppend;
534
535 // Find the last column that matches the parent's tree
536 int currentColumn = -1;
537 QModelIndex parentIndex = parent;
538 while (currentColumn == -1 && parentIndex.isValid()) {
539 if (columns.isEmpty())
540 break;
541 parentIndex = parentIndex.parent();
542 if (root == parentIndex)
543 passThroughRoot = true;
544 if (!parentIndex.isValid())
545 break;
546 for (int i = columns.size() - 1; i >= 0; --i) {
547 if (columns.at(i)->rootIndex() == parentIndex) {
548 currentColumn = i;
549 break;
550 }
551 }
552 if (currentColumn == -1)
553 dirsToAppend.append(t: parentIndex);
554 }
555
556 // Someone wants to go to an index that can be reached without changing
557 // the root index, don't allow them
558 if (!clearAll && !passThroughRoot && currentColumn == -1)
559 return;
560
561 if (currentColumn == -1 && parent.isValid())
562 currentColumn = 0;
563
564 // Optimization so we don't go deleting and then creating the same thing
565 bool alreadyExists = false;
566 if (build && columns.size() > currentColumn + 1) {
567 bool viewingParent = (columns.at(i: currentColumn + 1)->rootIndex() == parent);
568 bool viewingChild = (!model->hasChildren(parent)
569 && !columns.at(i: currentColumn + 1)->rootIndex().isValid());
570 if (viewingParent || viewingChild) {
571 currentColumn++;
572 alreadyExists = true;
573 }
574 }
575
576 // Delete columns that don't match our path
577 for (int i = columns.size() - 1; i > currentColumn; --i) {
578 QAbstractItemView* notShownAnymore = columns.at(i);
579 columns.removeAt(i);
580 notShownAnymore->setVisible(false);
581 if (notShownAnymore != previewColumn)
582 notShownAnymore->deleteLater();
583 }
584
585 if (columns.isEmpty()) {
586 offset = 0;
587 updateScrollbars();
588 }
589
590 // Now fill in missing columns
591 while (!dirsToAppend.isEmpty()) {
592 QAbstractItemView *newView = createColumn(index: dirsToAppend.takeLast(), show: true);
593 if (!dirsToAppend.isEmpty())
594 newView->setCurrentIndex(dirsToAppend.constLast());
595 }
596
597 if (build && !alreadyExists)
598 createColumn(index: parent, show: false);
599}
600
601void QColumnViewPrivate::_q_clicked(const QModelIndex &index)
602{
603 Q_Q(QColumnView);
604 QModelIndex parent = index.parent();
605 QAbstractItemView *columnClicked = nullptr;
606 for (int column = 0; column < columns.size(); ++column) {
607 if (columns.at(i: column)->rootIndex() == parent) {
608 columnClicked = columns[column];
609 break;
610 }
611 }
612 if (q->selectionModel() && columnClicked) {
613 QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current;
614 if (columnClicked->selectionModel()->isSelected(index))
615 flags |= QItemSelectionModel::Select;
616 q->selectionModel()->setCurrentIndex(index, command: flags);
617 }
618}
619
620/*!
621 \internal
622 Create a new column for \a index. A grip is attached if requested and it is shown
623 if requested.
624
625 Return the new view
626
627 \sa createColumn(), setPreviewWidget()
628 \sa doLayout()
629*/
630QAbstractItemView *QColumnViewPrivate::createColumn(const QModelIndex &index, bool show)
631{
632 Q_Q(QColumnView);
633 QAbstractItemView *view = nullptr;
634 if (model->hasChildren(parent: index)) {
635 view = q->createColumn(rootIndex: index);
636 q->connect(sender: view, SIGNAL(clicked(QModelIndex)),
637 receiver: q, SLOT(_q_clicked(QModelIndex)));
638 } else {
639 if (!previewColumn)
640 setPreviewWidget(new QWidget(q));
641 view = previewColumn;
642 view->setMinimumWidth(qMax(a: view->minimumWidth(), b: previewWidget->minimumWidth()));
643 }
644
645 q->connect(sender: view, SIGNAL(activated(QModelIndex)),
646 receiver: q, SIGNAL(activated(QModelIndex)));
647 q->connect(sender: view, SIGNAL(clicked(QModelIndex)),
648 receiver: q, SIGNAL(clicked(QModelIndex)));
649 q->connect(sender: view, SIGNAL(doubleClicked(QModelIndex)),
650 receiver: q, SIGNAL(doubleClicked(QModelIndex)));
651 q->connect(sender: view, SIGNAL(entered(QModelIndex)),
652 receiver: q, SIGNAL(entered(QModelIndex)));
653 q->connect(sender: view, SIGNAL(pressed(QModelIndex)),
654 receiver: q, SIGNAL(pressed(QModelIndex)));
655
656 view->setFocusPolicy(Qt::NoFocus);
657 view->setParent(viewport);
658 Q_ASSERT(view);
659
660 // Setup corner grip
661 if (showResizeGrips) {
662 QColumnViewGrip *grip = new QColumnViewGrip(view);
663 view->setCornerWidget(grip);
664 q->connect(sender: grip, SIGNAL(gripMoved(int)), receiver: q, SLOT(_q_gripMoved(int)));
665 }
666
667 if (columnSizes.size() > columns.size()) {
668 view->setGeometry(ax: 0, ay: 0, aw: columnSizes.at(i: columns.size()), ah: viewport->height());
669 } else {
670 int initialWidth = view->sizeHint().width();
671 if (q->isRightToLeft())
672 view->setGeometry(ax: viewport->width() - initialWidth, ay: 0, aw: initialWidth, ah: viewport->height());
673 else
674 view->setGeometry(ax: 0, ay: 0, aw: initialWidth, ah: viewport->height());
675 columnSizes.resize(size: qMax(a: columnSizes.size(), b: columns.size() + 1));
676 columnSizes[columns.size()] = initialWidth;
677 }
678 if (!columns.isEmpty() && columns.constLast()->isHidden())
679 columns.constLast()->setVisible(true);
680
681 columns.append(t: view);
682 doLayout();
683 updateScrollbars();
684 if (show && view->isHidden())
685 view->setVisible(true);
686 return view;
687}
688
689/*!
690 \fn void QColumnView::updatePreviewWidget(const QModelIndex &index)
691
692 This signal is emitted when the preview widget should be updated to
693 provide rich information about \a index
694
695 \sa previewWidget()
696 */
697
698/*!
699 To use a custom widget for the final column when you select
700 an item overload this function and return a widget.
701 \a index is the root index that will be assigned to the view.
702
703 Return the new view. QColumnView will automatically take ownership of the widget.
704
705 \sa setPreviewWidget()
706 */
707QAbstractItemView *QColumnView::createColumn(const QModelIndex &index)
708{
709 QListView *view = new QListView(viewport());
710
711 initializeColumn(column: view);
712
713 view->setRootIndex(index);
714 if (model()->canFetchMore(parent: index))
715 model()->fetchMore(parent: index);
716
717 return view;
718}
719
720/*!
721 Copies the behavior and options of the column view and applies them to
722 the \a column such as the iconSize(), textElideMode() and
723 alternatingRowColors(). This can be useful when reimplementing
724 createColumn().
725
726 \since 4.4
727 \sa createColumn()
728 */
729void QColumnView::initializeColumn(QAbstractItemView *column) const
730{
731 Q_D(const QColumnView);
732
733 column->setFrameShape(QFrame::NoFrame);
734 column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
735 column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
736 column->setMinimumWidth(100);
737 column->setAttribute(Qt::WA_MacShowFocusRect, on: false);
738
739#if QT_CONFIG(draganddrop)
740 column->setDragDropMode(dragDropMode());
741 column->setDragDropOverwriteMode(dragDropOverwriteMode());
742 column->setDropIndicatorShown(showDropIndicator());
743#endif
744 column->setAlternatingRowColors(alternatingRowColors());
745 column->setAutoScroll(hasAutoScroll());
746 column->setEditTriggers(editTriggers());
747 column->setHorizontalScrollMode(horizontalScrollMode());
748 column->setIconSize(iconSize());
749 column->setSelectionBehavior(selectionBehavior());
750 column->setSelectionMode(selectionMode());
751 column->setTabKeyNavigation(tabKeyNavigation());
752 column->setTextElideMode(textElideMode());
753 column->setVerticalScrollMode(verticalScrollMode());
754
755 column->setModel(model());
756
757 // Copy the custom delegate per row
758 for (auto i = d->rowDelegates.cbegin(), end = d->rowDelegates.cend(); i != end; ++i)
759 column->setItemDelegateForRow(row: i.key(), delegate: i.value());
760
761 // set the delegate to be the columnview delegate
762 QAbstractItemDelegate *delegate = column->itemDelegate();
763 column->setItemDelegate(d->itemDelegate);
764 delete delegate;
765}
766
767/*!
768 Returns the preview widget, or \nullptr if there is none.
769
770 \sa setPreviewWidget(), updatePreviewWidget()
771*/
772QWidget *QColumnView::previewWidget() const
773{
774 Q_D(const QColumnView);
775 return d->previewWidget;
776}
777
778/*!
779 Sets the preview \a widget.
780
781 The \a widget becomes a child of the column view, and will be
782 destroyed when the column area is deleted or when a new widget is
783 set.
784
785 \sa previewWidget(), updatePreviewWidget()
786*/
787void QColumnView::setPreviewWidget(QWidget *widget)
788{
789 Q_D(QColumnView);
790 d->setPreviewWidget(widget);
791}
792
793/*!
794 \internal
795*/
796void QColumnViewPrivate::setPreviewWidget(QWidget *widget)
797{
798 Q_Q(QColumnView);
799 if (previewColumn) {
800 if (!columns.isEmpty() && columns.constLast() == previewColumn)
801 columns.removeLast();
802 previewColumn->deleteLater();
803 }
804 QColumnViewPreviewColumn *column = new QColumnViewPreviewColumn(q);
805 column->setPreviewWidget(widget);
806 previewColumn = column;
807 previewColumn->hide();
808 previewColumn->setFrameShape(QFrame::NoFrame);
809 previewColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
810 previewColumn->setSelectionMode(QAbstractItemView::NoSelection);
811 previewColumn->setMinimumWidth(qMax(a: previewColumn->verticalScrollBar()->width(),
812 b: previewColumn->minimumWidth()));
813 previewWidget = widget;
814 previewWidget->setParent(previewColumn->viewport());
815}
816
817/*!
818 Sets the column widths to the values given in the \a list. Extra values in the list are
819 kept and used when the columns are created.
820
821 If list contains too few values, only width of the rest of the columns will not be modified.
822
823 \sa columnWidths(), createColumn()
824*/
825void QColumnView::setColumnWidths(const QList<int> &list)
826{
827 Q_D(QColumnView);
828 int i = 0;
829 const int listCount = list.size();
830 const int count = qMin(a: listCount, b: d->columns.size());
831 for (; i < count; ++i) {
832 d->columns.at(i)->resize(w: list.at(i), h: d->columns.at(i)->height());
833 d->columnSizes[i] = list.at(i);
834 }
835
836 d->columnSizes.reserve(size: listCount);
837 for (; i < listCount; ++i)
838 d->columnSizes.append(t: list.at(i));
839}
840
841/*!
842 Returns a list of the width of all the columns in this view.
843
844 \sa setColumnWidths()
845*/
846QList<int> QColumnView::columnWidths() const
847{
848 Q_D(const QColumnView);
849 QList<int> list;
850 const int columnCount = d->columns.size();
851 list.reserve(size: columnCount);
852 for (int i = 0; i < columnCount; ++i)
853 list.append(t: d->columnSizes.at(i));
854 return list;
855}
856
857/*!
858 \reimp
859*/
860void QColumnView::rowsInserted(const QModelIndex &parent, int start, int end)
861{
862 QAbstractItemView::rowsInserted(parent, start, end);
863 d_func()->checkColumnCreation(parent);
864}
865
866/*!
867 \reimp
868*/
869void QColumnView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
870{
871 Q_D(QColumnView);
872 if (!current.isValid()) {
873 QAbstractItemView::currentChanged(current, previous);
874 return;
875 }
876
877 QModelIndex currentParent = current.parent();
878 // optimize for just moving up/down in a list where the child view doesn't change
879 if (currentParent == previous.parent()
880 && model()->hasChildren(parent: current) && model()->hasChildren(parent: previous)) {
881 for (int i = 0; i < d->columns.size(); ++i) {
882 if (currentParent == d->columns.at(i)->rootIndex()) {
883 if (d->columns.size() > i + 1) {
884 QAbstractItemView::currentChanged(current, previous);
885 return;
886 }
887 break;
888 }
889 }
890 }
891
892 // Scrolling to the right we need to have an empty spot
893 bool found = false;
894 if (currentParent == previous) {
895 for (int i = 0; i < d->columns.size(); ++i) {
896 if (currentParent == d->columns.at(i)->rootIndex()) {
897 found = true;
898 if (d->columns.size() < i + 2) {
899 d->createColumn(index: current, show: false);
900 }
901 break;
902 }
903 }
904 }
905 if (!found)
906 d->closeColumns(parent: current, build: true);
907
908 if (!model()->hasChildren(parent: current))
909 emit updatePreviewWidget(index: current);
910
911 QAbstractItemView::currentChanged(current, previous);
912}
913
914/*
915 We have change the current column and need to update focus and selection models
916 on the new current column.
917*/
918void QColumnViewPrivate::_q_changeCurrentColumn()
919{
920 Q_Q(QColumnView);
921 if (columns.isEmpty())
922 return;
923
924 QModelIndex current = q->currentIndex();
925 if (!current.isValid())
926 return;
927
928 // We might have scrolled far to the left so we need to close all of the children
929 closeColumns(parent: current, build: true);
930
931 // Set up the "current" column with focus
932 int currentColumn = qMax(a: 0, b: columns.size() - 2);
933 QAbstractItemView *parentColumn = columns.at(i: currentColumn);
934 if (q->hasFocus())
935 parentColumn->setFocus(Qt::OtherFocusReason);
936 q->setFocusProxy(parentColumn);
937
938 // find the column that is our current selection model and give it a new one.
939 for (int i = 0; i < columns.size(); ++i) {
940 if (columns.at(i)->selectionModel() == q->selectionModel()) {
941 QItemSelectionModel *replacementSelectionModel =
942 new QItemSelectionModel(parentColumn->model());
943 replacementSelectionModel->setCurrentIndex(
944 index: q->selectionModel()->currentIndex(), command: QItemSelectionModel::Current);
945 replacementSelectionModel->select(
946 selection: q->selectionModel()->selection(), command: QItemSelectionModel::Select);
947 QAbstractItemView *view = columns.at(i);
948 view->setSelectionModel(replacementSelectionModel);
949 view->setFocusPolicy(Qt::NoFocus);
950 if (columns.size() > i + 1) {
951 const QModelIndex newRootIndex = columns.at(i: i + 1)->rootIndex();
952 if (newRootIndex.isValid())
953 view->setCurrentIndex(newRootIndex);
954 }
955 break;
956 }
957 }
958 parentColumn->selectionModel()->deleteLater();
959 parentColumn->setFocusPolicy(Qt::StrongFocus);
960 parentColumn->setSelectionModel(q->selectionModel());
961 // We want the parent selection to stay highlighted (but dimmed depending upon the color theme)
962 if (currentColumn > 0) {
963 parentColumn = columns.at(i: currentColumn - 1);
964 if (parentColumn->currentIndex() != current.parent())
965 parentColumn->setCurrentIndex(current.parent());
966 }
967
968 if (columns.constLast()->isHidden()) {
969 columns.constLast()->setVisible(true);
970 }
971 if (columns.constLast()->selectionModel())
972 columns.constLast()->selectionModel()->clear();
973 updateScrollbars();
974}
975
976/*!
977 \reimp
978*/
979void QColumnView::selectAll()
980{
981 if (!model() || !selectionModel())
982 return;
983
984 QModelIndexList indexList = selectionModel()->selectedIndexes();
985 QModelIndex parent = rootIndex();
986 QItemSelection selection;
987 if (indexList.size() >= 1)
988 parent = indexList.at(i: 0).parent();
989 if (indexList.size() == 1) {
990 parent = indexList.at(i: 0);
991 if (!model()->hasChildren(parent))
992 parent = parent.parent();
993 else
994 selection.append(t: QItemSelectionRange(parent, parent));
995 }
996
997 QModelIndex tl = model()->index(row: 0, column: 0, parent);
998 QModelIndex br = model()->index(row: model()->rowCount(parent) - 1,
999 column: model()->columnCount(parent) - 1,
1000 parent);
1001 selection.append(t: QItemSelectionRange(tl, br));
1002 selectionModel()->select(selection, command: QItemSelectionModel::ClearAndSelect);
1003}
1004
1005/*
1006 * private object implementation
1007 */
1008QColumnViewPrivate::QColumnViewPrivate()
1009: QAbstractItemViewPrivate()
1010,showResizeGrips(true)
1011,offset(0)
1012,previewWidget(nullptr)
1013,previewColumn(nullptr)
1014{
1015}
1016
1017QColumnViewPrivate::~QColumnViewPrivate()
1018{
1019}
1020
1021/*!
1022 \internal
1023
1024 */
1025void QColumnViewPrivate::_q_columnsInserted(const QModelIndex &parent, int start, int end)
1026{
1027 QAbstractItemViewPrivate::_q_columnsInserted(parent, start, end);
1028 checkColumnCreation(parent);
1029}
1030
1031/*!
1032 \internal
1033
1034 Makes sure we create a corresponding column as a result of changing the model.
1035
1036 */
1037void QColumnViewPrivate::checkColumnCreation(const QModelIndex &parent)
1038{
1039 if (parent == q_func()->currentIndex() && model->hasChildren(parent)) {
1040 //the parent has children and is the current
1041 //let's try to find out if there is already a mapping that is good
1042 for (int i = 0; i < columns.size(); ++i) {
1043 QAbstractItemView *view = columns.at(i);
1044 if (view->rootIndex() == parent) {
1045 if (view == previewColumn) {
1046 //let's recreate the parent
1047 closeColumns(parent, build: false);
1048 createColumn(index: parent, show: true /*show*/);
1049 }
1050 break;
1051 }
1052 }
1053 }
1054}
1055
1056/*!
1057 \internal
1058 Place all of the columns where they belong inside of the viewport, resize as necessary.
1059*/
1060void QColumnViewPrivate::doLayout()
1061{
1062 Q_Q(QColumnView);
1063 if (!model || columns.isEmpty())
1064 return;
1065
1066 int viewportHeight = viewport->height();
1067 int x = columns.at(i: 0)->x();
1068
1069 if (q->isRightToLeft()) {
1070 x = viewport->width() + q->horizontalOffset();
1071 for (int i = 0; i < columns.size(); ++i) {
1072 QAbstractItemView *view = columns.at(i);
1073 x -= view->width();
1074 if (x != view->x() || viewportHeight != view->height())
1075 view->setGeometry(ax: x, ay: 0, aw: view->width(), ah: viewportHeight);
1076 }
1077 } else {
1078 for (int i = 0; i < columns.size(); ++i) {
1079 QAbstractItemView *view = columns.at(i);
1080 int currentColumnWidth = view->width();
1081 if (x != view->x() || viewportHeight != view->height())
1082 view->setGeometry(ax: x, ay: 0, aw: currentColumnWidth, ah: viewportHeight);
1083 x += currentColumnWidth;
1084 }
1085 }
1086}
1087
1088/*!
1089 \internal
1090
1091 Draws a delegate with a > if an object has children.
1092
1093 \sa {Model/View Programming}, QStyledItemDelegate
1094*/
1095void QColumnViewDelegate::paint(QPainter *painter,
1096 const QStyleOptionViewItem &option,
1097 const QModelIndex &index) const
1098{
1099 bool reverse = (option.direction == Qt::RightToLeft);
1100 int width = ((option.rect.height() * 2) / 3);
1101 // Modify the options to give us room to add an arrow
1102 QStyleOptionViewItem opt = option;
1103 if (reverse)
1104 opt.rect.adjust(dx1: width,dy1: 0,dx2: 0,dy2: 0);
1105 else
1106 opt.rect.adjust(dx1: 0,dy1: 0,dx2: -width,dy2: 0);
1107
1108 if (!(index.model()->flags(index) & Qt::ItemIsEnabled)) {
1109 opt.showDecorationSelected = true;
1110 opt.state |= QStyle::State_Selected;
1111 }
1112
1113 QStyledItemDelegate::paint(painter, option: opt, index);
1114
1115 if (reverse)
1116 opt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height());
1117 else
1118 opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(),
1119 width, option.rect.height());
1120
1121 // Draw >
1122 if (index.model()->hasChildren(parent: index)) {
1123 const QWidget *view = opt.widget;
1124 QStyle *style = view ? view->style() : QApplication::style();
1125 style->drawPrimitive(pe: QStyle::PE_IndicatorColumnViewArrow, opt: &opt, p: painter, w: view);
1126 }
1127}
1128
1129QT_END_NAMESPACE
1130
1131#include "moc_qcolumnview.cpp"
1132
1133#endif // QT_CONFIG(columnview)
1134

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