Upstream version 7.35.139.0
[platform/framework/web/crosswalk.git] / src / apps / ui / views / app_window_frame_view.cc
1 // Copyright 2014 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 "apps/ui/views/app_window_frame_view.h"
6
7 #include "apps/ui/native_app_window.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "extensions/common/draggable_region.h"
10 #include "grit/theme_resources.h"
11 #include "grit/ui_strings.h"  // Accessibility names
12 #include "third_party/skia/include/core/SkPaint.h"
13 #include "third_party/skia/include/core/SkRegion.h"
14 #include "ui/base/hit_test.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/base/resource/resource_bundle.h"
17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/image/image.h"
19 #include "ui/gfx/path.h"
20 #include "ui/views/controls/button/image_button.h"
21 #include "ui/views/layout/grid_layout.h"
22 #include "ui/views/views_delegate.h"
23 #include "ui/views/widget/widget.h"
24 #include "ui/views/widget/widget_delegate.h"
25
26 #if defined(USE_AURA)
27 #include "ui/aura/env.h"
28 #include "ui/aura/window.h"
29 #endif
30
31 namespace {
32 // Height of the chrome-style caption, in pixels.
33 const int kCaptionHeight = 25;
34 }  // namespace
35
36 namespace apps {
37
38 const char AppWindowFrameView::kViewClassName[] =
39     "browser/ui/views/extensions/AppWindowFrameView";
40
41 AppWindowFrameView::AppWindowFrameView()
42     : widget_(NULL),
43       window_(NULL),
44       close_button_(NULL),
45       maximize_button_(NULL),
46       restore_button_(NULL),
47       minimize_button_(NULL),
48       resize_inside_bounds_size_(0),
49       resize_outside_bounds_size_(0),
50       resize_area_corner_size_(0) {}
51
52 AppWindowFrameView::~AppWindowFrameView() {}
53
54 void AppWindowFrameView::Init(views::Widget* widget,
55                               NativeAppWindow* window,
56                               bool draw_frame,
57                               const SkColor& frame_color,
58                               int resize_inside_bounds_size,
59                               int resize_outside_bounds_size,
60                               int resize_outside_scale_for_touch,
61                               int resize_area_corner_size) {
62   widget_ = widget;
63   window_ = window;
64   draw_frame_ = draw_frame;
65   frame_color_ = frame_color;
66   resize_inside_bounds_size_ = resize_inside_bounds_size;
67   resize_outside_bounds_size_ = resize_outside_bounds_size;
68   resize_area_corner_size_ = resize_area_corner_size;
69
70   if (draw_frame) {
71     ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
72     close_button_ = new views::ImageButton(this);
73     close_button_->SetImage(
74         views::CustomButton::STATE_NORMAL,
75         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
76     close_button_->SetImage(
77         views::CustomButton::STATE_HOVERED,
78         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_H).ToImageSkia());
79     close_button_->SetImage(
80         views::CustomButton::STATE_PRESSED,
81         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_P).ToImageSkia());
82     close_button_->SetAccessibleName(
83         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE));
84     AddChildView(close_button_);
85     maximize_button_ = new views::ImageButton(this);
86     maximize_button_->SetImage(
87         views::CustomButton::STATE_NORMAL,
88         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE).ToImageSkia());
89     maximize_button_->SetImage(
90         views::CustomButton::STATE_HOVERED,
91         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_H).ToImageSkia());
92     maximize_button_->SetImage(
93         views::CustomButton::STATE_PRESSED,
94         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_P).ToImageSkia());
95     maximize_button_->SetImage(
96         views::CustomButton::STATE_DISABLED,
97         rb.GetNativeImageNamed(IDR_APP_WINDOW_MAXIMIZE_D).ToImageSkia());
98     maximize_button_->SetAccessibleName(
99         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE));
100     AddChildView(maximize_button_);
101     restore_button_ = new views::ImageButton(this);
102     restore_button_->SetImage(
103         views::CustomButton::STATE_NORMAL,
104         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE).ToImageSkia());
105     restore_button_->SetImage(
106         views::CustomButton::STATE_HOVERED,
107         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_H).ToImageSkia());
108     restore_button_->SetImage(
109         views::CustomButton::STATE_PRESSED,
110         rb.GetNativeImageNamed(IDR_APP_WINDOW_RESTORE_P).ToImageSkia());
111     restore_button_->SetAccessibleName(
112         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_RESTORE));
113     AddChildView(restore_button_);
114     minimize_button_ = new views::ImageButton(this);
115     minimize_button_->SetImage(
116         views::CustomButton::STATE_NORMAL,
117         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE).ToImageSkia());
118     minimize_button_->SetImage(
119         views::CustomButton::STATE_HOVERED,
120         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_H).ToImageSkia());
121     minimize_button_->SetImage(
122         views::CustomButton::STATE_PRESSED,
123         rb.GetNativeImageNamed(IDR_APP_WINDOW_MINIMIZE_P).ToImageSkia());
124     minimize_button_->SetAccessibleName(
125         l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE));
126     AddChildView(minimize_button_);
127   }
128 }
129
130 // views::NonClientFrameView implementation.
131
132 gfx::Rect AppWindowFrameView::GetBoundsForClientView() const {
133   if (!draw_frame_ || widget_->IsFullscreen())
134     return bounds();
135   return gfx::Rect(
136       0, kCaptionHeight, width(), std::max(0, height() - kCaptionHeight));
137 }
138
139 gfx::Rect AppWindowFrameView::GetWindowBoundsForClientBounds(
140     const gfx::Rect& client_bounds) const {
141   gfx::Rect window_bounds = client_bounds;
142 #if defined(OS_LINUX) && !defined(OS_CHROMEOS)
143   // Get the difference between the widget's client area bounds and window
144   // bounds, and grow |window_bounds| by that amount.
145   gfx::Insets native_frame_insets =
146       widget_->GetClientAreaBoundsInScreen().InsetsFrom(
147           widget_->GetWindowBoundsInScreen());
148   window_bounds.Inset(native_frame_insets);
149 #endif
150   if (!draw_frame_) {
151     // Enforce minimum size (1, 1) in case that client_bounds is passed with
152     // empty size. This could occur when the frameless window is being
153     // initialized.
154     if (window_bounds.IsEmpty()) {
155       window_bounds.set_width(1);
156       window_bounds.set_height(1);
157     }
158     return window_bounds;
159   }
160
161   int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
162   int header_width = close_button_->width() + closeButtonOffsetX * 2;
163   return gfx::Rect(window_bounds.x(),
164                    window_bounds.y() - kCaptionHeight,
165                    std::max(header_width, window_bounds.width()),
166                    window_bounds.height() + kCaptionHeight);
167 }
168
169 int AppWindowFrameView::NonClientHitTest(const gfx::Point& point) {
170   if (widget_->IsFullscreen())
171     return HTCLIENT;
172
173   gfx::Rect expanded_bounds = bounds();
174   if (resize_outside_bounds_size_) {
175     expanded_bounds.Inset(gfx::Insets(-resize_outside_bounds_size_,
176                                       -resize_outside_bounds_size_,
177                                       -resize_outside_bounds_size_,
178                                       -resize_outside_bounds_size_));
179   }
180   // Points outside the (possibly expanded) bounds can be discarded.
181   if (!expanded_bounds.Contains(point))
182     return HTNOWHERE;
183
184   // Check the frame first, as we allow a small area overlapping the contents
185   // to be used for resize handles.
186   bool can_ever_resize = widget_->widget_delegate()
187                              ? widget_->widget_delegate()->CanResize()
188                              : false;
189   // Don't allow overlapping resize handles when the window is maximized or
190   // fullscreen, as it can't be resized in those states.
191   int resize_border = (widget_->IsMaximized() || widget_->IsFullscreen())
192                           ? 0
193                           : resize_inside_bounds_size_;
194   int frame_component = GetHTComponentForFrame(point,
195                                                resize_border,
196                                                resize_border,
197                                                resize_area_corner_size_,
198                                                resize_area_corner_size_,
199                                                can_ever_resize);
200   if (frame_component != HTNOWHERE)
201     return frame_component;
202
203   // Check for possible draggable region in the client area for the frameless
204   // window.
205   SkRegion* draggable_region = window_->GetDraggableRegion();
206   if (draggable_region && draggable_region->contains(point.x(), point.y()))
207     return HTCAPTION;
208
209   int client_component = widget_->client_view()->NonClientHitTest(point);
210   if (client_component != HTNOWHERE)
211     return client_component;
212
213   // Then see if the point is within any of the window controls.
214   if (close_button_ && close_button_->visible() &&
215       close_button_->GetMirroredBounds().Contains(point)) {
216     return HTCLOSE;
217   }
218   if ((maximize_button_ && maximize_button_->visible() &&
219        maximize_button_->GetMirroredBounds().Contains(point)) ||
220       (restore_button_ && restore_button_->visible() &&
221        restore_button_->GetMirroredBounds().Contains(point))) {
222     return HTMAXBUTTON;
223   }
224   if (minimize_button_ && minimize_button_->visible() &&
225       minimize_button_->GetMirroredBounds().Contains(point)) {
226     return HTMINBUTTON;
227   }
228
229   // Caption is a safe default.
230   return HTCAPTION;
231 }
232
233 void AppWindowFrameView::GetWindowMask(const gfx::Size& size,
234                                        gfx::Path* window_mask) {
235   // We got nothing to say about no window mask.
236 }
237
238 // views::View implementation.
239
240 gfx::Size AppWindowFrameView::GetPreferredSize() {
241   gfx::Size pref = widget_->client_view()->GetPreferredSize();
242   gfx::Rect bounds(0, 0, pref.width(), pref.height());
243   return widget_->non_client_view()
244       ->GetWindowBoundsForClientBounds(bounds)
245       .size();
246 }
247
248 void AppWindowFrameView::Layout() {
249   if (!draw_frame_)
250     return;
251   gfx::Size close_size = close_button_->GetPreferredSize();
252   const int kButtonOffsetY = 0;
253   const int kButtonSpacing = 1;
254   const int kRightMargin = 3;
255
256   close_button_->SetBounds(width() - kRightMargin - close_size.width(),
257                            kButtonOffsetY,
258                            close_size.width(),
259                            close_size.height());
260
261   bool can_ever_resize = widget_->widget_delegate()
262                              ? widget_->widget_delegate()->CanResize()
263                              : false;
264   maximize_button_->SetEnabled(can_ever_resize);
265   gfx::Size maximize_size = maximize_button_->GetPreferredSize();
266   maximize_button_->SetBounds(
267       close_button_->x() - kButtonSpacing - maximize_size.width(),
268       kButtonOffsetY,
269       maximize_size.width(),
270       maximize_size.height());
271   gfx::Size restore_size = restore_button_->GetPreferredSize();
272   restore_button_->SetBounds(
273       close_button_->x() - kButtonSpacing - restore_size.width(),
274       kButtonOffsetY,
275       restore_size.width(),
276       restore_size.height());
277
278   bool maximized = widget_->IsMaximized();
279   maximize_button_->SetVisible(!maximized);
280   restore_button_->SetVisible(maximized);
281   if (maximized)
282     maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
283   else
284     restore_button_->SetState(views::CustomButton::STATE_NORMAL);
285
286   gfx::Size minimize_size = minimize_button_->GetPreferredSize();
287   minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
288   minimize_button_->SetBounds(
289       maximize_button_->x() - kButtonSpacing - minimize_size.width(),
290       kButtonOffsetY,
291       minimize_size.width(),
292       minimize_size.height());
293 }
294
295 void AppWindowFrameView::OnPaint(gfx::Canvas* canvas) {
296   if (!draw_frame_)
297     return;
298
299   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
300   if (ShouldPaintAsActive()) {
301     close_button_->SetImage(
302         views::CustomButton::STATE_NORMAL,
303         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE).ToImageSkia());
304   } else {
305     close_button_->SetImage(
306         views::CustomButton::STATE_NORMAL,
307         rb.GetNativeImageNamed(IDR_APP_WINDOW_CLOSE_U).ToImageSkia());
308   }
309
310   // TODO(jeremya): different look for inactive?
311   SkPaint paint;
312   paint.setAntiAlias(false);
313   paint.setStyle(SkPaint::kFill_Style);
314   paint.setColor(frame_color_);
315   gfx::Path path;
316   path.moveTo(0, 0);
317   path.lineTo(width(), 0);
318   path.lineTo(width(), kCaptionHeight);
319   path.lineTo(0, kCaptionHeight);
320   path.close();
321   canvas->DrawPath(path, paint);
322 }
323
324 const char* AppWindowFrameView::GetClassName() const { return kViewClassName; }
325
326 gfx::Size AppWindowFrameView::GetMinimumSize() {
327   gfx::Size min_size = widget_->client_view()->GetMinimumSize();
328   if (!draw_frame_)
329     return min_size;
330
331   // Ensure we can display the top of the caption area.
332   gfx::Rect client_bounds = GetBoundsForClientView();
333   min_size.Enlarge(0, client_bounds.y());
334   // Ensure we have enough space for the window icon and buttons.  We allow
335   // the title string to collapse to zero width.
336   int closeButtonOffsetX = (kCaptionHeight - close_button_->height()) / 2;
337   int header_width = close_button_->width() + closeButtonOffsetX * 2;
338   if (header_width > min_size.width())
339     min_size.set_width(header_width);
340   return min_size;
341 }
342
343 gfx::Size AppWindowFrameView::GetMaximumSize() {
344   gfx::Size max_size = widget_->client_view()->GetMaximumSize();
345
346   // Add to the client maximum size the height of any title bar and borders.
347   gfx::Size client_size = GetBoundsForClientView().size();
348   if (max_size.width())
349     max_size.Enlarge(width() - client_size.width(), 0);
350   if (max_size.height())
351     max_size.Enlarge(0, height() - client_size.height());
352
353   return max_size;
354 }
355
356 // views::ButtonListener implementation.
357
358 void AppWindowFrameView::ButtonPressed(views::Button* sender,
359                                        const ui::Event& event) {
360   DCHECK(draw_frame_);
361   if (sender == close_button_)
362     widget_->Close();
363   else if (sender == maximize_button_)
364     widget_->Maximize();
365   else if (sender == restore_button_)
366     widget_->Restore();
367   else if (sender == minimize_button_)
368     widget_->Minimize();
369 }
370
371 }  // namespace apps