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
This commit is contained in:
Abdelrazak Younes 2009-08-08 17:05:31 +00:00
parent ad0f2a7dff
commit 21daab357b
11 changed files with 192 additions and 46 deletions

View File

@ -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 <hunspell/hunspell.hxx>
#include <map>
#include <string>
// 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,25 +55,41 @@ 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
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;
}
Hunspell * HunspellChecker::Private::speller(string const & lang)
@ -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;
if (info & SPELL_COMPOUND) {
// FIXME: What to do with that?
switch (info) {
case SPELL_COMPOUND:
case SPELL_FORBIDDEN:
default:
return UNKNOWN_WORD;
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
}

View File

@ -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

View File

@ -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();

View File

@ -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:

View File

@ -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) {

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -300,6 +300,7 @@ private Q_SLOTS:
void selectBackupdir();
void selectWorkingdir();
void selectThesaurusdir();
void selectHunspelldir();
void selectLyxPipe();
};

View File

@ -19,7 +19,7 @@
<property name="spacing" >
<number>6</number>
</property>
<item row="8" column="0" colspan="3" >
<item row="11" column="0" colspan="3" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
@ -27,7 +27,7 @@
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0" >
<size>
<width>329</width>
<height>16</height>
@ -35,7 +35,7 @@
</property>
</spacer>
</item>
<item row="7" column="0" >
<item row="10" column="0" >
<widget class="QLabel" name="pathPrefixLA" >
<property name="text" >
<string>&amp;PATH prefix:</string>
@ -45,7 +45,7 @@
</property>
</widget>
</item>
<item row="7" column="1" colspan="2" >
<item row="10" column="1" colspan="2" >
<widget class="QLineEdit" name="pathPrefixED" />
</item>
<item row="6" column="2" >
@ -209,6 +209,29 @@
</property>
</widget>
</item>
<item row="8" column="0" >
<widget class="QLabel" name="hunspellDirLA" >
<property name="text" >
<string>Hunspell dictionaries:</string>
</property>
<property name="buddy" >
<cstring>thesaurusDirED</cstring>
</property>
</widget>
</item>
<item row="8" column="1" >
<widget class="QLineEdit" name="hunspellDirED" />
</item>
<item row="8" column="2" >
<widget class="QPushButton" name="hunspellDirPB" >
<property name="text" >
<string>Browse...</string>
</property>
<property name="autoDefault" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>

View File

@ -19,7 +19,7 @@
<property name="spacing" >
<number>6</number>
</property>
<item row="0" column="0" >
<item row="1" column="0" >
<widget class="QLabel" name="altLanguageLA" >
<property name="text" >
<string>Al&amp;ternative language:</string>
@ -29,14 +29,14 @@
</property>
</widget>
</item>
<item row="0" column="1" >
<item row="1" column="1" >
<widget class="QLineEdit" name="altLanguageED" >
<property name="toolTip" >
<string>Override the language used for the spellchecker</string>
</property>
</widget>
</item>
<item row="1" column="0" >
<item row="2" column="0" >
<widget class="QLabel" name="escapeCharactersLA" >
<property name="text" >
<string>&amp;Escape characters:</string>
@ -46,14 +46,14 @@
</property>
</widget>
</item>
<item row="1" column="1" >
<item row="2" column="1" >
<widget class="QLineEdit" name="escapeCharactersED" >
<property name="toolTip" >
<string>The characters inserted here are ignored by the spellchecker. </string>
</property>
</widget>
</item>
<item row="2" column="3" >
<item row="3" column="3" >
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
@ -61,7 +61,7 @@
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0" >
<size>
<width>41</width>
<height>22</height>
@ -69,7 +69,7 @@
</property>
</spacer>
</item>
<item row="4" column="0" colspan="3" >
<item row="5" column="0" colspan="3" >
<widget class="QCheckBox" name="spellcheckContinuouslyCB" >
<property name="toolTip" >
<string>Mark misspelled words with a wavy underline.</string>
@ -79,7 +79,7 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="4" >
<item row="6" column="0" colspan="4" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
@ -87,7 +87,7 @@
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>20</height>
@ -95,7 +95,7 @@
</property>
</spacer>
</item>
<item rowspan="2" row="2" column="0" colspan="4" >
<item rowspan="2" row="3" column="0" colspan="4" >
<widget class="QCheckBox" name="compoundWordCB" >
<property name="toolTip" >
<string>Accept words such as "diskdrive"</string>
@ -105,6 +105,19 @@
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QComboBox" name="spellcheckerCB" />
</item>
<item row="0" column="0" >
<widget class="QLabel" name="spellcheckerLA" >
<property name="text" >
<string>&amp;Spellchecker engine:</string>
</property>
<property name="buddy" >
<cstring>altLanguageED</cstring>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>