2 * \file GuiSpellchecker.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
9 * Full author contact details are available in file CREDITS.
14 #include "GuiSpellchecker.h"
16 #include "qt_helpers.h"
19 #include "BufferParams.h"
20 #include "BufferView.h"
22 #include "CutAndPaste.h"
25 #include "Paragraph.h"
27 #include "support/debug.h"
28 #include "support/docstring.h"
29 #include "support/gettext.h"
30 #include "support/lstrings.h"
31 #include "support/textutils.h"
33 #include <QListWidgetItem>
35 #if defined(USE_ASPELL)
36 # include "ASpell_local.h"
39 #include "SpellBase.h"
41 #include "frontends/alert.h"
44 using namespace lyx::support;
50 GuiSpellchecker::GuiSpellchecker(GuiView & lv)
51 : GuiDialog(lv, "spellchecker", qt_("Spellchecker")), exitEarly_(false),
52 oldval_(0), newvalue_(0), count_(0), speller_(0)
56 connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
57 connect(replacePB, SIGNAL(clicked()), this, SLOT(replace()));
58 connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
59 connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept()));
60 connect(addPB, SIGNAL(clicked()), this, SLOT(add()));
62 connect(replaceCO, SIGNAL(highlighted(QString)),
63 this, SLOT(replaceChanged(QString)));
64 connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
65 this, SLOT(replace()));
66 connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
67 this, SLOT(suggestionChanged(QListWidgetItem*)));
69 wordED->setReadOnly(true);
71 bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
72 bc().setCancel(closePB);
76 GuiSpellchecker::~GuiSpellchecker()
82 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
84 if (replaceCO->count() != 0)
85 replaceCO->setItemText(0, item->text());
87 replaceCO->addItem(item->text());
89 replaceCO->setCurrentIndex(0);
93 void GuiSpellchecker::replaceChanged(const QString & str)
95 if (suggestionsLW->currentItem()->text() == str)
98 for (int i = 0; i != suggestionsLW->count(); ++i) {
99 if (suggestionsLW->item(i)->text() == str) {
100 suggestionsLW->setCurrentRow(i);
107 void GuiSpellchecker::reject()
114 void GuiSpellchecker::updateContents()
116 // The clauses below are needed because the spellchecker
117 // has many flaws (see bugs 1950, 2218).
118 // Basically, we have to distinguish the case where a
119 // spellcheck has already been performed for the whole
120 // document (exitEarly() == true, isVisible() == false)
121 // from the rest (exitEarly() == false, isVisible() == true).
122 // FIXME: rewrite the whole beast!
123 static bool check_after_early_exit;
125 // a spellcheck has already been performed,
127 check_after_early_exit = true;
129 else if (isVisible()) {
130 // the above check triggers a second update,
131 // and isVisible() is true then. Prevent a
132 // second check that skips the first word
133 if (check_after_early_exit)
134 // don't check, but reset the bool.
135 // business as usual after this.
136 check_after_early_exit = false;
138 // perform spellcheck (default case)
144 void GuiSpellchecker::accept()
150 void GuiSpellchecker::add()
156 void GuiSpellchecker::ignore()
162 void GuiSpellchecker::replace()
164 replace(qstring_to_ucs4(replaceCO->currentText()));
168 void GuiSpellchecker::partialUpdate(int state)
171 case SPELL_PROGRESSED:
172 spellcheckPR->setValue(getProgress());
175 case SPELL_FOUND_WORD: {
176 wordED->setText(toqstr(getWord()));
177 suggestionsLW->clear();
180 while (!(w = getSuggestion()).empty())
181 suggestionsLW->addItem(toqstr(w));
183 if (suggestionsLW->count() == 0)
184 suggestionChanged(new QListWidgetItem(wordED->text()));
186 suggestionChanged(suggestionsLW->item(0));
188 suggestionsLW->setCurrentRow(0);
195 static SpellBase * createSpeller(BufferParams const & bp)
197 string lang = lyxrc.spellchecker_use_alt_lang
198 ? lyxrc.spellchecker_alt_lang
199 : bp.language->code();
201 #if defined(USE_ASPELL)
202 return new ASpell(bp, lang);
204 return new SpellBase;
208 bool GuiSpellchecker::initialiseParams(string const &)
210 LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
212 speller_ = createSpeller(buffer().params());
216 // reset values to initial
221 bool const success = speller_->error().empty();
224 Alert::error(_("Spellchecker error"),
225 _("The spellchecker could not be started\n")
226 + speller_->error());
235 void GuiSpellchecker::clearParams()
237 LYXERR(Debug::GUI, "Spellchecker::clearParams");
243 static bool isLetter(DocIterator const & dit)
245 return dit.inTexted()
246 && dit.inset().allowSpellCheck()
247 && dit.pos() != dit.lastpos()
248 && (dit.paragraph().isLetter(dit.pos())
249 // We want to pass the ' and escape chars to ispell
250 || contains(from_utf8(lyxrc.spellchecker_esc_chars + '\''),
251 dit.paragraph().getChar(dit.pos())))
252 && !dit.paragraph().isDeleted(dit.pos());
256 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
258 BufferParams const & bp = cur.bv().buffer().params();
260 bool ignoreword = false;
265 while (cur.depth()) {
272 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
274 // Insets like optional hyphens and ligature
275 // break are part of a word.
276 if (!cur.paragraph().isInset(cur.pos())) {
277 char_type const c = cur.paragraph().getChar(cur.pos());
282 } else { // !isLetter(cur)
284 if (!word.empty() && !ignoreword) {
286 return WordLangTuple(word, lang_code);
295 return WordLangTuple(docstring(), string());
299 void GuiSpellchecker::check()
301 LYXERR(Debug::GUI, "Check the spelling of a word");
303 SpellBase::Result res = SpellBase::OK;
305 Cursor cur = bufferview()->cursor();
306 while (cur && cur.pos() && isLetter(cur))
311 DocIterator it = doc_iterator_begin(&buffer());
312 for (start = 1; it != cur; it.forwardPos())
315 for (total = start; it; it.forwardPos())
320 while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
321 word_ = nextWord(cur, start);
324 if (getWord().empty()) {
332 // Update slider if and only if value has changed
333 float progress = total ? float(start)/total : 1;
334 newvalue_ = int(100.0 * progress);
335 if (newvalue_!= oldval_) {
336 LYXERR(Debug::GUI, "Updating spell progress.");
339 partialUpdate(SPELL_PROGRESSED);
342 // speller might be dead ...
346 res = speller_->check(word_);
348 // ... or it might just be reporting an error
353 LYXERR(Debug::GUI, "Found word \"" << to_utf8(getWord()) << "\"");
355 int const size = cur.selEnd().pos() - cur.selBegin().pos();
357 BufferView * bv = const_cast<BufferView *>(bufferview());
358 bv->putSelectionAt(cur, size, false);
359 // FIXME: if we used a lfun like in find/replace, dispatch would do
361 // FIXME: this Controller is very badly designed...
362 bv->processUpdateFlags(Update::Force | Update::FitCursor);
365 if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
366 LYXERR(Debug::GUI, "Found a word needing checking.");
367 partialUpdate(SPELL_FOUND_WORD);
372 bool GuiSpellchecker::checkAlive()
374 if (speller_->alive() && speller_->error().empty())
378 if (speller_->error().empty())
379 message = _("The spellchecker has died for some reason.\n"
380 "Maybe it has been killed.");
382 message = _("The spellchecker has failed.\n") + speller_->error();
386 Alert::error(_("The spellchecker has failed"), message);
391 void GuiSpellchecker::showSummary()
393 if (!checkAlive() || count_ == 0) {
400 message = bformat(_("%1$d words checked."), count_);
402 message = _("One word checked.");
405 Alert::information(_("Spelling check completed"), message);
409 void GuiSpellchecker::replace(docstring const & replacement)
411 LYXERR(Debug::GUI, "GuiSpellchecker::replace("
412 << to_utf8(replacement) << ")");
413 BufferView * bv = const_cast<BufferView *>(bufferview());
414 cap::replaceSelectionWithString(bv->cursor(), replacement, true);
415 bv->buffer().markDirty();
416 // If we used an LFUN, we would not need that
417 bv->processUpdateFlags(Update::Force | Update::FitCursor);
424 void GuiSpellchecker::replaceAll(docstring const & replacement)
427 replace(replacement);
431 void GuiSpellchecker::insert()
433 speller_->insert(word_);
438 docstring GuiSpellchecker::getSuggestion() const
440 return speller_->nextMiss();
444 docstring GuiSpellchecker::getWord() const
450 void GuiSpellchecker::ignoreAll()
452 speller_->accept(word_);
457 Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); }
459 } // namespace frontend
462 #include "moc_GuiSpellchecker.cpp"