| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/system/ime_menu/ime_menu_tray.h" |
| |
| #include <memory> |
| |
| #include "ash/accessibility/a11y_feature_type.h" |
| #include "ash/accessibility/accessibility_controller.h" |
| #include "ash/constants/tray_background_view_catalog.h" |
| #include "ash/ime/ime_controller_impl.h" |
| #include "ash/keyboard/keyboard_controller_impl.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/keyboard/virtual_keyboard_controller.h" |
| #include "ash/metrics/user_metrics_recorder.h" |
| #include "ash/public/cpp/ash_view_ids.h" |
| #include "ash/public/cpp/system_tray_client.h" |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/style/ash_color_id.h" |
| #include "ash/style/icon_button.h" |
| #include "ash/style/rounded_container.h" |
| #include "ash/style/typography.h" |
| #include "ash/system/ime_menu/ime_list_view.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/system/tray/detailed_view_delegate.h" |
| #include "ash/system/tray/system_menu_button.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "ash/system/tray/tray_background_view.h" |
| #include "ash/system/tray/tray_constants.h" |
| #include "ash/system/tray/tray_container.h" |
| #include "ash/system/tray/tray_popup_utils.h" |
| #include "ash/system/tray/tray_utils.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/session_manager/session_manager_types.h" |
| #include "ui/base/emoji/emoji_panel_helper.h" |
| #include "ui/base/ime/ash/extension_ime_util.h" |
| #include "ui/base/ime/ash/ime_bridge.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/models/image_model.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/chromeos/styles/cros_tokens_color_mappings.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/range/range.h" |
| #include "ui/views/accessibility/view_accessibility.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/controls/scroll_view.h" |
| #include "ui/views/controls/separator.h" |
| #include "ui/views/layout/box_layout.h" |
| #include "ui/views/layout/box_layout_view.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Used for testing. |
| const int kEmojiButtonId = 1; |
| const int kSettingsButtonId = 2; |
| const int kVoiceButtonId = 3; |
| |
| // Insets for the title view (dp). |
| constexpr auto kTitleViewPadding = |
| gfx::Insets::VH(0, kMenuEdgeEffectivePadding); |
| |
| // Insets for the bubble view to fix the overlapping |
| // between the floating menu and the IME tray in kiosk session (dp). |
| constexpr auto kKioskBubbleViewPadding = gfx::Insets::TLBR(-19, 0, 27, 0); |
| |
| // The scroll view has no margin when the bottom buttons are shown at the top or |
| // bottom to make it flush with the header and footer. |
| constexpr auto kQsScrollViewMargin = gfx::Insets::TLBR(0, 16, 0, 16); |
| |
| // When the bottom buttons are not shown (e.g Lockscreen) we need to have a 16px |
| // inset on the bottom in addition to the existing insets. |
| constexpr auto kQsScrollViewMarginWithoutBottomButtons = |
| gfx::Insets::TLBR(0, 16, 16, 16); |
| |
| // Returns the height range of ImeListView. |
| gfx::Range GetImeListViewRange() { |
| const int max_items = 5; |
| const int min_items = 1; |
| const int tray_item_height = kTrayPopupItemMinHeight; |
| // Insets at the top and bottom of the RoundedContainer. |
| const int insets = RoundedContainer::kBorderInsets.top() + |
| RoundedContainer::kBorderInsets.bottom(); |
| return gfx::Range(tray_item_height * min_items + insets, |
| tray_item_height * max_items + insets); |
| } |
| |
| // Returns true if the current screen is login or lock screen. |
| bool IsInLoginOrLockScreen() { |
| using session_manager::SessionState; |
| SessionState state = Shell::Get()->session_controller()->GetSessionState(); |
| return state == SessionState::LOGIN_PRIMARY || |
| state == SessionState::LOCKED || |
| state == SessionState::LOGIN_SECONDARY; |
| } |
| |
| // Returns true if the current input context type is password. |
| bool IsInPasswordInputContext() { |
| return IMEBridge::Get()->GetCurrentInputContext().type == |
| ui::TEXT_INPUT_TYPE_PASSWORD; |
| } |
| |
| // Returns true if it is Kiosk Session. |
| bool IsKioskSession() { |
| return Shell::Get()->session_controller()->IsRunningInAppMode(); |
| } |
| |
| bool ShouldShowVoiceButton() { |
| auto* ime_controller = Shell::Get()->ime_controller(); |
| const bool is_dictation_enabled = |
| Shell::Get() |
| ->accessibility_controller() |
| ->GetFeature(A11yFeatureType::kDictation) |
| .enabled(); |
| |
| // Only enable voice button in IME tray if the function is enabled and |
| // the accessibility dictation is not enabled in the shelf. |
| return ime_controller->is_voice_enabled() && !is_dictation_enabled; |
| } |
| |
| // Returns true if the menu should show emoji, handwriting and voice buttons |
| // on the bottom. |
| bool ShouldShowBottomButtons() { |
| // Emoji, handwriting and voice input is not supported for these cases: |
| // 1) third party IME extensions. |
| // 2) login/lock screen. |
| // 3) password input client. |
| auto* ime_controller = Shell::Get()->ime_controller(); |
| bool bottom_buttons_enabled = |
| ime_controller->is_extra_input_options_enabled() && |
| !ime_controller->current_ime().third_party && !IsInLoginOrLockScreen() && |
| !IsInPasswordInputContext(); |
| if (!bottom_buttons_enabled) { |
| return false; |
| } |
| |
| return ime_controller->is_emoji_enabled() || |
| ime_controller->is_handwriting_enabled() || ShouldShowVoiceButton(); |
| } |
| |
| class ImeMenuLabel : public views::Label { |
| METADATA_HEADER(ImeMenuLabel, views::Label) |
| |
| public: |
| ImeMenuLabel() { |
| // Sometimes the label will be more than 2 characters, e.g. INTL and EXTD. |
| // This border makes sure we only leave room for ~2 and the others are |
| // truncated. |
| SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(0, 6))); |
| } |
| ImeMenuLabel(const ImeMenuLabel&) = delete; |
| ImeMenuLabel& operator=(const ImeMenuLabel&) = delete; |
| ~ImeMenuLabel() override = default; |
| |
| // views:Label: |
| gfx::Size CalculatePreferredSize( |
| const views::SizeBounds& available_size) const override { |
| return gfx::Size(kTrayItemSize, kTrayItemSize); |
| } |
| }; |
| |
| BEGIN_METADATA(ImeMenuLabel) |
| END_METADATA |
| |
| class ImeMenuImageView : public views::ImageView { |
| METADATA_HEADER(ImeMenuImageView, views::ImageView) |
| |
| public: |
| ImeMenuImageView() { |
| SetBorder(views::CreateEmptyBorder(gfx::Insets::VH(0, 6))); |
| } |
| ImeMenuImageView(const ImeMenuImageView&) = delete; |
| ImeMenuImageView& operator=(const ImeMenuImageView&) = delete; |
| ~ImeMenuImageView() override = default; |
| }; |
| |
| BEGIN_METADATA(ImeMenuImageView) |
| END_METADATA |
| |
| // The view that contains IME menu title. |
| class ImeTitleView : public views::BoxLayoutView { |
| METADATA_HEADER(ImeTitleView, views::BoxLayoutView) |
| |
| public: |
| ImeTitleView() { |
| SetID(VIEW_ID_IME_TITLE_VIEW); |
| SetOrientation(views::BoxLayout::Orientation::kHorizontal); |
| SetInsideBorderInsets(kTitleViewPadding); |
| SetMinimumCrossAxisSize(kTrayPopupItemMinHeight); |
| SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter); |
| |
| auto* title_label = AddChildView(std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_IME))); |
| title_label->SetBorder( |
| views::CreateEmptyBorder(gfx::Insets::TLBR(0, 0, 1, 0))); |
| title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| title_label->SetEnabledColor(kColorAshTextColorPrimary); |
| title_label->SetAutoColorReadabilityEnabled(false); |
| TypographyProvider::Get()->StyleLabel(TypographyToken::kCrosTitle1, |
| *title_label); |
| SetFlexForView(title_label, 1); |
| |
| // Don't create Settings Button if it is Kiosk session. |
| if (!IsKioskSession()) { |
| settings_button_ = AddChildView(std::make_unique<IconButton>( |
| base::BindRepeating([]() { |
| base::RecordAction( |
| base::UserMetricsAction("StatusArea_IME_Detailed")); |
| Shell::Get()->system_tray_model()->client()->ShowIMESettings(); |
| }), |
| IconButton::Type::kMedium, &kSystemMenuSettingsIcon, |
| IDS_ASH_STATUS_TRAY_IME_SETTINGS)); |
| settings_button_->SetEnabled(TrayPopupUtils::CanOpenWebUISettings()); |
| settings_button_->SetID(kSettingsButtonId); |
| } |
| } |
| ImeTitleView(const ImeTitleView&) = delete; |
| ImeTitleView& operator=(const ImeTitleView&) = delete; |
| |
| ~ImeTitleView() override = default; |
| |
| private: |
| raw_ptr<IconButton> settings_button_ = nullptr; |
| }; |
| |
| BEGIN_METADATA(ImeTitleView) |
| END_METADATA |
| |
| // The view that contains buttons shown on the bottom of IME menu. |
| class ImeButtonsView : public views::View { |
| METADATA_HEADER(ImeButtonsView, views::View) |
| |
| public: |
| ImeButtonsView(ImeMenuTray* ime_menu_tray, |
| bool show_emoji, |
| bool show_handwriting, |
| bool show_voice) |
| : ime_menu_tray_(ime_menu_tray) { |
| DCHECK(ime_menu_tray_); |
| SetID(VIEW_ID_IME_BUTTONS_VIEW); |
| Init(show_emoji, show_handwriting, show_voice); |
| } |
| ImeButtonsView(const ImeButtonsView&) = delete; |
| ImeButtonsView& operator=(const ImeButtonsView&) = delete; |
| |
| ~ImeButtonsView() override = default; |
| |
| void KeysetButtonPressed(input_method::ImeKeyset keyset) { |
| // TODO(dcheng): When https://crbug.com/742517 is fixed, Mojo will |
| // generate a constant for the number of values in the enum. For now, we |
| // just define it here and keep it in sync with the enum. |
| const int kImeKeysetUmaBoundary = 4; |
| UMA_HISTOGRAM_ENUMERATION("InputMethod.ImeMenu.EmojiHandwritingVoiceButton", |
| keyset, kImeKeysetUmaBoundary); |
| |
| // The |keyset| will be used for drawing input view keyset in IME |
| // extensions. ImeMenuTray::ShowKeyboardWithKeyset() will deal with |
| // the |keyset| string to generate the right input view url. |
| ime_menu_tray_->ShowKeyboardWithKeyset(keyset); |
| } |
| |
| private: |
| void Init(bool show_emoji, bool show_handwriting, bool show_voice) { |
| auto box_layout = std::make_unique<views::BoxLayout>( |
| views::BoxLayout::Orientation::kHorizontal); |
| box_layout->set_minimum_cross_axis_size(kTrayPopupItemMinHeight); |
| SetLayoutManager(std::move(box_layout)); |
| SetBorder(views::CreateEmptyBorder( |
| gfx::Insets::VH(0, kMenuExtraMarginFromLeftEdge))); |
| if (show_emoji) { |
| emoji_button_ = new SystemMenuButton( |
| base::BindRepeating(&ImeButtonsView::KeysetButtonPressed, |
| base::Unretained(this), |
| input_method::ImeKeyset::kEmoji), |
| kImeMenuEmoticonIcon, IDS_ASH_STATUS_TRAY_IME_EMOJI); |
| emoji_button_->SetID(kEmojiButtonId); |
| AddChildViewRaw(emoji_button_.get()); |
| } |
| |
| if (show_handwriting) { |
| handwriting_button_ = new SystemMenuButton( |
| base::BindRepeating(&ImeButtonsView::KeysetButtonPressed, |
| base::Unretained(this), |
| input_method::ImeKeyset::kHandwriting), |
| kImeMenuWriteIcon, IDS_ASH_STATUS_TRAY_IME_HANDWRITING); |
| AddChildViewRaw(handwriting_button_.get()); |
| } |
| |
| if (show_voice) { |
| voice_button_ = new SystemMenuButton( |
| base::BindRepeating(&ImeButtonsView::KeysetButtonPressed, |
| base::Unretained(this), |
| input_method::ImeKeyset::kVoice), |
| kImeMenuMicrophoneIcon, IDS_ASH_STATUS_TRAY_IME_VOICE); |
| voice_button_->SetID(kVoiceButtonId); |
| AddChildViewRaw(voice_button_.get()); |
| } |
| } |
| |
| raw_ptr<ImeMenuTray, DanglingUntriaged> ime_menu_tray_; |
| raw_ptr<SystemMenuButton> emoji_button_; |
| raw_ptr<SystemMenuButton> handwriting_button_; |
| raw_ptr<SystemMenuButton> voice_button_; |
| }; |
| |
| BEGIN_METADATA(ImeButtonsView) |
| END_METADATA |
| |
| // A list of available IMEs shown in the opt-in IME menu, which has a |
| // different height depending on the number of IMEs in the list. |
| class ImeMenuListView : public ImeListView { |
| METADATA_HEADER(ImeMenuListView, ImeListView) |
| |
| public: |
| ImeMenuListView() : ImeMenuListView(std::make_unique<Delegate>()) { |
| SetID(VIEW_ID_IME_MENU_LIST_VIEW); |
| } |
| ImeMenuListView(const ImeMenuListView&) = delete; |
| ImeMenuListView& operator=(const ImeMenuListView&) = delete; |
| |
| ~ImeMenuListView() override = default; |
| |
| private: |
| class Delegate : public DetailedViewDelegate { |
| public: |
| Delegate() : DetailedViewDelegate(nullptr /* tray_controller */) {} |
| |
| Delegate(const Delegate&) = delete; |
| Delegate& operator=(const Delegate&) = delete; |
| |
| // DetailedViewDelegate: |
| void TransitionToMainView(bool restore_focus) override {} |
| void CloseBubble() override {} |
| gfx::Insets GetScrollViewMargin() const override { |
| return ShouldShowBottomButtons() |
| ? kQsScrollViewMargin |
| : kQsScrollViewMarginWithoutBottomButtons; |
| } |
| }; |
| |
| explicit ImeMenuListView(std::unique_ptr<Delegate> delegate) |
| : ImeListView(delegate.get()) { |
| set_should_focus_ime_after_selection_with_keyboard(true); |
| delegate_ = std::move(delegate); |
| } |
| |
| // ImeListView: |
| void Layout(PassKey) override { |
| gfx::Range height_range = GetImeListViewRange(); |
| scroller()->ClipHeightTo(height_range.start(), height_range.end()); |
| LayoutSuperclass<ImeListView>(this); |
| } |
| |
| std::unique_ptr<Delegate> delegate_; |
| }; |
| |
| BEGIN_METADATA(ImeMenuListView) |
| END_METADATA |
| |
| } // namespace |
| |
| ImeMenuTray::ImeMenuTray(Shelf* shelf) |
| : TrayBackgroundView(shelf, TrayBackgroundViewCatalogName::kImeMenu), |
| ime_controller_(Shell::Get()->ime_controller()), |
| label_(nullptr), |
| image_view_(nullptr), |
| keyboard_suppressed_(false), |
| show_bubble_after_keyboard_hidden_(false), |
| is_emoji_enabled_(false), |
| is_handwriting_enabled_(false), |
| is_voice_enabled_(false) { |
| DCHECK(ime_controller_); |
| SetCallback(base::BindRepeating(&ImeMenuTray::OnTrayButtonPressed, |
| weak_ptr_factory_.GetWeakPtr())); |
| CreateLabel(); |
| SystemTrayNotifier* tray_notifier = Shell::Get()->system_tray_notifier(); |
| tray_notifier->AddIMEObserver(this); |
| tray_notifier->AddVirtualKeyboardObserver(this); |
| |
| // Show the tray even if virtual keyboard is shown. (Other tray buttons will |
| // be hidden). |
| set_show_with_virtual_keyboard(true); |
| |
| GetViewAccessibility().SetName( |
| l10n_util::GetStringUTF16(IDS_ASH_IME_MENU_ACCESSIBLE_NAME)); |
| } |
| |
| ImeMenuTray::~ImeMenuTray() { |
| if (bubble_) { |
| bubble_->bubble_view()->ResetDelegate(); |
| } |
| SystemTrayNotifier* tray_notifier = Shell::Get()->system_tray_notifier(); |
| tray_notifier->RemoveIMEObserver(this); |
| tray_notifier->RemoveVirtualKeyboardObserver(this); |
| auto* keyboard_controller = keyboard::KeyboardUIController::Get(); |
| if (keyboard_controller->HasObserver(this)) { |
| keyboard_controller->RemoveObserver(this); |
| } |
| } |
| |
| void ImeMenuTray::OnTrayButtonPressed() { |
| UserMetricsRecorder::RecordUserClickOnTray( |
| LoginMetricsRecorder::TrayClickTarget::kImeTray); |
| |
| if (GetBubbleWidget()) { |
| CloseBubble(); |
| return; |
| } |
| |
| ShowBubble(); |
| } |
| |
| void ImeMenuTray::ShowImeMenuBubbleInternal() { |
| TrayBubbleView::InitParams init_params = CreateInitParamsForTrayBubble(this); |
| if (IsKioskSession()) { |
| init_params.insets = kKioskBubbleViewPadding; |
| } |
| |
| std::unique_ptr<TrayBubbleView> bubble_view = |
| std::make_unique<TrayBubbleView>(init_params); |
| |
| // Add a title item with a separator on the top of the IME menu. |
| bubble_view->AddChildView(std::make_unique<ImeTitleView>()); |
| |
| // Adds IME list to the bubble. |
| ime_list_view_ = |
| bubble_view->AddChildView(std::make_unique<ImeMenuListView>()); |
| ime_list_view_->Init(ShouldShowKeyboardToggle(), |
| ImeListView::SHOW_SINGLE_IME); |
| |
| if (ShouldShowBottomButtons()) { |
| auto* ime_controller = Shell::Get()->ime_controller(); |
| |
| is_emoji_enabled_ = ime_controller->is_emoji_enabled(); |
| is_handwriting_enabled_ = ime_controller->is_handwriting_enabled(); |
| is_voice_enabled_ = ShouldShowVoiceButton(); |
| |
| bubble_view->AddChildView(std::make_unique<ImeButtonsView>( |
| this, is_emoji_enabled_, is_handwriting_enabled_, is_voice_enabled_)); |
| } else { |
| is_emoji_enabled_ = is_handwriting_enabled_ = is_voice_enabled_ = false; |
| } |
| |
| bubble_ = std::make_unique<TrayBubbleWrapper>(this); |
| bubble_->ShowBubble(std::move(bubble_view)); |
| SetIsActive(true); |
| |
| Shell::Get()->system_tray_notifier()->NotifyImeMenuTrayBubbleShown(); |
| } |
| |
| void ImeMenuTray::ShowKeyboardWithKeyset(input_method::ImeKeyset keyset) { |
| CloseBubble(); |
| |
| // Show emoji in the same way as other means of opening and showing emoji |
| // for laptop and tablet mode. |
| if (keyset == input_method::ImeKeyset::kEmoji) { |
| ui::ShowEmojiPanel(); |
| } else { |
| Shell::Get() |
| ->keyboard_controller() |
| ->virtual_keyboard_controller() |
| ->ForceShowKeyboardWithKeyset(keyset); |
| } |
| } |
| |
| bool ImeMenuTray::ShouldShowKeyboardToggle() const { |
| return keyboard_suppressed_ && !Shell::Get() |
| ->accessibility_controller() |
| ->virtual_keyboard() |
| .enabled(); |
| } |
| |
| void ImeMenuTray::OnThemeChanged() { |
| TrayBackgroundView::OnThemeChanged(); |
| UpdateTrayLabel(); |
| } |
| |
| void ImeMenuTray::HandleLocaleChange() { |
| if (image_view_) { |
| image_view_->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_IME)); |
| } |
| |
| if (label_) { |
| label_->SetCustomTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_IME)); |
| } |
| } |
| |
| void ImeMenuTray::HideBubbleWithView(const TrayBubbleView* bubble_view) { |
| if (bubble_->bubble_view() == bubble_view) { |
| CloseBubble(); |
| } |
| } |
| |
| void ImeMenuTray::ClickedOutsideBubble(const ui::LocatedEvent& event) { |
| CloseBubble(); |
| } |
| |
| void ImeMenuTray::UpdateTrayItemColor(bool is_active) { |
| UpdateTrayImageOrLabelColor( |
| extension_ime_util::IsArcIME(ime_controller_->current_ime().id)); |
| } |
| |
| void ImeMenuTray::CloseBubbleInternal() { |
| bubble_.reset(); |
| ime_list_view_ = nullptr; |
| SetIsActive(false); |
| shelf()->UpdateAutoHideState(); |
| } |
| |
| void ImeMenuTray::ShowBubble() { |
| auto* keyboard_controller = keyboard::KeyboardUIController::Get(); |
| if (keyboard_controller->IsKeyboardVisible()) { |
| show_bubble_after_keyboard_hidden_ = true; |
| keyboard_controller->AddObserver(this); |
| keyboard_controller->HideKeyboardExplicitlyBySystem(); |
| } else { |
| base::RecordAction(base::UserMetricsAction("Tray_ImeMenu_Opened")); |
| ShowImeMenuBubbleInternal(); |
| } |
| } |
| |
| TrayBubbleView* ImeMenuTray::GetBubbleView() { |
| return bubble_ ? bubble_->GetBubbleView() : nullptr; |
| } |
| |
| views::Widget* ImeMenuTray::GetBubbleWidget() const { |
| return bubble_ ? bubble_->GetBubbleWidget() : nullptr; |
| } |
| |
| void ImeMenuTray::AddedToWidget() { |
| // SetVisiblePreferred cannot be called until after the view has been added to |
| // a widget. |
| auto* ime_controller = Shell::Get()->ime_controller(); |
| |
| // On the primary display, `ImeMenuTray` is created for the primary shelf, and |
| // then `ImeObserver`s (of which `ImeMenuTray` is one) can react to IME menu |
| // activation. If the IME menu is active, and then a display is connected, |
| // this object will not have been notified of previous IME menu activations. |
| // So check for that here and modify visibility. Only necessary for secondary |
| // displays. |
| if (!ime_controller || !ime_controller->is_menu_active()) { |
| return; |
| } |
| |
| SetVisiblePreferred(true); |
| UpdateTrayLabel(); |
| } |
| |
| void ImeMenuTray::OnIMERefresh() { |
| UpdateTrayLabel(); |
| if (bubble_ && ime_list_view_) { |
| ime_list_view_->Update( |
| ime_controller_->current_ime().id, ime_controller_->GetVisibleImes(), |
| ime_controller_->current_ime_menu_items(), ShouldShowKeyboardToggle(), |
| ImeListView::SHOW_SINGLE_IME); |
| } |
| } |
| |
| void ImeMenuTray::OnIMEMenuActivationChanged(bool is_activated) { |
| SetVisiblePreferred(is_activated); |
| if (is_activated) { |
| UpdateTrayLabel(); |
| } else { |
| CloseBubble(); |
| } |
| } |
| |
| std::u16string ImeMenuTray::GetAccessibleNameForBubble() { |
| return l10n_util::GetStringUTF16(IDS_ASH_IME_MENU_ACCESSIBLE_NAME); |
| } |
| |
| bool ImeMenuTray::ShouldEnableExtraKeyboardAccessibility() { |
| return Shell::Get()->accessibility_controller()->spoken_feedback().enabled(); |
| } |
| |
| void ImeMenuTray::HideBubble(const TrayBubbleView* bubble_view) { |
| HideBubbleWithView(bubble_view); |
| } |
| |
| void ImeMenuTray::OnKeyboardHidden(bool is_temporary_hide) { |
| if (show_bubble_after_keyboard_hidden_) { |
| show_bubble_after_keyboard_hidden_ = false; |
| auto* keyboard_controller = keyboard::KeyboardUIController::Get(); |
| keyboard_controller->RemoveObserver(this); |
| |
| ShowImeMenuBubbleInternal(); |
| return; |
| } |
| } |
| |
| void ImeMenuTray::OnKeyboardSuppressionChanged(bool suppressed) { |
| if (suppressed != keyboard_suppressed_ && bubble_) { |
| CloseBubble(); |
| } |
| keyboard_suppressed_ = suppressed; |
| } |
| |
| bool ImeMenuTray::AnyBottomButtonShownForTest() const { |
| return is_emoji_enabled_ || is_handwriting_enabled_ || is_voice_enabled_; |
| } |
| |
| void ImeMenuTray::UpdateTrayLabel() { |
| const ImeInfo& current_ime = ime_controller_->current_ime(); |
| |
| // For ARC IMEs, we use the globe icon instead of the short name of the active |
| // IME. |
| if (extension_ime_util::IsArcIME(current_ime.id)) { |
| CreateImageView(); |
| UpdateTrayImageOrLabelColor(/*is_image=*/true); |
| return; |
| } |
| |
| // Updates the tray label based on the current input method. |
| CreateLabel(); |
| UpdateTrayImageOrLabelColor(/*is_image=*/false); |
| |
| if (current_ime.third_party) { |
| label_->SetText(current_ime.short_name + u"*"); |
| } else { |
| label_->SetText(current_ime.short_name); |
| } |
| } |
| |
| void ImeMenuTray::CreateLabel() { |
| // Do nothing if label_ is already created. |
| if (label_) { |
| return; |
| } |
| // Remove image_view_ at first if it's created. |
| if (image_view_) { |
| tray_container()->RemoveChildView(image_view_); |
| image_view_ = nullptr; |
| } |
| label_ = new ImeMenuLabel(); |
| SetupLabelForTray(label_); |
| label_->SetElideBehavior(gfx::TRUNCATE); |
| label_->SetCustomTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_IME)); |
| tray_container()->AddChildViewRaw(label_.get()); |
| } |
| |
| void ImeMenuTray::CreateImageView() { |
| // Do nothing if image_view_ is already created. |
| if (image_view_) { |
| return; |
| } |
| // Remove label_ at first if it's created. |
| if (label_) { |
| tray_container()->RemoveChildView(label_); |
| label_ = nullptr; |
| } |
| image_view_ = new ImeMenuImageView(); |
| image_view_->SetTooltipText( |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_IME)); |
| tray_container()->AddChildViewRaw(image_view_.get()); |
| } |
| |
| void ImeMenuTray::UpdateTrayImageOrLabelColor(bool is_image) { |
| const ui::ColorId color_id = |
| is_active() ? cros_tokens::kCrosSysSystemOnPrimaryContainer |
| : cros_tokens::kCrosSysOnSurface; |
| |
| if (is_image) { |
| image_view_->SetImage( |
| ui::ImageModel::FromVectorIcon(kShelfGlobeIcon, color_id)); |
| return; |
| } |
| |
| label_->SetEnabledColor(color_id); |
| } |
| |
| BEGIN_METADATA(ImeMenuTray) |
| END_METADATA |
| |
| } // namespace ash |