- add sources.
[platform/framework/web/crosswalk.git] / src / ui / app_list / views / search_result_view.cc
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 app_list {
24
25 namespace {
26
27 const int kPreferredWidth = 300;
28 const int kPreferredHeight = 52;
29 const int kIconDimension = 32;
30 const int kIconPadding = 14;
31 const int kIconViewWidth = kIconDimension + 2 * kIconPadding;
32 const int kTextTrailPadding = kIconPadding;
33 const int kBorderSize = 1;
34
35 // Extra margin at the right of the rightmost action icon.
36 const int kActionButtonRightMargin = 8;
37
38 // Creates a RenderText of given |text| and |styles|. Caller takes ownership
39 // of returned RenderText.
40 gfx::RenderText* CreateRenderText(const base::string16& text,
41                                   const SearchResult::Tags& tags) {
42   gfx::RenderText* render_text = gfx::RenderText::CreateInstance();
43   render_text->SetText(text);
44   render_text->SetColor(kResultDefaultTextColor);
45
46   for (SearchResult::Tags::const_iterator it = tags.begin();
47        it != tags.end();
48        ++it) {
49     // NONE means default style so do nothing.
50     if (it->styles == SearchResult::Tag::NONE)
51       continue;
52
53     if (it->styles & SearchResult::Tag::MATCH)
54       render_text->ApplyStyle(gfx::BOLD, true, it->range);
55     if (it->styles & SearchResult::Tag::DIM)
56       render_text->ApplyColor(kResultDimmedTextColor, it->range);
57     else if (it->styles & SearchResult::Tag::URL)
58       render_text->ApplyColor(kResultURLTextColor, it->range);
59   }
60
61   return render_text;
62 }
63
64 }  // namespace
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