KPimTextEdit

plaintextsyntaxspellcheckinghighlighter.cpp
1 /*
2  SPDX-FileCopyrightText: 2015-2022 Laurent Montel <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "plaintextsyntaxspellcheckinghighlighter.h"
8 #include "kpimtextedit_debug.h"
9 #include "plaintexteditor.h"
10 
11 #include <KSyntaxHighlighting/Definition>
12 #include <KSyntaxHighlighting/Format>
13 #include <KSyntaxHighlighting/State>
14 #include <KSyntaxHighlighting/Theme>
15 
16 #include <vector>
17 
18 Q_DECLARE_METATYPE(QTextBlock)
19 
20 using namespace KPIMTextEdit;
21 
22 namespace KPIMTextEdit
23 {
24 struct SpellCheckRange {
25  SpellCheckRange(int o, int l)
26  : offset(o)
27  , length(l)
28  {
29  }
30 
31  Q_REQUIRED_RESULT int end() const
32  {
33  return offset + length;
34  }
35 
36  int offset;
37  int length;
38 };
39 
40 QDebug operator<<(QDebug dbg, SpellCheckRange s)
41 {
42  dbg << '(' << s.offset << ',' << s.length << ')';
43  return dbg;
44 }
45 
46 class TextBlockUserData : public QTextBlockUserData
47 {
48 public:
50  QTextBlockUserData *spellData = nullptr;
51 };
52 
53 class PlainTextSyntaxSpellCheckingHighlighterPrivate
54 {
55 public:
56  explicit PlainTextSyntaxSpellCheckingHighlighterPrivate(PlainTextEditor *plainText)
57  : editor(plainText)
58  {
59  }
60 
61  PlainTextEditor *const editor;
62  QColor misspelledColor;
63  bool spellCheckingEnabled = false;
64 
65  // We can't use QTextBlock user data, Sonnet is occupying that already
66  // and we can't just daisy-chain them, as QTextBlock deletes the previous
67  // one when setting a new one. Instead, we need to store this out-of-band.
69 
70  std::vector<SpellCheckRange> spellCheckRanges;
71 };
72 }
73 
74 PlainTextSyntaxSpellCheckingHighlighter::PlainTextSyntaxSpellCheckingHighlighter(PlainTextEditor *plainText, const QColor &misspelledColor)
75  : Sonnet::Highlighter(plainText)
76  , d(new PlainTextSyntaxSpellCheckingHighlighterPrivate(plainText))
77 {
78  qRegisterMetaType<QTextBlock>();
79  d->misspelledColor = misspelledColor;
80  setAutomatic(false);
81 }
82 
83 PlainTextSyntaxSpellCheckingHighlighter::~PlainTextSyntaxSpellCheckingHighlighter() = default;
84 
85 void PlainTextSyntaxSpellCheckingHighlighter::toggleSpellHighlighting(bool on)
86 {
87  if (on != d->spellCheckingEnabled) {
88  d->spellCheckingEnabled = on;
89  rehighlight();
90  }
91 }
92 
93 void PlainTextSyntaxSpellCheckingHighlighter::setDefinition(const KSyntaxHighlighting::Definition &def)
94 {
95  const auto needsRehighlight = definition() != def;
96  AbstractHighlighter::setDefinition(def);
97  if (needsRehighlight) {
98  rehighlight();
99  }
100 }
101 
103 {
104  d->spellCheckRanges.clear();
105 
107  if (currentBlock().position() > 0) {
108  const auto prevBlock = currentBlock().previous();
109  state = d->blockState.value(prevBlock.userState());
110  }
111 
112  state = highlightLine(text, state);
113  if (d->spellCheckingEnabled && d->editor->isEnabled() && !d->spellCheckRanges.empty()) {
114  Highlighter::highlightBlock(text);
115  }
116 
117  if (currentBlockState() <= 0) { // first time we highlight this
118  setCurrentBlockState(d->blockState.size() + 1);
119  d->blockState.insert(currentBlockState(), state);
120  return;
121  }
122 
123  if (d->blockState.value(currentBlockState()) == state) {
124  return;
125  }
126  d->blockState.insert(currentBlockState(), state);
127 
128  const auto nextBlock = currentBlock().next();
129  if (nextBlock.isValid()) {
131  this,
132  [this, nextBlock] {
133  rehighlightBlock(nextBlock);
134  },
136  }
137 }
138 
140 {
141  Q_UNUSED(start)
142  Q_UNUSED(count)
143 }
144 
146 {
147  setMisspelledColor(d->misspelledColor);
148  for (const auto &range : d->spellCheckRanges) {
149  if (range.offset <= start && range.end() >= start + count) {
150  auto f = format(start);
151  f.setFontUnderline(true);
152  f.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
153  f.setUnderlineColor(d->misspelledColor);
154  setFormat(start, count, f);
155  return;
156  }
157  }
158 }
159 
160 void PlainTextSyntaxSpellCheckingHighlighter::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format)
161 {
162  if (format.spellCheck() && length > 0) {
163  if (d->spellCheckRanges.empty()) {
164  d->spellCheckRanges.emplace_back(offset, length);
165  } else if (d->spellCheckRanges.back().end() + 1 == offset) {
166  d->spellCheckRanges.back().length += length;
167  } else {
168  d->spellCheckRanges.emplace_back(offset, length);
169  }
170  }
171 
172  if (format.isDefaultTextStyle(theme()) || length == 0) {
173  return;
174  }
175 
176  QTextCharFormat tf;
177  if (format.hasTextColor(theme())) {
178  tf.setForeground(format.textColor(theme()));
179  }
180  if (format.hasBackgroundColor(theme())) {
181  tf.setBackground(format.backgroundColor(theme()));
182  }
183 
184  if (format.isBold(theme())) {
186  }
187  if (format.isItalic(theme())) {
188  tf.setFontItalic(true);
189  }
190  if (format.isUnderline(theme())) {
191  tf.setFontUnderline(true);
192  }
193  if (format.isStrikeThrough(theme())) {
194  tf.setFontStrikeOut(true);
195  }
196 
197  QSyntaxHighlighter::setFormat(offset, length, tf);
198 }
KCALENDARCORE_EXPORT QDataStream & operator<<(QDataStream &out, const KCalendarCore::Alarm::Ptr &)
Q_SCRIPTABLE Q_NOREPLY void start()
void setFormat(int start, int count, const QTextCharFormat &format)
void setFontWeight(int weight)
QTextBlock currentBlock() const const
void unsetMisspelled(int start, int count) override
Reimplemented, the base version sets the text color to black, which is not what we want.
QTextBlock next() const const
void setFontStrikeOut(bool strikeOut)
void setBackground(const QBrush &brush)
void setFontItalic(bool italic)
void highlightBlock(const QString &text) override
Reimplemented to highlight quote blocks.
void setForeground(const QBrush &brush)
QTextBlock previous() const const
void rehighlightBlock(const QTextBlock &block)
QueuedConnection
void setMisspelledColor(const QColor &color)
QTextCharFormat format(int position) const const
void setCurrentBlockState(int newState)
void setMisspelled(int start, int count) override
Reimplemented to set the color of the misspelled word to a color defined by setQuoteColor().
State highlightLine(QStringView text, const State &state)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
void setFontUnderline(bool underline)
The PlainTextEditor class.
const QList< QKeySequence > & end()
int currentBlockState() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 04:08:59 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.