Home | History | Annotate | Download | only in wm
      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