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