| // Copyright 2017 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/ime/ime_controller_impl.h" |
| |
| #include <utility> |
| |
| #include "ash/ime/ime_mode_indicator_view.h" |
| #include "ash/ime/ime_switch_type.h" |
| #include "ash/ime/mode_indicator_observer.h" |
| #include "ash/shell.h" |
| #include "ash/system/tray/system_tray_notifier.h" |
| #include "base/containers/contains.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/ranges/algorithm.h" |
| #include "ui/base/accelerators/accelerator.h" |
| #include "ui/base/ime/ash/extension_ime_util.h" |
| #include "ui/display/manager/display_manager.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The result of pressing VKEY_MODECHANGE (for metrics). |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class ModeChangeKeyAction { |
| kShowIndicator = 0, |
| kSwitchIme = 1, |
| kMaxValue = kSwitchIme |
| }; |
| |
| // The ID for the Accessibility Common IME (used for Dictation). |
| const char* kAccessibilityCommonIMEId = |
| "_ext_ime_egfdjlfmgnehecnclamagfafdccgfndpdictation"; |
| |
| } // namespace |
| |
| ImeControllerImpl::ImeControllerImpl() |
| : mode_indicator_observer_(std::make_unique<ModeIndicatorObserver>()) {} |
| |
| ImeControllerImpl::~ImeControllerImpl() { |
| SetClient(nullptr); |
| } |
| |
| void ImeControllerImpl::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void ImeControllerImpl::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| const std::vector<ImeInfo>& ImeControllerImpl::GetVisibleImes() const { |
| return visible_imes_; |
| } |
| |
| bool ImeControllerImpl::IsCurrentImeVisible() const { |
| return current_ime_.id != kAccessibilityCommonIMEId; |
| } |
| |
| void ImeControllerImpl::SetClient(ImeControllerClient* client) { |
| client_ = client; |
| } |
| |
| bool ImeControllerImpl::CanSwitchIme() const { |
| // Cannot switch unless there is an active IME. |
| if (current_ime_.id.empty()) |
| return false; |
| |
| // Do not consume key event if there is only one input method is enabled. |
| // Ctrl+Space or Alt+Shift may be used by other application. |
| return GetVisibleImes().size() > 1; |
| } |
| |
| void ImeControllerImpl::SwitchToNextIme() { |
| if (client_) |
| client_->SwitchToNextIme(); |
| } |
| |
| void ImeControllerImpl::SwitchToLastUsedIme() { |
| if (client_) |
| client_->SwitchToLastUsedIme(); |
| } |
| |
| void ImeControllerImpl::SwitchImeById(const std::string& ime_id, |
| bool show_message) { |
| if (client_) |
| client_->SwitchImeById(ime_id, show_message); |
| } |
| |
| void ImeControllerImpl::ActivateImeMenuItem(const std::string& key) { |
| if (client_) |
| client_->ActivateImeMenuItem(key); |
| } |
| |
| bool ImeControllerImpl::CanSwitchImeWithAccelerator( |
| const ui::Accelerator& accelerator) const { |
| // If none of the input methods associated with |accelerator| are active, we |
| // should ignore the accelerator. |
| std::vector<std::string> candidate_ids = |
| GetCandidateImesForAccelerator(accelerator); |
| return !candidate_ids.empty(); |
| } |
| |
| void ImeControllerImpl::SwitchImeWithAccelerator( |
| const ui::Accelerator& accelerator) { |
| if (!client_) |
| return; |
| |
| std::vector<std::string> candidate_ids = |
| GetCandidateImesForAccelerator(accelerator); |
| if (candidate_ids.empty()) |
| return; |
| auto it = base::ranges::find(candidate_ids, current_ime_.id); |
| if (it != candidate_ids.end()) |
| ++it; |
| if (it == candidate_ids.end()) |
| it = candidate_ids.begin(); |
| client_->SwitchImeById(*it, true /* show_message */); |
| } |
| |
| // ImeControllerImpl: |
| void ImeControllerImpl::RefreshIme(const std::string& current_ime_id, |
| std::vector<ImeInfo> available_imes, |
| std::vector<ImeMenuItem> menu_items) { |
| if (current_ime_id.empty()) |
| current_ime_ = ImeInfo(); |
| |
| available_imes_.clear(); |
| available_imes_.reserve(available_imes.size()); |
| visible_imes_.clear(); |
| visible_imes_.reserve(visible_imes_.size()); |
| for (const auto& ime : available_imes) { |
| if (ime.id.empty()) { |
| DLOG(ERROR) << "Received IME with invalid ID."; |
| continue; |
| } |
| available_imes_.push_back(ime); |
| if (ime.id != kAccessibilityCommonIMEId) { |
| visible_imes_.push_back(ime); |
| } |
| if (ime.id == current_ime_id) |
| current_ime_ = ime; |
| } |
| |
| // Either there is no current IME or we found a valid one in the list of |
| // available IMEs. |
| DCHECK(current_ime_id.empty() || !current_ime_.id.empty()); |
| |
| current_ime_menu_items_.clear(); |
| current_ime_menu_items_.reserve(menu_items.size()); |
| for (const auto& item : menu_items) |
| current_ime_menu_items_.push_back(item); |
| |
| Shell::Get()->system_tray_notifier()->NotifyRefreshIME(); |
| } |
| |
| void ImeControllerImpl::SetImesManagedByPolicy(bool managed) { |
| managed_by_policy_ = managed; |
| Shell::Get()->system_tray_notifier()->NotifyRefreshIME(); |
| } |
| |
| void ImeControllerImpl::ShowImeMenuOnShelf(bool show) { |
| is_menu_active_ = show; |
| Shell::Get()->system_tray_notifier()->NotifyRefreshIMEMenu(show); |
| } |
| |
| void ImeControllerImpl::UpdateCapsLockState(bool caps_enabled) { |
| is_caps_lock_enabled_ = caps_enabled; |
| |
| for (ImeControllerImpl::Observer& observer : observers_) |
| observer.OnCapsLockChanged(caps_enabled); |
| } |
| |
| void ImeControllerImpl::OnKeyboardLayoutNameChanged( |
| const std::string& layout_name) { |
| keyboard_layout_name_ = layout_name; |
| |
| for (ImeControllerImpl::Observer& observer : observers_) |
| observer.OnKeyboardLayoutNameChanged(layout_name); |
| } |
| |
| void ImeControllerImpl::SetExtraInputOptionsEnabledState( |
| bool is_extra_input_options_enabled, |
| bool is_emoji_enabled, |
| bool is_handwriting_enabled, |
| bool is_voice_enabled) { |
| is_extra_input_options_enabled_ = is_extra_input_options_enabled; |
| is_emoji_enabled_ = is_emoji_enabled; |
| is_handwriting_enabled_ = is_handwriting_enabled; |
| is_voice_enabled_ = is_voice_enabled; |
| } |
| |
| void ImeControllerImpl::ShowModeIndicator( |
| const gfx::Rect& anchor_bounds, |
| const std::u16string& ime_short_name) { |
| ImeModeIndicatorView* mi_view = |
| new ImeModeIndicatorView(anchor_bounds, ime_short_name); |
| views::BubbleDialogDelegateView::CreateBubble(mi_view); |
| mode_indicator_observer_->AddModeIndicatorWidget(mi_view->GetWidget()); |
| mi_view->ShowAndFadeOut(); |
| } |
| |
| void ImeControllerImpl::SetCapsLockEnabled(bool caps_enabled) { |
| is_caps_lock_enabled_ = caps_enabled; |
| |
| if (client_) |
| client_->SetCapsLockEnabled(caps_enabled); |
| } |
| |
| void ImeControllerImpl::OverrideKeyboardKeyset(input_method::ImeKeyset keyset) { |
| OverrideKeyboardKeyset(keyset, base::DoNothing()); |
| } |
| |
| void ImeControllerImpl::OverrideKeyboardKeyset( |
| input_method::ImeKeyset keyset, |
| ImeControllerClient::OverrideKeyboardKeysetCallback callback) { |
| if (client_) |
| client_->OverrideKeyboardKeyset(keyset, std::move(callback)); |
| } |
| |
| bool ImeControllerImpl::IsCapsLockEnabled() const { |
| return is_caps_lock_enabled_; |
| } |
| |
| std::vector<std::string> ImeControllerImpl::GetCandidateImesForAccelerator( |
| const ui::Accelerator& accelerator) const { |
| std::vector<std::string> candidate_ids; |
| |
| using extension_ime_util::GetInputMethodIDByEngineID; |
| std::vector<std::string> input_method_ids_to_switch; |
| switch (accelerator.key_code()) { |
| case ui::VKEY_CONVERT: // Henkan key on JP106 keyboard |
| input_method_ids_to_switch.push_back( |
| GetInputMethodIDByEngineID("nacl_mozc_jp")); |
| break; |
| case ui::VKEY_NONCONVERT: // Muhenkan key on JP106 keyboard |
| input_method_ids_to_switch.push_back( |
| GetInputMethodIDByEngineID("xkb:jp::jpn")); |
| break; |
| case ui::VKEY_DBE_SBCSCHAR: // ZenkakuHankaku key on JP106 keyboard |
| case ui::VKEY_DBE_DBCSCHAR: |
| input_method_ids_to_switch.push_back( |
| GetInputMethodIDByEngineID("nacl_mozc_jp")); |
| input_method_ids_to_switch.push_back( |
| GetInputMethodIDByEngineID("xkb:jp::jpn")); |
| break; |
| default: |
| break; |
| } |
| if (input_method_ids_to_switch.empty()) { |
| DVLOG(1) << "Unexpected VKEY: " << accelerator.key_code(); |
| return std::vector<std::string>(); |
| } |
| |
| // Obtain the intersection of input_method_ids_to_switch and available_imes_. |
| for (const ImeInfo& ime : available_imes_) { |
| if (base::Contains(input_method_ids_to_switch, ime.id)) |
| candidate_ids.push_back(ime.id); |
| } |
| return candidate_ids; |
| } |
| |
| } // namespace ash |