OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 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 "views/widget/tooltip_manager_win.h" | |
6 | |
7 #include <windowsx.h> | |
8 | |
9 #include <limits> | |
10 | |
11 #include "base/bind.h" | |
12 #include "base/i18n/rtl.h" | |
13 #include "base/logging.h" | |
14 #include "base/message_loop.h" | |
15 #include "base/string_util.h" | |
16 #include "ui/base/l10n/l10n_util_win.h" | |
17 #include "ui/base/win/hwnd_util.h" | |
18 #include "ui/gfx/font.h" | |
19 #include "ui/gfx/screen.h" | |
20 #include "views/view.h" | |
21 #include "views/widget/monitor_win.h" | |
22 #include "views/widget/widget.h" | |
23 | |
24 namespace views { | |
25 | |
26 static int tooltip_height_ = 0; | |
27 | |
28 // Default timeout for the tooltip displayed using keyboard. | |
29 // Timeout is mentioned in milliseconds. | |
30 static const int kDefaultTimeout = 4000; | |
31 | |
32 // static | |
33 int TooltipManager::GetTooltipHeight() { | |
34 DCHECK_GT(tooltip_height_, 0); | |
35 return tooltip_height_; | |
36 } | |
37 | |
38 static gfx::Font DetermineDefaultFont() { | |
39 HWND window = CreateWindowEx( | |
40 WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), | |
41 TOOLTIPS_CLASS, NULL, 0 , 0, 0, 0, 0, NULL, NULL, NULL, NULL); | |
42 if (!window) | |
43 return gfx::Font(); | |
44 HFONT hfont = reinterpret_cast<HFONT>(SendMessage(window, WM_GETFONT, 0, 0)); | |
45 gfx::Font font = hfont ? gfx::Font(hfont) : gfx::Font(); | |
46 DestroyWindow(window); | |
47 return font; | |
48 } | |
49 | |
50 // static | |
51 gfx::Font TooltipManager::GetDefaultFont() { | |
52 static gfx::Font* font = NULL; | |
53 if (!font) | |
54 font = new gfx::Font(DetermineDefaultFont()); | |
55 return *font; | |
56 } | |
57 | |
58 // static | |
59 int TooltipManager::GetMaxWidth(int x, int y) { | |
60 gfx::Rect monitor_bounds = | |
61 gfx::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y)); | |
62 // Allow the tooltip to be almost as wide as the screen. | |
63 // Otherwise, we would truncate important text, since we're not word-wrapping | |
64 // the text onto multiple lines. | |
65 return monitor_bounds.width() == 0 ? 800 : monitor_bounds.width() - 30; | |
66 } | |
67 | |
68 TooltipManagerWin::TooltipManagerWin(Widget* widget) | |
69 : widget_(widget), | |
70 tooltip_hwnd_(NULL), | |
71 last_mouse_pos_(-1, -1), | |
72 tooltip_showing_(false), | |
73 last_tooltip_view_(NULL), | |
74 last_view_out_of_sync_(false), | |
75 tooltip_width_(0), | |
76 keyboard_tooltip_hwnd_(NULL), | |
77 ALLOW_THIS_IN_INITIALIZER_LIST(keyboard_tooltip_factory_(this)) { | |
78 DCHECK(widget); | |
79 DCHECK(widget->GetNativeView()); | |
80 } | |
81 | |
82 TooltipManagerWin::~TooltipManagerWin() { | |
83 if (tooltip_hwnd_) | |
84 DestroyWindow(tooltip_hwnd_); | |
85 if (keyboard_tooltip_hwnd_) | |
86 DestroyWindow(keyboard_tooltip_hwnd_); | |
87 } | |
88 | |
89 bool TooltipManagerWin::Init() { | |
90 DCHECK(!tooltip_hwnd_); | |
91 // Create the tooltip control. | |
92 tooltip_hwnd_ = CreateWindowEx( | |
93 WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), | |
94 TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, | |
95 GetParent(), NULL, NULL, NULL); | |
96 if (!tooltip_hwnd_) | |
97 return false; | |
98 | |
99 l10n_util::AdjustUIFontForWindow(tooltip_hwnd_); | |
100 | |
101 // This effectively turns off clipping of tooltips. We need this otherwise | |
102 // multi-line text (\r\n) won't work right. The size doesn't really matter | |
103 // (just as long as its bigger than the monitor's width) as we clip to the | |
104 // screen size before rendering. | |
105 SendMessage(tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, | |
106 std::numeric_limits<int16>::max()); | |
107 | |
108 // Add one tool that is used for all tooltips. | |
109 toolinfo_.cbSize = sizeof(toolinfo_); | |
110 toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND; | |
111 toolinfo_.hwnd = GetParent(); | |
112 toolinfo_.uId = reinterpret_cast<UINT_PTR>(GetParent()); | |
113 // Setting this tells windows to call GetParent() back (using a WM_NOTIFY | |
114 // message) for the actual tooltip contents. | |
115 toolinfo_.lpszText = LPSTR_TEXTCALLBACK; | |
116 toolinfo_.lpReserved = NULL; | |
117 SetRectEmpty(&toolinfo_.rect); | |
118 SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); | |
119 return true; | |
120 } | |
121 | |
122 gfx::NativeView TooltipManagerWin::GetParent() { | |
123 return widget_->GetNativeView(); | |
124 } | |
125 | |
126 void TooltipManagerWin::UpdateTooltip() { | |
127 // Set last_view_out_of_sync_ to indicate the view is currently out of sync. | |
128 // This doesn't update the view under the mouse immediately as it may cause | |
129 // timing problems. | |
130 last_view_out_of_sync_ = true; | |
131 last_tooltip_view_ = NULL; | |
132 // Hide the tooltip. | |
133 SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
134 } | |
135 | |
136 void TooltipManagerWin::TooltipTextChanged(View* view) { | |
137 if (view == last_tooltip_view_) | |
138 UpdateTooltip(last_mouse_pos_); | |
139 } | |
140 | |
141 LRESULT TooltipManagerWin::OnNotify(int w_param, | |
142 NMHDR* l_param, | |
143 bool* handled) { | |
144 *handled = false; | |
145 if (l_param->hwndFrom == tooltip_hwnd_ && keyboard_tooltip_hwnd_ == NULL) { | |
146 switch (l_param->code) { | |
147 case TTN_GETDISPINFO: { | |
148 if (last_view_out_of_sync_) { | |
149 // View under the mouse is out of sync, determine it now. | |
150 View* root_view = widget_->GetRootView(); | |
151 last_tooltip_view_ = | |
152 root_view->GetEventHandlerForPoint(last_mouse_pos_); | |
153 last_view_out_of_sync_ = false; | |
154 } | |
155 // Tooltip control is asking for the tooltip to display. | |
156 NMTTDISPINFOW* tooltip_info = | |
157 reinterpret_cast<NMTTDISPINFOW*>(l_param); | |
158 // Initialize the string, if we have a valid tooltip the string will | |
159 // get reset below. | |
160 tooltip_info->szText[0] = TEXT('\0'); | |
161 tooltip_text_.clear(); | |
162 tooltip_info->lpszText = NULL; | |
163 clipped_text_.clear(); | |
164 if (last_tooltip_view_ != NULL) { | |
165 tooltip_text_.clear(); | |
166 // Mouse is over a View, ask the View for its tooltip. | |
167 gfx::Point view_loc = last_mouse_pos_; | |
168 View::ConvertPointToView(widget_->GetRootView(), | |
169 last_tooltip_view_, &view_loc); | |
170 if (last_tooltip_view_->GetTooltipText(view_loc, &tooltip_text_) && | |
171 !tooltip_text_.empty()) { | |
172 // View has a valid tip, copy it into TOOLTIPINFO. | |
173 clipped_text_ = tooltip_text_; | |
174 gfx::Point screen_loc = last_mouse_pos_; | |
175 View::ConvertPointToScreen(widget_->GetRootView(), &screen_loc); | |
176 TrimTooltipToFit(&clipped_text_, &tooltip_width_, &line_count_, | |
177 screen_loc.x(), screen_loc.y()); | |
178 // Adjust the clipped tooltip text for locale direction. | |
179 base::i18n::AdjustStringForLocaleDirection(&clipped_text_); | |
180 tooltip_info->lpszText = const_cast<WCHAR*>(clipped_text_.c_str()); | |
181 } else { | |
182 tooltip_text_.clear(); | |
183 } | |
184 } | |
185 *handled = true; | |
186 return 0; | |
187 } | |
188 case TTN_POP: | |
189 tooltip_showing_ = false; | |
190 *handled = true; | |
191 return 0; | |
192 case TTN_SHOW: { | |
193 *handled = true; | |
194 tooltip_showing_ = true; | |
195 // The tooltip is about to show, allow the view to position it | |
196 gfx::Point text_origin; | |
197 if (tooltip_height_ == 0) | |
198 tooltip_height_ = CalcTooltipHeight(); | |
199 gfx::Point view_loc = last_mouse_pos_; | |
200 View::ConvertPointToView(widget_->GetRootView(), | |
201 last_tooltip_view_, &view_loc); | |
202 if (last_tooltip_view_->GetTooltipTextOrigin(view_loc, &text_origin) && | |
203 SetTooltipPosition(text_origin.x(), text_origin.y())) { | |
204 // Return true, otherwise the rectangle we specified is ignored. | |
205 return TRUE; | |
206 } | |
207 return 0; | |
208 } | |
209 default: | |
210 // Fall through. | |
211 break; | |
212 } | |
213 } | |
214 return 0; | |
215 } | |
216 | |
217 bool TooltipManagerWin::SetTooltipPosition(int text_x, int text_y) { | |
218 // NOTE: this really only tests that the y location fits on screen, but that | |
219 // is good enough for our usage. | |
220 | |
221 // Calculate the bounds the tooltip will get. | |
222 gfx::Point view_loc; | |
223 View::ConvertPointToScreen(last_tooltip_view_, &view_loc); | |
224 RECT bounds = { view_loc.x() + text_x, | |
225 view_loc.y() + text_y, | |
226 view_loc.x() + text_x + tooltip_width_, | |
227 view_loc.y() + line_count_ * GetTooltipHeight() }; | |
228 SendMessage(tooltip_hwnd_, TTM_ADJUSTRECT, TRUE, (LPARAM)&bounds); | |
229 | |
230 // Make sure the rectangle completely fits on the current monitor. If it | |
231 // doesn't, return false so that windows positions the tooltip at the | |
232 // default location. | |
233 gfx::Rect monitor_bounds = | |
234 views::GetMonitorBoundsForRect(gfx::Rect(bounds.left, bounds.right, | |
235 0, 0)); | |
236 if (!monitor_bounds.Contains(gfx::Rect(bounds))) { | |
237 return false; | |
238 } | |
239 | |
240 ::SetWindowPos(tooltip_hwnd_, NULL, bounds.left, bounds.top, 0, 0, | |
241 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); | |
242 return true; | |
243 } | |
244 | |
245 int TooltipManagerWin::CalcTooltipHeight() { | |
246 // Ask the tooltip for it's font. | |
247 int height; | |
248 HFONT hfont = reinterpret_cast<HFONT>( | |
249 SendMessage(tooltip_hwnd_, WM_GETFONT, 0, 0)); | |
250 if (hfont != NULL) { | |
251 HDC dc = GetDC(tooltip_hwnd_); | |
252 HFONT previous_font = static_cast<HFONT>(SelectObject(dc, hfont)); | |
253 int last_map_mode = SetMapMode(dc, MM_TEXT); | |
254 TEXTMETRIC font_metrics; | |
255 GetTextMetrics(dc, &font_metrics); | |
256 height = font_metrics.tmHeight; | |
257 // To avoid the DC referencing font_handle_, select the previous font. | |
258 SelectObject(dc, previous_font); | |
259 SetMapMode(dc, last_map_mode); | |
260 ReleaseDC(NULL, dc); | |
261 } else { | |
262 // Tooltip is using the system font. Use gfx::Font, which should pick | |
263 // up the system font. | |
264 height = gfx::Font().GetHeight(); | |
265 } | |
266 // Get the margins from the tooltip | |
267 RECT tooltip_margin; | |
268 SendMessage(tooltip_hwnd_, TTM_GETMARGIN, 0, (LPARAM)&tooltip_margin); | |
269 return height + tooltip_margin.top + tooltip_margin.bottom; | |
270 } | |
271 | |
272 void TooltipManagerWin::UpdateTooltip(const gfx::Point& mouse_pos) { | |
273 View* root_view = widget_->GetRootView(); | |
274 View* view = root_view->GetEventHandlerForPoint(mouse_pos); | |
275 if (view != last_tooltip_view_) { | |
276 // NOTE: This *must* be sent regardless of the visibility of the tooltip. | |
277 // It triggers Windows to ask for the tooltip again. | |
278 SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
279 last_tooltip_view_ = view; | |
280 } else if (last_tooltip_view_ != NULL) { | |
281 // Tooltip is showing, and mouse is over the same view. See if the tooltip | |
282 // text has changed. | |
283 gfx::Point view_point = mouse_pos; | |
284 View::ConvertPointToView(root_view, last_tooltip_view_, &view_point); | |
285 string16 new_tooltip_text; | |
286 bool has_tooltip_text = | |
287 last_tooltip_view_->GetTooltipText(view_point, &new_tooltip_text); | |
288 if (!has_tooltip_text || (new_tooltip_text != tooltip_text_)) { | |
289 // The text has changed, hide the popup. | |
290 SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
291 if (has_tooltip_text && !new_tooltip_text.empty() && tooltip_showing_) { | |
292 // New text is valid, show the popup. | |
293 SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); | |
294 } | |
295 } | |
296 } | |
297 } | |
298 | |
299 void TooltipManagerWin::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { | |
300 gfx::Point mouse_pos(l_param); | |
301 | |
302 if (u_msg >= WM_NCMOUSEMOVE && u_msg <= WM_NCXBUTTONDBLCLK) { | |
303 // NC message coordinates are in screen coordinates. | |
304 POINT temp = mouse_pos.ToPOINT(); | |
305 ::MapWindowPoints(HWND_DESKTOP, GetParent(), &temp, 1); | |
306 mouse_pos.SetPoint(temp.x, temp.y); | |
307 } | |
308 | |
309 if (u_msg != WM_MOUSEMOVE || last_mouse_pos_ != mouse_pos) { | |
310 last_mouse_pos_ = mouse_pos; | |
311 HideKeyboardTooltip(); | |
312 UpdateTooltip(mouse_pos); | |
313 } | |
314 // Forward the message onto the tooltip. | |
315 MSG msg; | |
316 msg.hwnd = GetParent(); | |
317 msg.message = u_msg; | |
318 msg.wParam = w_param; | |
319 msg.lParam = l_param; | |
320 SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, 0, (LPARAM)&msg); | |
321 } | |
322 | |
323 void TooltipManagerWin::ShowKeyboardTooltip(View* focused_view) { | |
324 if (tooltip_showing_) { | |
325 SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); | |
326 tooltip_text_.clear(); | |
327 } | |
328 HideKeyboardTooltip(); | |
329 string16 tooltip_text; | |
330 if (!focused_view->GetTooltipText(gfx::Point(), &tooltip_text)) | |
331 return; | |
332 gfx::Rect focused_bounds = focused_view->bounds(); | |
333 gfx::Point screen_point; | |
334 focused_view->ConvertPointToScreen(focused_view, &screen_point); | |
335 keyboard_tooltip_hwnd_ = CreateWindowEx( | |
336 WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), | |
337 TOOLTIPS_CLASS, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); | |
338 if (!keyboard_tooltip_hwnd_) | |
339 return; | |
340 | |
341 SendMessage(keyboard_tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, | |
342 std::numeric_limits<int16>::max()); | |
343 int tooltip_width; | |
344 int line_count; | |
345 TrimTooltipToFit(&tooltip_text, &tooltip_width, &line_count, | |
346 screen_point.x(), screen_point.y()); | |
347 ReplaceSubstringsAfterOffset(&tooltip_text, 0, L"\n", L"\r\n"); | |
348 TOOLINFO keyboard_toolinfo; | |
349 memset(&keyboard_toolinfo, 0, sizeof(keyboard_toolinfo)); | |
350 keyboard_toolinfo.cbSize = sizeof(keyboard_toolinfo); | |
351 keyboard_toolinfo.hwnd = GetParent(); | |
352 keyboard_toolinfo.uFlags = TTF_TRACK | TTF_TRANSPARENT | TTF_IDISHWND; | |
353 keyboard_toolinfo.lpszText = const_cast<WCHAR*>(tooltip_text.c_str()); | |
354 SendMessage(keyboard_tooltip_hwnd_, TTM_ADDTOOL, 0, | |
355 reinterpret_cast<LPARAM>(&keyboard_toolinfo)); | |
356 SendMessage(keyboard_tooltip_hwnd_, TTM_TRACKACTIVATE, TRUE, | |
357 reinterpret_cast<LPARAM>(&keyboard_toolinfo)); | |
358 if (!tooltip_height_) | |
359 tooltip_height_ = CalcTooltipHeight(); | |
360 RECT rect_bounds = {screen_point.x(), | |
361 screen_point.y() + focused_bounds.height(), | |
362 screen_point.x() + tooltip_width, | |
363 screen_point.y() + focused_bounds.height() + | |
364 line_count * tooltip_height_ }; | |
365 gfx::Rect monitor_bounds = | |
366 views::GetMonitorBoundsForRect(gfx::Rect(rect_bounds)); | |
367 rect_bounds = gfx::Rect(rect_bounds).AdjustToFit(monitor_bounds).ToRECT(); | |
368 ::SetWindowPos(keyboard_tooltip_hwnd_, NULL, rect_bounds.left, | |
369 rect_bounds.top, 0, 0, | |
370 SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); | |
371 MessageLoop::current()->PostDelayedTask( | |
372 FROM_HERE, | |
373 base::Bind(&TooltipManagerWin::DestroyKeyboardTooltipWindow, | |
374 keyboard_tooltip_factory_.GetWeakPtr(), | |
375 keyboard_tooltip_hwnd_), | |
376 kDefaultTimeout); | |
377 } | |
378 | |
379 void TooltipManagerWin::HideKeyboardTooltip() { | |
380 if (keyboard_tooltip_hwnd_ != NULL) { | |
381 SendMessage(keyboard_tooltip_hwnd_, WM_CLOSE, 0, 0); | |
382 keyboard_tooltip_hwnd_ = NULL; | |
383 } | |
384 } | |
385 | |
386 void TooltipManagerWin::DestroyKeyboardTooltipWindow(HWND window_to_destroy) { | |
387 if (keyboard_tooltip_hwnd_ == window_to_destroy) | |
388 HideKeyboardTooltip(); | |
389 } | |
390 | |
391 } // namespace views | |
OLD | NEW |