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