1 // Copyright (c) 2012 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/widget/tooltip_manager_win.h"
12 #include "base/bind.h"
13 #include "base/i18n/rtl.h"
14 #include "base/logging.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/strings/string_split.h"
17 #include "base/strings/string_util.h"
18 #include "base/win/scoped_hdc.h"
19 #include "base/win/scoped_select_object.h"
20 #include "ui/base/l10n/l10n_util_win.h"
21 #include "ui/gfx/font_list.h"
22 #include "ui/gfx/screen.h"
23 #include "ui/gfx/text_elider.h"
24 #include "ui/gfx/text_utils.h"
25 #include "ui/gfx/win/dpi.h"
26 #include "ui/gfx/win/hwnd_util.h"
27 #include "ui/gfx/win/scoped_set_map_mode.h"
28 #include "ui/views/view.h"
29 #include "ui/views/widget/monitor_win.h"
30 #include "ui/views/widget/widget.h"
36 static int tooltip_height_
= 0;
38 // Maximum number of lines we allow in the tooltip.
39 const size_t kMaxLines
= 6;
41 // Trims the tooltip to fit, setting |text| to the clipped result, |max_width|
42 // to the width (in pixels) of the clipped text and |line_count| to the number
43 // of lines of text in the tooltip. |available_width| gives the space available
45 void TrimTooltipToFit(const gfx::FontList
& font_list
,
53 TooltipManager::TrimTooltipText(text
);
55 // Split the string into at most kMaxLines lines.
56 std::vector
<base::string16
> lines
;
57 base::SplitString(*text
, '\n', &lines
);
58 if (lines
.size() > kMaxLines
)
59 lines
.resize(kMaxLines
);
60 *line_count
= static_cast<int>(lines
.size());
62 // Format each line to fit.
63 base::string16 result
;
64 for (std::vector
<base::string16
>::iterator i
= lines
.begin();
65 i
!= lines
.end(); ++i
) {
66 base::string16 elided_text
=
67 gfx::ElideText(*i
, font_list
, available_width
, gfx::ELIDE_AT_END
);
68 *max_width
= std::max(*max_width
,
69 gfx::GetStringWidth(elided_text
, font_list
));
71 result
.push_back('\n');
72 result
.append(elided_text
);
80 int TooltipManager::GetTooltipHeight() {
81 DCHECK_GT(tooltip_height_
, 0);
82 return tooltip_height_
;
85 static gfx::Font
DetermineDefaultFont() {
86 HWND window
= CreateWindowEx(
87 WS_EX_TRANSPARENT
| l10n_util::GetExtendedTooltipStyles(),
88 TOOLTIPS_CLASS
, NULL
, 0 , 0, 0, 0, 0, NULL
, NULL
, NULL
, NULL
);
91 HFONT hfont
= reinterpret_cast<HFONT
>(SendMessage(window
, WM_GETFONT
, 0, 0));
92 gfx::Font font
= hfont
? gfx::Font(hfont
) : gfx::Font();
93 DestroyWindow(window
);
97 TooltipManagerWin::TooltipManagerWin(Widget
* widget
)
100 last_mouse_pos_(-1, -1),
101 tooltip_showing_(false),
102 last_tooltip_view_(NULL
),
103 last_view_out_of_sync_(false),
106 DCHECK(widget
->GetNativeView());
109 TooltipManagerWin::~TooltipManagerWin() {
111 DestroyWindow(tooltip_hwnd_
);
114 bool TooltipManagerWin::Init() {
115 DCHECK(!tooltip_hwnd_
);
116 // Create the tooltip control.
117 tooltip_hwnd_
= CreateWindowEx(
118 WS_EX_TRANSPARENT
| l10n_util::GetExtendedTooltipStyles(),
119 TOOLTIPS_CLASS
, NULL
, TTS_NOPREFIX
, 0, 0, 0, 0,
120 GetParent(), NULL
, NULL
, NULL
);
124 l10n_util::AdjustUIFontForWindow(tooltip_hwnd_
);
126 // This effectively turns off clipping of tooltips. We need this otherwise
127 // multi-line text (\r\n) won't work right. The size doesn't really matter
128 // (just as long as its bigger than the monitor's width) as we clip to the
129 // screen size before rendering.
130 SendMessage(tooltip_hwnd_
, TTM_SETMAXTIPWIDTH
, 0,
131 std::numeric_limits
<int16
>::max());
133 // Add one tool that is used for all tooltips.
134 toolinfo_
.cbSize
= sizeof(toolinfo_
);
135 toolinfo_
.uFlags
= TTF_TRANSPARENT
| TTF_IDISHWND
;
136 toolinfo_
.hwnd
= GetParent();
137 toolinfo_
.uId
= reinterpret_cast<UINT_PTR
>(GetParent());
138 // Setting this tells windows to call GetParent() back (using a WM_NOTIFY
139 // message) for the actual tooltip contents.
140 toolinfo_
.lpszText
= LPSTR_TEXTCALLBACK
;
141 toolinfo_
.lpReserved
= NULL
;
142 SetRectEmpty(&toolinfo_
.rect
);
143 SendMessage(tooltip_hwnd_
, TTM_ADDTOOL
, 0, (LPARAM
)&toolinfo_
);
147 gfx::NativeView
TooltipManagerWin::GetParent() {
148 return widget_
->GetNativeView();
151 const gfx::FontList
& TooltipManagerWin::GetFontList() const {
152 static gfx::FontList
* font_list
= NULL
;
154 font_list
= new gfx::FontList(DetermineDefaultFont());
158 void TooltipManagerWin::UpdateTooltip() {
159 // Set last_view_out_of_sync_ to indicate the view is currently out of sync.
160 // This doesn't update the view under the mouse immediately as it may cause
162 last_view_out_of_sync_
= true;
163 last_tooltip_view_
= NULL
;
165 SendMessage(tooltip_hwnd_
, TTM_POP
, 0, 0);
168 void TooltipManagerWin::TooltipTextChanged(View
* view
) {
169 if (view
== last_tooltip_view_
)
170 UpdateTooltip(last_mouse_pos_
);
173 LRESULT
TooltipManagerWin::OnNotify(int w_param
,
177 if (l_param
->hwndFrom
!= tooltip_hwnd_
)
180 switch (l_param
->code
) {
181 case TTN_GETDISPINFO
: {
182 if (last_view_out_of_sync_
) {
183 // View under the mouse is out of sync, determine it now.
184 View
* root_view
= widget_
->GetRootView();
186 root_view
->GetTooltipHandlerForPoint(last_mouse_pos_
);
187 last_view_out_of_sync_
= false;
189 // Tooltip control is asking for the tooltip to display.
190 NMTTDISPINFOW
* tooltip_info
=
191 reinterpret_cast<NMTTDISPINFOW
*>(l_param
);
192 // Initialize the string, if we have a valid tooltip the string will
194 tooltip_info
->szText
[0] = TEXT('\0');
195 tooltip_text_
.clear();
196 tooltip_info
->lpszText
= NULL
;
197 clipped_text_
.clear();
198 if (last_tooltip_view_
!= NULL
) {
199 tooltip_text_
.clear();
200 // Mouse is over a View, ask the View for its tooltip.
201 gfx::Point view_loc
= last_mouse_pos_
;
202 View::ConvertPointToTarget(widget_
->GetRootView(),
203 last_tooltip_view_
, &view_loc
);
204 if (last_tooltip_view_
->GetTooltipText(view_loc
, &tooltip_text_
) &&
205 !tooltip_text_
.empty()) {
206 // View has a valid tip, copy it into TOOLTIPINFO.
207 clipped_text_
= tooltip_text_
;
208 gfx::Point screen_loc
= last_mouse_pos_
;
209 View::ConvertPointToScreen(widget_
->GetRootView(), &screen_loc
);
212 GetMaxWidth(screen_loc
.x(), screen_loc
.y(),
213 widget_
->GetNativeView()),
214 &clipped_text_
, &tooltip_width_
, &line_count_
);
215 // Adjust the clipped tooltip text for locale direction.
216 base::i18n::AdjustStringForLocaleDirection(&clipped_text_
);
217 tooltip_info
->lpszText
= const_cast<WCHAR
*>(clipped_text_
.c_str());
219 tooltip_text_
.clear();
226 tooltip_showing_
= false;
231 tooltip_showing_
= true;
232 // The tooltip is about to show, allow the view to position it
233 gfx::Point text_origin
;
234 if (tooltip_height_
== 0)
235 tooltip_height_
= CalcTooltipHeight();
236 gfx::Point view_loc
= last_mouse_pos_
;
237 View::ConvertPointToTarget(widget_
->GetRootView(),
238 last_tooltip_view_
, &view_loc
);
239 if (last_tooltip_view_
->GetTooltipTextOrigin(view_loc
, &text_origin
) &&
240 SetTooltipPosition(text_origin
.x(), text_origin
.y())) {
241 // Return true, otherwise the rectangle we specified is ignored.
253 bool TooltipManagerWin::SetTooltipPosition(int text_x
, int text_y
) {
254 // NOTE: this really only tests that the y location fits on screen, but that
255 // is good enough for our usage.
257 // Calculate the bounds the tooltip will get.
259 View::ConvertPointToScreen(last_tooltip_view_
, &view_loc
);
260 view_loc
= gfx::win::DIPToScreenPoint(view_loc
);
261 RECT bounds
= { view_loc
.x() + text_x
,
262 view_loc
.y() + text_y
,
263 view_loc
.x() + text_x
+ tooltip_width_
,
264 view_loc
.y() + line_count_
* GetTooltipHeight() };
265 SendMessage(tooltip_hwnd_
, TTM_ADJUSTRECT
, TRUE
, (LPARAM
)&bounds
);
267 // Make sure the rectangle completely fits on the current monitor. If it
268 // doesn't, return false so that windows positions the tooltip at the
270 gfx::Rect monitor_bounds
=
271 views::GetMonitorBoundsForRect(gfx::Rect(bounds
.left
, bounds
.right
,
273 if (!monitor_bounds
.Contains(gfx::Rect(bounds
))) {
277 ::SetWindowPos(tooltip_hwnd_
, NULL
, bounds
.left
, bounds
.top
, 0, 0,
278 SWP_NOZORDER
| SWP_NOACTIVATE
| SWP_NOSIZE
);
282 int TooltipManagerWin::CalcTooltipHeight() {
283 // Ask the tooltip for its font.
285 HFONT hfont
= reinterpret_cast<HFONT
>(
286 SendMessage(tooltip_hwnd_
, WM_GETFONT
, 0, 0));
288 base::win::ScopedGetDC
dc(tooltip_hwnd_
);
289 base::win::ScopedSelectObject
font(dc
, hfont
);
290 gfx::ScopedSetMapMode
mode(dc
, MM_TEXT
);
291 TEXTMETRIC font_metrics
;
292 GetTextMetrics(dc
, &font_metrics
);
293 height
= font_metrics
.tmHeight
;
295 // Tooltip is using the system font. Use gfx::Font, which should pick
296 // up the system font.
297 height
= gfx::Font().GetHeight();
299 // Get the margins from the tooltip
301 SendMessage(tooltip_hwnd_
, TTM_GETMARGIN
, 0, (LPARAM
)&tooltip_margin
);
302 return height
+ tooltip_margin
.top
+ tooltip_margin
.bottom
;
305 void TooltipManagerWin::UpdateTooltip(const gfx::Point
& mouse_pos
) {
306 View
* root_view
= widget_
->GetRootView();
307 View
* view
= root_view
->GetTooltipHandlerForPoint(mouse_pos
);
308 if (view
!= last_tooltip_view_
) {
309 // NOTE: This *must* be sent regardless of the visibility of the tooltip.
310 // It triggers Windows to ask for the tooltip again.
311 SendMessage(tooltip_hwnd_
, TTM_POP
, 0, 0);
312 last_tooltip_view_
= view
;
313 } else if (last_tooltip_view_
!= NULL
) {
314 // Tooltip is showing, and mouse is over the same view. See if the tooltip
316 gfx::Point view_point
= mouse_pos
;
317 View::ConvertPointToTarget(root_view
, last_tooltip_view_
, &view_point
);
318 string16 new_tooltip_text
;
319 bool has_tooltip_text
=
320 last_tooltip_view_
->GetTooltipText(view_point
, &new_tooltip_text
);
321 if (!has_tooltip_text
|| (new_tooltip_text
!= tooltip_text_
)) {
322 // The text has changed, hide the popup.
323 SendMessage(tooltip_hwnd_
, TTM_POP
, 0, 0);
324 if (has_tooltip_text
&& !new_tooltip_text
.empty() && tooltip_showing_
) {
325 // New text is valid, show the popup.
326 SendMessage(tooltip_hwnd_
, TTM_POPUP
, 0, 0);
332 void TooltipManagerWin::OnMouse(UINT u_msg
, WPARAM w_param
, LPARAM l_param
) {
333 gfx::Point
mouse_pos_in_pixels(l_param
);
334 gfx::Point mouse_pos
= gfx::win::ScreenToDIPPoint(mouse_pos_in_pixels
);
336 if (u_msg
>= WM_NCMOUSEMOVE
&& u_msg
<= WM_NCXBUTTONDBLCLK
) {
337 // NC message coordinates are in screen coordinates.
338 POINT temp
= mouse_pos_in_pixels
.ToPOINT();
339 ::MapWindowPoints(HWND_DESKTOP
, GetParent(), &temp
, 1);
340 mouse_pos_in_pixels
.SetPoint(temp
.x
, temp
.y
);
341 mouse_pos
= gfx::win::ScreenToDIPPoint(mouse_pos_in_pixels
);
344 if (u_msg
!= WM_MOUSEMOVE
|| last_mouse_pos_
!= mouse_pos
) {
345 last_mouse_pos_
= mouse_pos
;
346 UpdateTooltip(mouse_pos
);
348 // Forward the message onto the tooltip.
350 msg
.hwnd
= GetParent();
352 msg
.wParam
= w_param
;
353 msg
.lParam
= l_param
;
354 SendMessage(tooltip_hwnd_
, TTM_RELAYEVENT
, 0, (LPARAM
)&msg
);