search_box_view.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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
5#include "ui/app_list/views/search_box_view.h"
6
7#include <algorithm>
8
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/search_box_view_delegate.h"
16#include "ui/base/resource/resource_bundle.h"
17#include "ui/events/event.h"
18#include "ui/views/border.h"
19#include "ui/views/controls/button/image_button.h"
20#include "ui/views/controls/button/menu_button.h"
21#include "ui/views/controls/image_view.h"
22#include "ui/views/controls/textfield/textfield.h"
23
24namespace app_list {
25
26namespace {
27
28const int kPadding = 14;
29const int kIconDimension = 32;
30const int kPreferredWidth = 360;
31const int kPreferredHeight = 48;
32#if !defined(OS_CHROMEOS)
33const int kMenuButtonDimension = 29;
34#endif
35
36const SkColor kHintTextColor = SkColorSetRGB(0xA0, 0xA0, 0xA0);
37
38// Menu offset relative to the bottom-right corner of the menu button.
39const int kMenuYOffsetFromButton = -4;
40const int kMenuXOffsetFromButton = -7;
41
42}  // namespace
43
44SearchBoxView::SearchBoxView(SearchBoxViewDelegate* delegate,
45                             AppListViewDelegate* view_delegate)
46    : delegate_(delegate),
47      view_delegate_(view_delegate),
48      model_(NULL),
49      icon_view_(new views::ImageView),
50      speech_button_(NULL),
51      search_box_(new views::Textfield),
52      contents_view_(NULL) {
53  AddChildView(icon_view_);
54
55  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
56
57#if !defined(OS_CHROMEOS)
58  menu_button_ = new views::MenuButton(NULL, base::string16(), this, false);
59  menu_button_->SetBorder(views::Border::NullBorder());
60  menu_button_->SetImage(views::Button::STATE_NORMAL,
61                         *rb.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_NORMAL));
62  menu_button_->SetImage(views::Button::STATE_HOVERED,
63                         *rb.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_HOVER));
64  menu_button_->SetImage(views::Button::STATE_PRESSED,
65                         *rb.GetImageSkiaNamed(IDR_APP_LIST_TOOLS_PRESSED));
66  AddChildView(menu_button_);
67#endif
68
69  search_box_->SetBorder(views::Border::NullBorder());
70  search_box_->SetFontList(rb.GetFontList(ui::ResourceBundle::MediumFont));
71  search_box_->set_placeholder_text_color(kHintTextColor);
72  search_box_->set_controller(this);
73  AddChildView(search_box_);
74
75  view_delegate_->GetSpeechUI()->AddObserver(this);
76  ModelChanged();
77}
78
79SearchBoxView::~SearchBoxView() {
80  view_delegate_->GetSpeechUI()->RemoveObserver(this);
81  model_->search_box()->RemoveObserver(this);
82}
83
84void SearchBoxView::ModelChanged() {
85  if (model_)
86    model_->search_box()->RemoveObserver(this);
87
88  model_ = view_delegate_->GetModel();
89  DCHECK(model_);
90  model_->search_box()->AddObserver(this);
91  IconChanged();
92  SpeechRecognitionButtonPropChanged();
93  HintTextChanged();
94}
95
96bool SearchBoxView::HasSearch() const {
97  return !search_box_->text().empty();
98}
99
100void SearchBoxView::ClearSearch() {
101  search_box_->SetText(base::string16());
102  view_delegate_->AutoLaunchCanceled();
103  // Updates model and fires query changed manually because SetText() above
104  // does not generate ContentsChanged() notification.
105  UpdateModel();
106  NotifyQueryChanged();
107}
108
109void SearchBoxView::InvalidateMenu() {
110  menu_.reset();
111}
112
113gfx::Size SearchBoxView::GetPreferredSize() const {
114  return gfx::Size(kPreferredWidth, kPreferredHeight);
115}
116
117void SearchBoxView::Layout() {
118  gfx::Rect rect(GetContentsBounds());
119  if (rect.IsEmpty())
120    return;
121
122  gfx::Rect icon_frame(rect);
123  icon_frame.set_width(kIconDimension + 2 * kPadding);
124  icon_view_->SetBoundsRect(icon_frame);
125
126  // Places |speech_button_| if exists. |speech_button_frame| holds its bounds
127  // to calculate the search box bounds.
128  gfx::Rect speech_button_frame;
129  if (speech_button_) {
130    speech_button_frame = icon_frame;
131    speech_button_frame.set_x(rect.right() - icon_frame.width());
132    gfx::Size button_size = speech_button_->GetPreferredSize();
133    gfx::Point button_origin = speech_button_frame.CenterPoint();
134    button_origin.Offset(-button_size.width() / 2, -button_size.height() / 2);
135    speech_button_->SetBoundsRect(gfx::Rect(button_origin, button_size));
136  }
137
138  gfx::Rect menu_button_frame(rect);
139#if !defined(OS_CHROMEOS)
140  menu_button_frame.set_width(kMenuButtonDimension);
141  menu_button_frame.set_x(rect.right() - menu_button_frame.width() - kPadding);
142  menu_button_frame.ClampToCenteredSize(gfx::Size(menu_button_frame.width(),
143                                                  kMenuButtonDimension));
144  menu_button_->SetBoundsRect(menu_button_frame);
145#else
146  menu_button_frame.set_width(0);
147#endif
148
149  gfx::Rect edit_frame(rect);
150  edit_frame.set_x(icon_frame.right());
151  int edit_frame_width =
152      rect.width() - icon_frame.width() - kPadding - menu_button_frame.width();
153  if (!speech_button_frame.IsEmpty())
154    edit_frame_width -= speech_button_frame.width() + kPadding;
155  edit_frame.set_width(edit_frame_width);
156  edit_frame.ClampToCenteredSize(
157      gfx::Size(edit_frame.width(), search_box_->GetPreferredSize().height()));
158  search_box_->SetBoundsRect(edit_frame);
159}
160
161bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent& event) {
162  if (contents_view_)
163    return contents_view_->OnMouseWheel(event);
164
165  return false;
166}
167
168void SearchBoxView::UpdateModel() {
169  // Temporarily remove from observer to ignore notifications caused by us.
170  model_->search_box()->RemoveObserver(this);
171  model_->search_box()->SetText(search_box_->text());
172  model_->search_box()->SetSelectionModel(search_box_->GetSelectionModel());
173  model_->search_box()->AddObserver(this);
174}
175
176void SearchBoxView::NotifyQueryChanged() {
177  DCHECK(delegate_);
178  delegate_->QueryChanged(this);
179}
180
181void SearchBoxView::ContentsChanged(views::Textfield* sender,
182                                    const base::string16& new_contents) {
183  UpdateModel();
184  view_delegate_->AutoLaunchCanceled();
185  NotifyQueryChanged();
186}
187
188bool SearchBoxView::HandleKeyEvent(views::Textfield* sender,
189                                   const ui::KeyEvent& key_event) {
190  bool handled = false;
191  if (contents_view_ && contents_view_->visible())
192    handled = contents_view_->OnKeyPressed(key_event);
193
194  return handled;
195}
196
197void SearchBoxView::ButtonPressed(views::Button* sender,
198                                  const ui::Event& event) {
199  DCHECK(speech_button_ && sender == speech_button_);
200  view_delegate_->ToggleSpeechRecognition();
201}
202
203void SearchBoxView::OnMenuButtonClicked(View* source, const gfx::Point& point) {
204  if (!menu_)
205    menu_.reset(new AppListMenuViews(view_delegate_));
206
207  const gfx::Point menu_location =
208      menu_button_->GetBoundsInScreen().bottom_right() +
209      gfx::Vector2d(kMenuXOffsetFromButton, kMenuYOffsetFromButton);
210  menu_->RunMenuAt(menu_button_, menu_location);
211}
212
213void SearchBoxView::IconChanged() {
214  icon_view_->SetImage(model_->search_box()->icon());
215}
216
217void SearchBoxView::SpeechRecognitionButtonPropChanged() {
218  const SearchBoxModel::SpeechButtonProperty* speech_button_prop =
219      model_->search_box()->speech_button();
220  if (speech_button_prop) {
221    if (!speech_button_) {
222      speech_button_ = new views::ImageButton(this);
223      AddChildView(speech_button_);
224    }
225
226    if (view_delegate_->GetSpeechUI()->state() ==
227        SPEECH_RECOGNITION_HOTWORD_LISTENING) {
228      speech_button_->SetImage(
229          views::Button::STATE_NORMAL, &speech_button_prop->on_icon);
230      speech_button_->SetTooltipText(speech_button_prop->on_tooltip);
231    } else {
232      speech_button_->SetImage(
233          views::Button::STATE_NORMAL, &speech_button_prop->off_icon);
234      speech_button_->SetTooltipText(speech_button_prop->off_tooltip);
235    }
236  } else {
237    if (speech_button_) {
238      // Deleting a view will detach it from its parent.
239      delete speech_button_;
240      speech_button_ = NULL;
241    }
242  }
243}
244
245void SearchBoxView::HintTextChanged() {
246  search_box_->set_placeholder_text(model_->search_box()->hint_text());
247}
248
249void SearchBoxView::SelectionModelChanged() {
250  search_box_->SelectSelectionModel(model_->search_box()->selection_model());
251}
252
253void SearchBoxView::TextChanged() {
254  search_box_->SetText(model_->search_box()->text());
255  NotifyQueryChanged();
256}
257
258void SearchBoxView::OnSpeechRecognitionStateChanged(
259    SpeechRecognitionState new_state) {
260  SpeechRecognitionButtonPropChanged();
261  SchedulePaint();
262}
263
264}  // namespace app_list
265