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.
5 #include "ui/app_list/views/search_box_view.h"
9 #include "ui/app_list/app_list_constants.h"
10 #include "ui/app_list/app_list_model.h"
11 #include "ui/app_list/app_list_switches.h"
12 #include "ui/app_list/app_list_view_delegate.h"
13 #include "ui/app_list/search_box_model.h"
14 #include "ui/app_list/speech_ui_model.h"
15 #include "ui/app_list/views/app_list_menu_views.h"
16 #include "ui/app_list/views/search_box_view_delegate.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/events/event.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/resources/grit/ui_resources.h"
21 #include "ui/views/border.h"
22 #include "ui/views/controls/button/image_button.h"
23 #include "ui/views/controls/button/menu_button.h"
24 #include "ui/views/controls/image_view.h"
25 #include "ui/views/controls/textfield/textfield.h"
26 #include "ui/views/layout/box_layout.h"
32 const int kPadding
= 14;
33 const int kPreferredWidth
= 360;
34 const int kPreferredHeight
= 48;
36 const SkColor kHintTextColor
= SkColorSetRGB(0xA0, 0xA0, 0xA0);
38 // Menu offset relative to the bottom-right corner of the menu button.
39 const int kMenuYOffsetFromButton
= -4;
40 const int kMenuXOffsetFromButton
= -7;
42 const int kBackgroundBorderWidth
= 1;
43 const int kBackgroundBorderBottomWidth
= 1;
44 const int kBackgroundBorderCornerRadius
= 2;
45 const SkColor kBackgroundBorderColor
= SkColorSetRGB(0xEE, 0xEE, 0xEE);
46 const SkColor kBackgroundBorderBottomColor
= SkColorSetRGB(0xCC, 0xCC, 0xCC);
48 // A background that paints a solid white rounded rect with a thin grey border.
49 class ExperimentalSearchBoxBackground
: public views::Background
{
51 ExperimentalSearchBoxBackground() {}
52 ~ExperimentalSearchBoxBackground() override
{}
55 // views::Background overrides:
56 void Paint(gfx::Canvas
* canvas
, views::View
* view
) const override
{
57 gfx::Rect bounds
= view
->GetContentsBounds();
60 paint
.setFlags(SkPaint::kAntiAlias_Flag
);
61 paint
.setColor(kBackgroundBorderColor
);
62 canvas
->DrawRoundRect(bounds
, kBackgroundBorderCornerRadius
, paint
);
63 bounds
.Inset(kBackgroundBorderWidth
,
64 kBackgroundBorderWidth
,
65 kBackgroundBorderWidth
,
67 paint
.setColor(kBackgroundBorderBottomColor
);
68 canvas
->DrawRoundRect(bounds
, kBackgroundBorderCornerRadius
, paint
);
69 bounds
.Inset(0, 0, 0, kBackgroundBorderBottomWidth
);
70 paint
.setColor(kSearchBoxBackground
);
71 canvas
->DrawRoundRect(bounds
, kBackgroundBorderCornerRadius
, paint
);
74 DISALLOW_COPY_AND_ASSIGN(ExperimentalSearchBoxBackground
);
79 SearchBoxView::SearchBoxView(SearchBoxViewDelegate
* delegate
,
80 AppListViewDelegate
* view_delegate
)
81 : delegate_(delegate
),
82 view_delegate_(view_delegate
),
88 search_box_(new views::Textfield
),
89 contents_view_(NULL
) {
90 if (switches::IsExperimentalAppListEnabled()) {
91 back_button_
= new views::ImageButton(this);
92 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
93 back_button_
->SetImage(
94 views::ImageButton::STATE_NORMAL
,
95 rb
.GetImageSkiaNamed(IDR_APP_LIST_FOLDER_BACK_NORMAL
));
96 back_button_
->SetImageAlignment(views::ImageButton::ALIGN_CENTER
,
97 views::ImageButton::ALIGN_MIDDLE
);
98 AddChildView(back_button_
);
100 set_background(new ExperimentalSearchBoxBackground());
103 views::Background::CreateSolidBackground(kSearchBoxBackground
));
105 views::Border::CreateSolidSidedBorder(0, 0, 1, 0, kTopSeparatorColor
));
106 icon_view_
= new views::ImageView
;
107 AddChildView(icon_view_
);
110 views::BoxLayout
* layout
=
111 new views::BoxLayout(views::BoxLayout::kHorizontal
,
114 kPadding
- views::Textfield::kTextPadding
);
115 SetLayoutManager(layout
);
116 layout
->set_cross_axis_alignment(
117 views::BoxLayout::CROSS_AXIS_ALIGNMENT_CENTER
);
118 layout
->set_minimum_cross_axis_size(kPreferredHeight
);
120 ui::ResourceBundle
& rb
= ui::ResourceBundle::GetSharedInstance();
122 search_box_
->SetBorder(views::Border::NullBorder());
123 search_box_
->SetFontList(rb
.GetFontList(ui::ResourceBundle::MediumFont
));
124 search_box_
->set_placeholder_text_color(kHintTextColor
);
125 search_box_
->set_controller(this);
126 AddChildView(search_box_
);
127 layout
->SetFlexForView(search_box_
, 1);
129 #if !defined(OS_CHROMEOS)
130 menu_button_
= new views::MenuButton(NULL
, base::string16(), this, false);
131 menu_button_
->SetBorder(views::Border::NullBorder());
132 menu_button_
->SetImage(views::Button::STATE_NORMAL
,
133 *rb
.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_NORMAL
));
134 menu_button_
->SetImage(views::Button::STATE_HOVERED
,
135 *rb
.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_HOVER
));
136 menu_button_
->SetImage(views::Button::STATE_PRESSED
,
137 *rb
.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_PRESSED
));
138 AddChildView(menu_button_
);
141 view_delegate_
->GetSpeechUI()->AddObserver(this);
145 SearchBoxView::~SearchBoxView() {
146 view_delegate_
->GetSpeechUI()->RemoveObserver(this);
147 model_
->search_box()->RemoveObserver(this);
150 void SearchBoxView::ModelChanged() {
152 model_
->search_box()->RemoveObserver(this);
154 model_
= view_delegate_
->GetModel();
156 model_
->search_box()->AddObserver(this);
158 SpeechRecognitionButtonPropChanged();
162 bool SearchBoxView::HasSearch() const {
163 return !search_box_
->text().empty();
166 void SearchBoxView::ClearSearch() {
167 search_box_
->SetText(base::string16());
168 view_delegate_
->AutoLaunchCanceled();
169 // Updates model and fires query changed manually because SetText() above
170 // does not generate ContentsChanged() notification.
172 NotifyQueryChanged();
175 void SearchBoxView::InvalidateMenu() {
179 gfx::Size
SearchBoxView::GetPreferredSize() const {
180 return gfx::Size(kPreferredWidth
, kPreferredHeight
);
183 bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent
& event
) {
185 return contents_view_
->OnMouseWheel(event
);
190 void SearchBoxView::OnEnabledChanged() {
191 search_box_
->SetEnabled(enabled());
193 menu_button_
->SetEnabled(enabled());
195 speech_button_
->SetEnabled(enabled());
198 void SearchBoxView::UpdateModel() {
199 // Temporarily remove from observer to ignore notifications caused by us.
200 model_
->search_box()->RemoveObserver(this);
201 model_
->search_box()->SetText(search_box_
->text());
202 model_
->search_box()->SetSelectionModel(search_box_
->GetSelectionModel());
203 model_
->search_box()->AddObserver(this);
206 void SearchBoxView::NotifyQueryChanged() {
208 delegate_
->QueryChanged(this);
211 void SearchBoxView::ContentsChanged(views::Textfield
* sender
,
212 const base::string16
& new_contents
) {
214 view_delegate_
->AutoLaunchCanceled();
215 NotifyQueryChanged();
218 bool SearchBoxView::HandleKeyEvent(views::Textfield
* sender
,
219 const ui::KeyEvent
& key_event
) {
220 bool handled
= false;
221 if (contents_view_
&& contents_view_
->visible())
222 handled
= contents_view_
->OnKeyPressed(key_event
);
224 // Prevent Shift+Tab from locking up the whole chrome browser process.
225 // Explicitly capturing the Shift+Tab event here compensates for a focus
226 // search issue. We get away with this because there are no other focus
227 // targets. See http://crbug.com/438425 for details.
228 if (key_event
.key_code() == ui::VKEY_TAB
&& key_event
.IsShiftDown())
234 void SearchBoxView::ButtonPressed(views::Button
* sender
,
235 const ui::Event
& event
) {
236 if (back_button_
&& sender
== back_button_
)
237 delegate_
->BackButtonPressed();
238 else if (speech_button_
&& sender
== speech_button_
)
239 view_delegate_
->ToggleSpeechRecognition();
244 void SearchBoxView::OnMenuButtonClicked(View
* source
, const gfx::Point
& point
) {
246 menu_
.reset(new AppListMenuViews(view_delegate_
));
248 const gfx::Point menu_location
=
249 menu_button_
->GetBoundsInScreen().bottom_right() +
250 gfx::Vector2d(kMenuXOffsetFromButton
, kMenuYOffsetFromButton
);
251 menu_
->RunMenuAt(menu_button_
, menu_location
);
254 void SearchBoxView::IconChanged() {
256 icon_view_
->SetImage(model_
->search_box()->icon());
259 void SearchBoxView::SpeechRecognitionButtonPropChanged() {
260 const SearchBoxModel::SpeechButtonProperty
* speech_button_prop
=
261 model_
->search_box()->speech_button();
262 if (speech_button_prop
) {
263 if (!speech_button_
) {
264 speech_button_
= new views::ImageButton(this);
265 AddChildView(speech_button_
);
268 if (view_delegate_
->GetSpeechUI()->state() ==
269 SPEECH_RECOGNITION_HOTWORD_LISTENING
) {
270 speech_button_
->SetImage(
271 views::Button::STATE_NORMAL
, &speech_button_prop
->on_icon
);
272 speech_button_
->SetTooltipText(speech_button_prop
->on_tooltip
);
274 speech_button_
->SetImage(
275 views::Button::STATE_NORMAL
, &speech_button_prop
->off_icon
);
276 speech_button_
->SetTooltipText(speech_button_prop
->off_tooltip
);
279 if (speech_button_
) {
280 // Deleting a view will detach it from its parent.
281 delete speech_button_
;
282 speech_button_
= NULL
;
288 void SearchBoxView::HintTextChanged() {
289 search_box_
->set_placeholder_text(model_
->search_box()->hint_text());
292 void SearchBoxView::SelectionModelChanged() {
293 search_box_
->SelectSelectionModel(model_
->search_box()->selection_model());
296 void SearchBoxView::TextChanged() {
297 search_box_
->SetText(model_
->search_box()->text());
298 NotifyQueryChanged();
301 void SearchBoxView::OnSpeechRecognitionStateChanged(
302 SpeechRecognitionState new_state
) {
303 SpeechRecognitionButtonPropChanged();
307 } // namespace app_list