KTextEditor

spellcheckdialog.cpp
1/*
2 SPDX-FileCopyrightText: 2009-2010 Michel Ludwig <michel.ludwig@kdemail.net>
3 SPDX-FileCopyrightText: 2008 Mirko Stocker <me@misto.ch>
4 SPDX-FileCopyrightText: 2004-2005 Anders Lund <anders@alweb.dk>
5 SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
6 SPDX-FileCopyrightText: 2001-2004 Christoph Cullmann <cullmann@kde.org>
7 SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
8 SPDX-FileCopyrightText: 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
9
10 SPDX-License-Identifier: LGPL-2.0-or-later
11*/
12
13#include "spellcheckdialog.h"
14
15#include "katedocument.h"
16#include "kateglobal.h"
17#include "kateview.h"
18#include "spellcheck/spellcheck.h"
19#include "spellcheck/spellcheckbar.h"
20
21#include <KActionCollection>
22#include <KLocalizedString>
23#include <KStandardAction>
24
25#include <sonnet/backgroundchecker.h>
26#include <sonnet/speller.h>
27
28KateSpellCheckDialog::KateSpellCheckDialog(KTextEditor::ViewPrivate *view)
29 : QObject(view)
30 , m_view(view)
31 , m_speller(nullptr)
32 , m_backgroundChecker(nullptr)
33 , m_sonnetDialog(nullptr)
34 , m_globalSpellCheckRange(nullptr)
35 , m_spellCheckCancelledByUser(false)
36{
37}
38
39KateSpellCheckDialog::~KateSpellCheckDialog()
40{
41 delete m_globalSpellCheckRange;
42 delete m_sonnetDialog;
43 delete m_backgroundChecker;
44 delete m_speller;
45}
46
47void KateSpellCheckDialog::createActions(KActionCollection *ac)
48{
49 ac->addAction(KStandardAction::Spelling, this, SLOT(spellcheck()));
50
51 QAction *a = new QAction(i18n("Spelling (from Cursor)..."), this);
52 ac->addAction(QStringLiteral("tools_spelling_from_cursor"), a);
53 a->setIcon(QIcon::fromTheme(QStringLiteral("tools-check-spelling")));
54 a->setWhatsThis(i18n("Check the document's spelling from the cursor and forward"));
55 connect(a, &QAction::triggered, this, &KateSpellCheckDialog::spellcheckFromCursor);
56}
57
58void KateSpellCheckDialog::spellcheckFromCursor()
59{
60 if (m_view->selection()) {
61 spellcheckSelection();
62 } else {
63 spellcheck(m_view->cursorPosition());
64 }
65}
66
67void KateSpellCheckDialog::spellcheckSelection()
68{
69 spellcheck(m_view->selectionRange().start(), m_view->selectionRange().end());
70}
71
72void KateSpellCheckDialog::spellcheck()
73{
74 if (m_view->selection()) {
75 spellcheckSelection();
76 } else {
77 spellcheck(KTextEditor::Cursor(0, 0));
78 }
79}
80
81void KateSpellCheckDialog::spellcheck(const KTextEditor::Cursor from, const KTextEditor::Cursor to)
82{
85
86 if (end.line() == 0 && end.column() == 0) {
87 end = m_view->doc()->documentEnd();
88 }
89
90 if (!m_speller) {
91 m_speller = new Sonnet::Speller();
92 }
93 m_speller->restore();
94
95 if (!m_backgroundChecker) {
96 m_backgroundChecker = new Sonnet::BackgroundChecker(*m_speller);
97 }
98
99 if (!m_sonnetDialog) {
100 m_sonnetDialog = new SpellCheckBar(m_backgroundChecker, m_view);
101 m_sonnetDialog->showProgressDialog(200);
102 m_sonnetDialog->showSpellCheckCompletionMessage();
103 m_sonnetDialog->setSpellCheckContinuedAfterReplacement(false);
104
105 connect(m_sonnetDialog, &SpellCheckBar::done, this, &KateSpellCheckDialog::installNextSpellCheckRange);
106
107 connect(m_sonnetDialog, &SpellCheckBar::replace, this, &KateSpellCheckDialog::corrected);
108
109 connect(m_sonnetDialog, &SpellCheckBar::misspelling, this, &KateSpellCheckDialog::misspelling);
110
111 connect(m_sonnetDialog, &SpellCheckBar::cancel, this, &KateSpellCheckDialog::cancelClicked);
112
113 connect(m_sonnetDialog, &SpellCheckBar::destroyed, this, &KateSpellCheckDialog::objectDestroyed);
114
115 connect(m_sonnetDialog, &SpellCheckBar::languageChanged, this, &KateSpellCheckDialog::languageChanged);
116 }
117
118 m_view->bottomViewBar()->addBarWidget(m_sonnetDialog);
119
120 m_userSpellCheckLanguage.clear();
121 m_previousGivenSpellCheckLanguage.clear();
122 delete m_globalSpellCheckRange;
123 // we expand to handle the situation when the last word in the range is replace by a new one
124 m_globalSpellCheckRange =
126 m_spellCheckCancelledByUser = false;
127 performSpellCheck(*m_globalSpellCheckRange);
128}
129
130KTextEditor::Cursor KateSpellCheckDialog::locatePosition(int pos)
131{
132 uint remains;
133
134 while (m_spellLastPos < (uint)pos) {
135 remains = pos - m_spellLastPos;
136 uint l = m_view->doc()->lineLength(m_spellPosCursor.line()) - m_spellPosCursor.column();
137 if (l > remains) {
138 m_spellPosCursor.setColumn(m_spellPosCursor.column() + remains);
139 m_spellLastPos = pos;
140 } else {
141 m_spellPosCursor.setLine(m_spellPosCursor.line() + 1);
142 m_spellPosCursor.setColumn(0);
143 m_spellLastPos += l + 1;
144 }
145 }
146
147 return m_spellPosCursor;
148}
149
150void KateSpellCheckDialog::misspelling(const QString &word, int pos)
151{
152 KTextEditor::Cursor cursor;
153 int length;
154 int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos);
155 cursor = locatePosition(origPos);
156 length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos;
157
158 m_view->setCursorPositionInternal(cursor, 1);
159 m_view->setSelection(KTextEditor::Range(cursor, length));
160}
161
162void KateSpellCheckDialog::corrected(const QString &word, int pos, const QString &newWord)
163{
164 int origPos = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos);
165
166 int length = m_view->doc()->computePositionWrtOffsets(m_currentDecToEncOffsetList, pos + word.length()) - origPos;
167
168 KTextEditor::Cursor replacementStartCursor = locatePosition(origPos);
169 KTextEditor::Range replacementRange = KTextEditor::Range(replacementStartCursor, length);
170 KTextEditor::DocumentPrivate *doc = m_view->doc();
171 KTextEditor::EditorPrivate::self()->spellCheckManager()->replaceCharactersEncodedIfNecessary(newWord, doc, replacementRange);
172
173 // we have to be careful here: due to static word wrapping the text might change in addition to simply
174 // the misspelled word being replaced, i.e. new line breaks might be inserted as well. As such, the text
175 // in the 'Sonnet::Dialog' might be eventually out of sync with the visible text. Therefore, we 'restart'
176 // spell checking from the current position.
177 performSpellCheck(KTextEditor::Range(replacementStartCursor, m_globalSpellCheckRange->end()));
178}
179
180void KateSpellCheckDialog::performSpellCheck(KTextEditor::Range range)
181{
182 if (range.isEmpty()) {
183 spellCheckDone();
184 m_sonnetDialog->closed();
185 return;
186 }
187 m_languagesInSpellCheckRange = KTextEditor::EditorPrivate::self()->spellCheckManager()->spellCheckLanguageRanges(m_view->doc(), range);
188 m_currentLanguageRangeIterator = m_languagesInSpellCheckRange.begin();
189 m_currentSpellCheckRange = KTextEditor::Range::invalid();
190 installNextSpellCheckRange();
191 // first check if there is really something to spell check
192 if (m_currentSpellCheckRange.isValid()) {
193 m_view->bottomViewBar()->showBarWidget(m_sonnetDialog);
194 m_sonnetDialog->show();
195 m_sonnetDialog->setFocus();
196 } else {
197 m_sonnetDialog->closed();
198 }
199}
200
201void KateSpellCheckDialog::installNextSpellCheckRange()
202{
203 if (m_spellCheckCancelledByUser || m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) {
204 spellCheckDone();
205 return;
206 }
207 KateSpellCheckManager *spellCheckManager = KTextEditor::EditorPrivate::self()->spellCheckManager();
208 KTextEditor::Cursor nextRangeBegin = (m_currentSpellCheckRange.isValid() ? m_currentSpellCheckRange.end() : KTextEditor::Cursor::invalid());
209 m_currentSpellCheckRange = KTextEditor::Range::invalid();
210 m_currentDecToEncOffsetList.clear();
211 QList<QPair<KTextEditor::Range, QString>> rangeDictionaryPairList;
212 while (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) {
213 KTextEditor::Range currentLanguageRange = (*m_currentLanguageRangeIterator).first;
214 const QString &dictionary = (*m_currentLanguageRangeIterator).second;
215 KTextEditor::Range languageSubRange =
216 (nextRangeBegin.isValid() ? KTextEditor::Range(nextRangeBegin, currentLanguageRange.end()) : currentLanguageRange);
217 rangeDictionaryPairList = spellCheckManager->spellCheckWrtHighlightingRanges(m_view->doc(), languageSubRange, dictionary, false, true);
218 Q_ASSERT(rangeDictionaryPairList.size() <= 1);
219 if (rangeDictionaryPairList.size() == 0) {
220 ++m_currentLanguageRangeIterator;
221 if (m_currentLanguageRangeIterator != m_languagesInSpellCheckRange.end()) {
222 nextRangeBegin = (*m_currentLanguageRangeIterator).first.start();
223 }
224 } else {
225 m_currentSpellCheckRange = rangeDictionaryPairList.first().first;
226 QString dictionary = rangeDictionaryPairList.first().second;
227 const bool languageChanged = (dictionary != m_previousGivenSpellCheckLanguage);
228 m_previousGivenSpellCheckLanguage = dictionary;
229
230 // if there was no change of dictionary stemming from the document language ranges and
231 // the user has set a dictionary in the dialog, we use that one
232 if (!languageChanged && !m_userSpellCheckLanguage.isEmpty()) {
233 dictionary = m_userSpellCheckLanguage;
234 }
235 // we only allow the user to override the preset dictionary within a language range
236 // given by the document
237 else if (languageChanged) {
238 m_userSpellCheckLanguage.clear();
239 }
240
241 m_spellPosCursor = m_currentSpellCheckRange.start();
242 m_spellLastPos = 0;
243
244 m_currentDecToEncOffsetList.clear();
245 KTextEditor::DocumentPrivate::OffsetList encToDecOffsetList;
246 QString text = m_view->doc()->decodeCharacters(m_currentSpellCheckRange, m_currentDecToEncOffsetList, encToDecOffsetList);
247 // ensure that no empty string is passed on to Sonnet as this can lead to a crash
248 // (bug 228789)
249 if (text.isEmpty()) {
250 nextRangeBegin = m_currentSpellCheckRange.end();
251 continue;
252 }
253
254 if (m_speller->language() != dictionary) {
255 m_speller->setLanguage(dictionary);
256 m_backgroundChecker->setSpeller(*m_speller);
257 }
258
259 m_sonnetDialog->setBuffer(text);
260 break;
261 }
262 }
263 if (m_currentLanguageRangeIterator == m_languagesInSpellCheckRange.end()) {
264 spellCheckDone();
265 return;
266 }
267}
268
269void KateSpellCheckDialog::cancelClicked()
270{
271 m_spellCheckCancelledByUser = true;
272 spellCheckDone();
273}
274
275void KateSpellCheckDialog::spellCheckDone()
276{
277 m_currentSpellCheckRange = KTextEditor::Range::invalid();
278 m_currentDecToEncOffsetList.clear();
279 m_view->clearSelection();
280}
281
282void KateSpellCheckDialog::objectDestroyed(QObject *object)
283{
284 Q_UNUSED(object);
285 m_sonnetDialog = nullptr;
286}
287
288void KateSpellCheckDialog::languageChanged(const QString &language)
289{
290 m_userSpellCheckLanguage = language;
291}
292
293// END
294
295#include "moc_spellcheckdialog.cpp"
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
static constexpr Cursor start() noexcept
Returns a cursor representing the start of any document - i.e., line 0, column 0.
Definition cursor.h:120
void setLine(int line) noexcept
Set the cursor line to line.
Definition cursor.h:183
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
KateSpellCheckManager * spellCheckManager()
spell check manager
Definition kateglobal.h:304
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
virtual const MovingCursor & end() const =0
Retrieve end cursor of this range, read-only.
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
QString language() const
void setLanguage(const QString &lang)
Spellcheck dialog.
void setSpellCheckContinuedAfterReplacement(bool b)
Controls whether the spell checking is continued after the replacement of a misspelled word has been ...
void showSpellCheckCompletionMessage(bool b=true)
Controls whether a message box indicating the completion of the spell checking is shown or not.
void languageChanged(const QString &language)
Emitted when the user changes the language used for spellchecking, which is shown in a combobox of th...
void done(const QString &newBuffer)
The dialog won't be closed if you setBuffer() in slot connected to this signal.
void showProgressDialog(int timeout=500)
Controls whether an (indefinite) progress dialog is shown when the spell checking takes longer than t...
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
void setIcon(const QIcon &icon)
void triggered(bool checked)
void setWhatsThis(const QString &what)
QIcon fromTheme(const QString &name)
iterator begin()
void clear()
iterator end()
T & first()
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
void clear()
bool isEmpty() const const
qsizetype length() const const
void setFocus()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.