1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "ash/shelf/shelf_view.h" 6 7 #include <algorithm> 8 9 #include "ash/ash_constants.h" 10 #include "ash/ash_switches.h" 11 #include "ash/drag_drop/drag_image_view.h" 12 #include "ash/metrics/user_metrics_recorder.h" 13 #include "ash/root_window_controller.h" 14 #include "ash/scoped_target_root_window.h" 15 #include "ash/shelf/app_list_button.h" 16 #include "ash/shelf/overflow_bubble.h" 17 #include "ash/shelf/overflow_bubble_view.h" 18 #include "ash/shelf/overflow_button.h" 19 #include "ash/shelf/shelf_button.h" 20 #include "ash/shelf/shelf_constants.h" 21 #include "ash/shelf/shelf_delegate.h" 22 #include "ash/shelf/shelf_icon_observer.h" 23 #include "ash/shelf/shelf_item_delegate.h" 24 #include "ash/shelf/shelf_item_delegate_manager.h" 25 #include "ash/shelf/shelf_layout_manager.h" 26 #include "ash/shelf/shelf_menu_model.h" 27 #include "ash/shelf/shelf_model.h" 28 #include "ash/shelf/shelf_tooltip_manager.h" 29 #include "ash/shelf/shelf_widget.h" 30 #include "ash/shell.h" 31 #include "ash/wm/coordinate_conversion.h" 32 #include "base/auto_reset.h" 33 #include "base/memory/scoped_ptr.h" 34 #include "base/metrics/histogram.h" 35 #include "grit/ash_strings.h" 36 #include "ui/accessibility/ax_view_state.h" 37 #include "ui/aura/client/screen_position_client.h" 38 #include "ui/aura/window.h" 39 #include "ui/aura/window_event_dispatcher.h" 40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/models/simple_menu_model.h" 42 #include "ui/base/resource/resource_bundle.h" 43 #include "ui/compositor/layer.h" 44 #include "ui/compositor/layer_animator.h" 45 #include "ui/compositor/scoped_animation_duration_scale_mode.h" 46 #include "ui/gfx/canvas.h" 47 #include "ui/gfx/point.h" 48 #include "ui/views/animation/bounds_animator.h" 49 #include "ui/views/border.h" 50 #include "ui/views/controls/button/image_button.h" 51 #include "ui/views/controls/menu/menu_model_adapter.h" 52 #include "ui/views/controls/menu/menu_runner.h" 53 #include "ui/views/focus/focus_search.h" 54 #include "ui/views/view_model.h" 55 #include "ui/views/view_model_utils.h" 56 #include "ui/views/widget/widget.h" 57 #include "ui/wm/core/coordinate_conversion.h" 58 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 explicit BoundsAnimatorDisabler(views::BoundsAnimator* bounds_animator) 119 : old_duration_(bounds_animator->GetAnimationDuration()), 120 bounds_animator_(bounds_animator) { 121 bounds_animator_->SetAnimationDuration(1); 122 } 123 124 ~BoundsAnimatorDisabler() { 125 bounds_animator_->SetAnimationDuration(old_duration_); 126 } 127 128 private: 129 // The previous animation duration. 130 int old_duration_; 131 // The bounds animator which gets used. 132 views::BoundsAnimator* bounds_animator_; 133 134 DISALLOW_COPY_AND_ASSIGN(BoundsAnimatorDisabler); 135 }; 136 137 // The MenuModelAdapter gets slightly changed to adapt the menu appearance to 138 // our requirements. 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 ::wm::ConvertPointFromScreen( 621 ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root); 622 ui::MouseEvent event(ui::ET_MOUSE_PRESSED, pt, point_in_root, 0, 0); 623 PointerPressedOnButton(drag_and_drop_view, 624 ShelfButtonHost::DRAG_AND_DROP, 625 event); 626 627 // Drag the item where it really belongs. 628 Drag(location_in_screen_coordinates); 629 return true; 630 } 631 632 bool ShelfView::Drag(const gfx::Point& location_in_screen_coordinates) { 633 if (!drag_and_drop_shelf_id_ || 634 !GetBoundsInScreen().Contains(location_in_screen_coordinates)) 635 return false; 636 637 gfx::Point pt = location_in_screen_coordinates; 638 views::View* drag_and_drop_view = view_model_->view_at( 639 model_->ItemIndexByID(drag_and_drop_shelf_id_)); 640 ConvertPointFromScreen(drag_and_drop_view, &pt); 641 gfx::Point point_in_root = location_in_screen_coordinates; 642 ::wm::ConvertPointFromScreen( 643 ash::wm::GetRootWindowAt(location_in_screen_coordinates), &point_in_root); 644 ui::MouseEvent event(ui::ET_MOUSE_DRAGGED, pt, point_in_root, 0, 0); 645 PointerDraggedOnButton(drag_and_drop_view, 646 ShelfButtonHost::DRAG_AND_DROP, 647 event); 648 return true; 649 } 650 651 void ShelfView::EndDrag(bool cancel) { 652 if (!drag_and_drop_shelf_id_) 653 return; 654 655 views::View* drag_and_drop_view = view_model_->view_at( 656 model_->ItemIndexByID(drag_and_drop_shelf_id_)); 657 PointerReleasedOnButton( 658 drag_and_drop_view, ShelfButtonHost::DRAG_AND_DROP, cancel); 659 660 // Either destroy the temporarily created item - or - make the item visible. 661 if (drag_and_drop_item_pinned_ && cancel) { 662 delegate_->UnpinAppWithID(drag_and_drop_app_id_); 663 } else if (drag_and_drop_view) { 664 if (cancel) { 665 // When a hosted drag gets canceled, the item can remain in the same slot 666 // and it might have moved within the bounds. In that case the item need 667 // to animate back to its correct location. 668 AnimateToIdealBounds(); 669 } else { 670 drag_and_drop_view->SetSize(pre_drag_and_drop_size_); 671 } 672 } 673 674 drag_and_drop_shelf_id_ = 0; 675 } 676 677 void ShelfView::LayoutToIdealBounds() { 678 if (bounds_animator_->IsAnimating()) { 679 AnimateToIdealBounds(); 680 return; 681 } 682 683 IdealBounds ideal_bounds; 684 CalculateIdealBounds(&ideal_bounds); 685 views::ViewModelUtils::SetViewBoundsToIdealBounds(*view_model_); 686 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds); 687 } 688 689 void ShelfView::UpdateAllButtonsVisibilityInOverflowMode() { 690 // The overflow button is not shown in overflow mode. 691 overflow_button_->SetVisible(false); 692 DCHECK_LT(last_visible_index_, view_model_->view_size()); 693 for (int i = 0; i < view_model_->view_size(); ++i) { 694 bool visible = i >= first_visible_index_ && 695 i <= last_visible_index_; 696 // To track the dragging of |drag_view_| continuously, its visibility 697 // should be always true regardless of its position. 698 if (dragged_off_from_overflow_to_shelf_ && 699 view_model_->view_at(i) == drag_view_) 700 view_model_->view_at(i)->SetVisible(true); 701 else 702 view_model_->view_at(i)->SetVisible(visible); 703 } 704 } 705 706 void ShelfView::CalculateIdealBounds(IdealBounds* bounds) const { 707 int available_size = layout_manager_->PrimaryAxisValue(width(), height()); 708 DCHECK(model_->item_count() == view_model_->view_size()); 709 if (!available_size) 710 return; 711 712 int first_panel_index = model_->FirstPanelIndex(); 713 int last_button_index = first_panel_index - 1; 714 715 int x = 0; 716 int y = 0; 717 int button_size = kShelfButtonSize; 718 int button_spacing = kShelfButtonSpacing; 719 720 int w = layout_manager_->PrimaryAxisValue(button_size, width()); 721 int h = layout_manager_->PrimaryAxisValue(height(), button_size); 722 for (int i = 0; i < view_model_->view_size(); ++i) { 723 if (i < first_visible_index_) { 724 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, 0, 0)); 725 continue; 726 } 727 728 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h)); 729 if (i != last_button_index) { 730 x = layout_manager_->PrimaryAxisValue(x + w + button_spacing, x); 731 y = layout_manager_->PrimaryAxisValue(y, y + h + button_spacing); 732 } 733 } 734 735 if (is_overflow_mode()) { 736 const_cast<ShelfView*>(this)->UpdateAllButtonsVisibilityInOverflowMode(); 737 return; 738 } 739 740 // Right aligned icons. 741 int end_position = available_size - button_spacing; 742 x = layout_manager_->PrimaryAxisValue(end_position, 0); 743 y = layout_manager_->PrimaryAxisValue(0, end_position); 744 for (int i = view_model_->view_size() - 1; 745 i >= first_panel_index; --i) { 746 x = layout_manager_->PrimaryAxisValue(x - w - button_spacing, x); 747 y = layout_manager_->PrimaryAxisValue(y, y - h - button_spacing); 748 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h)); 749 end_position = layout_manager_->PrimaryAxisValue(x, y); 750 } 751 752 // Icons on the left / top are guaranteed up to kLeftIconProportion of 753 // the available space. 754 int last_icon_position = layout_manager_->PrimaryAxisValue( 755 view_model_->ideal_bounds(last_button_index).right(), 756 view_model_->ideal_bounds(last_button_index).bottom()) + button_size; 757 int reserved_icon_space = available_size * kReservedNonPanelIconProportion; 758 if (last_icon_position < reserved_icon_space) 759 end_position = last_icon_position; 760 else 761 end_position = std::max(end_position, reserved_icon_space); 762 763 bounds->overflow_bounds.set_size( 764 gfx::Size(layout_manager_->PrimaryAxisValue(w, width()), 765 layout_manager_->PrimaryAxisValue(height(), h))); 766 767 last_visible_index_ = DetermineLastVisibleIndex( 768 end_position - button_size); 769 last_hidden_index_ = DetermineFirstVisiblePanelIndex(end_position) - 1; 770 bool show_overflow = last_visible_index_ < last_button_index || 771 last_hidden_index_ >= first_panel_index; 772 773 // Create Space for the overflow button 774 if (show_overflow && 775 last_visible_index_ > 0 && last_visible_index_ < last_button_index) 776 --last_visible_index_; 777 for (int i = 0; i < view_model_->view_size(); ++i) { 778 bool visible = i <= last_visible_index_ || i > last_hidden_index_; 779 // To receive drag event continuously from |drag_view_| during the dragging 780 // off from the shelf, don't make |drag_view_| invisible. It will be 781 // eventually invisible and removed from the |view_model_| by 782 // FinalizeRipOffDrag(). 783 if (dragged_off_shelf_ && view_model_->view_at(i) == drag_view_) 784 continue; 785 view_model_->view_at(i)->SetVisible(visible); 786 } 787 788 overflow_button_->SetVisible(show_overflow); 789 if (show_overflow) { 790 DCHECK_NE(0, view_model_->view_size()); 791 if (last_visible_index_ == -1) { 792 x = 0; 793 y = 0; 794 } else { 795 x = layout_manager_->PrimaryAxisValue( 796 view_model_->ideal_bounds(last_visible_index_).right(), 797 view_model_->ideal_bounds(last_visible_index_).x()); 798 y = layout_manager_->PrimaryAxisValue( 799 view_model_->ideal_bounds(last_visible_index_).y(), 800 view_model_->ideal_bounds(last_visible_index_).bottom()); 801 } 802 // Set all hidden panel icon positions to be on the overflow button. 803 for (int i = first_panel_index; i <= last_hidden_index_; ++i) 804 view_model_->set_ideal_bounds(i, gfx::Rect(x, y, w, h)); 805 806 // Add more space between last visible item and overflow button. 807 // Without this, two buttons look too close compared with other items. 808 x = layout_manager_->PrimaryAxisValue(x + button_spacing, x); 809 y = layout_manager_->PrimaryAxisValue(y, y + button_spacing); 810 811 bounds->overflow_bounds.set_x(x); 812 bounds->overflow_bounds.set_y(y); 813 if (overflow_bubble_.get() && overflow_bubble_->IsShowing()) 814 UpdateOverflowRange(overflow_bubble_->shelf_view()); 815 } else { 816 if (overflow_bubble_) 817 overflow_bubble_->Hide(); 818 } 819 } 820 821 int ShelfView::DetermineLastVisibleIndex(int max_value) const { 822 int index = model_->FirstPanelIndex() - 1; 823 while (index >= 0 && 824 layout_manager_->PrimaryAxisValue( 825 view_model_->ideal_bounds(index).right(), 826 view_model_->ideal_bounds(index).bottom()) > max_value) { 827 index--; 828 } 829 return index; 830 } 831 832 int ShelfView::DetermineFirstVisiblePanelIndex(int min_value) const { 833 int index = model_->FirstPanelIndex(); 834 while (index < view_model_->view_size() && 835 layout_manager_->PrimaryAxisValue( 836 view_model_->ideal_bounds(index).right(), 837 view_model_->ideal_bounds(index).bottom()) < min_value) { 838 ++index; 839 } 840 return index; 841 } 842 843 void ShelfView::AddIconObserver(ShelfIconObserver* observer) { 844 observers_.AddObserver(observer); 845 } 846 847 void ShelfView::RemoveIconObserver(ShelfIconObserver* observer) { 848 observers_.RemoveObserver(observer); 849 } 850 851 void ShelfView::AnimateToIdealBounds() { 852 IdealBounds ideal_bounds; 853 CalculateIdealBounds(&ideal_bounds); 854 for (int i = 0; i < view_model_->view_size(); ++i) { 855 View* view = view_model_->view_at(i); 856 bounds_animator_->AnimateViewTo(view, view_model_->ideal_bounds(i)); 857 // Now that the item animation starts, we have to make sure that the 858 // padding of the first gets properly transferred to the new first item. 859 if (i && view->border()) 860 view->SetBorder(views::Border::NullBorder()); 861 } 862 overflow_button_->SetBoundsRect(ideal_bounds.overflow_bounds); 863 } 864 865 views::View* ShelfView::CreateViewForItem(const ShelfItem& item) { 866 views::View* view = NULL; 867 switch (item.type) { 868 case TYPE_BROWSER_SHORTCUT: 869 case TYPE_APP_SHORTCUT: 870 case TYPE_WINDOWED_APP: 871 case TYPE_PLATFORM_APP: 872 case TYPE_DIALOG: 873 case TYPE_APP_PANEL: { 874 ShelfButton* button = ShelfButton::Create(this, this, layout_manager_); 875 button->SetImage(item.image); 876 ReflectItemStatus(item, button); 877 view = button; 878 break; 879 } 880 881 case TYPE_APP_LIST: { 882 view = new AppListButton(this, this, layout_manager_->shelf_widget()); 883 break; 884 } 885 886 default: 887 break; 888 } 889 view->set_context_menu_controller(this); 890 891 DCHECK(view); 892 ConfigureChildView(view); 893 return view; 894 } 895 896 void ShelfView::FadeIn(views::View* view) { 897 view->SetVisible(true); 898 view->layer()->SetOpacity(0); 899 AnimateToIdealBounds(); 900 bounds_animator_->SetAnimationDelegate( 901 view, 902 scoped_ptr<gfx::AnimationDelegate>(new FadeInAnimationDelegate(view))); 903 } 904 905 void ShelfView::PrepareForDrag(Pointer pointer, const ui::LocatedEvent& event) { 906 DCHECK(!dragging()); 907 DCHECK(drag_view_); 908 drag_pointer_ = pointer; 909 start_drag_index_ = view_model_->GetIndexOfView(drag_view_); 910 911 if (start_drag_index_== -1) { 912 CancelDrag(-1); 913 return; 914 } 915 916 // If the item is no longer draggable, bail out. 917 ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( 918 model_->items()[start_drag_index_].id); 919 if (!item_delegate->IsDraggable()) { 920 CancelDrag(-1); 921 return; 922 } 923 924 // Move the view to the front so that it appears on top of other views. 925 ReorderChildView(drag_view_, -1); 926 bounds_animator_->StopAnimatingView(drag_view_); 927 } 928 929 void ShelfView::ContinueDrag(const ui::LocatedEvent& event) { 930 // Due to a syncing operation the application might have been removed. 931 // Bail if it is gone. 932 int current_index = view_model_->GetIndexOfView(drag_view_); 933 DCHECK_NE(-1, current_index); 934 935 ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( 936 model_->items()[current_index].id); 937 if (!item_delegate->IsDraggable()) { 938 CancelDrag(-1); 939 return; 940 } 941 942 // If this is not a drag and drop host operation and not the app list item, 943 // check if the item got ripped off the shelf - if it did we are done. 944 if (!drag_and_drop_shelf_id_ && 945 RemovableByRipOff(current_index) != NOT_REMOVABLE) { 946 if (HandleRipOffDrag(event)) 947 return; 948 // The rip off handler could have changed the location of the item. 949 current_index = view_model_->GetIndexOfView(drag_view_); 950 } 951 952 // TODO: I don't think this works correctly with RTL. 953 gfx::Point drag_point(event.location()); 954 ConvertPointToTarget(drag_view_, this, &drag_point); 955 956 // Constrain the location to the range of valid indices for the type. 957 std::pair<int, int> indices(GetDragRange(current_index)); 958 int first_drag_index = indices.first; 959 int last_drag_index = indices.second; 960 // If the last index isn't valid, we're overflowing. Constrain to the app list 961 // (which is the last visible item). 962 if (first_drag_index < model_->FirstPanelIndex() && 963 last_drag_index > last_visible_index_) 964 last_drag_index = last_visible_index_; 965 int x = 0, y = 0; 966 if (layout_manager_->IsHorizontalAlignment()) { 967 x = std::max(view_model_->ideal_bounds(indices.first).x(), 968 drag_point.x() - drag_origin_.x()); 969 x = std::min(view_model_->ideal_bounds(last_drag_index).right() - 970 view_model_->ideal_bounds(current_index).width(), 971 x); 972 if (drag_view_->x() == x) 973 return; 974 drag_view_->SetX(x); 975 } else { 976 y = std::max(view_model_->ideal_bounds(indices.first).y(), 977 drag_point.y() - drag_origin_.y()); 978 y = std::min(view_model_->ideal_bounds(last_drag_index).bottom() - 979 view_model_->ideal_bounds(current_index).height(), 980 y); 981 if (drag_view_->y() == y) 982 return; 983 drag_view_->SetY(y); 984 } 985 986 int target_index = 987 views::ViewModelUtils::DetermineMoveIndex( 988 *view_model_, drag_view_, 989 layout_manager_->IsHorizontalAlignment() ? 990 views::ViewModelUtils::HORIZONTAL : 991 views::ViewModelUtils::VERTICAL, 992 x, y); 993 target_index = 994 std::min(indices.second, std::max(target_index, indices.first)); 995 if (target_index == current_index) 996 return; 997 998 // Change the model, the ShelfItemMoved() callback will handle the 999 // |view_model_| update. 1000 model_->Move(current_index, target_index); 1001 bounds_animator_->StopAnimatingView(drag_view_); 1002 } 1003 1004 bool ShelfView::HandleRipOffDrag(const ui::LocatedEvent& event) { 1005 int current_index = view_model_->GetIndexOfView(drag_view_); 1006 DCHECK_NE(-1, current_index); 1007 std::string dragged_app_id = 1008 delegate_->GetAppIDForShelfID(model_->items()[current_index].id); 1009 1010 gfx::Point screen_location = event.root_location(); 1011 ::wm::ConvertPointToScreen(GetWidget()->GetNativeWindow()->GetRootWindow(), 1012 &screen_location); 1013 1014 // To avoid ugly forwards and backwards flipping we use different constants 1015 // for ripping off / re-inserting the items. 1016 if (dragged_off_shelf_) { 1017 // If the shelf/overflow bubble bounds contains |screen_location| we insert 1018 // the item back into the shelf. 1019 if (GetBoundsForDragInsertInScreen().Contains(screen_location)) { 1020 if (dragged_off_from_overflow_to_shelf_) { 1021 // During the dragging an item from Shelf to Overflow, it can enter here 1022 // directly because both are located very closly. 1023 main_shelf_->EndDrag(true); 1024 // Stops the animation of |drag_view_| and sets its bounds explicitly 1025 // becase ContinueDrag() stops its animation. Without this, unexpected 1026 // bounds will be set. 1027 bounds_animator_->StopAnimatingView(drag_view_); 1028 int drag_view_index = view_model_->GetIndexOfView(drag_view_); 1029 drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index)); 1030 dragged_off_from_overflow_to_shelf_ = false; 1031 } 1032 // Destroy our proxy view item. 1033 DestroyDragIconProxy(); 1034 // Re-insert the item and return simply false since the caller will handle 1035 // the move as in any normal case. 1036 dragged_off_shelf_ = false; 1037 drag_view_->layer()->SetOpacity(1.0f); 1038 // The size of Overflow bubble should be updated immediately when an item 1039 // is re-inserted. 1040 if (is_overflow_mode()) 1041 PreferredSizeChanged(); 1042 return false; 1043 } else if (is_overflow_mode() && 1044 main_shelf_->GetBoundsForDragInsertInScreen().Contains( 1045 screen_location)) { 1046 if (!dragged_off_from_overflow_to_shelf_) { 1047 dragged_off_from_overflow_to_shelf_ = true; 1048 drag_image_->SetOpacity(1.0f); 1049 main_shelf_->StartDrag(dragged_app_id, screen_location); 1050 } else { 1051 main_shelf_->Drag(screen_location); 1052 } 1053 } else if (dragged_off_from_overflow_to_shelf_) { 1054 // Makes the |drag_image_| partially disappear again. 1055 dragged_off_from_overflow_to_shelf_ = false; 1056 drag_image_->SetOpacity(kDraggedImageOpacity); 1057 main_shelf_->EndDrag(true); 1058 bounds_animator_->StopAnimatingView(drag_view_); 1059 int drag_view_index = view_model_->GetIndexOfView(drag_view_); 1060 drag_view_->SetBoundsRect(view_model_->ideal_bounds(drag_view_index)); 1061 } 1062 // Move our proxy view item. 1063 UpdateDragIconProxy(screen_location); 1064 return true; 1065 } 1066 // Check if we are too far away from the shelf to enter the ripped off state. 1067 // Determine the distance to the shelf. 1068 int delta = CalculateShelfDistance(screen_location); 1069 if (delta > kRipOffDistance) { 1070 // Create a proxy view item which can be moved anywhere. 1071 ShelfButton* button = static_cast<ShelfButton*>(drag_view_); 1072 CreateDragIconProxy(event.root_location(), 1073 button->GetImage(), 1074 drag_view_, 1075 gfx::Vector2d(0, 0), 1076 kDragAndDropProxyScale); 1077 drag_view_->layer()->SetOpacity(0.0f); 1078 dragged_off_shelf_ = true; 1079 if (RemovableByRipOff(current_index) == REMOVABLE) { 1080 // Move the item to the front of the first panel item and hide it. 1081 // ShelfItemMoved() callback will handle the |view_model_| update and 1082 // call AnimateToIdealBounds(). 1083 if (current_index != model_->FirstPanelIndex() - 1) { 1084 model_->Move(current_index, model_->FirstPanelIndex() - 1); 1085 StartFadeInLastVisibleItem(); 1086 } else if (is_overflow_mode()) { 1087 // Overflow bubble should be shrunk when an item is ripped off. 1088 PreferredSizeChanged(); 1089 } 1090 // Make the item partially disappear to show that it will get removed if 1091 // dropped. 1092 drag_image_->SetOpacity(kDraggedImageOpacity); 1093 } 1094 return true; 1095 } 1096 return false; 1097 } 1098 1099 void ShelfView::FinalizeRipOffDrag(bool cancel) { 1100 if (!dragged_off_shelf_) 1101 return; 1102 // Make sure we do not come in here again. 1103 dragged_off_shelf_ = false; 1104 1105 // Coming here we should always have a |drag_view_|. 1106 DCHECK(drag_view_); 1107 int current_index = view_model_->GetIndexOfView(drag_view_); 1108 // If the view isn't part of the model anymore (|current_index| == -1), a sync 1109 // operation must have removed it. In that case we shouldn't change the model 1110 // and only delete the proxy image. 1111 if (current_index == -1) { 1112 DestroyDragIconProxy(); 1113 return; 1114 } 1115 1116 // Set to true when the animation should snap back to where it was before. 1117 bool snap_back = false; 1118 // Items which cannot be dragged off will be handled as a cancel. 1119 if (!cancel) { 1120 if (dragged_off_from_overflow_to_shelf_) { 1121 dragged_off_from_overflow_to_shelf_ = false; 1122 main_shelf_->EndDrag(false); 1123 drag_view_->layer()->SetOpacity(1.0f); 1124 } else if (RemovableByRipOff(current_index) != REMOVABLE) { 1125 // Make sure we do not try to remove un-removable items like items which 1126 // were not pinned or have to be always there. 1127 cancel = true; 1128 snap_back = true; 1129 } else { 1130 // Make sure the item stays invisible upon removal. 1131 drag_view_->SetVisible(false); 1132 std::string app_id = 1133 delegate_->GetAppIDForShelfID(model_->items()[current_index].id); 1134 delegate_->UnpinAppWithID(app_id); 1135 } 1136 } 1137 if (cancel || snap_back) { 1138 if (dragged_off_from_overflow_to_shelf_) { 1139 dragged_off_from_overflow_to_shelf_ = false; 1140 // Main shelf handles revert of dragged item. 1141 main_shelf_->EndDrag(true); 1142 drag_view_->layer()->SetOpacity(1.0f); 1143 } else if (!cancelling_drag_model_changed_) { 1144 // Only do something if the change did not come through a model change. 1145 gfx::Rect drag_bounds = drag_image_->GetBoundsInScreen(); 1146 gfx::Point relative_to = GetBoundsInScreen().origin(); 1147 gfx::Rect target( 1148 gfx::PointAtOffsetFromOrigin(drag_bounds.origin()- relative_to), 1149 drag_bounds.size()); 1150 drag_view_->SetBoundsRect(target); 1151 // Hide the status from the active item since we snap it back now. Upon 1152 // animation end the flag gets cleared if |snap_back_from_rip_off_view_| 1153 // is set. 1154 snap_back_from_rip_off_view_ = drag_view_; 1155 ShelfButton* button = static_cast<ShelfButton*>(drag_view_); 1156 button->AddState(ShelfButton::STATE_HIDDEN); 1157 // When a canceling drag model is happening, the view model is diverged 1158 // from the menu model and movements / animations should not be done. 1159 model_->Move(current_index, start_drag_index_); 1160 AnimateToIdealBounds(); 1161 } 1162 drag_view_->layer()->SetOpacity(1.0f); 1163 } 1164 DestroyDragIconProxy(); 1165 } 1166 1167 ShelfView::RemovableState ShelfView::RemovableByRipOff(int index) const { 1168 DCHECK(index >= 0 && index < model_->item_count()); 1169 ShelfItemType type = model_->items()[index].type; 1170 if (type == TYPE_APP_LIST || type == TYPE_DIALOG || !delegate_->CanPin()) 1171 return NOT_REMOVABLE; 1172 1173 std::string app_id = delegate_->GetAppIDForShelfID(model_->items()[index].id); 1174 // Note: Only pinned app shortcuts can be removed! 1175 return (type == TYPE_APP_SHORTCUT && delegate_->IsAppPinned(app_id)) ? 1176 REMOVABLE : DRAGGABLE; 1177 } 1178 1179 bool ShelfView::SameDragType(ShelfItemType typea, ShelfItemType typeb) const { 1180 switch (typea) { 1181 case TYPE_APP_SHORTCUT: 1182 case TYPE_BROWSER_SHORTCUT: 1183 return (typeb == TYPE_APP_SHORTCUT || typeb == TYPE_BROWSER_SHORTCUT); 1184 case TYPE_APP_LIST: 1185 case TYPE_PLATFORM_APP: 1186 case TYPE_WINDOWED_APP: 1187 case TYPE_APP_PANEL: 1188 case TYPE_DIALOG: 1189 return typeb == typea; 1190 case TYPE_UNDEFINED: 1191 NOTREACHED() << "ShelfItemType must be set."; 1192 return false; 1193 } 1194 NOTREACHED(); 1195 return false; 1196 } 1197 1198 std::pair<int, int> ShelfView::GetDragRange(int index) { 1199 int min_index = -1; 1200 int max_index = -1; 1201 ShelfItemType type = model_->items()[index].type; 1202 for (int i = 0; i < model_->item_count(); ++i) { 1203 if (SameDragType(model_->items()[i].type, type)) { 1204 if (min_index == -1) 1205 min_index = i; 1206 max_index = i; 1207 } 1208 } 1209 return std::pair<int, int>(min_index, max_index); 1210 } 1211 1212 void ShelfView::ConfigureChildView(views::View* view) { 1213 view->SetPaintToLayer(true); 1214 view->layer()->SetFillsBoundsOpaquely(false); 1215 } 1216 1217 void ShelfView::ToggleOverflowBubble() { 1218 if (IsShowingOverflowBubble()) { 1219 overflow_bubble_->Hide(); 1220 return; 1221 } 1222 1223 if (!overflow_bubble_) 1224 overflow_bubble_.reset(new OverflowBubble()); 1225 1226 ShelfView* overflow_view = 1227 new ShelfView(model_, delegate_, layout_manager_); 1228 overflow_view->overflow_mode_ = true; 1229 overflow_view->Init(); 1230 overflow_view->set_owner_overflow_bubble(overflow_bubble_.get()); 1231 overflow_view->OnShelfAlignmentChanged(); 1232 overflow_view->main_shelf_ = this; 1233 UpdateOverflowRange(overflow_view); 1234 1235 overflow_bubble_->Show(overflow_button_, overflow_view); 1236 1237 Shell::GetInstance()->UpdateShelfVisibility(); 1238 } 1239 1240 void ShelfView::OnFadeOutAnimationEnded() { 1241 AnimateToIdealBounds(); 1242 StartFadeInLastVisibleItem(); 1243 } 1244 1245 void ShelfView::StartFadeInLastVisibleItem() { 1246 // If overflow button is visible and there is a valid new last item, fading 1247 // the new last item in after sliding animation is finished. 1248 if (overflow_button_->visible() && last_visible_index_ >= 0) { 1249 views::View* last_visible_view = view_model_->view_at(last_visible_index_); 1250 last_visible_view->layer()->SetOpacity(0); 1251 bounds_animator_->SetAnimationDelegate( 1252 last_visible_view, 1253 scoped_ptr<gfx::AnimationDelegate>( 1254 new StartFadeAnimationDelegate(this, last_visible_view))); 1255 } 1256 } 1257 1258 void ShelfView::UpdateOverflowRange(ShelfView* overflow_view) const { 1259 const int first_overflow_index = last_visible_index_ + 1; 1260 const int last_overflow_index = last_hidden_index_; 1261 DCHECK_LE(first_overflow_index, last_overflow_index); 1262 DCHECK_LT(last_overflow_index, view_model_->view_size()); 1263 1264 overflow_view->first_visible_index_ = first_overflow_index; 1265 overflow_view->last_visible_index_ = last_overflow_index; 1266 } 1267 1268 bool ShelfView::ShouldHideTooltip(const gfx::Point& cursor_location) { 1269 gfx::Rect active_bounds; 1270 1271 for (int i = 0; i < child_count(); ++i) { 1272 views::View* child = child_at(i); 1273 if (child == overflow_button_) 1274 continue; 1275 if (!ShouldShowTooltipForView(child)) 1276 continue; 1277 1278 gfx::Rect child_bounds = child->GetMirroredBounds(); 1279 active_bounds.Union(child_bounds); 1280 } 1281 1282 return !active_bounds.Contains(cursor_location); 1283 } 1284 1285 gfx::Rect ShelfView::GetVisibleItemsBoundsInScreen() { 1286 gfx::Size preferred_size = GetPreferredSize(); 1287 gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0); 1288 ConvertPointToScreen(this, &origin); 1289 return gfx::Rect(origin, preferred_size); 1290 } 1291 1292 gfx::Rect ShelfView::GetBoundsForDragInsertInScreen() { 1293 gfx::Size preferred_size; 1294 if (is_overflow_mode()) { 1295 DCHECK(owner_overflow_bubble_); 1296 gfx::Rect bubble_bounds = 1297 owner_overflow_bubble_->bubble_view()->GetBubbleBounds(); 1298 preferred_size = bubble_bounds.size(); 1299 } else { 1300 const int last_button_index = view_model_->view_size() - 1; 1301 gfx::Rect last_button_bounds = 1302 view_model_->view_at(last_button_index)->bounds(); 1303 if (overflow_button_->visible() && 1304 model_->GetItemIndexForType(TYPE_APP_PANEL) == -1) { 1305 // When overflow button is visible and shelf has no panel items, 1306 // last_button_bounds should be overflow button's bounds. 1307 last_button_bounds = overflow_button_->bounds(); 1308 } 1309 1310 if (layout_manager_->IsHorizontalAlignment()) { 1311 preferred_size = gfx::Size(last_button_bounds.right() + leading_inset_, 1312 kShelfSize); 1313 } else { 1314 preferred_size = gfx::Size(kShelfSize, 1315 last_button_bounds.bottom() + leading_inset_); 1316 } 1317 } 1318 gfx::Point origin(GetMirroredXWithWidthInView(0, preferred_size.width()), 0); 1319 1320 // In overflow mode, we should use OverflowBubbleView as a source for 1321 // converting |origin| to screen coordinates. When a scroll operation is 1322 // occurred in OverflowBubble, the bounds of ShelfView in OverflowBubble can 1323 // be changed. 1324 if (is_overflow_mode()) 1325 ConvertPointToScreen(owner_overflow_bubble_->bubble_view(), &origin); 1326 else 1327 ConvertPointToScreen(this, &origin); 1328 1329 return gfx::Rect(origin, preferred_size); 1330 } 1331 1332 int ShelfView::CancelDrag(int modified_index) { 1333 FinalizeRipOffDrag(true); 1334 if (!drag_view_) 1335 return modified_index; 1336 bool was_dragging = dragging(); 1337 int drag_view_index = view_model_->GetIndexOfView(drag_view_); 1338 drag_pointer_ = NONE; 1339 drag_view_ = NULL; 1340 if (drag_view_index == modified_index) { 1341 // The view that was being dragged is being modified. Don't do anything. 1342 return modified_index; 1343 } 1344 if (!was_dragging) 1345 return modified_index; 1346 1347 // Restore previous position, tracking the position of the modified view. 1348 bool at_end = modified_index == view_model_->view_size(); 1349 views::View* modified_view = 1350 (modified_index >= 0 && !at_end) ? 1351 view_model_->view_at(modified_index) : NULL; 1352 model_->Move(drag_view_index, start_drag_index_); 1353 1354 // If the modified view will be at the end of the list, return the new end of 1355 // the list. 1356 if (at_end) 1357 return view_model_->view_size(); 1358 return modified_view ? view_model_->GetIndexOfView(modified_view) : -1; 1359 } 1360 1361 gfx::Size ShelfView::GetPreferredSize() const { 1362 IdealBounds ideal_bounds; 1363 CalculateIdealBounds(&ideal_bounds); 1364 1365 int last_button_index = is_overflow_mode() ? 1366 last_visible_index_ : view_model_->view_size() - 1; 1367 1368 // When an item is dragged off from the overflow bubble, it is moved to last 1369 // position and and changed to invisible. Overflow bubble size should be 1370 // shrunk to fit only for visible items. 1371 // If |dragged_off_from_overflow_to_shelf_| is set, there will be no invisible 1372 // items in the shelf. 1373 if (is_overflow_mode() && 1374 dragged_off_shelf_ && 1375 !dragged_off_from_overflow_to_shelf_ && 1376 RemovableByRipOff(view_model_->GetIndexOfView(drag_view_)) == REMOVABLE) 1377 last_button_index--; 1378 1379 const gfx::Rect last_button_bounds = 1380 last_button_index >= first_visible_index_ ? 1381 view_model_->ideal_bounds(last_button_index) : 1382 gfx::Rect(gfx::Size(kShelfSize, kShelfSize)); 1383 1384 if (layout_manager_->IsHorizontalAlignment()) { 1385 return gfx::Size(last_button_bounds.right() + leading_inset_, kShelfSize); 1386 } 1387 1388 return gfx::Size(kShelfSize, 1389 last_button_bounds.bottom() + leading_inset_); 1390 } 1391 1392 void ShelfView::OnBoundsChanged(const gfx::Rect& previous_bounds) { 1393 // This bounds change is produced by the shelf movement and all content has 1394 // to follow. Using an animation at that time would produce a time lag since 1395 // the animation of the BoundsAnimator has itself a delay before it arrives 1396 // at the required location. As such we tell the animator to go there 1397 // immediately. 1398 BoundsAnimatorDisabler disabler(bounds_animator_.get()); 1399 LayoutToIdealBounds(); 1400 FOR_EACH_OBSERVER(ShelfIconObserver, observers_, 1401 OnShelfIconPositionsChanged()); 1402 1403 if (IsShowingOverflowBubble()) 1404 overflow_bubble_->Hide(); 1405 } 1406 1407 views::FocusTraversable* ShelfView::GetPaneFocusTraversable() { 1408 return this; 1409 } 1410 1411 void ShelfView::GetAccessibleState(ui::AXViewState* state) { 1412 state->role = ui::AX_ROLE_TOOLBAR; 1413 state->name = l10n_util::GetStringUTF16(IDS_ASH_SHELF_ACCESSIBLE_NAME); 1414 } 1415 1416 void ShelfView::OnGestureEvent(ui::GestureEvent* event) { 1417 if (gesture_handler_.ProcessGestureEvent(*event)) 1418 event->StopPropagation(); 1419 } 1420 1421 void ShelfView::ShelfItemAdded(int model_index) { 1422 { 1423 base::AutoReset<bool> cancelling_drag( 1424 &cancelling_drag_model_changed_, true); 1425 model_index = CancelDrag(model_index); 1426 } 1427 views::View* view = CreateViewForItem(model_->items()[model_index]); 1428 AddChildView(view); 1429 // Hide the view, it'll be made visible when the animation is done. Using 1430 // opacity 0 here to avoid messing with CalculateIdealBounds which touches 1431 // the view's visibility. 1432 view->layer()->SetOpacity(0); 1433 view_model_->Add(view, model_index); 1434 1435 // Give the button its ideal bounds. That way if we end up animating the 1436 // button before this animation completes it doesn't appear at some random 1437 // spot (because it was in the middle of animating from 0,0 0x0 to its 1438 // target). 1439 IdealBounds ideal_bounds; 1440 CalculateIdealBounds(&ideal_bounds); 1441 view->SetBoundsRect(view_model_->ideal_bounds(model_index)); 1442 1443 // The first animation moves all the views to their target position. |view| 1444 // is hidden, so it visually appears as though we are providing space for 1445 // it. When done we'll fade the view in. 1446 AnimateToIdealBounds(); 1447 if (model_index <= last_visible_index_ || 1448 model_index >= model_->FirstPanelIndex()) { 1449 bounds_animator_->SetAnimationDelegate( 1450 view, 1451 scoped_ptr<gfx::AnimationDelegate>( 1452 new StartFadeAnimationDelegate(this, view))); 1453 } else { 1454 // Undo the hiding if animation does not run. 1455 view->layer()->SetOpacity(1.0f); 1456 } 1457 } 1458 1459 void ShelfView::ShelfItemRemoved(int model_index, ShelfID id) { 1460 if (id == context_menu_id_) 1461 launcher_menu_runner_.reset(); 1462 { 1463 base::AutoReset<bool> cancelling_drag( 1464 &cancelling_drag_model_changed_, true); 1465 model_index = CancelDrag(model_index); 1466 } 1467 views::View* view = view_model_->view_at(model_index); 1468 view_model_->Remove(model_index); 1469 1470 // When the overflow bubble is visible, the overflow range needs to be set 1471 // before CalculateIdealBounds() gets called. Otherwise CalculateIdealBounds() 1472 // could trigger a ShelfItemChanged() by hiding the overflow bubble and 1473 // since the overflow bubble is not yet synced with the ShelfModel this 1474 // could cause a crash. 1475 if (overflow_bubble_ && overflow_bubble_->IsShowing()) { 1476 last_hidden_index_ = std::min(last_hidden_index_, 1477 view_model_->view_size() - 1); 1478 UpdateOverflowRange(overflow_bubble_->shelf_view()); 1479 } 1480 1481 if (view->visible()) { 1482 // The first animation fades out the view. When done we'll animate the rest 1483 // of the views to their target location. 1484 bounds_animator_->AnimateViewTo(view, view->bounds()); 1485 bounds_animator_->SetAnimationDelegate( 1486 view, 1487 scoped_ptr<gfx::AnimationDelegate>( 1488 new FadeOutAnimationDelegate(this, view))); 1489 } else { 1490 // We don't need to show a fade out animation for invisible |view|. When an 1491 // item is ripped out from the shelf, its |view| is already invisible. 1492 AnimateToIdealBounds(); 1493 } 1494 1495 // Close the tooltip because it isn't needed any longer and its anchor view 1496 // will be deleted soon. 1497 if (tooltip_->GetCurrentAnchorView() == view) 1498 tooltip_->Close(); 1499 } 1500 1501 void ShelfView::ShelfItemChanged(int model_index, const ShelfItem& old_item) { 1502 const ShelfItem& item(model_->items()[model_index]); 1503 if (old_item.type != item.type) { 1504 // Type changed, swap the views. 1505 model_index = CancelDrag(model_index); 1506 scoped_ptr<views::View> old_view(view_model_->view_at(model_index)); 1507 bounds_animator_->StopAnimatingView(old_view.get()); 1508 // Removing and re-inserting a view in our view model will strip the ideal 1509 // bounds from the item. To avoid recalculation of everything the bounds 1510 // get remembered and restored after the insertion to the previous value. 1511 gfx::Rect old_ideal_bounds = view_model_->ideal_bounds(model_index); 1512 view_model_->Remove(model_index); 1513 views::View* new_view = CreateViewForItem(item); 1514 AddChildView(new_view); 1515 view_model_->Add(new_view, model_index); 1516 view_model_->set_ideal_bounds(model_index, old_ideal_bounds); 1517 new_view->SetBoundsRect(old_view->bounds()); 1518 return; 1519 } 1520 1521 views::View* view = view_model_->view_at(model_index); 1522 switch (item.type) { 1523 case TYPE_BROWSER_SHORTCUT: 1524 // Fallthrough for the new Shelf since it needs to show the activation 1525 // change as well. 1526 case TYPE_APP_SHORTCUT: 1527 case TYPE_WINDOWED_APP: 1528 case TYPE_PLATFORM_APP: 1529 case TYPE_DIALOG: 1530 case TYPE_APP_PANEL: { 1531 ShelfButton* button = static_cast<ShelfButton*>(view); 1532 ReflectItemStatus(item, button); 1533 // The browser shortcut is currently not a "real" item and as such the 1534 // the image is bogous as well. We therefore keep the image as is for it. 1535 if (item.type != TYPE_BROWSER_SHORTCUT) 1536 button->SetImage(item.image); 1537 button->SchedulePaint(); 1538 break; 1539 } 1540 1541 default: 1542 break; 1543 } 1544 } 1545 1546 void ShelfView::ShelfItemMoved(int start_index, int target_index) { 1547 view_model_->Move(start_index, target_index); 1548 // When cancelling a drag due to a shelf item being added, the currently 1549 // dragged item is moved back to its initial position. AnimateToIdealBounds 1550 // will be called again when the new item is added to the |view_model_| but 1551 // at this time the |view_model_| is inconsistent with the |model_|. 1552 if (!cancelling_drag_model_changed_) 1553 AnimateToIdealBounds(); 1554 } 1555 1556 void ShelfView::ShelfStatusChanged() { 1557 // Nothing to do here. 1558 } 1559 1560 void ShelfView::PointerPressedOnButton(views::View* view, 1561 Pointer pointer, 1562 const ui::LocatedEvent& event) { 1563 if (drag_view_) 1564 return; 1565 1566 int index = view_model_->GetIndexOfView(view); 1567 if (index == -1) 1568 return; 1569 1570 ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( 1571 model_->items()[index].id); 1572 if (view_model_->view_size() <= 1 || !item_delegate->IsDraggable()) 1573 return; // View is being deleted or not draggable, ignore request. 1574 1575 drag_view_ = view; 1576 drag_origin_ = gfx::Point(event.x(), event.y()); 1577 UMA_HISTOGRAM_ENUMERATION("Ash.ShelfAlignmentUsage", 1578 layout_manager_->SelectValueForShelfAlignment( 1579 SHELF_ALIGNMENT_UMA_ENUM_VALUE_BOTTOM, 1580 SHELF_ALIGNMENT_UMA_ENUM_VALUE_LEFT, 1581 SHELF_ALIGNMENT_UMA_ENUM_VALUE_RIGHT, 1582 -1), 1583 SHELF_ALIGNMENT_UMA_ENUM_VALUE_COUNT); 1584 } 1585 1586 void ShelfView::PointerDraggedOnButton(views::View* view, 1587 Pointer pointer, 1588 const ui::LocatedEvent& event) { 1589 // To prepare all drag types (moving an item in the shelf and dragging off), 1590 // we should check the x-axis and y-axis offset. 1591 if (!dragging() && drag_view_ && 1592 ((std::abs(event.x() - drag_origin_.x()) >= kMinimumDragDistance) || 1593 (std::abs(event.y() - drag_origin_.y()) >= kMinimumDragDistance))) { 1594 PrepareForDrag(pointer, event); 1595 } 1596 if (drag_pointer_ == pointer) 1597 ContinueDrag(event); 1598 } 1599 1600 void ShelfView::PointerReleasedOnButton(views::View* view, 1601 Pointer pointer, 1602 bool canceled) { 1603 if (canceled) { 1604 CancelDrag(-1); 1605 } else if (drag_pointer_ == pointer) { 1606 FinalizeRipOffDrag(false); 1607 drag_pointer_ = NONE; 1608 AnimateToIdealBounds(); 1609 } 1610 // If the drag pointer is NONE, no drag operation is going on and the 1611 // drag_view can be released. 1612 if (drag_pointer_ == NONE) 1613 drag_view_ = NULL; 1614 } 1615 1616 void ShelfView::MouseMovedOverButton(views::View* view) { 1617 if (!ShouldShowTooltipForView(view)) 1618 return; 1619 1620 if (!tooltip_->IsVisible()) 1621 tooltip_->ResetTimer(); 1622 } 1623 1624 void ShelfView::MouseEnteredButton(views::View* view) { 1625 if (!ShouldShowTooltipForView(view)) 1626 return; 1627 1628 if (tooltip_->IsVisible()) { 1629 tooltip_->ShowImmediately(view, GetAccessibleName(view)); 1630 } else { 1631 tooltip_->ShowDelayed(view, GetAccessibleName(view)); 1632 } 1633 } 1634 1635 void ShelfView::MouseExitedButton(views::View* view) { 1636 if (!tooltip_->IsVisible()) 1637 tooltip_->StopTimer(); 1638 } 1639 1640 base::string16 ShelfView::GetAccessibleName(const views::View* view) { 1641 int view_index = view_model_->GetIndexOfView(view); 1642 // May be -1 while in the process of animating closed. 1643 if (view_index == -1) 1644 return base::string16(); 1645 1646 ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( 1647 model_->items()[view_index].id); 1648 return item_delegate->GetTitle(); 1649 } 1650 1651 void ShelfView::ButtonPressed(views::Button* sender, const ui::Event& event) { 1652 // Do not handle mouse release during drag. 1653 if (dragging()) 1654 return; 1655 1656 if (sender == overflow_button_) { 1657 ToggleOverflowBubble(); 1658 return; 1659 } 1660 1661 int view_index = view_model_->GetIndexOfView(sender); 1662 // May be -1 while in the process of animating closed. 1663 if (view_index == -1) 1664 return; 1665 1666 // If the previous menu was closed by the same event as this one, we ignore 1667 // the call. 1668 if (!IsUsableEvent(event)) 1669 return; 1670 1671 // Don't activate the item twice on double-click. Otherwise the window starts 1672 // animating open due to the first click, then immediately minimizes due to 1673 // the second click. The user most likely intended to open or minimize the 1674 // item once, not do both. 1675 if (event.flags() & ui::EF_IS_DOUBLE_CLICK) 1676 return; 1677 1678 { 1679 ScopedTargetRootWindow scoped_target( 1680 sender->GetWidget()->GetNativeView()->GetRootWindow()); 1681 // Slow down activation animations if shift key is pressed. 1682 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slowing_animations; 1683 if (event.IsShiftDown()) { 1684 slowing_animations.reset(new ui::ScopedAnimationDurationScaleMode( 1685 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION)); 1686 } 1687 1688 // Collect usage statistics before we decide what to do with the click. 1689 switch (model_->items()[view_index].type) { 1690 case TYPE_APP_SHORTCUT: 1691 case TYPE_WINDOWED_APP: 1692 case TYPE_PLATFORM_APP: 1693 case TYPE_BROWSER_SHORTCUT: 1694 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 1695 UMA_LAUNCHER_CLICK_ON_APP); 1696 break; 1697 1698 case TYPE_APP_LIST: 1699 Shell::GetInstance()->metrics()->RecordUserMetricsAction( 1700 UMA_LAUNCHER_CLICK_ON_APPLIST_BUTTON); 1701 break; 1702 1703 case TYPE_APP_PANEL: 1704 case TYPE_DIALOG: 1705 break; 1706 1707 case TYPE_UNDEFINED: 1708 NOTREACHED() << "ShelfItemType must be set."; 1709 break; 1710 } 1711 1712 ShelfItemDelegate* item_delegate = 1713 item_manager_->GetShelfItemDelegate(model_->items()[view_index].id); 1714 if (!item_delegate->ItemSelected(event)) 1715 ShowListMenuForView(model_->items()[view_index], sender, event); 1716 } 1717 } 1718 1719 bool ShelfView::ShowListMenuForView(const ShelfItem& item, 1720 views::View* source, 1721 const ui::Event& event) { 1722 ShelfItemDelegate* item_delegate = 1723 item_manager_->GetShelfItemDelegate(item.id); 1724 scoped_ptr<ui::MenuModel> list_menu_model( 1725 item_delegate->CreateApplicationMenu(event.flags())); 1726 1727 // Make sure we have a menu and it has at least two items in addition to the 1728 // application title and the 3 spacing separators. 1729 if (!list_menu_model.get() || list_menu_model->GetItemCount() <= 5) 1730 return false; 1731 1732 ShowMenu(list_menu_model.get(), 1733 source, 1734 gfx::Point(), 1735 false, 1736 ui::GetMenuSourceTypeForEvent(event)); 1737 return true; 1738 } 1739 1740 void ShelfView::ShowContextMenuForView(views::View* source, 1741 const gfx::Point& point, 1742 ui::MenuSourceType source_type) { 1743 int view_index = view_model_->GetIndexOfView(source); 1744 if (view_index == -1) { 1745 Shell::GetInstance()->ShowContextMenu(point, source_type); 1746 return; 1747 } 1748 1749 ShelfItemDelegate* item_delegate = item_manager_->GetShelfItemDelegate( 1750 model_->items()[view_index].id); 1751 context_menu_model_.reset(item_delegate->CreateContextMenu( 1752 source->GetWidget()->GetNativeView()->GetRootWindow())); 1753 if (!context_menu_model_) 1754 return; 1755 1756 base::AutoReset<ShelfID> reseter( 1757 &context_menu_id_, 1758 view_index == -1 ? 0 : model_->items()[view_index].id); 1759 1760 ShowMenu(context_menu_model_.get(), 1761 source, 1762 point, 1763 true, 1764 source_type); 1765 } 1766 1767 void ShelfView::ShowMenu(ui::MenuModel* menu_model, 1768 views::View* source, 1769 const gfx::Point& click_point, 1770 bool context_menu, 1771 ui::MenuSourceType source_type) { 1772 closing_event_time_ = base::TimeDelta(); 1773 launcher_menu_runner_.reset(new views::MenuRunner( 1774 menu_model, context_menu ? views::MenuRunner::CONTEXT_MENU : 0)); 1775 1776 ScopedTargetRootWindow scoped_target( 1777 source->GetWidget()->GetNativeView()->GetRootWindow()); 1778 1779 // Determine the menu alignment dependent on the shelf. 1780 views::MenuAnchorPosition menu_alignment = views::MENU_ANCHOR_TOPLEFT; 1781 gfx::Rect anchor_point = gfx::Rect(click_point, gfx::Size()); 1782 1783 ShelfWidget* shelf = RootWindowController::ForShelf( 1784 GetWidget()->GetNativeView())->shelf(); 1785 if (!context_menu) { 1786 // Application lists use a bubble. 1787 ShelfAlignment align = shelf->GetAlignment(); 1788 anchor_point = source->GetBoundsInScreen(); 1789 1790 // It is possible to invoke the menu while it is sliding into view. To cover 1791 // that case, the screen coordinates are offsetted by the animation delta. 1792 gfx::Vector2d offset = 1793 source->GetWidget()->GetNativeWindow()->bounds().origin() - 1794 source->GetWidget()->GetNativeWindow()->GetTargetBounds().origin(); 1795 anchor_point.set_x(anchor_point.x() - offset.x()); 1796 anchor_point.set_y(anchor_point.y() - offset.y()); 1797 1798 // Shelf items can have an asymmetrical border for spacing reasons. 1799 // Adjust anchor location for this. 1800 if (source->border()) 1801 anchor_point.Inset(source->border()->GetInsets()); 1802 1803 switch (align) { 1804 case SHELF_ALIGNMENT_BOTTOM: 1805 menu_alignment = views::MENU_ANCHOR_BUBBLE_ABOVE; 1806 break; 1807 case SHELF_ALIGNMENT_LEFT: 1808 menu_alignment = views::MENU_ANCHOR_BUBBLE_RIGHT; 1809 break; 1810 case SHELF_ALIGNMENT_RIGHT: 1811 menu_alignment = views::MENU_ANCHOR_BUBBLE_LEFT; 1812 break; 1813 case SHELF_ALIGNMENT_TOP: 1814 menu_alignment = views::MENU_ANCHOR_BUBBLE_BELOW; 1815 break; 1816 } 1817 } 1818 // If this gets deleted while we are in the menu, the shelf will be gone 1819 // as well. 1820 bool got_deleted = false; 1821 got_deleted_ = &got_deleted; 1822 1823 shelf->ForceUndimming(true); 1824 // NOTE: if you convert to HAS_MNEMONICS be sure and update menu building 1825 // code. 1826 if (launcher_menu_runner_->RunMenuAt(source->GetWidget(), 1827 NULL, 1828 anchor_point, 1829 menu_alignment, 1830 source_type) == 1831 views::MenuRunner::MENU_DELETED) { 1832 if (!got_deleted) { 1833 got_deleted_ = NULL; 1834 shelf->ForceUndimming(false); 1835 } 1836 return; 1837 } 1838 got_deleted_ = NULL; 1839 shelf->ForceUndimming(false); 1840 1841 // If it is a context menu and we are showing overflow bubble 1842 // we want to hide overflow bubble. 1843 if (owner_overflow_bubble_) 1844 owner_overflow_bubble_->HideBubbleAndRefreshButton(); 1845 1846 // Unpinning an item will reset the |launcher_menu_runner_| before coming 1847 // here. 1848 if (launcher_menu_runner_) 1849 closing_event_time_ = launcher_menu_runner_->closing_event_time(); 1850 Shell::GetInstance()->UpdateShelfVisibility(); 1851 } 1852 1853 void ShelfView::OnBoundsAnimatorProgressed(views::BoundsAnimator* animator) { 1854 FOR_EACH_OBSERVER(ShelfIconObserver, observers_, 1855 OnShelfIconPositionsChanged()); 1856 PreferredSizeChanged(); 1857 } 1858 1859 void ShelfView::OnBoundsAnimatorDone(views::BoundsAnimator* animator) { 1860 if (snap_back_from_rip_off_view_ && animator == bounds_animator_) { 1861 if (!animator->IsAnimating(snap_back_from_rip_off_view_)) { 1862 // Coming here the animation of the ShelfButton is finished and the 1863 // previously hidden status can be shown again. Since the button itself 1864 // might have gone away or changed locations we check that the button 1865 // is still in the shelf and show its status again. 1866 for (int index = 0; index < view_model_->view_size(); index++) { 1867 views::View* view = view_model_->view_at(index); 1868 if (view == snap_back_from_rip_off_view_) { 1869 ShelfButton* button = static_cast<ShelfButton*>(view); 1870 button->ClearState(ShelfButton::STATE_HIDDEN); 1871 break; 1872 } 1873 } 1874 snap_back_from_rip_off_view_ = NULL; 1875 } 1876 } 1877 } 1878 1879 bool ShelfView::IsUsableEvent(const ui::Event& event) { 1880 if (closing_event_time_ == base::TimeDelta()) 1881 return true; 1882 1883 base::TimeDelta delta = 1884 base::TimeDelta(event.time_stamp() - closing_event_time_); 1885 closing_event_time_ = base::TimeDelta(); 1886 // TODO(skuhne): This time seems excessive, but it appears that the reposting 1887 // takes that long. Need to come up with a better way of doing this. 1888 return (delta.InMilliseconds() < 0 || delta.InMilliseconds() > 130); 1889 } 1890 1891 const ShelfItem* ShelfView::ShelfItemForView(const views::View* view) const { 1892 int view_index = view_model_->GetIndexOfView(view); 1893 if (view_index == -1) 1894 return NULL; 1895 return &(model_->items()[view_index]); 1896 } 1897 1898 bool ShelfView::ShouldShowTooltipForView(const views::View* view) const { 1899 if (view == GetAppListButtonView() && 1900 Shell::GetInstance()->GetAppListWindow()) 1901 return false; 1902 const ShelfItem* item = ShelfItemForView(view); 1903 if (!item) 1904 return true; 1905 ShelfItemDelegate* item_delegate = 1906 item_manager_->GetShelfItemDelegate(item->id); 1907 return item_delegate->ShouldShowTooltip(); 1908 } 1909 1910 int ShelfView::CalculateShelfDistance(const gfx::Point& coordinate) const { 1911 ShelfWidget* shelf = RootWindowController::ForShelf( 1912 GetWidget()->GetNativeView())->shelf(); 1913 ShelfAlignment align = shelf->GetAlignment(); 1914 const gfx::Rect bounds = GetBoundsInScreen(); 1915 int distance = 0; 1916 switch (align) { 1917 case SHELF_ALIGNMENT_BOTTOM: 1918 distance = bounds.y() - coordinate.y(); 1919 break; 1920 case SHELF_ALIGNMENT_LEFT: 1921 distance = coordinate.x() - bounds.right(); 1922 break; 1923 case SHELF_ALIGNMENT_RIGHT: 1924 distance = bounds.x() - coordinate.x(); 1925 break; 1926 case SHELF_ALIGNMENT_TOP: 1927 distance = coordinate.y() - bounds.bottom(); 1928 break; 1929 } 1930 return distance > 0 ? distance : 0; 1931 } 1932 1933 } // namespace ash 1934