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