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/wm/maximize_bubble_controller.h" 6 7 #include "ash/shell.h" 8 #include "ash/shell_delegate.h" 9 #include "ash/shell_window_ids.h" 10 #include "ash/wm/window_animations.h" 11 #include "ash/wm/workspace/frame_maximize_button.h" 12 #include "base/timer/timer.h" 13 #include "grit/ash_resources.h" 14 #include "grit/ash_strings.h" 15 #include "third_party/skia/include/core/SkPath.h" 16 #include "ui/aura/window.h" 17 #include "ui/base/animation/animation.h" 18 #include "ui/base/l10n/l10n_util.h" 19 #include "ui/base/resource/resource_bundle.h" 20 #include "ui/gfx/canvas.h" 21 #include "ui/gfx/path.h" 22 #include "ui/gfx/screen.h" 23 #include "ui/views/bubble/bubble_delegate.h" 24 #include "ui/views/bubble/bubble_frame_view.h" 25 #include "ui/views/controls/button/button.h" 26 #include "ui/views/controls/button/image_button.h" 27 #include "ui/views/controls/label.h" 28 #include "ui/views/layout/box_layout.h" 29 #include "ui/views/mouse_watcher.h" 30 #include "ui/views/widget/widget.h" 31 32 namespace { 33 34 // The spacing between two buttons. 35 const int kLayoutSpacing = -1; 36 37 // The background color. 38 const SkColor kBubbleBackgroundColor = 0xFF141414; 39 40 // The text color within the bubble. 41 const SkColor kBubbleTextColor = SK_ColorWHITE; 42 43 // The line width of the bubble. 44 const int kLineWidth = 1; 45 46 // The spacing for the top and bottom of the info label. 47 const int kLabelSpacing = 4; 48 49 // The pixel dimensions of the arrow. 50 const int kArrowHeight = 10; 51 const int kArrowWidth = 20; 52 53 // The animation offset in y for the bubble when appearing. 54 const int kBubbleAnimationOffsetY = 5; 55 56 class MaximizeBubbleBorder : public views::BubbleBorder { 57 public: 58 MaximizeBubbleBorder(views::View* content_view, views::View* anchor); 59 60 virtual ~MaximizeBubbleBorder() {} 61 62 // Get the mouse active area of the window. 63 void GetMask(gfx::Path* mask); 64 65 // Overridden from views::BubbleBorder to match the design specs. 66 virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to, 67 const gfx::Size& contents_size) const OVERRIDE; 68 69 // Overridden from views::Border. 70 virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE; 71 72 private: 73 // Note: Animations can continue after then main window frame was destroyed. 74 // To avoid this problem, the owning screen metrics get extracted upon 75 // creation. 76 gfx::Size anchor_size_; 77 gfx::Point anchor_screen_origin_; 78 views::View* content_view_; 79 80 DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder); 81 }; 82 83 MaximizeBubbleBorder::MaximizeBubbleBorder(views::View* content_view, 84 views::View* anchor) 85 : views::BubbleBorder(views::BubbleBorder::TOP_RIGHT, 86 views::BubbleBorder::NO_SHADOW, 87 kBubbleBackgroundColor), 88 anchor_size_(anchor->size()), 89 anchor_screen_origin_(0, 0), 90 content_view_(content_view) { 91 views::View::ConvertPointToScreen(anchor, &anchor_screen_origin_); 92 set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); 93 } 94 95 void MaximizeBubbleBorder::GetMask(gfx::Path* mask) { 96 gfx::Insets inset = GetInsets(); 97 // Note: Even though the tip could be added as activatable, it is left out 98 // since it would not change the action behavior in any way plus it makes 99 // more sense to keep the focus on the underlying button for clicks. 100 int left = inset.left() - kLineWidth; 101 int right = inset.left() + content_view_->width() + kLineWidth; 102 int top = inset.top() - kLineWidth; 103 int bottom = inset.top() + content_view_->height() + kLineWidth; 104 mask->moveTo(left, top); 105 mask->lineTo(right, top); 106 mask->lineTo(right, bottom); 107 mask->lineTo(left, bottom); 108 mask->lineTo(left, top); 109 mask->close(); 110 } 111 112 gfx::Rect MaximizeBubbleBorder::GetBounds( 113 const gfx::Rect& position_relative_to, 114 const gfx::Size& contents_size) const { 115 gfx::Size border_size(contents_size); 116 gfx::Insets insets = GetInsets(); 117 border_size.Enlarge(insets.width(), insets.height()); 118 119 // Position the bubble to center the box on the anchor. 120 int x = (-border_size.width() + anchor_size_.width()) / 2; 121 // Position the bubble under the anchor, overlapping the arrow with it. 122 int y = anchor_size_.height() - insets.top(); 123 124 gfx::Point view_origin(x + anchor_screen_origin_.x(), 125 y + anchor_screen_origin_.y()); 126 127 return gfx::Rect(view_origin, border_size); 128 } 129 130 void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { 131 gfx::Insets inset = GetInsets(); 132 133 // Draw the border line around everything. 134 int y = inset.top(); 135 // Top 136 canvas->FillRect(gfx::Rect(inset.left(), 137 y - kLineWidth, 138 content_view_->width(), 139 kLineWidth), 140 kBubbleBackgroundColor); 141 // Bottom 142 canvas->FillRect(gfx::Rect(inset.left(), 143 y + content_view_->height(), 144 content_view_->width(), 145 kLineWidth), 146 kBubbleBackgroundColor); 147 // Left 148 canvas->FillRect(gfx::Rect(inset.left() - kLineWidth, 149 y - kLineWidth, 150 kLineWidth, 151 content_view_->height() + 2 * kLineWidth), 152 kBubbleBackgroundColor); 153 // Right 154 canvas->FillRect(gfx::Rect(inset.left() + content_view_->width(), 155 y - kLineWidth, 156 kLineWidth, 157 content_view_->height() + 2 * kLineWidth), 158 kBubbleBackgroundColor); 159 160 // Draw the arrow afterwards covering the border. 161 SkPath path; 162 path.incReserve(4); 163 // The center of the tip should be in the middle of the button. 164 int tip_x = inset.left() + content_view_->width() / 2; 165 int left_base_x = tip_x - kArrowWidth / 2; 166 int left_base_y = y; 167 int tip_y = left_base_y - kArrowHeight; 168 path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y)); 169 path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y)); 170 path.lineTo(SkIntToScalar(left_base_x + kArrowWidth), 171 SkIntToScalar(left_base_y)); 172 173 SkPaint paint; 174 paint.setStyle(SkPaint::kFill_Style); 175 paint.setColor(kBubbleBackgroundColor); 176 canvas->DrawPath(path, paint); 177 } 178 179 } // namespace 180 181 namespace ash { 182 183 class BubbleContentsButtonRow; 184 class BubbleContentsView; 185 class BubbleDialogButton; 186 187 // The mouse watcher host which makes sure that the bubble does not get closed 188 // while the mouse cursor is over the maximize button or the balloon content. 189 // Note: This object gets destroyed when the MouseWatcher gets destroyed. 190 class BubbleMouseWatcherHost: public views::MouseWatcherHost { 191 public: 192 explicit BubbleMouseWatcherHost(MaximizeBubbleController::Bubble* bubble) 193 : bubble_(bubble) {} 194 virtual ~BubbleMouseWatcherHost() {} 195 196 // Implementation of MouseWatcherHost. 197 virtual bool Contains(const gfx::Point& screen_point, 198 views::MouseWatcherHost::MouseEventType type) OVERRIDE; 199 private: 200 MaximizeBubbleController::Bubble* bubble_; 201 202 DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost); 203 }; 204 205 // The class which creates and manages the bubble menu element. 206 // It creates a 'bubble border' and the content accordingly. 207 // Note: Since the SnapSizer will show animations on top of the maximize button 208 // this menu gets created as a separate window and the SnapSizer will be 209 // created underneath this window. 210 class MaximizeBubbleController::Bubble : public views::BubbleDelegateView, 211 public views::MouseWatcherListener { 212 public: 213 explicit Bubble(MaximizeBubbleController* owner, int appearance_delay_ms_); 214 virtual ~Bubble() {} 215 216 // The window of the menu under which the SnapSizer will get created. 217 aura::Window* GetBubbleWindow(); 218 219 // Overridden from views::BubbleDelegateView. 220 virtual gfx::Rect GetAnchorRect() OVERRIDE; 221 virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE; 222 virtual bool CanActivate() const OVERRIDE { return false; } 223 224 // Overridden from views::WidgetDelegateView. 225 virtual bool WidgetHasHitTestMask() const OVERRIDE; 226 virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE; 227 228 // Implementation of MouseWatcherListener. 229 virtual void MouseMovedOutOfHost() OVERRIDE; 230 231 // Implementation of MouseWatcherHost. 232 virtual bool Contains(const gfx::Point& screen_point, 233 views::MouseWatcherHost::MouseEventType type); 234 235 // Overridden from views::View. 236 virtual gfx::Size GetPreferredSize() OVERRIDE; 237 238 // Overridden from views::Widget::Observer. 239 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE; 240 241 // Called from the controller class to indicate that the menu should get 242 // destroyed. 243 virtual void ControllerRequestsCloseAndDelete(); 244 245 // Called from the owning class to change the menu content to the given 246 // |snap_type| so that the user knows what is selected. 247 void SetSnapType(SnapType snap_type); 248 249 // Get the owning MaximizeBubbleController. This might return NULL in case 250 // of an asynchronous shutdown. 251 MaximizeBubbleController* controller() const { return owner_; } 252 253 // Added for unit test: Retrieve the button for an action. 254 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. 255 views::CustomButton* GetButtonForUnitTest(SnapType state); 256 257 private: 258 // True if the shut down has been initiated. 259 bool shutting_down_; 260 261 // Our owning class. 262 MaximizeBubbleController* owner_; 263 264 // The widget which contains our menu and the bubble border. 265 views::Widget* bubble_widget_; 266 267 // The content accessor of the menu. 268 BubbleContentsView* contents_view_; 269 270 // The bubble border. 271 MaximizeBubbleBorder* bubble_border_; 272 273 // The rectangle before the animation starts. 274 gfx::Rect initial_position_; 275 276 // The mouse watcher which takes care of out of window hover events. 277 scoped_ptr<views::MouseWatcher> mouse_watcher_; 278 279 // The fade delay - if 0 it will show / hide immediately. 280 const int appearance_delay_ms_; 281 282 DISALLOW_COPY_AND_ASSIGN(Bubble); 283 }; 284 285 // A class that creates all buttons and put them into a view. 286 class BubbleContentsButtonRow : public views::View, 287 public views::ButtonListener { 288 public: 289 explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble* bubble); 290 291 virtual ~BubbleContentsButtonRow() {} 292 293 // Overridden from ButtonListener. 294 virtual void ButtonPressed(views::Button* sender, 295 const ui::Event& event) OVERRIDE; 296 // Called from BubbleDialogButton. 297 void ButtonHovered(BubbleDialogButton* sender); 298 299 // Added for unit test: Retrieve the button for an action. 300 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. 301 views::CustomButton* GetButtonForUnitTest(SnapType state); 302 303 MaximizeBubbleController::Bubble* bubble() { return bubble_; } 304 305 private: 306 // Functions to add the left and right maximize / restore buttons. 307 void AddMaximizeLeftButton(); 308 void AddMaximizeRightButton(); 309 void AddMinimizeButton(); 310 311 // The owning object which gets notifications. 312 MaximizeBubbleController::Bubble* bubble_; 313 314 // The created buttons for our menu. 315 BubbleDialogButton* left_button_; 316 BubbleDialogButton* minimize_button_; 317 BubbleDialogButton* right_button_; 318 319 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow); 320 }; 321 322 // A class which creates the content of the bubble: The buttons, and the label. 323 class BubbleContentsView : public views::View { 324 public: 325 explicit BubbleContentsView(MaximizeBubbleController::Bubble* bubble); 326 327 virtual ~BubbleContentsView() {} 328 329 // Set the label content to reflect the currently selected |snap_type|. 330 // This function can be executed through the frame maximize button as well as 331 // through hover operations. 332 void SetSnapType(SnapType snap_type); 333 334 // Added for unit test: Retrieve the button for an action. 335 // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE. 336 views::CustomButton* GetButtonForUnitTest(SnapType state) { 337 return buttons_view_->GetButtonForUnitTest(state); 338 } 339 340 private: 341 // The owning class. 342 MaximizeBubbleController::Bubble* bubble_; 343 344 // The object which owns all the buttons. 345 BubbleContentsButtonRow* buttons_view_; 346 347 // The label object which shows the user the selected action. 348 views::Label* label_view_; 349 350 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView); 351 }; 352 353 // The image button gets overridden to be able to capture mouse hover events. 354 // The constructor also assigns all button states and 355 class BubbleDialogButton : public views::ImageButton { 356 public: 357 explicit BubbleDialogButton( 358 BubbleContentsButtonRow* button_row_listener, 359 int normal_image, 360 int hovered_image, 361 int pressed_image); 362 virtual ~BubbleDialogButton() {} 363 364 // CustomButton overrides: 365 virtual void OnMouseCaptureLost() OVERRIDE; 366 virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE; 367 virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE; 368 virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE; 369 370 private: 371 // The creating class which needs to get notified in case of a hover event. 372 BubbleContentsButtonRow* button_row_; 373 374 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton); 375 }; 376 377 MaximizeBubbleController::Bubble::Bubble( 378 MaximizeBubbleController* owner, 379 int appearance_delay_ms) 380 : views::BubbleDelegateView(owner->frame_maximize_button(), 381 views::BubbleBorder::TOP_RIGHT), 382 shutting_down_(false), 383 owner_(owner), 384 bubble_widget_(NULL), 385 contents_view_(NULL), 386 bubble_border_(NULL), 387 appearance_delay_ms_(appearance_delay_ms) { 388 set_margins(gfx::Insets()); 389 390 // The window needs to be owned by the root so that the SnapSizer does not 391 // cover it upon animation. 392 aura::Window* parent = Shell::GetContainer( 393 Shell::GetActiveRootWindow(), 394 internal::kShellWindowId_ShelfContainer); 395 set_parent_window(parent); 396 397 set_notify_enter_exit_on_child(true); 398 set_adjust_if_offscreen(false); 399 SetPaintToLayer(true); 400 set_color(kBubbleBackgroundColor); 401 set_close_on_deactivate(false); 402 set_background( 403 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); 404 405 SetLayoutManager(new views::BoxLayout( 406 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); 407 408 contents_view_ = new BubbleContentsView(this); 409 AddChildView(contents_view_); 410 411 // Note that the returned widget has an observer which points to our 412 // functions. 413 bubble_widget_ = views::BubbleDelegateView::CreateBubble(this); 414 bubble_widget_->set_focus_on_creation(false); 415 416 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE); 417 bubble_widget_->non_client_view()->frame_view()->set_background(NULL); 418 419 bubble_border_ = new MaximizeBubbleBorder(this, anchor_view()); 420 GetBubbleFrameView()->SetBubbleBorder(bubble_border_); 421 GetBubbleFrameView()->set_background(NULL); 422 423 // Recalculate size with new border. 424 SizeToContents(); 425 426 if (!appearance_delay_ms_) 427 GetWidget()->Show(); 428 else 429 StartFade(true); 430 431 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction( 432 ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE); 433 434 mouse_watcher_.reset(new views::MouseWatcher( 435 new BubbleMouseWatcherHost(this), 436 this)); 437 mouse_watcher_->Start(); 438 } 439 440 bool BubbleMouseWatcherHost::Contains( 441 const gfx::Point& screen_point, 442 views::MouseWatcherHost::MouseEventType type) { 443 return bubble_->Contains(screen_point, type); 444 } 445 446 aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() { 447 return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL; 448 } 449 450 gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() { 451 if (!owner_) 452 return gfx::Rect(); 453 454 gfx::Rect anchor_rect = 455 owner_->frame_maximize_button()->GetBoundsInScreen(); 456 return anchor_rect; 457 } 458 459 void MaximizeBubbleController::Bubble::AnimationProgressed( 460 const ui::Animation* animation) { 461 // First do everything needed for the fade by calling the base function. 462 BubbleDelegateView::AnimationProgressed(animation); 463 // When fading in we are done. 464 if (!shutting_down_) 465 return; 466 // Upon fade out an additional shift is required. 467 int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0); 468 gfx::Rect rect = initial_position_; 469 470 rect.set_y(rect.y() + shift); 471 bubble_widget_->GetNativeWindow()->SetBounds(rect); 472 } 473 474 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const { 475 return bubble_border_ != NULL; 476 } 477 478 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask( 479 gfx::Path* mask) const { 480 DCHECK(mask); 481 DCHECK(bubble_border_); 482 bubble_border_->GetMask(mask); 483 } 484 485 void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() { 486 if (!owner_ || shutting_down_) 487 return; 488 // When we leave the bubble, we might be still be in gesture mode or over 489 // the maximize button. So only close if none of the other cases apply. 490 if (!owner_->frame_maximize_button()->is_snap_enabled()) { 491 gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint(); 492 if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains( 493 screen_location)) { 494 owner_->RequestDestructionThroughOwner(); 495 } 496 } 497 } 498 499 bool MaximizeBubbleController::Bubble::Contains( 500 const gfx::Point& screen_point, 501 views::MouseWatcherHost::MouseEventType type) { 502 if (!owner_ || shutting_down_) 503 return false; 504 bool inside_button = 505 owner_->frame_maximize_button()->GetBoundsInScreen().Contains( 506 screen_point); 507 if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) { 508 SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ? 509 SNAP_RESTORE : SNAP_MAXIMIZE); 510 return true; 511 } 512 // Check if either a gesture is taking place (=> bubble stays no matter what 513 // the mouse does) or the mouse is over the maximize button or the bubble 514 // content. 515 return (owner_->frame_maximize_button()->is_snap_enabled() || 516 inside_button || 517 contents_view_->GetBoundsInScreen().Contains(screen_point)); 518 } 519 520 gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() { 521 return contents_view_->GetPreferredSize(); 522 } 523 524 void MaximizeBubbleController::Bubble::OnWidgetDestroying( 525 views::Widget* widget) { 526 if (bubble_widget_ == widget) { 527 mouse_watcher_->Stop(); 528 529 if (owner_) { 530 // If the bubble destruction was triggered by some other external 531 // influence then ourselves, the owner needs to be informed that the menu 532 // is gone. 533 shutting_down_ = true; 534 owner_->RequestDestructionThroughOwner(); 535 owner_ = NULL; 536 } 537 } 538 BubbleDelegateView::OnWidgetDestroying(widget); 539 } 540 541 void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() { 542 // This only gets called from the owning base class once it is deleted. 543 if (shutting_down_) 544 return; 545 shutting_down_ = true; 546 owner_ = NULL; 547 548 // Close the widget asynchronously after the hide animation is finished. 549 initial_position_ = bubble_widget_->GetNativeWindow()->bounds(); 550 if (!appearance_delay_ms_) 551 bubble_widget_->CloseNow(); 552 else 553 StartFade(false); 554 } 555 556 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) { 557 if (contents_view_) 558 contents_view_->SetSnapType(snap_type); 559 } 560 561 views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest( 562 SnapType state) { 563 return contents_view_->GetButtonForUnitTest(state); 564 } 565 566 BubbleContentsButtonRow::BubbleContentsButtonRow( 567 MaximizeBubbleController::Bubble* bubble) 568 : bubble_(bubble), 569 left_button_(NULL), 570 minimize_button_(NULL), 571 right_button_(NULL) { 572 SetLayoutManager(new views::BoxLayout( 573 views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing)); 574 set_background( 575 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); 576 577 if (base::i18n::IsRTL()) { 578 AddMaximizeRightButton(); 579 AddMinimizeButton(); 580 AddMaximizeLeftButton(); 581 } else { 582 AddMaximizeLeftButton(); 583 AddMinimizeButton(); 584 AddMaximizeRightButton(); 585 } 586 } 587 588 // Overridden from ButtonListener. 589 void BubbleContentsButtonRow::ButtonPressed(views::Button* sender, 590 const ui::Event& event) { 591 // While shutting down, the connection to the owner might already be broken. 592 if (!bubble_->controller()) 593 return; 594 if (sender == left_button_) 595 bubble_->controller()->OnButtonClicked( 596 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ? 597 SNAP_RESTORE : SNAP_LEFT); 598 else if (sender == minimize_button_) 599 bubble_->controller()->OnButtonClicked(SNAP_MINIMIZE); 600 else if (sender == right_button_) 601 bubble_->controller()->OnButtonClicked( 602 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ? 603 SNAP_RESTORE : SNAP_RIGHT); 604 else 605 NOTREACHED() << "Unknown button pressed."; 606 } 607 608 // Called from BubbleDialogButton. 609 void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton* sender) { 610 // While shutting down, the connection to the owner might already be broken. 611 if (!bubble_->controller()) 612 return; 613 if (sender == left_button_) 614 bubble_->controller()->OnButtonHover( 615 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ? 616 SNAP_RESTORE : SNAP_LEFT); 617 else if (sender == minimize_button_) 618 bubble_->controller()->OnButtonHover(SNAP_MINIMIZE); 619 else if (sender == right_button_) 620 bubble_->controller()->OnButtonHover( 621 bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ? 622 SNAP_RESTORE : SNAP_RIGHT); 623 else 624 bubble_->controller()->OnButtonHover(SNAP_NONE); 625 } 626 627 views::CustomButton* BubbleContentsButtonRow::GetButtonForUnitTest( 628 SnapType state) { 629 switch (state) { 630 case SNAP_LEFT: 631 return left_button_; 632 case SNAP_MINIMIZE: 633 return minimize_button_; 634 case SNAP_RIGHT: 635 return right_button_; 636 default: 637 NOTREACHED(); 638 return NULL; 639 } 640 } 641 642 void BubbleContentsButtonRow::AddMaximizeLeftButton() { 643 if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT) { 644 left_button_ = new BubbleDialogButton( 645 this, 646 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE, 647 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H, 648 IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P); 649 } else { 650 left_button_ = new BubbleDialogButton( 651 this, 652 IDR_AURA_WINDOW_POSITION_LEFT, 653 IDR_AURA_WINDOW_POSITION_LEFT_H, 654 IDR_AURA_WINDOW_POSITION_LEFT_P); 655 } 656 } 657 658 void BubbleContentsButtonRow::AddMaximizeRightButton() { 659 if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT) { 660 right_button_ = new BubbleDialogButton( 661 this, 662 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE, 663 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H, 664 IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P); 665 } else { 666 right_button_ = new BubbleDialogButton( 667 this, 668 IDR_AURA_WINDOW_POSITION_RIGHT, 669 IDR_AURA_WINDOW_POSITION_RIGHT_H, 670 IDR_AURA_WINDOW_POSITION_RIGHT_P); 671 } 672 } 673 674 void BubbleContentsButtonRow::AddMinimizeButton() { 675 minimize_button_ = new BubbleDialogButton( 676 this, 677 IDR_AURA_WINDOW_POSITION_MIDDLE, 678 IDR_AURA_WINDOW_POSITION_MIDDLE_H, 679 IDR_AURA_WINDOW_POSITION_MIDDLE_P); 680 } 681 682 BubbleContentsView::BubbleContentsView( 683 MaximizeBubbleController::Bubble* bubble) 684 : bubble_(bubble), 685 buttons_view_(NULL), 686 label_view_(NULL) { 687 SetLayoutManager(new views::BoxLayout( 688 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing)); 689 set_background( 690 views::Background::CreateSolidBackground(kBubbleBackgroundColor)); 691 692 buttons_view_ = new BubbleContentsButtonRow(bubble); 693 AddChildView(buttons_view_); 694 695 label_view_ = new views::Label(); 696 SetSnapType(SNAP_NONE); 697 label_view_->SetBackgroundColor(kBubbleBackgroundColor); 698 label_view_->SetEnabledColor(kBubbleTextColor); 699 label_view_->set_border(views::Border::CreateEmptyBorder( 700 kLabelSpacing, 0, kLabelSpacing, 0)); 701 AddChildView(label_view_); 702 } 703 704 // Set the label content to reflect the currently selected |snap_type|. 705 // This function can be executed through the frame maximize button as well as 706 // through hover operations. 707 void BubbleContentsView::SetSnapType(SnapType snap_type) { 708 if (!bubble_->controller()) 709 return; 710 711 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 712 int id = 0; 713 switch (snap_type) { 714 case SNAP_LEFT: 715 id = IDS_ASH_SNAP_WINDOW_LEFT; 716 break; 717 case SNAP_RIGHT: 718 id = IDS_ASH_SNAP_WINDOW_RIGHT; 719 break; 720 case SNAP_MAXIMIZE: 721 DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type()); 722 id = IDS_ASH_MAXIMIZE_WINDOW; 723 break; 724 case SNAP_MINIMIZE: 725 id = IDS_ASH_MINIMIZE_WINDOW; 726 break; 727 case SNAP_RESTORE: 728 DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type()); 729 id = IDS_ASH_RESTORE_WINDOW; 730 break; 731 default: 732 // If nothing is selected, we automatically select the click operation. 733 id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ? 734 IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW; 735 break; 736 } 737 label_view_->SetText(rb.GetLocalizedString(id)); 738 } 739 740 MaximizeBubbleController::MaximizeBubbleController( 741 FrameMaximizeButton* frame_maximize_button, 742 MaximizeBubbleFrameState maximize_type, 743 int appearance_delay_ms) 744 : frame_maximize_button_(frame_maximize_button), 745 bubble_(NULL), 746 maximize_type_(maximize_type), 747 appearance_delay_ms_(appearance_delay_ms) { 748 // Create the task which will create the bubble delayed. 749 base::OneShotTimer<MaximizeBubbleController>* new_timer = 750 new base::OneShotTimer<MaximizeBubbleController>(); 751 // Note: Even if there was no delay time given, we need to have a timer. 752 new_timer->Start( 753 FROM_HERE, 754 base::TimeDelta::FromMilliseconds( 755 appearance_delay_ms_ ? appearance_delay_ms_ : 10), 756 this, 757 &MaximizeBubbleController::CreateBubble); 758 timer_.reset(new_timer); 759 if (!appearance_delay_ms_) 760 CreateBubble(); 761 } 762 763 MaximizeBubbleController::~MaximizeBubbleController() { 764 // Note: The destructor only gets initiated through the owner. 765 timer_.reset(); 766 if (bubble_) { 767 bubble_->ControllerRequestsCloseAndDelete(); 768 bubble_ = NULL; 769 } 770 } 771 772 void MaximizeBubbleController::SetSnapType(SnapType snap_type) { 773 if (bubble_) 774 bubble_->SetSnapType(snap_type); 775 } 776 777 aura::Window* MaximizeBubbleController::GetBubbleWindow() { 778 return bubble_ ? bubble_->GetBubbleWindow() : NULL; 779 } 780 781 void MaximizeBubbleController::DelayCreation() { 782 if (timer_.get() && timer_->IsRunning()) 783 timer_->Reset(); 784 } 785 786 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) { 787 frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type); 788 } 789 790 void MaximizeBubbleController::OnButtonHover(SnapType snap_type) { 791 frame_maximize_button_->SnapButtonHovered(snap_type); 792 } 793 794 views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest( 795 SnapType state) { 796 return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL; 797 } 798 799 void MaximizeBubbleController::RequestDestructionThroughOwner() { 800 // Tell the parent to destroy us (if this didn't happen yet). 801 if (timer_) { 802 timer_.reset(NULL); 803 // Informs the owner that the menu is gone and requests |this| destruction. 804 frame_maximize_button_->DestroyMaximizeMenu(); 805 // Note: After this call |this| is destroyed. 806 } 807 } 808 809 void MaximizeBubbleController::CreateBubble() { 810 if (!bubble_) 811 bubble_ = new Bubble(this, appearance_delay_ms_); 812 813 timer_->Stop(); 814 } 815 816 BubbleDialogButton::BubbleDialogButton( 817 BubbleContentsButtonRow* button_row, 818 int normal_image, 819 int hovered_image, 820 int pressed_image) 821 : views::ImageButton(button_row), 822 button_row_(button_row) { 823 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 824 SetImage(views::CustomButton::STATE_NORMAL, 825 rb.GetImageSkiaNamed(normal_image)); 826 SetImage(views::CustomButton::STATE_HOVERED, 827 rb.GetImageSkiaNamed(hovered_image)); 828 SetImage(views::CustomButton::STATE_PRESSED, 829 rb.GetImageSkiaNamed(pressed_image)); 830 button_row->AddChildView(this); 831 } 832 833 void BubbleDialogButton::OnMouseCaptureLost() { 834 button_row_->ButtonHovered(NULL); 835 views::ImageButton::OnMouseCaptureLost(); 836 } 837 838 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) { 839 button_row_->ButtonHovered(this); 840 views::ImageButton::OnMouseEntered(event); 841 } 842 843 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) { 844 button_row_->ButtonHovered(NULL); 845 views::ImageButton::OnMouseExited(event); 846 } 847 848 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) { 849 if (!button_row_->bubble()->controller()) 850 return false; 851 852 // Remove the phantom window when we leave the button. 853 gfx::Point screen_location(event.location()); 854 View::ConvertPointToScreen(this, &screen_location); 855 if (!GetBoundsInScreen().Contains(screen_location)) 856 button_row_->ButtonHovered(NULL); 857 else 858 button_row_->ButtonHovered(this); 859 860 // Pass the event on to the normal handler. 861 return views::ImageButton::OnMouseDragged(event); 862 } 863 864 } // namespace ash 865