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 54 using ui::Animation; 55 using views::View; 56 57 namespace ash { 58 namespace internal { 59 60 // Default amount content is inset on the left edge. 61 const int kDefaultLeadingInset = 8; 62 63 // Minimum distance before drag starts. 64 const int kMinimumDragDistance = 8; 65 66 // Size between the buttons. 67 const int kButtonSpacing = 4; 68 const int kAlternateButtonSpacing = 10; 69 70 // Size allocated to for each button. 71 const int kButtonSize = 44; 72 73 // Additional spacing for the left and right side of icons. 74 const int kHorizontalIconSpacing = 2; 75 76 // Inset for items which do not have an icon. 77 const 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. 83 const float kReservedNonPanelIconProportion = 0.67f; 84 85 // This is the command id of the menu item which contains the name of the menu. 86 const int kCommandIdOfMenuName = 0; 87 88 // The background color of the active item in the list. 89 const SkColor kActiveListItemBackgroundColor = SkColorSetRGB(203 , 219, 241); 90 91 // The background color of the active & hovered item in the list. 92 const SkColor kFocusedActiveListItemBackgroundColor = 93 SkColorSetRGB(193, 211, 236); 94 95 // The text color of the caption item in a list. 96 const SkColor kCaptionItemForegroundColor = SK_ColorBLACK; 97 98 // The maximum allowable length of a menu line of an application menu in pixels. 99 const int kMaximumAppMenuItemLength = 350; 100 101 namespace { 102 103 // The MenuModelAdapter gets slightly changed to adapt the menu appearance to 104 // our requirements. 105 class 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 133 LauncherMenuModelAdapter::LauncherMenuModelAdapter( 134 ash::LauncherMenuModel* menu_model) 135 : MenuModelAdapter(menu_model), 136 launcher_menu_model_(menu_model) {} 137 138 const 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 147 bool LauncherMenuModelAdapter::IsCommandEnabled(int id) const { 148 return id != kCommandIdOfMenuName; 149 } 150 151 bool 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 162 bool 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 174 void 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 184 int LauncherMenuModelAdapter::GetMaxWidthForMenu(views::MenuItemView* menu) { 185 return kMaximumAppMenuItemLength; 186 } 187 188 bool 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. 194 class 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 231 class 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. 250 class 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. 264 class 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 290 void 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. 317 gfx::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. 331 class 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. 360 class 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 384 LauncherView::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 414 LauncherView::~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 423 void 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 443 void 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 472 void 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 481 gfx::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 498 void 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 526 bool 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 534 bool LauncherView::IsShowingOverflowBubble() const { 535 return overflow_bubble_.get() && overflow_bubble_->IsShowing(); 536 } 537 538 views::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 551 views::FocusSearch* LauncherView::GetFocusSearch() { 552 return focus_search_.get(); 553 } 554 555 views::FocusTraversable* LauncherView::GetFocusTraversableParent() { 556 return parent()->GetFocusTraversable(); 557 } 558 559 View* LauncherView::GetFocusTraversableParentView() { 560 return this; 561 } 562 563 void 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 585 void 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 592 void LauncherView::DestroyDragIconProxy() { 593 drag_image_.reset(); 594 drag_image_offset_ = gfx::Vector2d(0, 0); 595 } 596 597 bool 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 646 bool 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 662 void 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 680 void 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 692 void 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 846 int 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 859 int 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 872 void LauncherView::AddIconObserver(LauncherIconObserver* observer) { 873 observers_.AddObserver(observer); 874 } 875 876 void LauncherView::RemoveIconObserver(LauncherIconObserver* observer) { 877 observers_.RemoveObserver(observer); 878 } 879 880 void 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 896 views::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 962 void 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 970 void 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 989 void 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 1053 bool 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 1071 std::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 1085 void LauncherView::ConfigureChildView(views::View* view) { 1086 view->SetPaintToLayer(true); 1087 view->layer()->SetFillsBoundsOpaquely(false); 1088 } 1089 1090 void 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 1110 void 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 1128 void 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 1143 void 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 1153 bool 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 1170 int 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 1198 gfx::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 1222 void 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 1231 views::FocusTraversable* LauncherView::GetPaneFocusTraversable() { 1232 return this; 1233 } 1234 1235 void 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 1240 void LauncherView::OnGestureEvent(ui::GestureEvent* event) { 1241 if (gesture_handler_.ProcessGestureEvent(*event)) 1242 event->StopPropagation(); 1243 } 1244 1245 void 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 1281 void 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 1312 void 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 1363 void 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 1373 void 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 1384 void 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 1403 void 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 1417 void 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 1432 void LauncherView::MouseMovedOverButton(views::View* view) { 1433 if (!ShouldShowTooltipForView(view)) 1434 return; 1435 1436 if (!tooltip_->IsVisible()) 1437 tooltip_->ResetTimer(); 1438 } 1439 1440 void 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 1451 void LauncherView::MouseExitedButton(views::View* view) { 1452 if (!tooltip_->IsVisible()) 1453 tooltip_->StopTimer(); 1454 } 1455 1456 base::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 1479 void 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 1538 bool 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 1558 void 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 1590 void 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 1675 void LauncherView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) { 1676 FOR_EACH_OBSERVER(LauncherIconObserver, observers_, 1677 OnLauncherIconPositionsChanged()); 1678 PreferredSizeChanged(); 1679 } 1680 1681 void LauncherView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { 1682 } 1683 1684 bool 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 1696 const 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 1704 bool 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