Home | History | Annotate | Download | only in views
      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