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_result_view.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "ui/app_list/app_list_constants.h"
     10 #include "ui/app_list/search_result.h"
     11 #include "ui/app_list/views/progress_bar_view.h"
     12 #include "ui/app_list/views/search_result_actions_view.h"
     13 #include "ui/app_list/views/search_result_list_view.h"
     14 #include "ui/gfx/canvas.h"
     15 #include "ui/gfx/font.h"
     16 #include "ui/gfx/image/image_skia_operations.h"
     17 #include "ui/gfx/render_text.h"
     18 #include "ui/views/controls/button/image_button.h"
     19 #include "ui/views/controls/image_view.h"
     20 #include "ui/views/controls/menu/menu_runner.h"
     21 
     22 namespace app_list {
     23 
     24 namespace {
     25 
     26 const int kPreferredWidth = 300;
     27 const int kPreferredHeight = 52;
     28 const int kIconDimension = 32;
     29 const int kIconPadding = 14;
     30 const int kIconViewWidth = kIconDimension + 2 * kIconPadding;
     31 const int kTextTrailPadding = kIconPadding;
     32 const int kBorderSize = 1;
     33 
     34 // Extra margin at the right of the rightmost action icon.
     35 const int kActionButtonRightMargin = 8;
     36 
     37 // Creates a RenderText of given |text| and |styles|. Caller takes ownership
     38 // of returned RenderText.
     39 gfx::RenderText* CreateRenderText(const base::string16& text,
     40                                   const SearchResult::Tags& tags) {
     41   gfx::RenderText* render_text = gfx::RenderText::CreateInstance();
     42   render_text->SetText(text);
     43   render_text->SetColor(kResultDefaultTextColor);
     44 
     45   for (SearchResult::Tags::const_iterator it = tags.begin();
     46        it != tags.end();
     47        ++it) {
     48     // NONE means default style so do nothing.
     49     if (it->styles == SearchResult::Tag::NONE)
     50       continue;
     51 
     52     if (it->styles & SearchResult::Tag::MATCH)
     53       render_text->ApplyStyle(gfx::BOLD, true, it->range);
     54     if (it->styles & SearchResult::Tag::DIM)
     55       render_text->ApplyColor(kResultDimmedTextColor, it->range);
     56     else if (it->styles & SearchResult::Tag::URL)
     57       render_text->ApplyColor(kResultURLTextColor, it->range);
     58   }
     59 
     60   return render_text;
     61 }
     62 
     63 }  // namespace
     64 
     65 // static
     66 const char SearchResultView::kViewClassName[] = "ui/app_list/SearchResultView";
     67 
     68 SearchResultView::SearchResultView(SearchResultListView* list_view)
     69     : views::CustomButton(this),
     70       result_(NULL),
     71       list_view_(list_view),
     72       icon_(new views::ImageView),
     73       actions_view_(new SearchResultActionsView(this)),
     74       progress_bar_(new ProgressBarView) {
     75   icon_->set_interactive(false);
     76 
     77   AddChildView(icon_);
     78   AddChildView(actions_view_);
     79   AddChildView(progress_bar_);
     80   set_context_menu_controller(this);
     81 }
     82 
     83 SearchResultView::~SearchResultView() {
     84   ClearResultNoRepaint();
     85 }
     86 
     87 void SearchResultView::SetResult(SearchResult* result) {
     88   ClearResultNoRepaint();
     89 
     90   result_ = result;
     91   if (result_)
     92     result_->AddObserver(this);
     93 
     94   OnIconChanged();
     95   OnActionsChanged();
     96   UpdateTitleText();
     97   UpdateDetailsText();
     98   OnIsInstallingChanged();
     99   SchedulePaint();
    100 }
    101 
    102 void SearchResultView::ClearResultNoRepaint() {
    103   if (result_)
    104     result_->RemoveObserver(this);
    105   result_ = NULL;
    106 }
    107 
    108 void SearchResultView::ClearSelectedAction() {
    109   actions_view_->SetSelectedAction(-1);
    110 }
    111 
    112 void SearchResultView::UpdateTitleText() {
    113   if (!result_ || result_->title().empty()) {
    114     title_text_.reset();
    115     SetAccessibleName(base::string16());
    116   } else {
    117     title_text_.reset(CreateRenderText(result_->title(),
    118                                        result_->title_tags()));
    119     SetAccessibleName(result_->title());
    120   }
    121 }
    122 
    123 void SearchResultView::UpdateDetailsText() {
    124   if (!result_ || result_->details().empty()) {
    125     details_text_.reset();
    126   } else {
    127     details_text_.reset(CreateRenderText(result_->details(),
    128                                          result_->details_tags()));
    129   }
    130 }
    131 
    132 const char* SearchResultView::GetClassName() const {
    133   return kViewClassName;
    134 }
    135 
    136 gfx::Size SearchResultView::GetPreferredSize() const {
    137   return gfx::Size(kPreferredWidth, kPreferredHeight);
    138 }
    139 
    140 void SearchResultView::Layout() {
    141   gfx::Rect rect(GetContentsBounds());
    142   if (rect.IsEmpty())
    143     return;
    144 
    145   gfx::Rect icon_bounds(rect);
    146   icon_bounds.set_width(kIconViewWidth);
    147   icon_bounds.Inset(kIconPadding, (rect.height() - kIconDimension) / 2);
    148   icon_bounds.Intersect(rect);
    149   icon_->SetBoundsRect(icon_bounds);
    150 
    151   const int max_actions_width =
    152       (rect.right() - kActionButtonRightMargin - icon_bounds.right()) / 2;
    153   int actions_width = std::min(max_actions_width,
    154                                actions_view_->GetPreferredSize().width());
    155 
    156   gfx::Rect actions_bounds(rect);
    157   actions_bounds.set_x(rect.right() - kActionButtonRightMargin - actions_width);
    158   actions_bounds.set_width(actions_width);
    159   actions_view_->SetBoundsRect(actions_bounds);
    160 
    161   const int progress_width = rect.width() / 5;
    162   const int progress_height = progress_bar_->GetPreferredSize().height();
    163   const gfx::Rect progress_bounds(
    164       rect.right() - kActionButtonRightMargin - progress_width,
    165       rect.y() + (rect.height() - progress_height) / 2,
    166       progress_width,
    167       progress_height);
    168   progress_bar_->SetBoundsRect(progress_bounds);
    169 }
    170 
    171 bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
    172   // |result_| could be NULL when result list is changing.
    173   if (!result_)
    174     return false;
    175 
    176   switch (event.key_code()) {
    177     case ui::VKEY_TAB: {
    178       int new_selected = actions_view_->selected_action()
    179           + (event.IsShiftDown() ? -1 : 1);
    180       actions_view_->SetSelectedAction(new_selected);
    181       return actions_view_->IsValidActionIndex(new_selected);
    182     }
    183     case ui::VKEY_RETURN: {
    184       int selected = actions_view_->selected_action();
    185       if (actions_view_->IsValidActionIndex(selected)) {
    186         OnSearchResultActionActivated(selected, event.flags());
    187       } else {
    188         list_view_->SearchResultActivated(this, event.flags());
    189       }
    190       return true;
    191     }
    192     default:
    193       break;
    194   }
    195 
    196   return false;
    197 }
    198 
    199 void SearchResultView::ChildPreferredSizeChanged(views::View* child) {
    200   Layout();
    201 }
    202 
    203 void SearchResultView::OnPaint(gfx::Canvas* canvas) {
    204   gfx::Rect rect(GetContentsBounds());
    205   if (rect.IsEmpty())
    206     return;
    207 
    208   gfx::Rect content_rect(rect);
    209   content_rect.set_height(rect.height() - kBorderSize);
    210 
    211   const bool selected = list_view_->IsResultViewSelected(this);
    212   const bool hover = state() == STATE_HOVERED || state() == STATE_PRESSED;
    213   if (selected)
    214     canvas->FillRect(content_rect, kSelectedColor);
    215   else if (hover)
    216     canvas->FillRect(content_rect, kHighlightedColor);
    217   else
    218     canvas->FillRect(content_rect, kContentsBackgroundColor);
    219 
    220   gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
    221   canvas->FillRect(border_bottom, kResultBorderColor);
    222 
    223   gfx::Rect text_bounds(rect);
    224   text_bounds.set_x(kIconViewWidth);
    225   if (actions_view_->visible()) {
    226     text_bounds.set_width(
    227         rect.width() - kIconViewWidth - kTextTrailPadding -
    228         actions_view_->bounds().width() -
    229         (actions_view_->has_children() ? kActionButtonRightMargin : 0));
    230   } else {
    231     text_bounds.set_width(
    232         rect.width() - kIconViewWidth - kTextTrailPadding -
    233         progress_bar_->bounds().width() - kActionButtonRightMargin);
    234   }
    235   text_bounds.set_x(GetMirroredXWithWidthInView(text_bounds.x(),
    236                                                 text_bounds.width()));
    237 
    238   if (title_text_ && details_text_) {
    239     gfx::Size title_size(text_bounds.width(),
    240                          title_text_->GetStringSize().height());
    241     gfx::Size details_size(text_bounds.width(),
    242                            details_text_->GetStringSize().height());
    243     int total_height = title_size.height() + + details_size.height();
    244     int y = text_bounds.y() + (text_bounds.height() - total_height) / 2;
    245 
    246     title_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
    247                                           title_size));
    248     title_text_->Draw(canvas);
    249 
    250     y += title_size.height();
    251     details_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
    252                                             details_size));
    253     details_text_->Draw(canvas);
    254   } else if (title_text_) {
    255     gfx::Size title_size(text_bounds.width(),
    256                          title_text_->GetStringSize().height());
    257     gfx::Rect centered_title_rect(text_bounds);
    258     centered_title_rect.ClampToCenteredSize(title_size);
    259     title_text_->SetDisplayRect(centered_title_rect);
    260     title_text_->Draw(canvas);
    261   }
    262 }
    263 
    264 void SearchResultView::ButtonPressed(views::Button* sender,
    265                                      const ui::Event& event) {
    266   DCHECK(sender == this);
    267 
    268   list_view_->SearchResultActivated(this, event.flags());
    269 }
    270 
    271 void SearchResultView::OnIconChanged() {
    272   gfx::ImageSkia image(result_ ? result_->icon() : gfx::ImageSkia());
    273   // Note this might leave the view with an old icon. But it is needed to avoid
    274   // flash when a SearchResult's icon is loaded asynchronously. In this case, it
    275   // looks nicer to keep the stale icon for a little while on screen instead of
    276   // clearing it out. It should work correctly as long as the SearchResult does
    277   // not forget to SetIcon when it's ready.
    278   if (image.isNull())
    279     return;
    280 
    281   // Scales down big icons but leave small ones unchanged.
    282   if (image.width() > kIconDimension || image.height() > kIconDimension) {
    283     image = gfx::ImageSkiaOperations::CreateResizedImage(
    284         image,
    285         skia::ImageOperations::RESIZE_BEST,
    286         gfx::Size(kIconDimension, kIconDimension));
    287   } else {
    288     icon_->ResetImageSize();
    289   }
    290 
    291   // Set the image to an empty image before we reset the image because
    292   // since we're using the same backing store for our images, sometimes
    293   // ImageView won't detect that we have a new image set due to the pixel
    294   // buffer pointers remaining the same despite the image changing.
    295   icon_->SetImage(gfx::ImageSkia());
    296   icon_->SetImage(image);
    297 }
    298 
    299 void SearchResultView::OnActionsChanged() {
    300   actions_view_->SetActions(result_ ? result_->actions()
    301                                     : SearchResult::Actions());
    302 }
    303 
    304 void SearchResultView::OnIsInstallingChanged() {
    305   const bool is_installing = result_ && result_->is_installing();
    306   actions_view_->SetVisible(!is_installing);
    307   progress_bar_->SetVisible(is_installing);
    308 }
    309 
    310 void SearchResultView::OnPercentDownloadedChanged() {
    311   progress_bar_->SetValue(result_ ? result_->percent_downloaded() / 100.0 : 0);
    312 }
    313 
    314 void SearchResultView::OnItemInstalled() {
    315   list_view_->OnSearchResultInstalled(this);
    316 }
    317 
    318 void SearchResultView::OnItemUninstalled() {
    319   list_view_->OnSearchResultUninstalled(this);
    320 }
    321 
    322 void SearchResultView::OnSearchResultActionActivated(size_t index,
    323                                                      int event_flags) {
    324   // |result_| could be NULL when result list is changing.
    325   if (!result_)
    326     return;
    327 
    328   DCHECK_LT(index, result_->actions().size());
    329 
    330   list_view_->SearchResultActionActivated(this, index, event_flags);
    331 }
    332 
    333 void SearchResultView::ShowContextMenuForView(views::View* source,
    334                                               const gfx::Point& point,
    335                                               ui::MenuSourceType source_type) {
    336   // |result_| could be NULL when result list is changing.
    337   if (!result_)
    338     return;
    339 
    340   ui::MenuModel* menu_model = result_->GetContextMenuModel();
    341   if (!menu_model)
    342     return;
    343 
    344   context_menu_runner_.reset(new views::MenuRunner(menu_model));
    345   if (context_menu_runner_->RunMenuAt(GetWidget(),
    346                                       NULL,
    347                                       gfx::Rect(point, gfx::Size()),
    348                                       views::MENU_ANCHOR_TOPLEFT,
    349                                       source_type,
    350                                       views::MenuRunner::HAS_MNEMONICS) ==
    351       views::MenuRunner::MENU_DELETED)
    352     return;
    353 }
    354 
    355 }  // namespace app_list
    356