• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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