Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // This file implements the interface defined in spellchecker_platform_engine.h
      6 // for the OS X platform.
      7 
      8 #include "chrome/browser/spellchecker_platform_engine.h"
      9 
     10 #import <Cocoa/Cocoa.h>
     11 
     12 #include "base/logging.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/task.h"
     15 #include "base/time.h"
     16 #include "base/sys_string_conversions.h"
     17 #include "chrome/common/spellcheck_common.h"
     18 #include "chrome/common/spellcheck_messages.h"
     19 #include "content/browser/browser_thread.h"
     20 #include "content/browser/browser_message_filter.h"
     21 #include "third_party/WebKit/Source/WebKit/chromium/public/WebTextCheckingResult.h"
     22 
     23 using base::TimeTicks;
     24 namespace {
     25 // The number of characters in the first part of the language code.
     26 const unsigned int kShortLanguageCodeSize = 2;
     27 
     28 // TextCheckingTask is reserved for spell checking against large size
     29 // of text, which possible contains multiple paragrpahs.  Checking
     30 // that size of text might take time, and should be done as a task on
     31 // the FILE thread.
     32 //
     33 // The result of the check is returned back as a
     34 // SpellCheckMsg_RespondTextCheck message.
     35 class TextCheckingTask : public Task {
     36  public:
     37   TextCheckingTask(BrowserMessageFilter* destination,
     38                    int route_id,
     39                    int identifier,
     40                    const string16& text,
     41                    int document_tag)
     42       : destination_(destination),
     43         route_id_(route_id),
     44         identifier_(identifier),
     45         text_(text),
     46         document_tag_(document_tag) {
     47   }
     48 
     49   virtual void Run() {
     50     // TODO(morrita): Use [NSSpellChecker requestCheckingOfString]
     51     // when the build target goes up to 10.6
     52     std::vector<WebKit::WebTextCheckingResult> check_results;
     53     NSString* text_to_check = base::SysUTF16ToNSString(text_);
     54     size_t starting_at = 0;
     55     while (starting_at < text_.size()) {
     56       NSRange range = [[NSSpellChecker sharedSpellChecker]
     57                          checkSpellingOfString:text_to_check
     58                                     startingAt:starting_at
     59                                       language:nil
     60                                           wrap:NO
     61                         inSpellDocumentWithTag:document_tag_
     62                                      wordCount:NULL];
     63       if (range.length == 0)
     64         break;
     65       check_results.push_back(WebKit::WebTextCheckingResult(
     66           WebKit::WebTextCheckingResult::ErrorSpelling,
     67           range.location,
     68           range.length));
     69       starting_at = range.location + range.length;
     70     }
     71 
     72     BrowserThread::PostTask(
     73         BrowserThread::IO,
     74         FROM_HERE,
     75         NewRunnableMethod(
     76             destination_.get(),
     77             &BrowserMessageFilter::Send,
     78             new SpellCheckMsg_RespondTextCheck(route_id_,
     79                                                identifier_,
     80                                                document_tag_,
     81                                                check_results)));
     82   }
     83 
     84  private:
     85   scoped_refptr<BrowserMessageFilter> destination_;
     86   int route_id_;
     87   int identifier_;
     88   string16 text_;
     89   int document_tag_;
     90 };
     91 
     92 // A private utility function to convert hunspell language codes to OS X
     93 // language codes.
     94 NSString* ConvertLanguageCodeToMac(const std::string& hunspell_lang_code) {
     95   NSString* whole_code = base::SysUTF8ToNSString(hunspell_lang_code);
     96 
     97   if ([whole_code length] > kShortLanguageCodeSize) {
     98     NSString* lang_code = [whole_code
     99                            substringToIndex:kShortLanguageCodeSize];
    100     // Add 1 here to skip the underscore.
    101     NSString* region_code = [whole_code
    102                              substringFromIndex:(kShortLanguageCodeSize + 1)];
    103 
    104     // Check for the special case of en-US and pt-PT, since OS X lists these
    105     // as just en and pt respectively.
    106     // TODO(pwicks): Find out if there are other special cases for languages
    107     // not installed on the system by default. Are there others like pt-PT?
    108     if (([lang_code isEqualToString:@"en"] &&
    109        [region_code isEqualToString:@"US"]) ||
    110         ([lang_code isEqualToString:@"pt"] &&
    111        [region_code isEqualToString:@"PT"])) {
    112       return lang_code;
    113     }
    114 
    115     // Otherwise, just build a string that uses an underscore instead of a
    116     // dash between the language and the region code, since this is the
    117     // format that OS X uses.
    118     NSString* os_x_language =
    119         [NSString stringWithFormat:@"%@_%@", lang_code, region_code];
    120     return os_x_language;
    121   } else {
    122     // Special case for Polish.
    123     if ([whole_code isEqualToString:@"pl"]) {
    124       return @"pl_PL";
    125     }
    126     // This is just a language code with the same format as OS X
    127     // language code.
    128     return whole_code;
    129   }
    130 }
    131 
    132 std::string ConvertLanguageCodeFromMac(NSString* lang_code) {
    133   // TODO(pwicks):figure out what to do about Multilingual
    134   // Guards for strange cases.
    135   if ([lang_code isEqualToString:@"en"]) return std::string("en-US");
    136   if ([lang_code isEqualToString:@"pt"]) return std::string("pt-PT");
    137   if ([lang_code isEqualToString:@"pl_PL"]) return std::string("pl");
    138 
    139   if ([lang_code length] > kShortLanguageCodeSize &&
    140       [lang_code characterAtIndex:kShortLanguageCodeSize] == '_') {
    141     return base::SysNSStringToUTF8([NSString stringWithFormat:@"%@-%@",
    142                 [lang_code substringToIndex:kShortLanguageCodeSize],
    143                 [lang_code substringFromIndex:(kShortLanguageCodeSize + 1)]]);
    144   }
    145   return base::SysNSStringToUTF8(lang_code);
    146 }
    147 
    148 } // namespace
    149 
    150 namespace SpellCheckerPlatform {
    151 
    152 void GetAvailableLanguages(std::vector<std::string>* spellcheck_languages) {
    153   NSArray* availableLanguages = [[NSSpellChecker sharedSpellChecker]
    154                         availableLanguages];
    155   for (NSString* lang_code in availableLanguages) {
    156     spellcheck_languages->push_back(
    157               ConvertLanguageCodeFromMac(lang_code));
    158   }
    159 }
    160 
    161 bool SpellCheckerAvailable() {
    162   // If this file was compiled, then we know that we are on OS X 10.5 at least
    163   // and can safely return true here.
    164   return true;
    165 }
    166 
    167 bool SpellCheckerProvidesPanel() {
    168   // OS X has a Spelling Panel, so we can return true here.
    169   return true;
    170 }
    171 
    172 bool SpellingPanelVisible() {
    173   // This should only be called from the main thread.
    174   DCHECK([NSThread currentThread] == [NSThread mainThread]);
    175   return [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible];
    176 }
    177 
    178 void ShowSpellingPanel(bool show) {
    179   if (show) {
    180     [[[NSSpellChecker sharedSpellChecker] spellingPanel]
    181         performSelectorOnMainThread:@selector(makeKeyAndOrderFront:)
    182                          withObject:nil
    183                       waitUntilDone:YES];
    184   } else {
    185     [[[NSSpellChecker sharedSpellChecker] spellingPanel]
    186         performSelectorOnMainThread:@selector(close)
    187                          withObject:nil
    188                       waitUntilDone:YES];
    189   }
    190 }
    191 
    192 void UpdateSpellingPanelWithMisspelledWord(const string16& word) {
    193   NSString * word_to_display = base::SysUTF16ToNSString(word);
    194   [[NSSpellChecker sharedSpellChecker]
    195       performSelectorOnMainThread:
    196         @selector(updateSpellingPanelWithMisspelledWord:)
    197                        withObject:word_to_display
    198                     waitUntilDone:YES];
    199 }
    200 
    201 void Init() {
    202 }
    203 
    204 bool PlatformSupportsLanguage(const std::string& current_language) {
    205   // First, convert the language to an OS X language code.
    206   NSString* mac_lang_code = ConvertLanguageCodeToMac(current_language);
    207 
    208   // Then grab the languages available.
    209   NSArray* availableLanguages;
    210   availableLanguages = [[NSSpellChecker sharedSpellChecker]
    211                         availableLanguages];
    212 
    213   // Return true if the given language is supported by OS X.
    214   return [availableLanguages containsObject:mac_lang_code];
    215 }
    216 
    217 void SetLanguage(const std::string& lang_to_set) {
    218   NSString* NS_lang_to_set = ConvertLanguageCodeToMac(lang_to_set);
    219   [[NSSpellChecker sharedSpellChecker] setLanguage:NS_lang_to_set];
    220 }
    221 
    222 static int last_seen_tag_;
    223 
    224 bool CheckSpelling(const string16& word_to_check, int tag) {
    225   last_seen_tag_ = tag;
    226 
    227   // [[NSSpellChecker sharedSpellChecker] checkSpellingOfString] returns an
    228   // NSRange that we can look at to determine if a word is misspelled.
    229   NSRange spell_range = {0,0};
    230 
    231   // Convert the word to an NSString.
    232   NSString* NS_word_to_check = base::SysUTF16ToNSString(word_to_check);
    233   // Check the spelling, starting at the beginning of the word.
    234   spell_range = [[NSSpellChecker sharedSpellChecker]
    235                   checkSpellingOfString:NS_word_to_check startingAt:0
    236                   language:nil wrap:NO inSpellDocumentWithTag:tag
    237                   wordCount:NULL];
    238 
    239   // If the length of the misspelled word == 0,
    240   // then there is no misspelled word.
    241   bool word_correct = (spell_range.length == 0);
    242   return word_correct;
    243 }
    244 
    245 void FillSuggestionList(const string16& wrong_word,
    246                         std::vector<string16>* optional_suggestions) {
    247   NSString* NS_wrong_word = base::SysUTF16ToNSString(wrong_word);
    248   TimeTicks begin_time = TimeTicks::Now();
    249   // The suggested words for |wrong_word|.
    250   NSArray* guesses =
    251       [[NSSpellChecker sharedSpellChecker] guessesForWord:NS_wrong_word];
    252   DHISTOGRAM_TIMES("Spellcheck.SuggestTime",
    253                    TimeTicks::Now() - begin_time);
    254 
    255   for (int i = 0; i < static_cast<int>([guesses count]); i++) {
    256     if (i < SpellCheckCommon::kMaxSuggestions) {
    257       optional_suggestions->push_back(base::SysNSStringToUTF16(
    258                                       [guesses objectAtIndex:i]));
    259     }
    260   }
    261 }
    262 
    263 void AddWord(const string16& word) {
    264     NSString* word_to_add = base::SysUTF16ToNSString(word);
    265   [[NSSpellChecker sharedSpellChecker] learnWord:word_to_add];
    266 }
    267 
    268 void RemoveWord(const string16& word) {
    269   NSString *word_to_remove = base::SysUTF16ToNSString(word);
    270   [[NSSpellChecker sharedSpellChecker] unlearnWord:word_to_remove];
    271 }
    272 
    273 int GetDocumentTag() {
    274   NSInteger doc_tag = [NSSpellChecker uniqueSpellDocumentTag];
    275   return static_cast<int>(doc_tag);
    276 }
    277 
    278 void IgnoreWord(const string16& word) {
    279   [[NSSpellChecker sharedSpellChecker] ignoreWord:base::SysUTF16ToNSString(word)
    280                            inSpellDocumentWithTag:last_seen_tag_];
    281 }
    282 
    283 void CloseDocumentWithTag(int tag) {
    284   [[NSSpellChecker sharedSpellChecker]
    285     closeSpellDocumentWithTag:static_cast<NSInteger>(tag)];
    286 }
    287 
    288 void RequestTextCheck(int route_id,
    289                       int identifier,
    290                       int document_tag,
    291                       const string16& text, BrowserMessageFilter* destination) {
    292   BrowserThread::PostTask(
    293       BrowserThread::FILE, FROM_HERE,
    294       new TextCheckingTask(
    295           destination, route_id, identifier, text, document_tag));
    296 }
    297 
    298 }  // namespace SpellCheckerPlatform
    299