Enable snappy for IndexedDB.
[chromium-blink-merge.git] / ash / wm / caption_buttons / maximize_bubble_controller.cc
blob413cc0e67682748aea131ccccd430f88eb48c83c
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.
5 #include "ash/wm/caption_buttons/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/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"
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 Bubble(MaximizeBubbleController* owner,
214 int appearance_delay_ms,
215 SnapType initial_snap_type);
216 virtual ~Bubble() {}
218 // The window of the menu under which the SnapSizer will get created.
219 aura::Window* GetBubbleWindow();
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; }
226 // Overridden from views::WidgetDelegateView.
227 virtual bool WidgetHasHitTestMask() const OVERRIDE;
228 virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE;
230 // Implementation of MouseWatcherListener.
231 virtual void MouseMovedOutOfHost() OVERRIDE;
233 // Implementation of MouseWatcherHost.
234 virtual bool Contains(const gfx::Point& screen_point,
235 views::MouseWatcherHost::MouseEventType type);
237 // Overridden from views::View.
238 virtual gfx::Size GetPreferredSize() OVERRIDE;
240 // Overridden from views::Widget::Observer.
241 virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
243 // Called from the controller class to indicate that the menu should get
244 // destroyed.
245 virtual void ControllerRequestsCloseAndDelete();
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);
251 // Get the owning MaximizeBubbleController. This might return NULL in case
252 // of an asynchronous shutdown.
253 MaximizeBubbleController* controller() const { return owner_; }
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);
259 private:
260 // True if the shut down has been initiated.
261 bool shutting_down_;
263 // Our owning class.
264 MaximizeBubbleController* owner_;
266 // The widget which contains our menu and the bubble border.
267 views::Widget* bubble_widget_;
269 // The content accessor of the menu.
270 BubbleContentsView* contents_view_;
272 // The bubble border.
273 MaximizeBubbleBorder* bubble_border_;
275 // The rectangle before the animation starts.
276 gfx::Rect initial_position_;
278 // The mouse watcher which takes care of out of window hover events.
279 scoped_ptr<views::MouseWatcher> mouse_watcher_;
281 // The fade delay - if 0 it will show / hide immediately.
282 const int appearance_delay_ms_;
284 DISALLOW_COPY_AND_ASSIGN(Bubble);
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);
293 virtual ~BubbleContentsButtonRow() {}
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);
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);
305 MaximizeBubbleController::Bubble* bubble() { return bubble_; }
307 private:
308 // Functions to add the left and right maximize / restore buttons.
309 void AddMaximizeLeftButton();
310 void AddMaximizeRightButton();
311 void AddMinimizeButton();
313 // The owning object which gets notifications.
314 MaximizeBubbleController::Bubble* bubble_;
316 // The created buttons for our menu.
317 BubbleDialogButton* left_button_;
318 BubbleDialogButton* minimize_button_;
319 BubbleDialogButton* right_button_;
321 DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow);
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);
330 virtual ~BubbleContentsView() {}
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);
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);
343 private:
344 // The owning class.
345 MaximizeBubbleController::Bubble* bubble_;
347 // The object which owns all the buttons.
348 BubbleContentsButtonRow* buttons_view_;
350 // The label object which shows the user the selected action.
351 views::Label* label_view_;
353 DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
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() {}
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;
373 private:
374 // The creating class which needs to get notified in case of a hover event.
375 BubbleContentsButtonRow* button_row_;
377 DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton);
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());
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);
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));
409 SetLayoutManager(new views::BoxLayout(
410 views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
412 contents_view_ = new BubbleContentsView(this, initial_snap_type);
413 AddChildView(contents_view_);
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);
420 SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
421 bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
423 bubble_border_ = new MaximizeBubbleBorder(this, GetAnchorView());
424 GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
425 GetBubbleFrameView()->set_background(NULL);
427 // Recalculate size with new border.
428 SizeToContents();
430 if (!appearance_delay_ms_)
431 GetWidget()->Show();
432 else
433 StartFade(true);
435 ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
436 ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE);
438 mouse_watcher_.reset(new views::MouseWatcher(
439 new BubbleMouseWatcherHost(this),
440 this));
441 mouse_watcher_->Start();
444 bool BubbleMouseWatcherHost::Contains(
445 const gfx::Point& screen_point,
446 views::MouseWatcherHost::MouseEventType type) {
447 return bubble_->Contains(screen_point, type);
450 aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() {
451 return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
454 gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() {
455 if (!owner_)
456 return gfx::Rect();
458 gfx::Rect anchor_rect =
459 owner_->frame_maximize_button()->GetBoundsInScreen();
460 return anchor_rect;
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_;
474 rect.set_y(rect.y() + shift);
475 bubble_widget_->GetNativeWindow()->SetBounds(rect);
478 bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
479 return bubble_border_ != NULL;
482 void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
483 gfx::Path* mask) const {
484 DCHECK(mask);
485 DCHECK(bubble_border_);
486 bubble_border_->GetMask(mask);
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();
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;
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));
524 gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() {
525 return contents_view_->GetPreferredSize();
528 void MaximizeBubbleController::Bubble::OnWidgetDestroying(
529 views::Widget* widget) {
530 if (bubble_widget_ == widget) {
531 mouse_watcher_->Stop();
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;
542 BubbleDelegateView::OnWidgetDestroying(widget);
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;
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);
560 void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) {
561 if (contents_view_)
562 contents_view_->SetSnapType(snap_type);
565 views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
566 SnapType state) {
567 return contents_view_->GetButtonForUnitTest(state);
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));
581 if (base::i18n::IsRTL()) {
582 AddMaximizeRightButton();
583 AddMinimizeButton();
584 AddMaximizeLeftButton();
585 } else {
586 AddMaximizeLeftButton();
587 AddMinimizeButton();
588 AddMaximizeRightButton();
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.";
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);
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;
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);
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);
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);
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));
697 buttons_view_ = new BubbleContentsButtonRow(bubble);
698 AddChildView(buttons_view_);
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_);
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;
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;
742 label_view_->SetText(rb.GetLocalizedString(id));
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();
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;
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;
788 aura::Window* MaximizeBubbleController::GetBubbleWindow() {
789 return bubble_ ? bubble_->GetBubbleWindow() : NULL;
792 void MaximizeBubbleController::DelayCreation() {
793 if (timer_.get() && timer_->IsRunning())
794 timer_->Reset();
797 void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) {
798 frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type);
801 void MaximizeBubbleController::OnButtonHover(SnapType snap_type) {
802 frame_maximize_button_->SnapButtonHovered(snap_type);
805 views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest(
806 SnapType state) {
807 return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL;
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.
820 void MaximizeBubbleController::CreateBubble() {
821 if (!bubble_)
822 bubble_ = new Bubble(this, appearance_delay_ms_, snap_type_for_creation_);
824 timer_->Stop();
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);
844 void BubbleDialogButton::OnMouseCaptureLost() {
845 button_row_->ButtonHovered(NULL);
846 views::ImageButton::OnMouseCaptureLost();
849 void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) {
850 button_row_->ButtonHovered(this);
851 views::ImageButton::OnMouseEntered(event);
854 void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) {
855 button_row_->ButtonHovered(NULL);
856 views::ImageButton::OnMouseExited(event);
859 bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) {
860 if (!button_row_->bubble()->controller())
861 return false;
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);
871 // Pass the event on to the normal handler.
872 return views::ImageButton::OnMouseDragged(event);
875 } // namespace ash