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