7#include "richtexteditor.h"
9#include "config-textcustomeditor.h"
10#include "textcustomeditor_debug.h"
11#include "widgets/textmessageindicator.h"
13#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
14#include <TextEditTextToSpeech/TextToSpeech>
16#include <TextEmoticonsWidgets/EmoticonTextEditAction>
18#include <KColorScheme>
20#include <KConfigGroup>
22#include <KLocalizedString>
24#include <KSharedConfig>
25#include <KStandardActions>
26#include <KStandardGuiItem>
27#if HAVE_KTEXTADDONS_KIO_SUPPORT
28#include <KIO/KUriFilterSearchProviderActions>
30#include <Sonnet/BackgroundChecker>
31#include <Sonnet/Dialog>
32#include <Sonnet/Highlighter>
33#include <Sonnet/SpellCheckDecorator>
34#include <Sonnet/Speller>
36#include <QActionGroup>
37#include <QApplication>
39#include <QContextMenuEvent>
40#include <QDialogButtonBox>
46#include <QTextDocumentFragment>
48using namespace TextCustomEditor;
54 RichTextEditorPrivate(RichTextEditor *qq)
56 , textIndicator(new TextCustomEditor::TextMessageIndicator(q))
57#if HAVE_KTEXTADDONS_KIO_SUPPORT
58 , webshortcutMenuManager(new KIO::KUriFilterSearchProviderActions(q))
61 KConfig sonnetKConfig(QStringLiteral(
"sonnetrc"));
62 KConfigGroup group(&sonnetKConfig,
"Spelling"_L1);
63 checkSpellingEnabled = group.readEntry(
"checkerEnabledByDefault",
false);
64 supportFeatures |= RichTextEditor::Search;
65 supportFeatures |= RichTextEditor::SpellChecking;
66 supportFeatures |= RichTextEditor::TextToSpeech;
67 supportFeatures |= RichTextEditor::AllowTab;
68#if HAVE_KTEXTADDONS_KIO_SUPPORT
69 supportFeatures |= RichTextEditor::AllowWebShortcut;
79 QTextCursor c = q->textCursor();
84 QTextCursor probe = c;
97 ~RichTextEditorPrivate()
99 delete richTextDecorator;
103 QStringList ignoreSpellCheckingWords;
104 RichTextEditor *
const q;
105 TextCustomEditor::TextMessageIndicator *
const textIndicator;
106 QString spellCheckingConfigFileName;
107 QString spellCheckingLanguage;
108 QTextDocumentFragment originalDoc;
109 Sonnet::SpellCheckDecorator *richTextDecorator =
nullptr;
110 Sonnet::Speller *speller =
nullptr;
111#if HAVE_KTEXTADDONS_KIO_SUPPORT
112 KIO::KUriFilterSearchProviderActions *
const webshortcutMenuManager;
114 RichTextEditor::SupportFeatures supportFeatures;
115 QColor mReadOnlyBackgroundColor;
116 int mInitialFontSize;
117 bool customPalette =
false;
118 bool checkSpellingEnabled =
false;
119 bool activateLanguageMenu =
true;
120 bool showAutoCorrectionButton =
false;
123RichTextEditor::RichTextEditor(
QWidget *parent)
125 , d(new RichTextEditorPrivate(this))
127 setAcceptRichText(
true);
129 setSpellCheckingConfigFileName(
QString());
130 d->mInitialFontSize = font().pointSize();
131 regenerateColorScheme();
134RichTextEditor::~RichTextEditor() =
default;
136void RichTextEditor::regenerateColorScheme()
139 updateReadOnlyColor();
142void RichTextEditor::setDefaultFontSize(
int val)
144 d->mInitialFontSize = val;
148void RichTextEditor::slotDisplayMessageIndicator(
const QString &message)
150 d->textIndicator->display(message);
153Sonnet::Highlighter *RichTextEditor::highlighter()
const
155 if (d->richTextDecorator) {
156 return d->richTextDecorator->highlighter();
162bool RichTextEditor::activateLanguageMenu()
const
164 return d->activateLanguageMenu;
167void RichTextEditor::setActivateLanguageMenu(
bool activate)
169 d->activateLanguageMenu = activate;
172void RichTextEditor::contextMenuEvent(QContextMenuEvent *event)
174 QMenu *popup = mousePopupMenu(event->pos());
176 popup->
exec(event->globalPos());
181QMenu *RichTextEditor::mousePopupMenu(QPoint pos)
185 const bool emptyDocument =
document()->isEmpty();
187 const QList<QAction *> actionList = popup->
actions();
198 QAction *separatorAction =
nullptr;
199 const int idx = actionList.
indexOf(actionList[SelectAllAct]) + 1;
200 if (idx < actionList.
count()) {
201 separatorAction = actionList.
at(idx);
203 if (separatorAction) {
204 QAction *clearAllAction = KStandardActions::clear(
this, &RichTextEditor::slotUndoableClear, popup);
211 if (searchSupport()) {
213 QAction *findAction = KStandardActions::find(
this, &RichTextEditor::findText, popup);
220 QAction *act = KStandardActions::replace(
this, &RichTextEditor::replaceText, popup);
231 if (!
isReadOnly() && spellCheckingSupport()) {
233 d->speller =
new Sonnet::Speller();
235 if (!d->speller->availableBackends().isEmpty()) {
237 i18n(
"Check Spelling…"),
239 &RichTextEditor::slotCheckSpelling);
244 QAction *autoSpellCheckAction = popup->
addAction(
i18n(
"Auto Spell Check"),
this, &RichTextEditor::slotToggleAutoSpellCheck);
246 autoSpellCheckAction->
setChecked(checkSpellingEnabled());
249 if (checkSpellingEnabled() && d->activateLanguageMenu) {
250 auto languagesMenu =
new QMenu(
i18n(
"Spell Checking Language"), popup);
251 auto languagesGroup =
new QActionGroup(languagesMenu);
252 languagesGroup->setExclusive(
true);
254 QString defaultSpellcheckingLanguage = spellCheckingLanguage();
255 if (defaultSpellcheckingLanguage.
isEmpty()) {
256 defaultSpellcheckingLanguage = d->speller->defaultLanguage();
259 QMapIterator<QString, QString> i(d->speller->availableDictionaries());
260 while (i.hasNext()) {
262 QAction *languageAction = languagesMenu->addAction(i.key());
264 languageAction->
setChecked(defaultSpellcheckingLanguage == i.value());
265 languageAction->
setData(i.value());
276 QAction *allowTabAction = popup->
addAction(
i18n(
"Allow Tabulations"));
281#if HAVE_KTEXTADDONS_TEXT_TO_SPEECH_SUPPORT
282 if (!emptyDocument) {
288#if HAVE_KTEXTADDONS_KIO_SUPPORT
289 if (webShortcutSupport() &&
textCursor().hasSelection()) {
292 d->webshortcutMenuManager->setSelectedText(selectedText);
293 d->webshortcutMenuManager->addWebShortcutsToMenu(popup);
296 if (emojiSupport()) {
298 auto action =
new TextEmoticonsWidgets::EmoticonTextEditAction(
this);
302 addExtraMenuEntry(popup,
pos);
308void RichTextEditor::slotInsertEmoticon(
const QString &str)
313void RichTextEditor::slotSpeakText()
324void RichTextEditor::setWebShortcutSupport(
bool b)
326#if HAVE_KTEXTADDONS_KIO_SUPPORT
328 d->supportFeatures |= AllowWebShortcut;
330 d->supportFeatures = (d->supportFeatures & ~AllowWebShortcut);
337bool RichTextEditor::webShortcutSupport()
const
339#if HAVE_KTEXTADDONS_KIO_SUPPORT
340 return d->supportFeatures & AllowWebShortcut;
346void RichTextEditor::setEmojiSupport(
bool b)
349 d->supportFeatures |= Emoji;
351 d->supportFeatures = (d->supportFeatures & ~Emoji);
355bool RichTextEditor::emojiSupport()
const
357 return d->supportFeatures & Emoji;
360void RichTextEditor::addIgnoreWords(
const QStringList &lst)
362 d->ignoreSpellCheckingWords = lst;
363 addIgnoreWordsToHighLighter();
366void RichTextEditor::forceAutoCorrection(
bool selectedText)
368 Q_UNUSED(selectedText)
372void RichTextEditor::setSearchSupport(
bool b)
375 d->supportFeatures |= Search;
377 d->supportFeatures = (d->supportFeatures & ~Search);
381bool RichTextEditor::searchSupport()
const
383 return d->supportFeatures & Search;
386void RichTextEditor::setAllowTabSupport(
bool b)
389 d->supportFeatures |= AllowTab;
391 d->supportFeatures = (d->supportFeatures & ~AllowTab);
395bool RichTextEditor::allowTabSupport()
const
397 return d->supportFeatures & AllowTab;
400void RichTextEditor::setShowAutoCorrectButton(
bool b)
402 d->showAutoCorrectionButton = b;
405bool RichTextEditor::showAutoCorrectButton()
const
407 return d->showAutoCorrectionButton;
410bool RichTextEditor::spellCheckingSupport()
const
412 return d->supportFeatures & SpellChecking;
415void RichTextEditor::setSpellCheckingSupport(
bool check)
418 d->supportFeatures |= SpellChecking;
420 d->supportFeatures = (d->supportFeatures & ~SpellChecking);
424void RichTextEditor::setTextToSpeechSupport(
bool b)
427 d->supportFeatures |= TextToSpeech;
429 d->supportFeatures = (d->supportFeatures & ~TextToSpeech);
433bool RichTextEditor::textToSpeechSupport()
const
435 return d->supportFeatures & TextToSpeech;
438void RichTextEditor::slotAllowTab()
443void RichTextEditor::addExtraMenuEntry(QMenu *menu, QPoint pos)
449void RichTextEditor::slotUndoableClear()
455 cursor.removeSelectedText();
459void RichTextEditor::updateReadOnlyColor()
469void RichTextEditor::setReadOnly(
bool readOnly)
471 if (!
readOnly &&
hasFocus() && checkSpellingEnabled() && !d->richTextDecorator) {
482 updateReadOnlyColor();
498void RichTextEditor::checkSpelling(
bool force)
501 slotDisplayMessageIndicator(
i18n(
"Nothing to spell check."));
503 Q_EMIT spellCheckingFinished();
507 auto backgroundSpellCheck =
new Sonnet::BackgroundChecker;
508 if (backgroundSpellCheck->speller().availableBackends().isEmpty()) {
511 i18n(
"No backend available for spell checking. Do you want to send the email anyways?"),
513 KGuiItem(
i18nc(
"@action:button",
"Send"), QStringLiteral(
"mail-send")),
515 if (answer == KMessageBox::ButtonCode::PrimaryAction) {
516 Q_EMIT spellCheckingFinished();
519 slotDisplayMessageIndicator(
i18n(
"No backend available for spell checking."));
521 delete backgroundSpellCheck;
524 if (!d->spellCheckingLanguage.isEmpty()) {
525 backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage);
527 if (!d->ignoreSpellCheckingWords.isEmpty()) {
528 for (
const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
529 backgroundSpellCheck->speller().addToSession(word);
532 auto spellDialog =
new Sonnet::Dialog(backgroundSpellCheck, force ?
this :
nullptr);
533 auto buttonBox = spellDialog->findChild<QDialogButtonBox *>();
535 auto skipButton =
new QPushButton(
i18nc(
"@action:button",
"Skip"));
542 qCWarning(TEXTCUSTOMEDITOR_LOG) <<
" Impossible to find qdialogbuttonbox";
544 backgroundSpellCheck->setParent(spellDialog);
546 spellDialog->activeAutoCorrect(d->showAutoCorrectionButton);
547 connect(spellDialog, &Sonnet::Dialog::replace,
this, &RichTextEditor::slotSpellCheckerCorrected);
548 connect(spellDialog, &Sonnet::Dialog::misspelling,
this, &RichTextEditor::slotSpellCheckerMisspelling);
549 connect(spellDialog, &Sonnet::Dialog::autoCorrect,
this, &RichTextEditor::slotSpellCheckerAutoCorrect);
551 connect(spellDialog, &Sonnet::Dialog::cancel,
this, &RichTextEditor::slotSpellCheckerCanceled);
556 connect(spellDialog, &Sonnet::Dialog::cancel,
this, &RichTextEditor::spellCheckingCanceled);
558 d->originalDoc = QTextDocumentFragment(
document());
563void RichTextEditor::slotCheckSpelling()
565 checkSpelling(
false);
568void RichTextEditor::forceSpellChecking()
573void RichTextEditor::slotSpellCheckerCanceled()
578 cursor.insertFragment(d->originalDoc);
579 slotSpellCheckerFinished();
582void RichTextEditor::slotSpellCheckerAutoCorrect(
const QString ¤tWord,
const QString &autoCorrectWord)
584 Q_EMIT spellCheckerAutoCorrect(currentWord, autoCorrectWord);
587void RichTextEditor::slotSpellCheckerMisspelling(
const QString &text,
int pos)
592void RichTextEditor::slotSpellCheckerCorrected(
const QString &oldWord,
int pos,
const QString &newWord)
594 if (oldWord != newWord) {
598 cursor.insertText(newWord);
602void RichTextEditor::slotSpellCheckerFinished()
608 highlighter()->rehighlight();
612void RichTextEditor::highlightWord(
int length,
int pos)
621void RichTextEditor::createHighlighter()
623 auto highlighter =
new Sonnet::Highlighter(
this);
624 highlighter->setCurrentLanguage(spellCheckingLanguage());
625 setHighlighter(highlighter);
628Sonnet::SpellCheckDecorator *RichTextEditor::createSpellCheckDecorator()
630 return new Sonnet::SpellCheckDecorator(
this);
633void RichTextEditor::addIgnoreWordsToHighLighter()
635 if (d->ignoreSpellCheckingWords.isEmpty()) {
638 if (d->richTextDecorator) {
639 Sonnet::Highlighter *_highlighter = d->richTextDecorator->highlighter();
640 for (
const QString &word : std::as_const(d->ignoreSpellCheckingWords)) {
646void RichTextEditor::setHighlighter(Sonnet::Highlighter *_highLighter)
648 Sonnet::SpellCheckDecorator *decorator = createSpellCheckDecorator();
652 d->richTextDecorator = decorator;
653 addIgnoreWordsToHighLighter();
656void RichTextEditor::focusInEvent(QFocusEvent *event)
658 if (d->checkSpellingEnabled && !
isReadOnly() && !d->richTextDecorator && spellCheckingSupport()) {
665void RichTextEditor::setSpellCheckingConfigFileName(
const QString &_fileName)
667 d->spellCheckingConfigFileName = _fileName;
669 if (config->hasGroup(
"Spelling"_L1)) {
670 KConfigGroup group(config,
"Spelling"_L1);
671 d->checkSpellingEnabled = group.readEntry(
"checkerEnabledByDefault",
false);
672 d->spellCheckingLanguage = group.readEntry(
"Language", QString());
674 setCheckSpellingEnabled(checkSpellingEnabled());
676 if (!d->spellCheckingLanguage.isEmpty() && highlighter()) {
677 highlighter()->setCurrentLanguage(d->spellCheckingLanguage);
678 highlighter()->rehighlight();
682QString RichTextEditor::spellCheckingConfigFileName()
const
684 return d->spellCheckingConfigFileName;
687bool RichTextEditor::checkSpellingEnabled()
const
689 return d->checkSpellingEnabled;
692void RichTextEditor::setCheckSpellingEnabled(
bool check)
694 if (check == d->checkSpellingEnabled) {
697 d->checkSpellingEnabled = check;
698 Q_EMIT checkSpellingChanged(check);
705 if (!d->richTextDecorator) {
708 if (!d->spellCheckingLanguage.isEmpty()) {
709 setSpellCheckingLanguage(spellCheckingLanguage());
718void RichTextEditor::updateHighLighter()
722void RichTextEditor::clearDecorator()
724 delete d->richTextDecorator;
725 d->richTextDecorator =
nullptr;
728const QString &RichTextEditor::spellCheckingLanguage()
const
730 return d->spellCheckingLanguage;
733void RichTextEditor::setSpellCheckingLanguage(
const QString &_language)
736 highlighter()->setCurrentLanguage(_language);
739 if (_language != d->spellCheckingLanguage) {
740 d->spellCheckingLanguage = _language;
742 KConfigGroup group(config,
"Spelling"_L1);
743 group.writeEntry(
"Language", d->spellCheckingLanguage);
745 Q_EMIT languageChanged(_language);
749void RichTextEditor::slotToggleAutoSpellCheck()
751 setCheckSpellingEnabled(!checkSpellingEnabled());
753 KConfigGroup group(config,
"Spelling"_L1);
754 group.writeEntry(
"checkerEnabledByDefault", d->checkSpellingEnabled);
757void RichTextEditor::slotLanguageSelected()
760 setSpellCheckingLanguage(languageAction->
data().
toString());
770void RichTextEditor::deleteWordBack()
775void RichTextEditor::deleteWordForward()
780bool RichTextEditor::event(QEvent *ev)
783 auto e =
static_cast<QKeyEvent *
>(ev);
784 if (overrideShortcut(e)) {
789 regenerateColorScheme();
794void RichTextEditor::wheelEvent(QWheelEvent *event)
797 const int angleDeltaY{
event->angleDelta().y()};
798 if (angleDeltaY > 0) {
800 }
else if (angleDeltaY < 0) {
809bool RichTextEditor::handleShortcut(QKeyEvent *event)
811 const int key =
event->key() |
event->modifiers();
924 QTextBlock block =
cursor.block();
930 cursor.removeSelectedText();
938bool RichTextEditor::overrideShortcut(QKeyEvent *event)
940 const int key =
event->key() |
event->modifiers();
988void RichTextEditor::keyPressEvent(QKeyEvent *event)
992 if (handleShortcut(event)) {
994 }
else if (event->key() ==
Qt::Key_Up && isControlClicked && isShiftClicked) {
995 moveLineUpDown(
true);
997 }
else if (event->key() ==
Qt::Key_Down && isControlClicked && isShiftClicked) {
998 moveLineUpDown(
false);
1000 }
else if (event->key() ==
Qt::Key_Up && isControlClicked) {
1001 moveCursorBeginUpDown(
true);
1003 }
else if (event->key() ==
Qt::Key_Down && isControlClicked) {
1004 moveCursorBeginUpDown(
false);
1011int RichTextEditor::zoomFactor()
const
1013 int pourcentage = 100;
1014 const QFont f =
font();
1015 if (d->mInitialFontSize != f.
pointSize()) {
1016 pourcentage = (f.
pointSize() * 100) / d->mInitialFontSize;
1021void RichTextEditor::slotZoomReset()
1024 if (d->mInitialFontSize != f.
pointSize()) {
1030void RichTextEditor::moveCursorBeginUpDown(
bool moveUp)
1034 move.beginEditBlock();
1038 move.endEditBlock();
1042void RichTextEditor::moveLineUpDown(
bool moveUp)
1046 move.beginEditBlock();
1048 const bool hasSelection =
cursor.hasSelection();
1059 const QString text =
move.selectedText();
1062 move.removeSelectedText();
1070 if (
move.atBlockStart()) {
1080 move.clearSelection();
1081 move.insertText(text);
1085 move.setPosition(end);
1090 move.endEditBlock();
1095#include "moc_richtexteditor.cpp"
static void setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter=false)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void spellCheckDone(const QString &newBuffer)
void spellCheckStatus(const QString &)
void languageChanged(const QString &language)
void ignoreWord(const QString &word)
Highlighter * highlighter() const
void setHighlighter(Highlighter *highlighter)
The RichTextEditor class.
void insertEmoticon(const QString &)
This signal is emitted each time the user selects an emoji.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
ButtonCode questionTwoActions(QWidget *parent, const QString &text, const QString &title, const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const QString &dontAskAgainName=QString(), Options options=Notify)
const QList< QKeySequence > & beginningOfLine()
const QList< QKeySequence > & begin()
const QList< QKeySequence > & cut()
const QList< QKeySequence > & undo()
const QList< QKeySequence > & next()
const QList< QKeySequence > & deleteWordBack()
const QList< QKeySequence > & find()
const QList< QKeySequence > & paste()
const QList< QKeySequence > & end()
const QList< QKeySequence > & copy()
const QList< QKeySequence > & backwardWord()
const QList< QKeySequence > & endOfLine()
const QList< QKeySequence > & forwardWord()
const QList< QKeySequence > & deleteWordForward()
const QList< QKeySequence > & findNext()
const QList< QKeySequence > & prior()
const QList< QKeySequence > & replace()
const QList< QKeySequence > & pasteSelection()
const QList< QKeySequence > & redo()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
void triggerAction(SliderAction action)
QVariant data() const const
void setIcon(const QIcon &icon)
void setActionGroup(QActionGroup *group)
void setData(const QVariant &data)
void triggered(bool checked)
QString text(Mode mode) const const
int pointSize() const const
void setPointSize(int pointSize)
Qt::KeyboardModifiers keyboardModifiers()
QIcon fromTheme(const QString &name)
const_reference at(qsizetype i) const const
qsizetype count() const const
qsizetype indexOf(const AT &value, qsizetype from) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * sender() const const
const QColor & color(ColorGroup group, ColorRole role) const const
void setColor(ColorGroup group, ColorRole role, const QColor &color)
bool isEmpty() const const
qsizetype length() const const
int position() const const
bool isAnchor() const const
bool atBlockEnd() const const
bool atBlockStart() const const
QTextCharFormat charFormat() const const
bool hasSelection() const const
bool movePosition(MoveOperation operation, MoveMode mode, int n)
void removeSelectedText()
QString selectedText() const const
void setCharFormat(const QTextCharFormat &format)
QMenu * createStandardContextMenu()
QRect cursorRect() const const
void ensureCursorVisible()
virtual void focusInEvent(QFocusEvent *e) override
void insertPlainText(const QString &text)
virtual void keyPressEvent(QKeyEvent *e) override
bool isReadOnly() const const
void setTextCursor(const QTextCursor &cursor)
QTextCursor textCursor() const const
QString toPlainText() const const
virtual void wheelEvent(QWheelEvent *e) override
QString toString() const const