Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(298)

Side by Side Diff: chrome/browser/chromeos/status/input_method_menu.cc

Issue 10056001: chromeos: Remove old status-area related code. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: . Created 8 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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/input_method/input_method_util.h"
16 #include "chrome/browser/chromeos/status/status_area_view_chromeos.h"
17 #include "content/public/browser/user_metrics.h"
18 #include "grit/generated_resources.h"
19 #include "grit/theme_resources.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/models/simple_menu_model.h"
22 #include "ui/base/resource/resource_bundle.h"
23 #include "ui/views/controls/menu/menu_model_adapter.h"
24 #include "ui/views/controls/menu/menu_runner.h"
25 #include "ui/views/controls/menu/submenu_view.h"
26 #include "ui/views/widget/widget.h"
27
28 using content::UserMetricsAction;
29
30 // The language menu consists of 3 parts (in this order):
31 //
32 // (1) input method names. The size of the list is always >= 1.
33 // (2) input method properties. This list might be empty.
34 // (3) "Customize language and input..." button.
35 //
36 // Example of the menu (Japanese):
37 //
38 // ============================== (border of the popup window)
39 // [ ] English (|index| in the following functions is 0)
40 // [*] Japanese
41 // [ ] Chinese (Simplified)
42 // ------------------------------ (separator)
43 // [*] Hiragana (index = 5, The property has 2 radio groups)
44 // [ ] Katakana
45 // [ ] HalfWidthKatakana
46 // [*] Roman
47 // [ ] Kana
48 // ------------------------------ (separator)
49 // Customize language and input...(index = 11)
50 // ============================== (border of the popup window)
51 //
52 // Example of the menu (Simplified Chinese):
53 //
54 // ============================== (border of the popup window)
55 // [ ] English
56 // [ ] Japanese
57 // [*] Chinese (Simplified)
58 // ------------------------------ (separator)
59 // Switch to full letter mode (The property has 2 command buttons)
60 // Switch to half punctuation mode
61 // ------------------------------ (separator)
62 // Customize language and input...
63 // ============================== (border of the popup window)
64 //
65
66 namespace {
67
68 // Constants to specify the type of items in |model_|.
69 enum {
70 COMMAND_ID_INPUT_METHODS = 0, // English, Chinese, Japanese, Arabic, ...
71 COMMAND_ID_IME_PROPERTIES, // Hiragana, Katakana, ...
72 COMMAND_ID_CUSTOMIZE_LANGUAGE, // "Customize language and input..." button.
73 };
74
75 // A group ID for IME properties starts from 0. We use the huge value for the
76 // input method list to avoid conflict.
77 const int kRadioGroupLanguage = 1 << 16;
78 const int kRadioGroupNone = -1;
79
80 // Returns the language name for the given |language_code|.
81 string16 GetLanguageName(const std::string& language_code) {
82 const string16 language_name = l10n_util::GetDisplayNameForLocale(
83 language_code, g_browser_process->GetApplicationLocale(), true);
84 return language_name;
85 }
86
87 } // namespace
88
89 namespace chromeos {
90
91 using input_method::InputMethodManager;
92
93 ////////////////////////////////////////////////////////////////////////////////
94 // InputMethodMenu
95
96 InputMethodMenu::InputMethodMenu()
97 : input_method_descriptors_(
98 InputMethodManager::GetInstance()->GetActiveInputMethods()),
99 model_(new ui::SimpleMenuModel(NULL)),
100 ALLOW_THIS_IN_INITIALIZER_LIST(input_method_menu_delegate_(
101 new views::MenuModelAdapter(this))),
102 input_method_menu_(
103 new views::MenuItemView(input_method_menu_delegate_.get())),
104 input_method_menu_runner_(new views::MenuRunner(input_method_menu_)),
105 minimum_input_method_menu_width_(0),
106 menu_alignment_(views::MenuItemView::TOPRIGHT) {
107 DCHECK(input_method_descriptors_.get() &&
108 !input_method_descriptors_->empty());
109 AddObserver();
110 }
111
112 InputMethodMenu::~InputMethodMenu() {
113 // RemoveObserver() is no-op if |this| object is already removed from the
114 // observer list
115 RemoveObserver();
116 }
117
118 ////////////////////////////////////////////////////////////////////////////////
119 // ui::MenuModel implementation:
120
121 int InputMethodMenu::GetCommandIdAt(int index) const {
122 return index;
123 }
124
125 bool InputMethodMenu::IsItemDynamicAt(int index) const {
126 // Menu content for the language button could change time by time.
127 return true;
128 }
129
130 bool InputMethodMenu::GetAcceleratorAt(
131 int index, ui::Accelerator* accelerator) const {
132 // Views for Chromium OS does not support accelerators yet.
133 return false;
134 }
135
136 bool InputMethodMenu::IsItemCheckedAt(int index) const {
137 DCHECK_GE(index, 0);
138 DCHECK(input_method_descriptors_.get());
139
140 if (IndexIsInInputMethodList(index)) {
141 const input_method::InputMethodDescriptor& input_method
142 = input_method_descriptors_->at(index);
143 return input_method == InputMethodManager::GetInstance()->
144 GetCurrentInputMethod();
145 }
146
147 if (GetPropertyIndex(index, &index)) {
148 const input_method::InputMethodPropertyList& property_list
149 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
150 return property_list.at(index).is_selection_item_checked;
151 }
152
153 // Separator(s) or the "Customize language and input..." button.
154 return false;
155 }
156
157 int InputMethodMenu::GetGroupIdAt(int index) const {
158 DCHECK_GE(index, 0);
159
160 if (IndexIsInInputMethodList(index))
161 return kRadioGroupLanguage;
162
163 if (GetPropertyIndex(index, &index)) {
164 const input_method::InputMethodPropertyList& property_list
165 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
166 return property_list.at(index).selection_item_id;
167 }
168
169 return kRadioGroupNone;
170 }
171
172 bool InputMethodMenu::HasIcons() const {
173 // We don't support icons on Chrome OS.
174 return false;
175 }
176
177 bool InputMethodMenu::GetIconAt(int index, SkBitmap* icon) {
178 return false;
179 }
180
181 ui::ButtonMenuItemModel* InputMethodMenu::GetButtonMenuItemAt(
182 int index) const {
183 return NULL;
184 }
185
186 bool InputMethodMenu::IsEnabledAt(int index) const {
187 // Just return true so all input method names and input method propertie names
188 // could be clicked.
189 return true;
190 }
191
192 ui::MenuModel* InputMethodMenu::GetSubmenuModelAt(int index) const {
193 // We don't use nested menus.
194 return NULL;
195 }
196
197 void InputMethodMenu::HighlightChangedTo(int index) {
198 // Views for Chromium OS does not support this interface yet.
199 }
200
201 void InputMethodMenu::MenuWillShow() {
202 // Views for Chromium OS does not support this interface yet.
203 }
204
205 void InputMethodMenu::SetMenuModelDelegate(ui::MenuModelDelegate* delegate) {
206 // Not needed for current usage.
207 }
208
209 int InputMethodMenu::GetItemCount() const {
210 if (!model_.get()) {
211 // Model is not constructed yet. This means that
212 // InputMethodMenu is being constructed. Return zero.
213 return 0;
214 }
215 return model_->GetItemCount();
216 }
217
218 ui::MenuModel::ItemType InputMethodMenu::GetTypeAt(int index) const {
219 DCHECK_GE(index, 0);
220
221 if (IndexPointsToConfigureImeMenuItem(index)) {
222 return ui::MenuModel::TYPE_COMMAND; // "Customize language and input"
223 }
224
225 if (IndexIsInInputMethodList(index))
226 return ui::MenuModel::TYPE_RADIO;
227
228 if (GetPropertyIndex(index, &index)) {
229 const input_method::InputMethodPropertyList& property_list
230 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
231 if (property_list.at(index).is_selection_item) {
232 return ui::MenuModel::TYPE_RADIO;
233 }
234 return ui::MenuModel::TYPE_COMMAND;
235 }
236
237 return ui::MenuModel::TYPE_SEPARATOR;
238 }
239
240 string16 InputMethodMenu::GetLabelAt(int index) const {
241 DCHECK_GE(index, 0);
242 DCHECK(input_method_descriptors_.get());
243
244 // We use IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE here as the button
245 // opens the same dialog that is opened from the main options dialog.
246 if (IndexPointsToConfigureImeMenuItem(index)) {
247 return l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CUSTOMIZE);
248 }
249
250 string16 name;
251 if (IndexIsInInputMethodList(index)) {
252 name = GetTextForMenu(input_method_descriptors_->at(index));
253 } else if (GetPropertyIndex(index, &index)) {
254 InputMethodManager* manager = InputMethodManager::GetInstance();
255 const input_method::InputMethodPropertyList& property_list =
256 manager->GetCurrentInputMethodProperties();
257 return manager->GetInputMethodUtil()->TranslateString(
258 property_list.at(index).label);
259 }
260
261 return name;
262 }
263
264 void InputMethodMenu::ActivatedAt(int index) {
265 DCHECK_GE(index, 0);
266 DCHECK(input_method_descriptors_.get());
267
268 if (IndexPointsToConfigureImeMenuItem(index)) {
269 OpenConfigUI();
270 return;
271 }
272
273 if (IndexIsInInputMethodList(index)) {
274 // Inter-IME switching.
275 const input_method::InputMethodDescriptor& input_method
276 = input_method_descriptors_->at(index);
277 InputMethodManager::GetInstance()->ChangeInputMethod(
278 input_method.id());
279 content::RecordAction(
280 UserMetricsAction("LanguageMenuButton_InputMethodChanged"));
281 return;
282 }
283
284 if (GetPropertyIndex(index, &index)) {
285 // Intra-IME switching (e.g. Japanese-Hiragana to Japanese-Katakana).
286 const input_method::InputMethodPropertyList& property_list
287 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
288 const std::string key = property_list.at(index).key;
289 if (property_list.at(index).is_selection_item) {
290 // Radio button is clicked.
291 const int id = property_list.at(index).selection_item_id;
292 // First, deactivate all other properties in the same radio group.
293 for (int i = 0; i < static_cast<int>(property_list.size()); ++i) {
294 if (i != index && id == property_list.at(i).selection_item_id) {
295 InputMethodManager::GetInstance()->SetImePropertyActivated(
296 property_list.at(i).key, false);
297 }
298 }
299 // Then, activate the property clicked.
300 InputMethodManager::GetInstance()->SetImePropertyActivated(
301 key, true);
302 } else {
303 // Command button like "Switch to half punctuation mode" is clicked.
304 // We can always use "Deactivate" for command buttons.
305 InputMethodManager::GetInstance()->SetImePropertyActivated(
306 key, false);
307 }
308 return;
309 }
310
311 LOG(ERROR) << "Unexpected index: " << index;
312 }
313
314 ////////////////////////////////////////////////////////////////////////////////
315 // views::MenuButtonListener implementation:
316
317 void InputMethodMenu::OnMenuButtonClicked(views::View* source,
318 const gfx::Point& point) {
319 PrepareForMenuOpen();
320
321 if (minimum_input_method_menu_width_ > 0) {
322 DCHECK(input_method_menu_->HasSubmenu());
323 views::SubmenuView* submenu = input_method_menu_->GetSubmenu();
324 submenu->set_minimum_preferred_width(minimum_input_method_menu_width_);
325 }
326
327 gfx::Point screen_location;
328 views::View::ConvertPointToScreen(source, &screen_location);
329 gfx::Rect bounds(screen_location, source->size());
330 if (input_method_menu_runner_->RunMenuAt(
331 source->GetWidget()->GetTopLevelWidget(), NULL, bounds,
332 menu_alignment_, views::MenuRunner::HAS_MNEMONICS) ==
333 views::MenuRunner::MENU_DELETED)
334 return;
335 }
336
337 ////////////////////////////////////////////////////////////////////////////////
338 // InputMethodManager::Observer implementation:
339
340 void InputMethodMenu::InputMethodChanged(
341 InputMethodManager* manager,
342 const input_method::InputMethodDescriptor& current_input_method,
343 size_t num_active_input_methods) {
344 UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
345 }
346
347 void InputMethodMenu::PropertyListChanged(
348 InputMethodManager* manager,
349 const input_method::InputMethodPropertyList& current_ime_properties) {
350 // Usual order of notifications of input method change is:
351 // 1. RegisterProperties(empty)
352 // 2. RegisterProperties(list-of-new-properties)
353 // 3. GlobalInputMethodChanged
354 // However, due to the asynchronicity, we occasionally (but rarely) face to
355 // 1. RegisterProperties(empty)
356 // 2. GlobalInputMethodChanged
357 // 3. RegisterProperties(list-of-new-properties)
358 // this order. On this unusual case, we must rebuild the menu after the last
359 // RegisterProperties. For the other cases, no rebuild is needed. Actually
360 // it is better to be avoided. Otherwise users can sometimes observe the
361 // awkward clear-then-register behavior.
362 if (!current_ime_properties.empty()) {
363 const input_method::InputMethodDescriptor& input_method =
364 manager->GetCurrentInputMethod();
365 size_t num_active_input_methods = manager->GetNumActiveInputMethods();
366 UpdateUIFromInputMethod(input_method, num_active_input_methods);
367 }
368 }
369
370 void InputMethodMenu::PrepareForMenuOpen() {
371 content::RecordAction(UserMetricsAction("LanguageMenuButton_Open"));
372 PrepareMenuModel();
373 }
374
375 void InputMethodMenu::PrepareMenuModel() {
376 input_method_descriptors_.reset(InputMethodManager::GetInstance()->
377 GetActiveInputMethods());
378 RebuildModel();
379 }
380
381 void InputMethodMenu::ActiveInputMethodsChanged(
382 InputMethodManager* manager,
383 const input_method::InputMethodDescriptor& current_input_method,
384 size_t num_active_input_methods) {
385 // Update the icon if active input methods are changed. See also
386 // comments in UpdateUI() in input_method_menu_button.cc.
387 UpdateUIFromInputMethod(current_input_method, num_active_input_methods);
388 }
389
390 void InputMethodMenu::UpdateUIFromInputMethod(
391 const input_method::InputMethodDescriptor& input_method,
392 size_t num_active_input_methods) {
393 InputMethodManager* manager = InputMethodManager::GetInstance();
394 const string16 name = manager->GetInputMethodUtil()->
395 GetInputMethodShortName(input_method);
396 const string16 tooltip = GetTextForMenu(input_method);
397 UpdateUI(input_method.id(), name, tooltip, num_active_input_methods);
398 }
399
400 void InputMethodMenu::RebuildModel() {
401 model_->Clear();
402 string16 dummy_label = UTF8ToUTF16("");
403 // Indicates if separator's needed before each section.
404 bool need_separator = false;
405
406 if (!input_method_descriptors_->empty()) {
407 // We "abuse" the command_id and group_id arguments of AddRadioItem method.
408 // A COMMAND_ID_XXX enum value is passed as command_id, and array index of
409 // |input_method_descriptors_| or |property_list| is passed as group_id.
410 for (size_t i = 0; i < input_method_descriptors_->size(); ++i) {
411 model_->AddRadioItem(COMMAND_ID_INPUT_METHODS, dummy_label, i);
412 }
413
414 need_separator = true;
415 }
416
417 const input_method::InputMethodPropertyList& property_list
418 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
419 if (!property_list.empty()) {
420 if (need_separator) {
421 model_->AddSeparator();
422 }
423 for (size_t i = 0; i < property_list.size(); ++i) {
424 model_->AddRadioItem(COMMAND_ID_IME_PROPERTIES, dummy_label, i);
425 }
426 need_separator = true;
427 }
428
429 if (ShouldSupportConfigUI()) {
430 // Note: We use AddSeparator() for separators, and AddRadioItem() for all
431 // other items even if an item is not actually a radio item.
432 if (need_separator) {
433 model_->AddSeparator();
434 }
435 model_->AddRadioItem(COMMAND_ID_CUSTOMIZE_LANGUAGE, dummy_label,
436 0 /* dummy */);
437 }
438
439 // Rebuild the menu from the model.
440 input_method_menu_delegate_->BuildMenu(input_method_menu_);
441 }
442
443 bool InputMethodMenu::IndexIsInInputMethodList(int index) const {
444 DCHECK_GE(index, 0);
445 DCHECK(model_.get());
446 if (index >= model_->GetItemCount()) {
447 return false;
448 }
449
450 return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
451 (model_->GetCommandIdAt(index) == COMMAND_ID_INPUT_METHODS) &&
452 input_method_descriptors_.get() &&
453 (index < static_cast<int>(input_method_descriptors_->size())));
454 }
455
456 bool InputMethodMenu::GetPropertyIndex(int index, int* property_index) const {
457 DCHECK_GE(index, 0);
458 DCHECK(property_index);
459 DCHECK(model_.get());
460 if (index >= model_->GetItemCount()) {
461 return false;
462 }
463
464 if ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
465 (model_->GetCommandIdAt(index) == COMMAND_ID_IME_PROPERTIES)) {
466 const int tmp_property_index = model_->GetGroupIdAt(index);
467 const input_method::InputMethodPropertyList& property_list
468 = InputMethodManager::GetInstance()->GetCurrentInputMethodProperties();
469 if (tmp_property_index < static_cast<int>(property_list.size())) {
470 *property_index = tmp_property_index;
471 return true;
472 }
473 }
474 return false;
475 }
476
477 bool InputMethodMenu::IndexPointsToConfigureImeMenuItem(int index) const {
478 DCHECK_GE(index, 0);
479 DCHECK(model_.get());
480 if (index >= model_->GetItemCount()) {
481 return false;
482 }
483
484 return ((model_->GetTypeAt(index) == ui::MenuModel::TYPE_RADIO) &&
485 (model_->GetCommandIdAt(index) == COMMAND_ID_CUSTOMIZE_LANGUAGE));
486 }
487
488 string16 InputMethodMenu::GetTextForMenu(
489 const input_method::InputMethodDescriptor& input_method) {
490 if (!input_method.name().empty()) {
491 // If the descriptor has a name, use it.
492 return UTF8ToUTF16(input_method.name());
493 }
494
495 // We don't show language here. Name of keyboard layout or input method
496 // usually imply (or explicitly include) its language.
497
498 input_method::InputMethodManager* manager =
499 input_method::InputMethodManager::GetInstance();
500
501 // Special case for German, French and Dutch: these languages have multiple
502 // keyboard layouts and share the same layout of keyboard (Belgian). We need
503 // to show explicitly the language for the layout. For Arabic, Amharic, and
504 // Indic languages: they share "Standard Input Method".
505 const string16 standard_input_method_text = l10n_util::GetStringUTF16(
506 IDS_OPTIONS_SETTINGS_LANGUAGES_M17N_STANDARD_INPUT_METHOD);
507 const std::string language_code = input_method.language_code();
508
509 string16 text =
510 manager->GetInputMethodUtil()->TranslateString(input_method.id());
511 if (text == standard_input_method_text ||
512 language_code == "de" ||
513 language_code == "fr" ||
514 language_code == "nl") {
515 text = GetLanguageName(language_code) + UTF8ToUTF16(" - ") + text;
516 }
517
518 DCHECK(!text.empty());
519 return text;
520 }
521
522 void InputMethodMenu::SetMinimumWidth(int width) {
523 // On the OOBE network selection screen, fixed width menu would be preferable.
524 minimum_input_method_menu_width_ = width;
525 }
526
527 void InputMethodMenu::AddObserver() {
528 InputMethodManager* manager = InputMethodManager::GetInstance();
529 manager->AddObserver(this);
530 }
531
532 void InputMethodMenu::RemoveObserver() {
533 InputMethodManager* manager = InputMethodManager::GetInstance();
534 manager->RemoveObserver(this);
535 }
536
537 } // namespace chromeos
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/status/input_method_menu.h ('k') | chrome/browser/chromeos/status/input_method_menu_button.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698