2 * \file ControlSpellchecker.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * Full author contact details are available in file CREDITS.
13 #include "ControlSpellchecker.h"
16 #include "BufferParams.h"
17 #include "BufferView.h"
19 #include "CutAndPaste.h"
24 #include "Paragraph.h"
26 #if defined(USE_ASPELL)
27 # include "ASpell_local.h"
28 #elif defined(USE_PSPELL)
32 #if defined(USE_ISPELL)
35 # include "SpellBase.h"
38 #include "support/textutils.h"
39 #include "support/convert.h"
40 #include "support/docstring.h"
42 #include "frontends/alert.h"
43 // FIXME: those two headers are needed because of the
44 // WorkArea::redraw() call below.
45 #include "frontends/LyXView.h"
46 #include "frontends/WorkArea.h"
55 using support::bformat;
56 using support::contains;
61 ControlSpellchecker::ControlSpellchecker(Dialog & parent)
62 : Dialog::Controller(parent), exitEarly_(false),
63 oldval_(0), newvalue_(0), count_(0)
68 ControlSpellchecker::~ControlSpellchecker()
74 SpellBase * getSpeller(BufferParams const & bp)
76 string lang = (lyxrc.isp_use_alt_lang)
78 : bp.language->code();
80 #if defined(USE_ASPELL)
81 if (lyxrc.use_spell_lib)
82 return new ASpell(bp, lang);
83 #elif defined(USE_PSPELL)
84 if (lyxrc.use_spell_lib)
85 return new PSpell(bp, lang);
88 #if defined(USE_ISPELL)
89 lang = (lyxrc.isp_use_alt_lang) ?
90 lyxrc.isp_alt_lang : bp.language->lang();
92 return new ISpell(bp, lang);
101 bool ControlSpellchecker::initialiseParams(std::string const &)
103 LYXERR(Debug::GUI) << "Spellchecker::initialiseParams" << endl;
105 speller_.reset(getSpeller(kernel().buffer().params()));
109 // reset values to initial
114 bool const success = speller_->error().empty();
117 Alert::error(_("Spellchecker error"),
118 _("The spellchecker could not be started\n")
119 + speller_->error());
127 void ControlSpellchecker::clearParams()
129 LYXERR(Debug::GUI) << "Spellchecker::clearParams" << endl;
136 bool isLetter(DocIterator const & dit)
138 return dit.inTexted()
139 && dit.inset().allowSpellCheck()
140 && dit.pos() != dit.lastpos()
141 && (dit.paragraph().isLetter(dit.pos())
142 // We want to pass the ' and escape chars to ispell
143 || contains(lyx::from_utf8(lyxrc.isp_esc_chars + '\''),
144 dit.paragraph().getChar(dit.pos())))
145 && !dit.paragraph().isDeleted(dit.pos());
149 WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
151 BufferParams const & bp = cur.bv().buffer().params();
153 bool ignoreword = false;
158 while (cur.depth()) {
165 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
167 // Insets like optional hyphens and ligature
168 // break are part of a word.
169 if (!cur.paragraph().isInset(cur.pos())) {
170 Paragraph::value_type const c =
171 cur.paragraph().getChar(cur.pos());
176 } else { // !isLetter(cur)
178 if (!word.empty() && !ignoreword) {
180 return WordLangTuple(word, lang_code);
189 return WordLangTuple(docstring(), string());
196 void ControlSpellchecker::check()
198 LYXERR(Debug::GUI) << "Check the spelling of a word" << endl;
200 SpellBase::Result res = SpellBase::OK;
202 Cursor cur = kernel().bufferview()->cursor();
203 while (cur && cur.pos() && isLetter(cur)) {
207 ptrdiff_t start = 0, total = 0;
208 DocIterator it = DocIterator(kernel().buffer().inset());
209 for (start = 0; it != cur; it.forwardPos())
212 for (total = start; it; it.forwardPos())
217 while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
218 word_ = nextWord(cur, start);
221 if (getWord().empty()) {
229 // Update slider if and only if value has changed
230 float progress = total ? float(start)/total : 1;
231 newvalue_ = int(100.0 * progress);
232 if (newvalue_!= oldval_) {
233 LYXERR(Debug::GUI) << "Updating spell progress." << endl;
236 dialog().view().partialUpdate(SPELL_PROGRESSED);
239 // speller might be dead ...
243 res = speller_->check(word_);
245 // ... or it might just be reporting an error
250 LYXERR(Debug::GUI) << "Found word \"" << to_utf8(getWord()) << "\"" << endl;
252 int const size = cur.selEnd().pos() - cur.selBegin().pos();
254 kernel().bufferview()->putSelectionAt(cur, size, false);
255 // FIXME: if we used a lfun like in find/replace, dispatch would do
257 kernel().bufferview()->update();
258 // FIXME: this Controller is very badly designed...
259 kernel().lyxview().currentWorkArea()->redraw();
262 if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
263 LYXERR(Debug::GUI) << "Found a word needing checking." << endl;
264 dialog().view().partialUpdate(SPELL_FOUND_WORD);
269 bool ControlSpellchecker::checkAlive()
271 if (speller_->alive() && speller_->error().empty())
275 if (speller_->error().empty())
276 message = _("The spellchecker has died for some reason.\n"
277 "Maybe it has been killed.");
279 message = _("The spellchecker has failed.\n") + speller_->error();
281 dialog().CancelButton();
283 Alert::error(_("The spellchecker has failed"), message);
288 void ControlSpellchecker::showSummary()
290 if (!checkAlive() || count_ == 0) {
291 dialog().CancelButton();
297 message = bformat(_("%1$d words checked."), count_);
299 message = _("One word checked.");
301 dialog().CancelButton();
302 Alert::information(_("Spelling check completed"), message);
306 void ControlSpellchecker::replace(docstring const & replacement)
308 LYXERR(Debug::GUI) << "ControlSpellchecker::replace("
309 << to_utf8(replacement) << ")" << std::endl;
310 BufferView & bufferview = *kernel().bufferview();
311 cap::replaceSelectionWithString(bufferview.cursor(), replacement, true);
312 kernel().buffer().markDirty();
313 // If we used an LFUN, we would not need that
321 void ControlSpellchecker::replaceAll(docstring const & replacement)
324 replace(replacement);
328 void ControlSpellchecker::insert()
330 speller_->insert(word_);
335 docstring const ControlSpellchecker::getSuggestion() const
337 return speller_->nextMiss();
341 docstring const ControlSpellchecker::getWord() const
347 void ControlSpellchecker::ignoreAll()
349 speller_->accept(word_);
353 } // namespace frontend