]> git.lyx.org Git - lyx.git/blobdiff - src/AspellChecker.cpp
Fix bug #6744: Crash when copying an inset from a deleted section.
[lyx.git] / src / AspellChecker.cpp
index f9fff666c4c97f9b590b47130e2c5e4a9040d971..d5bd9c04afe217bc36b43ba945c53175e9218916 100644 (file)
 #include "support/debug.h"
 #include "support/docstring_list.h"
 
+#include "support/Package.h"
+#include "support/FileName.h"
+#include "support/Path.h"
+
 #include <aspell.h>
 
 #include <map>
 #include <string>
 
+#ifdef __APPLE__
+
+# ifndef ASPELL_FRAMEWORK
+# define ASPELL_FRAMEWORK "Aspell.framework"
+# endif
+# ifndef ASPELL_FRAMEWORK_DATA
+# define ASPELL_FRAMEWORK_DATA "/Resources/data"
+# endif
+# ifndef ASPELL_FRAMEWORK_DICT
+# define ASPELL_FRAMEWORK_DICT "/Resources/dict"
+# endif
+
+# ifndef ASPELL_MACPORTS
+# define ASPELL_MACPORTS "/opt/local"
+# endif
+# ifndef ASPELL_MACPORTS_DATA
+# define ASPELL_MACPORTS_DATA "/lib/aspell-0.60"
+# endif
+# ifndef ASPELL_MACPORTS_DICT
+# define ASPELL_MACPORTS_DICT "/share/aspell"
+# endif
+
+#endif /* __APPLE__ */
+
 using namespace std;
