comctl32/tooltips: Fix callback tip text retrieval.
[wine] / dlls / comctl32 / tests / tooltips.c
1 /*
2  * Copyright 2005 Dmitry Timoshkov
3  * Copyright 2008 Jason Edmeades
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  */
19
20 #include <assert.h>
21 #include <windows.h>
22 #include <commctrl.h>
23
24 #include "wine/test.h"
25
26 static void test_create_tooltip(void)
27 {
28     HWND parent, hwnd;
29     DWORD style, exp_style;
30
31     parent = CreateWindowEx(0, "static", NULL, WS_POPUP,
32                           0, 0, 0, 0,
33                           NULL, NULL, NULL, 0);
34     assert(parent);
35
36     hwnd = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, 0x7fffffff | WS_POPUP,
37                           10, 10, 300, 100,
38                           parent, NULL, NULL, 0);
39     assert(hwnd);
40
41     style = GetWindowLong(hwnd, GWL_STYLE);
42     trace("style = %08x\n", style);
43     exp_style = 0x7fffffff | WS_POPUP;
44     exp_style &= ~(WS_CHILD | WS_MAXIMIZE | WS_BORDER | WS_DLGFRAME);
45     ok(style == exp_style,"wrong style %08x/%08x\n", style, exp_style);
46
47     DestroyWindow(hwnd);
48
49     hwnd = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, 0,
50                           10, 10, 300, 100,
51                           parent, NULL, NULL, 0);
52     assert(hwnd);
53
54     style = GetWindowLong(hwnd, GWL_STYLE);
55     trace("style = %08x\n", style);
56     ok(style == (WS_POPUP | WS_CLIPSIBLINGS | WS_BORDER),
57        "wrong style %08x\n", style);
58
59     DestroyWindow(hwnd);
60
61     DestroyWindow(parent);
62 }
63
64 /* try to make sure pending X events have been processed before continuing */
65 static void flush_events(int waitTime)
66 {
67     MSG msg;
68     int diff = waitTime;
69     DWORD time = GetTickCount() + waitTime;
70
71     while (diff > 0)
72     {
73         if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min(100,diff), QS_ALLEVENTS) == WAIT_TIMEOUT) break;
74         while (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessage( &msg );
75         diff = time - GetTickCount();
76     }
77 }
78
79 static int CD_Stages;
80 static LRESULT CD_Result;
81 static HWND g_hwnd;
82
83 #define TEST_CDDS_PREPAINT           0x00000001
84 #define TEST_CDDS_POSTPAINT          0x00000002
85 #define TEST_CDDS_PREERASE           0x00000004
86 #define TEST_CDDS_POSTERASE          0x00000008
87 #define TEST_CDDS_ITEMPREPAINT       0x00000010
88 #define TEST_CDDS_ITEMPOSTPAINT      0x00000020
89 #define TEST_CDDS_ITEMPREERASE       0x00000040
90 #define TEST_CDDS_ITEMPOSTERASE      0x00000080
91 #define TEST_CDDS_SUBITEM            0x00000100
92
93 static LRESULT CALLBACK CustomDrawWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
94 {
95     switch(msg) {
96
97     case WM_DESTROY:
98         PostQuitMessage(0);
99         break;
100
101     case WM_NOTIFY:
102         if (((NMHDR *)lParam)->code == NM_CUSTOMDRAW) {
103             NMTTCUSTOMDRAW *ttcd = (NMTTCUSTOMDRAW*) lParam;
104             ok(ttcd->nmcd.hdr.hwndFrom == g_hwnd, "Unexpected hwnd source %p (%p)\n",
105                  ttcd->nmcd.hdr.hwndFrom, g_hwnd);
106             ok(ttcd->nmcd.hdr.idFrom == 0x1234ABCD, "Unexpected id %x\n", (int)ttcd->nmcd.hdr.idFrom);
107
108             switch (ttcd->nmcd.dwDrawStage) {
109             case CDDS_PREPAINT     : CD_Stages |= TEST_CDDS_PREPAINT; break;
110             case CDDS_POSTPAINT    : CD_Stages |= TEST_CDDS_POSTPAINT; break;
111             case CDDS_PREERASE     : CD_Stages |= TEST_CDDS_PREERASE; break;
112             case CDDS_POSTERASE    : CD_Stages |= TEST_CDDS_POSTERASE; break;
113             case CDDS_ITEMPREPAINT : CD_Stages |= TEST_CDDS_ITEMPREPAINT; break;
114             case CDDS_ITEMPOSTPAINT: CD_Stages |= TEST_CDDS_ITEMPOSTPAINT; break;
115             case CDDS_ITEMPREERASE : CD_Stages |= TEST_CDDS_ITEMPREERASE; break;
116             case CDDS_ITEMPOSTERASE: CD_Stages |= TEST_CDDS_ITEMPOSTERASE; break;
117             case CDDS_SUBITEM      : CD_Stages |= TEST_CDDS_SUBITEM; break;
118             default: CD_Stages = -1;
119             }
120
121             if (ttcd->nmcd.dwDrawStage == CDDS_PREPAINT) return CD_Result;
122         }
123         /* drop through */
124
125     default:
126         return DefWindowProcA(hWnd, msg, wParam, lParam);
127     }
128
129     return 0L;
130 }
131
132 static void test_customdraw(void) {
133     static struct {
134         LRESULT FirstReturnValue;
135         int ExpectedCalls;
136     } expectedResults[] = {
137         /* Valid notification responses */
138         {CDRF_DODEFAULT, TEST_CDDS_PREPAINT},
139         {CDRF_SKIPDEFAULT, TEST_CDDS_PREPAINT},
140         {CDRF_NOTIFYPOSTPAINT, TEST_CDDS_PREPAINT | TEST_CDDS_POSTPAINT},
141
142         /* Invalid notification responses */
143         {CDRF_NOTIFYITEMDRAW, TEST_CDDS_PREPAINT},
144         {CDRF_NOTIFYPOSTERASE, TEST_CDDS_PREPAINT},
145         {CDRF_NOTIFYSUBITEMDRAW, TEST_CDDS_PREPAINT},
146         {CDRF_NEWFONT, TEST_CDDS_PREPAINT}
147     };
148
149    int       iterationNumber;
150    WNDCLASSA wc;
151    LRESULT   lResult;
152
153    /* Create a class to use the custom draw wndproc */
154    wc.style = CS_HREDRAW | CS_VREDRAW;
155    wc.cbClsExtra = 0;
156    wc.cbWndExtra = 0;
157    wc.hInstance = GetModuleHandleA(NULL);
158    wc.hIcon = NULL;
159    wc.hCursor = LoadCursorA(NULL, IDC_ARROW);
160    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
161    wc.lpszMenuName = NULL;
162    wc.lpszClassName = "CustomDrawClass";
163    wc.lpfnWndProc = CustomDrawWndProc;
164    RegisterClass(&wc);
165
166    for (iterationNumber = 0;
167         iterationNumber < sizeof(expectedResults)/sizeof(expectedResults[0]);
168         iterationNumber++) {
169
170        HWND parent, hwndTip;
171        TOOLINFO toolInfo = { 0 };
172
173        /* Create a main window */
174        parent = CreateWindowEx(0, "CustomDrawClass", NULL,
175                                WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
176                                WS_MAXIMIZEBOX | WS_VISIBLE,
177                                50, 50,
178                                300, 300,
179                                NULL, NULL, NULL, 0);
180        ok(parent != NULL, "Creation of main window failed\n");
181
182        /* Make it show */
183        ShowWindow(parent, SW_SHOWNORMAL);
184        flush_events(100);
185
186        /* Create Tooltip */
187        hwndTip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS,
188                                 NULL, TTS_NOPREFIX | TTS_ALWAYSTIP,
189                                 CW_USEDEFAULT, CW_USEDEFAULT,
190                                 CW_USEDEFAULT, CW_USEDEFAULT,
191                                 parent, NULL, GetModuleHandleA(NULL), 0);
192        ok(hwndTip != NULL, "Creation of tooltip window failed\n");
193
194        /* Set up parms for the wndproc to handle */
195        CD_Stages = 0;
196        CD_Result = expectedResults[iterationNumber].FirstReturnValue;
197        g_hwnd    = hwndTip;
198
199        /* Make it topmost, as per the MSDN */
200        SetWindowPos(hwndTip, HWND_TOPMOST, 0, 0, 0, 0,
201              SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
202
203        /* Create a tool */
204        toolInfo.cbSize = sizeof(TOOLINFO);
205        toolInfo.hwnd = parent;
206        toolInfo.hinst = GetModuleHandleA(NULL);
207        toolInfo.uFlags = TTF_SUBCLASS;
208        toolInfo.uId = 0x1234ABCD;
209        toolInfo.lpszText = (LPSTR)"This is a test tooltip";
210        toolInfo.lParam = 0xdeadbeef;
211        GetClientRect (parent, &toolInfo.rect);
212        lResult = SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo);
213        ok(lResult, "Adding the tool to the tooltip failed\n");
214
215        /* Make tooltip appear quickly */
216        SendMessage(hwndTip, TTM_SETDELAYTIME, TTDT_INITIAL, MAKELPARAM(1,0));
217
218        /* Put cursor inside window, tooltip will appear immediately */
219        SetCursorPos(100, 100);
220        flush_events(200);
221
222        /* Check CustomDraw results */
223        ok(CD_Stages == expectedResults[iterationNumber].ExpectedCalls,
224           "CustomDraw run %d stages %x, expected %x\n", iterationNumber, CD_Stages,
225           expectedResults[iterationNumber].ExpectedCalls);
226
227        /* Clean up */
228        DestroyWindow(hwndTip);
229        DestroyWindow(parent);
230    }
231
232
233 }
234
235 static const CHAR testcallbackA[]  = "callback";
236
237 static LRESULT WINAPI parent_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
238 {
239     if (message == WM_NOTIFY && lParam)
240     {
241         NMTTDISPINFOA *ttnmdi = (NMTTDISPINFOA*)lParam;
242
243         if (ttnmdi->hdr.code == TTN_GETDISPINFOA)
244             lstrcpy(ttnmdi->lpszText, testcallbackA);
245     }
246
247     return DefWindowProcA(hwnd, message, wParam, lParam);
248 }
249
250 static BOOL register_parent_wnd_class(void)
251 {
252     WNDCLASSA cls;
253
254     cls.style = 0;
255     cls.lpfnWndProc = parent_wnd_proc;
256     cls.cbClsExtra = 0;
257     cls.cbWndExtra = 0;
258     cls.hInstance = GetModuleHandleA(NULL);
259     cls.hIcon = 0;
260     cls.hCursor = LoadCursorA(0, IDC_ARROW);
261     cls.hbrBackground = GetStockObject(WHITE_BRUSH);
262     cls.lpszMenuName = NULL;
263     cls.lpszClassName = "Tooltips test parent class";
264     return RegisterClassA(&cls);
265 }
266
267 static HWND create_parent_window(void)
268 {
269     if (!register_parent_wnd_class())
270         return NULL;
271
272     return CreateWindowEx(0, "Tooltips test parent class",
273                           "Tooltips test parent window",
274                           WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |
275                           WS_MAXIMIZEBOX | WS_VISIBLE,
276                           0, 0, 100, 100,
277                           GetDesktopWindow(), NULL, GetModuleHandleA(NULL), NULL);
278 }
279
280 static void test_gettext(void)
281 {
282     HWND hwnd, notify;
283     TTTOOLINFOA toolinfoA;
284     TTTOOLINFOW toolinfoW;
285     LRESULT r;
286     CHAR bufA[10] = "";
287     WCHAR bufW[10] = { 0 };
288     static const CHAR testtipA[] = "testtip";
289
290     notify = create_parent_window();
291     ok(notify != NULL, "Expected notification window to be created\n");
292
293     /* For bug 14790 - lpszText is NULL */
294     hwnd = CreateWindowExA(0, TOOLTIPS_CLASSA, NULL, 0,
295                            10, 10, 300, 100,
296                            NULL, NULL, NULL, 0);
297     assert(hwnd);
298
299     toolinfoA.cbSize = sizeof(TTTOOLINFOA);
300     toolinfoA.hwnd = NULL;
301     toolinfoA.hinst = GetModuleHandleA(NULL);
302     toolinfoA.uFlags = 0;
303     toolinfoA.uId = 0x1234ABCD;
304     toolinfoA.lpszText = NULL;
305     toolinfoA.lParam = 0xdeadbeef;
306     GetClientRect(hwnd, &toolinfoA.rect);
307     r = SendMessageA(hwnd, TTM_ADDTOOL, 0, (LPARAM)&toolinfoA);
308     ok(r, "Adding the tool to the tooltip failed\n");
309     if (r)
310     {
311         toolinfoA.hwnd = NULL;
312         toolinfoA.uId = 0x1234ABCD;
313         toolinfoA.lpszText = bufA;
314         SendMessageA(hwnd, TTM_GETTEXTA, 0, (LPARAM)&toolinfoA);
315         ok(strcmp(toolinfoA.lpszText, "") == 0, "lpszText should be an empty string\n");
316     }
317
318     /* add another tool with text */
319     toolinfoA.cbSize = sizeof(TTTOOLINFOA);
320     toolinfoA.hwnd = NULL;
321     toolinfoA.hinst = GetModuleHandleA(NULL);
322     toolinfoA.uFlags = 0;
323     toolinfoA.uId = 0x1235ABCD;
324     strcpy(bufA, testtipA);
325     toolinfoA.lpszText = bufA;
326     toolinfoA.lParam = 0xdeadbeef;
327     GetClientRect(hwnd, &toolinfoA.rect);
328     r = SendMessageA(hwnd, TTM_ADDTOOL, 0, (LPARAM)&toolinfoA);
329     ok(r, "Adding the tool to the tooltip failed\n");
330     if (r)
331     {
332         DWORD length;
333
334         length = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0);
335         ok(length == 0, "Expected 0, got %d\n", length);
336
337         toolinfoA.hwnd = NULL;
338         toolinfoA.uId = 0x1235ABCD;
339         toolinfoA.lpszText = bufA;
340         SendMessageA(hwnd, TTM_GETTEXTA, 0, (LPARAM)&toolinfoA);
341         ok(strcmp(toolinfoA.lpszText, testtipA) == 0, "lpszText should be an empty string\n");
342
343         length = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0);
344         ok(length == 0, "Expected 0, got %d\n", length);
345     }
346
347     /* add another with callback text */
348     toolinfoA.cbSize = sizeof(TTTOOLINFOA);
349     toolinfoA.hwnd = notify;
350     toolinfoA.hinst = GetModuleHandleA(NULL);
351     toolinfoA.uFlags = 0;
352     toolinfoA.uId = 0x1236ABCD;
353     toolinfoA.lpszText = LPSTR_TEXTCALLBACKA;
354     toolinfoA.lParam = 0xdeadbeef;
355     GetClientRect(hwnd, &toolinfoA.rect);
356     r = SendMessageA(hwnd, TTM_ADDTOOL, 0, (LPARAM)&toolinfoA);
357     ok(r, "Adding the tool to the tooltip failed\n");
358     if (r)
359     {
360         toolinfoA.hwnd = notify;
361         toolinfoA.uId = 0x1236ABCD;
362         toolinfoA.lpszText = bufA;
363         SendMessageA(hwnd, TTM_GETTEXTA, 0, (LPARAM)&toolinfoA);
364         ok(strcmp(toolinfoA.lpszText, testcallbackA) == 0,
365            "lpszText should be an (%s) string\n", testcallbackA);
366     }
367
368     DestroyWindow(hwnd);
369     DestroyWindow(notify);
370
371     SetLastError(0xdeadbeef);
372     hwnd = CreateWindowExW(0, TOOLTIPS_CLASSW, NULL, 0,
373                            10, 10, 300, 100,
374                            NULL, NULL, NULL, 0);
375
376     if (!hwnd && GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
377         win_skip("CreateWindowExW is not implemented\n");
378         return;
379     }
380
381     assert(hwnd);
382
383     toolinfoW.cbSize = sizeof(TTTOOLINFOW);
384     toolinfoW.hwnd = NULL;
385     toolinfoW.hinst = GetModuleHandleA(NULL);
386     toolinfoW.uFlags = 0;
387     toolinfoW.uId = 0x1234ABCD;
388     toolinfoW.lpszText = NULL;
389     toolinfoW.lParam = 0xdeadbeef;
390     GetClientRect(hwnd, &toolinfoW.rect);
391     r = SendMessageW(hwnd, TTM_ADDTOOL, 0, (LPARAM)&toolinfoW);
392     ok(r, "Adding the tool to the tooltip failed\n");
393
394     if (0)  /* crashes on NT4 */
395     {
396         toolinfoW.hwnd = NULL;
397         toolinfoW.uId = 0x1234ABCD;
398         toolinfoW.lpszText = bufW;
399         SendMessageW(hwnd, TTM_GETTEXTW, 0, (LPARAM)&toolinfoW);
400         ok(toolinfoW.lpszText[0] == 0, "lpszText should be an empty string\n");
401     }
402
403     DestroyWindow(hwnd);
404 }
405
406 START_TEST(tooltips)
407 {
408     InitCommonControls();
409
410     test_create_tooltip();
411     test_customdraw();
412     test_gettext();
413 }