1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui 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 http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://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 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include <qglobal.h>
43 
44 #ifndef QT_NO_COLUMNVIEW
45 
46 #include "qcolumnview.h"
47 #include "qcolumnview_p.h"
48 #include "qcolumnviewgrip_p.h"
49 
50 #include <qlistview.h>
51 #include <qabstractitemdelegate.h>
52 #include <qscrollbar.h>
53 #include <qpainter.h>
54 #include <qdebug.h>
55 
56 QT_BEGIN_NAMESPACE
57 
58 #define ANIMATION_DURATION_MSEC 150
59 
60 /*!
61     \since 4.3
62     \class QColumnView
63     \brief The QColumnView class provides a model/view implementation of a column view.
64     \ingroup model-view
65     \ingroup advanced
66 
67 
68     QColumnView displays a model in a number of QListViews, one for each
69     hierarchy in the tree.  This is sometimes referred to as a cascading list.
70 
71     The QColumnView class is one of the \l{Model/View Classes}
72     and is part of Qt's \l{Model/View Programming}{model/view framework}.
73 
74     QColumnView implements the interfaces defined by the
75     QAbstractItemView class to allow it to display data provided by
76     models derived from the QAbstractItemModel class.
77 
78     \image qcolumnview.png
79 
80     \sa \link model-view-programming.html Model/View Programming\endlink
81 */
82 
83 /*!
84     Constructs a column view with a \a parent to represent a model's
85     data. Use setModel() to set the model.
86 
87     \sa QAbstractItemModel
88 */
QColumnView(QWidget * parent)89 QColumnView::QColumnView(QWidget * parent)
90 :  QAbstractItemView(*new QColumnViewPrivate, parent)
91 {
92     Q_D(QColumnView);
93     d->initialize();
94 }
95 
96 /*!
97     \internal
98 */
QColumnView(QColumnViewPrivate & dd,QWidget * parent)99 QColumnView::QColumnView(QColumnViewPrivate & dd, QWidget * parent)
100 :  QAbstractItemView(dd, parent)
101 {
102     Q_D(QColumnView);
103     d->initialize();
104 }
105 
initialize()106 void QColumnViewPrivate::initialize()
107 {
108     Q_Q(QColumnView);
109     q->setTextElideMode(Qt::ElideMiddle);
110 #ifndef QT_NO_ANIMATION
111     QObject::connect(&currentAnimation, SIGNAL(finished()), q, SLOT(_q_changeCurrentColumn()));
112     currentAnimation.setDuration(ANIMATION_DURATION_MSEC);
113     currentAnimation.setTargetObject(hbar);
114     currentAnimation.setPropertyName("value");
115     currentAnimation.setEasingCurve(QEasingCurve::InOutQuad);
116 #endif //QT_NO_ANIMATION
117     delete itemDelegate;
118     q->setItemDelegate(new QColumnViewDelegate(q));
119 }
120 
121 /*!
122     Destroys the column view.
123 */
~QColumnView()124 QColumnView::~QColumnView()
125 {
126 }
127 
128 /*!
129     \property QColumnView::resizeGripsVisible
130     \brief the way to specify if the list views gets resize grips or not
131 
132     By default, \c visible is set to true
133 
134     \sa setRootIndex()
135 */
setResizeGripsVisible(bool visible)136 void QColumnView::setResizeGripsVisible(bool visible)
137 {
138     Q_D(QColumnView);
139     if (d->showResizeGrips == visible)
140         return;
141     d->showResizeGrips = visible;
142     for (int i = 0; i < d->columns.count(); ++i) {
143         QAbstractItemView *view = d->columns[i];
144         if (visible) {
145             QColumnViewGrip *grip = new QColumnViewGrip(view);
146             view->setCornerWidget(grip);
147             connect(grip, SIGNAL(gripMoved(int)), this, SLOT(_q_gripMoved(int)));
148         } else {
149             QWidget *widget = view->cornerWidget();
150             view->setCornerWidget(0);
151             widget->deleteLater();
152         }
153     }
154 }
155 
resizeGripsVisible() const156 bool QColumnView::resizeGripsVisible() const
157 {
158     Q_D(const QColumnView);
159     return d->showResizeGrips;
160 }
161 
162 /*!
163     \reimp
164 */
setModel(QAbstractItemModel * model)165 void QColumnView::setModel(QAbstractItemModel *model)
166 {
167     Q_D(QColumnView);
168     if (model == d->model)
169         return;
170     d->closeColumns();
171     QAbstractItemView::setModel(model);
172 }
173 
174 /*!
175     \reimp
176 */
setRootIndex(const QModelIndex & index)177 void QColumnView::setRootIndex(const QModelIndex &index)
178 {
179     Q_D(QColumnView);
180     if (!model())
181         return;
182 
183     d->closeColumns();
184     Q_ASSERT(d->columns.count() == 0);
185 
186     QAbstractItemView *view = d->createColumn(index, true);
187     if (view->selectionModel())
188         view->selectionModel()->deleteLater();
189     if (view->model())
190         view->setSelectionModel(selectionModel());
191 
192     QAbstractItemView::setRootIndex(index);
193     d->updateScrollbars();
194 }
195 
196 /*!
197     \reimp
198 */
isIndexHidden(const QModelIndex & index) const199 bool QColumnView::isIndexHidden(const QModelIndex &index) const
200 {
201     Q_UNUSED(index);
202     return false;
203 }
204 
205 /*!
206     \reimp
207 */
indexAt(const QPoint & point) const208 QModelIndex QColumnView::indexAt(const QPoint &point) const
209 {
210     Q_D(const QColumnView);
211     for (int i = 0; i < d->columns.size(); ++i) {
212         QPoint topLeft = d->columns.at(i)->frameGeometry().topLeft();
213         QPoint adjustedPoint(point.x() - topLeft.x(), point.y() - topLeft.y());
214         QModelIndex index = d->columns.at(i)->indexAt(adjustedPoint);
215         if (index.isValid())
216             return index;
217     }
218     return QModelIndex();
219 }
220 
221 /*!
222     \reimp
223 */
visualRect(const QModelIndex & index) const224 QRect QColumnView::visualRect(const QModelIndex &index) const
225 {
226     if (!index.isValid())
227         return QRect();
228 
229     Q_D(const QColumnView);
230     for (int i = 0; i < d->columns.size(); ++i) {
231         QRect rect = d->columns.at(i)->visualRect(index);
232         if (!rect.isNull()) {
233             rect.translate(d->columns.at(i)->frameGeometry().topLeft());
234             return rect;
235         }
236     }
237     return QRect();
238 }
239 
240 /*!
241     \reimp
242  */
scrollContentsBy(int dx,int dy)243 void QColumnView::scrollContentsBy(int dx, int dy)
244 {
245     Q_D(QColumnView);
246     if (d->columns.isEmpty() || dx == 0)
247         return;
248 
249     dx = isRightToLeft() ? -dx : dx;
250     for (int i = 0; i < d->columns.count(); ++i)
251         d->columns.at(i)->move(d->columns.at(i)->x() + dx, 0);
252     d->offset += dx;
253     QAbstractItemView::scrollContentsBy(dx, dy);
254 }
255 
256 /*!
257     \reimp
258 */
scrollTo(const QModelIndex & index,ScrollHint hint)259 void QColumnView::scrollTo(const QModelIndex &index, ScrollHint hint)
260 {
261     Q_D(QColumnView);
262     Q_UNUSED(hint);
263     if (!index.isValid() || d->columns.isEmpty())
264         return;
265 
266 #ifndef QT_NO_ANIMATION
267     if (d->currentAnimation.state() == QPropertyAnimation::Running)
268         return;
269 
270     d->currentAnimation.stop();
271 #endif //QT_NO_ANIMATION
272 
273     // Fill up what is needed to get to index
274     d->closeColumns(index, true);
275 
276     QModelIndex indexParent = index.parent();
277     // Find the left edge of the column that contains index
278     int currentColumn = 0;
279     int leftEdge = 0;
280     while (currentColumn < d->columns.size()) {
281         if (indexParent == d->columns.at(currentColumn)->rootIndex())
282             break;
283         leftEdge += d->columns.at(currentColumn)->width();
284         ++currentColumn;
285     }
286 
287     // Don't let us scroll above the root index
288     if (currentColumn == d->columns.size())
289         return;
290 
291     int indexColumn = currentColumn;
292     // Find the width of what we want to show (i.e. the right edge)
293     int visibleWidth = d->columns.at(currentColumn)->width();
294     // We want to always try to show two columns
295     if (currentColumn + 1 < d->columns.size()) {
296         ++currentColumn;
297         visibleWidth += d->columns.at(currentColumn)->width();
298     }
299 
300     int rightEdge = leftEdge + visibleWidth;
301     if (isRightToLeft()) {
302         leftEdge = viewport()->width() - leftEdge;
303         rightEdge = leftEdge - visibleWidth;
304         qSwap(rightEdge, leftEdge);
305     }
306 
307     // If it is already visible don't animate
308     if (leftEdge > -horizontalOffset()
309         && rightEdge <= ( -horizontalOffset() + viewport()->size().width())) {
310             d->columns.at(indexColumn)->scrollTo(index);
311             d->_q_changeCurrentColumn();
312             return;
313     }
314 
315     int newScrollbarValue = 0;
316     if (isRightToLeft()) {
317         if (leftEdge < 0) {
318             // scroll to the right
319             newScrollbarValue = viewport()->size().width() - leftEdge;
320         } else {
321             // scroll to the left
322             newScrollbarValue = rightEdge + horizontalOffset();
323         }
324     } else {
325         if (leftEdge > -horizontalOffset()) {
326             // scroll to the right
327             newScrollbarValue = rightEdge - viewport()->size().width();
328         } else {
329             // scroll to the left
330             newScrollbarValue = leftEdge;
331         }
332     }
333 
334 #ifndef QT_NO_ANIMATION
335     d->currentAnimation.setEndValue(newScrollbarValue);
336     d->currentAnimation.start();
337 #else
338     horizontalScrollBar()->setValue(newScrollbarValue);
339 #endif //QT_NO_ANIMATION
340 }
341 
342 /*!
343     \reimp
344     Move left should go to the parent index
345     Move right should go to the child index or down if there is no child
346 */
moveCursor(CursorAction cursorAction,Qt::KeyboardModifiers modifiers)347 QModelIndex QColumnView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
348 {
349     // the child views which have focus get to deal with this first and if
350     // they don't accept it then it comes up this view and we only grip left/right
351     Q_UNUSED(modifiers);
352     if (!model())
353         return QModelIndex();
354 
355     QModelIndex current = currentIndex();
356     if (isRightToLeft()) {
357         if (cursorAction == MoveLeft)
358             cursorAction = MoveRight;
359         else if (cursorAction == MoveRight)
360             cursorAction = MoveLeft;
361     }
362     switch (cursorAction) {
363     case MoveLeft:
364         if (current.parent().isValid() && current.parent() != rootIndex())
365             return (current.parent());
366         else
367             return current;
368         break;
369 
370     case MoveRight:
371         if (model()->hasChildren(current))
372             return model()->index(0, 0, current);
373         else
374             return current.sibling(current.row() + 1, current.column());
375         break;
376 
377     default:
378         break;
379     }
380 
381     return QModelIndex();
382 }
383 
384 /*!
385     \reimp
386 */
resizeEvent(QResizeEvent * event)387 void 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 */
updateScrollbars()405 void QColumnViewPrivate::updateScrollbars()
406 {
407     Q_Q(QColumnView);
408 #ifndef QT_NO_ANIMATION
409     if (currentAnimation.state() == QPropertyAnimation::Running)
410         return;
411 #endif //QT_NO_ANIMATION
412 
413     // find the total horizontal length of the laid out columns
414     int horizontalLength = 0;
415     if (!columns.isEmpty()) {
416         horizontalLength = (columns.last()->x() + columns.last()->width()) - columns.first()->x();
417         if (horizontalLength <= 0) // reverse mode
418             horizontalLength = (columns.first()->x() + columns.first()->width()) - columns.last()->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 */
horizontalOffset() const443 int QColumnView::horizontalOffset() const
444 {
445     Q_D(const QColumnView);
446     return d->offset;
447 }
448 
449 /*!
450     \reimp
451 */
verticalOffset() const452 int QColumnView::verticalOffset() const
453 {
454     return 0;
455 }
456 
457 /*!
458     \reimp
459 */
visualRegionForSelection(const QItemSelection & selection) const460 QRegion QColumnView::visualRegionForSelection(const QItemSelection &selection) const
461 {
462     int ranges = selection.count();
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.unite(lastRegion);
485 }
486 
487 /*!
488     \reimp
489 */
setSelection(const QRect & rect,QItemSelectionModel::SelectionFlags command)490 void QColumnView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
491 {
492     Q_UNUSED(rect);
493     Q_UNUSED(command);
494 }
495 
496 /*!
497     \reimp
498 */
setSelectionModel(QItemSelectionModel * newSelectionModel)499 void 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 */
sizeHint() const514 QSize 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   */
_q_gripMoved(int offset)528 void QColumnViewPrivate::_q_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   */
closeColumns(const QModelIndex & parent,bool build)564 void 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     }
623 
624     if (columns.isEmpty()) {
625         offset = 0;
626         updateScrollbars();
627     }
628 
629     // Now fill in missing columns
630     while (!dirsToAppend.isEmpty()) {
631         QAbstractItemView *newView = createColumn(dirsToAppend.takeLast(), true);
632         if (!dirsToAppend.isEmpty())
633             newView->setCurrentIndex(dirsToAppend.last());
634     }
635 
636     if (build && !alreadyExists)
637         createColumn(parent, false);
638 }
639 
_q_clicked(const QModelIndex & index)640 void QColumnViewPrivate::_q_clicked(const QModelIndex &index)
641 {
642     Q_Q(QColumnView);
643     QModelIndex parent = index.parent();
644     QAbstractItemView *columnClicked = 0;
645     for (int column = 0; column < columns.count(); ++column) {
646         if (columns.at(column)->rootIndex() == parent) {
647             columnClicked = columns[column];
648             break;
649         }
650     }
651     if (q->selectionModel() && columnClicked) {
652         QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::Current;
653         if (columnClicked->selectionModel()->isSelected(index))
654             flags |= QItemSelectionModel::Select;
655         q->selectionModel()->setCurrentIndex(index, flags);
656     }
657 }
658 
659 /*!
660     \internal
661     Create a new column for \a index.  A grip is attached if requested and it is shown
662     if requested.
663 
664     Return the new view
665 
666     \sa createColumn() setPreviewWidget()
667     \sa doLayout()
668 */
createColumn(const QModelIndex & index,bool show)669 QAbstractItemView *QColumnViewPrivate::createColumn(const QModelIndex &index, bool show)
670 {
671     Q_Q(QColumnView);
672     QAbstractItemView *view = 0;
673     if (model->hasChildren(index)) {
674         view = q->createColumn(index);
675         q->connect(view, SIGNAL(clicked(QModelIndex)),
676                    q, SLOT(_q_clicked(QModelIndex)));
677     } else {
678         if (!previewColumn)
679             setPreviewWidget(new QWidget(q));
680         view = previewColumn;
681         view->setMinimumWidth(qMax(view->minimumWidth(), previewWidget->minimumWidth()));
682     }
683 
684     q->connect(view, SIGNAL(activated(QModelIndex)),
685             q, SIGNAL(activated(QModelIndex)));
686     q->connect(view, SIGNAL(clicked(QModelIndex)),
687             q, SIGNAL(clicked(QModelIndex)));
688     q->connect(view, SIGNAL(doubleClicked(QModelIndex)),
689             q, SIGNAL(doubleClicked(QModelIndex)));
690     q->connect(view, SIGNAL(entered(QModelIndex)),
691             q, SIGNAL(entered(QModelIndex)));
692     q->connect(view, SIGNAL(pressed(QModelIndex)),
693             q, SIGNAL(pressed(QModelIndex)));
694 
695     view->setFocusPolicy(Qt::NoFocus);
696     view->setParent(viewport);
697     Q_ASSERT(view);
698 
699     // Setup corner grip
700     if (showResizeGrips) {
701         QColumnViewGrip *grip = new QColumnViewGrip(view);
702         view->setCornerWidget(grip);
703         q->connect(grip, SIGNAL(gripMoved(int)), q, SLOT(_q_gripMoved(int)));
704     }
705 
706     if (columnSizes.count() > columns.count()) {
707         view->setGeometry(0, 0, columnSizes.at(columns.count()), viewport->height());
708     } else {
709         int initialWidth = view->sizeHint().width();
710         if (q->isRightToLeft())
711             view->setGeometry(viewport->width() - initialWidth, 0, initialWidth, viewport->height());
712         else
713             view->setGeometry(0, 0, initialWidth, viewport->height());
714         columnSizes.resize(qMax(columnSizes.count(), columns.count() + 1));
715         columnSizes[columns.count()] = initialWidth;
716     }
717     if (!columns.isEmpty() && columns.last()->isHidden())
718         columns.last()->setVisible(true);
719 
720     columns.append(view);
721     doLayout();
722     updateScrollbars();
723     if (show && view->isHidden())
724         view->setVisible(true);
725     return view;
726 }
727 
728 /*!
729     \fn void QColumnView::updatePreviewWidget(const QModelIndex &index)
730 
731     This signal is emitted when the preview widget should be updated to
732     provide rich information about \a index
733 
734     \sa previewWidget()
735  */
736 
737 /*!
738     To use a custom widget for the final column when you select
739     an item overload this function and return a widget.
740     \a index is the root index that will be assigned to the view.
741 
742     Return the new view.  QColumnView will automatically take ownership of the widget.
743 
744     \sa setPreviewWidget()
745  */
createColumn(const QModelIndex & index)746 QAbstractItemView *QColumnView::createColumn(const QModelIndex &index)
747 {
748     QListView *view = new QListView(viewport());
749 
750     initializeColumn(view);
751 
752     view->setRootIndex(index);
753     if (model()->canFetchMore(index))
754         model()->fetchMore(index);
755 
756     return view;
757 }
758 
759 /*!
760     Copies the behavior and options of the column view and applies them to
761     the \a column such as the iconSize(), textElideMode() and
762     alternatingRowColors(). This can be useful when reimplementing
763     createColumn().
764 
765     \since 4.4
766     \sa createColumn()
767  */
initializeColumn(QAbstractItemView * column) const768 void QColumnView::initializeColumn(QAbstractItemView *column) const
769 {
770     Q_D(const QColumnView);
771 
772     column->setFrameShape(QFrame::NoFrame);
773     column->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
774     column->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
775     column->setMinimumWidth(100);
776     column->setAttribute(Qt::WA_MacShowFocusRect, false);
777 
778 #ifndef QT_NO_DRAGANDDROP
779     column->setDragDropMode(dragDropMode());
780     column->setDragDropOverwriteMode(dragDropOverwriteMode());
781     column->setDropIndicatorShown(showDropIndicator());
782 #endif
783     column->setAlternatingRowColors(alternatingRowColors());
784     column->setAutoScroll(hasAutoScroll());
785     column->setEditTriggers(editTriggers());
786     column->setHorizontalScrollMode(horizontalScrollMode());
787     column->setIconSize(iconSize());
788     column->setSelectionBehavior(selectionBehavior());
789     column->setSelectionMode(selectionMode());
790     column->setTabKeyNavigation(tabKeyNavigation());
791     column->setTextElideMode(textElideMode());
792     column->setVerticalScrollMode(verticalScrollMode());
793 
794     column->setModel(model());
795 
796     // Copy the custom delegate per row
797     QMapIterator<int, QPointer<QAbstractItemDelegate> > i(d->rowDelegates);
798     while (i.hasNext()) {
799         i.next();
800         column->setItemDelegateForRow(i.key(), i.value());
801     }
802 
803     // set the delegate to be the columnview delegate
804     QAbstractItemDelegate *delegate = column->itemDelegate();
805     column->setItemDelegate(d->itemDelegate);
806     delete delegate;
807 }
808 
809 /*!
810     Returns the preview widget, or 0 if there is none.
811 
812     \sa setPreviewWidget(), updatePreviewWidget()
813 */
previewWidget() const814 QWidget *QColumnView::previewWidget() const
815 {
816     Q_D(const QColumnView);
817     return d->previewWidget;
818 }
819 
820 /*!
821     Sets the preview \a widget.
822 
823     The \a widget becomes a child of the column view, and will be
824     destroyed when the column area is deleted or when a new widget is
825     set.
826 
827     \sa previewWidget(), updatePreviewWidget()
828 */
setPreviewWidget(QWidget * widget)829 void QColumnView::setPreviewWidget(QWidget *widget)
830 {
831     Q_D(QColumnView);
832     d->setPreviewWidget(widget);
833 }
834 
835 /*!
836     \internal
837 */
setPreviewWidget(QWidget * widget)838 void QColumnViewPrivate::setPreviewWidget(QWidget *widget)
839 {
840     Q_Q(QColumnView);
841     if (previewColumn) {
842         if (!columns.isEmpty() && columns.last() == previewColumn)
843             columns.removeLast();
844         previewColumn->deleteLater();
845     }
846     QColumnViewPreviewColumn *column = new QColumnViewPreviewColumn(q);
847     column->setPreviewWidget(widget);
848     previewColumn = column;
849     previewColumn->hide();
850     previewColumn->setFrameShape(QFrame::NoFrame);
851     previewColumn->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
852     previewColumn->setSelectionMode(QAbstractItemView::NoSelection);
853     previewColumn->setMinimumWidth(qMax(previewColumn->verticalScrollBar()->width(),
854                 previewColumn->minimumWidth()));
855     previewWidget = widget;
856     previewWidget->setParent(previewColumn->viewport());
857 }
858 
859 /*!
860     Sets the column widths to the values given in the \a list.  Extra values in the list are
861     kept and used when the columns are created.
862 
863     If list contains too few values, only width of the rest of the columns will not be modified.
864 
865     \sa columnWidths(), createColumn()
866 */
setColumnWidths(const QList<int> & list)867 void QColumnView::setColumnWidths(const QList<int> &list)
868 {
869     Q_D(QColumnView);
870     int i = 0;
871     for (; (i < list.count() && i < d->columns.count()); ++i) {
872         d->columns.at(i)->resize(list.at(i), d->columns.at(i)->height());
873         d->columnSizes[i] = list.at(i);
874     }
875     for (; i < list.count(); ++i)
876         d->columnSizes.append(list.at(i));
877 }
878 
879 /*!
880     Returns a list of the width of all the columns in this view.
881 
882     \sa setColumnWidths()
883 */
columnWidths() const884 QList<int> QColumnView::columnWidths() const
885 {
886     Q_D(const QColumnView);
887     QList<int> list;
888     for (int i = 0; i < d->columns.count(); ++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                 view->setCurrentIndex(columns.at(i+1)->rootIndex());
988             break;
989         }
990     }
991     parentColumn->selectionModel()->deleteLater();
992     parentColumn->setFocusPolicy(Qt::StrongFocus);
993     parentColumn->setSelectionModel(q->selectionModel());
994     // We want the parent selection to stay highlighted (but dimmed depending upon the color theme)
995     if (currentColumn > 0) {
996         parentColumn = columns.at(currentColumn - 1);
997         if (parentColumn->currentIndex() != current.parent())
998             parentColumn->setCurrentIndex(current.parent());
999     }
1000 
1001     if (columns.last()->isHidden()) {
1002         columns.last()->setVisible(true);
1003     }
1004     if (columns.last()->selectionModel())
1005         columns.last()->selectionModel()->clear();
1006     updateScrollbars();
1007 }
1008 
1009 /*!
1010     \reimp
1011 */
selectAll()1012 void QColumnView::selectAll()
1013 {
1014     if (!model() || !selectionModel())
1015         return;
1016 
1017     QModelIndexList indexList = selectionModel()->selectedIndexes();
1018     QModelIndex parent = rootIndex();
1019     QItemSelection selection;
1020     if (indexList.count() >= 1)
1021         parent = indexList.at(0).parent();
1022     if (indexList.count() == 1) {
1023         parent = indexList.at(0);
1024         if (!model()->hasChildren(parent))
1025             parent = parent.parent();
1026         else
1027             selection.append(QItemSelectionRange(parent, parent));
1028     }
1029 
1030     QModelIndex tl = model()->index(0, 0, parent);
1031     QModelIndex br = model()->index(model()->rowCount(parent) - 1,
1032                                     model()->columnCount(parent) - 1,
1033                                     parent);
1034     selection.append(QItemSelectionRange(tl, br));
1035     selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
1036 }
1037 
1038 /*
1039  * private object implementation
1040  */
QColumnViewPrivate()1041 QColumnViewPrivate::QColumnViewPrivate()
1042 :  QAbstractItemViewPrivate()
1043 ,showResizeGrips(true)
1044 ,offset(0)
1045 ,previewWidget(0)
1046 ,previewColumn(0)
1047 {
1048 }
1049 
~QColumnViewPrivate()1050 QColumnViewPrivate::~QColumnViewPrivate()
1051 {
1052 }
1053 
1054 /*!
1055     \internal
1056 
1057   */
_q_columnsInserted(const QModelIndex & parent,int start,int end)1058 void QColumnViewPrivate::_q_columnsInserted(const QModelIndex &parent, int start, int end)
1059 {
1060     QAbstractItemViewPrivate::_q_columnsInserted(parent, start, end);
1061     checkColumnCreation(parent);
1062 }
1063 
1064 /*!
1065     \internal
1066 
1067     Makes sure we create a corresponding column as a result of changing the model.
1068 
1069   */
checkColumnCreation(const QModelIndex & parent)1070 void QColumnViewPrivate::checkColumnCreation(const QModelIndex &parent)
1071 {
1072     if (parent == q_func()->currentIndex() && model->hasChildren(parent)) {
1073         //the parent has children and is the current
1074         //let's try to find out if there is already a mapping that is good
1075         for (int i = 0; i < columns.count(); ++i) {
1076             QAbstractItemView *view = columns.at(i);
1077             if (view->rootIndex() == parent) {
1078                 if (view == previewColumn) {
1079                     //let's recreate the parent
1080                     closeColumns(parent, false);
1081                     createColumn(parent, true /*show*/);
1082                 }
1083                 break;
1084             }
1085         }
1086     }
1087 }
1088 
1089 /*!
1090     \internal
1091     Place all of the columns where they belong inside of the viewport, resize as necessary.
1092 */
doLayout()1093 void QColumnViewPrivate::doLayout()
1094 {
1095     Q_Q(QColumnView);
1096     if (!model || columns.isEmpty())
1097         return;
1098 
1099     int viewportHeight = viewport->height();
1100     int x = columns.at(0)->x();
1101 
1102     if (q->isRightToLeft()) {
1103         x = viewport->width() + q->horizontalOffset();
1104         for (int i = 0; i < columns.size(); ++i) {
1105             QAbstractItemView *view = columns.at(i);
1106             x -= view->width();
1107             if (x != view->x() || viewportHeight != view->height())
1108                 view->setGeometry(x, 0, view->width(), viewportHeight);
1109         }
1110     } else {
1111         for (int i = 0; i < columns.size(); ++i) {
1112             QAbstractItemView *view = columns.at(i);
1113             int currentColumnWidth = view->width();
1114             if (x != view->x() || viewportHeight != view->height())
1115                 view->setGeometry(x, 0, currentColumnWidth, viewportHeight);
1116             x += currentColumnWidth;
1117         }
1118     }
1119 }
1120 
1121 /*!
1122     \internal
1123 
1124     Draws a delegate with a > if an object has children.
1125 
1126     \sa {Model/View Programming}, QItemDelegate
1127 */
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const1128 void QColumnViewDelegate::paint(QPainter *painter,
1129                           const QStyleOptionViewItem &option,
1130                           const QModelIndex &index) const
1131 {
1132     drawBackground(painter, option, index );
1133 
1134     bool reverse = (option.direction == Qt::RightToLeft);
1135     int width = ((option.rect.height() * 2) / 3);
1136     // Modify the options to give us room to add an arrow
1137     QStyleOptionViewItemV4 opt = option;
1138     if (reverse)
1139         opt.rect.adjust(width,0,0,0);
1140     else
1141         opt.rect.adjust(0,0,-width,0);
1142 
1143     if (!(index.model()->flags(index) & Qt::ItemIsEnabled)) {
1144         opt.showDecorationSelected = true;
1145         opt.state |= QStyle::State_Selected;
1146     }
1147 
1148     QItemDelegate::paint(painter, opt, index);
1149 
1150     if (reverse)
1151         opt.rect = QRect(option.rect.x(), option.rect.y(), width, option.rect.height());
1152     else
1153         opt.rect = QRect(option.rect.x() + option.rect.width() - width, option.rect.y(),
1154                          width, option.rect.height());
1155 
1156     // Draw >
1157     if (index.model()->hasChildren(index)) {
1158         const QWidget *view = opt.widget;
1159         QStyle *style = view ? view->style() : QApplication::style();
1160         style->drawPrimitive(QStyle::PE_IndicatorColumnViewArrow, &opt, painter, view);
1161     }
1162 }
1163 
1164 QT_END_NAMESPACE
1165 
1166 #include "moc_qcolumnview.cpp"
1167 
1168 #endif // QT_NO_COLUMNVIEW
1169