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"
27 #include "Paragraph.h"
29 #include "support/textutils.h"
30 #include "support/docstring.h"
31 #include "support/lstrings.h"
33 #include <QProgressBar>
35 #include <QPushButton>
36 #include <QListWidget>
37 #include <QListWidgetItem>
38 #include <QCloseEvent>
39 #include <QSyntaxHighlighter>
40 #include <QTextCharFormat>
41 #include <QTextDocument>
43 #if defined(USE_ASPELL)
44 # include "ASpell_local.h"
45 #elif defined(USE_PSPELL)
49 #if defined(USE_ISPELL)
52 # include "SpellBase.h"
55 #include "frontends/alert.h"
66 using support::bformat;
67 using support::contains;
69 GuiSpellchecker::GuiSpellchecker(LyXView & lv)
70 : GuiDialog(lv, "spellchecker"), exitEarly_(false),
71 oldval_(0), newvalue_(0), count_(0), speller_(0)
74 setViewTitle(_("Spellchecker"));
76 connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
77 connect(replacePB, SIGNAL(clicked()), this, SLOT(replace()));
78 connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignore()));
79 connect(replacePB_3, SIGNAL(clicked()), this, SLOT(accept()));
80 connect(addPB, SIGNAL(clicked()), this, SLOT(add()));
82 connect(replaceCO, SIGNAL(highlighted(QString)),
83 this, SLOT(replaceChanged(QString)));
84 connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
85 this, SLOT(replace()));
86 connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
87 this, SLOT(suggestionChanged(QListWidgetItem*)));
89 wordED->setReadOnly(true);
91 bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
92 bc().setCancel(closePB);
96 GuiSpellchecker::~GuiSpellchecker()
102 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
104 if (replaceCO->count() != 0)
105 replaceCO->setItemText(0, item->text());
107 replaceCO->addItem(item->text());
109 replaceCO->setCurrentIndex(0);
113 void GuiSpellchecker::replaceChanged(const QString & str)
115 if (suggestionsLW->currentItem()->text() == str)
118 for (int i = 0; i != suggestionsLW->count(); ++i) {
119 if (suggestionsLW->item(i)->text() == str) {
120 suggestionsLW->setCurrentRow(i);
127 void GuiSpellchecker::closeEvent(QCloseEvent * e)
130 GuiDialog::closeEvent(e);
134 void GuiSpellchecker::reject()
141 void GuiSpellchecker::updateContents()
143 // The clauses below are needed because the spellchecker
144 // has many flaws (see bugs 1950, 2218).
145 // Basically, we have to distinguish the case where a
146 // spellcheck has already been performed for the whole
147 // document (exitEarly() == true, isVisible() == false)
148 // from the rest (exitEarly() == false, isVisible() == true).
149 // FIXME: rewrite the whole beast!
150 static bool check_after_early_exit;
152 // a spellcheck has already been performed,
154 check_after_early_exit = true;
156 else if (isVisible()) {
157 // the above check triggers a second update,
158 // and isVisible() is true then. Prevent a
159 // second check that skips the first word
160 if (check_after_early_exit)
161 // don't check, but reset the bool.
162 // business as usual after this.
163 check_after_early_exit = false;
165 // perform spellcheck (default case)
171 void GuiSpellchecker::accept()
177 void GuiSpellchecker::add()
183 void GuiSpellchecker::ignore()
189 void GuiSpellchecker::replace()
191 replace(qstring_to_ucs4(replaceCO->currentText()));
195 void GuiSpellchecker::partialUpdate(int state)
198 case SPELL_PROGRESSED:
199 spellcheckPR->setValue(getProgress());
202 case SPELL_FOUND_WORD: {
203 wordED->setText(toqstr(getWord()));
204 suggestionsLW->clear();
207 while (!(w = getSuggestion()).empty())
208 suggestionsLW->addItem(toqstr(w));
210 if (suggestionsLW->count() == 0)
211 suggestionChanged(new QListWidgetItem(wordED->text()));
213 suggestionChanged(suggestionsLW->item(0));
215 suggestionsLW->setCurrentRow(0);
222 static SpellBase * getSpeller(BufferParams const & bp)
224 string lang = (lyxrc.isp_use_alt_lang)
226 : bp.language->code();
228 #if defined(USE_ASPELL)
229 if (lyxrc.use_spell_lib)
230 return new ASpell(bp, lang);
231 #elif defined(USE_PSPELL)
232 if (lyxrc.use_spell_lib)
233 return new PSpell(bp, lang);
236 #if defined(USE_ISPELL)
237 lang = lyxrc.isp_use_alt_lang ?
238 lyxrc.isp_alt_lang : bp.language->lang();
240 return new ISpell(bp, lang);
242 return new SpellBase;
247 bool GuiSpellchecker::initialiseParams(std::string const &)
249 LYXERR(Debug::GUI, "Spellchecker::initialiseParams");
251 speller_ = getSpeller(buffer().params());
255 // reset values to initial
260 bool const success = speller_->error().empty();
263 Alert::error(_("Spellchecker error"),
264 _("The spellchecker could not be started\n")
265 + speller_->error());
274 void GuiSpellchecker::clearParams()
276 LYXERR(Debug::GUI, "Spellchecker::clearParams");
282 static bool isLetter(DocIterator const & dit)
284 return dit.inTexted()
285 && dit.inset().allowSpellCheck()
286 && dit.pos() != dit.lastpos()
287 && (dit.paragraph().isLetter(dit.pos())
288 // We want to pass the ' and escape chars to ispell
289 || contains(from_utf8(lyxrc.isp_esc_chars + '\''),
290 dit.paragraph().getChar(dit.pos())))
291 && !dit.paragraph().isDeleted(dit.pos());
295 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
297 BufferParams const & bp = cur.bv().buffer().params();
299 bool ignoreword = false;
304 while (cur.depth()) {
311 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
313 // Insets like optional hyphens and ligature
314 // break are part of a word.
315 if (!cur.paragraph().isInset(cur.pos())) {
316 char_type const c = cur.paragraph().getChar(cur.pos());
321 } else { // !isLetter(cur)
323 if (!word.empty() && !ignoreword) {
325 return WordLangTuple(word, lang_code);
334 return WordLangTuple(docstring(), string());
338 void GuiSpellchecker::check()
340 LYXERR(Debug::GUI, "Check the spelling of a word");
342 SpellBase::Result res = SpellBase::OK;
344 Cursor cur = bufferview()->cursor();
345 while (cur && cur.pos() && isLetter(cur))
350 DocIterator it = DocIterator(buffer().inset());
351 for (start = 0; it != cur; it.forwardPos())
354 for (total = start; it; it.forwardPos())
359 while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
360 word_ = nextWord(cur, start);
363 if (getWord().empty()) {
371 // Update slider if and only if value has changed
372 float progress = total ? float(start)/total : 1;
373 newvalue_ = int(100.0 * progress);
374 if (newvalue_!= oldval_) {
375 LYXERR(Debug::GUI, "Updating spell progress.");
378 partialUpdate(SPELL_PROGRESSED);
381 // speller might be dead ...
385 res = speller_->check(word_);
387 // ... or it might just be reporting an error
392 LYXERR(Debug::GUI, "Found word \"" << to_utf8(getWord()) << "\"");
394 int const size = cur.selEnd().pos() - cur.selBegin().pos();
396 bufferview()->putSelectionAt(cur, size, false);
397 // FIXME: if we used a lfun like in find/replace, dispatch would do
399 // FIXME: this Controller is very badly designed...
400 bufferview()->processUpdateFlags(Update::Force | Update::FitCursor);
403 if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
404 LYXERR(Debug::GUI, "Found a word needing checking.");
405 partialUpdate(SPELL_FOUND_WORD);
410 bool GuiSpellchecker::checkAlive()
412 if (speller_->alive() && speller_->error().empty())
416 if (speller_->error().empty())
417 message = _("The spellchecker has died for some reason.\n"
418 "Maybe it has been killed.");
420 message = _("The spellchecker has failed.\n") + speller_->error();
424 Alert::error(_("The spellchecker has failed"), message);
429 void GuiSpellchecker::showSummary()
431 if (!checkAlive() || count_ == 0) {
438 message = bformat(_("%1$d words checked."), count_);
440 message = _("One word checked.");
443 Alert::information(_("Spelling check completed"), message);
447 void GuiSpellchecker::replace(docstring const & replacement)
449 LYXERR(Debug::GUI, "GuiSpellchecker::replace("
450 << to_utf8(replacement) << ")");
451 cap::replaceSelectionWithString(bufferview()->cursor(), replacement, true);
452 buffer().markDirty();
453 // If we used an LFUN, we would not need that
454 bufferview()->processUpdateFlags(Update::Force | Update::FitCursor);
461 void GuiSpellchecker::replaceAll(docstring const & replacement)
464 replace(replacement);
468 void GuiSpellchecker::insert()
470 speller_->insert(word_);
475 docstring GuiSpellchecker::getSuggestion() const
477 return speller_->nextMiss();
481 docstring GuiSpellchecker::getWord() const
487 void GuiSpellchecker::ignoreAll()
489 speller_->accept(word_);
494 Dialog * createGuiSpellchecker(LyXView & lv) { return new GuiSpellchecker(lv); }
496 } // namespace frontend
499 #include "GuiSpellchecker_moc.cpp"