6383890715f28f7ccefedd8a31d3f368a3bc21b2
[reactos.git] / rostests / winetests / comctl32 / propsheet.c
1 /* Unit test suite for property sheet control.
2 *
3 * Copyright 2006 Huw Davies
4 * Copyright 2009 Jan de Mooij
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 */
20
21 #include <wine/test.h>
22
23 //#include <windows.h>
24 #include <wingdi.h>
25 #include <winuser.h>
26 #include <commctrl.h>
27 #include <reactos/undocuser.h>
28 #include "msg.h"
29
30 #include "resources.h"
31
32 static HWND parenthwnd;
33 static HWND sheethwnd;
34
35 static LONG active_page = -1;
36
37 #define IDC_APPLY_BUTTON 12321
38
39
40 /* try to make sure pending X events have been processed before continuing */
41 static void flush_events(void)
42 {
43 MSG msg;
44 int diff = 200;
45 int min_timeout = 100;
46 DWORD time = GetTickCount() + diff;
47
48 while (diff > 0)
49 {
50 if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break;
51 while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) DispatchMessageA( &msg );
52 diff = time - GetTickCount();
53 }
54 }
55
56
57 static int CALLBACK sheet_callback(HWND hwnd, UINT msg, LPARAM lparam)
58 {
59 switch(msg)
60 {
61 case PSCB_INITIALIZED:
62 {
63 char caption[256];
64 GetWindowTextA(hwnd, caption, sizeof(caption));
65 ok(!strcmp(caption,"test caption"), "caption: %s\n", caption);
66 sheethwnd = hwnd;
67 return 0;
68 }
69 }
70 return 0;
71 }
72
73 static INT_PTR CALLBACK page_dlg_proc(HWND hwnd, UINT msg, WPARAM wparam,
74 LPARAM lparam)
75 {
76 switch(msg)
77 {
78 case WM_INITDIALOG:
79 {
80 HWND sheet = GetParent(hwnd);
81 char caption[256];
82 GetWindowTextA(sheet, caption, sizeof(caption));
83 ok(!strcmp(caption,"test caption"), "caption: %s\n", caption);
84 return TRUE;
85 }
86
87 case WM_NOTIFY:
88 {
89 NMHDR *nmhdr = (NMHDR *)lparam;
90 switch(nmhdr->code)
91 {
92 case PSN_APPLY:
93 return TRUE;
94 default:
95 return FALSE;
96 }
97 }
98 case WM_NCDESTROY:
99 ok(!SendMessageA(sheethwnd, PSM_INDEXTOHWND, 400, 0),"Should always be 0\n");
100 return TRUE;
101
102 default:
103 return FALSE;
104 }
105 }
106
107 static void test_title(void)
108 {
109 HPROPSHEETPAGE hpsp[1];
110 PROPSHEETPAGEA psp;
111 PROPSHEETHEADERA psh;
112 HWND hdlg;
113 DWORD style;
114
115 memset(&psp, 0, sizeof(psp));
116 psp.dwSize = sizeof(psp);
117 psp.dwFlags = 0;
118 psp.hInstance = GetModuleHandleA(NULL);
119 U(psp).pszTemplate = "prop_page1";
120 U2(psp).pszIcon = NULL;
121 psp.pfnDlgProc = page_dlg_proc;
122 psp.lParam = 0;
123
124 hpsp[0] = CreatePropertySheetPageA(&psp);
125
126 memset(&psh, 0, sizeof(psh));
127 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
128 psh.dwFlags = PSH_MODELESS | PSH_USECALLBACK;
129 psh.pszCaption = "test caption";
130 psh.nPages = 1;
131 psh.hwndParent = GetDesktopWindow();
132 U3(psh).phpage = hpsp;
133 psh.pfnCallback = sheet_callback;
134
135 hdlg = (HWND)PropertySheetA(&psh);
136 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle value %p\n", hdlg);
137
138 style = GetWindowLongA(hdlg, GWL_STYLE);
139 ok(style == (WS_POPUP|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CAPTION|WS_SYSMENU|
140 DS_CONTEXTHELP|DS_MODALFRAME|DS_SETFONT|DS_3DLOOK),
141 "got unexpected style: %x\n", style);
142
143 DestroyWindow(hdlg);
144 }
145
146 static void test_nopage(void)
147 {
148 HPROPSHEETPAGE hpsp[1];
149 PROPSHEETPAGEA psp;
150 PROPSHEETHEADERA psh;
151 HWND hdlg, hpage;
152 MSG msg;
153
154 memset(&psp, 0, sizeof(psp));
155 psp.dwSize = sizeof(psp);
156 psp.dwFlags = 0;
157 psp.hInstance = GetModuleHandleA(NULL);
158 U(psp).pszTemplate = "prop_page1";
159 U2(psp).pszIcon = NULL;
160 psp.pfnDlgProc = page_dlg_proc;
161 psp.lParam = 0;
162
163 hpsp[0] = CreatePropertySheetPageA(&psp);
164
165 memset(&psh, 0, sizeof(psh));
166 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
167 psh.dwFlags = PSH_MODELESS | PSH_USECALLBACK;
168 psh.pszCaption = "test caption";
169 psh.nPages = 1;
170 psh.hwndParent = GetDesktopWindow();
171 U3(psh).phpage = hpsp;
172 psh.pfnCallback = sheet_callback;
173
174 hdlg = (HWND)PropertySheetA(&psh);
175 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle value %p\n", hdlg);
176
177 ShowWindow(hdlg,SW_NORMAL);
178 SendMessageA(hdlg, PSM_REMOVEPAGE, 0, 0);
179 hpage = /* PropSheet_GetCurrentPageHwnd(hdlg); */
180 (HWND)SendMessageA(hdlg, PSM_GETCURRENTPAGEHWND, 0, 0);
181 active_page = /* PropSheet_HwndToIndex(hdlg, hpage)); */
182 (int)SendMessageA(hdlg, PSM_HWNDTOINDEX, (WPARAM)hpage, 0);
183 ok(hpage == NULL, "expected no current page, got %p, index=%d\n", hpage, active_page);
184 flush_events();
185 RedrawWindow(hdlg,NULL,NULL,RDW_UPDATENOW|RDW_ERASENOW);
186
187 /* Check that the property sheet was fully redrawn */
188 ok(!PeekMessageA(&msg, 0, WM_PAINT, WM_PAINT, PM_NOREMOVE),
189 "expected no pending WM_PAINT messages\n");
190 DestroyWindow(hdlg);
191 }
192
193 static int CALLBACK disableowner_callback(HWND hwnd, UINT msg, LPARAM lparam)
194 {
195 switch(msg)
196 {
197 case PSCB_INITIALIZED:
198 {
199 ok(IsWindowEnabled(parenthwnd) == 0, "parent window should be disabled\n");
200 PostQuitMessage(0);
201 return FALSE;
202 }
203 }
204 return FALSE;
205 }
206
207 static void register_parent_wnd_class(void)
208 {
209 WNDCLASSA cls;
210
211 cls.style = 0;
212 cls.lpfnWndProc = DefWindowProcA;
213 cls.cbClsExtra = 0;
214 cls.cbWndExtra = 0;
215 cls.hInstance = GetModuleHandleA(NULL);
216 cls.hIcon = 0;
217 cls.hCursor = LoadCursorA(0, (LPCSTR)IDC_ARROW);
218 cls.hbrBackground = GetStockObject(WHITE_BRUSH);
219 cls.lpszMenuName = NULL;
220 cls.lpszClassName = "parent class";
221 RegisterClassA(&cls);
222 }
223
224 static void test_disableowner(void)
225 {
226 HPROPSHEETPAGE hpsp[1];
227 PROPSHEETPAGEA psp;
228 PROPSHEETHEADERA psh;
229 INT_PTR p;
230
231 register_parent_wnd_class();
232 parenthwnd = CreateWindowA("parent class", "", WS_CAPTION | WS_SYSMENU | WS_VISIBLE, 100, 100, 100, 100, GetDesktopWindow(), NULL, GetModuleHandleA(NULL), 0);
233
234 memset(&psp, 0, sizeof(psp));
235 psp.dwSize = sizeof(psp);
236 psp.dwFlags = 0;
237 psp.hInstance = GetModuleHandleA(NULL);
238 U(psp).pszTemplate = "prop_page1";
239 U2(psp).pszIcon = NULL;
240 psp.pfnDlgProc = NULL;
241 psp.lParam = 0;
242
243 hpsp[0] = CreatePropertySheetPageA(&psp);
244
245 memset(&psh, 0, sizeof(psh));
246 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
247 psh.dwFlags = PSH_USECALLBACK;
248 psh.pszCaption = "test caption";
249 psh.nPages = 1;
250 psh.hwndParent = parenthwnd;
251 U3(psh).phpage = hpsp;
252 psh.pfnCallback = disableowner_callback;
253
254 p = PropertySheetA(&psh);
255 todo_wine
256 ok(p == 0, "Expected 0, got %ld\n", p);
257 ok(IsWindowEnabled(parenthwnd) != 0, "parent window should be enabled\n");
258 DestroyWindow(parenthwnd);
259 }
260
261 static INT_PTR CALLBACK nav_page_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
262 {
263 switch(msg){
264 case WM_NOTIFY:
265 {
266 LPNMHDR hdr = (LPNMHDR)lparam;
267 switch(hdr->code){
268 case PSN_SETACTIVE:
269 active_page = /* PropSheet_HwndToIndex(hdr->hwndFrom, hwnd); */
270 (int)SendMessageA(hdr->hwndFrom, PSM_HWNDTOINDEX, (WPARAM)hwnd, 0);
271 return TRUE;
272 case PSN_KILLACTIVE:
273 /* prevent navigation away from the fourth page */
274 if(active_page == 3){
275 SetWindowLongPtrA(hwnd, DWLP_MSGRESULT, TRUE);
276 return TRUE;
277 }
278 }
279 break;
280 }
281 }
282 return FALSE;
283 }
284
285 static WNDPROC old_nav_dialog_proc;
286
287 static LRESULT CALLBACK new_nav_dialog_proc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
288 {
289 switch (msg)
290 {
291 case DM_SETDEFID:
292 ok( IsWindowEnabled( GetDlgItem(hwnd, wp) ), "button is not enabled\n" );
293 break;
294 }
295 return CallWindowProcW( old_nav_dialog_proc, hwnd, msg, wp, lp );
296 }
297
298 static LRESULT CALLBACK hook_proc( int code, WPARAM wp, LPARAM lp )
299 {
300 static BOOL done;
301 if (code == HCBT_CREATEWND)
302 {
303 CBT_CREATEWNDW *c = (CBT_CREATEWNDW *)lp;
304
305 /* The first dialog created will be the parent dialog */
306 if (!done && c->lpcs->lpszClass == (LPWSTR)WC_DIALOG)
307 {
308 old_nav_dialog_proc = (WNDPROC)SetWindowLongPtrW( (HWND)wp, GWLP_WNDPROC, (LONG_PTR)new_nav_dialog_proc );
309 done = TRUE;
310 }
311 }
312
313 return CallNextHookEx( NULL, code, wp, lp );
314 }
315
316 static void test_wiznavigation(void)
317 {
318 HPROPSHEETPAGE hpsp[4];
319 PROPSHEETPAGEA psp[4];
320 PROPSHEETHEADERA psh;
321 HWND hdlg, control;
322 LONG_PTR controlID;
323 DWORD style;
324 LRESULT defidres;
325 BOOL hwndtoindex_supported = TRUE;
326 const INT nextID = 12324;
327 const INT backID = 12323;
328 HHOOK hook;
329
330 /* set up a hook proc in order to subclass the main dialog early on */
331 hook = SetWindowsHookExW( WH_CBT, hook_proc, NULL, GetCurrentThreadId() );
332
333 /* create the property sheet pages */
334 memset(psp, 0, sizeof(PROPSHEETPAGEA) * 4);
335
336 psp[0].dwSize = sizeof(PROPSHEETPAGEA);
337 psp[0].hInstance = GetModuleHandleA(NULL);
338 U(psp[0]).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_INTRO);
339 psp[0].pfnDlgProc = nav_page_proc;
340 hpsp[0] = CreatePropertySheetPageA(&psp[0]);
341
342 psp[1].dwSize = sizeof(PROPSHEETPAGEA);
343 psp[1].hInstance = GetModuleHandleA(NULL);
344 U(psp[1]).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_EDIT);
345 psp[1].pfnDlgProc = nav_page_proc;
346 hpsp[1] = CreatePropertySheetPageA(&psp[1]);
347
348 psp[2].dwSize = sizeof(PROPSHEETPAGEA);
349 psp[2].hInstance = GetModuleHandleA(NULL);
350 U(psp[2]).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_RADIO);
351 psp[2].pfnDlgProc = nav_page_proc;
352 hpsp[2] = CreatePropertySheetPageA(&psp[2]);
353
354 psp[3].dwSize = sizeof(PROPSHEETPAGEA);
355 psp[3].hInstance = GetModuleHandleA(NULL);
356 U(psp[3]).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_EXIT);
357 psp[3].pfnDlgProc = nav_page_proc;
358 hpsp[3] = CreatePropertySheetPageA(&psp[3]);
359
360 /* set up the property sheet dialog */
361 memset(&psh, 0, sizeof(psh));
362 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
363 psh.dwFlags = PSH_MODELESS | PSH_WIZARD;
364 psh.pszCaption = "A Wizard";
365 psh.nPages = 4;
366 psh.hwndParent = GetDesktopWindow();
367 U3(psh).phpage = hpsp;
368 hdlg = (HWND)PropertySheetA(&psh);
369 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle %p\n", hdlg);
370
371 ok(active_page == 0, "Active page should be 0. Is: %d\n", active_page);
372
373 style = GetWindowLongA(hdlg, GWL_STYLE) & ~(DS_CONTEXTHELP|WS_SYSMENU);
374 ok(style == (WS_POPUP|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CAPTION|
375 DS_MODALFRAME|DS_SETFONT|DS_3DLOOK),
376 "got unexpected style: %x\n", style);
377
378 control = GetFocus();
379 controlID = GetWindowLongPtrA(control, GWLP_ID);
380 ok(controlID == nextID, "Focus should have been set to the Next button. Expected: %d, Found: %ld\n", nextID, controlID);
381
382 /* simulate pressing the Next button */
383 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_NEXT, 0);
384 if (!active_page) hwndtoindex_supported = FALSE;
385 if (hwndtoindex_supported)
386 ok(active_page == 1, "Active page should be 1 after pressing Next. Is: %d\n", active_page);
387
388 control = GetFocus();
389 controlID = GetWindowLongPtrA(control, GWLP_ID);
390 ok(controlID == IDC_PS_EDIT1, "Focus should be set to the first item on the second page. Expected: %d, Found: %ld\n", IDC_PS_EDIT1, controlID);
391
392 defidres = SendMessageA(hdlg, DM_GETDEFID, 0, 0);
393 ok(defidres == MAKELRESULT(nextID, DC_HASDEFID), "Expected default button ID to be %d, is %d\n", nextID, LOWORD(defidres));
394
395 /* set the focus to the second edit box on this page */
396 SetFocus(GetNextDlgTabItem(hdlg, control, FALSE));
397
398 /* press next again */
399 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_NEXT, 0);
400 if (hwndtoindex_supported)
401 ok(active_page == 2, "Active page should be 2 after pressing Next. Is: %d\n", active_page);
402
403 control = GetFocus();
404 controlID = GetWindowLongPtrA(control, GWLP_ID);
405 ok(controlID == IDC_PS_RADIO1, "Focus should have been set to item on third page. Expected: %d, Found %ld\n", IDC_PS_RADIO1, controlID);
406
407 /* back button */
408 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_BACK, 0);
409 if (hwndtoindex_supported)
410 ok(active_page == 1, "Active page should be 1 after pressing Back. Is: %d\n", active_page);
411
412 control = GetFocus();
413 controlID = GetWindowLongPtrA(control, GWLP_ID);
414 ok(controlID == IDC_PS_EDIT1, "Focus should have been set to the first item on second page. Expected: %d, Found %ld\n", IDC_PS_EDIT1, controlID);
415
416 defidres = SendMessageA(hdlg, DM_GETDEFID, 0, 0);
417 ok(defidres == MAKELRESULT(backID, DC_HASDEFID), "Expected default button ID to be %d, is %d\n", backID, LOWORD(defidres));
418
419 /* press next twice */
420 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_NEXT, 0);
421 if (hwndtoindex_supported)
422 ok(active_page == 2, "Active page should be 2 after pressing Next. Is: %d\n", active_page);
423 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_NEXT, 0);
424 if (hwndtoindex_supported)
425 ok(active_page == 3, "Active page should be 3 after pressing Next. Is: %d\n", active_page);
426 else
427 active_page = 3;
428
429 control = GetFocus();
430 controlID = GetWindowLongPtrA(control, GWLP_ID);
431 ok(controlID == nextID, "Focus should have been set to the Next button. Expected: %d, Found: %ld\n", nextID, controlID);
432
433 /* try to navigate away, but shouldn't be able to */
434 SendMessageA(hdlg, PSM_PRESSBUTTON, PSBTN_BACK, 0);
435 ok(active_page == 3, "Active page should still be 3 after pressing Back. Is: %d\n", active_page);
436
437 defidres = SendMessageA(hdlg, DM_GETDEFID, 0, 0);
438 ok(defidres == MAKELRESULT(nextID, DC_HASDEFID), "Expected default button ID to be %d, is %d\n", nextID, LOWORD(defidres));
439
440 DestroyWindow(hdlg);
441 UnhookWindowsHookEx( hook );
442 }
443
444 static void test_buttons(void)
445 {
446 HPROPSHEETPAGE hpsp[1];
447 PROPSHEETPAGEA psp;
448 PROPSHEETHEADERA psh;
449 HWND hdlg;
450 HWND button;
451 RECT rc;
452 int prevRight, top;
453
454 memset(&psp, 0, sizeof(psp));
455 psp.dwSize = sizeof(psp);
456 psp.dwFlags = 0;
457 psp.hInstance = GetModuleHandleA(NULL);
458 U(psp).pszTemplate = "prop_page1";
459 U2(psp).pszIcon = NULL;
460 psp.pfnDlgProc = page_dlg_proc;
461 psp.lParam = 0;
462
463 hpsp[0] = CreatePropertySheetPageA(&psp);
464
465 memset(&psh, 0, sizeof(psh));
466 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
467 psh.dwFlags = PSH_MODELESS | PSH_USECALLBACK;
468 psh.pszCaption = "test caption";
469 psh.nPages = 1;
470 psh.hwndParent = GetDesktopWindow();
471 U3(psh).phpage = hpsp;
472 psh.pfnCallback = sheet_callback;
473
474 hdlg = (HWND)PropertySheetA(&psh);
475 ok(hdlg != INVALID_HANDLE_VALUE, "got null handle\n");
476
477 /* OK button */
478 button = GetDlgItem(hdlg, IDOK);
479 GetWindowRect(button, &rc);
480 prevRight = rc.right;
481 top = rc.top;
482
483 /* Cancel button */
484 button = GetDlgItem(hdlg, IDCANCEL);
485 GetWindowRect(button, &rc);
486 ok(rc.top == top, "Cancel button should have same top as OK button\n");
487 ok(rc.left > prevRight, "Cancel button should be to the right of OK button\n");
488 prevRight = rc.right;
489
490 button = GetDlgItem(hdlg, IDC_APPLY_BUTTON);
491 GetWindowRect(button, &rc);
492 ok(rc.top == top, "Apply button should have same top as OK button\n");
493 ok(rc.left > prevRight, "Apply button should be to the right of Cancel button\n");
494 prevRight = rc.right;
495
496 button = GetDlgItem(hdlg, IDHELP);
497 GetWindowRect(button, &rc);
498 ok(rc.top == top, "Help button should have same top as OK button\n");
499 ok(rc.left > prevRight, "Help button should be to the right of Apply button\n");
500
501 DestroyWindow(hdlg);
502 }
503
504 static BOOL add_button_has_been_pressed;
505
506 static INT_PTR CALLBACK
507 page_with_custom_default_button_dlg_proc(HWND hdlg, UINT msg, WPARAM wparam, LPARAM lparam)
508 {
509 switch (msg)
510 {
511 case WM_COMMAND:
512 switch(LOWORD(wparam))
513 {
514 case IDC_PS_PUSHBUTTON1:
515 switch(HIWORD(wparam))
516 {
517 case BN_CLICKED:
518 add_button_has_been_pressed = TRUE;
519 return TRUE;
520 }
521 break;
522 }
523 break;
524 }
525 return FALSE;
526 }
527
528 static void test_custom_default_button(void)
529 {
530 HWND hdlg, page;
531 PROPSHEETPAGEA psp[1];
532 PROPSHEETHEADERA psh;
533 MSG msg;
534 LRESULT result;
535
536 psp[0].dwSize = sizeof (PROPSHEETPAGEA);
537 psp[0].dwFlags = PSP_USETITLE;
538 psp[0].hInstance = GetModuleHandleA(NULL);
539 U(psp[0]).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_WITH_CUSTOM_DEFAULT_BUTTON);
540 U2(psp[0]).pszIcon = NULL;
541 psp[0].pfnDlgProc = page_with_custom_default_button_dlg_proc;
542 psp[0].pszTitle = "Page1";
543 psp[0].lParam = 0;
544
545 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
546 psh.dwFlags = PSH_PROPSHEETPAGE | PSH_MODELESS;
547 psh.hwndParent = GetDesktopWindow();
548 psh.hInstance = GetModuleHandleA(NULL);
549 U(psh).pszIcon = NULL;
550 psh.pszCaption = "PropertySheet1";
551 psh.nPages = 1;
552 U3(psh).ppsp = psp;
553 U2(psh).nStartPage = 0;
554
555 /* The goal of the test is to make sure that the Add button is pressed
556 * when the ENTER key is pressed and a different control, a combobox,
557 * has the keyboard focus. */
558 add_button_has_been_pressed = FALSE;
559
560 /* Create the modeless property sheet. */
561 hdlg = (HWND)PropertySheetA(&psh);
562 ok(hdlg != INVALID_HANDLE_VALUE, "Cannot create the property sheet\n");
563
564 /* Set the Add button as the default button. */
565 SendMessageA(hdlg, DM_SETDEFID, (WPARAM)IDC_PS_PUSHBUTTON1, 0);
566
567 /* Make sure the default button is the Add button. */
568 result = SendMessageA(hdlg, DM_GETDEFID, 0, 0);
569 ok(DC_HASDEFID == HIWORD(result), "The property sheet does not have a default button\n");
570 ok(IDC_PS_PUSHBUTTON1 == LOWORD(result), "The default button is not the Add button\n");
571
572 /* At this point, the combobox should have keyboard focus, so we press ENTER.
573 * Pull the lever, Kronk! */
574 page = (HWND)SendMessageW(hdlg, PSM_GETCURRENTPAGEHWND, 0, 0);
575 PostMessageW(GetDlgItem(page, IDC_PS_COMBO1), WM_KEYDOWN, VK_RETURN, 0);
576
577 /* Process all the messages in the queue for this thread. */
578 while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
579 {
580 /* (!PropSheet_IsDialogMessage(hdlg, &msg)) */
581 if (!((BOOL)SendMessageA(hdlg, PSM_ISDIALOGMESSAGE, 0, (LPARAM)&msg)))
582 {
583 TranslateMessage(&msg);
584 DispatchMessageA(&msg);
585 }
586 }
587
588 ok(add_button_has_been_pressed, "The Add button has not been pressed!\n");
589
590 DestroyWindow(hdlg);
591 }
592
593 #define RECEIVER_SHEET_CALLBACK 0
594 #define RECEIVER_SHEET_WINPROC 1
595 #define RECEIVER_PAGE 2
596
597 #define NUM_MSG_SEQUENCES 1
598 #define PROPSHEET_SEQ_INDEX 0
599
600 static struct msg_sequence *sequences[NUM_MSG_SEQUENCES];
601 static WNDPROC oldWndProc;
602
603 static const struct message property_sheet_seq[] = {
604 { PSCB_PRECREATE, sent|id, 0, 0, RECEIVER_SHEET_CALLBACK },
605 { PSCB_INITIALIZED, sent|id, 0, 0, RECEIVER_SHEET_CALLBACK },
606 { WM_WINDOWPOSCHANGING, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
607 /*{ WM_NCCALCSIZE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
608 { WM_WINDOWPOSCHANGED, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
609 { WM_MOVE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
610 { WM_SIZE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
611 /*{ WM_GETTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
612 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
613 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
614 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
615 { WM_NCCALCSIZE, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
616 { DM_REPOSITION, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
617 { WM_WINDOWPOSCHANGING, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
618 { WM_WINDOWPOSCHANGING, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
619 { WM_ACTIVATEAPP, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
620 /*{ WM_NCACTIVATE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
621 { WM_GETTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
622 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
623 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
624 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
625 { WM_GETTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
626 { WM_ACTIVATE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
627 /*{ WM_IME_SETCONTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
628 { WM_IME_NOTIFY, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
629 { WM_SETFOCUS, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
630 { WM_KILLFOCUS, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
631 /*{ WM_IME_SETCONTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
632 { WM_PARENTNOTIFY, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
633 { WM_INITDIALOG, sent|id, 0, 0, RECEIVER_PAGE },
634 { WM_WINDOWPOSCHANGING, sent|id, 0, 0, RECEIVER_PAGE },
635 /*{ WM_NCCALCSIZE, sent|id, 0, 0, RECEIVER_PAGE },*/
636 { WM_CHILDACTIVATE, sent|id, 0, 0, RECEIVER_PAGE },
637 { WM_WINDOWPOSCHANGED, sent|id, 0, 0, RECEIVER_PAGE },
638 { WM_MOVE, sent|id, 0, 0, RECEIVER_PAGE },
639 { WM_SIZE, sent|id, 0, 0, RECEIVER_PAGE },
640 { WM_NOTIFY, sent|id, 0, 0, RECEIVER_PAGE },
641 { WM_STYLECHANGING, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
642 { WM_STYLECHANGED, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
643 /*{ WM_GETTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
644 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
645 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
646 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
647 { WM_SETTEXT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
648 { WM_SHOWWINDOW, sent|id, 0, 0, RECEIVER_PAGE },
649 /*{ 0x00000401, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
650 { 0x00000400, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
651 { WM_CHANGEUISTATE, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
652 { WM_UPDATEUISTATE, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
653 { WM_UPDATEUISTATE, sent|id|optional, 0, 0, RECEIVER_PAGE },
654 { WM_SHOWWINDOW, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
655 { WM_WINDOWPOSCHANGING, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
656 /*{ WM_NCPAINT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
657 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
658 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
659 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
660 { WM_ERASEBKGND, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
661 { WM_CTLCOLORDLG, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
662 { WM_WINDOWPOSCHANGED, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
663 /*{ WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
664 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
665 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
666 { WM_PAINT, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
667 { WM_PAINT, sent|id, 0, 0, RECEIVER_PAGE },
668 { WM_NCPAINT, sent|id, 0, 0, RECEIVER_PAGE },
669 { WM_ERASEBKGND, sent|id, 0, 0, RECEIVER_PAGE },*/
670 { WM_CTLCOLORDLG, sent|id, 0, 0, RECEIVER_PAGE },
671 { WM_CTLCOLORSTATIC, sent|id, 0, 0, RECEIVER_PAGE },
672 { WM_CTLCOLORSTATIC, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
673 { WM_CTLCOLORBTN, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
674 { WM_CTLCOLORBTN, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
675 { WM_CTLCOLORBTN, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
676 /*{ WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
677 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
678 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
679 { WM_COMMAND, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
680 { WM_NOTIFY, sent|id|optional, 0, 0, RECEIVER_PAGE },
681 { WM_NOTIFY, sent|id|optional, 0, 0, RECEIVER_PAGE },
682 { WM_WINDOWPOSCHANGING, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
683 { WM_WINDOWPOSCHANGED, sent|id|optional, 0, 0, RECEIVER_SHEET_WINPROC },
684 /*{ WM_NCACTIVATE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
685 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
686 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
687 { WM_GETICON, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
688 /*{ WM_ACTIVATE, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
689 { WM_ACTIVATE, sent|id, 0, 0, RECEIVER_PAGE },
690 { WM_ACTIVATEAPP, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
691 { WM_ACTIVATEAPP, sent|id, 0, 0, RECEIVER_PAGE },
692 { WM_DESTROY, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },
693 { WM_DESTROY, sent|id, 0, 0, RECEIVER_PAGE },*/
694 /*{ WM_NCDESTROY, sent|id, 0, 0, RECEIVER_PAGE },
695 { WM_NCDESTROY, sent|id, 0, 0, RECEIVER_SHEET_WINPROC },*/
696 { 0 }
697 };
698
699 static void save_message(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, INT receiver)
700 {
701 struct message msg;
702
703 if (message < WM_USER &&
704 message != WM_GETICON &&
705 message != WM_GETTEXT &&
706 message != WM_IME_SETCONTEXT &&
707 message != WM_IME_NOTIFY &&
708 message != WM_PAINT &&
709 message != WM_ERASEBKGND &&
710 message != WM_SETCURSOR &&
711 (message < WM_NCCREATE || message > WM_NCMBUTTONDBLCLK) &&
712 (message < WM_MOUSEFIRST || message > WM_MOUSEHWHEEL) &&
713 message != 0x90)
714 {
715 msg.message = message;
716 msg.flags = sent|wparam|lparam|id;
717 msg.wParam = wParam;
718 msg.lParam = lParam;
719 msg.id = receiver;
720 add_message(sequences, PROPSHEET_SEQ_INDEX, &msg);
721 }
722 }
723
724 static LRESULT CALLBACK sheet_callback_messages_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
725 {
726 save_message(hwnd, msg, wParam, lParam, RECEIVER_SHEET_WINPROC);
727
728 return CallWindowProcA(oldWndProc, hwnd, msg, wParam, lParam);
729 }
730
731 static int CALLBACK sheet_callback_messages(HWND hwnd, UINT msg, LPARAM lParam)
732 {
733 save_message(hwnd, msg, 0, lParam, RECEIVER_SHEET_CALLBACK);
734
735 switch (msg)
736 {
737 case PSCB_INITIALIZED:
738 oldWndProc = (WNDPROC)GetWindowLongPtrA(hwnd, GWLP_WNDPROC);
739 SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)&sheet_callback_messages_proc);
740 return TRUE;
741 }
742
743 return TRUE;
744 }
745
746 static INT_PTR CALLBACK page_dlg_proc_messages(HWND hwnd, UINT msg, WPARAM wParam,
747 LPARAM lParam)
748 {
749 save_message(hwnd, msg, wParam, lParam, RECEIVER_PAGE);
750
751 return FALSE;
752 }
753
754 static void test_messages(void)
755 {
756 HPROPSHEETPAGE hpsp[1];
757 PROPSHEETPAGEA psp;
758 PROPSHEETHEADERA psh;
759 HWND hdlg;
760
761 init_msg_sequences(sequences, NUM_MSG_SEQUENCES);
762
763 memset(&psp, 0, sizeof(psp));
764 psp.dwSize = sizeof(psp);
765 psp.dwFlags = 0;
766 psp.hInstance = GetModuleHandleA(NULL);
767 U(psp).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_MESSAGE_TEST);
768 U2(psp).pszIcon = NULL;
769 psp.pfnDlgProc = page_dlg_proc_messages;
770 psp.lParam = 0;
771
772 hpsp[0] = CreatePropertySheetPageA(&psp);
773
774 memset(&psh, 0, sizeof(psh));
775 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
776 psh.dwFlags = PSH_NOAPPLYNOW | PSH_WIZARD | PSH_USECALLBACK
777 | PSH_MODELESS | PSH_USEICONID;
778 psh.pszCaption = "test caption";
779 psh.nPages = 1;
780 psh.hwndParent = GetDesktopWindow();
781 U3(psh).phpage = hpsp;
782 psh.pfnCallback = sheet_callback_messages;
783
784 hdlg = (HWND)PropertySheetA(&psh);
785 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle %p\n", hdlg);
786
787 ShowWindow(hdlg,SW_NORMAL);
788
789 ok_sequence(sequences, PROPSHEET_SEQ_INDEX, property_sheet_seq, "property sheet with custom window proc", TRUE);
790
791 DestroyWindow(hdlg);
792 }
793
794 static void test_PSM_ADDPAGE(void)
795 {
796 HPROPSHEETPAGE hpsp[5];
797 PROPSHEETPAGEA psp;
798 PROPSHEETHEADERA psh;
799 HWND hdlg, tab;
800 BOOL ret;
801 DWORD r;
802
803 memset(&psp, 0, sizeof(psp));
804 psp.dwSize = sizeof(psp);
805 psp.dwFlags = 0;
806 psp.hInstance = GetModuleHandleA(NULL);
807 U(psp).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_MESSAGE_TEST);
808 U2(psp).pszIcon = NULL;
809 psp.pfnDlgProc = page_dlg_proc_messages;
810 psp.lParam = 0;
811
812 /* multiple pages with the same data */
813 hpsp[0] = CreatePropertySheetPageA(&psp);
814 hpsp[1] = CreatePropertySheetPageA(&psp);
815 hpsp[2] = CreatePropertySheetPageA(&psp);
816
817 U(psp).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_ERROR);
818 hpsp[3] = CreatePropertySheetPageA(&psp);
819
820 psp.dwFlags = PSP_PREMATURE;
821 hpsp[4] = CreatePropertySheetPageA(&psp);
822
823 memset(&psh, 0, sizeof(psh));
824 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
825 psh.dwFlags = PSH_MODELESS;
826 psh.pszCaption = "test caption";
827 psh.nPages = 1;
828 psh.hwndParent = GetDesktopWindow();
829 U3(psh).phpage = hpsp;
830
831 hdlg = (HWND)PropertySheetA(&psh);
832 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle %p\n", hdlg);
833
834 /* add pages one by one */
835 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, (LPARAM)hpsp[1]);
836 ok(ret == TRUE, "got %d\n", ret);
837
838 /* try with null and invalid value */
839 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, 0);
840 ok(ret == FALSE, "got %d\n", ret);
841
842 if (0)
843 {
844 /* crashes on native */
845 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, (LPARAM)INVALID_HANDLE_VALUE);
846 }
847 /* check item count */
848 tab = (HWND)SendMessageA(hdlg, PSM_GETTABCONTROL, 0, 0);
849
850 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
851 ok(r == 2, "got %d\n", r);
852
853 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, (LPARAM)hpsp[2]);
854 ok(ret == TRUE, "got %d\n", ret);
855
856 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
857 ok(r == 3, "got %d\n", r);
858
859 /* add property sheet page that can't be created */
860 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, (LPARAM)hpsp[3]);
861 ok(ret == TRUE, "got %d\n", ret);
862
863 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
864 ok(r == 4, "got %d\n", r);
865
866 /* select page that can't be created */
867 ret = SendMessageA(hdlg, PSM_SETCURSEL, 3, 1);
868 ok(ret == TRUE, "got %d\n", ret);
869
870 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
871 ok(r == 3, "got %d\n", r);
872
873 /* test PSP_PREMATURE flag with incorrect property sheet page */
874 ret = SendMessageA(hdlg, PSM_ADDPAGE, 0, (LPARAM)hpsp[4]);
875 ok(ret == FALSE, "got %d\n", ret);
876
877 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
878 ok(r == 3, "got %d\n", r);
879
880 DestroyPropertySheetPage(hpsp[4]);
881 DestroyWindow(hdlg);
882 }
883
884 static void test_PSM_INSERTPAGE(void)
885 {
886 HPROPSHEETPAGE hpsp[5];
887 PROPSHEETPAGEA psp;
888 PROPSHEETHEADERA psh;
889 HWND hdlg, tab;
890 BOOL ret;
891 DWORD r;
892
893 memset(&psp, 0, sizeof(psp));
894 psp.dwSize = sizeof(psp);
895 psp.dwFlags = 0;
896 psp.hInstance = GetModuleHandleA(NULL);
897 U(psp).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_MESSAGE_TEST);
898 U2(psp).pszIcon = NULL;
899 psp.pfnDlgProc = page_dlg_proc_messages;
900 psp.lParam = 0;
901
902 /* multiple pages with the same data */
903 hpsp[0] = CreatePropertySheetPageA(&psp);
904 hpsp[1] = CreatePropertySheetPageA(&psp);
905 hpsp[2] = CreatePropertySheetPageA(&psp);
906
907 U(psp).pszTemplate = (LPCSTR)MAKEINTRESOURCE(IDD_PROP_PAGE_ERROR);
908 hpsp[3] = CreatePropertySheetPageA(&psp);
909
910 psp.dwFlags = PSP_PREMATURE;
911 hpsp[4] = CreatePropertySheetPageA(&psp);
912
913 memset(&psh, 0, sizeof(psh));
914 psh.dwSize = PROPSHEETHEADERA_V1_SIZE;
915 psh.dwFlags = PSH_MODELESS;
916 psh.pszCaption = "test caption";
917 psh.nPages = 1;
918 psh.hwndParent = GetDesktopWindow();
919 U3(psh).phpage = hpsp;
920
921 hdlg = (HWND)PropertySheetA(&psh);
922 ok(hdlg != INVALID_HANDLE_VALUE, "got invalid handle %p\n", hdlg);
923
924 /* add pages one by one */
925 ret = SendMessageA(hdlg, PSM_INSERTPAGE, 5, (LPARAM)hpsp[1]);
926 ok(ret == TRUE, "got %d\n", ret);
927
928 /* try with invalid values */
929 ret = SendMessageA(hdlg, PSM_INSERTPAGE, 0, 0);
930 ok(ret == FALSE, "got %d\n", ret);
931
932 if (0)
933 {
934 /* crashes on native */
935 ret = SendMessageA(hdlg, PSM_INSERTPAGE, 0, (LPARAM)INVALID_HANDLE_VALUE);
936 }
937
938 ret = SendMessageA(hdlg, PSM_INSERTPAGE, (LPARAM)INVALID_HANDLE_VALUE, (LPARAM)hpsp[2]);
939 ok(ret == FALSE, "got %d\n", ret);
940
941 /* check item count */
942 tab = (HWND)SendMessageA(hdlg, PSM_GETTABCONTROL, 0, 0);
943
944 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
945 ok(r == 2, "got %d\n", r);
946
947 ret = SendMessageA(hdlg, PSM_INSERTPAGE, (WPARAM)hpsp[1], (LPARAM)hpsp[2]);
948 ok(ret == TRUE, "got %d\n", ret);
949
950 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
951 ok(r == 3, "got %d\n", r);
952
953 /* add property sheet page that can't be created */
954 ret = SendMessageA(hdlg, PSM_INSERTPAGE, 1, (LPARAM)hpsp[3]);
955 ok(ret == TRUE, "got %d\n", ret);
956
957 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
958 ok(r == 4, "got %d\n", r);
959
960 /* select page that can't be created */
961 ret = SendMessageA(hdlg, PSM_SETCURSEL, 1, 0);
962 ok(ret == TRUE, "got %d\n", ret);
963
964 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
965 ok(r == 3, "got %d\n", r);
966
967 /* test PSP_PREMATURE flag with incorrect property sheet page */
968 ret = SendMessageA(hdlg, PSM_INSERTPAGE, 0, (LPARAM)hpsp[4]);
969 ok(ret == FALSE, "got %d\n", ret);
970
971 r = SendMessageA(tab, TCM_GETITEMCOUNT, 0, 0);
972 ok(r == 3, "got %d\n", r);
973
974 DestroyPropertySheetPage(hpsp[4]);
975 DestroyWindow(hdlg);
976 }
977
978 START_TEST(propsheet)
979 {
980 test_title();
981 test_nopage();
982 test_disableowner();
983 test_wiznavigation();
984 test_buttons();
985 test_custom_default_button();
986 test_messages();
987 test_PSM_ADDPAGE();
988 test_PSM_INSERTPAGE();
989 }