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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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