OLD | NEW |
| (Empty) |
1 // Copyright 2016 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 "ash/common/system/chromeos/ime_menu/ime_list_view.h" | |
6 | |
7 #include "ash/common/material_design/material_design_controller.h" | |
8 #include "ash/common/system/tray/hover_highlight_view.h" | |
9 #include "ash/common/system/tray/ime_info.h" | |
10 #include "ash/common/system/tray/system_menu_button.h" | |
11 #include "ash/common/system/tray/system_tray_delegate.h" | |
12 #include "ash/common/system/tray/tray_constants.h" | |
13 #include "ash/common/system/tray/tray_details_view.h" | |
14 #include "ash/common/system/tray/tray_popup_header_button.h" | |
15 #include "ash/common/system/tray/tray_popup_item_style.h" | |
16 #include "ash/common/system/tray/tray_popup_utils.h" | |
17 #include "ash/common/system/tray/tri_view.h" | |
18 #include "ash/common/wm_shell.h" | |
19 #include "ash/resources/grit/ash_resources.h" | |
20 #include "ash/strings/grit/ash_strings.h" | |
21 #include "ui/accessibility/ax_node_data.h" | |
22 #include "ui/base/l10n/l10n_util.h" | |
23 #include "ui/base/resource/resource_bundle.h" | |
24 #include "ui/gfx/color_palette.h" | |
25 #include "ui/gfx/paint_vector_icon.h" | |
26 #include "ui/gfx/vector_icons_public.h" | |
27 #include "ui/keyboard/keyboard_util.h" | |
28 #include "ui/views/background.h" | |
29 #include "ui/views/border.h" | |
30 #include "ui/views/controls/button/toggle_button.h" | |
31 #include "ui/views/controls/image_view.h" | |
32 #include "ui/views/controls/label.h" | |
33 #include "ui/views/controls/separator.h" | |
34 #include "ui/views/layout/fill_layout.h" | |
35 #include "ui/views/painter.h" | |
36 #include "ui/views/view.h" | |
37 #include "ui/views/widget/widget.h" | |
38 | |
39 namespace ash { | |
40 namespace { | |
41 | |
42 const int kMinFontSizeDelta = -10; | |
43 | |
44 const SkColor kKeyboardRowSeparatorColor = SkColorSetA(SK_ColorBLACK, 0x1F); | |
45 | |
46 // A |HoverHighlightView| that uses bold or normal font depending on whether it | |
47 // is selected. This view exposes itself as a checkbox to the accessibility | |
48 // framework. | |
49 class SelectableHoverHighlightView : public HoverHighlightView { | |
50 public: | |
51 SelectableHoverHighlightView(ViewClickListener* listener, | |
52 const base::string16& label, | |
53 bool selected) | |
54 : HoverHighlightView(listener), selected_(selected) { | |
55 AddLabel(label, gfx::ALIGN_LEFT, selected); | |
56 } | |
57 | |
58 ~SelectableHoverHighlightView() override {} | |
59 | |
60 protected: | |
61 // views::View: | |
62 void GetAccessibleNodeData(ui::AXNodeData* node_data) override { | |
63 HoverHighlightView::GetAccessibleNodeData(node_data); | |
64 node_data->role = ui::AX_ROLE_CHECK_BOX; | |
65 if (selected_) | |
66 node_data->AddStateFlag(ui::AX_STATE_CHECKED); | |
67 } | |
68 | |
69 private: | |
70 bool selected_; | |
71 | |
72 DISALLOW_COPY_AND_ASSIGN(SelectableHoverHighlightView); | |
73 }; | |
74 | |
75 // The IME list item view used in the material design. It contains IME info | |
76 // (name and label) and a check button if the item is selected. It's also used | |
77 // for IME property item, which has no name but label and a gray checked icon. | |
78 class ImeListItemView : public ActionableView { | |
79 public: | |
80 ImeListItemView(SystemTrayItem* owner, | |
81 ImeListView* list_view, | |
82 const base::string16& id, | |
83 const base::string16& label, | |
84 bool selected, | |
85 const SkColor button_color) | |
86 : ActionableView(owner, TrayPopupInkDropStyle::FILL_BOUNDS), | |
87 ime_list_view_(list_view), | |
88 selected_(selected) { | |
89 if (MaterialDesignController::IsSystemTrayMenuMaterial()) | |
90 SetInkDropMode(InkDropHostView::InkDropMode::ON); | |
91 | |
92 TriView* tri_view = TrayPopupUtils::CreateDefaultRowView(); | |
93 AddChildView(tri_view); | |
94 SetLayoutManager(new views::FillLayout); | |
95 | |
96 // The id button shows the IME short name. | |
97 views::Label* id_label = TrayPopupUtils::CreateDefaultLabel(); | |
98 id_label->SetText(id); | |
99 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | |
100 const gfx::FontList& base_font_list = | |
101 rb.GetFontList(ui::ResourceBundle::MediumBoldFont); | |
102 id_label->SetFontList(base_font_list); | |
103 | |
104 // For IMEs whose short name are more than 2 characters (INTL, EXTD, etc.), | |
105 // |kMenuIconSize| is not enough. The label will trigger eliding as "I..." | |
106 // or "...". So we shrink the font size until it fits within the bounds. | |
107 int size_delta = -1; | |
108 while ((id_label->GetPreferredSize().width() - | |
109 id_label->GetInsets().width()) > kMenuIconSize && | |
110 size_delta >= kMinFontSizeDelta) { | |
111 id_label->SetFontList(base_font_list.DeriveWithSizeDelta(size_delta)); | |
112 --size_delta; | |
113 } | |
114 tri_view->AddView(TriView::Container::START, id_label); | |
115 | |
116 // The label shows the IME name. | |
117 auto* label_view = TrayPopupUtils::CreateDefaultLabel(); | |
118 label_view->SetText(label); | |
119 TrayPopupItemStyle style( | |
120 TrayPopupItemStyle::FontStyle::DETAILED_VIEW_LABEL); | |
121 style.SetupLabel(label_view); | |
122 | |
123 label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT); | |
124 tri_view->AddView(TriView::Container::CENTER, label_view); | |
125 | |
126 if (selected) { | |
127 // The checked button indicates the IME is selected. | |
128 views::ImageView* checked_image = TrayPopupUtils::CreateMainImageView(); | |
129 checked_image->SetImage(gfx::CreateVectorIcon( | |
130 gfx::VectorIconId::CHECK_CIRCLE, kMenuIconSize, button_color)); | |
131 tri_view->AddView(TriView::Container::END, checked_image); | |
132 } | |
133 SetAccessibleName(label_view->text()); | |
134 } | |
135 | |
136 ~ImeListItemView() override {} | |
137 | |
138 // ActionableView: | |
139 bool PerformAction(const ui::Event& event) override { | |
140 if (ime_list_view_->should_focus_ime_after_selection_with_keyboard() && | |
141 event.type() == ui::EventType::ET_KEY_PRESSED) { | |
142 ime_list_view_->set_last_item_selected_with_keyboard(true); | |
143 } else { | |
144 ime_list_view_->set_last_item_selected_with_keyboard(false); | |
145 } | |
146 | |
147 ime_list_view_->HandleViewClicked(this); | |
148 return true; | |
149 } | |
150 | |
151 void OnFocus() override { | |
152 ActionableView::OnFocus(); | |
153 if (ime_list_view_ && ime_list_view_->scroll_content()) | |
154 ime_list_view_->scroll_content()->ScrollRectToVisible(bounds()); | |
155 } | |
156 | |
157 void GetAccessibleNodeData(ui::AXNodeData* node_data) override { | |
158 ActionableView::GetAccessibleNodeData(node_data); | |
159 node_data->role = ui::AX_ROLE_CHECK_BOX; | |
160 node_data->AddStateFlag(selected_ ? ui::AX_STATE_CHECKED | |
161 : ui::AX_STATE_NONE); | |
162 } | |
163 | |
164 private: | |
165 ImeListView* ime_list_view_; | |
166 bool selected_; | |
167 | |
168 DISALLOW_COPY_AND_ASSIGN(ImeListItemView); | |
169 }; | |
170 | |
171 } // namespace | |
172 | |
173 // The view that contains a |KeyboardButtonView| and a toggle button. | |
174 class MaterialKeyboardStatusRowView : public views::View { | |
175 public: | |
176 MaterialKeyboardStatusRowView(views::ButtonListener* listener, bool enabled) | |
177 : listener_(listener), toggle_(nullptr) { | |
178 Init(); | |
179 toggle_->SetIsOn(enabled, false); | |
180 } | |
181 | |
182 ~MaterialKeyboardStatusRowView() override {} | |
183 | |
184 views::Button* toggle() const { return toggle_; } | |
185 bool is_toggled() const { return toggle_->is_on(); } | |
186 | |
187 protected: | |
188 // views::View: | |
189 int GetHeightForWidth(int w) const override { | |
190 return GetPreferredSize().height(); | |
191 } | |
192 | |
193 private: | |
194 void Init() { | |
195 TrayPopupUtils::ConfigureAsStickyHeader(this); | |
196 SetLayoutManager(new views::FillLayout); | |
197 | |
198 TriView* tri_view = TrayPopupUtils::CreateDefaultRowView(); | |
199 AddChildView(tri_view); | |
200 | |
201 // The on-screen keyboard image button. | |
202 views::ImageView* keyboard_image = TrayPopupUtils::CreateMainImageView(); | |
203 keyboard_image->SetImage(gfx::CreateVectorIcon( | |
204 kImeMenuOnScreenKeyboardIcon, kMenuIconSize, kMenuIconColor)); | |
205 tri_view->AddView(TriView::Container::START, keyboard_image); | |
206 | |
207 // The on-screen keyboard label ('On-screen keyboard'). | |
208 auto* label = TrayPopupUtils::CreateDefaultLabel(); | |
209 label->SetText(ui::ResourceBundle::GetSharedInstance().GetLocalizedString( | |
210 IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD)); | |
211 TrayPopupItemStyle style( | |
212 TrayPopupItemStyle::FontStyle::DETAILED_VIEW_LABEL); | |
213 style.SetupLabel(label); | |
214 tri_view->AddView(TriView::Container::CENTER, label); | |
215 | |
216 // The on-screen keyboard toggle button. | |
217 toggle_ = TrayPopupUtils::CreateToggleButton( | |
218 listener_, IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD); | |
219 tri_view->AddView(TriView::Container::END, toggle_); | |
220 } | |
221 | |
222 // ButtonListener to notify when |toggle_| is clicked. | |
223 views::ButtonListener* listener_; | |
224 | |
225 // ToggleButton to toggle keyboard on or off. | |
226 views::ToggleButton* toggle_; | |
227 | |
228 DISALLOW_COPY_AND_ASSIGN(MaterialKeyboardStatusRowView); | |
229 }; | |
230 | |
231 ImeListView::ImeListView(SystemTrayItem* owner) | |
232 : TrayDetailsView(owner), | |
233 last_item_selected_with_keyboard_(false), | |
234 should_focus_ime_after_selection_with_keyboard_(false), | |
235 current_ime_view_(nullptr) {} | |
236 | |
237 ImeListView::~ImeListView() {} | |
238 | |
239 void ImeListView::Init(bool show_keyboard_toggle, | |
240 SingleImeBehavior single_ime_behavior) { | |
241 SystemTrayDelegate* delegate = WmShell::Get()->system_tray_delegate(); | |
242 IMEInfoList list; | |
243 delegate->GetAvailableIMEList(&list); | |
244 IMEPropertyInfoList property_list; | |
245 delegate->GetCurrentIMEProperties(&property_list); | |
246 Update(list, property_list, show_keyboard_toggle, single_ime_behavior); | |
247 } | |
248 | |
249 void ImeListView::Update(const IMEInfoList& list, | |
250 const IMEPropertyInfoList& property_list, | |
251 bool show_keyboard_toggle, | |
252 SingleImeBehavior single_ime_behavior) { | |
253 ResetImeListView(); | |
254 ime_map_.clear(); | |
255 property_map_.clear(); | |
256 CreateScrollableList(); | |
257 | |
258 // Appends IME list and IME properties. | |
259 if (single_ime_behavior == ImeListView::SHOW_SINGLE_IME || list.size() > 1) { | |
260 if (MaterialDesignController::IsSystemTrayMenuMaterial()) { | |
261 AppendImeListAndProperties(list, property_list); | |
262 } else { | |
263 AppendIMEList(list); | |
264 if (!property_list.empty()) | |
265 AppendIMEProperties(property_list); | |
266 } | |
267 } | |
268 | |
269 if (show_keyboard_toggle) { | |
270 if (MaterialDesignController::IsSystemTrayMenuMaterial()) { | |
271 PrependMaterialKeyboardStatus(); | |
272 } else { | |
273 if (list.size() > 1 || !property_list.empty()) | |
274 AddScrollSeparator(); | |
275 AppendKeyboardStatus(); | |
276 } | |
277 } | |
278 | |
279 Layout(); | |
280 SchedulePaint(); | |
281 | |
282 if (should_focus_ime_after_selection_with_keyboard_ && | |
283 last_item_selected_with_keyboard_) { | |
284 FocusCurrentImeIfNeeded(); | |
285 } else if (current_ime_view_) { | |
286 scroll_content()->ScrollRectToVisible(current_ime_view_->bounds()); | |
287 } | |
288 } | |
289 | |
290 void ImeListView::ResetImeListView() { | |
291 // Children are removed from the view hierarchy and deleted in Reset(). | |
292 Reset(); | |
293 material_keyboard_status_view_ = nullptr; | |
294 keyboard_status_ = nullptr; | |
295 current_ime_view_ = nullptr; | |
296 } | |
297 | |
298 void ImeListView::CloseImeListView() { | |
299 last_selected_item_id_.clear(); | |
300 current_ime_view_ = nullptr; | |
301 last_item_selected_with_keyboard_ = false; | |
302 GetWidget()->Close(); | |
303 } | |
304 | |
305 void ImeListView::AppendIMEList(const IMEInfoList& list) { | |
306 DCHECK(ime_map_.empty()); | |
307 for (size_t i = 0; i < list.size(); i++) { | |
308 HoverHighlightView* container = | |
309 new SelectableHoverHighlightView(this, list[i].name, list[i].selected); | |
310 scroll_content()->AddChildView(container); | |
311 ime_map_[container] = list[i].id; | |
312 } | |
313 } | |
314 | |
315 void ImeListView::AppendIMEProperties( | |
316 const IMEPropertyInfoList& property_list) { | |
317 DCHECK(property_map_.empty()); | |
318 for (size_t i = 0; i < property_list.size(); i++) { | |
319 HoverHighlightView* container = new SelectableHoverHighlightView( | |
320 this, property_list[i].name, property_list[i].selected); | |
321 if (i == 0) | |
322 container->SetBorder( | |
323 views::CreateSolidSidedBorder(1, 0, 0, 0, kBorderLightColor)); | |
324 scroll_content()->AddChildView(container); | |
325 property_map_[container] = property_list[i].key; | |
326 } | |
327 } | |
328 | |
329 void ImeListView::AppendImeListAndProperties( | |
330 const IMEInfoList& list, | |
331 const IMEPropertyInfoList& property_list) { | |
332 DCHECK(ime_map_.empty()); | |
333 for (size_t i = 0; i < list.size(); i++) { | |
334 views::View* ime_view = | |
335 new ImeListItemView(owner(), this, list[i].short_name, list[i].name, | |
336 list[i].selected, gfx::kGoogleGreen700); | |
337 scroll_content()->AddChildView(ime_view); | |
338 ime_map_[ime_view] = list[i].id; | |
339 | |
340 if (list[i].selected) | |
341 current_ime_view_ = ime_view; | |
342 | |
343 // In material design, the property items will be added after the current | |
344 // selected IME item. | |
345 if (list[i].selected && !property_list.empty()) { | |
346 // Adds a separator on the top of property items. | |
347 scroll_content()->AddChildView( | |
348 TrayPopupUtils::CreateListItemSeparator(true)); | |
349 | |
350 // Adds the property items. | |
351 for (size_t i = 0; i < property_list.size(); i++) { | |
352 ImeListItemView* property_view = new ImeListItemView( | |
353 owner(), this, base::string16(), property_list[i].name, | |
354 property_list[i].selected, kMenuIconColor); | |
355 scroll_content()->AddChildView(property_view); | |
356 property_map_[property_view] = property_list[i].key; | |
357 } | |
358 | |
359 // Adds a separator on the bottom of property items if there are still | |
360 // other IMEs under the current one. | |
361 if (i < list.size() - 1) | |
362 scroll_content()->AddChildView( | |
363 TrayPopupUtils::CreateListItemSeparator(true)); | |
364 } | |
365 } | |
366 } | |
367 | |
368 void ImeListView::AppendKeyboardStatus() { | |
369 DCHECK(!MaterialDesignController::IsSystemTrayMenuMaterial()); | |
370 HoverHighlightView* container = new HoverHighlightView(this); | |
371 int id = keyboard::IsKeyboardEnabled() ? IDS_ASH_STATUS_TRAY_DISABLE_KEYBOARD | |
372 : IDS_ASH_STATUS_TRAY_ENABLE_KEYBOARD; | |
373 container->AddLabel( | |
374 ui::ResourceBundle::GetSharedInstance().GetLocalizedString(id), | |
375 gfx::ALIGN_LEFT, false /* highlight */); | |
376 scroll_content()->AddChildView(container); | |
377 keyboard_status_ = container; | |
378 } | |
379 | |
380 void ImeListView::PrependMaterialKeyboardStatus() { | |
381 DCHECK(MaterialDesignController::IsSystemTrayMenuMaterial()); | |
382 DCHECK(!material_keyboard_status_view_); | |
383 MaterialKeyboardStatusRowView* view = | |
384 new MaterialKeyboardStatusRowView(this, keyboard::IsKeyboardEnabled()); | |
385 scroll_content()->AddChildViewAt(view, 0); | |
386 material_keyboard_status_view_ = view; | |
387 } | |
388 | |
389 void ImeListView::HandleViewClicked(views::View* view) { | |
390 if (view == keyboard_status_) { | |
391 WmShell::Get()->ToggleIgnoreExternalKeyboard(); | |
392 last_selected_item_id_.clear(); | |
393 last_item_selected_with_keyboard_ = false; | |
394 return; | |
395 } | |
396 | |
397 SystemTrayDelegate* delegate = WmShell::Get()->system_tray_delegate(); | |
398 std::map<views::View*, std::string>::const_iterator ime = ime_map_.find(view); | |
399 if (ime != ime_map_.end()) { | |
400 WmShell::Get()->RecordUserMetricsAction(UMA_STATUS_AREA_IME_SWITCH_MODE); | |
401 std::string ime_id = ime->second; | |
402 last_selected_item_id_ = ime_id; | |
403 delegate->SwitchIME(ime_id); | |
404 } else { | |
405 std::map<views::View*, std::string>::const_iterator property = | |
406 property_map_.find(view); | |
407 if (property == property_map_.end()) | |
408 return; | |
409 const std::string key = property->second; | |
410 last_selected_item_id_ = key; | |
411 delegate->ActivateIMEProperty(key); | |
412 } | |
413 | |
414 if (!should_focus_ime_after_selection_with_keyboard_ || | |
415 !last_item_selected_with_keyboard_) { | |
416 CloseImeListView(); | |
417 } | |
418 } | |
419 | |
420 void ImeListView::HandleButtonPressed(views::Button* sender, | |
421 const ui::Event& event) { | |
422 if (material_keyboard_status_view_ && | |
423 sender == material_keyboard_status_view_->toggle()) { | |
424 WmShell::Get()->ToggleIgnoreExternalKeyboard(); | |
425 last_selected_item_id_.clear(); | |
426 last_item_selected_with_keyboard_ = false; | |
427 } | |
428 } | |
429 | |
430 void ImeListView::VisibilityChanged(View* starting_from, bool is_visible) { | |
431 if (!is_visible || (should_focus_ime_after_selection_with_keyboard_ && | |
432 last_item_selected_with_keyboard_) || | |
433 !current_ime_view_) { | |
434 return; | |
435 } | |
436 | |
437 scroll_content()->ScrollRectToVisible(current_ime_view_->bounds()); | |
438 } | |
439 | |
440 void ImeListView::FocusCurrentImeIfNeeded() { | |
441 views::FocusManager* manager = GetFocusManager(); | |
442 if (!manager || manager->GetFocusedView() || last_selected_item_id_.empty()) | |
443 return; | |
444 | |
445 for (auto ime_map : ime_map_) { | |
446 if (ime_map.second == last_selected_item_id_) { | |
447 (ime_map.first)->RequestFocus(); | |
448 return; | |
449 } | |
450 } | |
451 | |
452 for (auto property_map : property_map_) { | |
453 if (property_map.second == last_selected_item_id_) { | |
454 (property_map.first)->RequestFocus(); | |
455 return; | |
456 } | |
457 } | |
458 } | |
459 | |
460 ImeListViewTestApi::ImeListViewTestApi(ImeListView* ime_list_view) | |
461 : ime_list_view_(ime_list_view) {} | |
462 | |
463 ImeListViewTestApi::~ImeListViewTestApi() {} | |
464 | |
465 views::View* ImeListViewTestApi::GetToggleView() const { | |
466 return ime_list_view_->material_keyboard_status_view_->toggle(); | |
467 } | |
468 | |
469 } // namespace ash | |
OLD | NEW |