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/frame_caption_button_container_view.h"
6
7 #include "ash/ash_switches.h"
8 #include "ash/metrics/user_metrics_recorder.h"
9 #include "ash/shell.h"
10 #include "ash/wm/caption_buttons/alternate_frame_size_button.h"
11 #include "ash/wm/caption_buttons/frame_caption_button.h"
12 #include "ash/wm/caption_buttons/frame_maximize_button.h"
13 #include "grit/ash_resources.h"
14 #include "grit/ui_strings.h" // Accessibility names
15 #include "ui/base/hit_test.h"
16 #include "ui/base/l10n/l10n_util.h"
17 #include "ui/base/resource/resource_bundle.h"
18 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/point.h"
22 #include "ui/views/controls/button/image_button.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
25
26 namespace ash {
27
28 namespace {
29
30 // The distance between buttons.
31 const int kDistanceBetweenButtons = -1;
32
33 // Converts |point| from |src| to |dst| and hittests against |dst|.
ConvertPointToViewAndHitTest(const views::View * src,const views::View * dst,const gfx::Point & point)34 bool ConvertPointToViewAndHitTest(const views::View* src,
35 const views::View* dst,
36 const gfx::Point& point) {
37 gfx::Point converted(point);
38 views::View::ConvertPointToTarget(src, dst, &converted);
39 return dst->HitTestPoint(converted);
40 }
41
42 } // namespace
43
44 // static
45 const char FrameCaptionButtonContainerView::kViewClassName[] =
46 "FrameCaptionButtonContainerView";
47
FrameCaptionButtonContainerView(views::Widget * frame,MinimizeAllowed minimize_allowed)48 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
49 views::Widget* frame,
50 MinimizeAllowed minimize_allowed)
51 : frame_(frame),
52 header_style_(HEADER_STYLE_SHORT),
53 minimize_button_(NULL),
54 size_button_(NULL),
55 close_button_(NULL) {
56 bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
57
58 // Insert the buttons left to right.
59 minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE);
60 minimize_button_->SetAccessibleName(
61 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
62 // Hide |minimize_button_| when using the non-alternate button style because
63 // |size_button_| is capable of minimizing in this case.
64 minimize_button_->SetVisible(
65 minimize_allowed == MINIMIZE_ALLOWED &&
66 (alternate_style || !frame_->widget_delegate()->CanMaximize()));
67 AddChildView(minimize_button_);
68
69 if (alternate_style)
70 size_button_ = new AlternateFrameSizeButton(this, frame, this);
71 else
72 size_button_ = new FrameMaximizeButton(this, frame);
73 size_button_->SetAccessibleName(
74 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
75 size_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
76 AddChildView(size_button_);
77
78 close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE);
79 close_button_->SetAccessibleName(
80 l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
81 AddChildView(close_button_);
82
83 button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
84 IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia();
85 }
86
~FrameCaptionButtonContainerView()87 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
88 }
89
90 FrameMaximizeButton*
GetOldStyleSizeButton()91 FrameCaptionButtonContainerView::GetOldStyleSizeButton() {
92 return switches::UseAlternateFrameCaptionButtonStyle() ?
93 NULL : static_cast<FrameMaximizeButton*>(size_button_);
94 }
95
ResetWindowControls()96 void FrameCaptionButtonContainerView::ResetWindowControls() {
97 SetButtonsToNormal(ANIMATE_NO);
98 }
99
NonClientHitTest(const gfx::Point & point) const100 int FrameCaptionButtonContainerView::NonClientHitTest(
101 const gfx::Point& point) const {
102 if (close_button_->visible() &&
103 ConvertPointToViewAndHitTest(this, close_button_, point)) {
104 return HTCLOSE;
105 } else if (size_button_->visible() &&
106 ConvertPointToViewAndHitTest(this, size_button_, point)) {
107 return HTMAXBUTTON;
108 } else if (minimize_button_->visible() &&
109 ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
110 return HTMINBUTTON;
111 }
112 return HTNOWHERE;
113 }
114
GetPreferredSize()115 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
116 int width = 0;
117 bool first_visible = true;
118 for (int i = 0; i < child_count(); ++i) {
119 views::View* child = child_at(i);
120 if (!child->visible())
121 continue;
122
123 width += child_at(i)->GetPreferredSize().width();
124 if (!first_visible)
125 width += kDistanceBetweenButtons;
126 first_visible = false;
127 }
128 return gfx::Size(width, close_button_->GetPreferredSize().height());
129 }
130
Layout()131 void FrameCaptionButtonContainerView::Layout() {
132 FrameCaptionButton::Style style = FrameCaptionButton::STYLE_SHORT_RESTORED;
133 if (header_style_ == HEADER_STYLE_SHORT) {
134 if (frame_->IsMaximized() || frame_->IsFullscreen())
135 style = FrameCaptionButton::STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN;
136 // Else: FrameCaptionButton::STYLE_SHORT_RESTORED;
137 } else {
138 style = FrameCaptionButton::STYLE_TALL_RESTORED;
139 }
140
141 minimize_button_->SetStyle(style);
142 size_button_->SetStyle(style);
143 close_button_->SetStyle(style);
144
145 int x = 0;
146 for (int i = 0; i < child_count(); ++i) {
147 views::View* child = child_at(i);
148 if (!child->visible())
149 continue;
150
151 gfx::Size size = child->GetPreferredSize();
152 child->SetBounds(x, 0, size.width(), size.height());
153 x += size.width() + kDistanceBetweenButtons;
154 }
155 }
156
GetClassName() const157 const char* FrameCaptionButtonContainerView::GetClassName() const {
158 return kViewClassName;
159 }
160
OnPaint(gfx::Canvas * canvas)161 void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) {
162 views::View::OnPaint(canvas);
163
164 // The alternate button style does not paint the button separator.
165 if (!switches::UseAlternateFrameCaptionButtonStyle()) {
166 // We should have at most two visible buttons. The button separator is
167 // always painted underneath the close button regardless of whether a
168 // button other than the close button is visible.
169 gfx::Rect divider(close_button_->bounds().origin(),
170 button_separator_.size());
171 canvas->DrawImageInt(button_separator_,
172 GetMirroredXForRect(divider),
173 divider.y());
174 }
175 }
176
ButtonPressed(views::Button * sender,const ui::Event & event)177 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
178 const ui::Event& event) {
179 // When shift-clicking, slow down animations for visual debugging.
180 // We used to do this via an event filter that looked for the shift key being
181 // pressed but this interfered with several normal keyboard shortcuts.
182 scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
183 if (event.IsShiftDown()) {
184 slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
185 ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
186 }
187
188 // Abort any animations of the button icons.
189 SetButtonsToNormal(ANIMATE_NO);
190
191 ash::UserMetricsAction action =
192 ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
193 if (sender == minimize_button_) {
194 frame_->Minimize();
195 } else if (sender == size_button_) {
196 if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
197 frame_->SetFullscreen(false);
198 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
199 } else if (frame_->IsMaximized()) {
200 frame_->Restore();
201 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
202 } else {
203 frame_->Maximize();
204 action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
205 }
206 } else if(sender == close_button_) {
207 frame_->Close();
208 action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
209 } else {
210 return;
211 }
212 ash::Shell::GetInstance()->metrics()->RecordUserMetricsAction(action);
213 }
214
IsMinimizeButtonVisible() const215 bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const {
216 return minimize_button_->visible();
217 }
218
SetButtonsToNormal(Animate animate)219 void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) {
220 SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE,
221 animate);
222 minimize_button_->SetState(views::Button::STATE_NORMAL);
223 size_button_->SetState(views::Button::STATE_NORMAL);
224 close_button_->SetState(views::Button::STATE_NORMAL);
225 }
226
SetButtonIcons(CaptionButtonIcon minimize_button_icon,CaptionButtonIcon close_button_icon,Animate animate)227 void FrameCaptionButtonContainerView::SetButtonIcons(
228 CaptionButtonIcon minimize_button_icon,
229 CaptionButtonIcon close_button_icon,
230 Animate animate) {
231 FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ?
232 FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO;
233 minimize_button_->SetIcon(minimize_button_icon, fcb_animate);
234 close_button_->SetIcon(close_button_icon, fcb_animate);
235 }
236
237 const FrameCaptionButton*
PressButtonAt(const gfx::Point & position_in_screen,const gfx::Insets & pressed_hittest_outer_insets) const238 FrameCaptionButtonContainerView::PressButtonAt(
239 const gfx::Point& position_in_screen,
240 const gfx::Insets& pressed_hittest_outer_insets) const {
241 DCHECK(switches::UseAlternateFrameCaptionButtonStyle());
242 gfx::Point position(position_in_screen);
243 views::View::ConvertPointFromScreen(this, &position);
244
245 FrameCaptionButton* buttons[] = {
246 close_button_, size_button_, minimize_button_
247 };
248 FrameCaptionButton* pressed_button = NULL;
249 for (size_t i = 0; i < arraysize(buttons); ++i) {
250 FrameCaptionButton* button = buttons[i];
251 if (!button->visible())
252 continue;
253
254 if (button->state() == views::Button::STATE_PRESSED) {
255 gfx::Rect expanded_bounds = button->bounds();
256 expanded_bounds.Inset(pressed_hittest_outer_insets);
257 if (expanded_bounds.Contains(position)) {
258 pressed_button = button;
259 // Do not break in order to give preference to buttons which are
260 // closer to |position_in_screen| than the currently pressed button.
261 // TODO(pkotwicz): Make the caption buttons not overlap.
262 }
263 } else if (ConvertPointToViewAndHitTest(this, button, position)) {
264 pressed_button = button;
265 break;
266 }
267 }
268
269 for (size_t i = 0; i < arraysize(buttons); ++i) {
270 buttons[i]->SetState(buttons[i] == pressed_button ?
271 views::Button::STATE_PRESSED : views::Button::STATE_NORMAL);
272 }
273 return pressed_button;
274 }
275
276 } // namespace ash
277