Libksieve

sievetextedit.cpp
1 /* SPDX-FileCopyrightText: 2011-2021 Laurent Montel <[email protected]>
2  *
3  * SPDX-License-Identifier: LGPL-2.0-or-later
4  */
5 
6 #include "sievetextedit.h"
7 #include "editor/sieveeditorutil.h"
8 #include "editor/sievelinenumberarea.h"
9 #include "editor/sievetexteditorspellcheckdecorator.h"
10 
11 #include <KPIMTextEdit/EditorUtil>
12 #include <KPIMTextEdit/PlainTextSyntaxSpellCheckingHighlighter>
13 #include <KPIMTextEdit/TextEditorCompleter>
14 
15 #include <KLocalizedString>
16 #include <KSyntaxHighlighting/Definition>
17 #include <KSyntaxHighlighting/Repository>
18 #include <KSyntaxHighlighting/Theme>
19 
20 #include <QAbstractItemView>
21 #include <QAction>
22 #include <QCompleter>
23 #include <QFontDatabase>
24 #include <QIcon>
25 #include <QKeyEvent>
26 #include <QMenu>
27 #include <QPainter>
28 #include <QTextDocumentFragment>
29 using namespace KSieveUi;
30 
31 class KSieveUi::SieveTextEditPrivate
32 {
33 public:
34  SieveTextEditPrivate()
35  {
36  }
37 
38  SieveLineNumberArea *m_sieveLineNumberArea = nullptr;
39  KPIMTextEdit::TextEditorCompleter *mTextEditorCompleter = nullptr;
41  bool mShowHelpMenu = true;
42 };
43 
44 SieveTextEdit::SieveTextEdit(QWidget *parent)
45  : KPIMTextEdit::PlainTextEditor(parent)
46  , d(new KSieveUi::SieveTextEditPrivate)
47 {
48  setSpellCheckingConfigFileName(QStringLiteral("sieveeditorrc"));
49  setWordWrapMode(QTextOption::NoWrap);
51  d->m_sieveLineNumberArea = new SieveLineNumberArea(this);
52 
53  connect(this, &SieveTextEdit::blockCountChanged, this, &SieveTextEdit::slotUpdateLineNumberAreaWidth);
54  connect(this, &SieveTextEdit::updateRequest, this, &SieveTextEdit::slotUpdateLineNumberArea);
55 
56  slotUpdateLineNumberAreaWidth(0);
57 
58  initCompleter();
59  createHighlighter();
60 }
61 
62 SieveTextEdit::~SieveTextEdit()
63 {
64  // disconnect these manually as the destruction of KPIMTextEdit::PlainTextEditorPrivate will trigger them
65  disconnect(this, &SieveTextEdit::blockCountChanged, this, &SieveTextEdit::slotUpdateLineNumberAreaWidth);
66  disconnect(this, &SieveTextEdit::updateRequest, this, &SieveTextEdit::slotUpdateLineNumberArea);
67 }
68 
69 void SieveTextEdit::updateHighLighter()
70 {
71  auto hlighter = dynamic_cast<KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter *>(highlighter());
72  if (hlighter) {
73  hlighter->toggleSpellHighlighting(checkSpellingEnabled());
74  }
75 }
76 
77 void SieveTextEdit::clearDecorator()
78 {
79  // Nothing
80 }
81 
82 void SieveTextEdit::createHighlighter()
83 {
84  auto highlighter = new KPIMTextEdit::PlainTextSyntaxSpellCheckingHighlighter(this);
85  highlighter->toggleSpellHighlighting(checkSpellingEnabled());
86  highlighter->setCurrentLanguage(spellCheckingLanguage());
87  highlighter->setDefinition(d->mSyntaxRepo.definitionForName(QStringLiteral("Sieve")));
88  highlighter->setTheme((palette().color(QPalette::Base).lightness() < 128) ? d->mSyntaxRepo.defaultTheme(KSyntaxHighlighting::Repository::DarkTheme)
89  : d->mSyntaxRepo.defaultTheme(KSyntaxHighlighting::Repository::LightTheme));
90  setHighlighter(highlighter);
91 }
92 
93 void SieveTextEdit::resizeEvent(QResizeEvent *e)
94 {
96 
97  const QRect cr = contentsRect();
98  d->m_sieveLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
99 }
100 
101 int SieveTextEdit::lineNumberAreaWidth() const
102 {
103  int digits = 1;
104  int max = qMax(1, blockCount());
105  while (max >= 10) {
106  max /= 10;
107  ++digits;
108  }
109 
110  const int space = 2 + fontMetrics().boundingRect(QLatin1Char('9')).width() * digits;
111  return space;
112 }
113 
114 void SieveTextEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
115 {
116  QPainter painter(d->m_sieveLineNumberArea);
117  painter.fillRect(event->rect(), Qt::lightGray);
118 
119  QTextBlock block = firstVisibleBlock();
120  int blockNumber = block.blockNumber();
121  int top = static_cast<int>(blockBoundingGeometry(block).translated(contentOffset()).top());
122  int bottom = top + static_cast<int>(blockBoundingRect(block).height());
123  while (block.isValid() && top <= event->rect().bottom()) {
124  if (block.isVisible() && bottom >= event->rect().top()) {
125  const QString number = QString::number(blockNumber + 1);
126  painter.setPen(Qt::black);
127  painter.drawText(0, top, d->m_sieveLineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number);
128  }
129 
130  block = block.next();
131  top = bottom;
132  bottom = top + static_cast<int>(blockBoundingRect(block).height());
133  ++blockNumber;
134  }
135 }
136 
137 void SieveTextEdit::slotUpdateLineNumberAreaWidth(int)
138 {
139  setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
140 }
141 
142 void SieveTextEdit::slotUpdateLineNumberArea(const QRect &rect, int dy)
143 {
144  if (dy) {
145  d->m_sieveLineNumberArea->scroll(0, dy);
146  } else {
147  d->m_sieveLineNumberArea->update(0, rect.y(), d->m_sieveLineNumberArea->width(), rect.height());
148  }
149 
150  if (rect.contains(viewport()->rect())) {
151  slotUpdateLineNumberAreaWidth(0);
152  }
153 }
154 
155 QStringList SieveTextEdit::completerList() const
156 {
157  QStringList listWord;
158 
159  listWord << QStringLiteral("require") << QStringLiteral("stop");
160  listWord << QStringLiteral(":contains") << QStringLiteral(":matches") << QStringLiteral(":is") << QStringLiteral(":over") << QStringLiteral(":under")
161  << QStringLiteral(":all") << QStringLiteral(":domain") << QStringLiteral(":localpart");
162  listWord << QStringLiteral("if") << QStringLiteral("elsif") << QStringLiteral("else");
163  listWord << QStringLiteral("keep") << QStringLiteral("reject") << QStringLiteral("discard") << QStringLiteral("redirect") << QStringLiteral("addflag")
164  << QStringLiteral("setflag");
165  listWord << QStringLiteral("address") << QStringLiteral("allof") << QStringLiteral("anyof") << QStringLiteral("exists") << QStringLiteral("false")
166  << QStringLiteral("header") << QStringLiteral("not") << QStringLiteral("size") << QStringLiteral("true");
167  listWord << QStringLiteral(":days") << QStringLiteral(":seconds") << QStringLiteral(":subject") << QStringLiteral(":addresses") << QStringLiteral(":text");
168  listWord << QStringLiteral(":name") << QStringLiteral(":headers") << QStringLiteral(":first") << QStringLiteral(":importance");
169  listWord << QStringLiteral(":message") << QStringLiteral(":from");
170 
171  return listWord;
172 }
173 
174 void SieveTextEdit::setCompleterList(const QStringList &list)
175 {
176  d->mTextEditorCompleter->setCompleterStringList(list);
177 }
178 
179 void SieveTextEdit::initCompleter()
180 {
181  const QStringList listWord = completerList();
182 
183  d->mTextEditorCompleter = new KPIMTextEdit::TextEditorCompleter(this, this);
184  d->mTextEditorCompleter->setCompleterStringList(listWord);
185 }
186 
187 bool SieveTextEdit::event(QEvent *ev)
188 {
189  if (ev->type() == QEvent::ShortcutOverride) {
190  auto e = static_cast<QKeyEvent *>(ev);
191  if (overrideShortcut(e)) {
192  e->accept();
193  return true;
194  }
195  }
197 }
198 
199 Sonnet::SpellCheckDecorator *SieveTextEdit::createSpellCheckDecorator()
200 {
201  return new SieveTextEditorSpellCheckDecorator(this);
202 }
203 
204 bool SieveTextEdit::overrideShortcut(QKeyEvent *event)
205 {
206  if (event->key() == Qt::Key_F1) {
207  if (openVariableHelp()) {
208  return true;
209  }
210  }
211  return PlainTextEditor::overrideShortcut(event);
212 }
213 
214 bool SieveTextEdit::openVariableHelp()
215 {
216  if (!textCursor().hasSelection()) {
217  const QString word = selectedWord();
218  const KSieveUi::SieveEditorUtil::HelpVariableName type = KSieveUi::SieveEditorUtil::strToVariableName(word);
219  if (type != KSieveUi::SieveEditorUtil::UnknownHelp) {
220  const QUrl url = KSieveUi::SieveEditorUtil::helpUrl(type);
221  if (!url.isEmpty()) {
222  return true;
223  }
224  }
225  }
226  return false;
227 }
228 
229 void SieveTextEdit::keyPressEvent(QKeyEvent *e)
230 {
231  if (d->mTextEditorCompleter->completer()->popup()->isVisible()) {
232  switch (e->key()) {
233  case Qt::Key_Enter:
234  case Qt::Key_Return:
235  case Qt::Key_Escape:
236  case Qt::Key_Tab:
237  case Qt::Key_Backtab:
238  e->ignore();
239  return; // let the completer do default behavior
240  default:
241  break;
242  }
243  } else if (handleShortcut(e)) {
244  return;
245  }
247  if (e->key() == Qt::Key_F1 && !textCursor().hasSelection()) {
248  const QString word = selectedWord();
249  const KSieveUi::SieveEditorUtil::HelpVariableName type = KSieveUi::SieveEditorUtil::strToVariableName(word);
250  if (type != KSieveUi::SieveEditorUtil::UnknownHelp) {
251  const QUrl url = KSieveUi::SieveEditorUtil::helpUrl(type);
252  if (!url.isEmpty()) {
253  Q_EMIT openHelp(url);
254  }
255  }
256  return;
257  }
258  d->mTextEditorCompleter->completeText();
259 }
260 
261 void SieveTextEdit::setSieveCapabilities(const QStringList &capabilities)
262 {
263  setCompleterList(completerList() + capabilities);
264 }
265 
266 void SieveTextEdit::setShowHelpMenu(bool b)
267 {
268  d->mShowHelpMenu = b;
269 }
270 
271 void SieveTextEdit::addExtraMenuEntry(QMenu *menu, QPoint pos)
272 {
273  if (!d->mShowHelpMenu) {
274  return;
275  }
276 
277  if (!textCursor().hasSelection()) {
278  if (!isReadOnly()) {
279  auto insertRules = new QAction(i18n("Insert Rule"), menu);
280  // editRules->setIcon(QIcon::fromTheme(QStringLiteral("help-hint")));
281  connect(insertRules, &QAction::triggered, this, &SieveTextEdit::insertRule);
282  QAction *act = menu->addSeparator();
283  menu->insertActions(menu->actions().at(0), {insertRules, act});
284  }
285 
286  const QString word = selectedWord(pos);
287  const KSieveUi::SieveEditorUtil::HelpVariableName type = KSieveUi::SieveEditorUtil::strToVariableName(word);
288  if (type != KSieveUi::SieveEditorUtil::UnknownHelp) {
289  auto separator = new QAction(menu);
290  separator->setSeparator(true);
291  menu->insertAction(menu->actions().at(0), separator);
292 
293  auto searchAction = new QAction(i18n("Help about: \'%1\'", word), menu);
294  searchAction->setShortcut(Qt::Key_F1);
295  searchAction->setIcon(QIcon::fromTheme(QStringLiteral("help-hint")));
296  searchAction->setData(word);
297  connect(searchAction, &QAction::triggered, this, &SieveTextEdit::slotHelp);
298  menu->insertAction(menu->actions().at(0), searchAction);
299  }
300  } else {
301  if (!isReadOnly()) {
302  auto editRules = new QAction(i18n("Edit Rule"), menu);
303  // editRules->setIcon(QIcon::fromTheme(QStringLiteral("help-hint")));
304  connect(editRules, &QAction::triggered, this, &SieveTextEdit::slotEditRule);
305  QAction *act = menu->addSeparator();
306  menu->insertActions(menu->actions().at(0), {editRules, act});
307  }
308  }
309 }
310 
311 QString SieveTextEdit::selectedWord(const QPoint &pos) const
312 {
313  QTextCursor wordSelectCursor(pos.isNull() ? textCursor() : cursorForPosition(pos));
314  wordSelectCursor.clearSelection();
315  wordSelectCursor.select(QTextCursor::WordUnderCursor);
316  const QString word = wordSelectCursor.selectedText();
317  return word;
318 }
319 
320 void SieveTextEdit::slotEditRule()
321 {
322  QTextCursor textcursor = textCursor();
323  Q_EMIT editRule(textcursor.selection().toPlainText());
324 }
325 
326 void SieveTextEdit::slotHelp()
327 {
328  auto act = qobject_cast<QAction *>(sender());
329  if (act) {
330  const QString word = act->data().toString();
331  const KSieveUi::SieveEditorUtil::HelpVariableName type = KSieveUi::SieveEditorUtil::strToVariableName(word);
332  const QUrl url = KSieveUi::SieveEditorUtil::helpUrl(type);
333  if (!url.isEmpty()) {
334  Q_EMIT openHelp(url);
335  }
336  }
337 }
338 
339 void SieveTextEdit::comment()
340 {
341  QTextCursor textcursor = textCursor();
342  if (textcursor.hasSelection()) {
343  // Move start block
345  QString text = textcursor.selectedText();
346  text = QLatin1Char('#') + text;
347  text.replace(QChar::ParagraphSeparator, QStringLiteral("\n#"));
348  textcursor.insertText(text);
349  setTextCursor(textcursor);
350  } else {
353  const QString s = textcursor.selectedText();
354  const QString str = QLatin1Char('#') + s;
355  textcursor.insertText(str);
356  setTextCursor(textcursor);
357  }
358 }
359 
360 void SieveTextEdit::upperCase()
361 {
362  KPIMTextEdit::EditorUtil editorUtil;
363  QTextCursor cursorText = textCursor();
364  editorUtil.upperCase(cursorText);
365 }
366 
367 void SieveTextEdit::lowerCase()
368 {
369  KPIMTextEdit::EditorUtil editorUtil;
370  QTextCursor cursorText = textCursor();
371  editorUtil.lowerCase(cursorText);
372 }
373 
374 void SieveTextEdit::sentenceCase()
375 {
376  KPIMTextEdit::EditorUtil editorUtil;
377  QTextCursor cursorText = textCursor();
378  editorUtil.sentenceCase(cursorText);
379 }
380 
381 void SieveTextEdit::reverseCase()
382 {
383  KPIMTextEdit::EditorUtil editorUtil;
384  QTextCursor cursorText = textCursor();
385  editorUtil.reverseCase(cursorText);
386 }
387 
388 void SieveTextEdit::uncomment()
389 {
390  QTextCursor textcursor = textCursor();
391  if (textcursor.hasSelection()) {
393  QString text = textcursor.selectedText();
394  if (text.startsWith(QLatin1Char('#'))) {
395  text.remove(0, 1);
396  }
397  QString newText = text;
398  for (int i = 0; i < newText.length();) {
399  if (newText.at(i) == QChar::ParagraphSeparator || newText.at(i) == QChar::LineSeparator) {
400  ++i;
401  if (i < newText.length()) {
402  if (newText.at(i) == QLatin1Char('#')) {
403  newText.remove(i, 1);
404  } else {
405  ++i;
406  }
407  }
408  } else {
409  ++i;
410  }
411  }
412 
413  textcursor.insertText(newText);
414  setTextCursor(textcursor);
415  } else {
418  QString text = textcursor.selectedText();
419  if (text.startsWith(QLatin1Char('#'))) {
420  text.remove(0, 1);
421  }
422  textcursor.insertText(text);
423  setTextCursor(textcursor);
424  }
425 }
426 
427 bool SieveTextEdit::isWordWrap() const
428 {
429  return wordWrapMode() == QTextOption::WordWrap;
430 }
431 
432 void SieveTextEdit::wordWrap(bool state)
433 {
434  setWordWrapMode(state ? QTextOption::WordWrap : QTextOption::NoWrap);
435 }
void triggered(bool checked)
ShortcutOverride
QEvent::Type type() const const
const QPalette & palette() const const
void clearSelection()
QTextBlock next() const const
QRect contentsRect() const const
QObject * sender() const const
QVariant data() const const
const T & at(int i) const const
QString selectedText() const const
int height() const const
int y() const const
QString & remove(int position, int n)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
bool isEmpty() const const
AlignRight
bool movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n)
const QRect & rect() const const
QRect boundingRect(QChar ch) const const
bool hasSelection() const const
void insertAction(QAction *before, QAction *action)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
QString number(int n, int base)
void updateRequest(const QRect &rect, int dy)
void ignore()
int top() const const
QTextDocumentFragment selection() const const
Type type(const QSqlDatabase &db)
int left() const const
void insertText(const QString &text)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isValid() const const
QPoint pos() const const
QAction * addSeparator()
void blockCountChanged(int newBlockCount)
virtual void keyPressEvent(QKeyEvent *e) override
virtual bool event(QEvent *event) override
QRect rect() const const
int key() const const
void accept()
void insertActions(QAction *before, QList< QAction * > actions)
void setFont(const QFont &)
QString i18n(const char *text, const TYPE &arg...)
QString & replace(int position, int n, QChar after)
QFont systemFont(QFontDatabase::SystemFont type)
bool contains(const QRect &rectangle, bool proper) const const
bool isNull() const const
int width() const const
QFontMetrics fontMetrics() const const
virtual void resizeEvent(QResizeEvent *e) override
const QChar at(int position) const const
QString toPlainText() const const
int height() const const
int blockNumber() const const
int length() const const
ParagraphSeparator
QIcon fromTheme(const QString &name)
lightGray
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QList< QAction * > actions() const const
T qobject_cast(QObject *object)
QString toString() const const
virtual bool event(QEvent *event) override
Q_EMITQ_EMIT
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Apr 10 2021 23:09:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.