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