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/convert.h"
31 #include "support/docstring.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"
56 // FIXME: those two headers are needed because of the
57 // WorkArea::redraw() call below.
58 #include "frontends/LyXView.h"
59 #include "frontends/WorkArea.h"
70 using support::bformat;
71 using support::contains;
73 GuiSpellchecker::GuiSpellchecker(LyXView & lv)
74 : GuiDialog(lv, "spellchecker"), Controller(this), exitEarly_(false),
75 oldval_(0), newvalue_(0), count_(0), speller_(0)
78 setViewTitle(_("Spellchecker"));
79 setController(this, false);
81 connect(closePB, SIGNAL(clicked()), this, SLOT(slotClose()));
82 connect(replacePB, SIGNAL(clicked()), this, SLOT(replaceClicked()));
83 connect(ignorePB, SIGNAL(clicked()), this, SLOT(ignoreClicked()));
84 connect(replacePB_3, SIGNAL(clicked()), this, SLOT(acceptClicked()));
85 connect(addPB, SIGNAL(clicked()), this, SLOT(addClicked()));
87 connect(replaceCO, SIGNAL(highlighted(QString)),
88 this, SLOT(replaceChanged(QString)));
89 connect(suggestionsLW, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
90 this, SLOT(replaceClicked()));
91 connect(suggestionsLW, SIGNAL(itemClicked(QListWidgetItem*)),
92 this, SLOT(suggestionChanged(QListWidgetItem*)));
94 wordED->setReadOnly(true);
96 bc().setPolicy(ButtonPolicy::NoRepeatedApplyReadOnlyPolicy);
97 bc().setCancel(closePB);
101 GuiSpellchecker::~GuiSpellchecker()
107 void GuiSpellchecker::suggestionChanged(QListWidgetItem * item)
109 if (replaceCO->count() != 0)
110 replaceCO->setItemText(0, item->text());
112 replaceCO->addItem(item->text());
114 replaceCO->setCurrentIndex(0);
118 void GuiSpellchecker::replaceChanged(const QString & str)
120 if (suggestionsLW->currentItem()->text() == str)
123 for (int i = 0; i != suggestionsLW->count(); ++i) {
124 if (suggestionsLW->item(i)->text() == str) {
125 suggestionsLW->setCurrentRow(i);
132 void GuiSpellchecker::closeEvent(QCloseEvent * e)
135 GuiDialog::closeEvent(e);
139 void GuiSpellchecker::reject()
146 void GuiSpellchecker::updateContents()
148 if (isVisibleView() || exitEarly())
153 void GuiSpellchecker::accept()
159 void GuiSpellchecker::add()
165 void GuiSpellchecker::ignore()
171 void GuiSpellchecker::replace()
173 replace(qstring_to_ucs4(replaceCO->currentText()));
177 void GuiSpellchecker::partialUpdate(int state)
180 case SPELL_PROGRESSED:
181 spellcheckPR->setValue(getProgress());
184 case SPELL_FOUND_WORD: {
185 wordED->setText(toqstr(getWord()));
186 suggestionsLW->clear();
189 while (!(w = getSuggestion()).empty())
190 suggestionsLW->addItem(toqstr(w));
192 if (suggestionsLW->count() == 0)
193 suggestionChanged(new QListWidgetItem(wordED->text()));
195 suggestionChanged(suggestionsLW->item(0));
197 suggestionsLW->setCurrentRow(0);
204 static SpellBase * getSpeller(BufferParams const & bp)
206 string lang = (lyxrc.isp_use_alt_lang)
208 : bp.language->code();
210 #if defined(USE_ASPELL)
211 if (lyxrc.use_spell_lib)
212 return new ASpell(bp, lang);
213 #elif defined(USE_PSPELL)
214 if (lyxrc.use_spell_lib)
215 return new PSpell(bp, lang);
218 #if defined(USE_ISPELL)
219 lang = lyxrc.isp_use_alt_lang ?
220 lyxrc.isp_alt_lang : bp.language->lang();
222 return new ISpell(bp, lang);
224 return new SpellBase;
229 bool GuiSpellchecker::initialiseParams(std::string const &)
231 LYXERR(Debug::GUI) << "Spellchecker::initialiseParams" << endl;
233 speller_ = getSpeller(buffer().params());
237 // reset values to initial
242 bool const success = speller_->error().empty();
245 Alert::error(_("Spellchecker error"),
246 _("The spellchecker could not be started\n")
247 + speller_->error());
256 void GuiSpellchecker::clearParams()
258 LYXERR(Debug::GUI) << "Spellchecker::clearParams" << endl;
264 static bool isLetter(DocIterator const & dit)
266 return dit.inTexted()
267 && dit.inset().allowSpellCheck()
268 && dit.pos() != dit.lastpos()
269 && (dit.paragraph().isLetter(dit.pos())
270 // We want to pass the ' and escape chars to ispell
271 || contains(from_utf8(lyxrc.isp_esc_chars + '\''),
272 dit.paragraph().getChar(dit.pos())))
273 && !dit.paragraph().isDeleted(dit.pos());
277 static WordLangTuple nextWord(Cursor & cur, ptrdiff_t & progress)
279 BufferParams const & bp = cur.bv().buffer().params();
281 bool ignoreword = false;
286 while (cur.depth()) {
293 lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
295 // Insets like optional hyphens and ligature
296 // break are part of a word.
297 if (!cur.paragraph().isInset(cur.pos())) {
298 Paragraph::value_type const c =
299 cur.paragraph().getChar(cur.pos());
304 } else { // !isLetter(cur)
306 if (!word.empty() && !ignoreword) {
308 return WordLangTuple(word, lang_code);
317 return WordLangTuple(docstring(), string());
321 void GuiSpellchecker::check()
323 LYXERR(Debug::GUI) << "Check the spelling of a word" << endl;
325 SpellBase::Result res = SpellBase::OK;
327 Cursor cur = bufferview()->cursor();
328 while (cur && cur.pos() && isLetter(cur))
333 DocIterator it = DocIterator(buffer().inset());
334 for (start = 0; it != cur; it.forwardPos())
337 for (total = start; it; it.forwardPos())
342 while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
343 word_ = nextWord(cur, start);
346 if (getWord().empty()) {
354 // Update slider if and only if value has changed
355 float progress = total ? float(start)/total : 1;
356 newvalue_ = int(100.0 * progress);
357 if (newvalue_!= oldval_) {
358 LYXERR(Debug::GUI) << "Updating spell progress." << endl;
361 dialog().partialUpdateView(SPELL_PROGRESSED);
364 // speller might be dead ...
368 res = speller_->check(word_);
370 // ... or it might just be reporting an error
375 LYXERR(Debug::GUI) << "Found word \"" << to_utf8(getWord()) << "\"" << endl;
377 int const size = cur.selEnd().pos() - cur.selBegin().pos();
379 bufferview()->putSelectionAt(cur, size, false);
380 // FIXME: if we used a lfun like in find/replace, dispatch would do
382 bufferview()->update();
383 // FIXME: this Controller is very badly designed...
384 lyxview().currentWorkArea()->redraw();
387 if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
388 LYXERR(Debug::GUI) << "Found a word needing checking." << endl;
389 dialog().partialUpdateView(SPELL_FOUND_WORD);
394 bool GuiSpellchecker::checkAlive()
396 if (speller_->alive() && speller_->error().empty())
400 if (speller_->error().empty())
401 message = _("The spellchecker has died for some reason.\n"
402 "Maybe it has been killed.");
404 message = _("The spellchecker has failed.\n") + speller_->error();
406 dialog().slotClose();
408 Alert::error(_("The spellchecker has failed"), message);
413 void GuiSpellchecker::showSummary()
415 if (!checkAlive() || count_ == 0) {
416 dialog().slotClose();
422 message = bformat(_("%1$d words checked."), count_);
424 message = _("One word checked.");
426 dialog().slotClose();
427 Alert::information(_("Spelling check completed"), message);
431 void GuiSpellchecker::replace(docstring const & replacement)
433 LYXERR(Debug::GUI) << "GuiSpellchecker::replace("
434 << to_utf8(replacement) << ")" << std::endl;
435 cap::replaceSelectionWithString(bufferview()->cursor(), replacement, true);
436 buffer().markDirty();
437 // If we used an LFUN, we would not need that
438 bufferview()->update();
445 void GuiSpellchecker::replaceAll(docstring const & replacement)
448 replace(replacement);
452 void GuiSpellchecker::insert()
454 speller_->insert(word_);
459 docstring GuiSpellchecker::getSuggestion() const
461 return speller_->nextMiss();
465 docstring GuiSpellchecker::getWord() const
471 void GuiSpellchecker::ignoreAll()
473 speller_->accept(word_);
478 Dialog * createGuiSpellchecker(LyXView & lv) { return new GuiSpellchecker(lv); }
480 } // namespace frontend
483 #include "GuiSpellchecker_moc.cpp"