blob: 3d328929e4c2252562eaf512a00c6e56de32f3f4 [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// 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"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000010#include "ash/ash_switches.h"
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +010011#include "ash/drag_drop/drag_image_view.h"
Ben Murdocheb525c52013-07-10 11:40:50 +010012#include "ash/launcher/alternate_app_list_button.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000013#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"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000022#include "ash/root_window_controller.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010023#include "ash/scoped_target_root_window.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000024#include "ash/shelf/shelf_layout_manager.h"
25#include "ash/shelf/shelf_widget.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000026#include "ash/shell_delegate.h"
27#include "base/auto_reset.h"
28#include "base/memory/scoped_ptr.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000029#include "grit/ash_resources.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000030#include "grit/ash_strings.h"
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +010031#include "ui/aura/client/screen_position_client.h"
32#include "ui/aura/root_window.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000033#include "ui/aura/window.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010034#include "ui/base/accessibility/accessible_view_state.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000035#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"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000040#include "ui/compositor/scoped_animation_duration_scale_mode.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000041#include "ui/gfx/canvas.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010042#include "ui/gfx/point.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000043#include "ui/views/animation/bounds_animator.h"
44#include "ui/views/border.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000045#include "ui/views/controls/button/image_button.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000046#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;
68
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000069// Additional spacing for the left and right side of icons.
70const int kHorizontalIconSpacing = 2;
71
72// Inset for items which do not have an icon.
73const int kHorizontalNoIconInsetSpacing =
74 kHorizontalIconSpacing + kDefaultLeadingInset;
75
76// The proportion of the launcher space reserved for non-panel icons. Panels
77// may flow into this space but will be put into the overflow bubble if there
78// is contention for the space.
79const float kReservedNonPanelIconProportion = 0.67f;
80
81// This is the command id of the menu item which contains the name of the menu.
82const int kCommandIdOfMenuName = 0;
83
84// The background color of the active item in the list.
85const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241);
86
87// The background color of the active & hovered item in the list.
88const SkColor kFocusedActiveListItemBackgroundColor =
89 SkColorSetRGB(193, 211, 236);
90
91// The text color of the caption item in a list.
92const SkColor kCaptionItemForegroundColor = SK_ColorBLACK;
93
94// The maximum allowable length of a menu line of an application menu in pixels.
95const int kMaximumAppMenuItemLength = 350;
96
Torne (Richard Coles)58218062012-11-14 11:43:16 +000097namespace {
98
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000099// The MenuModelAdapter gets slightly changed to adapt the menu appearance to
100// our requirements.
101class LauncherMenuModelAdapter
102 : public views::MenuModelAdapter {
103 public:
104 explicit LauncherMenuModelAdapter(ash::LauncherMenuModel* menu_model);
105
106 // Overriding MenuModelAdapter's MenuDelegate implementation.
107 virtual const gfx::Font* GetLabelFont(int command_id) const OVERRIDE;
108 virtual bool IsCommandEnabled(int id) const OVERRIDE;
109 virtual void GetHorizontalIconMargins(int id,
110 int icon_size,
111 int* left_margin,
112 int* right_margin) const OVERRIDE;
113 virtual bool GetForegroundColor(int command_id,
114 bool is_hovered,
115 SkColor* override_color) const OVERRIDE;
116 virtual bool GetBackgroundColor(int command_id,
117 bool is_hovered,
118 SkColor* override_color) const OVERRIDE;
119 virtual int GetMaxWidthForMenu(views::MenuItemView* menu) OVERRIDE;
120 virtual bool ShouldReserveSpaceForSubmenuIndicator() const OVERRIDE;
121
122 private:
123 ash::LauncherMenuModel* launcher_menu_model_;
124
125 DISALLOW_COPY_AND_ASSIGN(LauncherMenuModelAdapter);
126};
127
128
129LauncherMenuModelAdapter::LauncherMenuModelAdapter(
130 ash::LauncherMenuModel* menu_model)
131 : MenuModelAdapter(menu_model),
132 launcher_menu_model_(menu_model) {}
133
134const gfx::Font* LauncherMenuModelAdapter::GetLabelFont(
135 int command_id) const {
136 if (command_id != kCommandIdOfMenuName)
137 return MenuModelAdapter::GetLabelFont(command_id);
138
139 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
140 return &rb.GetFont(ui::ResourceBundle::BoldFont);
141}
142
143bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const {
144 return id != kCommandIdOfMenuName;
145}
146
147bool LauncherMenuModelAdapter::GetForegroundColor(
148 int command_id,
149 bool is_hovered,
150 SkColor* override_color) const {
151 if (command_id != kCommandIdOfMenuName)
152 return false;
153
154 *override_color = kCaptionItemForegroundColor;
155 return true;
156}
157
158bool LauncherMenuModelAdapter::GetBackgroundColor(
159 int command_id,
160 bool is_hovered,
161 SkColor* override_color) const {
162 if (!launcher_menu_model_->IsCommandActive(command_id))
163 return false;
164
165 *override_color = is_hovered ? kFocusedActiveListItemBackgroundColor :
166 kActiveListItemBackgroundColor;
167 return true;
168}
169
170void LauncherMenuModelAdapter::GetHorizontalIconMargins(
171 int command_id,
172 int icon_size,
173 int* left_margin,
174 int* right_margin) const {
175 *left_margin = kHorizontalIconSpacing;
176 *right_margin = (command_id != kCommandIdOfMenuName) ?
177 kHorizontalIconSpacing : -(icon_size + kHorizontalNoIconInsetSpacing);
178}
179
180int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) {
181 return kMaximumAppMenuItemLength;
182}
183
184bool LauncherMenuModelAdapter::ShouldReserveSpaceForSubmenuIndicator() const {
185 return false;
186}
187
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000188// Custom FocusSearch used to navigate the launcher in the order items are in
189// the ViewModel.
190class LauncherFocusSearch : public views::FocusSearch {
191 public:
192 explicit LauncherFocusSearch(views::ViewModel* view_model)
193 : FocusSearch(NULL, true, true),
194 view_model_(view_model) {}
195 virtual ~LauncherFocusSearch() {}
196
197 // views::FocusSearch overrides:
198 virtual View* FindNextFocusableView(
199 View* starting_view,
200 bool reverse,
201 Direction direction,
202 bool check_starting_view,
203 views::FocusTraversable** focus_traversable,
204 View** focus_traversable_view) OVERRIDE {
205 int index = view_model_->GetIndexOfView(starting_view);
206 if (index == -1)
207 return view_model_->view_at(0);
208
209 if (reverse) {
210 --index;
211 if (index < 0)
212 index = view_model_->view_size() - 1;
213 } else {
214 ++index;
215 if (index >= view_model_->view_size())
216 index = 0;
217 }
218 return view_model_->view_at(index);
219 }
220
221 private:
222 views::ViewModel* view_model_;
223
224 DISALLOW_COPY_AND_ASSIGN(LauncherFocusSearch);
225};
226
227class LauncherButtonFocusBorder : public views::FocusBorder {
228 public:
229 LauncherButtonFocusBorder() {}
230 virtual ~LauncherButtonFocusBorder() {}
231
232 private:
233 // views::FocusBorder overrides:
234 virtual void Paint(const View& view, gfx::Canvas* canvas) const OVERRIDE {
235 gfx::Rect rect(view.GetLocalBounds());
236 rect.Inset(1, 1);
237 canvas->DrawRect(rect, kFocusBorderColor);
238 }
239
240 DISALLOW_COPY_AND_ASSIGN(LauncherButtonFocusBorder);
241};
242
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000243// AnimationDelegate that deletes a view when done. This is used when a launcher
244// item is removed, which triggers a remove animation. When the animation is
245// done we delete the view.
246class DeleteViewAnimationDelegate
247 : public views::BoundsAnimator::OwnedAnimationDelegate {
248 public:
249 explicit DeleteViewAnimationDelegate(views::View* view) : view_(view) {}
250 virtual ~DeleteViewAnimationDelegate() {}
251
252 private:
253 scoped_ptr<views::View> view_;
254
255 DISALLOW_COPY_AND_ASSIGN(DeleteViewAnimationDelegate);
256};
257
258// AnimationDelegate used when inserting a new item. This steadily increases the
259// opacity of the layer as the animation progress.
260class FadeInAnimationDelegate
261 : public views::BoundsAnimator::OwnedAnimationDelegate {
262 public:
263 explicit FadeInAnimationDelegate(views::View* view) : view_(view) {}
264 virtual ~FadeInAnimationDelegate() {}
265
266 // AnimationDelegate overrides:
267 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
268 view_->layer()->SetOpacity(animation->GetCurrentValue());
269 view_->layer()->ScheduleDraw();
270 }
271 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
272 view_->layer()->SetOpacity(1.0f);
273 view_->layer()->ScheduleDraw();
274 }
275 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
276 view_->layer()->SetOpacity(1.0f);
277 view_->layer()->ScheduleDraw();
278 }
279
280 private:
281 views::View* view_;
282
283 DISALLOW_COPY_AND_ASSIGN(FadeInAnimationDelegate);
284};
285
286void ReflectItemStatus(const ash::LauncherItem& item,
287 LauncherButton* button) {
288 switch (item.status) {
289 case STATUS_CLOSED:
290 button->ClearState(LauncherButton::STATE_ACTIVE);
291 button->ClearState(LauncherButton::STATE_RUNNING);
292 button->ClearState(LauncherButton::STATE_ATTENTION);
293 break;
294 case STATUS_RUNNING:
295 button->ClearState(LauncherButton::STATE_ACTIVE);
296 button->AddState(LauncherButton::STATE_RUNNING);
297 button->ClearState(LauncherButton::STATE_ATTENTION);
298 break;
299 case STATUS_ACTIVE:
300 button->AddState(LauncherButton::STATE_ACTIVE);
301 button->ClearState(LauncherButton::STATE_RUNNING);
302 button->ClearState(LauncherButton::STATE_ATTENTION);
303 break;
304 case STATUS_ATTENTION:
305 button->ClearState(LauncherButton::STATE_ACTIVE);
306 button->ClearState(LauncherButton::STATE_RUNNING);
307 button->AddState(LauncherButton::STATE_ATTENTION);
308 break;
309 }
310}
311
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100312// Get the event location in screen coordinates.
313gfx::Point GetPositionInScreen(const gfx::Point& root_location,
314 views::View* view) {
315 gfx::Point root_location_in_screen = root_location;
316 aura::RootWindow* root_window =
317 view->GetWidget()->GetNativeWindow()->GetRootWindow();
318 aura::client::GetScreenPositionClient(root_window->GetRootWindow())->
319 ConvertPointToScreen(root_window, &root_location_in_screen);
320 return root_location_in_screen;
321}
322
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000323} // namespace
324
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000325// AnimationDelegate used when deleting an item. This steadily decreased the
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000326// opacity of the layer as the animation progress.
327class LauncherView::FadeOutAnimationDelegate
328 : public views::BoundsAnimator::OwnedAnimationDelegate {
329 public:
330 FadeOutAnimationDelegate(LauncherView* host, views::View* view)
331 : launcher_view_(host),
332 view_(view) {}
333 virtual ~FadeOutAnimationDelegate() {}
334
335 // AnimationDelegate overrides:
336 virtual void AnimationProgressed(const Animation* animation) OVERRIDE {
337 view_->layer()->SetOpacity(1 - animation->GetCurrentValue());
338 view_->layer()->ScheduleDraw();
339 }
340 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000341 launcher_view_->OnFadeOutAnimationEnded();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000342 }
343 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
344 }
345
346 private:
347 LauncherView* launcher_view_;
348 scoped_ptr<views::View> view_;
349
350 DISALLOW_COPY_AND_ASSIGN(FadeOutAnimationDelegate);
351};
352
353// AnimationDelegate used to trigger fading an element in. When an item is
354// inserted this delegate is attached to the animation that expands the size of
355// the item. When done it kicks off another animation to fade the item in.
356class LauncherView::StartFadeAnimationDelegate
357 : public views::BoundsAnimator::OwnedAnimationDelegate {
358 public:
359 StartFadeAnimationDelegate(LauncherView* host,
360 views::View* view)
361 : launcher_view_(host),
362 view_(view) {}
363 virtual ~StartFadeAnimationDelegate() {}
364
365 // AnimationDelegate overrides:
366 virtual void AnimationEnded(const Animation* animation) OVERRIDE {
367 launcher_view_->FadeIn(view_);
368 }
369 virtual void AnimationCanceled(const Animation* animation) OVERRIDE {
370 view_->layer()->SetOpacity(1.0f);
371 }
372
373 private:
374 LauncherView* launcher_view_;
375 views::View* view_;
376
377 DISALLOW_COPY_AND_ASSIGN(StartFadeAnimationDelegate);
378};
379
380LauncherView::LauncherView(LauncherModel* model,
381 LauncherDelegate* delegate,
382 ShelfLayoutManager* shelf_layout_manager)
383 : model_(model),
384 delegate_(delegate),
385 view_model_(new views::ViewModel),
386 first_visible_index_(0),
387 last_visible_index_(-1),
388 overflow_button_(NULL),
389 drag_pointer_(NONE),
390 drag_view_(NULL),
391 drag_offset_(0),
392 start_drag_index_(-1),
393 context_menu_id_(0),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000394 leading_inset_(kDefaultLeadingInset),
395 cancelling_drag_model_changed_(false),
396 last_hidden_index_(0),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100397 closing_event_time_(base::TimeDelta()),
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100398 got_deleted_(NULL),
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100399 drag_and_drop_item_pinned_(false),
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100400 drag_and_drop_launcher_id_(0) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000401 DCHECK(model_);
402 bounds_animator_.reset(new views::BoundsAnimator(this));
403 bounds_animator_->AddObserver(this);
404 set_context_menu_controller(this);
405 focus_search_.reset(new LauncherFocusSearch(view_model_.get()));
406 tooltip_.reset(new LauncherTooltipManager(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000407 shelf_layout_manager, this));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000408}
409
410LauncherView::~LauncherView() {
411 bounds_animator_->RemoveObserver(this);
412 model_->RemoveObserver(this);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100413 // If we are inside the MenuRunner, we need to know if we were getting
414 // deleted while it was running.
415 if (got_deleted_)
416 *got_deleted_ = true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000417}
418
419void LauncherView::Init() {
420 model_->AddObserver(this);
421
422 const LauncherItems& items(model_->items());
423 for (LauncherItems::const_iterator i = items.begin(); i != items.end(); ++i) {
424 views::View* child = CreateViewForItem(*i);
425 child->SetPaintToLayer(true);
426 view_model_->Add(child, static_cast<int>(i - items.begin()));
427 AddChildView(child);
428 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000429 LauncherStatusChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000430 overflow_button_ = new OverflowButton(this);
431 overflow_button_->set_context_menu_controller(this);
432 ConfigureChildView(overflow_button_);
433 AddChildView(overflow_button_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000434 UpdateFirstButtonPadding();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000435
436 // We'll layout when our bounds change.
437}
438
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000439void LauncherView::OnShelfAlignmentChanged() {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000440 UpdateFirstButtonPadding();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000441 overflow_button_->OnShelfAlignmentChanged();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000442 LayoutToIdealBounds();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000443 for (int i=0; i < view_model_->view_size(); ++i) {
444 // TODO: remove when AppIcon is a Launcher Button.
Ben Murdocheb525c52013-07-10 11:40:50 +0100445 if (TYPE_APP_LIST == model_->items()[i].type &&
446 !ash::switches::UseAlternateShelfLayout()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000447 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
448 static_cast<AppListButton*>(view_model_->view_at(i))->SetImageAlignment(
449 shelf->SelectValueForShelfAlignment(
450 views::ImageButton::ALIGN_CENTER,
451 views::ImageButton::ALIGN_LEFT,
452 views::ImageButton::ALIGN_RIGHT,
453 views::ImageButton::ALIGN_CENTER),
454 shelf->SelectValueForShelfAlignment(
455 views::ImageButton::ALIGN_TOP,
456 views::ImageButton::ALIGN_MIDDLE,
457 views::ImageButton::ALIGN_MIDDLE,
458 views::ImageButton::ALIGN_BOTTOM));
459 }
460 if (i >= first_visible_index_ && i <= last_visible_index_)
461 view_model_->view_at(i)->Layout();
462 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100463 tooltip_->UpdateArrow();
464 if (overflow_bubble_)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000465 overflow_bubble_->Hide();
466}
467
468gfx::Rect LauncherView::GetIdealBoundsOfItemIcon(LauncherID id) {
469 int index = model_->ItemIndexByID(id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000470 if (index == -1 || (index > last_visible_index_ &&
471 index < model_->FirstPanelIndex()))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000472 return gfx::Rect();
473 const gfx::Rect& ideal_bounds(view_model_->ideal_bounds(index));
474 DCHECK_NE(TYPE_APP_LIST, model_->items()[index].type);
475 LauncherButton* button =
476 static_cast<LauncherButton*>(view_model_->view_at(index));
477 gfx::Rect icon_bounds = button->GetIconBounds();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100478 return gfx::Rect(GetMirroredXWithWidthInView(
479 ideal_bounds.x() + icon_bounds.x(), icon_bounds.width()),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000480 ideal_bounds.y() + icon_bounds.y(),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000481 icon_bounds.width(),
482 icon_bounds.height());
483}
484
485void LauncherView::UpdatePanelIconPosition(LauncherID id,
486 const gfx::Point& midpoint) {
487 int current_index = model_->ItemIndexByID(id);
488 int first_panel_index = model_->FirstPanelIndex();
489 if (current_index < first_panel_index)
490 return;
491
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100492 gfx::Point midpoint_in_view(GetMirroredXInView(midpoint.x()),
493 midpoint.y());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000494 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
495 int target_index = current_index;
496 while (target_index > first_panel_index &&
497 shelf->PrimaryAxisValue(view_model_->ideal_bounds(target_index).x(),
498 view_model_->ideal_bounds(target_index).y()) >
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100499 shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000500 --target_index;
501 }
502 while (target_index < view_model_->view_size() - 1 &&
503 shelf->PrimaryAxisValue(
504 view_model_->ideal_bounds(target_index).right(),
505 view_model_->ideal_bounds(target_index).bottom()) <
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100506 shelf->PrimaryAxisValue(midpoint_in_view.x(), midpoint_in_view.y())) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000507 ++target_index;
508 }
509 if (current_index != target_index)
510 model_->Move(current_index, target_index);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000511}
512
513bool LauncherView::IsShowingMenu() const {
514#if !defined(OS_MACOSX)
515 return (launcher_menu_runner_.get() &&
516 launcher_menu_runner_->IsRunning());
517#endif
518 return false;
519}
520
521bool LauncherView::IsShowingOverflowBubble() const {
522 return overflow_bubble_.get() && overflow_bubble_->IsShowing();
523}
524
525views::View* LauncherView::GetAppListButtonView() const {
526 for (int i = 0; i < model_->item_count(); ++i) {
527 if (model_->items()[i].type == TYPE_APP_LIST)
528 return view_model_->view_at(i);
529 }
530
531 NOTREACHED() << "Applist button not found";
532 return NULL;
533}
534
535////////////////////////////////////////////////////////////////////////////////
536// LauncherView, FocusTraversable implementation:
537
538views::FocusSearch* LauncherView::GetFocusSearch() {
539 return focus_search_.get();
540}
541
542views::FocusTraversable* LauncherView::GetFocusTraversableParent() {
543 return parent()->GetFocusTraversable();
544}
545
546View* LauncherView::GetFocusTraversableParentView() {
547 return this;
548}
549
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100550void LauncherView::CreateDragIconProxy(
551 const gfx::Point& location_in_screen_coordinates,
552 const gfx::ImageSkia& icon,
553 views::View* replaced_view,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100554 const gfx::Vector2d& cursor_offset_from_center,
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100555 float scale_factor) {
556 drag_replaced_view_ = replaced_view;
557 drag_image_.reset(new ash::internal::DragImageView(
558 drag_replaced_view_->GetWidget()->GetNativeWindow()->GetRootWindow()));
559 drag_image_->SetImage(icon);
560 gfx::Size size = drag_image_->GetPreferredSize();
561 size.set_width(size.width() * scale_factor);
562 size.set_height(size.height() * scale_factor);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100563 drag_image_offset_ = gfx::Vector2d(size.width() / 2, size.height() / 2) +
564 cursor_offset_from_center;
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100565 gfx::Rect drag_image_bounds(
566 GetPositionInScreen(location_in_screen_coordinates,
567 drag_replaced_view_) - drag_image_offset_, size);
568 drag_image_->SetBoundsInScreen(drag_image_bounds);
569 drag_image_->SetWidgetVisible(true);
570}
571
572void LauncherView::UpdateDragIconProxy(
573 const gfx::Point& location_in_screen_coordinates) {
574 drag_image_->SetScreenPosition(
575 GetPositionInScreen(location_in_screen_coordinates,
576 drag_replaced_view_) - drag_image_offset_);
577}
578
579void LauncherView::DestroyDragIconProxy() {
580 drag_image_.reset();
581 drag_image_offset_ = gfx::Vector2d(0, 0);
582}
583
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100584bool LauncherView::StartDrag(const std::string& app_id,
585 const gfx::Point& location_in_screen_coordinates) {
586 // Bail if an operation is already going on - or the cursor is not inside.
587 // This could happen if mouse / touch operations overlap.
588 if (drag_and_drop_launcher_id_ ||
589 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
590 return false;
591
592 // If the AppsGridView (which was dispatching this event) was opened by our
593 // button, LauncherView dragging operations are locked and we have to unlock.
594 CancelDrag(-1);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100595 drag_and_drop_item_pinned_ = false;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100596 drag_and_drop_app_id_ = app_id;
597 drag_and_drop_launcher_id_ =
598 delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100599 // Check if the application is known and pinned - if not, we have to pin it so
600 // that we can re-arrange the launcher order accordingly. Note that items have
601 // to be pinned to give them the same (order) possibilities as a shortcut.
602 if (!drag_and_drop_launcher_id_ || !delegate_->IsAppPinned(app_id)) {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100603 delegate_->PinAppWithID(app_id);
604 drag_and_drop_launcher_id_ =
605 delegate_->GetLauncherIDForAppID(drag_and_drop_app_id_);
606 if (!drag_and_drop_launcher_id_)
607 return false;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100608 drag_and_drop_item_pinned_ = true;
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100609 }
610 views::View* drag_and_drop_view = view_model_->view_at(
611 model_->ItemIndexByID(drag_and_drop_launcher_id_));
612 DCHECK(drag_and_drop_view);
613
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100614 // Since there is already an icon presented by the caller, we hide this item
615 // for now. That has to be done by reducing the size since the visibility will
616 // change once a regrouping animation is performed.
617 pre_drag_and_drop_size_ = drag_and_drop_view->size();
618 drag_and_drop_view->SetSize(gfx::Size());
619
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100620 // First we have to center the mouse cursor over the item.
621 gfx::Point pt = drag_and_drop_view->GetBoundsInScreen().CenterPoint();
622 views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
623 ui::MouseEvent event(ui::ET_MOUSE_PRESSED,
624 pt, location_in_screen_coordinates, 0);
625 PointerPressedOnButton(
626 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
627
628 // Drag the item where it really belongs.
629 Drag(location_in_screen_coordinates);
630 return true;
631}
632
633bool LauncherView::Drag(const gfx::Point& location_in_screen_coordinates) {
634 if (!drag_and_drop_launcher_id_ ||
635 !GetBoundsInScreen().Contains(location_in_screen_coordinates))
636 return false;
637
638 gfx::Point pt = location_in_screen_coordinates;
639 views::View* drag_and_drop_view = view_model_->view_at(
640 model_->ItemIndexByID(drag_and_drop_launcher_id_));
641 views::View::ConvertPointFromScreen(drag_and_drop_view, &pt);
642
643 ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, gfx::Point(), 0);
644 PointerDraggedOnButton(
645 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, event);
646 return true;
647}
648
649void LauncherView::EndDrag(bool cancel) {
650 if (!drag_and_drop_launcher_id_)
651 return;
652
653 views::View* drag_and_drop_view = view_model_->view_at(
654 model_->ItemIndexByID(drag_and_drop_launcher_id_));
655 PointerReleasedOnButton(
656 drag_and_drop_view, LauncherButtonHost::DRAG_AND_DROP, cancel);
657
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100658 // Either destroy the temporarily created item - or - make the item visible.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100659 if (drag_and_drop_item_pinned_ && cancel)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100660 delegate_->UnpinAppsWithID(drag_and_drop_app_id_);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100661 else if (drag_and_drop_view)
662 drag_and_drop_view->SetSize(pre_drag_and_drop_size_);
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100663
664 drag_and_drop_launcher_id_ = 0;
665}
666
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000667void LauncherView::LayoutToIdealBounds() {
668 IdealBounds ideal_bounds;
669 CalculateIdealBounds(&ideal_bounds);
670
671 if (bounds_animator_->IsAnimating())
672 AnimateToIdealBounds();
673 else
674 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_);
675
676 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
677}
678
679void LauncherView::CalculateIdealBounds(IdealBounds* bounds) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000680 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
681
682 int available_size = shelf->PrimaryAxisValue(width(), height());
683 DCHECK(model_->item_count() == view_model_->view_size());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000684 if (!available_size)
685 return;
686
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000687 int first_panel_index = model_->FirstPanelIndex();
Ben Murdocheb525c52013-07-10 11:40:50 +0100688 // TODO(harrym): if alternate shelf layout stays, rename app_list_index.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000689 int app_list_index = first_panel_index - 1;
690
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000691 // Initial x,y values account both leading_inset in primary
692 // coordinate and secondary coordinate based on the dynamic edge of the
693 // launcher (eg top edge on bottom-aligned launcher).
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000694 int x = shelf->SelectValueForShelfAlignment(
695 leading_inset(),
696 0,
697 0,
698 leading_inset());
699 int y = shelf->SelectValueForShelfAlignment(
700 0,
701 leading_inset(),
702 leading_inset(),
703 0);
Ben Murdocheb525c52013-07-10 11:40:50 +0100704
705 int shelf_size = ash::switches::UseAlternateShelfLayout() ?
706 ShelfLayoutManager::kShelfSize : kLauncherPreferredSize;
707
708 int w = shelf->PrimaryAxisValue(shelf_size, width());
709 int h = shelf->PrimaryAxisValue(height(), shelf_size);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000710 for (int i = 0; i < view_model_->view_size(); ++i) {
711 if (i < first_visible_index_) {
712 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0));
713 continue;
714 }
715
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000716 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000717 if (i != app_list_index) {
718 x = shelf->PrimaryAxisValue(x + w + kButtonSpacing, x);
719 y = shelf->PrimaryAxisValue(y, y + h + kButtonSpacing);
720 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000721 }
722
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000723 if (is_overflow_mode()) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000724 DCHECK_LT(last_visible_index_, view_model_->view_size());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000725 for (int i = 0; i < view_model_->view_size(); ++i) {
726 view_model_->view_at(i)->SetVisible(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000727 i >= first_visible_index_ &&
728 i != app_list_index &&
729 i <= last_visible_index_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000730 }
731 return;
732 }
733
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000734 // To address Fitt's law, we make the first launcher button include the
735 // leading inset (if there is one).
736 if (view_model_->view_size() > 0) {
737 view_model_->set_ideal_bounds(0, gfx::Rect(gfx::Size(
738 shelf->PrimaryAxisValue(leading_inset() + w, w),
739 shelf->PrimaryAxisValue(h, leading_inset() + h))));
740 }
741
742 // Right aligned icons.
743 int end_position = available_size - kButtonSpacing;
744 x = shelf->PrimaryAxisValue(end_position, 0);
745 y = shelf->PrimaryAxisValue(0, end_position);
746 for (int i = view_model_->view_size() - 1;
747 i >= first_panel_index; --i) {
748 x = shelf->PrimaryAxisValue(x - w - kButtonSpacing, x);
749 y = shelf->PrimaryAxisValue(y, y - h - kButtonSpacing);
750 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
751 end_position = shelf->PrimaryAxisValue(x, y);
752 }
753
754 // Icons on the left / top are guaranteed up to kLeftIconProportion of
755 // the available space.
756 int last_icon_position = shelf->PrimaryAxisValue(
757 view_model_->ideal_bounds(first_panel_index - 1).right(),
758 view_model_->ideal_bounds(first_panel_index - 1).bottom()) +
759 2 * kLauncherPreferredSize + leading_inset();
760 int reserved_icon_space = available_size * kReservedNonPanelIconProportion;
761 if (last_icon_position < reserved_icon_space)
762 end_position = last_icon_position;
763 else
764 end_position = std::max(end_position, reserved_icon_space);
765
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000766 bounds->overflow_bounds.set_size(gfx::Size(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000767 shelf->PrimaryAxisValue(w, width()),
768 shelf->PrimaryAxisValue(height(), h)));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000769 last_visible_index_ = DetermineLastVisibleIndex(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000770 end_position - leading_inset() - 2 * kLauncherPreferredSize);
771 last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1;
772 bool show_overflow = (last_visible_index_ + 1 < app_list_index ||
773 last_hidden_index_ >= first_panel_index);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000774
775 for (int i = 0; i < view_model_->view_size(); ++i) {
776 view_model_->view_at(i)->SetVisible(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000777 i <= last_visible_index_ ||
778 i == app_list_index ||
779 i > last_hidden_index_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000780 }
781
782 overflow_button_->SetVisible(show_overflow);
783 if (show_overflow) {
784 DCHECK_NE(0, view_model_->view_size());
785 if (last_visible_index_ == -1) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000786 x = shelf->SelectValueForShelfAlignment(
787 leading_inset(),
788 0,
789 0,
790 leading_inset());
791 y = shelf->SelectValueForShelfAlignment(
792 0,
793 leading_inset(),
794 leading_inset(),
795 0);
796 } else if (last_visible_index_ == app_list_index) {
797 x = view_model_->ideal_bounds(last_visible_index_).x();
798 y = view_model_->ideal_bounds(last_visible_index_).y();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000799 } else {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000800 x = shelf->PrimaryAxisValue(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000801 view_model_->ideal_bounds(last_visible_index_).right(),
802 view_model_->ideal_bounds(last_visible_index_).x());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000803 y = shelf->PrimaryAxisValue(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000804 view_model_->ideal_bounds(last_visible_index_).y(),
805 view_model_->ideal_bounds(last_visible_index_).bottom());
806 }
807 gfx::Rect app_list_bounds = view_model_->ideal_bounds(app_list_index);
808 bounds->overflow_bounds.set_x(x);
809 bounds->overflow_bounds.set_y(y);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000810
811 // Set all hidden panel icon positions to be on the overflow button.
812 for (int i = first_panel_index; i <= last_hidden_index_; ++i)
813 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h));
814
815 x = shelf->PrimaryAxisValue(x + w + kButtonSpacing, x);
816 y = shelf->PrimaryAxisValue(y, y + h + kButtonSpacing);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000817 app_list_bounds.set_x(x);
818 app_list_bounds.set_y(y);
819 view_model_->set_ideal_bounds(app_list_index, app_list_bounds);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000820
821 if (overflow_bubble_.get() && overflow_bubble_->IsShowing())
822 UpdateOverflowRange(overflow_bubble_->launcher_view());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000823 } else {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100824 if (overflow_bubble_)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000825 overflow_bubble_->Hide();
826 }
827}
828
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000829int LauncherView::DetermineLastVisibleIndex(int max_value) const {
830 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
831
832 int index = model_->FirstPanelIndex() - 1;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000833 while (index >= 0 &&
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000834 shelf->PrimaryAxisValue(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000835 view_model_->ideal_bounds(index).right(),
836 view_model_->ideal_bounds(index).bottom()) > max_value) {
837 index--;
838 }
839 return index;
840}
841
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000842int LauncherView::DetermineFirstVisiblePanelIndex(int min_value) const {
843 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
844
845 int index = model_->FirstPanelIndex();
846 while (index < view_model_->view_size() &&
847 shelf->PrimaryAxisValue(
848 view_model_->ideal_bounds(index).right(),
849 view_model_->ideal_bounds(index).bottom()) < min_value) {
850 ++index;
851 }
852 return index;
853}
854
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000855void LauncherView::AddIconObserver(LauncherIconObserver* observer) {
856 observers_.AddObserver(observer);
857}
858
859void LauncherView::RemoveIconObserver(LauncherIconObserver* observer) {
860 observers_.RemoveObserver(observer);
861}
862
863void LauncherView::AnimateToIdealBounds() {
864 IdealBounds ideal_bounds;
865 CalculateIdealBounds(&ideal_bounds);
866 for (int i = 0; i < view_model_->view_size(); ++i) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000867 View* view = view_model_->view_at(i);
868 bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i));
869 // Now that the item animation starts, we have to make sure that the
870 // padding of the first gets properly transferred to the new first item.
871 if (i && view->border())
872 view->set_border(NULL);
873 else if (!i && !view->border())
874 UpdateFirstButtonPadding();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000875 }
876 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds);
877}
878
879views::View* LauncherView::CreateViewForItem(const LauncherItem& item) {
880 views::View* view = NULL;
881 switch (item.type) {
882 case TYPE_TABBED: {
883 TabbedLauncherButton* button =
884 TabbedLauncherButton::Create(
885 this,
886 this,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000887 tooltip_->shelf_layout_manager(),
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000888 item.is_incognito ?
889 TabbedLauncherButton::STATE_INCOGNITO :
890 TabbedLauncherButton::STATE_NOT_INCOGNITO);
891 button->SetTabImage(item.image);
892 ReflectItemStatus(item, button);
893 view = button;
894 break;
895 }
896
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100897 case TYPE_BROWSER_SHORTCUT:
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000898 case TYPE_APP_SHORTCUT:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000899 case TYPE_WINDOWED_APP:
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000900 case TYPE_PLATFORM_APP:
901 case TYPE_APP_PANEL: {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000902 LauncherButton* button = LauncherButton::Create(
903 this, this, tooltip_->shelf_layout_manager());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000904 button->SetImage(item.image);
905 ReflectItemStatus(item, button);
906 view = button;
907 break;
908 }
909
910 case TYPE_APP_LIST: {
Ben Murdocheb525c52013-07-10 11:40:50 +0100911 if (ash::switches::UseAlternateShelfLayout()) {
912 view = new AlternateAppListButton(this, this,
913 tooltip_->shelf_layout_manager()->shelf_widget());
914 } else {
915 // TODO(dave): turn this into a LauncherButton too.
916 AppListButton* button = new AppListButton(this, this);
917 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
918 button->SetImageAlignment(
919 shelf->SelectValueForShelfAlignment(
920 views::ImageButton::ALIGN_CENTER,
921 views::ImageButton::ALIGN_LEFT,
922 views::ImageButton::ALIGN_RIGHT,
923 views::ImageButton::ALIGN_CENTER),
924 shelf->SelectValueForShelfAlignment(
925 views::ImageButton::ALIGN_TOP,
926 views::ImageButton::ALIGN_MIDDLE,
927 views::ImageButton::ALIGN_MIDDLE,
928 views::ImageButton::ALIGN_BOTTOM));
929 view = button;
930 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000931 break;
932 }
933
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000934 default:
935 break;
936 }
937 view->set_context_menu_controller(this);
938 view->set_focus_border(new LauncherButtonFocusBorder);
939
940 DCHECK(view);
941 ConfigureChildView(view);
942 return view;
943}
944
945void LauncherView::FadeIn(views::View* view) {
946 view->SetVisible(true);
947 view->layer()->SetOpacity(0);
948 AnimateToIdealBounds();
949 bounds_animator_->SetAnimationDelegate(
950 view, new FadeInAnimationDelegate(view), true);
951}
952
953void LauncherView::PrepareForDrag(Pointer pointer,
954 const ui::LocatedEvent& event) {
955 DCHECK(!dragging());
956 DCHECK(drag_view_);
957 drag_pointer_ = pointer;
958 start_drag_index_ = view_model_->GetIndexOfView(drag_view_);
959
960 // If the item is no longer draggable, bail out.
961 if (start_drag_index_ == -1 ||
962 !delegate_->IsDraggable(model_->items()[start_drag_index_])) {
963 CancelDrag(-1);
964 return;
965 }
966
967 // Move the view to the front so that it appears on top of other views.
968 ReorderChildView(drag_view_, -1);
969 bounds_animator_->StopAnimatingView(drag_view_);
970}
971
972void LauncherView::ContinueDrag(const ui::LocatedEvent& event) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000973 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
974
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000975 // TODO: I don't think this works correctly with RTL.
976 gfx::Point drag_point(event.location());
977 views::View::ConvertPointToTarget(drag_view_, this, &drag_point);
978 int current_index = view_model_->GetIndexOfView(drag_view_);
979 DCHECK_NE(-1, current_index);
980
981 // If the item is no longer draggable, bail out.
982 if (current_index == -1 ||
983 !delegate_->IsDraggable(model_->items()[current_index])) {
984 CancelDrag(-1);
985 return;
986 }
987
988 // Constrain the location to the range of valid indices for the type.
989 std::pair<int, int> indices(GetDragRange(current_index));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000990 int first_drag_index = indices.first;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000991 int last_drag_index = indices.second;
992 // If the last index isn't valid, we're overflowing. Constrain to the app list
993 // (which is the last visible item).
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000994 if (first_drag_index < model_->FirstPanelIndex() &&
995 last_drag_index > last_visible_index_)
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000996 last_drag_index = last_visible_index_;
997 int x = 0, y = 0;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000998 if (shelf->IsHorizontalAlignment()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000999 x = std::max(view_model_->ideal_bounds(indices.first).x(),
1000 drag_point.x() - drag_offset_);
1001 x = std::min(view_model_->ideal_bounds(last_drag_index).right() -
1002 view_model_->ideal_bounds(current_index).width(),
1003 x);
1004 if (drag_view_->x() == x)
1005 return;
1006 drag_view_->SetX(x);
1007 } else {
1008 y = std::max(view_model_->ideal_bounds(indices.first).y(),
1009 drag_point.y() - drag_offset_);
1010 y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() -
1011 view_model_->ideal_bounds(current_index).height(),
1012 y);
1013 if (drag_view_->y() == y)
1014 return;
1015 drag_view_->SetY(y);
1016 }
1017
1018 int target_index =
1019 views::ViewModelUtils::DetermineMoveIndex(
1020 *view_model_, drag_view_,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001021 shelf->IsHorizontalAlignment() ?
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001022 views::ViewModelUtils::HORIZONTAL :
1023 views::ViewModelUtils::VERTICAL,
1024 x, y);
1025 target_index =
1026 std::min(indices.second, std::max(target_index, indices.first));
1027 if (target_index == current_index)
1028 return;
1029
1030 // Change the model, the LauncherItemMoved() callback will handle the
1031 // |view_model_| update.
1032 model_->Move(current_index, target_index);
1033 bounds_animator_->StopAnimatingView(drag_view_);
1034}
1035
1036bool LauncherView::SameDragType(LauncherItemType typea,
1037 LauncherItemType typeb) const {
Ben Murdocheb525c52013-07-10 11:40:50 +01001038 if (ash::switches::UseAlternateShelfLayout()) {
1039 // TODO(harrym): Allow app list to be repositionable, if this goes live
1040 // (no flag) the pref file has to be updated so the changes persist.
1041 switch (typea) {
1042 case TYPE_TABBED:
1043 case TYPE_PLATFORM_APP:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001044 return (typeb == TYPE_TABBED || typeb == TYPE_PLATFORM_APP);
Ben Murdocheb525c52013-07-10 11:40:50 +01001045 case TYPE_APP_SHORTCUT:
1046 case TYPE_APP_LIST:
1047 case TYPE_BROWSER_SHORTCUT:
1048 return (typeb == TYPE_APP_SHORTCUT ||
1049 typeb == TYPE_APP_LIST ||
1050 typeb == TYPE_BROWSER_SHORTCUT);
1051 case TYPE_WINDOWED_APP:
1052 case TYPE_APP_PANEL:
1053 return typeb == typea;
1054 }
1055 } else {
1056 switch (typea) {
1057 case TYPE_TABBED:
1058 case TYPE_PLATFORM_APP:
1059 return (typeb == TYPE_TABBED || typeb == TYPE_PLATFORM_APP);
1060 case TYPE_APP_SHORTCUT:
1061 case TYPE_BROWSER_SHORTCUT:
1062 return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT);
1063 case TYPE_WINDOWED_APP:
1064 case TYPE_APP_LIST:
1065 case TYPE_APP_PANEL:
1066 return typeb == typea;
1067 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001068 }
1069 NOTREACHED();
1070 return false;
1071}
1072
1073std::pair<int, int> LauncherView::GetDragRange(int index) {
1074 int min_index = -1;
1075 int max_index = -1;
1076 LauncherItemType type = model_->items()[index].type;
1077 for (int i = 0; i < model_->item_count(); ++i) {
1078 if (SameDragType(model_->items()[i].type, type)) {
1079 if (min_index == -1)
1080 min_index = i;
1081 max_index = i;
1082 }
1083 }
1084 return std::pair<int, int>(min_index, max_index);
1085}
1086
1087void LauncherView::ConfigureChildView(views::View* view) {
1088 view->SetPaintToLayer(true);
1089 view->layer()->SetFillsBoundsOpaquely(false);
1090}
1091
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001092void LauncherView::ToggleOverflowBubble() {
1093 if (IsShowingOverflowBubble()) {
1094 overflow_bubble_->Hide();
1095 return;
1096 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001097
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001098 if (!overflow_bubble_)
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001099 overflow_bubble_.reset(new OverflowBubble());
1100
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001101 LauncherView* overflow_view = new LauncherView(
1102 model_, delegate_, tooltip_->shelf_layout_manager());
1103 overflow_view->Init();
1104 overflow_view->OnShelfAlignmentChanged();
1105 UpdateOverflowRange(overflow_view);
1106
1107 overflow_bubble_->Show(overflow_button_, overflow_view);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001108
1109 Shell::GetInstance()->UpdateShelfVisibility();
1110}
1111
1112void LauncherView::UpdateFirstButtonPadding() {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001113 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1114
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001115 // Creates an empty border for first launcher button to make included leading
1116 // inset act as the button's padding. This is only needed on button creation
1117 // and when shelf alignment changes.
1118 if (view_model_->view_size() > 0) {
1119 view_model_->view_at(0)->set_border(views::Border::CreateEmptyBorder(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001120 shelf->PrimaryAxisValue(0, leading_inset()),
1121 shelf->PrimaryAxisValue(leading_inset(), 0),
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001122 0,
1123 0));
1124 }
1125}
1126
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001127void LauncherView::OnFadeOutAnimationEnded() {
1128 AnimateToIdealBounds();
1129
1130 // If overflow button is visible and there is a valid new last item, fading
1131 // the new last item in after sliding animation is finished.
1132 if (overflow_button_->visible() && last_visible_index_ >= 0) {
1133 views::View* last_visible_view = view_model_->view_at(last_visible_index_);
1134 last_visible_view->layer()->SetOpacity(0);
1135 bounds_animator_->SetAnimationDelegate(
1136 last_visible_view,
1137 new LauncherView::StartFadeAnimationDelegate(this, last_visible_view),
1138 true);
1139 }
1140}
1141
1142void LauncherView::UpdateOverflowRange(LauncherView* overflow_view) {
1143 const int first_overflow_index = last_visible_index_ + 1;
1144 const int last_overflow_index = last_hidden_index_;
1145 DCHECK_LE(first_overflow_index, last_overflow_index);
1146 DCHECK_LT(last_overflow_index, view_model_->view_size());
1147
1148 overflow_view->first_visible_index_ = first_overflow_index;
1149 overflow_view->last_visible_index_ = last_overflow_index;
1150}
1151
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001152bool LauncherView::ShouldHideTooltip(const gfx::Point& cursor_location) {
1153 gfx::Rect active_bounds;
1154
1155 for (int i = 0; i < child_count(); ++i) {
1156 views::View* child = child_at(i);
1157 if (child == overflow_button_)
1158 continue;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001159 if (!ShouldShowTooltipForView(child))
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001160 continue;
1161
1162 gfx::Rect child_bounds = child->GetMirroredBounds();
1163 active_bounds.Union(child_bounds);
1164 }
1165
1166 return !active_bounds.Contains(cursor_location);
1167}
1168
1169int LauncherView::CancelDrag(int modified_index) {
1170 if (!drag_view_)
1171 return modified_index;
1172 bool was_dragging = dragging();
1173 int drag_view_index = view_model_->GetIndexOfView(drag_view_);
1174 drag_pointer_ = NONE;
1175 drag_view_ = NULL;
1176 if (drag_view_index == modified_index) {
1177 // The view that was being dragged is being modified. Don't do anything.
1178 return modified_index;
1179 }
1180 if (!was_dragging)
1181 return modified_index;
1182
1183 // Restore previous position, tracking the position of the modified view.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001184 bool at_end = modified_index == view_model_->view_size();
1185 views::View* modified_view =
1186 (modified_index >= 0 && !at_end) ?
1187 view_model_->view_at(modified_index) : NULL;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001188 model_->Move(drag_view_index, start_drag_index_);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001189
1190 // If the modified view will be at the end of the list, return the new end of
1191 // the list.
1192 if (at_end)
1193 return view_model_->view_size();
1194 return modified_view ? view_model_->GetIndexOfView(modified_view) : -1;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001195}
1196
1197gfx::Size LauncherView::GetPreferredSize() {
1198 IdealBounds ideal_bounds;
1199 CalculateIdealBounds(&ideal_bounds);
1200
1201 const int app_list_index = view_model_->view_size() - 1;
1202 const int last_button_index = is_overflow_mode() ?
1203 last_visible_index_ : app_list_index;
1204 const gfx::Rect last_button_bounds =
1205 last_button_index >= first_visible_index_ ?
1206 view_model_->view_at(last_button_index)->bounds() :
1207 gfx::Rect(gfx::Size(kLauncherPreferredSize,
1208 kLauncherPreferredSize));
1209
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001210 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1211
1212 if (shelf->IsHorizontalAlignment()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001213 return gfx::Size(last_button_bounds.right() + leading_inset(),
1214 kLauncherPreferredSize);
1215 }
1216
1217 return gfx::Size(kLauncherPreferredSize,
1218 last_button_bounds.bottom() + leading_inset());
1219}
1220
1221void LauncherView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1222 LayoutToIdealBounds();
1223 FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1224 OnLauncherIconPositionsChanged());
1225
1226 if (IsShowingOverflowBubble())
1227 overflow_bubble_->Hide();
1228}
1229
1230views::FocusTraversable* LauncherView::GetPaneFocusTraversable() {
1231 return this;
1232}
1233
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001234void LauncherView::GetAccessibleState(ui::AccessibleViewState* state) {
1235 state->role = ui::AccessibilityTypes::ROLE_TOOLBAR;
Ben Murdoch2385ea32013-08-06 11:01:04 +01001236 state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001237}
1238
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001239void LauncherView::OnGestureEvent(ui::GestureEvent* event) {
1240 if (gesture_handler_.ProcessGestureEvent(*event))
1241 event->StopPropagation();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001242}
1243
1244void LauncherView::LauncherItemAdded(int model_index) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001245 {
1246 base::AutoReset<bool> cancelling_drag(
1247 &cancelling_drag_model_changed_, true);
1248 model_index = CancelDrag(model_index);
1249 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001250 views::View* view = CreateViewForItem(model_->items()[model_index]);
1251 AddChildView(view);
1252 // Hide the view, it'll be made visible when the animation is done. Using
1253 // opacity 0 here to avoid messing with CalculateIdealBounds which touches
1254 // the view's visibility.
1255 view->layer()->SetOpacity(0);
1256 view_model_->Add(view, model_index);
1257
1258 // Give the button its ideal bounds. That way if we end up animating the
1259 // button before this animation completes it doesn't appear at some random
1260 // spot (because it was in the middle of animating from 0,0 0x0 to its
1261 // target).
1262 IdealBounds ideal_bounds;
1263 CalculateIdealBounds(&ideal_bounds);
1264 view->SetBoundsRect(view_model_->ideal_bounds(model_index));
1265
1266 // The first animation moves all the views to their target position. |view|
1267 // is hidden, so it visually appears as though we are providing space for
1268 // it. When done we'll fade the view in.
1269 AnimateToIdealBounds();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001270 if (model_index <= last_visible_index_ ||
1271 model_index >= model_->FirstPanelIndex()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001272 bounds_animator_->SetAnimationDelegate(
1273 view, new StartFadeAnimationDelegate(this, view), true);
1274 } else {
1275 // Undo the hiding if animation does not run.
1276 view->layer()->SetOpacity(1.0f);
1277 }
1278}
1279
1280void LauncherView::LauncherItemRemoved(int model_index, LauncherID id) {
1281#if !defined(OS_MACOSX)
1282 if (id == context_menu_id_)
1283 launcher_menu_runner_.reset();
1284#endif
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001285 {
1286 base::AutoReset<bool> cancelling_drag(
1287 &cancelling_drag_model_changed_, true);
1288 model_index = CancelDrag(model_index);
1289 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001290 views::View* view = view_model_->view_at(model_index);
1291 view_model_->Remove(model_index);
1292 // The first animation fades out the view. When done we'll animate the rest of
1293 // the views to their target location.
1294 bounds_animator_->AnimateViewTo(view, view->bounds());
1295 bounds_animator_->SetAnimationDelegate(
1296 view, new FadeOutAnimationDelegate(this, view), true);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001297
1298 // If overflow bubble is visible, sanitize overflow range first and when the
1299 // above animation finishes, CalculateIdealBounds will be called to get
1300 // correct overflow range. CalculateIdealBounds could hide overflow bubble
1301 // and triggers LauncherItemChanged. And since we are still in the middle
1302 // of LauncherItemRemoved, LauncherView in overflow bubble is not synced
1303 // with LauncherModel and will crash.
1304 if (overflow_bubble_ && overflow_bubble_->IsShowing()) {
1305 last_hidden_index_ = std::min(last_hidden_index_,
1306 view_model_->view_size() - 1);
1307 UpdateOverflowRange(overflow_bubble_->launcher_view());
1308 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001309}
1310
1311void LauncherView::LauncherItemChanged(int model_index,
1312 const ash::LauncherItem& old_item) {
1313 const LauncherItem& item(model_->items()[model_index]);
1314 if (old_item.type != item.type) {
1315 // Type changed, swap the views.
1316 model_index = CancelDrag(model_index);
1317 scoped_ptr<views::View> old_view(view_model_->view_at(model_index));
1318 bounds_animator_->StopAnimatingView(old_view.get());
1319 view_model_->Remove(model_index);
1320 views::View* new_view = CreateViewForItem(item);
1321 AddChildView(new_view);
1322 view_model_->Add(new_view, model_index);
1323 new_view->SetBoundsRect(old_view->bounds());
1324 return;
1325 }
1326
1327 views::View* view = view_model_->view_at(model_index);
1328 switch (item.type) {
1329 case TYPE_TABBED: {
1330 TabbedLauncherButton* button = static_cast<TabbedLauncherButton*>(view);
1331 gfx::Size pref = button->GetPreferredSize();
1332 button->SetTabImage(item.image);
1333 if (pref != button->GetPreferredSize())
1334 AnimateToIdealBounds();
1335 else
1336 button->SchedulePaint();
1337 ReflectItemStatus(item, button);
1338 break;
1339 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001340 case TYPE_BROWSER_SHORTCUT:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001341 // Fallthrough for the new Launcher since it needs to show the activation
1342 // change as well.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001343 case TYPE_APP_SHORTCUT:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001344 case TYPE_WINDOWED_APP:
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001345 case TYPE_PLATFORM_APP:
1346 case TYPE_APP_PANEL: {
1347 LauncherButton* button = static_cast<LauncherButton*>(view);
1348 ReflectItemStatus(item, button);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001349 // The browser shortcut is currently not a "real" item and as such the
1350 // the image is bogous as well. We therefore keep the image as is for it.
1351 if (item.type != TYPE_BROWSER_SHORTCUT)
1352 button->SetImage(item.image);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001353 button->SchedulePaint();
1354 break;
1355 }
1356
1357 default:
1358 break;
1359 }
1360}
1361
1362void LauncherView::LauncherItemMoved(int start_index, int target_index) {
1363 view_model_->Move(start_index, target_index);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001364 // When cancelling a drag due to a launcher item being added, the currently
1365 // dragged item is moved back to its initial position. AnimateToIdealBounds
1366 // will be called again when the new item is added to the |view_model_| but
1367 // at this time the |view_model_| is inconsistent with the |model_|.
1368 if (!cancelling_drag_model_changed_)
1369 AnimateToIdealBounds();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001370}
1371
1372void LauncherView::LauncherStatusChanged() {
Ben Murdocheb525c52013-07-10 11:40:50 +01001373 if (ash::switches::UseAlternateShelfLayout())
1374 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001375 AppListButton* app_list_button =
1376 static_cast<AppListButton*>(GetAppListButtonView());
1377 if (model_->status() == LauncherModel::STATUS_LOADING)
1378 app_list_button->StartLoadingAnimation();
1379 else
1380 app_list_button->StopLoadingAnimation();
1381}
1382
1383void LauncherView::PointerPressedOnButton(views::View* view,
1384 Pointer pointer,
1385 const ui::LocatedEvent& event) {
1386 if (drag_view_)
1387 return;
1388
1389 tooltip_->Close();
1390 int index = view_model_->GetIndexOfView(view);
1391 if (index == -1 ||
1392 view_model_->view_size() <= 1 ||
1393 !delegate_->IsDraggable(model_->items()[index]))
1394 return; // View is being deleted or not draggable, ignore request.
1395
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001396 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
1397
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001398 drag_view_ = view;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001399 drag_offset_ = shelf->PrimaryAxisValue(event.x(), event.y());
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001400}
1401
1402void LauncherView::PointerDraggedOnButton(views::View* view,
1403 Pointer pointer,
1404 const ui::LocatedEvent& event) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001405 ShelfLayoutManager* shelf = tooltip_->shelf_layout_manager();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001406 if (!dragging() && drag_view_ &&
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001407 shelf->PrimaryAxisValue(abs(event.x() - drag_offset_),
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001408 abs(event.y() - drag_offset_)) >=
1409 kMinimumDragDistance) {
1410 PrepareForDrag(pointer, event);
1411 }
1412 if (drag_pointer_ == pointer)
1413 ContinueDrag(event);
1414}
1415
1416void LauncherView::PointerReleasedOnButton(views::View* view,
1417 Pointer pointer,
1418 bool canceled) {
1419 if (canceled) {
1420 CancelDrag(-1);
1421 } else if (drag_pointer_ == pointer) {
1422 drag_pointer_ = NONE;
1423 drag_view_ = NULL;
1424 AnimateToIdealBounds();
1425 }
1426}
1427
1428void LauncherView::MouseMovedOverButton(views::View* view) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001429 if (!ShouldShowTooltipForView(view))
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001430 return;
1431
1432 if (!tooltip_->IsVisible())
1433 tooltip_->ResetTimer();
1434}
1435
1436void LauncherView::MouseEnteredButton(views::View* view) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001437 if (!ShouldShowTooltipForView(view))
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001438 return;
1439
1440 if (tooltip_->IsVisible()) {
1441 tooltip_->ShowImmediately(view, GetAccessibleName(view));
1442 } else {
1443 tooltip_->ShowDelayed(view, GetAccessibleName(view));
1444 }
1445}
1446
1447void LauncherView::MouseExitedButton(views::View* view) {
1448 if (!tooltip_->IsVisible())
1449 tooltip_->StopTimer();
1450}
1451
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001452base::string16 LauncherView::GetAccessibleName(const views::View* view) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001453 int view_index = view_model_->GetIndexOfView(view);
1454 // May be -1 while in the process of animating closed.
1455 if (view_index == -1)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001456 return base::string16();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001457
1458 switch (model_->items()[view_index].type) {
1459 case TYPE_TABBED:
1460 case TYPE_APP_PANEL:
1461 case TYPE_APP_SHORTCUT:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001462 case TYPE_WINDOWED_APP:
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001463 case TYPE_PLATFORM_APP:
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001464 case TYPE_BROWSER_SHORTCUT:
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001465 return delegate_->GetTitle(model_->items()[view_index]);
1466
1467 case TYPE_APP_LIST:
1468 return model_->status() == LauncherModel::STATUS_LOADING ?
1469 l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_SYNCING_TITLE) :
1470 l10n_util::GetStringUTF16(IDS_AURA_APP_LIST_TITLE);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001471 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001472 return base::string16();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001473}
1474
1475void LauncherView::ButtonPressed(views::Button* sender,
1476 const ui::Event& event) {
1477 // Do not handle mouse release during drag.
1478 if (dragging())
1479 return;
1480
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001481 tooltip_->Close();
1482
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001483 if (sender == overflow_button_) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001484 ToggleOverflowBubble();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001485 return;
1486 }
1487
1488 int view_index = view_model_->GetIndexOfView(sender);
1489 // May be -1 while in the process of animating closed.
1490 if (view_index == -1)
1491 return;
1492
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001493 // If the previous menu was closed by the same event as this one, we ignore
1494 // the call.
1495 if (!IsUsableEvent(event))
1496 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001497
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001498 {
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001499 ScopedTargetRootWindow scoped_target(
1500 sender->GetWidget()->GetNativeView()->GetRootWindow());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001501 // Slow down activation animations if shift key is pressed.
1502 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations;
1503 if (event.IsShiftDown()) {
1504 slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode(
1505 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
1506 }
1507
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001508 // Collect usage statistics before we decide what to do with the click.
1509 switch (model_->items()[view_index].type) {
1510 case TYPE_APP_SHORTCUT:
1511 case TYPE_WINDOWED_APP:
1512 case TYPE_PLATFORM_APP:
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001513 case TYPE_BROWSER_SHORTCUT:
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001514 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1515 UMA_LAUNCHER_CLICK_ON_APP);
1516 // Fallthrough
1517 case TYPE_TABBED:
1518 case TYPE_APP_PANEL:
1519 delegate_->ItemSelected(model_->items()[view_index], event);
1520 break;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001521
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001522 case TYPE_APP_LIST:
1523 Shell::GetInstance()->delegate()->RecordUserMetricsAction(
1524 UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON);
1525 Shell::GetInstance()->ToggleAppList(GetWidget()->GetNativeView());
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +01001526 break;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001527 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001528 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001529
1530 if (model_->items()[view_index].type != TYPE_APP_LIST)
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001531 ShowListMenuForView(model_->items()[view_index], sender, event);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001532}
1533
1534bool LauncherView::ShowListMenuForView(const LauncherItem& item,
1535 views::View* source,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001536 const ui::Event& event) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001537 scoped_ptr<ash::LauncherMenuModel> menu_model;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001538 menu_model.reset(delegate_->CreateApplicationMenu(item, event.flags()));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001539
1540 // Make sure we have a menu and it has at least two items in addition to the
1541 // application title and the 3 spacing separators.
1542 if (!menu_model.get() || menu_model->GetItemCount() <= 5)
1543 return false;
1544
1545 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1546 new LauncherMenuModelAdapter(menu_model.get())),
1547 source,
1548 gfx::Point(),
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001549 false,
1550 ui::GetMenuSourceTypeForEvent(event));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001551 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001552}
1553
1554void LauncherView::ShowContextMenuForView(views::View* source,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001555 const gfx::Point& point,
1556 ui:: MenuSourceType source_type) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001557 int view_index = view_model_->GetIndexOfView(source);
1558 if (view_index != -1 &&
1559 model_->items()[view_index].type == TYPE_APP_LIST) {
1560 view_index = -1;
1561 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001562
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001563 tooltip_->Close();
1564
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001565 if (view_index == -1) {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001566 Shell::GetInstance()->ShowContextMenu(point, source_type);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001567 return;
1568 }
1569 scoped_ptr<ui::MenuModel> menu_model(delegate_->CreateContextMenu(
1570 model_->items()[view_index],
1571 source->GetWidget()->GetNativeView()->GetRootWindow()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001572 if (!menu_model)
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001573 return;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001574 base::AutoReset<LauncherID> reseter(
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001575 &context_menu_id_,
1576 view_index == -1 ? 0 : model_->items()[view_index].id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001577
1578 ShowMenu(scoped_ptr<views::MenuModelAdapter>(
1579 new views::MenuModelAdapter(menu_model.get())),
1580 source,
1581 point,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001582 true,
1583 source_type);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001584}
1585
1586void LauncherView::ShowMenu(
1587 scoped_ptr<views::MenuModelAdapter> menu_model_adapter,
1588 views::View* source,
1589 const gfx::Point& click_point,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001590 bool context_menu,
1591 ui::MenuSourceType source_type) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001592 closing_event_time_ = base::TimeDelta();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001593 launcher_menu_runner_.reset(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001594 new views::MenuRunner(menu_model_adapter->CreateMenu()));
1595
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001596 ScopedTargetRootWindow scoped_target(
1597 source->GetWidget()->GetNativeView()->GetRootWindow());
1598
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001599 // Determine the menu alignment dependent on the shelf.
1600 views::MenuItemView::AnchorPosition menu_alignment =
1601 views::MenuItemView::TOPLEFT;
1602 gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size());
1603
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001604 ShelfWidget* shelf = RootWindowController::ForLauncher(
1605 GetWidget()->GetNativeView())->shelf();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001606 if (!context_menu) {
1607 // Application lists use a bubble.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001608 ash::ShelfAlignment align = shelf->GetAlignment();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001609 anchor_point = source->GetBoundsInScreen();
1610
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001611 // It is possible to invoke the menu while it is sliding into view. To cover
1612 // that case, the screen coordinates are offsetted by the animation delta.
1613 gfx::Vector2d offset =
1614 source->GetWidget()->GetNativeWindow()->bounds().origin() -
1615 source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin();
1616 anchor_point.set_x(anchor_point.x() - offset.x());
1617 anchor_point.set_y(anchor_point.y() - offset.y());
1618
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001619 // Launcher items can have an asymmetrical border for spacing reasons.
1620 // Adjust anchor location for this.
1621 if (source->border())
1622 anchor_point.Inset(source->border()->GetInsets());
1623
1624 switch (align) {
1625 case ash::SHELF_ALIGNMENT_BOTTOM:
1626 menu_alignment = views::MenuItemView::BUBBLE_ABOVE;
1627 break;
1628 case ash::SHELF_ALIGNMENT_LEFT:
1629 menu_alignment = views::MenuItemView::BUBBLE_RIGHT;
1630 break;
1631 case ash::SHELF_ALIGNMENT_RIGHT:
1632 menu_alignment = views::MenuItemView::BUBBLE_LEFT;
1633 break;
1634 case ash::SHELF_ALIGNMENT_TOP:
1635 menu_alignment = views::MenuItemView::BUBBLE_BELOW;
1636 break;
1637 }
1638 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001639 // If this gets deleted while we are in the menu, the launcher will be gone
1640 // as well.
1641 bool got_deleted = false;
1642 got_deleted_ = &got_deleted;
1643
1644 shelf->ForceUndimming(true);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001645 // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building
1646 // code.
1647 if (launcher_menu_runner_->RunMenuAt(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001648 source->GetWidget(),
1649 NULL,
1650 anchor_point,
1651 menu_alignment,
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +01001652 source_type,
1653 context_menu ? views::MenuRunner::CONTEXT_MENU : 0) ==
1654 views::MenuRunner::MENU_DELETED) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001655 if (!got_deleted) {
1656 got_deleted_ = NULL;
1657 shelf->ForceUndimming(false);
1658 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001659 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001660 }
1661 got_deleted_ = NULL;
1662 shelf->ForceUndimming(false);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001663
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001664 // Unpinning an item will reset the |launcher_menu_runner_| before coming
1665 // here.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001666 if (launcher_menu_runner_)
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001667 closing_event_time_ = launcher_menu_runner_->closing_event_time();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001668 Shell::GetInstance()->UpdateShelfVisibility();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001669}
1670
1671void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) {
1672 FOR_EACH_OBSERVER(LauncherIconObserver, observers_,
1673 OnLauncherIconPositionsChanged());
1674 PreferredSizeChanged();
1675}
1676
1677void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) {
1678}
1679
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001680bool LauncherView::IsUsableEvent(const ui::Event& event) {
1681 if (closing_event_time_ == base::TimeDelta())
1682 return true;
1683
1684 base::TimeDelta delta =
1685 base::TimeDelta(event.time_stamp() - closing_event_time_);
1686 closing_event_time_ = base::TimeDelta();
1687 // TODO(skuhne): This time seems excessive, but it appears that the reposting
1688 // takes that long. Need to come up with a better way of doing this.
1689 return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130);
1690}
1691
1692const LauncherItem* LauncherView::LauncherItemForView(
1693 const views::View* view) const {
1694 int view_index = view_model_->GetIndexOfView(view);
1695 if (view_index == -1)
1696 return NULL;
1697 return &(model_->items()[view_index]);
1698}
1699
1700bool LauncherView::ShouldShowTooltipForView(const views::View* view) const {
1701 if (view == GetAppListButtonView() &&
1702 Shell::GetInstance()->GetAppListWindow())
1703 return false;
1704 const LauncherItem* item = LauncherItemForView(view);
1705 return (!item || delegate_->ShouldShowTooltip(*item));
1706}
1707
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001708} // namespace internal
1709} // namespace ash