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"
26 #include "Paragraph.h"
28 #include "support/debug.h"
29 #include "support/docstring.h"
30 #include "support/gettext.h"
31 #include "support/lstrings.h"
32 #include "support/textutils.h"
34 #include <QListWidgetItem>
36 #if defined(USE_ASPELL)
37 # include "ASpell_local.h"
40 #include "SpellChecker.h"
42 #include "frontends/alert.h"
45 using namespace lyx::support;
51 GuiSpellchecker::GuiSpellchecker(GuiView & lv)
52 : GuiDialog(lv, "spellchecker", qt_("Spellchecker")), exitEarly_(false),
53 oldprogress_(0), newprogress_(0), count_(0), speller_(0)
57 connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
58 connect(replacePB, SIGNAL(clicked()), this, SLOT(replace()));
59 connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
60 connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept()));
61 connect(addPB, SIGNAL(clicked()), this, SLOT(add()));
63 connect(replaceCO, SIGNAL(highlighted(QString)),
64 this, SLOT(replaceChanged(QString)));
65 connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
66 this, SLOT(replace()));
67 connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
68 this, SLOT(suggestionChanged(QListWidgetItem*)));
70 wordED->setReadOnly(true);
72 bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
73 bc().setCancel(closePB);
77 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
79 if (replaceCO->count() != 0)
80 replaceCO->setItemText(0, item->text());
82 replaceCO->addItem(item->text());
84 replaceCO->setCurrentIndex(0);
88 void GuiSpellchecker::replaceChanged(const QString & str)
90 if (suggestionsLW->currentItem()
91 && suggestionsLW->currentItem()->text() == str)
94 for (int i = 0; i != suggestionsLW->count(); ++i) {
95 if (suggestionsLW->item(i)->text() == str) {
96 suggestionsLW->setCurrentRow(i);
103 void GuiSpellchecker::reject()
110 void GuiSpellchecker::updateContents()
112 // The clauses below are needed because the spellchecker
113 // has many flaws (see bugs 1950, 2218).
114 // Basically, we have to distinguish the case where a
115 // spellcheck has already been performed for the whole
116 // document (exitEarly() == true, isVisible() == false)
117 // from the rest (exitEarly() == false, isVisible() == true).
118 // FIXME: rewrite the whole beast!
119 static bool check_after_early_exit;
121 // a spellcheck has already been performed,
123 check_after_early_exit = true;
125 else if (isVisible()) {
126 // the above check triggers a second update,
127 // and isVisible() is true then. Prevent a
128 // second check that skips the first word
129 if (check_after_early_exit)
130 // don't check, but reset the bool.
131 // business as usual after this.
132 check_after_early_exit = false;
134 // perform spellcheck (default case)
140 void GuiSpellchecker::accept()
146 void GuiSpellchecker::add()
152 void GuiSpellchecker::ignore()
158 void GuiSpellchecker::replace()
160 replace(qstring_to_ucs4(replaceCO->currentText()));
164 void GuiSpellchecker::partialUpdate(int state)
167 case SPELL_PROGRESSED:
168 spellcheckPR->setValue(oldprogress_);
171 case SPELL_FOUND_WORD: {
172 wordED->setText(toqstr(word_.word()));
173 suggestionsLW->clear();
176 while (!(w = speller_->nextMiss()).empty())
177 suggestionsLW->addItem(toqstr(w));
179 if (suggestionsLW->count() == 0)
180 suggestionChanged(new QListWidgetItem(wordED->text()));
182 suggestionChanged(suggestionsLW->item(0));
184 suggestionsLW->setCurrentRow(0);
191 bool GuiSpellchecker::initialiseParams(string const &)
193 LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
195 speller_ = theSpellChecker();
199 // reset values to initial
204 bool const success = speller_->error().empty();
207 Alert::error(_("Spellchecker error"),
208 _("The spellchecker could not be started\n")
209 + speller_->error());
217 void GuiSpellchecker::clearParams()
219 LYXERR(Debug::GUI, "Spellchecker::clearParams");
224 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
226 Buffer const & buf = cur.bv().buffer();
229 DocIterator from = cur;
231 if (!buf.nextWord(from, to, word))
232 return WordLangTuple(docstring(), string());
238 string lang_code = lyxrc.spellchecker_use_alt_lang
239 ? lyxrc.spellchecker_alt_lang
240 : from.paragraph().getFontSettings(buf.params(), cur.pos()).language()->code();
242 return WordLangTuple(word, lang_code);
246 void GuiSpellchecker::check()
248 LYXERR(Debug::GUI, "Check the spelling of a word");
250 SpellChecker::Result res = SpellChecker::OK;
252 Cursor cur = bufferview()->cursor();
253 while (cur && cur.pos() && isLetter(cur))
258 DocIterator it = doc_iterator_begin(&buffer());
259 for (start = 1; it != cur; it.forwardPos())
262 for (total = start; it; it.forwardPos())
267 while (res == SpellChecker::OK || res == SpellChecker::IGNORED_WORD) {
268 word_ = nextWord(cur, start);
271 if (word_.word().empty()) {
279 // Update slider if and only if value has changed
280 float progress = total ? float(start)/total : 1;
281 newprogress_ = int(100.0 * progress);
282 if (newprogress_!= oldprogress_) {
283 LYXERR(Debug::GUI, "Updating spell progress.");
284 oldprogress_ = newprogress_;
286 partialUpdate(SPELL_PROGRESSED);
289 res = speller_->check(word_);
291 // ... just bail out if the spellchecker reports an error.
292 if (!speller_->error().empty()) {
293 docstring const message =
294 _("The spellchecker has failed.\n") + speller_->error();
300 LYXERR(Debug::GUI, "Found word \"" << to_utf8(word_.word()) << "\"");
302 int const size = cur.selEnd().pos() - cur.selBegin().pos();
304 BufferView * bv = const_cast<BufferView *>(bufferview());
305 bv->putSelectionAt(cur, size, false);
306 // FIXME: if we used a lfun like in find/replace, dispatch would do
308 // FIXME: this Controller is very badly designed...
309 bv->processUpdateFlags(Update::Force | Update::FitCursor);
312 if (res != SpellChecker::OK && res != SpellChecker::IGNORED_WORD) {
313 LYXERR(Debug::GUI, "Found a word needing checking.");
314 partialUpdate(SPELL_FOUND_WORD);
319 void GuiSpellchecker::showSummary()
328 message = bformat(_("%1$d words checked."), count_);
330 message = _("One word checked.");
333 Alert::information(_("Spelling check completed"), message);
337 void GuiSpellchecker::replace(docstring const & replacement)
339 LYXERR(Debug::GUI, "GuiSpellchecker::replace("
340 << to_utf8(replacement) << ")");
341 BufferView * bv = const_cast<BufferView *>(bufferview());
342 cap::replaceSelectionWithString(bv->cursor(), replacement, true);
343 bv->buffer().markDirty();
344 // If we used an LFUN, we would not need that
345 bv->processUpdateFlags(Update::Force | Update::FitCursor);
352 void GuiSpellchecker::replaceAll(docstring const & replacement)
355 replace(replacement);
359 void GuiSpellchecker::insert()
361 speller_->insert(word_);
366 void GuiSpellchecker::ignoreAll()
368 speller_->accept(word_);
373 Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); }
375 } // namespace frontend
378 #include "moc_GuiSpellchecker.cpp"