Hi,

thinking about the way to go with spell checkers and thesaurus...

Currently the build on Mac is able to include aspell with a subset of 
dictionaries.
I've prepared a patch to allow for searching dictionaries at different 
locations.
I'll attach it below. Now LyX/Mac is looking at runtime 
1) in LYX_USERDIR,
2) included aspell framework and
3) systems macports installation 
until it finds any support for the requested language.

There are some questions remaining:
* Are the locations of the runtime lookup above sensible?
* Is aspell the best spell checker available?
  (At least its the one compiling out of the box on Mac, I failed with enchant 
and hunspell)
* JMarc asked for some more general way for distributing bundled dictionaries.
  Is there any proposal how to proceed here?
* How to give the user the option to install missing dictionaries on her own?
  The lookup in LYX_USERDIR is only the first step. Should there be some 
build-in mechanism
  to help installing (aspell)-dictionaries there?
* What is the state of thesaurus support? Can anyone give me some pointer to 
documentation
  how it should be enabled for Mac?
* What is the potential gain in support for native spellchecker/thesaurus of 
Mac OS X?
  Would that be desirably?

Stephan

Index: development/LyX-Mac-binary-release.sh
===================================================================
--- development/LyX-Mac-binary-release.sh       (Revision 34191)
+++ development/LyX-Mac-binary-release.sh       (Arbeitskopie)
@@ -22,9 +22,9 @@
        echo Build script for LyX on Mac OS X
        echo
        echo Optional arguments:
-       echo " --tiger-support=yes|no ....." default yes
        echo " --dict-deployment=yes|no ..." default yes
        echo " --qt4-deployment=yes|no ...." default yes
+       echo " --with-macosx-target=TARGET " default 10.4 "(Tiger)"
        echo " --with-arch=ARCH ..........." default ppc,i386
        echo " --with-build-path=PATH ....." default 
\${lyx-src-dir}/../lyx-build
        echo " --with-dmg-location=PATH ..." default \${build-path}
@@ -41,9 +41,8 @@
                QTDIR=`echo ${1}|cut -d= -f2`
                shift
                ;;
-       --tiger-support=[Nn][Oo])
-               MACOSX_DEPLOYMENT_TARGET=""
-               MYCFLAGS=""
+       --with-macosx-target=*)
+               MACOSX_DEPLOYMENT_TARGET=`echo ${1}|cut -d= -f2`
                shift
                ;;
        --dict-deployment=*)
@@ -151,9 +150,11 @@
 HostSystem_i386="i686-apple-darwin8"
 HostSystem_ppc="powerpc-apple-darwin8"
 
-DMGNAME="${LyxBase}-Uncompressed.dmg"
+# don't change order here...
+QtLibraries="QtSvg QtXml QtGui QtNetwork QtCore"
+
+DMGNAME="${LyxBase}"
 DMGSIZE="550m"
-COMPRESSEDDMGNAME="${LyxBase}.dmg"
 BACKGROUND="${LyxAppDir}.app/Contents/Resources/images/banner.png"
 
 # Check for existing SDKs
@@ -347,8 +348,7 @@
                        cp -p "${libname}" "${condir}/PlugIns/${dirname}"
                done
        fi
