File indexing completed on 2024-05-05 04:38:44
0001 /* 0002 SPDX-License-Identifier: LGPL-2.0-only 0003 */ 0004 0005 #include "focusedtreeview.h" 0006 0007 #include <QResizeEvent> 0008 #include <QScrollBar> 0009 #include <QTimer> 0010 0011 namespace KDevelop { 0012 0013 class FocusedTreeViewPrivate 0014 { 0015 public: 0016 bool autoScrollAtEnd = false; 0017 QTimer timer; 0018 bool wasAtEnd = false; 0019 int insertedBegin = -1; 0020 int insertedEnd = -1; 0021 }; 0022 0023 FocusedTreeView::FocusedTreeView(QWidget* parent) 0024 : QTreeView(parent) 0025 , d_ptr(new FocusedTreeViewPrivate) 0026 { 0027 Q_D(FocusedTreeView); 0028 0029 setVerticalScrollMode(ScrollPerItem); 0030 0031 d->timer.setInterval(200); 0032 d->timer.setSingleShot(true); 0033 connect(&d->timer, &QTimer::timeout, this, &FocusedTreeView::delayedAutoScrollAndResize); 0034 0035 connect(verticalScrollBar(), &QScrollBar::valueChanged, 0036 &d->timer, QOverload<>::of(&QTimer::start)); 0037 } 0038 0039 FocusedTreeView::~FocusedTreeView() 0040 { 0041 } 0042 0043 void FocusedTreeView::setModel(QAbstractItemModel* newModel) 0044 { 0045 if (QAbstractItemModel* oldModel = model()) { 0046 disconnect(oldModel, nullptr, this, nullptr); 0047 } 0048 0049 QTreeView::setModel(newModel); 0050 0051 if (newModel) { 0052 connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, &FocusedTreeView::rowsAboutToBeInserted); 0053 connect(newModel, &QAbstractItemModel::rowsRemoved, this, &FocusedTreeView::rowsRemoved); 0054 } 0055 } 0056 0057 void FocusedTreeView::setAutoScrollAtEnd(bool enable) 0058 { 0059 Q_D(FocusedTreeView); 0060 0061 d->autoScrollAtEnd = enable; 0062 } 0063 0064 int FocusedTreeView::sizeHintForColumn(int column) const 0065 { 0066 QModelIndex i = indexAt(QPoint(0, 0)); 0067 if (i.isValid()) { 0068 QSize hint = sizeHintForIndex(i); 0069 int maxWidth = hint.width(); 0070 if (hint.height()) { 0071 //Also consider one item above, because else we can get problems with 0072 //the vertical scroll-bar 0073 for (int a = -1; a < (height() / hint.height()) + 1; ++a) { 0074 QModelIndex current = i.sibling(i.row() + a, column); 0075 QSize tempHint = sizeHintForIndex(current); 0076 if (tempHint.width() > maxWidth) 0077 maxWidth = tempHint.width(); 0078 } 0079 } 0080 return maxWidth; 0081 } 0082 return columnWidth(column); 0083 } 0084 0085 void FocusedTreeView::fitColumns() 0086 { 0087 if (!model()) { 0088 return; 0089 } 0090 0091 for (int c = 0, columnCount = model()->columnCount(); c < columnCount; ++c) { 0092 resizeColumnToContents(c); 0093 } 0094 } 0095 0096 void FocusedTreeView::resizeEvent(QResizeEvent* event) 0097 { 0098 QTreeView::resizeEvent(event); 0099 0100 // Rewrap lines when the width changes. 0101 if (wordWrap() && event->size().width() != event->oldSize().width()) { 0102 // fitColumns() calls resizeColumnToContents(), which executes items layout 0103 // scheduled here (QTreeView::setWordWrap() also schedules items layout). 0104 // Redoing the items layout is necessary for correct rewrapping, but causes a roughly 0105 // 5-second-long UI freeze inside QItemDelegate::sizeHint() when the user selects 0106 // a 100'000th line of output. The freeze happens because non-uniform row heights (necessary 0107 // for word-wrapping) force QTreeView to calculate heights of all items above the selected one. 0108 // The number of consecutive resize events does not affect the duration of the resulting UI freeze. 0109 scheduleDelayedItemsLayout(); 0110 // Resizing columns to contents here rewraps lines immediately rather than only after subsequent vertical 0111 // scrolling. This is relatively fast and does not noticeably contribute to the UI freeze duration. 0112 fitColumns(); 0113 } 0114 } 0115 0116 void FocusedTreeView::delayedAutoScrollAndResize() 0117 { 0118 Q_D(FocusedTreeView); 0119 0120 if (!model()) { 0121 // might happen on shutdown 0122 return; 0123 } 0124 0125 if (d->autoScrollAtEnd && d->insertedBegin != -1 && d->wasAtEnd && d->insertedEnd == model()->rowCount()) { 0126 scrollToBottom(); 0127 } 0128 0129 for (int a = 0; a < model()->columnCount(); ++a) 0130 resizeColumnToContents(a); 0131 0132 d->insertedBegin = -1; 0133 0134 // Timer is single-shot, but the code above may have recursively restarted the timer 0135 // (e.g. via the connection from the scroll-bar signal), so explicitly prevent a redundant 0136 // call here. 0137 d->timer.stop(); 0138 } 0139 0140 void FocusedTreeView::rowsAboutToBeInserted(const QModelIndex&, int first, int last) 0141 { 0142 Q_D(FocusedTreeView); 0143 0144 if (d->insertedBegin == -1) { 0145 d->insertedBegin = d->insertedEnd = first; 0146 0147 d->wasAtEnd = true; 0148 QModelIndex last = model()->index(model()->rowCount() - 1, 0); 0149 if (last.isValid()) { 0150 auto rect = visualRect(last); 0151 d->wasAtEnd = rect.isValid() && viewport()->rect().intersects(rect); 0152 } 0153 } 0154 if (first == d->insertedEnd) { 0155 d->insertedEnd = last + 1; 0156 } 0157 0158 if (!d->timer.isActive()) 0159 d->timer.start(); 0160 } 0161 0162 // Removing rows can make longer rows move into the visible region, so we also must trigger a 0163 // column resize 0164 void FocusedTreeView::rowsRemoved(const QModelIndex& parent, int first, int last) 0165 { 0166 Q_D(FocusedTreeView); 0167 0168 QTreeView::rowsRemoved(parent, first, last); 0169 0170 if (!d->timer.isActive()) 0171 d->timer.start(); 0172 } 0173 0174 } 0175 0176 #include "moc_focusedtreeview.cpp"