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