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(gfx::ScreenType screen_type
)
51 : screen_type_(screen_type
),
53 tooltip_window_(NULL
) {
54 label_
.set_owned_by_client();
55 label_
.SetMultiLine(true);
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 int TooltipAura::GetMaxWidth(const gfx::Point
& location
) const {
145 // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
146 // out a way to merge.
147 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
148 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(location
).bounds());
149 return (display_bounds
.width() + 1) / 2;
152 void TooltipAura::SetTooltipBounds(const gfx::Point
& mouse_pos
,
153 const gfx::Size
& tooltip_size
) {
154 gfx::Rect
tooltip_rect(mouse_pos
, tooltip_size
);
155 tooltip_rect
.Offset(kCursorOffsetX
, kCursorOffsetY
);
156 gfx::Screen
* screen
= gfx::Screen::GetScreenByType(screen_type_
);
157 gfx::Rect
display_bounds(screen
->GetDisplayNearestPoint(mouse_pos
).bounds());
159 // If tooltip is out of bounds on the x axis, we simply shift it
160 // horizontally by the offset.
161 if (tooltip_rect
.right() > display_bounds
.right()) {
162 int h_offset
= tooltip_rect
.right() - display_bounds
.right();
163 tooltip_rect
.Offset(-h_offset
, 0);
166 // If tooltip is out of bounds on the y axis, we flip it to appear above the
167 // mouse cursor instead of below.
168 if (tooltip_rect
.bottom() > display_bounds
.bottom())
169 tooltip_rect
.set_y(mouse_pos
.y() - tooltip_size
.height());
171 tooltip_rect
.AdjustToFit(display_bounds
);
172 widget_
->SetBounds(tooltip_rect
);
175 void TooltipAura::DestroyWidget() {
177 widget_
->RemoveObserver(this);
183 void TooltipAura::SetText(aura::Window
* window
,
184 const base::string16
& tooltip_text
,
185 const gfx::Point
& location
) {
186 tooltip_window_
= window
;
189 base::string16
trimmed_text(tooltip_text
);
190 TrimTooltipToFit(label_
.font_list(), GetMaxWidth(location
), &trimmed_text
,
191 &max_width
, &line_count
);
192 label_
.SetText(trimmed_text
);
195 widget_
= CreateTooltipWidget(tooltip_window_
);
196 widget_
->SetContentsView(&label_
);
197 widget_
->AddObserver(this);
200 label_
.SizeToFit(max_width
+ label_
.GetInsets().width());
201 SetTooltipBounds(location
, label_
.size());
203 ui::NativeTheme
* native_theme
= widget_
->GetNativeTheme();
204 label_
.set_background(
205 views::Background::CreateSolidBackground(
206 native_theme
->GetSystemColor(
207 ui::NativeTheme::kColorId_TooltipBackground
)));
209 label_
.SetAutoColorReadabilityEnabled(false);
210 label_
.SetEnabledColor(native_theme
->GetSystemColor(
211 ui::NativeTheme::kColorId_TooltipText
));
214 void TooltipAura::Show() {
217 widget_
->StackAtTop();
221 void TooltipAura::Hide() {
222 tooltip_window_
= NULL
;
227 bool TooltipAura::IsVisible() {
228 return widget_
&& widget_
->IsVisible();
231 void TooltipAura::OnWidgetDestroying(views::Widget
* widget
) {
232 DCHECK_EQ(widget_
, widget
);
234 tooltip_window_
= NULL
;
237 } // namespace corewm