Views Omnibox: tolerate minor click-to-select-all dragging.
[chromium-blink-merge.git] / ui / app_list / views / search_box_view.cc
blobaee2c2f0edd9f5a732f50e65b66fdb3efe32f8cb
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"
7 #include <algorithm>
9 #include "grit/ui_resources.h"
10 #include "ui/app_list/app_list_model.h"
11 #include "ui/app_list/app_list_view_delegate.h"
12 #include "ui/app_list/search_box_model.h"
13 #include "ui/app_list/speech_ui_model.h"
14 #include "ui/app_list/views/app_list_menu_views.h"
15 #include "ui/app_list/views/contents_view.h"
16 #include "ui/app_list/views/search_box_view_delegate.h"
17 #include "ui/base/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/events/event.h"
20 #include "ui/views/controls/button/image_button.h"
21 #include "ui/views/controls/button/menu_button.h"
22 #include "ui/views/controls/image_view.h"
23 #include "ui/views/controls/textfield/textfield.h"
25 namespace app_list {
27 namespace {
29 const int kPadding = 14;
30 const int kIconDimension = 32;
31 const int kPreferredWidth = 360;
32 const int kPreferredHeight = 48;
33 #if !defined(OS_CHROMEOS)
34 const int kMenuButtonDimension = 29;
35 #endif
37 const SkColor kHintTextColor = SkColorSetRGB(0xA0, 0xA0, 0xA0);
39 // Menu offset relative to the bottom-right corner of the menu button.
40 const int kMenuYOffsetFromButton = -4;
41 const int kMenuXOffsetFromButton = -7;
43 } // namespace
45 SearchBoxView::SearchBoxView(SearchBoxViewDelegate* delegate,
46 AppListViewDelegate* view_delegate)
47 : delegate_(delegate),
48 view_delegate_(view_delegate),
49 model_(NULL),
50 icon_view_(new views::ImageView),
51 speech_button_(NULL),
52 search_box_(new views::Textfield),
53 contents_view_(NULL) {
54 AddChildView(icon_view_);
56 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
58 #if !defined(OS_CHROMEOS)
59 menu_button_ = new views::MenuButton(NULL, base::string16(), this, false);
60 menu_button_->SetBorder(views::Border::NullBorder());
61 menu_button_->SetIcon(*rb.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_NORMAL));
62 menu_button_->SetHoverIcon(*rb.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_HOVER));
63 menu_button_->SetPushedIcon(*rb.GetImageSkiaNamed(
64 IDR_APP_LIST_TOOLS_PRESSED));
65 AddChildView(menu_button_);
66 #endif
68 search_box_->SetBorder(views::Border::NullBorder());
69 search_box_->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
70 search_box_->set_placeholder_text_color(kHintTextColor);
71 search_box_->set_controller(this);
72 AddChildView(search_box_);
74 view_delegate_->GetSpeechUI()->AddObserver(this);
75 ModelChanged();
78 SearchBoxView::~SearchBoxView() {
79 view_delegate_->GetSpeechUI()->RemoveObserver(this);
80 model_->search_box()->RemoveObserver(this);
83 void SearchBoxView::ModelChanged() {
84 if (model_)
85 model_->search_box()->RemoveObserver(this);
87 model_ = view_delegate_->GetModel();
88 DCHECK(model_);
89 model_->search_box()->AddObserver(this);
90 IconChanged();
91 SpeechRecognitionButtonPropChanged();
92 HintTextChanged();
95 bool SearchBoxView::HasSearch() const {
96 return !search_box_->text().empty();
99 void SearchBoxView::ClearSearch() {
100 search_box_->SetText(base::string16());
101 // Updates model and fires query changed manually because SetText() above
102 // does not generate ContentsChanged() notification.
103 UpdateModel();
104 NotifyQueryChanged();
107 void SearchBoxView::InvalidateMenu() {
108 menu_.reset();
111 gfx::Size SearchBoxView::GetPreferredSize() {
112 return gfx::Size(kPreferredWidth, kPreferredHeight);
115 void SearchBoxView::Layout() {
116 gfx::Rect rect(GetContentsBounds());
117 if (rect.IsEmpty())
118 return;
120 gfx::Rect icon_frame(rect);
121 icon_frame.set_width(kIconDimension + 2 * kPadding);
122 icon_view_->SetBoundsRect(icon_frame);
124 // Places |speech_button_| if exists. |speech_button_frame| holds its bounds
125 // to calculate the search box bounds.
126 gfx::Rect speech_button_frame;
127 if (speech_button_) {
128 speech_button_frame = icon_frame;
129 speech_button_frame.set_x(rect.right() - icon_frame.width());
130 gfx::Size button_size = speech_button_->GetPreferredSize();
131 gfx::Point button_origin = speech_button_frame.CenterPoint();
132 button_origin.Offset(-button_size.width() / 2, -button_size.height() / 2);
133 speech_button_->SetBoundsRect(gfx::Rect(button_origin, button_size));
136 gfx::Rect menu_button_frame(rect);
137 #if !defined(OS_CHROMEOS)
138 menu_button_frame.set_width(kMenuButtonDimension);
139 menu_button_frame.set_x(rect.right() - menu_button_frame.width() - kPadding);
140 menu_button_frame.ClampToCenteredSize(gfx::Size(menu_button_frame.width(),
141 kMenuButtonDimension));
142 menu_button_->SetBoundsRect(menu_button_frame);
143 #else
144 menu_button_frame.set_width(0);
145 #endif
147 gfx::Rect edit_frame(rect);
148 edit_frame.set_x(icon_frame.right());
149 int edit_frame_width =
150 rect.width() - icon_frame.width() - kPadding - menu_button_frame.width();
151 if (!speech_button_frame.IsEmpty())
152 edit_frame_width -= speech_button_frame.width() + kPadding;
153 edit_frame.set_width(edit_frame_width);
154 edit_frame.ClampToCenteredSize(
155 gfx::Size(edit_frame.width(), search_box_->GetPreferredSize().height()));
156 search_box_->SetBoundsRect(edit_frame);
159 bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent& event) {
160 if (contents_view_)
161 return contents_view_->OnMouseWheel(event);
163 return false;
166 void SearchBoxView::UpdateModel() {
167 // Temporarily remove from observer to ignore notifications caused by us.
168 model_->search_box()->RemoveObserver(this);
169 model_->search_box()->SetText(search_box_->text());
170 model_->search_box()->SetSelectionModel(search_box_->GetSelectionModel());
171 model_->search_box()->AddObserver(this);
174 void SearchBoxView::NotifyQueryChanged() {
175 DCHECK(delegate_);
176 delegate_->QueryChanged(this);
179 void SearchBoxView::ContentsChanged(views::Textfield* sender,
180 const base::string16& new_contents) {
181 UpdateModel();
182 contents_view_->CancelAutoLaunch();
183 NotifyQueryChanged();
186 bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
187 const ui::KeyEvent& key_event) {
188 bool handled = false;
189 if (contents_view_ && contents_view_->visible())
190 handled = contents_view_->OnKeyPressed(key_event);
192 return handled;
195 void SearchBoxView::ButtonPressed(views::Button* sender,
196 const ui::Event& event) {
197 DCHECK(speech_button_ && sender == speech_button_);
198 view_delegate_->ToggleSpeechRecognition();
201 void SearchBoxView::OnMenuButtonClicked(View* source, const gfx::Point& point) {
202 if (!menu_)
203 menu_.reset(new AppListMenuViews(view_delegate_));
205 const gfx::Point menu_location =
206 menu_button_->GetBoundsInScreen().bottom_right() +
207 gfx::Vector2d(kMenuXOffsetFromButton, kMenuYOffsetFromButton);
208 menu_->RunMenuAt(menu_button_, menu_location);
211 void SearchBoxView::IconChanged() {
212 icon_view_->SetImage(model_->search_box()->icon());
215 void SearchBoxView::SpeechRecognitionButtonPropChanged() {
216 const SearchBoxModel::SpeechButtonProperty* speech_button_prop =
217 model_->search_box()->speech_button();
218 if (speech_button_prop) {
219 if (!speech_button_) {
220 speech_button_ = new views::ImageButton(this);
221 AddChildView(speech_button_);
224 if (view_delegate_->GetSpeechUI()->state() ==
225 SPEECH_RECOGNITION_HOTWORD_LISTENING) {
226 speech_button_->SetImage(
227 views::Button::STATE_NORMAL, &speech_button_prop->on_icon);
228 speech_button_->SetTooltipText(speech_button_prop->on_tooltip);
229 } else {
230 speech_button_->SetImage(
231 views::Button::STATE_NORMAL, &speech_button_prop->off_icon);
232 speech_button_->SetTooltipText(speech_button_prop->off_tooltip);
234 } else {
235 if (speech_button_) {
236 // Deleting a view will detach it from its parent.
237 delete speech_button_;
238 speech_button_ = NULL;
243 void SearchBoxView::HintTextChanged() {
244 search_box_->set_placeholder_text(model_->search_box()->hint_text());
247 void SearchBoxView::SelectionModelChanged() {
248 search_box_->SelectSelectionModel(model_->search_box()->selection_model());
251 void SearchBoxView::TextChanged() {
252 search_box_->SetText(model_->search_box()->text());
253 NotifyQueryChanged();
256 void SearchBoxView::OnSpeechRecognitionStateChanged(
257 SpeechRecognitionState new_state) {
258 SpeechRecognitionButtonPropChanged();
259 SchedulePaint();
262 } // namespace app_list