Upstream version 5.34.104.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/app_list/app_list_constants.h"
11 #include "ui/app_list/app_list_item.h"
12 #include "ui/app_list/app_list_switches.h"
13 #include "ui/app_list/views/apps_grid_view.h"
14 #include "ui/app_list/views/cached_label.h"
15 #include "ui/app_list/views/progress_bar_view.h"
16 #include "ui/base/accessibility/accessible_view_state.h"
17 #include "ui/base/dragdrop/drag_utils.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/compositor/layer.h"
20 #include "ui/compositor/scoped_layer_animation_settings.h"
21 #include "ui/gfx/animation/throb_animation.h"
22 #include "ui/gfx/canvas.h"
23 #include "ui/gfx/font_list.h"
24 #include "ui/gfx/image/image_skia_operations.h"
25 #include "ui/gfx/point.h"
26 #include "ui/gfx/transform_util.h"
27 #include "ui/views/controls/image_view.h"
28 #include "ui/views/controls/label.h"
29 #include "ui/views/controls/menu/menu_item_view.h"
30 #include "ui/views/controls/menu/menu_runner.h"
31 #include "ui/views/drag_controller.h"
32
33 namespace app_list {
34
35 namespace {
36
37 const int kTopPadding = 20;
38 const int kIconTitleSpacing = 7;
39 const int kProgressBarHorizontalPadding = 12;
40
41 // Radius of the folder dropping preview circle.
42 const int kFolderPreviewRadius = 40;
43
44 const int kLeftRightPaddingChars = 1;
45
46 // Scale to transform the icon when a drag starts.
47 const float kDraggingIconScale = 1.5f;
48
49 // Delay in milliseconds of when the dragging UI should be shown for mouse drag.
50 const int kMouseDragUIDelayInMs = 200;
51
52 }  // namespace
53
54 // static
55 const char AppListItemView::kViewClassName[] = "ui/app_list/AppListItemView";
56
57 AppListItemView::AppListItemView(AppsGridView* apps_grid_view,
58                                  AppListItem* item)
59     : CustomButton(apps_grid_view),
60       item_(item),
61       apps_grid_view_(apps_grid_view),
62       icon_(new views::ImageView),
63       title_(new CachedLabel),
64       progress_bar_(new ProgressBarView),
65       ui_state_(UI_STATE_NORMAL),
66       touch_dragging_(false) {
67   icon_->set_interactive(false);
68
69   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
70   title_->SetBackgroundColor(0);
71   title_->SetAutoColorReadabilityEnabled(false);
72   title_->SetEnabledColor(kGridTitleColor);
73   title_->SetFontList(rb.GetFontList(kItemTextFontStyle));
74   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
75   title_->SetVisible(!item_->is_installing());
76   title_->Invalidate();
77
78   const gfx::ShadowValue kIconShadows[] = {
79     gfx::ShadowValue(gfx::Point(0, 2), 2, SkColorSetARGB(0x24, 0, 0, 0)),
80   };
81   icon_shadows_.assign(kIconShadows, kIconShadows + arraysize(kIconShadows));
82
83   AddChildView(icon_);
84   AddChildView(title_);
85   AddChildView(progress_bar_);
86
87   ItemIconChanged();
88   ItemTitleChanged();
89   ItemIsInstallingChanged();
90   item_->AddObserver(this);
91
92   set_context_menu_controller(this);
93   set_request_focus_on_press(false);
94
95   SetAnimationDuration(0);
96 }
97
98 AppListItemView::~AppListItemView() {
99   item_->RemoveObserver(this);
100 }
101
102 void AppListItemView::SetIconSize(const gfx::Size& size) {
103   if (icon_size_ == size)
104     return;
105
106   icon_size_ = size;
107   UpdateIcon();
108 }
109
110 void AppListItemView::UpdateIcon() {
111   // Skip if |icon_size_| has not been determined.
112   if (icon_size_.IsEmpty())
113     return;
114
115   gfx::ImageSkia icon = item_->icon();
116   // Clear icon and bail out if item icon is empty.
117   if (icon.isNull()) {
118     icon_->SetImage(NULL);
119     return;
120   }
121
122   gfx::ImageSkia resized(gfx::ImageSkiaOperations::CreateResizedImage(icon,
123       skia::ImageOperations::RESIZE_BEST, icon_size_));
124   if (item_->has_shadow()) {
125     gfx::ImageSkia shadow(
126         gfx::ImageSkiaOperations::CreateImageWithDropShadow(resized,
127                                                             icon_shadows_));
128     icon_->SetImage(shadow);
129     return;
130   }
131
132   icon_->SetImage(resized);
133 }
134
135 void AppListItemView::UpdateTooltip() {
136   title_->SetTooltipText(item_->title() == item_->full_name() ? base::string16()
137                          : base::UTF8ToUTF16(item_->full_name()));
138 }
139
140 void AppListItemView::SetUIState(UIState state) {
141   if (ui_state_ == state)
142     return;
143
144   ui_state_ = state;
145
146 #if defined(USE_AURA)
147   switch (ui_state_) {
148     case UI_STATE_NORMAL:
149       title_->SetVisible(!item_->is_installing());
150       progress_bar_->SetVisible(item_->is_installing());
151       break;
152     case UI_STATE_DRAGGING:
153       title_->SetVisible(false);
154       progress_bar_->SetVisible(false);
155       break;
156     case UI_STATE_DROPPING_IN_FOLDER:
157       break;
158   }
159 #if !defined(OS_WIN)
160   ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
161   switch (ui_state_) {
162     case UI_STATE_NORMAL:
163       layer()->SetTransform(gfx::Transform());
164       break;
165     case UI_STATE_DRAGGING: {
166       const gfx::Rect bounds(layer()->bounds().size());
167       layer()->SetTransform(gfx::GetScaleTransform(
168           bounds.CenterPoint(),
169           kDraggingIconScale));
170       break;
171     }
172     case UI_STATE_DROPPING_IN_FOLDER:
173       break;
174   }
175
176   SchedulePaint();
177 #endif  // !OS_WIN
178 #endif  // USE_AURA
179 }
180
181 void AppListItemView::SetTouchDragging(bool touch_dragging) {
182   if (touch_dragging_ == touch_dragging)
183     return;
184
185   touch_dragging_ = touch_dragging;
186   SetUIState(touch_dragging_ ? UI_STATE_DRAGGING : UI_STATE_NORMAL);
187 }
188
189 void AppListItemView::OnMouseDragTimer() {
190   DCHECK(apps_grid_view_->IsDraggedView(this));
191   SetUIState(UI_STATE_DRAGGING);
192 }
193
194 void AppListItemView::Prerender() {
195   title_->PaintToBackingImage();
196 }
197
198 void AppListItemView::CancelContextMenu() {
199   if (context_menu_runner_)
200     context_menu_runner_->Cancel();
201 }
202
203 gfx::ImageSkia AppListItemView::GetDragImage() {
204   return icon_->GetImage();
205 }
206
207 void AppListItemView::OnDragEnded() {
208   mouse_drag_timer_.Stop();
209   SetUIState(UI_STATE_NORMAL);
210 }
211
212 gfx::Point AppListItemView::GetDragImageOffset() {
213   gfx::Point image = icon_->GetImageBounds().origin();
214   return gfx::Point(icon_->x() + image.x(), icon_->y() + image.y());
215 }
216
217 void AppListItemView::SetAsAttemptedFolderTarget(bool is_target_folder) {
218   if (is_target_folder)
219     SetUIState(UI_STATE_DROPPING_IN_FOLDER);
220   else
221     SetUIState(UI_STATE_NORMAL);
222 }
223
224 void AppListItemView::ItemIconChanged() {
225   UpdateIcon();
226 }
227
228 void AppListItemView::ItemTitleChanged() {
229   title_->SetText(base::UTF8ToUTF16(item_->title()));
230   title_->Invalidate();
231   UpdateTooltip();
232   Layout();
233 }
234
235 void AppListItemView::ItemHighlightedChanged() {
236   apps_grid_view_->EnsureViewVisible(this);
237   SchedulePaint();
238 }
239
240 void AppListItemView::ItemIsInstallingChanged() {
241   if (item_->is_installing())
242     apps_grid_view_->EnsureViewVisible(this);
243   title_->SetVisible(!item_->is_installing());
244   progress_bar_->SetVisible(item_->is_installing());
245   SchedulePaint();
246 }
247
248 void AppListItemView::ItemPercentDownloadedChanged() {
249   // A percent_downloaded() of -1 can mean it's not known how much percent is
250   // completed, or the download hasn't been marked complete, as is the case
251   // while an extension is being installed after being downloaded.
252   if (item_->percent_downloaded() == -1)
253     return;
254   progress_bar_->SetValue(item_->percent_downloaded() / 100.0);
255 }
256
257 const char* AppListItemView::GetClassName() const {
258   return kViewClassName;
259 }
260
261 void AppListItemView::Layout() {
262   gfx::Rect rect(GetContentsBounds());
263
264   const int left_right_padding =
265       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
266   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
267   const int y = rect.y();
268
269   icon_->SetBoundsRect(GetIconBoundsForTargetViewBounds(GetContentsBounds()));
270   const gfx::Size title_size = title_->GetPreferredSize();
271   gfx::Rect title_bounds(rect.x() + (rect.width() - title_size.width()) / 2,
272                          y + icon_size_.height() + kIconTitleSpacing,
273                          title_size.width(),
274                          title_size.height());
275   title_bounds.Intersect(rect);
276   title_->SetBoundsRect(title_bounds);
277
278   gfx::Rect progress_bar_bounds(progress_bar_->GetPreferredSize());
279   progress_bar_bounds.set_x(GetContentsBounds().x() +
280                             kProgressBarHorizontalPadding);
281   progress_bar_bounds.set_y(title_bounds.y());
282   progress_bar_->SetBoundsRect(progress_bar_bounds);
283 }
284
285 void AppListItemView::OnPaint(gfx::Canvas* canvas) {
286   if (apps_grid_view_->IsDraggedView(this))
287     return;
288
289   gfx::Rect rect(GetContentsBounds());
290   if (item_->highlighted() && !item_->is_installing()) {
291     canvas->FillRect(rect, kHighlightedColor);
292     return;
293   } else if (apps_grid_view_->IsSelectedView(this)) {
294       canvas->FillRect(rect, kSelectedColor);
295   }
296
297   if (!switches::IsFolderUIEnabled()) {
298     if (apps_grid_view_->IsSelectedView(this)) {
299       canvas->FillRect(rect, kSelectedColor);
300     } else if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
301       canvas->FillRect(rect, kHighlightedColor);
302     }
303   } else if (ui_state_ == UI_STATE_DROPPING_IN_FOLDER) {
304     // Draw folder dropping preview circle.
305     gfx::Point center = gfx::Point(icon_->x() + icon_->size().width() / 2,
306                                    icon_->y() + icon_->size().height() / 2);
307     SkPaint paint;
308     paint.setStyle(SkPaint::kFill_Style);
309     paint.setAntiAlias(true);
310     paint.setColor(kFolderBubbleColor);
311     canvas->DrawCircle(center, kFolderPreviewRadius, paint);
312   }
313 }
314
315 void AppListItemView::GetAccessibleState(ui::AccessibleViewState* state) {
316   state->role = ui::AccessibilityTypes::ROLE_PUSHBUTTON;
317   state->name = base::UTF8ToUTF16(item_->title());
318 }
319
320 void AppListItemView::ShowContextMenuForView(views::View* source,
321                                              const gfx::Point& point,
322                                              ui::MenuSourceType source_type) {
323   ui::MenuModel* menu_model = item_->GetContextMenuModel();
324   if (!menu_model)
325     return;
326
327   context_menu_runner_.reset(new views::MenuRunner(menu_model));
328   if (context_menu_runner_->RunMenuAt(
329           GetWidget(), NULL, gfx::Rect(point, gfx::Size()),
330           views::MenuItemView::TOPLEFT, source_type,
331           views::MenuRunner::HAS_MNEMONICS) ==
332       views::MenuRunner::MENU_DELETED)
333     return;
334 }
335
336 void AppListItemView::StateChanged() {
337   const bool is_folder_ui_enabled = switches::IsFolderUIEnabled();
338   if (is_folder_ui_enabled)
339     apps_grid_view_->ClearAnySelectedView();
340
341   if (state() == STATE_HOVERED || state() == STATE_PRESSED) {
342     if (!is_folder_ui_enabled)
343       apps_grid_view_->SetSelectedView(this);
344     title_->SetEnabledColor(kGridTitleHoverColor);
345   } else {
346     if (!is_folder_ui_enabled)
347       apps_grid_view_->ClearSelectedView(this);
348     item_->SetHighlighted(false);
349     title_->SetEnabledColor(kGridTitleColor);
350   }
351   title_->Invalidate();
352 }
353
354 bool AppListItemView::ShouldEnterPushedState(const ui::Event& event) {
355   // Don't enter pushed state for ET_GESTURE_TAP_DOWN so that hover gray
356   // background does not show up during scroll.
357   if (event.type() == ui::ET_GESTURE_TAP_DOWN)
358     return false;
359
360   return views::CustomButton::ShouldEnterPushedState(event);
361 }
362
363 bool AppListItemView::OnMousePressed(const ui::MouseEvent& event) {
364   CustomButton::OnMousePressed(event);
365
366   if (!ShouldEnterPushedState(event))
367     return true;
368
369   apps_grid_view_->InitiateDrag(this, AppsGridView::MOUSE, event);
370
371   if (apps_grid_view_->IsDraggedView(this)) {
372     mouse_drag_timer_.Start(FROM_HERE,
373         base::TimeDelta::FromMilliseconds(kMouseDragUIDelayInMs),
374         this, &AppListItemView::OnMouseDragTimer);
375   }
376   return true;
377 }
378
379 bool AppListItemView::OnKeyPressed(const ui::KeyEvent& event) {
380   // Disable space key to press the button. The keyboard events received
381   // by this view are forwarded from a Textfield (SearchBoxView) and key
382   // released events are not forwarded. This leaves the button in pressed
383   // state.
384   if (event.key_code() == ui::VKEY_SPACE)
385     return false;
386
387   return CustomButton::OnKeyPressed(event);
388 }
389
390 void AppListItemView::OnMouseReleased(const ui::MouseEvent& event) {
391   CustomButton::OnMouseReleased(event);
392   apps_grid_view_->EndDrag(false);
393 }
394
395 void AppListItemView::OnMouseCaptureLost() {
396   // We don't cancel the dag on mouse capture lost for windows as entering a
397   // synchronous drag causes mouse capture to be lost and pressing escape
398   // dismisses the app list anyway.
399 #if !defined(OS_WIN)
400   CustomButton::OnMouseCaptureLost();
401   apps_grid_view_->EndDrag(true);
402 #endif
403 }
404
405 bool AppListItemView::OnMouseDragged(const ui::MouseEvent& event) {
406   CustomButton::OnMouseDragged(event);
407   if (apps_grid_view_->IsDraggedView(this))
408     apps_grid_view_->UpdateDragFromItem(AppsGridView::MOUSE, event);
409
410   // Shows dragging UI when it's confirmed without waiting for the timer.
411   if (ui_state_ != UI_STATE_DRAGGING &&
412       apps_grid_view_->dragging() &&
413       apps_grid_view_->IsDraggedView(this)) {
414     mouse_drag_timer_.Stop();
415     SetUIState(UI_STATE_DRAGGING);
416   }
417   return true;
418 }
419
420 void AppListItemView::OnGestureEvent(ui::GestureEvent* event) {
421   switch (event->type()) {
422     case ui::ET_GESTURE_SCROLL_BEGIN:
423       if (touch_dragging_) {
424         apps_grid_view_->InitiateDrag(this, AppsGridView::TOUCH, *event);
425         event->SetHandled();
426       }
427       break;
428     case ui::ET_GESTURE_SCROLL_UPDATE:
429       if (touch_dragging_ && apps_grid_view_->IsDraggedView(this)) {
430         apps_grid_view_->UpdateDragFromItem(AppsGridView::TOUCH, *event);
431         event->SetHandled();
432       }
433       break;
434     case ui::ET_GESTURE_SCROLL_END:
435     case ui::ET_SCROLL_FLING_START:
436       if (touch_dragging_) {
437         SetTouchDragging(false);
438         apps_grid_view_->EndDrag(false);
439         event->SetHandled();
440       }
441       break;
442     case ui::ET_GESTURE_LONG_PRESS:
443       if (!apps_grid_view_->has_dragged_view())
444         SetTouchDragging(true);
445       event->SetHandled();
446       break;
447     case ui::ET_GESTURE_LONG_TAP:
448     case ui::ET_GESTURE_END:
449       if (touch_dragging_)
450         SetTouchDragging(false);
451       break;
452     default:
453       break;
454   }
455   if (!event->handled())
456     CustomButton::OnGestureEvent(event);
457 }
458
459 void AppListItemView::OnSyncDragEnd() {
460   SetUIState(UI_STATE_NORMAL);
461 }
462
463 const gfx::Rect& AppListItemView::GetIconBounds() const {
464   return icon_->bounds();
465 }
466
467 void AppListItemView::SetDragUIState() {
468   SetUIState(UI_STATE_DRAGGING);
469 }
470
471 gfx::Rect AppListItemView::GetIconBoundsForTargetViewBounds(
472     const gfx::Rect& target_bounds) {
473   gfx::Rect rect(target_bounds);
474
475   const int left_right_padding =
476       title_->font_list().GetExpectedTextWidth(kLeftRightPaddingChars);
477   rect.Inset(left_right_padding, kTopPadding, left_right_padding, 0);
478
479   gfx::Rect icon_bounds(rect.x(), rect.y(), rect.width(), icon_size_.height());
480   icon_bounds.Inset(gfx::ShadowValue::GetMargin(icon_shadows_));
481   return icon_bounds;
482 }
483
484 }  // namespace app_list