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