b3db4037878837c44d1eddb42c356ed176c9cec6
[platform/framework/web/crosswalk.git] / src / ash / wm / caption_buttons / maximize_bubble_controller_bubble.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_bubble.h"
6
7 #include "ash/metrics/user_metrics_recorder.h"
8 #include "ash/shell.h"
9 #include "ash/shell_window_ids.h"
10 #include "ash/wm/caption_buttons/bubble_contents_button_row.h"
11 #include "ash/wm/caption_buttons/frame_maximize_button.h"
12 #include "ash/wm/caption_buttons/maximize_bubble_controller.h"
13 #include "grit/ash_strings.h"
14 #include "ui/base/resource/resource_bundle.h"
15 #include "ui/gfx/animation/animation.h"
16 #include "ui/gfx/canvas.h"
17 #include "ui/gfx/path.h"
18 #include "ui/views/bubble/bubble_frame_view.h"
19 #include "ui/views/controls/label.h"
20 #include "ui/views/layout/box_layout.h"
21 #include "ui/views/mouse_watcher.h"
22 #include "ui/wm/public/masked_window_targeter.h"
23
24 namespace ash {
25
26 // BubbleContentsView ---------------------------------------------------------
27
28 // A class which creates the content of the bubble: The buttons, and the label.
29 class BubbleContentsView : public views::View {
30  public:
31   BubbleContentsView(MaximizeBubbleControllerBubble* bubble,
32                      SnapType initial_snap_type);
33   virtual ~BubbleContentsView();
34
35   // Set the label content to reflect the currently selected |snap_type|.
36   // This function can be executed through the frame maximize button as well as
37   // through hover operations.
38   void SetSnapType(SnapType snap_type);
39
40   // Added for unit test: Retrieve the button for an action.
41   // |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
42   views::CustomButton* GetButtonForUnitTest(SnapType state);
43
44  private:
45   // The owning class.
46   MaximizeBubbleControllerBubble* bubble_;
47
48   // The object which owns all the buttons.
49   BubbleContentsButtonRow* buttons_view_;
50
51   // The label object which shows the user the selected action.
52   views::Label* label_view_;
53
54   DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
55 };
56
57 BubbleContentsView::BubbleContentsView(
58     MaximizeBubbleControllerBubble* bubble,
59     SnapType initial_snap_type)
60     : bubble_(bubble),
61       buttons_view_(NULL),
62       label_view_(NULL) {
63   SetLayoutManager(new views::BoxLayout(
64       views::BoxLayout::kVertical, 0, 0,
65       MaximizeBubbleControllerBubble::kLayoutSpacing));
66   set_background(views::Background::CreateSolidBackground(
67       MaximizeBubbleControllerBubble::kBubbleBackgroundColor));
68
69   buttons_view_ = new BubbleContentsButtonRow(bubble);
70   AddChildView(buttons_view_);
71
72   label_view_ = new views::Label();
73   SetSnapType(initial_snap_type);
74   label_view_->SetBackgroundColor(
75       MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
76   const SkColor kBubbleTextColor = SK_ColorWHITE;
77   label_view_->SetEnabledColor(kBubbleTextColor);
78   const int kLabelSpacing = 4;
79   label_view_->SetBorder(
80       views::Border::CreateEmptyBorder(kLabelSpacing, 0, kLabelSpacing, 0));
81   AddChildView(label_view_);
82 }
83
84 BubbleContentsView::~BubbleContentsView() {
85 }
86
87 // Set the label content to reflect the currently selected |snap_type|.
88 // This function can be executed through the frame maximize button as well as
89 // through hover operations.
90 void BubbleContentsView::SetSnapType(SnapType snap_type) {
91   if (!bubble_->controller())
92     return;
93
94   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
95   int id = 0;
96   switch (snap_type) {
97     case SNAP_LEFT:
98       id = IDS_ASH_SNAP_WINDOW_LEFT;
99       break;
100     case SNAP_RIGHT:
101       id = IDS_ASH_SNAP_WINDOW_RIGHT;
102       break;
103     case SNAP_MAXIMIZE:
104       DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type());
105       id = IDS_ASH_MAXIMIZE_WINDOW;
106       break;
107     case SNAP_MINIMIZE:
108       id = IDS_ASH_MINIMIZE_WINDOW;
109       break;
110     case SNAP_RESTORE:
111       DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type());
112       id = IDS_ASH_RESTORE_WINDOW;
113       break;
114     default:
115       // If nothing is selected, we automatically select the click operation.
116       id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ?
117                IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW;
118       break;
119   }
120   label_view_->SetText(rb.GetLocalizedString(id));
121 }
122
123 views::CustomButton* BubbleContentsView::GetButtonForUnitTest(SnapType state) {
124   return buttons_view_->GetButtonForUnitTest(state);
125 }
126
127
128 // MaximizeBubbleBorder -------------------------------------------------------
129
130 namespace {
131
132 const int kLineWidth = 1;
133 const int kArrowHeight = 10;
134 const int kArrowWidth = 20;
135
136 }  // namespace
137
138 class MaximizeBubbleBorder : public views::BubbleBorder {
139  public:
140   MaximizeBubbleBorder(views::View* content_view, views::View* anchor);
141
142   virtual ~MaximizeBubbleBorder() {}
143
144   // Get the mouse active area of the window.
145   void GetMask(gfx::Path* mask);
146
147   // views::BubbleBorder:
148   virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
149                               const gfx::Size& contents_size) const OVERRIDE;
150   virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
151   virtual gfx::Size GetMinimumSize() const OVERRIDE;
152
153  private:
154   // Note: Animations can continue after then main window frame was destroyed.
155   // To avoid this problem, the owning screen metrics get extracted upon
156   // creation.
157   gfx::Size anchor_size_;
158   gfx::Point anchor_screen_origin_;
159   views::View* content_view_;
160
161   DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder);
162 };
163
164 MaximizeBubbleBorder::MaximizeBubbleBorder(views::View* content_view,
165                                            views::View* anchor)
166     : views::BubbleBorder(
167           views::BubbleBorder::TOP_RIGHT, views::BubbleBorder::NO_SHADOW,
168           MaximizeBubbleControllerBubble::kBubbleBackgroundColor),
169       anchor_size_(anchor->size()),
170       anchor_screen_origin_(0, 0),
171       content_view_(content_view) {
172   views::View::ConvertPointToScreen(anchor, &anchor_screen_origin_);
173   set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
174 }
175
176 void MaximizeBubbleBorder::GetMask(gfx::Path* mask) {
177   gfx::Insets inset = GetInsets();
178   // Note: Even though the tip could be added as activatable, it is left out
179   // since it would not change the action behavior in any way plus it makes
180   // more sense to keep the focus on the underlying button for clicks.
181   int left = inset.left() - kLineWidth;
182   int right = inset.left() + content_view_->width() + kLineWidth;
183   int top = inset.top() - kLineWidth;
184   int bottom = inset.top() + content_view_->height() + kLineWidth;
185   mask->moveTo(left, top);
186   mask->lineTo(right, top);
187   mask->lineTo(right, bottom);
188   mask->lineTo(left, bottom);
189   mask->lineTo(left, top);
190   mask->close();
191 }
192
193 gfx::Rect MaximizeBubbleBorder::GetBounds(
194     const gfx::Rect& position_relative_to,
195     const gfx::Size& contents_size) const {
196   gfx::Size border_size(contents_size);
197   gfx::Insets insets = GetInsets();
198   border_size.Enlarge(insets.width(), insets.height());
199
200   // Position the bubble to center the box on the anchor.
201   int x = (anchor_size_.width() - border_size.width()) / 2;
202   // Position the bubble under the anchor, overlapping the arrow with it.
203   int y = anchor_size_.height() - insets.top();
204
205   gfx::Point view_origin(x + anchor_screen_origin_.x(),
206                          y + anchor_screen_origin_.y());
207
208   return gfx::Rect(view_origin, border_size);
209 }
210
211 void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
212   gfx::Insets inset = GetInsets();
213
214   // Draw the border line around everything.
215   int y = inset.top();
216   // Top
217   canvas->FillRect(gfx::Rect(inset.left(),
218                              y - kLineWidth,
219                              content_view_->width(),
220                              kLineWidth),
221                    MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
222   // Bottom
223   canvas->FillRect(gfx::Rect(inset.left(),
224                              y + content_view_->height(),
225                              content_view_->width(),
226                              kLineWidth),
227                    MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
228   // Left
229   canvas->FillRect(gfx::Rect(inset.left() - kLineWidth,
230                              y - kLineWidth,
231                              kLineWidth,
232                              content_view_->height() + 2 * kLineWidth),
233                    MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
234   // Right
235   canvas->FillRect(gfx::Rect(inset.left() + content_view_->width(),
236                              y - kLineWidth,
237                              kLineWidth,
238                              content_view_->height() + 2 * kLineWidth),
239                    MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
240
241   // Draw the arrow afterwards covering the border.
242   SkPath path;
243   path.incReserve(4);
244   // The center of the tip should be in the middle of the button.
245   int tip_x = inset.left() + content_view_->width() / 2;
246   int left_base_x = tip_x - kArrowWidth / 2;
247   int left_base_y = y;
248   int tip_y = left_base_y - kArrowHeight;
249   path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y));
250   path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
251   path.lineTo(SkIntToScalar(left_base_x + kArrowWidth),
252               SkIntToScalar(left_base_y));
253
254   SkPaint paint;
255   paint.setStyle(SkPaint::kFill_Style);
256   paint.setColor(MaximizeBubbleControllerBubble::kBubbleBackgroundColor);
257   canvas->DrawPath(path, paint);
258 }
259
260 gfx::Size MaximizeBubbleBorder::GetMinimumSize() const {
261   return gfx::Size(kLineWidth * 2 + kArrowWidth,
262                    std::max(kLineWidth, kArrowHeight) + kLineWidth);
263 }
264
265 namespace {
266
267 // MaximizebubbleTargeter  -----------------------------------------------------
268
269 // Window targeter used for the bubble.
270 class MaximizeBubbleTargeter : public ::wm::MaskedWindowTargeter {
271  public:
272   MaximizeBubbleTargeter(aura::Window* window,
273                          MaximizeBubbleBorder* border)
274       : ::wm::MaskedWindowTargeter(window),
275         border_(border) {
276   }
277
278   virtual ~MaximizeBubbleTargeter() {}
279
280  private:
281   // ::wm::MaskedWindowTargeter:
282   virtual bool GetHitTestMask(aura::Window* window,
283                               gfx::Path* mask) const OVERRIDE {
284     border_->GetMask(mask);
285     return true;
286   }
287
288   MaximizeBubbleBorder* border_;
289
290   DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleTargeter);
291 };
292
293 }  // namespace
294
295
296 // BubbleMouseWatcherHost -----------------------------------------------------
297
298 // The mouse watcher host which makes sure that the bubble does not get closed
299 // while the mouse cursor is over the maximize button or the balloon content.
300 // Note: This object gets destroyed when the MouseWatcher gets destroyed.
301 class BubbleMouseWatcherHost: public views::MouseWatcherHost {
302  public:
303   explicit BubbleMouseWatcherHost(MaximizeBubbleControllerBubble* bubble);
304   virtual ~BubbleMouseWatcherHost();
305
306   // views::MouseWatcherHost:
307   virtual bool Contains(const gfx::Point& screen_point,
308                         views::MouseWatcherHost::MouseEventType type) OVERRIDE;
309  private:
310   MaximizeBubbleControllerBubble* bubble_;
311
312   DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost);
313 };
314
315 BubbleMouseWatcherHost::BubbleMouseWatcherHost(
316     MaximizeBubbleControllerBubble* bubble)
317     : bubble_(bubble) {
318 }
319
320 BubbleMouseWatcherHost::~BubbleMouseWatcherHost() {
321 }
322
323 bool BubbleMouseWatcherHost::Contains(
324     const gfx::Point& screen_point,
325     views::MouseWatcherHost::MouseEventType type) {
326   return bubble_->Contains(screen_point, type);
327 }
328
329
330 // MaximizeBubbleControllerBubble ---------------------------------------------
331
332 // static
333 const SkColor MaximizeBubbleControllerBubble::kBubbleBackgroundColor =
334     0xFF141414;
335 const int MaximizeBubbleControllerBubble::kLayoutSpacing = -1;
336
337 MaximizeBubbleControllerBubble::MaximizeBubbleControllerBubble(
338     MaximizeBubbleController* owner,
339     int appearance_delay_ms,
340     SnapType initial_snap_type)
341     : views::BubbleDelegateView(owner->frame_maximize_button(),
342                                 views::BubbleBorder::TOP_RIGHT),
343       shutting_down_(false),
344       owner_(owner),
345       bubble_widget_(NULL),
346       contents_view_(NULL),
347       bubble_border_(NULL),
348       appearance_delay_ms_(appearance_delay_ms) {
349   set_margins(gfx::Insets());
350
351   // The window needs to be owned by the root so that the SnapSizer does not
352   // cover it upon animation.
353   aura::Window* parent = Shell::GetContainer(
354       Shell::GetTargetRootWindow(),
355       internal::kShellWindowId_ShelfContainer);
356   set_parent_window(parent);
357
358   set_notify_enter_exit_on_child(true);
359   set_adjust_if_offscreen(false);
360   SetPaintToLayer(true);
361   set_color(kBubbleBackgroundColor);
362   set_close_on_deactivate(false);
363   set_background(
364       views::Background::CreateSolidBackground(kBubbleBackgroundColor));
365
366   SetLayoutManager(new views::BoxLayout(
367       views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
368
369   contents_view_ = new BubbleContentsView(this, initial_snap_type);
370   AddChildView(contents_view_);
371
372   // Note that the returned widget has an observer which points to our
373   // functions.
374   bubble_widget_ = views::BubbleDelegateView::CreateBubble(this);
375   bubble_widget_->set_focus_on_creation(false);
376
377   SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
378   bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
379
380   bubble_border_ = new MaximizeBubbleBorder(this, GetAnchorView());
381   GetBubbleFrameView()->SetBubbleBorder(
382       scoped_ptr<views::BubbleBorder>(bubble_border_));
383   GetBubbleFrameView()->set_background(NULL);
384
385   // Recalculate size with new border.
386   SizeToContents();
387
388   if (!appearance_delay_ms_)
389     GetWidget()->Show();
390   else
391     StartFade(true);
392
393   aura::Window* window = bubble_widget_->GetNativeWindow();
394   window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
395       new MaximizeBubbleTargeter(window, bubble_border_)));
396
397   ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(
398       ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE);
399
400   mouse_watcher_.reset(new views::MouseWatcher(
401       new BubbleMouseWatcherHost(this),
402       this));
403   mouse_watcher_->Start();
404 }
405
406 MaximizeBubbleControllerBubble::~MaximizeBubbleControllerBubble() {
407 }
408
409 aura::Window* MaximizeBubbleControllerBubble::GetBubbleWindow() {
410   return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
411 }
412
413 gfx::Rect MaximizeBubbleControllerBubble::GetAnchorRect() {
414   if (!owner_)
415     return gfx::Rect();
416
417   gfx::Rect anchor_rect =
418       owner_->frame_maximize_button()->GetBoundsInScreen();
419   return anchor_rect;
420 }
421
422 void MaximizeBubbleControllerBubble::AnimationProgressed(
423     const gfx::Animation* animation) {
424   // First do everything needed for the fade by calling the base function.
425   BubbleDelegateView::AnimationProgressed(animation);
426   // When fading in we are done.
427   if (!shutting_down_)
428     return;
429   // Upon fade out an additional shift is required.
430   const int kBubbleAnimationOffsetY = 5;
431   int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0);
432   gfx::Rect rect = initial_position_;
433
434   rect.set_y(rect.y() + shift);
435   bubble_widget_->GetNativeWindow()->SetBounds(rect);
436 }
437
438 bool MaximizeBubbleControllerBubble::CanActivate() const {
439   return false;
440 }
441
442 bool MaximizeBubbleControllerBubble::WidgetHasHitTestMask() const {
443   return bubble_border_ != NULL;
444 }
445
446 void MaximizeBubbleControllerBubble::GetWidgetHitTestMask(
447     gfx::Path* mask) const {
448   DCHECK(mask);
449   DCHECK(bubble_border_);
450   bubble_border_->GetMask(mask);
451 }
452
453 void MaximizeBubbleControllerBubble::MouseMovedOutOfHost() {
454   if (!owner_ || shutting_down_)
455     return;
456   // When we leave the bubble, we might be still be in gesture mode or over
457   // the maximize button. So only close if none of the other cases apply.
458   if (!owner_->frame_maximize_button()->is_snap_enabled()) {
459     gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
460     if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
461         screen_location)) {
462       owner_->RequestDestructionThroughOwner();
463     }
464   }
465 }
466
467 bool MaximizeBubbleControllerBubble::Contains(
468     const gfx::Point& screen_point,
469     views::MouseWatcherHost::MouseEventType type) {
470   if (!owner_ || shutting_down_)
471     return false;
472   bool inside_button =
473       owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
474           screen_point);
475   if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) {
476     SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ?
477         SNAP_RESTORE : SNAP_MAXIMIZE);
478     return true;
479   }
480   // Check if either a gesture is taking place (=> bubble stays no matter what
481   // the mouse does) or the mouse is over the maximize button or the bubble
482   // content.
483   return (owner_->frame_maximize_button()->is_snap_enabled() ||
484           inside_button ||
485           contents_view_->GetBoundsInScreen().Contains(screen_point));
486 }
487
488 gfx::Size MaximizeBubbleControllerBubble::GetPreferredSize() {
489   return contents_view_->GetPreferredSize();
490 }
491
492 void MaximizeBubbleControllerBubble::OnWidgetDestroying(views::Widget* widget) {
493   if (bubble_widget_ == widget) {
494     mouse_watcher_->Stop();
495
496     if (owner_) {
497       // If the bubble destruction was triggered by some other external
498       // influence then ourselves, the owner needs to be informed that the menu
499       // is gone.
500       shutting_down_ = true;
501       owner_->RequestDestructionThroughOwner();
502       owner_ = NULL;
503     }
504   }
505   BubbleDelegateView::OnWidgetDestroying(widget);
506 }
507
508 void MaximizeBubbleControllerBubble::ControllerRequestsCloseAndDelete() {
509   // This only gets called from the owning base class once it is deleted.
510   if (shutting_down_)
511     return;
512   shutting_down_ = true;
513   owner_ = NULL;
514
515   // Close the widget asynchronously after the hide animation is finished.
516   initial_position_ = bubble_widget_->GetNativeWindow()->bounds();
517   if (!appearance_delay_ms_)
518     bubble_widget_->CloseNow();
519   else
520     StartFade(false);
521 }
522
523 void MaximizeBubbleControllerBubble::SetSnapType(SnapType snap_type) {
524   if (contents_view_)
525     contents_view_->SetSnapType(snap_type);
526 }
527
528 views::CustomButton* MaximizeBubbleControllerBubble::GetButtonForUnitTest(
529     SnapType state) {
530   return contents_view_->GetButtonForUnitTest(state);
531 }
532
533 }  // namespace ash