KTextEditor

expandingdelegate.cpp
1 /* SPDX-License-Identifier: LGPL-2.0-or-later
2 
3  Copyright (C) 2006 Hamish Rodda <[email protected]>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "expandingdelegate.h"
22 
23 #include <QApplication>
24 #include <QBrush>
25 #include <QKeyEvent>
26 #include <QPainter>
27 #include <QTextLine>
28 #include <QTreeView>
29 
30 #include "katepartdebug.h"
31 
32 #include "expandingwidgetmodel.h"
33 
34 ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel *model, QObject *parent)
35  : QItemDelegate(parent)
36  , m_currentColumnStart(0)
37  , m_model(model)
38 {
39 }
40 
41 // Gets the background-color in the way QItemDelegate does it
42 static QColor getUsedBackgroundColor(const QStyleOptionViewItem &option, const QModelIndex &index)
43 {
44  if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) {
46  if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) {
47  cg = QPalette::Inactive;
48  }
49 
50  return option.palette.brush(cg, QPalette::Highlight).color();
51  } else {
52  QVariant value = index.data(Qt::BackgroundRole);
53  if (value.canConvert<QBrush>()) {
54  return qvariant_cast<QBrush>(value).color();
55  }
56  }
57 
58  return QApplication::palette().base().color();
59 }
60 
61 static void dampColors(QColor &col)
62 {
63  // Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast
64  // The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant.
65 
66  col.setBlue(0);
67  col.setRed(col.red() / 2);
68 }
69 
70 // A hack to compute more eye-focused contrast values
71 static double readabilityContrast(QColor foreground, QColor background)
72 {
73  dampColors(foreground);
74  dampColors(background);
75  return abs(foreground.green() - background.green()) + abs(foreground.red() - background.red()) + abs(foreground.blue() - background.blue());
76 }
77 
78 void ExpandingDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optionOld, const QModelIndex &index) const
79 {
80  QStyleOptionViewItem option(optionOld);
81 
82  m_currentIndex = index;
83 
84  adjustStyle(index, option);
85 
86  if (index.column() == 0) {
87  model()->placeExpandingWidget(index);
88  }
89 
90  // Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget.
91  if (model()->isPartiallyExpanded(index) == ExpandingWidgetModel::ExpandUpwards) {
92  m_cachedAlignment = Qt::AlignBottom;
93  } else {
94  m_cachedAlignment = Qt::AlignTop;
95  }
96 
97  option.decorationAlignment = m_cachedAlignment;
98  option.displayAlignment = m_cachedAlignment;
99 
100  // qCDebug(LOG_KTE) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state &
101  // QStyle::State_Selected);
102 
103  m_cachedHighlights.clear();
104  m_backgroundColor = getUsedBackgroundColor(option, index);
105 
106  if (model()->indexIsItem(index)) {
107  m_currentColumnStart = 0;
108  m_cachedHighlights = createHighlighting(index, option);
109  }
110 
111  /*qCDebug(LOG_KTE) << "Highlights for line:";
112  for (const QTextLayout::FormatRange& fr : m_cachedHighlights)
113  qCDebug(LOG_KTE) << fr.start << " len " << fr.length << " format ";*/
114 
115  QItemDelegate::paint(painter, option, index);
116 
119  if (model()->isExpanded(index) && model()->expandingWidget(index)) {
120  model()->expandingWidget(index)->update();
121  }
122 }
123 
124 QVector<QTextLayout::FormatRange> ExpandingDelegate::createHighlighting(const QModelIndex &index, QStyleOptionViewItem &option) const
125 {
126  Q_UNUSED(index);
127  Q_UNUSED(option);
129 }
130 
131 QSize ExpandingDelegate::basicSizeHint(const QModelIndex &index) const
132 {
134 }
135 
136 QSize ExpandingDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
137 {
138  QSize s = QItemDelegate::sizeHint(option, index);
139  if (model()->isExpanded(index) && model()->expandingWidget(index)) {
140  QWidget *widget = model()->expandingWidget(index);
141  QSize widgetSize = widget->size();
142 
143  s.setHeight(widgetSize.height() + s.height() + 10); // 10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets
144  } else if (model()->isPartiallyExpanded(index)) {
145  s.setHeight(s.height() + 30 + 10);
146  }
147  return s;
148 }
149 
150 void ExpandingDelegate::adjustStyle(const QModelIndex &index, QStyleOptionViewItem &option) const
151 {
152  Q_UNUSED(index)
153  Q_UNUSED(option)
154 }
155 
156 void ExpandingDelegate::adjustRect(QRect &rect) const
157 {
158  if (!model()->indexIsItem(m_currentIndex) /*&& m_currentIndex.column() == 0*/) {
159  rect.setLeft(model()->treeView()->columnViewportPosition(0));
160  int columnCount = model()->columnCount(m_currentIndex.parent());
161 
162  if (!columnCount) {
163  return;
164  }
165  rect.setRight(model()->treeView()->columnViewportPosition(columnCount - 1) + model()->treeView()->columnWidth(columnCount - 1));
166  }
167 }
168 
169 void ExpandingDelegate::drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &_rect, const QString &text) const
170 {
171  QRect rect(_rect);
172 
173  adjustRect(rect);
174 
175  QTextLayout layout(text, option.font, painter->device());
176 
177  QVector<QTextLayout::FormatRange> additionalFormats;
178 
179  int missingFormats = text.length();
180 
181  for (int i = 0; i < m_cachedHighlights.count(); ++i) {
182  if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) {
183  continue;
184  }
185 
186  if (additionalFormats.isEmpty())
187  if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) {
189  before.start = 0;
190  before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart;
191  before.format = m_cachedHighlights[i - 1].format;
192  additionalFormats.append(before);
193  }
194 
196  format.start = m_cachedHighlights[i].start - m_currentColumnStart;
197  format.length = m_cachedHighlights[i].length;
198  format.format = m_cachedHighlights[i].format;
199 
200  additionalFormats.append(format);
201  }
202  if (!additionalFormats.isEmpty()) {
203  missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start);
204  }
205 
206  if (missingFormats > 0) {
208  format.start = text.length() - missingFormats;
209  format.length = missingFormats;
210  QTextCharFormat fm;
211  fm.setForeground(option.palette.text());
212  format.format = fm;
213  additionalFormats.append(format);
214  }
215 
216  if (m_backgroundColor.isValid()) {
217  QColor background = m_backgroundColor;
218  // qCDebug(LOG_KTE) << text << "background:" << background.name();
219  // Now go through the formats, and make sure the contrast background/foreground is readable
220  for (int a = 0; a < additionalFormats.size(); ++a) {
221  QColor currentBackground = background;
222  if (additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) {
223  currentBackground = additionalFormats[a].format.background().color();
224  }
225 
226  QColor currentColor = additionalFormats[a].format.foreground().color();
227 
228  double currentContrast = readabilityContrast(currentColor, currentBackground);
229  QColor invertedColor(0xffffffff - additionalFormats[a].format.foreground().color().rgb());
230  double invertedContrast = readabilityContrast(invertedColor, currentBackground);
231 
232  // qCDebug(LOG_KTE) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name();
233 
234  if (invertedContrast > currentContrast) {
235  // qCDebug(LOG_KTE) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name();
236  QBrush b(additionalFormats[a].format.foreground());
237  b.setColor(invertedColor);
238  additionalFormats[a].format.setForeground(b);
239  }
240  }
241  }
242 
243  for (int a = additionalFormats.size() - 1; a >= 0; --a) {
244  if (additionalFormats[a].length == 0) {
245  additionalFormats.removeAt(a);
246  } else {
249  QTextCharFormat fm;
250  fm.setForeground(QBrush(additionalFormats[a].format.foreground().color()));
251  fm.setBackground(additionalFormats[a].format.background());
252  fm.setUnderlineStyle(additionalFormats[a].format.underlineStyle());
253  fm.setUnderlineColor(additionalFormats[a].format.underlineColor());
254  fm.setFontWeight(additionalFormats[a].format.fontWeight());
255  additionalFormats[a].format = fm;
256  }
257  }
258 
259  layout.setFormats(additionalFormats);
260 
261  QTextOption to;
262 
263  to.setAlignment(static_cast<Qt::Alignment>(m_cachedAlignment | option.displayAlignment));
264 
266  layout.setTextOption(to);
267 
268  layout.beginLayout();
269  QTextLine line = layout.createLine();
270  // Leave some extra space when the text is right-aligned
271  line.setLineWidth(rect.width() - (option.displayAlignment == Qt::AlignRight ? 8 : 0));
272  layout.endLayout();
273 
274  // We need to do some hand layouting here
275  if (to.alignment() & Qt::AlignBottom) {
276  layout.draw(painter, QPoint(rect.left(), rect.bottom() - (int)line.height()));
277  } else {
278  layout.draw(painter, rect.topLeft());
279  }
280 
281  return;
282 
283  // if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n')))
284  // str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text);
285  // qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter);
286 }
287 
288 void ExpandingDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const
289 {
290  if (model()->indexIsItem(m_currentIndex)) {
291  QItemDelegate::drawDecoration(painter, option, rect, pixmap);
292  }
293 }
294 
295 void ExpandingDelegate::drawBackground(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
296 {
297  Q_UNUSED(index)
298  // Problem: This isn't called at all, because drawBrackground is not virtual :-/
299  QStyle *style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style();
300  style->drawControl(QStyle::CE_ItemViewItem, &option, painter);
301 }
302 
303 ExpandingWidgetModel *ExpandingDelegate::model() const
304 {
305  return m_model;
306 }
307 
308 void ExpandingDelegate::heightChanged() const
309 {
310 }
311 
312 bool ExpandingDelegate::editorEvent(QEvent *event, QAbstractItemModel * /*model*/, const QStyleOptionViewItem & /*option*/, const QModelIndex &index)
313 {
314  if (event->type() == QEvent::MouseButtonRelease) {
315  event->accept();
316  model()->setExpanded(index, !model()->isExpanded(index));
317  heightChanged();
318 
319  return true;
320  } else {
321  event->ignore();
322  }
323 
324  return false;
325 }
326 
328 {
330 
331  for (int i = 0; i + 2 < customHighlights.count(); i += 3) {
332  if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i + 1].canConvert(QVariant::Int) || !customHighlights[i + 2].canConvert<QTextFormat>()) {
333  qCWarning(LOG_KTE) << "Unable to convert triple to custom formatting.";
334  continue;
335  }
336 
338  format.start = customHighlights[i].toInt();
339  format.length = customHighlights[i + 1].toInt();
340  format.format = customHighlights[i + 2].value<QTextFormat>().toCharFormat();
341 
342  if (!format.format.isValid()) {
343  qCWarning(LOG_KTE) << "Format is not valid";
344  }
345 
346  ret << format;
347  }
348  return ret;
349 }
QLayout * layout() const const
bool canConvert(int targetTypeId) const const
void drawDisplay(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QString &text) const override
MouseButtonRelease
void setHeight(int height)
void setUnderlineStyle(QTextCharFormat::UnderlineStyle style)
QEvent::Type type() const const
qreal height() const const
virtual void drawDecoration(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, const QPixmap &pixmap) const const
QStyle * style() const const
void setBlue(int blue)
Qt::Alignment alignment() const const
virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const const override
void setRed(int red)
AlignBottom
T value(int i) const const
void setLineWidth(qreal width)
const QColor & color() const const
void setForeground(const QBrush &brush)
QVector< QTextLayout::FormatRange > highlightingFromVariantList(const QList< QVariant > &customHighlights) const
Creates a list of FormatRanges as should be returned by createHighlighting from a list of QVariants a...
void setWrapMode(QTextOption::WrapMode mode)
int count(const T &value) const const
int red() const const
QPalette palette()
int left() const const
BackgroundRole
void setBackground(const QBrush &brush)
QPaintDevice * device() const const
virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const const override
QRect rect() const const
int green() const const
void setFontWeight(int weight)
void setRight(int x)
int blue() const const
const QBrush & base() const const
int width() const const
QVariant data(int role) const const
QStyle * style()
virtual void drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const =0
int height() const const
int bottom() const const
QPoint topLeft() const const
int column() const const
int length() const const
void setUnderlineColor(const QColor &color)
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
void setColor(const QColor &color)
void setAlignment(Qt::Alignment alignment)
virtual bool event(QEvent *event) override
void setLeft(int x)
Cares about expanding/un-expanding items in a tree-view together with ExpandingDelegate.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Thu Aug 6 2020 22:56:43 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.