0
Files
src/ash/quick_insert/quick_insert_controller.cc
Grey Wang 44fec236e9 [lobster] Pass correct caret bounds to lobster view
Bug: b:373720913
Change-Id: Ibf3c3fb84c479f8f6eed2b5371e56707627edd3c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6162334
Reviewed-by: Darren Shen <shend@chromium.org>
Reviewed-by: Chuong Ho <hdchuong@chromium.org>
Commit-Queue: Grey Wang <greywang@google.com>
Cr-Commit-Position: refs/heads/main@{#1405850}
2025-01-13 17:57:00 -08:00

780 lines
30 KiB
C++

// Copyright 2023 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/quick_insert/quick_insert_controller.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "ash/accessibility/accessibility_controller.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/clipboard_history_controller.h"
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/quick_insert/model/quick_insert_action_type.h"
#include "ash/quick_insert/model/quick_insert_caps_lock_position.h"
#include "ash/quick_insert/model/quick_insert_emoji_history_model.h"
#include "ash/quick_insert/model/quick_insert_emoji_suggester.h"
#include "ash/quick_insert/model/quick_insert_mode_type.h"
#include "ash/quick_insert/model/quick_insert_model.h"
#include "ash/quick_insert/model/quick_insert_search_results_section.h"
#include "ash/quick_insert/quick_insert_action_on_next_focus_request.h"
#include "ash/quick_insert/quick_insert_asset_fetcher.h"
#include "ash/quick_insert/quick_insert_asset_fetcher_impl.h"
#include "ash/quick_insert/quick_insert_caps_lock_bubble_controller.h"
#include "ash/quick_insert/quick_insert_client.h"
#include "ash/quick_insert/quick_insert_copy_media.h"
#include "ash/quick_insert/quick_insert_insert_media_request.h"
#include "ash/quick_insert/quick_insert_paste_request.h"
#include "ash/quick_insert/quick_insert_rich_media.h"
#include "ash/quick_insert/quick_insert_search_result.h"
#include "ash/quick_insert/quick_insert_suggestions_controller.h"
#include "ash/quick_insert/quick_insert_transform_case.h"
#include "ash/quick_insert/search/quick_insert_search_controller.h"
#include "ash/quick_insert/views/quick_insert_caps_lock_state_view.h"
#include "ash/quick_insert/views/quick_insert_icons.h"
#include "ash/quick_insert/views/quick_insert_positioning.h"
#include "ash/quick_insert/views/quick_insert_view.h"
#include "ash/quick_insert/views/quick_insert_view_delegate.h"
#include "ash/quick_insert/views/quick_insert_widget.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/wm/window_util.h"
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/overloaded.h"
#include "base/hash/sha1.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/base/emoji/emoji_panel_helper.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/text_input_target.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/display/screen.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
namespace ash {
namespace {
bool g_feature_tour_enabled = true;
// When spoken feedback is enabled, closing the widget after an insert is
// delayed by this amount.
constexpr base::TimeDelta kCloseWidgetDelay = base::Milliseconds(200);
// Time to wait for a focus event after triggering caps lock.
constexpr base::TimeDelta kCapsLockRequestTimeout = base::Seconds(1);
constexpr int kCapsLockMinimumTopDisplayCount = 5;
constexpr float kCapsLockRatioThresholdForTop = 0.8;
constexpr float kCapsLockRatioThresholdForBottom = 0.2;
constexpr std::string_view kSupportUrl =
"https://support.google.com/chromebook?p=dugong";
ui::TextInputClient* GetFocusedTextInputClient() {
const ui::InputMethod* input_method =
IMEBridge::Get()->GetInputContextHandler()->GetInputMethod();
if (!input_method || !input_method->GetTextInputClient()) {
return nullptr;
}
return input_method->GetTextInputClient();
}
// Gets the current caret bounds in universal screen coordinates in DIP. Returns
// an empty rect if there is no active caret or the caret bounds can't be
// determined (e.g. no focused input field).
gfx::Rect GetCaretBounds() {
if (ui::TextInputClient* client = GetFocusedTextInputClient()) {
return client->GetCaretBounds();
}
return gfx::Rect();
}
// Gets the current cursor point in universal screen coordinates in DIP.
gfx::Point GetCursorPoint() {
return display::Screen::GetScreen()->GetCursorScreenPoint();
}
// Gets the bounds of the current focused window in universal screen coordinates
// in DIP. Returns an empty rect if there is no currently focused window.
gfx::Rect GetFocusedWindowBounds() {
return window_util::GetFocusedWindow()
? window_util::GetFocusedWindow()->GetBoundsInScreen()
: gfx::Rect();
}
input_method::ImeKeyboard& GetImeKeyboard() {
auto* input_method_manager = input_method::InputMethodManager::Get();
CHECK(input_method_manager);
input_method::ImeKeyboard* ime_keyboard =
input_method_manager->GetImeKeyboard();
CHECK(ime_keyboard);
return *ime_keyboard;
}
// The user can ask to insert rich media, a clipboard item, or insert nothing.
using InsertionContent = std::
variant<QuickInsertRichMedia, QuickInsertClipboardResult, std::monostate>;
InsertionContent GetInsertionContentForResult(
const QuickInsertSearchResult& result) {
using ReturnType = InsertionContent;
return std::visit(
base::Overloaded{
[](const QuickInsertTextResult& data) -> ReturnType {
return QuickInsertTextMedia(data.primary_text);
},
[](const QuickInsertEmojiResult& data) -> ReturnType {
return QuickInsertTextMedia(data.text);
},
[](const QuickInsertClipboardResult& data) -> ReturnType {
return data;
},
[](const QuickInsertBrowsingHistoryResult& data) -> ReturnType {
return QuickInsertLinkMedia(data.url,
base::UTF16ToUTF8(data.title));
},
[](const QuickInsertGifResult& data) -> ReturnType {
return QuickInsertImageMedia(data.full_url, data.full_dimensions,
data.content_description);
},
[](const QuickInsertLocalFileResult& data) -> ReturnType {
return QuickInsertLocalFileMedia(data.file_path);
},
[](const QuickInsertDriveFileResult& data) -> ReturnType {
return QuickInsertLinkMedia(data.url,
base::UTF16ToUTF8(data.title));
},
[](const QuickInsertCategoryResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertSearchRequestResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertEditorResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertLobsterResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertNewWindowResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertCapsLockResult& data) -> ReturnType {
return std::monostate();
},
[](const QuickInsertCaseTransformResult& data) -> ReturnType {
return std::monostate();
},
},
result);
}
std::vector<QuickInsertSearchResultsSection>
CreateSingleSectionForCategoryResults(
QuickInsertSectionType section_type,
std::vector<QuickInsertSearchResult> results) {
if (results.empty()) {
return {};
}
return {QuickInsertSearchResultsSection(section_type, std::move(results),
/*has_more_results=*/false)};
}
std::u16string TransformText(std::u16string_view text,
QuickInsertCaseTransformResult::Type type) {
switch (type) {
case QuickInsertCaseTransformResult::Type::kUpperCase:
return QuickInsertTransformToUpperCase(text);
case QuickInsertCaseTransformResult::Type::kLowerCase:
return QuickInsertTransformToLowerCase(text);
case QuickInsertCaseTransformResult::Type::kTitleCase:
return QuickInsertTransformToTitleCase(text);
}
NOTREACHED();
}
void OpenLink(const GURL& url) {
NewWindowDelegate::GetPrimary()->OpenUrl(
url, NewWindowDelegate::OpenUrlFrom::kUserInteraction,
NewWindowDelegate::Disposition::kNewForegroundTab);
}
void OpenFile(const base::FilePath& path) {
NewWindowDelegate::GetPrimary()->OpenFile(path);
}
GURL GetUrlForNewWindow(QuickInsertNewWindowResult::Type type) {
switch (type) {
case QuickInsertNewWindowResult::Type::kDoc:
return GURL("https://docs.new");
case QuickInsertNewWindowResult::Type::kSheet:
return GURL("https://sheets.new");
case QuickInsertNewWindowResult::Type::kSlide:
return GURL("https://slides.new");
case QuickInsertNewWindowResult::Type::kChrome:
return GURL("chrome://newtab");
}
}
ui::EmojiPickerCategory EmojiResultTypeToCategory(
QuickInsertEmojiResult::Type type) {
switch (type) {
case QuickInsertEmojiResult::Type::kEmoji:
return ui::EmojiPickerCategory::kEmojis;
case QuickInsertEmojiResult::Type::kSymbol:
return ui::EmojiPickerCategory::kSymbols;
case QuickInsertEmojiResult::Type::kEmoticon:
return ui::EmojiPickerCategory::kEmoticons;
}
}
QuickInsertSectionType GetSectionTypeForCategorySuggestion(
QuickInsertCategory category) {
switch (category) {
case QuickInsertCategory::kUnitsMaths:
case QuickInsertCategory::kDatesTimes:
return QuickInsertSectionType::kExamples;
case QuickInsertCategory::kGifs:
return QuickInsertSectionType::kFeaturedGifs;
default:
return QuickInsertSectionType::kNone;
}
}
} // namespace
QuickInsertController::QuickInsertController()
: caps_lock_bubble_controller_(&GetImeKeyboard()),
asset_fetcher_(std::make_unique<QuickInsertAssetFetcherImpl>(this)),
search_controller_(kBurnInPeriod) {}
QuickInsertController::~QuickInsertController() {
// `widget_` depends on `this`. Destroy the widget synchronously to avoid a
// dangling pointer.
if (widget_) {
widget_->CloseNow();
}
}
void QuickInsertController::RegisterProfilePrefs(PrefRegistrySimple* registry) {
QuickInsertFeatureTour::RegisterProfilePrefs(registry);
QuickInsertSessionMetrics::RegisterProfilePrefs(registry);
}
void QuickInsertController::DisableFeatureTourForTesting() {
CHECK_IS_TEST();
g_feature_tour_enabled = false;
}
void QuickInsertController::SetClient(QuickInsertClient* client) {
// `QuickInsertSearchController` may depend on the current client via
// `StartSearch`. Stop the search before changing the `client`. This may send
// a `StopSearch` call to the current `client_`.
search_controller_.StopSearch();
client_ = client;
}
void QuickInsertController::OnClientPrefsSet(PrefService* prefs) {
if (client_ == nullptr) {
return;
}
search_controller_.LoadEmojiLanguagesFromPrefs(prefs);
}
void QuickInsertController::ToggleWidget(
const base::TimeTicks trigger_event_timestamp) {
// Show the feature tour if it's the first time this feature is used.
if (PrefService* prefs = GetPrefs();
g_feature_tour_enabled && prefs &&
feature_tour_.MaybeShowForFirstUse(
prefs,
client_->IsEligibleForEditor()
? QuickInsertFeatureTour::EditorStatus::kEligible
: QuickInsertFeatureTour::EditorStatus::kNotEligible,
base::BindRepeating(OpenLink, GURL(kSupportUrl)),
base::BindRepeating(&QuickInsertController::ShowWidgetPostFeatureTour,
weak_ptr_factory_.GetWeakPtr()))) {
return;
}
if (widget_) {
CloseWidget();
} else {
ShowWidget(trigger_event_timestamp, WidgetTriggerSource::kDefault);
}
}
std::vector<QuickInsertCategory>
QuickInsertController::GetAvailableCategories() {
return session_ == nullptr ? std::vector<QuickInsertCategory>{}
: session_->model.GetAvailableCategories();
}
void QuickInsertController::GetZeroStateSuggestedResults(
SuggestedResultsCallback callback) {
CHECK(client_);
suggestions_controller_.GetSuggestions(*client_, session_->model,
std::move(callback));
}
void QuickInsertController::GetResultsForCategory(
QuickInsertCategory category,
SearchResultsCallback callback) {
CHECK(client_);
suggestions_controller_.GetSuggestionsForCategory(
*client_, category,
base::BindRepeating(CreateSingleSectionForCategoryResults,
GetSectionTypeForCategorySuggestion(category))
.Then(std::move(callback)));
}
void QuickInsertController::StartSearch(
std::u16string_view query,
std::optional<QuickInsertCategory> category,
SearchResultsCallback callback) {
CHECK(session_);
CHECK(client_);
search_controller_.StartSearch(
client_, query, std::move(category), GetAvailableCategories(),
!session_->model.is_caps_lock_enabled(),
session_->model.GetMode() == QuickInsertModeType::kHasSelection,
std::move(callback));
}
void QuickInsertController::StopSearch() {
search_controller_.StopSearch();
}
void QuickInsertController::StartEmojiSearch(
std::u16string_view query,
EmojiSearchResultsCallback callback) {
search_controller_.StartEmojiSearch(GetPrefs(), query, std::move(callback));
}
void QuickInsertController::CloseWidgetThenInsertResultOnNextFocus(
const QuickInsertSearchResult& result) {
InsertResultOnNextFocus(result);
client_->Announce(
l10n_util::GetStringUTF16(IDS_PICKER_INSERTION_ANNOUNCEMENT_TEXT));
if (Shell::Get()->accessibility_controller()->spoken_feedback().enabled()) {
close_widget_delay_timer_.Start(
FROM_HERE, kCloseWidgetDelay,
base::BindOnce(&QuickInsertController::CloseWidget,
weak_ptr_factory_.GetWeakPtr()));
} else {
CloseWidget();
}
}
void QuickInsertController::OpenResult(const QuickInsertSearchResult& result) {
return std::visit(
base::Overloaded{
[](const QuickInsertTextResult& data) { NOTREACHED(); },
[](const QuickInsertEmojiResult& data) { NOTREACHED(); },
[](const QuickInsertGifResult& data) { NOTREACHED(); },
[](const QuickInsertClipboardResult& data) { NOTREACHED(); },
[&](const QuickInsertBrowsingHistoryResult& data) {
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kOpenLink);
OpenLink(data.url);
},
[&](const QuickInsertLocalFileResult& data) {
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kOpenFile);
OpenFile(data.file_path);
},
[&](const QuickInsertDriveFileResult& data) {
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kOpenLink);
OpenLink(data.url);
},
[](const QuickInsertCategoryResult& data) { NOTREACHED(); },
[](const QuickInsertSearchRequestResult& data) { NOTREACHED(); },
[](const QuickInsertEditorResult& data) { NOTREACHED(); },
[](const QuickInsertLobsterResult& data) { NOTREACHED(); },
[&](const QuickInsertNewWindowResult& data) {
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kCreate);
OpenLink(GetUrlForNewWindow(data.type));
},
[&](const QuickInsertCapsLockResult& data) {
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kFormat);
caps_lock_request_ =
std::make_unique<QuickInsertActionOnNextFocusRequest>(
widget_->GetInputMethod(), kCapsLockRequestTimeout,
base::BindOnce(
[](bool enabled) {
GetImeKeyboard().SetCapsLockEnabled(enabled);
},
data.enabled),
base::BindOnce(
[](bool enabled) {
GetImeKeyboard().SetCapsLockEnabled(enabled);
},
data.enabled));
},
[&](const QuickInsertCaseTransformResult& data) {
if (!session_) {
return;
}
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kFormat);
std::u16string_view selected_text = session_->model.selected_text();
InsertResultOnNextFocus(QuickInsertTextResult(
TransformText(selected_text, data.type),
QuickInsertTextResult::Source::kCaseTransform));
},
},
result);
}
void QuickInsertController::ShowEmojiPicker(ui::EmojiPickerCategory category,
std::u16string_view query) {
ui::ShowEmojiPanelInSpecificMode(category,
ui::EmojiPickerFocusBehavior::kAlwaysShow,
base::UTF16ToUTF8(query));
}
void QuickInsertController::ShowEditor(
std::optional<std::string> preset_query_id,
std::optional<std::string> freeform_text) {
if (!show_editor_callback_.is_null()) {
std::move(show_editor_callback_)
.Run(std::move(preset_query_id), std::move(freeform_text));
}
}
// TODO: b:370885630 - Considers making selected_text as an argument of this
// method.
void QuickInsertController::ShowLobster(
std::optional<std::string> freeform_text) {
if (!show_lobster_callback_.is_null()) {
std::move(show_lobster_callback_)
.Run(session_ != nullptr && session_->model.selected_text() != u""
? base::UTF16ToUTF8(session_->model.selected_text())
: std::move(freeform_text));
}
}
QuickInsertAssetFetcher* QuickInsertController::GetAssetFetcher() {
return asset_fetcher_.get();
}
QuickInsertSessionMetrics& QuickInsertController::GetSessionMetrics() {
return session_->session_metrics;
}
QuickInsertActionType QuickInsertController::GetActionForResult(
const QuickInsertSearchResult& result) {
CHECK(session_);
const QuickInsertModeType mode = session_->model.GetMode();
return std::visit(
base::Overloaded{[mode](const QuickInsertTextResult& data) {
CHECK(mode == QuickInsertModeType::kNoSelection ||
mode == QuickInsertModeType::kHasSelection);
return QuickInsertActionType::kInsert;
},
[mode](const QuickInsertEmojiResult& data) {
CHECK(mode == QuickInsertModeType::kNoSelection ||
mode == QuickInsertModeType::kHasSelection);
return QuickInsertActionType::kInsert;
},
[mode](const QuickInsertGifResult& data) {
CHECK(mode == QuickInsertModeType::kNoSelection ||
mode == QuickInsertModeType::kHasSelection);
return QuickInsertActionType::kInsert;
},
[mode](const QuickInsertClipboardResult& data) {
CHECK(mode == QuickInsertModeType::kNoSelection ||
mode == QuickInsertModeType::kHasSelection);
return QuickInsertActionType::kInsert;
},
[mode](const QuickInsertBrowsingHistoryResult& data) {
return mode == QuickInsertModeType::kUnfocused
? QuickInsertActionType::kOpen
: QuickInsertActionType::kInsert;
},
[mode](const QuickInsertLocalFileResult& data) {
return mode == QuickInsertModeType::kUnfocused
? QuickInsertActionType::kOpen
: QuickInsertActionType::kInsert;
},
[mode](const QuickInsertDriveFileResult& data) {
return mode == QuickInsertModeType::kUnfocused
? QuickInsertActionType::kOpen
: QuickInsertActionType::kInsert;
},
[](const QuickInsertCategoryResult& data) {
return QuickInsertActionType::kDo;
},
[](const QuickInsertSearchRequestResult& data) {
return QuickInsertActionType::kDo;
},
[](const QuickInsertEditorResult& data) {
return QuickInsertActionType::kCreate;
},
[](const QuickInsertLobsterResult& data) {
return QuickInsertActionType::kCreate;
},
[](const QuickInsertNewWindowResult& data) {
return QuickInsertActionType::kDo;
},
[](const QuickInsertCapsLockResult& data) {
return QuickInsertActionType::kDo;
},
[&](const QuickInsertCaseTransformResult& data) {
return QuickInsertActionType::kDo;
}},
result);
}
std::vector<QuickInsertEmojiResult> QuickInsertController::GetSuggestedEmoji() {
CHECK(session_);
return session_->emoji_suggester.GetSuggestedEmoji();
}
bool QuickInsertController::IsGifsEnabled() {
CHECK(session_);
return session_->model.IsGifsEnabled();
}
PrefService* QuickInsertController::GetPrefs() {
return Shell::Get()->session_controller()->GetLastActiveUserPrefService();
}
QuickInsertModeType QuickInsertController::GetMode() {
CHECK(session_);
return session_->model.GetMode();
}
void QuickInsertController::OnViewIsDeleting(views::View* view) {
view_observation_.Reset();
session_.reset();
}
scoped_refptr<network::SharedURLLoaderFactory>
QuickInsertController::GetSharedURLLoaderFactory() {
return client_->GetSharedURLLoaderFactory();
}
void QuickInsertController::FetchFileThumbnail(
const base::FilePath& path,
const gfx::Size& size,
FetchFileThumbnailCallback callback) {
client_->FetchFileThumbnail(path, size, std::move(callback));
}
QuickInsertController::Session::Session(
PrefService* prefs,
ui::TextInputClient* focused_client,
input_method::ImeKeyboard* ime_keyboard,
QuickInsertModel::EditorStatus editor_status,
QuickInsertModel::LobsterStatus lobster_status,
QuickInsertEmojiSuggester::GetNameCallback get_name)
: model(prefs, focused_client, ime_keyboard, editor_status, lobster_status),
emoji_history_model(prefs),
emoji_suggester(&emoji_history_model, std::move(get_name)),
session_metrics(prefs) {
session_metrics.OnStartSession(focused_client);
feature_usage_metrics.StartUsage();
}
QuickInsertController::Session::~Session() {
feature_usage_metrics.StopUsage();
}
void QuickInsertController::ShowWidget(base::TimeTicks trigger_event_timestamp,
WidgetTriggerSource trigger_source) {
ui::TextInputClient* focused_text_input_client = GetFocusedTextInputClient();
show_editor_callback_ = client_->CacheEditorContext();
show_lobster_callback_ = client_->CacheLobsterContext(
/*support_image_insertion=*/focused_text_input_client &&
focused_text_input_client->CanInsertImage(),
/*caret_bounds=*/GetCaretBounds());
input_method::ImeKeyboard& keyboard = GetImeKeyboard();
if (focused_text_input_client &&
focused_text_input_client->GetTextInputType() ==
ui::TEXT_INPUT_TYPE_PASSWORD) {
bool should_enable = !keyboard.IsCapsLockEnabled();
keyboard.SetCapsLockEnabled(should_enable);
return;
}
session_ = std::make_unique<Session>(
GetPrefs(), focused_text_input_client, &keyboard,
show_editor_callback_.is_null()
? QuickInsertModel::EditorStatus::kDisabled
: QuickInsertModel::EditorStatus::kEnabled,
show_lobster_callback_.is_null()
? QuickInsertModel::LobsterStatus::kDisabled
: QuickInsertModel::LobsterStatus::kEnabled,
base::BindRepeating(
[](base::WeakPtr<QuickInsertController> weak_controller,
std::string_view emoji) -> std::string {
if (weak_controller == nullptr) {
return "";
}
return weak_controller->search_controller_.GetEmojiName(emoji);
},
weak_ptr_factory_.GetWeakPtr()));
const gfx::Rect anchor_bounds = GetQuickInsertAnchorBounds(
GetCaretBounds(), GetCursorPoint(), GetFocusedWindowBounds());
if (trigger_source == WidgetTriggerSource::kFeatureTour &&
session_->model.GetMode() == QuickInsertModeType::kUnfocused) {
widget_ = QuickInsertWidget::CreateCentered(this, anchor_bounds,
trigger_event_timestamp);
} else {
widget_ =
QuickInsertWidget::Create(this, anchor_bounds, trigger_event_timestamp);
}
widget_->Show();
view_observation_.Observe(widget_->GetContentsView());
}
void QuickInsertController::CloseWidget() {
if (!widget_) {
return;
}
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kAbandoned);
widget_->Close();
}
void QuickInsertController::ShowWidgetPostFeatureTour() {
ShowWidget(base::TimeTicks::Now(), WidgetTriggerSource::kFeatureTour);
}
std::optional<QuickInsertWebPasteTarget>
QuickInsertController::GetWebPasteTarget() {
return client_ ? client_->GetWebPasteTarget() : std::nullopt;
}
void QuickInsertController::InsertResultOnNextFocus(
const QuickInsertSearchResult& result) {
if (!widget_) {
return;
}
// Update emoji history in prefs the result is an emoji/symbol/emoticon.
CHECK(session_);
if (auto* data = std::get_if<QuickInsertEmojiResult>(&result);
data != nullptr && session_->model.should_do_learning()) {
session_->emoji_history_model.UpdateRecentEmoji(
EmojiResultTypeToCategory(data->type), base::UTF16ToUTF8(data->text));
}
std::visit(
base::Overloaded{
[&](QuickInsertRichMedia media) {
ui::InputMethod* input_method = widget_->GetInputMethod();
if (input_method == nullptr) {
return;
}
// This cancels the previous request if there was one.
insert_media_request_ = std::make_unique<
QuickInsertInsertMediaRequest>(
input_method, media, kInsertMediaTimeout,
base::BindOnce(
[](base::WeakPtr<QuickInsertController> weak_controller) {
return weak_controller
? weak_controller->GetWebPasteTarget()
: std::nullopt;
},
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&QuickInsertController::OnInsertCompleted,
weak_ptr_factory_.GetWeakPtr(), media));
},
[&](QuickInsertClipboardResult data) {
// This cancels the previous request if there was one.
paste_request_ = std::make_unique<QuickInsertPasteRequest>(
ClipboardHistoryController::Get(),
aura::client::GetFocusClient(widget_->GetNativeView()),
data.item_id);
},
[](std::monostate) { NOTREACHED(); },
},
GetInsertionContentForResult(result));
session_->session_metrics.SetOutcome(
QuickInsertSessionMetrics::SessionOutcome::kInsertedOrCopied);
}
void QuickInsertController::OnInsertCompleted(
const QuickInsertRichMedia& media,
QuickInsertInsertMediaRequest::Result result) {
// Fallback to copying to the clipboard on failure.
if (result != QuickInsertInsertMediaRequest::Result::kSuccess) {
CopyMediaToClipboard(media);
}
}
QuickInsertCapsLockPosition QuickInsertController::GetCapsLockPosition() {
// Always put the caps lock entry point at the top if the user has caps lock
// enabled, since it is they will likely want to disable it.
if (GetImeKeyboard().IsCapsLockEnabled()) {
return QuickInsertCapsLockPosition::kTop;
}
PrefService* prefs = GetPrefs();
if (prefs == nullptr) {
return QuickInsertCapsLockPosition::kTop;
}
int caps_lock_displayed_count =
prefs->GetInteger(prefs::kQuickInsertCapsLockDisplayedCountPrefName);
int caps_lock_selected_count =
prefs->GetInteger(prefs::kQuickInsertLockSelectedCountPrefName);
float caps_lock_selected_ratio =
static_cast<float>(caps_lock_selected_count) / caps_lock_displayed_count;
if (caps_lock_displayed_count < kCapsLockMinimumTopDisplayCount ||
caps_lock_selected_ratio >= kCapsLockRatioThresholdForTop) {
return QuickInsertCapsLockPosition::kTop;
}
if (caps_lock_selected_ratio >= kCapsLockRatioThresholdForBottom) {
return QuickInsertCapsLockPosition::kMiddle;
}
return QuickInsertCapsLockPosition::kBottom;
}
} // namespace ash