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"