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