File indexing completed on 2024-04-28 05:49:14

0001 /*
0002     SPDX-FileCopyrightText: 2011 Kåre Särs <kare.sars@iki.fi>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "SearchResultsDelegate.h"
0008 #include "MatchModel.h"
0009 
0010 #include <KLocalizedString>
0011 #include <KSyntaxHighlighting/Theme>
0012 #include <KTextEditor/Editor>
0013 #include <QAbstractTextDocumentLayout>
0014 #include <QApplication>
0015 #include <QModelIndex>
0016 #include <QPainter>
0017 #include <QTextCharFormat>
0018 #include <QTextDocument>
0019 
0020 #include <drawing_utils.h>
0021 #include <ktexteditor_utils.h>
0022 
0023 // make list spacing resemble the default list spacing
0024 // (which would not be the case with default QTextDocument margin)
0025 static const int s_ItemMargin = 1;
0026 
0027 SearchResultsDelegate::SearchResultsDelegate(QObject *parent)
0028     : QStyledItemDelegate(parent)
0029 {
0030     const auto e = KTextEditor::Editor::instance();
0031     const auto theme = e->theme();
0032 
0033     const auto updateColors = [this] {
0034         m_font = Utils::editorFont();
0035         const auto theme = KTextEditor::Editor::instance()->theme();
0036 
0037         m_textColorLight = m_textColor = QColor::fromRgba(theme.textColor(KSyntaxHighlighting::Theme::Normal));
0038         m_textColorLight.setAlpha(150);
0039 
0040         m_numBackground = m_altBackground = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::IconBorder));
0041         m_numBackground.setAlpha(150);
0042 
0043         m_borderColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::Separator));
0044 
0045         m_searchColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::SearchHighlight));
0046         m_searchColor.setAlpha(200);
0047 
0048         m_replaceColor = QColor::fromRgba(theme.editorColor(KSyntaxHighlighting::Theme::ReplaceHighlight));
0049         m_replaceColor.setAlpha(200);
0050     };
0051     connect(e, &KTextEditor::Editor::configChanged, this, updateColors);
0052     updateColors();
0053     m_font = Utils::editorFont();
0054 }
0055 
0056 static int lineNumAreaWidth(const QModelIndex &index, const QFontMetrics &fm)
0057 {
0058     const auto lastRangeForFile = index.parent().data(MatchModel::LastMatchedRangeInFileRole).value<KTextEditor::Range>();
0059     const QString lineCol = QStringLiteral("%1:%2").arg(lastRangeForFile.start().line() + 1).arg(lastRangeForFile.start().column() + 1);
0060     return fm.horizontalAdvance(lineCol);
0061 }
0062 
0063 void SearchResultsDelegate::paintMatchItem(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const
0064 {
0065     const KateSearchMatch match = index.data(MatchModel::MatchItemRole).value<KateSearchMatch>();
0066     const int line = match.range.start().line() + 1;
0067     const int col = match.range.start().column() + 1;
0068     const QString lineCol = QStringLiteral("%1:%2").arg(line).arg(col);
0069 
0070     QStyle *style = opt.widget->style() ? opt.widget->style() : qApp->style();
0071     const QFontMetrics fm(m_font);
0072 
0073     static constexpr int hMargins = 2;
0074 
0075     const QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, opt.widget);
0076 
0077     QRectF iconBorderRect = textRect;
0078 
0079     p->save();
0080 
0081     p->setFont(m_font);
0082 
0083     const bool rtl = opt.direction == Qt::RightToLeft;
0084 
0085     // line num area
0086     const bool selected = opt.state & QStyle::State_Selected;
0087 
0088     const int lineColWidth = lineNumAreaWidth(index, fm) + (hMargins * 2);
0089     if (rtl) {
0090         iconBorderRect.setX(textRect.width() - lineColWidth);
0091     }
0092     iconBorderRect.setWidth(lineColWidth);
0093 
0094     // line number area background
0095     p->fillRect(iconBorderRect, m_numBackground);
0096 
0097     // line numbers
0098     const QBrush lineNumCol = selected ? m_textColor : m_textColorLight;
0099     p->setPen(QPen(lineNumCol, 1));
0100     p->drawText(iconBorderRect.adjusted(2., 0., -2., 0.), Qt::AlignVCenter, lineCol);
0101 
0102     // draw the line number area separator line
0103     p->setPen(QPen(m_borderColor, 1));
0104     const QPointF p1 = rtl ? iconBorderRect.topLeft() : iconBorderRect.topRight();
0105     const QPointF p2 = rtl ? iconBorderRect.bottomLeft() : iconBorderRect.bottomRight();
0106     p->drawLine(p1, p2);
0107 
0108     // match
0109     p->setPen(QPen(m_textColor, 1));
0110     QString text;
0111     bool replacing = !match.replaceText.isEmpty();
0112     if (replacing) {
0113         text = match.preMatchStr + match.matchStr + match.replaceText + match.postMatchStr;
0114     } else {
0115         text = match.preMatchStr + match.matchStr + match.postMatchStr;
0116     }
0117 
0118     QList<QTextLayout::FormatRange> formats;
0119 
0120     QTextLayout::FormatRange fontFmt;
0121     fontFmt.start = 0;
0122     fontFmt.length = text.size();
0123     fontFmt.format.setFont(m_font);
0124     formats << fontFmt;
0125 
0126     QTextLayout::FormatRange matchFmt;
0127     matchFmt.start = match.preMatchStr.size();
0128     matchFmt.length = match.matchStr.size();
0129     matchFmt.format.setBackground(m_searchColor);
0130     matchFmt.format.setFontStrikeOut(replacing);
0131     formats << matchFmt;
0132 
0133     if (replacing) {
0134         QTextLayout::FormatRange repFmt;
0135         repFmt.start = match.preMatchStr.size() + match.matchStr.size();
0136         repFmt.length = match.replaceText.size();
0137         repFmt.format.setBackground(m_replaceColor);
0138         formats << repFmt;
0139     }
0140 
0141     // paint the match text
0142     auto opts = opt;
0143     opts.rect = rtl ? textRect.adjusted(0, 0, -(iconBorderRect.width() + hMargins * 2), 0) : textRect.adjusted(iconBorderRect.width() + hMargins * 2, 0, 0, 0);
0144     Utils::paintItemViewText(p, text, opts, formats);
0145 
0146     p->restore();
0147 }
0148 
0149 static bool isMatchItem(const QModelIndex &index)
0150 {
0151     return index.parent().isValid() && index.parent().parent().isValid();
0152 }
0153 
0154 void SearchResultsDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0155 {
0156     if (!index.isValid()) {
0157         QStyledItemDelegate::paint(painter, option, index);
0158         return;
0159     }
0160 
0161     QStyleOptionViewItem options = option;
0162     initStyleOption(&options, index);
0163 
0164     // draw item without text
0165     options.text = QString();
0166     if (!isMatchItem(index)) {
0167         options.backgroundBrush = m_altBackground;
0168     }
0169     options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget);
0170 
0171     if (isMatchItem(index)) {
0172         paintMatchItem(painter, options, index);
0173     } else {
0174         QTextDocument doc;
0175         doc.setDefaultFont(m_font);
0176         doc.setDocumentMargin(s_ItemMargin);
0177         doc.setHtml(index.data().toString());
0178 
0179         painter->save();
0180 
0181         // draw area
0182         QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options);
0183         if (index.flags() == Qt::NoItemFlags) {
0184             painter->setBrush(QBrush(QWidget().palette().color(QPalette::Base)));
0185             painter->setPen(QWidget().palette().color(QPalette::Base));
0186             painter->drawRect(QRect(clip.topLeft() - QPoint(20, 0), clip.bottomRight()));
0187             painter->translate(clip.topLeft() - QPoint(20, 0));
0188         } else {
0189             painter->translate(clip.topLeft() - QPoint(0, 0));
0190         }
0191         QAbstractTextDocumentLayout::PaintContext pcontext;
0192         pcontext.palette.setColor(QPalette::Text, options.palette.text().color());
0193         doc.documentLayout()->draw(painter, pcontext);
0194 
0195         painter->restore();
0196     }
0197 }
0198 
0199 QSize SearchResultsDelegate::sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const
0200 {
0201     QSize s = QStyledItemDelegate::sizeHint(opt, index);
0202     QFontMetrics fm(m_font);
0203     s.setHeight(fm.lineSpacing());
0204     s = s.grownBy({0, 2, 0, 2});
0205     return s;
0206 }
0207 
0208 #include "moc_SearchResultsDelegate.cpp"