user32: Add wsprintfW %C tests.
[wine.git] / dlls / user32 / combo.c
blobff5ed18878bac8363049c27b0d4b464b800268a1
1 /*
2 * Combo controls
4 * Copyright 1997 Alex Korobka
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.
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.
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
20 * TODO:
21 * - CB_SETTOPINDEX
24 #include <stdarg.h>
25 #include <string.h>
27 #define OEMRESOURCE
29 #include "windef.h"
30 #include "winbase.h"
31 #include "wingdi.h"
32 #include "winuser.h"
33 #include "wine/unicode.h"
34 #include "user_private.h"
35 #include "win.h"
36 #include "controls.h"
37 #include "winternl.h"
38 #include "wine/debug.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(combo);
42 /* bits in the dwKeyData */
43 #define KEYDATA_ALT 0x2000
44 #define KEYDATA_PREVSTATE 0x4000
47 * Additional combo box definitions
50 #define CB_NOTIFY( lphc, code ) \
51 (SendMessageW((lphc)->owner, WM_COMMAND, \
52 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
54 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
55 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
56 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
57 #define CB_HWND( lphc ) ((lphc)->self)
58 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
60 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
63 * Drawing globals
65 static HBITMAP hComboBmp = 0;
66 static UINT CBitHeight, CBitWidth;
69 * Look and feel dependent "constants"
72 #define COMBO_YBORDERGAP 5
73 #define COMBO_XBORDERSIZE() 2
74 #define COMBO_YBORDERSIZE() 2
75 #define COMBO_EDITBUTTONSPACE() 0
76 #define EDIT_CONTROL_PADDING() 1
78 static void CBCalcPlacement(HEADCOMBO *combo);
79 static void CBResetPos(HEADCOMBO *combo, BOOL redraw);
81 /*********************************************************************
82 * combo class descriptor
84 static const WCHAR comboboxW[] = {'C','o','m','b','o','B','o','x',0};
85 const struct builtin_class_descr COMBO_builtin_class =
87 comboboxW, /* name */
88 CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, /* style */
89 WINPROC_COMBO, /* proc */
90 sizeof(HEADCOMBO *), /* extra */
91 IDC_ARROW, /* cursor */
92 0 /* brush */
96 /***********************************************************************
97 * COMBO_Init
99 * Load combo button bitmap.
101 static BOOL COMBO_Init(void)
103 HDC hDC;
105 if( hComboBmp ) return TRUE;
106 if( (hDC = CreateCompatibleDC(0)) )
108 BOOL bRet = FALSE;
109 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
111 BITMAP bm;
112 HBITMAP hPrevB;
113 RECT r;
115 GetObjectW( hComboBmp, sizeof(bm), &bm );
116 CBitHeight = bm.bmHeight;
117 CBitWidth = bm.bmWidth;
119 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
121 hPrevB = SelectObject( hDC, hComboBmp);
122 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
123 InvertRect( hDC, &r );
124 SelectObject( hDC, hPrevB );
125 bRet = TRUE;
127 DeleteDC( hDC );
128 return bRet;
130 return FALSE;
133 /***********************************************************************
134 * COMBO_NCCreate
136 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style)
138 LPHEADCOMBO lphc;
140 if (COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEADCOMBO))) )
142 lphc->self = hwnd;
143 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)lphc );
145 /* some braindead apps do try to use scrollbar/border flags */
147 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
148 SetWindowLongW( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) );
151 * We also have to remove the client edge style to make sure
152 * we don't end-up with a non client area.
154 SetWindowLongW( hwnd, GWL_EXSTYLE,
155 GetWindowLongW( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
157 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
158 lphc->dwStyle |= CBS_HASSTRINGS;
159 if( !(GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
160 lphc->wState |= CBF_NOTIFY;
162 TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle );
163 return TRUE;
165 return FALSE;
168 /***********************************************************************
169 * COMBO_NCDestroy
171 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
174 if( lphc )
176 TRACE("[%p]: freeing storage\n", lphc->self);
178 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
179 DestroyWindow( lphc->hWndLBox );
181 SetWindowLongPtrW( lphc->self, 0, 0 );
182 HeapFree( GetProcessHeap(), 0, lphc );
184 return 0;
187 static INT combo_get_text_height(const HEADCOMBO *combo)
189 HDC hdc = GetDC(combo->self);
190 HFONT prev_font = 0;
191 TEXTMETRICW tm;
193 if (combo->hFont)
194 prev_font = SelectObject(hdc, combo->hFont);
196 GetTextMetricsW(hdc, &tm);
198 if (prev_font)
199 SelectObject(hdc, prev_font);
201 ReleaseDC(combo->self, hdc);
203 return tm.tmHeight + 4;
206 /***********************************************************************
207 * CBGetTextAreaHeight
209 * This method will calculate the height of the text area of the
210 * combobox.
211 * The height of the text area is set in two ways.
212 * It can be set explicitly through a combobox message or through a
213 * WM_MEASUREITEM callback.
214 * If this is not the case, the height is set to font height + 4px
215 * This height was determined through experimentation.
216 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
218 static INT CBGetTextAreaHeight(HEADCOMBO *lphc, BOOL clip_item_height)
220 INT item_height, text_height;
222 if (clip_item_height && !CB_OWNERDRAWN(lphc))
224 text_height = combo_get_text_height(lphc);
225 if (lphc->item_height < text_height)
226 lphc->item_height = text_height;
229 item_height = lphc->item_height;
232 * Check the ownerdraw case if we haven't asked the parent the size
233 * of the item yet.
235 if ( CB_OWNERDRAWN(lphc) &&
236 (lphc->wState & CBF_MEASUREITEM) )
238 MEASUREITEMSTRUCT measureItem;
239 RECT clientRect;
240 INT originalItemHeight = item_height;
241 UINT id = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
244 * We use the client rect for the width of the item.
246 GetClientRect(lphc->self, &clientRect);
248 lphc->wState &= ~CBF_MEASUREITEM;
251 * Send a first one to measure the size of the text area
253 measureItem.CtlType = ODT_COMBOBOX;
254 measureItem.CtlID = id;
255 measureItem.itemID = -1;
256 measureItem.itemWidth = clientRect.right;
257 measureItem.itemHeight = item_height - 6; /* ownerdrawn cb is taller */
258 measureItem.itemData = 0;
259 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
260 item_height = 6 + measureItem.itemHeight;
263 * Send a second one in the case of a fixed ownerdraw list to calculate the
264 * size of the list items. (we basically do this on behalf of the listbox)
266 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
268 measureItem.CtlType = ODT_COMBOBOX;
269 measureItem.CtlID = id;
270 measureItem.itemID = 0;
271 measureItem.itemWidth = clientRect.right;
272 measureItem.itemHeight = originalItemHeight;
273 measureItem.itemData = 0;
274 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
275 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
279 * Keep the size for the next time
281 lphc->item_height = item_height;
284 return item_height;
287 /***********************************************************************
288 * CBForceDummyResize
290 * The dummy resize is used for listboxes that have a popup to trigger
291 * a re-arranging of the contents of the combobox and the recalculation
292 * of the size of the "real" control window.
294 static void CBForceDummyResize(LPHEADCOMBO lphc)
296 RECT windowRect;
297 int newComboHeight;
299 newComboHeight = CBGetTextAreaHeight(lphc, FALSE) + 2*COMBO_YBORDERSIZE();
301 GetWindowRect(lphc->self, &windowRect);
304 * We have to be careful, resizing a combobox also has the meaning that the
305 * dropped rect will be resized. In this case, we want to trigger a resize
306 * to recalculate layout but we don't want to change the dropped rectangle
307 * So, we pass the height of text area of control as the height.
308 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
309 * message.
311 lphc->wState |= CBF_NORESIZE;
312 SetWindowPos( lphc->self,
313 NULL,
314 0, 0,
315 windowRect.right - windowRect.left,
316 newComboHeight,
317 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
318 lphc->wState &= ~CBF_NORESIZE;
320 CBCalcPlacement(lphc);
321 CBResetPos(lphc, FALSE);
324 /***********************************************************************
325 * CBCalcPlacement
327 * Set up component coordinates given valid lphc->RectCombo.
329 static void CBCalcPlacement(HEADCOMBO *combo)
331 /* Start with the client rectangle. */
332 GetClientRect(combo->self, &combo->textRect);
334 /* Remove the borders */
335 InflateRect(&combo->textRect, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
337 /* Chop off the bottom part to fit with the height of the text area. */
338 combo->textRect.bottom = combo->textRect.top + CBGetTextAreaHeight(combo, FALSE);
340 /* The button starts the same vertical position as the text area. */
341 combo->buttonRect = combo->textRect;
343 /* If the combobox is "simple" there is no button. */
344 if (CB_GETTYPE(combo) == CBS_SIMPLE)
345 combo->buttonRect.left = combo->buttonRect.right = combo->buttonRect.bottom = 0;
346 else
349 * Let's assume the combobox button is the same width as the
350 * scrollbar button.
351 * size the button horizontally and cut-off the text area.
353 combo->buttonRect.left = combo->buttonRect.right - GetSystemMetrics(SM_CXVSCROLL);
354 combo->textRect.right = combo->buttonRect.left;
357 /* In the case of a dropdown, there is an additional spacing between the text area and the button. */
358 if (CB_GETTYPE(combo) == CBS_DROPDOWN)
359 combo->textRect.right -= COMBO_EDITBUTTONSPACE();
361 /* If we have an edit control, we space it away from the borders slightly. */
362 if (CB_GETTYPE(combo) != CBS_DROPDOWNLIST)
363 InflateRect(&combo->textRect, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
365 /* Adjust the size of the listbox popup. */
366 if (CB_GETTYPE(combo) == CBS_SIMPLE)
368 GetClientRect(combo->self, &combo->droppedRect);
369 combo->droppedRect.top = combo->textRect.bottom + COMBO_YBORDERSIZE();
371 else
373 /* Make sure the dropped width is as large as the combobox itself. */
374 if (combo->droppedWidth < (combo->buttonRect.right + COMBO_XBORDERSIZE()))
376 combo->droppedRect.right = combo->droppedRect.left + (combo->buttonRect.right + COMBO_XBORDERSIZE());
378 /* In the case of a dropdown, the popup listbox is offset to the right. We want to make sure it's flush
379 with the right side of the combobox. */
380 if (CB_GETTYPE(combo) == CBS_DROPDOWN)
381 combo->droppedRect.right -= COMBO_EDITBUTTONSPACE();
383 else
384 combo->droppedRect.right = combo->droppedRect.left + combo->droppedWidth;
387 /* Disallow negative window width */
388 if (combo->textRect.right < combo->textRect.left)
389 combo->textRect.right = combo->textRect.left;
391 TRACE("text %s, button %s, lbox %s.\n", wine_dbgstr_rect(&combo->textRect), wine_dbgstr_rect(&combo->buttonRect),
392 wine_dbgstr_rect(&combo->droppedRect));
395 /***********************************************************************
396 * CBGetDroppedControlRect
398 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
400 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
401 of the combo box and the lower right corner of the listbox */
403 GetWindowRect(lphc->self, lpRect);
405 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
406 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
410 /***********************************************************************
411 * COMBO_Create
413 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style,
414 BOOL unicode )
416 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
417 static const WCHAR editName[] = {'E','d','i','t',0};
419 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
420 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
422 lphc->owner = hwndParent;
424 lphc->droppedWidth = 0;
426 lphc->item_height = combo_get_text_height(lphc);
429 * The first time we go through, we want to measure the ownerdraw item
431 lphc->wState |= CBF_MEASUREITEM;
433 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
435 if( lphc->owner || !(style & WS_VISIBLE) )
437 UINT lbeStyle = 0;
438 UINT lbeExStyle = 0;
441 * Initialize the dropped rect to the size of the client area of the
442 * control and then, force all the areas of the combobox to be
443 * recalculated.
445 GetClientRect( hwnd, &lphc->droppedRect );
446 CBCalcPlacement(lphc);
449 * Adjust the position of the popup listbox if it's necessary
451 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
453 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
456 * If it's a dropdown, the listbox is offset
458 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
459 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
461 if (lphc->droppedRect.bottom < lphc->droppedRect.top)
462 lphc->droppedRect.bottom = lphc->droppedRect.top;
463 if (lphc->droppedRect.right < lphc->droppedRect.left)
464 lphc->droppedRect.right = lphc->droppedRect.left;
465 MapWindowPoints( hwnd, 0, (LPPOINT)&lphc->droppedRect, 2 );
468 /* create listbox popup */
470 lbeStyle = (LBS_NOTIFY | LBS_COMBOBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
471 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
473 if( lphc->dwStyle & CBS_SORT )
474 lbeStyle |= LBS_SORT;
475 if( lphc->dwStyle & CBS_HASSTRINGS )
476 lbeStyle |= LBS_HASSTRINGS;
477 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
478 lbeStyle |= LBS_NOINTEGRALHEIGHT;
479 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
480 lbeStyle |= LBS_DISABLENOSCROLL;
482 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
484 lbeStyle |= WS_VISIBLE;
487 * In win 95 look n feel, the listbox in the simple combobox has
488 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
490 lbeStyle &= ~WS_BORDER;
491 lbeExStyle |= WS_EX_CLIENTEDGE;
493 else
495 lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
498 if (unicode)
499 lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
500 lphc->droppedRect.left,
501 lphc->droppedRect.top,
502 lphc->droppedRect.right - lphc->droppedRect.left,
503 lphc->droppedRect.bottom - lphc->droppedRect.top,
504 hwnd, (HMENU)ID_CB_LISTBOX,
505 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
506 else
507 lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle,
508 lphc->droppedRect.left,
509 lphc->droppedRect.top,
510 lphc->droppedRect.right - lphc->droppedRect.left,
511 lphc->droppedRect.bottom - lphc->droppedRect.top,
512 hwnd, (HMENU)ID_CB_LISTBOX,
513 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
515 if( lphc->hWndLBox )
517 BOOL bEdit = TRUE;
518 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
520 if( lphc->wState & CBF_EDIT )
522 if( lphc->dwStyle & CBS_OEMCONVERT )
523 lbeStyle |= ES_OEMCONVERT;
524 if( lphc->dwStyle & CBS_AUTOHSCROLL )
525 lbeStyle |= ES_AUTOHSCROLL;
526 if( lphc->dwStyle & CBS_LOWERCASE )
527 lbeStyle |= ES_LOWERCASE;
528 else if( lphc->dwStyle & CBS_UPPERCASE )
529 lbeStyle |= ES_UPPERCASE;
531 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
533 if (unicode)
534 lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
535 lphc->textRect.left, lphc->textRect.top,
536 lphc->textRect.right - lphc->textRect.left,
537 lphc->textRect.bottom - lphc->textRect.top,
538 hwnd, (HMENU)ID_CB_EDIT,
539 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
540 else
541 lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle,
542 lphc->textRect.left, lphc->textRect.top,
543 lphc->textRect.right - lphc->textRect.left,
544 lphc->textRect.bottom - lphc->textRect.top,
545 hwnd, (HMENU)ID_CB_EDIT,
546 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
548 if( !lphc->hWndEdit )
549 bEdit = FALSE;
552 if( bEdit )
554 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
556 /* Now do the trick with parent */
557 SetParent(lphc->hWndLBox, HWND_DESKTOP);
559 * If the combo is a dropdown, we must resize the control
560 * to fit only the text area and button. To do this,
561 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
562 * will take care of setting the height for us.
564 CBForceDummyResize(lphc);
567 TRACE("init done\n");
568 return 0;
570 ERR("edit control failure.\n");
571 } else ERR("listbox failure.\n");
572 } else ERR("no owner for visible combo.\n");
574 /* CreateWindow() will send WM_NCDESTROY to cleanup */
576 return -1;
579 /***********************************************************************
580 * CBPaintButton
582 * Paint combo button (normal, pressed, and disabled states).
584 static void CBPaintButton(HEADCOMBO *lphc, HDC hdc)
586 UINT buttonState = DFCS_SCROLLCOMBOBOX;
588 if (IsRectEmpty(&lphc->buttonRect))
589 return;
591 if( lphc->wState & CBF_NOREDRAW )
592 return;
595 if (lphc->wState & CBF_BUTTONDOWN)
596 buttonState |= DFCS_PUSHED;
598 if (CB_DISABLED(lphc))
599 buttonState |= DFCS_INACTIVE;
601 DrawFrameControl(hdc, &lphc->buttonRect, DFC_SCROLL, buttonState);
604 /***********************************************************************
605 * COMBO_PrepareColors
607 * This method will sent the appropriate WM_CTLCOLOR message to
608 * prepare and setup the colors for the combo's DC.
610 * It also returns the brush to use for the background.
612 static HBRUSH COMBO_PrepareColors(
613 LPHEADCOMBO lphc,
614 HDC hDC)
616 HBRUSH hBkgBrush;
619 * Get the background brush for this control.
621 if (CB_DISABLED(lphc))
623 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
624 (WPARAM)hDC, (LPARAM)lphc->self );
627 * We have to change the text color since WM_CTLCOLORSTATIC will
628 * set it to the "enabled" color. This is the same behavior as the
629 * edit control
631 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
633 else
635 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
636 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
637 (WPARAM)hDC, (LPARAM)lphc->self );
641 * Catch errors.
643 if( !hBkgBrush )
644 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
646 return hBkgBrush;
649 /***********************************************************************
650 * CBPaintText
652 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
654 static void CBPaintText(
655 LPHEADCOMBO lphc,
656 HDC hdc_paint)
658 RECT rectEdit = lphc->textRect;
659 INT id, size = 0;
660 LPWSTR pText = NULL;
662 TRACE("\n");
664 /* follow Windows combobox that sends a bunch of text
665 * inquiries to its listbox while processing WM_PAINT. */
667 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
669 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
670 if (size == LB_ERR)
671 FIXME("LB_ERR probably not handled yet\n");
672 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
674 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
675 size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, id, (LPARAM)pText);
676 pText[size] = '\0'; /* just in case */
677 } else return;
680 if( lphc->wState & CBF_EDIT )
682 static const WCHAR empty_stringW[] = { 0 };
683 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
684 if( lphc->wState & CBF_FOCUSED )
685 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, MAXLONG);
687 else if(!(lphc->wState & CBF_NOREDRAW) && IsWindowVisible( lphc->self ))
689 /* paint text field ourselves */
690 HDC hdc = hdc_paint ? hdc_paint : GetDC(lphc->self);
691 UINT itemState = ODS_COMBOBOXEDIT;
692 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
693 HBRUSH hPrevBrush, hBkgBrush;
696 * Give ourselves some space.
698 InflateRect( &rectEdit, -1, -1 );
700 hBkgBrush = COMBO_PrepareColors( lphc, hdc );
701 hPrevBrush = SelectObject( hdc, hBkgBrush );
702 FillRect( hdc, &rectEdit, hBkgBrush );
704 if( CB_OWNERDRAWN(lphc) )
706 DRAWITEMSTRUCT dis;
707 HRGN clipRegion;
708 UINT ctlid = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
710 /* setup state for DRAWITEM message. Owner will highlight */
711 if ( (lphc->wState & CBF_FOCUSED) &&
712 !(lphc->wState & CBF_DROPPED) )
713 itemState |= ODS_SELECTED | ODS_FOCUS;
715 if (!IsWindowEnabled(lphc->self)) itemState |= ODS_DISABLED;
717 dis.CtlType = ODT_COMBOBOX;
718 dis.CtlID = ctlid;
719 dis.hwndItem = lphc->self;
720 dis.itemAction = ODA_DRAWENTIRE;
721 dis.itemID = id;
722 dis.itemState = itemState;
723 dis.hDC = hdc;
724 dis.rcItem = rectEdit;
725 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, id, 0);
728 * Clip the DC and have the parent draw the item.
730 clipRegion = set_control_clipping( hdc, &rectEdit );
732 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
734 SelectClipRgn( hdc, clipRegion );
735 if (clipRegion) DeleteObject( clipRegion );
737 else
739 static const WCHAR empty_stringW[] = { 0 };
741 if ( (lphc->wState & CBF_FOCUSED) &&
742 !(lphc->wState & CBF_DROPPED) ) {
744 /* highlight */
745 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
746 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
747 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
750 ExtTextOutW( hdc,
751 rectEdit.left + 1,
752 rectEdit.top + 1,
753 ETO_OPAQUE | ETO_CLIPPED,
754 &rectEdit,
755 pText ? pText : empty_stringW , size, NULL );
757 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
758 DrawFocusRect( hdc, &rectEdit );
761 if( hPrevFont )
762 SelectObject(hdc, hPrevFont );
764 if( hPrevBrush )
765 SelectObject( hdc, hPrevBrush );
767 if( !hdc_paint )
768 ReleaseDC( lphc->self, hdc );
770 HeapFree( GetProcessHeap(), 0, pText );
773 /***********************************************************************
774 * CBPaintBorder
776 static void CBPaintBorder(const HEADCOMBO *lphc, HDC hdc)
778 RECT clientRect;
780 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
782 GetClientRect(lphc->self, &clientRect);
784 else
786 clientRect = lphc->textRect;
788 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
789 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
792 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
795 /***********************************************************************
796 * COMBO_Paint
798 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
800 PAINTSTRUCT ps;
801 HDC hDC;
803 hDC = (hParamDC) ? hParamDC
804 : BeginPaint( lphc->self, &ps);
806 TRACE("hdc=%p\n", hDC);
808 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
810 HBRUSH hPrevBrush, hBkgBrush;
813 * Retrieve the background brush and select it in the
814 * DC.
816 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
818 hPrevBrush = SelectObject( hDC, hBkgBrush );
819 if (!(lphc->wState & CBF_EDIT))
820 FillRect(hDC, &lphc->textRect, hBkgBrush);
823 * In non 3.1 look, there is a sunken border on the combobox
825 CBPaintBorder(lphc, hDC);
826 CBPaintButton(lphc, hDC);
828 /* paint the edit control padding area */
829 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
831 RECT rPadEdit = lphc->textRect;
833 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
835 FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
838 if( !(lphc->wState & CBF_EDIT) )
839 CBPaintText( lphc, hDC );
841 if( hPrevBrush )
842 SelectObject( hDC, hPrevBrush );
845 if( !hParamDC )
846 EndPaint(lphc->self, &ps);
848 return 0;
851 /***********************************************************************
852 * CBUpdateLBox
854 * Select listbox entry according to the contents of the edit control.
856 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
858 INT length, idx;
859 LPWSTR pText = NULL;
861 idx = LB_ERR;
862 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
864 if( length > 0 )
865 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
867 TRACE("\t edit text length %i\n", length );
869 if( pText )
871 GetWindowTextW( lphc->hWndEdit, pText, length + 1);
872 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, -1, (LPARAM)pText);
873 HeapFree( GetProcessHeap(), 0, pText );
876 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, bSelect ? idx : -1, 0);
878 /* probably superfluous but Windows sends this too */
879 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, idx < 0 ? 0 : idx, 0);
880 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, idx < 0 ? 0 : idx, 0);
882 return idx;
885 /***********************************************************************
886 * CBUpdateEdit
888 * Copy a listbox entry to the edit control.
890 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
892 INT length;
893 LPWSTR pText = NULL;
894 static const WCHAR empty_stringW[] = { 0 };
896 TRACE("\t %i\n", index );
898 if( index >= 0 ) /* got an entry */
900 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, index, 0);
901 if( length != LB_ERR)
903 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
905 SendMessageW(lphc->hWndLBox, LB_GETTEXT, index, (LPARAM)pText);
910 if( CB_HASSTRINGS(lphc) )
912 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
913 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
914 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
917 if( lphc->wState & CBF_FOCUSED )
918 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
920 HeapFree( GetProcessHeap(), 0, pText );
923 /***********************************************************************
924 * CBDropDown
926 * Show listbox popup.
928 static void CBDropDown( LPHEADCOMBO lphc )
930 HMONITOR monitor;
931 MONITORINFO mon_info;
932 RECT rect,r;
933 int nItems;
934 int nDroppedHeight;
936 TRACE("[%p]: drop down\n", lphc->self);
938 CB_NOTIFY( lphc, CBN_DROPDOWN );
940 /* set selection */
942 lphc->wState |= CBF_DROPPED;
943 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
945 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
947 /* Update edit only if item is in the list */
948 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
949 CBUpdateEdit( lphc, lphc->droppedIndex );
951 else
953 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
955 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
956 lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex, 0);
957 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
960 /* now set popup position */
961 GetWindowRect( lphc->self, &rect );
964 * If it's a dropdown, the listbox is offset
966 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
967 rect.left += COMBO_EDITBUTTONSPACE();
969 /* if the dropped height is greater than the total height of the dropped
970 items list, then force the drop down list height to be the total height
971 of the items in the dropped list */
973 /* And Remove any extra space (Best Fit) */
974 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
975 /* if listbox length has been set directly by its handle */
976 GetWindowRect(lphc->hWndLBox, &r);
977 if (nDroppedHeight < r.bottom - r.top)
978 nDroppedHeight = r.bottom - r.top;
979 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
981 if (nItems > 0)
983 int nHeight;
984 int nIHeight;
986 nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
988 nHeight = nIHeight*nItems;
990 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
991 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
994 r.left = rect.left;
995 r.top = rect.bottom;
996 r.right = r.left + lphc->droppedRect.right - lphc->droppedRect.left;
997 r.bottom = r.top + nDroppedHeight;
999 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1000 monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1001 mon_info.cbSize = sizeof(mon_info);
1002 GetMonitorInfoW( monitor, &mon_info );
1004 if (r.bottom > mon_info.rcWork.bottom)
1006 r.top = max( rect.top - nDroppedHeight, mon_info.rcWork.top );
1007 r.bottom = min( r.top + nDroppedHeight, mon_info.rcWork.bottom );
1010 SetWindowPos( lphc->hWndLBox, HWND_TOPMOST, r.left, r.top, r.right - r.left, r.bottom - r.top,
1011 SWP_NOACTIVATE | SWP_SHOWWINDOW );
1014 if( !(lphc->wState & CBF_NOREDRAW) )
1015 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1016 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1018 EnableWindow( lphc->hWndLBox, TRUE );
1019 if (GetCapture() != lphc->self)
1020 SetCapture(lphc->hWndLBox);
1023 /***********************************************************************
1024 * CBRollUp
1026 * Hide listbox popup.
1028 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1030 HWND hWnd = lphc->self;
1032 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1033 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1035 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1037 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1040 if( lphc->wState & CBF_DROPPED )
1042 RECT rect;
1044 lphc->wState &= ~CBF_DROPPED;
1045 ShowWindow( lphc->hWndLBox, SW_HIDE );
1047 if(GetCapture() == lphc->hWndLBox)
1049 ReleaseCapture();
1052 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1054 rect = lphc->buttonRect;
1056 else
1058 if( bButton )
1060 UnionRect( &rect,
1061 &lphc->buttonRect,
1062 &lphc->textRect);
1064 else
1065 rect = lphc->textRect;
1067 bButton = TRUE;
1070 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1071 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1072 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1073 CB_NOTIFY( lphc, CBN_CLOSEUP );
1078 /***********************************************************************
1079 * COMBO_FlipListbox
1081 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1083 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1085 if( lphc->wState & CBF_DROPPED )
1087 CBRollUp( lphc, ok, bRedrawButton );
1088 return FALSE;
1091 CBDropDown( lphc );
1092 return TRUE;
1095 /***********************************************************************
1096 * CBRepaintButton
1098 static void CBRepaintButton( LPHEADCOMBO lphc )
1100 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1101 UpdateWindow(lphc->self);
1104 /***********************************************************************
1105 * COMBO_SetFocus
1107 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1109 if( !(lphc->wState & CBF_FOCUSED) )
1111 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1112 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1114 /* This is wrong. Message sequences seem to indicate that this
1115 is set *after* the notify. */
1116 /* lphc->wState |= CBF_FOCUSED; */
1118 if( !(lphc->wState & CBF_EDIT) )
1119 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1121 CB_NOTIFY( lphc, CBN_SETFOCUS );
1122 lphc->wState |= CBF_FOCUSED;
1126 /***********************************************************************
1127 * COMBO_KillFocus
1129 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1131 HWND hWnd = lphc->self;
1133 if( lphc->wState & CBF_FOCUSED )
1135 CBRollUp( lphc, FALSE, TRUE );
1136 if( IsWindow( hWnd ) )
1138 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1139 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1141 lphc->wState &= ~CBF_FOCUSED;
1143 /* redraw text */
1144 if( !(lphc->wState & CBF_EDIT) )
1145 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1147 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1152 /***********************************************************************
1153 * COMBO_Command
1155 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1157 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1159 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1161 switch( HIWORD(wParam) >> 8 )
1163 case (EN_SETFOCUS >> 8):
1165 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1167 COMBO_SetFocus( lphc );
1168 break;
1170 case (EN_KILLFOCUS >> 8):
1172 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1174 /* NOTE: it seems that Windows' edit control sends an
1175 * undocumented message WM_USER + 0x1B instead of this
1176 * notification (only when it happens to be a part of
1177 * the combo). ?? - AK.
1180 COMBO_KillFocus( lphc );
1181 break;
1184 case (EN_CHANGE >> 8):
1186 * In some circumstances (when the selection of the combobox
1187 * is changed for example) we don't want the EN_CHANGE notification
1188 * to be forwarded to the parent of the combobox. This code
1189 * checks a flag that is set in these occasions and ignores the
1190 * notification.
1192 if (lphc->wState & CBF_NOLBSELECT)
1194 lphc->wState &= ~CBF_NOLBSELECT;
1196 else
1198 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1201 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1202 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1203 break;
1205 case (EN_UPDATE >> 8):
1206 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1207 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1208 break;
1210 case (EN_ERRSPACE >> 8):
1211 CB_NOTIFY( lphc, CBN_ERRSPACE );
1214 else if( lphc->hWndLBox == hWnd )
1216 switch( (short)HIWORD(wParam) )
1218 case LBN_ERRSPACE:
1219 CB_NOTIFY( lphc, CBN_ERRSPACE );
1220 break;
1222 case LBN_DBLCLK:
1223 CB_NOTIFY( lphc, CBN_DBLCLK );
1224 break;
1226 case LBN_SELCHANGE:
1227 case LBN_SELCANCEL:
1229 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1231 /* do not roll up if selection is being tracked
1232 * by arrow keys in the dropdown listbox */
1233 if (!(lphc->wState & CBF_NOROLLUP))
1235 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1237 else lphc->wState &= ~CBF_NOROLLUP;
1239 CB_NOTIFY( lphc, CBN_SELCHANGE );
1241 if( HIWORD(wParam) == LBN_SELCHANGE)
1243 if( lphc->wState & CBF_EDIT )
1244 lphc->wState |= CBF_NOLBSELECT;
1245 CBPaintText( lphc, NULL );
1247 break;
1249 case LBN_SETFOCUS:
1250 case LBN_KILLFOCUS:
1251 /* nothing to do here since ComboLBox always resets the focus to its
1252 * combo/edit counterpart */
1253 break;
1256 return 0;
1259 /***********************************************************************
1260 * COMBO_ItemOp
1262 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1264 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1266 HWND hWnd = lphc->self;
1267 UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1269 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1271 switch( msg )
1273 case WM_DELETEITEM:
1275 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1276 lpIS->CtlType = ODT_COMBOBOX;
1277 lpIS->CtlID = id;
1278 lpIS->hwndItem = hWnd;
1279 break;
1281 case WM_DRAWITEM:
1283 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1284 lpIS->CtlType = ODT_COMBOBOX;
1285 lpIS->CtlID = id;
1286 lpIS->hwndItem = hWnd;
1287 break;
1289 case WM_COMPAREITEM:
1291 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1292 lpIS->CtlType = ODT_COMBOBOX;
1293 lpIS->CtlID = id;
1294 lpIS->hwndItem = hWnd;
1295 break;
1297 case WM_MEASUREITEM:
1299 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1300 lpIS->CtlType = ODT_COMBOBOX;
1301 lpIS->CtlID = id;
1302 break;
1305 return SendMessageW(lphc->owner, msg, id, lParam);
1309 /***********************************************************************
1310 * COMBO_GetTextW
1312 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1314 INT length;
1316 if( lphc->wState & CBF_EDIT )
1317 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1319 /* get it from the listbox */
1321 if (!count || !buf) return 0;
1322 if( lphc->hWndLBox )
1324 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1325 if (idx == LB_ERR) goto error;
1326 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1327 if (length == LB_ERR) goto error;
1329 /* 'length' is without the terminating character */
1330 if (length >= count)
1332 LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1333 if (!lpBuffer) goto error;
1334 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1336 /* truncate if buffer is too short */
1337 if (length != LB_ERR)
1339 lstrcpynW( buf, lpBuffer, count );
1340 length = count;
1342 HeapFree( GetProcessHeap(), 0, lpBuffer );
1344 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1346 if (length == LB_ERR) return 0;
1347 return length;
1350 error: /* error - truncate string, return zero */
1351 buf[0] = 0;
1352 return 0;
1356 /***********************************************************************
1357 * COMBO_GetTextA
1359 * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1360 * also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1362 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1364 INT length;
1366 if( lphc->wState & CBF_EDIT )
1367 return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1369 /* get it from the listbox */
1371 if (!count || !buf) return 0;
1372 if( lphc->hWndLBox )
1374 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1375 if (idx == LB_ERR) goto error;
1376 length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1377 if (length == LB_ERR) goto error;
1379 /* 'length' is without the terminating character */
1380 if (length >= count)
1382 LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1383 if (!lpBuffer) goto error;
1384 length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1386 /* truncate if buffer is too short */
1387 if (length != LB_ERR)
1389 lstrcpynA( buf, lpBuffer, count );
1390 length = count;
1392 HeapFree( GetProcessHeap(), 0, lpBuffer );
1394 else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1396 if (length == LB_ERR) return 0;
1397 return length;
1400 error: /* error - truncate string, return zero */
1401 buf[0] = 0;
1402 return 0;
1406 /***********************************************************************
1407 * CBResetPos
1409 * This function sets window positions according to the updated
1410 * component placement struct.
1412 static void CBResetPos(HEADCOMBO *combo, BOOL redraw)
1414 BOOL drop = CB_GETTYPE(combo) != CBS_SIMPLE;
1416 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1417 * sizing messages */
1418 if (combo->wState & CBF_EDIT)
1419 SetWindowPos(combo->hWndEdit, 0, combo->textRect.left, combo->textRect.top,
1420 combo->textRect.right - combo->textRect.left,
1421 combo->textRect.bottom - combo->textRect.top,
1422 SWP_NOZORDER | SWP_NOACTIVATE | (drop ? SWP_NOREDRAW : 0));
1424 SetWindowPos(combo->hWndLBox, 0, combo->droppedRect.left, combo->droppedRect.top,
1425 combo->droppedRect.right - combo->droppedRect.left,
1426 combo->droppedRect.bottom - combo->droppedRect.top,
1427 SWP_NOACTIVATE | SWP_NOZORDER | (drop ? SWP_NOREDRAW : 0));
1429 if (drop)
1431 if (combo->wState & CBF_DROPPED)
1433 combo->wState &= ~CBF_DROPPED;
1434 ShowWindow(combo->hWndLBox, SW_HIDE);
1437 if (redraw && !(combo->wState & CBF_NOREDRAW))
1438 RedrawWindow(combo->self, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW);
1442 /***********************************************************************
1443 * COMBO_Size
1445 static void COMBO_Size( HEADCOMBO *lphc )
1447 if (!lphc->hWndLBox || (lphc->wState & CBF_NORESIZE))
1448 return;
1451 * Those controls are always the same height. So we have to make sure
1452 * they are not resized to another value.
1454 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1456 int newComboHeight, curComboHeight, curComboWidth;
1457 RECT rc;
1459 GetWindowRect(lphc->self, &rc);
1460 curComboHeight = rc.bottom - rc.top;
1461 curComboWidth = rc.right - rc.left;
1462 newComboHeight = CBGetTextAreaHeight(lphc, TRUE) + 2*COMBO_YBORDERSIZE();
1465 * Resizing a combobox has another side effect, it resizes the dropped
1466 * rectangle as well. However, it does it only if the new height for the
1467 * combobox is more than the height it should have. In other words,
1468 * if the application resizing the combobox only had the intention to resize
1469 * the actual control, for example, to do the layout of a dialog that is
1470 * resized, the height of the dropdown is not changed.
1472 if( curComboHeight > newComboHeight )
1474 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1475 curComboHeight, newComboHeight, lphc->droppedRect.bottom,
1476 lphc->droppedRect.top);
1477 lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight;
1480 * Restore original height
1482 if (curComboHeight != newComboHeight)
1484 lphc->wState |= CBF_NORESIZE;
1485 SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight,
1486 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOREDRAW);
1487 lphc->wState &= ~CBF_NORESIZE;
1491 CBCalcPlacement(lphc);
1493 CBResetPos(lphc, FALSE);
1497 /***********************************************************************
1498 * COMBO_Font
1500 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1502 lphc->hFont = hFont;
1503 lphc->item_height = combo_get_text_height(lphc);
1506 * Propagate to owned windows.
1508 if( lphc->wState & CBF_EDIT )
1509 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1510 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1513 * Redo the layout of the control.
1515 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1517 CBCalcPlacement(lphc);
1519 CBResetPos(lphc, TRUE);
1521 else
1523 CBForceDummyResize(lphc);
1528 /***********************************************************************
1529 * COMBO_SetItemHeight
1531 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1533 LRESULT lRet = CB_ERR;
1535 if( index == -1 ) /* set text field height */
1537 if( height < 32768 )
1539 lphc->item_height = height + 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1542 * Redo the layout of the control.
1544 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1546 CBCalcPlacement(lphc);
1548 CBResetPos(lphc, TRUE);
1550 else
1552 CBForceDummyResize(lphc);
1555 lRet = height;
1558 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1559 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1560 return lRet;
1563 /***********************************************************************
1564 * COMBO_SelectString
1566 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1568 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText) :
1569 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1570 if( index >= 0 )
1572 if( lphc->wState & CBF_EDIT )
1573 CBUpdateEdit( lphc, index );
1574 else
1576 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1579 return (LRESULT)index;
1582 /***********************************************************************
1583 * COMBO_LButtonDown
1585 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1587 POINT pt;
1588 BOOL bButton;
1589 HWND hWnd = lphc->self;
1591 pt.x = (short)LOWORD(lParam);
1592 pt.y = (short)HIWORD(lParam);
1593 bButton = PtInRect(&lphc->buttonRect, pt);
1595 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1596 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1598 lphc->wState |= CBF_BUTTONDOWN;
1599 if( lphc->wState & CBF_DROPPED )
1601 /* got a click to cancel selection */
1603 lphc->wState &= ~CBF_BUTTONDOWN;
1604 CBRollUp( lphc, TRUE, FALSE );
1605 if( !IsWindow( hWnd ) ) return;
1607 if( lphc->wState & CBF_CAPTURE )
1609 lphc->wState &= ~CBF_CAPTURE;
1610 ReleaseCapture();
1613 else
1615 /* drop down the listbox and start tracking */
1617 lphc->wState |= CBF_CAPTURE;
1618 SetCapture( hWnd );
1619 CBDropDown( lphc );
1621 if( bButton ) CBRepaintButton( lphc );
1625 /***********************************************************************
1626 * COMBO_LButtonUp
1628 * Release capture and stop tracking if needed.
1630 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1632 if( lphc->wState & CBF_CAPTURE )
1634 lphc->wState &= ~CBF_CAPTURE;
1635 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1637 INT index = CBUpdateLBox( lphc, TRUE );
1638 /* Update edit only if item is in the list */
1639 if(index >= 0)
1641 lphc->wState |= CBF_NOLBSELECT;
1642 CBUpdateEdit( lphc, index );
1643 lphc->wState &= ~CBF_NOLBSELECT;
1646 ReleaseCapture();
1647 SetCapture(lphc->hWndLBox);
1650 if( lphc->wState & CBF_BUTTONDOWN )
1652 lphc->wState &= ~CBF_BUTTONDOWN;
1653 CBRepaintButton( lphc );
1657 /***********************************************************************
1658 * COMBO_MouseMove
1660 * Two things to do - track combo button and release capture when
1661 * pointer goes into the listbox.
1663 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1665 POINT pt;
1666 RECT lbRect;
1668 pt.x = (short)LOWORD(lParam);
1669 pt.y = (short)HIWORD(lParam);
1671 if( lphc->wState & CBF_BUTTONDOWN )
1673 BOOL bButton;
1675 bButton = PtInRect(&lphc->buttonRect, pt);
1677 if( !bButton )
1679 lphc->wState &= ~CBF_BUTTONDOWN;
1680 CBRepaintButton( lphc );
1684 GetClientRect( lphc->hWndLBox, &lbRect );
1685 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1686 if( PtInRect(&lbRect, pt) )
1688 lphc->wState &= ~CBF_CAPTURE;
1689 ReleaseCapture();
1690 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1692 /* hand over pointer tracking */
1693 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1697 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1699 if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1700 return FALSE;
1702 pcbi->rcItem = lphc->textRect;
1703 pcbi->rcButton = lphc->buttonRect;
1704 pcbi->stateButton = 0;
1705 if (lphc->wState & CBF_BUTTONDOWN)
1706 pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1707 if (IsRectEmpty(&lphc->buttonRect))
1708 pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1709 pcbi->hwndCombo = lphc->self;
1710 pcbi->hwndItem = lphc->hWndEdit;
1711 pcbi->hwndList = lphc->hWndLBox;
1712 return TRUE;
1715 static char *strdupA(LPCSTR str)
1717 char *ret;
1718 DWORD len;
1720 if(!str) return NULL;
1722 len = strlen(str);
1723 ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1724 memcpy(ret, str, len + 1);
1725 return ret;
1728 /***********************************************************************
1729 * ComboWndProc_common
1731 LRESULT ComboWndProc_common( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, BOOL unicode )
1733 LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1735 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1736 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1738 if (!IsWindow(hwnd)) return 0;
1740 if( lphc || message == WM_NCCREATE )
1741 switch(message)
1744 /* System messages */
1746 case WM_NCCREATE:
1748 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1749 ((LPCREATESTRUCTA)lParam)->style;
1750 return COMBO_NCCreate(hwnd, style);
1752 case WM_NCDESTROY:
1753 COMBO_NCDestroy(lphc);
1754 break;/* -> DefWindowProc */
1756 case WM_CREATE:
1758 HWND hwndParent;
1759 LONG style;
1760 if(unicode)
1762 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1763 style = ((LPCREATESTRUCTW)lParam)->style;
1765 else
1767 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1768 style = ((LPCREATESTRUCTA)lParam)->style;
1770 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1773 case WM_PRINTCLIENT:
1774 /* Fallthrough */
1775 case WM_PAINT:
1776 /* wParam may contain a valid HDC! */
1777 return COMBO_Paint(lphc, (HDC)wParam);
1779 case WM_ERASEBKGND:
1780 /* do all painting in WM_PAINT like Windows does */
1781 return 1;
1783 case WM_GETDLGCODE:
1785 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1786 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1788 int vk = (int)((LPMSG)lParam)->wParam;
1790 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1791 result |= DLGC_WANTMESSAGE;
1793 return result;
1795 case WM_SIZE:
1796 COMBO_Size( lphc );
1797 return TRUE;
1798 case WM_SETFONT:
1799 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1800 return TRUE;
1801 case WM_GETFONT:
1802 return (LRESULT)lphc->hFont;
1803 case WM_SETFOCUS:
1804 if( lphc->wState & CBF_EDIT ) {
1805 SetFocus( lphc->hWndEdit );
1806 /* The first time focus is received, select all the text */
1807 if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1808 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1809 lphc->wState |= CBF_BEENFOCUSED;
1812 else
1813 COMBO_SetFocus( lphc );
1814 return TRUE;
1815 case WM_KILLFOCUS:
1817 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1818 if( !hwndFocus ||
1819 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1820 COMBO_KillFocus( lphc );
1821 return TRUE;
1823 case WM_COMMAND:
1824 return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1825 case WM_GETTEXT:
1826 return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1827 : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1828 case WM_SETTEXT:
1829 case WM_GETTEXTLENGTH:
1830 case WM_CLEAR:
1831 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1833 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1834 if (j == -1) return 0;
1835 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1836 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1838 else if( lphc->wState & CBF_EDIT )
1840 LRESULT ret;
1841 lphc->wState |= CBF_NOEDITNOTIFY;
1842 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1843 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1844 lphc->wState &= ~CBF_NOEDITNOTIFY;
1845 return ret;
1847 else return CB_ERR;
1848 case WM_CUT:
1849 case WM_PASTE:
1850 case WM_COPY:
1851 if( lphc->wState & CBF_EDIT )
1853 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1854 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1856 else return CB_ERR;
1858 case WM_DRAWITEM:
1859 case WM_DELETEITEM:
1860 case WM_COMPAREITEM:
1861 case WM_MEASUREITEM:
1862 return COMBO_ItemOp(lphc, message, lParam);
1863 case WM_ENABLE:
1864 if( lphc->wState & CBF_EDIT )
1865 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1866 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1868 /* Force the control to repaint when the enabled state changes. */
1869 InvalidateRect(lphc->self, NULL, TRUE);
1870 return TRUE;
1871 case WM_SETREDRAW:
1872 if( wParam )
1873 lphc->wState &= ~CBF_NOREDRAW;
1874 else
1875 lphc->wState |= CBF_NOREDRAW;
1877 if( lphc->wState & CBF_EDIT )
1878 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1879 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1880 return 0;
1881 case WM_SYSKEYDOWN:
1882 if( KEYDATA_ALT & HIWORD(lParam) )
1883 if( wParam == VK_UP || wParam == VK_DOWN )
1884 COMBO_FlipListbox( lphc, FALSE, FALSE );
1885 return 0;
1887 case WM_KEYDOWN:
1888 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1889 (lphc->wState & CBF_DROPPED))
1891 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1892 return TRUE;
1894 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1896 COMBO_FlipListbox( lphc, FALSE, FALSE );
1897 return TRUE;
1899 /* fall through */
1900 case WM_CHAR:
1901 case WM_IME_CHAR:
1903 HWND hwndTarget;
1905 if( lphc->wState & CBF_EDIT )
1906 hwndTarget = lphc->hWndEdit;
1907 else
1908 hwndTarget = lphc->hWndLBox;
1910 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
1911 SendMessageA(hwndTarget, message, wParam, lParam);
1913 case WM_LBUTTONDOWN:
1914 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1915 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1916 return TRUE;
1917 case WM_LBUTTONUP:
1918 COMBO_LButtonUp( lphc );
1919 return TRUE;
1920 case WM_MOUSEMOVE:
1921 if( lphc->wState & CBF_CAPTURE )
1922 COMBO_MouseMove( lphc, wParam, lParam );
1923 return TRUE;
1925 case WM_MOUSEWHEEL:
1926 if (wParam & (MK_SHIFT | MK_CONTROL))
1927 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
1928 DefWindowProcA(hwnd, message, wParam, lParam);
1930 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
1931 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
1932 return TRUE;
1934 case WM_CTLCOLOR:
1935 case WM_CTLCOLORMSGBOX:
1936 case WM_CTLCOLOREDIT:
1937 case WM_CTLCOLORLISTBOX:
1938 case WM_CTLCOLORBTN:
1939 case WM_CTLCOLORDLG:
1940 case WM_CTLCOLORSCROLLBAR:
1941 case WM_CTLCOLORSTATIC:
1942 if (lphc->owner)
1943 return SendMessageW(lphc->owner, message, wParam, lParam);
1944 break;
1946 /* Combo messages */
1948 case CB_ADDSTRING:
1949 if( unicode )
1951 if( lphc->dwStyle & CBS_LOWERCASE )
1952 CharLowerW((LPWSTR)lParam);
1953 else if( lphc->dwStyle & CBS_UPPERCASE )
1954 CharUpperW((LPWSTR)lParam);
1955 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
1957 else /* unlike the unicode version, the ansi version does not overwrite
1958 the string if converting case */
1960 char *string = NULL;
1961 LRESULT ret;
1962 if( lphc->dwStyle & CBS_LOWERCASE )
1964 string = strdupA((LPSTR)lParam);
1965 CharLowerA(string);
1968 else if( lphc->dwStyle & CBS_UPPERCASE )
1970 string = strdupA((LPSTR)lParam);
1971 CharUpperA(string);
1974 ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
1975 HeapFree(GetProcessHeap(), 0, string);
1976 return ret;
1978 case CB_INSERTSTRING:
1979 if( unicode )
1981 if( lphc->dwStyle & CBS_LOWERCASE )
1982 CharLowerW((LPWSTR)lParam);
1983 else if( lphc->dwStyle & CBS_UPPERCASE )
1984 CharUpperW((LPWSTR)lParam);
1985 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1987 else
1989 if( lphc->dwStyle & CBS_LOWERCASE )
1990 CharLowerA((LPSTR)lParam);
1991 else if( lphc->dwStyle & CBS_UPPERCASE )
1992 CharUpperA((LPSTR)lParam);
1994 return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1996 case CB_DELETESTRING:
1997 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
1998 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
1999 case CB_SELECTSTRING:
2000 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2001 case CB_FINDSTRING:
2002 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2003 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2004 case CB_FINDSTRINGEXACT:
2005 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2006 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2007 case CB_SETITEMHEIGHT:
2008 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2009 case CB_GETITEMHEIGHT:
2010 if( (INT)wParam >= 0 ) /* listbox item */
2011 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2012 return CBGetTextAreaHeight(lphc, FALSE);
2013 case CB_RESETCONTENT:
2014 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2015 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2017 static const WCHAR empty_stringW[] = { 0 };
2018 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2020 else
2021 InvalidateRect(lphc->self, NULL, TRUE);
2022 return TRUE;
2023 case CB_INITSTORAGE:
2024 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2025 case CB_GETHORIZONTALEXTENT:
2026 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2027 case CB_SETHORIZONTALEXTENT:
2028 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2029 case CB_GETTOPINDEX:
2030 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2031 case CB_GETLOCALE:
2032 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2033 case CB_SETLOCALE:
2034 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2035 case CB_SETDROPPEDWIDTH:
2036 if( (CB_GETTYPE(lphc) == CBS_SIMPLE) ||
2037 (INT)wParam >= 32768 )
2038 return CB_ERR;
2039 /* new value must be higher than combobox width */
2040 if((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2041 lphc->droppedWidth = wParam;
2042 else if(wParam)
2043 lphc->droppedWidth = 0;
2045 /* recalculate the combobox area */
2046 CBCalcPlacement(lphc);
2048 /* fall through */
2049 case CB_GETDROPPEDWIDTH:
2050 if( lphc->droppedWidth )
2051 return lphc->droppedWidth;
2052 return lphc->droppedRect.right - lphc->droppedRect.left;
2053 case CB_GETDROPPEDCONTROLRECT:
2054 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2055 return CB_OKAY;
2056 case CB_GETDROPPEDSTATE:
2057 return (lphc->wState & CBF_DROPPED) != 0;
2058 case CB_DIR:
2059 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2060 SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2062 case CB_SHOWDROPDOWN:
2063 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2065 if( wParam )
2067 if( !(lphc->wState & CBF_DROPPED) )
2068 CBDropDown( lphc );
2070 else
2071 if( lphc->wState & CBF_DROPPED )
2072 CBRollUp( lphc, FALSE, TRUE );
2074 return TRUE;
2075 case CB_GETCOUNT:
2076 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2077 case CB_GETCURSEL:
2078 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2079 case CB_SETCURSEL:
2080 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2081 if( lParam >= 0 )
2082 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2084 /* no LBN_SELCHANGE in this case, update manually */
2085 CBPaintText( lphc, NULL );
2086 lphc->wState &= ~CBF_SELCHANGE;
2087 return lParam;
2088 case CB_GETLBTEXT:
2089 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2090 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2091 case CB_GETLBTEXTLEN:
2092 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2093 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2094 case CB_GETITEMDATA:
2095 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2096 case CB_SETITEMDATA:
2097 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2098 case CB_GETEDITSEL:
2099 /* Edit checks passed parameters itself */
2100 if( lphc->wState & CBF_EDIT )
2101 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2102 return CB_ERR;
2103 case CB_SETEDITSEL:
2104 if( lphc->wState & CBF_EDIT )
2105 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2106 (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2107 return CB_ERR;
2108 case CB_SETEXTENDEDUI:
2109 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2110 return CB_ERR;
2111 if( wParam )
2112 lphc->wState |= CBF_EUI;
2113 else lphc->wState &= ~CBF_EUI;
2114 return CB_OKAY;
2115 case CB_GETEXTENDEDUI:
2116 return (lphc->wState & CBF_EUI) != 0;
2117 case CB_GETCOMBOBOXINFO:
2118 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2119 case CB_LIMITTEXT:
2120 if( lphc->wState & CBF_EDIT )
2121 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2122 return TRUE;
2123 default:
2124 if (message >= WM_USER)
2125 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2126 message - WM_USER, wParam, lParam );
2127 break;
2129 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2130 DefWindowProcA(hwnd, message, wParam, lParam);
2133 /*************************************************************************
2134 * GetComboBoxInfo (USER32.@)
2136 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */
2137 PCOMBOBOXINFO pcbi /* [in/out] combo box information */)
2139 TRACE("(%p, %p)\n", hwndCombo, pcbi);
2140 return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);