File indexing completed on 2024-07-14 03:58:58

0001 // SPDX-FileCopyrightText: 2004 Zack Rusin <zack@kde.org>
0002 // SPDX-FileCopyrightText: 2013 Martin Sandsmark <martin.sandsmark@kde.org>
0003 // SPDX-FileCopyrightText: 2013 Aurélien Gâteau <agateau@kde.org>
0004 // SPDX-FileCopyrightText: 2020 Christian Mollekopf <mollekopf@kolabsystems.com>
0005 // SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0006 // SPDX-License-Identifier: LGPL-2.1-or-later
0007 
0008 #pragma once
0009 
0010 // TODO KF6 create AbstractSpellcheckHighlighter and make the QtQuick and QtWidget inherit
0011 // from it.
0012 
0013 #include <QQuickTextDocument>
0014 #include <QSyntaxHighlighter>
0015 
0016 class HighlighterPrivate;
0017 
0018 /// \brief The Sonnet Highlighter class, used for drawing red lines in text fields
0019 /// when detecting spelling mistakes.
0020 ///
0021 /// SpellcheckHighlighter is adapted for QML applications. In usual Kirigami/QQC2-desktop-style
0022 /// applications, this can be used directly by adding `Kirigami.SpellCheck.enabled: true` on
0023 /// a TextArea.
0024 ///
0025 /// On other QML applications, you can add the SpellcheckHighlighter as a child of a TextArea.
0026 ///
0027 /// Note: TextField is not supported, as it lacks QTextDocument API that Sonnet relies on.
0028 ///
0029 /// \code{.qml}
0030 /// TextArea {
0031 ///     id: textArea
0032 ///     Sonnet.SpellcheckHighlighter {
0033 ///         id: spellcheckhighlighter
0034 ///         document: textArea.textDocument
0035 ///         cursorPosition: textArea.cursorPosition
0036 ///         selectionStart: textArea.selectionStart
0037 ///         selectionEnd: textArea.selectionEnd
0038 ///         misspelledColor: Kirigami.Theme.negativeTextColor
0039 ///         active: true
0040 ///
0041 ///         onChangeCursorPosition: {
0042 ///             textArea.cursorPosition = start;
0043 ///             textArea.moveCursorSelection(end, TextEdit.SelectCharacters);
0044 ///         }
0045 ///     }
0046 /// }
0047 /// \endcode
0048 ///
0049 /// Additionally SpellcheckHighlighter provides some convenient methods to create
0050 /// a context menu with suggestions. \see suggestions
0051 ///
0052 /// \since 5.88
0053 class SpellcheckHighlighter : public QSyntaxHighlighter
0054 {
0055     Q_OBJECT
0056     QML_ELEMENT
0057     /// This property holds the underneath document from a QML TextEdit.
0058     /// \since 5.88
0059     Q_PROPERTY(QQuickTextDocument *document READ quickDocument WRITE setQuickDocument NOTIFY documentChanged)
0060 
0061     /// This property holds the current cursor position.
0062     /// \since 5.88
0063     Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
0064 
0065     /// This property holds the start of the selection.
0066     /// \since 5.88
0067     Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
0068 
0069     /// This property holds the end of the selection.
0070     /// \since 5.88
0071     Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged)
0072 
0073     /// This property holds whether the current word under the mouse is misspelled.
0074     /// \since 5.88
0075     Q_PROPERTY(bool wordIsMisspelled READ wordIsMisspelled NOTIFY wordIsMisspelledChanged)
0076 
0077     /// This property holds the current word under the mouse.
0078     /// \since 5.88
0079     Q_PROPERTY(QString wordUnderMouse READ wordUnderMouse NOTIFY wordUnderMouseChanged)
0080 
0081     /// This property holds the spell color. By default, it's red.
0082     /// \since 5.88
0083     Q_PROPERTY(QColor misspelledColor READ misspelledColor WRITE setMisspelledColor NOTIFY misspelledColorChanged)
0084 
0085     /// This property holds the current language used for spell checking.
0086     /// \since 5.88
0087     Q_PROPERTY(QString currentLanguage READ currentLanguage NOTIFY currentLanguageChanged)
0088 
0089     /// This property holds whether a spell checking backend with support for the
0090     /// \ref currentLanguage was found.
0091     /// \since 5.88
0092     Q_PROPERTY(bool spellCheckerFound READ spellCheckerFound CONSTANT)
0093 
0094     /// \brief This property holds whether spell checking is enabled.
0095     ///
0096     /// If \p active is true then spell checking is enabled; otherwise it
0097     /// is disabled. Note that you have to disable automatic (de)activation
0098     /// with \ref automatic before you change the state of spell
0099     /// checking if you want to persistently enable/disable spell
0100     /// checking.
0101     ///
0102     /// \see automatic
0103     /// \since 5.88
0104     Q_PROPERTY(bool active READ active WRITE setActive NOTIFY activeChanged)
0105 
0106     /// This property holds whether spell checking is automatically disabled
0107     /// if there's too many errors.
0108     /// \since 5.88
0109     Q_PROPERTY(bool automatic READ automatic WRITE setAutomatic NOTIFY automaticChanged)
0110 
0111     /// This property holds whether the automatic language detection is disabled
0112     /// overriding the Sonnet global settings.
0113     /// \since 5.88
0114     Q_PROPERTY(bool autoDetectLanguageDisabled READ autoDetectLanguageDisabled WRITE setAutoDetectLanguageDisabled NOTIFY autoDetectLanguageDisabledChanged)
0115 
0116 public:
0117     explicit SpellcheckHighlighter(QObject *parent = nullptr);
0118     ~SpellcheckHighlighter() override;
0119 
0120     /// Returns a list of suggested replacements for the given misspelled word.
0121     /// If the word is not misspelled, the list will be empty.
0122     ///
0123     /// \param word the misspelled word
0124     /// \param max at most this many suggestions will be returned. If this is
0125     ///            -1, as many suggestions as the spell backend supports will
0126     ///            be returned.
0127     /// \return a list of suggested replacements for the word
0128     /// \since 5.88
0129     Q_INVOKABLE QStringList suggestions(int position, int max = 5);
0130 
0131     /// Ignores the given word. This word will not be marked misspelled for
0132     /// this session. It will again be marked as misspelled when creating
0133     /// new highlighters.
0134     ///
0135     /// \param word the word which will be ignored
0136     /// \since 5.88
0137     Q_INVOKABLE void ignoreWord(const QString &word);
0138 
0139     /// Adds the given word permanently to the dictionary. It will never
0140     /// be marked as misspelled again, even after restarting the application.
0141     ///
0142     /// \param word the word which will be added to the dictionary
0143     /// \since 5.88
0144     Q_INVOKABLE void addWordToDictionary(const QString &word);
0145 
0146     /// Replace word at the current cursor position, or @param at if
0147     /// @param at is not -1.
0148     /// \since 5.88
0149     Q_INVOKABLE void replaceWord(const QString &word, int at = -1);
0150 
0151     /// Checks if a given word is marked as misspelled by the highlighter.
0152     ///
0153     /// \param word the word to be checked
0154     /// \return true if the given word is misspelled.
0155     /// \since 5.88
0156     Q_INVOKABLE bool isWordMisspelled(const QString &word);
0157 
0158     Q_REQUIRED_RESULT QQuickTextDocument *quickDocument() const;
0159     void setQuickDocument(QQuickTextDocument *document);
0160     Q_REQUIRED_RESULT int cursorPosition() const;
0161     void setCursorPosition(int position);
0162     Q_REQUIRED_RESULT int selectionStart() const;
0163     void setSelectionStart(int position);
0164     Q_REQUIRED_RESULT int selectionEnd() const;
0165     void setSelectionEnd(int position);
0166     Q_REQUIRED_RESULT bool wordIsMisspelled() const;
0167     Q_REQUIRED_RESULT QString wordUnderMouse() const;
0168     Q_REQUIRED_RESULT bool spellCheckerFound() const;
0169     Q_REQUIRED_RESULT QString currentLanguage() const;
0170     void setActive(bool active);
0171     Q_REQUIRED_RESULT bool active() const;
0172     void setAutomatic(bool automatic);
0173     Q_REQUIRED_RESULT bool automatic() const;
0174     void setAutoDetectLanguageDisabled(bool autoDetectDisabled);
0175     Q_REQUIRED_RESULT bool autoDetectLanguageDisabled() const;
0176     void setMisspelledColor(const QColor &color);
0177     Q_REQUIRED_RESULT QColor misspelledColor() const;
0178     void setQuoteColor(const QColor &color);
0179     Q_REQUIRED_RESULT QColor quoteColor() const;
0180 
0181     /// Return true if checker is enabled by default
0182     /// \since 5.88
0183     bool checkerEnabledByDefault() const;
0184 
0185     /// Set a new @ref QTextDocument for this highlighter to operate on.
0186     /// \param document the new document to operate on.
0187     /// \since 5.88
0188     void setDocument(QTextDocument *document);
0189 
0190 Q_SIGNALS:
0191     void documentChanged();
0192     void cursorPositionChanged();
0193     void selectionStartChanged();
0194     void selectionEndChanged();
0195     void wordIsMisspelledChanged();
0196     void wordUnderMouseChanged();
0197     void changeCursorPosition(int start, int end);
0198     void activeChanged();
0199     void misspelledColorChanged();
0200     void autoDetectLanguageDisabledChanged();
0201     void automaticChanged();
0202     void currentLanguageChanged();
0203 
0204     /// Emitted when as-you-type spell checking is enabled or disabled.
0205     ///
0206     /// \param description is a i18n description of the new state,
0207     ///        with an optional reason
0208     /// \since 5.88
0209     void activeChanged(const QString &description);
0210 
0211 protected:
0212     void highlightBlock(const QString &text) override;
0213     virtual void setMisspelled(int start, int count);
0214     virtual void setMisspelledSelected(int start, int count);
0215     virtual void unsetMisspelled(int start, int count);
0216     bool eventFilter(QObject *o, QEvent *e) override;
0217 
0218     bool intraWordEditing() const;
0219     void setIntraWordEditing(bool editing);
0220 
0221 public Q_SLOTS:
0222     /// Set language to use for spell checking.
0223     ///
0224     /// \param language the language code for the new language to use.
0225     /// \since 5.88
0226     void setCurrentLanguage(const QString &language);
0227 
0228     /// Run auto detection, disabling spell checking if too many errors are found.
0229     /// \since 5.88
0230     void slotAutoDetection();
0231 
0232     /// Force a new highlighting.
0233     /// \since 5.88
0234     void slotRehighlight();
0235 
0236 private:
0237     Q_REQUIRED_RESULT QTextCursor textCursor() const;
0238     Q_REQUIRED_RESULT QTextDocument *textDocument() const;
0239     void contentsChange(int pos, int add, int rem);
0240 
0241     void autodetectLanguage(const QString &sentence);
0242 
0243     HighlighterPrivate *const d;
0244     Q_DISABLE_COPY(SpellcheckHighlighter)
0245 };