1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/wm/maximize_bubble_controller.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "ash/shell_window_ids.h"
#include "ash/wm/window_animations.h"
#include "ash/wm/workspace/frame_maximize_button.h"
#include "base/timer/timer.h"
#include "grit/ash_resources.h"
#include "grit/ash_strings.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/aura/window.h"
#include "ui/base/animation/animation.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/path.h"
#include "ui/gfx/screen.h"
#include "ui/views/bubble/bubble_delegate.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/mouse_watcher.h"
#include "ui/views/widget/widget.h"
namespace {
// The spacing between two buttons.
const int kLayoutSpacing = -1;
// The background color.
const SkColor kBubbleBackgroundColor = 0xFF141414;
// The text color within the bubble.
const SkColor kBubbleTextColor = SK_ColorWHITE;
// The line width of the bubble.
const int kLineWidth = 1;
// The spacing for the top and bottom of the info label.
const int kLabelSpacing = 4;
// The pixel dimensions of the arrow.
const int kArrowHeight = 10;
const int kArrowWidth = 20;
// The animation offset in y for the bubble when appearing.
const int kBubbleAnimationOffsetY = 5;
class MaximizeBubbleBorder : public views::BubbleBorder {
public:
MaximizeBubbleBorder(views::View* content_view, views::View* anchor);
virtual ~MaximizeBubbleBorder() {}
// Get the mouse active area of the window.
void GetMask(gfx::Path* mask);
// Overridden from views::BubbleBorder to match the design specs.
virtual gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
const gfx::Size& contents_size) const OVERRIDE;
// Overridden from views::Border.
virtual void Paint(const views::View& view, gfx::Canvas* canvas) OVERRIDE;
private:
// Note: Animations can continue after then main window frame was destroyed.
// To avoid this problem, the owning screen metrics get extracted upon
// creation.
gfx::Size anchor_size_;
gfx::Point anchor_screen_origin_;
views::View* content_view_;
DISALLOW_COPY_AND_ASSIGN(MaximizeBubbleBorder);
};
MaximizeBubbleBorder::MaximizeBubbleBorder(views::View* content_view,
views::View* anchor)
: views::BubbleBorder(views::BubbleBorder::TOP_RIGHT,
views::BubbleBorder::NO_SHADOW,
kBubbleBackgroundColor),
anchor_size_(anchor->size()),
anchor_screen_origin_(0, 0),
content_view_(content_view) {
views::View::ConvertPointToScreen(anchor, &anchor_screen_origin_);
set_alignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
}
void MaximizeBubbleBorder::GetMask(gfx::Path* mask) {
gfx::Insets inset = GetInsets();
// Note: Even though the tip could be added as activatable, it is left out
// since it would not change the action behavior in any way plus it makes
// more sense to keep the focus on the underlying button for clicks.
int left = inset.left() - kLineWidth;
int right = inset.left() + content_view_->width() + kLineWidth;
int top = inset.top() - kLineWidth;
int bottom = inset.top() + content_view_->height() + kLineWidth;
mask->moveTo(left, top);
mask->lineTo(right, top);
mask->lineTo(right, bottom);
mask->lineTo(left, bottom);
mask->lineTo(left, top);
mask->close();
}
gfx::Rect MaximizeBubbleBorder::GetBounds(
const gfx::Rect& position_relative_to,
const gfx::Size& contents_size) const {
gfx::Size border_size(contents_size);
gfx::Insets insets = GetInsets();
border_size.Enlarge(insets.width(), insets.height());
// Position the bubble to center the box on the anchor.
int x = (-border_size.width() + anchor_size_.width()) / 2;
// Position the bubble under the anchor, overlapping the arrow with it.
int y = anchor_size_.height() - insets.top();
gfx::Point view_origin(x + anchor_screen_origin_.x(),
y + anchor_screen_origin_.y());
return gfx::Rect(view_origin, border_size);
}
void MaximizeBubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) {
gfx::Insets inset = GetInsets();
// Draw the border line around everything.
int y = inset.top();
// Top
canvas->FillRect(gfx::Rect(inset.left(),
y - kLineWidth,
content_view_->width(),
kLineWidth),
kBubbleBackgroundColor);
// Bottom
canvas->FillRect(gfx::Rect(inset.left(),
y + content_view_->height(),
content_view_->width(),
kLineWidth),
kBubbleBackgroundColor);
// Left
canvas->FillRect(gfx::Rect(inset.left() - kLineWidth,
y - kLineWidth,
kLineWidth,
content_view_->height() + 2 * kLineWidth),
kBubbleBackgroundColor);
// Right
canvas->FillRect(gfx::Rect(inset.left() + content_view_->width(),
y - kLineWidth,
kLineWidth,
content_view_->height() + 2 * kLineWidth),
kBubbleBackgroundColor);
// Draw the arrow afterwards covering the border.
SkPath path;
path.incReserve(4);
// The center of the tip should be in the middle of the button.
int tip_x = inset.left() + content_view_->width() / 2;
int left_base_x = tip_x - kArrowWidth / 2;
int left_base_y = y;
int tip_y = left_base_y - kArrowHeight;
path.moveTo(SkIntToScalar(left_base_x), SkIntToScalar(left_base_y));
path.lineTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
path.lineTo(SkIntToScalar(left_base_x + kArrowWidth),
SkIntToScalar(left_base_y));
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor(kBubbleBackgroundColor);
canvas->DrawPath(path, paint);
}
} // namespace
namespace ash {
class BubbleContentsButtonRow;
class BubbleContentsView;
class BubbleDialogButton;
// The mouse watcher host which makes sure that the bubble does not get closed
// while the mouse cursor is over the maximize button or the balloon content.
// Note: This object gets destroyed when the MouseWatcher gets destroyed.
class BubbleMouseWatcherHost: public views::MouseWatcherHost {
public:
explicit BubbleMouseWatcherHost(MaximizeBubbleController::Bubble* bubble)
: bubble_(bubble) {}
virtual ~BubbleMouseWatcherHost() {}
// Implementation of MouseWatcherHost.
virtual bool Contains(const gfx::Point& screen_point,
views::MouseWatcherHost::MouseEventType type) OVERRIDE;
private:
MaximizeBubbleController::Bubble* bubble_;
DISALLOW_COPY_AND_ASSIGN(BubbleMouseWatcherHost);
};
// The class which creates and manages the bubble menu element.
// It creates a 'bubble border' and the content accordingly.
// Note: Since the SnapSizer will show animations on top of the maximize button
// this menu gets created as a separate window and the SnapSizer will be
// created underneath this window.
class MaximizeBubbleController::Bubble : public views::BubbleDelegateView,
public views::MouseWatcherListener {
public:
explicit Bubble(MaximizeBubbleController* owner, int appearance_delay_ms_);
virtual ~Bubble() {}
// The window of the menu under which the SnapSizer will get created.
aura::Window* GetBubbleWindow();
// Overridden from views::BubbleDelegateView.
virtual gfx::Rect GetAnchorRect() OVERRIDE;
virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
virtual bool CanActivate() const OVERRIDE { return false; }
// Overridden from views::WidgetDelegateView.
virtual bool WidgetHasHitTestMask() const OVERRIDE;
virtual void GetWidgetHitTestMask(gfx::Path* mask) const OVERRIDE;
// Implementation of MouseWatcherListener.
virtual void MouseMovedOutOfHost() OVERRIDE;
// Implementation of MouseWatcherHost.
virtual bool Contains(const gfx::Point& screen_point,
views::MouseWatcherHost::MouseEventType type);
// Overridden from views::View.
virtual gfx::Size GetPreferredSize() OVERRIDE;
// Overridden from views::Widget::Observer.
virtual void OnWidgetDestroying(views::Widget* widget) OVERRIDE;
// Called from the controller class to indicate that the menu should get
// destroyed.
virtual void ControllerRequestsCloseAndDelete();
// Called from the owning class to change the menu content to the given
// |snap_type| so that the user knows what is selected.
void SetSnapType(SnapType snap_type);
// Get the owning MaximizeBubbleController. This might return NULL in case
// of an asynchronous shutdown.
MaximizeBubbleController* controller() const { return owner_; }
// Added for unit test: Retrieve the button for an action.
// |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
views::CustomButton* GetButtonForUnitTest(SnapType state);
private:
// True if the shut down has been initiated.
bool shutting_down_;
// Our owning class.
MaximizeBubbleController* owner_;
// The widget which contains our menu and the bubble border.
views::Widget* bubble_widget_;
// The content accessor of the menu.
BubbleContentsView* contents_view_;
// The bubble border.
MaximizeBubbleBorder* bubble_border_;
// The rectangle before the animation starts.
gfx::Rect initial_position_;
// The mouse watcher which takes care of out of window hover events.
scoped_ptr<views::MouseWatcher> mouse_watcher_;
// The fade delay - if 0 it will show / hide immediately.
const int appearance_delay_ms_;
DISALLOW_COPY_AND_ASSIGN(Bubble);
};
// A class that creates all buttons and put them into a view.
class BubbleContentsButtonRow : public views::View,
public views::ButtonListener {
public:
explicit BubbleContentsButtonRow(MaximizeBubbleController::Bubble* bubble);
virtual ~BubbleContentsButtonRow() {}
// Overridden from ButtonListener.
virtual void ButtonPressed(views::Button* sender,
const ui::Event& event) OVERRIDE;
// Called from BubbleDialogButton.
void ButtonHovered(BubbleDialogButton* sender);
// Added for unit test: Retrieve the button for an action.
// |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
views::CustomButton* GetButtonForUnitTest(SnapType state);
MaximizeBubbleController::Bubble* bubble() { return bubble_; }
private:
// Functions to add the left and right maximize / restore buttons.
void AddMaximizeLeftButton();
void AddMaximizeRightButton();
void AddMinimizeButton();
// The owning object which gets notifications.
MaximizeBubbleController::Bubble* bubble_;
// The created buttons for our menu.
BubbleDialogButton* left_button_;
BubbleDialogButton* minimize_button_;
BubbleDialogButton* right_button_;
DISALLOW_COPY_AND_ASSIGN(BubbleContentsButtonRow);
};
// A class which creates the content of the bubble: The buttons, and the label.
class BubbleContentsView : public views::View {
public:
explicit BubbleContentsView(MaximizeBubbleController::Bubble* bubble);
virtual ~BubbleContentsView() {}
// Set the label content to reflect the currently selected |snap_type|.
// This function can be executed through the frame maximize button as well as
// through hover operations.
void SetSnapType(SnapType snap_type);
// Added for unit test: Retrieve the button for an action.
// |state| can be either SNAP_LEFT, SNAP_RIGHT or SNAP_MINIMIZE.
views::CustomButton* GetButtonForUnitTest(SnapType state) {
return buttons_view_->GetButtonForUnitTest(state);
}
private:
// The owning class.
MaximizeBubbleController::Bubble* bubble_;
// The object which owns all the buttons.
BubbleContentsButtonRow* buttons_view_;
// The label object which shows the user the selected action.
views::Label* label_view_;
DISALLOW_COPY_AND_ASSIGN(BubbleContentsView);
};
// The image button gets overridden to be able to capture mouse hover events.
// The constructor also assigns all button states and
class BubbleDialogButton : public views::ImageButton {
public:
explicit BubbleDialogButton(
BubbleContentsButtonRow* button_row_listener,
int normal_image,
int hovered_image,
int pressed_image);
virtual ~BubbleDialogButton() {}
// CustomButton overrides:
virtual void OnMouseCaptureLost() OVERRIDE;
virtual void OnMouseEntered(const ui::MouseEvent& event) OVERRIDE;
virtual void OnMouseExited(const ui::MouseEvent& event) OVERRIDE;
virtual bool OnMouseDragged(const ui::MouseEvent& event) OVERRIDE;
private:
// The creating class which needs to get notified in case of a hover event.
BubbleContentsButtonRow* button_row_;
DISALLOW_COPY_AND_ASSIGN(BubbleDialogButton);
};
MaximizeBubbleController::Bubble::Bubble(
MaximizeBubbleController* owner,
int appearance_delay_ms)
: views::BubbleDelegateView(owner->frame_maximize_button(),
views::BubbleBorder::TOP_RIGHT),
shutting_down_(false),
owner_(owner),
bubble_widget_(NULL),
contents_view_(NULL),
bubble_border_(NULL),
appearance_delay_ms_(appearance_delay_ms) {
set_margins(gfx::Insets());
// The window needs to be owned by the root so that the SnapSizer does not
// cover it upon animation.
aura::Window* parent = Shell::GetContainer(
Shell::GetActiveRootWindow(),
internal::kShellWindowId_ShelfContainer);
set_parent_window(parent);
set_notify_enter_exit_on_child(true);
set_adjust_if_offscreen(false);
SetPaintToLayer(true);
set_color(kBubbleBackgroundColor);
set_close_on_deactivate(false);
set_background(
views::Background::CreateSolidBackground(kBubbleBackgroundColor));
SetLayoutManager(new views::BoxLayout(
views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
contents_view_ = new BubbleContentsView(this);
AddChildView(contents_view_);
// Note that the returned widget has an observer which points to our
// functions.
bubble_widget_ = views::BubbleDelegateView::CreateBubble(this);
bubble_widget_->set_focus_on_creation(false);
SetAlignment(views::BubbleBorder::ALIGN_EDGE_TO_ANCHOR_EDGE);
bubble_widget_->non_client_view()->frame_view()->set_background(NULL);
bubble_border_ = new MaximizeBubbleBorder(this, anchor_view());
GetBubbleFrameView()->SetBubbleBorder(bubble_border_);
GetBubbleFrameView()->set_background(NULL);
// Recalculate size with new border.
SizeToContents();
if (!appearance_delay_ms_)
GetWidget()->Show();
else
StartFade(true);
ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(
ash::UMA_WINDOW_MAXIMIZE_BUTTON_SHOW_BUBBLE);
mouse_watcher_.reset(new views::MouseWatcher(
new BubbleMouseWatcherHost(this),
this));
mouse_watcher_->Start();
}
bool BubbleMouseWatcherHost::Contains(
const gfx::Point& screen_point,
views::MouseWatcherHost::MouseEventType type) {
return bubble_->Contains(screen_point, type);
}
aura::Window* MaximizeBubbleController::Bubble::GetBubbleWindow() {
return bubble_widget_ ? bubble_widget_->GetNativeWindow() : NULL;
}
gfx::Rect MaximizeBubbleController::Bubble::GetAnchorRect() {
if (!owner_)
return gfx::Rect();
gfx::Rect anchor_rect =
owner_->frame_maximize_button()->GetBoundsInScreen();
return anchor_rect;
}
void MaximizeBubbleController::Bubble::AnimationProgressed(
const ui::Animation* animation) {
// First do everything needed for the fade by calling the base function.
BubbleDelegateView::AnimationProgressed(animation);
// When fading in we are done.
if (!shutting_down_)
return;
// Upon fade out an additional shift is required.
int shift = animation->CurrentValueBetween(kBubbleAnimationOffsetY, 0);
gfx::Rect rect = initial_position_;
rect.set_y(rect.y() + shift);
bubble_widget_->GetNativeWindow()->SetBounds(rect);
}
bool MaximizeBubbleController::Bubble::WidgetHasHitTestMask() const {
return bubble_border_ != NULL;
}
void MaximizeBubbleController::Bubble::GetWidgetHitTestMask(
gfx::Path* mask) const {
DCHECK(mask);
DCHECK(bubble_border_);
bubble_border_->GetMask(mask);
}
void MaximizeBubbleController::Bubble::MouseMovedOutOfHost() {
if (!owner_ || shutting_down_)
return;
// When we leave the bubble, we might be still be in gesture mode or over
// the maximize button. So only close if none of the other cases apply.
if (!owner_->frame_maximize_button()->is_snap_enabled()) {
gfx::Point screen_location = Shell::GetScreen()->GetCursorScreenPoint();
if (!owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
screen_location)) {
owner_->RequestDestructionThroughOwner();
}
}
}
bool MaximizeBubbleController::Bubble::Contains(
const gfx::Point& screen_point,
views::MouseWatcherHost::MouseEventType type) {
if (!owner_ || shutting_down_)
return false;
bool inside_button =
owner_->frame_maximize_button()->GetBoundsInScreen().Contains(
screen_point);
if (!owner_->frame_maximize_button()->is_snap_enabled() && inside_button) {
SetSnapType(controller()->maximize_type() == FRAME_STATE_FULL ?
SNAP_RESTORE : SNAP_MAXIMIZE);
return true;
}
// Check if either a gesture is taking place (=> bubble stays no matter what
// the mouse does) or the mouse is over the maximize button or the bubble
// content.
return (owner_->frame_maximize_button()->is_snap_enabled() ||
inside_button ||
contents_view_->GetBoundsInScreen().Contains(screen_point));
}
gfx::Size MaximizeBubbleController::Bubble::GetPreferredSize() {
return contents_view_->GetPreferredSize();
}
void MaximizeBubbleController::Bubble::OnWidgetDestroying(
views::Widget* widget) {
if (bubble_widget_ == widget) {
mouse_watcher_->Stop();
if (owner_) {
// If the bubble destruction was triggered by some other external
// influence then ourselves, the owner needs to be informed that the menu
// is gone.
shutting_down_ = true;
owner_->RequestDestructionThroughOwner();
owner_ = NULL;
}
}
BubbleDelegateView::OnWidgetDestroying(widget);
}
void MaximizeBubbleController::Bubble::ControllerRequestsCloseAndDelete() {
// This only gets called from the owning base class once it is deleted.
if (shutting_down_)
return;
shutting_down_ = true;
owner_ = NULL;
// Close the widget asynchronously after the hide animation is finished.
initial_position_ = bubble_widget_->GetNativeWindow()->bounds();
if (!appearance_delay_ms_)
bubble_widget_->CloseNow();
else
StartFade(false);
}
void MaximizeBubbleController::Bubble::SetSnapType(SnapType snap_type) {
if (contents_view_)
contents_view_->SetSnapType(snap_type);
}
views::CustomButton* MaximizeBubbleController::Bubble::GetButtonForUnitTest(
SnapType state) {
return contents_view_->GetButtonForUnitTest(state);
}
BubbleContentsButtonRow::BubbleContentsButtonRow(
MaximizeBubbleController::Bubble* bubble)
: bubble_(bubble),
left_button_(NULL),
minimize_button_(NULL),
right_button_(NULL) {
SetLayoutManager(new views::BoxLayout(
views::BoxLayout::kHorizontal, 0, 0, kLayoutSpacing));
set_background(
views::Background::CreateSolidBackground(kBubbleBackgroundColor));
if (base::i18n::IsRTL()) {
AddMaximizeRightButton();
AddMinimizeButton();
AddMaximizeLeftButton();
} else {
AddMaximizeLeftButton();
AddMinimizeButton();
AddMaximizeRightButton();
}
}
// Overridden from ButtonListener.
void BubbleContentsButtonRow::ButtonPressed(views::Button* sender,
const ui::Event& event) {
// While shutting down, the connection to the owner might already be broken.
if (!bubble_->controller())
return;
if (sender == left_button_)
bubble_->controller()->OnButtonClicked(
bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
SNAP_RESTORE : SNAP_LEFT);
else if (sender == minimize_button_)
bubble_->controller()->OnButtonClicked(SNAP_MINIMIZE);
else if (sender == right_button_)
bubble_->controller()->OnButtonClicked(
bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
SNAP_RESTORE : SNAP_RIGHT);
else
NOTREACHED() << "Unknown button pressed.";
}
// Called from BubbleDialogButton.
void BubbleContentsButtonRow::ButtonHovered(BubbleDialogButton* sender) {
// While shutting down, the connection to the owner might already be broken.
if (!bubble_->controller())
return;
if (sender == left_button_)
bubble_->controller()->OnButtonHover(
bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT ?
SNAP_RESTORE : SNAP_LEFT);
else if (sender == minimize_button_)
bubble_->controller()->OnButtonHover(SNAP_MINIMIZE);
else if (sender == right_button_)
bubble_->controller()->OnButtonHover(
bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT ?
SNAP_RESTORE : SNAP_RIGHT);
else
bubble_->controller()->OnButtonHover(SNAP_NONE);
}
views::CustomButton* BubbleContentsButtonRow::GetButtonForUnitTest(
SnapType state) {
switch (state) {
case SNAP_LEFT:
return left_button_;
case SNAP_MINIMIZE:
return minimize_button_;
case SNAP_RIGHT:
return right_button_;
default:
NOTREACHED();
return NULL;
}
}
void BubbleContentsButtonRow::AddMaximizeLeftButton() {
if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_LEFT) {
left_button_ = new BubbleDialogButton(
this,
IDR_AURA_WINDOW_POSITION_LEFT_RESTORE,
IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_H,
IDR_AURA_WINDOW_POSITION_LEFT_RESTORE_P);
} else {
left_button_ = new BubbleDialogButton(
this,
IDR_AURA_WINDOW_POSITION_LEFT,
IDR_AURA_WINDOW_POSITION_LEFT_H,
IDR_AURA_WINDOW_POSITION_LEFT_P);
}
}
void BubbleContentsButtonRow::AddMaximizeRightButton() {
if (bubble_->controller()->maximize_type() == FRAME_STATE_SNAP_RIGHT) {
right_button_ = new BubbleDialogButton(
this,
IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE,
IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_H,
IDR_AURA_WINDOW_POSITION_RIGHT_RESTORE_P);
} else {
right_button_ = new BubbleDialogButton(
this,
IDR_AURA_WINDOW_POSITION_RIGHT,
IDR_AURA_WINDOW_POSITION_RIGHT_H,
IDR_AURA_WINDOW_POSITION_RIGHT_P);
}
}
void BubbleContentsButtonRow::AddMinimizeButton() {
minimize_button_ = new BubbleDialogButton(
this,
IDR_AURA_WINDOW_POSITION_MIDDLE,
IDR_AURA_WINDOW_POSITION_MIDDLE_H,
IDR_AURA_WINDOW_POSITION_MIDDLE_P);
}
BubbleContentsView::BubbleContentsView(
MaximizeBubbleController::Bubble* bubble)
: bubble_(bubble),
buttons_view_(NULL),
label_view_(NULL) {
SetLayoutManager(new views::BoxLayout(
views::BoxLayout::kVertical, 0, 0, kLayoutSpacing));
set_background(
views::Background::CreateSolidBackground(kBubbleBackgroundColor));
buttons_view_ = new BubbleContentsButtonRow(bubble);
AddChildView(buttons_view_);
label_view_ = new views::Label();
SetSnapType(SNAP_NONE);
label_view_->SetBackgroundColor(kBubbleBackgroundColor);
label_view_->SetEnabledColor(kBubbleTextColor);
label_view_->set_border(views::Border::CreateEmptyBorder(
kLabelSpacing, 0, kLabelSpacing, 0));
AddChildView(label_view_);
}
// Set the label content to reflect the currently selected |snap_type|.
// This function can be executed through the frame maximize button as well as
// through hover operations.
void BubbleContentsView::SetSnapType(SnapType snap_type) {
if (!bubble_->controller())
return;
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
int id = 0;
switch (snap_type) {
case SNAP_LEFT:
id = IDS_ASH_SNAP_WINDOW_LEFT;
break;
case SNAP_RIGHT:
id = IDS_ASH_SNAP_WINDOW_RIGHT;
break;
case SNAP_MAXIMIZE:
DCHECK_NE(FRAME_STATE_FULL, bubble_->controller()->maximize_type());
id = IDS_ASH_MAXIMIZE_WINDOW;
break;
case SNAP_MINIMIZE:
id = IDS_ASH_MINIMIZE_WINDOW;
break;
case SNAP_RESTORE:
DCHECK_NE(FRAME_STATE_NONE, bubble_->controller()->maximize_type());
id = IDS_ASH_RESTORE_WINDOW;
break;
default:
// If nothing is selected, we automatically select the click operation.
id = bubble_->controller()->maximize_type() == FRAME_STATE_FULL ?
IDS_ASH_RESTORE_WINDOW : IDS_ASH_MAXIMIZE_WINDOW;
break;
}
label_view_->SetText(rb.GetLocalizedString(id));
}
MaximizeBubbleController::MaximizeBubbleController(
FrameMaximizeButton* frame_maximize_button,
MaximizeBubbleFrameState maximize_type,
int appearance_delay_ms)
: frame_maximize_button_(frame_maximize_button),
bubble_(NULL),
maximize_type_(maximize_type),
appearance_delay_ms_(appearance_delay_ms) {
// Create the task which will create the bubble delayed.
base::OneShotTimer<MaximizeBubbleController>* new_timer =
new base::OneShotTimer<MaximizeBubbleController>();
// Note: Even if there was no delay time given, we need to have a timer.
new_timer->Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(
appearance_delay_ms_ ? appearance_delay_ms_ : 10),
this,
&MaximizeBubbleController::CreateBubble);
timer_.reset(new_timer);
if (!appearance_delay_ms_)
CreateBubble();
}
MaximizeBubbleController::~MaximizeBubbleController() {
// Note: The destructor only gets initiated through the owner.
timer_.reset();
if (bubble_) {
bubble_->ControllerRequestsCloseAndDelete();
bubble_ = NULL;
}
}
void MaximizeBubbleController::SetSnapType(SnapType snap_type) {
if (bubble_)
bubble_->SetSnapType(snap_type);
}
aura::Window* MaximizeBubbleController::GetBubbleWindow() {
return bubble_ ? bubble_->GetBubbleWindow() : NULL;
}
void MaximizeBubbleController::DelayCreation() {
if (timer_.get() && timer_->IsRunning())
timer_->Reset();
}
void MaximizeBubbleController::OnButtonClicked(SnapType snap_type) {
frame_maximize_button_->ExecuteSnapAndCloseMenu(snap_type);
}
void MaximizeBubbleController::OnButtonHover(SnapType snap_type) {
frame_maximize_button_->SnapButtonHovered(snap_type);
}
views::CustomButton* MaximizeBubbleController::GetButtonForUnitTest(
SnapType state) {
return bubble_ ? bubble_->GetButtonForUnitTest(state) : NULL;
}
void MaximizeBubbleController::RequestDestructionThroughOwner() {
// Tell the parent to destroy us (if this didn't happen yet).
if (timer_) {
timer_.reset(NULL);
// Informs the owner that the menu is gone and requests |this| destruction.
frame_maximize_button_->DestroyMaximizeMenu();
// Note: After this call |this| is destroyed.
}
}
void MaximizeBubbleController::CreateBubble() {
if (!bubble_)
bubble_ = new Bubble(this, appearance_delay_ms_);
timer_->Stop();
}
BubbleDialogButton::BubbleDialogButton(
BubbleContentsButtonRow* button_row,
int normal_image,
int hovered_image,
int pressed_image)
: views::ImageButton(button_row),
button_row_(button_row) {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
SetImage(views::CustomButton::STATE_NORMAL,
rb.GetImageSkiaNamed(normal_image));
SetImage(views::CustomButton::STATE_HOVERED,
rb.GetImageSkiaNamed(hovered_image));
SetImage(views::CustomButton::STATE_PRESSED,
rb.GetImageSkiaNamed(pressed_image));
button_row->AddChildView(this);
}
void BubbleDialogButton::OnMouseCaptureLost() {
button_row_->ButtonHovered(NULL);
views::ImageButton::OnMouseCaptureLost();
}
void BubbleDialogButton::OnMouseEntered(const ui::MouseEvent& event) {
button_row_->ButtonHovered(this);
views::ImageButton::OnMouseEntered(event);
}
void BubbleDialogButton::OnMouseExited(const ui::MouseEvent& event) {
button_row_->ButtonHovered(NULL);
views::ImageButton::OnMouseExited(event);
}
bool BubbleDialogButton::OnMouseDragged(const ui::MouseEvent& event) {
if (!button_row_->bubble()->controller())
return false;
// Remove the phantom window when we leave the button.
gfx::Point screen_location(event.location());
View::ConvertPointToScreen(this, &screen_location);
if (!GetBoundsInScreen().Contains(screen_location))
button_row_->ButtonHovered(NULL);
else
button_row_->ButtonHovered(this);
// Pass the event on to the normal handler.
return views::ImageButton::OnMouseDragged(event);
}
} // namespace ash
|