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 27namespace app_list { 28 29namespace { 30 31const int kPadding = 14; 32const int kPreferredWidth = 360; 33const int kPreferredHeight = 48; 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 41const int kBackgroundBorderWidth = 1; 42const int kBackgroundBorderBottomWidth = 1; 43const int kBackgroundBorderCornerRadius = 2; 44const SkColor kBackgroundBorderColor = SkColorSetRGB(0xEE, 0xEE, 0xEE); 45const SkColor kBackgroundBorderBottomColor = SkColorSetRGB(0xCC, 0xCC, 0xCC); 46 47// A background that paints a solid white rounded rect with a thin grey border. 48class 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 78SearchBoxView::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 129SearchBoxView::~SearchBoxView() { 130 view_delegate_->GetSpeechUI()->RemoveObserver(this); 131 model_->search_box()->RemoveObserver(this); 132} 133 134void 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 146bool SearchBoxView::HasSearch() const { 147 return !search_box_->text().empty(); 148} 149 150void 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 159void SearchBoxView::InvalidateMenu() { 160 menu_.reset(); 161} 162 163gfx::Size SearchBoxView::GetPreferredSize() const { 164 return gfx::Size(kPreferredWidth, kPreferredHeight); 165} 166 167bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent& event) { 168 if (contents_view_) 169 return contents_view_->OnMouseWheel(event); 170 171 return false; 172} 173 174void 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 182void SearchBoxView::NotifyQueryChanged() { 183 DCHECK(delegate_); 184 delegate_->QueryChanged(this); 185} 186 187void SearchBoxView::ContentsChanged(views::Textfield* sender, 188 const base::string16& new_contents) { 189 UpdateModel(); 190 view_delegate_->AutoLaunchCanceled(); 191 NotifyQueryChanged(); 192} 193 194bool 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 203void SearchBoxView::ButtonPressed(views::Button* sender, 204 const ui::Event& event) { 205 DCHECK(speech_button_ && sender == speech_button_); 206 view_delegate_->ToggleSpeechRecognition(); 207} 208 209void 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 219void SearchBoxView::IconChanged() { 220 if (icon_view_) 221 icon_view_->SetImage(model_->search_box()->icon()); 222} 223 224void 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 252void SearchBoxView::HintTextChanged() { 253 search_box_->set_placeholder_text(model_->search_box()->hint_text()); 254} 255 256void SearchBoxView::SelectionModelChanged() { 257 search_box_->SelectSelectionModel(model_->search_box()->selection_model()); 258} 259 260void SearchBoxView::TextChanged() { 261 search_box_->SetText(model_->search_box()->text()); 262 NotifyQueryChanged(); 263} 264 265void SearchBoxView::OnSpeechRecognitionStateChanged( 266 SpeechRecognitionState new_state) { 267 SpeechRecognitionButtonPropChanged(); 268 SchedulePaint(); 269} 270 271} // namespace app_list 272