Upstream version 7.36.149.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 "grit/ui_strings.h"
11 #include "ui/accessibility/ax_view_state.h"
12 #include "ui/app_list/app_list_constants.h"
13 #include "ui/app_list/app_list_folder_item.h"
14 #include "ui/app_list/app_list_item.h"
15 #include "ui/app_list/app_list_switches.h"
16 #include "ui/app_list/views/apps_grid_view.h"
17 #include "ui/app_list/views/cached_label.h"
18 #include "ui/app_list/views/progress_bar_view.h"
19 #include "ui/base/dragdrop/drag_utils.h"
20 #include "ui/base/l10n/l10n_util.h"
21 #include "ui/base/resource/resource_bundle.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/image/image_skia_operations.h"
28 #include "ui/gfx/point.h"
29 #include "ui/gfx/transform_util.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 // The font is different on each platform. The font size is adjusted on some
45 // platforms to keep a consistent look.
46 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
47 // Reducing the font size by 2 makes it the same as the Windows font size.
48 const int kFontSizeDelta = -2;
49 #else
50 const int kFontSizeDelta = 0;
51 #endif
52
53 // Radius of the folder dropping preview circle.
54 const int kFolderPreviewRadius = 40;
55
56 const int kLeftRightPaddingChars = 1;
57
58 // Scale to transform the icon when a drag starts.
59 const float kDraggingIconScale = 1.5f;
60
61 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
62 const int kMouseDragUIDelayInMs = 200;
63
64 }  // namespace
65
66 // static
67 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
68
69 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
70                                  AppListItem* item)
71     : CustomButton(apps_grid_view),
72       item_(item),
73       apps_grid_view_(apps_grid_view),
74       icon_(new views::ImageView),
75       title_(new CachedLabel),
76       progress_bar_(new ProgressBarView),
77       ui_state_(UI_STATE_NORMAL),
78       touch_dragging_(false) {
79   icon_->set_interactive(false);
80
81   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
82   title_->SetBackgroundColor(0);
83   title_->SetAutoColorReadabilityEnabled(false);
84   title_->SetEnabledColor(kGridTitleColor);
85   title_->SetFontList(
86       rb.GetFontList(kItemTextFontStyle).DeriveWithSizeDelta(kFontSizeDelta));
87   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
88   title_->SetVisible(!item_->is_installing());
89   title_->Invalidate();
90   SetTitleSubpixelAA();
91
92   const gfx::ShadowValue kIconShadows[] = {
93     gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
94   };
95   icon_shadows_.assign(kIconShadows, kIconShadows + arraysize(kIconShadows));
96
97   AddChildView(icon_);
98   AddChildView(title_);
99   AddChildView(progress_bar_);
100
101   ItemIconChanged();
102   ItemNameChanged();
103   ItemIsInstallingChanged();
104   item_->AddObserver(this);
105
106   set_context_menu_controller(this);
107   set_request_focus_on_press(false);
108
109   SetAnimationDuration(0);
110 }
111
112 AppListItemView::~AppListItemView() {
113   item_->RemoveObserver(this);
114 }
115
116 void AppListItemView::SetIconSize(const gfx::Size& size) {
117   if (icon_size_ == size)
118     return;
119
120   icon_size_ = size;
121   UpdateIcon();
122 }
123
124 void AppListItemView::UpdateIcon() {
125   // Skip if |icon_size_| has not been determined.
126   if (icon_size_.IsEmpty())
127     return;
128
129   gfx::ImageSkia icon = item_->icon();
130   // Clear icon and bail out if item icon is empty.
131   if (icon.isNull()) {
132     icon_->SetImage(NULL);
133     return;
134   }
135
136   gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(icon,
137       skia::ImageOperations::RESIZE_BEST, icon_size_));
138   if (item_->has_shadow()) {
139     gfx::ImageSkia shadow(
140         gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized,
141                                                             icon_shadows_));
142     icon_->SetImage(shadow);
143     return;
144   }
145
146   icon_->SetImage(resized);
147 }
148
149 void AppListItemView::UpdateTooltip() {
150   std::string display_name = item_->GetDisplayName();
151   title_->SetTooltipText(display_name == item_->name() ? base::string16()
152                          : base::UTF8ToUTF16(item_->name()));
153 }
154
155 void AppListItemView::SetUIState(UIState state) {
156   if (ui_state_ == state)
157     return;
158
159   ui_state_ = state;
160
161   switch (ui_state_) {
162     case UI_STATE_NORMAL:
163       title_->SetVisible(!item_->is_installing());
164       progress_bar_->SetVisible(item_->is_installing());
165       break;
166     case UI_STATE_DRAGGING:
167       title_->SetVisible(false);
168       progress_bar_->SetVisible(false);
169       break;
170     case UI_STATE_DROPPING_IN_FOLDER:
171       break;
172   }
173 #if !defined(OS_WIN)
174   ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
175   switch (ui_state_) {
176     case UI_STATE_NORMAL:
177       layer()->SetTransform(gfx::Transform());
178       break;
179     case UI_STATE_DRAGGING: {
180       const gfx::Rect bounds(layer()->bounds().size());
181       layer()->SetTransform(gfx::GetScaleTransform(
182           bounds.CenterPoint(),
183           kDraggingIconScale));
184       break;
185     }
186     case UI_STATE_DROPPING_IN_FOLDER:
187       break;
188   }
189 #endif  // !OS_WIN
190
191   SchedulePaint();
192 }
193
194 void AppListItemView::SetTouchDragging(bool touch_dragging) {
195   if (touch_dragging_ == touch_dragging)
196     return;
197
198   touch_dragging_ = touch_dragging;
199   SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
200 }
201
202 void AppListItemView::OnMouseDragTimer() {
203   DCHECK(apps_grid_view_->IsDraggedView(this));
204   SetUIState(UI_STATE_DRAGGING);
205 }
206
207 void AppListItemView::SetTitleSubpixelAA() {
208   // TODO(tapted): Enable AA for folders as well, taking care to play nice with
209   // the folder bubble animation.
210   bool enable_aa = !item_->IsInFolder() && ui_state_ == UI_STATE_NORMAL &&
211                    !item_->highlighted() &&
212                    !apps_grid_view_->IsSelectedView(this) &&
213                    !apps_grid_view_->IsAnimatingView(this);
214
215   bool currently_enabled = title_->background() != NULL;
216   if (currently_enabled == enable_aa)
217     return;
218
219   if (enable_aa) {
220     title_->SetBackgroundColor(app_list::kContentsBackgroundColor);
221     title_->set_background(views::Background::CreateSolidBackground(
222         app_list::kContentsBackgroundColor));
223   } else {
224     // In other cases, keep the background transparent to ensure correct
225     // interactions with animations. This will temporarily disable subpixel AA.
226     title_->SetBackgroundColor(0);
227     title_->set_background(NULL);
228   }
229   title_->Invalidate();
230   title_->SchedulePaint();
231 }
232
233 void AppListItemView::Prerender() {
234   title_->PaintToBackingImage();
235 }
236
237 void AppListItemView::CancelContextMenu() {
238   if (context_menu_runner_)
239     context_menu_runner_->Cancel();
240 }
241
242 gfx::ImageSkia AppListItemView::GetDragImage() {
243   return icon_->GetImage();
244 }
245
246 void AppListItemView::OnDragEnded() {
247   mouse_drag_timer_.Stop();
248   SetUIState(UI_STATE_NORMAL);
249 }
250
251 gfx::Point AppListItemView::GetDragImageOffset() {
252   gfx::Point image = icon_->GetImageBounds().origin();
253   return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
254 }
255
256 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
257   if (is_target_folder)
258     SetUIState(UI_STATE_DROPPING_IN_FOLDER);
259   else
260     SetUIState(UI_STATE_NORMAL);
261 }
262
263 void AppListItemView::ItemIconChanged() {
264   UpdateIcon();
265 }
266
267 void AppListItemView::ItemNameChanged() {
268   title_->SetText(base::UTF8ToUTF16(item_->GetDisplayName()));
269   title_->Invalidate();
270   UpdateTooltip();
271   // Use full name for accessibility.
272   SetAccessibleName(item_->GetItemType() == AppListFolderItem::kItemType
273                         ? l10n_util::GetStringFUTF16(
274                               IDS_APP_LIST_FOLDER_BUTTON_ACCESSIBILE_NAME,
275                               base::UTF8ToUTF16(item_->name()))
276                         : base::UTF8ToUTF16(item_->name()));
277   Layout();
278 }
279
280 void AppListItemView::ItemHighlightedChanged() {
281   apps_grid_view_->EnsureViewVisible(this);
282   SchedulePaint();
283 }
284
285 void AppListItemView::ItemIsInstallingChanged() {
286   if (item_->is_installing())
287     apps_grid_view_->EnsureViewVisible(this);
288   title_->SetVisible(!item_->is_installing());
289   progress_bar_->SetVisible(item_->is_installing());
290   SchedulePaint();
291 }
292
293 void AppListItemView::ItemPercentDownloadedChanged() {
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 (item_->percent_downloaded() == -1)
298     return;
299   progress_bar_->SetValue(item_->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 + icon_size_.height() + 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 (item_->highlighted() && !item_->is_installing()) {
341     canvas->FillRect(rect, kHighlightedColor);
342     return;
343   } else if (apps_grid_view_->IsSelectedView(this)) {
344       canvas->FillRect(rect, kSelectedColor);
345   }
346
347   if (!switches::IsFolderUIEnabled()) {
348     if (apps_grid_view_->IsSelectedView(this)) {
349       canvas->FillRect(rect, kSelectedColor);
350     } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
351       canvas->FillRect(rect, kHighlightedColor);
352     }
353   } else if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
354     // Draw folder dropping preview circle.
355     gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
356                                    icon_->y() + icon_->size().height() / 2);
357     SkPaint paint;
358     paint.setStyle(SkPaint::kFill_Style);
359     paint.setAntiAlias(true);
360     paint.setColor(kFolderBubbleColor);
361     canvas->DrawCircle(center, kFolderPreviewRadius, paint);
362   }
363 }
364
365 void AppListItemView::ShowContextMenuForView(views::View* source,
366                                              const gfx::Point& point,
367                                              ui::MenuSourceType source_type) {
368   ui::MenuModel* menu_model = item_->GetContextMenuModel();
369   if (!menu_model)
370     return;
371
372   context_menu_runner_.reset(new views::MenuRunner(menu_model));
373   if (context_menu_runner_->RunMenuAt(GetWidget(),
374                                       NULL,
375                                       gfx::Rect(point, gfx::Size()),
376                                       views::MENU_ANCHOR_TOPLEFT,
377                                       source_type,
378                                       views::MenuRunner::HAS_MNEMONICS) ==
379       views::MenuRunner::MENU_DELETED) {
380     return;
381   }
382 }
383
384 void AppListItemView::StateChanged() {
385   const bool is_folder_ui_enabled = switches::IsFolderUIEnabled();
386   if (is_folder_ui_enabled)
387     apps_grid_view_->ClearAnySelectedView();
388
389   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
390     if (!is_folder_ui_enabled)
391       apps_grid_view_->SetSelectedView(this);
392     title_->SetEnabledColor(kGridTitleHoverColor);
393   } else {
394     if (!is_folder_ui_enabled)
395       apps_grid_view_->ClearSelectedView(this);
396     item_->SetHighlighted(false);
397     title_->SetEnabledColor(kGridTitleColor);
398   }
399   title_->Invalidate();
400 }
401
402 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
403   // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
404   // background does not show up during scroll.
405   if (event.type() == ui::ET_GESTURE_TAP_DOWN)
406     return false;
407
408   return views::CustomButton::ShouldEnterPushedState(event);
409 }
410
411 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
412   CustomButton::OnMousePressed(event);
413
414   if (!ShouldEnterPushedState(event))
415     return true;
416
417   apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
418
419   if (apps_grid_view_->IsDraggedView(this)) {
420     mouse_drag_timer_.Start(FROM_HERE,
421         base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
422         this, &AppListItemView::OnMouseDragTimer);
423   }
424   return true;
425 }
426
427 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
428   // Disable space key to press the button. The keyboard events received
429   // by this view are forwarded from a Textfield (SearchBoxView) and key
430   // released events are not forwarded. This leaves the button in pressed
431   // state.
432   if (event.key_code() == ui::VKEY_SPACE)
433     return false;
434
435   return CustomButton::OnKeyPressed(event);
436 }
437
438 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
439   CustomButton::OnMouseReleased(event);
440   apps_grid_view_->EndDrag(false);
441 }
442
443 void AppListItemView::OnMouseCaptureLost() {
444   // We don't cancel the dag on mouse capture lost for windows as entering a
445   // synchronous drag causes mouse capture to be lost and pressing escape
446   // dismisses the app list anyway.
447 #if !defined(OS_WIN)
448   CustomButton::OnMouseCaptureLost();
449   apps_grid_view_->EndDrag(true);
450 #endif
451 }
452
453 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
454   CustomButton::OnMouseDragged(event);
455   if (apps_grid_view_->IsDraggedView(this)) {
456     // If the drag is no longer happening, it could be because this item
457     // got removed, in which case this item has been destroyed. So, bail out
458     // now as there will be nothing else to do anyway as
459     // apps_grid_view_->dragging() will be false.
460     if (!apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event))
461       return true;
462   }
463
464   // Shows dragging UI when it's confirmed without waiting for the timer.
465   if (ui_state_ != UI_STATE_DRAGGING &&
466       apps_grid_view_->dragging() &&
467       apps_grid_view_->IsDraggedView(this)) {
468     mouse_drag_timer_.Stop();
469     SetUIState(UI_STATE_DRAGGING);
470   }
471   return true;
472 }
473
474 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
475   switch (event->type()) {
476     case ui::ET_GESTURE_SCROLL_BEGIN:
477       if (touch_dragging_) {
478         apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
479         event->SetHandled();
480       }
481       break;
482     case ui::ET_GESTURE_SCROLL_UPDATE:
483       if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
484         apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
485         event->SetHandled();
486       }
487       break;
488     case ui::ET_GESTURE_SCROLL_END:
489     case ui::ET_SCROLL_FLING_START:
490       if (touch_dragging_) {
491         SetTouchDragging(false);
492         apps_grid_view_->EndDrag(false);
493         event->SetHandled();
494       }
495       break;
496     case ui::ET_GESTURE_LONG_PRESS:
497       if (!apps_grid_view_->has_dragged_view())
498         SetTouchDragging(true);
499       event->SetHandled();
500       break;
501     case ui::ET_GESTURE_LONG_TAP:
502     case ui::ET_GESTURE_END:
503       if (touch_dragging_)
504         SetTouchDragging(false);
505       break;
506     default:
507       break;
508   }
509   if (!event->handled())
510     CustomButton::OnGestureEvent(event);
511 }
512
513 void AppListItemView::OnSyncDragEnd() {
514   SetUIState(UI_STATE_NORMAL);
515 }
516
517 const gfx::Rect& AppListItemView::GetIconBounds() const {
518   return icon_->bounds();
519 }
520
521 void AppListItemView::SetDragUIState() {
522   SetUIState(UI_STATE_DRAGGING);
523 }
524
525 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
526     const gfx::Rect& target_bounds) {
527   gfx::Rect rect(target_bounds);
528
529   const int left_right_padding =
530       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
531   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
532
533   gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), icon_size_.height());
534   icon_bounds.Inset(gfx::ShadowValue::GetMargin(icon_shadows_));
535   return icon_bounds;
536 }
537
538 }  // namespace app_list