KTextEditor

katecompletiontree.cpp
1/*
2 SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
3 SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "katecompletiontree.h"
9
10#include <QApplication>
11#include <QHeaderView>
12#include <QList>
13#include <QScreen>
14#include <QScrollBar>
15#include <QTimer>
16
17#include "kateconfig.h"
18#include "katepartdebug.h"
19#include "katerenderer.h"
20#include "kateview.h"
21
22#include "documentation_tip.h"
23#include "katecompletiondelegate.h"
24#include "katecompletionmodel.h"
25#include "katecompletionwidget.h"
26
27KateCompletionTree::KateCompletionTree(KateCompletionWidget *parent)
28 : QTreeView(parent)
29{
30 m_scrollingEnabled = true;
31 header()->hide();
32 setRootIsDecorated(false);
33 setIndentation(0);
34 setFrameStyle(QFrame::NoFrame);
35 setAllColumnsShowFocus(true);
36 setAlternatingRowColors(true);
37 setUniformRowHeights(true);
38 header()->setMinimumSectionSize(0);
39
40 // We need ScrollPerItem, because ScrollPerPixel is too slow with a very large completion-list(see KDevelop).
41 setVerticalScrollMode(QAbstractItemView::ScrollPerItem);
42
43 m_resizeTimer = new QTimer(this);
44 m_resizeTimer->setSingleShot(true);
45
46 connect(m_resizeTimer, &QTimer::timeout, this, &KateCompletionTree::resizeColumnsSlot);
47
48 // Provide custom highlighting to completion entries
49 setItemDelegate(new KateCompletionDelegate(this));
50 // make sure we adapt to size changes when the model got reset
51 // this is important for delayed creation of groups, without this
52 // the first column would never get resized to the correct size
53 connect(widget()->model(), &QAbstractItemModel::modelReset, this, &KateCompletionTree::scheduleUpdate, Qt::QueuedConnection);
54
55 // Prevent user from expanding / collapsing with the mouse
56 setItemsExpandable(false);
57 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
58}
59
60void KateCompletionTree::currentChanged(const QModelIndex &current, const QModelIndex &previous)
61{
62 // If config is enabled OR the tip is already visible => show / update it
63 if (widget()->view()->config()->showDocWithCompletion() || widget()->docTip()->isVisible()) {
64 widget()->showDocTip(current);
65 }
66 widget()->model()->rowSelected(current);
67 QTreeView::currentChanged(current, previous);
68}
69
70void KateCompletionTree::setScrollingEnabled(bool enabled)
71{
72 m_scrollingEnabled = enabled;
73}
74
75int KateCompletionTree::textColumnOffset() const
76{
77 QStyleOptionViewItem item;
78 item.index = currentIndex();
79 initViewItemOption(&item);
81 int nameColumn = kateModel()->translateColumn(KTextEditor::CodeCompletionModel::Name);
82 return margins + item.decorationSize.width() + columnViewportPosition(nameColumn);
83}
84
85void KateCompletionTree::scrollContentsBy(int dx, int dy)
86{
87 if (m_scrollingEnabled) {
89 }
90
91 if (isVisible()) {
92 scheduleUpdate();
93 }
94}
95
96KateCompletionWidget *KateCompletionTree::widget() const
97{
98 return static_cast<KateCompletionWidget *>(const_cast<QObject *>(parent()));
99}
100
101void KateCompletionTree::resizeColumnsSlot()
102{
103 if (model()) {
104 resizeColumns();
105
106 if (!widget()->docTip()->isHidden()) {
107 widget()->docTip()->updatePosition(widget());
108 }
109 }
110}
111
112/**
113 * Measure the width of visible columns.
114 *
115 * This iterates from the start index @p current down until a dead end is hit.
116 * In a tree model, it will recurse into child indices. Iteration is stopped if
117 * no more items are available, or the visited rows exceed the available @p maxHeight.
118 *
119 * If the model is a tree model, and @p current points to a leaf, and the max height
120 * is not exceeded, then iteration will continue from the next parent sibling.
121 */
122static bool measureColumnSizes(const KateCompletionTree *tree,
123 QModelIndex current,
124 QVarLengthArray<int, 8> &columnSize,
125 int &currentYPos,
126 const int maxHeight,
127 bool recursed = false)
128{
129 while (current.isValid() && currentYPos < maxHeight) {
130 currentYPos += tree->sizeHintForIndex(current).height();
131 const int row = current.row();
132 for (int a = 0; a < columnSize.size(); a++) {
133 QSize s = tree->sizeHintForIndex(current.sibling(row, a));
134 if (s.width() > 2000) {
135 qCDebug(LOG_KTE) << "got invalid size-hint of width " << s.width();
136 } else if (s.width() > columnSize[a]) {
137 columnSize[a] = s.width();
138 }
139 }
140
141 const QAbstractItemModel *model = current.model();
142 const int children = model->rowCount(current);
143 if (children > 0) {
144 for (int i = 0; i < children; ++i) {
145 if (measureColumnSizes(tree, model->index(i, 0, current), columnSize, currentYPos, maxHeight, true)) {
146 break;
147 }
148 }
149 }
150
151 QModelIndex oldCurrent = current;
152 current = current.sibling(current.row() + 1, 0);
153
154 // Are we at the end of a group? If yes, move up into the next group
155 // only do this when we did not recurse already
156 while (!recursed && !current.isValid() && oldCurrent.parent().isValid()) {
157 oldCurrent = oldCurrent.parent();
158 current = oldCurrent.sibling(oldCurrent.row() + 1, 0);
159 }
160 }
161
162 return currentYPos >= maxHeight;
163}
164
165void KateCompletionTree::resizeColumns(bool firstShow, bool forceResize)
166{
167 static bool preventRecursion = false;
168 if (preventRecursion) {
169 return;
170 }
171 m_resizeTimer->stop();
172
173 if (firstShow) {
174 forceResize = true;
175 }
176
177 preventRecursion = true;
178
179 widget()->setUpdatesEnabled(false);
180
181 int modelIndexOfName = kateModel()->translateColumn(KTextEditor::CodeCompletionModel::Name);
182 int oldIndentWidth = columnViewportPosition(modelIndexOfName);
183
184 /// Step 1: Compute the needed column-sizes for the visible content
185 const int numColumns = model()->columnCount();
186 QVarLengthArray<int, 8> columnSize(numColumns);
187 for (int i = 0; i < numColumns; ++i) {
188 columnSize[i] = 0;
189 }
190 QModelIndex current = indexAt(QPoint(1, 1));
191 // const bool changed = current.isValid();
192 int currentYPos = 0;
193 measureColumnSizes(this, current, columnSize, currentYPos, height());
194
195 auto totalColumnsWidth = 0;
196 auto originalViewportWidth = viewport()->width();
197
198 const int maxWidth = (widget()->parentWidget()->geometry().width()) / 2;
199
200 /// Step 2: Update column-sizes
201 // This contains several hacks to reduce the amount of resizing that happens. Generally,
202 // resizes only happen if a) More than a specific amount of space is saved by the resize, or
203 // b) the resizing is required so the list can show all of its contents.
204 int minimumResize = 0;
205 int maximumResize = 0;
206
207 for (int n = 0; n < numColumns; n++) {
208 totalColumnsWidth += columnSize[n];
209
210 int diff = columnSize[n] - columnWidth(n);
211 if (diff < minimumResize) {
212 minimumResize = diff;
213 }
214 if (diff > maximumResize) {
215 maximumResize = diff;
216 }
217 }
218
219 int noReduceTotalWidth = 0; // The total width of the widget of no columns are reduced
220 for (int n = 0; n < numColumns; n++) {
221 if (columnSize[n] < columnWidth(n)) {
222 noReduceTotalWidth += columnWidth(n);
223 } else {
224 noReduceTotalWidth += columnSize[n];
225 }
226 }
227
228 // Check whether we can afford to reduce none of the columns
229 // Only reduce size if we widget would else be too wide.
230 bool noReduce = noReduceTotalWidth < maxWidth && !forceResize;
231
232 if (noReduce) {
233 totalColumnsWidth = 0;
234 for (int n = 0; n < numColumns; n++) {
235 if (columnSize[n] < columnWidth(n)) {
236 columnSize[n] = columnWidth(n);
237 }
238
239 totalColumnsWidth += columnSize[n];
240 }
241 }
242
243 if (minimumResize > -40 && maximumResize == 0 && !forceResize) {
244 // No column needs to be exanded, and no column needs to be reduced by more than 40 pixels.
245 // To prevent flashing, do not resize at all.
246 totalColumnsWidth = 0;
247 for (int n = 0; n < numColumns; n++) {
248 columnSize[n] = columnWidth(n);
249 totalColumnsWidth += columnSize[n];
250 }
251 } else {
252 // viewport()->resize( 5000, viewport()->height() );
253 for (int n = 0; n < numColumns; n++) {
254 setColumnWidth(n, columnSize[n]);
255 }
256 // For the first column (which is arrow-down / arrow-right) we keep its width to 20
257 // to prevent glitches and weird resizes when we have no expanding items in the view
258 // qCDebug(LOG_KTE) << "resizing viewport to" << totalColumnsWidth;
259 viewport()->resize(totalColumnsWidth, viewport()->height());
260 }
261
262 /// Step 3: Update widget-size and -position
263
264 int scrollBarWidth = verticalScrollBar()->width();
265
266 int newIndentWidth = columnViewportPosition(modelIndexOfName);
267
268 int newWidth = qMin(maxWidth, qMax(75, totalColumnsWidth));
269
270 if (newWidth == maxWidth) {
272 } else {
274 }
275
276 if (maximumResize > 0 || forceResize || oldIndentWidth != newIndentWidth) {
277 // qCDebug(LOG_KTE) << geometry() << "newWidth" << newWidth << "current width" << width() << "target width" << newWidth + scrollBarWidth;
278
279 if ((newWidth + scrollBarWidth) != width() && originalViewportWidth != totalColumnsWidth) {
280 auto width = newWidth + scrollBarWidth + 2;
281 widget()->resize(width, widget()->height());
282 resize(width, widget()->height() - (2 * widget()->frameWidth()));
283 }
284
285 // qCDebug(LOG_KTE) << "created geometry:" << widget()->geometry() << geometry() << "newWidth" << newWidth << "viewport" << viewport()->width();
286
287 if (viewport()->width() > totalColumnsWidth) { // Set the size of the last column to fill the whole rest of the widget
288 setColumnWidth(numColumns - 1, viewport()->width() - columnViewportPosition(numColumns - 1));
289 }
290
291 /* for(int a = 0; a < numColumns; ++a)
292 qCDebug(LOG_KTE) << "column" << a << columnWidth(a) << "target:" << columnSize[a];*/
293
294 if (oldIndentWidth != newIndentWidth) {
295 if (!forceResize) {
296 preventRecursion = false;
297 resizeColumns(true, true);
298 }
299 }
300 }
301
302 widget()->setUpdatesEnabled(true);
303
304 preventRecursion = false;
305}
306
307void KateCompletionTree::initViewItemOption(QStyleOptionViewItem *option) const
308{
310 option->font = widget()->view()->renderer()->currentFont();
311}
312
313KateCompletionModel *KateCompletionTree::kateModel() const
314{
315 return static_cast<KateCompletionModel *>(model());
316}
317
318bool KateCompletionTree::nextCompletion()
319{
320 QModelIndex current;
321 QModelIndex firstCurrent = currentIndex();
322
323 do {
324 QModelIndex oldCurrent = currentIndex();
325
327
328 if (current != oldCurrent && current.isValid()) {
329 setCurrentIndex(current);
330 } else {
331 if (firstCurrent.isValid()) {
332 setCurrentIndex(firstCurrent);
333 }
334 return false;
335 }
336
337 } while (!kateModel()->indexIsItem(current));
338
339 return true;
340}
341
342bool KateCompletionTree::previousCompletion()
343{
344 QModelIndex current;
345 QModelIndex firstCurrent = currentIndex();
346
347 do {
348 QModelIndex oldCurrent = currentIndex();
349
350 current = moveCursor(MoveUp, Qt::NoModifier);
351
352 if (current != oldCurrent && current.isValid()) {
353 setCurrentIndex(current);
354
355 } else {
356 if (firstCurrent.isValid()) {
357 setCurrentIndex(firstCurrent);
358 }
359 return false;
360 }
361
362 } while (!kateModel()->indexIsItem(current));
363
364 return true;
365}
366
367bool KateCompletionTree::pageDown()
368{
369 QModelIndex old = currentIndex();
370
371 QModelIndex current = moveCursor(MovePageDown, Qt::NoModifier);
372
373 if (current.isValid()) {
374 setCurrentIndex(current);
375 if (!kateModel()->indexIsItem(current)) {
376 if (!nextCompletion()) {
377 previousCompletion();
378 }
379 }
380 }
381
382 return current != old;
383}
384
385bool KateCompletionTree::pageUp()
386{
387 QModelIndex old = currentIndex();
388 QModelIndex current = moveCursor(MovePageUp, Qt::NoModifier);
389
390 if (current.isValid()) {
391 setCurrentIndex(current);
392 if (!kateModel()->indexIsItem(current)) {
393 if (!previousCompletion()) {
394 nextCompletion();
395 }
396 }
397 }
398 return current != old;
399}
400
401void KateCompletionTree::top()
402{
403 QModelIndex current = moveCursor(MoveHome, Qt::NoModifier);
404 setCurrentIndex(current);
405
406 if (current.isValid()) {
407 setCurrentIndex(current);
408 if (!kateModel()->indexIsItem(current)) {
409 nextCompletion();
410 }
411 }
412}
413
414void KateCompletionTree::scheduleUpdate()
415{
416 m_resizeTimer->start(0);
417}
418
419void KateCompletionTree::bottom()
420{
421 QModelIndex current = moveCursor(MoveEnd, Qt::NoModifier);
422 setCurrentIndex(current);
423
424 if (current.isValid()) {
425 setCurrentIndex(current);
426 if (!kateModel()->indexIsItem(current)) {
427 previousCompletion();
428 }
429 }
430}
This class has the responsibility for filtering, sorting, and manipulating code completion data provi...
This is the code completion's main widget, and also contains the core interface logic.
virtual int columnCount(const QModelIndex &parent) const const=0
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
QModelIndex currentIndex() const const
virtual void initViewItemOption(QStyleOptionViewItem *option) const const
QAbstractItemModel * model() const const
void setCurrentIndex(const QModelIndex &index)
QSize sizeHintForIndex(const QModelIndex &index) const const
void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)
QScrollBar * verticalScrollBar() const const
QWidget * viewport() const const
bool isValid() const const
const QAbstractItemModel * model() const const
QModelIndex parent() const const
int row() const const
QModelIndex sibling(int row, int column) const const
QObject(QObject *parent)
QObject * parent() const const
int height() const const
int width() const const
PM_FocusFrameHMargin
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
QueuedConnection
NoModifier
ScrollBarAlwaysOff
void timeout()
int columnViewportPosition(int column) const const
int columnWidth(int column) const const
virtual void currentChanged(const QModelIndex &current, const QModelIndex &previous) override
virtual QModelIndex indexAt(const QPoint &point) const const override
virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
virtual void scrollContentsBy(int dx, int dy) override
void setColumnWidth(int column, int width)
qsizetype size() const const
void hide()
bool isHidden() const const
void resize(const QSize &)
QStyle * style() const const
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:55:24 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.