Linux: Depend on liberation-fonts package for RPMs.
[chromium-blink-merge.git] / ui / views / bubble / bubble_frame_view.cc
blob88025b04b54a3f8f4b1ed59a39d5c71226a0f530
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/l10n/l10n_util.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/native_theme/native_theme.h"
16 #include "ui/resources/grit/ui_resources.h"
17 #include "ui/strings/grit/ui_strings.h"
18 #include "ui/views/bubble/bubble_border.h"
19 #include "ui/views/controls/button/label_button.h"
20 #include "ui/views/controls/image_view.h"
21 #include "ui/views/resources/grit/views_resources.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
24 #include "ui/views/window/client_view.h"
26 namespace {
28 // Insets for the title bar views in pixels.
29 const int kTitleTopInset = 12;
30 const int kTitleLeftInset = 19;
31 const int kTitleBottomInset = 12;
32 const int kTitleRightInset = 7;
34 // The horizontal padding between the title and the icon.
35 const int kTitleHorizontalPadding = 5;
37 // Get the |vertical| or horizontal amount that |available_bounds| overflows
38 // |window_bounds|.
39 int GetOffScreenLength(const gfx::Rect& available_bounds,
40 const gfx::Rect& window_bounds,
41 bool vertical) {
42 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
43 return 0;
45 // window_bounds
46 // +---------------------------------+
47 // | top |
48 // | +------------------+ |
49 // | left | available_bounds | right |
50 // | +------------------+ |
51 // | bottom |
52 // +---------------------------------+
53 if (vertical)
54 return std::max(0, available_bounds.y() - window_bounds.y()) +
55 std::max(0, window_bounds.bottom() - available_bounds.bottom());
56 return std::max(0, available_bounds.x() - window_bounds.x()) +
57 std::max(0, window_bounds.right() - available_bounds.right());
60 } // namespace
62 namespace views {
64 // static
65 const char BubbleFrameView::kViewClassName[] = "BubbleFrameView";
67 BubbleFrameView::BubbleFrameView(const gfx::Insets& content_margins)
68 : bubble_border_(nullptr),
69 content_margins_(content_margins),
70 title_icon_(new views::ImageView()),
71 title_(nullptr),
72 close_(nullptr),
73 titlebar_extra_view_(nullptr) {
74 AddChildView(title_icon_);
76 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
77 title_ = new Label(base::string16(),
78 rb.GetFontList(ui::ResourceBundle::MediumFont));
79 title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
80 title_->set_collapse_when_hidden(true);
81 title_->SetVisible(false);
82 AddChildView(title_);
84 close_ = CreateCloseButton(this);
85 close_->SetVisible(false);
86 AddChildView(close_);
89 BubbleFrameView::~BubbleFrameView() {}
91 // static
92 gfx::Insets BubbleFrameView::GetTitleInsets() {
93 return gfx::Insets(
94 kTitleTopInset, kTitleLeftInset, kTitleBottomInset, kTitleRightInset);
97 // static
98 LabelButton* BubbleFrameView::CreateCloseButton(ButtonListener* listener) {
99 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
100 LabelButton* close = new LabelButton(listener, base::string16());
101 close->SetImage(CustomButton::STATE_NORMAL,
102 *rb.GetImageNamed(IDR_CLOSE_DIALOG).ToImageSkia());
103 close->SetImage(CustomButton::STATE_HOVERED,
104 *rb.GetImageNamed(IDR_CLOSE_DIALOG_H).ToImageSkia());
105 close->SetImage(CustomButton::STATE_PRESSED,
106 *rb.GetImageNamed(IDR_CLOSE_DIALOG_P).ToImageSkia());
107 close->SetBorder(nullptr);
108 close->SetSize(close->GetPreferredSize());
109 #if !defined(OS_WIN)
110 // Windows will automatically create a tooltip for the close button based on
111 // the HTCLOSE result from NonClientHitTest().
112 close->SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_CLOSE));
113 #endif
114 return close;
117 gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
118 gfx::Rect client_bounds = GetLocalBounds();
119 client_bounds.Inset(GetInsets());
120 client_bounds.Inset(bubble_border_->GetInsets());
121 return client_bounds;
124 gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
125 const gfx::Rect& client_bounds) const {
126 gfx::Size size(GetSizeForClientSize(client_bounds.size()));
127 return bubble_border_->GetBounds(gfx::Rect(), size);
130 int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
131 if (!bounds().Contains(point))
132 return HTNOWHERE;
133 if (close_->visible() && close_->GetMirroredBounds().Contains(point))
134 return HTCLOSE;
136 // Allow dialogs to show the system menu and be dragged.
137 if (GetWidget()->widget_delegate()->AsDialogDelegate()) {
138 gfx::Rect sys_rect(0, 0, title_->x(), title_->y());
139 sys_rect.set_origin(gfx::Point(GetMirroredXForRect(sys_rect), 0));
140 if (sys_rect.Contains(point))
141 return HTSYSMENU;
142 if (point.y() < title_->bounds().bottom())
143 return HTCAPTION;
146 return GetWidget()->client_view()->NonClientHitTest(point);
149 void BubbleFrameView::GetWindowMask(const gfx::Size& size,
150 gfx::Path* window_mask) {
151 // NOTE: this only provides implementations for the types used by dialogs.
152 if ((bubble_border_->arrow() != BubbleBorder::NONE &&
153 bubble_border_->arrow() != BubbleBorder::FLOAT) ||
154 (bubble_border_->shadow() != BubbleBorder::SMALL_SHADOW &&
155 bubble_border_->shadow() != BubbleBorder::NO_SHADOW_OPAQUE_BORDER))
156 return;
158 // Use a window mask roughly matching the border in the image assets.
159 static const int kBorderStrokeSize = 1;
160 static const SkScalar kCornerRadius = SkIntToScalar(6);
161 const gfx::Insets border_insets = bubble_border_->GetInsets();
162 SkRect rect = { SkIntToScalar(border_insets.left() - kBorderStrokeSize),
163 SkIntToScalar(border_insets.top() - kBorderStrokeSize),
164 SkIntToScalar(size.width() - border_insets.right() +
165 kBorderStrokeSize),
166 SkIntToScalar(size.height() - border_insets.bottom() +
167 kBorderStrokeSize) };
168 if (bubble_border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) {
169 window_mask->addRoundRect(rect, kCornerRadius, kCornerRadius);
170 } else {
171 static const int kBottomBorderShadowSize = 2;
172 rect.fBottom += SkIntToScalar(kBottomBorderShadowSize);
173 window_mask->addRect(rect);
177 void BubbleFrameView::ResetWindowControls() {
178 close_->SetVisible(GetWidget()->widget_delegate()->ShouldShowCloseButton());
181 void BubbleFrameView::UpdateWindowIcon() {
182 gfx::ImageSkia image;
183 if (GetWidget()->widget_delegate()->ShouldShowWindowIcon())
184 image = GetWidget()->widget_delegate()->GetWindowIcon();
185 title_icon_->SetImage(&image);
189 void BubbleFrameView::UpdateWindowTitle() {
190 title_->SetText(GetWidget()->widget_delegate()->GetWindowTitle());
191 title_->SetVisible(GetWidget()->widget_delegate()->ShouldShowWindowTitle());
194 void BubbleFrameView::SizeConstraintsChanged() {}
196 void BubbleFrameView::SetTitleFontList(const gfx::FontList& font_list) {
197 title_->SetFontList(font_list);
200 gfx::Insets BubbleFrameView::GetInsets() const {
201 gfx::Insets insets = content_margins_;
203 const int icon_height = title_icon_->GetPreferredSize().height();
204 const int label_height = title_->GetPreferredSize().height();
205 const bool has_title = icon_height > 0 || label_height > 0;
206 const int title_padding = has_title ? kTitleTopInset + kTitleBottomInset : 0;
207 const int title_height = std::max(icon_height, label_height) + title_padding;
208 const int close_height = close_->visible() ? close_->height() : 0;
209 insets += gfx::Insets(std::max(title_height, close_height), 0, 0, 0);
210 return insets;
213 gfx::Size BubbleFrameView::GetPreferredSize() const {
214 // Get the preferred size of the client area.
215 gfx::Size client_size = GetWidget()->client_view()->GetPreferredSize();
216 // Expand it to include the bubble border and space for the arrow.
217 return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
220 gfx::Size BubbleFrameView::GetMinimumSize() const {
221 // Get the minimum size of the client area.
222 gfx::Size client_size = GetWidget()->client_view()->GetMinimumSize();
223 // Expand it to include the bubble border and space for the arrow.
224 return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
227 gfx::Size BubbleFrameView::GetMaximumSize() const {
228 #if defined(OS_WIN)
229 // On Windows, this causes problems, so do not set a maximum size (it doesn't
230 // take the drop shadow area into account, resulting in a too-small window;
231 // see http://crbug.com/506206). This isn't necessary on Windows anyway, since
232 // the OS doesn't give the user controls to resize a bubble.
233 return gfx::Size();
234 #else
235 #if defined(OS_MACOSX)
236 // Allow BubbleFrameView dialogs to be resizable on Mac.
237 if (GetWidget()->widget_delegate()->CanResize()) {
238 gfx::Size client_size = GetWidget()->client_view()->GetMaximumSize();
239 if (client_size.IsEmpty())
240 return client_size;
241 return GetWindowBoundsForClientBounds(gfx::Rect(client_size)).size();
243 #endif // OS_MACOSX
244 // Non-dialog bubbles should be non-resizable, so its max size is its
245 // preferred size.
246 return GetPreferredSize();
247 #endif
250 void BubbleFrameView::Layout() {
251 gfx::Rect bounds(GetContentsBounds());
252 bounds.Inset(GetTitleInsets());
253 if (bounds.IsEmpty())
254 return;
256 // The close button top inset is actually smaller than the title top inset.
257 close_->SetPosition(gfx::Point(bounds.right() - close_->width(),
258 bounds.y() - 5));
260 gfx::Size title_icon_size(title_icon_->GetPreferredSize());
261 gfx::Size title_label_size(title_->GetPreferredSize());
262 int padding = 0;
263 if (title_icon_size.width() > 0 && title_label_size.width() > 0)
264 padding = kTitleHorizontalPadding;
265 const int title_height = std::max(title_icon_size.height(),
266 title_label_size.height());
268 const int title_icon_width = std::max(0, close_->x() - bounds.x());
269 title_icon_size.SetToMin(gfx::Size(title_icon_width, title_height));
270 gfx::Rect title_icon_bounds(
271 bounds.x(), bounds.y(), title_icon_size.width(), title_height);
272 title_icon_->SetBoundsRect(title_icon_bounds);
274 const int title_label_x = title_icon_->bounds().right() + padding;
275 const int title_label_width = std::max(0, close_->x() - title_label_x);
276 title_label_size.SetToMin(gfx::Size(title_label_width,
277 title_label_size.height()));
278 gfx::Rect title_label_bounds(
279 title_label_x, bounds.y(), title_label_size.width(), title_height);
280 title_->SetBoundsRect(title_label_bounds);
282 bounds.set_width(
283 title_icon_size.width() + title_label_size.width() + padding);
284 bounds.set_height(title_height);
286 if (titlebar_extra_view_) {
287 const int extra_width = close_->x() - bounds.right();
288 gfx::Size size = titlebar_extra_view_->GetPreferredSize();
289 size.SetToMin(gfx::Size(std::max(0, extra_width), size.height()));
290 gfx::Rect titlebar_extra_view_bounds(
291 close_->x() - size.width(),
292 bounds.y(),
293 size.width(),
294 bounds.height());
295 titlebar_extra_view_bounds.Subtract(bounds);
296 titlebar_extra_view_->SetBoundsRect(titlebar_extra_view_bounds);
300 const char* BubbleFrameView::GetClassName() const {
301 return kViewClassName;
304 void BubbleFrameView::ChildPreferredSizeChanged(View* child) {
305 if (child == titlebar_extra_view_ || child == title_)
306 Layout();
309 void BubbleFrameView::OnThemeChanged() {
310 UpdateWindowTitle();
311 ResetWindowControls();
312 UpdateWindowIcon();
315 void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) {
316 if (bubble_border_ && bubble_border_->use_theme_background_color()) {
317 bubble_border_->set_background_color(GetNativeTheme()->
318 GetSystemColor(ui::NativeTheme::kColorId_DialogBackground));
319 SchedulePaint();
323 void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) {
324 if (sender == close_)
325 GetWidget()->Close();
328 void BubbleFrameView::SetBubbleBorder(scoped_ptr<BubbleBorder> border) {
329 bubble_border_ = border.get();
330 SetBorder(border.Pass());
332 // Update the background, which relies on the border.
333 set_background(new views::BubbleBackground(bubble_border_));
336 void BubbleFrameView::SetTitlebarExtraView(View* view) {
337 DCHECK(view);
338 DCHECK(!titlebar_extra_view_);
339 AddChildView(view);
340 titlebar_extra_view_ = view;
343 gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect,
344 gfx::Size client_size,
345 bool adjust_if_offscreen) {
346 gfx::Size size(GetSizeForClientSize(client_size));
348 const BubbleBorder::Arrow arrow = bubble_border_->arrow();
349 if (adjust_if_offscreen && BubbleBorder::has_arrow(arrow)) {
350 // Try to mirror the anchoring if the bubble does not fit on the screen.
351 if (!bubble_border_->is_arrow_at_center(arrow)) {
352 MirrorArrowIfOffScreen(true, anchor_rect, size);
353 MirrorArrowIfOffScreen(false, anchor_rect, size);
354 } else {
355 const bool mirror_vertical = BubbleBorder::is_arrow_on_horizontal(arrow);
356 MirrorArrowIfOffScreen(mirror_vertical, anchor_rect, size);
357 OffsetArrowIfOffScreen(anchor_rect, size);
361 // Calculate the bounds with the arrow in its updated location and offset.
362 return bubble_border_->GetBounds(anchor_rect, size);
365 gfx::Rect BubbleFrameView::GetAvailableScreenBounds(
366 const gfx::Rect& rect) const {
367 // The bubble attempts to fit within the current screen bounds.
368 // TODO(scottmg): Native is wrong. http://crbug.com/133312
369 return gfx::Screen::GetNativeScreen()->GetDisplayNearestPoint(
370 rect.CenterPoint()).work_area();
373 bool BubbleFrameView::IsCloseButtonVisible() const {
374 return close_->visible();
377 gfx::Rect BubbleFrameView::GetCloseButtonMirroredBounds() const {
378 return close_->GetMirroredBounds();
381 void BubbleFrameView::MirrorArrowIfOffScreen(
382 bool vertical,
383 const gfx::Rect& anchor_rect,
384 const gfx::Size& client_size) {
385 // Check if the bounds don't fit on screen.
386 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
387 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
388 if (GetOffScreenLength(available_bounds, window_bounds, vertical) > 0) {
389 BubbleBorder::Arrow arrow = bubble_border()->arrow();
390 // Mirror the arrow and get the new bounds.
391 bubble_border_->set_arrow(
392 vertical ? BubbleBorder::vertical_mirror(arrow) :
393 BubbleBorder::horizontal_mirror(arrow));
394 gfx::Rect mirror_bounds =
395 bubble_border_->GetBounds(anchor_rect, client_size);
396 // Restore the original arrow if mirroring doesn't show more of the bubble.
397 // Otherwise it should invoke parent's Layout() to layout the content based
398 // on the new bubble border.
399 if (GetOffScreenLength(available_bounds, mirror_bounds, vertical) >=
400 GetOffScreenLength(available_bounds, window_bounds, vertical)) {
401 bubble_border_->set_arrow(arrow);
402 } else {
403 if (parent())
404 parent()->Layout();
405 SchedulePaint();
410 void BubbleFrameView::OffsetArrowIfOffScreen(const gfx::Rect& anchor_rect,
411 const gfx::Size& client_size) {
412 BubbleBorder::Arrow arrow = bubble_border()->arrow();
413 DCHECK(BubbleBorder::is_arrow_at_center(arrow));
415 // Get the desired bubble bounds without adjustment.
416 bubble_border_->set_arrow_offset(0);
417 gfx::Rect window_bounds(bubble_border_->GetBounds(anchor_rect, client_size));
419 gfx::Rect available_bounds(GetAvailableScreenBounds(anchor_rect));
420 if (available_bounds.IsEmpty() || available_bounds.Contains(window_bounds))
421 return;
423 // Calculate off-screen adjustment.
424 const bool is_horizontal = BubbleBorder::is_arrow_on_horizontal(arrow);
425 int offscreen_adjust = 0;
426 if (is_horizontal) {
427 if (window_bounds.x() < available_bounds.x())
428 offscreen_adjust = available_bounds.x() - window_bounds.x();
429 else if (window_bounds.right() > available_bounds.right())
430 offscreen_adjust = available_bounds.right() - window_bounds.right();
431 } else {
432 if (window_bounds.y() < available_bounds.y())
433 offscreen_adjust = available_bounds.y() - window_bounds.y();
434 else if (window_bounds.bottom() > available_bounds.bottom())
435 offscreen_adjust = available_bounds.bottom() - window_bounds.bottom();
438 // For center arrows, arrows are moved in the opposite direction of
439 // |offscreen_adjust|, e.g. positive |offscreen_adjust| means bubble
440 // window needs to be moved to the right and that means we need to move arrow
441 // to the left, and that means negative offset.
442 bubble_border_->set_arrow_offset(
443 bubble_border_->GetArrowOffset(window_bounds.size()) - offscreen_adjust);
444 if (offscreen_adjust)
445 SchedulePaint();
448 gfx::Size BubbleFrameView::GetSizeForClientSize(
449 const gfx::Size& client_size) const {
450 // Accommodate the width of the title bar elements.
451 int title_bar_width = GetInsets().width() + border()->GetInsets().width();
452 gfx::Size title_icon_size = title_icon_->GetPreferredSize();
453 gfx::Size title_label_size = title_->GetPreferredSize();
454 if (title_icon_size.width() > 0 || title_label_size.width() > 0)
455 title_bar_width += kTitleLeftInset;
456 if (title_icon_size.width() > 0 && title_label_size.width() > 0)
457 title_bar_width += kTitleHorizontalPadding;
458 title_bar_width += title_icon_size.width();
459 title_bar_width += title_label_size.width();
460 if (close_->visible())
461 title_bar_width += close_->width() + 1;
462 if (titlebar_extra_view_ != NULL)
463 title_bar_width += titlebar_extra_view_->GetPreferredSize().width();
464 gfx::Size size(client_size);
465 size.SetToMax(gfx::Size(title_bar_width, 0));
466 const gfx::Insets insets(GetInsets());
467 size.Enlarge(insets.width(), insets.height());
468 return size;
471 } // namespace views