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