Here is my "final" patch. Less code, more features ! Please test.
There is one niggle left: we briefly show the dialog when there is no need (clean spell check). It's minorly visually distracting. I think it's worth it. Maybe Angus has a smart minimal fix for it. Kornel, you should be seeing your problem in a nice dialog now. I am right in assuming that you had the problem before this patch too ? There's nowhere in this patch that could have changed the behaviour you're seeing. JMarc, if this gets a good run for its money, would you consider it ? Bugs fixed (from the top of my head) : o 15-second hang on bad ispell startup o permanent hang on bad ispell word check o actual detection of ispell death o pass ispell errors back to the user o update xforms progress bar more often in clean document o remove bad UI of separate "Spell" stage o correctly save out personal word lists o correctly clean up ispell instances o use "Ignore All" not "Accept" - it's far clearer o fix broken Stop/Start in xforms dialog o fix slightly broken button enable/disable regards john Index: SpellBase.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/SpellBase.h,v retrieving revision 1.4 diff -u -r1.4 SpellBase.h --- SpellBase.h 1 Dec 2002 22:59:17 -0000 1.4 +++ SpellBase.h 17 Feb 2003 00:54:02 -0000 @@ -36,14 +36,8 @@ /// return true if the spellchecker instance still exists virtual bool alive() = 0; - /// clean up on messy exit - virtual void cleanUp() = 0; - /// check the given word of the given lang code and return the result virtual enum Result check(WordLangTuple const &) = 0; - - /// finish this spellchecker instance - virtual void close() = 0; /// insert the given word into the personal dictionary virtual void insert(WordLangTuple const &) = 0; Index: ispell.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/ispell.C,v retrieving revision 1.6 diff -u -r1.6 ispell.C --- ispell.C 13 Feb 2003 16:52:28 -0000 1.6 +++ ispell.C 17 Feb 2003 00:54:02 -0000 @@ -9,35 +9,6 @@ #include <config.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <unistd.h> -#include <fcntl.h> -#include <cstdio> - -// FIXME: do we need any of this horrible gook ? -#if TIME_WITH_SYS_TIME -# include <sys/time.h> -# include <ctime> -#else -# if HAVE_SYS_TIME_H -# include <sys/time.h> -# else -# include <ctime> -# endif -#endif - -#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 "LString.h" #include "lyxrc.h" #include "language.h" @@ -45,10 +16,13 @@ #include "encoding.h" #include "ispell.h" #include "WordLangTuple.h" +#include "gettext.h" #include "support/forkedcall.h" #include "support/lstrings.h" +#include <sys/select.h> + #ifndef CXX_GLOBAL_CSTD using std::strcpy; using std::strlen; @@ -57,18 +31,16 @@ #endif using std::endl; +using std::max; namespace { -/// pid for the `ispell' process. -pid_t isp_pid = -1; - class LaunchIspell : public ForkedProcess { public: /// LaunchIspell(BufferParams const & p, string const & l, - int * in, int * out) - : params(p), lang(l), pipein(in), pipeout(out) {} + int * in, int * out, int * err) + : params(p), lang(l), pipein(in), pipeout(out), pipeerr(err) {} /// virtual ForkedProcess * clone() const { return new LaunchIspell(*this); @@ -84,6 +56,7 @@ string const & lang; int * const pipein; int * const pipeout; + int * const pipeerr; }; @@ -96,7 +69,7 @@ int LaunchIspell::generateChild() { - isp_pid = fork(); + pid_t isp_pid = fork(); if (isp_pid != 0) { // failed (-1) or parent process (>0) @@ -106,10 +79,13 @@ // child process dup2(pipein[0], STDIN_FILENO); dup2(pipeout[1], STDOUT_FILENO); - ::close(pipein[0]); - ::close(pipein[1]); - ::close(pipeout[0]); - ::close(pipeout[1]); + dup2(pipeerr[1], STDERR_FILENO); + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); char * argv[14]; int argc = 0; @@ -184,6 +160,12 @@ argv[argc++] = 0; + lyxerr[Debug::GUI] << "spell invocation was "; + for (int i = 0; i < argc - 1; ++i) { + lyxerr[Debug::GUI] << argv[i] << " "; + } + lyxerr[Debug::GUI] << endl; + execvp(argv[0], const_cast<char * const *>(argv)); // free the memory used by string::copy in the @@ -200,122 +182,161 @@ ISpell::ISpell(BufferParams const & params, string const & lang) - : str(0) + : in(0), out(0), inerr(0), str(0) { - static char o_buf[BUFSIZ]; // jc: it could be smaller - int pipein[2]; - int pipeout[2]; + lyxerr[Debug::GUI] << "Created ispell" << endl; + + // static due to the setvbuf. Ugly. + static char o_buf[BUFSIZ]; + + // We need to throw an exception not do this + pipein[0] = pipein[1] = pipeout[0] = pipeout[1] + = pipeerr[0] = pipeerr[1] = -1; + + // This is what happens when goto gets banned. - isp_pid = -1; + if (pipe(pipein) == -1) { + error_ = _("Can't create pipe for spellchecker."); + return; + } - if (pipe(pipein) == -1 || pipe(pipeout) == -1) { - lyxerr << "LyX: Can't create pipe for spellchecker!" << endl; - setError(); + if (pipe(pipeout) == -1) { + close(pipein[0]); + close(pipein[1]); + error_ = _("Can't create pipe for spellchecker."); + return; + } + + if (pipe(pipeerr) == -1) { + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + error_ = _("Can't create pipe for spellchecker."); return; } if ((out = fdopen(pipein[1], "w")) == 0) { - lyxerr << "LyX: Can't create stream for pipe for spellchecker!" - << endl; - setError(); + error_ = _("Can't open pipe for spellchecker."); return; } if ((in = fdopen(pipeout[0], "r")) == 0) { - lyxerr <<"LyX: Can't create stream for pipe for spellchecker!" - << endl; - setError(); + error_ = _("Can't open pipe for spellchecker."); return; } - setvbuf(out, o_buf, _IOLBF, BUFSIZ); + if ((inerr = fdopen(pipeerr[0], "r")) == 0) { + error_ = _("Can't open pipe for spellchecker."); + return; + } - isp_fd = pipeout[0]; + setvbuf(out, o_buf, _IOLBF, BUFSIZ); - LaunchIspell childprocess(params, lang, pipein, pipeout); - isp_pid = childprocess.start(); - if (isp_pid == -1) { - lyxerr << "LyX: Can't create child process for spellchecker!" - << endl; - setError(); + LaunchIspell * li = new LaunchIspell(params, lang, pipein, pipeout, pipeerr); + child_.reset(li); + if (li->start() == -1) { + error_ = _("Could not create an ispell process.\nYou may not have " + " the right languages installed."); + child_.reset(0); return; } - setError(); /* Parent process: Read ispells identification message */ - // Hmm...what are we using this id msg for? Nothing? (Lgb) - // Actually I used it to tell if it's truly Ispell or if it's - // aspell -- ([EMAIL PROTECTED]) - // But no code actually used the results for anything useful - // so I removed it again. Perhaps we can remove this code too. - // - jbl - char buf[2048]; - fd_set infds; - struct timeval tv; - int retval = 0; - FD_ZERO(&infds); - FD_SET(pipeout[0], &infds); - tv.tv_sec = 15; // fifteen second timeout. Probably too much, - // but it can't really hurt. - tv.tv_usec = 0; - // Configure provides us with macros which are supposed to do - // the right typecast. - retval = select(SELECT_TYPE_ARG1 (pipeout[0]+1), - SELECT_TYPE_ARG234 (&infds), - 0, - 0, - SELECT_TYPE_ARG5 (&tv)); + bool err_read; + bool error = select(err_read); + + if (!error) { + if (!err_read) { + // Set terse mode (silently accept correct words) + fputs("!\n", out); + return; + } - if (retval > 0) { - // Ok, do the reading. We don't have to FD_ISSET since - // there is only one fd in infds. - fgets(buf, 2048, in); - - fputs("!\n", out); // Set terse mode (silently accept correct words) - - } else if (retval == 0) { - // timeout. Give nice message to user. - lyxerr << "Ispell read timed out, what now?" << endl; - // This probably works but could need some thought - isp_pid = -1; - ::close(pipeout[0]); - ::close(pipeout[1]); - ::close(pipein[0]); - ::close(pipein[1]); - isp_fd = -1; + /* must have read something from stderr */ + error_ = buf; } else { - // Select returned error - lyxerr << "Select on ispell returned error, what now?" << endl; + // select returned error + error_ = _("The spell process returned an error.\nPerhaps " + "it has been configured wrongly ?"); } + + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); + child_->kill(); + child_.reset(0); } ISpell::~ISpell() { - delete[] str; + lyxerr[Debug::GUI] << "Killing ispell" << endl; + + if (in) + fclose(in); + + if (inerr) + fclose(inerr); + + if (out) { + fputs("#\n", out); // Save personal dictionary + + fflush(out); + fclose(out); + } + + close(pipein[0]); + close(pipein[1]); + close(pipeout[0]); + close(pipeout[1]); + close(pipeerr[0]); + close(pipeerr[1]); + delete [] str; } -void ISpell::setError() +bool ISpell::select(bool & err_read) { - if (isp_pid == -1) { - error_ = - "\n\n" - "The spellcheck-process has died for some reason.\n" - "*One* possible reason could be that you do not have\n" - "a dictionary file for the language of this document\n" - "installed.\n" - "Check your spellchecker or set another dictionary\n" - "in the Spellchecker Options menu.\n\n"; - } else { - error_ = 0; + fd_set infds; + struct timeval tv; + int retval = 0; + FD_ZERO(&infds); + FD_SET(pipeout[0], &infds); + FD_SET(pipeerr[0], &infds); + tv.tv_sec = 2; + tv.tv_usec = 0; + + retval = ::select(SELECT_TYPE_ARG1 (max(pipeout[0], pipeerr[0]) + 1), + SELECT_TYPE_ARG234 (&infds), + 0, + 0, + SELECT_TYPE_ARG5 (&tv)); + + // error + if (retval <= 0) + return true; + + if (FD_ISSET(pipeerr[0], &infds)) { + fgets(buf, BUFSIZ, inerr); + err_read = true; + return false; } + + fgets(buf, BUFSIZ, in); + err_read = false; + return false; } string const ISpell::nextMiss() { + // Well, somebody is a sick fuck. + if (str == 0 || *(e+1) == '\0') return ""; char * b = e + 2; @@ -329,13 +350,7 @@ bool ISpell::alive() { - return isp_pid != -1; -} - - -void ISpell::cleanUp() -{ - ::fclose(out); + return child_.get() && child_->running(); } @@ -348,8 +363,18 @@ ::fputs(word.word().c_str(), out); ::fputc('\n', out); - char buf[1024]; - ::fgets(buf, 1024, in); + bool err_read; + bool error = select(err_read); + + if (error) { + error_ = _("Could not communicate with the spell-checker program"); + return UNKNOWN; + } + + if (err_read) { + error_ = buf; + return UNKNOWN; + } // I think we have to check if ispell is still alive here because // the signal-handler could have disabled blocking on the fd @@ -396,20 +421,6 @@ } -void ISpell::close() -{ - // Note: If you decide to optimize this out when it is not - // needed please note that when Aspell is used this command - // is also needed to save the replacement dictionary. - // -- Kevin Atkinson ([EMAIL PROTECTED]) - - fputs("#\n", out); // Save personal dictionary - - fflush(out); - fclose(out); -} - - void ISpell::insert(WordLangTuple const & word) { ::fputc('*', out); // Insert word in personal dictionary @@ -428,7 +439,5 @@ string const ISpell::error() { - if (error_) - return error_; - return ""; + return error_; } Index: ispell.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/ispell.h,v retrieving revision 1.5 diff -u -r1.5 ispell.h --- ispell.h 1 Dec 2002 22:59:17 -0000 1.5 +++ ispell.h 17 Feb 2003 00:54:02 -0000 @@ -10,11 +10,14 @@ #ifndef SP_ISPELL_H #define SP_ISPELL_H -#include <cstdio> - #include "SpellBase.h" +#include <boost/scoped_ptr.hpp> + +#include <cstdio> + class BufferParams; +class ForkedProcess; /// i/a spell process-based spellchecker class ISpell : public SpellBase { @@ -26,15 +29,9 @@ /// return true if the spellchecker instance still exists virtual bool alive(); - /// clean up on messy exit - virtual void cleanUp(); - /// check the given word and return the result virtual enum Result check(WordLangTuple const & word); - /// finish this spellchecker instance - virtual void close(); - /// insert the given word into the personal dictionary virtual void insert(WordLangTuple const & word); @@ -48,17 +45,29 @@ virtual string const error(); private: - /// - void setError(); + /// read some data. Returns true on an error. Sets err_read + /// to true if the data was from stderr. + bool select(bool & err_read); /// instream to communicate with ispell FILE * in; /// outstream to communicate with ispell FILE * out; + /// errstream for ispell + FILE * inerr; + + /// pipe fds + int pipein[2]; + int pipeout[2]; + int pipeerr[2]; + + /// buffer for reading + char buf[BUFSIZ]; + /// spell error - char const * error_; - /// the fd of the outgoing pipe - int isp_fd; + string error_; + + boost::scoped_ptr<ForkedProcess> child_; // vileness below ... please FIXME /// str ??? Index: pspell.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/pspell.C,v retrieving revision 1.8 diff -u -r1.8 pspell.C --- pspell.C 13 Feb 2003 16:52:40 -0000 1.8 +++ pspell.C 17 Feb 2003 00:54:03 -0000 @@ -12,6 +12,7 @@ #ifdef USE_PSPELL #include "support/LAssert.h" +#include "debug.h" #define USE_ORIGINAL_MANAGER_FUNCS 1 // new aspell pspell missing extern "C" @@ -22,38 +23,39 @@ #include "pspell.h" #include "WordLangTuple.h" +using std::endl; + PSpell::PSpell(BufferParams const &, string const & lang) : els(0), spell_error_object(0) { addManager(lang); + lyxerr[Debug::GUI] << "created pspell" << endl; } PSpell::~PSpell() { - cleanUp(); - close(); + lyxerr[Debug::GUI] << "killed pspell" << endl; + + if (spell_error_object) { + delete_pspell_can_have_error(spell_error_object); + spell_error_object = 0; + } + if (els) delete_pspell_string_emulation(els); + Managers::iterator it = managers_.begin(); Managers::iterator end = managers_.end(); for (; it != end; ++it) { + pspell_manager_save_all_word_lists(it->second.manager); delete_pspell_manager(it->second.manager); delete_pspell_config(it->second.config); } } -void PSpell::cleanUp() -{ - if (spell_error_object) { - delete_pspell_can_have_error(spell_error_object); - spell_error_object = 0; - } -} - - void PSpell::addManager(string const & lang) { PspellConfig * config = new_pspell_config(); @@ -105,17 +107,6 @@ res = MISSED; } return res; -} - - -void PSpell::close() -{ - Managers::iterator it = managers_.begin(); - Managers::iterator end = managers_.end(); - - for (; it != end; ++it) { - pspell_manager_save_all_word_lists(it->second.manager); - } } Index: pspell.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/pspell.h,v retrieving revision 1.3 diff -u -r1.3 pspell.h --- pspell.h 1 Dec 2002 22:59:17 -0000 1.3 +++ pspell.h 17 Feb 2003 00:54:03 -0000 @@ -37,14 +37,8 @@ */ virtual bool alive() { return true; } - /// clean up on messy exit - virtual void cleanUp(); - /// check the given word and return the result virtual enum Result check(WordLangTuple const &); - - /// finish this spellchecker instance - virtual void close(); /// insert the given word into the personal dictionary virtual void insert(WordLangTuple const &); Index: frontends/controllers/ControlSpellchecker.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/controllers/ControlSpellchecker.C,v retrieving revision 1.37 diff -u -r1.37 ControlSpellchecker.C --- frontends/controllers/ControlSpellchecker.C 13 Feb 2003 16:52:49 -0000 1.37 +++ frontends/controllers/ControlSpellchecker.C 17 Feb 2003 00:54:07 -0000 @@ -19,6 +19,7 @@ #include "language.h" #include "lyxrc.h" #include "lyxtext.h" +#include "debug.h" #include "ispell.h" #ifdef USE_PSPELL @@ -29,17 +30,41 @@ #include "BoostFormat.h" +using std::endl; + ControlSpellchecker::ControlSpellchecker(LyXView & lv, Dialogs & d) : ControlDialogBD(lv, d), - newval_(0.0), oldval_(0), newvalue_(0), count_(0), - stop_(false), speller_(0) + newval_(0.0), oldval_(0), newvalue_(0), count_(0) +{} + + +ControlSpellchecker::~ControlSpellchecker() {} void ControlSpellchecker::setParams() { - if (speller_) + lyxerr[Debug::GUI] << "spell setParams" << endl; + startSession(); +} + + +void ControlSpellchecker::clearParams() +{ + lyxerr[Debug::GUI] << "spell clearParams" << endl; + endSession(); +} + + +void ControlSpellchecker::startSession() +{ + lyxerr[Debug::GUI] << "spell startSession" << endl; + + if (speller_.get()) { + lyxerr[Debug::GUI] << "startSession: speller exists" << endl; + speller_.reset(0); return; + } // create spell object string tmp; @@ -48,70 +73,156 @@ tmp = (lyxrc.isp_use_alt_lang) ? lyxrc.isp_alt_lang : buffer()->params.language->code(); - speller_ = new PSpell(buffer()->params, tmp); + speller_.reset(new PSpell(buffer()->params, tmp)); } else { #endif tmp = (lyxrc.isp_use_alt_lang) ? lyxrc.isp_alt_lang : buffer()->params.language->lang(); - speller_ = new ISpell(buffer()->params, tmp); + speller_.reset(new ISpell(buffer()->params, tmp)); #ifdef USE_PSPELL } #endif - if (!speller_->error().empty()) { - emergency_exit_ = true; - Alert::alert("The spellchecker has failed", speller_->error()); - clearParams(); + // reset values to initial + newval_ = 0.0; + oldval_ = 0; + newvalue_ = 0; + count_ = 0; + emergency_exit_ = false; + + if (speller_->error().empty()) + return; + + emergency_exit_ = true; + string message = speller_->error(); + if (message.empty()) + message = _("The spell-checker could not be started.\n" + "Maybe it is mis-configured."); + + Alert::alert(_("The spell-checker has failed"), message); + speller_.reset(0); +} + + +void ControlSpellchecker::endSession() +{ + lyxerr[Debug::GUI] << "spell endSession" << endl; + + bufferview()->endOfSpellCheck(); + + if (!speller_.get()) { + lyxerr[Debug::GUI] << "endSession with no speller" << endl; return; } + + speller_.reset(0); } void ControlSpellchecker::check() { + lyxerr[Debug::GUI] << "spell check a word" << endl; + SpellBase::Result res = SpellBase::OK; - stop_ = false; // clear any old selection LyXText * text = bufferview()->getLyXText(); bufferview()->toggleSelection(true); bufferview()->update(text, BufferView::SELECT); - while ((res == SpellBase::OK || res == SpellBase::IGNORE) && !stop_) { + while ((res == SpellBase::OK || res == SpellBase::IGNORE)) { word_ = bufferview()->nextWord(newval_); - if (word_.word().empty()) { - clearParams(); + // end of document + if (word_.word().empty()) break; - } ++count_; // Update slider if and only if value has changed newvalue_ = int(100.0 * newval_); if (newvalue_!= oldval_) { + lyxerr[Debug::GUI] << "Updating spell progress." << endl; oldval_ = newvalue_; // set progress bar - view().partialUpdate(0); + view().partialUpdate(SPELL_PROGRESSED); } - if (!speller_ || !speller_->alive()) { - clearParams(); - stop(); + // 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_.word().empty()) + lyxerr[Debug::GUI] << "Found word \"" << word_.word() << "\"" << endl; + + if (!word_.word().empty()) { bufferview()->selectLastWord(); + } else { + showSummary(); + endSession(); + return; + } // set suggestions if (res != SpellBase::OK && res != SpellBase::IGNORE) { - view().partialUpdate(1); + lyxerr[Debug::GUI] << "Found a word needing checking." << endl; + view().partialUpdate(SPELL_FOUND_WORD); + } +} + + +bool ControlSpellchecker::checkAlive() +{ + if (speller_->alive() && speller_->error().empty()) + return true; + + string message = speller_->error(); + if (message.empty()) + message = _("The spell-checker has died for some reason.\n" + "Maybe it has been killed."); + + view().hide(); + speller_.reset(0); + + Alert::alert(_("The spell-checker has failed"), message); + return false; +} + + +void ControlSpellchecker::showSummary() +{ + if (!checkAlive() || count_ == 0) { + view().hide(); + return; + } + + string message; + +#if USE_BOOST_FORMAT + if (count_ != 1) { + boost::format fmter("%1$d words checked."); + fmter % count_; + message += fmter.str(); + } else { + message += _("One word checked."); } +#else + if (count_ != 1) { + message += tostr(count_) + " words checked"; + } else { + message = _("One word checked."); + } +#endif + + view().hide(); + Alert::alert(_("Spell-checking is complete"), message); } @@ -157,64 +268,3 @@ } -void ControlSpellchecker::stop() -{ - stop_ = true; - bufferview()->endOfSpellCheck(); -} - - -void ControlSpellchecker::clearParams() -{ - if (!speller_) - return; - - if (speller_->alive()) { - speller_->close(); - - message_ = string(_("Spellchecking completed!")) + '\n'; - -#if USE_BOOST_FORMAT - if (count_ != 1) { - boost::format fmter("%1$d words checked."); - fmter % count_; - message_ += fmter.str(); - } else { - message_ += _("One word checked."); - } -#else - if (count_ != 1) { - message_ += tostr(count_) + " words checked"; - } else { - message_ = _("One word checked."); - } -#endif - } else { - message_ = speller_->error(); - speller_->cleanUp(); - if (message_.empty()) - 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; - Alert::alert("The spellchecker has failed", message_); - } - - delete speller_; - - bufferview()->endOfSpellCheck(); - - // show closing message if any words were checked. - if (count_ > 0) - view().partialUpdate(2); - - // reset values to initial - newval_ = 0.0; - oldval_ = 0; - newvalue_ = 0; - count_ = 0; - message_.erase(); - stop_ = false; - speller_ = 0; -} Index: frontends/controllers/ControlSpellchecker.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/controllers/ControlSpellchecker.h,v retrieving revision 1.15 diff -u -r1.15 ControlSpellchecker.h --- frontends/controllers/ControlSpellchecker.h 13 Feb 2003 16:52:49 -0000 1.15 +++ frontends/controllers/ControlSpellchecker.h 17 Feb 2003 00:54:08 -0000 @@ -12,6 +12,7 @@ #ifndef CONTROLSPELLCHECKER_H #define CONTROLSPELLCHECKER_H +#include <boost/scoped_ptr.hpp> #include "ControlDialog_impl.h" #include "LString.h" @@ -23,9 +24,15 @@ */ class ControlSpellchecker : public ControlDialogBD { public: - /// + enum State { + SPELL_PROGRESSED, //< update progress bar + SPELL_FOUND_WORD //< found a bad word + }; + ControlSpellchecker(LyXView &, Dialogs &); + ~ControlSpellchecker(); + /// replace word with replacement void replace(string const &); @@ -38,10 +45,8 @@ /// ignore all occurances of word void ignoreAll(); - /// stop checking - void stop(); - /// check text until next misspelled/unknown word + /// returns true when finished void check(); /// get suggestion @@ -53,13 +58,22 @@ /// returns progress value int getProgress() const { return oldval_; } - /// returns exit message - string const getMessage() const { return message_; } - /// returns word count int getCount() const { return count_; } private: + /// give error message is spellchecker dies + bool checkAlive(); + + /// start a spell-checking session + void startSession(); + + /// end a spell-checking session + void endSession(); + + /// show count of checked words at normal exit + void showSummary(); + /// set the params before show or update void setParams(); /// clean-up on hide. @@ -79,14 +93,8 @@ /// word count int count_; - /// exit message - string message_; - - /// set to true to stop checking - bool stop_; - /// The actual spellchecker object - SpellBase * speller_; + boost::scoped_ptr<SpellBase> speller_; }; #endif // CONTROLSPELLCHECKER_H Index: frontends/qt2/QSpellchecker.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/QSpellchecker.C,v retrieving revision 1.19 diff -u -r1.19 QSpellchecker.C --- frontends/qt2/QSpellchecker.C 16 Feb 2003 05:08:14 -0000 1.19 +++ frontends/qt2/QSpellchecker.C 17 Feb 2003 00:54:08 -0000 @@ -49,13 +49,14 @@ dialog_->replaceCO->clear(); dialog_->suggestionsLB->clear(); dialog_->spellcheckPR->setProgress(0); - dialog_->spellcheckPB->setEnabled(true); - dialog_->wordED->setEnabled(false); - dialog_->replaceCO->setEnabled(false); - dialog_->replacePB->setEnabled(false); - dialog_->ignorePB->setEnabled(false); - dialog_->replacePB_3->setEnabled(false); - dialog_->addPB->setEnabled(false); +} + + +// FIXME can put in controller ?? +void QSpellchecker::show() +{ + Qt2Base::show(); + controller().check(); } @@ -83,35 +84,18 @@ } -void QSpellchecker::spellcheck() +void QSpellchecker::partialUpdate(int s) { - dialog_->spellcheckPB->setEnabled(false); - dialog_->wordED->setEnabled(true); - dialog_->replaceCO->setEnabled(true); - dialog_->replacePB->setEnabled(true); - dialog_->ignorePB->setEnabled(true); - dialog_->replacePB_3->setEnabled(true); - dialog_->addPB->setEnabled(true); - controller().check(); -} + ControlSpellchecker::State const state = + static_cast<ControlSpellchecker::State>(s); + switch (state) { -void QSpellchecker::stop() -{ - controller().stop(); - dialog_->spellcheckPB->setEnabled(true); - hide(); -} - - -void QSpellchecker::partialUpdate(int id) -{ - switch (id) { - case 0: + case ControlSpellchecker::SPELL_PROGRESSED: dialog_->spellcheckPR->setProgress(controller().getProgress()); break; - case 1: { + case ControlSpellchecker::SPELL_FOUND_WORD: { dialog_->wordED->setText(toqstr(controller().getWord())); dialog_->suggestionsLB->clear(); @@ -128,12 +112,5 @@ } break; - case 2: - dialog_->spellcheckPB->setEnabled(true); - hide(); - QMessageBox::information(0, qt_("Spellcheck complete"), - toqstr(controller().getMessage()), - qt_("OK")); - break; } } Index: frontends/qt2/QSpellchecker.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/QSpellchecker.h,v retrieving revision 1.8 diff -u -r1.8 QSpellchecker.h --- frontends/qt2/QSpellchecker.h 13 Feb 2003 16:52:53 -0000 1.8 +++ frontends/qt2/QSpellchecker.h 17 Feb 2003 00:54:08 -0000 @@ -31,12 +31,13 @@ /// update from controller void partialUpdate(int id); private: - void stop(); void accept(); void add(); void ignore(); void replace(); - void spellcheck(); + + /// Show + virtual void show(); /// Apply changes virtual void apply() {} Index: frontends/qt2/QSpellcheckerDialog.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/QSpellcheckerDialog.C,v retrieving revision 1.10 diff -u -r1.10 QSpellcheckerDialog.C --- frontends/qt2/QSpellcheckerDialog.C 13 Feb 2003 16:52:53 -0000 1.10 +++ frontends/qt2/QSpellcheckerDialog.C 17 Feb 2003 00:54:08 -0000 @@ -26,13 +26,7 @@ form_(form) { connect(closePB, SIGNAL(clicked()), - this, SLOT(stop())); -} - - -void QSpellcheckerDialog::stop() -{ - form_->stop(); + form, SLOT(slotClose())); } @@ -42,12 +36,6 @@ } -void QSpellcheckerDialog::spellcheckClicked() -{ - form_->spellcheck(); -} - - void QSpellcheckerDialog::addClicked() { form_->add(); @@ -95,7 +83,6 @@ void QSpellcheckerDialog::closeEvent(QCloseEvent * e) { - form_->stop(); form_->slotWMHide(); e->accept(); } Index: frontends/qt2/QSpellcheckerDialog.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/QSpellcheckerDialog.h,v retrieving revision 1.10 diff -u -r1.10 QSpellcheckerDialog.h --- frontends/qt2/QSpellcheckerDialog.h 13 Feb 2003 16:52:53 -0000 1.10 +++ frontends/qt2/QSpellcheckerDialog.h 17 Feb 2003 00:54:08 -0000 @@ -25,9 +25,7 @@ virtual void suggestionChanged(const QString &); protected slots: - virtual void stop(); virtual void acceptClicked(); - virtual void spellcheckClicked(); virtual void addClicked(); virtual void replaceClicked(); virtual void ignoreClicked(); Index: frontends/qt2/README =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/README,v retrieving revision 1.15 diff -u -r1.15 README --- frontends/qt2/README 15 Feb 2003 21:09:29 -0000 1.15 +++ frontends/qt2/README 17 Feb 2003 00:54:08 -0000 @@ -22,6 +22,8 @@ Remember to check sensible resizing behaviour on a dialog. +Remember to use Edit->Check Accelerators + If necessary, you should override Qt2Base::isValid() for determining the validity of the current dialog's contents. Index: frontends/qt2/ui/QSpellcheckerDialogBase.ui =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/qt2/ui/QSpellcheckerDialogBase.ui,v retrieving revision 1.3 diff -u -r1.3 QSpellcheckerDialogBase.ui --- frontends/qt2/ui/QSpellcheckerDialogBase.ui 15 Feb 2003 22:26:27 -0000 1.3 +++ frontends/qt2/ui/QSpellcheckerDialogBase.ui 17 Feb 2003 00:54:09 -0000 @@ -13,8 +13,8 @@ <rect> <x>0</x> <y>0</y> - <width>294</width> - <height>379</height> + <width>323</width> + <height>389</height> </rect> </property> <property stdset="1"> @@ -218,7 +218,7 @@ </property> <property stdset="1"> <name>text</name> - <string>Unknown:</string> + <string>Unknown word:</string> </property> <property> <name>buddy</name> @@ -263,21 +263,6 @@ <string>Replace with selected word</string> </property> </widget> - <widget row="1" column="2" > - <class>QPushButton</class> - <property stdset="1"> - <name>name</name> - <cstring>spellcheckPB</cstring> - </property> - <property stdset="1"> - <name>text</name> - <string>&Start...</string> - </property> - <property> - <name>toolTip</name> - <string>Start spellcheck</string> - </property> - </widget> </grid> </widget> <connections> @@ -312,12 +297,6 @@ <slot>addClicked()</slot> </connection> <connection> - <sender>spellcheckPB</sender> - <signal>clicked()</signal> - <receiver>QSpellcheckerDialogBase</receiver> - <slot>spellcheckClicked()</slot> - </connection> - <connection> <sender>suggestionsLB</sender> <signal>doubleClicked(QListBoxItem*)</signal> <receiver>QSpellcheckerDialogBase</receiver> @@ -335,11 +314,9 @@ <slot access="public">optionsClicked()</slot> <slot access="public">replaceChanged(const QString &)</slot> <slot access="public">replaceClicked()</slot> - <slot access="public">spellcheckClicked()</slot> <slot access="public">suggestionChanged(const QString &)</slot> </connections> <tabstops> - <tabstop>spellcheckPB</tabstop> <tabstop>wordED</tabstop> <tabstop>replaceCO</tabstop> <tabstop>suggestionsLB</tabstop> Index: frontends/xforms/FormSpellchecker.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/xforms/FormSpellchecker.C,v retrieving revision 1.31 diff -u -r1.31 FormSpellchecker.C --- frontends/xforms/FormSpellchecker.C 13 Feb 2003 16:52:58 -0000 1.31 +++ frontends/xforms/FormSpellchecker.C 17 Feb 2003 00:54:09 -0000 @@ -12,7 +12,6 @@ #include "xformsBC.h" -#include "ControlSpellchecker.h" #include "FormSpellchecker.h" #include "forms/form_spellchecker.h" @@ -23,11 +22,12 @@ #include FORMS_H_LOCATION -typedef FormCB<ControlSpellchecker, FormDB<FD_spellchecker> > base_class; +using std::endl; +typedef FormCB<ControlSpellchecker, FormDB<FD_spellchecker> > base_class; FormSpellchecker::FormSpellchecker() - : base_class(_("Spellchecker")), state_(STOPPED) + : base_class(_("Spellchecker")) {} @@ -61,8 +61,6 @@ tooltips().init(dialog_->browser_suggestions, str); // Work-around xforms' bug; enable tooltips for browser widgets. setPrehandler(dialog_->browser_suggestions); - str = _("Start the spellingchecker."); - tooltips().init(dialog_->button_start, str); str = _("Replace unknown word."); tooltips().init(dialog_->button_replace, str); str = _("Ignore unknown word."); @@ -76,16 +74,14 @@ } -void FormSpellchecker::updateState(State state) +void FormSpellchecker::partialUpdate(int s) { + ControlSpellchecker::State const state = + static_cast<ControlSpellchecker::State>(s); + switch (state) { - case READY_TO_START: - fl_set_slider_value(dialog_->slider_progress, 0.0); - fl_set_object_label(dialog_->slider_progress, "0 %"); - break; - case CHECKING: - { + case ControlSpellchecker::SPELL_FOUND_WORD: { // Set suggestions. string w = controller().getWord(); fl_set_input(dialog_->input_replacement, w.c_str()); @@ -98,8 +94,7 @@ // Fall through... } - case STARTED: - { + case ControlSpellchecker::SPELL_PROGRESSED: { int const progress = controller().getProgress(); if (progress == 0) break; @@ -111,68 +106,39 @@ fl_set_slider_bounds(dialog_->slider_progress, 0.0, total); fl_set_slider_value(dialog_->slider_progress, wordcount); fl_set_object_label(dialog_->slider_progress, label.c_str()); + fl_redraw_object(dialog_->slider_progress); break; } - case STOPPED: - { - controller().stop(); - - double const wordcount = controller().getCount(); - - fl_set_slider_bounds(dialog_->slider_progress, 0.0, wordcount); - fl_set_slider_value(dialog_->slider_progress, wordcount); - fl_set_object_label(dialog_->slider_progress, "100 %"); - break; - } } +} - bool const state_change = state_ != state; - state_ = state; - - if (!state_change) - return; - - bool const running = (state == STARTED || state == CHECKING); - string const label = running ? _("Stop|#S") : _("Start|#S"); - fl_set_object_label(dialog_->button_start, idex(label).c_str()); - fl_set_button_shortcut(dialog_->button_start, scex(label).c_str(), 1); - fl_redraw_object(dialog_->button_start); - - string const tip = running ? - _("Stop the spellingchecker.") : - _("Start the spellingchecker."); - tooltips().init(dialog_->button_start, tip); - - setEnabled(dialog_->button_replace, running); - setEnabled(dialog_->button_ignore, running); - setEnabled(dialog_->button_accept, running); - setEnabled(dialog_->button_add, running); - setEnabled(dialog_->browser_suggestions, running); - setEnabled(dialog_->input_replacement, running); +// FIXME: do in controller ? +void FormSpellchecker::show() +{ + FormBase::show(); + controller().check(); } void FormSpellchecker::update() { + lyxerr[Debug::GUI] << "Spell update() " << endl; + // clear input fields fl_set_input(dialog_->input_replacement, ""); fl_set_object_label(dialog_->text_unknown, ""); fl_clear_browser(dialog_->browser_suggestions); + fl_set_slider_value(dialog_->slider_progress, 0.0); + fl_set_object_label(dialog_->slider_progress, "0 %"); - // reset dialog and buttons into start condition - updateState(READY_TO_START); } ButtonPolicy::SMInput FormSpellchecker::input(FL_OBJECT * ob, long ob_value) { - if (ob == dialog_->button_start) { - updateState(STARTED); - controller().check(); - - } else if (ob == dialog_->button_replace) { + if (ob == dialog_->button_replace) { string const tmp = getString(dialog_->input_replacement); controller().replace(tmp); @@ -205,19 +171,4 @@ } return ButtonPolicy::SMI_VALID; -} - - -void FormSpellchecker::partialUpdate(int id) -{ - switch (id) { - case 1: - // Set suggestions. - updateState(CHECKING); - break; - case 2: - // End of spell checking. - updateState(STOPPED); - break; - } } Index: frontends/xforms/FormSpellchecker.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/xforms/FormSpellchecker.h,v retrieving revision 1.16 diff -u -r1.16 FormSpellchecker.h --- frontends/xforms/FormSpellchecker.h 13 Feb 2003 16:52:58 -0000 1.16 +++ frontends/xforms/FormSpellchecker.h 17 Feb 2003 00:54:09 -0000 @@ -14,8 +14,8 @@ #include "FormBase.h" +#include "ControlSpellchecker.h" -class ControlSpellchecker; struct FD_spellchecker; /** This class provides an XForms implementation of the FormSpellchecker Dialog. @@ -33,23 +33,13 @@ /// virtual void update(); + virtual void show(); + /// set suggestions and exit message virtual void partialUpdate(int); /// Filter the inputs virtual ButtonPolicy::SMInput input(FL_OBJECT *, long); - - /// - enum State { - READY_TO_START, - STARTED, - CHECKING, - STOPPED - }; - /// - void updateState(State state); - /// - State state_; }; #endif // FORMSPELLCHECKER_H Index: frontends/xforms/forms/form_spellchecker.fd =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/frontends/xforms/forms/form_spellchecker.fd,v retrieving revision 1.10 diff -u -r1.10 form_spellchecker.fd --- frontends/xforms/forms/form_spellchecker.fd 26 Jan 2003 16:58:37 -0000 1.10 +++ frontends/xforms/forms/form_spellchecker.fd 17 Feb 2003 00:54:10 -0000 @@ -11,7 +11,7 @@ Name: form_spellchecker Width: 385 Height: 375 -Number of Objects: 13 +Number of Objects: 12 -------------------- class: FL_BOX @@ -124,24 +124,6 @@ -------------------- class: FL_BUTTON type: NORMAL_BUTTON -box: 280 25 100 25 -boxtype: FL_UP_BOX -colors: FL_COL1 FL_COL1 -alignment: FL_ALIGN_CENTER -style: FL_NORMAL_STYLE -size: FL_NORMAL_SIZE -lcol: FL_BLACK -label: Start|#S -shortcut: -resize: FL_RESIZE_NONE -gravity: FL_NorthEast FL_NorthEast -name: button_start -callback: C_FormBaseInputCB -argument: 0 - --------------------- -class: FL_BUTTON -type: NORMAL_BUTTON box: 280 195 100 25 boxtype: FL_UP_BOX colors: FL_COL1 FL_COL1 @@ -185,7 +167,7 @@ style: FL_NORMAL_STYLE size: FL_NORMAL_SIZE lcol: FL_BLACK -label: Accept|#A +label: Ignore All|#g shortcut: resize: FL_RESIZE_X gravity: FL_NorthEast FL_NorthEast Index: support/forkedcall.C =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/support/forkedcall.C,v retrieving revision 1.12 diff -u -r1.12 forkedcall.C --- support/forkedcall.C 13 Feb 2003 16:53:14 -0000 1.12 +++ support/forkedcall.C 17 Feb 2003 00:54:10 -0000 @@ -143,6 +143,24 @@ return retval_; } + +bool ForkedProcess::running() const +{ + if (!pid()) + return false; + + // Un-UNIX like, but we don't have much use for + // knowing if a zombie exists, so just reap it first. + int waitstatus; + waitpid(pid(), &waitstatus, WNOHANG); + + // Racy of course, but it will do. + if (::kill(pid(), 0) && errno == ESRCH) + return false; + return true; +} + + void ForkedProcess::kill(int tol) { lyxerr << "ForkedProcess::kill(" << tol << ')' << endl; Index: support/forkedcall.h =================================================================== RCS file: /usr/local/lyx/cvsroot/lyx-devel/src/support/forkedcall.h,v retrieving revision 1.8 diff -u -r1.8 forkedcall.h --- support/forkedcall.h 13 Feb 2003 16:53:14 -0000 1.8 +++ support/forkedcall.h 17 Feb 2003 00:54:11 -0000 @@ -94,6 +94,9 @@ /// Returns the identifying command (for display in the GUI perhaps). string const & command() const { return command_; } + /// is the process running ? + bool running() const; + /** Kill child prematurely. * First, a SIGHUP is sent to the child. * If that does not end the child process within "tolerance"