-       # don't change order here...
-       for libnm in QtSvg QtXml QtGui QtNetwork QtCore ; do
+       for libnm in ${QtLibraries} ; do
                fwdir=`framework_name "$libnm"`
                dirname=`basename "${fwdir}"`
                test -d "${condir}/${fwdir}" || (
@@ -464,38 +464,37 @@
        BG_H=`echo ${BGSIZE} | awk '{h = $2 + 20 ;print h }'`
        BG_Y=`echo ${BGSIZE} | awk '{y = $2 - 60 ;print y }'`
 
-       rm -f ${DMGNAME}
-       rm -f ${COMPRESSEDDMGNAME}
+       rm -f "${DMGNAME}.sparseimage" "${DMGNAME}.dmg"
 
        hdiutil create -type SPARSE -size ${DMGSIZE:-"250m"} -fs HFS+ -volname 
"${LyxBase}" "${DMGNAME}"
        # Unmount currently mounted disk image
        test -d /Volumes/"${LyxBase}" && umount /Volumes/"${LyxBase}"
 
        # Mount the disk image
-       hdiutil attach ${DMGNAME}.sparseimage
+       hdiutil attach "${DMGNAME}.sparseimage"
 
        # Obtain device information
-       DEVS=$(hdiutil attach ${DMGNAME}.sparseimage | cut -f 1)
+       DEVS=$(hdiutil attach "${DMGNAME}.sparseimage" | cut -f 1)
        DEV=$(echo $DEVS | cut -f 1 -d ' ')
        VOLUME=$(mount |grep ${DEV} | cut -f 3 -d ' ')
 
        # copy in the application bundle
-       cp -Rp ${LyxAppDir}.app ${VOLUME}/${LyxName}.app
+       cp -Rp "${LyxAppDir}.app" "${VOLUME}/${LyxName}.app"
 
        # copy in background image
-       mkdir -p ${VOLUME}/Pictures
-       cp ${BACKGROUND} ${VOLUME}/Pictures/background.png
+       mkdir -p "${VOLUME}/Pictures"
+       cp "${BACKGROUND}" "${VOLUME}/Pictures/background.png"
        # symlink applications
-       ln -s /Applications/ ${VOLUME}/Applications
-       set_bundle_display_options ${VOLUME} ${BG_W} ${BG_H} ${BG_Y}
-       mv ${VOLUME}/Pictures ${VOLUME}/.Pictures
+       ln -s /Applications/ "${VOLUME}"/Applications
+       set_bundle_display_options "${VOLUME}" ${BG_W} ${BG_H} ${BG_Y}
+       mv "${VOLUME}/Pictures" "${VOLUME}/.Pictures"
 
        # Unmount the disk image
        hdiutil detach ${DEV}
 
        # Convert the disk image to read-only
-       hdiutil convert ${DMGNAME}.sparseimage -format UDBZ -o 
${COMPRESSEDDMGNAME}
-       rm -f ${DMGNAME}.sparseimage
+       hdiutil convert "${DMGNAME}.sparseimage" -format UDBZ -o 
"${DMGNAME}.dmg"
+       rm -f "${DMGNAME}.sparseimage"
 }
 
 build_lyx
@@ -510,4 +509,15 @@
        cd "${LyxAppPrefix}" && zip -r "${LyxAppZip}" .
 )
 
-test -n "${DMGLocation}" && make_dmg "${DMGLocation}"
+test -n "${DMGLocation}" && (
+       make_dmg "${DMGLocation}"
+       if [ -d 
"${QtInstallDir}/lib/QtCore.framework/Versions/${QtFrameworkVersion}" -a "yes" 
= "${qt4_deployment}" ]; then
+               rm -f "${DMGLocation}/${DMGNAME}+qt4.dmg"
+               mv "${DMGLocation}/${DMGNAME}.dmg" 
"${DMGLocation}/${DMGNAME}+qt4.dmg"
+               for libnm in ${QtLibraries} ; do
+                       fwdir=`framework_name "$libnm"`
+                       rm -rf "${LyxAppDir}.app/Contents/${fwdir}"
+               done
+               make_dmg "${DMGLocation}"
+       fi
+)
Index: src/AspellChecker.cpp
===================================================================
--- src/AspellChecker.cpp       (Revision 34191)
+++ src/AspellChecker.cpp       (Arbeitskopie)
@@ -19,6 +19,7 @@
 #include "support/debug.h"
 #include "support/docstring_list.h"
 
+#include "support/Package.h"
 #include "support/FileName.h"
 #include "support/Path.h"
 
