]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSpellchecker.cpp
'using namespace std' instead of 'using std::xxx'
[lyx.git] / src / frontends / qt4 / GuiSpellchecker.cpp
1 /**
2  * \file GuiSpellchecker.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Edwin Leuven
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "GuiSpellchecker.h"
15
16 #include "qt_helpers.h"
17
18 #include "Buffer.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
21 #include "Cursor.h"
22 #include "CutAndPaste.h"
23 #include "support/debug.h"
24 #include "support/gettext.h"
25 #include "Language.h"
26 #include "LyXRC.h"
27 #include "Paragraph.h"
28
29 #include "support/textutils.h"
30 #include "support/docstring.h"
31 #include "support/lstrings.h"
32
33 #include <QListWidgetItem>
34
35 #if defined(USE_ASPELL)
36 # include "ASpell_local.h"
37 #elif defined(USE_PSPELL)
38 # include "PSpell.h"
39 #endif
40
41 #if defined(USE_ISPELL)
42 # include "ISpell.h"
43 #else
44 # include "SpellBase.h"
45 #endif
46
47 #include "frontends/alert.h"
48
49 using namespace std;
50
51 namespace lyx {
52 namespace frontend {
53
54 using support::bformat;
55 using support::contains;
56
57 GuiSpellchecker::GuiSpellchecker(GuiView & lv)
58         : GuiDialog(lv, "spellchecker"), exitEarly_(false),
59           oldval_(0), newvalue_(0), count_(0), speller_(0)
60 {
61         setupUi(this);
62         setViewTitle(_("Spellchecker"));
63
64         connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
65         connect(replacePB, SIGNAL(clicked()), this, SLOT(replace()));
66         connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
67         connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept()));
68         connect(addPB, SIGNAL(clicked()), this, SLOT(add()));
69
70         connect(replaceCO, SIGNAL(highlighted(QString)),
71                 this, SLOT(replaceChanged(QString)));
72         connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
73                 this, SLOT(replace()));
74         connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
75                 this, SLOT(suggestionChanged(QListWidgetItem*)));
76
77         wordED->setReadOnly(true);
78
79         bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
80         bc().setCancel(closePB);
81 }
82
83
84 GuiSpellchecker::~GuiSpellchecker()
85 {
86         delete speller_;
87 }
88
89
90 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
91 {
92         if (replaceCO->count() != 0)
93                 replaceCO->setItemText(0, item->text());
94         else
95                 replaceCO->addItem(item->text());
96
97         replaceCO->setCurrentIndex(0);
98 }
99
100
101 void GuiSpellchecker::replaceChanged(const QString & str)
102 {
103         if (suggestionsLW->currentItem()->text() == str)
104                 return;
105
106         for (int i = 0; i != suggestionsLW->count(); ++i) {
107                 if (suggestionsLW->item(i)->text() == str) {
108                         suggestionsLW->setCurrentRow(i);
109                         break;
110                 }
111         }
112 }
113
114
115 void GuiSpellchecker::closeEvent(QCloseEvent * e)
116 {
117         slotClose();
118         GuiDialog::closeEvent(e);
119 }
120
121
122 void GuiSpellchecker::reject()
123 {
124         slotClose();
125         QDialog::reject();
126 }
127
128
129 void GuiSpellchecker::updateContents()
130 {
131         // The clauses below are needed because the spellchecker
132         // has many flaws (see bugs 1950, 2218).
133         // Basically, we have to distinguish the case where a
134         // spellcheck has already been performed for the whole
135         // document (exitEarly() == true, isVisible() == false) 
136         // from the rest (exitEarly() == false, isVisible() == true).
137         // FIXME: rewrite the whole beast!
138         static bool check_after_early_exit;
139         if (exitEarly()) {
140                 // a spellcheck has already been performed,
141                 check();
142                 check_after_early_exit = true;
143         }
144         else if (isVisible()) {
145                 // the above check triggers a second update,
146                 // and isVisible() is true then. Prevent a
147                 // second check that skips the first word
148                 if (check_after_early_exit)
149                         // don't check, but reset the bool.
150                         // business as usual after this.
151                         check_after_early_exit = false;
152                 else
153                         // perform spellcheck (default case)
154                         check();
155         }
156 }
157
158
159 void GuiSpellchecker::accept()
160 {
161         ignoreAll();
162 }
163
164
165 void GuiSpellchecker::add()
166 {
167         insert();
168 }
169
170
171 void GuiSpellchecker::ignore()
172 {
173         check();
174 }
175
176
177 void GuiSpellchecker::replace()
178 {
179         replace(qstring_to_ucs4(replaceCO->currentText()));
180 }
181
182
183 void GuiSpellchecker::partialUpdate(int state)
184 {
185         switch (state) {
186                 case SPELL_PROGRESSED:
187                         spellcheckPR->setValue(getProgress());
188                         break;
189
190                 case SPELL_FOUND_WORD: {
191                         wordED->setText(toqstr(getWord()));
192                         suggestionsLW->clear();
193
194                         docstring w;
195                         while (!(w = getSuggestion()).empty())
196                                 suggestionsLW->addItem(toqstr(w));
197
198                         if (suggestionsLW->count() == 0)
199                                 suggestionChanged(new QListWidgetItem(wordED->text()));
200                         else
201                                 suggestionChanged(suggestionsLW->item(0));
202
203                         suggestionsLW->setCurrentRow(0);
204                         break;
205                 }
206         }
207 }
208
209
210 static SpellBase * getSpeller(BufferParams const & bp)
211 {
212         string lang = (lyxrc.isp_use_alt_lang)
213                       ? lyxrc.isp_alt_lang
214                       : bp.language->code();
215
216 #if defined(USE_ASPELL)
217         if (lyxrc.use_spell_lib)
218                 return new ASpell(bp, lang);
219 #elif defined(USE_PSPELL)
220         if (lyxrc.use_spell_lib)
221                 return new PSpell(bp, lang);
222 #endif
223
224 #if defined(USE_ISPELL)
225         lang = lyxrc.isp_use_alt_lang ?
226                 lyxrc.isp_alt_lang : bp.language->lang();
227
228         return new ISpell(bp, lang);
229 #else
230         return new SpellBase;
231 #endif
232 }
233
234
235 bool GuiSpellchecker::initialiseParams(std::string const &)
236 {
237         LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
238
239         speller_ = getSpeller(buffer().params());
240         if (!speller_)
241                 return false;
242
243         // reset values to initial
244         oldval_ = 0;
245         newvalue_ = 0;
246         count_ = 0;
247
248         bool const success = speller_->error().empty();
249
250         if (!success) {
251                 Alert::error(_("Spellchecker error"),
252                              _("The spellchecker could not be started\n")
253                              + speller_->error());
254                 delete speller_;
255                 speller_ = 0;
256         }
257
258         return success;
259 }
260
261
262 void GuiSpellchecker::clearParams()
263 {
264         LYXERR(Debug::GUI, "Spellchecker::clearParams");
265         delete speller_;
266         speller_ = 0;
267 }
268
269
270 static bool isLetter(DocIterator const & dit)
271 {
272         return dit.inTexted()
273                 && dit.inset().allowSpellCheck()
274                 && dit.pos() != dit.lastpos()
275                 && (dit.paragraph().isLetter(dit.pos())
276                     // We want to pass the ' and escape chars to ispell
277                     || contains(from_utf8(lyxrc.isp_esc_chars + '\''),
278                                 dit.paragraph().getChar(dit.pos())))
279                 && !dit.paragraph().isDeleted(dit.pos());
280 }
281
282
283 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
284 {
285         BufferParams const & bp = cur.bv().buffer().params();
286         bool inword = false;
287         bool ignoreword = false;
288         cur.resetAnchor();
289         docstring word;
290         string lang_code;
291
292         while (cur.depth()) {
293                 if (isLetter(cur)) {
294                         if (!inword) {
295                                 inword = true;
296                                 ignoreword = false;
297                                 cur.resetAnchor();
298                                 word.clear();
299                                 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
300                         }
301                         // Insets like optional hyphens and ligature
302                         // break are part of a word.
303                         if (!cur.paragraph().isInset(cur.pos())) {
304                                 char_type const c = cur.paragraph().getChar(cur.pos());
305                                 word += c;
306                                 if (isDigit(c))
307                                         ignoreword = true;
308                         }
309                 } else { // !isLetter(cur)
310                         if (inword)
311                                 if (!word.empty() && !ignoreword) {
312                                         cur.setSelection();
313                                         return WordLangTuple(word, lang_code);
314                                 }
315                                 inword = false;
316                 }
317
318                 cur.forwardPos();
319                 ++progress;
320         }
321
322         return WordLangTuple(docstring(), string());
323 }
324
325
326 void GuiSpellchecker::check()
327 {
328         LYXERR(Debug::GUI, "Check the spelling of a word");
329
330         SpellBase::Result res = SpellBase::OK;
331
332         Cursor cur = bufferview()->cursor();
333         while (cur && cur.pos() && isLetter(cur))
334                 cur.backwardPos();
335
336         ptrdiff_t start = 0;
337         ptrdiff_t total = 0;
338         DocIterator it = DocIterator(buffer().inset());
339         for (start = 0; it != cur; it.forwardPos())
340                 ++start;
341
342         for (total = start; it; it.forwardPos())
343                 ++total;
344
345         exitEarly_ = false;
346
347         while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
348                 word_ = nextWord(cur, start);
349
350                 // end of document
351                 if (getWord().empty()) {
352                         showSummary();
353                         exitEarly_ = true;
354                         return;
355                 }
356
357                 ++count_;
358
359                 // Update slider if and only if value has changed
360                 float progress = total ? float(start)/total : 1;
361                 newvalue_ = int(100.0 * progress);
362                 if (newvalue_!= oldval_) {
363                         LYXERR(Debug::GUI, "Updating spell progress.");
364                         oldval_ = newvalue_;
365                         // set progress bar
366                         partialUpdate(SPELL_PROGRESSED);
367                 }
368
369                 // speller might be dead ...
370                 if (!checkAlive())
371                         return;
372
373                 res = speller_->check(word_);
374
375                 // ... or it might just be reporting an error
376                 if (!checkAlive())
377                         return;
378         }
379
380         LYXERR(Debug::GUI, "Found word \"" << to_utf8(getWord()) << "\"");
381
382         int const size = cur.selEnd().pos() - cur.selBegin().pos();
383         cur.pos() -= size;
384         bufferview()->putSelectionAt(cur, size, false);
385         // FIXME: if we used a lfun like in find/replace, dispatch would do
386         // that for us
387         // FIXME: this Controller is very badly designed...
388         bufferview()->processUpdateFlags(Update::Force | Update::FitCursor);
389
390         // set suggestions
391         if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
392                 LYXERR(Debug::GUI, "Found a word needing checking.");
393                 partialUpdate(SPELL_FOUND_WORD);
394         }
395 }
396
397
398 bool GuiSpellchecker::checkAlive()
399 {
400         if (speller_->alive() && speller_->error().empty())
401                 return true;
402
403         docstring message;
404         if (speller_->error().empty())
405                 message = _("The spellchecker has died for some reason.\n"
406                                          "Maybe it has been killed.");
407         else
408                 message = _("The spellchecker has failed.\n") + speller_->error();
409
410         slotClose();
411
412         Alert::error(_("The spellchecker has failed"), message);
413         return false;
414 }
415
416
417 void GuiSpellchecker::showSummary()
418 {
419         if (!checkAlive() || count_ == 0) {
420                 slotClose();
421                 return;
422         }
423
424         docstring message;
425         if (count_ != 1)
426                 message = bformat(_("%1$d words checked."), count_);
427         else
428                 message = _("One word checked.");
429
430         slotClose();
431         Alert::information(_("Spelling check completed"), message);
432 }
433
434
435 void GuiSpellchecker::replace(docstring const & replacement)
436 {
437         LYXERR(Debug::GUI, "GuiSpellchecker::replace("
438                            << to_utf8(replacement) << ")");
439         cap::replaceSelectionWithString(bufferview()->cursor(), replacement, true);
440         buffer().markDirty();
441         // If we used an LFUN, we would not need that
442         bufferview()->processUpdateFlags(Update::Force | Update::FitCursor);
443         // fix up the count
444         --count_;
445         check();
446 }
447
448
449 void GuiSpellchecker::replaceAll(docstring const & replacement)
450 {
451         // TODO: add to list
452         replace(replacement);
453 }
454
455
456 void GuiSpellchecker::insert()
457 {
458         speller_->insert(word_);
459         check();
460 }
461
462
463 docstring GuiSpellchecker::getSuggestion() const
464 {
465         return speller_->nextMiss();
466 }
467
468
469 docstring GuiSpellchecker::getWord() const
470 {
471         return word_.word();
472 }
473
474
475 void GuiSpellchecker::ignoreAll()
476 {
477         speller_->accept(word_);
478         check();
479 }
480
481
482 Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); }
483
484 } // namespace frontend
485 } // namespace lyx
486
487 #include "GuiSpellchecker_moc.cpp"