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