Update path of checkdeps to buildtools checkout
[chromium-blink-merge.git] / ui / app_list / views / search_result_view.cc
blobf5f11e6842c46132aa33d861f97f5ff14869954f
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 kIconDimension = 32;
29 const int kIconPadding = 14;
30 const int kIconViewWidth = kIconDimension + 2 * kIconPadding;
31 const int kTextTrailPadding = kIconPadding;
32 const int kBorderSize = 1;
34 // Extra margin at the right of the rightmost action icon.
35 const int kActionButtonRightMargin = 8;
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);
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;
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);
60 return render_text;
63 } // namespace
65 // static
66 const char SearchResultView::kViewClassName[] = "ui/app_list/SearchResultView";
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);
77 AddChildView(icon_);
78 AddChildView(actions_view_);
79 AddChildView(progress_bar_);
80 set_context_menu_controller(this);
83 SearchResultView::~SearchResultView() {
84 ClearResultNoRepaint();
87 void SearchResultView::SetResult(SearchResult* result) {
88 ClearResultNoRepaint();
90 result_ = result;
91 if (result_)
92 result_->AddObserver(this);
94 OnIconChanged();
95 OnActionsChanged();
96 UpdateTitleText();
97 UpdateDetailsText();
98 OnIsInstallingChanged();
99 SchedulePaint();
102 void SearchResultView::ClearResultNoRepaint() {
103 if (result_)
104 result_->RemoveObserver(this);
105 result_ = NULL;
108 void SearchResultView::ClearSelectedAction() {
109 actions_view_->SetSelectedAction(-1);
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());
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()));
132 const char* SearchResultView::GetClassName() const {
133 return kViewClassName;
136 gfx::Size SearchResultView::GetPreferredSize() const {
137 return gfx::Size(kPreferredWidth, kPreferredHeight);
140 void SearchResultView::Layout() {
141 gfx::Rect rect(GetContentsBounds());
142 if (rect.IsEmpty())
143 return;
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);
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());
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);
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);
171 bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
172 // |result_| could be NULL when result list is changing.
173 if (!result_)
174 return false;
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);
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());
190 return true;
192 default:
193 break;
196 return false;
199 void SearchResultView::ChildPreferredSizeChanged(views::View* child) {
200 Layout();
203 void SearchResultView::OnPaint(gfx::Canvas* canvas) {
204 gfx::Rect rect(GetContentsBounds());
205 if (rect.IsEmpty())
206 return;
208 gfx::Rect content_rect(rect);
209 content_rect.set_height(rect.height() - kBorderSize);
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);
220 gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
221 canvas->FillRect(border_bottom, kResultBorderColor);
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);
235 text_bounds.set_x(GetMirroredXWithWidthInView(text_bounds.x(),
236 text_bounds.width()));
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;
246 title_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
247 title_size));
248 title_text_->Draw(canvas);
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);
264 void SearchResultView::ButtonPressed(views::Button* sender,
265 const ui::Event& event) {
266 DCHECK(sender == this);
268 list_view_->SearchResultActivated(this, event.flags());
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;
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();
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);
299 void SearchResultView::OnActionsChanged() {
300 actions_view_->SetActions(result_ ? result_->actions()
301 : SearchResult::Actions());
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);
310 void SearchResultView::OnPercentDownloadedChanged() {
311 progress_bar_->SetValue(result_ ? result_->percent_downloaded() / 100.0 : 0);
314 void SearchResultView::OnItemInstalled() {
315 list_view_->OnSearchResultInstalled(this);
318 void SearchResultView::OnItemUninstalled() {
319 list_view_->OnSearchResultUninstalled(this);
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;
328 DCHECK_LT(index, result_->actions().size());
330 list_view_->SearchResultActionActivated(this, index, event_flags);
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;
340 ui::MenuModel* menu_model = result_->GetContextMenuModel();
341 if (!menu_model)
342 return;
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;
355 } // namespace app_list