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/launcher/launcher_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/launcher/alternate_app_list_button.h"
13#include "ash/launcher/app_list_button.h"
14#include "ash/launcher/launcher_button.h"
15#include "ash/launcher/launcher_delegate.h"
16#include "ash/launcher/launcher_icon_observer.h"
17#include "ash/launcher/launcher_model.h"
18#include "ash/launcher/launcher_tooltip_manager.h"
19#include "ash/launcher/overflow_bubble.h"
20#include "ash/launcher/overflow_button.h"
21#include "ash/launcher/tabbed_launcher_button.h"
22#include "ash/root_window_controller.h"
23#include "ash/scoped_target_root_window.h"
24#include "ash/shelf/shelf_layout_manager.h"
25#include "ash/shelf/shelf_widget.h"
26#include "ash/shell_delegate.h"
27#include "base/auto_reset.h"
28#include "base/memory/scoped_ptr.h"
29#include "grit/ash_resources.h"
30#include "grit/ash_strings.h"
31#include "ui/aura/client/screen_position_client.h"
32#include "ui/aura/root_window.h"
33#include "ui/aura/window.h"
34#include "ui/base/accessibility/accessible_view_state.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/models/simple_menu_model.h"
37#include "ui/base/resource/resource_bundle.h"
38#include "ui/compositor/layer.h"
39#include "ui/compositor/layer_animator.h"
40#include "ui/compositor/scoped_animation_duration_scale_mode.h"
41#include "ui/gfx/canvas.h"
42#include "ui/gfx/point.h"
43#include "ui/views/animation/bounds_animator.h"
44#include "ui/views/border.h"
45#include "ui/views/controls/button/image_button.h"
46#include "ui/views/controls/menu/menu_model_adapter.h"
47#include "ui/views/controls/menu/menu_runner.h"
48#include "ui/views/focus/focus_search.h"
49#include "ui/views/focus_border.h"
50#include "ui/views/view_model.h"
51#include "ui/views/view_model_utils.h"
52#include "ui/views/widget/widget.h"
53
54using ui::Animation;
55using views::View;
56
57namespace ash {
58namespace internal {
59
60// Default amount content is inset on the left edge.
61const int kDefaultLeadingInset = 8;
62
63// Minimum distance before drag starts.
64const int kMinimumDragDistance = 8;
65
66// Size between the buttons.
67const int kButtonSpacing = 4;
68const int kAlternateButtonSpacing = 10;
69
70// Size allocated to for each button.
71const int kButtonSize = 44;
72
73// Additional spacing for the left and right side of icons.
74const int kHorizontalIconSpacing = 2;
75
76// Inset for items which do not have an icon.
77const int kHorizontalNoIconInsetSpacing =
78    kHorizontalIconSpacing + kDefaultLeadingInset;
79
80// The proportion of the launcher space reserved for non-panel icons. Panels
81// may flow into this space but will be put into the overflow bubble if there
82// is contention for the space.
83const float kReservedNonPanelIconProportion = 0.67f;
84
85// This is the command id of the menu item which contains the name of the menu.
86const int kCommandIdOfMenuName = 0;
87
88// The background color of the active item in the list.
89const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
90
91// The background color of the active & hovered item in the list.
92const SkColor kFocusedActiveListItemBackgroundColor =
93    SkColorSetRGB(193, 211, 236);
94
95// The text color of the caption item in a list.
96const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
97
98// The maximum allowable length of a menu line of an application menu in pixels.
99const int kMaximumAppMenuItemLength = 350;
100
101namespace {
102
103// The MenuModelAdapter gets slightly changed to adapt the menu appearance to
104// our requirements.
105class LauncherMenuModelAdapter
106    : public views::MenuModelAdapter {
107 public:
108  explicit LauncherMenuModelAdapter(ash::LauncherMenuModel* menu_model);
109
110  // Overriding MenuModelAdapter's MenuDelegate implementation.
111  virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
112  virtual bool IsCommandEnabled(int id) const OVERRIDE;
113  virtual void GetHorizontalIconMargins(int id,
114                                        int icon_size,
115                                        int* left_margin,
116                                        int* right_margin) const OVERRIDE;
117  virtual bool GetForegroundColor(int command_id,
118                                  bool is_hovered,
119                                  SkColor* override_color) const OVERRIDE;
120  virtual bool GetBackgroundColor(int command_id,
121                                  bool is_hovered,
122                                  SkColor* override_color) const OVERRIDE;
123  virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
124  virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
125
126 private:
127  ash::LauncherMenuModel* launcher_menu_model_;
128
129  DISALLOW_COPY_AND_ASSIGN(LauncherMenuModelAdapter);
130};
131
132
133LauncherMenuModelAdapter::LauncherMenuModelAdapter(
134    ash::LauncherMenuModel* menu_model)
135    : MenuModelAdapter(menu_model),
136      launcher_menu_model_(menu_model) {}
137
138const gfx::Font* LauncherMenuModelAdapter::GetLabelFont(
139    int command_id) const {
140  if (command_id != kCommandIdOfMenuName)
141    return MenuModelAdapter::GetLabelFont(command_id);
142
143  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
144  return &rb.GetFont(ui::ResourceBundle::BoldFont);
145}
146
147bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const {
148  return id != kCommandIdOfMenuName;
149}
150
151bool LauncherMenuModelAdapter::GetForegroundColor(
152    int command_id,
153    bool is_hovered,
154    SkColor* override_color) const {
155  if (command_id != kCommandIdOfMenuName)
156    return false;
157
158  *override_color = kCaptionItemForegroundColor;
159  return true;
160}
161
162bool LauncherMenuModelAdapter::GetBackgroundColor(
163    int command_id,
164    bool is_hovered,
165    SkColor* override_color) const {
166  if (!launcher_menu_model_->IsCommandActive(command_id))
167    return false;
168
169  *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
170                                 kActiveListItemBackgroundColor;
171  return true;
172}
173
174void LauncherMenuModelAdapter::GetHorizontalIconMargins(
175    int command_id,
176    int icon_size,
177    int* left_margin,
178    int* right_margin) const {
179  *left_margin = kHorizontalIconSpacing;
180  *right_margin = (command_id != kCommandIdOfMenuName) ?
181      kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
182}
183
184int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
185  return kMaximumAppMenuItemLength;
186}
187
188bool LauncherMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
189  return false;
190}
191
192// Custom FocusSearch used to navigate the launcher in the order items are in
193// the ViewModel.
194class LauncherFocusSearch : public views::FocusSearch {
195 public:
196  explicit LauncherFocusSearch(views::ViewModel* view_model)
197      : FocusSearch(NULL, true, true),
198        view_model_(view_model) {}
199  virtual ~LauncherFocusSearch() {}
200
201  // views::FocusSearch overrides:
202  virtual View* FindNextFocusableView(
203      View* starting_view,
204      bool reverse,
205      Direction direction,
206      bool check_starting_view,
207      views::FocusTraversable** focus_traversable,
208      View** focus_traversable_view) OVERRIDE {
209    int index = view_model_->GetIndexOfView(starting_view);
210    if (index == -1)
211      return view_model_->view_at(0);
212
213    if (reverse) {
214      --index;
215      if (index < 0)
216        index = view_model_->view_size() - 1;
217    } else {
218      ++index;
219      if (index >= view_model_->view_size())
220        index = 0;
221    }
222    return view_model_->view_at(index);
223  }
224
225 private:
226  views::ViewModel* view_model_;
227
228  DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
229};
230
231class LauncherButtonFocusBorder : public views::FocusBorder {
232 public:
233  LauncherButtonFocusBorder() {}
234  virtual ~LauncherButtonFocusBorder() {}
235
236 private:
237  // views::FocusBorder overrides:
238  virtual void Paint(const View& view, gfx::Canvas* canvas) const OVERRIDE {
239    gfx::Rect rect(view.GetLocalBounds());
240    rect.Inset(1, 1);
241    canvas->DrawRect(rect, kFocusBorderColor);
242  }
243
244  DISALLOW_COPY_AND_ASSIGN(LauncherButtonFocusBorder);
245};
246
247// AnimationDelegate that deletes a view when done. This is used when a launcher
248// item is removed, which triggers a remove animation. When the animation is
249// done we delete the view.
250class DeleteViewAnimationDelegate
251    : public views::BoundsAnimator::OwnedAnimationDelegate {
252 public:
253  explicit DeleteViewAnimationDelegate(views::View* view) : view_(view) {}
254  virtual ~DeleteViewAnimationDelegate() {}
255
256 private:
257  scoped_ptr<views::View> view_;
258
259  DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate);
260};
261
262// AnimationDelegate used when inserting a new item. This steadily increases the
263// opacity of the layer as the animation progress.
264class FadeInAnimationDelegate
265    : public views::BoundsAnimator::OwnedAnimationDelegate {
266 public:
267  explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
268  virtual ~FadeInAnimationDelegate() {}
269
270  // AnimationDelegate overrides:
271  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
272    view_->layer()->SetOpacity(animation->GetCurrentValue());
273    view_->layer()->ScheduleDraw();
274  }
275  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
276    view_->layer()->SetOpacity(1.0f);
277    view_->layer()->ScheduleDraw();
278  }
279  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
280    view_->layer()->SetOpacity(1.0f);
281    view_->layer()->ScheduleDraw();
282  }
283
284 private:
285  views::View* view_;
286
287  DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
288};
289
290void ReflectItemStatus(const ash::LauncherItem& item,
291                       LauncherButton* button) {
292  switch (item.status) {
293    case STATUS_CLOSED:
294      button->ClearState(LauncherButton::STATE_ACTIVE);
295      button->ClearState(LauncherButton::STATE_RUNNING);
296      button->ClearState(LauncherButton::STATE_ATTENTION);
297      break;
298    case STATUS_RUNNING:
299      button->ClearState(LauncherButton::STATE_ACTIVE);
300      button->AddState(LauncherButton::STATE_RUNNING);
301      button->ClearState(LauncherButton::STATE_ATTENTION);
302      break;
303    case STATUS_ACTIVE:
304      button->AddState(LauncherButton::STATE_ACTIVE);
305      button->ClearState(LauncherButton::STATE_RUNNING);
306      button->ClearState(LauncherButton::STATE_ATTENTION);
307      break;
308    case STATUS_ATTENTION:
309      button->ClearState(LauncherButton::STATE_ACTIVE);
310      button->ClearState(LauncherButton::STATE_RUNNING);
311      button->AddState(LauncherButton::STATE_ATTENTION);
312      break;
313  }
314}
315
316// Get the event location in screen coordinates.
317gfx::Point GetPositionInScreen(const gfx::Point& root_location,
318                               views::View* view) {
319  gfx::Point root_location_in_screen = root_location;
320  aura::RootWindow* root_window =
321      view->GetWidget()->GetNativeWindow()->GetRootWindow();
322  aura::client::GetScreenPositionClient(root_window->GetRootWindow())->
323        ConvertPointToScreen(root_window, &root_location_in_screen);
324  return root_location_in_screen;
325}
326
327}  // namespace
328
329// AnimationDelegate used when deleting an item. This steadily decreased the
330// opacity of the layer as the animation progress.
331class LauncherView::FadeOutAnimationDelegate
332    : public views::BoundsAnimator::OwnedAnimationDelegate {
333 public:
334  FadeOutAnimationDelegate(LauncherView* host, views::View* view)
335      : launcher_view_(host),
336        view_(view) {}
337  virtual ~FadeOutAnimationDelegate() {}
338
339  // AnimationDelegate overrides:
340  virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
341    view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
342    view_->layer()->ScheduleDraw();
343  }
344  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
345    launcher_view_->OnFadeOutAnimationEnded();
346  }
347  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
348  }
349
350 private:
351  LauncherView* launcher_view_;
352  scoped_ptr<views::View> view_;
353
354  DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
355};
356
357// AnimationDelegate used to trigger fading an element in. When an item is
358// inserted this delegate is attached to the animation that expands the size of
359// the item.  When done it kicks off another animation to fade the item in.
360class LauncherView::StartFadeAnimationDelegate
361    : public views::BoundsAnimator::OwnedAnimationDelegate {
362 public:
363  StartFadeAnimationDelegate(LauncherView* host,
364                             views::View* view)
365      : launcher_view_(host),
366        view_(view) {}
367  virtual ~StartFadeAnimationDelegate() {}
368
369  // AnimationDelegate overrides:
370  virtual void AnimationEnded(const Animation* animation) OVERRIDE {
371    launcher_view_->FadeIn(view_);
372  }
373  virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
374    view_->layer()->SetOpacity(1.0f);
375  }
376
377 private:
378  LauncherView* launcher_view_;
379  views::View* view_;
380
381  DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
382};
383
384LauncherView::LauncherView(LauncherModel* model,
385                           LauncherDelegate* delegate,
386                           ShelfLayoutManager* shelf_layout_manager)
387    : model_(model),
388      delegate_(delegate),
389      view_model_(new views::ViewModel),
390      first_visible_index_(0),
391      last_visible_index_(-1),
392      overflow_button_(NULL),
393      drag_pointer_(NONE),
394      drag_view_(NULL),
395      drag_offset_(0),
396      start_drag_index_(-1),
397      context_menu_id_(0),
398      leading_inset_(kDefaultLeadingInset),
399      cancelling_drag_model_changed_(false),
400      last_hidden_index_(0),
401      closing_event_time_(base::TimeDelta()),
402      got_deleted_(NULL),
403      drag_and_drop_item_pinned_(false),
404      drag_and_drop_launcher_id_(0) {
405  DCHECK(model_);
406  bounds_animator_.reset(new views::BoundsAnimator(this));
407  bounds_animator_->AddObserver(this);
408  set_context_menu_controller(this);
409  focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
410  tooltip_.reset(new LauncherTooltipManager(
411      shelf_layout_manager, this));
412}
413
414LauncherView::~LauncherView() {
415  bounds_animator_->RemoveObserver(this);
416  model_->RemoveObserver(this);
417  // If we are inside the MenuRunner, we need to know if we were getting
418  // deleted while it was running.
419  if (got_deleted_)
420    *got_deleted_ = true;
421}
422
423void LauncherView::Init() {
424  model_->AddObserver(this);
425
426  const LauncherItems& items(model_->items());
427  for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
428    views::View* child = CreateViewForItem(*i);
429    child->SetPaintToLayer(true);
430    view_model_->Add(child, static_cast<int>(i - items.begin()));
431    AddChildView(child);
432  }
433  LauncherStatusChanged();
434  overflow_button_ = new OverflowButton(this);
435  overflow_button_->set_context_menu_controller(this);
436  ConfigureChildView(overflow_button_);
437  AddChildView(overflow_button_);
438  UpdateFirstButtonPadding();
439
440  // We'll layout when our bounds change.
441}
442
443void LauncherView::OnShelfAlignmentChanged() {
444  UpdateFirstButtonPadding();
445  overflow_button_->OnShelfAlignmentChanged();
446  LayoutToIdealBounds();
447  for (int i=0; i < view_model_->view_size(); ++i) {
448    // TODO: remove when AppIcon is a Launcher Button.
449    if (TYPE_APP_LIST == model_->items()[i].type &&
450        !ash::switches::UseAlternateShelfLayout()) {
451      ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
452      static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
453          shelf->SelectValueForShelfAlignment(
454              views::ImageButton::ALIGN_CENTER,
455              views::ImageButton::ALIGN_LEFT,
456              views::ImageButton::ALIGN_RIGHT,
457              views::ImageButton::ALIGN_CENTER),
458          shelf->SelectValueForShelfAlignment(
459              views::ImageButton::ALIGN_TOP,
460              views::ImageButton::ALIGN_MIDDLE,
461              views::ImageButton::ALIGN_MIDDLE,
462              views::ImageButton::ALIGN_BOTTOM));
463    }
464    if (i >= first_visible_index_ && i <= last_visible_index_)
465      view_model_->view_at(i)->Layout();
466  }
467  tooltip_->UpdateArrow();
468  if (overflow_bubble_)
469    overflow_bubble_->Hide();
470}
471
472void LauncherView::SchedulePaintForAllButtons() {
473  for (int i = 0; i < view_model_->view_size(); ++i) {
474    if (i >= first_visible_index_ && i <= last_visible_index_)
475      view_model_->view_at(i)->SchedulePaint();
476  }
477  if (overflow_button_ && overflow_button_->visible())
478    overflow_button_->SchedulePaint();
479}
480
481gfx::Rect LauncherView::GetIdealBoundsOfItemIcon(LauncherID id) {
482  int index = model_->ItemIndexByID(id);
483  if (index == -1 || (index > last_visible_index_ &&
484                      index < model_->FirstPanelIndex()))
485    return gfx::Rect();
486  const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
487  DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
488  LauncherButton* button =
489      static_cast<LauncherButton*>(view_model_->view_at(index));
490  gfx::Rect icon_bounds = button->GetIconBounds();
491  return gfx::Rect(GetMirroredXWithWidthInView(
492                       ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
493                   ideal_bounds.y() + icon_bounds.y(),
494                   icon_bounds.width(),
495                   icon_bounds.height());
496}
497
498void LauncherView::UpdatePanelIconPosition(LauncherID id,
499                                           const gfx::Point& midpoint) {
500  int current_index = model_->ItemIndexByID(id);
501  int first_panel_index = model_->FirstPanelIndex();
502  if (current_index < first_panel_index)
503    return;
504
505  gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
506                              midpoint.y());
507  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
508  int target_index = current_index;
509  while (target_index > first_panel_index &&
510         shelf->PrimaryAxisValue(view_model_->ideal_bounds(target_index).x(),
511                                 view_model_->ideal_bounds(target_index).y()) >
512         shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
513    --target_index;
514  }
515  while (target_index < view_model_->view_size() - 1 &&
516         shelf->PrimaryAxisValue(
517             view_model_->ideal_bounds(target_index).right(),
518             view_model_->ideal_bounds(target_index).bottom()) <
519         shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
520    ++target_index;
521  }
522  if (current_index != target_index)
523    model_->Move(current_index, target_index);
524}
525
526bool LauncherView::IsShowingMenu() const {
527#if !defined(OS_MACOSX)
528  return (launcher_menu_runner_.get() &&
529       launcher_menu_runner_->IsRunning());
530#endif
531  return false;
532}
533
534bool LauncherView::IsShowingOverflowBubble() const {
535  return overflow_bubble_.get() && overflow_bubble_->IsShowing();
536}
537
538views::View* LauncherView::GetAppListButtonView() const {
539  for (int i = 0; i < model_->item_count(); ++i) {
540    if (model_->items()[i].type == TYPE_APP_LIST)
541      return view_model_->view_at(i);
542  }
543
544  NOTREACHED() << "Applist button not found";
545  return NULL;
546}
547
548////////////////////////////////////////////////////////////////////////////////
549// LauncherView, FocusTraversable implementation:
550
551views::FocusSearch* LauncherView::GetFocusSearch() {
552  return focus_search_.get();
553}
554
555views::FocusTraversable* LauncherView::GetFocusTraversableParent() {
556  return parent()->GetFocusTraversable();
557}
558
559View* LauncherView::GetFocusTraversableParentView() {
560  return this;
561}
562
563void LauncherView::CreateDragIconProxy(
564    const gfx::Point& location_in_screen_coordinates,
565    const gfx::ImageSkia& icon,
566    views::View* replaced_view,
567    const gfx::Vector2d& cursor_offset_from_center,
568    float scale_factor) {
569  drag_replaced_view_ = replaced_view;
570  drag_image_.reset(new ash::internal::DragImageView(
571      drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow()));
572  drag_image_->SetImage(icon);
573  gfx::Size size = drag_image_->GetPreferredSize();
574  size.set_width(size.width() * scale_factor);
575  size.set_height(size.height() * scale_factor);
576  drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
577                       cursor_offset_from_center;
578  gfx::Rect drag_image_bounds(
579      GetPositionInScreen(location_in_screen_coordinates,
580                          drag_replaced_view_) - drag_image_offset_, size);
581  drag_image_->SetBoundsInScreen(drag_image_bounds);
582  drag_image_->SetWidgetVisible(true);
583}
584
585void LauncherView::UpdateDragIconProxy(
586    const gfx::Point& location_in_screen_coordinates) {
587  drag_image_->SetScreenPosition(
588      GetPositionInScreen(location_in_screen_coordinates,
589                          drag_replaced_view_) - drag_image_offset_);
590}
591
592void LauncherView::DestroyDragIconProxy() {
593  drag_image_.reset();
594  drag_image_offset_ = gfx::Vector2d(0, 0);
595}
596
597bool LauncherView::StartDrag(const std::string& app_id,
598                             const gfx::Point& location_in_screen_coordinates) {
599  // Bail if an operation is already going on - or the cursor is not inside.
600  // This could happen if mouse / touch operations overlap.
601  if (drag_and_drop_launcher_id_ ||
602      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
603    return false;
604
605  // If the AppsGridView (which was dispatching this event) was opened by our
606  // button, LauncherView dragging operations are locked and we have to unlock.
607  CancelDrag(-1);
608  drag_and_drop_item_pinned_ = false;
609  drag_and_drop_app_id_ = app_id;
610  drag_and_drop_launcher_id_ =
611      delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
612  // Check if the application is known and pinned - if not, we have to pin it so
613  // that we can re-arrange the launcher order accordingly. Note that items have
614  // to be pinned to give them the same (order) possibilities as a shortcut.
615  if (!drag_and_drop_launcher_id_ || !delegate_->IsAppPinned(app_id)) {
616    delegate_->PinAppWithID(app_id);
617    drag_and_drop_launcher_id_ =
618        delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
619    if (!drag_and_drop_launcher_id_)
620      return false;
621    drag_and_drop_item_pinned_ = true;
622  }
623  views::View* drag_and_drop_view = view_model_->view_at(
624      model_->ItemIndexByID(drag_and_drop_launcher_id_));
625  DCHECK(drag_and_drop_view);
626
627  // Since there is already an icon presented by the caller, we hide this item
628  // for now. That has to be done by reducing the size since the visibility will
629  // change once a regrouping animation is performed.
630  pre_drag_and_drop_size_ = drag_and_drop_view->size();
631  drag_and_drop_view->SetSize(gfx::Size());
632
633  // First we have to center the mouse cursor over the item.
634  gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
635  views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
636  ui::MouseEvent event(ui::ET_MOUSE_PRESSED,
637                       pt, location_in_screen_coordinates, 0);
638  PointerPressedOnButton(
639      drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
640
641  // Drag the item where it really belongs.
642  Drag(location_in_screen_coordinates);
643  return true;
644}
645
646bool LauncherView::Drag(const gfx::Point& location_in_screen_coordinates) {
647  if (!drag_and_drop_launcher_id_ ||
648      !GetBoundsInScreen().Contains(location_in_screen_coordinates))
649    return false;
650
651  gfx::Point pt = location_in_screen_coordinates;
652  views::View* drag_and_drop_view = view_model_->view_at(
653      model_->ItemIndexByID(drag_and_drop_launcher_id_));
654  views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
655
656  ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, gfx::Point(), 0);
657  PointerDraggedOnButton(
658      drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
659  return true;
660}
661
662void LauncherView::EndDrag(bool cancel) {
663  if (!drag_and_drop_launcher_id_)
664    return;
665
666  views::View* drag_and_drop_view = view_model_->view_at(
667      model_->ItemIndexByID(drag_and_drop_launcher_id_));
668  PointerReleasedOnButton(
669      drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, cancel);
670
671  // Either destroy the temporarily created item - or - make the item visible.
672  if (drag_and_drop_item_pinned_ && cancel)
673    delegate_->UnpinAppsWithID(drag_and_drop_app_id_);
674  else if (drag_and_drop_view)
675    drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
676
677  drag_and_drop_launcher_id_ = 0;
678}
679
680void LauncherView::LayoutToIdealBounds() {
681  IdealBounds ideal_bounds;
682  CalculateIdealBounds(&ideal_bounds);
683
684  if (bounds_animator_->IsAnimating())
685    AnimateToIdealBounds();
686  else
687    views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
688
689  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
690}
691
692void LauncherView::CalculateIdealBounds(IdealBounds* bounds) {
693  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
694
695  int available_size = shelf->PrimaryAxisValue(width(), height());
696  DCHECK(model_->item_count() == view_model_->view_size());
697  if (!available_size)
698    return;
699
700  int first_panel_index = model_->FirstPanelIndex();
701  int last_button_index = first_panel_index - 1;
702
703  // Initial x,y values account both leading_inset in primary
704  // coordinate and secondary coordinate based on the dynamic edge of the
705  // launcher (eg top edge on bottom-aligned launcher).
706  int inset = ash::switches::UseAlternateShelfLayout() ? 0 : leading_inset();
707  int x = shelf->SelectValueForShelfAlignment(inset, 0, 0, inset);
708  int y = shelf->SelectValueForShelfAlignment(0, inset, inset, 0);
709
710  int button_size = ash::switches::UseAlternateShelfLayout() ?
711      kButtonSize : kLauncherPreferredSize;
712  int button_spacing = ash::switches::UseAlternateShelfLayout() ?
713      kAlternateButtonSpacing : kButtonSpacing;
714
715  int w = shelf->PrimaryAxisValue(button_size, width());
716  int h = shelf->PrimaryAxisValue(height(), button_size);
717  for (int i = 0; i < view_model_->view_size(); ++i) {
718    if (i < first_visible_index_) {
719      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
720      continue;
721    }
722
723    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
724    if (i != last_button_index) {
725      x = shelf->PrimaryAxisValue(x + w + button_spacing, x);
726      y = shelf->PrimaryAxisValue(y, y + h + button_spacing);
727    }
728  }
729
730  if (is_overflow_mode()) {
731    DCHECK_LT(last_visible_index_, view_model_->view_size());
732    for (int i = 0; i < view_model_->view_size(); ++i) {
733      bool visible = i >= first_visible_index_ &&
734          i <= last_visible_index_;
735      if (!ash::switches::UseAlternateShelfLayout())
736        visible &= i != last_button_index;
737      view_model_->view_at(i)->SetVisible(visible);
738    }
739    return;
740  }
741
742  // To address Fitt's law, we make the first launcher button include the
743  // leading inset (if there is one).
744  if (!ash::switches::UseAlternateShelfLayout()) {
745    if (view_model_->view_size() > 0) {
746      view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
747          shelf->PrimaryAxisValue(inset + w, w),
748          shelf->PrimaryAxisValue(h, inset + h))));
749    }
750  }
751
752  // Right aligned icons.
753  int end_position = available_size - button_spacing;
754  x = shelf->PrimaryAxisValue(end_position, 0);
755  y = shelf->PrimaryAxisValue(0, end_position);
756  for (int i = view_model_->view_size() - 1;
757       i >= first_panel_index; --i) {
758    x = shelf->PrimaryAxisValue(x - w - button_spacing, x);
759    y = shelf->PrimaryAxisValue(y, y - h - button_spacing);
760    view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
761    end_position = shelf->PrimaryAxisValue(x, y);
762  }
763
764  // Icons on the left / top are guaranteed up to kLeftIconProportion of
765  // the available space.
766  int last_icon_position = shelf->PrimaryAxisValue(
767      view_model_->ideal_bounds(last_button_index).right(),
768      view_model_->ideal_bounds(last_button_index).bottom())
769      + button_size + inset;
770  if (!ash::switches::UseAlternateShelfLayout())
771      last_icon_position += button_size;
772  int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
773  if (last_icon_position < reserved_icon_space)
774    end_position = last_icon_position;
775  else
776    end_position = std::max(end_position, reserved_icon_space);
777
778  bounds->overflow_bounds.set_size(gfx::Size(
779      shelf->PrimaryAxisValue(w, width()),
780      shelf->PrimaryAxisValue(height(), h)));
781  if (ash::switches::UseAlternateShelfLayout())
782    last_visible_index_ = DetermineLastVisibleIndex(
783        end_position - button_size);
784  else
785    last_visible_index_ = DetermineLastVisibleIndex(
786        end_position - inset - 2 * button_size);
787  last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
788  bool show_overflow =
789      ((ash::switches::UseAlternateShelfLayout() ? 0 : 1) +
790      last_visible_index_ < last_button_index ||
791      last_hidden_index_ >= first_panel_index);
792
793  // Create Space for the overflow button
794  if (show_overflow && ash::switches::UseAlternateShelfLayout() &&
795      last_visible_index_ > 0)
796    --last_visible_index_;
797  for (int i = 0; i < view_model_->view_size(); ++i) {
798    bool visible = i <= last_visible_index_ || i > last_hidden_index_;
799    // Always show the app list.
800    if (!ash::switches::UseAlternateShelfLayout())
801      visible |= (i == last_button_index);
802    view_model_->view_at(i)->SetVisible(visible);
803  }
804
805  overflow_button_->SetVisible(show_overflow);
806  if (show_overflow) {
807    DCHECK_NE(0, view_model_->view_size());
808    if (last_visible_index_ == -1) {
809      x = shelf->SelectValueForShelfAlignment(inset, 0, 0, inset);
810      y = shelf->SelectValueForShelfAlignment(0, inset, inset, 0);
811    } else if (last_visible_index_ == last_button_index) {
812      x = view_model_->ideal_bounds(last_visible_index_).x();
813      y = view_model_->ideal_bounds(last_visible_index_).y();
814    } else {
815      x = shelf->PrimaryAxisValue(
816          view_model_->ideal_bounds(last_visible_index_).right(),
817          view_model_->ideal_bounds(last_visible_index_).x());
818      y = shelf->PrimaryAxisValue(
819          view_model_->ideal_bounds(last_visible_index_).y(),
820          view_model_->ideal_bounds(last_visible_index_).bottom());
821    }
822    // Set all hidden panel icon positions to be on the overflow button.
823    for (int i = first_panel_index; i <= last_hidden_index_; ++i)
824      view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
825
826    bounds->overflow_bounds.set_x(x);
827    bounds->overflow_bounds.set_y(y);
828    if (!ash::switches::UseAlternateShelfLayout()) {
829      // Position app list after overflow button.
830      gfx::Rect app_list_bounds = view_model_->ideal_bounds(last_button_index);
831
832      x = shelf->PrimaryAxisValue(x + w + button_spacing, x);
833      y = shelf->PrimaryAxisValue(y, y + h + button_spacing);
834      app_list_bounds.set_x(x);
835      app_list_bounds.set_y(y);
836      view_model_->set_ideal_bounds(last_button_index, app_list_bounds);
837    }
838    if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
839      UpdateOverflowRange(overflow_bubble_->launcher_view());
840  } else {
841    if (overflow_bubble_)
842      overflow_bubble_->Hide();
843  }
844}
845
846int LauncherView::DetermineLastVisibleIndex(int max_value) const {
847  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
848
849  int index = model_->FirstPanelIndex() - 1;
850  while (index >= 0 &&
851         shelf->PrimaryAxisValue(
852             view_model_->ideal_bounds(index).right(),
853             view_model_->ideal_bounds(index).bottom()) > max_value) {
854    index--;
855  }
856  return index;
857}
858
859int LauncherView::DetermineFirstVisiblePanelIndex(int min_value) const {
860  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
861
862  int index = model_->FirstPanelIndex();
863  while (index < view_model_->view_size() &&
864         shelf->PrimaryAxisValue(
865             view_model_->ideal_bounds(index).right(),
866             view_model_->ideal_bounds(index).bottom()) < min_value) {
867    ++index;
868  }
869  return index;
870}
871
872void LauncherView::AddIconObserver(LauncherIconObserver* observer) {
873  observers_.AddObserver(observer);
874}
875
876void LauncherView::RemoveIconObserver(LauncherIconObserver* observer) {
877  observers_.RemoveObserver(observer);
878}
879
880void LauncherView::AnimateToIdealBounds() {
881  IdealBounds ideal_bounds;
882  CalculateIdealBounds(&ideal_bounds);
883  for (int i = 0; i < view_model_->view_size(); ++i) {
884    View* view = view_model_->view_at(i);
885    bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
886    // Now that the item animation starts, we have to make sure that the
887    // padding of the first gets properly transferred to the new first item.
888    if (i && view->border())
889      view->set_border(NULL);
890    else if (!i && !view->border())
891      UpdateFirstButtonPadding();
892  }
893  overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
894}
895
896views::View* LauncherView::CreateViewForItem(const LauncherItem& item) {
897  views::View* view = NULL;
898  switch (item.type) {
899    case TYPE_TABBED: {
900      TabbedLauncherButton* button =
901          TabbedLauncherButton::Create(
902              this,
903              this,
904              tooltip_->shelf_layout_manager(),
905              item.is_incognito ?
906                  TabbedLauncherButton::STATE_INCOGNITO :
907                  TabbedLauncherButton::STATE_NOT_INCOGNITO);
908      button->SetTabImage(item.image);
909      ReflectItemStatus(item, button);
910      view = button;
911      break;
912    }
913
914    case TYPE_BROWSER_SHORTCUT:
915    case TYPE_APP_SHORTCUT:
916    case TYPE_WINDOWED_APP:
917    case TYPE_PLATFORM_APP:
918    case TYPE_APP_PANEL: {
919      LauncherButton* button = LauncherButton::Create(
920          this, this, tooltip_->shelf_layout_manager());
921      button->SetImage(item.image);
922      ReflectItemStatus(item, button);
923      view = button;
924      break;
925    }
926
927    case TYPE_APP_LIST: {
928      if (ash::switches::UseAlternateShelfLayout()) {
929        view = new AlternateAppListButton(this, this,
930            tooltip_->shelf_layout_manager()->shelf_widget());
931      } else {
932        // TODO(dave): turn this into a LauncherButton too.
933        AppListButton* button = new AppListButton(this, this);
934        ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
935        button->SetImageAlignment(
936            shelf->SelectValueForShelfAlignment(
937                views::ImageButton::ALIGN_CENTER,
938                views::ImageButton::ALIGN_LEFT,
939                views::ImageButton::ALIGN_RIGHT,
940                views::ImageButton::ALIGN_CENTER),
941            shelf->SelectValueForShelfAlignment(
942                views::ImageButton::ALIGN_TOP,
943                views::ImageButton::ALIGN_MIDDLE,
944                views::ImageButton::ALIGN_MIDDLE,
945                views::ImageButton::ALIGN_BOTTOM));
946        view = button;
947      }
948      break;
949    }
950
951    default:
952      break;
953  }
954  view->set_context_menu_controller(this);
955  view->set_focus_border(new LauncherButtonFocusBorder);
956
957  DCHECK(view);
958  ConfigureChildView(view);
959  return view;
960}
961
962void LauncherView::FadeIn(views::View* view) {
963  view->SetVisible(true);
964  view->layer()->SetOpacity(0);
965  AnimateToIdealBounds();
966  bounds_animator_->SetAnimationDelegate(
967      view, new FadeInAnimationDelegate(view), true);
968}
969
970void LauncherView::PrepareForDrag(Pointer pointer,
971                                  const ui::LocatedEvent& event) {
972  DCHECK(!dragging());
973  DCHECK(drag_view_);
974  drag_pointer_ = pointer;
975  start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
976
977  // If the item is no longer draggable, bail out.
978  if (start_drag_index_ == -1 ||
979      !delegate_->IsDraggable(model_->items()[start_drag_index_])) {
980    CancelDrag(-1);
981    return;
982  }
983
984  // Move the view to the front so that it appears on top of other views.
985  ReorderChildView(drag_view_, -1);
986  bounds_animator_->StopAnimatingView(drag_view_);
987}
988
989void LauncherView::ContinueDrag(const ui::LocatedEvent& event) {
990  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
991
992  // TODO: I don't think this works correctly with RTL.
993  gfx::Point drag_point(event.location());
994  views::View::ConvertPointToTarget(drag_view_, this, &drag_point);
995  int current_index = view_model_->GetIndexOfView(drag_view_);
996  DCHECK_NE(-1, current_index);
997
998  // If the item is no longer draggable, bail out.
999  if (current_index == -1 ||
1000      !delegate_->IsDraggable(model_->items()[current_index])) {
1001    CancelDrag(-1);
1002    return;
1003  }
1004
1005  // Constrain the location to the range of valid indices for the type.
1006  std::pair<int, int> indices(GetDragRange(current_index));
1007  int first_drag_index = indices.first;
1008  int last_drag_index = indices.second;
1009  // If the last index isn't valid, we're overflowing. Constrain to the app list
1010  // (which is the last visible item).
1011  if (first_drag_index < model_->FirstPanelIndex() &&
1012      last_drag_index > last_visible_index_)
1013    last_drag_index = last_visible_index_;
1014  int x = 0, y = 0;
1015  if (shelf->IsHorizontalAlignment()) {
1016    x = std::max(view_model_->ideal_bounds(indices.first).x(),
1017                     drag_point.x() - drag_offset_);
1018    x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
1019                 view_model_->ideal_bounds(current_index).width(),
1020                 x);
1021    if (drag_view_->x() == x)
1022      return;
1023    drag_view_->SetX(x);
1024  } else {
1025    y = std::max(view_model_->ideal_bounds(indices.first).y(),
1026                     drag_point.y() - drag_offset_);
1027    y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
1028                 view_model_->ideal_bounds(current_index).height(),
1029                 y);
1030    if (drag_view_->y() == y)
1031      return;
1032    drag_view_->SetY(y);
1033  }
1034
1035  int target_index =
1036      views::ViewModelUtils::DetermineMoveIndex(
1037          *view_model_, drag_view_,
1038          shelf->IsHorizontalAlignment() ?
1039              views::ViewModelUtils::HORIZONTAL :
1040              views::ViewModelUtils::VERTICAL,
1041          x, y);
1042  target_index =
1043      std::min(indices.second, std::max(target_index, indices.first));
1044  if (target_index == current_index)
1045    return;
1046
1047  // Change the model, the LauncherItemMoved() callback will handle the
1048  // |view_model_| update.
1049  model_->Move(current_index, target_index);
1050  bounds_animator_->StopAnimatingView(drag_view_);
1051}
1052
1053bool LauncherView::SameDragType(LauncherItemType typea,
1054                                LauncherItemType typeb) const {
1055  switch (typea) {
1056    case TYPE_TABBED:
1057    case TYPE_PLATFORM_APP:
1058      return (typeb == TYPE_TABBED || typeb == TYPE_PLATFORM_APP);
1059    case TYPE_APP_SHORTCUT:
1060    case TYPE_BROWSER_SHORTCUT:
1061      return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1062    case TYPE_WINDOWED_APP:
1063    case TYPE_APP_LIST:
1064    case TYPE_APP_PANEL:
1065      return typeb == typea;
1066  }
1067  NOTREACHED();
1068  return false;
1069}
1070
1071std::pair<int, int> LauncherView::GetDragRange(int index) {
1072  int min_index = -1;
1073  int max_index = -1;
1074  LauncherItemType type = model_->items()[index].type;
1075  for (int i = 0; i < model_->item_count(); ++i) {
1076    if (SameDragType(model_->items()[i].type, type)) {
1077      if (min_index == -1)
1078        min_index = i;
1079      max_index = i;
1080    }
1081  }
1082  return std::pair<int, int>(min_index, max_index);
1083}
1084
1085void LauncherView::ConfigureChildView(views::View* view) {
1086  view->SetPaintToLayer(true);
1087  view->layer()->SetFillsBoundsOpaquely(false);
1088}
1089
1090void LauncherView::ToggleOverflowBubble() {
1091  if (IsShowingOverflowBubble()) {
1092    overflow_bubble_->Hide();
1093    return;
1094  }
1095
1096  if (!overflow_bubble_)
1097    overflow_bubble_.reset(new OverflowBubble());
1098
1099  LauncherView* overflow_view = new LauncherView(
1100      model_, delegate_, tooltip_->shelf_layout_manager());
1101  overflow_view->Init();
1102  overflow_view->OnShelfAlignmentChanged();
1103  UpdateOverflowRange(overflow_view);
1104
1105  overflow_bubble_->Show(overflow_button_, overflow_view);
1106
1107  Shell::GetInstance()->UpdateShelfVisibility();
1108}
1109
1110void LauncherView::UpdateFirstButtonPadding() {
1111  if (ash::switches::UseAlternateShelfLayout())
1112    return;
1113
1114  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1115
1116  // Creates an empty border for first launcher button to make included leading
1117  // inset act as the button's padding. This is only needed on button creation
1118  // and when shelf alignment changes.
1119  if (view_model_->view_size() > 0) {
1120    view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
1121        shelf->PrimaryAxisValue(0, leading_inset()),
1122        shelf->PrimaryAxisValue(leading_inset(), 0),
1123        0,
1124        0));
1125  }
1126}
1127
1128void LauncherView::OnFadeOutAnimationEnded() {
1129  AnimateToIdealBounds();
1130
1131  // If overflow button is visible and there is a valid new last item, fading
1132  // the new last item in after sliding animation is finished.
1133  if (overflow_button_->visible() && last_visible_index_ >= 0) {
1134    views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1135    last_visible_view->layer()->SetOpacity(0);
1136    bounds_animator_->SetAnimationDelegate(
1137        last_visible_view,
1138        new LauncherView::StartFadeAnimationDelegate(this, last_visible_view),
1139        true);
1140  }
1141}
1142
1143void LauncherView::UpdateOverflowRange(LauncherView* overflow_view) {
1144  const int first_overflow_index = last_visible_index_ + 1;
1145  const int last_overflow_index = last_hidden_index_;
1146  DCHECK_LE(first_overflow_index, last_overflow_index);
1147  DCHECK_LT(last_overflow_index, view_model_->view_size());
1148
1149  overflow_view->first_visible_index_ = first_overflow_index;
1150  overflow_view->last_visible_index_ = last_overflow_index;
1151}
1152
1153bool LauncherView::ShouldHideTooltip(const gfx::Point& cursor_location) {
1154  gfx::Rect active_bounds;
1155
1156  for (int i = 0; i < child_count(); ++i) {
1157    views::View* child = child_at(i);
1158    if (child == overflow_button_)
1159      continue;
1160    if (!ShouldShowTooltipForView(child))
1161      continue;
1162
1163    gfx::Rect child_bounds = child->GetMirroredBounds();
1164    active_bounds.Union(child_bounds);
1165  }
1166
1167  return !active_bounds.Contains(cursor_location);
1168}
1169
1170int LauncherView::CancelDrag(int modified_index) {
1171  if (!drag_view_)
1172    return modified_index;
1173  bool was_dragging = dragging();
1174  int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1175  drag_pointer_ = NONE;
1176  drag_view_ = NULL;
1177  if (drag_view_index == modified_index) {
1178    // The view that was being dragged is being modified. Don't do anything.
1179    return modified_index;
1180  }
1181  if (!was_dragging)
1182    return modified_index;
1183
1184  // Restore previous position, tracking the position of the modified view.
1185  bool at_end = modified_index == view_model_->view_size();
1186  views::View* modified_view =
1187      (modified_index >= 0 && !at_end) ?
1188      view_model_->view_at(modified_index) : NULL;
1189  model_->Move(drag_view_index, start_drag_index_);
1190
1191  // If the modified view will be at the end of the list, return the new end of
1192  // the list.
1193  if (at_end)
1194    return view_model_->view_size();
1195  return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
1196}
1197
1198gfx::Size LauncherView::GetPreferredSize() {
1199  IdealBounds ideal_bounds;
1200  CalculateIdealBounds(&ideal_bounds);
1201
1202  const int app_list_index = view_model_->view_size() - 1;
1203  const int last_button_index = is_overflow_mode() ?
1204      last_visible_index_ : app_list_index;
1205  const gfx::Rect last_button_bounds =
1206      last_button_index  >= first_visible_index_ ?
1207          view_model_->view_at(last_button_index)->bounds() :
1208          gfx::Rect(gfx::Size(kLauncherPreferredSize,
1209                              kLauncherPreferredSize));
1210
1211  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1212
1213  if (shelf->IsHorizontalAlignment()) {
1214    return gfx::Size(last_button_bounds.right() + leading_inset(),
1215                     kLauncherPreferredSize);
1216  }
1217
1218  return gfx::Size(kLauncherPreferredSize,
1219                   last_button_bounds.bottom() + leading_inset());
1220}
1221
1222void LauncherView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1223  LayoutToIdealBounds();
1224  FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1225                    OnLauncherIconPositionsChanged());
1226
1227  if (IsShowingOverflowBubble())
1228    overflow_bubble_->Hide();
1229}
1230
1231views::FocusTraversable* LauncherView::GetPaneFocusTraversable() {
1232  return this;
1233}
1234
1235void LauncherView::GetAccessibleState(ui::AccessibleViewState* state) {
1236  state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
1237  state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
1238}
1239
1240void LauncherView::OnGestureEvent(ui::GestureEvent* event) {
1241  if (gesture_handler_.ProcessGestureEvent(*event))
1242    event->StopPropagation();
1243}
1244
1245void LauncherView::LauncherItemAdded(int model_index) {
1246  {
1247    base::AutoReset<bool> cancelling_drag(
1248        &cancelling_drag_model_changed_, true);
1249    model_index = CancelDrag(model_index);
1250  }
1251  views::View* view = CreateViewForItem(model_->items()[model_index]);
1252  AddChildView(view);
1253  // Hide the view, it'll be made visible when the animation is done. Using
1254  // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1255  // the view's visibility.
1256  view->layer()->SetOpacity(0);
1257  view_model_->Add(view, model_index);
1258
1259  // Give the button its ideal bounds. That way if we end up animating the
1260  // button before this animation completes it doesn't appear at some random
1261  // spot (because it was in the middle of animating from 0,0 0x0 to its
1262  // target).
1263  IdealBounds ideal_bounds;
1264  CalculateIdealBounds(&ideal_bounds);
1265  view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1266
1267  // The first animation moves all the views to their target position. |view|
1268  // is hidden, so it visually appears as though we are providing space for
1269  // it. When done we'll fade the view in.
1270  AnimateToIdealBounds();
1271  if (model_index <= last_visible_index_ ||
1272      model_index >= model_->FirstPanelIndex()) {
1273    bounds_animator_->SetAnimationDelegate(
1274        view, new StartFadeAnimationDelegate(this, view), true);
1275  } else {
1276    // Undo the hiding if animation does not run.
1277    view->layer()->SetOpacity(1.0f);
1278  }
1279}
1280
1281void LauncherView::LauncherItemRemoved(int model_index, LauncherID id) {
1282#if !defined(OS_MACOSX)
1283  if (id == context_menu_id_)
1284    launcher_menu_runner_.reset();
1285#endif
1286  {
1287    base::AutoReset<bool> cancelling_drag(
1288        &cancelling_drag_model_changed_, true);
1289    model_index = CancelDrag(model_index);
1290  }
1291  views::View* view = view_model_->view_at(model_index);
1292  view_model_->Remove(model_index);
1293  // The first animation fades out the view. When done we'll animate the rest of
1294  // the views to their target location.
1295  bounds_animator_->AnimateViewTo(view, view->bounds());
1296  bounds_animator_->SetAnimationDelegate(
1297      view, new FadeOutAnimationDelegate(this, view), true);
1298
1299  // If overflow bubble is visible, sanitize overflow range first and when the
1300  // above animation finishes, CalculateIdealBounds will be called to get
1301  // correct overflow range. CalculateIdealBounds could hide overflow bubble
1302  // and triggers LauncherItemChanged. And since we are still in the middle
1303  // of LauncherItemRemoved, LauncherView in overflow bubble is not synced
1304  // with LauncherModel and will crash.
1305  if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1306    last_hidden_index_ = std::min(last_hidden_index_,
1307                                  view_model_->view_size() - 1);
1308    UpdateOverflowRange(overflow_bubble_->launcher_view());
1309  }
1310}
1311
1312void LauncherView::LauncherItemChanged(int model_index,
1313                                       const ash::LauncherItem& old_item) {
1314  const LauncherItem& item(model_->items()[model_index]);
1315  if (old_item.type != item.type) {
1316    // Type changed, swap the views.
1317    model_index = CancelDrag(model_index);
1318    scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1319    bounds_animator_->StopAnimatingView(old_view.get());
1320    view_model_->Remove(model_index);
1321    views::View* new_view = CreateViewForItem(item);
1322    AddChildView(new_view);
1323    view_model_->Add(new_view, model_index);
1324    new_view->SetBoundsRect(old_view->bounds());
1325    return;
1326  }
1327
1328  views::View* view = view_model_->view_at(model_index);
1329  switch (item.type) {
1330    case TYPE_TABBED: {
1331      TabbedLauncherButton* button = static_cast<TabbedLauncherButton*>(view);
1332      gfx::Size pref = button->GetPreferredSize();
1333      button->SetTabImage(item.image);
1334      if (pref != button->GetPreferredSize())
1335        AnimateToIdealBounds();
1336      else
1337        button->SchedulePaint();
1338      ReflectItemStatus(item, button);
1339      break;
1340    }
1341    case TYPE_BROWSER_SHORTCUT:
1342      // Fallthrough for the new Launcher since it needs to show the activation
1343      // change as well.
1344    case TYPE_APP_SHORTCUT:
1345    case TYPE_WINDOWED_APP:
1346    case TYPE_PLATFORM_APP:
1347    case TYPE_APP_PANEL: {
1348      LauncherButton* button = static_cast<LauncherButton*>(view);
1349      ReflectItemStatus(item, button);
1350      // The browser shortcut is currently not a "real" item and as such the
1351      // the image is bogous as well. We therefore keep the image as is for it.
1352      if (item.type != TYPE_BROWSER_SHORTCUT)
1353        button->SetImage(item.image);
1354      button->SchedulePaint();
1355      break;
1356    }
1357
1358    default:
1359      break;
1360  }
1361}
1362
1363void LauncherView::LauncherItemMoved(int start_index, int target_index) {
1364  view_model_->Move(start_index, target_index);
1365  // When cancelling a drag due to a launcher item being added, the currently
1366  // dragged item is moved back to its initial position. AnimateToIdealBounds
1367  // will be called again when the new item is added to the |view_model_| but
1368  // at this time the |view_model_| is inconsistent with the |model_|.
1369  if (!cancelling_drag_model_changed_)
1370    AnimateToIdealBounds();
1371}
1372
1373void LauncherView::LauncherStatusChanged() {
1374  if (ash::switches::UseAlternateShelfLayout())
1375    return;
1376  AppListButton* app_list_button =
1377      static_cast<AppListButton*>(GetAppListButtonView());
1378  if (model_->status() == LauncherModel::STATUS_LOADING)
1379    app_list_button->StartLoadingAnimation();
1380  else
1381    app_list_button->StopLoadingAnimation();
1382}
1383
1384void LauncherView::PointerPressedOnButton(views::View* view,
1385                                          Pointer pointer,
1386                                          const ui::LocatedEvent& event) {
1387  if (drag_view_)
1388    return;
1389
1390  tooltip_->Close();
1391  int index = view_model_->GetIndexOfView(view);
1392  if (index == -1 ||
1393      view_model_->view_size() <= 1 ||
1394      !delegate_->IsDraggable(model_->items()[index]))
1395    return;  // View is being deleted or not draggable, ignore request.
1396
1397  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1398
1399  drag_view_ = view;
1400  drag_offset_ = shelf->PrimaryAxisValue(event.x(), event.y());
1401}
1402
1403void LauncherView::PointerDraggedOnButton(views::View* view,
1404                                          Pointer pointer,
1405                                          const ui::LocatedEvent& event) {
1406  ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1407  if (!dragging() && drag_view_ &&
1408      shelf->PrimaryAxisValue(abs(event.x() - drag_offset_),
1409                              abs(event.y() - drag_offset_)) >=
1410      kMinimumDragDistance) {
1411    PrepareForDrag(pointer, event);
1412  }
1413  if (drag_pointer_ == pointer)
1414    ContinueDrag(event);
1415}
1416
1417void LauncherView::PointerReleasedOnButton(views::View* view,
1418                                           Pointer pointer,
1419                                           bool canceled) {
1420  if (canceled) {
1421    CancelDrag(-1);
1422  } else if (drag_pointer_ == pointer) {
1423    drag_pointer_ = NONE;
1424    AnimateToIdealBounds();
1425  }
1426  // If the drag pointer is NONE, no drag operation is going on and the
1427  // drag_view can be released.
1428  if (drag_pointer_ == NONE)
1429    drag_view_ = NULL;
1430}
1431
1432void LauncherView::MouseMovedOverButton(views::View* view) {
1433  if (!ShouldShowTooltipForView(view))
1434    return;
1435
1436  if (!tooltip_->IsVisible())
1437    tooltip_->ResetTimer();
1438}
1439
1440void LauncherView::MouseEnteredButton(views::View* view) {
1441  if (!ShouldShowTooltipForView(view))
1442    return;
1443
1444  if (tooltip_->IsVisible()) {
1445    tooltip_->ShowImmediately(view, GetAccessibleName(view));
1446  } else {
1447    tooltip_->ShowDelayed(view, GetAccessibleName(view));
1448  }
1449}
1450
1451void LauncherView::MouseExitedButton(views::View* view) {
1452  if (!tooltip_->IsVisible())
1453    tooltip_->StopTimer();
1454}
1455
1456base::string16 LauncherView::GetAccessibleName(const views::View* view) {
1457  int view_index = view_model_->GetIndexOfView(view);
1458  // May be -1 while in the process of animating closed.
1459  if (view_index == -1)
1460    return base::string16();
1461
1462  switch (model_->items()[view_index].type) {
1463    case TYPE_TABBED:
1464    case TYPE_APP_PANEL:
1465    case TYPE_APP_SHORTCUT:
1466    case TYPE_WINDOWED_APP:
1467    case TYPE_PLATFORM_APP:
1468    case TYPE_BROWSER_SHORTCUT:
1469      return delegate_->GetTitle(model_->items()[view_index]);
1470
1471    case TYPE_APP_LIST:
1472      return model_->status() == LauncherModel::STATUS_LOADING ?
1473          l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_SYNCING_TITLE) :
1474          l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE);
1475  }
1476  return base::string16();
1477}
1478
1479void LauncherView::ButtonPressed(views::Button* sender,
1480                                 const ui::Event& event) {
1481  // Do not handle mouse release during drag.
1482  if (dragging())
1483    return;
1484
1485  tooltip_->Close();
1486
1487  if (sender == overflow_button_) {
1488    ToggleOverflowBubble();
1489    return;
1490  }
1491
1492  int view_index = view_model_->GetIndexOfView(sender);
1493  // May be -1 while in the process of animating closed.
1494  if (view_index == -1)
1495    return;
1496
1497  // If the previous menu was closed by the same event as this one, we ignore
1498  // the call.
1499  if (!IsUsableEvent(event))
1500    return;
1501
1502  {
1503    ScopedTargetRootWindow scoped_target(
1504        sender->GetWidget()->GetNativeView()->GetRootWindow());
1505    // Slow down activation animations if shift key is pressed.
1506    scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1507    if (event.IsShiftDown()) {
1508      slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1509            ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1510    }
1511
1512    // Collect usage statistics before we decide what to do with the click.
1513    switch (model_->items()[view_index].type) {
1514      case TYPE_APP_SHORTCUT:
1515      case TYPE_WINDOWED_APP:
1516      case TYPE_PLATFORM_APP:
1517      case TYPE_BROWSER_SHORTCUT:
1518        Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1519            UMA_LAUNCHER_CLICK_ON_APP);
1520        // Fallthrough
1521      case TYPE_TABBED:
1522      case TYPE_APP_PANEL:
1523        delegate_->ItemSelected(model_->items()[view_index], event);
1524        break;
1525
1526      case TYPE_APP_LIST:
1527        Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1528            UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1529        Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView());
1530        break;
1531    }
1532  }
1533
1534  if (model_->items()[view_index].type != TYPE_APP_LIST)
1535    ShowListMenuForView(model_->items()[view_index], sender, event);
1536}
1537
1538bool LauncherView::ShowListMenuForView(const LauncherItem& item,
1539                                       views::View* source,
1540                                       const ui::Event& event) {
1541  scoped_ptr<ash::LauncherMenuModel> menu_model;
1542  menu_model.reset(delegate_->CreateApplicationMenu(item, event.flags()));
1543
1544  // Make sure we have a menu and it has at least two items in addition to the
1545  // application title and the 3 spacing separators.
1546  if (!menu_model.get() || menu_model->GetItemCount() <= 5)
1547    return false;
1548
1549  ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1550               new LauncherMenuModelAdapter(menu_model.get())),
1551           source,
1552           gfx::Point(),
1553           false,
1554           ui::GetMenuSourceTypeForEvent(event));
1555  return true;
1556}
1557
1558void LauncherView::ShowContextMenuForView(views::View* source,
1559                                          const gfx::Point& point,
1560                                          ui:: MenuSourceType source_type) {
1561  int view_index = view_model_->GetIndexOfView(source);
1562  if (view_index != -1 &&
1563      model_->items()[view_index].type == TYPE_APP_LIST) {
1564    view_index = -1;
1565  }
1566
1567  tooltip_->Close();
1568
1569  if (view_index == -1) {
1570    Shell::GetInstance()->ShowContextMenu(point, source_type);
1571    return;
1572  }
1573  scoped_ptr<ui::MenuModel> menu_model(delegate_->CreateContextMenu(
1574      model_->items()[view_index],
1575      source->GetWidget()->GetNativeView()->GetRootWindow()));
1576  if (!menu_model)
1577    return;
1578  base::AutoReset<LauncherID> reseter(
1579      &context_menu_id_,
1580      view_index == -1 ? 0 : model_->items()[view_index].id);
1581
1582  ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1583               new views::MenuModelAdapter(menu_model.get())),
1584           source,
1585           point,
1586           true,
1587           source_type);
1588}
1589
1590void LauncherView::ShowMenu(
1591    scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
1592    views::View* source,
1593    const gfx::Point& click_point,
1594    bool context_menu,
1595    ui::MenuSourceType source_type) {
1596  closing_event_time_ = base::TimeDelta();
1597  launcher_menu_runner_.reset(
1598      new views::MenuRunner(menu_model_adapter->CreateMenu()));
1599
1600  ScopedTargetRootWindow scoped_target(
1601      source->GetWidget()->GetNativeView()->GetRootWindow());
1602
1603  // Determine the menu alignment dependent on the shelf.
1604  views::MenuItemView::AnchorPosition menu_alignment =
1605      views::MenuItemView::TOPLEFT;
1606  gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1607
1608  ShelfWidget* shelf = RootWindowController::ForLauncher(
1609      GetWidget()->GetNativeView())->shelf();
1610  if (!context_menu) {
1611    // Application lists use a bubble.
1612    ash::ShelfAlignment align = shelf->GetAlignment();
1613    anchor_point = source->GetBoundsInScreen();
1614
1615    // It is possible to invoke the menu while it is sliding into view. To cover
1616    // that case, the screen coordinates are offsetted by the animation delta.
1617    gfx::Vector2d offset =
1618        source->GetWidget()->GetNativeWindow()->bounds().origin() -
1619        source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
1620    anchor_point.set_x(anchor_point.x() - offset.x());
1621    anchor_point.set_y(anchor_point.y() - offset.y());
1622
1623    // Launcher items can have an asymmetrical border for spacing reasons.
1624    // Adjust anchor location for this.
1625    if (source->border())
1626      anchor_point.Inset(source->border()->GetInsets());
1627
1628    switch (align) {
1629      case ash::SHELF_ALIGNMENT_BOTTOM:
1630        menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
1631        break;
1632      case ash::SHELF_ALIGNMENT_LEFT:
1633        menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
1634        break;
1635      case ash::SHELF_ALIGNMENT_RIGHT:
1636        menu_alignment = views::MenuItemView::BUBBLE_LEFT;
1637        break;
1638      case ash::SHELF_ALIGNMENT_TOP:
1639        menu_alignment = views::MenuItemView::BUBBLE_BELOW;
1640        break;
1641    }
1642  }
1643  // If this gets deleted while we are in the menu, the launcher will be gone
1644  // as well.
1645  bool got_deleted = false;
1646  got_deleted_ = &got_deleted;
1647
1648  shelf->ForceUndimming(true);
1649  // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1650  // code.
1651  if (launcher_menu_runner_->RunMenuAt(
1652          source->GetWidget(),
1653          NULL,
1654          anchor_point,
1655          menu_alignment,
1656          source_type,
1657          context_menu ? views::MenuRunner::CONTEXT_MENU : 0) ==
1658      views::MenuRunner::MENU_DELETED) {
1659    if (!got_deleted) {
1660      got_deleted_ = NULL;
1661      shelf->ForceUndimming(false);
1662    }
1663    return;
1664  }
1665  got_deleted_ = NULL;
1666  shelf->ForceUndimming(false);
1667
1668  // Unpinning an item will reset the |launcher_menu_runner_| before coming
1669  // here.
1670  if (launcher_menu_runner_)
1671    closing_event_time_ = launcher_menu_runner_->closing_event_time();
1672  Shell::GetInstance()->UpdateShelfVisibility();
1673}
1674
1675void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1676  FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1677                    OnLauncherIconPositionsChanged());
1678  PreferredSizeChanged();
1679}
1680
1681void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1682}
1683
1684bool LauncherView::IsUsableEvent(const ui::Event& event) {
1685  if (closing_event_time_ == base::TimeDelta())
1686    return true;
1687
1688  base::TimeDelta delta =
1689      base::TimeDelta(event.time_stamp() - closing_event_time_);
1690  closing_event_time_ = base::TimeDelta();
1691  // TODO(skuhne): This time seems excessive, but it appears that the reposting
1692  // takes that long.  Need to come up with a better way of doing this.
1693  return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1694}
1695
1696const LauncherItem* LauncherView::LauncherItemForView(
1697    const views::View* view) const {
1698  int view_index = view_model_->GetIndexOfView(view);
1699  if (view_index == -1)
1700    return NULL;
1701  return &(model_->items()[view_index]);
1702}
1703
1704bool LauncherView::ShouldShowTooltipForView(const views::View* view) const {
1705  if (view == GetAppListButtonView() &&
1706      Shell::GetInstance()->GetAppListWindow())
1707    return false;
1708  const LauncherItem* item = LauncherItemForView(view);
1709  return (!item || delegate_->ShouldShowTooltip(*item));
1710}
1711
1712}  // namespace internal
1713}  // namespace ash
1714