1 /* Unit test suite for combo boxes.
3 * Copyright 2007 Mikolaj Zalewski
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.
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.
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
25 #define WIN32_LEAN_AND_MEAN
28 #include "wine/test.h"
34 #define expect_eq(expr, value, type, fmt); { type val = expr; ok(val == (value), #expr " expected " #fmt " got " #fmt "\n", (value), val); }
35 #define expect_rect(r, _left, _top, _right, _bottom) ok(r.left == _left && r.top == _top && \
36 r.bottom == _bottom && r.right == _right, "Invalid rect (%d,%d) (%d,%d) vs (%d,%d) (%d,%d)\n", \
37 r.left, r.top, r.right, r.bottom, _left, _top, _right, _bottom);
39 static HWND build_combo(DWORD style)
41 return CreateWindow("ComboBox", "Combo", WS_VISIBLE|WS_CHILD|style, 5, 5, 100, 100, hMainWnd, (HMENU)COMBO_ID, NULL, 0);
44 static int font_height(HFONT hFont)
50 hDC = CreateCompatibleDC(NULL);
51 hFontOld = SelectObject(hDC, hFont);
52 GetTextMetrics(hDC, &tm);
53 SelectObject(hDC, hFontOld);
59 static INT CALLBACK is_font_installed_proc(const LOGFONT *elf, const TEXTMETRIC *tm, DWORD type, LPARAM lParam)
64 static int is_font_installed(const char *name)
66 HDC hdc = GetDC(NULL);
67 BOOL ret = !EnumFontFamilies(hdc, name, is_font_installed_proc, 0);
72 static void test_setitemheight(DWORD style)
74 HWND hCombo = build_combo(style);
78 trace("Style %x\n", style);
79 GetClientRect(hCombo, &r);
80 expect_rect(r, 0, 0, 100, font_height(GetStockObject(SYSTEM_FONT)) + 8);
81 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&r);
82 MapWindowPoints(HWND_DESKTOP, hMainWnd, (LPPOINT)&r, 2);
83 todo_wine expect_rect(r, 5, 5, 105, 105);
85 for (i = 1; i < 30; i++)
87 SendMessage(hCombo, CB_SETITEMHEIGHT, -1, i);
88 GetClientRect(hCombo, &r);
89 expect_eq(r.bottom - r.top, i + 6, int, "%d");
92 DestroyWindow(hCombo);
95 static void test_setfont(DWORD style)
102 if (!is_font_installed("Marlett"))
104 skip("Marlett font not available\n");
108 trace("Style %x\n", style);
110 hCombo = build_combo(style);
111 hFont1 = CreateFont(10, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, "Marlett");
112 hFont2 = CreateFont(8, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, "Marlett");
114 GetClientRect(hCombo, &r);
115 expect_rect(r, 0, 0, 100, font_height(GetStockObject(SYSTEM_FONT)) + 8);
116 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&r);
117 MapWindowPoints(HWND_DESKTOP, hMainWnd, (LPPOINT)&r, 2);
118 todo_wine expect_rect(r, 5, 5, 105, 105);
120 /* The size of the dropped control is initially equal to the size
121 of the window when it was created. The size of the calculated
122 dropped area changes only by how much the selection area
123 changes, not by how much the list area changes. */
124 if (font_height(hFont1) == 10 && font_height(hFont2) == 8)
126 SendMessage(hCombo, WM_SETFONT, (WPARAM)hFont1, FALSE);
127 GetClientRect(hCombo, &r);
128 expect_rect(r, 0, 0, 100, 18);
129 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&r);
130 MapWindowPoints(HWND_DESKTOP, hMainWnd, (LPPOINT)&r, 2);
131 todo_wine expect_rect(r, 5, 5, 105, 105 - (font_height(GetStockObject(SYSTEM_FONT)) - font_height(hFont1)));
133 SendMessage(hCombo, WM_SETFONT, (WPARAM)hFont2, FALSE);
134 GetClientRect(hCombo, &r);
135 expect_rect(r, 0, 0, 100, 16);
136 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&r);
137 MapWindowPoints(HWND_DESKTOP, hMainWnd, (LPPOINT)&r, 2);
138 todo_wine expect_rect(r, 5, 5, 105, 105 - (font_height(GetStockObject(SYSTEM_FONT)) - font_height(hFont2)));
140 SendMessage(hCombo, WM_SETFONT, (WPARAM)hFont1, FALSE);
141 GetClientRect(hCombo, &r);
142 expect_rect(r, 0, 0, 100, 18);
143 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&r);
144 MapWindowPoints(HWND_DESKTOP, hMainWnd, (LPPOINT)&r, 2);
145 todo_wine expect_rect(r, 5, 5, 105, 105 - (font_height(GetStockObject(SYSTEM_FONT)) - font_height(hFont1)));
149 ok(0, "Expected Marlett font heights 10/8, got %d/%d\n",
150 font_height(hFont1), font_height(hFont2));
153 for (i = 1; i < 30; i++)
155 HFONT hFont = CreateFont(i, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, SYMBOL_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, "Marlett");
156 int height = font_height(hFont);
158 SendMessage(hCombo, WM_SETFONT, (WPARAM)hFont, FALSE);
159 GetClientRect(hCombo, &r);
160 expect_eq(r.bottom - r.top, height + 8, int, "%d");
161 SendMessage(hCombo, WM_SETFONT, 0, FALSE);
165 DestroyWindow(hCombo);
166 DeleteObject(hFont1);
167 DeleteObject(hFont2);
170 static LRESULT (CALLBACK *old_parent_proc)(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
171 static LPCSTR expected_edit_text;
172 static LPCSTR expected_list_text;
173 static BOOL selchange_fired;
175 static LRESULT CALLBACK parent_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
182 case MAKEWPARAM(COMBO_ID, CBN_SELCHANGE):
184 HWND hCombo = (HWND)lparam;
186 char list[20], edit[20];
188 memset(list, 0, sizeof(list));
189 memset(edit, 0, sizeof(edit));
191 idx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
192 SendMessage(hCombo, CB_GETLBTEXT, idx, (LPARAM)list);
193 SendMessage(hCombo, WM_GETTEXT, sizeof(edit), (LPARAM)edit);
195 ok(!strcmp(edit, expected_edit_text), "edit: got %s, expected %s\n",
196 edit, expected_edit_text);
197 ok(!strcmp(list, expected_list_text), "list: got %s, expected %s\n",
198 list, expected_list_text);
200 selchange_fired = TRUE;
207 return CallWindowProc(old_parent_proc, hwnd, msg, wparam, lparam);
210 static void test_selection(DWORD style, const char * const text[],
211 const int *edit, const int *list)
216 hCombo = build_combo(style);
218 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)text[0]);
219 SendMessage(hCombo, CB_ADDSTRING, 0, (LPARAM)text[1]);
220 SendMessage(hCombo, CB_SETCURSEL, -1, 0);
222 old_parent_proc = (void *)SetWindowLongPtr(hMainWnd, GWLP_WNDPROC, (ULONG_PTR)parent_wnd_proc);
224 idx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
225 ok(idx == -1, "expected selection -1, got %d\n", idx);
227 /* keyboard navigation */
229 expected_list_text = text[list[0]];
230 expected_edit_text = text[edit[0]];
231 selchange_fired = FALSE;
232 SendMessage(hCombo, WM_KEYDOWN, VK_DOWN, 0);
233 ok(selchange_fired, "CBN_SELCHANGE not sent!\n");
235 expected_list_text = text[list[1]];
236 expected_edit_text = text[edit[1]];
237 selchange_fired = FALSE;
238 SendMessage(hCombo, WM_KEYDOWN, VK_DOWN, 0);
239 ok(selchange_fired, "CBN_SELCHANGE not sent!\n");
241 expected_list_text = text[list[2]];
242 expected_edit_text = text[edit[2]];
243 selchange_fired = FALSE;
244 SendMessage(hCombo, WM_KEYDOWN, VK_UP, 0);
245 ok(selchange_fired, "CBN_SELCHANGE not sent!\n");
247 /* programmatic navigation */
249 expected_list_text = text[list[3]];
250 expected_edit_text = text[edit[3]];
251 selchange_fired = FALSE;
252 SendMessage(hCombo, CB_SETCURSEL, list[3], 0);
253 ok(!selchange_fired, "CBN_SELCHANGE sent!\n");
255 expected_list_text = text[list[4]];
256 expected_edit_text = text[edit[4]];
257 selchange_fired = FALSE;
258 SendMessage(hCombo, CB_SETCURSEL, list[4], 0);
259 ok(!selchange_fired, "CBN_SELCHANGE sent!\n");
261 SetWindowLongPtr(hMainWnd, GWLP_WNDPROC, (ULONG_PTR)old_parent_proc);
262 DestroyWindow(hCombo);
265 static void test_CBN_SELCHANGE(void)
267 static const char * const text[] = { "alpha", "beta", "" };
268 static const int sel_1[] = { 2, 0, 1, 0, 1 };
269 static const int sel_2[] = { 0, 1, 0, 0, 1 };
271 test_selection(CBS_SIMPLE, text, sel_1, sel_2);
272 test_selection(CBS_DROPDOWN, text, sel_1, sel_2);
273 test_selection(CBS_DROPDOWNLIST, text, sel_2, sel_2);
276 static void test_WM_LBUTTONDOWN(void)
278 HWND hCombo, hEdit, hList;
280 UINT x, y, item_height;
285 static const UINT choices[] = {8,9,10,11,12,14,16,18,20,22,24,26,28,36,48,72};
286 static const CHAR stringFormat[] = "%2d";
288 BOOL (WINAPI *pGetComboBoxInfo)(HWND, PCOMBOBOXINFO);
290 pGetComboBoxInfo = (void*)GetProcAddress(GetModuleHandleA("user32.dll"), "GetComboBoxInfo");
291 if (!pGetComboBoxInfo){
292 win_skip("GetComboBoxInfo is not available\n");
296 hCombo = CreateWindow("ComboBox", "Combo", WS_VISIBLE|WS_CHILD|CBS_DROPDOWN,
297 0, 0, 200, 150, hMainWnd, (HMENU)COMBO_ID, NULL, 0);
299 for (i = 0; i < sizeof(choices)/sizeof(UINT); i++){
300 sprintf(buffer, stringFormat, choices[i]);
301 result = SendMessageA(hCombo, CB_ADDSTRING, 0, (LPARAM)buffer);
303 "Failed to add item %d\n", i);
306 cbInfo.cbSize = sizeof(COMBOBOXINFO);
307 SetLastError(0xdeadbeef);
308 ret = pGetComboBoxInfo(hCombo, &cbInfo);
309 ok(ret, "Failed to get combobox info structure. LastError=%d\n",
311 hEdit = cbInfo.hwndItem;
312 hList = cbInfo.hwndList;
314 trace("hMainWnd=%p, hCombo=%p, hList=%p, hEdit=%p\n", hMainWnd, hCombo, hList, hEdit);
315 ok(GetFocus() == hMainWnd, "Focus not on Main Window, instead on %p\n", GetFocus());
317 /* Click on the button to drop down the list */
318 x = cbInfo.rcButton.left + (cbInfo.rcButton.right-cbInfo.rcButton.left)/2;
319 y = cbInfo.rcButton.top + (cbInfo.rcButton.bottom-cbInfo.rcButton.top)/2;
320 result = SendMessage(hCombo, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
321 ok(result, "WM_LBUTTONDOWN was not processed. LastError=%d\n",
323 ok(SendMessage(hCombo, CB_GETDROPPEDSTATE, 0, 0),
324 "The dropdown list should have appeared after clicking the button.\n");
326 ok(GetFocus() == hEdit,
327 "Focus not on ComboBox's Edit Control, instead on %p\n", GetFocus());
328 result = SendMessage(hCombo, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
329 ok(result, "WM_LBUTTONUP was not processed. LastError=%d\n",
331 ok(GetFocus() == hEdit,
332 "Focus not on ComboBox's Edit Control, instead on %p\n", GetFocus());
334 /* Click on the 5th item in the list */
335 item_height = SendMessage(hCombo, CB_GETITEMHEIGHT, 0, 0);
336 ok(GetClientRect(hList, &rect), "Failed to get list's client rect.\n");
337 x = rect.left + (rect.right-rect.left)/2;
338 y = item_height/2 + item_height*4;
339 result = SendMessage(hList, WM_LBUTTONDOWN, 0, MAKELPARAM(x, y));
340 ok(!result, "WM_LBUTTONDOWN was not processed. LastError=%d\n",
342 ok(GetFocus() == hEdit,
343 "Focus not on ComboBox's Edit Control, instead on %p\n", GetFocus());
345 result = SendMessage(hList, WM_MOUSEMOVE, 0, MAKELPARAM(x, y));
346 ok(!result, "WM_MOUSEMOVE was not processed. LastError=%d\n",
348 ok(GetFocus() == hEdit,
349 "Focus not on ComboBox's Edit Control, instead on %p\n", GetFocus());
350 ok(SendMessage(hCombo, CB_GETDROPPEDSTATE, 0, 0),
351 "The dropdown list should still be visible.\n");
353 result = SendMessage(hList, WM_LBUTTONUP, 0, MAKELPARAM(x, y));
354 ok(!result, "WM_LBUTTONUP was not processed. LastError=%d\n",
356 ok(GetFocus() == hEdit,
357 "Focus not on ComboBox's Edit Control, instead on %p\n", GetFocus());
358 ok(!SendMessage(hCombo, CB_GETDROPPEDSTATE, 0, 0),
359 "The dropdown list should have been rolled up.\n");
360 idx = SendMessage(hCombo, CB_GETCURSEL, 0, 0);
361 ok(idx, "Current Selection: expected %d, got %d\n", 4, idx);
363 DestroyWindow(hCombo);
366 static void test_changesize( DWORD style)
368 HWND hCombo = build_combo(style);
370 INT ddheight, clheight, ddwidth, clwidth;
371 /* get initial measurements */
372 GetClientRect( hCombo, &rc);
373 clheight = rc.bottom - rc.top;
374 clwidth = rc.right - rc.left;
375 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rc);
376 ddheight = rc.bottom - rc.top;
377 ddwidth = rc.right - rc.left;
378 /* use MoveWindow to move & resize the combo */
379 /* first make it slightly smaller */
380 MoveWindow( hCombo, 10, 10, clwidth - 2, clheight - 2, TRUE);
381 GetClientRect( hCombo, &rc);
382 ok( rc.right - rc.left == clwidth - 2, "clientrect witdh is %d vs %d\n",
383 rc.right - rc.left, clwidth - 2);
384 ok( rc.bottom - rc.top == clheight, "clientrect height is %d vs %d\n",
385 rc.bottom - rc.top, clheight);
386 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rc);
387 ok( rc.right - rc.left == clwidth - 2, "drop-down rect witdh is %d vs %d\n",
388 rc.right - rc.left, clwidth - 2);
389 ok( rc.bottom - rc.top == ddheight, "drop-down rect height is %d vs %d\n",
390 rc.bottom - rc.top, ddheight);
391 ok( rc.right - rc.left == ddwidth -2, "drop-down rect width is %d vs %d\n",
392 rc.right - rc.left, ddwidth - 2);
393 /* new cx, cy is slightly bigger than the initial values */
394 MoveWindow( hCombo, 10, 10, clwidth + 2, clheight + 2, TRUE);
395 GetClientRect( hCombo, &rc);
396 ok( rc.right - rc.left == clwidth + 2, "clientrect witdh is %d vs %d\n",
397 rc.right - rc.left, clwidth + 2);
398 ok( rc.bottom - rc.top == clheight, "clientrect height is %d vs %d\n",
399 rc.bottom - rc.top, clheight);
400 SendMessageA(hCombo, CB_GETDROPPEDCONTROLRECT, 0, (LPARAM)&rc);
401 ok( rc.right - rc.left == clwidth + 2, "drop-down rect witdh is %d vs %d\n",
402 rc.right - rc.left, clwidth + 2);
404 ok( rc.bottom - rc.top == clheight + 2, "drop-down rect height is %d vs %d\n",
405 rc.bottom - rc.top, clheight + 2);
407 DestroyWindow(hCombo);
410 static void test_editselection(void)
418 BOOL (WINAPI *pGetComboBoxInfo)(HWND, PCOMBOBOXINFO);
421 pGetComboBoxInfo = (void*)GetProcAddress(GetModuleHandleA("user32.dll"), "GetComboBoxInfo");
422 if (!pGetComboBoxInfo){
423 win_skip("GetComboBoxInfo is not available\n");
428 hCombo = build_combo(CBS_SIMPLE);
429 cbInfo.cbSize = sizeof(COMBOBOXINFO);
430 SetLastError(0xdeadbeef);
431 ret = pGetComboBoxInfo(hCombo, &cbInfo);
432 ok(ret, "Failed to get combobox info structure. LastError=%d\n",
434 hEdit = cbInfo.hwndItem;
436 /* Initially combo selection is empty*/
437 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
438 ok(LOWORD(len)==0, "Unexpected start position for selection %d\n", LOWORD(len));
439 ok(HIWORD(len)==0, "Unexpected end position for selection %d\n", HIWORD(len));
441 /* Set some text, and press a key to replace it */
443 SendMessage(hCombo, WM_SETTEXT, 0, (LPARAM)"Jason1");
444 SendMessage(hCombo, WM_GETTEXT, sizeof(edit), (LPARAM)edit);
445 ok(strcmp(edit, "Jason1")==0, "Unexpected text retrieved %s\n", edit);
447 /* Now what is the selection - still empty */
448 SendMessage(hCombo, CB_GETEDITSEL, (WPARAM)&start, (WPARAM)&end);
449 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
450 ok(LOWORD(len)==0, "Unexpected start position for selection %d\n", LOWORD(len));
451 ok(HIWORD(len)==0, "Unexpected end position for selection %d\n", HIWORD(len));
453 /* Give it focus, and it gets selected */
454 SendMessage(hCombo, WM_SETFOCUS, 0, (LPARAM)hEdit);
455 SendMessage(hCombo, CB_GETEDITSEL, (WPARAM)&start, (WPARAM)&end);
456 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
457 ok(LOWORD(len)==0, "Unexpected start position for selection %d\n", LOWORD(len));
458 ok(HIWORD(len)==6, "Unexpected end position for selection %d\n", HIWORD(len));
460 /* Now emulate a key press */
462 SendMessage(hCombo, WM_CHAR, 'A', 0x1c0001);
463 SendMessage(hCombo, WM_GETTEXT, sizeof(edit), (LPARAM)edit);
464 ok(strcmp(edit, "A")==0, "Unexpected text retrieved %s\n", edit);
466 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
467 ok(LOWORD(len)==1, "Unexpected start position for selection %d\n", LOWORD(len));
468 ok(HIWORD(len)==1, "Unexpected end position for selection %d\n", HIWORD(len));
470 /* Now what happens when it gets more focus a second time - it doesn't reselect */
471 SendMessage(hCombo, WM_SETFOCUS, 0, (LPARAM)hEdit);
472 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
473 ok(LOWORD(len)==1, "Unexpected start position for selection %d\n", LOWORD(len));
474 ok(HIWORD(len)==1, "Unexpected end position for selection %d\n", HIWORD(len));
475 DestroyWindow(hCombo);
477 /* Start again - Build a combo */
478 hCombo = build_combo(CBS_SIMPLE);
479 cbInfo.cbSize = sizeof(COMBOBOXINFO);
480 SetLastError(0xdeadbeef);
481 ret = pGetComboBoxInfo(hCombo, &cbInfo);
482 ok(ret, "Failed to get combobox info structure. LastError=%d\n",
484 hEdit = cbInfo.hwndItem;
486 /* Set some text and give focus so it gets selected */
488 SendMessage(hCombo, WM_SETTEXT, 0, (LPARAM)"Jason2");
489 SendMessage(hCombo, WM_GETTEXT, sizeof(edit), (LPARAM)edit);
490 ok(strcmp(edit, "Jason2")==0, "Unexpected text retrieved %s\n", edit);
492 SendMessage(hCombo, WM_SETFOCUS, 0, (LPARAM)hEdit);
494 /* Now what is the selection */
495 SendMessage(hCombo, CB_GETEDITSEL, (WPARAM)&start, (WPARAM)&end);
496 len = SendMessage(hCombo, CB_GETEDITSEL, 0,0);
497 ok(LOWORD(len)==0, "Unexpected start position for selection %d\n", LOWORD(len));
498 ok(HIWORD(len)==6, "Unexpected end position for selection %d\n", HIWORD(len));
500 /* Now change the selection to the apparently invalid start -1, end -1 and
501 show it means no selection (ie start -1) but cursor at end */
502 SendMessage(hCombo, CB_SETEDITSEL, 0, -1);
504 SendMessage(hCombo, WM_CHAR, 'A', 0x1c0001);
505 SendMessage(hCombo, WM_GETTEXT, sizeof(edit), (LPARAM)edit);
506 ok(strcmp(edit, "Jason2A")==0, "Unexpected text retrieved %s\n", edit);
507 DestroyWindow(hCombo);
512 hMainWnd = CreateWindow("static", "Test", WS_OVERLAPPEDWINDOW, 10, 10, 300, 300, NULL, NULL, NULL, 0);
513 ShowWindow(hMainWnd, SW_SHOW);
515 test_setfont(CBS_DROPDOWN);
516 test_setfont(CBS_DROPDOWNLIST);
517 test_setitemheight(CBS_DROPDOWN);
518 test_setitemheight(CBS_DROPDOWNLIST);
519 test_CBN_SELCHANGE();
520 test_WM_LBUTTONDOWN();
521 test_changesize(CBS_DROPDOWN);
522 test_changesize(CBS_DROPDOWNLIST);
523 test_editselection();
525 DestroyWindow(hMainWnd);