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
63namespace {
64
65// Constants to specify the type of items in |model_|.
66enum {
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.
74const int kRadioGroupLanguage = 1 << 16;
75const 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.
80const 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};
105const size_t kMappingFromIdToIndicatorTextLen =
106    ARRAYSIZE_UNSAFE(kMappingFromIdToIndicatorText);
107
108// Returns the language name for the given |language_code|.
109std::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
117namespace chromeos {
118
119////////////////////////////////////////////////////////////////////////////////
120// InputMethodMenu
121
122InputMethodMenu::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
158InputMethodMenu::~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
167int InputMethodMenu::GetCommandIdAt(int index) const {
168  return index;
169}
170
171bool InputMethodMenu::IsItemDynamicAt(int index) const {
172  // Menu content for the language button could change time by time.
173  return true;
174}
175
176bool InputMethodMenu::GetAcceleratorAt(
177    int index, ui::Accelerator* accelerator) const {
178  // Views for Chromium OS does not support accelerators yet.
179  return false;
180}
181
182bool 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
203int 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
220bool InputMethodMenu::HasIcons() const  {
221  // We don't support icons on Chrome OS.
222  return false;
223}
224
225bool InputMethodMenu::GetIconAt(int index, SkBitmap* icon) {
226  return false;
227}
228
229ui::ButtonMenuItemModel* InputMethodMenu::GetButtonMenuItemAt(
230    int index) const {
231  return NULL;
232}
233
234bool 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
240ui::MenuModel* InputMethodMenu::GetSubmenuModelAt(int index) const {
241  // We don't use nested menus.
242  return NULL;
243}
244
245void InputMethodMenu::HighlightChangedTo(int index) {
246  // Views for Chromium OS does not support this interface yet.
247}
248
249void InputMethodMenu::MenuWillShow() {
250  // Views for Chromium OS does not support this interface yet.
251}
252
253void InputMethodMenu::SetMenuModelDelegate(ui::MenuModelDelegate* delegate) {
254  // Not needed for current usage.
255}
256
257int 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
266ui::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
290string16 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
314void 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
367void 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
376void 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
383void 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
403void 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
426void 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
450void InputMethodMenu::PrepareForMenuOpen() {
451  UserMetrics::RecordAction(UserMetricsAction("LanguageMenuButton_Open"));
452  PrepareMenu();
453}
454
455void 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
465void 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
474void 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
482void 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
522bool 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
535bool 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
556bool 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
567std::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
618std::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
643void InputMethodMenu::RegisterPrefs(PrefService* local_state) {
644  local_state->RegisterStringPref(language_prefs::kPreferredKeyboardLayout, "");
645}
646
647void 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
658void 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