-/* This file is part of
- * ======================================================
- *
- * LyX, The Document Processor
- *
- * Copyright 2001 The LyX Team.
+/**
+ * \file ControlSpellchecker.C
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
*
- * ======================================================
+ * \author Edwin Leuven
*
- * \file ControlSpellchecker.C
- * \author Edwin Leuven <leuven@fee.uva.nl>
+ * Full author contact details are available in file CREDITS.
*/
#include <config.h>
-#ifdef __GNUG__
-#pragma implementation
-#endif
-
-#include FORMS_H_LOCATION
-
-#ifdef HAVE_SYS_SELECT_H
-# ifdef HAVE_STRINGS_H
- // <strings.h> is needed at least on AIX because FD_ZERO uses bzero().
- // BUT we cannot include both string.h and strings.h on Irix 6.5 :(
-# ifdef _AIX
-# include <strings.h>
-# endif
-# endif
-#include <sys/select.h>
-#endif
+#include "ControlSpellchecker.h"
#include "buffer.h"
-#include "lyxrc.h"
+#include "bufferparams.h"
#include "BufferView.h"
-#include "LyXView.h"
+#include "cursor.h"
+#include "CutAndPaste.h"
+#include "debug.h"
#include "gettext.h"
-#include "support/lstrings.h"
#include "language.h"
+#include "lyxrc.h"
+#include "paragraph.h"
-#include "ViewBase.h"
-#include "ButtonControllerBase.h"
-#include "ControlSpellchecker.h"
-#include "Dialogs.h"
-#include "Liason.h"
+#if defined(USE_ASPELL)
+# include "aspell_local.h"
+#elif defined(USE_PSPELL)
+# include "pspell.h"
+#endif
-# include "sp_ispell.h"
-#ifdef USE_PSPELL
-# include "sp_pspell.h"
+#if defined(USE_ISPELL)
+# include "ispell.h"
+#else
+# include "SpellBase.h"
#endif
-#include "debug.h"
+#include "support/textutils.h"
+#include "support/convert.h"
-using SigC::slot;
+#include "frontends/Alert.h"
+
+using std::advance;
+using std::distance;
+using std::endl;
+using std::string;
+
+namespace lyx {
+
+using support::bformat;
+using support::contains;
+
+namespace frontend {
+
+
+ControlSpellchecker::ControlSpellchecker(Dialog & parent)
+ : Dialog::Controller(parent), exitEarly_(false),
+ oldval_(0), newvalue_(0), count_(0)
+{}
-ControlSpellchecker::ControlSpellchecker(LyXView & lv, Dialogs & d)
- : ControlDialog<ControlConnectBD>(lv, d),
- rtl_(false), newval_(0.0), oldval_(0), newvalue_(0), count_(0),
- stop_(false), result_(SpellBase::ISP_UNKNOWN), speller_(0)
-{
- d_.showSpellchecker.connect(SigC::slot(this, &ControlSpellchecker::show));
-}
+ControlSpellchecker::~ControlSpellchecker()
+{}
-void ControlSpellchecker::setParams()
+
+namespace {
+
+SpellBase * getSpeller(BufferParams const & bp)
{
- if (!speller_) {
- // create spell object
- string tmp;
-#ifdef USE_PSPELL
- if (lyxrc.use_pspell) {
- tmp = (lyxrc.isp_use_alt_lang) ?
- lyxrc.isp_alt_lang : lv_.buffer()->params.language->code();
-
- speller_ = new PSpell(lv_.view()->buffer()->params, tmp);
- } else {
+ string lang = (lyxrc.isp_use_alt_lang)
+ ? lyxrc.isp_alt_lang
+ : bp.language->code();
+
+#if defined(USE_ASPELL)
+ if (lyxrc.use_spell_lib)
+ return new ASpell(bp, lang);
+#elif defined(USE_PSPELL)
+ if (lyxrc.use_spell_lib)
+ return new PSpell(bp, lang);
#endif
- tmp = (lyxrc.isp_use_alt_lang) ?
- lyxrc.isp_alt_lang : lv_.buffer()->params.language->lang();
-
- speller_ = new ISpell(lv_.view()->buffer()->params, tmp);
-#ifdef USE_PSPELL
- }
+
+#if defined(USE_ISPELL)
+ lang = (lyxrc.isp_use_alt_lang) ?
+ lyxrc.isp_alt_lang : bp.language->lang();
+
+ return new ISpell(bp, lang);
+#else
+ return new SpellBase;
#endif
-
- if (lyxrc.isp_use_alt_lang) {
- Language const * lang = languages.getLanguage(tmp);
- if (lang)
- rtl_ = lang->RightToLeft();
- } else {
- rtl_ = lv_.buffer()->params.language->RightToLeft();
- }
-
- if (speller_->error() != 0) {
- message_ = speller_->error();
- // show error message
- view().partialUpdate(2);
- clearParams();
- return;
+}
+
+} // namespace anon
+
+
+bool ControlSpellchecker::initialiseParams(std::string const &)
+{
+ lyxerr[Debug::GUI] << "Spellchecker::initialiseParams" << endl;
+
+ speller_.reset(getSpeller(kernel().buffer().params()));
+ if (!speller_.get())
+ return false;
+
+ // reset values to initial
+ oldval_ = 0;
+ newvalue_ = 0;
+ count_ = 0;
+
+ bool const success = speller_->error().empty();
+
+ if (!success) {
+ Alert::error(_("Spellchecker error"),
+ _("The spellchecker could not be started\n")
+ + speller_->error());
+ speller_.reset(0);
+ }
+
+ return success;
+}
+
+
+void ControlSpellchecker::clearParams()
+{
+ lyxerr[Debug::GUI] << "Spellchecker::clearParams" << endl;
+ speller_.reset(0);
+}
+
+
+namespace {
+
+bool isLetter(DocIterator const & cur)
+{
+ return cur.inTexted()
+ && cur.inset().allowSpellCheck()
+ && cur.pos() != cur.lastpos()
+ && (cur.paragraph().isLetter(cur.pos())
+ // We want to pass the ' and escape chars to ispell
+ || contains(lyxrc.isp_esc_chars + '\'',
+ cur.paragraph().getChar(cur.pos())))
+ && !isDeletedText(cur.paragraph(), cur.pos());
+}
+
+
+WordLangTuple nextWord(DocIterator & cur, ptrdiff_t & progress,
+ BufferParams & bp)
+{
+ bool inword = false;
+ bool ignoreword = false;
+ string word, lang_code;
+
+ while (cur.depth()) {
+ if (isLetter(cur)) {
+ if (!inword) {
+ inword = true;
+ ignoreword = false;
+ word.clear();
+ lang_code = cur.paragraph().getFontSettings(bp, cur.pos()).language()->code();
+ }
+ // Insets like optional hyphens and ligature
+ // break are part of a word.
+ if (!cur.paragraph().isInset(cur.pos())) {
+ Paragraph::value_type const c =
+ cur.paragraph().getChar(cur.pos());
+ word += c;
+ if (IsDigit(c))
+ ignoreword = true;
+ }
+ } else { // !isLetter(cur)
+ if (inword)
+ if (!word.empty() && !ignoreword)
+ return WordLangTuple(word, lang_code);
+ else
+ inword = false;
}
+
+ cur.forwardPos();
+ ++progress;
}
+
+ return WordLangTuple(string(), string());
}
+} // namespace anon
+
+
void ControlSpellchecker::check()
{
- result_ = SpellBase::ISP_UNKNOWN;
- stop_ = false;
-
- while (result_!=SpellBase::ISP_MISSED && !stop_) {
- word_ = lv_.view()->nextWord(newval_);
-
- if (word_.empty()) {
- clearParams();
- break;
+ lyxerr[Debug::GUI] << "Check the spelling of a word" << endl;
+
+ SpellBase::Result res = SpellBase::OK;
+
+ DocIterator cur = kernel().bufferview()->cursor();
+ while (cur && cur.pos() && isLetter(cur)) {
+ cur.backwardPos();
+ }
+
+ ptrdiff_t start = 0, total = 0;
+ DocIterator it = DocIterator(kernel().buffer().inset());
+ for (start = 0; it != cur; it.forwardPos())
+ ++start;
+
+ for (total = start; it; it.forwardPos())
+ ++total;
+
+ BufferParams & bufferparams = kernel().buffer().params();
+ exitEarly_ = false;
+
+ while (res == SpellBase::OK || res == SpellBase::IGNORED_WORD) {
+ word_ = nextWord(cur, start, bufferparams);
+
+ // end of document
+ if (getWord().empty()) {
+ showSummary();
+ exitEarly_ = true;
+ return;
}
-
+
++count_;
// Update slider if and only if value has changed
- newvalue_ = int(100.0*newval_);
+ float progress = total ? float(start)/total : 1;
+ newvalue_ = int(100.0 * progress);
if (newvalue_!= oldval_) {
+ lyxerr[Debug::GUI] << "Updating spell progress." << endl;
oldval_ = newvalue_;
// set progress bar
- view().partialUpdate(0);
+ dialog().view().partialUpdate(SPELL_PROGRESSED);
}
-
- if (!speller_->alive()) clearParams();
-
- result_ = speller_->check(word_);
+
+ // speller might be dead ...
+ if (!checkAlive())
+ return;
+
+ res = speller_->check(word_);
+
+ // ... or it might just be reporting an error
+ if (!checkAlive())
+ return;
}
-
- if (!stop_ && !word_.empty())
- lv_.view()->selectLastWord();
+
+ lyxerr[Debug::GUI] << "Found word \"" << getWord() << "\"" << endl;
+
+ int const size = getWord().size();
+ cur.pos() -= size;
+ kernel().bufferview()->putSelectionAt(cur, size, false);
+ // if we used a lfun like in find/replace, dispatch would do
+ // that for us
+ kernel().bufferview()->update();
// set suggestions
- if (result_==SpellBase::ISP_MISSED) {
- view().partialUpdate(1);
+ if (res != SpellBase::OK && res != SpellBase::IGNORED_WORD) {
+ lyxerr[Debug::GUI] << "Found a word needing checking." << endl;
+ dialog().view().partialUpdate(SPELL_FOUND_WORD);
+ }
+}
+
+
+bool ControlSpellchecker::checkAlive()
+{
+ if (speller_->alive() && speller_->error().empty())
+ return true;
+
+ string message;
+ if (speller_->error().empty())
+ message = _("The spellchecker has died for some reason.\n"
+ "Maybe it has been killed.");
+ else
+ message = _("The spellchecker has failed.\n")
+ + speller_->error();
+
+ dialog().CancelButton();
+
+ Alert::error(_("The spellchecker has failed"), message);
+ return false;
+}
+
+
+void ControlSpellchecker::showSummary()
+{
+ if (!checkAlive() || count_ == 0) {
+ dialog().CancelButton();
+ return;
}
+
+ string message;
+ if (count_ != 1)
+ message = bformat(_("%1$d words checked."), count_);
+ else
+ message = _("One word checked.");
+
+ dialog().CancelButton();
+ Alert::information(_("Spelling check completed"), message);
}
void ControlSpellchecker::replace(string const & replacement)
{
- lv_.view()->replaceWord(replacement);
+ lyxerr << "ControlSpellchecker::replace("
+ << replacement << ")" << std::endl;
+ BufferView & bufferview = *kernel().bufferview();
+ cap::replaceWord(bufferview.cursor(), replacement);
+ kernel().buffer().markDirty();
+ bufferview.update();
+ // fix up the count
+ --count_;
check();
}
}
-string ControlSpellchecker::getSuggestion()
+string const ControlSpellchecker::getSuggestion() const
{
- // this is needed because string tmp = nextmiss()
- // segfaults when nextMiss is 0
- string tmp;
- char const * w = speller_->nextMiss();
-
- if (w!=0) {
- tmp = w;
- if (rtl_) std::reverse(tmp.begin(), tmp.end());
- }
-
- return tmp;
+ return speller_->nextMiss();
}
-string ControlSpellchecker::getWord()
+string const ControlSpellchecker::getWord() const
{
- string tmp = word_;
- if (rtl_) std::reverse(tmp.begin(), tmp.end());
- return tmp;
+ return word_.word();
}
check();
}
-
-void ControlSpellchecker::stop()
-{
- stop_ = true;
- lv_.view()->endOfSpellCheck();
-}
-
-
-void ControlSpellchecker::clearParams()
-{
- if (!speller_) return;
-
- if (speller_->alive()) {
- speller_->close();
- message_ = tostr(count_);
- if (count_ != 1) {
- message_ += _(" words checked.");
-
- } else {
- message_ += _(" word checked.");
- }
- message_ = "\n" + message_;
- message_ = _("Spellchecking completed! ") + message_;
-
- } else {
- speller_->cleanUp();
- message_ = _("The spell checker has died for some reason.\n"
- "Maybe it has been killed.");
-
- // make sure that the dialog is not launched
- emergency_exit_ = true;
- }
-
- delete speller_;
-
- lv_.view()->endOfSpellCheck();
-
- // show closing message
- view().partialUpdate(2);
-
- // reset values to initial
- rtl_ = false;
- word_ = "";
- newval_ = 0.0;
- oldval_ = 0;
- newvalue_ = 0;
- count_ = 0;
- message_ = "";
- stop_ = false;
- result_ = SpellBase::ISP_UNKNOWN;
- speller_ = 0;
-}
-
-
-void ControlSpellchecker::options()
-{
- lv_.getDialogs()->showSpellcheckerPreferences();
-}
+} // namespace frontend
+} // namespace lyx