]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/controllers/ControlSpellchecker.C
fix crash due to invalidated iterator
[lyx.git] / src / frontends / controllers / ControlSpellchecker.C
index aab4862b9bd674ec85b36c522ae0a33b4be743ba..7e0c88d281d247a926c71ca8c48e638e65ac2387 100644 (file)
-/* 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();
 }
 
@@ -161,27 +321,15 @@ void ControlSpellchecker::insert()
 }
 
 
-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();
 }
 
 
@@ -191,61 +339,5 @@ void ControlSpellchecker::ignoreAll()
        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.");
-       }
-       
-       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;
-
-       // make sure that the dialog is not launched
-       emergency_exit_ = true;
-}
-
-
-void ControlSpellchecker::options()
-{
-       lv_.getDialogs()->showSpellcheckerPreferences();
-}
+} // namespace frontend
+} // namespace lyx