+using namespace lyx::support;
 
 namespace lyx {
 
 namespace {
 
 struct Speller {
-       AspellSpeller * speller;
+       ///AspellSpeller * speller;
        AspellConfig * config;
+       AspellCanHaveError * e_speller;
 };
 
 typedef std::map<std::string, Speller> Spellers;
@@ -41,7 +71,7 @@ typedef std::map<std::string, Speller> Spellers;
 
 struct AspellChecker::Private
 {
-       Private(): spell_error_object(0) {}
+       Private() {}
 
        ~Private();
 
@@ -60,39 +90,109 @@ struct AspellChecker::Private
        /// the spellers
        Spellers spellers_;
 
-       /// FIXME
-       AspellCanHaveError * spell_error_object;
 };
 
 
 AspellChecker::Private::~Private()
 {
-       if (spell_error_object) {
-               delete_aspell_can_have_error(spell_error_object);
-               spell_error_object = 0;
-       }
-
        Spellers::iterator it = spellers_.begin();
        Spellers::iterator end = spellers_.end();
 
        for (; it != end; ++it) {
-               aspell_speller_save_all_word_lists(it->second.speller);
-               delete_aspell_speller(it->second.speller);
+               if (it->second.e_speller) {
+                       AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
+                       aspell_speller_save_all_word_lists(speller);
+                       delete_aspell_can_have_error(it->second.e_speller);
+               }
                delete_aspell_config(it->second.config);
        }
 }
 
 
-AspellSpeller * AspellChecker::Private::addSpeller(string const & lang,
+bool isValidDictionary(AspellConfig * config,
+               string const & lang, string const & variety)
+{
+       bool have = false;
+       // code taken from aspell's list-dicts example
+       // the returned pointer should _not_ need to be deleted
+       AspellDictInfoList * dlist = get_aspell_dict_info_list(config);
+       AspellDictInfoEnumeration * dels = aspell_dict_info_list_elements(dlist);
+       const AspellDictInfo * entry;
+
+       while (0 != (entry = aspell_dict_info_enumeration_next(dels))) {
+               LYXERR(Debug::DEBUG, "aspell dict:"
+                       << " name="    << entry->name
+                       << ",code="    << entry->code
+                       << ",variety=" << entry->jargon);
+               if (entry->code == lang && (variety.empty() || entry->jargon == variety)) {
+                       have = true;
+                       break;
+               }
+       }
+       delete_aspell_dict_info_enumeration(dels);
+       LYXERR(Debug::FILES, "aspell dictionary: " << lang << (have ? " yes" : " no"));
+       return have;
+}
+
+
+bool checkAspellData(AspellConfig * config,
+       char const * basepath, char const * datapath, char const * dictpath,
+       string const & lang, string const & variety)
+{
+       bool have_dict = false;
+       FileName base(basepath);
+       FileName data(base.absFileName() + datapath);
+       FileName dict(base.absFileName() + dictpath);
+       have_dict = dict.isDirectory() && data.isDirectory();
+       if (have_dict) {
+               aspell_config_replace(config, "dict-dir", dict.absFileName().c_str());
+               aspell_config_replace(config, "data-dir", data.absFileName().c_str());
+               LYXERR(Debug::FILES, "aspell dict: " << dict);
+               have_dict = isValidDictionary(config, lang, variety);
+       }
+       return have_dict ;
+}
+
+
+AspellConfig * getConfig(string const & lang,
                                                   string const & variety)
 {
        AspellConfig * config = new_aspell_config();
+#ifdef __APPLE__
+       char buf[2048] ;
+       bool have_dict = false;
+       char const * sysdir = lyx::support::package().system_support().absFileName().c_str() ;
+       char const * userdir = lyx::support::package().user_support().absFileName().c_str() ;
+       char const * framework = ASPELL_FRAMEWORK ;
+
+       LYXERR(Debug::FILES, "aspell sysdir dir: " << sysdir);
+       LYXERR(Debug::FILES, "aspell user dir: " << userdir);
+       have_dict = checkAspellData(config, userdir, ASPELL_FRAMEWORK_DATA, ASPELL_FRAMEWORK_DICT, lang, variety);
+       if (!have_dict && strlen(framework) && getPrivateFrameworkPathName(buf, sizeof(buf), framework)) {
+               LYXERR(Debug::FILES, "aspell bundle path: " << buf);
+               have_dict = checkAspellData(config, buf, ASPELL_FRAMEWORK_DATA, ASPELL_FRAMEWORK_DICT, lang, variety);
+       }
+       if (!have_dict) {
+               // check for macports data
+               have_dict = checkAspellData(config, ASPELL_MACPORTS, ASPELL_MACPORTS_DATA, ASPELL_MACPORTS_DICT, lang, variety);
+       }
+#endif
+       return config ;
+}
+
+
+AspellSpeller * AspellChecker::Private::addSpeller(string const & lang,
+                                                  string const & variety)
+{
+       Speller m;
+
+       m.config = getConfig(lang, variety);
        // Aspell supports both languages and varieties (such as German
        // old vs. new spelling). The respective naming convention is
        // lang_REGION-variety (e.g. de_DE-alt).
-       aspell_config_replace(config, "lang", lang.c_str());
+       aspell_config_replace(m.config, "lang", lang.c_str());
        if (!variety.empty())
-               aspell_config_replace(config, "variety", variety.c_str());
+               aspell_config_replace(m.config, "variety", variety.c_str());
        // Set the encoding to utf-8.
        // aspell does also understand "ucs-4", so we would not need a
        // conversion in theory, but if this is used it expects all
@@ -100,29 +200,22 @@ AspellSpeller * AspellChecker::Private::addSpeller(string const & lang,
        // seems that this uint is not compatible with our char_type on some
        // platforms (cygwin, OS X). Therefore we use utf-8, that does
        // always work.
-       aspell_config_replace(config, "encoding", "utf-8");
+       aspell_config_replace(m.config, "encoding", "utf-8");
        if (lyxrc.spellchecker_accept_compound)
                // Consider run-together words as legal compounds
-               aspell_config_replace(config, "run-together", "true");
+               aspell_config_replace(m.config, "run-together", "true");
        else
                // Report run-together words as errors
-               aspell_config_replace(config, "run-together", "false");
-       AspellCanHaveError * err = new_aspell_speller(config);
-       if (spell_error_object)
-               delete_aspell_can_have_error(spell_error_object);
-       spell_error_object = 0;
-
-       if (aspell_error_number(err) != 0) {
-               // FIXME: We should we indicate somehow that this language is not
-               // supported.
-               spell_error_object = err;
-               return 0;
+               aspell_config_replace(m.config, "run-together", "false");
+
+       m.e_speller = new_aspell_speller(m.config);
+       if (aspell_error_number(m.e_speller) != 0) {
+               // FIXME: We should indicate somehow that this language is not supported.
+               LYXERR(Debug::FILES, "aspell error: " << aspell_error_message(m.e_speller));
        }
-       Speller m;
-       m.speller = to_aspell_speller(err);
-       m.config = config;
+
        spellers_[spellerID(lang, variety)] = m;
-       return m.speller;
+       return to_aspell_speller(m.e_speller);
 }
 
 
@@ -131,7 +224,7 @@ AspellSpeller * AspellChecker::Private::speller(string const & lang,
 {
        Spellers::iterator it = spellers_.find(spellerID(lang, variety));
        if (it != spellers_.end())
-               return it->second.speller;
+               return to_aspell_speller(it->second.e_speller);
        
        return addSpeller(lang, variety);
 }
@@ -181,8 +274,10 @@ void AspellChecker::insert(WordLangTuple const & word)
 {
        Spellers::iterator it = d->spellers_.find(
                d->spellerID(word.lang()->code(), word.lang()->variety()));
-       if (it != d->spellers_.end())
-               aspell_speller_add_to_personal(it->second.speller, to_utf8(word.word()).c_str(), -1);
+       if (it != d->spellers_.end()) {
+               AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
+               aspell_speller_add_to_personal(speller, to_utf8(word.word()).c_str(), -1);
+       }
 }
 
 
@@ -190,8 +285,10 @@ void AspellChecker::accept(WordLangTuple const & word)
 {
        Spellers::iterator it = d->spellers_.find(
                d->spellerID(word.lang()->code(), word.lang()->variety()));
-       if (it != d->spellers_.end())
-               aspell_speller_add_to_session(it->second.speller, to_utf8(word.word()).c_str(), -1);
+       if (it != d->spellers_.end()) {
+               AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
+               aspell_speller_add_to_session(speller, to_utf8(word.word()).c_str(), -1);
+       }
 }
 
 
@@ -223,12 +320,36 @@ void AspellChecker::suggest(WordLangTuple const & wl,
 }
 
 
+bool AspellChecker::hasDictionary(Language const * lang) const
+{
+       bool have = false;
+       Spellers::iterator it = d->spellers_.begin();
+       Spellers::iterator end = d->spellers_.end();
+
+       if (lang) {
+               for (; it != end && !have; ++it) {
+                       have = isValidDictionary(it->second.config, lang->code(), lang->variety());
+               }
+               if (!have) {
+                       AspellConfig * config = getConfig(lang->code(), lang->variety());
+                       have = isValidDictionary(config, lang->code(), lang->variety());
+                       delete_aspell_config(config);
+               }
+       }
+       return have;
+}
+
+
 docstring const AspellChecker::error()
 {
+       Spellers::iterator it = d->spellers_.begin();
+       Spellers::iterator end = d->spellers_.end();
        char const * err = 0;
 
-       if (d->spell_error_object && aspell_error_number(d->spell_error_object) != 0)
-               err = aspell_error_message(d->spell_error_object);
+       for (; it != end && 0 == err; ++it) {
+               if (it->second.e_speller && aspell_error_number(it->second.e_speller) != 0)
+                       err = aspell_error_message(it->second.e_speller);
+       }
 
        // FIXME UNICODE: err is not in UTF8, but probably the locale encoding
        return (err ? from_utf8(err) : docstring());