• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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 #include "chrome/browser/chromeos/status/input_method_menu.h"
6 
7 #include <string>
8 #include <vector>
9 
10 #include "base/string_split.h"
11 #include "base/string_util.h"
12 #include "base/time.h"
13 #include "base/utf_string_conversions.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/chromeos/cros/cros_library.h"
16 #include "chrome/browser/chromeos/input_method/input_method_util.h"
17 #include "chrome/browser/chromeos/language_preferences.h"
18 #include "chrome/browser/metrics/user_metrics.h"
19 #include "chrome/browser/prefs/pref_service.h"
20 #include "chrome/common/pref_names.h"
21 #include "content/common/notification_service.h"
22 #include "grit/generated_resources.h"
23 #include "grit/theme_resources.h"
24 #include "ui/base/l10n/l10n_util.h"
25 #include "ui/base/resource/resource_bundle.h"
26 
27 // The language menu consists of 3 parts (in this order):
28 //
29 //   (1) input method names. The size of the list is always >= 1.
30 //   (2) input method properties. This list might be empty.
31 //   (3) "Customize language and input..." button.
32 //
33 // Example of the menu (Japanese):
34 //
35 // ============================== (border of the popup window)
36 // [ ] English                    (|index| in the following functions is 0)
37 // [*] Japanese
38 // [ ] Chinese (Simplified)
39 // ------------------------------ (separator)
40 // [*] Hiragana                   (index = 5, The property has 2 radio groups)
41 // [ ] Katakana
42 // [ ] HalfWidthKatakana
43 // [*] Roman
44 // [ ] Kana
45 // ------------------------------ (separator)
46 // Customize language and input...(index = 11)
47 // ============================== (border of the popup window)
48 //
49 // Example of the menu (Simplified Chinese):
50 //
51 // ============================== (border of the popup window)
52 // [ ] English
53 // [ ] Japanese
54 // [*] Chinese (Simplified)
55 // ------------------------------ (separator)
56 // Switch to full letter mode     (The property has 2 command buttons)
57 // Switch to half punctuation mode
58 // ------------------------------ (separator)
59 // Customize language and input...
60 // ============================== (border of the popup window)
61 //
62 
63 namespace {
64 
65 // Constants to specify the type of items in |model_|.
66 enum {
67   COMMAND_ID_INPUT_METHODS = 0,  // English, Chinese, Japanese, Arabic, ...
68   COMMAND_ID_IME_PROPERTIES,  // Hiragana, Katakana, ...
69   COMMAND_ID_CUSTOMIZE_LANGUAGE,  // "Customize language and input..." button.
70 };
71 
72 // A group ID for IME properties starts from 0. We use the huge value for the
73 // input method list to avoid conflict.
74 const int kRadioGroupLanguage = 1 << 16;
75 const int kRadioGroupNone = -1;
76 
77 // A mapping from an input method id to a string for the language indicator. The
78 // mapping is necessary since some input methods belong to the same language.
79 // For example, both "xkb:us::eng" and "xkb:us:dvorak:eng" are for US English.
80 const struct {
81   const char* input_method_id;
82   const char* indicator_text;
83 } kMappingFromIdToIndicatorText[] = {
84   // To distinguish from "xkb:us::eng"
85   { "xkb:us:altgr-intl:eng", "EXTD" },
86   { "xkb:us:dvorak:eng", "DV" },
87   { "xkb:us:intl:eng", "INTL" },
88   { "xkb:us:colemak:eng", "CO" },
89   { "xkb:de:neo:ger", "NEO" },
90   // To distinguish from "xkb:gb::eng"
91   { "xkb:gb:dvorak:eng", "DV" },
92   // To distinguish from "xkb:jp::jpn"
93   { "mozc", "\xe3\x81\x82" },  // U+3042, Japanese Hiragana letter A in UTF-8.
94   { "mozc-dv", "\xe3\x81\x82" },
95   { "mozc-jp", "\xe3\x81\x82" },
96   // For simplified Chinese input methods
97   { "pinyin", "\xe6\x8b\xbc" },  // U+62FC
98   // For traditional Chinese input methods
99   { "mozc-chewing", "\xe9\x85\xb7" },  // U+9177
100   { "m17n:zh:cangjie", "\xe5\x80\x89" },  // U+5009
101   { "m17n:zh:quick", "\xe9\x80\x9f" },  // U+901F
102   // For Hangul input method.
103   { "hangul", "\xed\x95\x9c" },  // U+D55C
104 };
105 const size_t kMappingFromIdToIndicatorTextLen =
106     ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText);
107 
108 // Returns the language name for the given |language_code|.
GetLanguageName(const std::string & language_code)109 std::wstring GetLanguageName(const std::string& language_code) {
110   const string16 language_name = l10n_util::GetDisplayNameForLocale(
111       language_code, g_browser_process->GetApplicationLocale(), true);
112   return UTF16ToWide(language_name);
113 }
114 
115 }  // namespace
116 
117 namespace chromeos {
118 
119 ////////////////////////////////////////////////////////////////////////////////
120 // InputMethodMenu
121 
InputMethodMenu(PrefService * pref_service,StatusAreaHost::ScreenMode screen_mode,bool for_out_of_box_experience_dialog)122 InputMethodMenu::InputMethodMenu(PrefService* pref_service,
123                                  StatusAreaHost::ScreenMode screen_mode,
124                                  bool for_out_of_box_experience_dialog)
125     : input_method_descriptors_(CrosLibrary::Get()->GetInputMethodLibrary()->
126                                 GetActiveInputMethods()),
127       model_(NULL),
128       // Be aware that the constructor of |input_method_menu_| calls
129       // GetItemCount() in this class. Therefore, GetItemCount() have to return
130       // 0 when |model_| is NULL.
131       ALLOW_THIS_IN_INITIALIZER_LIST(input_method_menu_(this)),
132       minimum_input_method_menu_width_(0),
133       pref_service_(pref_service),
134       screen_mode_(screen_mode),
135       for_out_of_box_experience_dialog_(for_out_of_box_experience_dialog) {
136   DCHECK(input_method_descriptors_.get() &&
137          !input_method_descriptors_->empty());
138 
139   // Sync current and previous input methods on Chrome prefs with ibus-daemon.
140   if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) {
141     previous_input_method_pref_.Init(
142         prefs::kLanguagePreviousInputMethod, pref_service, this);
143     current_input_method_pref_.Init(
144         prefs::kLanguageCurrentInputMethod, pref_service, this);
145   }
146 
147   InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
148   library->AddObserver(this);  // FirstObserverIsAdded() might be called back.
149 
150   if (screen_mode_ == StatusAreaHost::kLoginMode) {
151     // This button is for the login screen.
152     registrar_.Add(this,
153                    NotificationType::LOGIN_USER_CHANGED,
154                    NotificationService::AllSources());
155   }
156 }
157 
~InputMethodMenu()158 InputMethodMenu::~InputMethodMenu() {
159   // RemoveObserver() is no-op if |this| object is already removed from the
160   // observer list.
161   CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this);
162 }
163 
164 ////////////////////////////////////////////////////////////////////////////////
165 // ui::MenuModel implementation:
166 
GetCommandIdAt(int index) const167 int InputMethodMenu::GetCommandIdAt(int index) const {
168   return index;
169 }
170 
IsItemDynamicAt(int index) const171 bool InputMethodMenu::IsItemDynamicAt(int index) const {
172   // Menu content for the language button could change time by time.
173   return true;
174 }
175 
GetAcceleratorAt(int index,ui::Accelerator * accelerator) const176 bool InputMethodMenu::GetAcceleratorAt(
177     int index, ui::Accelerator* accelerator) const {
178   // Views for Chromium OS does not support accelerators yet.
179   return false;
180 }
181 
IsItemCheckedAt(int index) const182 bool InputMethodMenu::IsItemCheckedAt(int index) const {
183   DCHECK_GE(index, 0);
184   DCHECK(input_method_descriptors_.get());
185 
186   if (IndexIsInInputMethodList(index)) {
187     const InputMethodDescriptor& input_method
188         = input_method_descriptors_->at(index);
189     return input_method == CrosLibrary::Get()->GetInputMethodLibrary()->
190           current_input_method();
191   }
192 
193   if (GetPropertyIndex(index, &index)) {
194     const ImePropertyList& property_list
195         = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
196     return property_list.at(index).is_selection_item_checked;
197   }
198 
199   // Separator(s) or the "Customize language and input..." button.
200   return false;
201 }
202 
GetGroupIdAt(int index) const203 int InputMethodMenu::GetGroupIdAt(int index) const {
204   DCHECK_GE(index, 0);
205 
206   if (IndexIsInInputMethodList(index)) {
207     return for_out_of_box_experience_dialog_ ?
208         kRadioGroupNone : kRadioGroupLanguage;
209   }
210 
211   if (GetPropertyIndex(index, &index)) {
212     const ImePropertyList& property_list
213         = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
214     return property_list.at(index).selection_item_id;
215   }
216 
217   return kRadioGroupNone;
218 }
219 
HasIcons() const220 bool InputMethodMenu::HasIcons() const  {
221   // We don't support icons on Chrome OS.
222   return false;
223 }
224 
GetIconAt(int index,SkBitmap * icon)225 bool InputMethodMenu::GetIconAt(int index, SkBitmap* icon) {
226   return false;
227 }
228 
GetButtonMenuItemAt(int index) const229 ui::ButtonMenuItemModel* InputMethodMenu::GetButtonMenuItemAt(
230     int index) const {
231   return NULL;
232 }
233 
IsEnabledAt(int index) const234 bool InputMethodMenu::IsEnabledAt(int index) const {
235   // Just return true so all input method names and input method propertie names
236   // could be clicked.
237   return true;
238 }
239 
GetSubmenuModelAt(int index) const240 ui::MenuModel* InputMethodMenu::GetSubmenuModelAt(int index) const {
241   // We don't use nested menus.
242   return NULL;
243 }
244 
HighlightChangedTo(int index)245 void InputMethodMenu::HighlightChangedTo(int index) {
246   // Views for Chromium OS does not support this interface yet.
247 }
248 
MenuWillShow()249 void InputMethodMenu::MenuWillShow() {
250   // Views for Chromium OS does not support this interface yet.
251 }
252 
SetMenuModelDelegate(ui::MenuModelDelegate * delegate)253 void InputMethodMenu::SetMenuModelDelegate(ui::MenuModelDelegate* delegate) {
254   // Not needed for current usage.
255 }
256 
GetItemCount() const257 int InputMethodMenu::GetItemCount() const {
258   if (!model_.get()) {
259     // Model is not constructed yet. This means that
260     // InputMethodMenu is being constructed. Return zero.
261     return 0;
262   }
263   return model_->GetItemCount();
264 }
265 
GetTypeAt(int index) const266 ui::MenuModel::ItemType InputMethodMenu::GetTypeAt(int index) const {
267   DCHECK_GE(index, 0);
268 
269   if (IndexPointsToConfigureImeMenuItem(index)) {
270     return ui::MenuModel::TYPE_COMMAND;  // "Customize language and input"
271   }
272 
273   if (IndexIsInInputMethodList(index)) {
274     return for_out_of_box_experience_dialog_ ?
275         ui::MenuModel::TYPE_COMMAND : ui::MenuModel::TYPE_RADIO;
276   }
277 
278   if (GetPropertyIndex(index, &index)) {
279     const ImePropertyList& property_list
280         = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
281     if (property_list.at(index).is_selection_item) {
282       return ui::MenuModel::TYPE_RADIO;
283     }
284     return ui::MenuModel::TYPE_COMMAND;
285   }
286 
287   return ui::MenuModel::TYPE_SEPARATOR;
288 }
289 
GetLabelAt(int index) const290 string16 InputMethodMenu::GetLabelAt(int index) const {
291   DCHECK_GE(index, 0);
292   DCHECK(input_method_descriptors_.get());
293 
294   // We use IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE here as the button
295   // opens the same dialog that is opened from the main options dialog.
296   if (IndexPointsToConfigureImeMenuItem(index)) {
297     return l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE);
298   }
299 
300   std::wstring name;
301   if (IndexIsInInputMethodList(index)) {
302     name = GetTextForMenu(input_method_descriptors_->at(index));
303   } else if (GetPropertyIndex(index, &index)) {
304     InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
305     const ImePropertyList& property_list = library->current_ime_properties();
306     const std::string& input_method_id = library->current_input_method().id;
307     return input_method::GetStringUTF16(
308         property_list.at(index).label, input_method_id);
309   }
310 
311   return WideToUTF16(name);
312 }
313 
ActivatedAt(int index)314 void InputMethodMenu::ActivatedAt(int index) {
315   DCHECK_GE(index, 0);
316   DCHECK(input_method_descriptors_.get());
317 
318   if (IndexPointsToConfigureImeMenuItem(index)) {
319     OpenConfigUI();
320     return;
321   }
322 
323   if (IndexIsInInputMethodList(index)) {
324     // Inter-IME switching.
325     const InputMethodDescriptor& input_method
326         = input_method_descriptors_->at(index);
327     CrosLibrary::Get()->GetInputMethodLibrary()->ChangeInputMethod(
328         input_method.id);
329     UserMetrics::RecordAction(
330         UserMetricsAction("LanguageMenuButton_InputMethodChanged"));
331     return;
332   }
333 
334   if (GetPropertyIndex(index, &index)) {
335     // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana).
336     const ImePropertyList& property_list
337         = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
338     const std::string key = property_list.at(index).key;
339     if (property_list.at(index).is_selection_item) {
340       // Radio button is clicked.
341       const int id = property_list.at(index).selection_item_id;
342       // First, deactivate all other properties in the same radio group.
343       for (int i = 0; i < static_cast<int>(property_list.size()); ++i) {
344         if (i != index && id == property_list.at(i).selection_item_id) {
345           CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
346               property_list.at(i).key, false);
347         }
348       }
349       // Then, activate the property clicked.
350       CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
351           key, true);
352     } else {
353       // Command button like "Switch to half punctuation mode" is clicked.
354       // We can always use "Deactivate" for command buttons.
355       CrosLibrary::Get()->GetInputMethodLibrary()->SetImePropertyActivated(
356           key, false);
357     }
358     return;
359   }
360 
361   LOG(ERROR) << "Unexpected index: " << index;
362 }
363 
364 ////////////////////////////////////////////////////////////////////////////////
365 // views::ViewMenuDelegate implementation:
366 
RunMenu(views::View * unused_source,const gfx::Point & pt)367 void InputMethodMenu::RunMenu(
368     views::View* unused_source, const gfx::Point& pt) {
369   PrepareForMenuOpen();
370   input_method_menu_.RunMenuAt(pt, views::Menu2::ALIGN_TOPRIGHT);
371 }
372 
373 ////////////////////////////////////////////////////////////////////////////////
374 // InputMethodLibrary::Observer implementation:
375 
InputMethodChanged(InputMethodLibrary * obj,const InputMethodDescriptor & current_input_method,size_t num_active_input_methods)376 void InputMethodMenu::InputMethodChanged(
377     InputMethodLibrary* obj,
378     const InputMethodDescriptor& current_input_method,
379     size_t num_active_input_methods) {
380   UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
381 }
382 
PreferenceUpdateNeeded(InputMethodLibrary * obj,const InputMethodDescriptor & previous_input_method,const InputMethodDescriptor & current_input_method)383 void InputMethodMenu::PreferenceUpdateNeeded(
384     InputMethodLibrary* obj,
385     const InputMethodDescriptor& previous_input_method,
386     const InputMethodDescriptor& current_input_method) {
387   if (screen_mode_ == StatusAreaHost::kBrowserMode) {
388     if (pref_service_) {  // make sure we're not in unit tests.
389       // Sometimes (e.g. initial boot) |previous_input_method.id| is empty.
390       previous_input_method_pref_.SetValue(previous_input_method.id);
391       current_input_method_pref_.SetValue(current_input_method.id);
392       pref_service_->ScheduleSavePersistentPrefs();
393     }
394   } else if (screen_mode_ == StatusAreaHost::kLoginMode) {
395     if (g_browser_process && g_browser_process->local_state()) {
396       g_browser_process->local_state()->SetString(
397           language_prefs::kPreferredKeyboardLayout, current_input_method.id);
398       g_browser_process->local_state()->SavePersistentPrefs();
399     }
400   }
401 }
402 
PropertyListChanged(InputMethodLibrary * obj,const ImePropertyList & current_ime_properties)403 void InputMethodMenu::PropertyListChanged(
404     InputMethodLibrary* obj,
405     const ImePropertyList& current_ime_properties) {
406   // Usual order of notifications of input method change is:
407   // 1. RegisterProperties(empty)
408   // 2. RegisterProperties(list-of-new-properties)
409   // 3. GlobalInputMethodChanged
410   // However, due to the asynchronicity, we occasionally (but rarely) face to
411   // 1. RegisterProperties(empty)
412   // 2. GlobalInputMethodChanged
413   // 3. RegisterProperties(list-of-new-properties)
414   // this order. On this unusual case, we must rebuild the menu after the last
415   // RegisterProperties. For the other cases, no rebuild is needed. Actually
416   // it is better to be avoided. Otherwise users can sometimes observe the
417   // awkward clear-then-register behavior.
418   if (!current_ime_properties.empty()) {
419     InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
420     const InputMethodDescriptor& input_method = library->current_input_method();
421     size_t num_active_input_methods = library->GetNumActiveInputMethods();
422     UpdateUIFromInputMethod(input_method, num_active_input_methods);
423   }
424 }
425 
FirstObserverIsAdded(InputMethodLibrary * obj)426 void InputMethodMenu::FirstObserverIsAdded(InputMethodLibrary* obj) {
427   // NOTICE: Since this function might be called from the constructor of this
428   // class, it's better to avoid calling virtual functions.
429 
430   if (pref_service_ && (screen_mode_ == StatusAreaHost::kBrowserMode)) {
431     // Get the input method name in the Preferences file which was in use last
432     // time, and switch to the method. We remember two input method names in the
433     // preference so that the Control+space hot-key could work fine from the
434     // beginning. InputMethodChanged() will be called soon and the indicator
435     // will be updated.
436     InputMethodLibrary* library = CrosLibrary::Get()->GetInputMethodLibrary();
437     const std::string previous_input_method_id =
438         previous_input_method_pref_.GetValue();
439     if (!previous_input_method_id.empty()) {
440       library->ChangeInputMethod(previous_input_method_id);
441     }
442     const std::string current_input_method_id =
443         current_input_method_pref_.GetValue();
444     if (!current_input_method_id.empty()) {
445       library->ChangeInputMethod(current_input_method_id);
446     }
447   }
448 }
449 
PrepareForMenuOpen()450 void InputMethodMenu::PrepareForMenuOpen() {
451   UserMetrics::RecordAction(UserMetricsAction("LanguageMenuButton_Open"));
452   PrepareMenu();
453 }
454 
PrepareMenu()455 void InputMethodMenu::PrepareMenu() {
456   input_method_descriptors_.reset(CrosLibrary::Get()->GetInputMethodLibrary()->
457                                   GetActiveInputMethods());
458   RebuildModel();
459   input_method_menu_.Rebuild();
460   if (minimum_input_method_menu_width_ > 0) {
461     input_method_menu_.SetMinimumWidth(minimum_input_method_menu_width_);
462   }
463 }
464 
ActiveInputMethodsChanged(InputMethodLibrary * obj,const InputMethodDescriptor & current_input_method,size_t num_active_input_methods)465 void InputMethodMenu::ActiveInputMethodsChanged(
466     InputMethodLibrary* obj,
467     const InputMethodDescriptor& current_input_method,
468     size_t num_active_input_methods) {
469   // Update the icon if active input methods are changed. See also
470   // comments in UpdateUI() in input_method_menu_button.cc.
471   UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
472 }
473 
UpdateUIFromInputMethod(const InputMethodDescriptor & input_method,size_t num_active_input_methods)474 void InputMethodMenu::UpdateUIFromInputMethod(
475     const InputMethodDescriptor& input_method,
476     size_t num_active_input_methods) {
477   const std::wstring name = GetTextForIndicator(input_method);
478   const std::wstring tooltip = GetTextForMenu(input_method);
479   UpdateUI(input_method.id, name, tooltip, num_active_input_methods);
480 }
481 
RebuildModel()482 void InputMethodMenu::RebuildModel() {
483   model_.reset(new ui::SimpleMenuModel(NULL));
484   string16 dummy_label = UTF8ToUTF16("");
485   // Indicates if separator's needed before each section.
486   bool need_separator = false;
487 
488   if (!input_method_descriptors_->empty()) {
489     // We "abuse" the command_id and group_id arguments of AddRadioItem method.
490     // A COMMAND_ID_XXX enum value is passed as command_id, and array index of
491     // |input_method_descriptors_| or |property_list| is passed as group_id.
492     for (size_t i = 0; i < input_method_descriptors_->size(); ++i) {
493       model_->AddRadioItem(COMMAND_ID_INPUT_METHODS, dummy_label, i);
494     }
495 
496     need_separator = true;
497   }
498 
499   const ImePropertyList& property_list
500       = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
501   if (!property_list.empty()) {
502     if (need_separator) {
503       model_->AddSeparator();
504     }
505     for (size_t i = 0; i < property_list.size(); ++i) {
506       model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i);
507     }
508     need_separator = true;
509   }
510 
511   if (ShouldSupportConfigUI()) {
512     // Note: We use AddSeparator() for separators, and AddRadioItem() for all
513     // other items even if an item is not actually a radio item.
514     if (need_separator) {
515       model_->AddSeparator();
516     }
517     model_->AddRadioItem(COMMAND_ID_CUSTOMIZE_LANGUAGE, dummy_label,
518                          0 /* dummy */);
519   }
520 }
521 
IndexIsInInputMethodList(int index) const522 bool InputMethodMenu::IndexIsInInputMethodList(int index) const {
523   DCHECK_GE(index, 0);
524   DCHECK(model_.get());
525   if (index >= model_->GetItemCount()) {
526     return false;
527   }
528 
529   return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
530           (model_->GetCommandIdAt(index) == COMMAND_ID_INPUT_METHODS) &&
531           input_method_descriptors_.get() &&
532           (index < static_cast<int>(input_method_descriptors_->size())));
533 }
534 
GetPropertyIndex(int index,int * property_index) const535 bool InputMethodMenu::GetPropertyIndex(int index, int* property_index) const {
536   DCHECK_GE(index, 0);
537   DCHECK(property_index);
538   DCHECK(model_.get());
539   if (index >= model_->GetItemCount()) {
540     return false;
541   }
542 
543   if ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
544       (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) {
545     const int tmp_property_index = model_->GetGroupIdAt(index);
546     const ImePropertyList& property_list
547         = CrosLibrary::Get()->GetInputMethodLibrary()->current_ime_properties();
548     if (tmp_property_index < static_cast<int>(property_list.size())) {
549       *property_index = tmp_property_index;
550       return true;
551     }
552   }
553   return false;
554 }
555 
IndexPointsToConfigureImeMenuItem(int index) const556 bool InputMethodMenu::IndexPointsToConfigureImeMenuItem(int index) const {
557   DCHECK_GE(index, 0);
558   DCHECK(model_.get());
559   if (index >= model_->GetItemCount()) {
560     return false;
561   }
562 
563   return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
564           (model_->GetCommandIdAt(index) == COMMAND_ID_CUSTOMIZE_LANGUAGE));
565 }
566 
GetTextForIndicator(const InputMethodDescriptor & input_method)567 std::wstring InputMethodMenu::GetTextForIndicator(
568     const InputMethodDescriptor& input_method) {
569   // For the status area, we use two-letter, upper-case language code like
570   // "US" and "JP".
571   std::wstring text;
572 
573   // Check special cases first.
574   for (size_t i = 0; i < kMappingFromIdToIndicatorTextLen; ++i) {
575     if (kMappingFromIdToIndicatorText[i].input_method_id == input_method.id) {
576       text = UTF8ToWide(kMappingFromIdToIndicatorText[i].indicator_text);
577       break;
578     }
579   }
580 
581   // Display the keyboard layout name when using a keyboard layout.
582   if (text.empty() && input_method::IsKeyboardLayout(input_method.id)) {
583     const size_t kMaxKeyboardLayoutNameLen = 2;
584     const std::wstring keyboard_layout = UTF8ToWide(
585         input_method::GetKeyboardLayoutName(input_method.id));
586     text = StringToUpperASCII(keyboard_layout).substr(
587         0, kMaxKeyboardLayoutNameLen);
588   }
589 
590   // TODO(yusukes): Some languages have two or more input methods. For example,
591   // Thai has 3, Vietnamese has 4. If these input methods could be activated at
592   // the same time, we should do either of the following:
593   //   (1) Add mappings to |kMappingFromIdToIndicatorText|
594   //   (2) Add suffix (1, 2, ...) to |text| when ambiguous.
595 
596   if (text.empty()) {
597     const size_t kMaxLanguageNameLen = 2;
598     std::string language_code =
599         input_method::GetLanguageCodeFromDescriptor(input_method);
600 
601     // Use "CN" for simplified Chinese and "TW" for traditonal Chinese,
602     // rather than "ZH".
603     if (StartsWithASCII(language_code, "zh-", false)) {
604       std::vector<std::string> portions;
605       base::SplitString(language_code, '-', &portions);
606       if (portions.size() >= 2 && !portions[1].empty()) {
607         language_code = portions[1];
608       }
609     }
610 
611     text = StringToUpperASCII(UTF8ToWide(language_code)).substr(
612         0, kMaxLanguageNameLen);
613   }
614   DCHECK(!text.empty());
615   return text;
616 }
617 
GetTextForMenu(const InputMethodDescriptor & input_method)618 std::wstring InputMethodMenu::GetTextForMenu(
619     const InputMethodDescriptor& input_method) {
620   // We don't show language here.  Name of keyboard layout or input method
621   // usually imply (or explicitly include) its language.
622 
623   // Special case for Dutch, French and German: these languages have multiple
624   // keyboard layouts and share the same laout of keyboard (Belgian). We need to
625   // show explicitly the language for the layout.
626   // For Arabic and Hindi: they share "Standard Input Method".
627   const std::string language_code
628       = input_method::GetLanguageCodeFromDescriptor(input_method);
629   std::wstring text;
630   if (language_code == "ar" ||
631       language_code == "hi" ||
632       language_code == "nl" ||
633       language_code == "fr" ||
634       language_code == "de") {
635     text = GetLanguageName(language_code) + L" - ";
636   }
637   text += input_method::GetString(input_method.display_name, input_method.id);
638 
639   DCHECK(!text.empty());
640   return text;
641 }
642 
RegisterPrefs(PrefService * local_state)643 void InputMethodMenu::RegisterPrefs(PrefService* local_state) {
644   local_state->RegisterStringPref(language_prefs::kPreferredKeyboardLayout, "");
645 }
646 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)647 void InputMethodMenu::Observe(NotificationType type,
648                               const NotificationSource& source,
649                               const NotificationDetails& details) {
650   if (type == NotificationType::LOGIN_USER_CHANGED) {
651     // When a user logs in, we should remove |this| object from the observer
652     // list so that PreferenceUpdateNeeded() does not update the local state
653     // anymore.
654     CrosLibrary::Get()->GetInputMethodLibrary()->RemoveObserver(this);
655   }
656 }
657 
SetMinimumWidth(int width)658 void InputMethodMenu::SetMinimumWidth(int width) {
659   // On the OOBE network selection screen, fixed width menu would be preferable.
660   minimum_input_method_menu_width_ = width;
661 }
662 
663 }  // namespace chromeos
664