search_box_view.cc 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. // Copyright (c) 2012 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/app_list/views/search_box_view.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <string>
  8. #include <utility>
  9. #include <vector>
  10. #include "ash/app_list/app_list_metrics.h"
  11. #include "ash/app_list/app_list_util.h"
  12. #include "ash/app_list/app_list_view_delegate.h"
  13. #include "ash/app_list/model/search/search_box_model.h"
  14. #include "ash/app_list/model/search/search_model.h"
  15. #include "ash/app_list/views/app_list_view.h"
  16. #include "ash/app_list/views/contents_view.h"
  17. #include "ash/app_list/views/result_selection_controller.h"
  18. #include "ash/app_list/views/search_box_view_delegate.h"
  19. #include "ash/app_list/views/search_result_base_view.h"
  20. #include "ash/constants/ash_features.h"
  21. #include "ash/keyboard/ui/keyboard_ui_controller.h"
  22. #include "ash/public/cpp/app_list/app_list_color_provider.h"
  23. #include "ash/public/cpp/app_list/app_list_config.h"
  24. #include "ash/public/cpp/app_list/app_list_features.h"
  25. #include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
  26. #include "ash/public/cpp/wallpaper/wallpaper_types.h"
  27. #include "ash/resources/vector_icons/vector_icons.h"
  28. #include "ash/search_box/search_box_constants.h"
  29. #include "ash/strings/grit/ash_strings.h"
  30. #include "ash/style/ash_color_provider.h"
  31. #include "base/metrics/histogram_functions.h"
  32. #include "base/metrics/histogram_macros.h"
  33. #include "base/metrics/user_metrics.h"
  34. #include "base/notreached.h"
  35. #include "base/rand_util.h"
  36. #include "chromeos/constants/chromeos_features.h"
  37. #include "chromeos/ui/vector_icons/vector_icons.h"
  38. #include "components/vector_icons/vector_icons.h"
  39. #include "ui/accessibility/ax_node_data.h"
  40. #include "ui/base/ime/composition_text.h"
  41. #include "ui/base/l10n/l10n_util.h"
  42. #include "ui/base/resource/resource_bundle.h"
  43. #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
  44. #include "ui/color/color_provider_manager.h"
  45. #include "ui/compositor/layer.h"
  46. #include "ui/compositor/paint_recorder.h"
  47. #include "ui/events/event.h"
  48. #include "ui/gfx/canvas.h"
  49. #include "ui/gfx/color_palette.h"
  50. #include "ui/gfx/geometry/insets.h"
  51. #include "ui/gfx/geometry/rounded_corners_f.h"
  52. #include "ui/gfx/paint_vector_icon.h"
  53. #include "ui/views/border.h"
  54. #include "ui/views/context_menu_controller.h"
  55. #include "ui/views/controls/button/image_button.h"
  56. #include "ui/views/controls/image_view.h"
  57. #include "ui/views/controls/textfield/textfield.h"
  58. #include "ui/views/highlight_border.h"
  59. #include "ui/views/layout/box_layout_view.h"
  60. #include "ui/views/vector_icons.h"
  61. #include "ui/views/view.h"
  62. #include "ui/views/view_class_properties.h"
  63. #include "ui/views/widget/widget.h"
  64. namespace ash {
  65. namespace {
  66. constexpr int kSearchBoxFocusRingWidth = 2;
  67. // Padding between the focus ring and the search box view
  68. constexpr int kSearchBoxFocusRingPadding = 4;
  69. constexpr int kSearchBoxFocusRingCornerRadius = 28;
  70. // Minimum amount of characters required to enable autocomplete.
  71. constexpr int kMinimumLengthToAutocomplete = 2;
  72. // Border insets for SearchBoxView in bubble launcher.
  73. constexpr auto kBorderInsetsForAppListBubble = gfx::Insets::TLBR(4, 4, 4, 0);
  74. // Margins for the search box text field in bubble launcher.
  75. constexpr auto kTextFieldMarginsForAppListBubble =
  76. gfx::Insets::TLBR(8, 0, 0, 0);
  77. // The default PlaceholderTextTypes used for productivity launcher. Randomly
  78. // selected when placeholder text would be shown.
  79. constexpr SearchBoxView::PlaceholderTextType kDefaultPlaceholders[3] = {
  80. SearchBoxView::PlaceholderTextType::kShortcuts,
  81. SearchBoxView::PlaceholderTextType::kTabs,
  82. SearchBoxView::PlaceholderTextType::kSettings,
  83. };
  84. // PlaceholderTextTypes used for productivity launcher for cloud gaming devices.
  85. // Randomly selected when placeholder text would be shown.
  86. constexpr SearchBoxView::PlaceholderTextType kGamingPlaceholders[4] = {
  87. SearchBoxView::PlaceholderTextType::kShortcuts,
  88. SearchBoxView::PlaceholderTextType::kTabs,
  89. SearchBoxView::PlaceholderTextType::kSettings,
  90. SearchBoxView::PlaceholderTextType::kGames,
  91. };
  92. bool IsTrimmedQueryEmpty(const std::u16string& query) {
  93. std::u16string trimmed_query;
  94. base::TrimWhitespace(query, base::TrimPositions::TRIM_ALL, &trimmed_query);
  95. return trimmed_query.empty();
  96. }
  97. std::u16string GetCategoryName(SearchResult* search_result) {
  98. switch (search_result->category()) {
  99. case ash::AppListSearchResultCategory::kApps:
  100. return l10n_util::GetStringUTF16(
  101. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_APPS);
  102. case ash::AppListSearchResultCategory::kAppShortcuts:
  103. return l10n_util::GetStringUTF16(
  104. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_APP_SHORTCUTS);
  105. case ash::AppListSearchResultCategory::kWeb:
  106. return l10n_util::GetStringUTF16(
  107. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_WEB);
  108. case ash::AppListSearchResultCategory::kFiles:
  109. return (l10n_util::GetStringUTF16(
  110. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_FILES));
  111. case ash::AppListSearchResultCategory::kSettings:
  112. return l10n_util::GetStringUTF16(
  113. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_SETTINGS);
  114. case ash::AppListSearchResultCategory::kHelp:
  115. return l10n_util::GetStringUTF16(
  116. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_HELP);
  117. case ash::AppListSearchResultCategory::kPlayStore:
  118. return l10n_util::GetStringUTF16(
  119. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_PLAY_STORE);
  120. case ash::AppListSearchResultCategory::kSearchAndAssistant:
  121. return l10n_util::GetStringUTF16(
  122. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_SEARCH_AND_ASSISTANT);
  123. case ash::AppListSearchResultCategory::kGames:
  124. return l10n_util::GetStringUTF16(
  125. IDS_ASH_SEARCH_RESULT_CATEGORY_LABEL_GAMES);
  126. case ash::AppListSearchResultCategory::kUnknown:
  127. return std::u16string();
  128. }
  129. }
  130. bool IsSubstringCaseInsensitive(std::u16string haystack_expr,
  131. std::u16string needle_expr) {
  132. // Convert complete given String to lower case
  133. std::transform(haystack_expr.begin(), haystack_expr.end(),
  134. haystack_expr.begin(), ::tolower);
  135. // Convert complete given Sub String to lower case
  136. std::transform(needle_expr.begin(), needle_expr.end(), needle_expr.begin(),
  137. ::tolower);
  138. // Find sub string in given string
  139. return haystack_expr.find(needle_expr) != std::string::npos;
  140. }
  141. void RecordAutocompleteMatchMetric(SearchBoxTextMatch match_type) {
  142. base::UmaHistogramEnumeration("Apps.AppListSearchAutocomplete", match_type);
  143. }
  144. } // namespace
  145. class SearchBoxView::FocusRingLayer : public ui::Layer, ui::LayerDelegate {
  146. public:
  147. FocusRingLayer() : Layer(ui::LAYER_TEXTURED) {
  148. SetName("search_box/FocusRing");
  149. SetFillsBoundsOpaquely(false);
  150. set_delegate(this);
  151. }
  152. FocusRingLayer(const FocusRingLayer&) = delete;
  153. FocusRingLayer& operator=(const FocusRingLayer&) = delete;
  154. ~FocusRingLayer() override {}
  155. private:
  156. // views::LayerDelegate:
  157. void OnPaintLayer(const ui::PaintContext& context) override {
  158. ui::PaintRecorder recorder(context, bounds().size());
  159. gfx::Canvas* canvas = recorder.canvas();
  160. // When using strokes to draw a rect, the bounds set is the center of the
  161. // rect, which means that setting draw bounds to `bounds()` will leave half
  162. // of the border outside the layer that may not be painted. Shrink the draw
  163. // bounds by half of the width to solve this problem.
  164. gfx::Rect draw_bounds(bounds().size());
  165. draw_bounds.Inset(kSearchBoxFocusRingWidth / 2);
  166. cc::PaintFlags flags;
  167. flags.setAntiAlias(true);
  168. flags.setColor(AppListColorProvider::Get()->GetFocusRingColor());
  169. flags.setStyle(cc::PaintFlags::Style::kStroke_Style);
  170. flags.setStrokeWidth(kSearchBoxFocusRingWidth);
  171. canvas->DrawRoundRect(draw_bounds, kSearchBoxFocusRingCornerRadius, flags);
  172. }
  173. void OnDeviceScaleFactorChanged(float old_device_scale_factor,
  174. float new_device_scale_factor) override {
  175. SchedulePaint(gfx::Rect(size()));
  176. }
  177. };
  178. SearchBoxView::SearchBoxView(SearchBoxViewDelegate* delegate,
  179. AppListViewDelegate* view_delegate,
  180. AppListView* app_list_view)
  181. : delegate_(delegate),
  182. view_delegate_(view_delegate),
  183. app_list_view_(app_list_view),
  184. is_app_list_bubble_(!app_list_view_),
  185. is_tablet_mode_(view_delegate_->IsInTabletMode()) {
  186. AppListModelProvider* const model_provider = AppListModelProvider::Get();
  187. model_provider->AddObserver(this);
  188. SearchBoxModel* const search_box_model =
  189. model_provider->search_model()->search_box();
  190. search_box_model_observer_.Observe(search_box_model);
  191. views::ImageButton* close_button = CreateCloseButton(base::BindRepeating(
  192. &SearchBoxView::CloseButtonPressed, base::Unretained(this)));
  193. std::u16string close_button_label(
  194. l10n_util::GetStringUTF16(IDS_APP_LIST_CLEAR_SEARCHBOX));
  195. close_button->SetAccessibleName(close_button_label);
  196. close_button->SetTooltipText(close_button_label);
  197. views::ImageButton* assistant_button =
  198. CreateAssistantButton(base::BindRepeating(
  199. &SearchBoxView::AssistantButtonPressed, base::Unretained(this)));
  200. assistant_button->SetFlipCanvasOnPaintForRTLUI(false);
  201. std::u16string assistant_button_label(
  202. l10n_util::GetStringUTF16(IDS_APP_LIST_START_ASSISTANT));
  203. assistant_button->SetAccessibleName(assistant_button_label);
  204. assistant_button->SetTooltipText(assistant_button_label);
  205. SetShowAssistantButton(search_box_model->show_assistant_button());
  206. }
  207. SearchBoxView::~SearchBoxView() {
  208. AppListModelProvider::Get()->RemoveObserver(this);
  209. }
  210. void SearchBoxView::InitializeForBubbleLauncher() {
  211. SearchBoxViewBase::InitParams params;
  212. params.show_close_button_when_active = false;
  213. params.create_background = false;
  214. params.animate_changing_search_icon = false;
  215. params.increase_child_view_padding = true;
  216. // Add margins to the text field because the BoxLayout vertical centering
  217. // does not properly align the text baseline with the icons.
  218. params.textfield_margins = kTextFieldMarginsForAppListBubble;
  219. SearchBoxViewBase::Init(params);
  220. UpdatePlaceholderTextAndAccessibleName();
  221. }
  222. void SearchBoxView::InitializeForFullscreenLauncher() {
  223. SearchBoxViewBase::InitParams params;
  224. params.show_close_button_when_active = true;
  225. params.create_background = true;
  226. params.animate_changing_search_icon = true;
  227. SearchBoxViewBase::Init(params);
  228. UpdatePlaceholderTextAndAccessibleName();
  229. }
  230. void SearchBoxView::SetResultSelectionController(
  231. ResultSelectionController* controller) {
  232. DCHECK(controller);
  233. result_selection_controller_ = controller;
  234. }
  235. void SearchBoxView::OnTabletModeChanged(bool started) {
  236. // Reset timer metrics when tablet mode changes.
  237. if (is_tablet_mode_ != started)
  238. user_initiated_model_update_time_ = base::TimeTicks();
  239. is_tablet_mode_ = started;
  240. UpdateKeyboardVisibility();
  241. // Search box accessible name may change depending on tablet mode state.
  242. UpdatePlaceholderTextAndAccessibleName();
  243. UpdateSearchBoxBorder();
  244. UpdateBackgroundColor(GetBackgroundColorForState(current_app_list_state_));
  245. }
  246. void SearchBoxView::ResetForShow() {
  247. // Avoid clearing an already inactive SearchBox in tablet mode because this
  248. // causes suggested chips to flash (http://crbug.com/979594).
  249. if (!is_search_box_active() && is_tablet_mode_)
  250. return;
  251. ClearSearchAndDeactivateSearchBox();
  252. if (contents_view_) {
  253. SetSearchBoxBackgroundCornerRadius(GetSearchBoxBorderCornerRadiusForState(
  254. contents_view_->GetActiveState()));
  255. }
  256. }
  257. void SearchBoxView::UpdateSearchTextfieldAccessibleNodeData(
  258. ui::AXNodeData* node_data) {
  259. if (a11y_active_descendant_) {
  260. node_data->AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
  261. *a11y_active_descendant_);
  262. }
  263. }
  264. void SearchBoxView::HandleSearchBoxEvent(ui::LocatedEvent* located_event) {
  265. if (located_event->type() == ui::ET_MOUSEWHEEL) {
  266. // TODO(crbug.com/1216082): Forward scroll events for bubble launcher.
  267. if (app_list_view_ &&
  268. !app_list_view_->HandleScroll(
  269. located_event->location(),
  270. located_event->AsMouseWheelEvent()->offset(), ui::ET_MOUSEWHEEL)) {
  271. return;
  272. }
  273. }
  274. SearchBoxViewBase::HandleSearchBoxEvent(located_event);
  275. }
  276. void SearchBoxView::OnActiveAppListModelsChanged(AppListModel* model,
  277. SearchModel* search_model) {
  278. search_box_model_observer_.Reset();
  279. search_box_model_observer_.Observe(search_model->search_box());
  280. ResetForShow();
  281. UpdateSearchIcon();
  282. OnWallpaperColorsChanged();
  283. ShowAssistantChanged();
  284. }
  285. void SearchBoxView::UpdateKeyboardVisibility() {
  286. if (!keyboard::KeyboardUIController::HasInstance())
  287. return;
  288. auto* const keyboard_controller = keyboard::KeyboardUIController::Get();
  289. bool should_show_keyboard =
  290. is_search_box_active() && search_box()->HasFocus();
  291. if (!keyboard_controller->IsEnabled() ||
  292. should_show_keyboard == keyboard_controller->IsKeyboardVisible()) {
  293. return;
  294. }
  295. if (should_show_keyboard) {
  296. keyboard_controller->ShowKeyboard(false);
  297. return;
  298. }
  299. keyboard_controller->HideKeyboardByUser();
  300. }
  301. void SearchBoxView::HandleQueryChange(const std::u16string& query,
  302. bool initiated_by_user) {
  303. // Randomly select a new placeholder text when we get an empty new query.
  304. if (query.empty() && features::IsProductivityLauncherEnabled())
  305. UpdatePlaceholderTextAndAccessibleName();
  306. MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
  307. // Update autocomplete text highlight range to track user typed text.
  308. if (ShouldProcessAutocomplete())
  309. ResetHighlightRange();
  310. if (initiated_by_user) {
  311. const base::TimeTicks current_time = base::TimeTicks::Now();
  312. if (current_query_.empty() && !query.empty()) {
  313. base::RecordAction(base::UserMetricsAction("AppList_SearchQueryStarted"));
  314. // Set 'user_initiated_model_update_time_' when initiating a new query.
  315. user_initiated_model_update_time_ = current_time;
  316. } else if (!current_query_.empty() && query.empty()) {
  317. base::RecordAction(base::UserMetricsAction("AppList_LeaveSearch"));
  318. // Reset 'user_initiated_model_update_time_' when clearing the search_box.
  319. user_initiated_model_update_time_ = base::TimeTicks();
  320. } else if (query != current_query_ &&
  321. !user_initiated_model_update_time_.is_null()) {
  322. if (is_tablet_mode_) {
  323. UMA_HISTOGRAM_TIMES("Ash.SearchModelUpdateTime.TabletMode",
  324. current_time - user_initiated_model_update_time_);
  325. } else {
  326. UMA_HISTOGRAM_TIMES("Ash.SearchModelUpdateTime.ClamshellMode",
  327. current_time - user_initiated_model_update_time_);
  328. }
  329. user_initiated_model_update_time_ = current_time;
  330. }
  331. }
  332. std::u16string trimmed_query;
  333. base::TrimWhitespace(query, base::TrimPositions::TRIM_ALL, &trimmed_query);
  334. const bool query_empty_changed =
  335. trimmed_query.empty() != IsTrimmedQueryEmpty(current_query_);
  336. current_query_ = query;
  337. // The search box background depens on whether the query is empty, so schedule
  338. // repaint when this changes.
  339. if (query_empty_changed)
  340. SchedulePaint();
  341. delegate_->QueryChanged(trimmed_query, initiated_by_user);
  342. // Don't reinitiate zero state search if the previous query was already empty
  343. // (to avoid issuing zero state search twice in a row while clearing up search
  344. // - see http://crbug.com/979594).
  345. if (initiated_by_user || !trimmed_query.empty() || query_empty_changed)
  346. view_delegate_->StartSearch(query);
  347. }
  348. void SearchBoxView::UpdatePlaceholderTextStyle() {
  349. if (is_app_list_bubble_) {
  350. // The bubble launcher text is always side-aligned.
  351. search_box()->set_placeholder_text_draw_flags(
  352. base::i18n::IsRTL() ? gfx::Canvas::TEXT_ALIGN_RIGHT
  353. : gfx::Canvas::TEXT_ALIGN_LEFT);
  354. // Bubble launcher uses standard text colors (light-on-dark by default).
  355. search_box()->set_placeholder_text_color(
  356. AshColorProvider::Get()->GetContentLayerColor(
  357. AshColorProvider::ContentLayerType::kTextColorSecondary));
  358. return;
  359. }
  360. // Fullscreen launcher centers the text when inactive.
  361. search_box()->set_placeholder_text_draw_flags(
  362. is_search_box_active()
  363. ? (base::i18n::IsRTL() ? gfx::Canvas::TEXT_ALIGN_RIGHT
  364. : gfx::Canvas::TEXT_ALIGN_LEFT)
  365. : gfx::Canvas::TEXT_ALIGN_CENTER);
  366. // Fullscreen launcher uses custom colors (dark-on-light by default).
  367. search_box()->set_placeholder_text_color(
  368. is_search_box_active()
  369. ? AppListColorProvider::Get()->GetSearchBoxSecondaryTextColor(
  370. kZeroQuerySearchboxColor)
  371. : AppListColorProvider::Get()->GetSearchBoxTextColor(
  372. kDefaultSearchboxPlaceholderTextColor));
  373. }
  374. void SearchBoxView::UpdateSearchBoxBorder() {
  375. gfx::Insets border_insets;
  376. if (!is_app_list_bubble_) {
  377. // Creates an empty border to create a region for the focus ring to appear.
  378. border_insets = gfx::Insets(GetFocusRingSpacing());
  379. } else {
  380. // Bubble search box does not use a focus ring.
  381. border_insets = kBorderInsetsForAppListBubble;
  382. }
  383. SetBorder(views::CreateEmptyBorder(border_insets));
  384. }
  385. void SearchBoxView::OnPaintBackground(gfx::Canvas* canvas) {
  386. // Paint the SearchBoxBackground defined in SearchBoxViewBase first.
  387. views::View::OnPaintBackground(canvas);
  388. if (is_app_list_bubble_) {
  389. // When the search box is focused, paint a vertical focus bar along the left
  390. // edge, vertically aligned with the search icon.
  391. if (search_box()->HasFocus() && IsTrimmedQueryEmpty(current_query_)) {
  392. gfx::Point icon_origin;
  393. views::View::ConvertPointToTarget(search_icon(), this, &icon_origin);
  394. PaintFocusBar(canvas, gfx::Point(0, icon_origin.y()),
  395. /*height=*/GetSearchBoxIconSize());
  396. }
  397. }
  398. }
  399. void SearchBoxView::OnPaintBorder(gfx::Canvas* canvas) {
  400. if (should_paint_highlight_border_) {
  401. views::HighlightBorder::PaintBorderToCanvas(
  402. canvas, *this, GetContentsBounds(),
  403. gfx::RoundedCornersF(corner_radius_),
  404. views::HighlightBorder::Type::kHighlightBorder1, false);
  405. }
  406. }
  407. const char* SearchBoxView::GetClassName() const {
  408. return "SearchBoxView";
  409. }
  410. void SearchBoxView::OnThemeChanged() {
  411. SearchBoxViewBase::OnThemeChanged();
  412. close_button()->SetImage(
  413. views::ImageButton::STATE_NORMAL,
  414. gfx::CreateVectorIcon(views::kIcCloseIcon, GetSearchBoxIconSize(),
  415. AppListColorProvider::Get()->GetSearchBoxIconColor(
  416. gfx::kGoogleGrey700)));
  417. assistant_button()->SetImage(
  418. views::ImageButton::STATE_NORMAL,
  419. gfx::CreateVectorIcon(chromeos::kAssistantIcon, GetSearchBoxIconSize(),
  420. AppListColorProvider::Get()->GetSearchBoxIconColor(
  421. gfx::kGoogleGrey700)));
  422. OnWallpaperColorsChanged();
  423. }
  424. void SearchBoxView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
  425. if (focus_ring_layer_)
  426. focus_ring_layer_->SetBounds(bounds());
  427. }
  428. // static
  429. int SearchBoxView::GetFocusRingSpacing() {
  430. return kSearchBoxFocusRingWidth + kSearchBoxFocusRingPadding;
  431. }
  432. void SearchBoxView::MaybeCreateFocusRing() {
  433. if (!is_app_list_bubble_) {
  434. focus_ring_layer_ = std::make_unique<FocusRingLayer>();
  435. layer()->parent()->Add(focus_ring_layer_.get());
  436. layer()->parent()->StackAtBottom(focus_ring_layer_.get());
  437. }
  438. }
  439. void SearchBoxView::RecordSearchBoxActivationHistogram(
  440. ui::EventType event_type) {
  441. ActivationSource activation_type;
  442. switch (event_type) {
  443. case ui::ET_GESTURE_TAP:
  444. activation_type = ActivationSource::kGestureTap;
  445. break;
  446. case ui::ET_MOUSE_PRESSED:
  447. activation_type = ActivationSource::kMousePress;
  448. break;
  449. case ui::ET_KEY_PRESSED:
  450. activation_type = ActivationSource::kKeyPress;
  451. break;
  452. default:
  453. return;
  454. }
  455. base::UmaHistogramEnumeration("Apps.AppListSearchBoxActivated",
  456. activation_type);
  457. if (is_tablet_mode_) {
  458. base::UmaHistogramEnumeration("Apps.AppListSearchBoxActivated.TabletMode",
  459. activation_type);
  460. } else {
  461. base::UmaHistogramEnumeration(
  462. "Apps.AppListSearchBoxActivated.ClamshellMode", activation_type);
  463. }
  464. }
  465. void SearchBoxView::OnSearchBoxActiveChanged(bool active) {
  466. UpdateSearchIcon();
  467. // Clear ghost text when toggling search box active state.
  468. MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
  469. if (active) {
  470. result_selection_controller_->ResetSelection(nullptr,
  471. true /* default_selection */);
  472. } else {
  473. result_selection_controller_->ClearSelection();
  474. }
  475. // Remove accessibility hint for classic launcher when search box is active
  476. // because there are no apps to navigate to.
  477. if (!features::IsProductivityLauncherEnabled()) {
  478. if (active) {
  479. search_box()->SetAccessibleName(std::u16string());
  480. } else {
  481. UpdatePlaceholderTextAndAccessibleName();
  482. }
  483. }
  484. delegate_->ActiveChanged(this);
  485. }
  486. void SearchBoxView::UpdateSearchBoxFocusPaint() {
  487. if (!focus_ring_layer_)
  488. return;
  489. // Paints the focus ring if the search box is focused.
  490. if (search_box()->HasFocus() && !is_search_box_active() &&
  491. view_delegate_->KeyboardTraversalEngaged()) {
  492. focus_ring_layer_->SetVisible(true);
  493. } else {
  494. focus_ring_layer_->SetVisible(false);
  495. }
  496. }
  497. void SearchBoxView::OnKeyEvent(ui::KeyEvent* evt) {
  498. // Handle keyboard navigation keys when close button is focused - move the
  499. // focus to the search box text field, and ensure result selection gets
  500. // updated according to the navigation key. The latter is the reason
  501. // navigation is handled here instead of the focus manager - intended result
  502. // selection depends on the key event that triggered the focus change.
  503. if (close_button()->HasFocus() && evt->type() == ui::ET_KEY_PRESSED &&
  504. (IsUnhandledArrowKeyEvent(*evt) || evt->key_code() == ui::VKEY_TAB)) {
  505. search_box()->RequestFocus();
  506. if (delegate_->CanSelectSearchResults() &&
  507. result_selection_controller_->MoveSelection(*evt) ==
  508. ResultSelectionController::MoveResult::kResultChanged) {
  509. UpdateSearchBoxForSelectedResult(
  510. result_selection_controller_->selected_result()->result());
  511. }
  512. evt->SetHandled();
  513. return;
  514. }
  515. delegate_->OnSearchBoxKeyEvent(evt);
  516. }
  517. bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent& event) {
  518. if (contents_view_)
  519. return contents_view_->OnMouseWheel(event);
  520. return false;
  521. }
  522. void SearchBoxView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
  523. if (HasAutocompleteText()) {
  524. node_data->role = ax::mojom::Role::kTextField;
  525. node_data->SetValue(l10n_util::GetStringFUTF16(
  526. IDS_APP_LIST_SEARCH_BOX_AUTOCOMPLETE, search_box()->GetText()));
  527. }
  528. }
  529. void SearchBoxView::UpdateBackground(AppListState target_state) {
  530. int corner_radius = GetSearchBoxBorderCornerRadiusForState(target_state);
  531. SetSearchBoxBackgroundCornerRadius(corner_radius);
  532. const bool is_corner_radius_changed = corner_radius_ != corner_radius;
  533. corner_radius_ = corner_radius;
  534. bool highlight_border_changed = false;
  535. // The background layer is only painted for the search box in tablet mode.
  536. // Also the layer is not painted when the search result page is visible.
  537. if (is_tablet_mode_ && (!search_result_page_visible_ ||
  538. target_state == AppListState::kStateApps)) {
  539. layer()->SetClipRect(GetContentsBounds());
  540. layer()->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
  541. layer()->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
  542. layer()->SetRoundedCornerRadius(gfx::RoundedCornersF(corner_radius));
  543. highlight_border_changed = !should_paint_highlight_border_;
  544. should_paint_highlight_border_ = true;
  545. } else {
  546. layer()->SetBackgroundBlur(0);
  547. layer()->SetBackdropFilterQuality(0);
  548. highlight_border_changed = should_paint_highlight_border_;
  549. should_paint_highlight_border_ = false;
  550. }
  551. if (is_corner_radius_changed || highlight_border_changed)
  552. SchedulePaint();
  553. UpdateBackgroundColor(GetBackgroundColorForState(target_state));
  554. UpdateTextColor();
  555. current_app_list_state_ = target_state;
  556. }
  557. void SearchBoxView::UpdateLayout(AppListState target_state,
  558. int target_state_height) {
  559. // Horizontal margins are selected to match search box icon's vertical
  560. // margins.
  561. const int horizontal_spacing =
  562. (target_state_height - GetSearchBoxIconSize()) / 2;
  563. const int horizontal_right_padding =
  564. horizontal_spacing -
  565. (GetSearchBoxButtonSize() - GetSearchBoxIconSize()) / 2;
  566. box_layout_view()->SetInsideBorderInsets(
  567. gfx::Insets::TLBR(0, horizontal_spacing, 0, horizontal_right_padding));
  568. box_layout_view()->SetBetweenChildSpacing(horizontal_spacing);
  569. InvalidateLayout();
  570. UpdateBackground(target_state);
  571. }
  572. int SearchBoxView::GetSearchBoxBorderCornerRadiusForState(
  573. AppListState state) const {
  574. if (state == AppListState::kStateSearchResults && app_list_view_ &&
  575. !app_list_view_->is_in_drag()) {
  576. return features::IsProductivityLauncherEnabled()
  577. ? kExpandedSearchBoxCornerRadiusForProductivityLauncher
  578. : kSearchBoxBorderCornerRadiusSearchResult;
  579. }
  580. return kSearchBoxBorderCornerRadius;
  581. }
  582. SkColor SearchBoxView::GetBackgroundColorForState(AppListState state) const {
  583. if (state == AppListState::kStateSearchResults) {
  584. if ((features::IsDarkLightModeEnabled() ||
  585. features::IsProductivityLauncherEnabled()) &&
  586. search_result_page_visible_) {
  587. return SK_ColorTRANSPARENT;
  588. }
  589. return AppListColorProvider::Get()->GetSearchBoxCardBackgroundColor();
  590. }
  591. return AppListColorProvider::Get()->GetSearchBoxBackgroundColor();
  592. }
  593. void SearchBoxView::ShowZeroStateSuggestions() {
  594. base::RecordAction(
  595. base::UserMetricsAction("AppList_ShowZeroStateSuggestions"));
  596. std::u16string empty_query;
  597. ContentsChanged(search_box(), empty_query);
  598. }
  599. void SearchBoxView::OnWallpaperColorsChanged() {
  600. UpdateSearchIcon();
  601. UpdatePlaceholderTextStyle();
  602. UpdateTextColor();
  603. if (features::IsDarkLightModeEnabled()) {
  604. UpdateBackgroundColor(
  605. AppListColorProvider::Get()->GetSearchBoxBackgroundColor());
  606. }
  607. SchedulePaint();
  608. }
  609. void SearchBoxView::ProcessAutocomplete(
  610. SearchResultBaseView* first_result_view) {
  611. if (!ShouldProcessAutocomplete())
  612. return;
  613. if (!first_result_view || !first_result_view->selected())
  614. return;
  615. SearchResult* const first_visible_result = first_result_view->result();
  616. if (first_result_view->is_default_result() &&
  617. current_query_ != search_box()->GetText()) {
  618. // Search box text has been set to the previous selected result. Reset
  619. // it back to the current query. This could happen due to the racing
  620. // between results update and user press key to select a result.
  621. // See crbug.com/1065454.
  622. search_box()->SetText(current_query_);
  623. }
  624. // Current non-autocompleted text.
  625. const std::u16string& user_typed_text =
  626. search_box()->GetText().substr(0, highlight_range_.start());
  627. if (last_key_pressed_ == ui::VKEY_BACK ||
  628. last_key_pressed_ == ui::VKEY_DELETE || IsArrowKey(last_key_pressed_) ||
  629. !first_visible_result ||
  630. user_typed_text.length() < kMinimumLengthToAutocomplete) {
  631. // If the suggestion was rejected, no results exist, or current text
  632. // is too short for a confident autocomplete suggestion.
  633. return;
  634. }
  635. const std::u16string& details = first_visible_result->details();
  636. const std::u16string& search_text = first_visible_result->title();
  637. // Don't set autocomplete text if it's the same as user typed text.
  638. if (user_typed_text == details || user_typed_text == search_text)
  639. return;
  640. if (ProcessPrefixMatchAutocomplete(first_visible_result, user_typed_text)) {
  641. RecordAutocompleteMatchMetric(SearchBoxTextMatch::kPrefixMatch);
  642. return;
  643. }
  644. if (IsValidAutocompleteText(search_text)) {
  645. // Setup autocomplete ghost text for eligible search_text.
  646. if (features::IsAutocompleteExtendedSuggestionsEnabled()) {
  647. MaybeSetAutocompleteGhostText(
  648. first_result_view->result()->title(),
  649. GetCategoryName(first_result_view->result()));
  650. }
  651. if (IsSubstringCaseInsensitive(search_text, user_typed_text)) {
  652. // user_typed_text is a substring of search_text and is eligible for
  653. // autocompletion.
  654. RecordAutocompleteMatchMetric(SearchBoxTextMatch::kSubstringMatch);
  655. } else {
  656. // user_typed_text does not match search_text but is eligible for
  657. // autocompletion.
  658. RecordAutocompleteMatchMetric(
  659. SearchBoxTextMatch::kAutocompletedWithoutMatch);
  660. }
  661. } else {
  662. // search_text is not eligible for autocompletion.
  663. RecordAutocompleteMatchMetric(SearchBoxTextMatch::kNoMatch);
  664. }
  665. ClearAutocompleteText();
  666. }
  667. bool SearchBoxView::ProcessPrefixMatchAutocomplete(
  668. SearchResult* search_result,
  669. const std::u16string& user_typed_text) {
  670. const std::u16string& details = search_result->details();
  671. const std::u16string& search_text = search_result->title();
  672. if (base::StartsWith(details, user_typed_text,
  673. base::CompareCase::INSENSITIVE_ASCII) &&
  674. IsValidAutocompleteText(details)) {
  675. // Current text in the search_box matches the first result's url.
  676. SetAutocompleteText(details);
  677. MaybeSetAutocompleteGhostText(std::u16string(),
  678. GetCategoryName(search_result));
  679. return true;
  680. }
  681. if (base::StartsWith(search_text, user_typed_text,
  682. base::CompareCase::INSENSITIVE_ASCII) &&
  683. IsValidAutocompleteText(search_text)) {
  684. // Current text in the search_box matches the first result's search result
  685. // text.
  686. SetAutocompleteText(search_text);
  687. MaybeSetAutocompleteGhostText(std::u16string(),
  688. GetCategoryName(search_result));
  689. return true;
  690. }
  691. return false;
  692. }
  693. void SearchBoxView::ClearAutocompleteText() {
  694. if (!ShouldProcessAutocomplete())
  695. return;
  696. // Clear ghost text.
  697. MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
  698. // Avoid triggering subsequent query by temporarily setting controller to
  699. // nullptr.
  700. search_box()->set_controller(nullptr);
  701. // search_box()->ClearCompositionText() does not work here because
  702. // SetAutocompleteText() calls SelectRange(), which comfirms the active
  703. // composition text (so there is nothing to clear here). Set empty composition
  704. // text to clear the selected range.
  705. search_box()->SetCompositionText(ui::CompositionText());
  706. search_box()->set_controller(this);
  707. ResetHighlightRange();
  708. }
  709. void SearchBoxView::OnResultContainerVisibilityChanged(bool visible) {
  710. if (search_result_page_visible_ == visible)
  711. return;
  712. search_result_page_visible_ = visible;
  713. UpdateBackground(current_app_list_state_);
  714. SchedulePaint();
  715. }
  716. bool SearchBoxView::HasValidQuery() {
  717. return !IsTrimmedQueryEmpty(current_query_);
  718. }
  719. int SearchBoxView::GetSearchBoxIconSize() {
  720. if (features::IsProductivityLauncherEnabled())
  721. return kBubbleLauncherSearchBoxIconSize;
  722. return kClassicSearchBoxIconSize;
  723. }
  724. int SearchBoxView::GetSearchBoxButtonSize() {
  725. if (features::IsProductivityLauncherEnabled())
  726. return kBubbleLauncherSearchBoxButtonSizeDip;
  727. return kClassicSearchBoxButtonSizeDip;
  728. }
  729. void SearchBoxView::CloseButtonPressed() {
  730. delegate_->CloseButtonPressed();
  731. }
  732. void SearchBoxView::AssistantButtonPressed() {
  733. delegate_->AssistantButtonPressed();
  734. }
  735. void SearchBoxView::UpdateSearchIcon() {
  736. const bool search_engine_is_google =
  737. AppListModelProvider::Get()->search_model()->search_engine_is_google();
  738. const gfx::VectorIcon& google_icon = is_search_box_active()
  739. ? vector_icons::kGoogleColorIcon
  740. : kGoogleBlackIcon;
  741. const gfx::VectorIcon& icon =
  742. search_engine_is_google ? google_icon : kSearchEngineNotGoogleIcon;
  743. SetSearchIconImage(
  744. gfx::CreateVectorIcon(icon, GetSearchBoxIconSize(),
  745. AppListColorProvider::Get()->GetSearchBoxIconColor(
  746. SkColorSetARGB(0xDE, 0x00, 0x00, 0x00))));
  747. }
  748. bool SearchBoxView::IsValidAutocompleteText(
  749. const std::u16string& autocomplete_text) {
  750. // Don't set autocomplete text if it's the same as current search box
  751. // text.
  752. if (autocomplete_text == search_box()->GetText())
  753. return false;
  754. // Don't set autocomplete text if the highlighted text is the same as
  755. // before.
  756. if (autocomplete_text.length() > highlight_range_.start() &&
  757. autocomplete_text.substr(highlight_range_.start()) ==
  758. search_box()->GetSelectedText()) {
  759. return false;
  760. }
  761. return true;
  762. }
  763. void SearchBoxView::UpdateTextColor() {
  764. if (is_app_list_bubble_) {
  765. // Bubble launcher uses standard text colors (light-on-dark by default).
  766. search_box()->SetTextColor(
  767. GetColorProvider()->GetColor(cros_tokens::kTextColorPrimary));
  768. } else {
  769. // Fullscreen launcher uses dark-on-light text by default.
  770. search_box()->SetTextColor(
  771. GetColorProvider()->GetColor(cros_tokens::kColorPrimaryInverted));
  772. }
  773. }
  774. void SearchBoxView::UpdatePlaceholderTextAndAccessibleName() {
  775. if (features::IsProductivityLauncherEnabled()) {
  776. switch (SelectPlaceholderText()) {
  777. case PlaceholderTextType::kShortcuts:
  778. search_box()->SetPlaceholderText(l10n_util::GetStringFUTF16(
  779. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE,
  780. l10n_util::GetStringUTF16(
  781. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_SHORTCUTS)));
  782. search_box()->SetAccessibleName(l10n_util::GetStringFUTF16(
  783. is_tablet_mode_
  784. ? IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_TABLET
  785. : IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_CLAMSHELL,
  786. l10n_util::GetStringUTF16(
  787. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_SHORTCUTS)));
  788. break;
  789. case PlaceholderTextType::kTabs:
  790. search_box()->SetPlaceholderText(l10n_util::GetStringFUTF16(
  791. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE,
  792. l10n_util::GetStringUTF16(
  793. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TABS)));
  794. search_box()->SetAccessibleName(l10n_util::GetStringFUTF16(
  795. is_tablet_mode_
  796. ? IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_TABLET
  797. : IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_CLAMSHELL,
  798. l10n_util::GetStringUTF16(
  799. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TABS)));
  800. break;
  801. case PlaceholderTextType::kSettings:
  802. search_box()->SetPlaceholderText(l10n_util::GetStringFUTF16(
  803. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE,
  804. l10n_util::GetStringUTF16(
  805. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_SETTINGS)));
  806. search_box()->SetAccessibleName(l10n_util::GetStringFUTF16(
  807. is_tablet_mode_
  808. ? IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_TABLET
  809. : IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_CLAMSHELL,
  810. l10n_util::GetStringUTF16(
  811. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_SETTINGS)));
  812. break;
  813. case PlaceholderTextType::kGames:
  814. search_box()->SetPlaceholderText(l10n_util::GetStringFUTF16(
  815. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE,
  816. l10n_util::GetStringUTF16(
  817. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_GAMES)));
  818. search_box()->SetAccessibleName(l10n_util::GetStringFUTF16(
  819. is_tablet_mode_
  820. ? IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_TABLET
  821. : IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_TEMPLATE_ACCESSIBILITY_NAME_CLAMSHELL,
  822. l10n_util::GetStringUTF16(
  823. IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER_GAMES)));
  824. break;
  825. }
  826. } else {
  827. search_box()->SetPlaceholderText(
  828. l10n_util::GetStringUTF16(IDS_APP_LIST_SEARCH_BOX_PLACEHOLDER));
  829. search_box()->SetAccessibleName(l10n_util::GetStringUTF16(
  830. is_tablet_mode_
  831. ? IDS_APP_LIST_SEARCH_BOX_ACCESSIBILITY_NAME_TABLET
  832. : IDS_APP_LIST_SEARCH_BOX_ACCESSIBILITY_NAME_CLAMSHELL));
  833. }
  834. }
  835. void SearchBoxView::AcceptAutocompleteText() {
  836. if (!ShouldProcessAutocomplete())
  837. return;
  838. // Do not trigger another search here in case the user is left clicking to
  839. // select existing autocomplete text. (This also matches omnibox behavior.)
  840. DCHECK(HasAutocompleteText());
  841. search_box()->ClearSelection();
  842. ResetHighlightRange();
  843. }
  844. bool SearchBoxView::HasAutocompleteText() {
  845. // If the selected range is non-empty, it will either be suggested by
  846. // autocomplete or selected by the user. If the recorded autocomplete
  847. // |highlight_range_| matches the selection range, this text is suggested by
  848. // autocomplete.
  849. return search_box()->GetSelectedRange().EqualsIgnoringDirection(
  850. highlight_range_) &&
  851. highlight_range_.length() > 0;
  852. }
  853. void SearchBoxView::OnBeforeUserAction(views::Textfield* sender) {
  854. if (a11y_active_descendant_)
  855. SetA11yActiveDescendant(absl::nullopt);
  856. }
  857. void SearchBoxView::SetAutocompleteText(
  858. const std::u16string& autocomplete_text) {
  859. if (!ShouldProcessAutocomplete())
  860. return;
  861. const std::u16string& current_text = search_box()->GetText();
  862. // Currrent text is a prefix of autocomplete text.
  863. DCHECK(base::StartsWith(autocomplete_text, current_text,
  864. base::CompareCase::INSENSITIVE_ASCII));
  865. // Autocomplete text should not be the same as current search box text.
  866. DCHECK(autocomplete_text != current_text);
  867. // Autocomplete text should not be the same as highlighted text.
  868. const std::u16string& highlighted_text =
  869. autocomplete_text.substr(highlight_range_.start());
  870. DCHECK(highlighted_text != current_text);
  871. highlight_range_.set_end(autocomplete_text.length());
  872. ui::CompositionText composition_text;
  873. composition_text.text = highlighted_text;
  874. composition_text.selection = gfx::Range(0, highlighted_text.length());
  875. // Avoid triggering subsequent query by temporarily setting controller to
  876. // nullptr.
  877. search_box()->set_controller(nullptr);
  878. search_box()->SetCompositionText(composition_text);
  879. search_box()->set_controller(this);
  880. // The controller was null briefly, so it was unaware of a highlight change.
  881. // As a result, we need to manually declare the range to allow for proper
  882. // selection behavior.
  883. search_box()->SetSelectedRange(highlight_range_);
  884. // Send an event to alert ChromeVox that an autocomplete has occurred.
  885. // The |kValueChanged| type lets ChromeVox know that it should scan
  886. // |node_data| for "Value".
  887. NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
  888. if (features::IsAutocompleteExtendedSuggestionsEnabled())
  889. MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
  890. }
  891. SearchBoxView::PlaceholderTextType SearchBoxView::SelectPlaceholderText()
  892. const {
  893. if (use_fixed_placeholder_text_for_test_)
  894. return kDefaultPlaceholders[0];
  895. if (chromeos::features::IsCloudGamingDeviceEnabled())
  896. return kGamingPlaceholders[rand() % std::size(kGamingPlaceholders)];
  897. return kDefaultPlaceholders[rand() % std::size(kDefaultPlaceholders)];
  898. }
  899. void SearchBoxView::UpdateQuery(const std::u16string& new_query) {
  900. search_box()->SetText(new_query);
  901. ContentsChanged(search_box(), new_query);
  902. }
  903. void SearchBoxView::ClearSearchAndDeactivateSearchBox() {
  904. if (!is_search_box_active())
  905. return;
  906. SetA11yActiveDescendant(absl::nullopt);
  907. ClearSearch();
  908. SetSearchBoxActive(false, ui::ET_UNKNOWN);
  909. if (features::IsAutocompleteExtendedSuggestionsEnabled())
  910. MaybeSetAutocompleteGhostText(std::u16string(), std::u16string());
  911. }
  912. void SearchBoxView::SetA11yActiveDescendant(
  913. const absl::optional<int32_t>& active_descendant) {
  914. a11y_active_descendant_ = active_descendant;
  915. search_box()->NotifyAccessibilityEvent(
  916. ax::mojom::Event::kActiveDescendantChanged, true);
  917. }
  918. void SearchBoxView::UseFixedPlaceholderTextForTest() {
  919. if (use_fixed_placeholder_text_for_test_)
  920. return;
  921. use_fixed_placeholder_text_for_test_ = true;
  922. UpdatePlaceholderTextAndAccessibleName();
  923. }
  924. bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
  925. const ui::KeyEvent& key_event) {
  926. DCHECK(result_selection_controller_);
  927. if (key_event.type() == ui::ET_KEY_RELEASED)
  928. return false;
  929. // Events occurring over an inactive search box are handled elsewhere, with
  930. // the exception of left/right arrow key events, and return.
  931. if (!is_search_box_active()) {
  932. if (key_event.key_code() == ui::VKEY_RETURN) {
  933. SetSearchBoxActive(true, key_event.type());
  934. return true;
  935. }
  936. if (IsUnhandledLeftRightKeyEvent(key_event)) {
  937. return ProcessLeftRightKeyTraversalForTextfield(search_box(), key_event);
  938. }
  939. return false;
  940. }
  941. // Nothing to do if no results are available (the rest of the method handles
  942. // result actions and result traversal). This might happen if zero state
  943. // suggestions are not enabled, and search box textfield is empty.
  944. if (!delegate_->CanSelectSearchResults())
  945. return false;
  946. // When search box is active, the focus cycles between close button and the
  947. // search_box - when close button is focused, traversal keys (arrows and
  948. // tab) should move the focus to the search box, and reset the selection
  949. // (which might have been cleared when focus moved to the close button).
  950. if (!search_box()->HasFocus()) {
  951. // Only handle result traversal keys.
  952. if (!IsUnhandledArrowKeyEvent(key_event) &&
  953. key_event.key_code() != ui::VKEY_TAB) {
  954. return false;
  955. }
  956. search_box()->RequestFocus();
  957. if (result_selection_controller_->MoveSelection(key_event) ==
  958. ResultSelectionController::MoveResult::kResultChanged) {
  959. UpdateSearchBoxForSelectedResult(
  960. result_selection_controller_->selected_result()->result());
  961. }
  962. return true;
  963. }
  964. // Handle return - opens the selected result.
  965. if (key_event.key_code() == ui::VKEY_RETURN) {
  966. // Hitting Enter when focus is on search box opens the selected result.
  967. ui::KeyEvent event(key_event);
  968. SearchResultBaseView* selected_result =
  969. result_selection_controller_->selected_result();
  970. if (selected_result && selected_result->result())
  971. selected_result->OnKeyEvent(&event);
  972. return true;
  973. }
  974. // Allows alt+back and alt+delete as a shortcut for the 'remove result'
  975. // dialog
  976. if (key_event.IsAltDown() &&
  977. ((key_event.key_code() == ui::VKEY_BROWSER_BACK) ||
  978. (key_event.key_code() == ui::VKEY_DELETE))) {
  979. ui::KeyEvent event(key_event);
  980. SearchResultBaseView* selected_result =
  981. result_selection_controller_->selected_result();
  982. if (selected_result && selected_result->result())
  983. selected_result->OnKeyEvent(&event);
  984. // Reset the selected result to the default result.
  985. result_selection_controller_->ResetSelection(nullptr,
  986. true /* default_selection */);
  987. search_box()->SetText(std::u16string());
  988. return true;
  989. }
  990. // Do not handle keys intended for result selection traversal here - these
  991. // should be handled elsewhere, for example by the search box text field.
  992. // Keys used for result selection traversal:
  993. // * TAB
  994. // * up/down key
  995. // * left/right, if the selected container is horizontal. For vertical
  996. // containers, left and right key should be handled by the text field
  997. // (to move cursor, and clear or accept autocomplete suggestion).
  998. const bool result_selection_traversal_key_event =
  999. key_event.key_code() == ui::VKEY_TAB ||
  1000. IsUnhandledUpDownKeyEvent(key_event) ||
  1001. (IsUnhandledLeftRightKeyEvent(key_event) &&
  1002. result_selection_controller_->selected_location_details() &&
  1003. result_selection_controller_->selected_location_details()
  1004. ->container_is_horizontal);
  1005. if (!result_selection_traversal_key_event) {
  1006. // Record the |last_key_pressed_| for autocomplete.
  1007. if (!search_box()->GetText().empty() && ShouldProcessAutocomplete())
  1008. last_key_pressed_ = key_event.key_code();
  1009. return false;
  1010. }
  1011. // Clear non-auto-complete generated selection to prevent navigation keys from
  1012. // deleting selected text.
  1013. if (search_box()->HasSelection() && !HasAutocompleteText())
  1014. search_box()->ClearSelection();
  1015. ResultSelectionController::MoveResult move_result =
  1016. result_selection_controller_->MoveSelection(key_event);
  1017. switch (move_result) {
  1018. case ResultSelectionController::MoveResult::kNone:
  1019. // If the |ResultSelectionController| decided not to change selection,
  1020. // return early, as what follows is actions for updating based on
  1021. // change.
  1022. break;
  1023. case ResultSelectionController::MoveResult::kSelectionCycleRejected:
  1024. // If move was about to cycle, clear the selection and move the focus to
  1025. // the next element in the SearchBoxView - close_button() (only
  1026. // close_button() and search_box() are expected to be in the focus cycle
  1027. // while the search box is active).
  1028. if (HasAutocompleteText())
  1029. ClearAutocompleteText();
  1030. result_selection_controller_->ClearSelection();
  1031. DCHECK(close_button()->GetVisible());
  1032. close_button()->RequestFocus();
  1033. SetA11yActiveDescendant(absl::nullopt);
  1034. break;
  1035. case ResultSelectionController::MoveResult::kResultChanged:
  1036. UpdateSearchBoxForSelectedResult(
  1037. result_selection_controller_->selected_result()->result());
  1038. break;
  1039. }
  1040. return true;
  1041. }
  1042. bool SearchBoxView::HandleMouseEvent(views::Textfield* sender,
  1043. const ui::MouseEvent& mouse_event) {
  1044. if (mouse_event.type() == ui::ET_MOUSEWHEEL) {
  1045. // TODO(crbug.com/1216082): Forward scroll events for bubble launcher.
  1046. if (!app_list_view_) {
  1047. NOTIMPLEMENTED_LOG_ONCE();
  1048. return false;
  1049. }
  1050. return app_list_view_->HandleScroll(
  1051. mouse_event.location(), (&mouse_event)->AsMouseWheelEvent()->offset(),
  1052. ui::ET_MOUSEWHEEL);
  1053. }
  1054. if (mouse_event.type() == ui::ET_MOUSE_PRESSED && HasAutocompleteText())
  1055. AcceptAutocompleteText();
  1056. // Don't activate search box for context menu click.
  1057. if (mouse_event.type() == ui::ET_MOUSE_PRESSED &&
  1058. mouse_event.IsOnlyRightMouseButton()) {
  1059. return false;
  1060. }
  1061. return SearchBoxViewBase::HandleMouseEvent(sender, mouse_event);
  1062. }
  1063. bool SearchBoxView::HandleGestureEvent(views::Textfield* sender,
  1064. const ui::GestureEvent& gesture_event) {
  1065. if (gesture_event.type() == ui::ET_GESTURE_TAP && HasAutocompleteText())
  1066. AcceptAutocompleteText();
  1067. return SearchBoxViewBase::HandleGestureEvent(sender, gesture_event);
  1068. }
  1069. void SearchBoxView::UpdateSearchBoxForSelectedResult(
  1070. SearchResult* selected_result) {
  1071. if (selected_result->result_type() ==
  1072. AppListSearchResultType::kInternalPrivacyInfo) {
  1073. // Privacy view should not change the search box text.
  1074. return;
  1075. }
  1076. if (features::IsAutocompleteExtendedSuggestionsEnabled()) {
  1077. ClearAutocompleteText();
  1078. const std::u16string& details = selected_result->details();
  1079. const std::u16string& search_text = selected_result->title();
  1080. // Don't set autocomplete text if it's the same as user typed text.
  1081. if (current_query_ == details || current_query_ == search_text)
  1082. return;
  1083. if (!ProcessPrefixMatchAutocomplete(selected_result, current_query_)) {
  1084. MaybeSetAutocompleteGhostText(selected_result->title(),
  1085. GetCategoryName(selected_result));
  1086. }
  1087. } else {
  1088. if (selected_result->result_type() == AppListSearchResultType::kOmnibox &&
  1089. !selected_result->is_omnibox_search() &&
  1090. !selected_result->details().empty()) {
  1091. // For url (non-search) results, use details to ensure that the url is
  1092. // displayed.
  1093. search_box()->SetText(selected_result->details());
  1094. } else {
  1095. search_box()->SetText(selected_result->title());
  1096. }
  1097. }
  1098. }
  1099. void SearchBoxView::SearchEngineChanged() {
  1100. UpdateSearchIcon();
  1101. }
  1102. void SearchBoxView::ShowAssistantChanged() {
  1103. SetShowAssistantButton(AppListModelProvider::Get()
  1104. ->search_model()
  1105. ->search_box()
  1106. ->show_assistant_button());
  1107. }
  1108. bool SearchBoxView::ShouldProcessAutocomplete() {
  1109. // IME sets composition text while the user is typing, so avoid handle
  1110. // autocomplete in this case to avoid conflicts.
  1111. return !(search_box()->IsIMEComposing() && highlight_range_.is_empty());
  1112. }
  1113. void SearchBoxView::ResetHighlightRange() {
  1114. DCHECK(ShouldProcessAutocomplete());
  1115. const uint32_t text_length = search_box()->GetText().length();
  1116. highlight_range_.set_start(text_length);
  1117. highlight_range_.set_end(text_length);
  1118. }
  1119. } // namespace ash