Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / ui / app_list / views / search_result_view.cc
blob3bfd6569e9e6ab9186856182910d4f465f1bd99a
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 "base/strings/utf_string_conversions.h"
10 #include "ui/app_list/app_list_constants.h"
11 #include "ui/app_list/app_list_switches.h"
12 #include "ui/app_list/search_result.h"
13 #include "ui/app_list/views/progress_bar_view.h"
14 #include "ui/app_list/views/search_result_actions_view.h"
15 #include "ui/app_list/views/search_result_list_view.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/font.h"
18 #include "ui/gfx/image/image_skia_operations.h"
19 #include "ui/gfx/render_text.h"
20 #include "ui/views/controls/button/image_button.h"
21 #include "ui/views/controls/image_view.h"
22 #include "ui/views/controls/menu/menu_runner.h"
24 namespace app_list {
26 namespace {
28 const int kPreferredWidth = 300;
29 const int kPreferredHeight = 56;
30 const int kIconLeftPadding = 16;
31 const int kIconRightPadding = 24;
32 const int kTextTrailPadding = 16;
33 const int kSeparatorPadding = 62;
34 const int kBorderSize = 1;
35 const SkColor kSeparatorColor = SkColorSetRGB(0xE1, 0xE1, 0xE1);
37 // Extra margin at the right of the rightmost action icon.
38 const int kActionButtonRightMargin = 8;
40 int GetIconViewWidth() {
41 return kListIconSize + kIconLeftPadding + kIconRightPadding;
44 // Creates a RenderText of given |text| and |styles|. Caller takes ownership
45 // of returned RenderText.
46 gfx::RenderText* CreateRenderText(const base::string16& text,
47 const SearchResult::Tags& tags) {
48 gfx::RenderText* render_text = gfx::RenderText::CreateInstance();
49 render_text->SetText(text);
50 render_text->SetColor(kResultDefaultTextColor);
52 for (SearchResult::Tags::const_iterator it = tags.begin();
53 it != tags.end();
54 ++it) {
55 // NONE means default style so do nothing.
56 if (it->styles == SearchResult::Tag::NONE)
57 continue;
59 if (it->styles & SearchResult::Tag::MATCH)
60 render_text->ApplyStyle(gfx::BOLD, true, it->range);
61 if (it->styles & SearchResult::Tag::DIM)
62 render_text->ApplyColor(kResultDimmedTextColor, it->range);
63 else if (it->styles & SearchResult::Tag::URL)
64 render_text->ApplyColor(kResultURLTextColor, it->range);
67 return render_text;
70 } // namespace
72 // static
73 const char SearchResultView::kViewClassName[] = "ui/app_list/SearchResultView";
75 SearchResultView::SearchResultView(SearchResultListView* list_view)
76 : views::CustomButton(this),
77 result_(NULL),
78 is_last_result_(false),
79 list_view_(list_view),
80 icon_(new views::ImageView),
81 actions_view_(new SearchResultActionsView(this)),
82 progress_bar_(new ProgressBarView) {
83 icon_->set_interactive(false);
85 AddChildView(icon_);
86 AddChildView(actions_view_);
87 AddChildView(progress_bar_);
88 set_context_menu_controller(this);
91 SearchResultView::~SearchResultView() {
92 ClearResultNoRepaint();
95 void SearchResultView::SetResult(SearchResult* result) {
96 ClearResultNoRepaint();
98 result_ = result;
99 if (result_)
100 result_->AddObserver(this);
102 OnIconChanged();
103 OnActionsChanged();
104 UpdateTitleText();
105 UpdateDetailsText();
106 OnIsInstallingChanged();
107 OnPercentDownloadedChanged();
108 SchedulePaint();
111 void SearchResultView::ClearResultNoRepaint() {
112 if (result_)
113 result_->RemoveObserver(this);
114 result_ = NULL;
117 void SearchResultView::ClearSelectedAction() {
118 actions_view_->SetSelectedAction(-1);
121 void SearchResultView::UpdateTitleText() {
122 if (!result_ || result_->title().empty()) {
123 title_text_.reset();
124 } else {
125 title_text_.reset(CreateRenderText(result_->title(),
126 result_->title_tags()));
129 UpdateAccessibleName();
132 void SearchResultView::UpdateDetailsText() {
133 if (!result_ || result_->details().empty()) {
134 details_text_.reset();
135 } else {
136 details_text_.reset(CreateRenderText(result_->details(),
137 result_->details_tags()));
140 UpdateAccessibleName();
143 base::string16 SearchResultView::ComputeAccessibleName() const {
144 if (!result_)
145 return base::string16();
147 base::string16 accessible_name = result_->title();
148 if (!result_->title().empty() && !result_->details().empty())
149 accessible_name += base::ASCIIToUTF16(", ");
150 accessible_name += result_->details();
152 return accessible_name;
155 void SearchResultView::UpdateAccessibleName() {
156 SetAccessibleName(ComputeAccessibleName());
159 const char* SearchResultView::GetClassName() const {
160 return kViewClassName;
163 gfx::Size SearchResultView::GetPreferredSize() const {
164 return gfx::Size(kPreferredWidth, kPreferredHeight);
167 void SearchResultView::Layout() {
168 gfx::Rect rect(GetContentsBounds());
169 if (rect.IsEmpty())
170 return;
172 gfx::Rect icon_bounds(rect);
173 icon_bounds.set_width(GetIconViewWidth());
174 const int top_bottom_padding = (rect.height() - kListIconSize) / 2;
175 icon_bounds.Inset(kIconLeftPadding, top_bottom_padding, kIconRightPadding,
176 top_bottom_padding);
177 icon_bounds.Intersect(rect);
178 icon_->SetBoundsRect(icon_bounds);
180 const int max_actions_width =
181 (rect.right() - kActionButtonRightMargin - icon_bounds.right()) / 2;
182 int actions_width = std::min(max_actions_width,
183 actions_view_->GetPreferredSize().width());
185 gfx::Rect actions_bounds(rect);
186 actions_bounds.set_x(rect.right() - kActionButtonRightMargin - actions_width);
187 actions_bounds.set_width(actions_width);
188 actions_view_->SetBoundsRect(actions_bounds);
190 const int progress_width = rect.width() / 5;
191 const int progress_height = progress_bar_->GetPreferredSize().height();
192 const gfx::Rect progress_bounds(
193 rect.right() - kActionButtonRightMargin - progress_width,
194 rect.y() + (rect.height() - progress_height) / 2,
195 progress_width,
196 progress_height);
197 progress_bar_->SetBoundsRect(progress_bounds);
200 bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
201 // |result_| could be NULL when result list is changing.
202 if (!result_)
203 return false;
205 switch (event.key_code()) {
206 case ui::VKEY_TAB: {
207 int new_selected = actions_view_->selected_action()
208 + (event.IsShiftDown() ? -1 : 1);
209 actions_view_->SetSelectedAction(new_selected);
210 return actions_view_->IsValidActionIndex(new_selected);
212 case ui::VKEY_RETURN: {
213 int selected = actions_view_->selected_action();
214 if (actions_view_->IsValidActionIndex(selected)) {
215 OnSearchResultActionActivated(selected, event.flags());
216 } else {
217 list_view_->SearchResultActivated(this, event.flags());
219 return true;
221 default:
222 break;
225 return false;
228 void SearchResultView::ChildPreferredSizeChanged(views::View* child) {
229 Layout();
232 void SearchResultView::OnPaint(gfx::Canvas* canvas) {
233 gfx::Rect rect(GetContentsBounds());
234 if (rect.IsEmpty())
235 return;
237 gfx::Rect content_rect(rect);
238 if (!switches::IsExperimentalAppListEnabled())
239 content_rect.set_height(rect.height() - kBorderSize);
241 const bool selected = list_view_->IsResultViewSelected(this);
242 const bool hover = state() == STATE_HOVERED || state() == STATE_PRESSED;
244 canvas->FillRect(content_rect, switches::IsExperimentalAppListEnabled()
245 ? kCardBackgroundColor
246 : kContentsBackgroundColor);
248 // Possibly call FillRect a second time (these colours are partially
249 // transparent, so the previous FillRect is not redundant).
250 if (selected)
251 canvas->FillRect(content_rect, kSelectedColor);
252 else if (hover)
253 canvas->FillRect(content_rect, kHighlightedColor);
255 if (switches::IsExperimentalAppListEnabled() && !is_last_result_) {
256 gfx::Rect line_rect = content_rect;
257 line_rect.set_height(kBorderSize);
258 line_rect.set_y(content_rect.bottom() - kBorderSize);
259 line_rect.set_x(kSeparatorPadding);
260 canvas->FillRect(line_rect, kSeparatorColor);
263 gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
264 canvas->FillRect(border_bottom, kResultBorderColor);
266 gfx::Rect text_bounds(rect);
267 text_bounds.set_x(GetIconViewWidth());
268 if (actions_view_->visible()) {
269 text_bounds.set_width(
270 rect.width() - GetIconViewWidth() - kTextTrailPadding -
271 actions_view_->bounds().width() -
272 (actions_view_->has_children() ? kActionButtonRightMargin : 0));
273 } else {
274 text_bounds.set_width(rect.width() - GetIconViewWidth() -
275 kTextTrailPadding - progress_bar_->bounds().width() -
276 kActionButtonRightMargin);
278 text_bounds.set_x(GetMirroredXWithWidthInView(text_bounds.x(),
279 text_bounds.width()));
281 if (title_text_ && details_text_) {
282 gfx::Size title_size(text_bounds.width(),
283 title_text_->GetStringSize().height());
284 gfx::Size details_size(text_bounds.width(),
285 details_text_->GetStringSize().height());
286 int total_height = title_size.height() + + details_size.height();
287 int y = text_bounds.y() + (text_bounds.height() - total_height) / 2;
289 title_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
290 title_size));
291 title_text_->Draw(canvas);
293 y += title_size.height();
294 details_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
295 details_size));
296 details_text_->Draw(canvas);
297 } else if (title_text_) {
298 gfx::Size title_size(text_bounds.width(),
299 title_text_->GetStringSize().height());
300 gfx::Rect centered_title_rect(text_bounds);
301 centered_title_rect.ClampToCenteredSize(title_size);
302 title_text_->SetDisplayRect(centered_title_rect);
303 title_text_->Draw(canvas);
307 void SearchResultView::ButtonPressed(views::Button* sender,
308 const ui::Event& event) {
309 DCHECK(sender == this);
311 list_view_->SearchResultActivated(this, event.flags());
314 void SearchResultView::OnIconChanged() {
315 gfx::ImageSkia image(result_ ? result_->icon() : gfx::ImageSkia());
316 // Note this might leave the view with an old icon. But it is needed to avoid
317 // flash when a SearchResult's icon is loaded asynchronously. In this case, it
318 // looks nicer to keep the stale icon for a little while on screen instead of
319 // clearing it out. It should work correctly as long as the SearchResult does
320 // not forget to SetIcon when it's ready.
321 if (image.isNull())
322 return;
324 // Scales down big icons but leave small ones unchanged.
325 if (image.width() > kListIconSize || image.height() > kListIconSize) {
326 image = gfx::ImageSkiaOperations::CreateResizedImage(
327 image,
328 skia::ImageOperations::RESIZE_BEST,
329 gfx::Size(kListIconSize, kListIconSize));
330 } else {
331 icon_->ResetImageSize();
334 // Set the image to an empty image before we reset the image because
335 // since we're using the same backing store for our images, sometimes
336 // ImageView won't detect that we have a new image set due to the pixel
337 // buffer pointers remaining the same despite the image changing.
338 icon_->SetImage(gfx::ImageSkia());
339 icon_->SetImage(image);
342 void SearchResultView::OnActionsChanged() {
343 actions_view_->SetActions(result_ ? result_->actions()
344 : SearchResult::Actions());
347 void SearchResultView::OnIsInstallingChanged() {
348 const bool is_installing = result_ && result_->is_installing();
349 actions_view_->SetVisible(!is_installing);
350 progress_bar_->SetVisible(is_installing);
353 void SearchResultView::OnPercentDownloadedChanged() {
354 progress_bar_->SetValue(result_ ? result_->percent_downloaded() / 100.0 : 0);
357 void SearchResultView::OnItemInstalled() {
358 list_view_->OnSearchResultInstalled(this);
361 void SearchResultView::OnSearchResultActionActivated(size_t index,
362 int event_flags) {
363 // |result_| could be NULL when result list is changing.
364 if (!result_)
365 return;
367 DCHECK_LT(index, result_->actions().size());
369 list_view_->SearchResultActionActivated(this, index, event_flags);
372 void SearchResultView::ShowContextMenuForView(views::View* source,
373 const gfx::Point& point,
374 ui::MenuSourceType source_type) {
375 // |result_| could be NULL when result list is changing.
376 if (!result_)
377 return;
379 ui::MenuModel* menu_model = result_->GetContextMenuModel();
380 if (!menu_model)
381 return;
383 context_menu_runner_.reset(
384 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
385 if (context_menu_runner_->RunMenuAt(GetWidget(),
386 NULL,
387 gfx::Rect(point, gfx::Size()),
388 views::MENU_ANCHOR_TOPLEFT,
389 source_type) ==
390 views::MenuRunner::MENU_DELETED)
391 return;
394 } // namespace app_list