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