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