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