Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / ui / views / bubble / bubble_frame_view.cc
1 // Copyright (c) 2012 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 "ui/views/bubble/bubble_frame_view.h"
6
7 #include <algorithm>
8
9 #include "ui/base/hit_test.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/path.h"
12 #include "ui/gfx/screen.h"
13 #include "ui/gfx/skia_util.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/resources/grit/ui_resources.h"
16 #include "ui/views/bubble/bubble_border.h"
17 #include "ui/views/controls/button/label_button.h"
18 #include "ui/views/widget/widget.h"
19 #include "ui/views/widget/widget_delegate.h"
20 #include "ui/views/window/client_view.h"
21
22 namespace {
23
24 // Insets for the title bar views in pixels.
25 const int kTitleTopInset = 12;
26 const int kTitleLeftInset = 19;
27 const int kTitleBottomInset = 12;
28 const int kTitleRightInset = 7;
29
30 // Get the |vertical| or horizontal amount that |available_bounds| overflows
31 // |window_bounds|.
32 int GetOffScreenLength(const gfx::Rect& available_bounds,
33                        const gfx::Rect& window_bounds,
34                        bool vertical) {
35   if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
36     return 0;
37
38   //  window_bounds
39   //  +---------------------------------+
40   //  |             top                 |
41   //  |      +------------------+       |
42   //  | left | available_bounds | right |
43   //  |      +------------------+       |
44   //  |            bottom               |
45   //  +---------------------------------+
46   if (vertical)
47     return std::max(0, available_bounds.y() - window_bounds.y()) +
48            std::max(0, window_bounds.bottom() - available_bounds.bottom());
49   return std::max(0, available_bounds.x() - window_bounds.x()) +
50          std::max(0, window_bounds.right() - available_bounds.right());
51 }
52
53 }  // namespace
54
55 namespace views {
56
57 // static
58 const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";
59
60 BubbleFrameView::BubbleFrameView(const gfx::Insets& content_margins)
61     : bubble_border_(NULL),
62       content_margins_(content_margins),
63       title_(NULL),
64       close_(NULL),
65       titlebar_extra_view_(NULL) {
66   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
67   title_ = new Label(base::string16(),
68                      rb.GetFontList(ui::ResourceBundle::MediumFont));
69   title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
70   AddChildView(title_);
71
72   close_ = CreateCloseButton(this);
73   close_->SetVisible(false);
74   AddChildView(close_);
75 }
76
77 BubbleFrameView::~BubbleFrameView() {}
78
79 // static
80 gfx::Insets BubbleFrameView::GetTitleInsets() {
81   return gfx::Insets(
82       kTitleTopInset, kTitleLeftInset, kTitleBottomInset, kTitleRightInset);
83 }
84
85 // static
86 LabelButton* BubbleFrameView::CreateCloseButton(ButtonListener* listener) {
87   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
88   LabelButton* close = new LabelButton(listener, base::string16());
89   close->SetImage(CustomButton::STATE_NORMAL,
90                   *rb.GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia());
91   close->SetImage(CustomButton::STATE_HOVERED,
92                   *rb.GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia());
93   close->SetImage(CustomButton::STATE_PRESSED,
94                   *rb.GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia());
95   close->SetBorder(scoped_ptr<Border>());
96   close->SetSize(close->GetPreferredSize());
97   return close;
98 }
99
100 gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
101   gfx::Rect client_bounds = GetLocalBounds();
102   client_bounds.Inset(GetInsets());
103   client_bounds.Inset(bubble_border_->GetInsets());
104   return client_bounds;
105 }
106
107 gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
108     const gfx::Rect& client_bounds) const {
109   return const_cast<BubbleFrameView*>(this)->GetUpdatedWindowBounds(
110       gfx::Rect(), client_bounds.size(), false);
111 }
112
113 int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
114   if (!bounds().Contains(point))
115     return HTNOWHERE;
116   if (close_->visible() && close_->GetMirroredBounds().Contains(point))
117     return HTCLOSE;
118
119   // Allow dialogs to show the system menu and be dragged.
120   if (GetWidget()->widget_delegate()->AsDialogDelegate()) {
121     gfx::Rect sys_rect(0, 0, title_->x(), title_->y());
122     sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
123     if (sys_rect.Contains(point))
124       return HTSYSMENU;
125     if (point.y() < title_->bounds().bottom())
126       return HTCAPTION;
127   }
128
129   return GetWidget()->client_view()->NonClientHitTest(point);
130 }
131
132 void BubbleFrameView::GetWindowMask(const gfx::Size& size,
133                                     gfx::Path* window_mask) {
134   // NOTE: this only provides implementations for the types used by dialogs.
135   if ((bubble_border_->arrow() != BubbleBorder::NONE &&
136        bubble_border_->arrow() != BubbleBorder::FLOAT) ||
137       (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
138        bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER))
139     return;
140
141   // Use a window mask roughly matching the border in the image assets.
142   static const int kBorderStrokeSize = 1;
143   static const SkScalar kCornerRadius = SkIntToScalar(6);
144   const gfx::Insets border_insets = bubble_border_->GetInsets();
145   SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize),
146                   SkIntToScalar(border_insets.top() - kBorderStrokeSize),
147                   SkIntToScalar(size.width() - border_insets.right() +
148                                 kBorderStrokeSize),
149                   SkIntToScalar(size.height() - border_insets.bottom() +
150                                 kBorderStrokeSize) };
151   if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) {
152     window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
153   } else {
154     static const int kBottomBorderShadowSize = 2;
155     rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
156     window_mask->addRect(rect);
157   }
158 }
159
160 void BubbleFrameView::ResetWindowControls() {
161   close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
162 }
163
164 void BubbleFrameView::UpdateWindowIcon() {}
165
166 void BubbleFrameView::UpdateWindowTitle() {
167   title_->SetText(GetWidget()->widget_delegate()->ShouldShowWindowTitle() ?
168       GetWidget()->widget_delegate()->GetWindowTitle() : base::string16());
169   // Update the close button visibility too, otherwise it's not intialized.
170   ResetWindowControls();
171 }
172
173 void BubbleFrameView::SizeConstraintsChanged() {}
174
175 void BubbleFrameView::SetTitleFontList(const gfx::FontList& font_list) {
176   title_->SetFontList(font_list);
177 }
178
179 gfx::Insets BubbleFrameView::GetInsets() const {
180   gfx::Insets insets = content_margins_;
181   const int title_height = title_->text().empty() ? 0 :
182       title_->GetPreferredSize().height() + kTitleTopInset + kTitleBottomInset;
183   const int close_height = close_->visible() ? close_->height() : 0;
184   insets += gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
185   return insets;
186 }
187
188 gfx::Size BubbleFrameView::GetPreferredSize() const {
189   return GetSizeForClientSize(GetWidget()->client_view()->GetPreferredSize());
190 }
191
192 gfx::Size BubbleFrameView::GetMinimumSize() const {
193   return GetSizeForClientSize(GetWidget()->client_view()->GetMinimumSize());
194 }
195
196 void BubbleFrameView::Layout() {
197   gfx::Rect bounds(GetContentsBounds());
198   bounds.Inset(GetTitleInsets());
199   if (bounds.IsEmpty())
200     return;
201
202   // The close button top inset is actually smaller than the title top inset.
203   close_->SetPosition(gfx::Point(bounds.right() - close_->width(),
204                                  bounds.y() - 5));
205
206   gfx::Size title_size(title_->GetPreferredSize());
207   const int title_width = std::max(0, close_->x() - bounds.x());
208   title_size.SetToMin(gfx::Size(title_width, title_size.height()));
209   bounds.set_size(title_size);
210   title_->SetBoundsRect(bounds);
211
212   if (titlebar_extra_view_) {
213     const int extra_width = close_->x() - title_->bounds().right();
214     gfx::Size size = titlebar_extra_view_->GetPreferredSize();
215     size.SetToMin(gfx::Size(std::max(0, extra_width), size.height()));
216     gfx::Rect titlebar_extra_view_bounds(
217         close_->x() - size.width(),
218         bounds.y(),
219         size.width(),
220         bounds.height());
221     titlebar_extra_view_bounds.Subtract(bounds);
222     titlebar_extra_view_->SetBoundsRect(titlebar_extra_view_bounds);
223   }
224 }
225
226 const char* BubbleFrameView::GetClassName() const {
227   return kViewClassName;
228 }
229
230 void BubbleFrameView::ChildPreferredSizeChanged(View* child) {
231   if (child == titlebar_extra_view_ || child == title_)
232     Layout();
233 }
234
235 void BubbleFrameView::OnThemeChanged() {
236   UpdateWindowTitle();
237   ResetWindowControls();
238   UpdateWindowIcon();
239 }
240
241 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
242   if (bubble_border_ && bubble_border_->use_theme_background_color()) {
243     bubble_border_->set_background_color(GetNativeTheme()->
244         GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
245     SchedulePaint();
246   }
247 }
248
249 void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
250   if (sender == close_)
251     GetWidget()->Close();
252 }
253
254 void BubbleFrameView::SetBubbleBorder(scoped_ptr<BubbleBorder> border) {
255   bubble_border_ = border.get();
256   SetBorder(border.Pass());
257
258   // Update the background, which relies on the border.
259   set_background(new views::BubbleBackground(bubble_border_));
260 }
261
262 void BubbleFrameView::SetTitlebarExtraView(View* view) {
263   DCHECK(view);
264   DCHECK(!titlebar_extra_view_);
265   AddChildView(view);
266   titlebar_extra_view_ = view;
267 }
268
269 gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
270                                                   gfx::Size client_size,
271                                                   bool adjust_if_offscreen) {
272   gfx::Size size(GetSizeForClientSize(client_size));
273
274   const BubbleBorder::Arrow arrow = bubble_border_->arrow();
275   if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
276     // Try to mirror the anchoring if the bubble does not fit on the screen.
277     if (!bubble_border_->is_arrow_at_center(arrow)) {
278       MirrorArrowIfOffScreen(true, anchor_rect, size);
279       MirrorArrowIfOffScreen(false, anchor_rect, size);
280     } else {
281       const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
282       MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
283       OffsetArrowIfOffScreen(anchor_rect, size);
284     }
285   }
286
287   // Calculate the bounds with the arrow in its updated location and offset.
288   return bubble_border_->GetBounds(anchor_rect, size);
289 }
290
291 gfx::Rect BubbleFrameView::GetAvailableScreenBounds(const gfx::Rect& rect) {
292   // The bubble attempts to fit within the current screen bounds.
293   // TODO(scottmg): Native is wrong. http://crbug.com/133312
294   return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint(
295       rect.CenterPoint()).work_area();
296 }
297
298 bool BubbleFrameView::IsCloseButtonVisible() const {
299   return close_->visible();
300 }
301
302 gfx::Rect BubbleFrameView::GetCloseButtonBounds() const {
303   return close_->bounds();
304 }
305
306 void BubbleFrameView::MirrorArrowIfOffScreen(
307     bool vertical,
308     const gfx::Rect& anchor_rect,
309     const gfx::Size& client_size) {
310   // Check if the bounds don't fit on screen.
311   gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
312   gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
313   if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
314     BubbleBorder::Arrow arrow = bubble_border()->arrow();
315     // Mirror the arrow and get the new bounds.
316     bubble_border_->set_arrow(
317         vertical ? BubbleBorder::vertical_mirror(arrow) :
318                    BubbleBorder::horizontal_mirror(arrow));
319     gfx::Rect mirror_bounds =
320         bubble_border_->GetBounds(anchor_rect, client_size);
321     // Restore the original arrow if mirroring doesn't show more of the bubble.
322     // Otherwise it should invoke parent's Layout() to layout the content based
323     // on the new bubble border.
324     if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
325         GetOffScreenLength(available_bounds, window_bounds, vertical))
326       bubble_border_->set_arrow(arrow);
327     else if (parent())
328       parent()->Layout();
329   }
330 }
331
332 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
333                                              const gfx::Size& client_size) {
334   BubbleBorder::Arrow arrow = bubble_border()->arrow();
335   DCHECK(BubbleBorder::is_arrow_at_center(arrow));
336
337   // Get the desired bubble bounds without adjustment.
338   bubble_border_->set_arrow_offset(0);
339   gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
340
341   gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
342   if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
343     return;
344
345   // Calculate off-screen adjustment.
346   const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
347   int offscreen_adjust = 0;
348   if (is_horizontal) {
349     if (window_bounds.x() < available_bounds.x())
350       offscreen_adjust = available_bounds.x() - window_bounds.x();
351     else if (window_bounds.right() > available_bounds.right())
352       offscreen_adjust = available_bounds.right() - window_bounds.right();
353   } else {
354     if (window_bounds.y() < available_bounds.y())
355       offscreen_adjust = available_bounds.y() - window_bounds.y();
356     else if (window_bounds.bottom() > available_bounds.bottom())
357       offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
358   }
359
360   // For center arrows, arrows are moved in the opposite direction of
361   // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
362   // window needs to be moved to the right and that means we need to move arrow
363   // to the left, and that means negative offset.
364   bubble_border_->set_arrow_offset(
365       bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust);
366   if (offscreen_adjust)
367     SchedulePaint();
368 }
369
370 gfx::Size BubbleFrameView::GetSizeForClientSize(
371     const gfx::Size& client_size) const {
372   // Accommodate the width of the title bar elements.
373   int title_bar_width = GetInsets().width() + border()->GetInsets().width();
374   if (!title_->text().empty())
375     title_bar_width += kTitleLeftInset + title_->GetPreferredSize().width();
376   if (close_->visible())
377     title_bar_width += close_->width() + 1;
378   if (titlebar_extra_view_ != NULL)
379     title_bar_width += titlebar_extra_view_->GetPreferredSize().width();
380   gfx::Size size(client_size);
381   size.SetToMax(gfx::Size(title_bar_width, 0));
382   const gfx::Insets insets(GetInsets());
383   size.Enlarge(insets.width(), insets.height());
384   return size;
385 }
386
387 }  // namespace views