Supervised user whitelists: Cleanup
[chromium-blink-merge.git] / ui / app_list / views / app_list_item_view.cc
blob6a39a78095347c1d25ed6140d888072e6eddf2f4
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/views/apps_grid_view.h"
15 #include "ui/app_list/views/cached_label.h"
16 #include "ui/app_list/views/progress_bar_view.h"
17 #include "ui/base/dragdrop/drag_utils.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/resource/resource_bundle.h"
20 #include "ui/base/ui_base_switches_util.h"
21 #include "ui/compositor/layer.h"
22 #include "ui/compositor/scoped_layer_animation_settings.h"
23 #include "ui/gfx/animation/throb_animation.h"
24 #include "ui/gfx/canvas.h"
25 #include "ui/gfx/font_list.h"
26 #include "ui/gfx/geometry/point.h"
27 #include "ui/gfx/geometry/vector2d.h"
28 #include "ui/gfx/image/image_skia_operations.h"
29 #include "ui/gfx/shadow_value.h"
30 #include "ui/gfx/transform_util.h"
31 #include "ui/strings/grit/ui_strings.h"
32 #include "ui/views/background.h"
33 #include "ui/views/controls/image_view.h"
34 #include "ui/views/controls/label.h"
35 #include "ui/views/controls/menu/menu_runner.h"
36 #include "ui/views/drag_controller.h"
38 namespace app_list {
40 namespace {
42 const int kTopPadding = 18;
43 const int kIconTitleSpacing = 6;
44 const int kProgressBarHorizontalPadding = 12;
46 // Radius of the folder dropping preview circle.
47 const int kFolderPreviewRadius = 40;
49 const int kLeftRightPaddingChars = 1;
51 // Scale to transform the icon when a drag starts.
52 const float kDraggingIconScale = 1.5f;
54 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
55 const int kMouseDragUIDelayInMs = 200;
57 const gfx::ShadowValues& GetIconShadows() {
58 CR_DEFINE_STATIC_LOCAL(const gfx::ShadowValues, icon_shadows,
59 (1, gfx::ShadowValue(gfx::Vector2d(0, 2), 2,
60 SkColorSetARGB(0x24, 0, 0, 0))));
61 return icon_shadows;
64 gfx::FontList GetFontList() {
65 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
66 const gfx::FontList& font_list = rb.GetFontList(kItemTextFontStyle);
67 // The font is different on each platform. The font size is adjusted on some
68 // platforms to keep a consistent look.
69 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
70 // Reducing the font size by 2 makes it the same as the Windows font size.
71 const int kFontSizeDelta = -2;
72 return font_list.DeriveWithSizeDelta(kFontSizeDelta);
73 #else
74 return font_list;
75 #endif
78 } // namespace
80 // static
81 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
83 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
84 AppListItem* item)
85 : CustomButton(apps_grid_view),
86 is_folder_(item->GetItemType() == AppListFolderItem::kItemType),
87 is_in_folder_(item->IsInFolder()),
88 item_weak_(item),
89 apps_grid_view_(apps_grid_view),
90 icon_(new views::ImageView),
91 title_(new CachedLabel),
92 progress_bar_(new ProgressBarView),
93 ui_state_(UI_STATE_NORMAL),
94 touch_dragging_(false),
95 is_installing_(false),
96 is_highlighted_(false) {
97 icon_->set_interactive(false);
99 title_->SetBackgroundColor(0);
100 title_->SetAutoColorReadabilityEnabled(false);
101 title_->SetEnabledColor(kGridTitleColor);
102 title_->SetHandlesTooltips(false);
104 static const gfx::FontList font_list = GetFontList();
105 title_->SetFontList(font_list);
106 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
107 title_->Invalidate();
108 SetTitleSubpixelAA();
110 AddChildView(icon_);
111 AddChildView(title_);
112 AddChildView(progress_bar_);
114 SetIcon(item->icon(), item->has_shadow());
115 SetItemName(base::UTF8ToUTF16(item->GetDisplayName()),
116 base::UTF8ToUTF16(item->name()));
117 SetItemIsInstalling(item->is_installing());
118 SetItemIsHighlighted(item->highlighted());
119 item->AddObserver(this);
121 set_context_menu_controller(this);
122 set_request_focus_on_press(false);
124 SetAnimationDuration(0);
127 AppListItemView::~AppListItemView() {
128 if (item_weak_)
129 item_weak_->RemoveObserver(this);
132 void AppListItemView::SetIcon(const gfx::ImageSkia& icon, bool has_shadow) {
133 // Clear icon and bail out if item icon is empty.
134 if (icon.isNull()) {
135 icon_->SetImage(NULL);
136 return;
139 gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
140 icon,
141 skia::ImageOperations::RESIZE_BEST,
142 gfx::Size(kGridIconDimension, kGridIconDimension)));
143 if (has_shadow) {
144 gfx::ImageSkia shadow(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
145 resized, GetIconShadows()));
146 icon_->SetImage(shadow);
147 return;
150 icon_->SetImage(resized);
153 void AppListItemView::SetUIState(UIState state) {
154 if (ui_state_ == state)
155 return;
157 ui_state_ = state;
159 switch (ui_state_) {
160 case UI_STATE_NORMAL:
161 title_->SetVisible(!is_installing_);
162 progress_bar_->SetVisible(is_installing_);
163 break;
164 case UI_STATE_DRAGGING:
165 title_->SetVisible(false);
166 progress_bar_->SetVisible(false);
167 break;
168 case UI_STATE_DROPPING_IN_FOLDER:
169 break;
171 #if !defined(OS_WIN)
172 ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
173 switch (ui_state_) {
174 case UI_STATE_NORMAL:
175 layer()->SetTransform(gfx::Transform());
176 break;
177 case UI_STATE_DRAGGING: {
178 const gfx::Rect bounds(layer()->bounds().size());
179 layer()->SetTransform(gfx::GetScaleTransform(
180 bounds.CenterPoint(),
181 kDraggingIconScale));
182 break;
184 case UI_STATE_DROPPING_IN_FOLDER:
185 break;
187 #endif // !OS_WIN
189 SetTitleSubpixelAA();
190 SchedulePaint();
193 void AppListItemView::SetTouchDragging(bool touch_dragging) {
194 if (touch_dragging_ == touch_dragging)
195 return;
197 touch_dragging_ = touch_dragging;
198 SetState(STATE_NORMAL);
199 SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
202 void AppListItemView::OnMouseDragTimer() {
203 DCHECK(apps_grid_view_->IsDraggedView(this));
204 SetUIState(UI_STATE_DRAGGING);
207 void AppListItemView::Prerender() {
208 title_->PaintToBackingImage();
211 void AppListItemView::CancelContextMenu() {
212 if (context_menu_runner_)
213 context_menu_runner_->Cancel();
216 gfx::ImageSkia AppListItemView::GetDragImage() {
217 return icon_->GetImage();
220 void AppListItemView::OnDragEnded() {
221 mouse_drag_timer_.Stop();
222 SetUIState(UI_STATE_NORMAL);
225 gfx::Point AppListItemView::GetDragImageOffset() {
226 gfx::Point image = icon_->GetImageBounds().origin();
227 return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
230 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
231 if (is_target_folder)
232 SetUIState(UI_STATE_DROPPING_IN_FOLDER);
233 else
234 SetUIState(UI_STATE_NORMAL);
237 void AppListItemView::SetItemName(const base::string16& display_name,
238 const base::string16& full_name) {
239 title_->SetText(display_name);
240 title_->Invalidate();
242 tooltip_text_ = display_name == full_name ? base::string16() : full_name;
244 // Use full name for accessibility.
245 SetAccessibleName(
246 is_folder_ ? l10n_util::GetStringFUTF16(
247 IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME, full_name)
248 : full_name);
249 Layout();
252 void AppListItemView::SetItemIsHighlighted(bool is_highlighted) {
253 is_highlighted_ = is_highlighted;
254 SetTitleSubpixelAA();
255 SchedulePaint();
258 void AppListItemView::SetItemIsInstalling(bool is_installing) {
259 is_installing_ = is_installing;
260 if (ui_state_ == UI_STATE_NORMAL) {
261 title_->SetVisible(!is_installing);
262 progress_bar_->SetVisible(is_installing);
264 SetTitleSubpixelAA();
265 SchedulePaint();
268 void AppListItemView::SetItemPercentDownloaded(int percent_downloaded) {
269 // A percent_downloaded() of -1 can mean it's not known how much percent is
270 // completed, or the download hasn't been marked complete, as is the case
271 // while an extension is being installed after being downloaded.
272 if (percent_downloaded == -1)
273 return;
274 progress_bar_->SetValue(percent_downloaded / 100.0);
277 const char* AppListItemView::GetClassName() const {
278 return kViewClassName;
281 void AppListItemView::Layout() {
282 gfx::Rect rect(GetContentsBounds());
284 const int left_right_padding =
285 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
286 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
287 const int y = rect.y();
289 icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
291 const gfx::Size title_size = title_->GetPreferredSize();
292 gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
293 y + kGridIconDimension + kIconTitleSpacing,
294 title_size.width(),
295 title_size.height());
296 title_bounds.Intersect(rect);
297 title_->SetBoundsRect(title_bounds);
298 SetTitleSubpixelAA();
300 gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
301 progress_bar_bounds.set_x(GetContentsBounds().x() +
302 kProgressBarHorizontalPadding);
303 progress_bar_bounds.set_y(title_bounds.y());
304 progress_bar_->SetBoundsRect(progress_bar_bounds);
307 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
308 if (apps_grid_view_->IsDraggedView(this))
309 return;
311 gfx::Rect rect(GetContentsBounds());
312 if (apps_grid_view_->IsSelectedView(this)) {
313 canvas->FillRect(rect, kSelectedColor);
314 } else if (is_highlighted_ && !is_installing_) {
315 canvas->FillRect(rect, kHighlightedColor);
316 return;
319 if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
320 DCHECK(apps_grid_view_->model()->folders_enabled());
322 // Draw folder dropping preview circle.
323 gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
324 icon_->y() + icon_->size().height() / 2);
325 SkPaint paint;
326 paint.setStyle(SkPaint::kFill_Style);
327 paint.setAntiAlias(true);
328 paint.setColor(kFolderBubbleColor);
329 canvas->DrawCircle(center, kFolderPreviewRadius, paint);
333 void AppListItemView::ShowContextMenuForView(views::View* source,
334 const gfx::Point& point,
335 ui::MenuSourceType source_type) {
336 ui::MenuModel* menu_model =
337 item_weak_ ? item_weak_->GetContextMenuModel() : NULL;
338 if (!menu_model)
339 return;
341 if (!apps_grid_view_->IsSelectedView(this))
342 apps_grid_view_->ClearAnySelectedView();
343 context_menu_runner_.reset(
344 new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
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::MENU_DELETED) {
351 return;
355 void AppListItemView::StateChanged() {
356 if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
357 // Show the hover/tap highlight: for tap, lighter highlight replaces darker
358 // keyboard selection; for mouse hover, keyboard selection takes precedence.
359 if (!apps_grid_view_->IsSelectedView(this) || state() == STATE_PRESSED)
360 SetItemIsHighlighted(true);
361 title_->SetEnabledColor(kGridTitleHoverColor);
362 } else {
363 SetItemIsHighlighted(false);
364 if (item_weak_)
365 item_weak_->set_highlighted(false);
366 title_->SetEnabledColor(kGridTitleColor);
368 SetTitleSubpixelAA();
371 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
372 // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
373 // background does not show up during scroll.
374 if (event.type() == ui::ET_GESTURE_TAP_DOWN)
375 return false;
377 return views::CustomButton::ShouldEnterPushedState(event);
380 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
381 CustomButton::OnMousePressed(event);
383 if (!ShouldEnterPushedState(event))
384 return true;
386 apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
388 if (apps_grid_view_->IsDraggedView(this)) {
389 mouse_drag_timer_.Start(FROM_HERE,
390 base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
391 this, &AppListItemView::OnMouseDragTimer);
393 return true;
396 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
397 // Disable space key to press the button. The keyboard events received
398 // by this view are forwarded from a Textfield (SearchBoxView) and key
399 // released events are not forwarded. This leaves the button in pressed
400 // state.
401 if (event.key_code() == ui::VKEY_SPACE)
402 return false;
404 return CustomButton::OnKeyPressed(event);
407 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
408 CustomButton::OnMouseReleased(event);
409 apps_grid_view_->EndDrag(false);
412 void AppListItemView::OnMouseCaptureLost() {
413 // We don't cancel the dag on mouse capture lost for windows as entering a
414 // synchronous drag causes mouse capture to be lost and pressing escape
415 // dismisses the app list anyway.
416 #if !defined(OS_WIN)
417 CustomButton::OnMouseCaptureLost();
418 apps_grid_view_->EndDrag(true);
419 #endif
422 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
423 CustomButton::OnMouseDragged(event);
424 if (apps_grid_view_->IsDraggedView(this)) {
425 // If the drag is no longer happening, it could be because this item
426 // got removed, in which case this item has been destroyed. So, bail out
427 // now as there will be nothing else to do anyway as
428 // apps_grid_view_->dragging() will be false.
429 if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
430 return true;
433 if (!apps_grid_view_->IsSelectedView(this))
434 apps_grid_view_->ClearAnySelectedView();
436 // Shows dragging UI when it's confirmed without waiting for the timer.
437 if (ui_state_ != UI_STATE_DRAGGING &&
438 apps_grid_view_->dragging() &&
439 apps_grid_view_->IsDraggedView(this)) {
440 mouse_drag_timer_.Stop();
441 SetUIState(UI_STATE_DRAGGING);
443 return true;
446 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
447 switch (event->type()) {
448 case ui::ET_GESTURE_SCROLL_BEGIN:
449 if (touch_dragging_) {
450 apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
451 event->SetHandled();
453 break;
454 case ui::ET_GESTURE_SCROLL_UPDATE:
455 if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
456 apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
457 event->SetHandled();
459 break;
460 case ui::ET_GESTURE_SCROLL_END:
461 case ui::ET_SCROLL_FLING_START:
462 if (touch_dragging_) {
463 SetTouchDragging(false);
464 apps_grid_view_->EndDrag(false);
465 event->SetHandled();
467 break;
468 case ui::ET_GESTURE_TAP_DOWN:
469 if (switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED) {
470 SetState(STATE_PRESSED);
471 event->SetHandled();
473 break;
474 case ui::ET_GESTURE_TAP:
475 case ui::ET_GESTURE_TAP_CANCEL:
476 if (switches::IsTouchFeedbackEnabled() && state_ != STATE_DISABLED)
477 SetState(STATE_NORMAL);
478 break;
479 case ui::ET_GESTURE_LONG_PRESS:
480 if (!apps_grid_view_->has_dragged_view())
481 SetTouchDragging(true);
482 event->SetHandled();
483 break;
484 case ui::ET_GESTURE_LONG_TAP:
485 case ui::ET_GESTURE_END:
486 if (touch_dragging_)
487 SetTouchDragging(false);
488 break;
489 default:
490 break;
492 if (!event->handled())
493 CustomButton::OnGestureEvent(event);
496 bool AppListItemView::GetTooltipText(const gfx::Point& p,
497 base::string16* tooltip) const {
498 // Use the label to generate a tooltip, so that it will consider its text
499 // truncation in making the tooltip. We do not want the label itself to have a
500 // tooltip, so we only temporarily enable it to get the tooltip text from the
501 // label, then disable it again.
502 title_->SetHandlesTooltips(true);
503 title_->SetTooltipText(tooltip_text_);
504 bool handled = title_->GetTooltipText(p, tooltip);
505 title_->SetHandlesTooltips(false);
506 return handled;
509 void AppListItemView::OnSyncDragEnd() {
510 SetUIState(UI_STATE_NORMAL);
513 const gfx::Rect& AppListItemView::GetIconBounds() const {
514 return icon_->bounds();
517 void AppListItemView::SetDragUIState() {
518 SetUIState(UI_STATE_DRAGGING);
521 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
522 const gfx::Rect& target_bounds) {
523 gfx::Rect rect(target_bounds);
525 const int left_right_padding =
526 title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
527 rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
529 gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), kGridIconDimension);
530 icon_bounds.Inset(gfx::ShadowValue::GetMargin(GetIconShadows()));
531 return icon_bounds;
534 void AppListItemView::SetTitleSubpixelAA() {
535 // TODO(tapted): Enable AA for folders as well, taking care to play nice with
536 // the folder bubble animation.
537 bool enable_aa = !is_in_folder_ && ui_state_ == UI_STATE_NORMAL &&
538 !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) &&
539 !apps_grid_view_->IsAnimatingView(this);
541 title_->SetSubpixelRenderingEnabled(enable_aa);
542 if (enable_aa) {
543 title_->SetBackgroundColor(app_list::kLabelBackgroundColor);
544 title_->set_background(views::Background::CreateSolidBackground(
545 app_list::kLabelBackgroundColor));
546 } else {
547 // In other cases, keep the background transparent to ensure correct
548 // interactions with animations. This will temporarily disable subpixel AA.
549 title_->SetBackgroundColor(0);
550 title_->set_background(NULL);
552 title_->Invalidate();
553 title_->SchedulePaint();
556 void AppListItemView::ItemIconChanged() {
557 SetIcon(item_weak_->icon(), item_weak_->has_shadow());
560 void AppListItemView::ItemNameChanged() {
561 SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
562 base::UTF8ToUTF16(item_weak_->name()));
565 void AppListItemView::ItemIsInstallingChanged() {
566 SetItemIsInstalling(item_weak_->is_installing());
569 void AppListItemView::ItemPercentDownloadedChanged() {
570 SetItemPercentDownloaded(item_weak_->percent_downloaded());
573 void AppListItemView::ItemBeingDestroyed() {
574 DCHECK(item_weak_);
575 item_weak_->RemoveObserver(this);
576 item_weak_ = NULL;
579 } // namespace app_list