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