|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtGui module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
37 ** |
|
38 ** $QT_END_LICENSE$ |
|
39 ** |
|
40 ****************************************************************************/ |
|
41 #include "qtreeview.h" |
|
42 |
|
43 #ifndef QT_NO_TREEVIEW |
|
44 #include <qheaderview.h> |
|
45 #include <qitemdelegate.h> |
|
46 #include <qapplication.h> |
|
47 #include <qscrollbar.h> |
|
48 #include <qpainter.h> |
|
49 #include <qstack.h> |
|
50 #include <qstyle.h> |
|
51 #include <qstyleoption.h> |
|
52 #include <qevent.h> |
|
53 #include <qpen.h> |
|
54 #include <qdebug.h> |
|
55 #ifndef QT_NO_ACCESSIBILITY |
|
56 #include <qaccessible.h> |
|
57 #endif |
|
58 |
|
59 #include <private/qtreeview_p.h> |
|
60 |
|
61 QT_BEGIN_NAMESPACE |
|
62 |
|
63 /*! |
|
64 \class QTreeView |
|
65 \brief The QTreeView class provides a default model/view implementation of a tree view. |
|
66 |
|
67 \ingroup model-view |
|
68 \ingroup advanced |
|
69 |
|
70 |
|
71 A QTreeView implements a tree representation of items from a |
|
72 model. This class is used to provide standard hierarchical lists that |
|
73 were previously provided by the \c QListView class, but using the more |
|
74 flexible approach provided by Qt's model/view architecture. |
|
75 |
|
76 The QTreeView class is one of the \l{Model/View Classes} and is part of |
|
77 Qt's \l{Model/View Programming}{model/view framework}. |
|
78 |
|
79 QTreeView implements the interfaces defined by the |
|
80 QAbstractItemView class to allow it to display data provided by |
|
81 models derived from the QAbstractItemModel class. |
|
82 |
|
83 It is simple to construct a tree view displaying data from a |
|
84 model. In the following example, the contents of a directory are |
|
85 supplied by a QDirModel and displayed as a tree: |
|
86 |
|
87 \snippet doc/src/snippets/shareddirmodel/main.cpp 3 |
|
88 \snippet doc/src/snippets/shareddirmodel/main.cpp 6 |
|
89 |
|
90 The model/view architecture ensures that the contents of the tree view |
|
91 are updated as the model changes. |
|
92 |
|
93 Items that have children can be in an expanded (children are |
|
94 visible) or collapsed (children are hidden) state. When this state |
|
95 changes a collapsed() or expanded() signal is emitted with the |
|
96 model index of the relevant item. |
|
97 |
|
98 The amount of indentation used to indicate levels of hierarchy is |
|
99 controlled by the \l indentation property. |
|
100 |
|
101 Headers in tree views are constructed using the QHeaderView class and can |
|
102 be hidden using \c{header()->hide()}. Note that each header is configured |
|
103 with its \l{QHeaderView::}{stretchLastSection} property set to true, |
|
104 ensuring that the view does not waste any of the space assigned to it for |
|
105 its header. If this value is set to true, this property will override the |
|
106 resize mode set on the last section in the header. |
|
107 |
|
108 |
|
109 \section1 Key Bindings |
|
110 |
|
111 QTreeView supports a set of key bindings that enable the user to |
|
112 navigate in the view and interact with the contents of items: |
|
113 |
|
114 \table |
|
115 \header \o Key \o Action |
|
116 \row \o Up \o Moves the cursor to the item in the same column on |
|
117 the previous row. If the parent of the current item has no more rows to |
|
118 navigate to, the cursor moves to the relevant item in the last row |
|
119 of the sibling that precedes the parent. |
|
120 \row \o Down \o Moves the cursor to the item in the same column on |
|
121 the next row. If the parent of the current item has no more rows to |
|
122 navigate to, the cursor moves to the relevant item in the first row |
|
123 of the sibling that follows the parent. |
|
124 \row \o Left \o Hides the children of the current item (if present) |
|
125 by collapsing a branch. |
|
126 \row \o Minus \o Same as LeftArrow. |
|
127 \row \o Right \o Reveals the children of the current item (if present) |
|
128 by expanding a branch. |
|
129 \row \o Plus \o Same as RightArrow. |
|
130 \row \o Asterisk \o Expands all children of the current item (if present). |
|
131 \row \o PageUp \o Moves the cursor up one page. |
|
132 \row \o PageDown \o Moves the cursor down one page. |
|
133 \row \o Home \o Moves the cursor to an item in the same column of the first |
|
134 row of the first top-level item in the model. |
|
135 \row \o End \o Moves the cursor to an item in the same column of the last |
|
136 row of the last top-level item in the model. |
|
137 \row \o F2 \o In editable models, this opens the current item for editing. |
|
138 The Escape key can be used to cancel the editing process and revert |
|
139 any changes to the data displayed. |
|
140 \endtable |
|
141 |
|
142 \omit |
|
143 Describe the expanding/collapsing concept if not covered elsewhere. |
|
144 \endomit |
|
145 |
|
146 \table 100% |
|
147 \row \o \inlineimage windowsxp-treeview.png Screenshot of a Windows XP style tree view |
|
148 \o \inlineimage macintosh-treeview.png Screenshot of a Macintosh style tree view |
|
149 \o \inlineimage plastique-treeview.png Screenshot of a Plastique style tree view |
|
150 \row \o A \l{Windows XP Style Widget Gallery}{Windows XP style} tree view. |
|
151 \o A \l{Macintosh Style Widget Gallery}{Macintosh style} tree view. |
|
152 \o A \l{Plastique Style Widget Gallery}{Plastique style} tree view. |
|
153 \endtable |
|
154 |
|
155 \section1 Improving Performance |
|
156 |
|
157 It is possible to give the view hints about the data it is handling in order |
|
158 to improve its performance when displaying large numbers of items. One approach |
|
159 that can be taken for views that are intended to display items with equal heights |
|
160 is to set the \l uniformRowHeights property to true. |
|
161 |
|
162 \sa QListView, QTreeWidget, {View Classes}, QAbstractItemModel, QAbstractItemView, |
|
163 {Dir View Example} |
|
164 */ |
|
165 |
|
166 |
|
167 /*! |
|
168 \fn void QTreeView::expanded(const QModelIndex &index) |
|
169 |
|
170 This signal is emitted when the item specified by \a index is expanded. |
|
171 */ |
|
172 |
|
173 |
|
174 /*! |
|
175 \fn void QTreeView::collapsed(const QModelIndex &index) |
|
176 |
|
177 This signal is emitted when the item specified by \a index is collapsed. |
|
178 */ |
|
179 |
|
180 /*! |
|
181 Constructs a table view with a \a parent to represent a model's |
|
182 data. Use setModel() to set the model. |
|
183 |
|
184 \sa QAbstractItemModel |
|
185 */ |
|
186 QTreeView::QTreeView(QWidget *parent) |
|
187 : QAbstractItemView(*new QTreeViewPrivate, parent) |
|
188 { |
|
189 Q_D(QTreeView); |
|
190 d->initialize(); |
|
191 } |
|
192 |
|
193 /*! |
|
194 \internal |
|
195 */ |
|
196 QTreeView::QTreeView(QTreeViewPrivate &dd, QWidget *parent) |
|
197 : QAbstractItemView(dd, parent) |
|
198 { |
|
199 Q_D(QTreeView); |
|
200 d->initialize(); |
|
201 } |
|
202 |
|
203 /*! |
|
204 Destroys the tree view. |
|
205 */ |
|
206 QTreeView::~QTreeView() |
|
207 { |
|
208 } |
|
209 |
|
210 /*! |
|
211 \reimp |
|
212 */ |
|
213 void QTreeView::setModel(QAbstractItemModel *model) |
|
214 { |
|
215 Q_D(QTreeView); |
|
216 if (model == d->model) |
|
217 return; |
|
218 if (d->selectionModel) { // support row editing |
|
219 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), |
|
220 d->model, SLOT(submit())); |
|
221 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
222 this, SLOT(rowsRemoved(QModelIndex,int,int))); |
|
223 disconnect(d->model, SIGNAL(modelAboutToBeReset()), this, SLOT(_q_modelAboutToBeReset())); |
|
224 } |
|
225 d->viewItems.clear(); |
|
226 d->expandedIndexes.clear(); |
|
227 d->hiddenIndexes.clear(); |
|
228 d->header->setModel(model); |
|
229 QAbstractItemView::setModel(model); |
|
230 |
|
231 // QAbstractItemView connects to a private slot |
|
232 disconnect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
233 this, SLOT(_q_rowsRemoved(QModelIndex,int,int))); |
|
234 // do header layout after the tree |
|
235 disconnect(d->model, SIGNAL(layoutChanged()), |
|
236 d->header, SLOT(_q_layoutChanged())); |
|
237 // QTreeView has a public slot for this |
|
238 connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), |
|
239 this, SLOT(rowsRemoved(QModelIndex,int,int))); |
|
240 |
|
241 connect(d->model, SIGNAL(modelAboutToBeReset()), SLOT(_q_modelAboutToBeReset())); |
|
242 |
|
243 if (d->sortingEnabled) |
|
244 d->_q_sortIndicatorChanged(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); |
|
245 } |
|
246 |
|
247 /*! |
|
248 \reimp |
|
249 */ |
|
250 void QTreeView::setRootIndex(const QModelIndex &index) |
|
251 { |
|
252 Q_D(QTreeView); |
|
253 d->header->setRootIndex(index); |
|
254 QAbstractItemView::setRootIndex(index); |
|
255 } |
|
256 |
|
257 /*! |
|
258 \reimp |
|
259 */ |
|
260 void QTreeView::setSelectionModel(QItemSelectionModel *selectionModel) |
|
261 { |
|
262 Q_D(QTreeView); |
|
263 Q_ASSERT(selectionModel); |
|
264 if (d->selectionModel) { |
|
265 // support row editing |
|
266 disconnect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), |
|
267 d->model, SLOT(submit())); |
|
268 } |
|
269 |
|
270 d->header->setSelectionModel(selectionModel); |
|
271 QAbstractItemView::setSelectionModel(selectionModel); |
|
272 |
|
273 if (d->selectionModel) { |
|
274 // support row editing |
|
275 connect(d->selectionModel, SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), |
|
276 d->model, SLOT(submit())); |
|
277 } |
|
278 } |
|
279 |
|
280 /*! |
|
281 Returns the header for the tree view. |
|
282 |
|
283 \sa QAbstractItemModel::headerData() |
|
284 */ |
|
285 QHeaderView *QTreeView::header() const |
|
286 { |
|
287 Q_D(const QTreeView); |
|
288 return d->header; |
|
289 } |
|
290 |
|
291 /*! |
|
292 Sets the header for the tree view, to the given \a header. |
|
293 |
|
294 The view takes ownership over the given \a header and deletes it |
|
295 when a new header is set. |
|
296 |
|
297 \sa QAbstractItemModel::headerData() |
|
298 */ |
|
299 void QTreeView::setHeader(QHeaderView *header) |
|
300 { |
|
301 Q_D(QTreeView); |
|
302 if (header == d->header || !header) |
|
303 return; |
|
304 if (d->header && d->header->parent() == this) |
|
305 delete d->header; |
|
306 d->header = header; |
|
307 d->header->setParent(this); |
|
308 |
|
309 if (!d->header->model()) { |
|
310 d->header->setModel(d->model); |
|
311 if (d->selectionModel) |
|
312 d->header->setSelectionModel(d->selectionModel); |
|
313 } |
|
314 |
|
315 connect(d->header, SIGNAL(sectionResized(int,int,int)), |
|
316 this, SLOT(columnResized(int,int,int))); |
|
317 connect(d->header, SIGNAL(sectionMoved(int,int,int)), |
|
318 this, SLOT(columnMoved())); |
|
319 connect(d->header, SIGNAL(sectionCountChanged(int,int)), |
|
320 this, SLOT(columnCountChanged(int,int))); |
|
321 connect(d->header, SIGNAL(sectionHandleDoubleClicked(int)), |
|
322 this, SLOT(resizeColumnToContents(int))); |
|
323 connect(d->header, SIGNAL(geometriesChanged()), |
|
324 this, SLOT(updateGeometries())); |
|
325 |
|
326 setSortingEnabled(d->sortingEnabled); |
|
327 } |
|
328 |
|
329 /*! |
|
330 \property QTreeView::autoExpandDelay |
|
331 \brief The delay time before items in a tree are opened during a drag and drop operation. |
|
332 \since 4.3 |
|
333 |
|
334 This property holds the amount of time in milliseconds that the user must wait over |
|
335 a node before that node will automatically open or close. If the time is |
|
336 set to less then 0 then it will not be activated. |
|
337 |
|
338 By default, this property has a value of -1, meaning that auto-expansion is disabled. |
|
339 */ |
|
340 int QTreeView::autoExpandDelay() const |
|
341 { |
|
342 Q_D(const QTreeView); |
|
343 return d->autoExpandDelay; |
|
344 } |
|
345 |
|
346 void QTreeView::setAutoExpandDelay(int delay) |
|
347 { |
|
348 Q_D(QTreeView); |
|
349 d->autoExpandDelay = delay; |
|
350 } |
|
351 |
|
352 /*! |
|
353 \property QTreeView::indentation |
|
354 \brief indentation of the items in the tree view. |
|
355 |
|
356 This property holds the indentation measured in pixels of the items for each |
|
357 level in the tree view. For top-level items, the indentation specifies the |
|
358 horizontal distance from the viewport edge to the items in the first column; |
|
359 for child items, it specifies their indentation from their parent items. |
|
360 |
|
361 By default, this property has a value of 20. |
|
362 */ |
|
363 int QTreeView::indentation() const |
|
364 { |
|
365 Q_D(const QTreeView); |
|
366 return d->indent; |
|
367 } |
|
368 |
|
369 void QTreeView::setIndentation(int i) |
|
370 { |
|
371 Q_D(QTreeView); |
|
372 if (i != d->indent) { |
|
373 d->indent = i; |
|
374 d->viewport->update(); |
|
375 } |
|
376 } |
|
377 |
|
378 /*! |
|
379 \property QTreeView::rootIsDecorated |
|
380 \brief whether to show controls for expanding and collapsing top-level items |
|
381 |
|
382 Items with children are typically shown with controls to expand and collapse |
|
383 them, allowing their children to be shown or hidden. If this property is |
|
384 false, these controls are not shown for top-level items. This can be used to |
|
385 make a single level tree structure appear like a simple list of items. |
|
386 |
|
387 By default, this property is true. |
|
388 */ |
|
389 bool QTreeView::rootIsDecorated() const |
|
390 { |
|
391 Q_D(const QTreeView); |
|
392 return d->rootDecoration; |
|
393 } |
|
394 |
|
395 void QTreeView::setRootIsDecorated(bool show) |
|
396 { |
|
397 Q_D(QTreeView); |
|
398 if (show != d->rootDecoration) { |
|
399 d->rootDecoration = show; |
|
400 d->viewport->update(); |
|
401 } |
|
402 } |
|
403 |
|
404 /*! |
|
405 \property QTreeView::uniformRowHeights |
|
406 \brief whether all items in the treeview have the same height |
|
407 |
|
408 This property should only be set to true if it is guaranteed that all items |
|
409 in the view has the same height. This enables the view to do some |
|
410 optimizations. |
|
411 |
|
412 The height is obtained from the first item in the view. It is updated |
|
413 when the data changes on that item. |
|
414 |
|
415 By default, this property is false. |
|
416 */ |
|
417 bool QTreeView::uniformRowHeights() const |
|
418 { |
|
419 Q_D(const QTreeView); |
|
420 return d->uniformRowHeights; |
|
421 } |
|
422 |
|
423 void QTreeView::setUniformRowHeights(bool uniform) |
|
424 { |
|
425 Q_D(QTreeView); |
|
426 d->uniformRowHeights = uniform; |
|
427 } |
|
428 |
|
429 /*! |
|
430 \property QTreeView::itemsExpandable |
|
431 \brief whether the items are expandable by the user. |
|
432 |
|
433 This property holds whether the user can expand and collapse items |
|
434 interactively. |
|
435 |
|
436 By default, this property is true. |
|
437 |
|
438 */ |
|
439 bool QTreeView::itemsExpandable() const |
|
440 { |
|
441 Q_D(const QTreeView); |
|
442 return d->itemsExpandable; |
|
443 } |
|
444 |
|
445 void QTreeView::setItemsExpandable(bool enable) |
|
446 { |
|
447 Q_D(QTreeView); |
|
448 d->itemsExpandable = enable; |
|
449 } |
|
450 |
|
451 /*! |
|
452 \property QTreeView::expandsOnDoubleClick |
|
453 \since 4.4 |
|
454 \brief whether the items can be expanded by double-clicking. |
|
455 |
|
456 This property holds whether the user can expand and collapse items |
|
457 by double-clicking. The default value is true. |
|
458 |
|
459 \sa itemsExpandable |
|
460 */ |
|
461 bool QTreeView::expandsOnDoubleClick() const |
|
462 { |
|
463 Q_D(const QTreeView); |
|
464 return d->expandsOnDoubleClick; |
|
465 } |
|
466 |
|
467 void QTreeView::setExpandsOnDoubleClick(bool enable) |
|
468 { |
|
469 Q_D(QTreeView); |
|
470 d->expandsOnDoubleClick = enable; |
|
471 } |
|
472 |
|
473 /*! |
|
474 Returns the horizontal position of the \a column in the viewport. |
|
475 */ |
|
476 int QTreeView::columnViewportPosition(int column) const |
|
477 { |
|
478 Q_D(const QTreeView); |
|
479 return d->header->sectionViewportPosition(column); |
|
480 } |
|
481 |
|
482 /*! |
|
483 Returns the width of the \a column. |
|
484 |
|
485 \sa resizeColumnToContents(), setColumnWidth() |
|
486 */ |
|
487 int QTreeView::columnWidth(int column) const |
|
488 { |
|
489 Q_D(const QTreeView); |
|
490 return d->header->sectionSize(column); |
|
491 } |
|
492 |
|
493 /*! |
|
494 \since 4.2 |
|
495 |
|
496 Sets the width of the given \a column to the \a width specified. |
|
497 |
|
498 \sa columnWidth(), resizeColumnToContents() |
|
499 */ |
|
500 void QTreeView::setColumnWidth(int column, int width) |
|
501 { |
|
502 Q_D(QTreeView); |
|
503 d->header->resizeSection(column, width); |
|
504 } |
|
505 |
|
506 /*! |
|
507 Returns the column in the tree view whose header covers the \a x |
|
508 coordinate given. |
|
509 */ |
|
510 int QTreeView::columnAt(int x) const |
|
511 { |
|
512 Q_D(const QTreeView); |
|
513 return d->header->logicalIndexAt(x); |
|
514 } |
|
515 |
|
516 /*! |
|
517 Returns true if the \a column is hidden; otherwise returns false. |
|
518 |
|
519 \sa hideColumn(), isRowHidden() |
|
520 */ |
|
521 bool QTreeView::isColumnHidden(int column) const |
|
522 { |
|
523 Q_D(const QTreeView); |
|
524 return d->header->isSectionHidden(column); |
|
525 } |
|
526 |
|
527 /*! |
|
528 If \a hide is true the \a column is hidden, otherwise the \a column is shown. |
|
529 |
|
530 \sa hideColumn(), setRowHidden() |
|
531 */ |
|
532 void QTreeView::setColumnHidden(int column, bool hide) |
|
533 { |
|
534 Q_D(QTreeView); |
|
535 if (column < 0 || column >= d->header->count()) |
|
536 return; |
|
537 d->header->setSectionHidden(column, hide); |
|
538 } |
|
539 |
|
540 /*! |
|
541 \property QTreeView::headerHidden |
|
542 \brief whether the header is shown or not. |
|
543 \since 4.4 |
|
544 |
|
545 If this property is true, the header is not shown otherwise it is. |
|
546 The default value is false. |
|
547 |
|
548 \sa header() |
|
549 */ |
|
550 bool QTreeView::isHeaderHidden() const |
|
551 { |
|
552 Q_D(const QTreeView); |
|
553 return d->header->isHidden(); |
|
554 } |
|
555 |
|
556 void QTreeView::setHeaderHidden(bool hide) |
|
557 { |
|
558 Q_D(QTreeView); |
|
559 d->header->setHidden(hide); |
|
560 } |
|
561 |
|
562 /*! |
|
563 Returns true if the item in the given \a row of the \a parent is hidden; |
|
564 otherwise returns false. |
|
565 |
|
566 \sa setRowHidden(), isColumnHidden() |
|
567 */ |
|
568 bool QTreeView::isRowHidden(int row, const QModelIndex &parent) const |
|
569 { |
|
570 Q_D(const QTreeView); |
|
571 if (!d->model) |
|
572 return false; |
|
573 return d->isRowHidden(d->model->index(row, 0, parent)); |
|
574 } |
|
575 |
|
576 /*! |
|
577 If \a hide is true the \a row with the given \a parent is hidden, otherwise the \a row is shown. |
|
578 |
|
579 \sa isRowHidden(), setColumnHidden() |
|
580 */ |
|
581 void QTreeView::setRowHidden(int row, const QModelIndex &parent, bool hide) |
|
582 { |
|
583 Q_D(QTreeView); |
|
584 if (!d->model) |
|
585 return; |
|
586 QModelIndex index = d->model->index(row, 0, parent); |
|
587 if (!index.isValid()) |
|
588 return; |
|
589 |
|
590 if (hide) { |
|
591 d->hiddenIndexes.insert(index); |
|
592 } else if(d->isPersistent(index)) { //if the index is not persistent, it cannot be in the set |
|
593 d->hiddenIndexes.remove(index); |
|
594 } |
|
595 |
|
596 d->doDelayedItemsLayout(); |
|
597 } |
|
598 |
|
599 /*! |
|
600 \since 4.3 |
|
601 |
|
602 Returns true if the item in first column in the given \a row |
|
603 of the \a parent is spanning all the columns; otherwise returns false. |
|
604 |
|
605 \sa setFirstColumnSpanned() |
|
606 */ |
|
607 bool QTreeView::isFirstColumnSpanned(int row, const QModelIndex &parent) const |
|
608 { |
|
609 Q_D(const QTreeView); |
|
610 if (d->spanningIndexes.isEmpty() || !d->model) |
|
611 return false; |
|
612 QModelIndex index = d->model->index(row, 0, parent); |
|
613 for (int i = 0; i < d->spanningIndexes.count(); ++i) |
|
614 if (d->spanningIndexes.at(i) == index) |
|
615 return true; |
|
616 return false; |
|
617 } |
|
618 |
|
619 /*! |
|
620 \since 4.3 |
|
621 |
|
622 If \a span is true the item in the first column in the \a row |
|
623 with the given \a parent is set to span all columns, otherwise all items |
|
624 on the \a row are shown. |
|
625 |
|
626 \sa isFirstColumnSpanned() |
|
627 */ |
|
628 void QTreeView::setFirstColumnSpanned(int row, const QModelIndex &parent, bool span) |
|
629 { |
|
630 Q_D(QTreeView); |
|
631 if (!d->model) |
|
632 return; |
|
633 QModelIndex index = d->model->index(row, 0, parent); |
|
634 if (!index.isValid()) |
|
635 return; |
|
636 |
|
637 if (span) { |
|
638 QPersistentModelIndex persistent(index); |
|
639 if (!d->spanningIndexes.contains(persistent)) |
|
640 d->spanningIndexes.append(persistent); |
|
641 } else { |
|
642 QPersistentModelIndex persistent(index); |
|
643 int i = d->spanningIndexes.indexOf(persistent); |
|
644 if (i >= 0) |
|
645 d->spanningIndexes.remove(i); |
|
646 } |
|
647 |
|
648 d->executePostedLayout(); |
|
649 int i = d->viewIndex(index); |
|
650 if (i >= 0) |
|
651 d->viewItems[i].spanning = span; |
|
652 |
|
653 d->viewport->update(); |
|
654 } |
|
655 |
|
656 /*! |
|
657 \reimp |
|
658 */ |
|
659 void QTreeView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
|
660 { |
|
661 Q_D(QTreeView); |
|
662 |
|
663 // if we are going to do a complete relayout anyway, there is no need to update |
|
664 if (d->delayedPendingLayout) |
|
665 return; |
|
666 |
|
667 // refresh the height cache here; we don't really lose anything by getting the size hint, |
|
668 // since QAbstractItemView::dataChanged() will get the visualRect for the items anyway |
|
669 |
|
670 int topViewIndex = d->viewIndex(topLeft); |
|
671 if (topViewIndex == 0) |
|
672 d->defaultItemHeight = indexRowSizeHint(topLeft); |
|
673 bool sizeChanged = false; |
|
674 if (topViewIndex != -1) { |
|
675 if (topLeft == bottomRight) { |
|
676 int oldHeight = d->itemHeight(topViewIndex); |
|
677 d->invalidateHeightCache(topViewIndex); |
|
678 sizeChanged = (oldHeight != d->itemHeight(topViewIndex)); |
|
679 } else { |
|
680 int bottomViewIndex = d->viewIndex(bottomRight); |
|
681 for (int i = topViewIndex; i <= bottomViewIndex; ++i) { |
|
682 int oldHeight = d->itemHeight(i); |
|
683 d->invalidateHeightCache(i); |
|
684 sizeChanged |= (oldHeight != d->itemHeight(i)); |
|
685 } |
|
686 } |
|
687 } |
|
688 |
|
689 if (sizeChanged) { |
|
690 d->updateScrollBars(); |
|
691 d->viewport->update(); |
|
692 } |
|
693 QAbstractItemView::dataChanged(topLeft, bottomRight); |
|
694 } |
|
695 |
|
696 /*! |
|
697 Hides the \a column given. |
|
698 |
|
699 \note This function should only be called after the model has been |
|
700 initialized, as the view needs to know the number of columns in order to |
|
701 hide \a column. |
|
702 |
|
703 \sa showColumn(), setColumnHidden() |
|
704 */ |
|
705 void QTreeView::hideColumn(int column) |
|
706 { |
|
707 Q_D(QTreeView); |
|
708 d->header->hideSection(column); |
|
709 } |
|
710 |
|
711 /*! |
|
712 Shows the given \a column in the tree view. |
|
713 |
|
714 \sa hideColumn(), setColumnHidden() |
|
715 */ |
|
716 void QTreeView::showColumn(int column) |
|
717 { |
|
718 Q_D(QTreeView); |
|
719 d->header->showSection(column); |
|
720 } |
|
721 |
|
722 /*! |
|
723 \fn void QTreeView::expand(const QModelIndex &index) |
|
724 |
|
725 Expands the model item specified by the \a index. |
|
726 |
|
727 \sa expanded() |
|
728 */ |
|
729 void QTreeView::expand(const QModelIndex &index) |
|
730 { |
|
731 Q_D(QTreeView); |
|
732 if (!d->isIndexValid(index)) |
|
733 return; |
|
734 if (d->delayedPendingLayout) { |
|
735 //A complete relayout is going to be performed, just store the expanded index, no need to layout. |
|
736 if (d->storeExpanded(index)) |
|
737 emit expanded(index); |
|
738 return; |
|
739 } |
|
740 |
|
741 int i = d->viewIndex(index); |
|
742 if (i != -1) { // is visible |
|
743 d->expand(i, true); |
|
744 if (!d->isAnimating()) { |
|
745 updateGeometries(); |
|
746 d->viewport->update(); |
|
747 } |
|
748 } else if (d->storeExpanded(index)) { |
|
749 emit expanded(index); |
|
750 } |
|
751 } |
|
752 |
|
753 /*! |
|
754 \fn void QTreeView::collapse(const QModelIndex &index) |
|
755 |
|
756 Collapses the model item specified by the \a index. |
|
757 |
|
758 \sa collapsed() |
|
759 */ |
|
760 void QTreeView::collapse(const QModelIndex &index) |
|
761 { |
|
762 Q_D(QTreeView); |
|
763 if (!d->isIndexValid(index)) |
|
764 return; |
|
765 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll |
|
766 d->delayedAutoScroll.stop(); |
|
767 |
|
768 if (d->delayedPendingLayout) { |
|
769 //A complete relayout is going to be performed, just un-store the expanded index, no need to layout. |
|
770 if (d->isPersistent(index) && d->expandedIndexes.remove(index)) |
|
771 emit collapsed(index); |
|
772 return; |
|
773 } |
|
774 int i = d->viewIndex(index); |
|
775 if (i != -1) { // is visible |
|
776 d->collapse(i, true); |
|
777 if (!d->isAnimating()) { |
|
778 updateGeometries(); |
|
779 viewport()->update(); |
|
780 } |
|
781 } else { |
|
782 if (d->isPersistent(index) && d->expandedIndexes.remove(index)) |
|
783 emit collapsed(index); |
|
784 } |
|
785 } |
|
786 |
|
787 /*! |
|
788 \fn bool QTreeView::isExpanded(const QModelIndex &index) const |
|
789 |
|
790 Returns true if the model item \a index is expanded; otherwise returns |
|
791 false. |
|
792 |
|
793 \sa expand(), expanded(), setExpanded() |
|
794 */ |
|
795 bool QTreeView::isExpanded(const QModelIndex &index) const |
|
796 { |
|
797 Q_D(const QTreeView); |
|
798 return d->isIndexExpanded(index); |
|
799 } |
|
800 |
|
801 /*! |
|
802 Sets the item referred to by \a index to either collapse or expanded, |
|
803 depending on the value of \a expanded. |
|
804 |
|
805 \sa expanded(), expand(), isExpanded() |
|
806 */ |
|
807 void QTreeView::setExpanded(const QModelIndex &index, bool expanded) |
|
808 { |
|
809 if (expanded) |
|
810 this->expand(index); |
|
811 else |
|
812 this->collapse(index); |
|
813 } |
|
814 |
|
815 /*! |
|
816 \since 4.2 |
|
817 \property QTreeView::sortingEnabled |
|
818 \brief whether sorting is enabled |
|
819 |
|
820 If this property is true, sorting is enabled for the tree; if the property |
|
821 is false, sorting is not enabled. The default value is false. |
|
822 |
|
823 \note In order to avoid performance issues, it is recommended that |
|
824 sorting is enabled \e after inserting the items into the tree. |
|
825 Alternatively, you could also insert the items into a list before inserting |
|
826 the items into the tree. |
|
827 |
|
828 \sa sortByColumn() |
|
829 */ |
|
830 |
|
831 void QTreeView::setSortingEnabled(bool enable) |
|
832 { |
|
833 Q_D(QTreeView); |
|
834 header()->setSortIndicatorShown(enable); |
|
835 header()->setClickable(enable); |
|
836 if (enable) { |
|
837 //sortByColumn has to be called before we connect or set the sortingEnabled flag |
|
838 // because otherwise it will not call sort on the model. |
|
839 sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); |
|
840 connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), |
|
841 this, SLOT(_q_sortIndicatorChanged(int, Qt::SortOrder)), Qt::UniqueConnection); |
|
842 } else { |
|
843 disconnect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), |
|
844 this, SLOT(_q_sortIndicatorChanged(int, Qt::SortOrder))); |
|
845 } |
|
846 d->sortingEnabled = enable; |
|
847 } |
|
848 |
|
849 bool QTreeView::isSortingEnabled() const |
|
850 { |
|
851 Q_D(const QTreeView); |
|
852 return d->sortingEnabled; |
|
853 } |
|
854 |
|
855 /*! |
|
856 \since 4.2 |
|
857 \property QTreeView::animated |
|
858 \brief whether animations are enabled |
|
859 |
|
860 If this property is true the treeview will animate expandsion |
|
861 and collasping of branches. If this property is false, the treeview |
|
862 will expand or collapse branches immediately without showing |
|
863 the animation. |
|
864 |
|
865 By default, this property is false. |
|
866 */ |
|
867 |
|
868 void QTreeView::setAnimated(bool animate) |
|
869 { |
|
870 Q_D(QTreeView); |
|
871 d->animationsEnabled = animate; |
|
872 } |
|
873 |
|
874 bool QTreeView::isAnimated() const |
|
875 { |
|
876 Q_D(const QTreeView); |
|
877 return d->animationsEnabled; |
|
878 } |
|
879 |
|
880 /*! |
|
881 \since 4.2 |
|
882 \property QTreeView::allColumnsShowFocus |
|
883 \brief whether items should show keyboard focus using all columns |
|
884 |
|
885 If this property is true all columns will show focus, otherwise only |
|
886 one column will show focus. |
|
887 |
|
888 The default is false. |
|
889 */ |
|
890 |
|
891 void QTreeView::setAllColumnsShowFocus(bool enable) |
|
892 { |
|
893 Q_D(QTreeView); |
|
894 if (d->allColumnsShowFocus == enable) |
|
895 return; |
|
896 d->allColumnsShowFocus = enable; |
|
897 d->viewport->update(); |
|
898 } |
|
899 |
|
900 bool QTreeView::allColumnsShowFocus() const |
|
901 { |
|
902 Q_D(const QTreeView); |
|
903 return d->allColumnsShowFocus; |
|
904 } |
|
905 |
|
906 /*! |
|
907 \property QTreeView::wordWrap |
|
908 \brief the item text word-wrapping policy |
|
909 \since 4.3 |
|
910 |
|
911 If this property is true then the item text is wrapped where |
|
912 necessary at word-breaks; otherwise it is not wrapped at all. |
|
913 This property is false by default. |
|
914 |
|
915 Note that even if wrapping is enabled, the cell will not be |
|
916 expanded to fit all text. Ellipsis will be inserted according to |
|
917 the current \l{QAbstractItemView::}{textElideMode}. |
|
918 */ |
|
919 void QTreeView::setWordWrap(bool on) |
|
920 { |
|
921 Q_D(QTreeView); |
|
922 if (d->wrapItemText == on) |
|
923 return; |
|
924 d->wrapItemText = on; |
|
925 d->doDelayedItemsLayout(); |
|
926 } |
|
927 |
|
928 bool QTreeView::wordWrap() const |
|
929 { |
|
930 Q_D(const QTreeView); |
|
931 return d->wrapItemText; |
|
932 } |
|
933 |
|
934 |
|
935 /*! |
|
936 \reimp |
|
937 */ |
|
938 void QTreeView::keyboardSearch(const QString &search) |
|
939 { |
|
940 Q_D(QTreeView); |
|
941 if (!d->model->rowCount(d->root) || !d->model->columnCount(d->root)) |
|
942 return; |
|
943 |
|
944 QModelIndex start; |
|
945 if (currentIndex().isValid()) |
|
946 start = currentIndex(); |
|
947 else |
|
948 start = d->model->index(0, 0, d->root); |
|
949 |
|
950 QTime now(QTime::currentTime()); |
|
951 bool skipRow = false; |
|
952 if (search.isEmpty() |
|
953 || (d->keyboardInputTime.msecsTo(now) > QApplication::keyboardInputInterval())) { |
|
954 d->keyboardInput = search; |
|
955 skipRow = true; |
|
956 } else { |
|
957 d->keyboardInput += search; |
|
958 } |
|
959 d->keyboardInputTime = now; |
|
960 |
|
961 // special case for searches with same key like 'aaaaa' |
|
962 bool sameKey = false; |
|
963 if (d->keyboardInput.length() > 1) { |
|
964 int c = d->keyboardInput.count(d->keyboardInput.at(d->keyboardInput.length() - 1)); |
|
965 sameKey = (c == d->keyboardInput.length()); |
|
966 if (sameKey) |
|
967 skipRow = true; |
|
968 } |
|
969 |
|
970 // skip if we are searching for the same key or a new search started |
|
971 if (skipRow) { |
|
972 if (indexBelow(start).isValid()) |
|
973 start = indexBelow(start); |
|
974 else |
|
975 start = d->model->index(0, start.column(), d->root); |
|
976 } |
|
977 |
|
978 d->executePostedLayout(); |
|
979 int startIndex = d->viewIndex(start); |
|
980 if (startIndex <= -1) |
|
981 return; |
|
982 |
|
983 int previousLevel = -1; |
|
984 int bestAbove = -1; |
|
985 int bestBelow = -1; |
|
986 QString searchString = sameKey ? QString(d->keyboardInput.at(0)) : d->keyboardInput; |
|
987 for (int i = 0; i < d->viewItems.count(); ++i) { |
|
988 if ((int)d->viewItems.at(i).level > previousLevel) { |
|
989 QModelIndex searchFrom = d->viewItems.at(i).index; |
|
990 if (searchFrom.parent() == start.parent()) |
|
991 searchFrom = start; |
|
992 QModelIndexList match = d->model->match(searchFrom, Qt::DisplayRole, searchString); |
|
993 if (match.count()) { |
|
994 int hitIndex = d->viewIndex(match.at(0)); |
|
995 if (hitIndex >= 0 && hitIndex < startIndex) |
|
996 bestAbove = bestAbove == -1 ? hitIndex : qMin(hitIndex, bestAbove); |
|
997 else if (hitIndex >= startIndex) |
|
998 bestBelow = bestBelow == -1 ? hitIndex : qMin(hitIndex, bestBelow); |
|
999 } |
|
1000 } |
|
1001 previousLevel = d->viewItems.at(i).level; |
|
1002 } |
|
1003 |
|
1004 QModelIndex index; |
|
1005 if (bestBelow > -1) |
|
1006 index = d->viewItems.at(bestBelow).index; |
|
1007 else if (bestAbove > -1) |
|
1008 index = d->viewItems.at(bestAbove).index; |
|
1009 |
|
1010 if (index.isValid()) { |
|
1011 QItemSelectionModel::SelectionFlags flags = (d->selectionMode == SingleSelection |
|
1012 ? QItemSelectionModel::SelectionFlags( |
|
1013 QItemSelectionModel::ClearAndSelect |
|
1014 |d->selectionBehaviorFlags()) |
|
1015 : QItemSelectionModel::SelectionFlags( |
|
1016 QItemSelectionModel::NoUpdate)); |
|
1017 selectionModel()->setCurrentIndex(index, flags); |
|
1018 } |
|
1019 } |
|
1020 |
|
1021 /*! |
|
1022 Returns the rectangle on the viewport occupied by the item at \a index. |
|
1023 If the index is not visible or explicitly hidden, the returned rectangle is invalid. |
|
1024 */ |
|
1025 QRect QTreeView::visualRect(const QModelIndex &index) const |
|
1026 { |
|
1027 Q_D(const QTreeView); |
|
1028 |
|
1029 if (!d->isIndexValid(index) || isIndexHidden(index)) |
|
1030 return QRect(); |
|
1031 |
|
1032 d->executePostedLayout(); |
|
1033 |
|
1034 int vi = d->viewIndex(index); |
|
1035 if (vi < 0) |
|
1036 return QRect(); |
|
1037 |
|
1038 bool spanning = d->viewItems.at(vi).spanning; |
|
1039 |
|
1040 // if we have a spanning item, make the selection stretch from left to right |
|
1041 int x = (spanning ? 0 : columnViewportPosition(index.column())); |
|
1042 int w = (spanning ? d->header->length() : columnWidth(index.column())); |
|
1043 // handle indentation |
|
1044 if (index.column() == 0) { |
|
1045 int i = d->indentationForItem(vi); |
|
1046 w -= i; |
|
1047 if (!isRightToLeft()) |
|
1048 x += i; |
|
1049 } |
|
1050 |
|
1051 int y = d->coordinateForItem(vi); |
|
1052 int h = d->itemHeight(vi); |
|
1053 |
|
1054 return QRect(x, y, w, h); |
|
1055 } |
|
1056 |
|
1057 /*! |
|
1058 Scroll the contents of the tree view until the given model item |
|
1059 \a index is visible. The \a hint parameter specifies more |
|
1060 precisely where the item should be located after the |
|
1061 operation. |
|
1062 If any of the parents of the model item are collapsed, they will |
|
1063 be expanded to ensure that the model item is visible. |
|
1064 */ |
|
1065 void QTreeView::scrollTo(const QModelIndex &index, ScrollHint hint) |
|
1066 { |
|
1067 Q_D(QTreeView); |
|
1068 |
|
1069 if (!d->isIndexValid(index)) |
|
1070 return; |
|
1071 |
|
1072 d->executePostedLayout(); |
|
1073 d->updateScrollBars(); |
|
1074 |
|
1075 // Expand all parents if the parent(s) of the node are not expanded. |
|
1076 QModelIndex parent = index.parent(); |
|
1077 while (parent.isValid() && state() == NoState && d->itemsExpandable) { |
|
1078 if (!isExpanded(parent)) |
|
1079 expand(parent); |
|
1080 parent = d->model->parent(parent); |
|
1081 } |
|
1082 |
|
1083 int item = d->viewIndex(index); |
|
1084 if (item < 0) |
|
1085 return; |
|
1086 |
|
1087 QRect area = d->viewport->rect(); |
|
1088 |
|
1089 // vertical |
|
1090 if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) { |
|
1091 int top = verticalScrollBar()->value(); |
|
1092 int bottom = top + verticalScrollBar()->pageStep(); |
|
1093 if (hint == EnsureVisible && item >= top && item < bottom) { |
|
1094 // nothing to do |
|
1095 } else if (hint == PositionAtTop || (hint == EnsureVisible && item < top)) { |
|
1096 verticalScrollBar()->setValue(item); |
|
1097 } else { // PositionAtBottom or PositionAtCenter |
|
1098 const int currentItemHeight = d->itemHeight(item); |
|
1099 int y = (hint == PositionAtCenter |
|
1100 //we center on the current item with a preference to the top item (ie. -1) |
|
1101 ? area.height() / 2 + currentItemHeight - 1 |
|
1102 //otherwise we simply take the whole space |
|
1103 : area.height()); |
|
1104 if (y > currentItemHeight) { |
|
1105 while (item >= 0) { |
|
1106 y -= d->itemHeight(item); |
|
1107 if (y < 0) { //there is no more space left |
|
1108 item++; |
|
1109 break; |
|
1110 } |
|
1111 item--; |
|
1112 } |
|
1113 } |
|
1114 verticalScrollBar()->setValue(item); |
|
1115 } |
|
1116 } else { // ScrollPerPixel |
|
1117 QRect rect(columnViewportPosition(index.column()), |
|
1118 d->coordinateForItem(item), // ### slow for items outside the view |
|
1119 columnWidth(index.column()), |
|
1120 d->itemHeight(item)); |
|
1121 |
|
1122 if (rect.isEmpty()) { |
|
1123 // nothing to do |
|
1124 } else if (hint == EnsureVisible && area.contains(rect)) { |
|
1125 d->viewport->update(rect); |
|
1126 // nothing to do |
|
1127 } else { |
|
1128 bool above = (hint == EnsureVisible |
|
1129 && (rect.top() < area.top() |
|
1130 || area.height() < rect.height())); |
|
1131 bool below = (hint == EnsureVisible |
|
1132 && rect.bottom() > area.bottom() |
|
1133 && rect.height() < area.height()); |
|
1134 |
|
1135 int verticalValue = verticalScrollBar()->value(); |
|
1136 if (hint == PositionAtTop || above) |
|
1137 verticalValue += rect.top(); |
|
1138 else if (hint == PositionAtBottom || below) |
|
1139 verticalValue += rect.bottom() - area.height(); |
|
1140 else if (hint == PositionAtCenter) |
|
1141 verticalValue += rect.top() - ((area.height() - rect.height()) / 2); |
|
1142 verticalScrollBar()->setValue(verticalValue); |
|
1143 } |
|
1144 } |
|
1145 // horizontal |
|
1146 int viewportWidth = d->viewport->width(); |
|
1147 int horizontalOffset = d->header->offset(); |
|
1148 int horizontalPosition = d->header->sectionPosition(index.column()); |
|
1149 int cellWidth = d->header->sectionSize(index.column()); |
|
1150 |
|
1151 if (hint == PositionAtCenter) { |
|
1152 horizontalScrollBar()->setValue(horizontalPosition - ((viewportWidth - cellWidth) / 2)); |
|
1153 } else { |
|
1154 if (horizontalPosition - horizontalOffset < 0 || cellWidth > viewportWidth) |
|
1155 horizontalScrollBar()->setValue(horizontalPosition); |
|
1156 else if (horizontalPosition - horizontalOffset + cellWidth > viewportWidth) |
|
1157 horizontalScrollBar()->setValue(horizontalPosition - viewportWidth + cellWidth); |
|
1158 } |
|
1159 } |
|
1160 |
|
1161 /*! |
|
1162 \reimp |
|
1163 */ |
|
1164 void QTreeView::timerEvent(QTimerEvent *event) |
|
1165 { |
|
1166 Q_D(QTreeView); |
|
1167 if (event->timerId() == d->columnResizeTimerID) { |
|
1168 updateGeometries(); |
|
1169 killTimer(d->columnResizeTimerID); |
|
1170 d->columnResizeTimerID = 0; |
|
1171 QRect rect; |
|
1172 int viewportHeight = d->viewport->height(); |
|
1173 int viewportWidth = d->viewport->width(); |
|
1174 for (int i = d->columnsToUpdate.size() - 1; i >= 0; --i) { |
|
1175 int column = d->columnsToUpdate.at(i); |
|
1176 int x = columnViewportPosition(column); |
|
1177 if (isRightToLeft()) |
|
1178 rect |= QRect(0, 0, x + columnWidth(column), viewportHeight); |
|
1179 else |
|
1180 rect |= QRect(x, 0, viewportWidth - x, viewportHeight); |
|
1181 } |
|
1182 d->viewport->update(rect.normalized()); |
|
1183 d->columnsToUpdate.clear(); |
|
1184 } else if (event->timerId() == d->openTimer.timerId()) { |
|
1185 QPoint pos = d->viewport->mapFromGlobal(QCursor::pos()); |
|
1186 if (state() == QAbstractItemView::DraggingState |
|
1187 && d->viewport->rect().contains(pos)) { |
|
1188 QModelIndex index = indexAt(pos); |
|
1189 setExpanded(index, !isExpanded(index)); |
|
1190 } |
|
1191 d->openTimer.stop(); |
|
1192 } |
|
1193 |
|
1194 QAbstractItemView::timerEvent(event); |
|
1195 } |
|
1196 |
|
1197 /*! |
|
1198 \reimp |
|
1199 */ |
|
1200 #ifndef QT_NO_DRAGANDDROP |
|
1201 void QTreeView::dragMoveEvent(QDragMoveEvent *event) |
|
1202 { |
|
1203 Q_D(QTreeView); |
|
1204 if (d->autoExpandDelay >= 0) |
|
1205 d->openTimer.start(d->autoExpandDelay, this); |
|
1206 QAbstractItemView::dragMoveEvent(event); |
|
1207 } |
|
1208 #endif |
|
1209 |
|
1210 /*! |
|
1211 \reimp |
|
1212 */ |
|
1213 bool QTreeView::viewportEvent(QEvent *event) |
|
1214 { |
|
1215 Q_D(QTreeView); |
|
1216 switch (event->type()) { |
|
1217 case QEvent::HoverEnter: |
|
1218 case QEvent::HoverLeave: |
|
1219 case QEvent::HoverMove: { |
|
1220 QHoverEvent *he = static_cast<QHoverEvent*>(event); |
|
1221 int oldBranch = d->hoverBranch; |
|
1222 d->hoverBranch = d->itemDecorationAt(he->pos()); |
|
1223 if (oldBranch != d->hoverBranch) { |
|
1224 QModelIndex oldIndex = d->modelIndex(oldBranch), |
|
1225 newIndex = d->modelIndex(d->hoverBranch); |
|
1226 if (oldIndex != newIndex) { |
|
1227 QRect oldRect = visualRect(oldIndex); |
|
1228 QRect newRect = visualRect(newIndex); |
|
1229 viewport()->update(oldRect.left() - d->indent, oldRect.top(), d->indent, oldRect.height()); |
|
1230 viewport()->update(newRect.left() - d->indent, newRect.top(), d->indent, newRect.height()); |
|
1231 } |
|
1232 } |
|
1233 if (selectionBehavior() == QAbstractItemView::SelectRows) { |
|
1234 QModelIndex newHoverIndex = indexAt(he->pos()); |
|
1235 if (d->hover != newHoverIndex) { |
|
1236 QRect oldHoverRect = visualRect(d->hover); |
|
1237 QRect newHoverRect = visualRect(newHoverIndex); |
|
1238 viewport()->update(QRect(0, newHoverRect.y(), viewport()->width(), newHoverRect.height())); |
|
1239 viewport()->update(QRect(0, oldHoverRect.y(), viewport()->width(), oldHoverRect.height())); |
|
1240 } |
|
1241 } |
|
1242 break; } |
|
1243 default: |
|
1244 break; |
|
1245 } |
|
1246 return QAbstractItemView::viewportEvent(event); |
|
1247 } |
|
1248 |
|
1249 /*! |
|
1250 \reimp |
|
1251 */ |
|
1252 void QTreeView::paintEvent(QPaintEvent *event) |
|
1253 { |
|
1254 Q_D(QTreeView); |
|
1255 d->executePostedLayout(); |
|
1256 QPainter painter(viewport()); |
|
1257 #ifndef QT_NO_ANIMATION |
|
1258 if (d->isAnimating()) { |
|
1259 drawTree(&painter, event->region() - d->animatedOperation.rect()); |
|
1260 d->drawAnimatedOperation(&painter); |
|
1261 } else |
|
1262 #endif //QT_NO_ANIMATION |
|
1263 { |
|
1264 drawTree(&painter, event->region()); |
|
1265 #ifndef QT_NO_DRAGANDDROP |
|
1266 d->paintDropIndicator(&painter); |
|
1267 #endif |
|
1268 } |
|
1269 } |
|
1270 |
|
1271 void QTreeViewPrivate::paintAlternatingRowColors(QPainter *painter, QStyleOptionViewItemV4 *option, int y, int bottom) const |
|
1272 { |
|
1273 Q_Q(const QTreeView); |
|
1274 if (!alternatingColors || !q->style()->styleHint(QStyle::SH_ItemView_PaintAlternatingRowColorsForEmptyArea, option, q)) |
|
1275 return; |
|
1276 int rowHeight = defaultItemHeight; |
|
1277 if (rowHeight <= 0) { |
|
1278 rowHeight = itemDelegate->sizeHint(*option, QModelIndex()).height(); |
|
1279 if (rowHeight <= 0) |
|
1280 return; |
|
1281 } |
|
1282 while (y <= bottom) { |
|
1283 option->rect.setRect(0, y, viewport->width(), rowHeight); |
|
1284 if (current & 1) { |
|
1285 option->features |= QStyleOptionViewItemV2::Alternate; |
|
1286 } else { |
|
1287 option->features &= ~QStyleOptionViewItemV2::Alternate; |
|
1288 } |
|
1289 ++current; |
|
1290 q->style()->drawPrimitive(QStyle::PE_PanelItemViewRow, option, painter, q); |
|
1291 y += rowHeight; |
|
1292 } |
|
1293 } |
|
1294 |
|
1295 bool QTreeViewPrivate::expandOrCollapseItemAtPos(const QPoint &pos) |
|
1296 { |
|
1297 Q_Q(QTreeView); |
|
1298 // we want to handle mousePress in EditingState (persistent editors) |
|
1299 if ((state != QAbstractItemView::NoState |
|
1300 && state != QAbstractItemView::EditingState) |
|
1301 || !viewport->rect().contains(pos)) |
|
1302 return true; |
|
1303 |
|
1304 int i = itemDecorationAt(pos); |
|
1305 if ((i != -1) && itemsExpandable && hasVisibleChildren(viewItems.at(i).index)) { |
|
1306 if (viewItems.at(i).expanded) |
|
1307 collapse(i, true); |
|
1308 else |
|
1309 expand(i, true); |
|
1310 if (!isAnimating()) { |
|
1311 q->updateGeometries(); |
|
1312 viewport->update(); |
|
1313 } |
|
1314 return true; |
|
1315 } |
|
1316 return false; |
|
1317 } |
|
1318 |
|
1319 void QTreeViewPrivate::_q_modelDestroyed() |
|
1320 { |
|
1321 //we need to clear that list because it contais QModelIndex to |
|
1322 //the model currently being destroyed |
|
1323 viewItems.clear(); |
|
1324 QAbstractItemViewPrivate::_q_modelDestroyed(); |
|
1325 } |
|
1326 |
|
1327 /*! |
|
1328 \reimp |
|
1329 |
|
1330 We have a QTreeView way of knowing what elements are on the viewport |
|
1331 */ |
|
1332 QItemViewPaintPairs QTreeViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const |
|
1333 { |
|
1334 Q_ASSERT(r); |
|
1335 return QAbstractItemViewPrivate::draggablePaintPairs(indexes, r); |
|
1336 Q_Q(const QTreeView); |
|
1337 QRect &rect = *r; |
|
1338 const QRect viewportRect = viewport->rect(); |
|
1339 int itemOffset = 0; |
|
1340 int row = firstVisibleItem(&itemOffset); |
|
1341 QPair<int, int> startEnd = startAndEndColumns(viewportRect); |
|
1342 QVector<int> columns; |
|
1343 for (int i = startEnd.first; i <= startEnd.second; ++i) { |
|
1344 int logical = header->logicalIndex(i); |
|
1345 if (!header->isSectionHidden(logical)) |
|
1346 columns += logical; |
|
1347 } |
|
1348 QSet<QModelIndex> visibleIndexes; |
|
1349 for (; itemOffset < viewportRect.bottom() && row < viewItems.count(); ++row) { |
|
1350 const QModelIndex &index = viewItems.at(row).index; |
|
1351 for (int colIndex = 0; colIndex < columns.count(); ++colIndex) |
|
1352 visibleIndexes += index.sibling(index.row(), columns.at(colIndex)); |
|
1353 itemOffset += itemHeight(row); |
|
1354 } |
|
1355 |
|
1356 //now that we have the visible indexes, we can try to find those which are selected |
|
1357 QItemViewPaintPairs ret; |
|
1358 for (int i = 0; i < indexes.count(); ++i) { |
|
1359 const QModelIndex &index = indexes.at(i); |
|
1360 if (visibleIndexes.contains(index)) { |
|
1361 const QRect current = q->visualRect(index); |
|
1362 ret += qMakePair(current, index); |
|
1363 rect |= current; |
|
1364 } |
|
1365 } |
|
1366 rect &= viewportRect; |
|
1367 return ret; |
|
1368 } |
|
1369 |
|
1370 |
|
1371 /*! |
|
1372 \since 4.2 |
|
1373 Draws the part of the tree intersecting the given \a region using the specified |
|
1374 \a painter. |
|
1375 |
|
1376 \sa paintEvent() |
|
1377 */ |
|
1378 void QTreeView::drawTree(QPainter *painter, const QRegion ®ion) const |
|
1379 { |
|
1380 Q_D(const QTreeView); |
|
1381 const QVector<QTreeViewItem> viewItems = d->viewItems; |
|
1382 |
|
1383 QStyleOptionViewItemV4 option = d->viewOptionsV4(); |
|
1384 const QStyle::State state = option.state; |
|
1385 d->current = 0; |
|
1386 |
|
1387 if (viewItems.count() == 0 || d->header->count() == 0 || !d->itemDelegate) { |
|
1388 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); |
|
1389 return; |
|
1390 } |
|
1391 |
|
1392 int firstVisibleItemOffset = 0; |
|
1393 const int firstVisibleItem = d->firstVisibleItem(&firstVisibleItemOffset); |
|
1394 if (firstVisibleItem < 0) { |
|
1395 d->paintAlternatingRowColors(painter, &option, 0, region.boundingRect().bottom()+1); |
|
1396 return; |
|
1397 } |
|
1398 |
|
1399 const int viewportWidth = d->viewport->width(); |
|
1400 |
|
1401 QVector<QRect> rects = region.rects(); |
|
1402 QVector<int> drawn; |
|
1403 bool multipleRects = (rects.size() > 1); |
|
1404 for (int a = 0; a < rects.size(); ++a) { |
|
1405 const QRect area = (multipleRects |
|
1406 ? QRect(0, rects.at(a).y(), viewportWidth, rects.at(a).height()) |
|
1407 : rects.at(a)); |
|
1408 d->leftAndRight = d->startAndEndColumns(area); |
|
1409 |
|
1410 int i = firstVisibleItem; // the first item at the top of the viewport |
|
1411 int y = firstVisibleItemOffset; // we may only see part of the first item |
|
1412 |
|
1413 // start at the top of the viewport and iterate down to the update area |
|
1414 for (; i < viewItems.count(); ++i) { |
|
1415 const int itemHeight = d->itemHeight(i); |
|
1416 if (y + itemHeight > area.top()) |
|
1417 break; |
|
1418 y += itemHeight; |
|
1419 } |
|
1420 |
|
1421 // paint the visible rows |
|
1422 for (; i < viewItems.count() && y <= area.bottom(); ++i) { |
|
1423 const int itemHeight = d->itemHeight(i); |
|
1424 option.rect.setRect(0, y, viewportWidth, itemHeight); |
|
1425 option.state = state | (viewItems.at(i).expanded |
|
1426 ? QStyle::State_Open : QStyle::State_None); |
|
1427 d->current = i; |
|
1428 d->spanning = viewItems.at(i).spanning; |
|
1429 if (!multipleRects || !drawn.contains(i)) { |
|
1430 drawRow(painter, option, viewItems.at(i).index); |
|
1431 if (multipleRects) // even if the rect only intersects the item, |
|
1432 drawn.append(i); // the entire item will be painted |
|
1433 } |
|
1434 y += itemHeight; |
|
1435 } |
|
1436 |
|
1437 if (y <= area.bottom()) { |
|
1438 d->current = i; |
|
1439 d->paintAlternatingRowColors(painter, &option, y, area.bottom()); |
|
1440 } |
|
1441 } |
|
1442 } |
|
1443 |
|
1444 /// ### move to QObject :) |
|
1445 static inline bool ancestorOf(QObject *widget, QObject *other) |
|
1446 { |
|
1447 for (QObject *parent = other; parent != 0; parent = parent->parent()) { |
|
1448 if (parent == widget) |
|
1449 return true; |
|
1450 } |
|
1451 return false; |
|
1452 } |
|
1453 |
|
1454 /*! |
|
1455 Draws the row in the tree view that contains the model item \a index, |
|
1456 using the \a painter given. The \a option control how the item is |
|
1457 displayed. |
|
1458 |
|
1459 \sa setAlternatingRowColors() |
|
1460 */ |
|
1461 void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, |
|
1462 const QModelIndex &index) const |
|
1463 { |
|
1464 Q_D(const QTreeView); |
|
1465 QStyleOptionViewItemV4 opt = option; |
|
1466 const QPoint offset = d->scrollDelayOffset; |
|
1467 const int y = option.rect.y() + offset.y(); |
|
1468 const QModelIndex parent = index.parent(); |
|
1469 const QHeaderView *header = d->header; |
|
1470 const QModelIndex current = currentIndex(); |
|
1471 const QModelIndex hover = d->hover; |
|
1472 const bool reverse = isRightToLeft(); |
|
1473 const QStyle::State state = opt.state; |
|
1474 const bool spanning = d->spanning; |
|
1475 const int left = (spanning ? header->visualIndex(0) : d->leftAndRight.first); |
|
1476 const int right = (spanning ? header->visualIndex(0) : d->leftAndRight.second); |
|
1477 const bool alternate = d->alternatingColors; |
|
1478 const bool enabled = (state & QStyle::State_Enabled) != 0; |
|
1479 const bool allColumnsShowFocus = d->allColumnsShowFocus; |
|
1480 |
|
1481 |
|
1482 // when the row contains an index widget which has focus, |
|
1483 // we want to paint the entire row as active |
|
1484 bool indexWidgetHasFocus = false; |
|
1485 if ((current.row() == index.row()) && !d->editors.isEmpty()) { |
|
1486 const int r = index.row(); |
|
1487 QWidget *fw = QApplication::focusWidget(); |
|
1488 for (int c = 0; c < header->count(); ++c) { |
|
1489 QModelIndex idx = d->model->index(r, c, parent); |
|
1490 if (QWidget *editor = indexWidget(idx)) { |
|
1491 if (ancestorOf(editor, fw)) { |
|
1492 indexWidgetHasFocus = true; |
|
1493 break; |
|
1494 } |
|
1495 } |
|
1496 } |
|
1497 } |
|
1498 |
|
1499 const bool widgetHasFocus = hasFocus(); |
|
1500 bool currentRowHasFocus = false; |
|
1501 if (allColumnsShowFocus && widgetHasFocus && current.isValid()) { |
|
1502 // check if the focus index is before or after the visible columns |
|
1503 const int r = index.row(); |
|
1504 for (int c = 0; c < left && !currentRowHasFocus; ++c) { |
|
1505 QModelIndex idx = d->model->index(r, c, parent); |
|
1506 currentRowHasFocus = (idx == current); |
|
1507 } |
|
1508 QModelIndex parent = d->model->parent(index); |
|
1509 for (int c = right; c < header->count() && !currentRowHasFocus; ++c) { |
|
1510 currentRowHasFocus = (d->model->index(r, c, parent) == current); |
|
1511 } |
|
1512 } |
|
1513 |
|
1514 // ### special case: treeviews with multiple columns draw |
|
1515 // the selections differently than with only one column |
|
1516 opt.showDecorationSelected = (d->selectionBehavior & SelectRows) |
|
1517 || option.showDecorationSelected; |
|
1518 |
|
1519 int width, height = option.rect.height(); |
|
1520 int position; |
|
1521 QModelIndex modelIndex; |
|
1522 int columnCount = header->count(); |
|
1523 const bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows |
|
1524 && index.parent() == hover.parent() |
|
1525 && index.row() == hover.row(); |
|
1526 |
|
1527 /* 'left' and 'right' are the left-most and right-most visible visual indices. |
|
1528 Compute the first visible logical indices before and after the left and right. |
|
1529 We will use these values to determine the QStyleOptionViewItemV4::viewItemPosition. */ |
|
1530 int logicalIndexBeforeLeft = -1, logicalIndexAfterRight = -1; |
|
1531 for (int visualIndex = left - 1; visualIndex >= 0; --visualIndex) { |
|
1532 int logicalIndex = header->logicalIndex(visualIndex); |
|
1533 if (!header->isSectionHidden(logicalIndex)) { |
|
1534 logicalIndexBeforeLeft = logicalIndex; |
|
1535 break; |
|
1536 } |
|
1537 } |
|
1538 QVector<int> logicalIndices; // vector of currently visibly logical indices |
|
1539 for (int visualIndex = left; visualIndex < columnCount; ++visualIndex) { |
|
1540 int logicalIndex = header->logicalIndex(visualIndex); |
|
1541 if (!header->isSectionHidden(logicalIndex)) { |
|
1542 if (visualIndex > right) { |
|
1543 logicalIndexAfterRight = logicalIndex; |
|
1544 break; |
|
1545 } |
|
1546 logicalIndices.append(logicalIndex); |
|
1547 } |
|
1548 } |
|
1549 |
|
1550 for (int currentLogicalSection = 0; currentLogicalSection < logicalIndices.count(); ++currentLogicalSection) { |
|
1551 int headerSection = logicalIndices.at(currentLogicalSection); |
|
1552 position = columnViewportPosition(headerSection) + offset.x(); |
|
1553 width = header->sectionSize(headerSection); |
|
1554 |
|
1555 if (spanning) { |
|
1556 int lastSection = header->logicalIndex(header->count() - 1); |
|
1557 if (!reverse) { |
|
1558 width = columnViewportPosition(lastSection) + header->sectionSize(lastSection) - position; |
|
1559 } else { |
|
1560 width += position - columnViewportPosition(lastSection); |
|
1561 position = columnViewportPosition(lastSection); |
|
1562 } |
|
1563 } |
|
1564 |
|
1565 modelIndex = d->model->index(index.row(), headerSection, parent); |
|
1566 if (!modelIndex.isValid()) |
|
1567 continue; |
|
1568 opt.state = state; |
|
1569 |
|
1570 // determine the viewItemPosition depending on the position of column 0 |
|
1571 int nextLogicalSection = currentLogicalSection + 1 >= logicalIndices.count() |
|
1572 ? logicalIndexAfterRight |
|
1573 : logicalIndices.at(currentLogicalSection + 1); |
|
1574 int prevLogicalSection = currentLogicalSection - 1 < 0 |
|
1575 ? logicalIndexBeforeLeft |
|
1576 : logicalIndices.at(currentLogicalSection - 1); |
|
1577 if (columnCount == 1 || (nextLogicalSection == 0 && prevLogicalSection == -1) |
|
1578 || (headerSection == 0 && nextLogicalSection == -1) || spanning) |
|
1579 opt.viewItemPosition = QStyleOptionViewItemV4::OnlyOne; |
|
1580 else if (headerSection == 0 || (nextLogicalSection != 0 && prevLogicalSection == -1)) |
|
1581 opt.viewItemPosition = QStyleOptionViewItemV4::Beginning; |
|
1582 else if (nextLogicalSection == 0 || nextLogicalSection == -1) |
|
1583 opt.viewItemPosition = QStyleOptionViewItemV4::End; |
|
1584 else |
|
1585 opt.viewItemPosition = QStyleOptionViewItemV4::Middle; |
|
1586 |
|
1587 // fake activeness when row editor has focus |
|
1588 if (indexWidgetHasFocus) |
|
1589 opt.state |= QStyle::State_Active; |
|
1590 |
|
1591 if (d->selectionModel->isSelected(modelIndex)) |
|
1592 opt.state |= QStyle::State_Selected; |
|
1593 if (widgetHasFocus && (current == modelIndex)) { |
|
1594 if (allColumnsShowFocus) |
|
1595 currentRowHasFocus = true; |
|
1596 else |
|
1597 opt.state |= QStyle::State_HasFocus; |
|
1598 } |
|
1599 if ((hoverRow || modelIndex == hover) |
|
1600 && (option.showDecorationSelected || (d->hoverBranch == -1))) |
|
1601 opt.state |= QStyle::State_MouseOver; |
|
1602 else |
|
1603 opt.state &= ~QStyle::State_MouseOver; |
|
1604 |
|
1605 if (enabled) { |
|
1606 QPalette::ColorGroup cg; |
|
1607 if ((d->model->flags(modelIndex) & Qt::ItemIsEnabled) == 0) { |
|
1608 opt.state &= ~QStyle::State_Enabled; |
|
1609 cg = QPalette::Disabled; |
|
1610 } else if (opt.state & QStyle::State_Active) { |
|
1611 cg = QPalette::Active; |
|
1612 } else { |
|
1613 cg = QPalette::Inactive; |
|
1614 } |
|
1615 opt.palette.setCurrentColorGroup(cg); |
|
1616 } |
|
1617 |
|
1618 if (alternate) { |
|
1619 if (d->current & 1) { |
|
1620 opt.features |= QStyleOptionViewItemV2::Alternate; |
|
1621 } else { |
|
1622 opt.features &= ~QStyleOptionViewItemV2::Alternate; |
|
1623 } |
|
1624 } |
|
1625 |
|
1626 /* Prior to Qt 4.3, the background of the branch (in selected state and |
|
1627 alternate row color was provided by the view. For backward compatibility, |
|
1628 this is now delegated to the style using PE_PanelViewItemRow which |
|
1629 does the appropriate fill */ |
|
1630 if (headerSection == 0) { |
|
1631 const int i = d->indentationForItem(d->current); |
|
1632 QRect branches(reverse ? position + width - i : position, y, i, height); |
|
1633 const bool setClipRect = branches.width() > width; |
|
1634 if (setClipRect) { |
|
1635 painter->save(); |
|
1636 painter->setClipRect(QRect(position, y, width, height)); |
|
1637 } |
|
1638 // draw background for the branch (selection + alternate row) |
|
1639 opt.rect = branches; |
|
1640 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); |
|
1641 |
|
1642 // draw background of the item (only alternate row). rest of the background |
|
1643 // is provided by the delegate |
|
1644 QStyle::State oldState = opt.state; |
|
1645 opt.state &= ~QStyle::State_Selected; |
|
1646 opt.rect.setRect(reverse ? position : i + position, y, width - i, height); |
|
1647 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); |
|
1648 opt.state = oldState; |
|
1649 |
|
1650 drawBranches(painter, branches, index); |
|
1651 if (setClipRect) |
|
1652 painter->restore(); |
|
1653 } else { |
|
1654 QStyle::State oldState = opt.state; |
|
1655 opt.state &= ~QStyle::State_Selected; |
|
1656 opt.rect.setRect(position, y, width, height); |
|
1657 style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &opt, painter, this); |
|
1658 opt.state = oldState; |
|
1659 } |
|
1660 |
|
1661 if (const QWidget *widget = d->editorForIndex(modelIndex).editor) { |
|
1662 painter->save(); |
|
1663 painter->setClipRect(widget->geometry()); |
|
1664 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); |
|
1665 painter->restore(); |
|
1666 } else { |
|
1667 d->delegateForIndex(modelIndex)->paint(painter, opt, modelIndex); |
|
1668 } |
|
1669 } |
|
1670 |
|
1671 if (currentRowHasFocus) { |
|
1672 QStyleOptionFocusRect o; |
|
1673 o.QStyleOption::operator=(option); |
|
1674 o.state |= QStyle::State_KeyboardFocusChange; |
|
1675 QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) |
|
1676 ? QPalette::Normal : QPalette::Disabled; |
|
1677 o.backgroundColor = option.palette.color(cg, d->selectionModel->isSelected(index) |
|
1678 ? QPalette::Highlight : QPalette::Background); |
|
1679 int x = 0; |
|
1680 if (!option.showDecorationSelected) |
|
1681 x = header->sectionPosition(0) + d->indentationForItem(d->current); |
|
1682 QRect focusRect(x - header->offset(), y, header->length() - x, height); |
|
1683 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), focusRect); |
|
1684 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); |
|
1685 // if we show focus on all columns and the first section is moved, |
|
1686 // we have to split the focus rect into two rects |
|
1687 if (allColumnsShowFocus && !option.showDecorationSelected |
|
1688 && header->sectionsMoved() && (header->visualIndex(0) != 0)) { |
|
1689 QRect sectionRect(0, y, header->sectionPosition(0), height); |
|
1690 o.rect = style()->visualRect(layoutDirection(), d->viewport->rect(), sectionRect); |
|
1691 style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); |
|
1692 } |
|
1693 } |
|
1694 } |
|
1695 |
|
1696 /*! |
|
1697 Draws the branches in the tree view on the same row as the model item |
|
1698 \a index, using the \a painter given. The branches are drawn in the |
|
1699 rectangle specified by \a rect. |
|
1700 */ |
|
1701 void QTreeView::drawBranches(QPainter *painter, const QRect &rect, |
|
1702 const QModelIndex &index) const |
|
1703 { |
|
1704 Q_D(const QTreeView); |
|
1705 const bool reverse = isRightToLeft(); |
|
1706 const int indent = d->indent; |
|
1707 const int outer = d->rootDecoration ? 0 : 1; |
|
1708 const int item = d->current; |
|
1709 const QTreeViewItem &viewItem = d->viewItems.at(item); |
|
1710 int level = viewItem.level; |
|
1711 QRect primitive(reverse ? rect.left() : rect.right() + 1, rect.top(), indent, rect.height()); |
|
1712 |
|
1713 QModelIndex parent = index.parent(); |
|
1714 QModelIndex current = parent; |
|
1715 QModelIndex ancestor = current.parent(); |
|
1716 |
|
1717 QStyleOptionViewItemV2 opt = viewOptions(); |
|
1718 QStyle::State extraFlags = QStyle::State_None; |
|
1719 if (isEnabled()) |
|
1720 extraFlags |= QStyle::State_Enabled; |
|
1721 if (window()->isActiveWindow()) |
|
1722 extraFlags |= QStyle::State_Active; |
|
1723 QPoint oldBO = painter->brushOrigin(); |
|
1724 if (verticalScrollMode() == QAbstractItemView::ScrollPerPixel) |
|
1725 painter->setBrushOrigin(QPoint(0, verticalOffset())); |
|
1726 |
|
1727 if (d->alternatingColors) { |
|
1728 if (d->current & 1) { |
|
1729 opt.features |= QStyleOptionViewItemV2::Alternate; |
|
1730 } else { |
|
1731 opt.features &= ~QStyleOptionViewItemV2::Alternate; |
|
1732 } |
|
1733 } |
|
1734 |
|
1735 // When hovering over a row, pass State_Hover for painting the branch |
|
1736 // indicators if it has the decoration (aka branch) selected. |
|
1737 bool hoverRow = selectionBehavior() == QAbstractItemView::SelectRows |
|
1738 && opt.showDecorationSelected |
|
1739 && index.parent() == d->hover.parent() |
|
1740 && index.row() == d->hover.row(); |
|
1741 |
|
1742 if (d->selectionModel->isSelected(index)) |
|
1743 extraFlags |= QStyle::State_Selected; |
|
1744 |
|
1745 if (level >= outer) { |
|
1746 // start with the innermost branch |
|
1747 primitive.moveLeft(reverse ? primitive.left() : primitive.left() - indent); |
|
1748 opt.rect = primitive; |
|
1749 |
|
1750 const bool expanded = viewItem.expanded; |
|
1751 const bool children = (((expanded && viewItem.total > 0)) // already laid out and has children |
|
1752 || d->hasVisibleChildren(index)); // not laid out yet, so we don't know |
|
1753 bool moreSiblings = false; |
|
1754 if (d->hiddenIndexes.isEmpty()) |
|
1755 moreSiblings = (d->model->rowCount(parent) - 1 > index.row()); |
|
1756 else |
|
1757 moreSiblings = ((d->viewItems.size() > item +1) |
|
1758 && (d->viewItems.at(item + 1).index.parent() == parent)); |
|
1759 |
|
1760 opt.state = QStyle::State_Item | extraFlags |
|
1761 | (moreSiblings ? QStyle::State_Sibling : QStyle::State_None) |
|
1762 | (children ? QStyle::State_Children : QStyle::State_None) |
|
1763 | (expanded ? QStyle::State_Open : QStyle::State_None); |
|
1764 if (hoverRow || item == d->hoverBranch) |
|
1765 opt.state |= QStyle::State_MouseOver; |
|
1766 else |
|
1767 opt.state &= ~QStyle::State_MouseOver; |
|
1768 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); |
|
1769 } |
|
1770 // then go out level by level |
|
1771 for (--level; level >= outer; --level) { // we have already drawn the innermost branch |
|
1772 primitive.moveLeft(reverse ? primitive.left() + indent : primitive.left() - indent); |
|
1773 opt.rect = primitive; |
|
1774 opt.state = extraFlags; |
|
1775 bool moreSiblings = false; |
|
1776 if (d->hiddenIndexes.isEmpty()) { |
|
1777 moreSiblings = (d->model->rowCount(ancestor) - 1 > current.row()); |
|
1778 } else { |
|
1779 int successor = item + viewItem.total + 1; |
|
1780 while (successor < d->viewItems.size() |
|
1781 && d->viewItems.at(successor).level >= uint(level)) { |
|
1782 const QTreeViewItem &successorItem = d->viewItems.at(successor); |
|
1783 if (successorItem.level == uint(level)) { |
|
1784 moreSiblings = true; |
|
1785 break; |
|
1786 } |
|
1787 successor += successorItem.total + 1; |
|
1788 } |
|
1789 } |
|
1790 if (moreSiblings) |
|
1791 opt.state |= QStyle::State_Sibling; |
|
1792 if (hoverRow || item == d->hoverBranch) |
|
1793 opt.state |= QStyle::State_MouseOver; |
|
1794 else |
|
1795 opt.state &= ~QStyle::State_MouseOver; |
|
1796 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this); |
|
1797 current = ancestor; |
|
1798 ancestor = current.parent(); |
|
1799 } |
|
1800 painter->setBrushOrigin(oldBO); |
|
1801 } |
|
1802 |
|
1803 /*! |
|
1804 \reimp |
|
1805 */ |
|
1806 void QTreeView::mousePressEvent(QMouseEvent *event) |
|
1807 { |
|
1808 Q_D(QTreeView); |
|
1809 bool handled = false; |
|
1810 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonPress) |
|
1811 handled = d->expandOrCollapseItemAtPos(event->pos()); |
|
1812 if (!handled && d->itemDecorationAt(event->pos()) == -1) |
|
1813 QAbstractItemView::mousePressEvent(event); |
|
1814 } |
|
1815 |
|
1816 /*! |
|
1817 \reimp |
|
1818 */ |
|
1819 void QTreeView::mouseReleaseEvent(QMouseEvent *event) |
|
1820 { |
|
1821 Q_D(QTreeView); |
|
1822 if (d->itemDecorationAt(event->pos()) == -1) { |
|
1823 QAbstractItemView::mouseReleaseEvent(event); |
|
1824 } else { |
|
1825 if (state() == QAbstractItemView::DragSelectingState) |
|
1826 setState(QAbstractItemView::NoState); |
|
1827 if (style()->styleHint(QStyle::SH_Q3ListViewExpand_SelectMouseType, 0, this) == QEvent::MouseButtonRelease) |
|
1828 d->expandOrCollapseItemAtPos(event->pos()); |
|
1829 } |
|
1830 } |
|
1831 |
|
1832 /*! |
|
1833 \reimp |
|
1834 */ |
|
1835 void QTreeView::mouseDoubleClickEvent(QMouseEvent *event) |
|
1836 { |
|
1837 Q_D(QTreeView); |
|
1838 if (state() != NoState || !d->viewport->rect().contains(event->pos())) |
|
1839 return; |
|
1840 |
|
1841 int i = d->itemDecorationAt(event->pos()); |
|
1842 if (i == -1) { |
|
1843 i = d->itemAtCoordinate(event->y()); |
|
1844 if (i == -1) |
|
1845 return; // user clicked outside the items |
|
1846 |
|
1847 const QPersistentModelIndex firstColumnIndex = d->viewItems.at(i).index; |
|
1848 |
|
1849 int column = d->header->logicalIndexAt(event->x()); |
|
1850 QPersistentModelIndex persistent = firstColumnIndex.sibling(firstColumnIndex.row(), column); |
|
1851 |
|
1852 if (d->pressedIndex != persistent) { |
|
1853 mousePressEvent(event); |
|
1854 return; |
|
1855 } |
|
1856 |
|
1857 // signal handlers may change the model |
|
1858 emit doubleClicked(persistent); |
|
1859 |
|
1860 if (!persistent.isValid()) |
|
1861 return; |
|
1862 |
|
1863 if (edit(persistent, DoubleClicked, event) || state() != NoState) |
|
1864 return; // the double click triggered editing |
|
1865 |
|
1866 if (!style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this)) |
|
1867 emit activated(persistent); |
|
1868 |
|
1869 d->executePostedLayout(); // we need to make sure viewItems is updated |
|
1870 if (d->itemsExpandable |
|
1871 && d->expandsOnDoubleClick |
|
1872 && d->hasVisibleChildren(persistent)) { |
|
1873 if (!((i < d->viewItems.count()) && (d->viewItems.at(i).index == firstColumnIndex))) { |
|
1874 // find the new index of the item |
|
1875 for (i = 0; i < d->viewItems.count(); ++i) { |
|
1876 if (d->viewItems.at(i).index == firstColumnIndex) |
|
1877 break; |
|
1878 } |
|
1879 if (i == d->viewItems.count()) |
|
1880 return; |
|
1881 } |
|
1882 if (d->viewItems.at(i).expanded) |
|
1883 d->collapse(i, true); |
|
1884 else |
|
1885 d->expand(i, true); |
|
1886 updateGeometries(); |
|
1887 viewport()->update(); |
|
1888 } |
|
1889 } |
|
1890 } |
|
1891 |
|
1892 /*! |
|
1893 \reimp |
|
1894 */ |
|
1895 void QTreeView::mouseMoveEvent(QMouseEvent *event) |
|
1896 { |
|
1897 Q_D(QTreeView); |
|
1898 if (d->itemDecorationAt(event->pos()) == -1) // ### what about expanding/collapsing state ? |
|
1899 QAbstractItemView::mouseMoveEvent(event); |
|
1900 } |
|
1901 |
|
1902 /*! |
|
1903 \reimp |
|
1904 */ |
|
1905 void QTreeView::keyPressEvent(QKeyEvent *event) |
|
1906 { |
|
1907 Q_D(QTreeView); |
|
1908 QModelIndex current = currentIndex(); |
|
1909 //this is the management of the expansion |
|
1910 if (d->isIndexValid(current) && d->model && d->itemsExpandable) { |
|
1911 switch (event->key()) { |
|
1912 case Qt::Key_Asterisk: { |
|
1913 QStack<QModelIndex> parents; |
|
1914 parents.push(current); |
|
1915 while (!parents.isEmpty()) { |
|
1916 QModelIndex parent = parents.pop(); |
|
1917 for (int row = 0; row < d->model->rowCount(parent); ++row) { |
|
1918 QModelIndex child = d->model->index(row, 0, parent); |
|
1919 if (!d->isIndexValid(child)) |
|
1920 break; |
|
1921 parents.push(child); |
|
1922 expand(child); |
|
1923 } |
|
1924 } |
|
1925 expand(current); |
|
1926 break; } |
|
1927 case Qt::Key_Plus: |
|
1928 expand(current); |
|
1929 break; |
|
1930 case Qt::Key_Minus: |
|
1931 collapse(current); |
|
1932 break; |
|
1933 } |
|
1934 } |
|
1935 |
|
1936 QAbstractItemView::keyPressEvent(event); |
|
1937 } |
|
1938 |
|
1939 /*! |
|
1940 \reimp |
|
1941 */ |
|
1942 QModelIndex QTreeView::indexAt(const QPoint &point) const |
|
1943 { |
|
1944 Q_D(const QTreeView); |
|
1945 d->executePostedLayout(); |
|
1946 |
|
1947 int visualIndex = d->itemAtCoordinate(point.y()); |
|
1948 QModelIndex idx = d->modelIndex(visualIndex); |
|
1949 if (!idx.isValid()) |
|
1950 return QModelIndex(); |
|
1951 |
|
1952 if (d->viewItems.at(visualIndex).spanning) |
|
1953 return idx; |
|
1954 |
|
1955 int column = d->columnAt(point.x()); |
|
1956 if (column == idx.column()) |
|
1957 return idx; |
|
1958 if (column < 0) |
|
1959 return QModelIndex(); |
|
1960 return idx.sibling(idx.row(), column); |
|
1961 } |
|
1962 |
|
1963 /*! |
|
1964 Returns the model index of the item above \a index. |
|
1965 */ |
|
1966 QModelIndex QTreeView::indexAbove(const QModelIndex &index) const |
|
1967 { |
|
1968 Q_D(const QTreeView); |
|
1969 if (!d->isIndexValid(index)) |
|
1970 return QModelIndex(); |
|
1971 d->executePostedLayout(); |
|
1972 int i = d->viewIndex(index); |
|
1973 if (--i < 0) |
|
1974 return QModelIndex(); |
|
1975 return d->viewItems.at(i).index; |
|
1976 } |
|
1977 |
|
1978 /*! |
|
1979 Returns the model index of the item below \a index. |
|
1980 */ |
|
1981 QModelIndex QTreeView::indexBelow(const QModelIndex &index) const |
|
1982 { |
|
1983 Q_D(const QTreeView); |
|
1984 if (!d->isIndexValid(index)) |
|
1985 return QModelIndex(); |
|
1986 d->executePostedLayout(); |
|
1987 int i = d->viewIndex(index); |
|
1988 if (++i >= d->viewItems.count()) |
|
1989 return QModelIndex(); |
|
1990 return d->viewItems.at(i).index; |
|
1991 } |
|
1992 |
|
1993 /*! |
|
1994 \internal |
|
1995 |
|
1996 Lays out the items in the tree view. |
|
1997 */ |
|
1998 void QTreeView::doItemsLayout() |
|
1999 { |
|
2000 Q_D(QTreeView); |
|
2001 d->viewItems.clear(); // prepare for new layout |
|
2002 QModelIndex parent = d->root; |
|
2003 if (d->model->hasChildren(parent)) { |
|
2004 d->layout(-1); |
|
2005 } |
|
2006 QAbstractItemView::doItemsLayout(); |
|
2007 d->header->doItemsLayout(); |
|
2008 } |
|
2009 |
|
2010 /*! |
|
2011 \reimp |
|
2012 */ |
|
2013 void QTreeView::reset() |
|
2014 { |
|
2015 Q_D(QTreeView); |
|
2016 d->expandedIndexes.clear(); |
|
2017 d->hiddenIndexes.clear(); |
|
2018 d->spanningIndexes.clear(); |
|
2019 d->viewItems.clear(); |
|
2020 QAbstractItemView::reset(); |
|
2021 } |
|
2022 |
|
2023 /*! |
|
2024 Returns the horizontal offset of the items in the treeview. |
|
2025 |
|
2026 Note that the tree view uses the horizontal header section |
|
2027 positions to determine the positions of columns in the view. |
|
2028 |
|
2029 \sa verticalOffset() |
|
2030 */ |
|
2031 int QTreeView::horizontalOffset() const |
|
2032 { |
|
2033 Q_D(const QTreeView); |
|
2034 return d->header->offset(); |
|
2035 } |
|
2036 |
|
2037 /*! |
|
2038 Returns the vertical offset of the items in the tree view. |
|
2039 |
|
2040 \sa horizontalOffset() |
|
2041 */ |
|
2042 int QTreeView::verticalOffset() const |
|
2043 { |
|
2044 Q_D(const QTreeView); |
|
2045 if (d->verticalScrollMode == QAbstractItemView::ScrollPerItem) { |
|
2046 if (d->uniformRowHeights) |
|
2047 return verticalScrollBar()->value() * d->defaultItemHeight; |
|
2048 // If we are scrolling per item and have non-uniform row heights, |
|
2049 // finding the vertical offset in pixels is going to be relatively slow. |
|
2050 // ### find a faster way to do this |
|
2051 d->executePostedLayout(); |
|
2052 int offset = 0; |
|
2053 for (int i = 0; i < d->viewItems.count(); ++i) { |
|
2054 if (i == verticalScrollBar()->value()) |
|
2055 return offset; |
|
2056 offset += d->itemHeight(i); |
|
2057 } |
|
2058 return 0; |
|
2059 } |
|
2060 // scroll per pixel |
|
2061 return verticalScrollBar()->value(); |
|
2062 } |
|
2063 |
|
2064 /*! |
|
2065 Move the cursor in the way described by \a cursorAction, using the |
|
2066 information provided by the button \a modifiers. |
|
2067 */ |
|
2068 QModelIndex QTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) |
|
2069 { |
|
2070 Q_D(QTreeView); |
|
2071 Q_UNUSED(modifiers); |
|
2072 |
|
2073 d->executePostedLayout(); |
|
2074 |
|
2075 QModelIndex current = currentIndex(); |
|
2076 if (!current.isValid()) { |
|
2077 int i = d->below(-1); |
|
2078 int c = 0; |
|
2079 while (c < d->header->count() && d->header->isSectionHidden(c)) |
|
2080 ++c; |
|
2081 if (i < d->viewItems.count() && c < d->header->count()) { |
|
2082 return d->modelIndex(i, c); |
|
2083 } |
|
2084 return QModelIndex(); |
|
2085 } |
|
2086 int vi = -1; |
|
2087 #if defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC) |
|
2088 // Selection behavior is slightly different on the Mac. |
|
2089 if (d->selectionMode == QAbstractItemView::ExtendedSelection |
|
2090 && d->selectionModel |
|
2091 && d->selectionModel->hasSelection()) { |
|
2092 |
|
2093 const bool moveUpDown = (cursorAction == MoveUp || cursorAction == MoveDown); |
|
2094 const bool moveNextPrev = (cursorAction == MoveNext || cursorAction == MovePrevious); |
|
2095 const bool contiguousSelection = moveUpDown && (modifiers & Qt::ShiftModifier); |
|
2096 |
|
2097 // Use the outermost index in the selection as the current index |
|
2098 if (!contiguousSelection && (moveUpDown || moveNextPrev)) { |
|
2099 |
|
2100 // Find outermost index. |
|
2101 const bool useTopIndex = (cursorAction == MoveUp || cursorAction == MovePrevious); |
|
2102 int index = useTopIndex ? INT_MAX : INT_MIN; |
|
2103 const QItemSelection selection = d->selectionModel->selection(); |
|
2104 for (int i = 0; i < selection.count(); ++i) { |
|
2105 const QItemSelectionRange &range = selection.at(i); |
|
2106 int candidate = d->viewIndex(useTopIndex ? range.topLeft() : range.bottomRight()); |
|
2107 if (candidate >= 0) |
|
2108 index = useTopIndex ? qMin(index, candidate) : qMax(index, candidate); |
|
2109 } |
|
2110 |
|
2111 if (index >= 0 && index < INT_MAX) |
|
2112 vi = index; |
|
2113 } |
|
2114 } |
|
2115 #endif |
|
2116 if (vi < 0) |
|
2117 vi = qMax(0, d->viewIndex(current)); |
|
2118 |
|
2119 switch (cursorAction) { |
|
2120 case MoveNext: |
|
2121 case MoveDown: |
|
2122 #ifdef QT_KEYPAD_NAVIGATION |
|
2123 if (vi == d->viewItems.count()-1 && QApplication::keypadNavigationEnabled()) |
|
2124 return d->model->index(0, current.column(), d->root); |
|
2125 #endif |
|
2126 return d->modelIndex(d->below(vi), current.column()); |
|
2127 case MovePrevious: |
|
2128 case MoveUp: |
|
2129 #ifdef QT_KEYPAD_NAVIGATION |
|
2130 if (vi == 0 && QApplication::keypadNavigationEnabled()) |
|
2131 return d->modelIndex(d->viewItems.count() - 1, current.column()); |
|
2132 #endif |
|
2133 return d->modelIndex(d->above(vi), current.column()); |
|
2134 case MoveLeft: { |
|
2135 QScrollBar *sb = horizontalScrollBar(); |
|
2136 if (vi < d->viewItems.count() && d->viewItems.at(vi).expanded && d->itemsExpandable && sb->value() == sb->minimum()) |
|
2137 d->collapse(vi, true); |
|
2138 else { |
|
2139 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); |
|
2140 if (descend) { |
|
2141 QModelIndex par = current.parent(); |
|
2142 if (par.isValid() && par != rootIndex()) |
|
2143 return par; |
|
2144 else |
|
2145 descend = false; |
|
2146 } |
|
2147 if (!descend) { |
|
2148 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { |
|
2149 int visualColumn = d->header->visualIndex(current.column()) - 1; |
|
2150 while (visualColumn >= 0 && isColumnHidden(d->header->logicalIndex(visualColumn))) |
|
2151 visualColumn--; |
|
2152 int newColumn = d->header->logicalIndex(visualColumn); |
|
2153 QModelIndex next = current.sibling(current.row(), newColumn); |
|
2154 if (next.isValid()) |
|
2155 return next; |
|
2156 } |
|
2157 |
|
2158 sb->setValue(sb->value() - sb->singleStep()); |
|
2159 } |
|
2160 |
|
2161 } |
|
2162 updateGeometries(); |
|
2163 viewport()->update(); |
|
2164 break; |
|
2165 } |
|
2166 case MoveRight: |
|
2167 if (vi < d->viewItems.count() && !d->viewItems.at(vi).expanded && d->itemsExpandable |
|
2168 && d->hasVisibleChildren(d->viewItems.at(vi).index)) { |
|
2169 d->expand(vi, true); |
|
2170 } else { |
|
2171 bool descend = style()->styleHint(QStyle::SH_ItemView_ArrowKeysNavigateIntoChildren, 0, this); |
|
2172 if (descend) { |
|
2173 QModelIndex idx = d->modelIndex(d->below(vi)); |
|
2174 if (idx.parent() == current) |
|
2175 return idx; |
|
2176 else |
|
2177 descend = false; |
|
2178 } |
|
2179 if (!descend) { |
|
2180 if (d->selectionBehavior == SelectItems || d->selectionBehavior == SelectColumns) { |
|
2181 int visualColumn = d->header->visualIndex(current.column()) + 1; |
|
2182 while (visualColumn < d->model->columnCount(current.parent()) && isColumnHidden(d->header->logicalIndex(visualColumn))) |
|
2183 visualColumn++; |
|
2184 |
|
2185 QModelIndex next = current.sibling(current.row(), visualColumn); |
|
2186 if (next.isValid()) |
|
2187 return next; |
|
2188 } |
|
2189 |
|
2190 //last restort: we change the scrollbar value |
|
2191 QScrollBar *sb = horizontalScrollBar(); |
|
2192 sb->setValue(sb->value() + sb->singleStep()); |
|
2193 } |
|
2194 } |
|
2195 updateGeometries(); |
|
2196 viewport()->update(); |
|
2197 break; |
|
2198 case MovePageUp: |
|
2199 return d->modelIndex(d->pageUp(vi), current.column()); |
|
2200 case MovePageDown: |
|
2201 return d->modelIndex(d->pageDown(vi), current.column()); |
|
2202 case MoveHome: |
|
2203 return d->model->index(0, current.column(), d->root); |
|
2204 case MoveEnd: |
|
2205 return d->modelIndex(d->viewItems.count() - 1, current.column()); |
|
2206 } |
|
2207 return current; |
|
2208 } |
|
2209 |
|
2210 /*! |
|
2211 Applies the selection \a command to the items in or touched by the |
|
2212 rectangle, \a rect. |
|
2213 |
|
2214 \sa selectionCommand() |
|
2215 */ |
|
2216 void QTreeView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command) |
|
2217 { |
|
2218 Q_D(QTreeView); |
|
2219 if (!selectionModel() || rect.isNull()) |
|
2220 return; |
|
2221 |
|
2222 d->executePostedLayout(); |
|
2223 QPoint tl(isRightToLeft() ? qMax(rect.left(), rect.right()) |
|
2224 : qMin(rect.left(), rect.right()), qMin(rect.top(), rect.bottom())); |
|
2225 QPoint br(isRightToLeft() ? qMin(rect.left(), rect.right()) : |
|
2226 qMax(rect.left(), rect.right()), qMax(rect.top(), rect.bottom())); |
|
2227 QModelIndex topLeft = indexAt(tl); |
|
2228 QModelIndex bottomRight = indexAt(br); |
|
2229 if (!topLeft.isValid() && !bottomRight.isValid()) { |
|
2230 if (command & QItemSelectionModel::Clear) |
|
2231 selectionModel()->clear(); |
|
2232 return; |
|
2233 } |
|
2234 if (!topLeft.isValid() && !d->viewItems.isEmpty()) |
|
2235 topLeft = d->viewItems.first().index; |
|
2236 if (!bottomRight.isValid() && !d->viewItems.isEmpty()) { |
|
2237 const int column = d->header->logicalIndex(d->header->count() - 1); |
|
2238 const QModelIndex index = d->viewItems.last().index; |
|
2239 bottomRight = index.sibling(index.row(), column); |
|
2240 } |
|
2241 |
|
2242 if (!d->isIndexEnabled(topLeft) || !d->isIndexEnabled(bottomRight)) |
|
2243 return; |
|
2244 |
|
2245 d->select(topLeft, bottomRight, command); |
|
2246 } |
|
2247 |
|
2248 /*! |
|
2249 Returns the rectangle from the viewport of the items in the given |
|
2250 \a selection. |
|
2251 */ |
|
2252 QRegion QTreeView::visualRegionForSelection(const QItemSelection &selection) const |
|
2253 { |
|
2254 Q_D(const QTreeView); |
|
2255 if (selection.isEmpty()) |
|
2256 return QRegion(); |
|
2257 |
|
2258 QRegion selectionRegion; |
|
2259 for (int i = 0; i < selection.count(); ++i) { |
|
2260 QItemSelectionRange range = selection.at(i); |
|
2261 if (!range.isValid()) |
|
2262 continue; |
|
2263 QModelIndex parent = range.parent(); |
|
2264 QModelIndex leftIndex = range.topLeft(); |
|
2265 int columnCount = d->model->columnCount(parent); |
|
2266 while (leftIndex.isValid() && isIndexHidden(leftIndex)) { |
|
2267 if (leftIndex.column() + 1 < columnCount) |
|
2268 leftIndex = d->model->index(leftIndex.row(), leftIndex.column() + 1, parent); |
|
2269 else |
|
2270 leftIndex = QModelIndex(); |
|
2271 } |
|
2272 if (!leftIndex.isValid()) |
|
2273 continue; |
|
2274 const QRect leftRect = visualRect(leftIndex); |
|
2275 int top = leftRect.top(); |
|
2276 QModelIndex rightIndex = range.bottomRight(); |
|
2277 while (rightIndex.isValid() && isIndexHidden(rightIndex)) { |
|
2278 if (rightIndex.column() - 1 >= 0) |
|
2279 rightIndex = d->model->index(rightIndex.row(), rightIndex.column() - 1, parent); |
|
2280 else |
|
2281 rightIndex = QModelIndex(); |
|
2282 } |
|
2283 if (!rightIndex.isValid()) |
|
2284 continue; |
|
2285 const QRect rightRect = visualRect(rightIndex); |
|
2286 int bottom = rightRect.bottom(); |
|
2287 if (top > bottom) |
|
2288 qSwap<int>(top, bottom); |
|
2289 int height = bottom - top + 1; |
|
2290 if (d->header->sectionsMoved()) { |
|
2291 for (int c = range.left(); c <= range.right(); ++c) |
|
2292 selectionRegion += QRegion(QRect(columnViewportPosition(c), top, |
|
2293 columnWidth(c), height)); |
|
2294 } else { |
|
2295 QRect combined = leftRect|rightRect; |
|
2296 combined.setX(columnViewportPosition(isRightToLeft() ? range.right() : range.left())); |
|
2297 selectionRegion += combined; |
|
2298 } |
|
2299 } |
|
2300 return selectionRegion; |
|
2301 } |
|
2302 |
|
2303 /*! |
|
2304 \reimp |
|
2305 */ |
|
2306 QModelIndexList QTreeView::selectedIndexes() const |
|
2307 { |
|
2308 QModelIndexList viewSelected; |
|
2309 QModelIndexList modelSelected; |
|
2310 if (selectionModel()) |
|
2311 modelSelected = selectionModel()->selectedIndexes(); |
|
2312 for (int i = 0; i < modelSelected.count(); ++i) { |
|
2313 // check that neither the parents nor the index is hidden before we add |
|
2314 QModelIndex index = modelSelected.at(i); |
|
2315 while (index.isValid() && !isIndexHidden(index)) |
|
2316 index = index.parent(); |
|
2317 if (index.isValid()) |
|
2318 continue; |
|
2319 viewSelected.append(modelSelected.at(i)); |
|
2320 } |
|
2321 return viewSelected; |
|
2322 } |
|
2323 |
|
2324 /*! |
|
2325 Scrolls the contents of the tree view by (\a dx, \a dy). |
|
2326 */ |
|
2327 void QTreeView::scrollContentsBy(int dx, int dy) |
|
2328 { |
|
2329 Q_D(QTreeView); |
|
2330 |
|
2331 d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling |
|
2332 |
|
2333 dx = isRightToLeft() ? -dx : dx; |
|
2334 if (dx) { |
|
2335 if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) { |
|
2336 int oldOffset = d->header->offset(); |
|
2337 if (horizontalScrollBar()->value() == horizontalScrollBar()->maximum()) |
|
2338 d->header->setOffsetToLastSection(); |
|
2339 else |
|
2340 d->header->setOffsetToSectionPosition(horizontalScrollBar()->value()); |
|
2341 int newOffset = d->header->offset(); |
|
2342 dx = isRightToLeft() ? newOffset - oldOffset : oldOffset - newOffset; |
|
2343 } else { |
|
2344 d->header->setOffset(horizontalScrollBar()->value()); |
|
2345 } |
|
2346 } |
|
2347 |
|
2348 const int itemHeight = d->defaultItemHeight <= 0 ? sizeHintForRow(0) : d->defaultItemHeight; |
|
2349 if (d->viewItems.isEmpty() || itemHeight == 0) |
|
2350 return; |
|
2351 |
|
2352 // guestimate the number of items in the viewport |
|
2353 int viewCount = d->viewport->height() / itemHeight; |
|
2354 int maxDeltaY = qMin(d->viewItems.count(), viewCount); |
|
2355 // no need to do a lot of work if we are going to redraw the whole thing anyway |
|
2356 if (qAbs(dy) > qAbs(maxDeltaY) && d->editors.isEmpty()) { |
|
2357 verticalScrollBar()->update(); |
|
2358 d->viewport->update(); |
|
2359 return; |
|
2360 } |
|
2361 |
|
2362 if (dy && verticalScrollMode() == QAbstractItemView::ScrollPerItem) { |
|
2363 int currentScrollbarValue = verticalScrollBar()->value(); |
|
2364 int previousScrollbarValue = currentScrollbarValue + dy; // -(-dy) |
|
2365 int currentViewIndex = currentScrollbarValue; // the first visible item |
|
2366 int previousViewIndex = previousScrollbarValue; |
|
2367 const QVector<QTreeViewItem> viewItems = d->viewItems; |
|
2368 dy = 0; |
|
2369 if (previousViewIndex < currentViewIndex) { // scrolling down |
|
2370 for (int i = previousViewIndex; i < currentViewIndex; ++i) { |
|
2371 if (i < d->viewItems.count()) |
|
2372 dy -= d->itemHeight(i); |
|
2373 } |
|
2374 } else if (previousViewIndex > currentViewIndex) { // scrolling up |
|
2375 for (int i = previousViewIndex - 1; i >= currentViewIndex; --i) { |
|
2376 if (i < d->viewItems.count()) |
|
2377 dy += d->itemHeight(i); |
|
2378 } |
|
2379 } |
|
2380 } |
|
2381 |
|
2382 d->scrollContentsBy(dx, dy); |
|
2383 } |
|
2384 |
|
2385 /*! |
|
2386 This slot is called whenever a column has been moved. |
|
2387 */ |
|
2388 void QTreeView::columnMoved() |
|
2389 { |
|
2390 Q_D(QTreeView); |
|
2391 updateEditorGeometries(); |
|
2392 d->viewport->update(); |
|
2393 } |
|
2394 |
|
2395 /*! |
|
2396 \internal |
|
2397 */ |
|
2398 void QTreeView::reexpand() |
|
2399 { |
|
2400 // do nothing |
|
2401 } |
|
2402 |
|
2403 /*! |
|
2404 \internal |
|
2405 */ |
|
2406 static bool treeViewItemLessThan(const QTreeViewItem &left, |
|
2407 const QTreeViewItem &right) |
|
2408 { |
|
2409 if (left.level != right.level) { |
|
2410 Q_ASSERT(left.level > right.level); |
|
2411 QModelIndex leftParent = left.index.parent(); |
|
2412 QModelIndex rightParent = right.index.parent(); |
|
2413 // computer parent, don't get |
|
2414 while (leftParent.isValid() && leftParent.parent() != rightParent) |
|
2415 leftParent = leftParent.parent(); |
|
2416 return (leftParent.row() < right.index.row()); |
|
2417 } |
|
2418 return (left.index.row() < right.index.row()); |
|
2419 } |
|
2420 |
|
2421 /*! |
|
2422 Informs the view that the rows from the \a start row to the \a end row |
|
2423 inclusive have been inserted into the \a parent model item. |
|
2424 */ |
|
2425 void QTreeView::rowsInserted(const QModelIndex &parent, int start, int end) |
|
2426 { |
|
2427 Q_D(QTreeView); |
|
2428 // if we are going to do a complete relayout anyway, there is no need to update |
|
2429 if (d->delayedPendingLayout) { |
|
2430 QAbstractItemView::rowsInserted(parent, start, end); |
|
2431 return; |
|
2432 } |
|
2433 |
|
2434 //don't add a hierarchy on a column != 0 |
|
2435 if (parent.column() != 0 && parent.isValid()) { |
|
2436 QAbstractItemView::rowsInserted(parent, start, end); |
|
2437 return; |
|
2438 } |
|
2439 |
|
2440 if (parent != d->root && !d->isIndexExpanded(parent) && d->model->rowCount(parent) > (end - start) + 1) { |
|
2441 QAbstractItemView::rowsInserted(parent, start, end); |
|
2442 return; |
|
2443 } |
|
2444 |
|
2445 const int parentItem = d->viewIndex(parent); |
|
2446 if (((parentItem != -1) && d->viewItems.at(parentItem).expanded && updatesEnabled()) |
|
2447 || (parent == d->root)) { |
|
2448 const uint childLevel = (parentItem == -1) |
|
2449 ? uint(0) : d->viewItems.at(parentItem).level + 1; |
|
2450 const int firstChildItem = parentItem + 1; |
|
2451 const int lastChildItem = firstChildItem + ((parentItem == -1) |
|
2452 ? d->viewItems.count() |
|
2453 : d->viewItems.at(parentItem).total) - 1; |
|
2454 |
|
2455 const int delta = end - start + 1; |
|
2456 QVector<QTreeViewItem> insertedItems(delta); |
|
2457 for (int i = 0; i < delta; ++i) { |
|
2458 insertedItems[i].index = d->model->index(i + start, 0, parent); |
|
2459 insertedItems[i].level = childLevel; |
|
2460 } |
|
2461 if (d->viewItems.isEmpty()) |
|
2462 d->defaultItemHeight = indexRowSizeHint(insertedItems[0].index); |
|
2463 |
|
2464 int insertPos; |
|
2465 if (lastChildItem < firstChildItem) { // no children |
|
2466 insertPos = firstChildItem; |
|
2467 } else { |
|
2468 // do a binary search to figure out where to insert |
|
2469 QVector<QTreeViewItem>::iterator it; |
|
2470 it = qLowerBound(d->viewItems.begin() + firstChildItem, |
|
2471 d->viewItems.begin() + lastChildItem + 1, |
|
2472 insertedItems.at(0), treeViewItemLessThan); |
|
2473 insertPos = it - d->viewItems.begin(); |
|
2474 |
|
2475 // update stale model indexes of siblings |
|
2476 for (int item = insertPos; item <= lastChildItem; ) { |
|
2477 Q_ASSERT(d->viewItems.at(item).level == childLevel); |
|
2478 const QModelIndex modelIndex = d->viewItems.at(item).index; |
|
2479 //Q_ASSERT(modelIndex.parent() == parent); |
|
2480 d->viewItems[item].index = d->model->index( |
|
2481 modelIndex.row() + delta, modelIndex.column(), parent); |
|
2482 |
|
2483 if (!d->viewItems[item].index.isValid()) { |
|
2484 // Something really bad is happening, a bad model is |
|
2485 // often the cause. We can't optimize in this case :( |
|
2486 qWarning() << "QTreeView::rowsInserted internal representation of the model has been corrupted, resetting."; |
|
2487 doItemsLayout(); |
|
2488 return; |
|
2489 } |
|
2490 |
|
2491 item += d->viewItems.at(item).total + 1; |
|
2492 } |
|
2493 } |
|
2494 |
|
2495 d->viewItems.insert(insertPos, delta, insertedItems.at(0)); |
|
2496 if (delta > 1) { |
|
2497 qCopy(insertedItems.begin() + 1, insertedItems.end(), |
|
2498 d->viewItems.begin() + insertPos + 1); |
|
2499 } |
|
2500 |
|
2501 d->updateChildCount(parentItem, delta); |
|
2502 updateGeometries(); |
|
2503 viewport()->update(); |
|
2504 } else if ((parentItem != -1) && d->viewItems.at(parentItem).expanded) { |
|
2505 d->doDelayedItemsLayout(); |
|
2506 } else if (parentItem != -1 && (d->model->rowCount(parent) == end - start + 1)) { |
|
2507 // the parent just went from 0 children to having some update to re-paint the decoration |
|
2508 viewport()->update(); |
|
2509 } |
|
2510 QAbstractItemView::rowsInserted(parent, start, end); |
|
2511 } |
|
2512 |
|
2513 /*! |
|
2514 Informs the view that the rows from the \a start row to the \a end row |
|
2515 inclusive are about to removed from the given \a parent model item. |
|
2516 */ |
|
2517 void QTreeView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
|
2518 { |
|
2519 Q_D(QTreeView); |
|
2520 d->rowsRemoved(parent, start, end, false); |
|
2521 QAbstractItemView::rowsAboutToBeRemoved(parent, start, end); |
|
2522 } |
|
2523 |
|
2524 /*! |
|
2525 \since 4.1 |
|
2526 |
|
2527 Informs the view that the rows from the \a start row to the \a end row |
|
2528 inclusive have been removed from the given \a parent model item. |
|
2529 */ |
|
2530 void QTreeView::rowsRemoved(const QModelIndex &parent, int start, int end) |
|
2531 { |
|
2532 Q_D(QTreeView); |
|
2533 d->rowsRemoved(parent, start, end, true); |
|
2534 } |
|
2535 |
|
2536 /*! |
|
2537 Informs the tree view that the number of columns in the tree view has |
|
2538 changed from \a oldCount to \a newCount. |
|
2539 */ |
|
2540 void QTreeView::columnCountChanged(int oldCount, int newCount) |
|
2541 { |
|
2542 Q_D(QTreeView); |
|
2543 if (oldCount == 0 && newCount > 0) { |
|
2544 //if the first column has just been added we need to relayout. |
|
2545 d->doDelayedItemsLayout(); |
|
2546 } |
|
2547 |
|
2548 if (isVisible()) |
|
2549 updateGeometries(); |
|
2550 viewport()->update(); |
|
2551 } |
|
2552 |
|
2553 /*! |
|
2554 Resizes the \a column given to the size of its contents. |
|
2555 |
|
2556 \sa columnWidth(), setColumnWidth() |
|
2557 */ |
|
2558 void QTreeView::resizeColumnToContents(int column) |
|
2559 { |
|
2560 Q_D(QTreeView); |
|
2561 d->executePostedLayout(); |
|
2562 if (column < 0 || column >= d->header->count()) |
|
2563 return; |
|
2564 int contents = sizeHintForColumn(column); |
|
2565 int header = d->header->isHidden() ? 0 : d->header->sectionSizeHint(column); |
|
2566 d->header->resizeSection(column, qMax(contents, header)); |
|
2567 } |
|
2568 |
|
2569 /*! |
|
2570 \obsolete |
|
2571 \overload |
|
2572 |
|
2573 Sorts the model by the values in the given \a column. |
|
2574 */ |
|
2575 void QTreeView::sortByColumn(int column) |
|
2576 { |
|
2577 Q_D(QTreeView); |
|
2578 sortByColumn(column, d->header->sortIndicatorOrder()); |
|
2579 } |
|
2580 |
|
2581 /*! |
|
2582 \since 4.2 |
|
2583 |
|
2584 Sets the model up for sorting by the values in the given \a column and \a order. |
|
2585 |
|
2586 \a column may be -1, in which case no sort indicator will be shown |
|
2587 and the model will return to its natural, unsorted order. Note that not |
|
2588 all models support this and may even crash in this case. |
|
2589 |
|
2590 \sa sortingEnabled |
|
2591 */ |
|
2592 void QTreeView::sortByColumn(int column, Qt::SortOrder order) |
|
2593 { |
|
2594 Q_D(QTreeView); |
|
2595 |
|
2596 //If sorting is enabled will emit a signal connected to _q_sortIndicatorChanged, which then actually sorts |
|
2597 d->header->setSortIndicator(column, order); |
|
2598 //If sorting is not enabled, force to sort now. |
|
2599 if (!d->sortingEnabled) |
|
2600 d->model->sort(column, order); |
|
2601 } |
|
2602 |
|
2603 /*! |
|
2604 \reimp |
|
2605 */ |
|
2606 void QTreeView::selectAll() |
|
2607 { |
|
2608 Q_D(QTreeView); |
|
2609 if (!selectionModel()) |
|
2610 return; |
|
2611 SelectionMode mode = d->selectionMode; |
|
2612 d->executePostedLayout(); //make sure we lay out the items |
|
2613 if (mode != SingleSelection && !d->viewItems.isEmpty()) |
|
2614 d->select(d->viewItems.first().index, d->viewItems.last().index, |
|
2615 QItemSelectionModel::ClearAndSelect |
|
2616 |QItemSelectionModel::Rows); |
|
2617 } |
|
2618 |
|
2619 /*! |
|
2620 \since 4.2 |
|
2621 Expands all expandable items. |
|
2622 |
|
2623 Warning: if the model contains a large number of items, |
|
2624 this function will take some time to execute. |
|
2625 |
|
2626 \sa collapseAll() expand() collapse() setExpanded() |
|
2627 */ |
|
2628 void QTreeView::expandAll() |
|
2629 { |
|
2630 Q_D(QTreeView); |
|
2631 d->viewItems.clear(); |
|
2632 d->expandedIndexes.clear(); |
|
2633 d->interruptDelayedItemsLayout(); |
|
2634 d->layout(-1); |
|
2635 for (int i = 0; i < d->viewItems.count(); ++i) { |
|
2636 if (d->viewItems[i].expanded) |
|
2637 continue; |
|
2638 d->viewItems[i].expanded = true; |
|
2639 d->layout(i); |
|
2640 QModelIndex idx = d->viewItems.at(i).index; |
|
2641 d->expandedIndexes.insert(idx); |
|
2642 } |
|
2643 updateGeometries(); |
|
2644 d->viewport->update(); |
|
2645 } |
|
2646 |
|
2647 /*! |
|
2648 \since 4.2 |
|
2649 |
|
2650 Collapses all expanded items. |
|
2651 |
|
2652 \sa expandAll() expand() collapse() setExpanded() |
|
2653 */ |
|
2654 void QTreeView::collapseAll() |
|
2655 { |
|
2656 Q_D(QTreeView); |
|
2657 d->expandedIndexes.clear(); |
|
2658 doItemsLayout(); |
|
2659 } |
|
2660 |
|
2661 /*! |
|
2662 \since 4.3 |
|
2663 Expands all expandable items to the given \a depth. |
|
2664 |
|
2665 \sa expandAll() collapseAll() expand() collapse() setExpanded() |
|
2666 */ |
|
2667 void QTreeView::expandToDepth(int depth) |
|
2668 { |
|
2669 Q_D(QTreeView); |
|
2670 d->viewItems.clear(); |
|
2671 d->expandedIndexes.clear(); |
|
2672 d->interruptDelayedItemsLayout(); |
|
2673 d->layout(-1); |
|
2674 for (int i = 0; i < d->viewItems.count(); ++i) { |
|
2675 if (d->viewItems.at(i).level <= (uint)depth) { |
|
2676 d->viewItems[i].expanded = true; |
|
2677 d->layout(i); |
|
2678 d->storeExpanded(d->viewItems.at(i).index); |
|
2679 } |
|
2680 } |
|
2681 updateGeometries(); |
|
2682 d->viewport->update(); |
|
2683 } |
|
2684 |
|
2685 /*! |
|
2686 This function is called whenever \a{column}'s size is changed in |
|
2687 the header. \a oldSize and \a newSize give the previous size and |
|
2688 the new size in pixels. |
|
2689 |
|
2690 \sa setColumnWidth() |
|
2691 */ |
|
2692 void QTreeView::columnResized(int column, int /* oldSize */, int /* newSize */) |
|
2693 { |
|
2694 Q_D(QTreeView); |
|
2695 d->columnsToUpdate.append(column); |
|
2696 if (d->columnResizeTimerID == 0) |
|
2697 d->columnResizeTimerID = startTimer(0); |
|
2698 } |
|
2699 |
|
2700 /*! |
|
2701 \reimp |
|
2702 */ |
|
2703 void QTreeView::updateGeometries() |
|
2704 { |
|
2705 Q_D(QTreeView); |
|
2706 if (d->header) { |
|
2707 if (d->geometryRecursionBlock) |
|
2708 return; |
|
2709 d->geometryRecursionBlock = true; |
|
2710 QSize hint = d->header->isHidden() ? QSize(0, 0) : d->header->sizeHint(); |
|
2711 setViewportMargins(0, hint.height(), 0, 0); |
|
2712 QRect vg = d->viewport->geometry(); |
|
2713 QRect geometryRect(vg.left(), vg.top() - hint.height(), vg.width(), hint.height()); |
|
2714 d->header->setGeometry(geometryRect); |
|
2715 //d->header->setOffset(horizontalScrollBar()->value()); // ### bug ??? |
|
2716 QMetaObject::invokeMethod(d->header, "updateGeometries"); |
|
2717 d->updateScrollBars(); |
|
2718 d->geometryRecursionBlock = false; |
|
2719 } |
|
2720 QAbstractItemView::updateGeometries(); |
|
2721 } |
|
2722 |
|
2723 /*! |
|
2724 Returns the size hint for the \a column's width or -1 if there is no |
|
2725 model. |
|
2726 |
|
2727 If you need to set the width of a given column to a fixed value, call |
|
2728 QHeaderView::resizeSection() on the view's header. |
|
2729 |
|
2730 If you reimplement this function in a subclass, note that the value you |
|
2731 return is only used when resizeColumnToContents() is called. In that case, |
|
2732 if a larger column width is required by either the view's header or |
|
2733 the item delegate, that width will be used instead. |
|
2734 |
|
2735 \sa QWidget::sizeHint, header() |
|
2736 */ |
|
2737 int QTreeView::sizeHintForColumn(int column) const |
|
2738 { |
|
2739 Q_D(const QTreeView); |
|
2740 d->executePostedLayout(); |
|
2741 if (d->viewItems.isEmpty()) |
|
2742 return -1; |
|
2743 int w = 0; |
|
2744 QStyleOptionViewItemV4 option = d->viewOptionsV4(); |
|
2745 const QVector<QTreeViewItem> viewItems = d->viewItems; |
|
2746 |
|
2747 int start = 0; |
|
2748 int end = viewItems.count(); |
|
2749 if(end > 1000) { //if we have too many item this function would be too slow. |
|
2750 //we get a good approximation by only iterate over 1000 items. |
|
2751 start = qMax(0, d->firstVisibleItem() - 100); |
|
2752 end = qMin(end, start + 900); |
|
2753 } |
|
2754 |
|
2755 for (int i = start; i < end; ++i) { |
|
2756 if (viewItems.at(i).spanning) |
|
2757 continue; // we have no good size hint |
|
2758 QModelIndex index = viewItems.at(i).index; |
|
2759 index = index.sibling(index.row(), column); |
|
2760 QWidget *editor = d->editorForIndex(index).editor; |
|
2761 if (editor && d->persistent.contains(editor)) { |
|
2762 w = qMax(w, editor->sizeHint().width()); |
|
2763 int min = editor->minimumSize().width(); |
|
2764 int max = editor->maximumSize().width(); |
|
2765 w = qBound(min, w, max); |
|
2766 } |
|
2767 int hint = d->delegateForIndex(index)->sizeHint(option, index).width(); |
|
2768 w = qMax(w, hint + (column == 0 ? d->indentationForItem(i) : 0)); |
|
2769 } |
|
2770 return w; |
|
2771 } |
|
2772 |
|
2773 /*! |
|
2774 Returns the size hint for the row indicated by \a index. |
|
2775 |
|
2776 \sa sizeHintForColumn(), uniformRowHeights() |
|
2777 */ |
|
2778 int QTreeView::indexRowSizeHint(const QModelIndex &index) const |
|
2779 { |
|
2780 Q_D(const QTreeView); |
|
2781 if (!d->isIndexValid(index) || !d->itemDelegate) |
|
2782 return 0; |
|
2783 |
|
2784 int start = -1; |
|
2785 int end = -1; |
|
2786 int count = d->header->count(); |
|
2787 bool emptyHeader = (count == 0); |
|
2788 QModelIndex parent = index.parent(); |
|
2789 |
|
2790 if (count && isVisible()) { |
|
2791 // If the sections have moved, we end up checking too many or too few |
|
2792 start = d->header->visualIndexAt(0); |
|
2793 } else { |
|
2794 // If the header has not been laid out yet, we use the model directly |
|
2795 count = d->model->columnCount(parent); |
|
2796 } |
|
2797 |
|
2798 if (isRightToLeft()) { |
|
2799 start = (start == -1 ? count - 1 : start); |
|
2800 end = 0; |
|
2801 } else { |
|
2802 start = (start == -1 ? 0 : start); |
|
2803 end = count - 1; |
|
2804 } |
|
2805 |
|
2806 if (end < start) |
|
2807 qSwap(end, start); |
|
2808 |
|
2809 int height = -1; |
|
2810 QStyleOptionViewItemV4 option = d->viewOptionsV4(); |
|
2811 // ### If we want word wrapping in the items, |
|
2812 // ### we need to go through all the columns |
|
2813 // ### and set the width of the column |
|
2814 |
|
2815 // Hack to speed up the function |
|
2816 option.rect.setWidth(-1); |
|
2817 |
|
2818 for (int column = start; column <= end; ++column) { |
|
2819 int logicalColumn = emptyHeader ? column : d->header->logicalIndex(column); |
|
2820 if (d->header->isSectionHidden(logicalColumn)) |
|
2821 continue; |
|
2822 QModelIndex idx = d->model->index(index.row(), logicalColumn, parent); |
|
2823 if (idx.isValid()) { |
|
2824 QWidget *editor = d->editorForIndex(idx).editor; |
|
2825 if (editor && d->persistent.contains(editor)) { |
|
2826 height = qMax(height, editor->sizeHint().height()); |
|
2827 int min = editor->minimumSize().height(); |
|
2828 int max = editor->maximumSize().height(); |
|
2829 height = qBound(min, height, max); |
|
2830 } |
|
2831 int hint = d->delegateForIndex(idx)->sizeHint(option, idx).height(); |
|
2832 height = qMax(height, hint); |
|
2833 } |
|
2834 } |
|
2835 |
|
2836 return height; |
|
2837 } |
|
2838 |
|
2839 /*! |
|
2840 \since 4.3 |
|
2841 Returns the height of the row indicated by the given \a index. |
|
2842 \sa indexRowSizeHint() |
|
2843 */ |
|
2844 int QTreeView::rowHeight(const QModelIndex &index) const |
|
2845 { |
|
2846 Q_D(const QTreeView); |
|
2847 d->executePostedLayout(); |
|
2848 int i = d->viewIndex(index); |
|
2849 if (i == -1) |
|
2850 return 0; |
|
2851 return d->itemHeight(i); |
|
2852 } |
|
2853 |
|
2854 /*! |
|
2855 \internal |
|
2856 */ |
|
2857 void QTreeView::horizontalScrollbarAction(int action) |
|
2858 { |
|
2859 QAbstractItemView::horizontalScrollbarAction(action); |
|
2860 } |
|
2861 |
|
2862 /*! |
|
2863 \reimp |
|
2864 */ |
|
2865 bool QTreeView::isIndexHidden(const QModelIndex &index) const |
|
2866 { |
|
2867 return (isColumnHidden(index.column()) || isRowHidden(index.row(), index.parent())); |
|
2868 } |
|
2869 |
|
2870 /* |
|
2871 private implementation |
|
2872 */ |
|
2873 void QTreeViewPrivate::initialize() |
|
2874 { |
|
2875 Q_Q(QTreeView); |
|
2876 updateStyledFrameWidths(); |
|
2877 q->setSelectionBehavior(QAbstractItemView::SelectRows); |
|
2878 q->setSelectionMode(QAbstractItemView::SingleSelection); |
|
2879 q->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); |
|
2880 q->setAttribute(Qt::WA_MacShowFocusRect); |
|
2881 |
|
2882 QHeaderView *header = new QHeaderView(Qt::Horizontal, q); |
|
2883 header->setMovable(true); |
|
2884 header->setStretchLastSection(true); |
|
2885 header->setDefaultAlignment(Qt::AlignLeft|Qt::AlignVCenter); |
|
2886 q->setHeader(header); |
|
2887 #ifndef QT_NO_ANIMATION |
|
2888 QObject::connect(&animatedOperation, SIGNAL(finished()), q, SLOT(_q_endAnimatedOperation())); |
|
2889 #endif //QT_NO_ANIMATION |
|
2890 } |
|
2891 |
|
2892 void QTreeViewPrivate::expand(int item, bool emitSignal) |
|
2893 { |
|
2894 Q_Q(QTreeView); |
|
2895 |
|
2896 if (item == -1 || viewItems.at(item).expanded) |
|
2897 return; |
|
2898 |
|
2899 #ifndef QT_NO_ANIMATION |
|
2900 if (emitSignal && animationsEnabled) |
|
2901 prepareAnimatedOperation(item, QVariantAnimation::Forward); |
|
2902 #endif //QT_NO_ANIMATION |
|
2903 QAbstractItemView::State oldState = state; |
|
2904 q->setState(QAbstractItemView::ExpandingState); |
|
2905 const QModelIndex index = viewItems.at(item).index; |
|
2906 storeExpanded(index); |
|
2907 viewItems[item].expanded = true; |
|
2908 layout(item); |
|
2909 q->setState(oldState); |
|
2910 |
|
2911 if (model->canFetchMore(index)) |
|
2912 model->fetchMore(index); |
|
2913 if (emitSignal) { |
|
2914 emit q->expanded(index); |
|
2915 #ifndef QT_NO_ANIMATION |
|
2916 if (animationsEnabled) |
|
2917 beginAnimatedOperation(); |
|
2918 #endif //QT_NO_ANIMATION |
|
2919 } |
|
2920 } |
|
2921 |
|
2922 void QTreeViewPrivate::collapse(int item, bool emitSignal) |
|
2923 { |
|
2924 Q_Q(QTreeView); |
|
2925 |
|
2926 if (item == -1 || expandedIndexes.isEmpty()) |
|
2927 return; |
|
2928 |
|
2929 //if the current item is now invisible, the autoscroll will expand the tree to see it, so disable the autoscroll |
|
2930 delayedAutoScroll.stop(); |
|
2931 |
|
2932 int total = viewItems.at(item).total; |
|
2933 const QModelIndex &modelIndex = viewItems.at(item).index; |
|
2934 if (!isPersistent(modelIndex)) |
|
2935 return; // if the index is not persistent, no chances it is expanded |
|
2936 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.find(modelIndex); |
|
2937 if (it == expandedIndexes.end() || viewItems.at(item).expanded == false) |
|
2938 return; // nothing to do |
|
2939 |
|
2940 #ifndef QT_NO_ANIMATION |
|
2941 if (emitSignal && animationsEnabled) |
|
2942 prepareAnimatedOperation(item, QVariantAnimation::Backward); |
|
2943 #endif //QT_NO_ANIMATION |
|
2944 |
|
2945 QAbstractItemView::State oldState = state; |
|
2946 q->setState(QAbstractItemView::CollapsingState); |
|
2947 expandedIndexes.erase(it); |
|
2948 viewItems[item].expanded = false; |
|
2949 int index = item; |
|
2950 QModelIndex parent = modelIndex; |
|
2951 while (parent.isValid() && parent != root) { |
|
2952 Q_ASSERT(index > -1); |
|
2953 viewItems[index].total -= total; |
|
2954 parent = parent.parent(); |
|
2955 index = viewIndex(parent); |
|
2956 } |
|
2957 viewItems.remove(item + 1, total); // collapse |
|
2958 q->setState(oldState); |
|
2959 |
|
2960 if (emitSignal) { |
|
2961 emit q->collapsed(modelIndex); |
|
2962 #ifndef QT_NO_ANIMATION |
|
2963 if (animationsEnabled) |
|
2964 beginAnimatedOperation(); |
|
2965 #endif //QT_NO_ANIMATION |
|
2966 } |
|
2967 } |
|
2968 |
|
2969 #ifndef QT_NO_ANIMATION |
|
2970 void QTreeViewPrivate::prepareAnimatedOperation(int item, QVariantAnimation::Direction direction) |
|
2971 { |
|
2972 animatedOperation.item = item; |
|
2973 animatedOperation.viewport = viewport; |
|
2974 animatedOperation.setDirection(direction); |
|
2975 |
|
2976 int top = coordinateForItem(item) + itemHeight(item); |
|
2977 QRect rect = viewport->rect(); |
|
2978 rect.setTop(top); |
|
2979 if (direction == QVariantAnimation::Backward) { |
|
2980 const int limit = rect.height() * 2; |
|
2981 int h = 0; |
|
2982 int c = item + viewItems.at(item).total + 1; |
|
2983 for (int i = item + 1; i < c && h < limit; ++i) |
|
2984 h += itemHeight(i); |
|
2985 rect.setHeight(h); |
|
2986 animatedOperation.setEndValue(top + h); |
|
2987 } |
|
2988 animatedOperation.setStartValue(top); |
|
2989 animatedOperation.before = renderTreeToPixmapForAnimation(rect); |
|
2990 } |
|
2991 |
|
2992 void QTreeViewPrivate::beginAnimatedOperation() |
|
2993 { |
|
2994 Q_Q(QTreeView); |
|
2995 |
|
2996 QRect rect = viewport->rect(); |
|
2997 rect.setTop(animatedOperation.top()); |
|
2998 if (animatedOperation.direction() == QVariantAnimation::Forward) { |
|
2999 const int limit = rect.height() * 2; |
|
3000 int h = 0; |
|
3001 int c = animatedOperation.item + viewItems.at(animatedOperation.item).total + 1; |
|
3002 for (int i = animatedOperation.item + 1; i < c && h < limit; ++i) |
|
3003 h += itemHeight(i); |
|
3004 rect.setHeight(h); |
|
3005 animatedOperation.setEndValue(animatedOperation.top() + h); |
|
3006 } |
|
3007 |
|
3008 if (!rect.isEmpty()) { |
|
3009 animatedOperation.after = renderTreeToPixmapForAnimation(rect); |
|
3010 |
|
3011 q->setState(QAbstractItemView::AnimatingState); |
|
3012 animatedOperation.start(); //let's start the animation |
|
3013 } |
|
3014 } |
|
3015 |
|
3016 void QTreeViewPrivate::drawAnimatedOperation(QPainter *painter) const |
|
3017 { |
|
3018 const int start = animatedOperation.startValue().toInt(), |
|
3019 end = animatedOperation.endValue().toInt(), |
|
3020 current = animatedOperation.currentValue().toInt(); |
|
3021 bool collapsing = animatedOperation.direction() == QVariantAnimation::Backward; |
|
3022 const QPixmap top = collapsing ? animatedOperation.before : animatedOperation.after; |
|
3023 painter->drawPixmap(0, start, top, 0, end - current - 1, top.width(), top.height()); |
|
3024 const QPixmap bottom = collapsing ? animatedOperation.after : animatedOperation.before; |
|
3025 painter->drawPixmap(0, current, bottom); |
|
3026 } |
|
3027 |
|
3028 QPixmap QTreeViewPrivate::renderTreeToPixmapForAnimation(const QRect &rect) const |
|
3029 { |
|
3030 Q_Q(const QTreeView); |
|
3031 QPixmap pixmap(rect.size()); |
|
3032 pixmap.fill(Qt::transparent); //the base might not be opaque, and we don't want uninitialized pixels. |
|
3033 QPainter painter(&pixmap); |
|
3034 painter.fillRect(QRect(QPoint(0,0), rect.size()), q->palette().base()); |
|
3035 painter.translate(0, -rect.top()); |
|
3036 q->drawTree(&painter, QRegion(rect)); |
|
3037 painter.end(); |
|
3038 |
|
3039 //and now let's render the editors the editors |
|
3040 QStyleOptionViewItemV4 option = viewOptionsV4(); |
|
3041 for (QList<QEditorInfo>::const_iterator it = editors.constBegin(); it != editors.constEnd(); ++it) { |
|
3042 QWidget *editor = it->editor; |
|
3043 QModelIndex index = it->index; |
|
3044 option.rect = q->visualRect(index); |
|
3045 if (option.rect.isValid()) { |
|
3046 |
|
3047 if (QAbstractItemDelegate *delegate = delegateForIndex(index)) |
|
3048 delegate->updateEditorGeometry(editor, option, index); |
|
3049 |
|
3050 const QPoint pos = editor->pos(); |
|
3051 if (rect.contains(pos)) { |
|
3052 editor->render(&pixmap, pos - rect.topLeft()); |
|
3053 //the animation uses pixmap to display the treeview's content |
|
3054 //the editor is rendered on this pixmap and thus can (should) be hidden |
|
3055 editor->hide(); |
|
3056 } |
|
3057 } |
|
3058 } |
|
3059 |
|
3060 |
|
3061 return pixmap; |
|
3062 } |
|
3063 |
|
3064 void QTreeViewPrivate::_q_endAnimatedOperation() |
|
3065 { |
|
3066 Q_Q(QTreeView); |
|
3067 q->setState(QAbstractItemView::NoState); |
|
3068 q->updateGeometries(); |
|
3069 viewport->update(); |
|
3070 } |
|
3071 #endif //QT_NO_ANIMATION |
|
3072 |
|
3073 void QTreeViewPrivate::_q_modelAboutToBeReset() |
|
3074 { |
|
3075 viewItems.clear(); |
|
3076 } |
|
3077 |
|
3078 void QTreeViewPrivate::_q_columnsAboutToBeRemoved(const QModelIndex &parent, int start, int end) |
|
3079 { |
|
3080 if (start <= 0 && 0 <= end) |
|
3081 viewItems.clear(); |
|
3082 QAbstractItemViewPrivate::_q_columnsAboutToBeRemoved(parent, start, end); |
|
3083 } |
|
3084 |
|
3085 void QTreeViewPrivate::_q_columnsRemoved(const QModelIndex &parent, int start, int end) |
|
3086 { |
|
3087 if (start <= 0 && 0 <= end) |
|
3088 doDelayedItemsLayout(); |
|
3089 QAbstractItemViewPrivate::_q_columnsRemoved(parent, start, end); |
|
3090 } |
|
3091 |
|
3092 void QTreeViewPrivate::layout(int i) |
|
3093 { |
|
3094 Q_Q(QTreeView); |
|
3095 QModelIndex current; |
|
3096 QModelIndex parent = (i < 0) ? (QModelIndex)root : modelIndex(i); |
|
3097 |
|
3098 if (i>=0 && !parent.isValid()) { |
|
3099 //modelIndex() should never return something invalid for the real items. |
|
3100 //This can happen if columncount has been set to 0. |
|
3101 //To avoid infinite loop we stop here. |
|
3102 return; |
|
3103 } |
|
3104 |
|
3105 int count = 0; |
|
3106 if (model->hasChildren(parent)) { |
|
3107 if (model->canFetchMore(parent)) |
|
3108 model->fetchMore(parent); |
|
3109 count = model->rowCount(parent); |
|
3110 } |
|
3111 |
|
3112 bool expanding = true; |
|
3113 if (i == -1) { |
|
3114 if (uniformRowHeights) { |
|
3115 QModelIndex index = model->index(0, 0, parent); |
|
3116 defaultItemHeight = q->indexRowSizeHint(index); |
|
3117 } |
|
3118 viewItems.resize(count); |
|
3119 } else if (viewItems[i].total != (uint)count) { |
|
3120 viewItems.insert(i + 1, count, QTreeViewItem()); // expand |
|
3121 } else { |
|
3122 expanding = false; |
|
3123 } |
|
3124 |
|
3125 int first = i + 1; |
|
3126 int level = (i >= 0 ? viewItems.at(i).level + 1 : 0); |
|
3127 int hidden = 0; |
|
3128 int last = 0; |
|
3129 int children = 0; |
|
3130 |
|
3131 for (int j = first; j < first + count; ++j) { |
|
3132 current = model->index(j - first, 0, parent); |
|
3133 if (isRowHidden(current)) { |
|
3134 ++hidden; |
|
3135 last = j - hidden + children; |
|
3136 } else { |
|
3137 last = j - hidden + children; |
|
3138 viewItems[last].index = current; |
|
3139 viewItems[last].level = level; |
|
3140 viewItems[last].height = 0; |
|
3141 viewItems[last].spanning = q->isFirstColumnSpanned(current.row(), parent); |
|
3142 viewItems[last].expanded = false; |
|
3143 viewItems[last].total = 0; |
|
3144 if (isIndexExpanded(current)) { |
|
3145 viewItems[last].expanded = true; |
|
3146 layout(last); |
|
3147 children += viewItems[last].total; |
|
3148 last = j - hidden + children; |
|
3149 } |
|
3150 } |
|
3151 } |
|
3152 |
|
3153 // remove hidden items |
|
3154 if (hidden > 0) |
|
3155 viewItems.remove(last + 1, hidden); // collapse |
|
3156 |
|
3157 if (!expanding) |
|
3158 return; // nothing changed |
|
3159 |
|
3160 while (parent != root) { |
|
3161 Q_ASSERT(i > -1); |
|
3162 viewItems[i].total += count - hidden; |
|
3163 parent = parent.parent(); |
|
3164 i = viewIndex(parent); |
|
3165 } |
|
3166 } |
|
3167 |
|
3168 int QTreeViewPrivate::pageUp(int i) const |
|
3169 { |
|
3170 int index = itemAtCoordinate(coordinateForItem(i) - viewport->height()); |
|
3171 return index == -1 ? 0 : index; |
|
3172 } |
|
3173 |
|
3174 int QTreeViewPrivate::pageDown(int i) const |
|
3175 { |
|
3176 int index = itemAtCoordinate(coordinateForItem(i) + viewport->height()); |
|
3177 return index == -1 ? viewItems.count() - 1 : index; |
|
3178 } |
|
3179 |
|
3180 int QTreeViewPrivate::indentationForItem(int item) const |
|
3181 { |
|
3182 if (item < 0 || item >= viewItems.count()) |
|
3183 return 0; |
|
3184 int level = viewItems.at(item).level; |
|
3185 if (rootDecoration) |
|
3186 ++level; |
|
3187 return level * indent; |
|
3188 } |
|
3189 |
|
3190 int QTreeViewPrivate::itemHeight(int item) const |
|
3191 { |
|
3192 if (uniformRowHeights) |
|
3193 return defaultItemHeight; |
|
3194 if (viewItems.isEmpty()) |
|
3195 return 0; |
|
3196 const QModelIndex &index = viewItems.at(item).index; |
|
3197 int height = viewItems.at(item).height; |
|
3198 if (height <= 0 && index.isValid()) { |
|
3199 height = q_func()->indexRowSizeHint(index); |
|
3200 viewItems[item].height = height; |
|
3201 } |
|
3202 if (!index.isValid() || height < 0) |
|
3203 return 0; |
|
3204 return height; |
|
3205 } |
|
3206 |
|
3207 |
|
3208 /*! |
|
3209 \internal |
|
3210 Returns the viewport y coordinate for \a item. |
|
3211 */ |
|
3212 int QTreeViewPrivate::coordinateForItem(int item) const |
|
3213 { |
|
3214 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { |
|
3215 if (uniformRowHeights) |
|
3216 return (item * defaultItemHeight) - vbar->value(); |
|
3217 // ### optimize (spans or caching) |
|
3218 int y = 0; |
|
3219 for (int i = 0; i < viewItems.count(); ++i) { |
|
3220 if (i == item) |
|
3221 return y - vbar->value(); |
|
3222 y += itemHeight(i); |
|
3223 } |
|
3224 } else { // ScrollPerItem |
|
3225 int topViewItemIndex = vbar->value(); |
|
3226 if (uniformRowHeights) |
|
3227 return defaultItemHeight * (item - topViewItemIndex); |
|
3228 if (item >= topViewItemIndex) { |
|
3229 // search in the visible area first and continue down |
|
3230 // ### slow if the item is not visible |
|
3231 int viewItemCoordinate = 0; |
|
3232 int viewItemIndex = topViewItemIndex; |
|
3233 while (viewItemIndex < viewItems.count()) { |
|
3234 if (viewItemIndex == item) |
|
3235 return viewItemCoordinate; |
|
3236 viewItemCoordinate += itemHeight(viewItemIndex); |
|
3237 ++viewItemIndex; |
|
3238 } |
|
3239 // below the last item in the view |
|
3240 Q_ASSERT(false); |
|
3241 return viewItemCoordinate; |
|
3242 } else { |
|
3243 // search the area above the viewport (used for editor widgets) |
|
3244 int viewItemCoordinate = 0; |
|
3245 for (int viewItemIndex = topViewItemIndex; viewItemIndex > 0; --viewItemIndex) { |
|
3246 if (viewItemIndex == item) |
|
3247 return viewItemCoordinate; |
|
3248 viewItemCoordinate -= itemHeight(viewItemIndex - 1); |
|
3249 } |
|
3250 return viewItemCoordinate; |
|
3251 } |
|
3252 } |
|
3253 return 0; |
|
3254 } |
|
3255 |
|
3256 /*! |
|
3257 \internal |
|
3258 Returns the index of the view item at the |
|
3259 given viewport \a coordinate. |
|
3260 |
|
3261 \sa modelIndex() |
|
3262 */ |
|
3263 int QTreeViewPrivate::itemAtCoordinate(int coordinate) const |
|
3264 { |
|
3265 const int itemCount = viewItems.count(); |
|
3266 if (itemCount == 0) |
|
3267 return -1; |
|
3268 if (uniformRowHeights && defaultItemHeight <= 0) |
|
3269 return -1; |
|
3270 if (verticalScrollMode == QAbstractItemView::ScrollPerPixel) { |
|
3271 if (uniformRowHeights) { |
|
3272 const int viewItemIndex = (coordinate + vbar->value()) / defaultItemHeight; |
|
3273 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); |
|
3274 } |
|
3275 // ### optimize |
|
3276 int viewItemCoordinate = 0; |
|
3277 const int contentsCoordinate = coordinate + vbar->value(); |
|
3278 for (int viewItemIndex = 0; viewItemIndex < viewItems.count(); ++viewItemIndex) { |
|
3279 viewItemCoordinate += itemHeight(viewItemIndex); |
|
3280 if (viewItemCoordinate >= contentsCoordinate) |
|
3281 return (viewItemIndex >= itemCount ? -1 : viewItemIndex); |
|
3282 } |
|
3283 } else { // ScrollPerItem |
|
3284 int topViewItemIndex = vbar->value(); |
|
3285 if (uniformRowHeights) { |
|
3286 if (coordinate < 0) |
|
3287 coordinate -= defaultItemHeight - 1; |
|
3288 const int viewItemIndex = topViewItemIndex + (coordinate / defaultItemHeight); |
|
3289 return ((viewItemIndex >= itemCount || viewItemIndex < 0) ? -1 : viewItemIndex); |
|
3290 } |
|
3291 if (coordinate >= 0) { |
|
3292 // the coordinate is in or below the viewport |
|
3293 int viewItemCoordinate = 0; |
|
3294 for (int viewItemIndex = topViewItemIndex; viewItemIndex < viewItems.count(); ++viewItemIndex) { |
|
3295 viewItemCoordinate += itemHeight(viewItemIndex); |
|
3296 if (viewItemCoordinate > coordinate) |
|
3297 return (viewItemIndex >= itemCount ? -1 : viewItemIndex); |
|
3298 } |
|
3299 } else { |
|
3300 // the coordinate is above the viewport |
|
3301 int viewItemCoordinate = 0; |
|
3302 for (int viewItemIndex = topViewItemIndex; viewItemIndex >= 0; --viewItemIndex) { |
|
3303 if (viewItemCoordinate <= coordinate) |
|
3304 return (viewItemIndex >= itemCount ? -1 : viewItemIndex); |
|
3305 viewItemCoordinate -= itemHeight(viewItemIndex); |
|
3306 } |
|
3307 } |
|
3308 } |
|
3309 return -1; |
|
3310 } |
|
3311 |
|
3312 int QTreeViewPrivate::viewIndex(const QModelIndex &_index) const |
|
3313 { |
|
3314 if (!_index.isValid() || viewItems.isEmpty()) |
|
3315 return -1; |
|
3316 |
|
3317 const int totalCount = viewItems.count(); |
|
3318 const QModelIndex index = _index.sibling(_index.row(), 0); |
|
3319 |
|
3320 |
|
3321 // A quick check near the last item to see if we are just incrementing |
|
3322 const int start = lastViewedItem > 2 ? lastViewedItem - 2 : 0; |
|
3323 const int end = lastViewedItem < totalCount - 2 ? lastViewedItem + 2 : totalCount; |
|
3324 int row = index.row(); |
|
3325 for (int i = start; i < end; ++i) { |
|
3326 const QModelIndex &idx = viewItems.at(i).index; |
|
3327 if (idx.row() == row) { |
|
3328 if (idx.internalId() == index.internalId()) { |
|
3329 lastViewedItem = i; |
|
3330 return i; |
|
3331 } |
|
3332 } |
|
3333 } |
|
3334 |
|
3335 // NOTE: this function is slow if the item is outside the visible area |
|
3336 // search in visible items first and below |
|
3337 int t = firstVisibleItem(); |
|
3338 t = t > 100 ? t - 100 : 0; // start 100 items above the visible area |
|
3339 |
|
3340 for (int i = t; i < totalCount; ++i) { |
|
3341 const QModelIndex &idx = viewItems.at(i).index; |
|
3342 if (idx.row() == row) { |
|
3343 if (idx.internalId() == index.internalId()) { |
|
3344 lastViewedItem = i; |
|
3345 return i; |
|
3346 } |
|
3347 } |
|
3348 } |
|
3349 // search from top to first visible |
|
3350 for (int j = 0; j < t; ++j) { |
|
3351 const QModelIndex &idx = viewItems.at(j).index; |
|
3352 if (idx.row() == row) { |
|
3353 if (idx.internalId() == index.internalId()) { |
|
3354 lastViewedItem = j; |
|
3355 return j; |
|
3356 } |
|
3357 } |
|
3358 } |
|
3359 // nothing found |
|
3360 return -1; |
|
3361 } |
|
3362 |
|
3363 QModelIndex QTreeViewPrivate::modelIndex(int i, int column) const |
|
3364 { |
|
3365 if (i < 0 || i >= viewItems.count()) |
|
3366 return QModelIndex(); |
|
3367 |
|
3368 QModelIndex ret = viewItems.at(i).index; |
|
3369 if (column) |
|
3370 ret = ret.sibling(ret.row(), column); |
|
3371 return ret; |
|
3372 } |
|
3373 |
|
3374 int QTreeViewPrivate::firstVisibleItem(int *offset) const |
|
3375 { |
|
3376 const int value = vbar->value(); |
|
3377 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { |
|
3378 if (offset) |
|
3379 *offset = 0; |
|
3380 return (value < 0 || value >= viewItems.count()) ? -1 : value; |
|
3381 } |
|
3382 // ScrollMode == ScrollPerPixel |
|
3383 if (uniformRowHeights) { |
|
3384 if (!defaultItemHeight) |
|
3385 return -1; |
|
3386 |
|
3387 if (offset) |
|
3388 *offset = -(value % defaultItemHeight); |
|
3389 return value / defaultItemHeight; |
|
3390 } |
|
3391 int y = 0; // ### optimize (use spans ?) |
|
3392 for (int i = 0; i < viewItems.count(); ++i) { |
|
3393 y += itemHeight(i); // the height value is cached |
|
3394 if (y > value) { |
|
3395 if (offset) |
|
3396 *offset = y - value - itemHeight(i); |
|
3397 return i; |
|
3398 } |
|
3399 } |
|
3400 return -1; |
|
3401 } |
|
3402 |
|
3403 int QTreeViewPrivate::columnAt(int x) const |
|
3404 { |
|
3405 return header->logicalIndexAt(x); |
|
3406 } |
|
3407 |
|
3408 void QTreeViewPrivate::relayout(const QModelIndex &parent) |
|
3409 { |
|
3410 Q_Q(QTreeView); |
|
3411 // do a local relayout of the items |
|
3412 if (parent.isValid()) { |
|
3413 int parentViewIndex = viewIndex(parent); |
|
3414 if (parentViewIndex > -1 && viewItems.at(parentViewIndex).expanded) { |
|
3415 collapse(parentViewIndex, false); // remove the current layout |
|
3416 expand(parentViewIndex, false); // do the relayout |
|
3417 q->updateGeometries(); |
|
3418 viewport->update(); |
|
3419 } |
|
3420 } else { |
|
3421 viewItems.clear(); |
|
3422 q->doItemsLayout(); |
|
3423 } |
|
3424 } |
|
3425 |
|
3426 |
|
3427 void QTreeViewPrivate::updateScrollBars() |
|
3428 { |
|
3429 Q_Q(QTreeView); |
|
3430 QSize viewportSize = viewport->size(); |
|
3431 if (!viewportSize.isValid()) |
|
3432 viewportSize = QSize(0, 0); |
|
3433 |
|
3434 int itemsInViewport = 0; |
|
3435 if (uniformRowHeights) { |
|
3436 if (defaultItemHeight <= 0) |
|
3437 itemsInViewport = viewItems.count(); |
|
3438 else |
|
3439 itemsInViewport = viewportSize.height() / defaultItemHeight; |
|
3440 } else { |
|
3441 const int itemsCount = viewItems.count(); |
|
3442 const int viewportHeight = viewportSize.height(); |
|
3443 for (int height = 0, item = itemsCount - 1; item >= 0; --item) { |
|
3444 height += itemHeight(item); |
|
3445 if (height > viewportHeight) |
|
3446 break; |
|
3447 ++itemsInViewport; |
|
3448 } |
|
3449 } |
|
3450 if (verticalScrollMode == QAbstractItemView::ScrollPerItem) { |
|
3451 if (!viewItems.isEmpty()) |
|
3452 itemsInViewport = qMax(1, itemsInViewport); |
|
3453 vbar->setRange(0, viewItems.count() - itemsInViewport); |
|
3454 vbar->setPageStep(itemsInViewport); |
|
3455 vbar->setSingleStep(1); |
|
3456 } else { // scroll per pixel |
|
3457 int contentsHeight = 0; |
|
3458 if (uniformRowHeights) { |
|
3459 contentsHeight = defaultItemHeight * viewItems.count(); |
|
3460 } else { // ### optimize (spans or caching) |
|
3461 for (int i = 0; i < viewItems.count(); ++i) |
|
3462 contentsHeight += itemHeight(i); |
|
3463 } |
|
3464 vbar->setRange(0, contentsHeight - viewportSize.height()); |
|
3465 vbar->setPageStep(viewportSize.height()); |
|
3466 vbar->setSingleStep(qMax(viewportSize.height() / (itemsInViewport + 1), 2)); |
|
3467 } |
|
3468 |
|
3469 const int columnCount = header->count(); |
|
3470 const int viewportWidth = viewportSize.width(); |
|
3471 int columnsInViewport = 0; |
|
3472 for (int width = 0, column = columnCount - 1; column >= 0; --column) { |
|
3473 int logical = header->logicalIndex(column); |
|
3474 width += header->sectionSize(logical); |
|
3475 if (width > viewportWidth) |
|
3476 break; |
|
3477 ++columnsInViewport; |
|
3478 } |
|
3479 if (columnCount > 0) |
|
3480 columnsInViewport = qMax(1, columnsInViewport); |
|
3481 if (horizontalScrollMode == QAbstractItemView::ScrollPerItem) { |
|
3482 hbar->setRange(0, columnCount - columnsInViewport); |
|
3483 hbar->setPageStep(columnsInViewport); |
|
3484 hbar->setSingleStep(1); |
|
3485 } else { // scroll per pixel |
|
3486 const int horizontalLength = header->length(); |
|
3487 const QSize maxSize = q->maximumViewportSize(); |
|
3488 if (maxSize.width() >= horizontalLength && vbar->maximum() <= 0) |
|
3489 viewportSize = maxSize; |
|
3490 hbar->setPageStep(viewportSize.width()); |
|
3491 hbar->setRange(0, qMax(horizontalLength - viewportSize.width(), 0)); |
|
3492 hbar->setSingleStep(qMax(viewportSize.width() / (columnsInViewport + 1), 2)); |
|
3493 } |
|
3494 } |
|
3495 |
|
3496 int QTreeViewPrivate::itemDecorationAt(const QPoint &pos) const |
|
3497 { |
|
3498 executePostedLayout(); |
|
3499 int x = pos.x(); |
|
3500 int column = header->logicalIndexAt(x); |
|
3501 if (column != 0) |
|
3502 return -1; // no logical index at x |
|
3503 |
|
3504 int viewItemIndex = itemAtCoordinate(pos.y()); |
|
3505 QRect returning = itemDecorationRect(modelIndex(viewItemIndex)); |
|
3506 if (!returning.contains(pos)) |
|
3507 return -1; |
|
3508 |
|
3509 return viewItemIndex; |
|
3510 } |
|
3511 |
|
3512 QRect QTreeViewPrivate::itemDecorationRect(const QModelIndex &index) const |
|
3513 { |
|
3514 Q_Q(const QTreeView); |
|
3515 if (!rootDecoration && index.parent() == root) |
|
3516 return QRect(); // no decoration at root |
|
3517 |
|
3518 int viewItemIndex = viewIndex(index); |
|
3519 if (viewItemIndex < 0 || !hasVisibleChildren(viewItems.at(viewItemIndex).index)) |
|
3520 return QRect(); |
|
3521 |
|
3522 int itemIndentation = indentationForItem(viewItemIndex); |
|
3523 int position = header->sectionViewportPosition(0); |
|
3524 int size = header->sectionSize(0); |
|
3525 |
|
3526 QRect rect; |
|
3527 if (q->isRightToLeft()) |
|
3528 rect = QRect(position + size - itemIndentation, coordinateForItem(viewItemIndex), |
|
3529 indent, itemHeight(viewItemIndex)); |
|
3530 else |
|
3531 rect = QRect(position + itemIndentation - indent, coordinateForItem(viewItemIndex), |
|
3532 indent, itemHeight(viewItemIndex)); |
|
3533 QStyleOption opt; |
|
3534 opt.initFrom(q); |
|
3535 opt.rect = rect; |
|
3536 return q->style()->subElementRect(QStyle::SE_TreeViewDisclosureItem, &opt, q); |
|
3537 } |
|
3538 |
|
3539 QList<QPair<int, int> > QTreeViewPrivate::columnRanges(const QModelIndex &topIndex, |
|
3540 const QModelIndex &bottomIndex) const |
|
3541 { |
|
3542 const int topVisual = header->visualIndex(topIndex.column()), |
|
3543 bottomVisual = header->visualIndex(bottomIndex.column()); |
|
3544 |
|
3545 const int start = qMin(topVisual, bottomVisual); |
|
3546 const int end = qMax(topVisual, bottomVisual); |
|
3547 |
|
3548 QList<int> logicalIndexes; |
|
3549 |
|
3550 //we iterate over the visual indexes to get the logical indexes |
|
3551 for (int c = start; c <= end; c++) { |
|
3552 const int logical = header->logicalIndex(c); |
|
3553 if (!header->isSectionHidden(logical)) { |
|
3554 logicalIndexes << logical; |
|
3555 } |
|
3556 } |
|
3557 //let's sort the list |
|
3558 qSort(logicalIndexes.begin(), logicalIndexes.end()); |
|
3559 |
|
3560 QList<QPair<int, int> > ret; |
|
3561 QPair<int, int> current; |
|
3562 current.first = -2; // -1 is not enough because -1+1 = 0 |
|
3563 current.second = -2; |
|
3564 for(int i = 0; i < logicalIndexes.count(); ++i) { |
|
3565 const int logicalColumn = logicalIndexes.at(i); |
|
3566 if (current.second + 1 != logicalColumn) { |
|
3567 if (current.first != -2) { |
|
3568 //let's save the current one |
|
3569 ret += current; |
|
3570 } |
|
3571 //let's start a new one |
|
3572 current.first = current.second = logicalColumn; |
|
3573 } else { |
|
3574 current.second++; |
|
3575 } |
|
3576 } |
|
3577 |
|
3578 //let's get the last range |
|
3579 if (current.first != -2) { |
|
3580 ret += current; |
|
3581 } |
|
3582 |
|
3583 return ret; |
|
3584 } |
|
3585 |
|
3586 void QTreeViewPrivate::select(const QModelIndex &topIndex, const QModelIndex &bottomIndex, |
|
3587 QItemSelectionModel::SelectionFlags command) |
|
3588 { |
|
3589 Q_Q(QTreeView); |
|
3590 QItemSelection selection; |
|
3591 const int top = viewIndex(topIndex), |
|
3592 bottom = viewIndex(bottomIndex); |
|
3593 |
|
3594 const QList< QPair<int, int> > colRanges = columnRanges(topIndex, bottomIndex); |
|
3595 QList< QPair<int, int> >::const_iterator it; |
|
3596 for (it = colRanges.begin(); it != colRanges.end(); ++it) { |
|
3597 const int left = (*it).first, |
|
3598 right = (*it).second; |
|
3599 |
|
3600 QModelIndex previous; |
|
3601 QItemSelectionRange currentRange; |
|
3602 QStack<QItemSelectionRange> rangeStack; |
|
3603 for (int i = top; i <= bottom; ++i) { |
|
3604 QModelIndex index = modelIndex(i); |
|
3605 QModelIndex parent = index.parent(); |
|
3606 QModelIndex previousParent = previous.parent(); |
|
3607 if (previous.isValid() && parent == previousParent) { |
|
3608 // same parent |
|
3609 if (qAbs(previous.row() - index.row()) > 1) { |
|
3610 //a hole (hidden index inside a range) has been detected |
|
3611 if (currentRange.isValid()) { |
|
3612 selection.append(currentRange); |
|
3613 } |
|
3614 //let's start a new range |
|
3615 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); |
|
3616 } else { |
|
3617 QModelIndex tl = model->index(currentRange.top(), currentRange.left(), |
|
3618 currentRange.parent()); |
|
3619 currentRange = QItemSelectionRange(tl, index.sibling(index.row(), right)); |
|
3620 } |
|
3621 } else if (previous.isValid() && parent == model->index(previous.row(), 0, previousParent)) { |
|
3622 // item is child of previous |
|
3623 rangeStack.push(currentRange); |
|
3624 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); |
|
3625 } else { |
|
3626 if (currentRange.isValid()) |
|
3627 selection.append(currentRange); |
|
3628 if (rangeStack.isEmpty()) { |
|
3629 currentRange = QItemSelectionRange(index.sibling(index.row(), left), index.sibling(index.row(), right)); |
|
3630 } else { |
|
3631 currentRange = rangeStack.pop(); |
|
3632 index = currentRange.bottomRight(); //let's resume the range |
|
3633 --i; //we process again the current item |
|
3634 } |
|
3635 } |
|
3636 previous = index; |
|
3637 } |
|
3638 if (currentRange.isValid()) |
|
3639 selection.append(currentRange); |
|
3640 for (int i = 0; i < rangeStack.count(); ++i) |
|
3641 selection.append(rangeStack.at(i)); |
|
3642 } |
|
3643 q->selectionModel()->select(selection, command); |
|
3644 } |
|
3645 |
|
3646 QPair<int,int> QTreeViewPrivate::startAndEndColumns(const QRect &rect) const |
|
3647 { |
|
3648 Q_Q(const QTreeView); |
|
3649 int start = header->visualIndexAt(rect.left()); |
|
3650 int end = header->visualIndexAt(rect.right()); |
|
3651 if (q->isRightToLeft()) { |
|
3652 start = (start == -1 ? header->count() - 1 : start); |
|
3653 end = (end == -1 ? 0 : end); |
|
3654 } else { |
|
3655 start = (start == -1 ? 0 : start); |
|
3656 end = (end == -1 ? header->count() - 1 : end); |
|
3657 } |
|
3658 return qMakePair<int,int>(qMin(start, end), qMax(start, end)); |
|
3659 } |
|
3660 |
|
3661 bool QTreeViewPrivate::hasVisibleChildren(const QModelIndex& parent) const |
|
3662 { |
|
3663 Q_Q(const QTreeView); |
|
3664 if (model->hasChildren(parent)) { |
|
3665 if (hiddenIndexes.isEmpty()) |
|
3666 return true; |
|
3667 if (q->isIndexHidden(parent)) |
|
3668 return false; |
|
3669 int rowCount = model->rowCount(parent); |
|
3670 for (int i = 0; i < rowCount; ++i) { |
|
3671 if (!q->isRowHidden(i, parent)) |
|
3672 return true; |
|
3673 } |
|
3674 if (rowCount == 0) |
|
3675 return true; |
|
3676 } |
|
3677 return false; |
|
3678 } |
|
3679 |
|
3680 void QTreeViewPrivate::rowsRemoved(const QModelIndex &parent, |
|
3681 int start, int end, bool after) |
|
3682 { |
|
3683 Q_Q(QTreeView); |
|
3684 // if we are going to do a complete relayout anyway, there is no need to update |
|
3685 if (delayedPendingLayout) { |
|
3686 _q_rowsRemoved(parent, start, end); |
|
3687 return; |
|
3688 } |
|
3689 |
|
3690 const int parentItem = viewIndex(parent); |
|
3691 if ((parentItem != -1) || (parent == root)) { |
|
3692 |
|
3693 const uint childLevel = (parentItem == -1) |
|
3694 ? uint(0) : viewItems.at(parentItem).level + 1; |
|
3695 Q_UNUSED(childLevel); // unused in release mode, used in assert below |
|
3696 |
|
3697 const int firstChildItem = parentItem + 1; |
|
3698 int lastChildItem = firstChildItem + ((parentItem == -1) |
|
3699 ? viewItems.count() |
|
3700 : viewItems.at(parentItem).total) - 1; |
|
3701 |
|
3702 const int delta = end - start + 1; |
|
3703 |
|
3704 int removedCount = 0; |
|
3705 for (int item = firstChildItem; item <= lastChildItem; ) { |
|
3706 Q_ASSERT(viewItems.at(item).level == childLevel); |
|
3707 const QModelIndex modelIndex = viewItems.at(item).index; |
|
3708 //Q_ASSERT(modelIndex.parent() == parent); |
|
3709 const int count = viewItems.at(item).total + 1; |
|
3710 if (modelIndex.row() < start) { |
|
3711 // not affected by the removal |
|
3712 item += count; |
|
3713 } else if (modelIndex.row() <= end) { |
|
3714 // removed |
|
3715 viewItems.remove(item, count); |
|
3716 removedCount += count; |
|
3717 lastChildItem -= count; |
|
3718 } else { |
|
3719 if (after) { |
|
3720 // moved; update the model index |
|
3721 viewItems[item].index = model->index( |
|
3722 modelIndex.row() - delta, modelIndex.column(), parent); |
|
3723 } |
|
3724 item += count; |
|
3725 } |
|
3726 } |
|
3727 |
|
3728 updateChildCount(parentItem, -removedCount); |
|
3729 if (after) { |
|
3730 q->updateGeometries(); |
|
3731 viewport->update(); |
|
3732 } else { |
|
3733 //we have removed items: we should at least update the scroll bar values. |
|
3734 // They are used to determine the item geometry. |
|
3735 updateScrollBars(); |
|
3736 } |
|
3737 } else { |
|
3738 // If an ancestor of root is removed then relayout |
|
3739 QModelIndex idx = root; |
|
3740 while (idx.isValid()) { |
|
3741 idx = idx.parent(); |
|
3742 if (idx == parent) { |
|
3743 doDelayedItemsLayout(); |
|
3744 break; |
|
3745 } |
|
3746 } |
|
3747 } |
|
3748 _q_rowsRemoved(parent, start, end); |
|
3749 |
|
3750 QSet<QPersistentModelIndex>::iterator it = expandedIndexes.begin(); |
|
3751 while (it != expandedIndexes.constEnd()) { |
|
3752 if (!it->isValid()) |
|
3753 it = expandedIndexes.erase(it); |
|
3754 else |
|
3755 ++it; |
|
3756 } |
|
3757 it = hiddenIndexes.begin(); |
|
3758 while (it != hiddenIndexes.constEnd()) { |
|
3759 if (!it->isValid()) |
|
3760 it = hiddenIndexes.erase(it); |
|
3761 else |
|
3762 ++it; |
|
3763 } |
|
3764 } |
|
3765 |
|
3766 void QTreeViewPrivate::updateChildCount(const int parentItem, const int delta) |
|
3767 { |
|
3768 if ((parentItem != -1) && delta) { |
|
3769 int level = viewItems.at(parentItem).level; |
|
3770 int item = parentItem; |
|
3771 do { |
|
3772 Q_ASSERT(item >= 0); |
|
3773 for ( ; int(viewItems.at(item).level) != level; --item) ; |
|
3774 viewItems[item].total += delta; |
|
3775 --level; |
|
3776 } while (level >= 0); |
|
3777 } |
|
3778 } |
|
3779 |
|
3780 |
|
3781 void QTreeViewPrivate::_q_sortIndicatorChanged(int column, Qt::SortOrder order) |
|
3782 { |
|
3783 model->sort(column, order); |
|
3784 } |
|
3785 |
|
3786 /*! |
|
3787 \reimp |
|
3788 */ |
|
3789 void QTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
|
3790 { |
|
3791 #ifndef QT_NO_ACCESSIBILITY |
|
3792 if (QAccessible::isActive()) { |
|
3793 int entry = visualIndex(current) + 1; |
|
3794 if (header()) |
|
3795 ++entry; |
|
3796 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Focus); |
|
3797 } |
|
3798 #endif |
|
3799 QAbstractItemView::currentChanged(current, previous); |
|
3800 |
|
3801 if (allColumnsShowFocus()) { |
|
3802 if (previous.isValid()) { |
|
3803 QRect previousRect = visualRect(previous); |
|
3804 previousRect.setX(0); |
|
3805 previousRect.setWidth(viewport()->width()); |
|
3806 viewport()->update(previousRect); |
|
3807 } |
|
3808 if (current.isValid()) { |
|
3809 QRect currentRect = visualRect(current); |
|
3810 currentRect.setX(0); |
|
3811 currentRect.setWidth(viewport()->width()); |
|
3812 viewport()->update(currentRect); |
|
3813 } |
|
3814 } |
|
3815 } |
|
3816 |
|
3817 /*! |
|
3818 \reimp |
|
3819 */ |
|
3820 void QTreeView::selectionChanged(const QItemSelection &selected, |
|
3821 const QItemSelection &deselected) |
|
3822 { |
|
3823 #ifndef QT_NO_ACCESSIBILITY |
|
3824 if (QAccessible::isActive()) { |
|
3825 // ### does not work properly for selection ranges. |
|
3826 QModelIndex sel = selected.indexes().value(0); |
|
3827 if (sel.isValid()) { |
|
3828 int entry = visualIndex(sel) + 1; |
|
3829 if (header()) |
|
3830 ++entry; |
|
3831 QAccessible::updateAccessibility(viewport(), entry, QAccessible::Selection); |
|
3832 } |
|
3833 QModelIndex desel = deselected.indexes().value(0); |
|
3834 if (desel.isValid()) { |
|
3835 int entry = visualIndex(desel) + 1; |
|
3836 if (header()) |
|
3837 ++entry; |
|
3838 QAccessible::updateAccessibility(viewport(), entry, QAccessible::SelectionRemove); |
|
3839 } |
|
3840 } |
|
3841 #endif |
|
3842 QAbstractItemView::selectionChanged(selected, deselected); |
|
3843 } |
|
3844 |
|
3845 int QTreeView::visualIndex(const QModelIndex &index) const |
|
3846 { |
|
3847 Q_D(const QTreeView); |
|
3848 d->executePostedLayout(); |
|
3849 return d->viewIndex(index); |
|
3850 } |
|
3851 |
|
3852 QT_END_NAMESPACE |
|
3853 |
|
3854 #include "moc_qtreeview.cpp" |
|
3855 |
|
3856 #endif // QT_NO_TREEVIEW |