KTextEditor

expandingwidgetmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2007 David Nolden <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "expandingwidgetmodel.h"
8 
9 #include <QApplication>
10 #include <QBrush>
11 #include <QModelIndex>
12 #include <QTreeView>
13 
14 #include <KColorUtils>
15 #include <KTextEdit>
16 #include <ktexteditor/codecompletionmodel.h>
17 
18 #include "katecompletiondelegate.h"
19 #include "katepartdebug.h"
20 
21 using namespace KTextEditor;
22 
23 inline QModelIndex firstColumn(const QModelIndex &index)
24 {
25  return index.sibling(index.row(), 0);
26 }
27 
28 ExpandingWidgetModel::ExpandingWidgetModel(QWidget *parent)
29  : QAbstractItemModel(parent)
30 {
31 }
32 
33 ExpandingWidgetModel::~ExpandingWidgetModel()
34 {
35  clearExpanding();
36 }
37 
38 static QColor doAlternate(const QColor &color)
39 {
40  QColor background = QApplication::palette().window().color();
41  return KColorUtils::mix(color, background, 0.15);
42 }
43 
45 {
46  int matchQuality = contextMatchQuality(index.sibling(index.row(), 0));
47 
48  if (matchQuality > 0) {
49  bool alternate = index.row() & 1;
50 
51  QColor badMatchColor(0xff00aa44); // Blue-ish green
52  QColor goodMatchColor(0xff00ff00); // Green
53 
54  QColor background = treeView()->palette().light().color();
55 
56  QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality) / 10.0);
57 
58  if (alternate) {
59  totalColor = doAlternate(totalColor);
60  }
61 
62  const qreal dynamicTint = 0.2;
63  const qreal minimumTint = 0.2;
64  qreal tintStrength = (dynamicTint * matchQuality) / 10;
65  if (tintStrength != 0.0) {
66  tintStrength += minimumTint; // Some minimum tinting strength, else it's not visible any more
67  }
68 
69  return KColorUtils::tint(background, totalColor, tintStrength).rgb();
70  } else {
71  return 0;
72  }
73 }
74 
75 QVariant ExpandingWidgetModel::data(const QModelIndex &index, int role) const
76 {
77  switch (role) {
78  case Qt::BackgroundRole: {
79  if (index.column() == 0) {
80  // Highlight by match-quality
81  uint color = matchColor(index);
82  if (color) {
83  return QBrush(color);
84  }
85  }
86  // Use a special background-color for expanded items
87  if (isExpanded(index)) {
88  if (index.row() & 1) {
89  return doAlternate(treeView()->palette().toolTipBase().color());
90  } else {
91  return treeView()->palette().toolTipBase();
92  }
93  }
94  }
95  }
96  return QVariant();
97 }
98 
100 {
101  QMap<QModelIndex, ExpandingWidgetModel::ExpandingType> oldExpandState = m_expandState;
102  for (auto &widget : std::as_const(m_expandingWidgets)) {
103  if (widget) {
104  widget->deleteLater(); // By using deleteLater, we prevent crashes when an action within a widget makes the completion cancel
105  }
106  }
107  m_expandingWidgets.clear();
108  m_expandState.clear();
109 
110  for (auto it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) {
111  if (it.value() == Expanded) {
112  Q_EMIT dataChanged(it.key(), it.key());
113  }
114  }
115 }
116 
118 {
119  QModelIndex idx(firstColumn(idx_));
120 
121  if (!m_expandState.contains(idx)) {
122  m_expandState.insert(idx, NotExpandable);
123  QVariant v = data(idx, CodeCompletionModel::IsExpandable);
124  if (v.canConvert<bool>() && v.toBool()) {
125  m_expandState[idx] = Expandable;
126  }
127  }
128 
129  return m_expandState[idx] != NotExpandable;
130 }
131 
133 {
134  QModelIndex idx(firstColumn(idx_));
135  return m_expandState.contains(idx) && m_expandState[idx] == Expanded;
136 }
137 
139 {
140  QModelIndex idx(firstColumn(idx_));
141 
142  // qCDebug(LOG_KTE) << "Setting expand-state of row " << idx.row() << " to " << expanded;
143  if (!idx.isValid()) {
144  return;
145  }
146 
147  if (isExpandable(idx)) {
148  if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) {
149  m_expandingWidgets[idx]->hide();
150  }
151 
152  m_expandState[idx] = expanded ? Expanded : Expandable;
153 
154  if (expanded && !m_expandingWidgets.contains(idx)) {
155  QVariant v = data(idx, CodeCompletionModel::ExpandingWidget);
156 
157  if (v.canConvert<QWidget *>()) {
158  m_expandingWidgets[idx] = v.value<QWidget *>();
159  } else if (v.canConvert<QString>()) {
160  // Create a html widget that shows the given string
161  QTextEdit *edit = new QTextEdit(v.toString());
162  edit->setReadOnly(true);
163  edit->resize(200, 50); // Make the widget small so it embeds nicely.
164  m_expandingWidgets[idx] = edit;
165  } else {
166  m_expandingWidgets[idx] = nullptr;
167  }
168  }
169 
170  Q_EMIT dataChanged(idx, idx);
171 
172  if (treeView()) {
173  treeView()->scrollTo(idx);
174  }
175  }
176 }
177 
178 int ExpandingWidgetModel::basicRowHeight(const QModelIndex &idx_) const
179 {
180  QModelIndex idx(firstColumn(idx_));
181 
182  auto *delegate = (KateCompletionDelegate *)treeView()->itemDelegate(idx);
183  if (!delegate || !idx.isValid()) {
184  qCDebug(LOG_KTE) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate";
185  return 15;
186  }
187  return delegate->basicSizeHint(idx).height();
188 }
189 
191 {
192  QModelIndex idx(firstColumn(idx_));
193  if (!idx.isValid() || !isExpanded(idx)) {
194  return;
195  }
196 
197  QWidget *w = m_expandingWidgets.value(idx);
198  if (!w) {
199  return;
200  }
201 
202  QRect rect = treeView()->visualRect(idx);
203 
204  if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) {
205  // The item is currently not visible
206  w->hide();
207  return;
208  }
209 
210  // Find out the basic width of the row
211  const int numColumns = idx.model()->columnCount(idx.parent());
212  int left = 0;
213  for (int i = 0; i < numColumns; ++i) {
214  auto index = idx.sibling(idx.row(), i);
215  auto text = index.data().toString();
216  if (!index.data(Qt::DecorationRole).isNull()) {
217  left += 24;
218  }
219 
220  if (!text.isEmpty()) {
221  left += treeView()->visualRect(index).left();
222  break;
223  }
224  }
225  rect.setLeft(rect.left() + left);
226 
227  for (int i = 0; i < numColumns; ++i) {
228  QModelIndex rightMostIndex = idx.sibling(idx.row(), i);
229  int right = treeView()->visualRect(rightMostIndex).right();
230  if (right > rect.right()) {
231  rect.setRight(right);
232  }
233  }
234  rect.setRight(rect.right() - 5);
235 
236  // These offsets must match exactly those used in KateCompletionDeleage::sizeHint()
237  rect.setTop(rect.top() + basicRowHeight(idx));
238  rect.setHeight(w->height());
239 
240  if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) {
241  w->setParent(treeView()->viewport());
242 
243  w->setGeometry(rect);
244  w->show();
245  }
246 }
247 
249 {
250  for (QMap<QModelIndex, QPointer<QWidget>>::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) {
251  placeExpandingWidget(it.key());
252  }
253 }
254 
256 {
257  QModelIndex idx(firstColumn(idx_));
258 
259  if (m_expandingWidgets.contains(idx)) {
260  return m_expandingWidgets[idx];
261  } else {
262  return nullptr;
263  }
264 }
265 
266 void ExpandingWidgetModel::cacheIcons() const
267 {
268  if (m_expandedIcon.isNull()) {
269  m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down"));
270  }
271 
272  if (m_collapsedIcon.isNull()) {
273  m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right"));
274  }
275 }
276 
277 QList<QVariant> mergeCustomHighlighting(int leftSize, const QList<QVariant> &left, int rightSize, const QList<QVariant> &right)
278 {
279  QList<QVariant> ret = left;
280  if (left.isEmpty()) {
281  ret << QVariant(0);
282  ret << QVariant(leftSize);
284  }
285 
286  if (right.isEmpty()) {
287  ret << QVariant(leftSize);
288  ret << QVariant(rightSize);
290  } else {
291  QList<QVariant>::const_iterator it = right.constBegin();
292  while (it != right.constEnd()) {
293  {
295  for (int a = 0; a < 2; a++) {
296  ++testIt;
297  if (testIt == right.constEnd()) {
298  qCWarning(LOG_KTE) << "Length of input is not multiple of 3";
299  break;
300  }
301  }
302  }
303 
304  ret << QVariant((*it).toInt() + leftSize);
305  ++it;
306  ret << QVariant((*it).toInt());
307  ++it;
308  ret << *it;
309  if (!(*it).value<QTextFormat>().isValid()) {
310  qCDebug(LOG_KTE) << "Text-format is invalid";
311  }
312  ++it;
313  }
314  }
315  return ret;
316 }
317 
318 // It is assumed that between each two strings, one space is inserted
319 QList<QVariant> mergeCustomHighlighting(QStringList strings, QList<QVariantList> highlights, int grapBetweenStrings)
320 {
321  if (strings.isEmpty()) {
322  qCWarning(LOG_KTE) << "List of strings is empty";
323  return QList<QVariant>();
324  }
325 
326  if (highlights.isEmpty()) {
327  qCWarning(LOG_KTE) << "List of highlightings is empty";
328  return QList<QVariant>();
329  }
330 
331  if (strings.count() != highlights.count()) {
332  qCWarning(LOG_KTE) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same";
333  return QList<QVariant>();
334  }
335 
336  // Merge them together
337  QString totalString = strings[0];
338  QVariantList totalHighlighting = highlights[0];
339 
340  strings.pop_front();
341  highlights.pop_front();
342 
343  while (!strings.isEmpty()) {
344  totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, strings[0].length(), highlights[0]);
345  totalString += strings[0];
346 
347  for (int a = 0; a < grapBetweenStrings; a++) {
348  totalString += QLatin1Char(' ');
349  }
350 
351  strings.pop_front();
352  highlights.pop_front();
353  }
354  // Combine the custom-highlightings
355  return totalHighlighting;
356 }
bool isValid() const const
bool isNull() const const
QMap::const_iterator constBegin() const const
const QColor & color() const const
void placeExpandingWidget(const QModelIndex &row)
Places and shows the expanding-widget for the given row, if it should be visible and is valid.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Does not request data from index, this only returns local data like highlighting for expanded rows an...
void setReadOnly(bool ro)
const QBrush & window() const const
QTextStream & right(QTextStream &stream)
void setParent(QWidget *parent)
BackgroundRole
KGUIADDONS_EXPORT QColor mix(const QColor &c1, const QColor &c2, qreal bias=0.5)
QModelIndex sibling(int row, int column) const const
int right() const const
uint matchColor(const QModelIndex &index) const
Returns the match-color for the given index, or zero if match-quality could not be computed.
void clear()
int count(const T &value) const const
int column() const const
T value() const const
QTextStream & left(QTextStream &stream)
QRgb rgb() const const
QIcon fromTheme(const QString &name)
QColor light(int factor) const const
KGUIADDONS_EXPORT QColor tint(const QColor &base, const QColor &color, qreal amount=0.3)
void clearExpanding()
Unexpand all rows and clear all cached information about them(this includes deleting the expanding-wi...
int left() const const
void hide()
void setRight(int x)
bool isVisible() const const
QVariant data(int role) const const
int bottom() const const
int top() const const
void setHeight(int height)
QMap::const_iterator constEnd() const const
void pop_front()
int length() const const
bool isValid() const const
bool isEmpty() const const
virtual int columnCount(const QModelIndex &parent) const const=0
void setExpanded(QModelIndex index, bool expanded)
Change the expand-state of the row given through index. The display will be updated.
bool isExpanded(const QModelIndex &row) const
QTextStream & left(QTextStream &s)
bool isValid() const const
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
Definition: katetextblock.h:22
bool toBool() const const
void show()
int row() const const
bool canConvert(int targetTypeId) const const
QTextStream & right(QTextStream &s)
void setGeometry(int x, int y, int w, int h)
void setTop(int y)
void resize(int w, int h)
QWidget * expandingWidget(const QModelIndex &row) const
bool isExpandable(const QModelIndex &index) const
QModelIndex parent() const const
QPalette palette()
void setLeft(int x)
QObject * parent() const const
const QAbstractItemModel * model() const const
QString toString() const const
void placeExpandingWidgets()
Place or hides all expanding-widgets to the correct positions. Should be called after the view was sc...
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Dec 8 2022 03:52:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.