- add sources.
[platform/framework/web/crosswalk.git] / src / ash / wm / caption_buttons / frame_caption_button_container_view.cc
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/shell.h"
9 #include "ash/shell_delegate.h"
10 #include "ash/wm/caption_buttons/alternate_frame_caption_button.h"
11 #include "ash/wm/caption_buttons/frame_maximize_button.h"
12 #include "ash/wm/window_state.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/views/border.h"
21 #include "ui/views/controls/button/image_button.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
24
25 namespace ash {
26
27 namespace {
28
29 // Constants for normal button style -------------------------------------------
30
31 // The distance between buttons. AlternateFrameCaptionButton::GetXOverlap() is
32 // used to compute the distance between buttons for the alternate button style.
33 const int kDistanceBetweenButtons = -1;
34
35 // Constants for alternate button style ----------------------------------------
36
37 // Spacings between the buttons and the view's top and bottom borders (if any).
38 const int kAlternateStyleShortHeaderTopInset = 0;
39 const int kAlternateStyleTallHeaderTopInset = 2;
40 const int kAlternateStyleShortHeaderBottomInset = 0;
41 const int kAlternateStyleTallHeaderBottomInset = 1;
42
43 // Ideal spacing between:
44 // - Right edge of the leftmost button's overlappable region and the view's left
45 //   edge.
46 // - Left edge of the rightmost button's overlappable region and the view's
47 //   right edge.
48 // Used in GetLeftInset() and GetRightInset().
49 const int kAlternateStyleSideInset = 5;
50
51 // Converts |point| from |src| to |dst| and hittests against |dst|.
52 bool ConvertPointToViewAndHitTest(const views::View* src,
53                                   const views::View* dst,
54                                   const gfx::Point& point) {
55   gfx::Point converted(point);
56   views::View::ConvertPointToTarget(src, dst, &converted);
57   return dst->HitTestPoint(converted);
58 }
59
60 }  // namespace
61
62 // static
63 const char FrameCaptionButtonContainerView::kViewClassName[] =
64     "FrameCaptionButtonContainerView";
65
66 FrameCaptionButtonContainerView::FrameCaptionButtonContainerView(
67     views::Widget* frame,
68     MinimizeAllowed minimize_allowed)
69     : frame_(frame),
70       header_style_(HEADER_STYLE_SHORT),
71       minimize_button_(NULL),
72       size_button_(NULL),
73       close_button_(NULL) {
74    bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle();
75
76   // Insert the buttons left to right.
77   if (alternate_style) {
78     minimize_button_ = new AlternateFrameCaptionButton(this,
79         AlternateFrameCaptionButton::ACTION_MINIMIZE);
80     size_button_ = new AlternateFrameCaptionButton(this,
81         AlternateFrameCaptionButton::ACTION_MAXIMIZE_RESTORE);
82     close_button_ = new AlternateFrameCaptionButton(this,
83         AlternateFrameCaptionButton::ACTION_CLOSE);
84   } else {
85     minimize_button_ = new views::ImageButton(this);
86     size_button_ = new FrameMaximizeButton(this, frame);
87     close_button_ = new views::ImageButton(this);
88   }
89
90   minimize_button_->SetAccessibleName(
91       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
92   // Hide |minimize_button_| when using the non-alternate button style because
93   // |size_button_| is capable of minimizing in this case.
94   // TODO(pkotwicz): We should probably show the minimize button when in
95   // "always maximized" mode.
96   minimize_button_->SetVisible(
97       minimize_allowed == MINIMIZE_ALLOWED &&
98       (alternate_style || !frame_->widget_delegate()->CanMaximize()));
99   AddChildView(minimize_button_);
100
101   size_button_->SetAccessibleName(
102       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
103   size_button_->SetVisible(frame_->widget_delegate()->CanMaximize());
104   AddChildView(size_button_);
105
106   close_button_->SetAccessibleName(
107       l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
108   AddChildView(close_button_);
109
110   button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed(
111       IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia();
112 }
113
114 FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() {
115 }
116
117 void FrameCaptionButtonContainerView::ResetWindowControls() {
118   minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
119   size_button_->SetState(views::CustomButton::STATE_NORMAL);
120 }
121
122 int FrameCaptionButtonContainerView::NonClientHitTest(
123     const gfx::Point& point) const {
124   if (close_button_->visible() &&
125       ConvertPointToViewAndHitTest(this, close_button_, point)) {
126     return HTCLOSE;
127   } else if (size_button_->visible() &&
128              ConvertPointToViewAndHitTest(this, size_button_, point)) {
129     return HTMAXBUTTON;
130   } else if (minimize_button_->visible() &&
131              ConvertPointToViewAndHitTest(this, minimize_button_, point)) {
132     return HTMINBUTTON;
133   }
134   return HTNOWHERE;
135 }
136
137 gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() {
138   int button_separation = GetDistanceBetweenButtons();
139
140   int width = 0;
141   bool first_visible = true;
142   for (int i = 0; i < child_count(); ++i) {
143     views::View* child = child_at(i);
144     if (!child->visible())
145       continue;
146
147     width += child_at(i)->GetPreferredSize().width();
148     if (!first_visible)
149       width += button_separation;
150     first_visible = false;
151   }
152   gfx::Insets insets(GetInsets());
153   return gfx::Size(
154       width + insets.width() + GetLeftInset() + GetRightInset(),
155       close_button_->GetPreferredSize().height() + insets.height());
156 }
157
158 void FrameCaptionButtonContainerView::Layout() {
159   if (switches::UseAlternateFrameCaptionButtonStyle()) {
160     int top_inset = kAlternateStyleShortHeaderTopInset;
161     int bottom_inset = kAlternateStyleShortHeaderBottomInset;
162     if (header_style_ == HEADER_STYLE_TALL) {
163       top_inset = kAlternateStyleTallHeaderTopInset;
164       bottom_inset = kAlternateStyleTallHeaderBottomInset;
165     }
166
167     minimize_button_->set_border(
168         views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
169     size_button_->set_border(
170         views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
171     close_button_->set_border(
172         views::Border::CreateEmptyBorder(top_inset, 0, bottom_inset, 0));
173   } else {
174     SetButtonImages(minimize_button_,
175                     IDR_AURA_WINDOW_MINIMIZE_SHORT,
176                     IDR_AURA_WINDOW_MINIMIZE_SHORT_H,
177                     IDR_AURA_WINDOW_MINIMIZE_SHORT_P);
178
179     if (header_style_ == HEADER_STYLE_MAXIMIZED_HOSTED_APP) {
180       SetButtonImages(size_button_,
181                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE,
182                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE_H,
183                       IDR_AURA_WINDOW_FULLSCREEN_RESTORE_P);
184       SetButtonImages(close_button_,
185                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE,
186                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE_H,
187                       IDR_AURA_WINDOW_FULLSCREEN_CLOSE_P);
188     } else if (header_style_ == HEADER_STYLE_SHORT) {
189       // The new assets only make sense if the window is maximized or fullscreen
190       // because we usually use a black header in this case.
191       if ((frame_->IsMaximized() || frame_->IsFullscreen()) &&
192           wm::GetWindowState(
193               frame_->GetNativeWindow())->tracked_by_workspace()) {
194         SetButtonImages(size_button_,
195                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE2,
196                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_H,
197                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE2_P);
198         SetButtonImages(close_button_,
199                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE2,
200                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_H,
201                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE2_P);
202       } else {
203         SetButtonImages(size_button_,
204                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE,
205                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE_H,
206                         IDR_AURA_WINDOW_MAXIMIZED_RESTORE_P);
207         SetButtonImages(close_button_,
208                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE,
209                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE_H,
210                         IDR_AURA_WINDOW_MAXIMIZED_CLOSE_P);
211       }
212     } else {
213       SetButtonImages(size_button_,
214                       IDR_AURA_WINDOW_MAXIMIZE,
215                       IDR_AURA_WINDOW_MAXIMIZE_H,
216                       IDR_AURA_WINDOW_MAXIMIZE_P);
217       SetButtonImages(close_button_,
218                       IDR_AURA_WINDOW_CLOSE,
219                       IDR_AURA_WINDOW_CLOSE_H,
220                       IDR_AURA_WINDOW_CLOSE_P);
221     }
222   }
223
224   gfx::Insets insets(GetInsets());
225   int x = insets.left() + GetLeftInset();
226   int y_inset = insets.top();
227   int button_separation = GetDistanceBetweenButtons();
228   for (int i = 0; i < child_count(); ++i) {
229     views::View* child = child_at(i);
230     if (!child->visible())
231       continue;
232
233     gfx::Size size = child->GetPreferredSize();
234     child->SetBounds(x, y_inset, size.width(), size.height());
235
236     // Do not allow |child| to paint over the left border.
237     child->set_clip_insets(
238         gfx::Insets(0, std::max(0, insets.left() - x), 0, 0));
239
240     x += size.width() + button_separation;
241   }
242 }
243
244 const char* FrameCaptionButtonContainerView::GetClassName() const {
245   return kViewClassName;
246 }
247
248 void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) {
249   views::View::OnPaint(canvas);
250
251   // The alternate button style and AppNonClientFrameViewAsh do not paint the
252   // button separator.
253   if (header_style_ != HEADER_STYLE_MAXIMIZED_HOSTED_APP &&
254       !switches::UseAlternateFrameCaptionButtonStyle()) {
255     // We should have at most two visible buttons. The button separator is
256     // always painted underneath the close button regardless of whether a
257     // button other than the close button is visible.
258     gfx::Rect divider(close_button_->bounds().origin(),
259                       button_separator_.size());
260     canvas->DrawImageInt(button_separator_,
261                          GetMirroredXForRect(divider),
262                          divider.y());
263   }
264 }
265
266 int FrameCaptionButtonContainerView::GetDistanceBetweenButtons() const {
267   if (switches::UseAlternateFrameCaptionButtonStyle())
268     return AlternateFrameCaptionButton::GetXOverlap() * -2;
269   return kDistanceBetweenButtons;
270 }
271
272 int FrameCaptionButtonContainerView::GetLeftInset() const {
273   if (switches::UseAlternateFrameCaptionButtonStyle()) {
274     // If using the alternate button style and there is a border, clip the
275     // left overlappable region of the leftmost button to
276     // |kAlternateStyleSideInset|.
277     // Otherwise, allow enough room for the entire left overlappable region of
278     // the leftmost button to fit in the view.
279     if (border() && border()->GetInsets().left()) {
280       return kAlternateStyleSideInset -
281           AlternateFrameCaptionButton::GetXOverlap();
282     }
283   }
284   return 0;
285 }
286
287 int FrameCaptionButtonContainerView::GetRightInset() const {
288   if (switches::UseAlternateFrameCaptionButtonStyle()) {
289     // Always clip the right overlappable region of the rightmost button to
290     // |kAlternateStyleSideInset| because the caption buttons are always
291     // at the right edge of the screen. (The left edge in RTL mode).
292     return kAlternateStyleSideInset -
293         AlternateFrameCaptionButton::GetXOverlap();
294   }
295   return 0;
296 }
297
298 void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender,
299                                                     const ui::Event& event) {
300   // When shift-clicking, slow down animations for visual debugging.
301   // We used to do this via an event filter that looked for the shift key being
302   // pressed but this interfered with several normal keyboard shortcuts.
303   scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode;
304   if (event.IsShiftDown()) {
305     slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode(
306         ui::ScopedAnimationDurationScaleMode::SLOW_DURATION));
307   }
308
309   ash::UserMetricsAction action =
310       ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE;
311   if (sender == minimize_button_) {
312     // The minimize button may move out from under the cursor.
313     ResetWindowControls();
314     frame_->Minimize();
315   } else if (sender == size_button_) {
316     // The size button may move out from under the cursor.
317     ResetWindowControls();
318     if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen.
319       frame_->SetFullscreen(false);
320       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN;
321     } else if (frame_->IsMaximized()) {
322       frame_->Restore();
323       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE;
324     } else {
325       frame_->Maximize();
326       action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE;
327     }
328   } else if(sender == close_button_) {
329     frame_->Close();
330     action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK;
331   } else {
332     return;
333   }
334   ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action);
335 }
336
337 void FrameCaptionButtonContainerView::SetButtonImages(
338     views::CustomButton* button,
339     int normal_image_id,
340     int hot_image_id,
341     int pushed_image_id) {
342   // When using the alternate button style, |button| does not inherit from
343   // views::ImageButton.
344   DCHECK(!switches::UseAlternateFrameCaptionButtonStyle());
345   views::ImageButton* image_button = static_cast<views::ImageButton*>(button);
346   ui::ResourceBundle& resource_bundle = ui::ResourceBundle::GetSharedInstance();
347   image_button->SetImage(views::CustomButton::STATE_NORMAL,
348                          resource_bundle.GetImageSkiaNamed(normal_image_id));
349   image_button->SetImage(views::CustomButton::STATE_HOVERED,
350                          resource_bundle.GetImageSkiaNamed(hot_image_id));
351   image_button->SetImage(views::CustomButton::STATE_PRESSED,
352                          resource_bundle.GetImageSkiaNamed(pushed_image_id));
353 }
354
355 }  // namespace ash