diff --git a/libaegisub/Makefile b/libaegisub/Makefile index 9e464c083..54c2ad50c 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -56,7 +56,7 @@ SRC += \ unix/util.cpp ifeq (yes, $(BUILD_DARWIN)) -SRC += osx/util.mm osx/dispatch.mm +SRC += osx/util.mm osx/dispatch.mm osx/spellchecker.mm else SRC += common/dispatch.cpp endif diff --git a/libaegisub/osx/spellchecker.mm b/libaegisub/osx/spellchecker.mm new file mode 100644 index 000000000..ea7cb3d87 --- /dev/null +++ b/libaegisub/osx/spellchecker.mm @@ -0,0 +1,109 @@ +// Copyright (c) 2014, Thomas Goyne +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// Aegisub Project http://www.aegisub.org/ + +#import + +#import +#import + +#import + +namespace { +std::vector array_to_vector(NSArray *arr) { + std::vector ret; + ret.reserve(arr.count); + for (NSString *str in arr) + ret.push_back(str.UTF8String); + return ret; +} + +struct releaser { + void operator()(id obj) { [obj release]; }; +}; + +template +using objc_ptr = std::unique_ptr; + +class CocoaSpellChecker final : public agi::SpellChecker { + objc_ptr language; + agi::signal::Connection lang_listener; + + void OnLanguageChanged(agi::OptionValue const& opt) { + language.reset([[NSString alloc] initWithCString:opt.GetString().c_str() + encoding:NSUTF8StringEncoding]); + } + +public: + CocoaSpellChecker(agi::OptionValue *opt) + : lang_listener(opt->Subscribe(&CocoaSpellChecker::OnLanguageChanged, this)) + { + OnLanguageChanged(*opt); + } + + std::vector GetLanguageList() override { + return array_to_vector([[NSSpellChecker sharedSpellChecker] availableLanguages]); + } + + bool CheckWord(std::string const& word) override { + auto str = [NSString stringWithUTF8String:word.c_str()]; + auto range = [NSSpellChecker.sharedSpellChecker checkSpellingOfString:str + startingAt:0 + language:language.get() + wrap:NO + inSpellDocumentWithTag:0 + wordCount:nullptr]; + return range.location == NSNotFound; + } + + std::vector GetSuggestions(std::string const& word) override { + auto str = [NSString stringWithUTF8String:word.c_str()]; + auto range = [NSSpellChecker.sharedSpellChecker checkSpellingOfString:str + startingAt:0 + language:language.get() + wrap:NO + inSpellDocumentWithTag:0 + wordCount:nullptr]; + return array_to_vector([NSSpellChecker.sharedSpellChecker guessesForWordRange:range + inString:str + language:language.get() + inSpellDocumentWithTag:0]); + } + + bool CanAddWord(std::string const&) override { + return true; + } + + bool CanRemoveWord(std::string const& str) override { + return [NSSpellChecker.sharedSpellChecker hasLearnedWord:[NSString stringWithUTF8String:str.c_str()]]; + } + + void AddWord(std::string const& word) override { + NSSpellChecker.sharedSpellChecker.language = language.get(); + [NSSpellChecker.sharedSpellChecker learnWord:[NSString stringWithUTF8String:word.c_str()]]; + } + + void RemoveWord(std::string const& word) override { + NSSpellChecker.sharedSpellChecker.language = language.get(); + [NSSpellChecker.sharedSpellChecker unlearnWord:[NSString stringWithUTF8String:word.c_str()]]; + } +}; +} + +namespace agi { +std::unique_ptr CreateCocoaSpellChecker(OptionValue *opt) { + return std::unique_ptr(new CocoaSpellChecker(opt)); +} +} diff --git a/src/spellchecker.cpp b/src/spellchecker.cpp index 5b1083905..4e328ab22 100644 --- a/src/spellchecker.cpp +++ b/src/spellchecker.cpp @@ -17,10 +17,20 @@ #include "include/aegisub/spellchecker.h" #include "spellchecker_hunspell.h" +#include "options.h" + #include +#include + +namespace agi { +class OptionValue; +std::unique_ptr CreateCocoaSpellChecker(OptionValue *opt); +} std::unique_ptr SpellCheckerFactory::GetSpellChecker() { -#ifdef WITH_HUNSPELL +#ifdef __APPLE__ + return agi::CreateCocoaSpellChecker(OPT_SET("Tool/Spell Checker/Language")); +#elif defined(WITH_HUNSPELL) return agi::make_unique(); #else return {};