- add sources.
[platform/framework/web/crosswalk.git] / src / ash / wm / caption_buttons / maximize_bubble_controller.cc
1 // Copyright 2013 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/caption_buttons/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/caption_buttons/frame_maximize_button.h"
11 #include "ash/wm/window_animations.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/l10n/l10n_util.h"
18 #include "ui/base/resource/resource_bundle.h"
19 #include "ui/gfx/animation/animation.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   Bubble(MaximizeBubbleController* owner,
214          int appearance_delay_ms,
215          SnapType initial_snap_type);
216   virtual ~Bubble() {}
217
218   // The window of the menu under which the SnapSizer will get created.
219   aura::Window* GetBubbleWindow();
220
221   // Overridden from views::BubbleDelegateView.
222   virtual gfx::Rect GetAnchorRect() OVERRIDE;
223   virtual void AnimationProgressed(const gfx::Animation* animation) OVERRIDE;
224   virtual bool CanActivate() const OVERRIDE { return false; }
225
226   // Overridden from views::WidgetDelegateView.
227   virtual bool WidgetHasHitTestMask() const OVERRIDE;
228   virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE;
229
230   // Implementation of MouseWatcherListener.
231   virtual void MouseMovedOutOfHost() OVERRIDE;
232
233   // Implementation of MouseWatcherHost.
234   virtual bool Contains(const gfx::Point& screen_point,
235                         views::MouseWatcherHost::MouseEventType type);
236
237   // Overridden from views::View.
238   virtual gfx::Size GetPreferredSize() OVERRIDE;
239
240   // Overridden from views::Widget::Observer.
241   virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
242
243   // Called from the controller class to indicate that the menu should get
244   // destroyed.
245   virtual void ControllerRequestsCloseAndDelete();
246
247   // Called from the owning class to change the menu content to the given
248   // |snap_type| so that the user knows what is selected.
249   void SetSnapType(SnapType snap_type);
250
251   // Get the owning MaximizeBubbleController. This might return NULL in case
252   // of an asynchronous shutdown.
253   MaximizeBubbleController* controller() const { return owner_; }
254
255   // Added for unit test: Retrieve the button for an action.
256   // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
257   views::CustomButton* GetButtonForUnitTest(SnapType state);
258
259  private:
260   // True if the shut down has been initiated.
261   bool shutting_down_;
262
263   // Our owning class.
264   MaximizeBubbleController* owner_;
265
266   // The widget which contains our menu and the bubble border.
267   views::Widget* bubble_widget_;
268
269   // The content accessor of the menu.
270   BubbleContentsView* contents_view_;
271
272   // The bubble border.
273   MaximizeBubbleBorder* bubble_border_;
274
275   // The rectangle before the animation starts.
276   gfx::Rect initial_position_;
277
278   // The mouse watcher which takes care of out of window hover events.
279   scoped_ptr<views::MouseWatcher> mouse_watcher_;
280
281   // The fade delay - if 0 it will show / hide immediately.
282   const int appearance_delay_ms_;
283
284   DISALLOW_COPY_AND_ASSIGN(Bubble);
285 };
286
287 // A class that creates all buttons and put them into a view.
288 class BubbleContentsButtonRow : public views::View,
289                                 public views::ButtonListener {
290  public:
291   explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble* bubble);
292
293   virtual ~BubbleContentsButtonRow() {}
294
295   // Overridden from ButtonListener.
296   virtual void ButtonPressed(views::Button* sender,
297                              const ui::Event& event) OVERRIDE;
298   // Called from BubbleDialogButton.
299   void ButtonHovered(BubbleDialogButton* sender);
300
301   // Added for unit test: Retrieve the button for an action.
302   // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
303   views::CustomButton* GetButtonForUnitTest(SnapType state);
304
305   MaximizeBubbleController::Bubble* bubble() { return bubble_; }
306
307  private:
308   // Functions to add the left and right maximize / restore buttons.
309   void AddMaximizeLeftButton();
310   void AddMaximizeRightButton();
311   void AddMinimizeButton();
312
313   // The owning object which gets notifications.
314   MaximizeBubbleController::Bubble* bubble_;
315
316   // The created buttons for our menu.
317   BubbleDialogButton* left_button_;
318   BubbleDialogButton* minimize_button_;
319   BubbleDialogButton* right_button_;
320
321   DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow);
322 };
323
324 // A class which creates the content of the bubble: The buttons, and the label.
325 class BubbleContentsView : public views::View {
326  public:
327   BubbleContentsView(MaximizeBubbleController::Bubble* bubble,
328                      SnapType initial_snap_type);
329
330   virtual ~BubbleContentsView() {}
331
332   // Set the label content to reflect the currently selected |snap_type|.
333   // This function can be executed through the frame maximize button as well as
334   // through hover operations.
335   void SetSnapType(SnapType snap_type);
336
337   // Added for unit test: Retrieve the button for an action.
338   // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
339   views::CustomButton* GetButtonForUnitTest(SnapType state) {
340     return buttons_view_->GetButtonForUnitTest(state);
341   }
342
343  private:
344   // The owning class.
345   MaximizeBubbleController::Bubble* bubble_;
346
347   // The object which owns all the buttons.
348   BubbleContentsButtonRow* buttons_view_;
349
350   // The label object which shows the user the selected action.
351   views::Label* label_view_;
352
353   DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
354 };
355
356 // The image button gets overridden to be able to capture mouse hover events.
357 // The constructor also assigns all button states and
358 class BubbleDialogButton : public views::ImageButton {
359  public:
360   explicit BubbleDialogButton(
361       BubbleContentsButtonRow* button_row_listener,
362       int normal_image,
363       int hovered_image,
364       int pressed_image);
365   virtual ~BubbleDialogButton() {}
366
367   // CustomButton overrides:
368   virtual void OnMouseCaptureLost() OVERRIDE;
369   virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
370   virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
371   virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
372
373  private:
374   // The creating class which needs to get notified in case of a hover event.
375   BubbleContentsButtonRow* button_row_;
376
377   DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton);
378 };
379
380 MaximizeBubbleController::Bubble::Bubble(
381     MaximizeBubbleController* owner,
382     int appearance_delay_ms,
383     SnapType initial_snap_type)
384     : views::BubbleDelegateView(owner->frame_maximize_button(),
385                                 views::BubbleBorder::TOP_RIGHT),
386       shutting_down_(false),
387       owner_(owner),
388       bubble_widget_(NULL),
389       contents_view_(NULL),
390       bubble_border_(NULL),
391       appearance_delay_ms_(appearance_delay_ms) {
392   set_margins(gfx::Insets());
393
394   // The window needs to be owned by the root so that the SnapSizer does not
395   // cover it upon animation.
396   aura::Window* parent = Shell::GetContainer(
397       Shell::GetTargetRootWindow(),
398       internal::kShellWindowId_ShelfContainer);
399   set_parent_window(parent);
400
401   set_notify_enter_exit_on_child(true);
402   set_adjust_if_offscreen(false);
403   SetPaintToLayer(true);
404   set_color(kBubbleBackgroundColor);
405   set_close_on_deactivate(false);
406   set_background(
407       views::Background::CreateSolidBackground(kBubbleBackgroundColor));
408
409   SetLayoutManager(new views::BoxLayout(
410       views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
411
412   contents_view_ = new BubbleContentsView(this, initial_snap_type);
413   AddChildView(contents_view_);
414
415   // Note that the returned widget has an observer which points to our
416   // functions.
417   bubble_widget_ = views::BubbleDelegateView::CreateBubble(this);
418   bubble_widget_->set_focus_on_creation(false);
419
420   SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
421   bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
422
423   bubble_border_ = new MaximizeBubbleBorder(this, GetAnchorView());
424   GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
425   GetBubbleFrameView()->set_background(NULL);
426
427   // Recalculate size with new border.
428   SizeToContents();
429
430   if (!appearance_delay_ms_)
431     GetWidget()->Show();
432   else
433     StartFade(true);
434
435   ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
436       ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE);
437
438   mouse_watcher_.reset(new views::MouseWatcher(
439       new BubbleMouseWatcherHost(this),
440       this));
441   mouse_watcher_->Start();
442 }
443
444 bool BubbleMouseWatcherHost::Contains(
445     const gfx::Point& screen_point,
446     views::MouseWatcherHost::MouseEventType type) {
447   return bubble_->Contains(screen_point, type);
448 }
449
450 aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() {
451   return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
452 }
453
454 gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() {
455   if (!owner_)
456     return gfx::Rect();
457
458   gfx::Rect anchor_rect =
459       owner_->frame_maximize_button()->GetBoundsInScreen();
460   return anchor_rect;
461 }
462
463 void MaximizeBubbleController::Bubble::AnimationProgressed(
464     const gfx::Animation* animation) {
465   // First do everything needed for the fade by calling the base function.
466   BubbleDelegateView::AnimationProgressed(animation);
467   // When fading in we are done.
468   if (!shutting_down_)
469     return;
470   // Upon fade out an additional shift is required.
471   int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0);
472   gfx::Rect rect = initial_position_;
473
474   rect.set_y(rect.y() + shift);
475   bubble_widget_->GetNativeWindow()->SetBounds(rect);
476 }
477
478 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
479   return bubble_border_ != NULL;
480 }
481
482 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
483     gfx::Path* mask) const {
484   DCHECK(mask);
485   DCHECK(bubble_border_);
486   bubble_border_->GetMask(mask);
487 }
488
489 void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() {
490   if (!owner_ || shutting_down_)
491     return;
492   // When we leave the bubble, we might be still be in gesture mode or over
493   // the maximize button. So only close if none of the other cases apply.
494   if (!owner_->frame_maximize_button()->is_snap_enabled()) {
495     gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
496     if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
497         screen_location)) {
498         owner_->RequestDestructionThroughOwner();
499     }
500   }
501 }
502
503 bool MaximizeBubbleController::Bubble::Contains(
504     const gfx::Point& screen_point,
505     views::MouseWatcherHost::MouseEventType type) {
506   if (!owner_ || shutting_down_)
507     return false;
508   bool inside_button =
509       owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
510           screen_point);
511   if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) {
512     SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ?
513         SNAP_RESTORE : SNAP_MAXIMIZE);
514     return true;
515   }
516   // Check if either a gesture is taking place (=> bubble stays no matter what
517   // the mouse does) or the mouse is over the maximize button or the bubble
518   // content.
519   return (owner_->frame_maximize_button()->is_snap_enabled() ||
520           inside_button ||
521           contents_view_->GetBoundsInScreen().Contains(screen_point));
522 }
523
524 gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() {
525   return contents_view_->GetPreferredSize();
526 }
527
528 void MaximizeBubbleController::Bubble::OnWidgetDestroying(
529     views::Widget* widget) {
530   if (bubble_widget_ == widget) {
531     mouse_watcher_->Stop();
532
533     if (owner_) {
534       // If the bubble destruction was triggered by some other external
535       // influence then ourselves, the owner needs to be informed that the menu
536       // is gone.
537       shutting_down_ = true;
538       owner_->RequestDestructionThroughOwner();
539       owner_ = NULL;
540     }
541   }
542   BubbleDelegateView::OnWidgetDestroying(widget);
543 }
544
545 void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() {
546   // This only gets called from the owning base class once it is deleted.
547   if (shutting_down_)
548     return;
549   shutting_down_ = true;
550   owner_ = NULL;
551
552   // Close the widget asynchronously after the hide animation is finished.
553   initial_position_ = bubble_widget_->GetNativeWindow()->bounds();
554   if (!appearance_delay_ms_)
555     bubble_widget_->CloseNow();
556   else
557     StartFade(false);
558 }
559
560 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) {
561   if (contents_view_)
562     contents_view_->SetSnapType(snap_type);
563 }
564
565 views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
566     SnapType state) {
567   return contents_view_->GetButtonForUnitTest(state);
568 }
569
570 BubbleContentsButtonRow::BubbleContentsButtonRow(
571     MaximizeBubbleController::Bubble* bubble)
572     : bubble_(bubble),
573       left_button_(NULL),
574       minimize_button_(NULL),
575       right_button_(NULL) {
576   SetLayoutManager(new views::BoxLayout(
577       views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing));
578   set_background(
579       views::Background::CreateSolidBackground(kBubbleBackgroundColor));
580
581   if (base::i18n::IsRTL()) {
582     AddMaximizeRightButton();
583     AddMinimizeButton();
584     AddMaximizeLeftButton();
585   } else {
586     AddMaximizeLeftButton();
587     AddMinimizeButton();
588     AddMaximizeRightButton();
589   }
590 }
591
592 // Overridden from ButtonListener.
593 void BubbleContentsButtonRow::ButtonPressed(views::Button* sender,
594                                             const ui::Event& event) {
595   // While shutting down, the connection to the owner might already be broken.
596   if (!bubble_->controller())
597     return;
598   if (sender == left_button_)
599     bubble_->controller()->OnButtonClicked(
600         bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
601             SNAP_RESTORE : SNAP_LEFT);
602   else if (sender == minimize_button_)
603     bubble_->controller()->OnButtonClicked(SNAP_MINIMIZE);
604   else if (sender == right_button_)
605     bubble_->controller()->OnButtonClicked(
606         bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
607             SNAP_RESTORE : SNAP_RIGHT);
608   else
609     NOTREACHED() << "Unknown button pressed.";
610 }
611
612 // Called from BubbleDialogButton.
613 void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton* sender) {
614   // While shutting down, the connection to the owner might already be broken.
615   if (!bubble_->controller())
616     return;
617   if (sender == left_button_)
618     bubble_->controller()->OnButtonHover(
619         bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
620             SNAP_RESTORE : SNAP_LEFT);
621   else if (sender == minimize_button_)
622     bubble_->controller()->OnButtonHover(SNAP_MINIMIZE);
623   else if (sender == right_button_)
624     bubble_->controller()->OnButtonHover(
625         bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
626             SNAP_RESTORE : SNAP_RIGHT);
627   else
628     bubble_->controller()->OnButtonHover(SNAP_NONE);
629 }
630
631 views::CustomButton* BubbleContentsButtonRow::GetButtonForUnitTest(
632     SnapType state) {
633   switch (state) {
634     case SNAP_LEFT:
635       return left_button_;
636     case SNAP_MINIMIZE:
637       return minimize_button_;
638     case SNAP_RIGHT:
639       return right_button_;
640     default:
641       NOTREACHED();
642       return NULL;
643   }
644 }
645
646 void BubbleContentsButtonRow::AddMaximizeLeftButton() {
647   if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT) {
648     left_button_ = new BubbleDialogButton(
649         this,
650         IDR_AURA_WINDOW_POSITION_LEFT_RESTORE,
651         IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H,
652         IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P);
653   } else {
654     left_button_ = new BubbleDialogButton(
655         this,
656         IDR_AURA_WINDOW_POSITION_LEFT,
657         IDR_AURA_WINDOW_POSITION_LEFT_H,
658         IDR_AURA_WINDOW_POSITION_LEFT_P);
659   }
660 }
661
662 void BubbleContentsButtonRow::AddMaximizeRightButton() {
663   if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT) {
664     right_button_ = new BubbleDialogButton(
665         this,
666         IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE,
667         IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H,
668         IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P);
669   } else {
670     right_button_ = new BubbleDialogButton(
671         this,
672         IDR_AURA_WINDOW_POSITION_RIGHT,
673         IDR_AURA_WINDOW_POSITION_RIGHT_H,
674         IDR_AURA_WINDOW_POSITION_RIGHT_P);
675   }
676 }
677
678 void BubbleContentsButtonRow::AddMinimizeButton() {
679   minimize_button_ = new BubbleDialogButton(
680       this,
681       IDR_AURA_WINDOW_POSITION_MIDDLE,
682       IDR_AURA_WINDOW_POSITION_MIDDLE_H,
683       IDR_AURA_WINDOW_POSITION_MIDDLE_P);
684 }
685
686 BubbleContentsView::BubbleContentsView(
687     MaximizeBubbleController::Bubble* bubble,
688     SnapType initial_snap_type)
689     : bubble_(bubble),
690       buttons_view_(NULL),
691       label_view_(NULL) {
692   SetLayoutManager(new views::BoxLayout(
693       views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
694   set_background(
695       views::Background::CreateSolidBackground(kBubbleBackgroundColor));
696
697   buttons_view_ = new BubbleContentsButtonRow(bubble);
698   AddChildView(buttons_view_);
699
700   label_view_ = new views::Label();
701   SetSnapType(initial_snap_type);
702   label_view_->SetBackgroundColor(kBubbleBackgroundColor);
703   label_view_->SetEnabledColor(kBubbleTextColor);
704   label_view_->set_border(views::Border::CreateEmptyBorder(
705       kLabelSpacing, 0, kLabelSpacing, 0));
706   AddChildView(label_view_);
707 }
708
709 // Set the label content to reflect the currently selected |snap_type|.
710 // This function can be executed through the frame maximize button as well as
711 // through hover operations.
712 void BubbleContentsView::SetSnapType(SnapType snap_type) {
713   if (!bubble_->controller())
714     return;
715
716   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
717   int id = 0;
718   switch (snap_type) {
719     case SNAP_LEFT:
720       id = IDS_ASH_SNAP_WINDOW_LEFT;
721       break;
722     case SNAP_RIGHT:
723       id = IDS_ASH_SNAP_WINDOW_RIGHT;
724       break;
725     case SNAP_MAXIMIZE:
726       DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type());
727       id = IDS_ASH_MAXIMIZE_WINDOW;
728       break;
729     case SNAP_MINIMIZE:
730       id = IDS_ASH_MINIMIZE_WINDOW;
731       break;
732     case SNAP_RESTORE:
733       DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type());
734       id = IDS_ASH_RESTORE_WINDOW;
735       break;
736     default:
737       // If nothing is selected, we automatically select the click operation.
738       id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ?
739                IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW;
740       break;
741   }
742   label_view_->SetText(rb.GetLocalizedString(id));
743 }
744
745 MaximizeBubbleController::MaximizeBubbleController(
746     FrameMaximizeButton* frame_maximize_button,
747     MaximizeBubbleFrameState maximize_type,
748     int appearance_delay_ms)
749     : frame_maximize_button_(frame_maximize_button),
750       bubble_(NULL),
751       maximize_type_(maximize_type),
752       snap_type_for_creation_(SNAP_NONE),
753       appearance_delay_ms_(appearance_delay_ms) {
754   // Create the task which will create the bubble delayed.
755   base::OneShotTimer<MaximizeBubbleController>* new_timer =
756       new base::OneShotTimer<MaximizeBubbleController>();
757   // Note: Even if there was no delay time given, we need to have a timer.
758   new_timer->Start(
759       FROM_HERE,
760       base::TimeDelta::FromMilliseconds(
761           appearance_delay_ms_ ? appearance_delay_ms_ : 10),
762       this,
763       &MaximizeBubbleController::CreateBubble);
764   timer_.reset(new_timer);
765   if (!appearance_delay_ms_)
766     CreateBubble();
767 }
768
769 MaximizeBubbleController::~MaximizeBubbleController() {
770   // Note: The destructor only gets initiated through the owner.
771   timer_.reset();
772   if (bubble_) {
773     bubble_->ControllerRequestsCloseAndDelete();
774     bubble_ = NULL;
775   }
776 }
777
778 void MaximizeBubbleController::SetSnapType(SnapType snap_type) {
779   if (bubble_) {
780     bubble_->SetSnapType(snap_type);
781   } else {
782     // The bubble has not been created yet. This can occur if bubble creation is
783     // delayed.
784     snap_type_for_creation_ = snap_type;
785   }
786 }
787
788 aura::Window* MaximizeBubbleController::GetBubbleWindow() {
789   return bubble_ ? bubble_->GetBubbleWindow() : NULL;
790 }
791
792 void MaximizeBubbleController::DelayCreation() {
793   if (timer_.get() && timer_->IsRunning())
794     timer_->Reset();
795 }
796
797 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) {
798   frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type);
799 }
800
801 void MaximizeBubbleController::OnButtonHover(SnapType snap_type) {
802   frame_maximize_button_->SnapButtonHovered(snap_type);
803 }
804
805 views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest(
806     SnapType state) {
807   return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL;
808 }
809
810 void MaximizeBubbleController::RequestDestructionThroughOwner() {
811   // Tell the parent to destroy us (if this didn't happen yet).
812   if (timer_) {
813     timer_.reset(NULL);
814     // Informs the owner that the menu is gone and requests |this| destruction.
815     frame_maximize_button_->DestroyMaximizeMenu();
816     // Note: After this call |this| is destroyed.
817   }
818 }
819
820 void MaximizeBubbleController::CreateBubble() {
821   if (!bubble_)
822     bubble_ = new Bubble(this, appearance_delay_ms_, snap_type_for_creation_);
823
824   timer_->Stop();
825 }
826
827 BubbleDialogButton::BubbleDialogButton(
828     BubbleContentsButtonRow* button_row,
829     int normal_image,
830     int hovered_image,
831     int pressed_image)
832     : views::ImageButton(button_row),
833       button_row_(button_row) {
834   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
835   SetImage(views::CustomButton::STATE_NORMAL,
836            rb.GetImageSkiaNamed(normal_image));
837   SetImage(views::CustomButton::STATE_HOVERED,
838            rb.GetImageSkiaNamed(hovered_image));
839   SetImage(views::CustomButton::STATE_PRESSED,
840            rb.GetImageSkiaNamed(pressed_image));
841   button_row->AddChildView(this);
842 }
843
844 void BubbleDialogButton::OnMouseCaptureLost() {
845   button_row_->ButtonHovered(NULL);
846   views::ImageButton::OnMouseCaptureLost();
847 }
848
849 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) {
850   button_row_->ButtonHovered(this);
851   views::ImageButton::OnMouseEntered(event);
852 }
853
854 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) {
855   button_row_->ButtonHovered(NULL);
856   views::ImageButton::OnMouseExited(event);
857 }
858
859 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) {
860   if (!button_row_->bubble()->controller())
861     return false;
862
863   // Remove the phantom window when we leave the button.
864   gfx::Point screen_location(event.location());
865   View::ConvertPointToScreen(this, &screen_location);
866   if (!GetBoundsInScreen().Contains(screen_location))
867     button_row_->ButtonHovered(NULL);
868   else
869     button_row_->ButtonHovered(this);
870
871   // Pass the event on to the normal handler.
872   return views::ImageButton::OnMouseDragged(event);
873 }
874
875 }  // namespace ash