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/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