Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / ui / app_list / views / app_list_item_view.cc
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.
4
5 #include "ui/app_list/views/app_list_item_view.h"
6
7 #include <algorithm>
8
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/compositor/layer.h"
21 #include "ui/compositor/scoped_layer_animation_settings.h"
22 #include "ui/gfx/animation/throb_animation.h"
23 #include "ui/gfx/canvas.h"
24 #include "ui/gfx/font_list.h"
25 #include "ui/gfx/image/image_skia_operations.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/shadow_value.h"
28 #include "ui/gfx/transform_util.h"
29 #include "ui/strings/grit/ui_strings.h"
30 #include "ui/views/background.h"
31 #include "ui/views/controls/image_view.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/menu/menu_runner.h"
34 #include "ui/views/drag_controller.h"
35
36 namespace app_list {
37
38 namespace {
39
40 const int kTopPadding = 20;
41 const int kIconTitleSpacing = 7;
42 const int kProgressBarHorizontalPadding = 12;
43
44 // Radius of the folder dropping preview circle.
45 const int kFolderPreviewRadius = 40;
46
47 const int kLeftRightPaddingChars = 1;
48
49 // Scale to transform the icon when a drag starts.
50 const float kDraggingIconScale = 1.5f;
51
52 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
53 const int kMouseDragUIDelayInMs = 200;
54
55 const gfx::ShadowValues& GetIconShadows() {
56   CR_DEFINE_STATIC_LOCAL(
57       const gfx::ShadowValues,
58       icon_shadows,
59       (1,
60        gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0))));
61   return icon_shadows;
62 }
63
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
76 }
77
78 }  // namespace
79
80 // static
81 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
82
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);
98
99   title_->SetBackgroundColor(0);
100   title_->SetAutoColorReadabilityEnabled(false);
101   title_->SetEnabledColor(kGridTitleColor);
102
103   static const gfx::FontList font_list = GetFontList();
104   title_->SetFontList(font_list);
105   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
106   title_->Invalidate();
107   SetTitleSubpixelAA();
108
109   AddChildView(icon_);
110   AddChildView(title_);
111   AddChildView(progress_bar_);
112
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);
119
120   set_context_menu_controller(this);
121   set_request_focus_on_press(false);
122
123   SetAnimationDuration(0);
124 }
125
126 AppListItemView::~AppListItemView() {
127   if (item_weak_)
128     item_weak_->RemoveObserver(this);
129 }
130
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;
136   }
137
138   gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(
139       icon,
140       skia::ImageOperations::RESIZE_BEST,
141       gfx::Size(kGridIconDimension, kGridIconDimension)));
142   if (has_shadow) {
143     gfx::ImageSkia shadow(gfx::ImageSkiaOperations::CreateImageWithDropShadow(
144         resized, GetIconShadows()));
145     icon_->SetImage(shadow);
146     return;
147   }
148
149   icon_->SetImage(resized);
150 }
151
152 void AppListItemView::SetUIState(UIState state) {
153   if (ui_state_ == state)
154     return;
155
156   ui_state_ = state;
157
158   switch (ui_state_) {
159     case UI_STATE_NORMAL:
160       title_->SetVisible(!is_installing_);
161       progress_bar_->SetVisible(is_installing_);
162       break;
163     case UI_STATE_DRAGGING:
164       title_->SetVisible(false);
165       progress_bar_->SetVisible(false);
166       break;
167     case UI_STATE_DROPPING_IN_FOLDER:
168       break;
169   }
170 #if !defined(OS_WIN)
171   ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
172   switch (ui_state_) {
173     case UI_STATE_NORMAL:
174       layer()->SetTransform(gfx::Transform());
175       break;
176     case UI_STATE_DRAGGING: {
177       const gfx::Rect bounds(layer()->bounds().size());
178       layer()->SetTransform(gfx::GetScaleTransform(
179           bounds.CenterPoint(),
180           kDraggingIconScale));
181       break;
182     }
183     case UI_STATE_DROPPING_IN_FOLDER:
184       break;
185   }
186 #endif  // !OS_WIN
187
188   SchedulePaint();
189 }
190
191 void AppListItemView::SetTouchDragging(bool touch_dragging) {
192   if (touch_dragging_ == touch_dragging)
193     return;
194
195   touch_dragging_ = touch_dragging;
196   SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
197 }
198
199 void AppListItemView::OnMouseDragTimer() {
200   DCHECK(apps_grid_view_->IsDraggedView(this));
201   SetUIState(UI_STATE_DRAGGING);
202 }
203
204 void AppListItemView::SetTitleSubpixelAA() {
205   // TODO(tapted): Enable AA for folders as well, taking care to play nice with
206   // the folder bubble animation.
207   bool enable_aa = !is_in_folder_ && ui_state_ == UI_STATE_NORMAL &&
208                    !is_highlighted_ && !apps_grid_view_->IsSelectedView(this) &&
209                    !apps_grid_view_->IsAnimatingView(this);
210
211   bool currently_enabled = title_->background() != NULL;
212   if (currently_enabled == enable_aa)
213     return;
214
215   if (enable_aa) {
216     title_->SetBackgroundColor(app_list::kContentsBackgroundColor);
217     title_->set_background(views::Background::CreateSolidBackground(
218         app_list::kContentsBackgroundColor));
219   } else {
220     // In other cases, keep the background transparent to ensure correct
221     // interactions with animations. This will temporarily disable subpixel AA.
222     title_->SetBackgroundColor(0);
223     title_->set_background(NULL);
224   }
225   title_->Invalidate();
226   title_->SchedulePaint();
227 }
228
229 void AppListItemView::Prerender() {
230   title_->PaintToBackingImage();
231 }
232
233 void AppListItemView::CancelContextMenu() {
234   if (context_menu_runner_)
235     context_menu_runner_->Cancel();
236 }
237
238 gfx::ImageSkia AppListItemView::GetDragImage() {
239   return icon_->GetImage();
240 }
241
242 void AppListItemView::OnDragEnded() {
243   mouse_drag_timer_.Stop();
244   SetUIState(UI_STATE_NORMAL);
245 }
246
247 gfx::Point AppListItemView::GetDragImageOffset() {
248   gfx::Point image = icon_->GetImageBounds().origin();
249   return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
250 }
251
252 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
253   if (is_target_folder)
254     SetUIState(UI_STATE_DROPPING_IN_FOLDER);
255   else
256     SetUIState(UI_STATE_NORMAL);
257 }
258
259 void AppListItemView::SetItemName(const base::string16& display_name,
260                                   const base::string16& full_name) {
261   title_->SetText(display_name);
262   title_->Invalidate();
263
264   title_->SetTooltipText(display_name == full_name ? base::string16()
265                                                    : full_name);
266
267   // Use full name for accessibility.
268   SetAccessibleName(
269       is_folder_ ? l10n_util::GetStringFUTF16(
270                        IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME, full_name)
271                  : full_name);
272   Layout();
273 }
274
275 void AppListItemView::SetItemIsHighlighted(bool is_highlighted) {
276   is_highlighted_ = is_highlighted;
277   apps_grid_view_->EnsureViewVisible(this);
278   SchedulePaint();
279 }
280
281 void AppListItemView::SetItemIsInstalling(bool is_installing) {
282   is_installing_ = is_installing;
283   if (is_installing_)
284     apps_grid_view_->EnsureViewVisible(this);
285
286   if (ui_state_ == UI_STATE_NORMAL) {
287     title_->SetVisible(!is_installing);
288     progress_bar_->SetVisible(is_installing);
289   }
290   SchedulePaint();
291 }
292
293 void AppListItemView::SetItemPercentDownloaded(int percent_downloaded) {
294   // A percent_downloaded() of -1 can mean it's not known how much percent is
295   // completed, or the download hasn't been marked complete, as is the case
296   // while an extension is being installed after being downloaded.
297   if (percent_downloaded == -1)
298     return;
299   progress_bar_->SetValue(percent_downloaded / 100.0);
300 }
301
302 const char* AppListItemView::GetClassName() const {
303   return kViewClassName;
304 }
305
306 void AppListItemView::Layout() {
307   gfx::Rect rect(GetContentsBounds());
308
309   const int left_right_padding =
310       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
311   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
312   const int y = rect.y();
313
314   icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
315   const gfx::Size title_size = title_->GetPreferredSize();
316   gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
317                          y + kGridIconDimension + kIconTitleSpacing,
318                          title_size.width(),
319                          title_size.height());
320   title_bounds.Intersect(rect);
321   title_->SetBoundsRect(title_bounds);
322
323   gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
324   progress_bar_bounds.set_x(GetContentsBounds().x() +
325                             kProgressBarHorizontalPadding);
326   progress_bar_bounds.set_y(title_bounds.y());
327   progress_bar_->SetBoundsRect(progress_bar_bounds);
328 }
329
330 void AppListItemView::SchedulePaintInRect(const gfx::Rect& r) {
331   SetTitleSubpixelAA();
332   views::CustomButton::SchedulePaintInRect(r);
333 }
334
335 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
336   if (apps_grid_view_->IsDraggedView(this))
337     return;
338
339   gfx::Rect rect(GetContentsBounds());
340   if (is_highlighted_ && !is_installing_) {
341     canvas->FillRect(rect, kHighlightedColor);
342     return;
343   }
344   if (apps_grid_view_->IsSelectedView(this))
345     canvas->FillRect(rect, kSelectedColor);
346
347   if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
348     DCHECK(apps_grid_view_->model()->folders_enabled());
349
350     // Draw folder dropping preview circle.
351     gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
352                                    icon_->y() + icon_->size().height() / 2);
353     SkPaint paint;
354     paint.setStyle(SkPaint::kFill_Style);
355     paint.setAntiAlias(true);
356     paint.setColor(kFolderBubbleColor);
357     canvas->DrawCircle(center, kFolderPreviewRadius, paint);
358   }
359 }
360
361 void AppListItemView::ShowContextMenuForView(views::View* source,
362                                              const gfx::Point& point,
363                                              ui::MenuSourceType source_type) {
364   ui::MenuModel* menu_model =
365       item_weak_ ? item_weak_->GetContextMenuModel() : NULL;
366   if (!menu_model)
367     return;
368
369   context_menu_runner_.reset(
370       new views::MenuRunner(menu_model, views::MenuRunner::HAS_MNEMONICS));
371   if (context_menu_runner_->RunMenuAt(GetWidget(),
372                                       NULL,
373                                       gfx::Rect(point, gfx::Size()),
374                                       views::MENU_ANCHOR_TOPLEFT,
375                                       source_type) ==
376       views::MenuRunner::MENU_DELETED) {
377     return;
378   }
379 }
380
381 void AppListItemView::StateChanged() {
382   const bool is_folder_ui_enabled = apps_grid_view_->model()->folders_enabled();
383   if (is_folder_ui_enabled)
384     apps_grid_view_->ClearAnySelectedView();
385
386   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
387     if (!is_folder_ui_enabled)
388       apps_grid_view_->SetSelectedView(this);
389     title_->SetEnabledColor(kGridTitleHoverColor);
390   } else {
391     if (!is_folder_ui_enabled)
392       apps_grid_view_->ClearSelectedView(this);
393     is_highlighted_ = false;
394     if (item_weak_)
395       item_weak_->SetHighlighted(false);
396     title_->SetEnabledColor(kGridTitleColor);
397   }
398   title_->Invalidate();
399 }
400
401 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
402   // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
403   // background does not show up during scroll.
404   if (event.type() == ui::ET_GESTURE_TAP_DOWN)
405     return false;
406
407   return views::CustomButton::ShouldEnterPushedState(event);
408 }
409
410 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
411   CustomButton::OnMousePressed(event);
412
413   if (!ShouldEnterPushedState(event))
414     return true;
415
416   apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
417
418   if (apps_grid_view_->IsDraggedView(this)) {
419     mouse_drag_timer_.Start(FROM_HERE,
420         base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
421         this, &AppListItemView::OnMouseDragTimer);
422   }
423   return true;
424 }
425
426 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
427   // Disable space key to press the button. The keyboard events received
428   // by this view are forwarded from a Textfield (SearchBoxView) and key
429   // released events are not forwarded. This leaves the button in pressed
430   // state.
431   if (event.key_code() == ui::VKEY_SPACE)
432     return false;
433
434   return CustomButton::OnKeyPressed(event);
435 }
436
437 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
438   CustomButton::OnMouseReleased(event);
439   apps_grid_view_->EndDrag(false);
440 }
441
442 void AppListItemView::OnMouseCaptureLost() {
443   // We don't cancel the dag on mouse capture lost for windows as entering a
444   // synchronous drag causes mouse capture to be lost and pressing escape
445   // dismisses the app list anyway.
446 #if !defined(OS_WIN)
447   CustomButton::OnMouseCaptureLost();
448   apps_grid_view_->EndDrag(true);
449 #endif
450 }
451
452 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
453   CustomButton::OnMouseDragged(event);
454   if (apps_grid_view_->IsDraggedView(this)) {
455     // If the drag is no longer happening, it could be because this item
456     // got removed, in which case this item has been destroyed. So, bail out
457     // now as there will be nothing else to do anyway as
458     // apps_grid_view_->dragging() will be false.
459     if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
460       return true;
461   }
462
463   // Shows dragging UI when it's confirmed without waiting for the timer.
464   if (ui_state_ != UI_STATE_DRAGGING &&
465       apps_grid_view_->dragging() &&
466       apps_grid_view_->IsDraggedView(this)) {
467     mouse_drag_timer_.Stop();
468     SetUIState(UI_STATE_DRAGGING);
469   }
470   return true;
471 }
472
473 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
474   switch (event->type()) {
475     case ui::ET_GESTURE_SCROLL_BEGIN:
476       if (touch_dragging_) {
477         apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
478         event->SetHandled();
479       }
480       break;
481     case ui::ET_GESTURE_SCROLL_UPDATE:
482       if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
483         apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
484         event->SetHandled();
485       }
486       break;
487     case ui::ET_GESTURE_SCROLL_END:
488     case ui::ET_SCROLL_FLING_START:
489       if (touch_dragging_) {
490         SetTouchDragging(false);
491         apps_grid_view_->EndDrag(false);
492         event->SetHandled();
493       }
494       break;
495     case ui::ET_GESTURE_LONG_PRESS:
496       if (!apps_grid_view_->has_dragged_view())
497         SetTouchDragging(true);
498       event->SetHandled();
499       break;
500     case ui::ET_GESTURE_LONG_TAP:
501     case ui::ET_GESTURE_END:
502       if (touch_dragging_)
503         SetTouchDragging(false);
504       break;
505     default:
506       break;
507   }
508   if (!event->handled())
509     CustomButton::OnGestureEvent(event);
510 }
511
512 void AppListItemView::OnSyncDragEnd() {
513   SetUIState(UI_STATE_NORMAL);
514 }
515
516 const gfx::Rect& AppListItemView::GetIconBounds() const {
517   return icon_->bounds();
518 }
519
520 void AppListItemView::SetDragUIState() {
521   SetUIState(UI_STATE_DRAGGING);
522 }
523
524 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
525     const gfx::Rect& target_bounds) {
526   gfx::Rect rect(target_bounds);
527
528   const int left_right_padding =
529       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
530   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
531
532   gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), kGridIconDimension);
533   icon_bounds.Inset(gfx::ShadowValue::GetMargin(GetIconShadows()));
534   return icon_bounds;
535 }
536
537 void AppListItemView::ItemIconChanged() {
538   SetIcon(item_weak_->icon(), item_weak_->has_shadow());
539 }
540
541 void AppListItemView::ItemNameChanged() {
542   SetItemName(base::UTF8ToUTF16(item_weak_->GetDisplayName()),
543               base::UTF8ToUTF16(item_weak_->name()));
544 }
545
546 void AppListItemView::ItemHighlightedChanged() {
547   SetItemIsHighlighted(item_weak_->highlighted());
548 }
549
550 void AppListItemView::ItemIsInstallingChanged() {
551   SetItemIsInstalling(item_weak_->is_installing());
552 }
553
554 void AppListItemView::ItemPercentDownloadedChanged() {
555   SetItemPercentDownloaded(item_weak_->percent_downloaded());
556 }
557
558 void AppListItemView::ItemBeingDestroyed() {
559   DCHECK(item_weak_);
560   item_weak_->RemoveObserver(this);
561   item_weak_ = NULL;
562 }
563
564 }  // namespace app_list