Libksieve

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

KDE's Doxygen guidelines are available online.