From 0a326e90f73b36d562bb67ba5ace926301c386bb Mon Sep 17 00:00:00 2001 From: Stephan Witt Date: Wed, 7 Jul 2010 17:40:50 +0000 Subject: [PATCH] provide native spell checker for Mac OS X git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@34801 a592a061-630c-0410-9148-cb99ea01b6c8 --- development/LyX-Mac-binary-release.sh | 108 +++++++++- development/cmake/src/support/CMakeLists.txt | 3 + src/AppleSpellChecker.cpp | 111 +++++++++++ src/AppleSpellChecker.h | 43 ++++ src/LyX.cpp | 14 +- src/LyXRC.cpp | 5 +- src/support/AppleSpeller.h | 35 ++++ src/support/AppleSpeller.m | 197 +++++++++++++++++++ src/support/Makefile.am | 8 + 9 files changed, 512 insertions(+), 12 deletions(-) create mode 100644 src/AppleSpellChecker.cpp create mode 100644 src/AppleSpellChecker.h create mode 100644 src/support/AppleSpeller.h create mode 100644 src/support/AppleSpeller.m diff --git a/development/LyX-Mac-binary-release.sh b/development/LyX-Mac-binary-release.sh index cea274d404..f41d33d791 100644 --- a/development/LyX-Mac-binary-release.sh +++ b/development/LyX-Mac-binary-release.sh @@ -199,7 +199,7 @@ HostSystem_ppc="powerpc-apple-darwin8" QtLibraries="QtSvg QtXml QtGui QtNetwork QtCore" DMGNAME="${LyxBase}" -DMGSIZE="350m" +DMGSIZE="550m" BACKGROUND="${LyxAppDir}.app/Contents/Resources/images/banner.png" # Check for existing SDKs @@ -228,7 +228,91 @@ case "$SDKs" in exit 1 ;; esac -MYCFLAGS="-mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" +case "${MACOSX_DEPLOYMENT_TARGET}" in +10.4) + MYCFLAGS="-DLYX_PLATFORM_DARWIN10=4" + ;; +10.5) + MYCFLAGS="-DLYX_PLATFORM_DARWIN10=5" + ;; +10.6) + MYCFLAGS="-DLYX_PLATFORM_DARWIN10=6" + ;; +esac +MYCFLAGS="$MYCFLAGS -mmacosx-version-min=${MACOSX_DEPLOYMENT_TARGET}" + +updateDictionaries() { + TMP_DIR="/tmp/lyx-build-$$" + mkdir -p "$1"/dict "$1"/thes + mkdir -p "$TMP_DIR" && ( + for pack in "$1"/*.zip ; do + case "${pack}" in + *de_DE-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" de_DE_comb.zip thes_de_DE_v2.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/de_DE_comb.zip + cd "$1"/thes && unzip -o "$TMP_DIR"/thes_de_DE_v2.zip + ;; + *pl_PL-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" pl_PL.zip thes_pl_PL_v2.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/pl_PL.zip + cd "$1"/thes && unzip -o "$TMP_DIR"/thes_pl_PL_v2.zip + ;; + *fr_FR-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" fr_FR.zip thes_fr_FR_v2.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/fr_FR.zip + cd "$1"/thes && unzip -o "$TMP_DIR"/thes_fr_FR_v2.zip + ;; + *es_ES-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" es_ES.zip es_MX.zip thes_es_ES_v2.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/es_ES.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/es_MX.zip + cd "$1"/thes && unzip -o "$TMP_DIR"/thes_es_ES_v2.zip + ;; + *pt_PT-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" pt_PT.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/pt_PT.zip + cd "$1"/dict && unzip -o "$1"/pt_BR.zip + cd "$1"/thes && unzip -o "$1"/thes_pt_PT_v2.zip + ;; + *it_IT-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" it_IT.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/it_IT.zip + cd "$1"/thes && unzip -o "$1"/thes_it_IT_v2.zip + ;; + *ru_RU-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" ru_RU.zip + cd "$1"/dict && unzip -o "$TMP_DIR"/ru_RU.zip + cd "$1"/thes && tar xvf "$1"/thes_ru_RU_v2.tar.bz2 + ;; + *en_EN-pack.zip) + cd "$TMP_DIR" && unzip "${pack}" en_AU.zip en_CA.zip en_GB.zip en_NZ.zip en_US.zip + for zipfile in en_AU.zip en_CA.zip en_GB.zip en_NZ.zip en_US.zip ; do + ( cd "$1"/dict && unzip -o "$TMP_DIR/$zipfile" ) + done + cd "$1"/thes && unzip -o "$1"/thes_en_US_v2.zip + ;; + XXXX*-pack*) + cd "$TMP_DIR" && unzip -l "${pack}" | while read len date time zipfile ; do + case "$zipfile" in + thes*_v2.zip) + echo "$zipfile" + cd "$TMP_DIR" && unzip -o "${pack}" "$zipfile" + cd "$1"/thes && unzip -o "$TMP_DIR"/"$zipfile" + ;; + [a-z][a-z]_[A-Z][A-Z].zip) + echo "$zipfile" + cd "$TMP_DIR" && unzip -o "${pack}" "$zipfile" + cd "$1"/dict && unzip -o "$TMP_DIR"/"$zipfile" + ;; + esac + done + # echo Ignore dictionary package `basename "${pack}"` + ;; + esac + done + ) + rm -rf "$TMP_DIR" +} if [ -d "${Qt4SourceDir}" -a ! -d "${Qt4BuildDir}" ]; then echo Build Qt4 library ${Qt4SourceDir} @@ -253,6 +337,9 @@ if [ -d "${Qt4SourceDir}" -a ! -d "${Qt4BuildDir}" ]; then ) fi +# updateDictionaries "${DictionarySourceDir}" +# exit + if [ -d "${HunSpellSourceDir}" -a ! -f "${HunSpellInstallHdr}" ]; then # we have a private HunSpell source tree at hand... # so let's build and install it @@ -317,9 +404,6 @@ if [ -d "${HunSpellSourceDir}" -a ! -f "${HunSpellInstallHdr}" ]; then done fi -#exit 0 - - if [ -d "${ASpellSourceDir}" -a ! -f "${ASpellInstallHdr}" -a "yes" = "${aspell_deployment}" ]; then # we have a private ASpell source tree at hand... # so let's build and install it @@ -383,11 +467,14 @@ if [ -d "${ASpellSourceDir}" -a ! -f "${ASpellInstallHdr}" -a "yes" = "${aspell_ done fi +# exit 0 + + framework_name() { echo "Frameworks/${1}.framework" } -if [ ! -f "${LyxSourceDir}"/configure ]; then +if [ ! -f "${LyxSourceDir}"/configure -o "${LyxSourceDir}"/configure -ot "${LyxSourceDir}"/configure.ac ]; then ( cd "${LyxSourceDir}" && sh autogen.sh ) fi @@ -565,18 +652,19 @@ convert_universal() { copy_dictionaries() { if [ -d "${ASpellInstallDir}" -a "yes" = "${aspell_deployment}" ]; then - ASpellFramework=`framework_name Aspell` - ASpellResources="${LyxAppPrefix}/Contents/${ASpellFramework}/Resources" + ASpellResources="${LyxAppPrefix}/Contents/Resources" # try to reuse macports dictionaries for now if [ -d /opt/local/lib/aspell-0.60 ]; then ASpellInstallDir=/opt/local ; fi mkdir -p "${ASpellResources}" echo Copy Aspell dictionaries from "${ASpellInstallDir}" - cp -p -r "${ASpellInstallDir}/lib/aspell-0.60" "${ASpellResources}"/data - cp -p -r "${ASpellInstallDir}/share/aspell" "${ASpellResources}"/dict + mkdir -p "${ASpellResources}"/data "${ASpellResources}"/dict + cp -p -r "${ASpellInstallDir}/lib/aspell-0.60"/* "${ASpellResources}"/data + cp -p -r "${ASpellInstallDir}/share/aspell"/* "${ASpellResources}"/dict fi if [ -d "${HunSpellInstallDir}" -a "yes" = "${hunspell_deployment}" ]; then HunSpellResources="${LyxAppPrefix}/Contents/Resources" if [ -d "${DictionarySourceDir}" ]; then + updateDictionaries "${DictionarySourceDir}" cp -p -r "${DictionarySourceDir}/dict" "${HunSpellResources}" fi fi diff --git a/development/cmake/src/support/CMakeLists.txt b/development/cmake/src/support/CMakeLists.txt index a0fe949cfd..4ff45876c8 100644 --- a/development/cmake/src/support/CMakeLists.txt +++ b/development/cmake/src/support/CMakeLists.txt @@ -9,6 +9,9 @@ project(support) file(GLOB support_sources ${TOP_SRC_DIR}/src/support/${LYX_CPP_FILES}) file(GLOB moc_files ${TOP_SRC_DIR}/src/support/${LYX_MOC_FILES}) list(REMOVE_ITEM support_sources ${moc_files} .) +if(APPLE) + list(APPEND support_sources ${TOP_SRC_DIR}/src/support/AppleSpeller.m) +endif() file(GLOB support_headers ${TOP_SRC_DIR}/src/support/${LYX_HPP_FILES}) diff --git a/src/AppleSpellChecker.cpp b/src/AppleSpellChecker.cpp new file mode 100644 index 0000000000..c5ba8088d0 --- /dev/null +++ b/src/AppleSpellChecker.cpp @@ -0,0 +1,111 @@ +/** + * \file AppleSpellChecker.cpp + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stephan Witt + * + * Full author contact details are available in file CREDITS. + */ + +#include + +#include "AppleSpellChecker.h" +#include "WordLangTuple.h" + +#include "support/lassert.h" +#include "support/debug.h" +#include "support/docstring_list.h" +#include "support/AppleSpeller.h" + +using namespace std; +using namespace lyx::support; + +namespace lyx { + +struct AppleSpellChecker::Private +{ + Private(); + + ~Private(); + + /// the speller + AppleSpeller speller; +}; + + +AppleSpellChecker::Private::Private() +{ + speller = newAppleSpeller(); +} + + +AppleSpellChecker::Private::~Private() +{ + freeAppleSpeller(speller); + speller = 0; +} + + +AppleSpellChecker::AppleSpellChecker(): d(new Private) +{ +} + + +AppleSpellChecker::~AppleSpellChecker() +{ + delete d; +} + + +SpellChecker::Result AppleSpellChecker::check(WordLangTuple const & word) +{ + string const word_str = to_utf8(word.word()); + int const word_ok = checkAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); + return (word_ok) ? OK : UNKNOWN_WORD; +} + + +// add to personal dictionary +void AppleSpellChecker::insert(WordLangTuple const & word) +{ + string const word_str = to_utf8(word.word()); + learnAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); +} + + +// ignore for session +void AppleSpellChecker::accept(WordLangTuple const & word) +{ + string const word_str = to_utf8(word.word()); + ignoreAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); +} + + +void AppleSpellChecker::suggest(WordLangTuple const & wl, + docstring_list & suggestions) +{ + suggestions.clear(); + string const word_str = to_utf8(wl.word()); + size_t num = makeSuggestionAppleSpeller(d->speller, word_str.c_str(), wl.lang()->code().c_str()); + for (size_t i = 0; i < num; i++) { + char const * next = getSuggestionAppleSpeller(d->speller, i); + if (!next) break; + suggestions.push_back(from_utf8(next)); + } +} + + +bool AppleSpellChecker::hasDictionary(Language const * lang) const +{ + return hasLanguageAppleSpeller(d->speller,lang->code().c_str()); +} + + +docstring const AppleSpellChecker::error() +{ + return docstring(); +} + + +} // namespace lyx diff --git a/src/AppleSpellChecker.h b/src/AppleSpellChecker.h new file mode 100644 index 0000000000..5a6657520c --- /dev/null +++ b/src/AppleSpellChecker.h @@ -0,0 +1,43 @@ +// -*- C++ -*- +/** + * \file AppleSpellChecker.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stephan Witt + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef LYX_APPLESPELL_H +#define LYX_APPLESPELL_H + +#include "SpellChecker.h" + +namespace lyx { + +class AppleSpellChecker : public SpellChecker +{ +public: + AppleSpellChecker(); + ~AppleSpellChecker(); + + /// \name SpellChecker inherited methods + //@{ + enum Result check(WordLangTuple const &); + void suggest(WordLangTuple const &, docstring_list &); + void insert(WordLangTuple const &); + void accept(WordLangTuple const &); + bool hasDictionary(Language const * lang) const; + docstring const error(); + //@} + +private: + struct Private; + Private * d; +}; + + +} // namespace lyx + +#endif // LYX_APPLESPELL_H diff --git a/src/LyX.cpp b/src/LyX.cpp index 5fb2d59eda..04d6fa96a4 100644 --- a/src/LyX.cpp +++ b/src/LyX.cpp @@ -17,6 +17,7 @@ #include "LyX.h" +#include "AppleSpellChecker.h" #include "AspellChecker.h" #include "Buffer.h" #include "BufferList.h" @@ -130,7 +131,7 @@ void reconfigureUserLyXDir() /// The main application class private implementation. struct LyX::Impl { - Impl() : spell_checker_(0), aspell_checker_(0), enchant_checker_(0), hunspell_checker_(0) + Impl() : spell_checker_(0), apple_spell_checker_(0), aspell_checker_(0), enchant_checker_(0), hunspell_checker_(0) { // Set the default User Interface language as soon as possible. // The language used will be derived from the environment @@ -140,6 +141,7 @@ struct LyX::Impl ~Impl() { + delete apple_spell_checker_; delete aspell_checker_; delete enchant_checker_; delete hunspell_checker_; @@ -187,6 +189,8 @@ struct LyX::Impl /// SpellChecker * spell_checker_; /// + SpellChecker * apple_spell_checker_; + /// SpellChecker * aspell_checker_; /// SpellChecker * enchant_checker_; @@ -1324,6 +1328,14 @@ SpellChecker * theSpellChecker() void setSpellChecker() { +#ifdef USE_MACOSX_PACKAGING || defined(LYX_PLATFORM_DARWIN10) + if (lyxrc.spellchecker == "native") { + if (!singleton_->pimpl_->apple_spell_checker_) + singleton_->pimpl_->apple_spell_checker_ = new AppleSpellChecker(); + singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->apple_spell_checker_; + return; + } +#endif #if defined(USE_ASPELL) if (lyxrc.spellchecker == "aspell") { if (!singleton_->pimpl_->aspell_checker_) diff --git a/src/LyXRC.cpp b/src/LyXRC.cpp index dee4bcb940..79c39d29b9 100644 --- a/src/LyXRC.cpp +++ b/src/LyXRC.cpp @@ -285,7 +285,10 @@ void LyXRC::setDefaults() backupdir_path.erase(); display_graphics = true; // Spellchecker settings: -#if defined(USE_ASPELL) +// FIXME: this check should test the target platform (darwin) +#ifdef USE_MACOSX_PACKAGING || defined(LYX_PLATFORM_DARWIN10) + spellchecker = "native"; +#elif defined(USE_ASPELL) spellchecker = "aspell"; #elif defined(USE_HUNSPELL) spellchecker = "hunspell"; diff --git a/src/support/AppleSpeller.h b/src/support/AppleSpeller.h new file mode 100644 index 0000000000..8f433955ee --- /dev/null +++ b/src/support/AppleSpeller.h @@ -0,0 +1,35 @@ +// -*- C++ -*- +/** + * \file AppleSpeller.h + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stephan Witt + * + * Full author contact details are available in file CREDITS. + */ + +#ifndef LYX_SUPPORT_SPELLCHECK_H +#define LYX_SUPPORT_SPELLCHECK_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct AppleSpellerRec * AppleSpeller ; + +AppleSpeller newAppleSpeller(void); +void freeAppleSpeller(AppleSpeller speller); + +int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +void ignoreAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +size_t makeSuggestionAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +const char * getSuggestionAppleSpeller(AppleSpeller speller, size_t pos); +void learnAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/src/support/AppleSpeller.m b/src/support/AppleSpeller.m new file mode 100644 index 0000000000..6530e53dd4 --- /dev/null +++ b/src/support/AppleSpeller.m @@ -0,0 +1,197 @@ +/** + * \file AppleSpeller.m + * This file is part of LyX, the document processor. + * Licence details can be found in the file COPYING. + * + * \author Stephan Witt + * + * Full author contact details are available in file CREDITS. + */ + +#include +#include + +#include "support/AppleSpeller.h" + +typedef struct AppleSpellerRec { + NSSpellChecker * checker; +#if defined(LYX_PLATFORM_DARWIN10) && (LYX_PLATFORM_DARWIN10 >= 5) + NSInteger doctag; +#else + int doctag; +#endif + char ** suggestions; + size_t numsug; +} AppleSpellerRec ; + + +static void freeSuggestionsAppleSpeller(AppleSpeller speller) +{ + if (speller->suggestions) { + while (speller->numsug--) { + free(speller->suggestions[speller->numsug]); + } + free(speller->suggestions); + speller->suggestions = 0; + } +} + + +AppleSpeller newAppleSpeller(void) +{ + AppleSpeller speller = calloc(1, sizeof(AppleSpellerRec)); + speller->checker = [NSSpellChecker sharedSpellChecker]; + speller->suggestions = 0; + speller->doctag = [NSSpellChecker uniqueSpellDocumentTag]; + return speller; +} + + +void freeAppleSpeller(AppleSpeller speller) +{ + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + + freeSuggestionsAppleSpeller(speller); + [speller->checker closeSpellDocumentWithTag:speller->doctag]; + + [pool release]; + + free(speller); +} + + +static NSString * toString(AppleSpeller speller, const char * word) +{ + return [[NSString alloc] initWithBytes:word length:strlen(word) encoding:NSUTF8StringEncoding]; +} + + +int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +{ + if (!speller->checker || !lang || !word) + return 0; + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#if defined(LYX_PLATFORM_DARWIN10) && (LYX_PLATFORM_DARWIN10 >= 5) + NSInteger wordcount; +#else + int wordcount; +#endif + NSString * word_ = toString(speller, word); + NSString * lang_ = toString(speller, lang); + + NSRange result = [speller->checker + checkSpellingOfString:word_ + startingAt:0 + language:lang_ + wrap:(BOOL)NO + inSpellDocumentWithTag:speller->doctag + wordCount:&wordcount]; + + [word_ release]; + [lang_ release]; + [pool release]; + + return (result.length ? 0 : 1); +} + + +void ignoreAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +{ + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * word_ = toString(speller, word); + + [speller->checker ignoreWord:word_ inSpellDocumentWithTag:(speller->doctag)]; + + [word_ release]; + [pool release]; +} + + +size_t makeSuggestionAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +{ + if (!speller->checker || !word || !lang) + return 0; + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * word_ = toString(speller, word); + NSString * lang_ = toString(speller, lang); + +#if defined(LYX_PLATFORM_DARWIN10) && (LYX_PLATFORM_DARWIN10 >= 6) + // Mac OS X 10.6 only + NSInteger slen = [word_ length]; + NSRange range = { 0, slen }; + NSArray * result = [speller->checker guessesForWordRange:range + inString:word_ + language:lang_ + inSpellDocumentWithTag:speller->doctag]; +#else + [speller->checker setLanguage:lang_]; + NSArray * result = [speller->checker guessesForWord:word_]; +#endif + + [word_ release]; + [lang_ release]; + + freeSuggestionsAppleSpeller(speller); + + speller->numsug = [result count]; + if (speller->numsug) { + speller->suggestions = calloc(speller->numsug + 1, sizeof(char *)); + if (speller->suggestions) { + size_t i; + for (i = 0; i < speller->numsug; i++) { + NSString * str = [result objectAtIndex:i]; + speller->suggestions[i] = strdup([str UTF8String]); + } + speller->suggestions[speller->numsug] = 0; + } + } + [pool release]; + return speller->numsug; +} + + +const char * getSuggestionAppleSpeller(AppleSpeller speller, size_t pos) +{ + const char * result = 0; + if (pos < speller->numsug && speller->suggestions) { + result = speller->suggestions[pos] ; + } + return result; +} + + +void learnAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +{ +#if defined(LYX_PLATFORM_DARWIN10) && (LYX_PLATFORM_DARWIN10 >= 5) + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * word_ = toString(speller, word); + + [speller->checker learnWord:word_]; + + [word_ release]; + [pool release]; +#endif +} + + +int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang) +{ + BOOL result = NO; +#if defined(LYX_PLATFORM_DARWIN10) && (LYX_PLATFORM_DARWIN10 >= 5) + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * lang_ = toString(speller, lang); + NSArray * languages = [speller->checker availableLanguages]; + + for (NSString *element in languages) { + result = [element isEqualToString:lang_] || [lang_ hasPrefix:element]; + if (result) break; + } + + [lang_ release]; + [pool release]; +#endif + + return result ? 1 : 0; +} diff --git a/src/support/Makefile.am b/src/support/Makefile.am index 6d8821e855..3c67ecdfa7 100644 --- a/src/support/Makefile.am +++ b/src/support/Makefile.am @@ -103,8 +103,16 @@ liblyxsupport_a_SOURCES = \ mythes/mythes.hxx \ mythes/license.readme +#if INSTALL_MACOSX +#liblyxsupport_a_SOURCES += \ +# AppleSpellChecker.h \ +# AppleSpellChecker.mm +#endif + if INSTALL_MACOSX liblyxsupport_a_SOURCES += \ + AppleSpeller.h \ + AppleSpeller.m \ linkback/LinkBack.h \ linkback/LinkBack.m \ linkback/LinkBackProxy.h \