]> git.lyx.org Git - lyx.git/blob - src/frontends/controllers/ControlSpellchecker.cpp
fix scrolling bug: 3320 and 3652, maybe not perfect
[lyx.git] / src / frontends / controllers / ControlSpellchecker.cpp
1 /**
2  * \file ControlSpellchecker.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Edwin Leuven
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "ControlSpellchecker.h"
14
15 #include "Buffer.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
18 #include "Cursor.h"
19 #include "CutAndPaste.h"
20 #include "debug.h"
21 #include "gettext.h"
22 #include "Language.h"
23 #include "LyXRC.h"
24 #include "Paragraph.h"
25
26 #if defined(USE_ASPELL)
27 # include "ASpell_local.h"
28 #elif defined(USE_PSPELL)
29 # include "PSpell.h"
30 #endif
31
32 #if defined(USE_ISPELL)
33 # include "ISpell.h"
34 #else
35 # include "SpellBase.h"
36 #endif
37
38 #include "support/textutils.h"
39 #include "support/convert.h"
40 #include "support/docstring.h"
41
42 #include "frontends/alert.h"
43 // FIXME: those two headers are needed because of the
44 // WorkArea::redraw() call below.
45 #include "frontends/LyXView.h"
46 #include "frontends/WorkArea.h"
47
48 using std::advance;
49 using std::distance;
50 using std::endl;
51 using std::string;
52
53 namespace lyx {
54
55 using support::bformat;
56 using support::contains;
57
58 namespace frontend {
59
60
61 ControlSpellchecker::ControlSpellchecker(Dialog & parent)
62         : Dialog::Controller(parent), exitEarly_(false),
63           oldval_(0), newvalue_(0), count_(0)
64 {
65 }
66
67
68 ControlSpellchecker::~ControlSpellchecker()
69 {}
70
71
72 namespace {
73
74 SpellBase * getSpeller(BufferParams const & bp)
75 {
76         string lang = (lyxrc.isp_use_alt_lang)
77                       ? lyxrc.isp_alt_lang
78                       : bp.language->code();
79
80 #if defined(USE_ASPELL)
81         if (lyxrc.use_spell_lib)
82                 return new ASpell(bp, lang);
83 #elif defined(USE_PSPELL)
84         if (lyxrc.use_spell_lib)
85                 return new PSpell(bp, lang);
86 #endif
87
88 #if defined(USE_ISPELL)
89         lang = (lyxrc.isp_use_alt_lang) ?
90                 lyxrc.isp_alt_lang : bp.language->lang();
91
92         return new ISpell(bp, lang);
93 #else
94         return new SpellBase;
95 #endif
96 }
97
98 } // namespace anon
99
100
101 bool ControlSpellchecker::initialiseParams(std::string const &)
102 {
103         LYXERR(Debug::GUI) << "Spellchecker::initialiseParams" << endl;
104
105         speller_.reset(getSpeller(kernel().buffer().params()));
106         if (!speller_.get())
107                 return false;
108
109         // reset values to initial
110         oldval_ = 0;
111         newvalue_ = 0;
112         count_ = 0;
113
114         bool const success = speller_->error().empty();
115
116         if (!success) {
117                 Alert::error(_("Spellchecker error"),
118                              _("The spellchecker could not be started\n")
119                              + speller_->error());
120                 speller_.reset(0);
121         }
122
123         return success;
124 }
125
126
127 void ControlSpellchecker::clearParams()
128 {
129         LYXERR(Debug::GUI) << "Spellchecker::clearParams" << endl;
130         speller_.reset(0);
131 }
132
133
134 namespace {
135
136 bool isLetter(DocIterator const & dit)
137 {
138         return dit.inTexted()
139                 && dit.inset().allowSpellCheck()
140                 && dit.pos() != dit.lastpos()
141                 && (dit.paragraph().isLetter(dit.pos())
142                     // We want to pass the ' and escape chars to ispell
143                     || contains(lyx::from_utf8(lyxrc.isp_esc_chars + '\''),
144                                 dit.paragraph().getChar(dit.pos())))
145                 && !dit.paragraph().isDeleted(dit.pos());
146 }
147
148
149 WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
150 {
151         BufferParams const & bp = cur.bv().buffer()->params();
152         bool inword = false;
153         bool ignoreword = false;
154         cur.resetAnchor();
155         docstring word;
156         string lang_code;
157
158         while (cur.depth()) {
159                 if (isLetter(cur)) {
160                         if (!inword) {
161                                 inword = true;
162                                 ignoreword = false;
163                                 cur.resetAnchor();
164                                 word.clear();
165                                 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
166                         }
167                         // Insets like optional hyphens and ligature
168                         // break are part of a word.
169                         if (!cur.paragraph().isInset(cur.pos())) {
170                                 Paragraph::value_type const c =
171                                         cur.paragraph().getChar(cur.pos());
172                                 word += c;
173                                 if (isDigit(c))
174                                         ignoreword = true;
175                         }
176                 } else { // !isLetter(cur)
177                         if (inword)
178                                 if (!word.empty() && !ignoreword) {
179                                         cur.setSelection();
180                                         return WordLangTuple(word, lang_code);
181                                 } else
182                                         inword = false;
183                 }
184
185                 cur.forwardPos();
186                 ++progress;
187         }
188
189         return WordLangTuple(docstring(), string());
190 }
191
192 } // namespace anon
193
194
195
196 void ControlSpellchecker::check()
197 {
198         LYXERR(Debug::GUI) << "Check the spelling of a word" << endl;
199
200         SpellBase::Result res = SpellBase::OK;
201
202         Cursor cur = kernel().bufferview()->cursor();
203         while (cur && cur.pos() && isLetter(cur)) {
204                 cur.backwardPos();
205         }
206
207         ptrdiff_t start = 0, total = 0;
208         DocIterator it = DocIterator(kernel().buffer().inset());
209         for (start = 0; it != cur; it.forwardPos())
210                 ++start;
211
212         for (total = start; it; it.forwardPos())
213                 ++total;
214
215         exitEarly_ = false;
216
217         while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
218                 word_ = nextWord(cur, start);
219
220                 // end of document
221                 if (getWord().empty()) {
222                         showSummary();
223                         exitEarly_ = true;
224                         return;
225                 }
226
227                 ++count_;
228
229                 // Update slider if and only if value has changed
230                 float progress = total ? float(start)/total : 1;
231                 newvalue_ = int(100.0 * progress);
232                 if (newvalue_!= oldval_) {
233                         LYXERR(Debug::GUI) << "Updating spell progress." << endl;
234                         oldval_ = newvalue_;
235                         // set progress bar
236                         dialog().view().partialUpdate(SPELL_PROGRESSED);
237                 }
238
239                 // speller might be dead ...
240                 if (!checkAlive())
241                         return;
242
243                 res = speller_->check(word_);
244
245                 // ... or it might just be reporting an error
246                 if (!checkAlive())
247                         return;
248         }
249
250         LYXERR(Debug::GUI) << "Found word \"" << to_utf8(getWord()) << "\"" << endl;
251
252         int const size = cur.selEnd().pos() - cur.selBegin().pos();
253         cur.pos() -= size;
254         kernel().bufferview()->putSelectionAt(cur, size, false);
255         // FIXME: if we used a lfun like in find/replace, dispatch would do
256         // that for us
257         kernel().bufferview()->update();
258         // FIXME: this Controller is very badly designed...
259         kernel().lyxview().currentWorkArea()->redraw();
260
261         // set suggestions
262         if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
263                 LYXERR(Debug::GUI) << "Found a word needing checking." << endl;
264                 dialog().view().partialUpdate(SPELL_FOUND_WORD);
265         }
266 }
267
268
269 bool ControlSpellchecker::checkAlive()
270 {
271         if (speller_->alive() && speller_->error().empty())
272                 return true;
273
274         docstring message;
275         if (speller_->error().empty())
276                 message = _("The spellchecker has died for some reason.\n"
277                                          "Maybe it has been killed.");
278         else
279                 message = _("The spellchecker has failed.\n") + speller_->error();
280
281         dialog().CancelButton();
282
283         Alert::error(_("The spellchecker has failed"), message);
284         return false;
285 }
286
287
288 void ControlSpellchecker::showSummary()
289 {
290         if (!checkAlive() || count_ == 0) {
291                 dialog().CancelButton();
292                 return;
293         }
294
295         docstring message;
296         if (count_ != 1)
297                 message = bformat(_("%1$d words checked."), count_);
298         else
299                 message = _("One word checked.");
300
301         dialog().CancelButton();
302         Alert::information(_("Spelling check completed"), message);
303 }
304
305
306 void ControlSpellchecker::replace(docstring const & replacement)
307 {
308         LYXERR(Debug::GUI) << "ControlSpellchecker::replace("
309                            << to_utf8(replacement) << ")" << std::endl;
310         BufferView & bufferview = *kernel().bufferview();
311         cap::replaceSelectionWithString(bufferview.cursor(), replacement, true);
312         kernel().buffer().markDirty();
313         // If we used an LFUN, we would not need that
314         bufferview.update();
315         // fix up the count
316         --count_;
317         check();
318 }
319
320
321 void ControlSpellchecker::replaceAll(docstring const & replacement)
322 {
323         // TODO: add to list
324         replace(replacement);
325 }
326
327
328 void ControlSpellchecker::insert()
329 {
330         speller_->insert(word_);
331         check();
332 }
333
334
335 docstring const ControlSpellchecker::getSuggestion() const
336 {
337         return speller_->nextMiss();
338 }
339
340
341 docstring const ControlSpellchecker::getWord() const
342 {
343         return word_.word();
344 }
345
346
347 void ControlSpellchecker::ignoreAll()
348 {
349         speller_->accept(word_);
350         check();
351 }
352
353 } // namespace frontend
354 } // namespace lyx