- add sources.
[platform/framework/web/crosswalk.git] / src / ui / views / corewm / tooltip_aura.cc
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/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"
15
16 namespace {
17
18 const SkColor kTooltipBackground = 0xFFFFFFCC;
19 const SkColor kTooltipBorder = 0xFF646450;
20 const int kTooltipBorderWidth = 1;
21 const int kTooltipHorizontalPadding = 3;
22
23 // Max visual tooltip width. If a tooltip is greater than this width, it will
24 // be wrapped.
25 const int kTooltipMaxWidthPixels = 400;
26
27 const size_t kMaxLines = 10;
28
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;
33
34 // FIXME: get cursor offset from actual cursor size.
35 const int kCursorOffsetX = 10;
36 const int kCursorOffsetY = 15;
37
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;
49   widget->Init(params);
50   return widget;
51 }
52
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);
58 }
59
60 }  // namespace
61
62 namespace views {
63 namespace corewm {
64
65 TooltipAura::TooltipAura(gfx::ScreenType screen_type)
66     : screen_type_(screen_type),
67       widget_(NULL),
68       tooltip_window_(NULL) {
69   label_.set_background(
70       views::Background::CreateSolidBackground(kTooltipBackground));
71   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNoDropShadows)) {
72     label_.set_border(
73         views::Border::CreateSolidBorder(kTooltipBorderWidth,
74                                          kTooltipBorder));
75   }
76   label_.set_owned_by_client();
77   label_.SetMultiLine(true);
78 }
79
80 TooltipAura::~TooltipAura() {
81   DestroyWidget();
82 }
83
84 // static
85 void TooltipAura::TrimTooltipToFit(int max_width,
86                                    string16* text,
87                                    int* width,
88                                    int* line_count) {
89   *width = 0;
90   *line_count = 0;
91
92   // Determine the available width for the tooltip.
93   int available_width = std::min(kTooltipMaxWidthPixels, max_width);
94
95   std::vector<string16> lines;
96   base::SplitString(*text, '\n', &lines);
97   std::vector<string16> result_lines;
98
99   // Format each line to fit.
100   gfx::Font font = GetDefaultFont();
101   for (std::vector<string16>::iterator l = lines.begin(); l != lines.end();
102       ++l) {
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
105     // to a new line.
106     std::vector<string16> words;
107     base::SplitStringDontTrim(*l, ' ', &words);
108     int current_width = 0;
109     string16 line;
110     for (std::vector<string16>::iterator w = words.begin(); w != words.end();
111         ++w) {
112       string16 word = *w;
113       if (w + 1 != words.end())
114         word.push_back(' ');
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.
118         if (!line.empty())
119           result_lines.push_back(line);
120         current_width = 0;
121         line.clear();
122       }
123       current_width += word_width;
124       line.append(word);
125     }
126     result_lines.push_back(line);
127   }
128
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);
135   }
136   *line_count = result_lines.size();
137
138   // Flatten the result.
139   string16 result;
140   for (std::vector<string16>::iterator l = result_lines.begin();
141       l != result_lines.end(); ++l) {
142     if (!result.empty())
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,
151                                    gfx::ELIDE_AT_END));
152     } else {
153       *width = std::max(*width, line_width);
154       result.append(*l);
155     }
156   }
157   *text = result;
158 }
159
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;
165 }
166
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());
178   }
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);
183   return bounds;
184 }
185
186 void TooltipAura::SetTooltipBounds(const gfx::Point& mouse_pos,
187                                    int tooltip_width,
188                                    int tooltip_height) {
189   gfx::Rect tooltip_rect(mouse_pos.x(), mouse_pos.y(), tooltip_width,
190                          tooltip_height);
191
192   tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY);
193   gfx::Rect display_bounds = GetBoundsForTooltip(mouse_pos);
194
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);
200   }
201
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);
206
207   tooltip_rect.AdjustToFit(display_bounds);
208   widget_->SetBounds(tooltip_rect);
209 }
210
211 void TooltipAura::CreateWidget() {
212   if (widget_) {
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())
219       return;
220     DestroyWidget();
221   }
222   widget_ = CreateTooltipWidget(tooltip_window_);
223   widget_->SetContentsView(&label_);
224   widget_->AddObserver(this);
225 }
226
227 void TooltipAura::DestroyWidget() {
228   if (widget_) {
229     widget_->RemoveObserver(this);
230     widget_->Close();
231     widget_ = NULL;
232   }
233 }
234
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);
241   TrimTooltipToFit(
242       GetMaxWidth(location), &trimmed_text, &max_width, &line_count);
243   label_.SetText(trimmed_text);
244
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;
251   }
252   CreateWidget();
253   SetTooltipBounds(location, width, height);
254 }
255
256 void TooltipAura::Show() {
257   if (widget_)
258     widget_->Show();
259 }
260
261 void TooltipAura::Hide() {
262   tooltip_window_ = NULL;
263   if (widget_)
264     widget_->Hide();
265 }
266
267 bool TooltipAura::IsVisible() {
268   return widget_ && widget_->IsVisible();
269 }
270
271 void TooltipAura::OnWidgetDestroying(views::Widget* widget) {
272   DCHECK_EQ(widget_, widget);
273   widget_ = NULL;
274   tooltip_window_ = NULL;
275 }
276
277 }  // namespace corewm
278 }  // namespace views