File indexing completed on 2024-04-28 07:46:15

0001 /*
0002     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "katecompletiontree.h"
0009 
0010 #include <QApplication>
0011 #include <QHeaderView>
0012 #include <QList>
0013 #include <QScreen>
0014 #include <QScrollBar>
0015 #include <QTimer>
0016 
0017 #include "kateconfig.h"
0018 #include "katepartdebug.h"
0019 #include "katerenderer.h"
0020 #include "kateview.h"
0021 
0022 #include "documentation_tip.h"
0023 #include "katecompletiondelegate.h"
0024 #include "katecompletionmodel.h"
0025 #include "katecompletionwidget.h"
0026 
0027 KateCompletionTree::KateCompletionTree(KateCompletionWidget *parent)
0028     : QTreeView(parent)
0029 {
0030     m_scrollingEnabled = true;
0031     header()->hide();
0032     setRootIsDecorated(false);
0033     setIndentation(0);
0034     setFrameStyle(QFrame::NoFrame);
0035     setAllColumnsShowFocus(true);
0036     setAlternatingRowColors(true);
0037     setUniformRowHeights(true);
0038     header()->setMinimumSectionSize(0);
0039 
0040     // We need ScrollPerItem, because ScrollPerPixel is too slow with a very large completion-list(see KDevelop).
0041     setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
0042 
0043     m_resizeTimer = new QTimer(this);
0044     m_resizeTimer->setSingleShot(true);
0045 
0046     connect(m_resizeTimer, &QTimer::timeout, this, &KateCompletionTree::resizeColumnsSlot);
0047 
0048     // Provide custom highlighting to completion entries
0049     setItemDelegate(new KateCompletionDelegate(this));
0050     // make sure we adapt to size changes when the model got reset
0051     // this is important for delayed creation of groups, without this
0052     // the first column would never get resized to the correct size
0053     connect(widget()->model(), &QAbstractItemModel::modelReset, this, &KateCompletionTree::scheduleUpdate, Qt::QueuedConnection);
0054 
0055     // Prevent user from expanding / collapsing with the mouse
0056     setItemsExpandable(false);
0057     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0058 }
0059 
0060 void KateCompletionTree::currentChanged(const QModelIndex &current, const QModelIndex &previous)
0061 {
0062     // If config is enabled OR the tip is already visible => show / update it
0063     if (widget()->view()->config()->showDocWithCompletion() || widget()->docTip()->isVisible()) {
0064         widget()->showDocTip(current);
0065     }
0066     widget()->model()->rowSelected(current);
0067     QTreeView::currentChanged(current, previous);
0068 }
0069 
0070 void KateCompletionTree::setScrollingEnabled(bool enabled)
0071 {
0072     m_scrollingEnabled = enabled;
0073 }
0074 
0075 void KateCompletionTree::scrollContentsBy(int dx, int dy)
0076 {
0077     if (m_scrollingEnabled) {
0078         QTreeView::scrollContentsBy(dx, dy);
0079     }
0080 
0081     if (isVisible()) {
0082         scheduleUpdate();
0083     }
0084 }
0085 
0086 KateCompletionWidget *KateCompletionTree::widget() const
0087 {
0088     return static_cast<KateCompletionWidget *>(const_cast<QObject *>(parent()));
0089 }
0090 
0091 void KateCompletionTree::resizeColumnsSlot()
0092 {
0093     if (model()) {
0094         resizeColumns();
0095 
0096         if (!widget()->docTip()->isHidden()) {
0097             widget()->docTip()->updatePosition(widget());
0098         }
0099     }
0100 }
0101 
0102 /**
0103  * Measure the width of visible columns.
0104  *
0105  * This iterates from the start index @p current down until a dead end is hit.
0106  * In a tree model, it will recurse into child indices. Iteration is stopped if
0107  * no more items are available, or the visited rows exceed the available @p maxHeight.
0108  *
0109  * If the model is a tree model, and @p current points to a leaf, and the max height
0110  * is not exceeded, then iteration will continue from the next parent sibling.
0111  */
0112 static bool measureColumnSizes(const KateCompletionTree *tree,
0113                                QModelIndex current,
0114                                QVarLengthArray<int, 8> &columnSize,
0115                                int &currentYPos,
0116                                const int maxHeight,
0117                                bool recursed = false)
0118 {
0119     while (current.isValid() && currentYPos < maxHeight) {
0120         currentYPos += tree->sizeHintForIndex(current).height();
0121         const int row = current.row();
0122         for (int a = 0; a < columnSize.size(); a++) {
0123             QSize s = tree->sizeHintForIndex(current.sibling(row, a));
0124             if (s.width() > 2000) {
0125                 qCDebug(LOG_KTE) << "got invalid size-hint of width " << s.width();
0126             } else if (s.width() > columnSize[a]) {
0127                 columnSize[a] = s.width();
0128             }
0129         }
0130 
0131         const QAbstractItemModel *model = current.model();
0132         const int children = model->rowCount(current);
0133         if (children > 0) {
0134             for (int i = 0; i < children; ++i) {
0135                 if (measureColumnSizes(tree, model->index(i, 0, current), columnSize, currentYPos, maxHeight, true)) {
0136                     break;
0137                 }
0138             }
0139         }
0140 
0141         QModelIndex oldCurrent = current;
0142         current = current.sibling(current.row() + 1, 0);
0143 
0144         // Are we at the end of a group? If yes, move up into the next group
0145         // only do this when we did not recurse already
0146         while (!recursed && !current.isValid() && oldCurrent.parent().isValid()) {
0147             oldCurrent = oldCurrent.parent();
0148             current = oldCurrent.sibling(oldCurrent.row() + 1, 0);
0149         }
0150     }
0151 
0152     return currentYPos >= maxHeight;
0153 }
0154 
0155 void KateCompletionTree::resizeColumns(bool firstShow, bool forceResize)
0156 {
0157     static bool preventRecursion = false;
0158     if (preventRecursion) {
0159         return;
0160     }
0161     m_resizeTimer->stop();
0162 
0163     if (firstShow) {
0164         forceResize = true;
0165     }
0166 
0167     preventRecursion = true;
0168 
0169     widget()->setUpdatesEnabled(false);
0170 
0171     int modelIndexOfName = kateModel()->translateColumn(KTextEditor::CodeCompletionModel::Name);
0172     int oldIndentWidth = columnViewportPosition(modelIndexOfName);
0173 
0174     /// Step 1: Compute the needed column-sizes for the visible content
0175     const int numColumns = model()->columnCount();
0176     QVarLengthArray<int, 8> columnSize(numColumns);
0177     for (int i = 0; i < numColumns; ++i) {
0178         columnSize[i] = 0;
0179     }
0180     QModelIndex current = indexAt(QPoint(1, 1));
0181     //    const bool changed = current.isValid();
0182     int currentYPos = 0;
0183     measureColumnSizes(this, current, columnSize, currentYPos, height());
0184 
0185     auto totalColumnsWidth = 0;
0186     auto originalViewportWidth = viewport()->width();
0187 
0188     const int maxWidth = (widget()->parentWidget()->geometry().width()) / 2;
0189 
0190     /// Step 2: Update column-sizes
0191     // This contains several hacks to reduce the amount of resizing that happens. Generally,
0192     // resizes only happen if a) More than a specific amount of space is saved by the resize, or
0193     // b) the resizing is required so the list can show all of its contents.
0194     int minimumResize = 0;
0195     int maximumResize = 0;
0196 
0197     for (int n = 0; n < numColumns; n++) {
0198         totalColumnsWidth += columnSize[n];
0199 
0200         int diff = columnSize[n] - columnWidth(n);
0201         if (diff < minimumResize) {
0202             minimumResize = diff;
0203         }
0204         if (diff > maximumResize) {
0205             maximumResize = diff;
0206         }
0207     }
0208 
0209     int noReduceTotalWidth = 0; // The total width of the widget of no columns are reduced
0210     for (int n = 0; n < numColumns; n++) {
0211         if (columnSize[n] < columnWidth(n)) {
0212             noReduceTotalWidth += columnWidth(n);
0213         } else {
0214             noReduceTotalWidth += columnSize[n];
0215         }
0216     }
0217 
0218     // Check whether we can afford to reduce none of the columns
0219     // Only reduce size if we widget would else be too wide.
0220     bool noReduce = noReduceTotalWidth < maxWidth && !forceResize;
0221 
0222     if (noReduce) {
0223         totalColumnsWidth = 0;
0224         for (int n = 0; n < numColumns; n++) {
0225             if (columnSize[n] < columnWidth(n)) {
0226                 columnSize[n] = columnWidth(n);
0227             }
0228 
0229             totalColumnsWidth += columnSize[n];
0230         }
0231     }
0232 
0233     if (minimumResize > -40 && maximumResize == 0 && !forceResize) {
0234         // No column needs to be exanded, and no column needs to be reduced by more than 40 pixels.
0235         // To prevent flashing, do not resize at all.
0236         totalColumnsWidth = 0;
0237         for (int n = 0; n < numColumns; n++) {
0238             columnSize[n] = columnWidth(n);
0239             totalColumnsWidth += columnSize[n];
0240         }
0241     } else {
0242         //       viewport()->resize( 5000, viewport()->height() );
0243         for (int n = 0; n < numColumns; n++) {
0244             setColumnWidth(n, columnSize[n]);
0245         }
0246         // For the first column (which is arrow-down / arrow-right) we keep its width to 20
0247         // to prevent glitches and weird resizes when we have no expanding items in the view
0248         //       qCDebug(LOG_KTE) << "resizing viewport to" << totalColumnsWidth;
0249         viewport()->resize(totalColumnsWidth, viewport()->height());
0250     }
0251 
0252     /// Step 3: Update widget-size and -position
0253 
0254     int scrollBarWidth = verticalScrollBar()->width();
0255 
0256     int newIndentWidth = columnViewportPosition(modelIndexOfName);
0257 
0258     int newWidth = qMin(maxWidth, qMax(75, totalColumnsWidth));
0259 
0260     if (newWidth == maxWidth) {
0261         setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0262     } else {
0263         setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0264     }
0265 
0266     if (maximumResize > 0 || forceResize || oldIndentWidth != newIndentWidth) {
0267         //   qCDebug(LOG_KTE) << geometry() << "newWidth" << newWidth << "current width" << width() << "target width" << newWidth + scrollBarWidth;
0268 
0269         if ((newWidth + scrollBarWidth) != width() && originalViewportWidth != totalColumnsWidth) {
0270             auto width = newWidth + scrollBarWidth + 2;
0271             widget()->resize(width, widget()->height());
0272             resize(width, widget()->height() - (2 * widget()->frameWidth()));
0273         }
0274 
0275         //   qCDebug(LOG_KTE) << "created geometry:" << widget()->geometry() << geometry() << "newWidth" << newWidth << "viewport" << viewport()->width();
0276 
0277         if (viewport()->width() > totalColumnsWidth) { // Set the size of the last column to fill the whole rest of the widget
0278             setColumnWidth(numColumns - 1, viewport()->width() - columnViewportPosition(numColumns - 1));
0279         }
0280 
0281         /*  for(int a = 0; a < numColumns; ++a)
0282             qCDebug(LOG_KTE) << "column" << a << columnWidth(a) << "target:" << columnSize[a];*/
0283 
0284         if (oldIndentWidth != newIndentWidth) {
0285             if (!forceResize) {
0286                 preventRecursion = false;
0287                 resizeColumns(true, true);
0288             }
0289         }
0290     }
0291 
0292     widget()->setUpdatesEnabled(true);
0293 
0294     preventRecursion = false;
0295 }
0296 
0297 void KateCompletionTree::initViewItemOption(QStyleOptionViewItem *option) const
0298 {
0299     QTreeView::initViewItemOption(option);
0300     option->font = widget()->view()->renderer()->currentFont();
0301 }
0302 
0303 KateCompletionModel *KateCompletionTree::kateModel() const
0304 {
0305     return static_cast<KateCompletionModel *>(model());
0306 }
0307 
0308 bool KateCompletionTree::nextCompletion()
0309 {
0310     QModelIndex current;
0311     QModelIndex firstCurrent = currentIndex();
0312 
0313     do {
0314         QModelIndex oldCurrent = currentIndex();
0315 
0316         current = moveCursor(MoveDown, Qt::NoModifier);
0317 
0318         if (current != oldCurrent && current.isValid()) {
0319             setCurrentIndex(current);
0320         } else {
0321             if (firstCurrent.isValid()) {
0322                 setCurrentIndex(firstCurrent);
0323             }
0324             return false;
0325         }
0326 
0327     } while (!kateModel()->indexIsItem(current));
0328 
0329     return true;
0330 }
0331 
0332 bool KateCompletionTree::previousCompletion()
0333 {
0334     QModelIndex current;
0335     QModelIndex firstCurrent = currentIndex();
0336 
0337     do {
0338         QModelIndex oldCurrent = currentIndex();
0339 
0340         current = moveCursor(MoveUp, Qt::NoModifier);
0341 
0342         if (current != oldCurrent && current.isValid()) {
0343             setCurrentIndex(current);
0344 
0345         } else {
0346             if (firstCurrent.isValid()) {
0347                 setCurrentIndex(firstCurrent);
0348             }
0349             return false;
0350         }
0351 
0352     } while (!kateModel()->indexIsItem(current));
0353 
0354     return true;
0355 }
0356 
0357 bool KateCompletionTree::pageDown()
0358 {
0359     QModelIndex old = currentIndex();
0360 
0361     QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier);
0362 
0363     if (current.isValid()) {
0364         setCurrentIndex(current);
0365         if (!kateModel()->indexIsItem(current)) {
0366             if (!nextCompletion()) {
0367                 previousCompletion();
0368             }
0369         }
0370     }
0371 
0372     return current != old;
0373 }
0374 
0375 bool KateCompletionTree::pageUp()
0376 {
0377     QModelIndex old = currentIndex();
0378     QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier);
0379 
0380     if (current.isValid()) {
0381         setCurrentIndex(current);
0382         if (!kateModel()->indexIsItem(current)) {
0383             if (!previousCompletion()) {
0384                 nextCompletion();
0385             }
0386         }
0387     }
0388     return current != old;
0389 }
0390 
0391 void KateCompletionTree::top()
0392 {
0393     QModelIndex current = moveCursor(MoveHome, Qt::NoModifier);
0394     setCurrentIndex(current);
0395 
0396     if (current.isValid()) {
0397         setCurrentIndex(current);
0398         if (!kateModel()->indexIsItem(current)) {
0399             nextCompletion();
0400         }
0401     }
0402 }
0403 
0404 void KateCompletionTree::scheduleUpdate()
0405 {
0406     m_resizeTimer->start(0);
0407 }
0408 
0409 void KateCompletionTree::bottom()
0410 {
0411     QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier);
0412     setCurrentIndex(current);
0413 
0414     if (current.isValid()) {
0415         setCurrentIndex(current);
0416         if (!kateModel()->indexIsItem(current)) {
0417             previousCompletion();
0418         }
0419     }
0420 }