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/gfx/screen.h"
11 #include "ui/gfx/text_elider.h"
12 #include "ui/gfx/text_utils.h"
13 #include "ui/native_theme/native_theme.h"
14 #include "ui/views/background.h"
15 #include "ui/views/border.h"
16 #include "ui/views/widget/widget.h"
20 const int kTooltipHorizontalPadding
= 3;
22 // Max visual tooltip width. If a tooltip is greater than this width, it will
24 const int kTooltipMaxWidthPixels
= 400;
26 const size_t kMaxLines
= 10;
28 // TODO(derat): This padding is needed on Chrome OS devices but seems excessive
29 // when running the same binary on a Linux workstation; presumably there's a
30 // difference in font metrics. Rationalize this.
31 const int kTooltipVerticalPadding
= 2;
33 // FIXME: get cursor offset from actual cursor size.
34 const int kCursorOffsetX
= 10;
35 const int kCursorOffsetY
= 15;
37 // Creates a widget of type TYPE_TOOLTIP
38 views::Widget
* CreateTooltipWidget(aura::Window
* tooltip_window
) {
39 views::Widget
* widget
= new views::Widget
;
40 views::Widget::InitParams params
;
41 // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
42 // auto-parented to the right container.
43 params
.type
= views::Widget::InitParams::TYPE_TOOLTIP
;
44 params
.context
= tooltip_window
;
45 DCHECK(params
.context
);
46 params
.keep_on_top
= true;
47 params
.accept_events
= false;
57 TooltipAura::TooltipAura(gfx::ScreenType screen_type
)
58 : screen_type_(screen_type
),
60 tooltip_window_(NULL
) {
61 label_
.set_owned_by_client();
62 label_
.SetMultiLine(true);
65 TooltipAura::~TooltipAura() {
70 void TooltipAura::TrimTooltipToFit(const gfx::FontList
& font_list
,
78 // Determine the available width for the tooltip.
79 int available_width
= std::min(kTooltipMaxWidthPixels
, max_width
);
81 std::vector
<base::string16
> lines
;
82 base::SplitString(*text
, '\n', &lines
);
83 std::vector
<base::string16
> result_lines
;
85 // Format each line to fit.
86 for (std::vector
<base::string16
>::iterator l
= lines
.begin();
87 l
!= lines
.end(); ++l
) {
88 // We break the line at word boundaries, then stuff as many words as we can
89 // in the available width to the current line, and move the remaining words
91 std::vector
<base::string16
> words
;
92 base::SplitStringDontTrim(*l
, ' ', &words
);
93 int current_width
= 0;
95 for (std::vector
<base::string16
>::iterator w
= words
.begin();
96 w
!= words
.end(); ++w
) {
97 base::string16 word
= *w
;
98 if (w
+ 1 != words
.end())
100 int word_width
= gfx::GetStringWidth(word
, font_list
);
101 if (current_width
+ word_width
> available_width
) {
102 // Current width will exceed the available width. Must start a new line.
104 result_lines
.push_back(line
);
108 current_width
+= word_width
;
111 result_lines
.push_back(line
);
114 // Clamp number of lines to |kMaxLines|.
115 if (result_lines
.size() > kMaxLines
) {
116 result_lines
.resize(kMaxLines
);
117 // Add ellipses character to last line.
118 result_lines
[kMaxLines
- 1] = gfx::TruncateString(
119 result_lines
.back(), result_lines
.back().length() - 1, gfx::WORD_BREAK
);
121 *line_count
= result_lines
.size();
123 // Flatten the result.
124 base::string16 result
;
125 for (std::vector
<base::string16
>::iterator l
= result_lines
.begin();
126 l
!= result_lines
.end(); ++l
) {
128 result
.push_back('\n');
129 int line_width
= gfx::GetStringWidth(*l
, font_list
);
130 // Since we only break at word boundaries, it could happen that due to some
131 // very long word, line_width is greater than the available_width. In such
132 // case, we simply truncate at available_width and add ellipses at the end.
133 if (line_width
> available_width
) {
134 *width
= available_width
;
135 result
.append(gfx::ElideText(*l
, font_list
, available_width
,
138 *width
= std::max(*width
, line_width
);
145 int TooltipAura::GetMaxWidth(const gfx::Point
& location
) const {
146 // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
147 // out a way to merge.
148 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
149 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(location
).bounds());
150 return (display_bounds
.width() + 1) / 2;
153 void TooltipAura::SetTooltipBounds(const gfx::Point
& mouse_pos
,
155 int tooltip_height
) {
156 gfx::Rect
tooltip_rect(mouse_pos
.x(), mouse_pos
.y(), tooltip_width
,
159 tooltip_rect
.Offset(kCursorOffsetX
, kCursorOffsetY
);
160 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
161 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(mouse_pos
).bounds());
163 // If tooltip is out of bounds on the x axis, we simply shift it
164 // horizontally by the offset.
165 if (tooltip_rect
.right() > display_bounds
.right()) {
166 int h_offset
= tooltip_rect
.right() - display_bounds
.right();
167 tooltip_rect
.Offset(-h_offset
, 0);
170 // If tooltip is out of bounds on the y axis, we flip it to appear above the
171 // mouse cursor instead of below.
172 if (tooltip_rect
.bottom() > display_bounds
.bottom())
173 tooltip_rect
.set_y(mouse_pos
.y() - tooltip_height
);
175 tooltip_rect
.AdjustToFit(display_bounds
);
176 widget_
->SetBounds(tooltip_rect
);
179 void TooltipAura::DestroyWidget() {
181 widget_
->RemoveObserver(this);
187 void TooltipAura::SetText(aura::Window
* window
,
188 const base::string16
& tooltip_text
,
189 const gfx::Point
& location
) {
190 tooltip_window_
= window
;
191 int max_width
, line_count
;
192 base::string16
trimmed_text(tooltip_text
);
193 TrimTooltipToFit(label_
.font_list(), GetMaxWidth(location
), &trimmed_text
,
194 &max_width
, &line_count
);
195 label_
.SetText(trimmed_text
);
197 int width
= max_width
+ 2 * kTooltipHorizontalPadding
;
198 int height
= label_
.GetHeightForWidth(max_width
) +
199 2 * kTooltipVerticalPadding
;
202 widget_
= CreateTooltipWidget(tooltip_window_
);
203 widget_
->SetContentsView(&label_
);
204 widget_
->AddObserver(this);
207 SetTooltipBounds(location
, width
, height
);
209 ui::NativeTheme
* native_theme
= widget_
->GetNativeTheme();
210 label_
.set_background(
211 views::Background::CreateSolidBackground(
212 native_theme
->GetSystemColor(
213 ui::NativeTheme::kColorId_TooltipBackground
)));
215 label_
.SetAutoColorReadabilityEnabled(false);
216 label_
.SetEnabledColor(native_theme
->GetSystemColor(
217 ui::NativeTheme::kColorId_TooltipText
));
220 void TooltipAura::Show() {
223 widget_
->StackAtTop();
227 void TooltipAura::Hide() {
228 tooltip_window_
= NULL
;
233 bool TooltipAura::IsVisible() {
234 return widget_
&& widget_
->IsVisible();
237 void TooltipAura::OnWidgetDestroying(views::Widget
* widget
) {
238 DCHECK_EQ(widget_
, widget
);
240 tooltip_window_
= NULL
;
243 } // namespace corewm