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 23 namespace app_list { 24 25 namespace { 26 27 const int kPadding = 14; 28 const int kIconDimension = 32; 29 const int kPreferredWidth = 360; 30 const int kPreferredHeight = 48; 31 #if !defined(OS_CHROMEOS) 32 const int kMenuButtonDimension = 29; 33 #endif 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 } // namespace 42 43 SearchBoxView::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 75 SearchBoxView::~SearchBoxView() { 76 model_->search_box()->RemoveObserver(this); 77 } 78 79 void 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 91 bool SearchBoxView::HasSearch() const { 92 return !search_box_->text().empty(); 93 } 94 95 void 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 103 void SearchBoxView::InvalidateMenu() { 104 menu_.reset(); 105 } 106 107 gfx::Size SearchBoxView::GetPreferredSize() { 108 return gfx::Size(kPreferredWidth, kPreferredHeight); 109 } 110 111 void 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 155 bool SearchBoxView::OnMouseWheel(const ui::MouseWheelEvent& event) { 156 if (contents_view_) 157 return contents_view_->OnMouseWheel(event); 158 159 return false; 160 } 161 162 void 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 170 void SearchBoxView::NotifyQueryChanged() { 171 DCHECK(delegate_); 172 delegate_->QueryChanged(this); 173 } 174 175 void SearchBoxView::ContentsChanged(views::Textfield* sender, 176 const base::string16& new_contents) { 177 UpdateModel(); 178 NotifyQueryChanged(); 179 } 180 181 bool 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 190 void SearchBoxView::ButtonPressed(views::Button* sender, 191 const ui::Event& event) { 192 DCHECK(!speech_button_ && sender == speech_button_); 193 view_delegate_->ToggleSpeechRecognition(); 194 } 195 196 void 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 206 void SearchBoxView::IconChanged() { 207 icon_view_->SetImage(model_->search_box()->icon()); 208 } 209 210 void 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 230 void SearchBoxView::HintTextChanged() { 231 search_box_->set_placeholder_text(model_->search_box()->hint_text()); 232 } 233 234 void SearchBoxView::SelectionModelChanged() { 235 search_box_->SelectSelectionModel(model_->search_box()->selection_model()); 236 } 237 238 void SearchBoxView::TextChanged() { 239 search_box_->SetText(model_->search_box()->text()); 240 NotifyQueryChanged(); 241 } 242 243 } // namespace app_list 244