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