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 // Max visual tooltip width. If a tooltip is greater than this width, it will
22 const int kTooltipMaxWidthPixels
= 400;
24 const size_t kMaxLines
= 10;
26 // FIXME: get cursor offset from actual cursor size.
27 const int kCursorOffsetX
= 10;
28 const int kCursorOffsetY
= 15;
30 // Creates a widget of type TYPE_TOOLTIP
31 views::Widget
* CreateTooltipWidget(aura::Window
* tooltip_window
) {
32 views::Widget
* widget
= new views::Widget
;
33 views::Widget::InitParams params
;
34 // For aura, since we set the type to TYPE_TOOLTIP, the widget will get
35 // auto-parented to the right container.
36 params
.type
= views::Widget::InitParams::TYPE_TOOLTIP
;
37 params
.context
= tooltip_window
;
38 DCHECK(params
.context
);
39 params
.keep_on_top
= true;
40 params
.accept_events
= false;
50 TooltipAura::TooltipAura()
52 tooltip_window_(NULL
) {
53 label_
.set_owned_by_client();
54 label_
.SetMultiLine(true);
55 label_
.SetHorizontalAlignment(gfx::ALIGN_TO_HEAD
);
57 const int kHorizontalPadding
= 3;
58 const int kVerticalPadding
= 2;
59 label_
.SetBorder(Border::CreateEmptyBorder(
60 kVerticalPadding
, kHorizontalPadding
,
61 kVerticalPadding
, kHorizontalPadding
));
64 TooltipAura::~TooltipAura() {
69 void TooltipAura::TrimTooltipToFit(const gfx::FontList
& font_list
,
77 // Determine the available width for the tooltip.
78 int available_width
= std::min(kTooltipMaxWidthPixels
, max_width
);
80 std::vector
<base::string16
> lines
;
81 base::SplitString(*text
, '\n', &lines
);
82 std::vector
<base::string16
> result_lines
;
84 // Format each line to fit.
85 for (std::vector
<base::string16
>::iterator l
= lines
.begin();
86 l
!= lines
.end(); ++l
) {
87 // We break the line at word boundaries, then stuff as many words as we can
88 // in the available width to the current line, and move the remaining words
90 std::vector
<base::string16
> words
;
91 base::SplitStringDontTrim(*l
, ' ', &words
);
92 int current_width
= 0;
94 for (std::vector
<base::string16
>::iterator w
= words
.begin();
95 w
!= words
.end(); ++w
) {
96 base::string16 word
= *w
;
97 if (w
+ 1 != words
.end())
99 int word_width
= gfx::GetStringWidth(word
, font_list
);
100 if (current_width
+ word_width
> available_width
) {
101 // Current width will exceed the available width. Must start a new line.
103 result_lines
.push_back(line
);
107 current_width
+= word_width
;
110 result_lines
.push_back(line
);
113 // Clamp number of lines to |kMaxLines|.
114 if (result_lines
.size() > kMaxLines
) {
115 result_lines
.resize(kMaxLines
);
116 // Add ellipses character to last line.
117 result_lines
[kMaxLines
- 1] = gfx::TruncateString(
118 result_lines
.back(), result_lines
.back().length() - 1, gfx::WORD_BREAK
);
120 *line_count
= result_lines
.size();
122 // Flatten the result.
123 base::string16 result
;
124 for (std::vector
<base::string16
>::iterator l
= result_lines
.begin();
125 l
!= result_lines
.end(); ++l
) {
127 result
.push_back('\n');
128 int line_width
= gfx::GetStringWidth(*l
, font_list
);
129 // Since we only break at word boundaries, it could happen that due to some
130 // very long word, line_width is greater than the available_width. In such
131 // case, we simply truncate at available_width and add ellipses at the end.
132 if (line_width
> available_width
) {
133 *width
= available_width
;
134 result
.append(gfx::ElideText(*l
, font_list
, available_width
,
137 *width
= std::max(*width
, line_width
);
144 void TooltipAura::SetTooltipBounds(const gfx::Point
& mouse_pos
,
145 const gfx::Size
& tooltip_size
) {
146 gfx::Rect
tooltip_rect(mouse_pos
, tooltip_size
);
147 tooltip_rect
.Offset(kCursorOffsetX
, kCursorOffsetY
);
148 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(tooltip_window_
);
149 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(mouse_pos
).bounds());
151 // If tooltip is out of bounds on the x axis, we simply shift it
152 // horizontally by the offset.
153 if (tooltip_rect
.right() > display_bounds
.right()) {
154 int h_offset
= tooltip_rect
.right() - display_bounds
.right();
155 tooltip_rect
.Offset(-h_offset
, 0);
158 // If tooltip is out of bounds on the y axis, we flip it to appear above the
159 // mouse cursor instead of below.
160 if (tooltip_rect
.bottom() > display_bounds
.bottom())
161 tooltip_rect
.set_y(mouse_pos
.y() - tooltip_size
.height());
163 tooltip_rect
.AdjustToFit(display_bounds
);
164 widget_
->SetBounds(tooltip_rect
);
167 void TooltipAura::DestroyWidget() {
169 widget_
->RemoveObserver(this);
175 int TooltipAura::GetMaxWidth(const gfx::Point
& location
,
176 aura::Window
* context
) const {
177 gfx::Screen
* screen
= gfx::Screen::GetScreenFor(context
);
178 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(location
).bounds());
179 return std::min(kTooltipMaxWidthPixels
, (display_bounds
.width() + 1) / 2);
182 void TooltipAura::SetText(aura::Window
* window
,
183 const base::string16
& tooltip_text
,
184 const gfx::Point
& location
) {
185 tooltip_window_
= window
;
188 base::string16
trimmed_text(tooltip_text
);
189 TrimTooltipToFit(label_
.font_list(), GetMaxWidth(location
, window
),
190 &trimmed_text
, &max_width
, &line_count
);
191 label_
.SetText(trimmed_text
);
194 widget_
= CreateTooltipWidget(tooltip_window_
);
195 widget_
->SetContentsView(&label_
);
196 widget_
->AddObserver(this);
199 label_
.SizeToFit(max_width
+ label_
.GetInsets().width());
200 SetTooltipBounds(location
, label_
.size());
202 ui::NativeTheme
* native_theme
= widget_
->GetNativeTheme();
203 label_
.set_background(
204 views::Background::CreateSolidBackground(
205 native_theme
->GetSystemColor(
206 ui::NativeTheme::kColorId_TooltipBackground
)));
208 label_
.SetAutoColorReadabilityEnabled(false);
209 label_
.SetEnabledColor(native_theme
->GetSystemColor(
210 ui::NativeTheme::kColorId_TooltipText
));
213 void TooltipAura::Show() {
216 widget_
->StackAtTop();
220 void TooltipAura::Hide() {
221 tooltip_window_
= NULL
;
226 bool TooltipAura::IsVisible() {
227 return widget_
&& widget_
->IsVisible();
230 void TooltipAura::OnWidgetDestroying(views::Widget
* widget
) {
231 DCHECK_EQ(widget_
, widget
);
233 tooltip_window_
= NULL
;
236 } // namespace corewm