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