• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ui/views/corewm/tooltip_aura.h"
6 
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"
17 
18 namespace {
19 
20 // Max visual tooltip width. If a tooltip is greater than this width, it will
21 // be wrapped.
22 const int kTooltipMaxWidthPixels = 400;
23 
24 const size_t kMaxLines = 10;
25 
26 // FIXME: get cursor offset from actual cursor size.
27 const int kCursorOffsetX = 10;
28 const int kCursorOffsetY = 15;
29 
30 // Creates a widget of type TYPE_TOOLTIP
CreateTooltipWidget(aura::Window * tooltip_window)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;
41   widget->Init(params);
42   return widget;
43 }
44 
45 }  // namespace
46 
47 namespace views {
48 namespace corewm {
49 
TooltipAura(gfx::ScreenType screen_type)50 TooltipAura::TooltipAura(gfx::ScreenType screen_type)
51     : screen_type_(screen_type),
52       widget_(NULL),
53       tooltip_window_(NULL) {
54   label_.set_owned_by_client();
55   label_.SetMultiLine(true);
56 
57   const int kHorizontalPadding = 3;
58   const int kVerticalPadding = 2;
59   label_.SetBorder(Border::CreateEmptyBorder(
60       kVerticalPadding, kHorizontalPadding,
61       kVerticalPadding, kHorizontalPadding));
62 }
63 
~TooltipAura()64 TooltipAura::~TooltipAura() {
65   DestroyWidget();
66 }
67 
68 // static
TrimTooltipToFit(const gfx::FontList & font_list,int max_width,base::string16 * text,int * width,int * line_count)69 void TooltipAura::TrimTooltipToFit(const gfx::FontList& font_list,
70                                    int max_width,
71                                    base::string16* text,
72                                    int* width,
73                                    int* line_count) {
74   *width = 0;
75   *line_count = 0;
76 
77   // Determine the available width for the tooltip.
78   int available_width = std::min(kTooltipMaxWidthPixels, max_width);
79 
80   std::vector<base::string16> lines;
81   base::SplitString(*text, '\n', &lines);
82   std::vector<base::string16> result_lines;
83 
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
89     // to a new line.
90     std::vector<base::string16> words;
91     base::SplitStringDontTrim(*l, ' ', &words);
92     int current_width = 0;
93     base::string16 line;
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())
98         word.push_back(' ');
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.
102         if (!line.empty())
103           result_lines.push_back(line);
104         current_width = 0;
105         line.clear();
106       }
107       current_width += word_width;
108       line.append(word);
109     }
110     result_lines.push_back(line);
111   }
112 
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);
119   }
120   *line_count = result_lines.size();
121 
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) {
126     if (!result.empty())
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,
135                                    gfx::ELIDE_TAIL));
136     } else {
137       *width = std::max(*width, line_width);
138       result.append(*l);
139     }
140   }
141   *text = result;
142 }
143 
GetMaxWidth(const gfx::Point & location) const144 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;
150 }
151 
SetTooltipBounds(const gfx::Point & mouse_pos,const gfx::Size & tooltip_size)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());
158 
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);
164   }
165 
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());
170 
171   tooltip_rect.AdjustToFit(display_bounds);
172   widget_->SetBounds(tooltip_rect);
173 }
174 
DestroyWidget()175 void TooltipAura::DestroyWidget() {
176   if (widget_) {
177     widget_->RemoveObserver(this);
178     widget_->Close();
179     widget_ = NULL;
180   }
181 }
182 
SetText(aura::Window * window,const base::string16 & tooltip_text,const gfx::Point & location)183 void TooltipAura::SetText(aura::Window* window,
184                           const base::string16& tooltip_text,
185                           const gfx::Point& location) {
186   tooltip_window_ = window;
187   int max_width = 0;
188   int line_count = 0;
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);
193 
194   if (!widget_) {
195     widget_ = CreateTooltipWidget(tooltip_window_);
196     widget_->SetContentsView(&label_);
197     widget_->AddObserver(this);
198   }
199 
200   label_.SizeToFit(max_width + label_.GetInsets().width());
201   SetTooltipBounds(location, label_.size());
202 
203   ui::NativeTheme* native_theme = widget_->GetNativeTheme();
204   label_.set_background(
205       views::Background::CreateSolidBackground(
206           native_theme->GetSystemColor(
207               ui::NativeTheme::kColorId_TooltipBackground)));
208 
209   label_.SetAutoColorReadabilityEnabled(false);
210   label_.SetEnabledColor(native_theme->GetSystemColor(
211       ui::NativeTheme::kColorId_TooltipText));
212 }
213 
Show()214 void TooltipAura::Show() {
215   if (widget_) {
216     widget_->Show();
217     widget_->StackAtTop();
218   }
219 }
220 
Hide()221 void TooltipAura::Hide() {
222   tooltip_window_ = NULL;
223   if (widget_)
224     widget_->Hide();
225 }
226 
IsVisible()227 bool TooltipAura::IsVisible() {
228   return widget_ && widget_->IsVisible();
229 }
230 
OnWidgetDestroying(views::Widget * widget)231 void TooltipAura::OnWidgetDestroying(views::Widget* widget) {
232   DCHECK_EQ(widget_, widget);
233   widget_ = NULL;
234   tooltip_window_ = NULL;
235 }
236 
237 }  // namespace corewm
238 }  // namespace views
239