Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / ash / wm / maximize_bubble_controller.cc
blobfa5b2edf259fab69afd75f9273505363770f6ded
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.
5 #include "ash/wm/maximize_bubble_controller.h"
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.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"
32 namespace {
34 // The spacing between two buttons.
35 const int kLayoutSpacing = -1;
37 // The background color.
38 const SkColor kBubbleBackgroundColor = 0xFF141414;
40 // The text color within the bubble.
41 const SkColor kBubbleTextColor = SK_ColorWHITE;
43 // The line width of the bubble.
44 const int kLineWidth = 1;
46 // The spacing for the top and bottom of the info label.
47 const int kLabelSpacing = 4;
49 // The pixel dimensions of the arrow.
50 const int kArrowHeight = 10;
51 const int kArrowWidth = 20;
53 // The animation offset in y for the bubble when appearing.
54 const int kBubbleAnimationOffsetY = 5;
56 class MaximizeBubbleBorder : public views::BubbleBorder {
57 public:
58 MaximizeBubbleBorder(views::View* content_view, views::View* anchor);
60 virtual ~MaximizeBubbleBorder() {}
62 // Get the mouse active area of the window.
63 void GetMask(gfx::Path* mask);
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;
69 // Overridden from views::Border.
70 virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
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_;
80 DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder);
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);
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();
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());
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();
124 gfx::Point view_origin(x + anchor_screen_origin_.x(),
125 y + anchor_screen_origin_.y());
127 return gfx::Rect(view_origin, border_size);
130 void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
131 gfx::Insets inset = GetInsets();
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);
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));
173 SkPaint paint;
174 paint.setStyle(SkPaint::kFill_Style);
175 paint.setColor(kBubbleBackgroundColor);
176 canvas->DrawPath(path, paint);
179 } // namespace
181 namespace ash {
183 class BubbleContentsButtonRow;
184 class BubbleContentsView;
185 class BubbleDialogButton;
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() {}
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_;
202 DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost);
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() {}
216 // The window of the menu under which the SnapSizer will get created.
217 aura::Window* GetBubbleWindow();
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; }
224 // Overridden from views::WidgetDelegateView.
225 virtual bool WidgetHasHitTestMask() const OVERRIDE;
226 virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE;
228 // Implementation of MouseWatcherListener.
229 virtual void MouseMovedOutOfHost() OVERRIDE;
231 // Implementation of MouseWatcherHost.
232 virtual bool Contains(const gfx::Point& screen_point,
233 views::MouseWatcherHost::MouseEventType type);
235 // Overridden from views::View.
236 virtual gfx::Size GetPreferredSize() OVERRIDE;
238 // Overridden from views::Widget::Observer.
239 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
241 // Called from the controller class to indicate that the menu should get
242 // destroyed.
243 virtual void ControllerRequestsCloseAndDelete();
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);
249 // Get the owning MaximizeBubbleController. This might return NULL in case
250 // of an asynchronous shutdown.
251 MaximizeBubbleController* controller() const { return owner_; }
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);
257 private:
258 // True if the shut down has been initiated.
259 bool shutting_down_;
261 // Our owning class.
262 MaximizeBubbleController* owner_;
264 // The widget which contains our menu and the bubble border.
265 views::Widget* bubble_widget_;
267 // The content accessor of the menu.
268 BubbleContentsView* contents_view_;
270 // The bubble border.
271 MaximizeBubbleBorder* bubble_border_;
273 // The rectangle before the animation starts.
274 gfx::Rect initial_position_;
276 // The mouse watcher which takes care of out of window hover events.
277 scoped_ptr<views::MouseWatcher> mouse_watcher_;
279 // The fade delay - if 0 it will show / hide immediately.
280 const int appearance_delay_ms_;
282 DISALLOW_COPY_AND_ASSIGN(Bubble);
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);
291 virtual ~BubbleContentsButtonRow() {}
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);
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);
303 MaximizeBubbleController::Bubble* bubble() { return bubble_; }
305 private:
306 // Functions to add the left and right maximize / restore buttons.
307 void AddMaximizeLeftButton();
308 void AddMaximizeRightButton();
309 void AddMinimizeButton();
311 // The owning object which gets notifications.
312 MaximizeBubbleController::Bubble* bubble_;
314 // The created buttons for our menu.
315 BubbleDialogButton* left_button_;
316 BubbleDialogButton* minimize_button_;
317 BubbleDialogButton* right_button_;
319 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow);
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);
327 virtual ~BubbleContentsView() {}
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);
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);
340 private:
341 // The owning class.
342 MaximizeBubbleController::Bubble* bubble_;
344 // The object which owns all the buttons.
345 BubbleContentsButtonRow* buttons_view_;
347 // The label object which shows the user the selected action.
348 views::Label* label_view_;
350 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
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() {}
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;
370 private:
371 // The creating class which needs to get notified in case of a hover event.
372 BubbleContentsButtonRow* button_row_;
374 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton);
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());
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);
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));
405 SetLayoutManager(new views::BoxLayout(
406 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
408 contents_view_ = new BubbleContentsView(this);
409 AddChildView(contents_view_);
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);
416 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
417 bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
419 bubble_border_ = new MaximizeBubbleBorder(this, anchor_view());
420 GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
421 GetBubbleFrameView()->set_background(NULL);
423 // Recalculate size with new border.
424 SizeToContents();
426 if (!appearance_delay_ms_)
427 GetWidget()->Show();
428 else
429 StartFade(true);
431 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
432 ash::UMA_MAXIMIZE_BUTTON_SHOW_BUBBLE);
434 mouse_watcher_.reset(new views::MouseWatcher(
435 new BubbleMouseWatcherHost(this),
436 this));
437 mouse_watcher_->Start();
440 bool BubbleMouseWatcherHost::Contains(
441 const gfx::Point& screen_point,
442 views::MouseWatcherHost::MouseEventType type) {
443 return bubble_->Contains(screen_point, type);
446 aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() {
447 return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
450 gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() {
451 if (!owner_)
452 return gfx::Rect();
454 gfx::Rect anchor_rect =
455 owner_->frame_maximize_button()->GetBoundsInScreen();
456 return anchor_rect;
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_;
470 rect.set_y(rect.y() + shift);
471 bubble_widget_->GetNativeWindow()->SetBounds(rect);
474 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
475 return bubble_border_ != NULL;
478 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
479 gfx::Path* mask) const {
480 DCHECK(mask);
481 DCHECK(bubble_border_);
482 bubble_border_->GetMask(mask);
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();
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;
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));
520 gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() {
521 return contents_view_->GetPreferredSize();
524 void MaximizeBubbleController::Bubble::OnWidgetDestroying(
525 views::Widget* widget) {
526 if (bubble_widget_ == widget) {
527 mouse_watcher_->Stop();
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;
538 BubbleDelegateView::OnWidgetDestroying(widget);
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;
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);
556 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) {
557 if (contents_view_)
558 contents_view_->SetSnapType(snap_type);
561 views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
562 SnapType state) {
563 return contents_view_->GetButtonForUnitTest(state);
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));
577 if (base::i18n::IsRTL()) {
578 AddMaximizeRightButton();
579 AddMinimizeButton();
580 AddMaximizeLeftButton();
581 } else {
582 AddMaximizeLeftButton();
583 AddMinimizeButton();
584 AddMaximizeRightButton();
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.";
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);
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;
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);
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);
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);
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));
692 buttons_view_ = new BubbleContentsButtonRow(bubble);
693 AddChildView(buttons_view_);
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_);
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;
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;
737 label_view_->SetText(rb.GetLocalizedString(id));
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();
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;
772 void MaximizeBubbleController::SetSnapType(SnapType snap_type) {
773 if (bubble_)
774 bubble_->SetSnapType(snap_type);
777 aura::Window* MaximizeBubbleController::GetBubbleWindow() {
778 return bubble_ ? bubble_->GetBubbleWindow() : NULL;
781 void MaximizeBubbleController::DelayCreation() {
782 if (timer_.get() && timer_->IsRunning())
783 timer_->Reset();
786 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) {
787 frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type);
790 void MaximizeBubbleController::OnButtonHover(SnapType snap_type) {
791 frame_maximize_button_->SnapButtonHovered(snap_type);
794 views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest(
795 SnapType state) {
796 return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL;
799 void MaximizeBubbleController::RequestDestructionThroughOwner() {
800 // Tell the parent to destroy us (if this didn't happen yet).
801 if (timer_.get()) {
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.
809 void MaximizeBubbleController::CreateBubble() {
810 if (!bubble_)
811 bubble_ = new Bubble(this, appearance_delay_ms_);
813 timer_->Stop();
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);
833 void BubbleDialogButton::OnMouseCaptureLost() {
834 button_row_->ButtonHovered(NULL);
835 views::ImageButton::OnMouseCaptureLost();
838 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) {
839 button_row_->ButtonHovered(this);
840 views::ImageButton::OnMouseEntered(event);
843 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) {
844 button_row_->ButtonHovered(NULL);
845 views::ImageButton::OnMouseExited(event);
848 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) {
849 if (!button_row_->bubble()->controller())
850 return false;
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);
860 // Pass the event on to the normal handler.
861 return views::ImageButton::OnMouseDragged(event);
864 } // namespace ash