Workaround for xkbcommon dead keys.
[chromium-blink-merge.git] / ui / app_list / views / search_result_view.cc
blob1a73c14f557c1e656385ca7085d95d215d93c5bc
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/app_list_switches.h"
11 #include "ui/app_list/search_result.h"
12 #include "ui/app_list/views/progress_bar_view.h"
13 #include "ui/app_list/views/search_result_actions_view.h"
14 #include "ui/app_list/views/search_result_list_view.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/font.h"
17 #include "ui/gfx/image/image_skia_operations.h"
18 #include "ui/gfx/render_text.h"
19 #include "ui/views/controls/button/image_button.h"
20 #include "ui/views/controls/image_view.h"
21 #include "ui/views/controls/menu/menu_runner.h"
23 namespace app_list {
25 namespace {
27 const int kPreferredWidth = 300;
28 const int kPreferredHeight = 52;
29 const int kIconPadding = 14;
30 const int kTextTrailPadding = kIconPadding;
31 const int kBorderSize = 1;
33 // Extra margin at the right of the rightmost action icon.
34 const int kActionButtonRightMargin = 8;
36 int GetIconViewWidth() {
37 return kListIconSize + 2 * kIconPadding;
40 // Creates a RenderText of given |text| and |styles|. Caller takes ownership
41 // of returned RenderText.
42 gfx::RenderText* CreateRenderText(const base::string16& text,
43 const SearchResult::Tags& tags) {
44 gfx::RenderText* render_text = gfx::RenderText::CreateInstance();
45 render_text->SetText(text);
46 render_text->SetColor(kResultDefaultTextColor);
48 for (SearchResult::Tags::const_iterator it = tags.begin();
49 it != tags.end();
50 ++it) {
51 // NONE means default style so do nothing.
52 if (it->styles == SearchResult::Tag::NONE)
53 continue;
55 if (it->styles & SearchResult::Tag::MATCH)
56 render_text->ApplyStyle(gfx::BOLD, true, it->range);
57 if (it->styles & SearchResult::Tag::DIM)
58 render_text->ApplyColor(kResultDimmedTextColor, it->range);
59 else if (it->styles & SearchResult::Tag::URL)
60 render_text->ApplyColor(kResultURLTextColor, it->range);
63 return render_text;
66 } // namespace
68 // static
69 const char SearchResultView::kViewClassName[] = "ui/app_list/SearchResultView";
71 SearchResultView::SearchResultView(SearchResultListView* list_view)
72 : views::CustomButton(this),
73 result_(NULL),
74 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);
80 AddChildView(icon_);
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();
93 result_ = result;
94 if (result_)
95 result_->AddObserver(this);
97 OnIconChanged();
98 OnActionsChanged();
99 UpdateTitleText();
100 UpdateDetailsText();
101 OnIsInstallingChanged();
102 OnPercentDownloadedChanged();
103 SchedulePaint();
106 void SearchResultView::ClearResultNoRepaint() {
107 if (result_)
108 result_->RemoveObserver(this);
109 result_ = NULL;
112 void SearchResultView::ClearSelectedAction() {
113 actions_view_->SetSelectedAction(-1);
116 void SearchResultView::UpdateTitleText() {
117 if (!result_ || result_->title().empty()) {
118 title_text_.reset();
119 SetAccessibleName(base::string16());
120 } else {
121 title_text_.reset(CreateRenderText(result_->title(),
122 result_->title_tags()));
123 SetAccessibleName(result_->title());
127 void SearchResultView::UpdateDetailsText() {
128 if (!result_ || result_->details().empty()) {
129 details_text_.reset();
130 } else {
131 details_text_.reset(CreateRenderText(result_->details(),
132 result_->details_tags()));
136 const char* SearchResultView::GetClassName() const {
137 return kViewClassName;
140 gfx::Size SearchResultView::GetPreferredSize() const {
141 return gfx::Size(kPreferredWidth, kPreferredHeight);
144 void SearchResultView::Layout() {
145 gfx::Rect rect(GetContentsBounds());
146 if (rect.IsEmpty())
147 return;
149 gfx::Rect icon_bounds(rect);
150 icon_bounds.set_width(GetIconViewWidth());
151 icon_bounds.Inset(kIconPadding, (rect.height() - kListIconSize) / 2);
152 icon_bounds.Intersect(rect);
153 icon_->SetBoundsRect(icon_bounds);
155 const int max_actions_width =
156 (rect.right() - kActionButtonRightMargin - icon_bounds.right()) / 2;
157 int actions_width = std::min(max_actions_width,
158 actions_view_->GetPreferredSize().width());
160 gfx::Rect actions_bounds(rect);
161 actions_bounds.set_x(rect.right() - kActionButtonRightMargin - actions_width);
162 actions_bounds.set_width(actions_width);
163 actions_view_->SetBoundsRect(actions_bounds);
165 const int progress_width = rect.width() / 5;
166 const int progress_height = progress_bar_->GetPreferredSize().height();
167 const gfx::Rect progress_bounds(
168 rect.right() - kActionButtonRightMargin - progress_width,
169 rect.y() + (rect.height() - progress_height) / 2,
170 progress_width,
171 progress_height);
172 progress_bar_->SetBoundsRect(progress_bounds);
175 bool SearchResultView::OnKeyPressed(const ui::KeyEvent& event) {
176 // |result_| could be NULL when result list is changing.
177 if (!result_)
178 return false;
180 switch (event.key_code()) {
181 case ui::VKEY_TAB: {
182 int new_selected = actions_view_->selected_action()
183 + (event.IsShiftDown() ? -1 : 1);
184 actions_view_->SetSelectedAction(new_selected);
185 return actions_view_->IsValidActionIndex(new_selected);
187 case ui::VKEY_RETURN: {
188 int selected = actions_view_->selected_action();
189 if (actions_view_->IsValidActionIndex(selected)) {
190 OnSearchResultActionActivated(selected, event.flags());
191 } else {
192 list_view_->SearchResultActivated(this, event.flags());
194 return true;
196 default:
197 break;
200 return false;
203 void SearchResultView::ChildPreferredSizeChanged(views::View* child) {
204 Layout();
207 void SearchResultView::OnPaint(gfx::Canvas* canvas) {
208 gfx::Rect rect(GetContentsBounds());
209 if (rect.IsEmpty())
210 return;
212 gfx::Rect content_rect(rect);
213 if (!switches::IsExperimentalAppListEnabled())
214 content_rect.set_height(rect.height() - kBorderSize);
216 const bool selected = list_view_->IsResultViewSelected(this);
217 const bool hover = state() == STATE_HOVERED || state() == STATE_PRESSED;
218 if (selected)
219 canvas->FillRect(content_rect, kSelectedColor);
220 else if (hover)
221 canvas->FillRect(content_rect, kHighlightedColor);
222 else if (!switches::IsExperimentalAppListEnabled())
223 canvas->FillRect(content_rect, kContentsBackgroundColor);
225 gfx::Rect border_bottom = gfx::SubtractRects(rect, content_rect);
226 canvas->FillRect(border_bottom, kResultBorderColor);
228 gfx::Rect text_bounds(rect);
229 text_bounds.set_x(GetIconViewWidth());
230 if (actions_view_->visible()) {
231 text_bounds.set_width(
232 rect.width() - GetIconViewWidth() - kTextTrailPadding -
233 actions_view_->bounds().width() -
234 (actions_view_->has_children() ? kActionButtonRightMargin : 0));
235 } else {
236 text_bounds.set_width(rect.width() - GetIconViewWidth() -
237 kTextTrailPadding - progress_bar_->bounds().width() -
238 kActionButtonRightMargin);
240 text_bounds.set_x(GetMirroredXWithWidthInView(text_bounds.x(),
241 text_bounds.width()));
243 if (title_text_ && details_text_) {
244 gfx::Size title_size(text_bounds.width(),
245 title_text_->GetStringSize().height());
246 gfx::Size details_size(text_bounds.width(),
247 details_text_->GetStringSize().height());
248 int total_height = title_size.height() + + details_size.height();
249 int y = text_bounds.y() + (text_bounds.height() - total_height) / 2;
251 title_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
252 title_size));
253 title_text_->Draw(canvas);
255 y += title_size.height();
256 details_text_->SetDisplayRect(gfx::Rect(gfx::Point(text_bounds.x(), y),
257 details_size));
258 details_text_->Draw(canvas);
259 } else if (title_text_) {
260 gfx::Size title_size(text_bounds.width(),
261 title_text_->GetStringSize().height());
262 gfx::Rect centered_title_rect(text_bounds);
263 centered_title_rect.ClampToCenteredSize(title_size);
264 title_text_->SetDisplayRect(centered_title_rect);
265 title_text_->Draw(canvas);
269 void SearchResultView::ButtonPressed(views::Button* sender,
270 const ui::Event& event) {
271 DCHECK(sender == this);
273 list_view_->SearchResultActivated(this, event.flags());
276 void SearchResultView::OnIconChanged() {
277 gfx::ImageSkia image(result_ ? result_->icon() : gfx::ImageSkia());
278 // Note this might leave the view with an old icon. But it is needed to avoid
279 // flash when a SearchResult's icon is loaded asynchronously. In this case, it
280 // looks nicer to keep the stale icon for a little while on screen instead of
281 // clearing it out. It should work correctly as long as the SearchResult does
282 // not forget to SetIcon when it's ready.
283 if (image.isNull())
284 return;
286 // Scales down big icons but leave small ones unchanged.
287 if (image.width() > kListIconSize || image.height() > kListIconSize) {
288 image = gfx::ImageSkiaOperations::CreateResizedImage(
289 image,
290 skia::ImageOperations::RESIZE_BEST,
291 gfx::Size(kListIconSize, kListIconSize));
292 } else {
293 icon_->ResetImageSize();
296 // Set the image to an empty image before we reset the image because
297 // since we're using the same backing store for our images, sometimes
298 // ImageView won't detect that we have a new image set due to the pixel
299 // buffer pointers remaining the same despite the image changing.
300 icon_->SetImage(gfx::ImageSkia());
301 icon_->SetImage(image);
304 void SearchResultView::OnActionsChanged() {
305 actions_view_->SetActions(result_ ? result_->actions()
306 : SearchResult::Actions());
309 void SearchResultView::OnIsInstallingChanged() {
310 const bool is_installing = result_ && result_->is_installing();
311 actions_view_->SetVisible(!is_installing);
312 progress_bar_->SetVisible(is_installing);
315 void SearchResultView::OnPercentDownloadedChanged() {
316 progress_bar_->SetValue(result_ ? result_->percent_downloaded() / 100.0 : 0);
319 void SearchResultView::OnItemInstalled() {
320 list_view_->OnSearchResultInstalled(this);
323 void SearchResultView::OnSearchResultActionActivated(size_t index,
324 int event_flags) {
325 // |result_| could be NULL when result list is changing.
326 if (!result_)
327 return;
329 DCHECK_LT(index, result_->actions().size());
331 list_view_->SearchResultActionActivated(this, index, event_flags);
334 void SearchResultView::ShowContextMenuForView(views::View* source,
335 const gfx::Point& point,
336 ui::MenuSourceType source_type) {
337 // |result_| could be NULL when result list is changing.
338 if (!result_)
339 return;
341 ui::MenuModel* menu_model = result_->GetContextMenuModel();
342 if (!menu_model)
343 return;
345 context_menu_runner_.reset(
346 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
347 if (context_menu_runner_->RunMenuAt(GetWidget(),
348 NULL,
349 gfx::Rect(point, gfx::Size()),
350 views::MENU_ANCHOR_TOPLEFT,
351 source_type) ==
352 views::MenuRunner::MENU_DELETED)
353 return;
356 } // namespace app_list