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 |