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(¤tAnimation, 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 ¤t, 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