1 // Copyright 2013 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/corewm/tooltip_aura.h"
7 #include "base/strings/string_split.h"
8 #include "ui/aura/window.h"
9 #include "ui/aura/window_tree_host.h"
10 #include "ui/base/resource/resource_bundle.h"
11 #include "ui/gfx/screen.h"
12 #include "ui/gfx/text_elider.h"
13 #include "ui/gfx/text_utils.h"
14 #include "ui/native_theme/native_theme.h"
15 #include "ui/views/background.h"
16 #include "ui/views/border.h"
17 #include "ui/views/widget/widget.h"
21 const int kTooltipHorizontalPadding
= 3;
23 // Max visual tooltip width. If a tooltip is greater than this width, it will
25 const int kTooltipMaxWidthPixels
= 400;
27 const size_t kMaxLines
= 10;
29 // TODO(derat): This padding is needed on Chrome OS devices but seems excessive
30 // when running the same binary on a Linux workstation; presumably there's a
31 // difference in font metrics. Rationalize this.
32 const int kTooltipVerticalPadding
= 2;
34 // FIXME: get cursor offset from actual cursor size.
35 const int kCursorOffsetX
= 10;
36 const int kCursorOffsetY
= 15;
38 // Creates a widget of type TYPE_TOOLTIP
39 views::Widget
* CreateTooltipWidget(aura::Window
* tooltip_window
) {
40 views::Widget
* widget
= new views::Widget
;
41 views::Widget::InitParams params
;
42 // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
43 // auto-parented to the right container.
44 params
.type
= views::Widget::InitParams::TYPE_TOOLTIP
;
45 params
.context
= tooltip_window
;
46 DCHECK(params
.context
);
47 params
.keep_on_top
= true;
48 params
.accept_events
= false;
58 TooltipAura::TooltipAura(gfx::ScreenType screen_type
)
59 : screen_type_(screen_type
),
61 tooltip_window_(NULL
) {
62 label_
.set_owned_by_client();
63 label_
.SetMultiLine(true);
66 TooltipAura::~TooltipAura() {
71 void TooltipAura::TrimTooltipToFit(const gfx::FontList
& font_list
,
79 // Determine the available width for the tooltip.
80 int available_width
= std::min(kTooltipMaxWidthPixels
, max_width
);
82 std::vector
<base::string16
> lines
;
83 base::SplitString(*text
, '\n', &lines
);
84 std::vector
<base::string16
> result_lines
;
86 // Format each line to fit.
87 for (std::vector
<base::string16
>::iterator l
= lines
.begin();
88 l
!= lines
.end(); ++l
) {
89 // We break the line at word boundaries, then stuff as many words as we can
90 // in the available width to the current line, and move the remaining words
92 std::vector
<base::string16
> words
;
93 base::SplitStringDontTrim(*l
, ' ', &words
);
94 int current_width
= 0;
96 for (std::vector
<base::string16
>::iterator w
= words
.begin();
97 w
!= words
.end(); ++w
) {
98 base::string16 word
= *w
;
99 if (w
+ 1 != words
.end())
101 int word_width
= gfx::GetStringWidth(word
, font_list
);
102 if (current_width
+ word_width
> available_width
) {
103 // Current width will exceed the available width. Must start a new line.
105 result_lines
.push_back(line
);
109 current_width
+= word_width
;
112 result_lines
.push_back(line
);
115 // Clamp number of lines to |kMaxLines|.
116 if (result_lines
.size() > kMaxLines
) {
117 result_lines
.resize(kMaxLines
);
118 // Add ellipses character to last line.
119 result_lines
[kMaxLines
- 1] = gfx::TruncateString(
120 result_lines
.back(), result_lines
.back().length() - 1);
122 *line_count
= result_lines
.size();
124 // Flatten the result.
125 base::string16 result
;
126 for (std::vector
<base::string16
>::iterator l
= result_lines
.begin();
127 l
!= result_lines
.end(); ++l
) {
129 result
.push_back('\n');
130 int line_width
= gfx::GetStringWidth(*l
, font_list
);
131 // Since we only break at word boundaries, it could happen that due to some
132 // very long word, line_width is greater than the available_width. In such
133 // case, we simply truncate at available_width and add ellipses at the end.
134 if (line_width
> available_width
) {
135 *width
= available_width
;
136 result
.append(gfx::ElideText(*l
, font_list
, available_width
,
139 *width
= std::max(*width
, line_width
);
146 int TooltipAura::GetMaxWidth(const gfx::Point
& location
) const {
147 // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
148 // out a way to merge.
149 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
150 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(location
).bounds());
151 return (display_bounds
.width() + 1) / 2;
154 void TooltipAura::SetTooltipBounds(const gfx::Point
& mouse_pos
,
156 int tooltip_height
) {
157 gfx::Rect
tooltip_rect(mouse_pos
.x(), mouse_pos
.y(), tooltip_width
,
160 tooltip_rect
.Offset(kCursorOffsetX
, kCursorOffsetY
);
161 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
162 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(mouse_pos
).bounds());
164 // If tooltip is out of bounds on the x axis, we simply shift it
165 // horizontally by the offset.
166 if (tooltip_rect
.right() > display_bounds
.right()) {
167 int h_offset
= tooltip_rect
.right() - display_bounds
.right();
168 tooltip_rect
.Offset(-h_offset
, 0);
171 // If tooltip is out of bounds on the y axis, we flip it to appear above the
172 // mouse cursor instead of below.
173 if (tooltip_rect
.bottom() > display_bounds
.bottom())
174 tooltip_rect
.set_y(mouse_pos
.y() - tooltip_height
);
176 tooltip_rect
.AdjustToFit(display_bounds
);
177 widget_
->SetBounds(tooltip_rect
);
180 void TooltipAura::DestroyWidget() {
182 widget_
->RemoveObserver(this);
188 void TooltipAura::SetText(aura::Window
* window
,
189 const base::string16
& tooltip_text
,
190 const gfx::Point
& location
) {
191 tooltip_window_
= window
;
192 int max_width
, line_count
;
193 base::string16
trimmed_text(tooltip_text
);
194 TrimTooltipToFit(label_
.font_list(), GetMaxWidth(location
), &trimmed_text
,
195 &max_width
, &line_count
);
196 label_
.SetText(trimmed_text
);
198 int width
= max_width
+ 2 * kTooltipHorizontalPadding
;
199 int height
= label_
.GetHeightForWidth(max_width
) +
200 2 * kTooltipVerticalPadding
;
203 widget_
= CreateTooltipWidget(tooltip_window_
);
204 widget_
->SetContentsView(&label_
);
205 widget_
->AddObserver(this);
208 SetTooltipBounds(location
, width
, height
);
210 ui::NativeTheme
* native_theme
= widget_
->GetNativeTheme();
211 label_
.set_background(
212 views::Background::CreateSolidBackground(
213 native_theme
->GetSystemColor(
214 ui::NativeTheme::kColorId_TooltipBackground
)));
216 label_
.SetAutoColorReadabilityEnabled(false);
217 label_
.SetEnabledColor(native_theme
->GetSystemColor(
218 ui::NativeTheme::kColorId_TooltipText
));
221 void TooltipAura::Show() {
224 widget_
->StackAtTop();
228 void TooltipAura::Hide() {
229 tooltip_window_
= NULL
;
234 bool TooltipAura::IsVisible() {
235 return widget_
&& widget_
->IsVisible();
238 void TooltipAura::OnWidgetDestroying(views::Widget
* widget
) {
239 DCHECK_EQ(widget_
, widget
);
241 tooltip_window_
= NULL
;
244 } // namespace corewm