1 // Copyright (c) 2011 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 "chrome/browser/ui/views/bubble/border_contents.h"
6
7 #include <algorithm>
8
9 #include "base/logging.h"
10 #include "chrome/browser/ui/window_sizer.h"
11 #include "third_party/skia/include/core/SkPaint.h"
12
Init()13 void BorderContents::Init() {
14 // Default arrow location.
15 BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_LEFT;
16 if (base::i18n::IsRTL())
17 arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
18 DCHECK(!bubble_border_);
19
20 bubble_border_ = new BubbleBorder(arrow_location);
21 set_border(bubble_border_);
22 set_background(new BubbleBackground(bubble_border_));
23 }
24
SetBackgroundColor(SkColor color)25 void BorderContents::SetBackgroundColor(SkColor color) {
26 bubble_border_->set_background_color(color);
27 }
28
SizeAndGetBounds(const gfx::Rect & position_relative_to,BubbleBorder::ArrowLocation arrow_location,bool allow_bubble_offscreen,const gfx::Size & contents_size,gfx::Rect * contents_bounds,gfx::Rect * window_bounds)29 void BorderContents::SizeAndGetBounds(
30 const gfx::Rect& position_relative_to,
31 BubbleBorder::ArrowLocation arrow_location,
32 bool allow_bubble_offscreen,
33 const gfx::Size& contents_size,
34 gfx::Rect* contents_bounds,
35 gfx::Rect* window_bounds) {
36 if (base::i18n::IsRTL())
37 arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
38 bubble_border_->set_arrow_location(arrow_location);
39 // Set the border.
40 set_border(bubble_border_);
41
42 // Give the contents a margin.
43 gfx::Size local_contents_size(contents_size);
44 local_contents_size.Enlarge(kLeftMargin + kRightMargin,
45 kTopMargin + kBottomMargin);
46
47 // Try putting the arrow in its initial location, and calculating the bounds.
48 *window_bounds =
49 bubble_border_->GetBounds(position_relative_to, local_contents_size);
50 if (!allow_bubble_offscreen) {
51 gfx::Rect monitor_bounds = GetMonitorBounds(position_relative_to);
52 if (!monitor_bounds.IsEmpty()) {
53 // Try to resize vertically if this does not fit on the screen.
54 MirrorArrowIfOffScreen(true, // |vertical|.
55 position_relative_to, monitor_bounds,
56 local_contents_size, &arrow_location,
57 window_bounds);
58 // Then try to resize horizontally if it still does not fit on the screen.
59 MirrorArrowIfOffScreen(false, // |vertical|.
60 position_relative_to, monitor_bounds,
61 local_contents_size, &arrow_location,
62 window_bounds);
63 }
64 }
65
66 // Calculate the bounds of the contained contents (in window coordinates) by
67 // subtracting the border dimensions and margin amounts.
68 *contents_bounds = gfx::Rect(gfx::Point(), window_bounds->size());
69 gfx::Insets insets;
70 bubble_border_->GetInsets(&insets);
71 contents_bounds->Inset(insets.left() + kLeftMargin, insets.top() + kTopMargin,
72 insets.right() + kRightMargin, insets.bottom() + kBottomMargin);
73 }
74
GetMonitorBounds(const gfx::Rect & rect)75 gfx::Rect BorderContents::GetMonitorBounds(const gfx::Rect& rect) {
76 scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_provider(
77 WindowSizer::CreateDefaultMonitorInfoProvider());
78 return monitor_provider->GetMonitorWorkAreaMatching(rect);
79 }
80
MirrorArrowIfOffScreen(bool vertical,const gfx::Rect & position_relative_to,const gfx::Rect & monitor_bounds,const gfx::Size & local_contents_size,BubbleBorder::ArrowLocation * arrow_location,gfx::Rect * window_bounds)81 void BorderContents::MirrorArrowIfOffScreen(
82 bool vertical,
83 const gfx::Rect& position_relative_to,
84 const gfx::Rect& monitor_bounds,
85 const gfx::Size& local_contents_size,
86 BubbleBorder::ArrowLocation* arrow_location,
87 gfx::Rect* window_bounds) {
88 // If the bounds don't fit, move the arrow to its mirrored position to see if
89 // it improves things.
90 gfx::Insets offscreen_insets;
91 if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
92 &offscreen_insets) &&
93 GetInsetsLength(offscreen_insets, vertical) > 0) {
94 BubbleBorder::ArrowLocation original_arrow_location = *arrow_location;
95 *arrow_location =
96 vertical ? BubbleBorder::vertical_mirror(*arrow_location) :
97 BubbleBorder::horizontal_mirror(*arrow_location);
98
99 // Change the arrow and get the new bounds.
100 bubble_border_->set_arrow_location(*arrow_location);
101 *window_bounds = bubble_border_->GetBounds(position_relative_to,
102 local_contents_size);
103 gfx::Insets new_offscreen_insets;
104 // If there is more of the window offscreen, we'll keep the old arrow.
105 if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
106 &new_offscreen_insets) &&
107 GetInsetsLength(new_offscreen_insets, vertical) >=
108 GetInsetsLength(offscreen_insets, vertical)) {
109 *arrow_location = original_arrow_location;
110 bubble_border_->set_arrow_location(*arrow_location);
111 *window_bounds = bubble_border_->GetBounds(position_relative_to,
112 local_contents_size);
113 }
114 }
115 }
116
117 // static
ComputeOffScreenInsets(const gfx::Rect & monitor_bounds,const gfx::Rect & window_bounds,gfx::Insets * offscreen_insets)118 bool BorderContents::ComputeOffScreenInsets(const gfx::Rect& monitor_bounds,
119 const gfx::Rect& window_bounds,
120 gfx::Insets* offscreen_insets) {
121 if (monitor_bounds.Contains(window_bounds))
122 return false;
123
124 if (!offscreen_insets)
125 return true;
126
127 // window_bounds
128 // +-------------------------------+
129 // | top |
130 // | +----------------+ |
131 // | left | monitor_bounds | right |
132 // | +----------------+ |
133 // | bottom |
134 // +-------------------------------+
135 int top = std::max(0, monitor_bounds.y() - window_bounds.y());
136 int left = std::max(0, monitor_bounds.x() - window_bounds.x());
137 int bottom = std::max(0, window_bounds.bottom() - monitor_bounds.bottom());
138 int right = std::max(0, window_bounds.right() - monitor_bounds.right());
139
140 offscreen_insets->Set(top, left, bottom, right);
141 return true;
142 }
143
144 // static
GetInsetsLength(const gfx::Insets & insets,bool vertical)145 int BorderContents::GetInsetsLength(const gfx::Insets& insets, bool vertical) {
146 return vertical ? insets.height() : insets.width();
147 }
148