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
75void KateCompletionTree::scrollContentsBy(int dx, int dy)
76{
77 if (m_scrollingEnabled) {
79 }
80
81 if (isVisible()) {
82 scheduleUpdate();
83 }
84}
85
86KateCompletionWidget *KateCompletionTree::widget() const
87{
88 return static_cast<KateCompletionWidget *>(const_cast<QObject *>(parent()));
89}
90
91void KateCompletionTree::resizeColumnsSlot()
92{
93 if (model()) {
94 resizeColumns();
95
96 if (!widget()->docTip()->isHidden()) {
97 widget()->docTip()->updatePosition(widget());
98 }
99 }
100}
101
102/**
103 * Measure the width of visible columns.
104 *
105 * This iterates from the start index @p current down until a dead end is hit.
106 * In a tree model, it will recurse into child indices. Iteration is stopped if
107 * no more items are available, or the visited rows exceed the available @p maxHeight.
108 *
109 * If the model is a tree model, and @p current points to a leaf, and the max height
110 * is not exceeded, then iteration will continue from the next parent sibling.
111 */
112static bool measureColumnSizes(const KateCompletionTree *tree,
113 QModelIndex current,
114 QVarLengthArray<int, 8> &columnSize,
115 int &currentYPos,
116 const int maxHeight,
117 bool recursed = false)
118{
119 while (current.isValid() && currentYPos < maxHeight) {
120 currentYPos += tree->sizeHintForIndex(current).height();
121 const int row = current.row();
122 for (int a = 0; a < columnSize.size(); a++) {
123 QSize s = tree->sizeHintForIndex(current.sibling(row, a));
124 if (s.width() > 2000) {
125 qCDebug(LOG_KTE) << "got invalid size-hint of width " << s.width();
126 } else if (s.width() > columnSize[a]) {
127 columnSize[a] = s.width();
128 }
129 }
130
131 const QAbstractItemModel *model = current.model();
132 const int children = model->rowCount(current);
133 if (children > 0) {
134 for (int i = 0; i < children; ++i) {
135 if (measureColumnSizes(tree, model->index(i, 0, current), columnSize, currentYPos, maxHeight, true)) {
136 break;
137 }
138 }
139 }
140
141 QModelIndex oldCurrent = current;
142 current = current.sibling(current.row() + 1, 0);
143
144 // Are we at the end of a group? If yes, move up into the next group
145 // only do this when we did not recurse already
146 while (!recursed && !current.isValid() && oldCurrent.parent().isValid()) {
147 oldCurrent = oldCurrent.parent();
148 current = oldCurrent.sibling(oldCurrent.row() + 1, 0);
149 }
150 }
151
152 return currentYPos >= maxHeight;
153}
154
155void KateCompletionTree::resizeColumns(bool firstShow, bool forceResize)
156{
157 static bool preventRecursion = false;
158 if (preventRecursion) {
159 return;
160 }
161 m_resizeTimer->stop();
162
163 if (firstShow) {
164 forceResize = true;
165 }
166
167 preventRecursion = true;
168
169 widget()->setUpdatesEnabled(false);
170
171 int modelIndexOfName = kateModel()->translateColumn(KTextEditor::CodeCompletionModel::Name);
172 int oldIndentWidth = columnViewportPosition(modelIndexOfName);
173
174 /// Step 1: Compute the needed column-sizes for the visible content
175 const int numColumns = model()->columnCount();
176 QVarLengthArray<int, 8> columnSize(numColumns);
177 for (int i = 0; i < numColumns; ++i) {
178 columnSize[i] = 0;
179 }
180 QModelIndex current = indexAt(QPoint(1, 1));
181 // const bool changed = current.isValid();
182 int currentYPos = 0;
183 measureColumnSizes(this, current, columnSize, currentYPos, height());
184
185 auto totalColumnsWidth = 0;
186 auto originalViewportWidth = viewport()->width();
187
188 const int maxWidth = (widget()->parentWidget()->geometry().width()) / 2;
189
190 /// Step 2: Update column-sizes
191 // This contains several hacks to reduce the amount of resizing that happens. Generally,
192 // resizes only happen if a) More than a specific amount of space is saved by the resize, or
193 // b) the resizing is required so the list can show all of its contents.
194 int minimumResize = 0;
195 int maximumResize = 0;
196
197 for (int n = 0; n < numColumns; n++) {
198 totalColumnsWidth += columnSize[n];
199
200 int diff = columnSize[n] - columnWidth(n);
201 if (diff < minimumResize) {
202 minimumResize = diff;
203 }
204 if (diff > maximumResize) {
205 maximumResize = diff;
206 }
207 }
208
209 int noReduceTotalWidth = 0; // The total width of the widget of no columns are reduced
210 for (int n = 0; n < numColumns; n++) {
211 if (columnSize[n] < columnWidth(n)) {
212 noReduceTotalWidth += columnWidth(n);
213 } else {
214 noReduceTotalWidth += columnSize[n];
215 }
216 }
217
218 // Check whether we can afford to reduce none of the columns
219 // Only reduce size if we widget would else be too wide.
220 bool noReduce = noReduceTotalWidth < maxWidth && !forceResize;
221
222 if (noReduce) {
223 totalColumnsWidth = 0;
224 for (int n = 0; n < numColumns; n++) {
225 if (columnSize[n] < columnWidth(n)) {
226 columnSize[n] = columnWidth(n);
227 }
228
229 totalColumnsWidth += columnSize[n];
230 }
231 }
232
233 if (minimumResize > -40 && maximumResize == 0 && !forceResize) {
234 // No column needs to be exanded, and no column needs to be reduced by more than 40 pixels.
235 // To prevent flashing, do not resize at all.
236 totalColumnsWidth = 0;
237 for (int n = 0; n < numColumns; n++) {
238 columnSize[n] = columnWidth(n);
239 totalColumnsWidth += columnSize[n];
240 }
241 } else {
242 // viewport()->resize( 5000, viewport()->height() );
243 for (int n = 0; n < numColumns; n++) {
244 setColumnWidth(n, columnSize[n]);
245 }
246 // For the first column (which is arrow-down / arrow-right) we keep its width to 20
247 // to prevent glitches and weird resizes when we have no expanding items in the view
248 // qCDebug(LOG_KTE) << "resizing viewport to" << totalColumnsWidth;
249 viewport()->resize(totalColumnsWidth, viewport()->height());
250 }
251
252 /// Step 3: Update widget-size and -position
253
254 int scrollBarWidth = verticalScrollBar()->width();
255
256 int newIndentWidth = columnViewportPosition(modelIndexOfName);
257
258 int newWidth = qMin(maxWidth, qMax(75, totalColumnsWidth));
259
260 if (newWidth == maxWidth) {
262 } else {
264 }
265
266 if (maximumResize > 0 || forceResize || oldIndentWidth != newIndentWidth) {
267 // qCDebug(LOG_KTE) << geometry() << "newWidth" << newWidth << "current width" << width() << "target width" << newWidth + scrollBarWidth;
268
269 if ((newWidth + scrollBarWidth) != width() && originalViewportWidth != totalColumnsWidth) {
270 auto width = newWidth + scrollBarWidth + 2;
271 widget()->resize(width, widget()->height());
272 resize(width, widget()->height() - (2 * widget()->frameWidth()));
273 }
274
275 // qCDebug(LOG_KTE) << "created geometry:" << widget()->geometry() << geometry() << "newWidth" << newWidth << "viewport" << viewport()->width();
276
277 if (viewport()->width() > totalColumnsWidth) { // Set the size of the last column to fill the whole rest of the widget
278 setColumnWidth(numColumns - 1, viewport()->width() - columnViewportPosition(numColumns - 1));
279 }
280
281 /* for(int a = 0; a < numColumns; ++a)
282 qCDebug(LOG_KTE) << "column" << a << columnWidth(a) << "target:" << columnSize[a];*/
283
284 if (oldIndentWidth != newIndentWidth) {
285 if (!forceResize) {
286 preventRecursion = false;
287 resizeColumns(true, true);
288 }
289 }
290 }
291
292 widget()->setUpdatesEnabled(true);
293
294 preventRecursion = false;
295}
296
297void KateCompletionTree::initViewItemOption(QStyleOptionViewItem *option) const
298{
299 QTreeView::initViewItemOption(option);
300 option->font = widget()->view()->renderer()->currentFont();
301}
302
303KateCompletionModel *KateCompletionTree::kateModel() const
304{
305 return static_cast<KateCompletionModel *>(model());
306}
307
308bool KateCompletionTree::nextCompletion()
309{
310 QModelIndex current;
311 QModelIndex firstCurrent = currentIndex();
312
313 do {
314 QModelIndex oldCurrent = currentIndex();
315
317
318 if (current != oldCurrent && current.isValid()) {
319 setCurrentIndex(current);
320 } else {
321 if (firstCurrent.isValid()) {
322 setCurrentIndex(firstCurrent);
323 }
324 return false;
325 }
326
327 } while (!kateModel()->indexIsItem(current));
328
329 return true;
330}
331
332bool KateCompletionTree::previousCompletion()
333{
334 QModelIndex current;
335 QModelIndex firstCurrent = currentIndex();
336
337 do {
338 QModelIndex oldCurrent = currentIndex();
339
340 current = moveCursor(MoveUp, Qt::NoModifier);
341
342 if (current != oldCurrent && current.isValid()) {
343 setCurrentIndex(current);
344
345 } else {
346 if (firstCurrent.isValid()) {
347 setCurrentIndex(firstCurrent);
348 }
349 return false;
350 }
351
352 } while (!kateModel()->indexIsItem(current));
353
354 return true;
355}
356
357bool KateCompletionTree::pageDown()
358{
360
362
363 if (current.isValid()) {
364 setCurrentIndex(current);
365 if (!kateModel()->indexIsItem(current)) {
366 if (!nextCompletion()) {
367 previousCompletion();
368 }
369 }
370 }
371
372 return current != old;
373}
374
375bool KateCompletionTree::pageUp()
376{
379
380 if (current.isValid()) {
381 setCurrentIndex(current);
382 if (!kateModel()->indexIsItem(current)) {
383 if (!previousCompletion()) {
384 nextCompletion();
385 }
386 }
387 }
388 return current != old;
389}
390
391void KateCompletionTree::top()
392{
394 setCurrentIndex(current);
395
396 if (current.isValid()) {
397 setCurrentIndex(current);
398 if (!kateModel()->indexIsItem(current)) {
399 nextCompletion();
400 }
401 }
402}
403
404void KateCompletionTree::scheduleUpdate()
405{
406 m_resizeTimer->start(0);
407}
408
409void KateCompletionTree::bottom()
410{
412 setCurrentIndex(current);
413
414 if (current.isValid()) {
415 setCurrentIndex(current);
416 if (!kateModel()->indexIsItem(current)) {
417 previousCompletion();
418 }
419 }
420}
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
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
QueuedConnection
NoModifier
ScrollBarAlwaysOff
void start(int msec)
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(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override
virtual void scrollContentsBy(int dx, int dy) override
void setColumnWidth(int column, int width)
int size() const const
void hide()
bool isHidden() const const
QWidget * parentWidget() const const
void resize(int w, int h)
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 Feb 24 2024 20:00:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.