| 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 | |