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