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"
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"
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;
35 // Extra margin at the right of the rightmost action icon.
36 const int kActionButtonRightMargin
= 8;
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
);
46 for (SearchResult::Tags::const_iterator it
= tags
.begin();
49 // NONE means default style so do nothing.
50 if (it
->styles
== SearchResult::Tag::NONE
)
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
);
67 const char SearchResultView::kViewClassName
[] = "ui/app_list/SearchResultView";
69 SearchResultView::SearchResultView(SearchResultListView
* list_view
,
70 SearchResultViewDelegate
* delegate
)
71 : views::CustomButton(this),
73 list_view_(list_view
),
75 icon_(new views::ImageView
),
76 actions_view_(new SearchResultActionsView(this)),
77 progress_bar_(new ProgressBarView
) {
78 icon_
->set_interactive(false);
81 AddChildView(actions_view_
);
82 AddChildView(progress_bar_
);
83 set_context_menu_controller(this);
86 SearchResultView::~SearchResultView() {
87 ClearResultNoRepaint();
90 void SearchResultView::SetResult(SearchResult
* result
) {
91 ClearResultNoRepaint();
95 result_
->AddObserver(this);
101 OnIsInstallingChanged();
105 void SearchResultView::ClearResultNoRepaint() {
107 result_
->RemoveObserver(this);
111 void SearchResultView::ClearSelectedAction() {
112 actions_view_
->SetSelectedAction(-1);
115 void SearchResultView::UpdateTitleText() {
116 if (!result_
|| result_
->title().empty()) {
118 SetAccessibleName(base::string16());
120 title_text_
.reset(CreateRenderText(result_
->title(),
121 result_
->title_tags()));
122 SetAccessibleName(result_
->title());
126 void SearchResultView::UpdateDetailsText() {
127 if (!result_
|| result_
->details().empty()) {
128 details_text_
.reset();
130 details_text_
.reset(CreateRenderText(result_
->details(),
131 result_
->details_tags()));
135 const char* SearchResultView::GetClassName() const {
136 return kViewClassName
;
139 gfx::Size
SearchResultView::GetPreferredSize() {
140 return gfx::Size(kPreferredWidth
, kPreferredHeight
);
143 void SearchResultView::Layout() {
144 gfx::Rect
rect(GetContentsBounds());
148 gfx::Rect
icon_bounds(rect
);
149 icon_bounds
.set_width(kIconViewWidth
);
150 icon_bounds
.Inset(kIconPadding
, (rect
.height() - kIconDimension
) / 2);
151 icon_bounds
.Intersect(rect
);
152 icon_
->SetBoundsRect(icon_bounds
);
154 const int max_actions_width
=
155 (rect
.right() - kActionButtonRightMargin
- icon_bounds
.right()) / 2;
156 int actions_width
= std::min(max_actions_width
,
157 actions_view_
->GetPreferredSize().width());
159 gfx::Rect
actions_bounds(rect
);
160 actions_bounds
.set_x(rect
.right() - kActionButtonRightMargin
- actions_width
);
161 actions_bounds
.set_width(actions_width
);
162 actions_view_
->SetBoundsRect(actions_bounds
);
164 const int progress_width
= rect
.width() / 5;
165 const int progress_height
= progress_bar_
->GetPreferredSize().height();
166 const gfx::Rect
progress_bounds(
167 rect
.right() - kActionButtonRightMargin
- progress_width
,
168 rect
.y() + (rect
.height() - progress_height
) / 2,
171 progress_bar_
->SetBoundsRect(progress_bounds
);
174 bool SearchResultView::OnKeyPressed(const ui::KeyEvent
& event
) {
175 // |result_| could be NULL when result list is changing.
179 switch (event
.key_code()) {
181 int new_selected
= actions_view_
->selected_action()
182 + (event
.IsShiftDown() ? -1 : 1);
183 actions_view_
->SetSelectedAction(new_selected
);
184 return actions_view_
->IsValidActionIndex(new_selected
);
186 case ui::VKEY_RETURN
: {
187 int selected
= actions_view_
->selected_action();
188 if (actions_view_
->IsValidActionIndex(selected
)) {
189 OnSearchResultActionActivated(selected
, event
.flags());
191 delegate_
->SearchResultActivated(this, event
.flags());
202 void SearchResultView::ChildPreferredSizeChanged(views::View
* child
) {
206 void SearchResultView::OnPaint(gfx::Canvas
* canvas
) {
207 gfx::Rect
rect(GetContentsBounds());
211 gfx::Rect
content_rect(rect
);
212 content_rect
.set_height(rect
.height() - kBorderSize
);
214 const bool selected
= list_view_
->IsResultViewSelected(this);
215 const bool hover
= state() == STATE_HOVERED
|| state() == STATE_PRESSED
;
217 canvas
->FillRect(content_rect
, kSelectedColor
);
219 canvas
->FillRect(content_rect
, kHighlightedColor
);
221 canvas
->FillRect(content_rect
, kContentsBackgroundColor
);
223 gfx::Rect border_bottom
= gfx::SubtractRects(rect
, content_rect
);
224 canvas
->FillRect(border_bottom
, kResultBorderColor
);
226 gfx::Rect
text_bounds(rect
);
227 text_bounds
.set_x(kIconViewWidth
);
228 if (actions_view_
->visible()) {
229 text_bounds
.set_width(
230 rect
.width() - kIconViewWidth
- kTextTrailPadding
-
231 actions_view_
->bounds().width() -
232 (actions_view_
->has_children() ? kActionButtonRightMargin
: 0));
234 text_bounds
.set_width(
235 rect
.width() - kIconViewWidth
- kTextTrailPadding
-
236 progress_bar_
->bounds().width() - kActionButtonRightMargin
);
238 text_bounds
.set_x(GetMirroredXWithWidthInView(text_bounds
.x(),
239 text_bounds
.width()));
241 if (title_text_
&& details_text_
) {
242 gfx::Size
title_size(text_bounds
.width(),
243 title_text_
->GetStringSize().height());
244 gfx::Size
details_size(text_bounds
.width(),
245 details_text_
->GetStringSize().height());
246 int total_height
= title_size
.height() + + details_size
.height();
247 int y
= text_bounds
.y() + (text_bounds
.height() - total_height
) / 2;
249 title_text_
->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds
.x(), y
),
251 title_text_
->Draw(canvas
);
253 y
+= title_size
.height();
254 details_text_
->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds
.x(), y
),
256 details_text_
->Draw(canvas
);
257 } else if (title_text_
) {
258 gfx::Size
title_size(text_bounds
.width(),
259 title_text_
->GetStringSize().height());
260 gfx::Rect
centered_title_rect(text_bounds
);
261 centered_title_rect
.ClampToCenteredSize(title_size
);
262 title_text_
->SetDisplayRect(centered_title_rect
);
263 title_text_
->Draw(canvas
);
267 void SearchResultView::ButtonPressed(views::Button
* sender
,
268 const ui::Event
& event
) {
269 DCHECK(sender
== this);
271 delegate_
->SearchResultActivated(this, event
.flags());
274 void SearchResultView::OnIconChanged() {
275 gfx::ImageSkia
image(result_
? result_
->icon() : gfx::ImageSkia());
276 // Note this might leave the view with an old icon. But it is needed to avoid
277 // flash when a SearchResult's icon is loaded asynchronously. In this case, it
278 // looks nicer to keep the stale icon for a little while on screen instead of
279 // clearing it out. It should work correctly as long as the SearchResult does
280 // not forget to SetIcon when it's ready.
284 // Scales down big icons but leave small ones unchanged.
285 if (image
.width() > kIconDimension
|| image
.height() > kIconDimension
) {
286 image
= gfx::ImageSkiaOperations::CreateResizedImage(
288 skia::ImageOperations::RESIZE_BEST
,
289 gfx::Size(kIconDimension
, kIconDimension
));
291 icon_
->ResetImageSize();
294 // Set the image to an empty image before we reset the image because
295 // since we're using the same backing store for our images, sometimes
296 // ImageView won't detect that we have a new image set due to the pixel
297 // buffer pointers remaining the same despite the image changing.
298 icon_
->SetImage(gfx::ImageSkia());
299 icon_
->SetImage(image
);
302 void SearchResultView::OnActionsChanged() {
303 actions_view_
->SetActions(result_
? result_
->actions()
304 : SearchResult::Actions());
307 void SearchResultView::OnIsInstallingChanged() {
308 const bool is_installing
= result_
&& result_
->is_installing();
309 actions_view_
->SetVisible(!is_installing
);
310 progress_bar_
->SetVisible(is_installing
);
313 void SearchResultView::OnPercentDownloadedChanged() {
314 progress_bar_
->SetValue(result_
? result_
->percent_downloaded() / 100.0 : 0);
317 void SearchResultView::OnItemInstalled() {
318 delegate_
->OnSearchResultInstalled(this);
321 void SearchResultView::OnItemUninstalled() {
322 delegate_
->OnSearchResultUninstalled(this);
325 void SearchResultView::OnSearchResultActionActivated(size_t index
,
327 // |result_| could be NULL when result list is changing.
331 DCHECK_LT(index
, result_
->actions().size());
333 delegate_
->SearchResultActionActivated(this, index
, event_flags
);
336 void SearchResultView::ShowContextMenuForView(views::View
* source
,
337 const gfx::Point
& point
,
338 ui::MenuSourceType source_type
) {
339 // |result_| could be NULL when result list is changing.
343 ui::MenuModel
* menu_model
= result_
->GetContextMenuModel();
347 context_menu_runner_
.reset(new views::MenuRunner(menu_model
));
348 if (context_menu_runner_
->RunMenuAt(
349 GetWidget(), NULL
, gfx::Rect(point
, gfx::Size()),
350 views::MenuItemView::TOPLEFT
, source_type
,
351 views::MenuRunner::HAS_MNEMONICS
) ==
352 views::MenuRunner::MENU_DELETED
)
356 } // namespace app_list