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/command_line.h"
8 #include "base/strings/string_split.h"
9 #include "ui/aura/root_window.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/views/corewm/corewm_switches.h"
14 #include "ui/views/widget/widget.h"
18 const SkColor kTooltipBackground = 0xFFFFFFCC;
19 const SkColor kTooltipBorder = 0xFF646450;
20 const int kTooltipBorderWidth = 1;
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;
53 gfx::Font GetDefaultFont() {
54 // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
55 // out a way to merge.
56 return ui::ResourceBundle::GetSharedInstance().GetFont(
57 ui::ResourceBundle::BaseFont);
65 TooltipAura::TooltipAura(gfx::ScreenType screen_type)
66 : screen_type_(screen_type),
68 tooltip_window_(NULL) {
69 label_.set_background(
70 views::Background::CreateSolidBackground(kTooltipBackground));
71 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoDropShadows)) {
73 views::Border::CreateSolidBorder(kTooltipBorderWidth,
76 label_.set_owned_by_client();
77 label_.SetMultiLine(true);
80 TooltipAura::~TooltipAura() {
85 void TooltipAura::TrimTooltipToFit(int max_width,
92 // Determine the available width for the tooltip.
93 int available_width = std::min(kTooltipMaxWidthPixels, max_width);
95 std::vector<string16> lines;
96 base::SplitString(*text, '\n', &lines);
97 std::vector<string16> result_lines;
99 // Format each line to fit.
100 gfx::Font font = GetDefaultFont();
101 for (std::vector<string16>::iterator l = lines.begin(); l != lines.end();
103 // We break the line at word boundaries, then stuff as many words as we can
104 // in the available width to the current line, and move the remaining words
106 std::vector<string16> words;
107 base::SplitStringDontTrim(*l, ' ', &words);
108 int current_width = 0;
110 for (std::vector<string16>::iterator w = words.begin(); w != words.end();
113 if (w + 1 != words.end())
115 int word_width = font.GetStringWidth(word);
116 if (current_width + word_width > available_width) {
117 // Current width will exceed the available width. Must start a new line.
119 result_lines.push_back(line);
123 current_width += word_width;
126 result_lines.push_back(line);
129 // Clamp number of lines to |kMaxLines|.
130 if (result_lines.size() > kMaxLines) {
131 result_lines.resize(kMaxLines);
132 // Add ellipses character to last line.
133 result_lines[kMaxLines - 1] = gfx::TruncateString(
134 result_lines.back(), result_lines.back().length() - 1);
136 *line_count = result_lines.size();
138 // Flatten the result.
140 for (std::vector<string16>::iterator l = result_lines.begin();
141 l != result_lines.end(); ++l) {
143 result.push_back('\n');
144 int line_width = font.GetStringWidth(*l);
145 // Since we only break at word boundaries, it could happen that due to some
146 // very long word, line_width is greater than the available_width. In such
147 // case, we simply truncate at available_width and add ellipses at the end.
148 if (line_width > available_width) {
149 *width = available_width;
150 result.append(gfx::ElideText(*l, font, available_width,
153 *width = std::max(*width, line_width);
160 int TooltipAura::GetMaxWidth(const gfx::Point& location) const {
161 // TODO(varunjain): implementation duplicated in tooltip_manager_aura. Figure
162 // out a way to merge.
163 gfx::Rect display_bounds = GetBoundsForTooltip(location);
164 return (display_bounds.width() + 1) / 2;
167 gfx::Rect TooltipAura::GetBoundsForTooltip(
168 const gfx::Point& origin) const {
169 DCHECK(tooltip_window_);
170 gfx::Rect widget_bounds;
171 // For Desktop aura we constrain the tooltip to the bounds of the Widget
172 // (which comes from the RootWindow).
173 if (screen_type_ == gfx::SCREEN_TYPE_NATIVE &&
174 gfx::SCREEN_TYPE_NATIVE != gfx::SCREEN_TYPE_ALTERNATE) {
175 aura::WindowEventDispatcher* dispatcher = tooltip_window_->GetDispatcher();
176 widget_bounds = gfx::Rect(dispatcher->GetHostOrigin(),
177 dispatcher->GetHostSize());
179 gfx::Screen* screen = gfx::Screen::GetScreenByType(screen_type_);
180 gfx::Rect bounds(screen->GetDisplayNearestPoint(origin).bounds());
181 if (!widget_bounds.IsEmpty())
182 bounds.Intersect(widget_bounds);
186 void TooltipAura::SetTooltipBounds(const gfx::Point& mouse_pos,
188 int tooltip_height) {
189 gfx::Rect tooltip_rect(mouse_pos.x(), mouse_pos.y(), tooltip_width,
192 tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY);
193 gfx::Rect display_bounds = GetBoundsForTooltip(mouse_pos);
195 // If tooltip is out of bounds on the x axis, we simply shift it
196 // horizontally by the offset.
197 if (tooltip_rect.right() > display_bounds.right()) {
198 int h_offset = tooltip_rect.right() - display_bounds.right();
199 tooltip_rect.Offset(-h_offset, 0);
202 // If tooltip is out of bounds on the y axis, we flip it to appear above the
203 // mouse cursor instead of below.
204 if (tooltip_rect.bottom() > display_bounds.bottom())
205 tooltip_rect.set_y(mouse_pos.y() - tooltip_height);
207 tooltip_rect.AdjustToFit(display_bounds);
208 widget_->SetBounds(tooltip_rect);
211 void TooltipAura::CreateWidget() {
213 // If the window for which the tooltip is being displayed changes and if the
214 // tooltip window and the tooltip widget belong to different rootwindows
215 // then we need to recreate the tooltip widget under the active root window
216 // hierarchy to get it to display.
217 if (widget_->GetNativeWindow()->GetRootWindow() ==
218 tooltip_window_->GetRootWindow())
222 widget_ = CreateTooltipWidget(tooltip_window_);
223 widget_->SetContentsView(&label_);
224 widget_->AddObserver(this);
227 void TooltipAura::DestroyWidget() {
229 widget_->RemoveObserver(this);
235 void TooltipAura::SetText(aura::Window* window,
236 const string16& tooltip_text,
237 const gfx::Point& location) {
238 tooltip_window_ = window;
239 int max_width, line_count;
240 string16 trimmed_text(tooltip_text);
242 GetMaxWidth(location), &trimmed_text, &max_width, &line_count);
243 label_.SetText(trimmed_text);
245 int width = max_width + 2 * kTooltipHorizontalPadding;
246 int height = label_.GetHeightForWidth(max_width) +
247 2 * kTooltipVerticalPadding;
248 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoDropShadows)) {
249 width += 2 * kTooltipBorderWidth;
250 height += 2 * kTooltipBorderWidth;
253 SetTooltipBounds(location, width, height);
256 void TooltipAura::Show() {
261 void TooltipAura::Hide() {
262 tooltip_window_ = NULL;
267 bool TooltipAura::IsVisible() {
268 return widget_ && widget_->IsVisible();
271 void TooltipAura::OnWidgetDestroying(views::Widget* widget) {
272 DCHECK_EQ(widget_, widget);
274 tooltip_window_ = NULL;
277 } // namespace corewm