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"