Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / views / bubble / bubble_frame_view.cc
blobe49956b515403c36e862957f093fba1c57199f34
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.
5 #include "ui/views/bubble/bubble_frame_view.h"
7 #include <algorithm>
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"
22 namespace {
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;
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;
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());
53 } // namespace
55 namespace views {
57 // static
58 const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";
60 // static
61 gfx::Insets BubbleFrameView::GetTitleInsets() {
62 return gfx::Insets(kTitleTopInset, kTitleLeftInset,
63 kTitleBottomInset, kTitleRightInset);
66 BubbleFrameView::BubbleFrameView(const gfx::Insets& content_margins)
67 : bubble_border_(NULL),
68 content_margins_(content_margins),
69 title_(NULL),
70 close_(NULL),
71 titlebar_extra_view_(NULL) {
72 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
73 title_ = new Label(base::string16(),
74 rb.GetFontList(ui::ResourceBundle::MediumFont));
75 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
76 AddChildView(title_);
78 close_ = new LabelButton(this, base::string16());
79 close_->SetImage(CustomButton::STATE_NORMAL,
80 *rb.GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia());
81 close_->SetImage(CustomButton::STATE_HOVERED,
82 *rb.GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia());
83 close_->SetImage(CustomButton::STATE_PRESSED,
84 *rb.GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia());
85 close_->SetBorder(scoped_ptr<Border>());
86 close_->SetSize(close_->GetPreferredSize());
87 close_->SetVisible(false);
88 AddChildView(close_);
91 BubbleFrameView::~BubbleFrameView() {}
93 gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
94 gfx::Rect client_bounds = GetLocalBounds();
95 client_bounds.Inset(GetInsets());
96 client_bounds.Inset(bubble_border_->GetInsets());
97 return client_bounds;
100 gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
101 const gfx::Rect& client_bounds) const {
102 return const_cast<BubbleFrameView*>(this)->GetUpdatedWindowBounds(
103 gfx::Rect(), client_bounds.size(), false);
106 int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
107 if (!bounds().Contains(point))
108 return HTNOWHERE;
109 if (close_->visible() && close_->GetMirroredBounds().Contains(point))
110 return HTCLOSE;
112 // Allow dialogs to show the system menu and be dragged.
113 if (GetWidget()->widget_delegate()->AsDialogDelegate()) {
114 gfx::Rect sys_rect(0, 0, title_->x(), title_->y());
115 sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
116 if (sys_rect.Contains(point))
117 return HTSYSMENU;
118 if (point.y() < title_->bounds().bottom())
119 return HTCAPTION;
122 return GetWidget()->client_view()->NonClientHitTest(point);
125 void BubbleFrameView::GetWindowMask(const gfx::Size& size,
126 gfx::Path* window_mask) {
127 // NOTE: this only provides implementations for the types used by dialogs.
128 if ((bubble_border_->arrow() != BubbleBorder::NONE &&
129 bubble_border_->arrow() != BubbleBorder::FLOAT) ||
130 (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
131 bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER))
132 return;
134 // Use a window mask roughly matching the border in the image assets.
135 static const int kBorderStrokeSize = 1;
136 static const SkScalar kCornerRadius = SkIntToScalar(6);
137 const gfx::Insets border_insets = bubble_border_->GetInsets();
138 SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize),
139 SkIntToScalar(border_insets.top() - kBorderStrokeSize),
140 SkIntToScalar(size.width() - border_insets.right() +
141 kBorderStrokeSize),
142 SkIntToScalar(size.height() - border_insets.bottom() +
143 kBorderStrokeSize) };
144 if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) {
145 window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
146 } else {
147 static const int kBottomBorderShadowSize = 2;
148 rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
149 window_mask->addRect(rect);
153 void BubbleFrameView::ResetWindowControls() {
154 close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
157 void BubbleFrameView::UpdateWindowIcon() {}
159 void BubbleFrameView::UpdateWindowTitle() {
160 title_->SetText(GetWidget()->widget_delegate()->ShouldShowWindowTitle() ?
161 GetWidget()->widget_delegate()->GetWindowTitle() : base::string16());
162 // Update the close button visibility too, otherwise it's not intialized.
163 ResetWindowControls();
166 void BubbleFrameView::SetTitleFontList(const gfx::FontList& font_list) {
167 title_->SetFontList(font_list);
170 gfx::Insets BubbleFrameView::GetInsets() const {
171 gfx::Insets insets = content_margins_;
172 const int title_height = title_->text().empty() ? 0 :
173 title_->GetPreferredSize().height() + kTitleTopInset + kTitleBottomInset;
174 const int close_height = close_->visible() ? close_->height() : 0;
175 insets += gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
176 return insets;
179 gfx::Size BubbleFrameView::GetPreferredSize() const {
180 return GetSizeForClientSize(GetWidget()->client_view()->GetPreferredSize());
183 gfx::Size BubbleFrameView::GetMinimumSize() const {
184 return GetSizeForClientSize(GetWidget()->client_view()->GetMinimumSize());
187 void BubbleFrameView::Layout() {
188 gfx::Rect bounds(GetContentsBounds());
189 bounds.Inset(GetTitleInsets());
190 if (bounds.IsEmpty())
191 return;
193 // The close button top inset is actually smaller than the title top inset.
194 close_->SetPosition(gfx::Point(bounds.right() - close_->width(),
195 bounds.y() - 5));
197 gfx::Size title_size(title_->GetPreferredSize());
198 const int title_width = std::max(0, close_->x() - bounds.x());
199 title_size.SetToMin(gfx::Size(title_width, title_size.height()));
200 bounds.set_size(title_size);
201 title_->SetBoundsRect(bounds);
203 if (titlebar_extra_view_) {
204 const int extra_width = close_->x() - title_->bounds().right();
205 gfx::Size size = titlebar_extra_view_->GetPreferredSize();
206 size.SetToMin(gfx::Size(std::max(0, extra_width), size.height()));
207 gfx::Rect titlebar_extra_view_bounds(
208 close_->x() - size.width(),
209 bounds.y(),
210 size.width(),
211 bounds.height());
212 titlebar_extra_view_bounds.Subtract(bounds);
213 titlebar_extra_view_->SetBoundsRect(titlebar_extra_view_bounds);
217 const char* BubbleFrameView::GetClassName() const {
218 return kViewClassName;
221 void BubbleFrameView::ChildPreferredSizeChanged(View* child) {
222 if (child == titlebar_extra_view_ || child == title_)
223 Layout();
226 void BubbleFrameView::OnThemeChanged() {
227 UpdateWindowTitle();
228 ResetWindowControls();
229 UpdateWindowIcon();
232 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
233 if (bubble_border_ && bubble_border_->use_theme_background_color()) {
234 bubble_border_->set_background_color(GetNativeTheme()->
235 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
236 SchedulePaint();
240 void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
241 if (sender == close_)
242 GetWidget()->Close();
245 void BubbleFrameView::SetBubbleBorder(scoped_ptr<BubbleBorder> border) {
246 bubble_border_ = border.get();
247 SetBorder(border.PassAs<Border>());
249 // Update the background, which relies on the border.
250 set_background(new views::BubbleBackground(bubble_border_));
253 void BubbleFrameView::SetTitlebarExtraView(View* view) {
254 DCHECK(view);
255 DCHECK(!titlebar_extra_view_);
256 AddChildView(view);
257 titlebar_extra_view_ = view;
260 gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
261 gfx::Size client_size,
262 bool adjust_if_offscreen) {
263 gfx::Size size(GetSizeForClientSize(client_size));
265 const BubbleBorder::Arrow arrow = bubble_border_->arrow();
266 if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
267 // Try to mirror the anchoring if the bubble does not fit on the screen.
268 if (!bubble_border_->is_arrow_at_center(arrow)) {
269 MirrorArrowIfOffScreen(true, anchor_rect, size);
270 MirrorArrowIfOffScreen(false, anchor_rect, size);
271 } else {
272 const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
273 MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
274 OffsetArrowIfOffScreen(anchor_rect, size);
278 // Calculate the bounds with the arrow in its updated location and offset.
279 return bubble_border_->GetBounds(anchor_rect, size);
282 gfx::Rect BubbleFrameView::GetAvailableScreenBounds(const gfx::Rect& rect) {
283 // The bubble attempts to fit within the current screen bounds.
284 // TODO(scottmg): Native is wrong. http://crbug.com/133312
285 return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint(
286 rect.CenterPoint()).work_area();
289 void BubbleFrameView::MirrorArrowIfOffScreen(
290 bool vertical,
291 const gfx::Rect& anchor_rect,
292 const gfx::Size& client_size) {
293 // Check if the bounds don't fit on screen.
294 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
295 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
296 if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
297 BubbleBorder::Arrow arrow = bubble_border()->arrow();
298 // Mirror the arrow and get the new bounds.
299 bubble_border_->set_arrow(
300 vertical ? BubbleBorder::vertical_mirror(arrow) :
301 BubbleBorder::horizontal_mirror(arrow));
302 gfx::Rect mirror_bounds =
303 bubble_border_->GetBounds(anchor_rect, client_size);
304 // Restore the original arrow if mirroring doesn't show more of the bubble.
305 // Otherwise it should invoke parent's Layout() to layout the content based
306 // on the new bubble border.
307 if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
308 GetOffScreenLength(available_bounds, window_bounds, vertical))
309 bubble_border_->set_arrow(arrow);
310 else if (parent())
311 parent()->Layout();
315 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
316 const gfx::Size& client_size) {
317 BubbleBorder::Arrow arrow = bubble_border()->arrow();
318 DCHECK(BubbleBorder::is_arrow_at_center(arrow));
320 // Get the desired bubble bounds without adjustment.
321 bubble_border_->set_arrow_offset(0);
322 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
324 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
325 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
326 return;
328 // Calculate off-screen adjustment.
329 const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
330 int offscreen_adjust = 0;
331 if (is_horizontal) {
332 if (window_bounds.x() < available_bounds.x())
333 offscreen_adjust = available_bounds.x() - window_bounds.x();
334 else if (window_bounds.right() > available_bounds.right())
335 offscreen_adjust = available_bounds.right() - window_bounds.right();
336 } else {
337 if (window_bounds.y() < available_bounds.y())
338 offscreen_adjust = available_bounds.y() - window_bounds.y();
339 else if (window_bounds.bottom() > available_bounds.bottom())
340 offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
343 // For center arrows, arrows are moved in the opposite direction of
344 // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
345 // window needs to be moved to the right and that means we need to move arrow
346 // to the left, and that means negative offset.
347 bubble_border_->set_arrow_offset(
348 bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust);
349 if (offscreen_adjust)
350 SchedulePaint();
353 gfx::Size BubbleFrameView::GetSizeForClientSize(
354 const gfx::Size& client_size) const {
355 // Accommodate the width of the title bar elements.
356 int title_bar_width = GetInsets().width() + border()->GetInsets().width();
357 if (!title_->text().empty())
358 title_bar_width += kTitleLeftInset + title_->GetPreferredSize().width();
359 if (close_->visible())
360 title_bar_width += close_->width() + 1;
361 if (titlebar_extra_view_ != NULL)
362 title_bar_width += titlebar_extra_view_->GetPreferredSize().width();
363 gfx::Size size(client_size);
364 size.SetToMax(gfx::Size(title_bar_width, 0));
365 const gfx::Insets insets(GetInsets());
366 size.Enlarge(insets.width(), insets.height());
367 return size;
370 } // namespace views