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 oldval_(0), newvalue_(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 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()
96 && suggestionsLW->currentItem()->text() == str)
99 for (int i = 0; i != suggestionsLW->count(); ++i) {
100 if (suggestionsLW->item(i)->text() == str) {
101 suggestionsLW->setCurrentRow(i);
108 void GuiSpellchecker::reject()
115 void GuiSpellchecker::updateContents()
117 // The clauses below are needed because the spellchecker
118 // has many flaws (see bugs 1950, 2218).
119 // Basically, we have to distinguish the case where a
120 // spellcheck has already been performed for the whole
121 // document (exitEarly() == true, isVisible() == false)
122 // from the rest (exitEarly() == false, isVisible() == true).
123 // FIXME: rewrite the whole beast!
124 static bool check_after_early_exit;
126 // a spellcheck has already been performed,
128 check_after_early_exit = true;
130 else if (isVisible()) {
131 // the above check triggers a second update,
132 // and isVisible() is true then. Prevent a
133 // second check that skips the first word
134 if (check_after_early_exit)
135 // don't check, but reset the bool.
136 // business as usual after this.
137 check_after_early_exit = false;
139 // perform spellcheck (default case)
145 void GuiSpellchecker::accept()
151 void GuiSpellchecker::add()
157 void GuiSpellchecker::ignore()
163 void GuiSpellchecker::replace()
165 replace(qstring_to_ucs4(replaceCO->currentText()));
169 void GuiSpellchecker::partialUpdate(int state)
172 case SPELL_PROGRESSED:
173 spellcheckPR->setValue(getProgress());
176 case SPELL_FOUND_WORD: {
177 wordED->setText(toqstr(getWord()));
178 suggestionsLW->clear();
181 while (!(w = getSuggestion()).empty())
182 suggestionsLW->addItem(toqstr(w));
184 if (suggestionsLW->count() == 0)
185 suggestionChanged(new QListWidgetItem(wordED->text()));
187 suggestionChanged(suggestionsLW->item(0));
189 suggestionsLW->setCurrentRow(0);
196 bool GuiSpellchecker::initialiseParams(string const &)
198 LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
200 speller_ = theSpellChecker();
204 // reset values to initial
209 bool const success = speller_->error().empty();
212 Alert::error(_("Spellchecker error"),
213 _("The spellchecker could not be started\n")
214 + speller_->error());
222 void GuiSpellchecker::clearParams()
224 LYXERR(Debug::GUI, "Spellchecker::clearParams");
229 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
231 Buffer const & buf = cur.bv().buffer();
234 DocIterator from = cur;
236 if (!buf.nextWord(from, to, word))
237 return WordLangTuple(docstring(), string());
243 string lang_code = lyxrc.spellchecker_use_alt_lang
244 ? lyxrc.spellchecker_alt_lang
245 : from.paragraph().getFontSettings(buf.params(), cur.pos()).language()->code();
247 return WordLangTuple(word, lang_code);
251 void GuiSpellchecker::check()
253 LYXERR(Debug::GUI, "Check the spelling of a word");
255 SpellChecker::Result res = SpellChecker::OK;
257 Cursor cur = bufferview()->cursor();
258 while (cur && cur.pos() && isLetter(cur))
263 DocIterator it = doc_iterator_begin(&buffer());
264 for (start = 1; it != cur; it.forwardPos())
267 for (total = start; it; it.forwardPos())
272 while (res == SpellChecker::OK || res == SpellChecker::IGNORED_WORD) {
273 word_ = nextWord(cur, start);
276 if (getWord().empty()) {
284 // Update slider if and only if value has changed
285 float progress = total ? float(start)/total : 1;
286 newvalue_ = int(100.0 * progress);
287 if (newvalue_!= oldval_) {
288 LYXERR(Debug::GUI, "Updating spell progress.");
291 partialUpdate(SPELL_PROGRESSED);
294 res = speller_->check(word_);
296 // ... just bail out if the spellchecker reports an error.
297 if (!speller_->error().empty()) {
298 docstring const message =
299 _("The spellchecker has failed.\n") + speller_->error();
305 LYXERR(Debug::GUI, "Found word \"" << to_utf8(getWord()) << "\"");
307 int const size = cur.selEnd().pos() - cur.selBegin().pos();
309 BufferView * bv = const_cast<BufferView *>(bufferview());
310 bv->putSelectionAt(cur, size, false);
311 // FIXME: if we used a lfun like in find/replace, dispatch would do
313 // FIXME: this Controller is very badly designed...
314 bv->processUpdateFlags(Update::Force | Update::FitCursor);
317 if (res != SpellChecker::OK && res != SpellChecker::IGNORED_WORD) {
318 LYXERR(Debug::GUI, "Found a word needing checking.");
319 partialUpdate(SPELL_FOUND_WORD);
324 void GuiSpellchecker::showSummary()
333 message = bformat(_("%1$d words checked."), count_);
335 message = _("One word checked.");
338 Alert::information(_("Spelling check completed"), message);
342 void GuiSpellchecker::replace(docstring const & replacement)
344 LYXERR(Debug::GUI, "GuiSpellchecker::replace("
345 << to_utf8(replacement) << ")");
346 BufferView * bv = const_cast<BufferView *>(bufferview());
347 cap::replaceSelectionWithString(bv->cursor(), replacement, true);
348 bv->buffer().markDirty();
349 // If we used an LFUN, we would not need that
350 bv->processUpdateFlags(Update::Force | Update::FitCursor);
357 void GuiSpellchecker::replaceAll(docstring const & replacement)
360 replace(replacement);
364 void GuiSpellchecker::insert()
366 speller_->insert(word_);
371 docstring GuiSpellchecker::getSuggestion() const
373 return speller_->nextMiss();
377 docstring GuiSpellchecker::getWord() const
383 void GuiSpellchecker::ignoreAll()
385 speller_->accept(word_);
390 Dialog * createGuiSpellchecker(GuiView & lv) { return new GuiSpellchecker(lv); }
392 } // namespace frontend
395 #include "moc_GuiSpellchecker.cpp"