1 | /* |
2 | SPDX-FileCopyrightText: 2017-18 Friedrich W. H. Kossebau <kossebau@kde.org> |
3 | |
4 | SPDX-License-Identifier: LGPL-2.0-or-later |
5 | */ |
6 | |
7 | #include "kateannotationitemdelegate.h" |
8 | |
9 | #include <ktexteditor/annotationinterface.h> |
10 | #include <ktexteditor/view.h> |
11 | |
12 | #include <QHelpEvent> |
13 | #include <QPainter> |
14 | #include <QToolTip> |
15 | |
16 | #include <math.h> |
17 | |
18 | KateAnnotationItemDelegate::KateAnnotationItemDelegate(QObject *parent) |
19 | : KTextEditor::AbstractAnnotationItemDelegate(parent) |
20 | , m_cachedDataContentFontMetrics(QFont()) |
21 | { |
22 | } |
23 | |
24 | KateAnnotationItemDelegate::~KateAnnotationItemDelegate() = default; |
25 | |
26 | void KateAnnotationItemDelegate::paint(QPainter *painter, |
27 | const KTextEditor::StyleOptionAnnotationItem &option, |
28 | KTextEditor::AnnotationModel *model, |
29 | int line) const |
30 | { |
31 | Q_ASSERT(painter); |
32 | Q_ASSERT(model); |
33 | if (!painter || !model) { |
34 | return; |
35 | } |
36 | // TODO: also test line for validity for sake of completeness? |
37 | |
38 | painter->save(); |
39 | |
40 | const int margin = 3; |
41 | |
42 | const QVariant background = model->data(line, role: Qt::BackgroundRole); |
43 | // Fill the background |
44 | if (background.isValid()) { |
45 | painter->fillRect(option.rect, background.value<QBrush>()); |
46 | } |
47 | |
48 | const QVariant foreground = model->data(line, role: Qt::ForegroundRole); |
49 | // Set the pen for drawing the foreground |
50 | if (foreground.isValid() && foreground.canConvert<QPen>()) { |
51 | painter->setPen(foreground.value<QPen>()); |
52 | } |
53 | |
54 | // Draw a border around all adjacent entries that have the same text as the currently hovered one |
55 | if ((option.state & QStyle::State_MouseOver) && (option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::InGroup)) { |
56 | // Use floating point coordinates to support scaled rendering |
57 | QRectF rect(option.rect); |
58 | rect.adjust(xp1: 0.5, yp1: 0.5, xp2: -0.5, yp2: -0.5); |
59 | |
60 | // draw left and right highlight borders |
61 | painter->drawLine(p1: rect.topLeft(), p2: rect.bottomLeft()); |
62 | painter->drawLine(p1: rect.topRight(), p2: rect.bottomRight()); |
63 | |
64 | if ((option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::GroupBegin) && (option.wrappedLine == 0)) { |
65 | painter->drawLine(p1: rect.topLeft(), p2: rect.topRight()); |
66 | } |
67 | |
68 | if ((option.annotationItemGroupingPosition & KTextEditor::StyleOptionAnnotationItem::GroupEnd) |
69 | && (option.wrappedLine == (option.wrappedLineCount - 1))) { |
70 | painter->drawLine(p1: rect.bottomLeft(), p2: rect.bottomRight()); |
71 | } |
72 | } |
73 | // reset pen |
74 | if (foreground.isValid()) { |
75 | QPen pen = painter->pen(); |
76 | pen.setWidth(1); |
77 | painter->setPen(pen); |
78 | } |
79 | |
80 | // Now draw the normal text |
81 | const QVariant text = model->data(line, role: Qt::DisplayRole); |
82 | if ((option.wrappedLine == 0) && text.isValid() && text.canConvert<QString>()) { |
83 | painter->drawText(x: option.rect.x() + margin, |
84 | y: option.rect.y(), |
85 | w: option.rect.width() - 2 * margin, |
86 | h: option.rect.height(), |
87 | flags: Qt::AlignLeft | Qt::AlignVCenter, |
88 | str: text.toString()); |
89 | } |
90 | |
91 | painter->restore(); |
92 | } |
93 | |
94 | bool KateAnnotationItemDelegate::helpEvent(QHelpEvent *event, |
95 | KTextEditor::View *view, |
96 | const KTextEditor::StyleOptionAnnotationItem &option, |
97 | KTextEditor::AnnotationModel *model, |
98 | int line) |
99 | { |
100 | Q_UNUSED(option); |
101 | |
102 | if (!model || event->type() != QEvent::ToolTip) { |
103 | return false; |
104 | } |
105 | |
106 | const QVariant data = model->data(line, role: Qt::ToolTipRole); |
107 | if (!data.isValid()) { |
108 | return false; |
109 | } |
110 | |
111 | const QString toolTipText = data.toString(); |
112 | if (toolTipText.isEmpty()) { |
113 | return false; |
114 | } |
115 | |
116 | QToolTip::showText(pos: event->globalPos(), text: toolTipText, w: view, rect: option.rect); |
117 | |
118 | return true; |
119 | } |
120 | |
121 | void KateAnnotationItemDelegate::hideTooltip(KTextEditor::View *view) |
122 | { |
123 | Q_UNUSED(view); |
124 | QToolTip::hideText(); |
125 | } |
126 | |
127 | QSize KateAnnotationItemDelegate::sizeHint(const KTextEditor::StyleOptionAnnotationItem &option, KTextEditor::AnnotationModel *model, int line) const |
128 | { |
129 | Q_ASSERT(model); |
130 | if (!model) { |
131 | return QSize(0, 0); |
132 | } |
133 | |
134 | // recalculate m_maxCharWidth if needed |
135 | if (m_maxCharWidth == 0.0 || (option.contentFontMetrics != m_cachedDataContentFontMetrics)) { |
136 | // based on old code written when just a hash was shown, could see an update |
137 | // Loop to determine the widest numeric character in the current font. |
138 | m_maxCharWidth = 0.0; |
139 | for (char c = '0'; c <= '9'; ++c) { |
140 | const qreal charWidth = ceil(x: option.contentFontMetrics.horizontalAdvance(QLatin1Char(c))); |
141 | m_maxCharWidth = qMax(a: m_maxCharWidth, b: charWidth); |
142 | } |
143 | |
144 | m_cachedDataContentFontMetrics = option.contentFontMetrics; |
145 | } |
146 | |
147 | const QString annotationText = model->data(line, role: Qt::DisplayRole).toString(); |
148 | return QSize(annotationText.length() * m_maxCharWidth + 8, option.contentFontMetrics.height()); |
149 | } |
150 | |