ime_list_view.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  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. #include "ash/system/ime_menu/ime_list_view.h"
  5. #include "ash/ime/ime_controller_impl.h"
  6. #include "ash/ime/ime_switch_type.h"
  7. #include "ash/keyboard/keyboard_controller_impl.h"
  8. #include "ash/keyboard/ui/keyboard_util.h"
  9. #include "ash/keyboard/virtual_keyboard_controller.h"
  10. #include "ash/public/cpp/ime_info.h"
  11. #include "ash/resources/vector_icons/vector_icons.h"
  12. #include "ash/shell.h"
  13. #include "ash/strings/grit/ash_strings.h"
  14. #include "ash/style/ash_color_provider.h"
  15. #include "ash/system/tray/actionable_view.h"
  16. #include "ash/system/tray/system_menu_button.h"
  17. #include "ash/system/tray/tray_detailed_view.h"
  18. #include "ash/system/tray/tray_popup_utils.h"
  19. #include "ash/system/tray/tray_toggle_button.h"
  20. #include "ash/system/tray/tri_view.h"
  21. #include "base/bind.h"
  22. #include "base/metrics/histogram_macros.h"
  23. #include "base/metrics/user_metrics.h"
  24. #include "ui/accessibility/ax_enums.mojom.h"
  25. #include "ui/accessibility/ax_node_data.h"
  26. #include "ui/base/l10n/l10n_util.h"
  27. #include "ui/base/metadata/metadata_impl_macros.h"
  28. #include "ui/base/resource/resource_bundle.h"
  29. #include "ui/gfx/color_palette.h"
  30. #include "ui/gfx/paint_vector_icon.h"
  31. #include "ui/views/animation/ink_drop.h"
  32. #include "ui/views/background.h"
  33. #include "ui/views/controls/button/toggle_button.h"
  34. #include "ui/views/controls/image_view.h"
  35. #include "ui/views/controls/label.h"
  36. #include "ui/views/controls/separator.h"
  37. #include "ui/views/layout/fill_layout.h"
  38. #include "ui/views/painter.h"
  39. #include "ui/views/view.h"
  40. #include "ui/views/widget/widget.h"
  41. namespace ash {
  42. namespace {
  43. const int kMinFontSizeDelta = -10;
  44. // Represents a row in the scrollable IME list; each row is either an IME or
  45. // an IME property. A checkmark icon is shown in the row if selected.
  46. class ImeListItemView : public ActionableView {
  47. public:
  48. METADATA_HEADER(ImeListItemView);
  49. ImeListItemView(ImeListView* list_view,
  50. const std::u16string& id,
  51. const std::u16string& label,
  52. bool selected,
  53. const SkColor button_color)
  54. : ActionableView(TrayPopupInkDropStyle::FILL_BOUNDS),
  55. ime_list_view_(list_view),
  56. selected_(selected) {
  57. views::InkDrop::Get(this)->SetMode(views::InkDropHost::InkDropMode::ON);
  58. TriView* tri_view = TrayPopupUtils::CreateDefaultRowView();
  59. AddChildView(tri_view);
  60. SetLayoutManager(std::make_unique<views::FillLayout>());
  61. // |id_label| contains the IME short name (e.g., 'US', 'GB', 'IT').
  62. views::Label* id_label = TrayPopupUtils::CreateDefaultLabel();
  63. id_label->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
  64. AshColorProvider::ContentLayerType::kTextColorPrimary));
  65. id_label->SetAutoColorReadabilityEnabled(false);
  66. id_label->SetText(id);
  67. ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
  68. const gfx::FontList& base_font_list =
  69. rb.GetFontList(ui::ResourceBundle::MediumBoldFont);
  70. id_label->SetFontList(base_font_list);
  71. // IMEs having a short name of more than two characters (e.g., 'INTL') will
  72. // elide if rendered within |kMenuIconSize|. Shrink the font size until the
  73. // entire short name fits within the bounds.
  74. int size_delta = -1;
  75. while ((id_label->GetPreferredSize().width() -
  76. id_label->GetInsets().width()) > kMenuIconSize &&
  77. size_delta >= kMinFontSizeDelta) {
  78. id_label->SetFontList(base_font_list.DeriveWithSizeDelta(size_delta));
  79. --size_delta;
  80. }
  81. tri_view->AddView(TriView::Container::START, id_label);
  82. // The label shows the IME full name.
  83. auto* label_view = TrayPopupUtils::CreateDefaultLabel();
  84. label_view->SetText(label);
  85. label_view->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
  86. AshColorProvider::ContentLayerType::kTextColorPrimary));
  87. TrayPopupUtils::SetLabelFontList(
  88. label_view, TrayPopupUtils::FontStyle::kDetailedViewLabel);
  89. label_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  90. tri_view->AddView(TriView::Container::CENTER, label_view);
  91. if (selected) {
  92. // The checked button indicates the IME is selected.
  93. views::ImageView* checked_image = TrayPopupUtils::CreateMainImageView();
  94. checked_image->SetImage(gfx::CreateVectorIcon(
  95. kHollowCheckCircleIcon, kMenuIconSize, button_color));
  96. tri_view->AddView(TriView::Container::END, checked_image);
  97. }
  98. SetAccessibleName(label_view->GetText());
  99. }
  100. ImeListItemView(const ImeListItemView&) = delete;
  101. ImeListItemView& operator=(const ImeListItemView&) = delete;
  102. ~ImeListItemView() override = default;
  103. // ActionableView:
  104. bool PerformAction(const ui::Event& event) override {
  105. ime_list_view_->set_last_item_selected_with_keyboard(
  106. ime_list_view_->should_focus_ime_after_selection_with_keyboard() &&
  107. event.type() == ui::EventType::ET_KEY_PRESSED);
  108. ime_list_view_->HandleViewClicked(this);
  109. return true;
  110. }
  111. void OnFocus() override {
  112. ActionableView::OnFocus();
  113. if (ime_list_view_)
  114. ime_list_view_->ScrollItemToVisible(this);
  115. }
  116. void GetAccessibleNodeData(ui::AXNodeData* node_data) override {
  117. ActionableView::GetAccessibleNodeData(node_data);
  118. node_data->role = ax::mojom::Role::kCheckBox;
  119. node_data->SetCheckedState(selected_ ? ax::mojom::CheckedState::kTrue
  120. : ax::mojom::CheckedState::kFalse);
  121. }
  122. private:
  123. ImeListView* ime_list_view_;
  124. bool selected_;
  125. };
  126. BEGIN_METADATA(ImeListItemView, ActionableView)
  127. END_METADATA
  128. } // namespace
  129. // Contains a toggle button to let the user enable/disable whether the
  130. // on-screen keyboard should be shown when focusing a textfield. This row is
  131. // shown only under certain conditions, e.g., when an external keyboard is
  132. // attached and the user is in TabletMode mode.
  133. class KeyboardStatusRow : public views::View {
  134. public:
  135. METADATA_HEADER(KeyboardStatusRow);
  136. KeyboardStatusRow() = default;
  137. KeyboardStatusRow(const KeyboardStatusRow&) = delete;
  138. KeyboardStatusRow& operator=(const KeyboardStatusRow&) = delete;
  139. ~KeyboardStatusRow() override = default;
  140. views::ToggleButton* toggle() const { return toggle_; }
  141. void Init(views::Button::PressedCallback callback) {
  142. TrayPopupUtils::ConfigureAsStickyHeader(this);
  143. SetLayoutManager(std::make_unique<views::FillLayout>());
  144. TriView* tri_view = TrayPopupUtils::CreateDefaultRowView();
  145. AddChildView(tri_view);
  146. auto* color_provider = AshColorProvider::Get();
  147. // The on-screen keyboard image button.
  148. views::ImageView* keyboard_image = TrayPopupUtils::CreateMainImageView();
  149. keyboard_image->SetImage(gfx::CreateVectorIcon(
  150. kImeMenuOnScreenKeyboardIcon, kMenuIconSize,
  151. color_provider->GetContentLayerColor(
  152. AshColorProvider::ContentLayerType::kIconColorPrimary)));
  153. tri_view->AddView(TriView::Container::START, keyboard_image);
  154. // The on-screen keyboard label ('On-screen keyboard').
  155. auto* label = TrayPopupUtils::CreateDefaultLabel();
  156. label->SetText(ui::ResourceBundle::GetSharedInstance().GetLocalizedString(
  157. IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD));
  158. label->SetEnabledColor(color_provider->GetContentLayerColor(
  159. AshColorProvider::ContentLayerType::kTextColorPrimary));
  160. TrayPopupUtils::SetLabelFontList(
  161. label, TrayPopupUtils::FontStyle::kDetailedViewLabel);
  162. tri_view->AddView(TriView::Container::CENTER, label);
  163. // The on-screen keyboard toggle button.
  164. toggle_ = new TrayToggleButton(
  165. std::move(callback),
  166. IDS_ASH_STATUS_TRAY_ACCESSIBILITY_VIRTUAL_KEYBOARD);
  167. toggle_->SetIsOn(keyboard::IsKeyboardEnabled());
  168. tri_view->AddView(TriView::Container::END, toggle_);
  169. }
  170. private:
  171. // ToggleButton to toggle keyboard on or off.
  172. views::ToggleButton* toggle_ = nullptr;
  173. };
  174. BEGIN_METADATA(KeyboardStatusRow, views::View)
  175. END_METADATA
  176. ImeListView::ImeListView(DetailedViewDelegate* delegate)
  177. : TrayDetailedView(delegate) {}
  178. ImeListView::~ImeListView() = default;
  179. void ImeListView::Init(bool show_keyboard_toggle,
  180. SingleImeBehavior single_ime_behavior) {
  181. ImeControllerImpl* ime_controller = Shell::Get()->ime_controller();
  182. Update(ime_controller->current_ime().id, ime_controller->GetVisibleImes(),
  183. ime_controller->current_ime_menu_items(), show_keyboard_toggle,
  184. single_ime_behavior);
  185. }
  186. void ImeListView::Update(const std::string& current_ime_id,
  187. const std::vector<ImeInfo>& list,
  188. const std::vector<ImeMenuItem>& property_items,
  189. bool show_keyboard_toggle,
  190. SingleImeBehavior single_ime_behavior) {
  191. ResetImeListView();
  192. ime_map_.clear();
  193. property_map_.clear();
  194. CreateScrollableList();
  195. if (single_ime_behavior == ImeListView::SHOW_SINGLE_IME || list.size() > 1)
  196. AppendImeListAndProperties(current_ime_id, list, property_items);
  197. if (show_keyboard_toggle)
  198. PrependKeyboardStatusRow();
  199. Layout();
  200. SchedulePaint();
  201. if (should_focus_ime_after_selection_with_keyboard_ &&
  202. last_item_selected_with_keyboard_) {
  203. FocusCurrentImeIfNeeded();
  204. } else if (current_ime_view_) {
  205. ScrollItemToVisible(current_ime_view_);
  206. }
  207. }
  208. void ImeListView::ResetImeListView() {
  209. // Children are removed from the view hierarchy and deleted in Reset().
  210. Reset();
  211. keyboard_status_row_ = nullptr;
  212. current_ime_view_ = nullptr;
  213. }
  214. void ImeListView::ScrollItemToVisible(views::View* item_view) {
  215. if (scroll_content())
  216. scroll_content()->ScrollRectToVisible(item_view->bounds());
  217. }
  218. void ImeListView::CloseImeListView() {
  219. last_selected_item_id_.clear();
  220. current_ime_view_ = nullptr;
  221. last_item_selected_with_keyboard_ = false;
  222. GetWidget()->Close();
  223. }
  224. void ImeListView::AppendImeListAndProperties(
  225. const std::string& current_ime_id,
  226. const std::vector<ImeInfo>& list,
  227. const std::vector<ImeMenuItem>& property_list) {
  228. DCHECK(ime_map_.empty());
  229. for (size_t i = 0; i < list.size(); i++) {
  230. const bool selected = current_ime_id == list[i].id;
  231. views::View* ime_view = new ImeListItemView(
  232. this, list[i].short_name, list[i].name, selected,
  233. AshColorProvider::Get()->GetContentLayerColor(
  234. AshColorProvider::ContentLayerType::kIconColorProminent));
  235. scroll_content()->AddChildView(ime_view);
  236. ime_map_[ime_view] = list[i].id;
  237. if (selected)
  238. current_ime_view_ = ime_view;
  239. // Add the properties, if any, of the currently-selected IME.
  240. if (selected && !property_list.empty()) {
  241. // Adds a separator on the top of property items.
  242. scroll_content()->AddChildView(
  243. TrayPopupUtils::CreateListItemSeparator(true));
  244. const SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
  245. AshColorProvider::ContentLayerType::kIconColorPrimary);
  246. // Adds the property items.
  247. for (size_t i = 0; i < property_list.size(); i++) {
  248. ImeListItemView* property_view =
  249. new ImeListItemView(this, std::u16string(), property_list[i].label,
  250. property_list[i].checked, icon_color);
  251. scroll_content()->AddChildView(property_view);
  252. property_map_[property_view] = property_list[i].key;
  253. }
  254. // Adds a separator on the bottom of property items if there are still
  255. // other IMEs under the current one.
  256. if (i < list.size() - 1)
  257. scroll_content()->AddChildView(
  258. TrayPopupUtils::CreateListItemSeparator(true));
  259. }
  260. }
  261. }
  262. void ImeListView::PrependKeyboardStatusRow() {
  263. DCHECK(!keyboard_status_row_);
  264. keyboard_status_row_ = new KeyboardStatusRow;
  265. keyboard_status_row_->Init(base::BindRepeating(
  266. &ImeListView::KeyboardStatusTogglePressed, base::Unretained(this)));
  267. scroll_content()->AddChildViewAt(keyboard_status_row_, 0);
  268. }
  269. void ImeListView::KeyboardStatusTogglePressed() {
  270. Shell::Get()
  271. ->keyboard_controller()
  272. ->virtual_keyboard_controller()
  273. ->ToggleIgnoreExternalKeyboard();
  274. last_selected_item_id_.clear();
  275. last_item_selected_with_keyboard_ = false;
  276. }
  277. void ImeListView::HandleViewClicked(views::View* view) {
  278. ImeControllerImpl* ime_controller = Shell::Get()->ime_controller();
  279. std::map<views::View*, std::string>::const_iterator ime = ime_map_.find(view);
  280. if (ime != ime_map_.end()) {
  281. base::RecordAction(base::UserMetricsAction("StatusArea_IME_SwitchMode"));
  282. std::string ime_id = ime->second;
  283. last_selected_item_id_ = ime_id;
  284. ime_controller->SwitchImeById(ime_id, false /* show_message */);
  285. UMA_HISTOGRAM_ENUMERATION("InputMethod.ImeSwitch", ImeSwitchType::kTray);
  286. } else {
  287. std::map<views::View*, std::string>::const_iterator property =
  288. property_map_.find(view);
  289. if (property == property_map_.end())
  290. return;
  291. const std::string key = property->second;
  292. last_selected_item_id_ = key;
  293. ime_controller->ActivateImeMenuItem(key);
  294. }
  295. if (!should_focus_ime_after_selection_with_keyboard_ ||
  296. !last_item_selected_with_keyboard_) {
  297. CloseImeListView();
  298. }
  299. }
  300. void ImeListView::VisibilityChanged(View* starting_from, bool is_visible) {
  301. if (!is_visible ||
  302. (should_focus_ime_after_selection_with_keyboard_ &&
  303. last_item_selected_with_keyboard_) ||
  304. !current_ime_view_) {
  305. return;
  306. }
  307. ScrollItemToVisible(current_ime_view_);
  308. }
  309. void ImeListView::FocusCurrentImeIfNeeded() {
  310. views::FocusManager* manager = GetFocusManager();
  311. if (!manager || manager->GetFocusedView() || last_selected_item_id_.empty())
  312. return;
  313. for (auto ime_map : ime_map_) {
  314. if (ime_map.second == last_selected_item_id_) {
  315. (ime_map.first)->RequestFocus();
  316. return;
  317. }
  318. }
  319. for (auto property_map : property_map_) {
  320. if (property_map.second == last_selected_item_id_) {
  321. (property_map.first)->RequestFocus();
  322. return;
  323. }
  324. }
  325. }
  326. BEGIN_METADATA(ImeListView, TrayDetailedView)
  327. END_METADATA
  328. ImeListViewTestApi::ImeListViewTestApi(ImeListView* ime_list_view)
  329. : ime_list_view_(ime_list_view) {}
  330. ImeListViewTestApi::~ImeListViewTestApi() = default;
  331. views::View* ImeListViewTestApi::GetToggleView() const {
  332. if (!ime_list_view_->keyboard_status_row_)
  333. return nullptr;
  334. return ime_list_view_->keyboard_status_row_->toggle();
  335. }
  336. } // namespace ash