Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob66faa989433c5cc279736585cdaf94f4472dc2ae
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/app_list_item_view.h"
7 #include <algorithm>
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/accessibility/ax_view_state.h"
11 #include "ui/app_list/app_list_constants.h"
12 #include "ui/app_list/app_list_folder_item.h"
13 #include "ui/app_list/app_list_item.h"
14 #include "ui/app_list/app_list_switches.h"
15 #include "ui/app_list/views/apps_grid_view.h"
16 #include "ui/app_list/views/cached_label.h"
17 #include "ui/app_list/views/progress_bar_view.h"
18 #include "ui/base/dragdrop/drag_utils.h"
19 #include "ui/base/l10n/l10n_util.h"
20 #include "ui/base/resource/resource_bundle.h"
21 #include "ui/base/ui_base_switches_util.h"
22 #include "ui/compositor/layer.h"
23 #include "ui/compositor/scoped_layer_animation_settings.h"
24 #include "ui/gfx/animation/throb_animation.h"
25 #include "ui/gfx/canvas.h"
26 #include "ui/gfx/font_list.h"
27 #include "ui/gfx/geometry/point.h"
28 #include "ui/gfx/geometry/vector2d.h"
29 #include "ui/gfx/image/image_skia_operations.h"
30 #include "ui/gfx/shadow_value.h"
31 #include "ui/gfx/transform_util.h"
32 #include "ui/strings/grit/ui_strings.h"
33 #include "ui/views/background.h"
34 #include "ui/views/controls/image_view.h"
35 #include "ui/views/controls/label.h"
36 #include "ui/views/controls/menu/menu_runner.h"
37 #include "ui/views/drag_controller.h"
39 namespace app_list {
41 namespace {
43 const int kTopPadding = 18;
44 const int kIconTitleSpacing = 6;
45 const int kProgressBarHorizontalPadding = 12;
47 // Radius of the folder dropping preview circle.
48 const int kFolderPreviewRadius = 40;
50 const int kLeftRightPaddingChars = 1;
52 // Scale to transform the icon when a drag starts.
53 const float kDraggingIconScale = 1.5f;
55 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
56 const int kMouseDragUIDelayInMs = 200;
58 gfx::FontList GetFontList() {
59 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
60 const gfx::FontList& font_list = rb.GetFontList(kItemTextFontStyle);
61 // The font is different on each platform. The font size is adjusted on some
62 // platforms to keep a consistent look.
63 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
64 // Reducing the font size by 2 makes it the same as the Windows font size.
65 const int kFontSizeDelta = -2;
66 return font_list.DeriveWithSizeDelta(kFontSizeDelta);
67 #else
68 return font_list;
69 #endif
72 } // namespace
74 // static
75 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
77 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
78 AppListItem* item)
79 : CustomButton(apps_grid_view),
80 is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
81 is_in_folder_(item->IsInFolder()),
82 item_weak_(item),
83 apps_grid_view_(apps_grid_view),
84 icon_(new views::ImageView),
85 title_(new CachedLabel),
86 progress_bar_(new ProgressBarView),
87 ui_state_(UI_STATE_NORMAL),
88 touch_dragging_(false),
89 shadow_animator_(this),
90 is_installing_(false),
91 is_highlighted_(false) {
92 shadow_animator_.animation()->SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
93 shadow_animator_.SetStartAndEndShadows(IconStartShadows(), IconEndShadows());
95 icon_->set_interactive(false);
96 icon_->SetVerticalAlignment(views::ImageView::LEADING);
98 title_->SetBackgroundColor(0);
99 title_->SetAutoColorReadabilityEnabled(false);
100 title_->SetEnabledColor(kGridTitleColor);
101 title_->SetHandlesTooltips(false);
103 static const gfx::FontList font_list = GetFontList();
104 title_->SetFontList(font_list);
105 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
106 title_->Invalidate();
107 SetTitleSubpixelAA();
109 AddChildView(icon_);
110 AddChildView(title_);
111 AddChildView(progress_bar_);
113 SetIcon(item->icon(), item->has_shadow());
114 SetItemName(base::UTF8ToUTF16(item->GetDisplayName()),
115 base::UTF8ToUTF16(item->name()));
116 SetItemIsInstalling(item->is_installing());
117 SetItemIsHighlighted(item->highlighted());
118 item->AddObserver(this);
120 set_context_menu_controller(this);
121 set_request_focus_on_press(false);
123 SetAnimationDuration(0);
126 AppListItemView::~AppListItemView() {
127 if (item_weak_)
128 item_weak_->RemoveObserver(this);
131 void AppListItemView::SetIcon(const gfx::ImageSkia& icon, bool has_shadow) {
132 // Clear icon and bail out if item icon is empty.
133 if (icon.isNull()) {
134 icon_->SetImage(NULL);
135 return;
138 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
139 icon,
140 skia::ImageOperations::RESIZE_BEST,
141 gfx::Size(kGridIconDimension, kGridIconDimension)));
142 if (has_shadow || app_list::switches::IsExperimentalAppListEnabled()) {
143 shadow_animator_.SetOriginalImage(resized);
144 return;
147 icon_->SetImage(resized);
150 void AppListItemView::SetUIState(UIState state) {
151 if (ui_state_ == state)
152 return;
154 ui_state_ = state;
156 switch (ui_state_) {
157 case UI_STATE_NORMAL:
158 title_->SetVisible(!is_installing_);
159 progress_bar_->SetVisible(is_installing_);
160 break;
161 case UI_STATE_DRAGGING:
162 title_->SetVisible(false);
163 progress_bar_->SetVisible(false);
164 break;
165 case UI_STATE_DROPPING_IN_FOLDER:
166 break;
168 #if !defined(OS_WIN)
169 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
170 switch (ui_state_) {
171 case UI_STATE_NORMAL:
172 layer()->SetTransform(gfx::Transform());
173 break;
174 case UI_STATE_DRAGGING: {
175 const gfx::Rect bounds(layer()->bounds().size());
176 layer()->SetTransform(gfx::GetScaleTransform(
177 bounds.CenterPoint(),
178 kDraggingIconScale));
179 break;
181 case UI_STATE_DROPPING_IN_FOLDER:
182 break;
184 #endif // !OS_WIN
186 SetTitleSubpixelAA();
187 SchedulePaint();
190 void AppListItemView::SetTouchDragging(bool touch_dragging) {
191 if (touch_dragging_ == touch_dragging)
192 return;
194 touch_dragging_ = touch_dragging;
195 SetState(STATE_NORMAL);
196 SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
199 void AppListItemView::OnMouseDragTimer() {
200 DCHECK(apps_grid_view_->IsDraggedView(this));
201 SetUIState(UI_STATE_DRAGGING);
204 void AppListItemView::Prerender() {
205 title_->PaintToBackingImage();
208 void AppListItemView::CancelContextMenu() {
209 if (context_menu_runner_)
210 context_menu_runner_->Cancel();
213 gfx::ImageSkia AppListItemView::GetDragImage() {
214 return icon_->GetImage();
217 void AppListItemView::OnDragEnded() {
218 mouse_drag_timer_.Stop();
219 SetUIState(UI_STATE_NORMAL);
222 gfx::Point AppListItemView::GetDragImageOffset() {
223 gfx::Point image = icon_->GetImageBounds().origin();
224 return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
227 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
228 if (is_target_folder)
229 SetUIState(UI_STATE_DROPPING_IN_FOLDER);
230 else
231 SetUIState(UI_STATE_NORMAL);
234 void AppListItemView::SetItemName(const base::string16& display_name,
235 const base::string16& full_name) {
236 title_->SetText(display_name);
237 title_->Invalidate();
239 tooltip_text_ = display_name == full_name ? base::string16() : full_name;
241 // Use full name for accessibility.
242 SetAccessibleName(
243 is_folder_ ? l10n_util::GetStringFUTF16(
244 IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME, full_name)
245 : full_name);
246 Layout();
249 void AppListItemView::SetItemIsHighlighted(bool is_highlighted) {
250 is_highlighted_ = is_highlighted;
251 SetTitleSubpixelAA();
252 SchedulePaint();
255 void AppListItemView::SetItemIsInstalling(bool is_installing) {
256 is_installing_ = is_installing;
257 if (ui_state_ == UI_STATE_NORMAL) {
258 title_->SetVisible(!is_installing);
259 progress_bar_->SetVisible(is_installing);
261 SetTitleSubpixelAA();
262 SchedulePaint();
265 void AppListItemView::SetItemPercentDownloaded(int percent_downloaded) {
266 // A percent_downloaded() of -1 can mean it's not known how much percent is
267 // completed, or the download hasn't been marked complete, as is the case
268 // while an extension is being installed after being downloaded.
269 if (percent_downloaded == -1)
270 return;
271 progress_bar_->SetValue(percent_downloaded / 100.0);
274 const char* AppListItemView::GetClassName() const {
275 return kViewClassName;
278 void AppListItemView::Layout() {
279 gfx::Rect rect(GetContentsBounds());
281 const int left_right_padding =
282 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
283 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
284 const int y = rect.y();
286 icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
288 const gfx::Size title_size = title_->GetPreferredSize();
289 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
290 y + kGridIconDimension + kIconTitleSpacing,
291 title_size.width(),
292 title_size.height());
293 title_bounds.Intersect(rect);
294 title_->SetBoundsRect(title_bounds);
295 SetTitleSubpixelAA();
297 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
298 progress_bar_bounds.set_x(GetContentsBounds().x() +
299 kProgressBarHorizontalPadding);
300 progress_bar_bounds.set_y(title_bounds.y());
301 progress_bar_->SetBoundsRect(progress_bar_bounds);
304 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
305 if (apps_grid_view_->IsDraggedView(this))
306 return;
308 gfx::Rect rect(GetContentsBounds());
309 if (apps_grid_view_->IsSelectedView(this)) {
310 canvas->FillRect(rect, kSelectedColor);
311 } else if (is_highlighted_ && !is_installing_ &&
312 !app_list::switches::IsExperimentalAppListEnabled()) {
313 canvas->FillRect(rect, kHighlightedColor);
314 return;
317 if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
318 DCHECK(apps_grid_view_->model()->folders_enabled());
320 // Draw folder dropping preview circle.
321 gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
322 icon_->y() + icon_->size().height() / 2);
323 SkPaint paint;
324 paint.setStyle(SkPaint::kFill_Style);
325 paint.setAntiAlias(true);
326 paint.setColor(kFolderBubbleColor);
327 canvas->DrawCircle(center, kFolderPreviewRadius, paint);
331 void AppListItemView::ShowContextMenuForView(views::View* source,
332 const gfx::Point& point,
333 ui::MenuSourceType source_type) {
334 ui::MenuModel* menu_model =
335 item_weak_ ? item_weak_->GetContextMenuModel() : NULL;
336 if (!menu_model)
337 return;
339 if (!apps_grid_view_->IsSelectedView(this))
340 apps_grid_view_->ClearAnySelectedView();
341 context_menu_runner_.reset(
342 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
343 if (context_menu_runner_->RunMenuAt(GetWidget(),
344 NULL,
345 gfx::Rect(point, gfx::Size()),
346 views::MENU_ANCHOR_TOPLEFT,
347 source_type) ==
348 views::MenuRunner::MENU_DELETED) {
349 return;
353 void AppListItemView::StateChanged() {
354 if (app_list::switches::IsExperimentalAppListEnabled()) {
355 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
356 shadow_animator_.animation()->Show();
357 } else {
358 shadow_animator_.animation()->Hide();
362 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
363 // Show the hover/tap highlight: for tap, lighter highlight replaces darker
364 // keyboard selection; for mouse hover, keyboard selection takes precedence.
365 if (!apps_grid_view_->IsSelectedView(this) || state() == STATE_PRESSED)
366 SetItemIsHighlighted(true);
367 } else {
368 SetItemIsHighlighted(false);
369 if (item_weak_)
370 item_weak_->set_highlighted(false);
372 SetTitleSubpixelAA();
375 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
376 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
377 // background does not show up during scroll.
378 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
379 return false;
381 return views::CustomButton::ShouldEnterPushedState(event);
384 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
385 CustomButton::OnMousePressed(event);
387 if (!ShouldEnterPushedState(event))
388 return true;
390 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
392 if (apps_grid_view_->IsDraggedView(this)) {
393 mouse_drag_timer_.Start(FROM_HERE,
394 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
395 this, &AppListItemView::OnMouseDragTimer);
397 return true;
400 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
401 // Disable space key to press the button. The keyboard events received
402 // by this view are forwarded from a Textfield (SearchBoxView) and key
403 // released events are not forwarded. This leaves the button in pressed
404 // state.
405 if (event.key_code() == ui::VKEY_SPACE)
406 return false;
408 return CustomButton::OnKeyPressed(event);
411 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
412 CustomButton::OnMouseReleased(event);
413 apps_grid_view_->EndDrag(false);
416 void AppListItemView::OnMouseCaptureLost() {
417 // We don't cancel the dag on mouse capture lost for windows as entering a
418 // synchronous drag causes mouse capture to be lost and pressing escape
419 // dismisses the app list anyway.
420 #if !defined(OS_WIN)
421 CustomButton::OnMouseCaptureLost();
422 apps_grid_view_->EndDrag(true);
423 #endif
426 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
427 CustomButton::OnMouseDragged(event);
428 if (apps_grid_view_->IsDraggedView(this)) {
429 // If the drag is no longer happening, it could be because this item
430 // got removed, in which case this item has been destroyed. So, bail out
431 // now as there will be nothing else to do anyway as
432 // apps_grid_view_->dragging() will be false.
433 if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
434 return true;
437 if (!apps_grid_view_->IsSelectedView(this))
438 apps_grid_view_->ClearAnySelectedView();
440 // Shows dragging UI when it's confirmed without waiting for the timer.
441 if (ui_state_ != UI_STATE_DRAGGING &&
442 apps_grid_view_->dragging() &&
443 apps_grid_view_->IsDraggedView(this)) {
444 mouse_drag_timer_.Stop();
445 SetUIState(UI_STATE_DRAGGING);
447 return true;
450 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
451 switch (event->type()) {
452 case ui::ET_GESTURE_SCROLL_BEGIN:
453 if (touch_dragging_) {
454 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
455 event->SetHandled();
457 break;
458 case ui::ET_GESTURE_SCROLL_UPDATE:
459 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
460 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
461 event->SetHandled();
463 break;
464 case ui::ET_GESTURE_SCROLL_END:
465 case ui::ET_SCROLL_FLING_START:
466 if (touch_dragging_) {
467 SetTouchDragging(false);
468 apps_grid_view_->EndDrag(false);
469 event->SetHandled();
471 break;
472 case ui::ET_GESTURE_TAP_DOWN:
473 if (::switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED) {
474 SetState(STATE_PRESSED);
475 event->SetHandled();
477 break;
478 case ui::ET_GESTURE_TAP:
479 case ui::ET_GESTURE_TAP_CANCEL:
480 if (::switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED)
481 SetState(STATE_NORMAL);
482 break;
483 case ui::ET_GESTURE_LONG_PRESS:
484 if (!apps_grid_view_->has_dragged_view())
485 SetTouchDragging(true);
486 event->SetHandled();
487 break;
488 case ui::ET_GESTURE_LONG_TAP:
489 case ui::ET_GESTURE_END:
490 if (touch_dragging_)
491 SetTouchDragging(false);
492 break;
493 default:
494 break;
496 if (!event->handled())
497 CustomButton::OnGestureEvent(event);
500 bool AppListItemView::GetTooltipText(const gfx::Point& p,
501 base::string16* tooltip) const {
502 // Use the label to generate a tooltip, so that it will consider its text
503 // truncation in making the tooltip. We do not want the label itself to have a
504 // tooltip, so we only temporarily enable it to get the tooltip text from the
505 // label, then disable it again.
506 title_->SetHandlesTooltips(true);
507 title_->SetTooltipText(tooltip_text_);
508 bool handled = title_->GetTooltipText(p, tooltip);
509 title_->SetHandlesTooltips(false);
510 return handled;
513 void AppListItemView::ImageShadowAnimationProgressed(
514 ImageShadowAnimator* animator) {
515 icon_->SetImage(animator->shadow_image());
516 Layout();
519 void AppListItemView::OnSyncDragEnd() {
520 SetUIState(UI_STATE_NORMAL);
523 const gfx::Rect& AppListItemView::GetIconBounds() const {
524 return icon_->bounds();
527 void AppListItemView::SetDragUIState() {
528 SetUIState(UI_STATE_DRAGGING);
531 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
532 const gfx::Rect& target_bounds) {
533 gfx::Rect rect(target_bounds);
534 rect.Inset(0, kTopPadding, 0, 0);
535 rect.set_height(icon_->GetImage().height());
536 rect.ClampToCenteredSize(icon_->GetImage().size());
537 return rect;
540 void AppListItemView::SetTitleSubpixelAA() {
541 // TODO(tapted): Enable AA for folders as well, taking care to play nice with
542 // the folder bubble animation.
543 bool enable_aa = !is_in_folder_ && ui_state_ == UI_STATE_NORMAL &&
544 !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) &&
545 !apps_grid_view_->IsAnimatingView(this);
547 title_->SetSubpixelRenderingEnabled(enable_aa);
548 if (enable_aa) {
549 title_->SetBackgroundColor(app_list::kLabelBackgroundColor);
550 title_->set_background(views::Background::CreateSolidBackground(
551 app_list::kLabelBackgroundColor));
552 } else {
553 // In other cases, keep the background transparent to ensure correct
554 // interactions with animations. This will temporarily disable subpixel AA.
555 title_->SetBackgroundColor(0);
556 title_->set_background(NULL);
558 title_->Invalidate();
559 title_->SchedulePaint();
562 void AppListItemView::ItemIconChanged() {
563 SetIcon(item_weak_->icon(), item_weak_->has_shadow());
566 void AppListItemView::ItemNameChanged() {
567 SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
568 base::UTF8ToUTF16(item_weak_->name()));
571 void AppListItemView::ItemIsInstallingChanged() {
572 SetItemIsInstalling(item_weak_->is_installing());
575 void AppListItemView::ItemPercentDownloadedChanged() {
576 SetItemPercentDownloaded(item_weak_->percent_downloaded());
579 void AppListItemView::ItemBeingDestroyed() {
580 DCHECK(item_weak_);
581 item_weak_->RemoveObserver(this);
582 item_weak_ = NULL;
585 } // namespace app_list