File indexing completed on 2024-03-24 04:00:09
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 ¤t, 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 ¤tYPos, 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 }