]> git.saurik.com Git - wxWidgets.git/blob - src/msw/tooltip.cpp
use TTF_TRANSPARENT to fix problem with flashing tooltips (patch 1821229)
[wxWidgets.git] / src / msw / tooltip.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/tooltip.cpp
3 // Purpose: wxToolTip class implementation for MSW
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 31.01.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1999 Vadim Zeitlin
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_TOOLTIPS
27
28 #include "wx/tooltip.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
32 #include "wx/app.h"
33 #include "wx/control.h"
34 #endif
35
36 #include "wx/tokenzr.h"
37 #include "wx/msw/private.h"
38
39 #ifndef TTTOOLINFO_V1_SIZE
40 #define TTTOOLINFO_V1_SIZE 0x28
41 #endif
42
43 // VZ: normally, the trick with subclassing the tooltip control and processing
44 // TTM_WINDOWFROMPOINT should work but, somehow, it doesn't. I leave the
45 // code here for now (but it's not compiled) in case we need it later.
46 //
47 // For now I use an ugly workaround and process TTN_NEEDTEXT directly in
48 // radio button wnd proc - fixing TTM_WINDOWFROMPOINT code would be nice
49 // because it would then work for all controls, not only radioboxes but for
50 // now I don't understand what's wrong with it...
51 #define wxUSE_TTM_WINDOWFROMPOINT 0
52
53 // ----------------------------------------------------------------------------
54 // global variables
55 // ----------------------------------------------------------------------------
56
57 // the tooltip parent window
58 WXHWND wxToolTip::ms_hwndTT = (WXHWND)NULL;
59
60 #if wxUSE_TTM_WINDOWFROMPOINT
61
62 // the tooltip window proc
63 static WNDPROC gs_wndprocToolTip = (WNDPROC)NULL;
64
65 #endif // wxUSE_TTM_WINDOWFROMPOINT
66
67 // ----------------------------------------------------------------------------
68 // private classes
69 // ----------------------------------------------------------------------------
70
71 // a wrapper around TOOLINFO Win32 structure
72 #ifdef __VISUALC__
73 #pragma warning( disable : 4097 ) // we inherit from a typedef - so what?
74 #endif
75
76 class wxToolInfo : public TOOLINFO
77 {
78 public:
79 wxToolInfo(HWND hwndOwner)
80 {
81 // initialize all members
82 ::ZeroMemory(this, sizeof(TOOLINFO));
83
84 // the structure TOOLINFO has been extended with a 4 byte field in
85 // version 4.70 of comctl32.dll and another one in 5.01 but we don't
86 // use these extended fields so use the old struct size to ensure that
87 // the tooltips work on old (Windows 95) systems too
88 cbSize = TTTOOLINFO_V1_SIZE;
89
90 hwnd = hwndOwner;
91 uFlags = TTF_IDISHWND;
92
93 // we use TTF_TRANSPARENT to fix a problem which arises at least with
94 // the text controls but may presumably happen with other controls
95 // which display the tooltip at mouse position: it can start flashing
96 // then as the control gets "focus lost" events and dismisses the
97 // tooltip which then reappears because mouse remains hovering over the
98 // control, see SF patch 1821229
99 if ( wxApp::GetComCtl32Version() >= 470 )
100 {
101 uFlags |= TTF_TRANSPARENT;
102 }
103
104 uId = (UINT)hwndOwner;
105 }
106 };
107
108 #ifdef __VISUALC__
109 #pragma warning( default : 4097 )
110 #endif
111
112 // ----------------------------------------------------------------------------
113 // private functions
114 // ----------------------------------------------------------------------------
115
116 // send a message to the tooltip control if it exists
117 //
118 // NB: wParam is always 0 for the TTM_XXX messages we use
119 static inline LRESULT SendTooltipMessage(WXHWND hwnd, UINT msg, void *lParam)
120 {
121 return hwnd ? ::SendMessage((HWND)hwnd, msg, 0, (LPARAM)lParam) : 0;
122 }
123
124 // send a message to all existing tooltip controls
125 static inline void
126 SendTooltipMessageToAll(WXHWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
127 {
128 if ( hwnd )
129 ::SendMessage((HWND)hwnd, msg, wParam, lParam);
130 }
131
132 // ============================================================================
133 // implementation
134 // ============================================================================
135
136 #if wxUSE_TTM_WINDOWFROMPOINT
137
138 // ----------------------------------------------------------------------------
139 // window proc for our tooltip control
140 // ----------------------------------------------------------------------------
141
142 LRESULT APIENTRY wxToolTipWndProc(HWND hwndTT,
143 UINT msg,
144 WPARAM wParam,
145 LPARAM lParam)
146 {
147 if ( msg == TTM_WINDOWFROMPOINT )
148 {
149 LPPOINT ppt = (LPPOINT)lParam;
150
151 // the window on which event occurred
152 HWND hwnd = ::WindowFromPoint(*ppt);
153
154 OutputDebugString("TTM_WINDOWFROMPOINT: ");
155 OutputDebugString(wxString::Format("0x%08x => ", hwnd));
156
157 // return a HWND corresponding to a wxWindow because only wxWidgets are
158 // associated with tooltips using TTM_ADDTOOL
159 wxWindow *win = wxGetWindowFromHWND((WXHWND)hwnd);
160
161 if ( win )
162 {
163 hwnd = GetHwndOf(win);
164 OutputDebugString(wxString::Format("0x%08x\r\n", hwnd));
165
166 #if 0
167 // modify the point too!
168 RECT rect;
169 GetWindowRect(hwnd, &rect);
170
171 ppt->x = (rect.right - rect.left) / 2;
172 ppt->y = (rect.bottom - rect.top) / 2;
173 #endif // 0
174 return (LRESULT)hwnd;
175 }
176 else
177 {
178 OutputDebugString("no window\r\n");
179 }
180 }
181
182 return ::CallWindowProc(CASTWNDPROC gs_wndprocToolTip, hwndTT, msg, wParam, lParam);
183 }
184
185 #endif // wxUSE_TTM_WINDOWFROMPOINT
186
187 // ----------------------------------------------------------------------------
188 // static functions
189 // ----------------------------------------------------------------------------
190
191 void wxToolTip::Enable(bool flag)
192 {
193 SendTooltipMessageToAll(ms_hwndTT, TTM_ACTIVATE, flag, 0);
194 }
195
196 void wxToolTip::SetDelay(long milliseconds)
197 {
198 SendTooltipMessageToAll(ms_hwndTT, TTM_SETDELAYTIME,
199 TTDT_INITIAL, milliseconds);
200 }
201
202 void wxToolTip::SetAutoPop(long milliseconds)
203 {
204 SendTooltipMessageToAll(ms_hwndTT, TTM_SETDELAYTIME,
205 TTDT_AUTOPOP, milliseconds);
206 }
207
208 void wxToolTip::SetReshow(long milliseconds)
209 {
210 SendTooltipMessageToAll(ms_hwndTT, TTM_SETDELAYTIME,
211 TTDT_RESHOW, milliseconds);
212 }
213
214 // ---------------------------------------------------------------------------
215 // implementation helpers
216 // ---------------------------------------------------------------------------
217
218 // create the tooltip ctrl for our parent frame if it doesn't exist yet
219 /* static */
220 WXHWND wxToolTip::GetToolTipCtrl()
221 {
222 if ( !ms_hwndTT )
223 {
224 WXDWORD exflags = 0;
225 if ( wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft )
226 {
227 exflags |= WS_EX_LAYOUTRTL;
228 }
229
230 // we want to show the tooltips always (even when the window is not
231 // active) and we don't want to strip "&"s from them
232 ms_hwndTT = (WXHWND)::CreateWindowEx(exflags,
233 TOOLTIPS_CLASS,
234 (LPCTSTR)NULL,
235 TTS_ALWAYSTIP | TTS_NOPREFIX,
236 CW_USEDEFAULT, CW_USEDEFAULT,
237 CW_USEDEFAULT, CW_USEDEFAULT,
238 NULL, (HMENU)NULL,
239 wxGetInstance(),
240 NULL);
241 if ( ms_hwndTT )
242 {
243 HWND hwnd = (HWND)ms_hwndTT;
244 SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
245 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
246
247 #if wxUSE_TTM_WINDOWFROMPOINT
248 // subclass the newly created control
249 gs_wndprocToolTip = wxSetWindowProc(hwnd, wxToolTipWndProc);
250 #endif // wxUSE_TTM_WINDOWFROMPOINT
251 }
252 }
253
254 return ms_hwndTT;
255 }
256
257 /* static */
258 void wxToolTip::RelayEvent(WXMSG *msg)
259 {
260 (void)SendTooltipMessage(GetToolTipCtrl(), TTM_RELAYEVENT, msg);
261 }
262
263 // ----------------------------------------------------------------------------
264 // ctor & dtor
265 // ----------------------------------------------------------------------------
266
267 IMPLEMENT_ABSTRACT_CLASS(wxToolTip, wxObject)
268
269 wxToolTip::wxToolTip(const wxString &tip)
270 : m_text(tip)
271 {
272 m_window = NULL;
273 }
274
275 wxToolTip::~wxToolTip()
276 {
277 // the tooltip has to be removed before deleting. Otherwise, if it is visible
278 // while being deleted, there will be a delay before it goes away.
279 Remove();
280 }
281
282 // ----------------------------------------------------------------------------
283 // others
284 // ----------------------------------------------------------------------------
285
286 void wxToolTip::Remove(WXHWND hWnd)
287 {
288 wxToolInfo ti((HWND)hWnd);
289 (void)SendTooltipMessage(GetToolTipCtrl(), TTM_DELTOOL, &ti);
290 }
291
292 void wxToolTip::Remove()
293 {
294 // remove this tool from the tooltip control
295 if ( m_window )
296 {
297 Remove(m_window->GetHWND());
298 }
299 }
300
301 void wxToolTip::Add(WXHWND hWnd)
302 {
303 HWND hwnd = (HWND)hWnd;
304
305 wxToolInfo ti(hwnd);
306
307 // another possibility would be to specify LPSTR_TEXTCALLBACK here as we
308 // store the tooltip text ourselves anyhow, and provide it in response to
309 // TTN_NEEDTEXT (sent via WM_NOTIFY), but then we would be limited to 79
310 // character tooltips as this is the size of the szText buffer in
311 // NMTTDISPINFO struct -- and setting the tooltip here we can have tooltips
312 // of any length
313 ti.hwnd = hwnd;
314 ti.lpszText = (wxChar *)m_text.wx_str(); // const_cast
315
316 if ( !SendTooltipMessage(GetToolTipCtrl(), TTM_ADDTOOL, &ti) )
317 {
318 wxLogDebug(_T("Failed to create the tooltip '%s'"), m_text.c_str());
319 }
320 else
321 {
322 // check for multiline toopltip
323 int index = m_text.Find(_T('\n'));
324
325 if ( index != wxNOT_FOUND )
326 {
327 #ifdef TTM_SETMAXTIPWIDTH
328 if ( wxApp::GetComCtl32Version() >= 470 )
329 {
330 // use TTM_SETMAXTIPWIDTH to make tooltip multiline using the
331 // extent of its first line as max value
332 HFONT hfont = (HFONT)
333 SendTooltipMessage(GetToolTipCtrl(), WM_GETFONT, 0);
334
335 if ( !hfont )
336 {
337 hfont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
338 if ( !hfont )
339 {
340 wxLogLastError(wxT("GetStockObject(DEFAULT_GUI_FONT)"));
341 }
342 }
343
344 MemoryHDC hdc;
345 if ( !hdc )
346 {
347 wxLogLastError(wxT("CreateCompatibleDC(NULL)"));
348 }
349
350 if ( !SelectObject(hdc, hfont) )
351 {
352 wxLogLastError(wxT("SelectObject(hfont)"));
353 }
354
355 // find the width of the widest line
356 int max = 0;
357 wxStringTokenizer tokenizer(m_text, _T("\n"));
358 wxString token = tokenizer.GetNextToken();
359 while (token.length())
360 {
361 SIZE sz;
362 if ( !::GetTextExtentPoint32(hdc, token.wx_str(), token.length(), &sz) )
363 {
364 wxLogLastError(wxT("GetTextExtentPoint32"));
365 }
366 if ( sz.cx > max )
367 max = sz.cx;
368
369 token = tokenizer.GetNextToken();
370 }
371
372 // only set a new width if it is bigger than the current setting
373 if (max > SendTooltipMessage(GetToolTipCtrl(), TTM_GETMAXTIPWIDTH, 0))
374 SendTooltipMessage(GetToolTipCtrl(), TTM_SETMAXTIPWIDTH,
375 (void *)max);
376 }
377 else
378 #endif // comctl32.dll >= 4.70
379 {
380 // replace the '\n's with spaces because otherwise they appear as
381 // unprintable characters in the tooltip string
382 m_text.Replace(_T("\n"), _T(" "));
383 ti.lpszText = (wxChar *)m_text.wx_str(); // const_cast
384
385 if ( !SendTooltipMessage(GetToolTipCtrl(), TTM_ADDTOOL, &ti) )
386 {
387 wxLogDebug(_T("Failed to create the tooltip '%s'"), m_text.c_str());
388 }
389 }
390 }
391 }
392 }
393
394 void wxToolTip::SetWindow(wxWindow *win)
395 {
396 Remove();
397
398 m_window = win;
399
400 // add the window itself
401 if ( m_window )
402 {
403 Add(m_window->GetHWND());
404 }
405 #if !defined(__WXUNIVERSAL__)
406 // and all of its subcontrols (e.g. radio buttons in a radiobox) as well
407 wxControl *control = wxDynamicCast(m_window, wxControl);
408 if ( control )
409 {
410 const wxArrayLong& subcontrols = control->GetSubcontrols();
411 size_t count = subcontrols.GetCount();
412 for ( size_t n = 0; n < count; n++ )
413 {
414 int id = subcontrols[n];
415 HWND hwnd = GetDlgItem(GetHwndOf(m_window), id);
416 if ( !hwnd )
417 {
418 // may be it's a child of parent of the control, in fact?
419 // (radiobuttons are subcontrols, i.e. children of the radiobox
420 // for wxWidgets but are its siblings at Windows level)
421 hwnd = GetDlgItem(GetHwndOf(m_window->GetParent()), id);
422 }
423
424 // must have it by now!
425 wxASSERT_MSG( hwnd, _T("no hwnd for subcontrol?") );
426
427 Add((WXHWND)hwnd);
428 }
429 }
430 #endif // !defined(__WXUNIVERSAL__)
431 }
432
433 void wxToolTip::SetTip(const wxString& tip)
434 {
435 m_text = tip;
436
437 if ( m_window )
438 {
439 // update the tip text shown by the control
440 wxToolInfo ti(GetHwndOf(m_window));
441 ti.lpszText = (wxChar *)m_text.wx_str();
442
443 (void)SendTooltipMessage(GetToolTipCtrl(), TTM_UPDATETIPTEXT, &ti);
444 }
445 }
446
447 #endif // wxUSE_TOOLTIPS