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 "ash/shelf/shelf_view.h"
6
7#include <algorithm>
8
9#include "ash/ash_constants.h"
10#include "ash/ash_switches.h"
11#include "ash/drag_drop/drag_image_view.h"
12#include "ash/metrics/user_metrics_recorder.h"
13#include "ash/root_window_controller.h"
14#include "ash/scoped_target_root_window.h"
15#include "ash/shelf/app_list_button.h"
16#include "ash/shelf/overflow_bubble.h"
17#include "ash/shelf/overflow_bubble_view.h"
18#include "ash/shelf/overflow_button.h"
19#include "ash/shelf/shelf_button.h"
20#include "ash/shelf/shelf_constants.h"
21#include "ash/shelf/shelf_delegate.h"
22#include "ash/shelf/shelf_icon_observer.h"
23#include "ash/shelf/shelf_item_delegate.h"
24#include "ash/shelf/shelf_item_delegate_manager.h"
25#include "ash/shelf/shelf_layout_manager.h"
26#include "ash/shelf/shelf_menu_model.h"
27#include "ash/shelf/shelf_model.h"
28#include "ash/shelf/shelf_tooltip_manager.h"
29#include "ash/shelf/shelf_widget.h"
30#include "ash/shell.h"
31#include "ash/wm/coordinate_conversion.h"
32#include "base/auto_reset.h"
33#include "base/memory/scoped_ptr.h"
34#include "base/metrics/histogram.h"
35#include "grit/ash_strings.h"
36#include "ui/accessibility/ax_view_state.h"
37#include "ui/aura/client/screen_position_client.h"
38#include "ui/aura/window.h"
39#include "ui/aura/window_event_dispatcher.h"
40#include "ui/base/l10n/l10n_util.h"
41#include "ui/base/models/simple_menu_model.h"
42#include "ui/base/resource/resource_bundle.h"
43#include "ui/compositor/layer.h"
44#include "ui/compositor/layer_animator.h"
45#include "ui/compositor/scoped_animation_duration_scale_mode.h"
46#include "ui/gfx/canvas.h"
47#include "ui/gfx/point.h"
48#include "ui/views/animation/bounds_animator.h"
49#include "ui/views/border.h"
50#include "ui/views/controls/button/image_button.h"
51#include "ui/views/controls/menu/menu_model_adapter.h"
52#include "ui/views/controls/menu/menu_runner.h"
53#include "ui/views/focus/focus_search.h"
54#include "ui/views/view_model.h"
55#include "ui/views/view_model_utils.h"
56#include "ui/views/widget/widget.h"
57#include "ui/wm/core/coordinate_conversion.h"
58
59using gfx::Animation;
60using views::View;
61
62namespace ash {
63
64const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM = 0;
65const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT = 1;
66const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT = 2;
67const int SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT = 3;
68
69// Default amount content is inset on the left edge.
70const int kDefaultLeadingInset = 8;
71
72// Minimum distance before drag starts.
73const int kMinimumDragDistance = 8;
74
75// Additional spacing for the left and right side of icons.
76const int kHorizontalIconSpacing = 2;
77
78// Inset for items which do not have an icon.
79const int kHorizontalNoIconInsetSpacing =
80    kHorizontalIconSpacing + kDefaultLeadingInset;
81
82// The proportion of the shelf space reserved for non-panel icons. Panels
83// may flow into this space but will be put into the overflow bubble if there
84// is contention for the space.
85const float kReservedNonPanelIconProportion = 0.67f;
86
87// This is the command id of the menu item which contains the name of the menu.
88const int kCommandIdOfMenuName = 0;
89
90// The background color of the active item in the list.
91const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
92
93// The background color of the active & hovered item in the list.
94const SkColor kFocusedActiveListItemBackgroundColor =
95    SkColorSetRGB(193, 211, 236);
96
97// The text color of the caption item in a list.
98const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
99
100// The maximum allowable length of a menu line of an application menu in pixels.
101const int kMaximumAppMenuItemLength = 350;
102
103// The distance of the cursor from the outer rim of the shelf before it
104// separates.
105const int kRipOffDistance = 48;
106
107// The rip off drag and drop proxy image should get scaled by this factor.
108const float kDragAndDropProxyScale = 1.5f;
109
110// The opacity represents that this partially disappeared item will get removed.
111const float kDraggedImageOpacity = 0.5f;
112
113namespace {
114
115// A class to temporarily disable a given bounds animator.
116class BoundsAnimatorDisabler {
117 public:
118  explicit BoundsAnimatorDisabler(views::BoundsAnimator* bounds_animator)
119      : old_duration_(bounds_animator->GetAnimationDuration()),
120        bounds_animator_(bounds_animator) {
121    bounds_animator_->SetAnimationDuration(1);
122  }
123
124  ~BoundsAnimatorDisabler() {
125    bounds_animator_->SetAnimationDuration(old_duration_);
126  }
127
128 private:
129  // The previous animation duration.
130  int old_duration_;
131  // The bounds animator which gets used.
132  views::BoundsAnimator* bounds_animator_;
133
134  DISALLOW_COPY_AND_ASSIGN(BoundsAnimatorDisabler);
135};
136
137// The MenuModelAdapter gets slightly changed to adapt the menu appearance to
138// our requirements.
139class ShelfMenuModelAdapter : public views::MenuModelAdapter {
140 public:
141  explicit ShelfMenuModelAdapter(ShelfMenuModel* menu_model);
142
143  // views::MenuModelAdapter:
144  virtual const gfx::FontList* GetLabelFontList(int command_id) const OVERRIDE;
145  virtual bool IsCommandEnabled(int id) const OVERRIDE;
146  virtual void GetHorizontalIconMargins(int id,
147                                        int icon_size,
148                                        int* left_margin,
149                                        int* right_margin) const OVERRIDE;
150  virtual bool GetForegroundColor(int command_id,
151                                  bool is_hovered,
152                                  SkColor* override_color) const OVERRIDE;
153  virtual bool GetBackgroundColor(int command_id,
154                                  bool is_hovered,
155                                  SkColor* override_color) const OVERRIDE;
156  virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
157  virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
158
159 private:
160  ShelfMenuModel* menu_model_;
161
162  DISALLOW_COPY_AND_ASSIGN(ShelfMenuModelAdapter);
163};
164
165ShelfMenuModelAdapter::ShelfMenuModelAdapter(ShelfMenuModel* menu_model)
166    : MenuModelAdapter(menu_model),
167      menu_model_(menu_model) {
168}
169
170const gfx::FontList* ShelfMenuModelAdapter::GetLabelFontList(
171    int command_id) const {
172  if (command_id != kCommandIdOfMenuName)
173    return MenuModelAdapter::GetLabelFontList(command_id);
174
175  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
176  return &rb.GetFontList(ui::ResourceBundle::BoldFont);
177}
178
179bool ShelfMenuModelAdapter::IsCommandEnabled(int id) const {
180  return id != kCommandIdOfMenuName;
181}
182
183bool ShelfMenuModelAdapter::GetForegroundColor(int command_id,
184                                               bool is_hovered,
185                                               SkColor* override_color) const {
186  if (command_id != kCommandIdOfMenuName)
187    return false;
188
189  *override_color = kCaptionItemForegroundColor;
190  return true;
191}
192
193bool ShelfMenuModelAdapter::GetBackgroundColor(int command_id,
194                                               bool is_hovered,
195                                               SkColor* override_color) const {
196  if (!menu_model_->IsCommandActive(command_id))
197    return false;
198
199  *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
200                                 kActiveListItemBackgroundColor;
201  return true;
202}
203
204void ShelfMenuModelAdapter::GetHorizontalIconMargins(int command_id,
205                                                     int icon_size,
206                                                     int* left_margin,
207                                                     int* right_margin) const {
208  *left_margin = kHorizontalIconSpacing;
209  *right_margin = (command_id != kCommandIdOfMenuName) ?
210      kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
211}
212
213int ShelfMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
214  return kMaximumAppMenuItemLength;
215}
216
217bool ShelfMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
218  return false;
219}
220
221// Custom FocusSearch used to navigate the shelf in the order items are in
222// the ViewModel.
223class ShelfFocusSearch : public views::FocusSearch {
224 public:
225  explicit ShelfFocusSearch(views::ViewModel* view_model)
226      : FocusSearch(NULL, true, true),
227        view_model_(view_model) {}
228  virtual ~ShelfFocusSearch() {}
229
230  // views::FocusSearch overrides:
231  virtual View* FindNextFocusableView(
232      View* starting_view,
233      bool reverse,
234      Direction direction,
235      bool check_starting_view,
236      views::FocusTraversable** focus_traversable,
237      View** focus_traversable_view) OVERRIDE {
238    int index = view_model_->GetIndexOfView(starting_view);
239    if (index == -1)
240      return view_model_->view_at(0);
241
242    if (reverse) {
243      --index;
244      if (index < 0)
245        index = view_model_->view_size() - 1;
246    } else {
247      ++index;
248      if (index >= view_model_->view_size())
249        index = 0;
250    }
251    return view_model_->view_at(index);
252  }
253
254 private:
255  views::ViewModel* view_model_;
256
257  DISALLOW_COPY_AND_ASSIGN(ShelfFocusSearch);
258};
259
260// AnimationDelegate used when inserting a new item. This steadily increases the
261// opacity of the layer as the animation progress.
262class FadeInAnimationDelegate : public gfx::AnimationDelegate {
263 public:
264  explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
265  virtual ~FadeInAnimationDelegate() {}
266
267  // AnimationDelegate overrides:
268  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
269    view_->layer()->SetOpacity(animation->GetCurrentValue());
270    view_->layer()->ScheduleDraw();
271  }
272  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
273    view_->layer()->SetOpacity(1.0f);
274    view_->layer()->ScheduleDraw();
275  }
276  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
277    view_->layer()->SetOpacity(1.0f);
278    view_->layer()->ScheduleDraw();
279  }
280
281 private:
282  views::View* view_;
283
284  DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
285};
286
287void ReflectItemStatus(const ShelfItem& item, ShelfButton* button) {
288  switch (item.status) {
289    case STATUS_CLOSED:
290      button->ClearState(ShelfButton::STATE_ACTIVE);
291      button->ClearState(ShelfButton::STATE_RUNNING);
292      button->ClearState(ShelfButton::STATE_ATTENTION);
293      break;
294    case STATUS_RUNNING:
295      button->ClearState(ShelfButton::STATE_ACTIVE);
296      button->AddState(ShelfButton::STATE_RUNNING);
297      button->ClearState(ShelfButton::STATE_ATTENTION);
298      break;
299    case STATUS_ACTIVE:
300      button->AddState(ShelfButton::STATE_ACTIVE);
301      button->ClearState(ShelfButton::STATE_RUNNING);
302      button->ClearState(ShelfButton::STATE_ATTENTION);
303      break;
304    case STATUS_ATTENTION:
305      button->ClearState(ShelfButton::STATE_ACTIVE);
306      button->ClearState(ShelfButton::STATE_RUNNING);
307      button->AddState(ShelfButton::STATE_ATTENTION);
308      break;
309  }
310}
311
312}  // namespace
313
314// AnimationDelegate used when deleting an item. This steadily decreased the
315// opacity of the layer as the animation progress.
316class ShelfView::FadeOutAnimationDelegate : public gfx::AnimationDelegate {
317 public:
318  FadeOutAnimationDelegate(ShelfView* host, views::View* view)
319      : shelf_view_(host),
320        view_(view) {}
321  virtual ~FadeOutAnimationDelegate() {}
322
323  // AnimationDelegate overrides:
324  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
325    view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
326    view_->layer()->ScheduleDraw();
327  }
328  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
329    shelf_view_->OnFadeOutAnimationEnded();
330  }
331  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
332  }
333
334 private:
335  ShelfView* shelf_view_;
336  scoped_ptr<views::View> view_;
337
338  DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
339};
340
341// AnimationDelegate used to trigger fading an element in. When an item is
342// inserted this delegate is attached to the animation that expands the size of
343// the item.  When done it kicks off another animation to fade the item in.
344class ShelfView::StartFadeAnimationDelegate : public gfx::AnimationDelegate {
345 public:
346  StartFadeAnimationDelegate(ShelfView* host,
347                             views::View* view)
348      : shelf_view_(host),
349        view_(view) {}
350  virtual ~StartFadeAnimationDelegate() {}
351
352  // AnimationDelegate overrides:
353  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
354    shelf_view_->FadeIn(view_);
355  }
356  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
357    view_->layer()->SetOpacity(1.0f);
358  }
359
360 private:
361  ShelfView* shelf_view_;
362  views::View* view_;
363
364  DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
365};
366
367ShelfView::ShelfView(ShelfModel* model,
368                     ShelfDelegate* delegate,
369                     ShelfLayoutManager* manager)
370    : model_(model),
371      delegate_(delegate),
372      view_model_(new views::ViewModel),
373      first_visible_index_(0),
374      last_visible_index_(-1),
375      overflow_button_(NULL),
376      owner_overflow_bubble_(NULL),
377      drag_pointer_(NONE),
378      drag_view_(NULL),
379      start_drag_index_(-1),
380      context_menu_id_(0),
381      leading_inset_(kDefaultLeadingInset),
382      cancelling_drag_model_changed_(false),
383      last_hidden_index_(0),
384      closing_event_time_(base::TimeDelta()),
385      got_deleted_(NULL),
386      drag_and_drop_item_pinned_(false),
387      drag_and_drop_shelf_id_(0),
388      dragged_off_shelf_(false),
389      snap_back_from_rip_off_view_(NULL),
390      item_manager_(Shell::GetInstance()->shelf_item_delegate_manager()),
391      layout_manager_(manager),
392      overflow_mode_(false),
393      main_shelf_(NULL),
394      dragged_off_from_overflow_to_shelf_(false) {
395  DCHECK(model_);
396  bounds_animator_.reset(new views::BoundsAnimator(this));
397  bounds_animator_->AddObserver(this);
398  set_context_menu_controller(this);
399  focus_search_.reset(new ShelfFocusSearch(view_model_.get()));
400  tooltip_.reset(new ShelfTooltipManager(manager, this));
401}
402
403ShelfView::~ShelfView() {
404  bounds_animator_->RemoveObserver(this);
405  model_->RemoveObserver(this);
406  // If we are inside the MenuRunner, we need to know if we were getting
407  // deleted while it was running.
408  if (got_deleted_)
409    *got_deleted_ = true;
410}
411
412void ShelfView::Init() {
413  model_->AddObserver(this);
414
415  const ShelfItems& items(model_->items());
416  for (ShelfItems::const_iterator i = items.begin(); i != items.end(); ++i) {
417    views::View* child = CreateViewForItem(*i);
418    child->SetPaintToLayer(true);
419    view_model_->Add(child, static_cast<int>(i - items.begin()));
420    AddChildView(child);
421  }
422  overflow_button_ = new OverflowButton(this);
423  overflow_button_->set_context_menu_controller(this);
424  ConfigureChildView(overflow_button_);
425  AddChildView(overflow_button_);
426
427  // We'll layout when our bounds change.
428}
429
430void ShelfView::OnShelfAlignmentChanged() {
431  overflow_button_->OnShelfAlignmentChanged();
432  LayoutToIdealBounds();
433  for (int i = 0; i < view_model_->view_size(); ++i) {
434    if (i >= first_visible_index_ && i <= last_visible_index_)
435      view_model_->view_at(i)->Layout();
436  }
437  tooltip_->Close();
438  if (overflow_bubble_)
439    overflow_bubble_->Hide();
440}
441
442void ShelfView::SchedulePaintForAllButtons() {
443  for (int i = 0; i < view_model_->view_size(); ++i) {
444    if (i >= first_visible_index_ && i <= last_visible_index_)
445      view_model_->view_at(i)->SchedulePaint();
446  }
447  if (overflow_button_ && overflow_button_->visible())
448    overflow_button_->SchedulePaint();
449}
450
451gfx::Rect ShelfView::GetIdealBoundsOfItemIcon(ShelfID id) {
452  int index = model_->ItemIndexByID(id);
453  if (index == -1)
454    return gfx::Rect();
455  // Map all items from overflow area to the overflow button. Note that the
456  // section between last_index_hidden_ and model_->FirstPanelIndex() is the
457  // list of invisible panel items. However, these items are currently nowhere
458  // represented and get dropped instead - see (crbug.com/378907). As such there
459  // is no way to address them or place them. We therefore move them over the
460  // overflow button.
461  if (index > last_visible_index_ && index < model_->FirstPanelIndex())
462    index = last_visible_index_ + 1;
463  const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
464  DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
465  ShelfButton* button = static_cast<ShelfButton*>(view_model_->view_at(index));
466  gfx::Rect icon_bounds = button->GetIconBounds();
467  return gfx::Rect(GetMirroredXWithWidthInView(
468                       ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
469                   ideal_bounds.y() + icon_bounds.y(),
470                   icon_bounds.width(),
471                   icon_bounds.height());
472}
473
474void ShelfView::UpdatePanelIconPosition(ShelfID id,
475                                        const gfx::Point& midpoint) {
476  int current_index = model_->ItemIndexByID(id);
477  int first_panel_index = model_->FirstPanelIndex();
478  if (current_index < first_panel_index)
479    return;
480
481  gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
482                              midpoint.y());
483  int target_index = current_index;
484  while (target_index > first_panel_index &&
485         layout_manager_->PrimaryAxisValue(
486             view_model_->ideal_bounds(target_index).x(),
487             view_model_->ideal_bounds(target_index).y()) >
488         layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
489                                           midpoint_in_view.y())) {
490    --target_index;
491  }
492  while (target_index < view_model_->view_size() - 1 &&
493         layout_manager_->PrimaryAxisValue(
494             view_model_->ideal_bounds(target_index).right(),
495             view_model_->ideal_bounds(target_index).bottom()) <
496         layout_manager_->PrimaryAxisValue(midpoint_in_view.x(),
497                                           midpoint_in_view.y())) {
498    ++target_index;
499  }
500  if (current_index != target_index)
501    model_->Move(current_index, target_index);
502}
503
504bool ShelfView::IsShowingMenu() const {
505  return (launcher_menu_runner_.get() &&
506       launcher_menu_runner_->IsRunning());
507}
508
509bool ShelfView::IsShowingOverflowBubble() const {
510  return overflow_bubble_.get() && overflow_bubble_->IsShowing();
511}
512
513views::View* ShelfView::GetAppListButtonView() const {
514  for (int i = 0; i < model_->item_count(); ++i) {
515    if (model_->items()[i].type == TYPE_APP_LIST)
516      return view_model_->view_at(i);
517  }
518
519  NOTREACHED() << "Applist button not found";
520  return NULL;
521}
522
523////////////////////////////////////////////////////////////////////////////////
524// ShelfView, FocusTraversable implementation:
525
526views::FocusSearch* ShelfView::GetFocusSearch() {
527  return focus_search_.get();
528}
529
530views::FocusTraversable* ShelfView::GetFocusTraversableParent() {
531  return parent()->GetFocusTraversable();
532}
533
534View* ShelfView::GetFocusTraversableParentView() {
535  return this;
536}
537
538void ShelfView::CreateDragIconProxy(
539    const gfx::Point& location_in_screen_coordinates,
540    const gfx::ImageSkia& icon,
541    views::View* replaced_view,
542    const gfx::Vector2d& cursor_offset_from_center,
543    float scale_factor) {
544  drag_replaced_view_ = replaced_view;
545  drag_image_.reset(new ash::DragImageView(
546      drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow(),
547      ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE));
548  drag_image_->SetImage(icon);
549  gfx::Size size = drag_image_->GetPreferredSize();
550  size.set_width(size.width() * scale_factor);
551  size.set_height(size.height() * scale_factor);
552  drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
553                       cursor_offset_from_center;
554  gfx::Rect drag_image_bounds(
555      location_in_screen_coordinates - drag_image_offset_,
556      size);
557  drag_image_->SetBoundsInScreen(drag_image_bounds);
558  drag_image_->SetWidgetVisible(true);
559}
560
561void ShelfView::UpdateDragIconProxy(
562    const gfx::Point& location_in_screen_coordinates) {
563  // TODO(jennyz): Investigate why drag_image_ becomes NULL at this point per
564  // crbug.com/34722, while the app list item is still being dragged around.
565  if (drag_image_) {
566    drag_image_->SetScreenPosition(
567        location_in_screen_coordinates - drag_image_offset_);
568  }
569}
570
571void ShelfView::DestroyDragIconProxy() {
572  drag_image_.reset();
573  drag_image_offset_ = gfx::Vector2d(0, 0);
574}
575
576bool ShelfView::StartDrag(const std::string& app_id,
577                          const gfx::Point& location_in_screen_coordinates) {
578  // Bail if an operation is already going on - or the cursor is not inside.
579  // This could happen if mouse / touch operations overlap.
580  if (drag_and_drop_shelf_id_ ||
581      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
582    return false;
583
584  // If the AppsGridView (which was dispatching this event) was opened by our
585  // button, ShelfView dragging operations are locked and we have to unlock.
586  CancelDrag(-1);
587  drag_and_drop_item_pinned_ = false;
588  drag_and_drop_app_id_ = app_id;
589  drag_and_drop_shelf_id_ =
590      delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
591  // Check if the application is known and pinned - if not, we have to pin it so
592  // that we can re-arrange the shelf order accordingly. Note that items have
593  // to be pinned to give them the same (order) possibilities as a shortcut.
594  // When an item is dragged from overflow to shelf, IsShowingOverflowBubble()
595  // returns true. At this time, we don't need to pin the item.
596  if (!IsShowingOverflowBubble() &&
597      (!drag_and_drop_shelf_id_ ||
598       !delegate_->IsAppPinned(app_id))) {
599    delegate_->PinAppWithID(app_id);
600    drag_and_drop_shelf_id_ =
601        delegate_->GetShelfIDForAppID(drag_and_drop_app_id_);
602    if (!drag_and_drop_shelf_id_)
603      return false;
604    drag_and_drop_item_pinned_ = true;
605  }
606  views::View* drag_and_drop_view = view_model_->view_at(
607      model_->ItemIndexByID(drag_and_drop_shelf_id_));
608  DCHECK(drag_and_drop_view);
609
610  // Since there is already an icon presented by the caller, we hide this item
611  // for now. That has to be done by reducing the size since the visibility will
612  // change once a regrouping animation is performed.
613  pre_drag_and_drop_size_ = drag_and_drop_view->size();
614  drag_and_drop_view->SetSize(gfx::Size());
615
616  // First we have to center the mouse cursor over the item.
617  gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
618  views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
619  gfx::Point point_in_root = location_in_screen_coordinates;
620  ::wm::ConvertPointFromScreen(
621      ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root);
622  ui::MouseEvent event(ui::ET_MOUSE_PRESSED, pt, point_in_root, 0, 0);
623  PointerPressedOnButton(drag_and_drop_view,
624                         ShelfButtonHost::DRAG_AND_DROP,
625                         event);
626
627  // Drag the item where it really belongs.
628  Drag(location_in_screen_coordinates);
629  return true;
630}
631
632bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) {
633  if (!drag_and_drop_shelf_id_ ||
634      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
635    return false;
636
637  gfx::Point pt = location_in_screen_coordinates;
638  views::View* drag_and_drop_view = view_model_->view_at(
639      model_->ItemIndexByID(drag_and_drop_shelf_id_));
640  ConvertPointFromScreen(drag_and_drop_view, &pt);
641  gfx::Point point_in_root = location_in_screen_coordinates;
642  ::wm::ConvertPointFromScreen(
643      ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root);
644  ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, point_in_root, 0, 0);
645  PointerDraggedOnButton(drag_and_drop_view,
646                         ShelfButtonHost::DRAG_AND_DROP,
647                         event);
648  return true;
649}
650
651void ShelfView::EndDrag(bool cancel) {
652  if (!drag_and_drop_shelf_id_)
653    return;
654
655  views::View* drag_and_drop_view = view_model_->view_at(
656      model_->ItemIndexByID(drag_and_drop_shelf_id_));
657  PointerReleasedOnButton(
658      drag_and_drop_view, ShelfButtonHost::DRAG_AND_DROP, cancel);
659
660  // Either destroy the temporarily created item - or - make the item visible.
661  if (drag_and_drop_item_pinned_ && cancel) {
662    delegate_->UnpinAppWithID(drag_and_drop_app_id_);
663  } else if (drag_and_drop_view) {
664    if (cancel) {
665      // When a hosted drag gets canceled, the item can remain in the same slot
666      // and it might have moved within the bounds. In that case the item need
667      // to animate back to its correct location.
668      AnimateToIdealBounds();
669    } else {
670      drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
671    }
672  }
673
674  drag_and_drop_shelf_id_ = 0;
675}
676
677void ShelfView::LayoutToIdealBounds() {
678  if (bounds_animator_->IsAnimating()) {
679    AnimateToIdealBounds();
680    return;
681  }
682
683  IdealBounds ideal_bounds;
684  CalculateIdealBounds(&ideal_bounds);
685  views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
686  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
687}
688
689void ShelfView::UpdateAllButtonsVisibilityInOverflowMode() {
690  // The overflow button is not shown in overflow mode.
691  overflow_button_->SetVisible(false);
692  DCHECK_LT(last_visible_index_, view_model_->view_size());
693  for (int i = 0; i < view_model_->view_size(); ++i) {
694    bool visible = i >= first_visible_index_ &&
695        i <= last_visible_index_;
696    // To track the dragging of |drag_view_| continuously, its visibility
697    // should be always true regardless of its position.
698    if (dragged_off_from_overflow_to_shelf_ &&
699        view_model_->view_at(i) == drag_view_)
700      view_model_->view_at(i)->SetVisible(true);
701    else
702      view_model_->view_at(i)->SetVisible(visible);
703  }
704}
705
706void ShelfView::CalculateIdealBounds(IdealBounds* bounds) const {
707  int available_size = layout_manager_->PrimaryAxisValue(width(), height());
708  DCHECK(model_->item_count() == view_model_->view_size());
709  if (!available_size)
710    return;
711
712  int first_panel_index = model_->FirstPanelIndex();
713  int last_button_index = first_panel_index - 1;
714
715  int x = 0;
716  int y = 0;
717  int button_size = kShelfButtonSize;
718  int button_spacing = kShelfButtonSpacing;
719
720  int w = layout_manager_->PrimaryAxisValue(button_size, width());
721  int h = layout_manager_->PrimaryAxisValue(height(), button_size);
722  for (int i = 0; i < view_model_->view_size(); ++i) {
723    if (i < first_visible_index_) {
724      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
725      continue;
726    }
727
728    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
729    if (i != last_button_index) {
730      x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x);
731      y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing);
732    }
733  }
734
735  if (is_overflow_mode()) {
736    const_cast<ShelfView*>(this)->UpdateAllButtonsVisibilityInOverflowMode();
737    return;
738  }
739
740  // Right aligned icons.
741  int end_position = available_size - button_spacing;
742  x = layout_manager_->PrimaryAxisValue(end_position, 0);
743  y = layout_manager_->PrimaryAxisValue(0, end_position);
744  for (int i = view_model_->view_size() - 1;
745       i >= first_panel_index; --i) {
746    x = layout_manager_->PrimaryAxisValue(x - w - button_spacing, x);
747    y = layout_manager_->PrimaryAxisValue(y, y - h - button_spacing);
748    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
749    end_position = layout_manager_->PrimaryAxisValue(x, y);
750  }
751
752  // Icons on the left / top are guaranteed up to kLeftIconProportion of
753  // the available space.
754  int last_icon_position = layout_manager_->PrimaryAxisValue(
755      view_model_->ideal_bounds(last_button_index).right(),
756      view_model_->ideal_bounds(last_button_index).bottom()) + button_size;
757  int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
758  if (last_icon_position < reserved_icon_space)
759    end_position = last_icon_position;
760  else
761    end_position = std::max(end_position, reserved_icon_space);
762
763  bounds->overflow_bounds.set_size(
764      gfx::Size(layout_manager_->PrimaryAxisValue(w, width()),
765                layout_manager_->PrimaryAxisValue(height(), h)));
766
767  last_visible_index_ = DetermineLastVisibleIndex(
768      end_position - button_size);
769  last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
770  bool show_overflow = last_visible_index_ < last_button_index ||
771      last_hidden_index_ >= first_panel_index;
772
773  // Create Space for the overflow button
774  if (show_overflow &&
775      last_visible_index_ > 0 && last_visible_index_ < last_button_index)
776    --last_visible_index_;
777  for (int i = 0; i < view_model_->view_size(); ++i) {
778    bool visible = i <= last_visible_index_ || i > last_hidden_index_;
779    // To receive drag event continuously from |drag_view_| during the dragging
780    // off from the shelf, don't make |drag_view_| invisible. It will be
781    // eventually invisible and removed from the |view_model_| by
782    // FinalizeRipOffDrag().
783    if (dragged_off_shelf_ && view_model_->view_at(i) == drag_view_)
784      continue;
785    view_model_->view_at(i)->SetVisible(visible);
786  }
787
788  overflow_button_->SetVisible(show_overflow);
789  if (show_overflow) {
790    DCHECK_NE(0, view_model_->view_size());
791    if (last_visible_index_ == -1) {
792      x = 0;
793      y = 0;
794    } else {
795      x = layout_manager_->PrimaryAxisValue(
796          view_model_->ideal_bounds(last_visible_index_).right(),
797          view_model_->ideal_bounds(last_visible_index_).x());
798      y = layout_manager_->PrimaryAxisValue(
799          view_model_->ideal_bounds(last_visible_index_).y(),
800          view_model_->ideal_bounds(last_visible_index_).bottom());
801    }
802    // Set all hidden panel icon positions to be on the overflow button.
803    for (int i = first_panel_index; i <= last_hidden_index_; ++i)
804      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
805
806    // Add more space between last visible item and overflow button.
807    // Without this, two buttons look too close compared with other items.
808    x = layout_manager_->PrimaryAxisValue(x + button_spacing, x);
809    y = layout_manager_->PrimaryAxisValue(y, y + button_spacing);
810
811    bounds->overflow_bounds.set_x(x);
812    bounds->overflow_bounds.set_y(y);
813    if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
814      UpdateOverflowRange(overflow_bubble_->shelf_view());
815  } else {
816    if (overflow_bubble_)
817      overflow_bubble_->Hide();
818  }
819}
820
821int ShelfView::DetermineLastVisibleIndex(int max_value) const {
822  int index = model_->FirstPanelIndex() - 1;
823  while (index >= 0 &&
824         layout_manager_->PrimaryAxisValue(
825             view_model_->ideal_bounds(index).right(),
826             view_model_->ideal_bounds(index).bottom()) > max_value) {
827    index--;
828  }
829  return index;
830}
831
832int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const {
833  int index = model_->FirstPanelIndex();
834  while (index < view_model_->view_size() &&
835         layout_manager_->PrimaryAxisValue(
836             view_model_->ideal_bounds(index).right(),
837             view_model_->ideal_bounds(index).bottom()) < min_value) {
838    ++index;
839  }
840  return index;
841}
842
843void ShelfView::AddIconObserver(ShelfIconObserver* observer) {
844  observers_.AddObserver(observer);
845}
846
847void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) {
848  observers_.RemoveObserver(observer);
849}
850
851void ShelfView::AnimateToIdealBounds() {
852  IdealBounds ideal_bounds;
853  CalculateIdealBounds(&ideal_bounds);
854  for (int i = 0; i < view_model_->view_size(); ++i) {
855    View* view = view_model_->view_at(i);
856    bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
857    // Now that the item animation starts, we have to make sure that the
858    // padding of the first gets properly transferred to the new first item.
859    if (i && view->border())
860      view->SetBorder(views::Border::NullBorder());
861  }
862  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
863}
864
865views::View* ShelfView::CreateViewForItem(const ShelfItem& item) {
866  views::View* view = NULL;
867  switch (item.type) {
868    case TYPE_BROWSER_SHORTCUT:
869    case TYPE_APP_SHORTCUT:
870    case TYPE_WINDOWED_APP:
871    case TYPE_PLATFORM_APP:
872    case TYPE_DIALOG:
873    case TYPE_APP_PANEL: {
874      ShelfButton* button = ShelfButton::Create(this, this, layout_manager_);
875      button->SetImage(item.image);
876      ReflectItemStatus(item, button);
877      view = button;
878      break;
879    }
880
881    case TYPE_APP_LIST: {
882      view = new AppListButton(this, this, layout_manager_->shelf_widget());
883      break;
884    }
885
886    default:
887      break;
888  }
889  view->set_context_menu_controller(this);
890
891  DCHECK(view);
892  ConfigureChildView(view);
893  return view;
894}
895
896void ShelfView::FadeIn(views::View* view) {
897  view->SetVisible(true);
898  view->layer()->SetOpacity(0);
899  AnimateToIdealBounds();
900  bounds_animator_->SetAnimationDelegate(
901      view,
902      scoped_ptr<gfx::AnimationDelegate>(new FadeInAnimationDelegate(view)));
903}
904
905void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) {
906  DCHECK(!dragging());
907  DCHECK(drag_view_);
908  drag_pointer_ = pointer;
909  start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
910
911  if (start_drag_index_== -1) {
912    CancelDrag(-1);
913    return;
914  }
915
916  // If the item is no longer draggable, bail out.
917  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
918      model_->items()[start_drag_index_].id);
919  if (!item_delegate->IsDraggable()) {
920    CancelDrag(-1);
921    return;
922  }
923
924  // Move the view to the front so that it appears on top of other views.
925  ReorderChildView(drag_view_, -1);
926  bounds_animator_->StopAnimatingView(drag_view_);
927}
928
929void ShelfView::ContinueDrag(const ui::LocatedEvent& event) {
930  // Due to a syncing operation the application might have been removed.
931  // Bail if it is gone.
932  int current_index = view_model_->GetIndexOfView(drag_view_);
933  DCHECK_NE(-1, current_index);
934
935  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
936      model_->items()[current_index].id);
937  if (!item_delegate->IsDraggable()) {
938    CancelDrag(-1);
939    return;
940  }
941
942  // If this is not a drag and drop host operation and not the app list item,
943  // check if the item got ripped off the shelf - if it did we are done.
944  if (!drag_and_drop_shelf_id_ &&
945      RemovableByRipOff(current_index) != NOT_REMOVABLE) {
946    if (HandleRipOffDrag(event))
947      return;
948    // The rip off handler could have changed the location of the item.
949    current_index = view_model_->GetIndexOfView(drag_view_);
950  }
951
952  // TODO: I don't think this works correctly with RTL.
953  gfx::Point drag_point(event.location());
954  ConvertPointToTarget(drag_view_, this, &drag_point);
955
956  // Constrain the location to the range of valid indices for the type.
957  std::pair<int, int> indices(GetDragRange(current_index));
958  int first_drag_index = indices.first;
959  int last_drag_index = indices.second;
960  // If the last index isn't valid, we're overflowing. Constrain to the app list
961  // (which is the last visible item).
962  if (first_drag_index < model_->FirstPanelIndex() &&
963      last_drag_index > last_visible_index_)
964    last_drag_index = last_visible_index_;
965  int x = 0, y = 0;
966  if (layout_manager_->IsHorizontalAlignment()) {
967    x = std::max(view_model_->ideal_bounds(indices.first).x(),
968                     drag_point.x() - drag_origin_.x());
969    x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
970                 view_model_->ideal_bounds(current_index).width(),
971                 x);
972    if (drag_view_->x() == x)
973      return;
974    drag_view_->SetX(x);
975  } else {
976    y = std::max(view_model_->ideal_bounds(indices.first).y(),
977                     drag_point.y() - drag_origin_.y());
978    y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
979                 view_model_->ideal_bounds(current_index).height(),
980                 y);
981    if (drag_view_->y() == y)
982      return;
983    drag_view_->SetY(y);
984  }
985
986  int target_index =
987      views::ViewModelUtils::DetermineMoveIndex(
988          *view_model_, drag_view_,
989          layout_manager_->IsHorizontalAlignment() ?
990              views::ViewModelUtils::HORIZONTAL :
991              views::ViewModelUtils::VERTICAL,
992          x, y);
993  target_index =
994      std::min(indices.second, std::max(target_index, indices.first));
995  if (target_index == current_index)
996    return;
997
998  // Change the model, the ShelfItemMoved() callback will handle the
999  // |view_model_| update.
1000  model_->Move(current_index, target_index);
1001  bounds_animator_->StopAnimatingView(drag_view_);
1002}
1003
1004bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) {
1005  int current_index = view_model_->GetIndexOfView(drag_view_);
1006  DCHECK_NE(-1, current_index);
1007  std::string dragged_app_id =
1008      delegate_->GetAppIDForShelfID(model_->items()[current_index].id);
1009
1010  gfx::Point screen_location = event.root_location();
1011  ::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(),
1012                             &screen_location);
1013
1014  // To avoid ugly forwards and backwards flipping we use different constants
1015  // for ripping off / re-inserting the items.
1016  if (dragged_off_shelf_) {
1017    // If the shelf/overflow bubble bounds contains |screen_location| we insert
1018    // the item back into the shelf.
1019    if (GetBoundsForDragInsertInScreen().Contains(screen_location)) {
1020      if (dragged_off_from_overflow_to_shelf_) {
1021        // During the dragging an item from Shelf to Overflow, it can enter here
1022        // directly because both are located very closly.
1023        main_shelf_->EndDrag(true);
1024        // Stops the animation of |drag_view_| and sets its bounds explicitly
1025        // becase ContinueDrag() stops its animation. Without this, unexpected
1026        // bounds will be set.
1027        bounds_animator_->StopAnimatingView(drag_view_);
1028        int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1029        drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1030        dragged_off_from_overflow_to_shelf_ = false;
1031      }
1032      // Destroy our proxy view item.
1033      DestroyDragIconProxy();
1034      // Re-insert the item and return simply false since the caller will handle
1035      // the move as in any normal case.
1036      dragged_off_shelf_ = false;
1037      drag_view_->layer()->SetOpacity(1.0f);
1038      // The size of Overflow bubble should be updated immediately when an item
1039      // is re-inserted.
1040      if (is_overflow_mode())
1041        PreferredSizeChanged();
1042      return false;
1043    } else if (is_overflow_mode() &&
1044               main_shelf_->GetBoundsForDragInsertInScreen().Contains(
1045                   screen_location)) {
1046      if (!dragged_off_from_overflow_to_shelf_) {
1047        dragged_off_from_overflow_to_shelf_ = true;
1048        drag_image_->SetOpacity(1.0f);
1049        main_shelf_->StartDrag(dragged_app_id, screen_location);
1050      } else {
1051        main_shelf_->Drag(screen_location);
1052      }
1053    } else if (dragged_off_from_overflow_to_shelf_) {
1054      // Makes the |drag_image_| partially disappear again.
1055      dragged_off_from_overflow_to_shelf_ = false;
1056      drag_image_->SetOpacity(kDraggedImageOpacity);
1057      main_shelf_->EndDrag(true);
1058      bounds_animator_->StopAnimatingView(drag_view_);
1059      int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1060      drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index));
1061    }
1062    // Move our proxy view item.
1063    UpdateDragIconProxy(screen_location);
1064    return true;
1065  }
1066  // Check if we are too far away from the shelf to enter the ripped off state.
1067  // Determine the distance to the shelf.
1068  int delta = CalculateShelfDistance(screen_location);
1069  if (delta > kRipOffDistance) {
1070    // Create a proxy view item which can be moved anywhere.
1071    ShelfButton* button = static_cast<ShelfButton*>(drag_view_);
1072    CreateDragIconProxy(event.root_location(),
1073                        button->GetImage(),
1074                        drag_view_,
1075                        gfx::Vector2d(0, 0),
1076                        kDragAndDropProxyScale);
1077    drag_view_->layer()->SetOpacity(0.0f);
1078    dragged_off_shelf_ = true;
1079    if (RemovableByRipOff(current_index) == REMOVABLE) {
1080      // Move the item to the front of the first panel item and hide it.
1081      // ShelfItemMoved() callback will handle the |view_model_| update and
1082      // call AnimateToIdealBounds().
1083      if (current_index != model_->FirstPanelIndex() - 1) {
1084        model_->Move(current_index, model_->FirstPanelIndex() - 1);
1085        StartFadeInLastVisibleItem();
1086      } else if (is_overflow_mode()) {
1087        // Overflow bubble should be shrunk when an item is ripped off.
1088        PreferredSizeChanged();
1089      }
1090      // Make the item partially disappear to show that it will get removed if
1091      // dropped.
1092      drag_image_->SetOpacity(kDraggedImageOpacity);
1093    }
1094    return true;
1095  }
1096  return false;
1097}
1098
1099void ShelfView::FinalizeRipOffDrag(bool cancel) {
1100  if (!dragged_off_shelf_)
1101    return;
1102  // Make sure we do not come in here again.
1103  dragged_off_shelf_ = false;
1104
1105  // Coming here we should always have a |drag_view_|.
1106  DCHECK(drag_view_);
1107  int current_index = view_model_->GetIndexOfView(drag_view_);
1108  // If the view isn't part of the model anymore (|current_index| == -1), a sync
1109  // operation must have removed it. In that case we shouldn't change the model
1110  // and only delete the proxy image.
1111  if (current_index == -1) {
1112    DestroyDragIconProxy();
1113    return;
1114  }
1115
1116  // Set to true when the animation should snap back to where it was before.
1117  bool snap_back = false;
1118  // Items which cannot be dragged off will be handled as a cancel.
1119  if (!cancel) {
1120    if (dragged_off_from_overflow_to_shelf_) {
1121      dragged_off_from_overflow_to_shelf_ = false;
1122      main_shelf_->EndDrag(false);
1123      drag_view_->layer()->SetOpacity(1.0f);
1124    } else if (RemovableByRipOff(current_index) != REMOVABLE) {
1125      // Make sure we do not try to remove un-removable items like items which
1126      // were not pinned or have to be always there.
1127      cancel = true;
1128      snap_back = true;
1129    } else {
1130      // Make sure the item stays invisible upon removal.
1131      drag_view_->SetVisible(false);
1132      std::string app_id =
1133          delegate_->GetAppIDForShelfID(model_->items()[current_index].id);
1134      delegate_->UnpinAppWithID(app_id);
1135    }
1136  }
1137  if (cancel || snap_back) {
1138    if (dragged_off_from_overflow_to_shelf_) {
1139      dragged_off_from_overflow_to_shelf_ = false;
1140      // Main shelf handles revert of dragged item.
1141      main_shelf_->EndDrag(true);
1142      drag_view_->layer()->SetOpacity(1.0f);
1143    } else if (!cancelling_drag_model_changed_) {
1144      // Only do something if the change did not come through a model change.
1145      gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen();
1146      gfx::Point relative_to = GetBoundsInScreen().origin();
1147      gfx::Rect target(
1148          gfx::PointAtOffsetFromOrigin(drag_bounds.origin()- relative_to),
1149          drag_bounds.size());
1150      drag_view_->SetBoundsRect(target);
1151      // Hide the status from the active item since we snap it back now. Upon
1152      // animation end the flag gets cleared if |snap_back_from_rip_off_view_|
1153      // is set.
1154      snap_back_from_rip_off_view_ = drag_view_;
1155      ShelfButton* button = static_cast<ShelfButton*>(drag_view_);
1156      button->AddState(ShelfButton::STATE_HIDDEN);
1157      // When a canceling drag model is happening, the view model is diverged
1158      // from the menu model and movements / animations should not be done.
1159      model_->Move(current_index, start_drag_index_);
1160      AnimateToIdealBounds();
1161    }
1162    drag_view_->layer()->SetOpacity(1.0f);
1163  }
1164  DestroyDragIconProxy();
1165}
1166
1167ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const {
1168  DCHECK(index >= 0 && index < model_->item_count());
1169  ShelfItemType type = model_->items()[index].type;
1170  if (type == TYPE_APP_LIST || type == TYPE_DIALOG || !delegate_->CanPin())
1171    return NOT_REMOVABLE;
1172
1173  std::string app_id = delegate_->GetAppIDForShelfID(model_->items()[index].id);
1174  // Note: Only pinned app shortcuts can be removed!
1175  return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ?
1176      REMOVABLE : DRAGGABLE;
1177}
1178
1179bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const {
1180  switch (typea) {
1181    case TYPE_APP_SHORTCUT:
1182    case TYPE_BROWSER_SHORTCUT:
1183      return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1184    case TYPE_APP_LIST:
1185    case TYPE_PLATFORM_APP:
1186    case TYPE_WINDOWED_APP:
1187    case TYPE_APP_PANEL:
1188    case TYPE_DIALOG:
1189      return typeb == typea;
1190    case TYPE_UNDEFINED:
1191      NOTREACHED() << "ShelfItemType must be set.";
1192      return false;
1193  }
1194  NOTREACHED();
1195  return false;
1196}
1197
1198std::pair<int, int> ShelfView::GetDragRange(int index) {
1199  int min_index = -1;
1200  int max_index = -1;
1201  ShelfItemType type = model_->items()[index].type;
1202  for (int i = 0; i < model_->item_count(); ++i) {
1203    if (SameDragType(model_->items()[i].type, type)) {
1204      if (min_index == -1)
1205        min_index = i;
1206      max_index = i;
1207    }
1208  }
1209  return std::pair<int, int>(min_index, max_index);
1210}
1211
1212void ShelfView::ConfigureChildView(views::View* view) {
1213  view->SetPaintToLayer(true);
1214  view->layer()->SetFillsBoundsOpaquely(false);
1215}
1216
1217void ShelfView::ToggleOverflowBubble() {
1218  if (IsShowingOverflowBubble()) {
1219    overflow_bubble_->Hide();
1220    return;
1221  }
1222
1223  if (!overflow_bubble_)
1224    overflow_bubble_.reset(new OverflowBubble());
1225
1226  ShelfView* overflow_view =
1227      new ShelfView(model_, delegate_, layout_manager_);
1228  overflow_view->overflow_mode_ = true;
1229  overflow_view->Init();
1230  overflow_view->set_owner_overflow_bubble(overflow_bubble_.get());
1231  overflow_view->OnShelfAlignmentChanged();
1232  overflow_view->main_shelf_ = this;
1233  UpdateOverflowRange(overflow_view);
1234
1235  overflow_bubble_->Show(overflow_button_, overflow_view);
1236
1237  Shell::GetInstance()->UpdateShelfVisibility();
1238}
1239
1240void ShelfView::OnFadeOutAnimationEnded() {
1241  AnimateToIdealBounds();
1242  StartFadeInLastVisibleItem();
1243}
1244
1245void ShelfView::StartFadeInLastVisibleItem() {
1246  // If overflow button is visible and there is a valid new last item, fading
1247  // the new last item in after sliding animation is finished.
1248  if (overflow_button_->visible() && last_visible_index_ >= 0) {
1249    views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1250    last_visible_view->layer()->SetOpacity(0);
1251    bounds_animator_->SetAnimationDelegate(
1252        last_visible_view,
1253        scoped_ptr<gfx::AnimationDelegate>(
1254            new StartFadeAnimationDelegate(this, last_visible_view)));
1255  }
1256}
1257
1258void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) const {
1259  const int first_overflow_index = last_visible_index_ + 1;
1260  const int last_overflow_index = last_hidden_index_;
1261  DCHECK_LE(first_overflow_index, last_overflow_index);
1262  DCHECK_LT(last_overflow_index, view_model_->view_size());
1263
1264  overflow_view->first_visible_index_ = first_overflow_index;
1265  overflow_view->last_visible_index_ = last_overflow_index;
1266}
1267
1268bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) {
1269  gfx::Rect active_bounds;
1270
1271  for (int i = 0; i < child_count(); ++i) {
1272    views::View* child = child_at(i);
1273    if (child == overflow_button_)
1274      continue;
1275    if (!ShouldShowTooltipForView(child))
1276      continue;
1277
1278    gfx::Rect child_bounds = child->GetMirroredBounds();
1279    active_bounds.Union(child_bounds);
1280  }
1281
1282  return !active_bounds.Contains(cursor_location);
1283}
1284
1285gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() {
1286  gfx::Size preferred_size = GetPreferredSize();
1287  gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1288  ConvertPointToScreen(this, &origin);
1289  return gfx::Rect(origin, preferred_size);
1290}
1291
1292gfx::Rect ShelfView::GetBoundsForDragInsertInScreen() {
1293  gfx::Size preferred_size;
1294  if (is_overflow_mode()) {
1295    DCHECK(owner_overflow_bubble_);
1296    gfx::Rect bubble_bounds =
1297        owner_overflow_bubble_->bubble_view()->GetBubbleBounds();
1298    preferred_size = bubble_bounds.size();
1299  } else {
1300    const int last_button_index = view_model_->view_size() - 1;
1301    gfx::Rect last_button_bounds =
1302        view_model_->view_at(last_button_index)->bounds();
1303    if (overflow_button_->visible() &&
1304        model_->GetItemIndexForType(TYPE_APP_PANEL) == -1) {
1305      // When overflow button is visible and shelf has no panel items,
1306      // last_button_bounds should be overflow button's bounds.
1307      last_button_bounds = overflow_button_->bounds();
1308    }
1309
1310    if (layout_manager_->IsHorizontalAlignment()) {
1311      preferred_size = gfx::Size(last_button_bounds.right() + leading_inset_,
1312                                 kShelfSize);
1313    } else {
1314      preferred_size = gfx::Size(kShelfSize,
1315                                 last_button_bounds.bottom() + leading_inset_);
1316    }
1317  }
1318  gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0);
1319
1320  // In overflow mode, we should use OverflowBubbleView as a source for
1321  // converting |origin| to screen coordinates. When a scroll operation is
1322  // occurred in OverflowBubble, the bounds of ShelfView in OverflowBubble can
1323  // be changed.
1324  if (is_overflow_mode())
1325    ConvertPointToScreen(owner_overflow_bubble_->bubble_view(), &origin);
1326  else
1327    ConvertPointToScreen(this, &origin);
1328
1329  return gfx::Rect(origin, preferred_size);
1330}
1331
1332int ShelfView::CancelDrag(int modified_index) {
1333  FinalizeRipOffDrag(true);
1334  if (!drag_view_)
1335    return modified_index;
1336  bool was_dragging = dragging();
1337  int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1338  drag_pointer_ = NONE;
1339  drag_view_ = NULL;
1340  if (drag_view_index == modified_index) {
1341    // The view that was being dragged is being modified. Don't do anything.
1342    return modified_index;
1343  }
1344  if (!was_dragging)
1345    return modified_index;
1346
1347  // Restore previous position, tracking the position of the modified view.
1348  bool at_end = modified_index == view_model_->view_size();
1349  views::View* modified_view =
1350      (modified_index >= 0 && !at_end) ?
1351      view_model_->view_at(modified_index) : NULL;
1352  model_->Move(drag_view_index, start_drag_index_);
1353
1354  // If the modified view will be at the end of the list, return the new end of
1355  // the list.
1356  if (at_end)
1357    return view_model_->view_size();
1358  return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1359}
1360
1361gfx::Size ShelfView::GetPreferredSize() const {
1362  IdealBounds ideal_bounds;
1363  CalculateIdealBounds(&ideal_bounds);
1364
1365  int last_button_index = is_overflow_mode() ?
1366      last_visible_index_ : view_model_->view_size() - 1;
1367
1368  // When an item is dragged off from the overflow bubble, it is moved to last
1369  // position and and changed to invisible. Overflow bubble size should be
1370  // shrunk to fit only for visible items.
1371  // If |dragged_off_from_overflow_to_shelf_| is set, there will be no invisible
1372  // items in the shelf.
1373  if (is_overflow_mode() &&
1374      dragged_off_shelf_ &&
1375      !dragged_off_from_overflow_to_shelf_ &&
1376      RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE)
1377    last_button_index--;
1378
1379  const gfx::Rect last_button_bounds =
1380      last_button_index  >= first_visible_index_ ?
1381          view_model_->ideal_bounds(last_button_index) :
1382          gfx::Rect(gfx::Size(kShelfSize, kShelfSize));
1383
1384  if (layout_manager_->IsHorizontalAlignment()) {
1385    return gfx::Size(last_button_bounds.right() + leading_inset_, kShelfSize);
1386  }
1387
1388  return gfx::Size(kShelfSize,
1389                   last_button_bounds.bottom() + leading_inset_);
1390}
1391
1392void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1393  // This bounds change is produced by the shelf movement and all content has
1394  // to follow. Using an animation at that time would produce a time lag since
1395  // the animation of the BoundsAnimator has itself a delay before it arrives
1396  // at the required location. As such we tell the animator to go there
1397  // immediately.
1398  BoundsAnimatorDisabler disabler(bounds_animator_.get());
1399  LayoutToIdealBounds();
1400  FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1401                    OnShelfIconPositionsChanged());
1402
1403  if (IsShowingOverflowBubble())
1404    overflow_bubble_->Hide();
1405}
1406
1407views::FocusTraversable* ShelfView::GetPaneFocusTraversable() {
1408  return this;
1409}
1410
1411void ShelfView::GetAccessibleState(ui::AXViewState* state) {
1412  state->role = ui::AX_ROLE_TOOLBAR;
1413  state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
1414}
1415
1416void ShelfView::OnGestureEvent(ui::GestureEvent* event) {
1417  if (gesture_handler_.ProcessGestureEvent(*event))
1418    event->StopPropagation();
1419}
1420
1421void ShelfView::ShelfItemAdded(int model_index) {
1422  {
1423    base::AutoReset<bool> cancelling_drag(
1424        &cancelling_drag_model_changed_, true);
1425    model_index = CancelDrag(model_index);
1426  }
1427  views::View* view = CreateViewForItem(model_->items()[model_index]);
1428  AddChildView(view);
1429  // Hide the view, it'll be made visible when the animation is done. Using
1430  // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1431  // the view's visibility.
1432  view->layer()->SetOpacity(0);
1433  view_model_->Add(view, model_index);
1434
1435  // Give the button its ideal bounds. That way if we end up animating the
1436  // button before this animation completes it doesn't appear at some random
1437  // spot (because it was in the middle of animating from 0,0 0x0 to its
1438  // target).
1439  IdealBounds ideal_bounds;
1440  CalculateIdealBounds(&ideal_bounds);
1441  view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1442
1443  // The first animation moves all the views to their target position. |view|
1444  // is hidden, so it visually appears as though we are providing space for
1445  // it. When done we'll fade the view in.
1446  AnimateToIdealBounds();
1447  if (model_index <= last_visible_index_ ||
1448      model_index >= model_->FirstPanelIndex()) {
1449    bounds_animator_->SetAnimationDelegate(
1450        view,
1451        scoped_ptr<gfx::AnimationDelegate>(
1452            new StartFadeAnimationDelegate(this, view)));
1453  } else {
1454    // Undo the hiding if animation does not run.
1455    view->layer()->SetOpacity(1.0f);
1456  }
1457}
1458
1459void ShelfView::ShelfItemRemoved(int model_index, ShelfID id) {
1460  if (id == context_menu_id_)
1461    launcher_menu_runner_.reset();
1462  {
1463    base::AutoReset<bool> cancelling_drag(
1464        &cancelling_drag_model_changed_, true);
1465    model_index = CancelDrag(model_index);
1466  }
1467  views::View* view = view_model_->view_at(model_index);
1468  view_model_->Remove(model_index);
1469
1470  // When the overflow bubble is visible, the overflow range needs to be set
1471  // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds()
1472  // could trigger a ShelfItemChanged() by hiding the overflow bubble and
1473  // since the overflow bubble is not yet synced with the ShelfModel this
1474  // could cause a crash.
1475  if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1476    last_hidden_index_ = std::min(last_hidden_index_,
1477                                  view_model_->view_size() - 1);
1478    UpdateOverflowRange(overflow_bubble_->shelf_view());
1479  }
1480
1481  if (view->visible()) {
1482    // The first animation fades out the view. When done we'll animate the rest
1483    // of the views to their target location.
1484    bounds_animator_->AnimateViewTo(view, view->bounds());
1485    bounds_animator_->SetAnimationDelegate(
1486        view,
1487        scoped_ptr<gfx::AnimationDelegate>(
1488            new FadeOutAnimationDelegate(this, view)));
1489  } else {
1490    // We don't need to show a fade out animation for invisible |view|. When an
1491    // item is ripped out from the shelf, its |view| is already invisible.
1492    AnimateToIdealBounds();
1493  }
1494
1495  // Close the tooltip because it isn't needed any longer and its anchor view
1496  // will be deleted soon.
1497  if (tooltip_->GetCurrentAnchorView() == view)
1498    tooltip_->Close();
1499}
1500
1501void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) {
1502  const ShelfItem& item(model_->items()[model_index]);
1503  if (old_item.type != item.type) {
1504    // Type changed, swap the views.
1505    model_index = CancelDrag(model_index);
1506    scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1507    bounds_animator_->StopAnimatingView(old_view.get());
1508    // Removing and re-inserting a view in our view model will strip the ideal
1509    // bounds from the item. To avoid recalculation of everything the bounds
1510    // get remembered and restored after the insertion to the previous value.
1511    gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index);
1512    view_model_->Remove(model_index);
1513    views::View* new_view = CreateViewForItem(item);
1514    AddChildView(new_view);
1515    view_model_->Add(new_view, model_index);
1516    view_model_->set_ideal_bounds(model_index, old_ideal_bounds);
1517    new_view->SetBoundsRect(old_view->bounds());
1518    return;
1519  }
1520
1521  views::View* view = view_model_->view_at(model_index);
1522  switch (item.type) {
1523    case TYPE_BROWSER_SHORTCUT:
1524      // Fallthrough for the new Shelf since it needs to show the activation
1525      // change as well.
1526    case TYPE_APP_SHORTCUT:
1527    case TYPE_WINDOWED_APP:
1528    case TYPE_PLATFORM_APP:
1529    case TYPE_DIALOG:
1530    case TYPE_APP_PANEL: {
1531      ShelfButton* button = static_cast<ShelfButton*>(view);
1532      ReflectItemStatus(item, button);
1533      // The browser shortcut is currently not a "real" item and as such the
1534      // the image is bogous as well. We therefore keep the image as is for it.
1535      if (item.type != TYPE_BROWSER_SHORTCUT)
1536        button->SetImage(item.image);
1537      button->SchedulePaint();
1538      break;
1539    }
1540
1541    default:
1542      break;
1543  }
1544}
1545
1546void ShelfView::ShelfItemMoved(int start_index, int target_index) {
1547  view_model_->Move(start_index, target_index);
1548  // When cancelling a drag due to a shelf item being added, the currently
1549  // dragged item is moved back to its initial position. AnimateToIdealBounds
1550  // will be called again when the new item is added to the |view_model_| but
1551  // at this time the |view_model_| is inconsistent with the |model_|.
1552  if (!cancelling_drag_model_changed_)
1553    AnimateToIdealBounds();
1554}
1555
1556void ShelfView::ShelfStatusChanged() {
1557  // Nothing to do here.
1558}
1559
1560void ShelfView::PointerPressedOnButton(views::View* view,
1561                                       Pointer pointer,
1562                                       const ui::LocatedEvent& event) {
1563  if (drag_view_)
1564    return;
1565
1566  int index = view_model_->GetIndexOfView(view);
1567  if (index == -1)
1568    return;
1569
1570  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1571      model_->items()[index].id);
1572  if (view_model_->view_size() <= 1 || !item_delegate->IsDraggable())
1573    return;  // View is being deleted or not draggable, ignore request.
1574
1575  drag_view_ = view;
1576  drag_origin_ = gfx::Point(event.x(), event.y());
1577  UMA_HISTOGRAM_ENUMERATION("Ash.ShelfAlignmentUsage",
1578      layout_manager_->SelectValueForShelfAlignment(
1579          SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM,
1580          SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT,
1581          SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT,
1582          -1),
1583      SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT);
1584}
1585
1586void ShelfView::PointerDraggedOnButton(views::View* view,
1587                                       Pointer pointer,
1588                                       const ui::LocatedEvent& event) {
1589  // To prepare all drag types (moving an item in the shelf and dragging off),
1590  // we should check the x-axis and y-axis offset.
1591  if (!dragging() && drag_view_ &&
1592      ((std::abs(event.x() - drag_origin_.x()) >= kMinimumDragDistance) ||
1593       (std::abs(event.y() - drag_origin_.y()) >= kMinimumDragDistance))) {
1594    PrepareForDrag(pointer, event);
1595  }
1596  if (drag_pointer_ == pointer)
1597    ContinueDrag(event);
1598}
1599
1600void ShelfView::PointerReleasedOnButton(views::View* view,
1601                                        Pointer pointer,
1602                                        bool canceled) {
1603  if (canceled) {
1604    CancelDrag(-1);
1605  } else if (drag_pointer_ == pointer) {
1606    FinalizeRipOffDrag(false);
1607    drag_pointer_ = NONE;
1608    AnimateToIdealBounds();
1609  }
1610  // If the drag pointer is NONE, no drag operation is going on and the
1611  // drag_view can be released.
1612  if (drag_pointer_ == NONE)
1613    drag_view_ = NULL;
1614}
1615
1616void ShelfView::MouseMovedOverButton(views::View* view) {
1617  if (!ShouldShowTooltipForView(view))
1618    return;
1619
1620  if (!tooltip_->IsVisible())
1621    tooltip_->ResetTimer();
1622}
1623
1624void ShelfView::MouseEnteredButton(views::View* view) {
1625  if (!ShouldShowTooltipForView(view))
1626    return;
1627
1628  if (tooltip_->IsVisible()) {
1629    tooltip_->ShowImmediately(view, GetAccessibleName(view));
1630  } else {
1631    tooltip_->ShowDelayed(view, GetAccessibleName(view));
1632  }
1633}
1634
1635void ShelfView::MouseExitedButton(views::View* view) {
1636  if (!tooltip_->IsVisible())
1637    tooltip_->StopTimer();
1638}
1639
1640base::string16 ShelfView::GetAccessibleName(const views::View* view) {
1641  int view_index = view_model_->GetIndexOfView(view);
1642  // May be -1 while in the process of animating closed.
1643  if (view_index == -1)
1644    return base::string16();
1645
1646  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1647      model_->items()[view_index].id);
1648  return item_delegate->GetTitle();
1649}
1650
1651void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) {
1652  // Do not handle mouse release during drag.
1653  if (dragging())
1654    return;
1655
1656  if (sender == overflow_button_) {
1657    ToggleOverflowBubble();
1658    return;
1659  }
1660
1661  int view_index = view_model_->GetIndexOfView(sender);
1662  // May be -1 while in the process of animating closed.
1663  if (view_index == -1)
1664    return;
1665
1666  // If the previous menu was closed by the same event as this one, we ignore
1667  // the call.
1668  if (!IsUsableEvent(event))
1669    return;
1670
1671  // Don't activate the item twice on double-click. Otherwise the window starts
1672  // animating open due to the first click, then immediately minimizes due to
1673  // the second click. The user most likely intended to open or minimize the
1674  // item once, not do both.
1675  if (event.flags() & ui::EF_IS_DOUBLE_CLICK)
1676    return;
1677
1678  {
1679    ScopedTargetRootWindow scoped_target(
1680        sender->GetWidget()->GetNativeView()->GetRootWindow());
1681    // Slow down activation animations if shift key is pressed.
1682    scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1683    if (event.IsShiftDown()) {
1684      slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1685            ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1686    }
1687
1688    // Collect usage statistics before we decide what to do with the click.
1689    switch (model_->items()[view_index].type) {
1690      case TYPE_APP_SHORTCUT:
1691      case TYPE_WINDOWED_APP:
1692      case TYPE_PLATFORM_APP:
1693      case TYPE_BROWSER_SHORTCUT:
1694        Shell::GetInstance()->metrics()->RecordUserMetricsAction(
1695            UMA_LAUNCHER_CLICK_ON_APP);
1696        break;
1697
1698      case TYPE_APP_LIST:
1699        Shell::GetInstance()->metrics()->RecordUserMetricsAction(
1700            UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1701        break;
1702
1703      case TYPE_APP_PANEL:
1704      case TYPE_DIALOG:
1705        break;
1706
1707      case TYPE_UNDEFINED:
1708        NOTREACHED() << "ShelfItemType must be set.";
1709        break;
1710    }
1711
1712    ShelfItemDelegate* item_delegate =
1713        item_manager_->GetShelfItemDelegate(model_->items()[view_index].id);
1714    if (!item_delegate->ItemSelected(event))
1715      ShowListMenuForView(model_->items()[view_index], sender, event);
1716  }
1717}
1718
1719bool ShelfView::ShowListMenuForView(const ShelfItem& item,
1720                                    views::View* source,
1721                                    const ui::Event& event) {
1722  ShelfItemDelegate* item_delegate =
1723      item_manager_->GetShelfItemDelegate(item.id);
1724  scoped_ptr<ui::MenuModel> list_menu_model(
1725      item_delegate->CreateApplicationMenu(event.flags()));
1726
1727  // Make sure we have a menu and it has at least two items in addition to the
1728  // application title and the 3 spacing separators.
1729  if (!list_menu_model.get() || list_menu_model->GetItemCount() <= 5)
1730    return false;
1731
1732  ShowMenu(list_menu_model.get(),
1733           source,
1734           gfx::Point(),
1735           false,
1736           ui::GetMenuSourceTypeForEvent(event));
1737  return true;
1738}
1739
1740void ShelfView::ShowContextMenuForView(views::View* source,
1741                                       const gfx::Point& point,
1742                                       ui::MenuSourceType source_type) {
1743  int view_index = view_model_->GetIndexOfView(source);
1744  if (view_index == -1) {
1745    Shell::GetInstance()->ShowContextMenu(point, source_type);
1746    return;
1747  }
1748
1749  ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate(
1750      model_->items()[view_index].id);
1751  context_menu_model_.reset(item_delegate->CreateContextMenu(
1752      source->GetWidget()->GetNativeView()->GetRootWindow()));
1753  if (!context_menu_model_)
1754    return;
1755
1756  base::AutoReset<ShelfID> reseter(
1757      &context_menu_id_,
1758      view_index == -1 ? 0 : model_->items()[view_index].id);
1759
1760  ShowMenu(context_menu_model_.get(),
1761           source,
1762           point,
1763           true,
1764           source_type);
1765}
1766
1767void ShelfView::ShowMenu(ui::MenuModel* menu_model,
1768                         views::View* source,
1769                         const gfx::Point& click_point,
1770                         bool context_menu,
1771                         ui::MenuSourceType source_type) {
1772  closing_event_time_ = base::TimeDelta();
1773  launcher_menu_runner_.reset(new views::MenuRunner(
1774      menu_model, context_menu ? views::MenuRunner::CONTEXT_MENU : 0));
1775
1776  ScopedTargetRootWindow scoped_target(
1777      source->GetWidget()->GetNativeView()->GetRootWindow());
1778
1779  // Determine the menu alignment dependent on the shelf.
1780  views::MenuAnchorPosition menu_alignment = views::MENU_ANCHOR_TOPLEFT;
1781  gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1782
1783  ShelfWidget* shelf = RootWindowController::ForShelf(
1784      GetWidget()->GetNativeView())->shelf();
1785  if (!context_menu) {
1786    // Application lists use a bubble.
1787    ShelfAlignment align = shelf->GetAlignment();
1788    anchor_point = source->GetBoundsInScreen();
1789
1790    // It is possible to invoke the menu while it is sliding into view. To cover
1791    // that case, the screen coordinates are offsetted by the animation delta.
1792    gfx::Vector2d offset =
1793        source->GetWidget()->GetNativeWindow()->bounds().origin() -
1794        source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
1795    anchor_point.set_x(anchor_point.x() - offset.x());
1796    anchor_point.set_y(anchor_point.y() - offset.y());
1797
1798    // Shelf items can have an asymmetrical border for spacing reasons.
1799    // Adjust anchor location for this.
1800    if (source->border())
1801      anchor_point.Inset(source->border()->GetInsets());
1802
1803    switch (align) {
1804      case SHELF_ALIGNMENT_BOTTOM:
1805        menu_alignment = views::MENU_ANCHOR_BUBBLE_ABOVE;
1806        break;
1807      case SHELF_ALIGNMENT_LEFT:
1808        menu_alignment = views::MENU_ANCHOR_BUBBLE_RIGHT;
1809        break;
1810      case SHELF_ALIGNMENT_RIGHT:
1811        menu_alignment = views::MENU_ANCHOR_BUBBLE_LEFT;
1812        break;
1813      case SHELF_ALIGNMENT_TOP:
1814        menu_alignment = views::MENU_ANCHOR_BUBBLE_BELOW;
1815        break;
1816    }
1817  }
1818  // If this gets deleted while we are in the menu, the shelf will be gone
1819  // as well.
1820  bool got_deleted = false;
1821  got_deleted_ = &got_deleted;
1822
1823  shelf->ForceUndimming(true);
1824  // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1825  // code.
1826  if (launcher_menu_runner_->RunMenuAt(source->GetWidget(),
1827                                       NULL,
1828                                       anchor_point,
1829                                       menu_alignment,
1830                                       source_type) ==
1831      views::MenuRunner::MENU_DELETED) {
1832    if (!got_deleted) {
1833      got_deleted_ = NULL;
1834      shelf->ForceUndimming(false);
1835    }
1836    return;
1837  }
1838  got_deleted_ = NULL;
1839  shelf->ForceUndimming(false);
1840
1841  // If it is a context menu and we are showing overflow bubble
1842  // we want to hide overflow bubble.
1843  if (owner_overflow_bubble_)
1844    owner_overflow_bubble_->HideBubbleAndRefreshButton();
1845
1846  // Unpinning an item will reset the |launcher_menu_runner_| before coming
1847  // here.
1848  if (launcher_menu_runner_)
1849    closing_event_time_ = launcher_menu_runner_->closing_event_time();
1850  Shell::GetInstance()->UpdateShelfVisibility();
1851}
1852
1853void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1854  FOR_EACH_OBSERVER(ShelfIconObserver, observers_,
1855                    OnShelfIconPositionsChanged());
1856  PreferredSizeChanged();
1857}
1858
1859void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1860  if (snap_back_from_rip_off_view_ && animator == bounds_animator_) {
1861    if (!animator->IsAnimating(snap_back_from_rip_off_view_)) {
1862      // Coming here the animation of the ShelfButton is finished and the
1863      // previously hidden status can be shown again. Since the button itself
1864      // might have gone away or changed locations we check that the button
1865      // is still in the shelf and show its status again.
1866      for (int index = 0; index < view_model_->view_size(); index++) {
1867        views::View* view = view_model_->view_at(index);
1868        if (view == snap_back_from_rip_off_view_) {
1869          ShelfButton* button = static_cast<ShelfButton*>(view);
1870          button->ClearState(ShelfButton::STATE_HIDDEN);
1871          break;
1872        }
1873      }
1874      snap_back_from_rip_off_view_ = NULL;
1875    }
1876  }
1877}
1878
1879bool ShelfView::IsUsableEvent(const ui::Event& event) {
1880  if (closing_event_time_ == base::TimeDelta())
1881    return true;
1882
1883  base::TimeDelta delta =
1884      base::TimeDelta(event.time_stamp() - closing_event_time_);
1885  closing_event_time_ = base::TimeDelta();
1886  // TODO(skuhne): This time seems excessive, but it appears that the reposting
1887  // takes that long.  Need to come up with a better way of doing this.
1888  return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1889}
1890
1891const ShelfItem* ShelfView::ShelfItemForView(const views::View* view) const {
1892  int view_index = view_model_->GetIndexOfView(view);
1893  if (view_index == -1)
1894    return NULL;
1895  return &(model_->items()[view_index]);
1896}
1897
1898bool ShelfView::ShouldShowTooltipForView(const views::View* view) const {
1899  if (view == GetAppListButtonView() &&
1900      Shell::GetInstance()->GetAppListWindow())
1901    return false;
1902  const ShelfItem* item = ShelfItemForView(view);
1903  if (!item)
1904    return true;
1905  ShelfItemDelegate* item_delegate =
1906      item_manager_->GetShelfItemDelegate(item->id);
1907  return item_delegate->ShouldShowTooltip();
1908}
1909
1910int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const {
1911  ShelfWidget* shelf = RootWindowController::ForShelf(
1912      GetWidget()->GetNativeView())->shelf();
1913  ShelfAlignment align = shelf->GetAlignment();
1914  const gfx::Rect bounds = GetBoundsInScreen();
1915  int distance = 0;
1916  switch (align) {
1917    case SHELF_ALIGNMENT_BOTTOM:
1918      distance = bounds.y() - coordinate.y();
1919      break;
1920    case SHELF_ALIGNMENT_LEFT:
1921      distance = coordinate.x() - bounds.right();
1922      break;
1923    case SHELF_ALIGNMENT_RIGHT:
1924      distance = bounds.x() - coordinate.x();
1925      break;
1926    case SHELF_ALIGNMENT_TOP:
1927      distance = coordinate.y() - bounds.bottom();
1928      break;
1929  }
1930  return distance > 0 ? distance : 0;
1931}
1932
1933}  // namespace ash
1934