From 21daab357bcff2bf26d63cb59df69f6afa2acf3f Mon Sep 17 00:00:00 2001 From: Abdelrazak Younes Date: Sat, 8 Aug 2009 17:05:31 +0000 Subject: [PATCH] Minimal support for hunspell. Spellchecking works but not suggestion, at least on Win/MSVC. There are two new rc preference: * spellchecker: can now be "aspell" or "hunspell", this is selectable in the SpellChecker preference dialog * hunspelldir_path: needed for hunspell dictionaries which are defined to be in a fixed location. This can be modified in the path preference dialog. The SpellChecker classes could be instanciated on the fly whenerver they are needed if we want that. Please test and help me finish this hunspell integration... git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@30927 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/HunspellChecker.cpp | 71 ++++++++++++++-------- src/LyX.cpp | 37 ++++++++--- src/LyX.h | 1 + src/LyXFunc.cpp | 4 ++ src/LyXRC.cpp | 29 +++++++++ src/LyXRC.h | 6 ++ src/SpellChecker.h | 4 ++ src/frontends/qt4/GuiPrefs.cpp | 21 +++++++ src/frontends/qt4/GuiPrefs.h | 1 + src/frontends/qt4/ui/PrefPathsUi.ui | 31 ++++++++-- src/frontends/qt4/ui/PrefSpellcheckerUi.ui | 33 +++++++--- 11 files changed, 192 insertions(+), 46 deletions(-) diff --git a/src/HunspellChecker.cpp b/src/HunspellChecker.cpp index c51c0001a6..f024f2ec02 100644 --- a/src/HunspellChecker.cpp +++ b/src/HunspellChecker.cpp @@ -15,20 +15,21 @@ #include "LyXRC.h" #include "WordLangTuple.h" -#include "support/lassert.h" #include "support/debug.h" #include "support/docstring_list.h" +#include "support/FileName.h" +#include "support/gettext.h" +#include "support/lassert.h" +#include "support/os.h" #include #include #include -// FIXME (Abdel): I still got linking problems but if anybody wants -// to try, defines this to 1. -#define TRY_HUNSPELL 0 - using namespace std; +using namespace lyx::support; +using namespace lyx::support::os; namespace lyx { @@ -54,24 +55,40 @@ struct HunspellChecker::Private HunspellChecker::Private::~Private() { -#if TRY_HUNSPELL Spellers::iterator it = spellers_.begin(); Spellers::iterator end = spellers_.end(); for (; it != end; ++it) { delete it->second; } -#endif } Hunspell * HunspellChecker::Private::addSpeller(string const & lang) { - // FIXME: not implemented! + string hunspell_path = external_path(lyxrc.hunspelldir_path); + LYXERR(Debug::FILES, "hunspell path: " << hunspell_path); + if (hunspell_path.empty()) + return false; - // FIXME: We should we indicate somehow that this language is not - // supported. - return 0; + hunspell_path += "/" + lang; + // replace '_' with '-' as this is the convention used by hunspell. + hunspell_path[hunspell_path.size() - 3] = '-'; + FileName const affix(hunspell_path + ".aff"); + FileName const dict(hunspell_path + ".dic"); + if (!affix.isReadableFile()) { + // FIXME: We should indicate somehow that this language is not + // supported. + LYXERR(Debug::FILES, "Hunspell affix file " << affix << " does not exist"); + return 0; + } + if (!dict.isReadableFile()) { + LYXERR(Debug::FILES, "Hunspell dictionary file " << dict << " does not exist"); + return 0; + } + Hunspell * h = new Hunspell(affix.absFilename().c_str(), dict.absFilename().c_str()); + spellers_[lang] = h; + return h; } @@ -99,31 +116,33 @@ HunspellChecker::~HunspellChecker() SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl) { string const word_to_check = to_utf8(wl.word()); -#if TRY_HUNSPELL Hunspell * h = d->speller(wl.lang_code()); + if (!h) + return OK; int info; if (h->spell(word_to_check.c_str(), &info)) return OK; - // FIXME: What to do with that? - switch (info) { - case SPELL_COMPOUND: - case SPELL_FORBIDDEN: - default: - return UNKNOWN_WORD; + + if (info & SPELL_COMPOUND) { + // FIXME: What to do with that? + LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check); } + if (info & SPELL_FORBIDDEN) { + // FIXME: What to do with that? + LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check); + } + return UNKNOWN_WORD; -#endif - return OK; } void HunspellChecker::insert(WordLangTuple const & wl) { string const word_to_check = to_utf8(wl.word()); -#if TRY_HUNSPELL Hunspell * h = d->speller(wl.lang_code()); + if (!h) + return; h->add(word_to_check.c_str()); -#endif } @@ -138,14 +157,18 @@ void HunspellChecker::suggest(WordLangTuple const & wl, { suggestions.clear(); string const word_to_check = to_utf8(wl.word()); -#if TRY_HUNSPELL Hunspell * h = d->speller(wl.lang_code()); + if (!h) + return; char *** suggestion_list = 0; + + // FIXME: Hunspell::suggest() crashes on Win/MSVC9 + return; + int const suggestion_number = h->suggest(suggestion_list, word_to_check.c_str()); if (suggestion_number == 0) return; h->free_list(suggestion_list, suggestion_number); -#endif } diff --git a/src/LyX.cpp b/src/LyX.cpp index 55cd35ceb5..f09edcdda2 100644 --- a/src/LyX.cpp +++ b/src/LyX.cpp @@ -29,6 +29,7 @@ #include "ErrorList.h" #include "Format.h" #include "FuncStatus.h" +#include "HunspellChecker.h" #include "KeyMap.h" #include "Language.h" #include "LayoutFile.h" @@ -118,22 +119,15 @@ void reconfigureUserLyXDir() } // namespace anon - /// The main application class private implementation. struct LyX::Impl { - Impl() + Impl() : spell_checker_(0), aspell_checker_(0), hunspell_checker_(0) { // Set the default User Interface language as soon as possible. // The language used will be derived from the environment // variables. messages_["GUI"] = Messages(); - -#if defined(USE_ASPELL) - spell_checker_ = new AspellChecker(); -#else - spell_checker_ = 0; -#endif } ~Impl() @@ -184,6 +178,10 @@ struct LyX::Impl graphics::Previews preview_; /// SpellChecker * spell_checker_; + /// + SpellChecker * aspell_checker_; + /// + SpellChecker * hunspell_checker_; }; /// @@ -228,6 +226,7 @@ LyX::LyX() { singleton_ = this; pimpl_ = new Impl; + setSpellChecker(); } @@ -1278,4 +1277,26 @@ SpellChecker * theSpellChecker() return singleton_->pimpl_->spell_checker_; } + +void setSpellChecker() +{ +#if defined(USE_ASPELL) + if (lyxrc.spellchecker == "aspell") { + if (!singleton_->pimpl_->aspell_checker_) + singleton_->pimpl_->aspell_checker_ = new AspellChecker(); + singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->aspell_checker_; + return; + } +#endif +#if defined(USE_HUNSPELL) + if (lyxrc.spellchecker == "hunspell") { + if (!singleton_->pimpl_->hunspell_checker_) + singleton_->pimpl_->hunspell_checker_ = new HunspellChecker(); + singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->hunspell_checker_; + return; + } +#endif + singleton_->pimpl_->spell_checker_ = 0; +} + } // namespace lyx diff --git a/src/LyX.h b/src/LyX.h index 100b593047..6c6db63c0e 100644 --- a/src/LyX.h +++ b/src/LyX.h @@ -139,6 +139,7 @@ private: friend Session & theSession(); friend CmdDef & theTopLevelCmdDef(); friend SpellChecker * theSpellChecker(); + friend void setSpellChecker(); friend void setRcGuiLanguage(); friend void emergencyCleanup(); friend void execBatchCommands(); diff --git a/src/LyXFunc.cpp b/src/LyXFunc.cpp index 8dac8f676b..52ef0102fc 100644 --- a/src/LyXFunc.cpp +++ b/src/LyXFunc.cpp @@ -1590,6 +1590,8 @@ void LyXFunc::dispatch(FuncRequest const & cmd) actOnUpdatedPrefs(lyxrc_orig, lyxrc); + setSpellChecker(); + theApp()->resetGui(); /// We force the redraw in any case because there might be @@ -1969,6 +1971,7 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new) case LyXRC::RC_FONT_ENCODING: case LyXRC::RC_FORMAT: case LyXRC::RC_GROUP_LAYOUTS: + case LyXRC::RC_HUNSPELLDIR_PATH: case LyXRC::RC_INDEX_ALTERNATIVES: case LyXRC::RC_INDEX_COMMAND: case LyXRC::RC_JBIBTEX_COMMAND: @@ -2036,6 +2039,7 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new) case LyXRC::RC_SHOW_BANNER: case LyXRC::RC_OPEN_BUFFERS_IN_TABS: case LyXRC::RC_SPELL_COMMAND: + case LyXRC::RC_SPELLCHECKER: case LyXRC::RC_SPELLCHECK_CONTINUOUSLY: case LyXRC::RC_SPLITINDEX_COMMAND: case LyXRC::RC_TEMPDIRPATH: diff --git a/src/LyXRC.cpp b/src/LyXRC.cpp index 00ac217963..0de230eccb 100644 --- a/src/LyXRC.cpp +++ b/src/LyXRC.cpp @@ -99,6 +99,7 @@ LexerKeyword lyxrcTags[] = { { "\\fullscreen_width", LyXRC::RC_FULL_SCREEN_WIDTH }, { "\\group_layouts", LyXRC::RC_GROUP_LAYOUTS }, { "\\gui_language", LyXRC::RC_GUI_LANGUAGE }, + { "\\hunspelldir_path", LyXRC::RC_HUNSPELLDIR_PATH }, { "\\index_alternatives", LyXRC::RC_INDEX_ALTERNATIVES }, { "\\index_command", LyXRC::RC_INDEX_COMMAND }, { "\\input", LyXRC::RC_INPUT }, @@ -168,6 +169,7 @@ LexerKeyword lyxrcTags[] = { { "\\sort_layouts", LyXRC::RC_SORT_LAYOUTS }, { "\\spell_command", LyXRC::RC_SPELL_COMMAND }, { "\\spellcheck_continuously", LyXRC::RC_SPELLCHECK_CONTINUOUSLY }, + { "\\spellchecker", LyXRC::RC_SPELLCHECKER }, { "\\splitindex_command", LyXRC::RC_SPLITINDEX_COMMAND }, { "\\tempdir_path", LyXRC::RC_TEMPDIRPATH }, { "\\template_path", LyXRC::RC_TEMPLATEPATH }, @@ -269,6 +271,7 @@ void LyXRC::setDefaults() backupdir_path.erase(); display_graphics = true; // Spellchecker settings: + spellchecker = "aspell"; spellchecker_accept_compound = false; spellcheck_continuously = false; use_kbmap = false; @@ -704,6 +707,13 @@ int LyXRC::read(Lexer & lexrc) } break; + case RC_HUNSPELLDIR_PATH: + if (lexrc.next()) { + hunspelldir_path = os::internal_path(lexrc.getString()); + hunspelldir_path = expandPath(hunspelldir_path); + } + break; + case RC_USELASTFILEPOS: lexrc >> use_lastfilepos; break; @@ -875,6 +885,9 @@ int LyXRC::read(Lexer & lexrc) case RC_USE_PIXMAP_CACHE: lexrc >> use_pixmap_cache; break; + case RC_SPELLCHECKER: + lexrc >> spellchecker; + break; case RC_ALT_LANG: lexrc >> spellchecker_alt_lang; break; @@ -2132,6 +2145,14 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c } if (tag != RC_LAST) break; + case RC_HUNSPELLDIR_PATH: + if (ignore_system_lyxrc || + hunspelldir_path != system_lyxrc.hunspelldir_path) { + string const path = os::external_path(hunspelldir_path); + os << "\\hunspelldir_path \"" << path << "\"\n"; + } + if (tag != RC_LAST) + break; case RC_USETEMPDIR: if (tag != RC_LAST) break; @@ -2242,6 +2263,14 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c if (tag != RC_LAST) break; + case RC_SPELLCHECKER: + if (ignore_system_lyxrc || + spellchecker != system_lyxrc.spellchecker) { + os << "\\spellchecker " << spellchecker << '\n'; + } + if (tag != RC_LAST) + break; + case RC_SPELLCHECK_CONTINUOUSLY: if (ignore_system_lyxrc || spellcheck_continuously != system_lyxrc.spellcheck_continuously) { diff --git a/src/LyXRC.h b/src/LyXRC.h index bc54504805..79f4fbcf15 100644 --- a/src/LyXRC.h +++ b/src/LyXRC.h @@ -84,6 +84,7 @@ public: RC_GEOMETRY_SESSION, RC_GROUP_LAYOUTS, RC_GUI_LANGUAGE, + RC_HUNSPELLDIR_PATH, RC_INDEX_ALTERNATIVES, RC_INDEX_COMMAND, RC_INPUT, @@ -153,6 +154,7 @@ public: RC_SORT_LAYOUTS, RC_SPELL_COMMAND, RC_SPELLCHECK_CONTINUOUSLY, + RC_SPELLCHECKER, RC_SPLITINDEX_COMMAND, RC_TEMPDIRPATH, RC_TEMPLATEPATH, @@ -276,6 +278,8 @@ public: /// std::string thesaurusdir_path; /// + std::string hunspelldir_path; + /// bool auto_region_delete; /// flag telling whether lastfiles should be checked for existance bool auto_reset_options; @@ -328,6 +332,8 @@ public: bool use_tooltip; /// Use pixmap cache? bool use_pixmap_cache; + /// Spellchecker engine: aspell, hunspell, etc + std::string spellchecker; /// Alternate language for spellchecker std::string spellchecker_alt_lang; /// Escape characters diff --git a/src/SpellChecker.h b/src/SpellChecker.h index c6e3748c84..6d8c77df00 100644 --- a/src/SpellChecker.h +++ b/src/SpellChecker.h @@ -64,6 +64,10 @@ public: /// Implemented in LyX.cpp SpellChecker * theSpellChecker(); +/// Set the singleton SpellChecker engine. +/// Implemented in LyX.cpp +void setSpellChecker(); + } // namespace lyx #endif // SPELL_BASE_H diff --git a/src/frontends/qt4/GuiPrefs.cpp b/src/frontends/qt4/GuiPrefs.cpp index 0789a964d1..aaeaa73042 100644 --- a/src/frontends/qt4/GuiPrefs.cpp +++ b/src/frontends/qt4/GuiPrefs.cpp @@ -34,6 +34,7 @@ #include "PanelStack.h" #include "paper.h" #include "Session.h" +#include "SpellChecker.h" #include "support/debug.h" #include "support/FileName.h" @@ -1129,6 +1130,7 @@ PrefPaths::PrefPaths(GuiPreferences * form) connect(workingDirPB, SIGNAL(clicked()), this, SLOT(selectWorkingdir())); connect(lyxserverDirPB, SIGNAL(clicked()), this, SLOT(selectLyxPipe())); connect(thesaurusDirPB, SIGNAL(clicked()), this, SLOT(selectThesaurusdir())); + connect(hunspellDirPB, SIGNAL(clicked()), this, SLOT(selectHunspelldir())); connect(workingDirED, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); connect(exampleDirED, SIGNAL(textChanged(QString)), @@ -1156,6 +1158,7 @@ void PrefPaths::apply(LyXRC & rc) const rc.backupdir_path = internal_path(fromqstr(backupDirED->text())); rc.tempdir_path = internal_path(fromqstr(tempDirED->text())); rc.thesaurusdir_path = internal_path(fromqstr(thesaurusDirED->text())); + rc.hunspelldir_path = internal_path(fromqstr(hunspellDirED->text())); rc.path_prefix = internal_path_list(fromqstr(pathPrefixED->text())); // FIXME: should be a checkbox only rc.lyxpipes = internal_path(fromqstr(lyxserverDirED->text())); @@ -1170,6 +1173,7 @@ void PrefPaths::update(LyXRC const & rc) backupDirED->setText(toqstr(external_path(rc.backupdir_path))); tempDirED->setText(toqstr(external_path(rc.tempdir_path))); thesaurusDirED->setText(toqstr(external_path(rc.thesaurusdir_path))); + hunspellDirED->setText(toqstr(external_path(rc.hunspelldir_path))); pathPrefixED->setText(toqstr(external_path_list(rc.path_prefix))); // FIXME: should be a checkbox only lyxserverDirED->setText(toqstr(external_path(rc.lyxpipes))); @@ -1230,6 +1234,15 @@ void PrefPaths::selectThesaurusdir() } +void PrefPaths::selectHunspelldir() +{ + QString file = browseDir(internalPath(hunspellDirED->text()), + qt_("Set the path to the Hunspell dictionaries")); + if (!file.isEmpty()) + hunspellDirED->setText(file); +} + + void PrefPaths::selectLyxPipe() { QString file = form_->browse(internalPath(lyxserverDirED->text()), @@ -1250,6 +1263,11 @@ PrefSpellchecker::PrefSpellchecker(GuiPreferences * form) { setupUi(this); + spellcheckerCB->addItem("aspell"); + spellcheckerCB->addItem("hunspell"); + + connect(spellcheckerCB, SIGNAL(currentIndexChanged(int)), + this, SIGNAL(changed())); connect(altLanguageED, SIGNAL(textChanged(QString)), this, SIGNAL(changed())); connect(escapeCharactersED, SIGNAL(textChanged(QString)), @@ -1263,6 +1281,7 @@ PrefSpellchecker::PrefSpellchecker(GuiPreferences * form) void PrefSpellchecker::apply(LyXRC & rc) const { + rc.spellchecker = fromqstr(spellcheckerCB->currentText()); rc.spellchecker_alt_lang = fromqstr(altLanguageED->text()); rc.spellchecker_esc_chars = fromqstr(escapeCharactersED->text()); rc.spellchecker_accept_compound = compoundWordCB->isChecked(); @@ -1272,6 +1291,8 @@ void PrefSpellchecker::apply(LyXRC & rc) const void PrefSpellchecker::update(LyXRC const & rc) { + spellcheckerCB->setCurrentIndex(spellcheckerCB->findText( + toqstr(rc.spellchecker))); altLanguageED->setText(toqstr(rc.spellchecker_alt_lang)); escapeCharactersED->setText(toqstr(rc.spellchecker_esc_chars)); compoundWordCB->setChecked(rc.spellchecker_accept_compound); diff --git a/src/frontends/qt4/GuiPrefs.h b/src/frontends/qt4/GuiPrefs.h index 737d37812f..5e5d03a9c7 100644 --- a/src/frontends/qt4/GuiPrefs.h +++ b/src/frontends/qt4/GuiPrefs.h @@ -300,6 +300,7 @@ private Q_SLOTS: void selectBackupdir(); void selectWorkingdir(); void selectThesaurusdir(); + void selectHunspelldir(); void selectLyxPipe(); }; diff --git a/src/frontends/qt4/ui/PrefPathsUi.ui b/src/frontends/qt4/ui/PrefPathsUi.ui index d288117afc..cfbeb3fece 100644 --- a/src/frontends/qt4/ui/PrefPathsUi.ui +++ b/src/frontends/qt4/ui/PrefPathsUi.ui @@ -19,7 +19,7 @@ 6 - + Qt::Vertical @@ -27,7 +27,7 @@ QSizePolicy::Expanding - + 329 16 @@ -35,7 +35,7 @@ - + &PATH prefix: @@ -45,7 +45,7 @@ - + @@ -209,6 +209,29 @@ + + + + Hunspell dictionaries: + + + thesaurusDirED + + + + + + + + + + Browse... + + + false + + + diff --git a/src/frontends/qt4/ui/PrefSpellcheckerUi.ui b/src/frontends/qt4/ui/PrefSpellcheckerUi.ui index ed24fcb6e5..6c77e7270a 100644 --- a/src/frontends/qt4/ui/PrefSpellcheckerUi.ui +++ b/src/frontends/qt4/ui/PrefSpellcheckerUi.ui @@ -19,7 +19,7 @@ 6 - + Al&ternative language: @@ -29,14 +29,14 @@ - + Override the language used for the spellchecker - + &Escape characters: @@ -46,14 +46,14 @@ - + The characters inserted here are ignored by the spellchecker. - + Qt::Horizontal @@ -61,7 +61,7 @@ QSizePolicy::Expanding - + 41 22 @@ -69,7 +69,7 @@ - + Mark misspelled words with a wavy underline. @@ -79,7 +79,7 @@ - + Qt::Vertical @@ -87,7 +87,7 @@ QSizePolicy::Expanding - + 20 20 @@ -95,7 +95,7 @@ - + Accept words such as "diskdrive" @@ -105,6 +105,19 @@ + + + + + + + &Spellchecker engine: + + + altLanguageED + + +