diff --git a/RELEASE-NOTES b/RELEASE-NOTES index 3cf7179e2c..dfe50f3d2b 100644 --- a/RELEASE-NOTES +++ b/RELEASE-NOTES @@ -84,6 +84,8 @@ The following new LyX functions have been introduced: - LFUN_SPELLING_IGNORE ("spelling-ignore"). +- LFUN_SPELLING_REMOVE ("spelling-remove"). + - LFUN_PREVIEW_INSERT ("preview-insert"). - LFUN_FORWARD_SEARCH ("forward-search"). diff --git a/src/AppleSpellChecker.cpp b/src/AppleSpellChecker.cpp index 2beadb2eae..003b8051ec 100644 --- a/src/AppleSpellChecker.cpp +++ b/src/AppleSpellChecker.cpp @@ -29,6 +29,9 @@ struct AppleSpellChecker::Private ~Private(); + SpellChecker::Result toResult(SpellCheckResult status); + string toString(SpellCheckResult status); + /// the speller AppleSpeller speller; }; @@ -58,11 +61,26 @@ AppleSpellChecker::~AppleSpellChecker() } +SpellChecker::Result AppleSpellChecker::Private::toResult(SpellCheckResult status) +{ + return status == SPELL_CHECK_FAILED ? UNKNOWN_WORD : + status == SPELL_CHECK_LEARNED ? LEARNED_WORD : WORD_OK ; +} + + +string AppleSpellChecker::Private::toString(SpellCheckResult status) +{ + return status == SPELL_CHECK_FAILED ? "FAILED" : + status == SPELL_CHECK_LEARNED ? "LEARNED" : "OK"; +} + + 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; + SpellCheckResult result = checkAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); + LYXERR(Debug::GUI, "spellCheck: \"" << word.word() << "\" = " << d->toString(result)) ; + return d->toResult(result); } @@ -71,6 +89,16 @@ void AppleSpellChecker::insert(WordLangTuple const & word) { string const word_str = to_utf8(word.word()); learnAppleSpeller(d->speller, word_str.c_str()); + LYXERR(Debug::GUI, "learn word: \"" << word.word() << "\"") ; +} + + +// remove from personal dictionary +void AppleSpellChecker::remove(WordLangTuple const & word) +{ + string const word_str = to_utf8(word.word()); + unlearnAppleSpeller(d->speller, word_str.c_str()); + LYXERR(Debug::GUI, "unlearn word: \"" << word.word() << "\"") ; } diff --git a/src/AppleSpellChecker.h b/src/AppleSpellChecker.h index 5a6657520c..e406325a70 100644 --- a/src/AppleSpellChecker.h +++ b/src/AppleSpellChecker.h @@ -27,6 +27,7 @@ public: enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &); void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); diff --git a/src/AspellChecker.cpp b/src/AspellChecker.cpp index 36e8e04c8f..06cc6bf49a 100644 --- a/src/AspellChecker.cpp +++ b/src/AspellChecker.cpp @@ -266,17 +266,17 @@ SpellChecker::Result AspellChecker::check(WordLangTuple const & word) d->speller(word.lang()->code(), word.lang()->variety()); if (!m) - return OK; + return WORD_OK; if (word.word().empty()) // MSVC compiled Aspell doesn't like it. - return OK; + return WORD_OK; string const word_str = to_utf8(word.word()); int const word_ok = aspell_speller_check(m, word_str.c_str(), -1); LASSERT(word_ok != -1, /**/); - return (word_ok) ? OK : UNKNOWN_WORD; + return (word_ok) ? WORD_OK : UNKNOWN_WORD; } diff --git a/src/AspellChecker.h b/src/AspellChecker.h index 73a685a457..6bef6a854a 100644 --- a/src/AspellChecker.h +++ b/src/AspellChecker.h @@ -28,6 +28,7 @@ public: enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); diff --git a/src/Buffer.cpp b/src/Buffer.cpp index ba77061463..9063b0ed23 100644 --- a/src/Buffer.cpp +++ b/src/Buffer.cpp @@ -3995,7 +3995,8 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to, if (from == end) break; to = from; - if (from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions)) { + SpellChecker::Result res = from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions); + if (SpellChecker::misspelled(res)) { word_lang = wl; break; } diff --git a/src/EnchantChecker.cpp b/src/EnchantChecker.cpp index 3f3b138f58..793bc16fb9 100644 --- a/src/EnchantChecker.cpp +++ b/src/EnchantChecker.cpp @@ -113,12 +113,12 @@ SpellChecker::Result EnchantChecker::check(WordLangTuple const & word) enchant::Dict * m = d->speller(word.lang()->code()); if (!m) - return OK; + return WORD_OK; string utf8word = to_utf8(word.word()); if (m->check(utf8word)) - return OK; + return WORD_OK; return UNKNOWN_WORD; } diff --git a/src/EnchantChecker.h b/src/EnchantChecker.h index 94f7e20b38..5016ed3d7b 100644 --- a/src/EnchantChecker.h +++ b/src/EnchantChecker.h @@ -34,6 +34,7 @@ public: enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); diff --git a/src/FuncCode.h b/src/FuncCode.h index 4b5d94a847..8867ce1c7a 100644 --- a/src/FuncCode.h +++ b/src/FuncCode.h @@ -444,6 +444,7 @@ enum FuncCode LFUN_SPELLING_ADD, // spitz 20100118 LFUN_SPELLING_IGNORE, // spitz 20100118 // 345 + LFUN_SPELLING_REMOVE, // switt 20100728 LFUN_PREVIEW_INSERT, // vfr, 20100328 LFUN_FORWARD_SEARCH, LFUN_INSET_COPY_AS, // vfr, 20100419 diff --git a/src/HunspellChecker.cpp b/src/HunspellChecker.cpp index f5b9d0c61a..f3b8c25edb 100644 --- a/src/HunspellChecker.cpp +++ b/src/HunspellChecker.cpp @@ -207,18 +207,18 @@ HunspellChecker::~HunspellChecker() SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl) { if (d->isIgnored(wl)) - return OK; + return WORD_OK; Hunspell * h = d->speller(wl.lang()->code()); if (!h) - return OK; + return WORD_OK; int info; string const encoding = h->get_dic_encoding(); string const word_to_check = to_iconv_encoding(wl.word(), encoding); if (h->spell(word_to_check.c_str(), &info)) - return OK; + return WORD_OK; if (info & SPELL_COMPOUND) { // FIXME: What to do with that? diff --git a/src/HunspellChecker.h b/src/HunspellChecker.h index ccb5257be5..f5daaf3c43 100644 --- a/src/HunspellChecker.h +++ b/src/HunspellChecker.h @@ -28,6 +28,7 @@ public: enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp index a731ead1ce..7ec036dec4 100644 --- a/src/LyXAction.cpp +++ b/src/LyXAction.cpp @@ -1015,6 +1015,17 @@ void LyXAction::init() * \endvar */ { LFUN_SPELLING_IGNORE, "spelling-ignore", ReadOnly, Edit }, +/*! + * \var lyx::FuncCode lyx::LFUN_SPELLING_REMOVE + * \li Action: Remove the word under the cursor from the respective + * spell checker dictionary. + * \li Syntax: spelling-remove [] [] + * \li Params: : word to remove + : language name (see file languages) + * \li Origin: SWitt, 28 July 2010 + * \endvar + */ + { LFUN_SPELLING_REMOVE, "spelling-remove", ReadOnly, Edit }, /*! * \var lyx::FuncCode lyx::LFUN_THESAURUS_ENTRY * \li Action: Look up thesaurus entries with respect to the word under the cursor. diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 150c28d6a9..cfbb73e1b6 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -3169,19 +3169,19 @@ void Paragraph::updateWords() } -bool Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, +SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, docstring_list & suggestions, bool do_suggestion) const { SpellChecker * speller = theSpellChecker(); if (!speller) - return false; + return SpellChecker::WORD_OK; if (!d->layout_->spellcheck || !inInset().allowSpellCheck()) - return false; + return SpellChecker::WORD_OK; locateWord(from, to, WHOLE_WORD); if (from == to || from >= pos_type(d->text_.size())) - return false; + return SpellChecker::WORD_OK; docstring word = asString(from, to, AS_STR_INSETS); // Ignore words with digits @@ -3200,29 +3200,19 @@ bool Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, } wl = WordLangTuple(word, lang); SpellChecker::Result res = ignored ? - SpellChecker::OK : speller->check(wl); -#if 0 -// FIXME: the code below makes aspell abort if a word in an unknown -// language is checked. - // Just ignore any error that the spellchecker reports. - // FIXME: we should through out an exception and catch it in the GUI to - // display the error. - if (!speller->error().empty()) - return false; -#endif + SpellChecker::WORD_OK : speller->check(wl); - bool const misspelled = res != SpellChecker::OK - && res != SpellChecker::IGNORED_WORD; + bool const misspelled_ = SpellChecker::misspelled(res) ; if (lyxrc.spellcheck_continuously) - d->fontlist_.setMisspelled(from, to, misspelled); + d->fontlist_.setMisspelled(from, to, misspelled_); - if (misspelled && do_suggestion) + if (misspelled_ && do_suggestion) speller->suggest(wl, suggestions); else suggestions.clear(); - return misspelled; + return res; } @@ -3232,7 +3222,8 @@ bool Paragraph::isMisspelled(pos_type pos) const pos_type to = pos; WordLangTuple wl; docstring_list suggestions; - return spellCheck(from, to, wl, suggestions, false); + SpellChecker::Result res = spellCheck(from, to, wl, suggestions, false); + return SpellChecker::misspelled(res) ; } diff --git a/src/Paragraph.h b/src/Paragraph.h index 65c0190d62..26389c6ced 100644 --- a/src/Paragraph.h +++ b/src/Paragraph.h @@ -17,6 +17,7 @@ #define PARAGRAPH_H #include "FontEnums.h" +#include "SpellChecker.h" #include "insets/InsetCode.h" @@ -422,8 +423,8 @@ public: /// Spellcheck word at position \p from and fill in found misspelled word /// and \p suggestions if \p do_suggestion is true. - /// \return true if pointed word is misspelled. - bool spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, + /// \return result from spell checker, SpellChecker::UNKNOWN_WORD when misspelled. + SpellChecker::Result spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, docstring_list & suggestions, bool do_suggestion = true) const; /// Spellcheck word at position \p pos. diff --git a/src/SpellChecker.h b/src/SpellChecker.h index bac19d1161..58a1fb2032 100644 --- a/src/SpellChecker.h +++ b/src/SpellChecker.h @@ -32,19 +32,27 @@ public: /// the result from checking a single word enum Result { /// word is correct - OK = 1, + WORD_OK = 1, /// root of given word was found - ROOT, + ROOT_FOUND, /// word found through compound formation COMPOUND_WORD, /// word not found UNKNOWN_WORD, /// number of other ignored "word" - IGNORED_WORD + IGNORED_WORD, + /// number of personal dictionary "word" + LEARNED_WORD }; virtual ~SpellChecker() {} + /// does the spell check failed + static bool misspelled(Result res) { + return res != SpellChecker::WORD_OK + && res != SpellChecker::IGNORED_WORD + && res != SpellChecker::LEARNED_WORD; } + /// check the given word of the given lang code and return the result virtual enum Result check(WordLangTuple const &) = 0; @@ -54,6 +62,9 @@ public: /// insert the given word into the personal dictionary virtual void insert(WordLangTuple const &) = 0; + /// remove the given word from the personal dictionary + virtual void remove(WordLangTuple const &) = 0; + /// accept the given word temporarily virtual void accept(WordLangTuple const &) = 0; diff --git a/src/Text3.cpp b/src/Text3.cpp index a871a62d1b..40fff5dbe5 100644 --- a/src/Text3.cpp +++ b/src/Text3.cpp @@ -2044,6 +2044,25 @@ void Text::dispatch(Cursor & cur, FuncRequest & cmd) break; } + case LFUN_SPELLING_REMOVE: { + docstring word = from_utf8(cmd.getArg(0)); + Language * lang; + if (word.empty()) { + word = cur.selectionAsString(false); + // FIXME + if (word.size() > 100 || word.empty()) { + // Get word or selection + selectWordWhenUnderCursor(cur, WHOLE_WORD); + word = cur.selectionAsString(false); + } + lang = const_cast(cur.getFont().language()); + } else + lang = const_cast(languages.getLanguage(cmd.getArg(1))); + WordLangTuple wl(word, lang); + theSpellChecker()->remove(wl); + break; + } + case LFUN_PARAGRAPH_PARAMS_APPLY: { // Given data, an encoding of the ParagraphParameters // generated in the Paragraph dialog, this function sets @@ -2591,6 +2610,7 @@ bool Text::getStatus(Cursor & cur, FuncRequest const & cmd, case LFUN_SPELLING_ADD: case LFUN_SPELLING_IGNORE: + case LFUN_SPELLING_REMOVE: enable = theSpellChecker(); break; diff --git a/src/frontends/qt4/Menus.cpp b/src/frontends/qt4/Menus.cpp index 5e4665499e..2abf282e7c 100644 --- a/src/frontends/qt4/Menus.cpp +++ b/src/frontends/qt4/Menus.cpp @@ -46,6 +46,7 @@ #include "Paragraph.h" #include "ParIterator.h" #include "Session.h" +#include "SpellChecker.h" #include "TextClass.h" #include "TocBackend.h" #include "Toolbars.h" @@ -730,39 +731,55 @@ void MenuDefinition::expandGraphicsGroups(BufferView const * bv) void MenuDefinition::expandSpellingSuggestions(BufferView const * bv) { - if (!bv || !lyxrc.spellcheck_continuously) + if (!bv) return; WordLangTuple wl; docstring_list suggestions; pos_type from = bv->cursor().pos(); pos_type to = from; Paragraph const & par = bv->cursor().paragraph(); - if (!par.spellCheck(from, to, wl, suggestions)) - return; - LYXERR(Debug::GUI, "Misspelled Word! Suggested Words = "); - size_t i = 0; - MenuItem item(MenuItem::Submenu, qt_("More Spelling Suggestions")); - item.setSubmenu(MenuDefinition(qt_("More Spelling Suggestions"))); - for (; i != suggestions.size(); ++i) { - docstring const & suggestion = suggestions[i]; - LYXERR(Debug::GUI, suggestion); - MenuItem w(MenuItem::Command, toqstr(suggestion), - FuncRequest(LFUN_WORD_REPLACE, suggestion)); - if (i < 10) - add(w); - else - item.submenu().add(w); + SpellChecker::Result res = par.spellCheck(from, to, wl, suggestions); + switch (res) { + case SpellChecker::UNKNOWN_WORD: + if (lyxrc.spellcheck_continuously) { + LYXERR(Debug::GUI, "Misspelled Word! Suggested Words = "); + size_t i = 0; + MenuItem item(MenuItem::Submenu, qt_("More Spelling Suggestions")); + item.setSubmenu(MenuDefinition(qt_("More Spelling Suggestions"))); + for (; i != suggestions.size(); ++i) { + docstring const & suggestion = suggestions[i]; + LYXERR(Debug::GUI, suggestion); + MenuItem w(MenuItem::Command, toqstr(suggestion), + FuncRequest(LFUN_WORD_REPLACE, suggestion)); + if (i < 10) + add(w); + else + item.submenu().add(w); + } + if (i >= 10) + add(item); + if (i > 0) + add(MenuItem(MenuItem::Separator)); + docstring const arg = wl.word() + " " + from_ascii(wl.lang()->lang()); + add(MenuItem(MenuItem::Command, qt_("Add to personal dictionary|c"), + FuncRequest(LFUN_SPELLING_ADD, arg))); + add(MenuItem(MenuItem::Command, qt_("Ignore all|I"), + FuncRequest(LFUN_SPELLING_IGNORE, arg))); + } + break; + case SpellChecker::LEARNED_WORD: { + LYXERR(Debug::GUI, "Learned Word."); + docstring const arg = wl.word() + " " + from_ascii(wl.lang()->lang()); + add(MenuItem(MenuItem::Command, qt_("Remove from personal dictionary|r"), + FuncRequest(LFUN_SPELLING_REMOVE, arg))); + } + break; + case SpellChecker::WORD_OK: + case SpellChecker::COMPOUND_WORD: + case SpellChecker::ROOT_FOUND: + case SpellChecker::IGNORED_WORD: + break; } - if (i >= 10) - add(item); - if (i > 0) - add(MenuItem(MenuItem::Separator)); - docstring const arg = wl.word() + " " + from_ascii(wl.lang()->lang()); - add(MenuItem(MenuItem::Command, qt_("Add to personal dictionary|c"), - FuncRequest(LFUN_SPELLING_ADD, arg))); - add(MenuItem(MenuItem::Command, qt_("Ignore all|I"), - FuncRequest(LFUN_SPELLING_IGNORE, arg))); - } struct sortLanguageByName { diff --git a/src/support/AppleSpeller.h b/src/support/AppleSpeller.h index 3ff3ea19ad..3012249410 100644 --- a/src/support/AppleSpeller.h +++ b/src/support/AppleSpeller.h @@ -16,16 +16,24 @@ extern "C" { #endif +typedef enum SpellCheckResult { + SPELL_CHECK_FAILED, + SPELL_CHECK_OK, + SPELL_CHECK_IGNORED, + SPELL_CHECK_LEARNED +} SpellCheckResult ; + typedef struct AppleSpellerRec * AppleSpeller ; AppleSpeller newAppleSpeller(void); void freeAppleSpeller(AppleSpeller speller); -int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +SpellCheckResult checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang); void ignoreAppleSpeller(AppleSpeller speller, const char * word); 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); +void unlearnAppleSpeller(AppleSpeller speller, const char * word); int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang); #ifdef __cplusplus diff --git a/src/support/AppleSpeller.m b/src/support/AppleSpeller.m index 24e9cc5c82..37a361b453 100644 --- a/src/support/AppleSpeller.m +++ b/src/support/AppleSpeller.m @@ -68,16 +68,16 @@ static NSString * toString(const char * word) } -int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +SpellCheckResult checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang) { if (!speller->checker || !lang || !word) - return 0; + return SPELL_CHECK_FAILED; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * word_ = toString(word); NSString * lang_ = toString(lang); - NSRange result = [speller->checker + NSRange match = [speller->checker checkSpellingOfString:word_ startingAt:0 language:lang_ @@ -85,11 +85,17 @@ int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang inSpellDocumentWithTag:speller->doctag wordCount:NULL]; + SpellCheckResult result = match.length == 0 ? SPELL_CHECK_OK : SPELL_CHECK_FAILED; + if (result == SPELL_CHECK_OK && [NSSpellChecker instancesRespondToSelector:@selector(hasLearnedWord:)]) { + if ([speller->checker hasLearnedWord:word_]) + result = SPELL_CHECK_LEARNED; + } + [word_ release]; [lang_ release]; [pool release]; - return (result.length ? 0 : 1); + return result; } @@ -172,7 +178,7 @@ void learnAppleSpeller(AppleSpeller speller, const char * word) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * word_ = toString(word); - if ([NSSpellChecker instancesRespondToSelector:@selector(learnWord)]) + if ([NSSpellChecker instancesRespondToSelector:@selector(learnWord:)]) [speller->checker learnWord:word_]; [word_ release]; @@ -181,13 +187,29 @@ void learnAppleSpeller(AppleSpeller speller, const char * word) } + +void unlearnAppleSpeller(AppleSpeller speller, const char * word) +{ +#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * word_ = toString(word); + + if ([NSSpellChecker instancesRespondToSelector:@selector(unlearnWord:)]) + [speller->checker unlearnWord:word_]; + + [word_ release]; + [pool release]; +#endif +} + + int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang) { BOOL result = NO; #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * lang_ = toString(lang); - if ([NSSpellChecker instancesRespondToSelector:@selector(availableLanguages)]) { + if ([NSSpellChecker instancesRespondToSelector:@selector(availableLanguages:)]) { NSArray * languages = [speller->checker availableLanguages]; for (NSString *element in languages) {