blob: 167bbcd2ae077dd327f6a72f8c1a20502d4eba01 [file] [log] [blame]
// Copyright (c) 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/input_method/assistive_window_controller.h"
#include <string>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "chrome/browser/ash/input_method/assistive_window_controller_delegate.h"
#include "chrome/browser/ash/input_method/assistive_window_properties.h"
#include "chrome/browser/ash/input_method/ui/suggestion_details.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/grit/generated_resources.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace input_method {
namespace {
gfx::NativeView GetParentView() {
gfx::NativeView parent = nullptr;
aura::Window* active_window = ash::window_util::GetActiveWindow();
// Use MenuContainer so that it works even with a system modal dialog.
parent = ash::Shell::GetContainer(
active_window ? active_window->GetRootWindow()
: ash::Shell::GetRootWindowForNewWindows(),
ash::kShellWindowId_MenuContainer);
return parent;
}
} // namespace
AssistiveWindowController::AssistiveWindowController(
AssistiveWindowControllerDelegate* delegate,
Profile* profile,
ui::ime::AssistiveAccessibilityView* accessibility_view)
: delegate_(delegate), accessibility_view_(accessibility_view) {}
AssistiveWindowController::~AssistiveWindowController() {
if (suggestion_window_view_ && suggestion_window_view_->GetWidget())
suggestion_window_view_->GetWidget()->RemoveObserver(this);
if (undo_window_ && undo_window_->GetWidget())
undo_window_->GetWidget()->RemoveObserver(this);
if (grammar_suggestion_window_ && grammar_suggestion_window_->GetWidget())
grammar_suggestion_window_->GetWidget()->RemoveObserver(this);
if (accessibility_view_ && accessibility_view_->GetWidget())
accessibility_view_->GetWidget()->RemoveObserver(this);
CHECK(!IsInObserverList());
}
void AssistiveWindowController::InitSuggestionWindow(
ui::ime::SuggestionWindowView::Orientation orientation) {
if (suggestion_window_view_)
return;
// suggestion_window_view_ is deleted by DialogDelegateView::DeleteDelegate.
suggestion_window_view_ =
ui::ime::SuggestionWindowView::Create(GetParentView(), this, orientation);
views::Widget* widget = suggestion_window_view_->GetWidget();
widget->AddObserver(this);
widget->Show();
}
void AssistiveWindowController::InitUndoWindow() {
if (undo_window_)
return;
// undo_window is deleted by DialogDelegateView::DeleteDelegate.
undo_window_ = new ui::ime::UndoWindow(GetParentView(), this);
views::Widget* widget = undo_window_->InitWidget();
widget->AddObserver(this);
widget->Show();
}
void AssistiveWindowController::InitGrammarSuggestionWindow() {
if (grammar_suggestion_window_)
return;
// grammar_suggestion_window_ is deleted by
// DialogDelegateView::DeleteDelegate.
grammar_suggestion_window_ =
new ui::ime::GrammarSuggestionWindow(GetParentView(), this);
views::Widget* widget = grammar_suggestion_window_->InitWidget();
widget->AddObserver(this);
widget->Show();
}
void AssistiveWindowController::InitAccessibilityView() {
if (accessibility_view_)
return;
// accessibility_view_ is deleted by DialogDelegateView::DeleteDelegate.
accessibility_view_ =
new ui::ime::AssistiveAccessibilityView(GetParentView());
accessibility_view_->GetWidget()->AddObserver(this);
}
void AssistiveWindowController::OnWidgetDestroying(views::Widget* widget) {
if (suggestion_window_view_ &&
widget == suggestion_window_view_->GetWidget()) {
widget->RemoveObserver(this);
suggestion_window_view_ = nullptr;
}
if (undo_window_ && widget == undo_window_->GetWidget()) {
widget->RemoveObserver(this);
undo_window_ = nullptr;
}
if (grammar_suggestion_window_ &&
widget == grammar_suggestion_window_->GetWidget()) {
widget->RemoveObserver(this);
grammar_suggestion_window_ = nullptr;
}
if (accessibility_view_ && widget == accessibility_view_->GetWidget()) {
widget->RemoveObserver(this);
accessibility_view_ = nullptr;
}
}
void AssistiveWindowController::Announce(const std::u16string& message) {
if (!accessibility_view_)
InitAccessibilityView();
accessibility_view_->Announce(message);
}
// TODO(crbug/1119570): Update AcceptSuggestion signature (either use
// announce_string, or no string)
void AssistiveWindowController::AcceptSuggestion(
const std::u16string& suggestion) {
if (window_.type == ui::ime::AssistiveWindowType::kEmojiSuggestion) {
Announce(l10n_util::GetStringUTF16(IDS_SUGGESTION_EMOJI_SUGGESTED));
} else {
Announce(l10n_util::GetStringUTF16(IDS_SUGGESTION_INSERTED));
}
HideSuggestion();
}
void AssistiveWindowController::HideSuggestion() {
suggestion_text_ = base::EmptyString16();
confirmed_length_ = 0;
tracking_last_suggestion_ = false;
if (suggestion_window_view_)
suggestion_window_view_->GetWidget()->Close();
if (grammar_suggestion_window_)
grammar_suggestion_window_->GetWidget()->Close();
}
void AssistiveWindowController::SetBounds(const Bounds& bounds) {
bounds_ = bounds;
// Sets suggestion_window_view_'s bounds here for most up-to-date cursor
// position. This is different from UndoWindow because UndoWindow gets cursors
// position before showing.
// TODO(crbug/1112982): Investigate getting bounds to suggester before sending
// show suggestion request.
if (suggestion_window_view_) {
suggestion_window_view_->SetAnchorRect(bounds.caret);
}
if (grammar_suggestion_window_) {
grammar_suggestion_window_->SetBounds(bounds_.caret);
}
}
void AssistiveWindowController::FocusStateChanged() {
HideSuggestion();
if (undo_window_)
undo_window_->Hide();
}
void AssistiveWindowController::ShowSuggestion(
const ui::ime::SuggestionDetails& details) {
if (!suggestion_window_view_)
// Since there is only one suggestion text in ShowSuggestion, we default to
// vertical layout.
InitSuggestionWindow(ui::ime::SuggestionWindowView::Orientation::kVertical);
tracking_last_suggestion_ = suggestion_text_ == details.text;
suggestion_text_ = details.text;
confirmed_length_ = details.confirmed_length;
suggestion_window_view_->Show(details);
}
void AssistiveWindowController::SetButtonHighlighted(
const ui::ime::AssistiveWindowButton& button,
bool highlighted) {
switch (button.window_type) {
case ui::ime::AssistiveWindowType::kEmojiSuggestion:
case ui::ime::AssistiveWindowType::kPersonalInfoSuggestion:
case ui::ime::AssistiveWindowType::kMultiWordSuggestion:
case ui::ime::AssistiveWindowType::kLongpressDiacriticsSuggestion:
if (!suggestion_window_view_)
return;
suggestion_window_view_->SetButtonHighlighted(button, highlighted);
if (highlighted)
Announce(button.announce_string);
break;
case ui::ime::AssistiveWindowType::kUndoWindow:
if (!undo_window_)
return;
undo_window_->SetButtonHighlighted(button, highlighted);
Announce(button.announce_string);
break;
case ui::ime::AssistiveWindowType::kGrammarSuggestion:
if (!grammar_suggestion_window_)
return;
grammar_suggestion_window_->SetButtonHighlighted(button, highlighted);
if (highlighted)
Announce(button.announce_string);
break;
case ui::ime::AssistiveWindowType::kNone:
break;
}
}
std::u16string AssistiveWindowController::GetSuggestionText() const {
return suggestion_text_;
}
size_t AssistiveWindowController::GetConfirmedLength() const {
return confirmed_length_;
}
ui::ime::SuggestionWindowView::Orientation
AssistiveWindowController::WindowOrientationFor(
ui::ime::AssistiveWindowType window_type) {
switch (window_type) {
case ui::ime::AssistiveWindowType::kLongpressDiacriticsSuggestion:
return ui::ime::SuggestionWindowView::Orientation::kHorizontal;
case ui::ime::AssistiveWindowType::kUndoWindow:
case ui::ime::AssistiveWindowType::kEmojiSuggestion:
case ui::ime::AssistiveWindowType::kPersonalInfoSuggestion:
case ui::ime::AssistiveWindowType::kMultiWordSuggestion:
case ui::ime::AssistiveWindowType::kGrammarSuggestion:
return ui::ime::SuggestionWindowView::Orientation::kVertical;
case ui::ime::AssistiveWindowType::kNone:
NOTREACHED();
}
NOTREACHED();
return ui::ime::SuggestionWindowView::Orientation::kVertical;
}
void AssistiveWindowController::SetAssistiveWindowProperties(
const AssistiveWindowProperties& window) {
window_ = window;
switch (window.type) {
case ui::ime::AssistiveWindowType::kUndoWindow:
if (!undo_window_)
InitUndoWindow();
if (window.visible) {
// Apply 4px padding to move the window away from the cursor.
gfx::Rect anchor_rect =
bounds_.autocorrect.IsEmpty() ? bounds_.caret : bounds_.autocorrect;
anchor_rect.Inset(-4);
undo_window_->SetAnchorRect(anchor_rect);
undo_window_->Show();
} else {
undo_window_->Hide();
}
break;
case ui::ime::AssistiveWindowType::kEmojiSuggestion:
case ui::ime::AssistiveWindowType::kPersonalInfoSuggestion:
case ui::ime::AssistiveWindowType::kMultiWordSuggestion:
case ui::ime::AssistiveWindowType::kLongpressDiacriticsSuggestion:
if (!suggestion_window_view_)
InitSuggestionWindow(WindowOrientationFor(window.type));
if (window_.visible) {
suggestion_window_view_->ShowMultipleCandidates(window);
} else {
HideSuggestion();
}
break;
case ui::ime::AssistiveWindowType::kGrammarSuggestion:
if (window.candidates.size() == 0)
return;
if (!grammar_suggestion_window_)
InitGrammarSuggestionWindow();
if (window.visible) {
grammar_suggestion_window_->SetBounds(bounds_.caret);
grammar_suggestion_window_->SetSuggestion(window.candidates[0]);
grammar_suggestion_window_->Show();
} else {
grammar_suggestion_window_->Hide();
}
break;
case ui::ime::AssistiveWindowType::kNone:
break;
}
Announce(window.announce_string);
}
void AssistiveWindowController::AssistiveWindowButtonClicked(
const ui::ime::AssistiveWindowButton& button) const {
delegate_->AssistiveWindowButtonClicked(button);
}
ui::ime::SuggestionWindowView*
AssistiveWindowController::GetSuggestionWindowViewForTesting() {
return suggestion_window_view_;
}
ui::ime::UndoWindow* AssistiveWindowController::GetUndoWindowForTesting()
const {
return undo_window_;
}
} // namespace input_method
} // namespace ash