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