@@ -27,6 +28,30 @@
 #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;
 
 namespace lyx {
@@ -34,8 +59,9 @@
 namespace {
 
 struct Speller {
-       AspellSpeller * speller;
+       ///AspellSpeller * speller;
        AspellConfig * config;
+       AspellCanHaveError * e_speller;
 };
 
 typedef std::map<std::string, Speller> Spellers;
@@ -44,7 +70,7 @@
 
 struct AspellChecker::Private
 {
-       Private(): spell_error_object(0) {}
+       Private() {}
 
        ~Private();
 
@@ -63,61 +89,89 @@
        /// 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);
        }
 }
 
 
-AspellConfig * getConfig()
+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;
+       lyx::support::FileName base(basepath);
+       lyx::support::FileName data(base.absFilename() + datapath);
+       lyx::support::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;
-#ifdef ASPELL_FRAMEWORK
-       char * framework = ASPELL_FRAMEWORK ;
+       char const * userdir = 
lyx::support::package().user_support().absFilename().c_str() ;
+       char const * framework = ASPELL_FRAMEWORK ;
 
-       if ( strlen(framework) && getPrivateFrameworkPathName(buf, sizeof(buf), 
framework) ) {
-               lyx::support::FileName const base(buf);
-               lyx::support::FileName const data(base.absFilename() + 
"/Resources/data");
-               lyx::support::FileName const dict(base.absFilename() + 
"/Resources/dict");
+       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 = 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 = checkAspellData(config, buf, ASPELL_FRAMEWORK_DATA, 
ASPELL_FRAMEWORK_DICT, lang, variety);
        }
-#endif
-       if ( !have_dict ) {
-               lyx::support::FileName const base("/opt/local"); // check for 
mac-ports data
-               lyx::support::FileName const data(base.absFilename() + 
"/lib/aspell-0.60");
-               lyx::support::FileName const dict(base.absFilename() + 
"/share/aspell");
-               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);
-               }
+       if (!have_dict) {
+               // check for macports data
+               have_dict = checkAspellData(config, ASPELL_MACPORTS, 
ASPELL_MACPORTS_DATA, ASPELL_MACPORTS_DICT, lang, variety);
        }
 #endif
        return config ;
@@ -127,13 +181,15 @@
 AspellSpeller * AspellChecker::Private::addSpeller(string const & lang,
                                                   string const & variety)
 {
-       AspellConfig * config = getConfig();
+       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
@@ -141,31 +197,22 @@
        // 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");
+               aspell_config_replace(m.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;
+       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));
+       }
 
-       if (aspell_error_number(err) != 0) {
-               // FIXME: We should we indicate somehow that this language is 
not
-               // supported.
-               spell_error_object = err;
-               LYXERR(Debug::FILES, "aspell error: " << 
aspell_error_message(err));
-               return 0;
-       }
-       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);
 }
 
 
@@ -174,7 +221,7 @@
 {
        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);
 }
@@ -224,8 +271,10 @@
 {
        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);
+       }
 }
 
 
@@ -233,8 +282,10 @@
 {
        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);
+       }
 }
 
 
@@ -268,46 +319,34 @@
 
 bool AspellChecker::hasDictionary(Language const * lang) const
 {
-       if (!lang)
-               return false;
-       // code taken from aspell's list-dicts example
-       AspellConfig * config;
-       AspellDictInfoList * dlist;
-       AspellDictInfoEnumeration * dels;
-       const AspellDictInfo * entry;
+       bool have = false;
+       Spellers::iterator it = d->spellers_.begin();
+       Spellers::iterator end = d->spellers_.end();
 
-       config = getConfig();
-
-       /* the returned pointer should _not_ need to be deleted */
-       dlist = get_aspell_dict_info_list(config);
-
-       /* config is no longer needed */
-       delete_aspell_config(config);
-
-       dels = aspell_dict_info_list_elements(dlist);
-
-       bool have = false;
-       while ((entry = aspell_dict_info_enumeration_next(dels)) != 0)
-       {
-               if (entry->code == lang->code()
-                   && (lang->variety().empty() || entry->jargon == 
lang->variety())) {
-                       have = true;
-                       break;
+       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);
+               }
        }
-
-       delete_aspell_dict_info_enumeration(dels);
-
        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());

Reply via email to