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