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{
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{
370
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{
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{
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{
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...
void rowSelected(const QModelIndex &row) const
This is the code completion's main widget, and also contains the core interface logic.
const QFont & currentFont() const
Access currently used font.
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 * 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
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void stop()
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
QWidget * parentWidget() const const
void resize(const QSize &)
QStyle * style() const const
void setUpdatesEnabled(bool enable)
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.