xref: /reactos/dll/win32/comctl32/combo.c (revision 8a978a17)
1 /*
2  * Combo controls
3  *
4  * Copyright 1997 Alex Korobka
5  * Copyright (c) 2005 by Frank Richter
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  *
21  */
22 
23 #include <stdarg.h>
24 #include <string.h>
25 
26 #define OEMRESOURCE
27 
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "uxtheme.h"
33 #include "vssym32.h"
34 #include "commctrl.h"
35 #include "wine/unicode.h"
36 #include "wine/debug.h"
37 #include "wine/heap.h"
38 
39 #include "comctl32.h"
40 
41 WINE_DEFAULT_DEBUG_CHANNEL(combo);
42 
43   /* bits in the dwKeyData */
44 #define KEYDATA_ALT             0x2000
45 #define KEYDATA_PREVSTATE       0x4000
46 
47 /*
48  * Additional combo box definitions
49  */
50 
51 #define CB_NOTIFY( lphc, code ) \
52     (SendMessageW((lphc)->owner, WM_COMMAND, \
53                   MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
54 
55 #define CB_DISABLED( lphc )   (!IsWindowEnabled((lphc)->self))
56 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
57 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
58 #define CB_HWND( lphc )       ((lphc)->self)
59 #define CB_GETTYPE( lphc )    ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
60 
61 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
62 
63 /*
64  * Drawing globals
65  */
66 static HBITMAP 	hComboBmp = 0;
67 static UINT	CBitHeight, CBitWidth;
68 
69 /*
70  * Look and feel dependent "constants"
71  */
72 
73 #define COMBO_YBORDERGAP         5
74 #define COMBO_XBORDERSIZE()      2
75 #define COMBO_YBORDERSIZE()      2
76 #define COMBO_EDITBUTTONSPACE()  0
77 #define EDIT_CONTROL_PADDING()   1
78 
79 #define ID_CB_LISTBOX           1000
80 #define ID_CB_EDIT              1001
81 
82 /***********************************************************************
83  *           COMBO_Init
84  *
85  * Load combo button bitmap.
86  */
87 static BOOL COMBO_Init(void)
88 {
89   HDC		hDC;
90 
91   if( hComboBmp ) return TRUE;
92   if( (hDC = CreateCompatibleDC(0)) )
93   {
94     BOOL	bRet = FALSE;
95     if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
96     {
97       BITMAP      bm;
98       HBITMAP     hPrevB;
99       RECT        r;
100 
101       GetObjectW( hComboBmp, sizeof(bm), &bm );
102       CBitHeight = bm.bmHeight;
103       CBitWidth  = bm.bmWidth;
104 
105       TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
106 
107       hPrevB = SelectObject( hDC, hComboBmp);
108       SetRect( &r, 0, 0, CBitWidth, CBitHeight );
109       InvertRect( hDC, &r );
110       SelectObject( hDC, hPrevB );
111       bRet = TRUE;
112     }
113     DeleteDC( hDC );
114     return bRet;
115   }
116   return FALSE;
117 }
118 
119 /***********************************************************************
120  *           COMBO_NCCreate
121  */
122 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style)
123 {
124     HEADCOMBO *lphc;
125 
126     if (COMBO_Init() && (lphc = heap_alloc_zero(sizeof(*lphc))))
127     {
128         lphc->self = hwnd;
129         SetWindowLongPtrW( hwnd, 0, (LONG_PTR)lphc );
130 
131        /* some braindead apps do try to use scrollbar/border flags */
132 
133 	lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
134         SetWindowLongW( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) );
135 
136 	/*
137 	 * We also have to remove the client edge style to make sure
138 	 * we don't end-up with a non client area.
139 	 */
140         SetWindowLongW( hwnd, GWL_EXSTYLE,
141                         GetWindowLongW( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
142 
143 	if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
144               lphc->dwStyle |= CBS_HASSTRINGS;
145 	if( !(GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
146 	      lphc->wState |= CBF_NOTIFY;
147 
148         TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle );
149         return TRUE;
150     }
151     return FALSE;
152 }
153 
154 /***********************************************************************
155  *           COMBO_NCDestroy
156  */
157 static LRESULT COMBO_NCDestroy( HEADCOMBO *lphc )
158 {
159     if (lphc)
160     {
161         TRACE("[%p]: freeing storage\n", lphc->self);
162 
163         if ( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
164             DestroyWindow( lphc->hWndLBox );
165 
166         SetWindowLongPtrW( lphc->self, 0, 0 );
167         heap_free( lphc );
168     }
169 
170     return 0;
171 }
172 
173 /***********************************************************************
174  *           CBGetTextAreaHeight
175  *
176  * This method will calculate the height of the text area of the
177  * combobox.
178  * The height of the text area is set in two ways.
179  * It can be set explicitly through a combobox message or through a
180  * WM_MEASUREITEM callback.
181  * If this is not the case, the height is set to font height + 4px
182  * This height was determined through experimentation.
183  * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
184  */
185 static INT CBGetTextAreaHeight(
186   HWND        hwnd,
187   LPHEADCOMBO lphc)
188 {
189   INT iTextItemHeight;
190 
191   if( lphc->editHeight ) /* explicitly set height */
192   {
193     iTextItemHeight = lphc->editHeight;
194   }
195   else
196   {
197     TEXTMETRICW tm;
198     HDC         hDC       = GetDC(hwnd);
199     HFONT       hPrevFont = 0;
200     INT         baseUnitY;
201 
202     if (lphc->hFont)
203       hPrevFont = SelectObject( hDC, lphc->hFont );
204 
205     GetTextMetricsW(hDC, &tm);
206 
207     baseUnitY = tm.tmHeight;
208 
209     if( hPrevFont )
210       SelectObject( hDC, hPrevFont );
211 
212     ReleaseDC(hwnd, hDC);
213 
214     iTextItemHeight = baseUnitY + 4;
215   }
216 
217   /*
218    * Check the ownerdraw case if we haven't asked the parent the size
219    * of the item yet.
220    */
221   if ( CB_OWNERDRAWN(lphc) &&
222        (lphc->wState & CBF_MEASUREITEM) )
223   {
224     MEASUREITEMSTRUCT measureItem;
225     RECT              clientRect;
226     INT               originalItemHeight = iTextItemHeight;
227     UINT id = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
228 
229     /*
230      * We use the client rect for the width of the item.
231      */
232     GetClientRect(hwnd, &clientRect);
233 
234     lphc->wState &= ~CBF_MEASUREITEM;
235 
236     /*
237      * Send a first one to measure the size of the text area
238      */
239     measureItem.CtlType    = ODT_COMBOBOX;
240     measureItem.CtlID      = id;
241     measureItem.itemID     = -1;
242     measureItem.itemWidth  = clientRect.right;
243     measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
244     measureItem.itemData   = 0;
245     SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
246     iTextItemHeight = 6 + measureItem.itemHeight;
247 
248     /*
249      * Send a second one in the case of a fixed ownerdraw list to calculate the
250      * size of the list items. (we basically do this on behalf of the listbox)
251      */
252     if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
253     {
254       measureItem.CtlType    = ODT_COMBOBOX;
255       measureItem.CtlID      = id;
256       measureItem.itemID     = 0;
257       measureItem.itemWidth  = clientRect.right;
258       measureItem.itemHeight = originalItemHeight;
259       measureItem.itemData   = 0;
260       SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
261       lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
262     }
263 
264     /*
265      * Keep the size for the next time
266      */
267     lphc->editHeight = iTextItemHeight;
268   }
269 
270   return iTextItemHeight;
271 }
272 
273 /***********************************************************************
274  *           CBForceDummyResize
275  *
276  * The dummy resize is used for listboxes that have a popup to trigger
277  * a re-arranging of the contents of the combobox and the recalculation
278  * of the size of the "real" control window.
279  */
280 static void CBForceDummyResize(
281   LPHEADCOMBO lphc)
282 {
283   RECT windowRect;
284   int newComboHeight;
285 
286   newComboHeight = CBGetTextAreaHeight(lphc->self,lphc) + 2*COMBO_YBORDERSIZE();
287 
288   GetWindowRect(lphc->self, &windowRect);
289 
290   /*
291    * We have to be careful, resizing a combobox also has the meaning that the
292    * dropped rect will be resized. In this case, we want to trigger a resize
293    * to recalculate layout but we don't want to change the dropped rectangle
294    * So, we pass the height of text area of control as the height.
295    * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
296    * message.
297    */
298   SetWindowPos( lphc->self,
299 		NULL,
300 		0, 0,
301 		windowRect.right  - windowRect.left,
302 		newComboHeight,
303 		SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
304 }
305 
306 /***********************************************************************
307  *           CBCalcPlacement
308  *
309  * Set up component coordinates given valid lphc->RectCombo.
310  */
311 static void CBCalcPlacement(
312   HWND        hwnd,
313   LPHEADCOMBO lphc,
314   LPRECT      lprEdit,
315   LPRECT      lprButton,
316   LPRECT      lprLB)
317 {
318   /*
319    * Again, start with the client rectangle.
320    */
321   GetClientRect(hwnd, lprEdit);
322 
323   /*
324    * Remove the borders
325    */
326   InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
327 
328   /*
329    * Chop off the bottom part to fit with the height of the text area.
330    */
331   lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
332 
333   /*
334    * The button starts the same vertical position as the text area.
335    */
336   CopyRect(lprButton, lprEdit);
337 
338   /*
339    * If the combobox is "simple" there is no button.
340    */
341   if( CB_GETTYPE(lphc) == CBS_SIMPLE )
342     lprButton->left = lprButton->right = lprButton->bottom = 0;
343   else
344   {
345     /*
346      * Let's assume the combobox button is the same width as the
347      * scrollbar button.
348      * size the button horizontally and cut-off the text area.
349      */
350     lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
351     lprEdit->right  = lprButton->left;
352   }
353 
354   /*
355    * In the case of a dropdown, there is an additional spacing between the
356    * text area and the button.
357    */
358   if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
359   {
360     lprEdit->right -= COMBO_EDITBUTTONSPACE();
361   }
362 
363   /*
364    * If we have an edit control, we space it away from the borders slightly.
365    */
366   if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
367   {
368     InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
369   }
370 
371   /*
372    * Adjust the size of the listbox popup.
373    */
374   if( CB_GETTYPE(lphc) == CBS_SIMPLE )
375   {
376     /*
377      * Use the client rectangle to initialize the listbox rectangle
378      */
379     GetClientRect(hwnd, lprLB);
380 
381     /*
382      * Then, chop-off the top part.
383      */
384     lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
385   }
386   else
387   {
388     /*
389      * Make sure the dropped width is as large as the combobox itself.
390      */
391     if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
392     {
393       lprLB->right  = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
394 
395       /*
396        * In the case of a dropdown, the popup listbox is offset to the right.
397        * so, we want to make sure it's flush with the right side of the
398        * combobox
399        */
400       if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
401 	lprLB->right -= COMBO_EDITBUTTONSPACE();
402     }
403     else
404        lprLB->right = lprLB->left + lphc->droppedWidth;
405   }
406 
407   /* don't allow negative window width */
408   if (lprEdit->right < lprEdit->left)
409     lprEdit->right = lprEdit->left;
410 
411   TRACE("\ttext\t= (%s)\n", wine_dbgstr_rect(lprEdit));
412 
413   TRACE("\tbutton\t= (%s)\n", wine_dbgstr_rect(lprButton));
414 
415   TRACE("\tlbox\t= (%s)\n", wine_dbgstr_rect(lprLB));
416 }
417 
418 /***********************************************************************
419  *           CBGetDroppedControlRect
420  */
421 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
422 {
423     /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
424      of the combo box and the lower right corner of the listbox */
425 
426     GetWindowRect(lphc->self, lpRect);
427 
428     lpRect->right =  lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
429     lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
430 
431 }
432 
433 /***********************************************************************
434  *           COMBO_Create
435  */
436 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style )
437 {
438   static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
439   static const WCHAR editName[] = {'E','d','i','t',0};
440 
441   OpenThemeData( hwnd, WC_COMBOBOXW );
442   if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
443   if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
444 
445   lphc->owner = hwndParent;
446 
447   /*
448    * The item height and dropped width are not set when the control
449    * is created.
450    */
451   lphc->droppedWidth = lphc->editHeight = 0;
452 
453   /*
454    * The first time we go through, we want to measure the ownerdraw item
455    */
456   lphc->wState |= CBF_MEASUREITEM;
457 
458   /*
459    * Per default the comctl32 version of combo shows up to 30 items
460    */
461   lphc->visibleItems = 30;
462 
463   /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
464 
465   if( lphc->owner || !(style & WS_VISIBLE) )
466   {
467       UINT lbeStyle   = 0;
468       UINT lbeExStyle = 0;
469 
470       /*
471        * Initialize the dropped rect to the size of the client area of the
472        * control and then, force all the areas of the combobox to be
473        * recalculated.
474        */
475       GetClientRect( hwnd, &lphc->droppedRect );
476       CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
477 
478       /*
479        * Adjust the position of the popup listbox if it's necessary
480        */
481       if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
482       {
483 	lphc->droppedRect.top   = lphc->textRect.bottom + COMBO_YBORDERSIZE();
484 
485 	/*
486 	 * If it's a dropdown, the listbox is offset
487 	 */
488 	if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
489 	  lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
490 
491         if (lphc->droppedRect.bottom < lphc->droppedRect.top)
492             lphc->droppedRect.bottom = lphc->droppedRect.top;
493         if (lphc->droppedRect.right < lphc->droppedRect.left)
494             lphc->droppedRect.right = lphc->droppedRect.left;
495         MapWindowPoints( hwnd, 0, (LPPOINT)&lphc->droppedRect, 2 );
496       }
497 
498       /* create listbox popup */
499 
500       lbeStyle = (LBS_NOTIFY | LBS_COMBOBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
501                  (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
502 
503       if( lphc->dwStyle & CBS_SORT )
504 	lbeStyle |= LBS_SORT;
505       if( lphc->dwStyle & CBS_HASSTRINGS )
506 	lbeStyle |= LBS_HASSTRINGS;
507       if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
508 	lbeStyle |= LBS_NOINTEGRALHEIGHT;
509       if( lphc->dwStyle & CBS_DISABLENOSCROLL )
510 	lbeStyle |= LBS_DISABLENOSCROLL;
511 
512       if( CB_GETTYPE(lphc) == CBS_SIMPLE ) 	/* child listbox */
513       {
514 	lbeStyle |= WS_VISIBLE;
515 
516 	/*
517 	 * In win 95 look n feel, the listbox in the simple combobox has
518 	 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
519 	 */
520 	lbeStyle   &= ~WS_BORDER;
521 	lbeExStyle |= WS_EX_CLIENTEDGE;
522       }
523       else
524       {
525         lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
526       }
527 
528       lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
529               lphc->droppedRect.left, lphc->droppedRect.top, lphc->droppedRect.right - lphc->droppedRect.left,
530               lphc->droppedRect.bottom - lphc->droppedRect.top, hwnd, (HMENU)ID_CB_LISTBOX,
531               (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
532       if( lphc->hWndLBox )
533       {
534 	  BOOL	bEdit = TRUE;
535 	  lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
536 
537 	  if( lphc->wState & CBF_EDIT )
538 	  {
539 	      if( lphc->dwStyle & CBS_OEMCONVERT )
540 		  lbeStyle |= ES_OEMCONVERT;
541 	      if( lphc->dwStyle & CBS_AUTOHSCROLL )
542 		  lbeStyle |= ES_AUTOHSCROLL;
543 	      if( lphc->dwStyle & CBS_LOWERCASE )
544 		  lbeStyle |= ES_LOWERCASE;
545 	      else if( lphc->dwStyle & CBS_UPPERCASE )
546 		  lbeStyle |= ES_UPPERCASE;
547 
548               if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
549 
550               lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
551                                                lphc->textRect.left, lphc->textRect.top,
552                                                lphc->textRect.right - lphc->textRect.left,
553                                                lphc->textRect.bottom - lphc->textRect.top,
554                                                hwnd, (HMENU)ID_CB_EDIT,
555                                                (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
556 	      if( !lphc->hWndEdit )
557 		bEdit = FALSE;
558 	  }
559 
560           if( bEdit )
561 	  {
562 	    if( CB_GETTYPE(lphc) != CBS_SIMPLE )
563 	    {
564               /* Now do the trick with parent */
565 	      SetParent(lphc->hWndLBox, HWND_DESKTOP);
566               /*
567                * If the combo is a dropdown, we must resize the control
568 	       * to fit only the text area and button. To do this,
569 	       * we send a dummy resize and the WM_WINDOWPOSCHANGING message
570 	       * will take care of setting the height for us.
571                */
572 	      CBForceDummyResize(lphc);
573 	    }
574 
575 	    TRACE("init done\n");
576 	    return 0;
577 	  }
578 	  ERR("edit control failure.\n");
579       } else ERR("listbox failure.\n");
580   } else ERR("no owner for visible combo.\n");
581 
582   /* CreateWindow() will send WM_NCDESTROY to cleanup */
583 
584   return -1;
585 }
586 
587 /***********************************************************************
588  *           CBPaintButton
589  *
590  * Paint combo button (normal, pressed, and disabled states).
591  */
592 static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton)
593 {
594     UINT buttonState = DFCS_SCROLLCOMBOBOX;
595 
596     if( lphc->wState & CBF_NOREDRAW )
597       return;
598 
599 
600     if (lphc->wState & CBF_BUTTONDOWN)
601 	buttonState |= DFCS_PUSHED;
602 
603     if (CB_DISABLED(lphc))
604 	buttonState |= DFCS_INACTIVE;
605 
606     DrawFrameControl(hdc, &rectButton, DFC_SCROLL, buttonState);
607 }
608 
609 /***********************************************************************
610  *           COMBO_PrepareColors
611  *
612  * This method will sent the appropriate WM_CTLCOLOR message to
613  * prepare and setup the colors for the combo's DC.
614  *
615  * It also returns the brush to use for the background.
616  */
617 static HBRUSH COMBO_PrepareColors(
618         LPHEADCOMBO lphc,
619         HDC         hDC)
620 {
621     HBRUSH  hBkgBrush;
622 
623     /*
624      * Get the background brush for this control.
625      */
626     if (CB_DISABLED(lphc))
627     {
628         hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
629                 (WPARAM)hDC, (LPARAM)lphc->self );
630 
631         /*
632          * We have to change the text color since WM_CTLCOLORSTATIC will
633          * set it to the "enabled" color. This is the same behavior as the
634          * edit control
635          */
636         SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
637     }
638     else
639     {
640         /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
641         hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
642                 (WPARAM)hDC, (LPARAM)lphc->self );
643     }
644 
645     /*
646      * Catch errors.
647      */
648     if( !hBkgBrush )
649         hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
650 
651     return hBkgBrush;
652 }
653 
654 /***********************************************************************
655  *           CBPaintText
656  *
657  * Paint CBS_DROPDOWNLIST text field / update edit control contents.
658  */
659 static void CBPaintText(HEADCOMBO *lphc, HDC hdc_paint)
660 {
661    RECT rectEdit = lphc->textRect;
662    INT	id, size = 0;
663    LPWSTR pText = NULL;
664 
665    TRACE("\n");
666 
667    /* follow Windows combobox that sends a bunch of text
668     * inquiries to its listbox while processing WM_PAINT. */
669 
670    if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
671    {
672         size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
673 	if (size == LB_ERR)
674 	  FIXME("LB_ERR probably not handled yet\n");
675         if ((pText = heap_alloc((size + 1) * sizeof(WCHAR))))
676 	{
677             /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
678            size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, id, (LPARAM)pText);
679 	    pText[size] = '\0';	/* just in case */
680 	} else return;
681    }
682 
683    if( lphc->wState & CBF_EDIT )
684    {
685         static const WCHAR empty_stringW[] = { 0 };
686 	if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
687 	if( lphc->wState & CBF_FOCUSED )
688            SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, MAXLONG);
689    }
690    else if(!(lphc->wState & CBF_NOREDRAW) && IsWindowVisible( lphc->self ))
691    {
692      /* paint text field ourselves */
693      HDC hdc = hdc_paint ? hdc_paint : GetDC(lphc->self);
694      UINT itemState = ODS_COMBOBOXEDIT;
695      HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
696      HBRUSH hPrevBrush, hBkgBrush;
697 
698      /*
699       * Give ourselves some space.
700       */
701      InflateRect( &rectEdit, -1, -1 );
702 
703      hBkgBrush = COMBO_PrepareColors( lphc, hdc );
704      hPrevBrush = SelectObject( hdc, hBkgBrush );
705      FillRect( hdc, &rectEdit, hBkgBrush );
706 
707      if( CB_OWNERDRAWN(lphc) )
708      {
709        DRAWITEMSTRUCT dis;
710        HRGN           clipRegion;
711        UINT ctlid = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
712 
713        /* setup state for DRAWITEM message. Owner will highlight */
714        if ( (lphc->wState & CBF_FOCUSED) &&
715 	    !(lphc->wState & CBF_DROPPED) )
716 	   itemState |= ODS_SELECTED | ODS_FOCUS;
717 
718        if (!IsWindowEnabled(lphc->self)) itemState |= ODS_DISABLED;
719 
720        dis.CtlType	= ODT_COMBOBOX;
721        dis.CtlID	= ctlid;
722        dis.hwndItem	= lphc->self;
723        dis.itemAction	= ODA_DRAWENTIRE;
724        dis.itemID	= id;
725        dis.itemState	= itemState;
726        dis.hDC		= hdc;
727        dis.rcItem	= rectEdit;
728        dis.itemData     = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, id, 0);
729 
730        /*
731 	* Clip the DC and have the parent draw the item.
732 	*/
733        clipRegion = set_control_clipping( hdc, &rectEdit );
734 
735        SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
736 
737        SelectClipRgn( hdc, clipRegion );
738        if (clipRegion) DeleteObject( clipRegion );
739      }
740      else
741      {
742        static const WCHAR empty_stringW[] = { 0 };
743 
744        if ( (lphc->wState & CBF_FOCUSED) &&
745 	    !(lphc->wState & CBF_DROPPED) ) {
746 
747 	   /* highlight */
748 	   FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
749 	   SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
750 	   SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
751        }
752 
753        ExtTextOutW( hdc,
754 		    rectEdit.left + 1,
755 		    rectEdit.top + 1,
756 		    ETO_OPAQUE | ETO_CLIPPED,
757 		    &rectEdit,
758 		    pText ? pText : empty_stringW , size, NULL );
759 
760        if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
761 	 DrawFocusRect( hdc, &rectEdit );
762      }
763 
764      if( hPrevFont )
765        SelectObject(hdc, hPrevFont );
766 
767     if( hPrevBrush )
768         SelectObject( hdc, hPrevBrush );
769 
770      if( !hdc_paint )
771        ReleaseDC( lphc->self, hdc );
772    }
773 
774     heap_free(pText);
775 }
776 
777 /***********************************************************************
778  *           CBPaintBorder
779  */
780 static void CBPaintBorder(
781   HWND            hwnd,
782   const HEADCOMBO *lphc,
783   HDC             hdc)
784 {
785   RECT clientRect;
786 
787   if (CB_GETTYPE(lphc) != CBS_SIMPLE)
788   {
789     GetClientRect(hwnd, &clientRect);
790   }
791   else
792   {
793     clientRect = lphc->textRect;
794 
795     InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
796     InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
797   }
798 
799   DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
800 }
801 
802 static LRESULT COMBO_ThemedPaint(HTHEME theme, HEADCOMBO *lphc, HDC hdc)
803 {
804     int button_state;
805     RECT frame;
806 
807     /* paint border */
808     if (CB_GETTYPE(lphc) != CBS_SIMPLE)
809         GetClientRect(lphc->self, &frame);
810     else
811     {
812         frame = lphc->textRect;
813         InflateRect(&frame, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
814         InflateRect(&frame, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
815     }
816 
817     DrawThemeBackground(theme, hdc, 0, IsWindowEnabled(lphc->self) ? CBXS_NORMAL : CBXS_DISABLED, &frame, NULL);
818 
819     /* Paint button */
820     if (!IsRectEmpty(&lphc->buttonRect))
821     {
822         if (!IsWindowEnabled(lphc->self))
823             button_state = CBXS_DISABLED;
824         else if (lphc->wState & CBF_BUTTONDOWN)
825             button_state = CBXS_PRESSED;
826         else if (lphc->wState & CBF_HOT)
827             button_state = CBXS_HOT;
828         else
829             button_state = CBXS_NORMAL;
830         DrawThemeBackground(theme, hdc, CP_DROPDOWNBUTTON, button_state, &lphc->buttonRect, NULL);
831     }
832 
833     if ((lphc->dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST)
834         CBPaintText(lphc, hdc);
835 
836     return 0;
837 }
838 
839 /***********************************************************************
840  *           COMBO_Paint
841  */
842 static LRESULT COMBO_Paint(HEADCOMBO *lphc, HDC hdc)
843 {
844     HBRUSH hPrevBrush, hBkgBrush;
845 
846     TRACE("hdc=%p\n", hdc);
847 
848     /*
849      * Retrieve the background brush and select it in the
850      * DC.
851      */
852     hBkgBrush = COMBO_PrepareColors(lphc, hdc);
853     hPrevBrush = SelectObject(hdc, hBkgBrush);
854     if (!(lphc->wState & CBF_EDIT))
855         FillRect(hdc, &lphc->textRect, hBkgBrush);
856 
857     /*
858      * In non 3.1 look, there is a sunken border on the combobox
859      */
860     CBPaintBorder(lphc->self, lphc, hdc);
861 
862     if (!IsRectEmpty(&lphc->buttonRect))
863         CBPaintButton(lphc, hdc, lphc->buttonRect);
864 
865     /* paint the edit control padding area */
866     if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
867     {
868         RECT rPadEdit = lphc->textRect;
869 
870         InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
871 
872         FrameRect(hdc, &rPadEdit, GetSysColorBrush(COLOR_WINDOW));
873     }
874 
875     if (!(lphc->wState & CBF_EDIT))
876         CBPaintText( lphc, hdc );
877 
878     if (hPrevBrush)
879         SelectObject( hdc, hPrevBrush );
880 
881     return 0;
882 }
883 
884 /***********************************************************************
885  *           CBUpdateLBox
886  *
887  * Select listbox entry according to the contents of the edit control.
888  */
889 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
890 {
891    INT	length, idx;
892    LPWSTR pText = NULL;
893 
894    idx = LB_ERR;
895    length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
896 
897     if (length > 0)
898         pText = heap_alloc((length + 1) * sizeof(WCHAR));
899 
900    TRACE("\t edit text length %i\n", length );
901 
902    if( pText )
903    {
904        GetWindowTextW( lphc->hWndEdit, pText, length + 1);
905        idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, -1, (LPARAM)pText);
906        heap_free( pText );
907    }
908 
909    SendMessageW(lphc->hWndLBox, LB_SETCURSEL, bSelect ? idx : -1, 0);
910 
911    /* probably superfluous but Windows sends this too */
912    SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, idx < 0 ? 0 : idx, 0);
913    SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, idx < 0 ? 0 : idx, 0);
914 
915    return idx;
916 }
917 
918 /***********************************************************************
919  *           CBUpdateEdit
920  *
921  * Copy a listbox entry to the edit control.
922  */
923 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
924 {
925    INT	length;
926    LPWSTR pText = NULL;
927    static const WCHAR empty_stringW[] = { 0 };
928 
929    TRACE("\t %i\n", index );
930 
931    if( index >= 0 ) /* got an entry */
932    {
933        length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, index, 0);
934        if( length != LB_ERR)
935        {
936            if ((pText = heap_alloc((length + 1) * sizeof(WCHAR))))
937                SendMessageW(lphc->hWndLBox, LB_GETTEXT, index, (LPARAM)pText);
938        }
939    }
940 
941    if( CB_HASSTRINGS(lphc) )
942    {
943       lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
944       SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
945       lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
946    }
947 
948    if( lphc->wState & CBF_FOCUSED )
949       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
950 
951     heap_free( pText );
952 }
953 
954 /***********************************************************************
955  *           CBDropDown
956  *
957  * Show listbox popup.
958  */
959 static void CBDropDown( LPHEADCOMBO lphc )
960 {
961     HMONITOR monitor;
962     MONITORINFO mon_info;
963    RECT rect,r;
964    int nItems;
965    int nDroppedHeight;
966 
967    TRACE("[%p]: drop down\n", lphc->self);
968 
969    CB_NOTIFY( lphc, CBN_DROPDOWN );
970 
971    /* set selection */
972 
973    lphc->wState |= CBF_DROPPED;
974    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
975    {
976        lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
977 
978        /* Update edit only if item is in the list */
979        if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
980 	 CBUpdateEdit( lphc, lphc->droppedIndex );
981    }
982    else
983    {
984        lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
985 
986        SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
987                     lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex, 0);
988        SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
989    }
990 
991    /* now set popup position */
992    GetWindowRect( lphc->self, &rect );
993 
994    /*
995     * If it's a dropdown, the listbox is offset
996     */
997    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
998      rect.left += COMBO_EDITBUTTONSPACE();
999 
1000   /* if the dropped height is greater than the total height of the dropped
1001      items list, then force the drop down list height to be the total height
1002      of the items in the dropped list */
1003 
1004   /* And Remove any extra space (Best Fit) */
1005    nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1006   /* if listbox length has been set directly by its handle */
1007    GetWindowRect(lphc->hWndLBox, &r);
1008    if (nDroppedHeight < r.bottom - r.top)
1009        nDroppedHeight = r.bottom - r.top;
1010    nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1011 
1012    if (nItems > 0)
1013    {
1014         int nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1015 
1016         if (lphc->dwStyle & CBS_NOINTEGRALHEIGHT)
1017         {
1018             nDroppedHeight -= 1;
1019         }
1020         else
1021         {
1022             if (nItems > lphc->visibleItems)
1023                 nItems = lphc->visibleItems;
1024             nDroppedHeight = nItems * nIHeight + COMBO_YBORDERSIZE();
1025         }
1026    }
1027 
1028    r.left = rect.left;
1029    r.top = rect.bottom;
1030    r.right = r.left + lphc->droppedRect.right - lphc->droppedRect.left;
1031    r.bottom = r.top + nDroppedHeight;
1032 
1033    /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1034    monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1035    mon_info.cbSize = sizeof(mon_info);
1036    GetMonitorInfoW( monitor, &mon_info );
1037 
1038    if (r.bottom > mon_info.rcWork.bottom)
1039    {
1040        r.top = max( rect.top - nDroppedHeight, mon_info.rcWork.top );
1041        r.bottom = min( r.top + nDroppedHeight, mon_info.rcWork.bottom );
1042    }
1043 
1044    SetWindowPos( lphc->hWndLBox, HWND_TOPMOST, r.left, r.top, r.right - r.left, r.bottom - r.top,
1045                  SWP_NOACTIVATE | SWP_SHOWWINDOW );
1046 
1047 
1048    if( !(lphc->wState & CBF_NOREDRAW) )
1049      RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1050 			   RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1051 
1052    EnableWindow( lphc->hWndLBox, TRUE );
1053    if (GetCapture() != lphc->self)
1054       SetCapture(lphc->hWndLBox);
1055 }
1056 
1057 /***********************************************************************
1058  *           CBRollUp
1059  *
1060  * Hide listbox popup.
1061  */
1062 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1063 {
1064    HWND	hWnd = lphc->self;
1065 
1066    TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1067 	 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1068 
1069    CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1070 
1071    if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1072    {
1073 
1074        if( lphc->wState & CBF_DROPPED )
1075        {
1076 	   RECT	rect;
1077 
1078 	   lphc->wState &= ~CBF_DROPPED;
1079 	   ShowWindow( lphc->hWndLBox, SW_HIDE );
1080 
1081            if(GetCapture() == lphc->hWndLBox)
1082            {
1083                ReleaseCapture();
1084            }
1085 
1086 	   if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1087 	   {
1088 	       rect = lphc->buttonRect;
1089 	   }
1090 	   else
1091            {
1092 	       if( bButton )
1093 	       {
1094 		 UnionRect( &rect,
1095 			    &lphc->buttonRect,
1096 			    &lphc->textRect);
1097 	       }
1098 	       else
1099 		 rect = lphc->textRect;
1100 
1101 	       bButton = TRUE;
1102 	   }
1103 
1104 	   if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1105 	       RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1106 			       RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1107 	   CB_NOTIFY( lphc, CBN_CLOSEUP );
1108        }
1109    }
1110 }
1111 
1112 /***********************************************************************
1113  *           COMBO_FlipListbox
1114  *
1115  * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1116  */
1117 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1118 {
1119    if( lphc->wState & CBF_DROPPED )
1120    {
1121        CBRollUp( lphc, ok, bRedrawButton );
1122        return FALSE;
1123    }
1124 
1125    CBDropDown( lphc );
1126    return TRUE;
1127 }
1128 
1129 /***********************************************************************
1130  *           CBRepaintButton
1131  */
1132 static void CBRepaintButton( LPHEADCOMBO lphc )
1133    {
1134   InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1135   UpdateWindow(lphc->self);
1136 }
1137 
1138 /***********************************************************************
1139  *           COMBO_SetFocus
1140  */
1141 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1142 {
1143    if( !(lphc->wState & CBF_FOCUSED) )
1144    {
1145        if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1146            SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1147 
1148        /* This is wrong. Message sequences seem to indicate that this
1149           is set *after* the notify. */
1150        /* lphc->wState |= CBF_FOCUSED;  */
1151 
1152        if( !(lphc->wState & CBF_EDIT) )
1153 	 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1154 
1155        CB_NOTIFY( lphc, CBN_SETFOCUS );
1156        lphc->wState |= CBF_FOCUSED;
1157    }
1158 }
1159 
1160 /***********************************************************************
1161  *           COMBO_KillFocus
1162  */
1163 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1164 {
1165    HWND	hWnd = lphc->self;
1166 
1167    if( lphc->wState & CBF_FOCUSED )
1168    {
1169        CBRollUp( lphc, FALSE, TRUE );
1170        if( IsWindow( hWnd ) )
1171        {
1172            if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1173                SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1174 
1175 	   lphc->wState &= ~CBF_FOCUSED;
1176 
1177            /* redraw text */
1178 	   if( !(lphc->wState & CBF_EDIT) )
1179 	     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1180 
1181            CB_NOTIFY( lphc, CBN_KILLFOCUS );
1182        }
1183    }
1184 }
1185 
1186 /***********************************************************************
1187  *           COMBO_Command
1188  */
1189 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1190 {
1191    if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1192    {
1193        /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1194 
1195        switch( HIWORD(wParam) >> 8 )
1196        {
1197 	   case (EN_SETFOCUS >> 8):
1198 
1199                TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1200 
1201 		COMBO_SetFocus( lphc );
1202 	        break;
1203 
1204 	   case (EN_KILLFOCUS >> 8):
1205 
1206                TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1207 
1208 		/* NOTE: it seems that Windows' edit control sends an
1209 		 * undocumented message WM_USER + 0x1B instead of this
1210 		 * notification (only when it happens to be a part of
1211 		 * the combo). ?? - AK.
1212 		 */
1213 
1214 		COMBO_KillFocus( lphc );
1215 		break;
1216 
1217 
1218 	   case (EN_CHANGE >> 8):
1219 	       /*
1220 	        * In some circumstances (when the selection of the combobox
1221 		* is changed for example) we don't want the EN_CHANGE notification
1222 		* to be forwarded to the parent of the combobox. This code
1223 		* checks a flag that is set in these occasions and ignores the
1224 		* notification.
1225 	        */
1226 		if (lphc->wState & CBF_NOLBSELECT)
1227 		{
1228 		  lphc->wState &= ~CBF_NOLBSELECT;
1229 		}
1230 		else
1231 		{
1232 		  CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1233 		}
1234 
1235 	        if (!(lphc->wState & CBF_NOEDITNOTIFY))
1236 		  CB_NOTIFY( lphc, CBN_EDITCHANGE );
1237 		break;
1238 
1239 	   case (EN_UPDATE >> 8):
1240 	        if (!(lphc->wState & CBF_NOEDITNOTIFY))
1241 		  CB_NOTIFY( lphc, CBN_EDITUPDATE );
1242 		break;
1243 
1244 	   case (EN_ERRSPACE >> 8):
1245 		CB_NOTIFY( lphc, CBN_ERRSPACE );
1246        }
1247    }
1248    else if( lphc->hWndLBox == hWnd )
1249    {
1250        switch( (short)HIWORD(wParam) )
1251        {
1252 	   case LBN_ERRSPACE:
1253 		CB_NOTIFY( lphc, CBN_ERRSPACE );
1254 		break;
1255 
1256 	   case LBN_DBLCLK:
1257 		CB_NOTIFY( lphc, CBN_DBLCLK );
1258 		break;
1259 
1260 	   case LBN_SELCHANGE:
1261 	   case LBN_SELCANCEL:
1262 
1263                 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1264 
1265                 /* do not roll up if selection is being tracked
1266                  * by arrow keys in the dropdown listbox */
1267                 if (!(lphc->wState & CBF_NOROLLUP))
1268                 {
1269                     CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1270                 }
1271                 else lphc->wState &= ~CBF_NOROLLUP;
1272 
1273 		CB_NOTIFY( lphc, CBN_SELCHANGE );
1274 
1275 		if( HIWORD(wParam) == LBN_SELCHANGE)
1276 		{
1277 		   if( lphc->wState & CBF_EDIT )
1278 		       lphc->wState |= CBF_NOLBSELECT;
1279 		   CBPaintText( lphc, NULL );
1280 		}
1281                 break;
1282 
1283 	   case LBN_SETFOCUS:
1284 	   case LBN_KILLFOCUS:
1285 		/* nothing to do here since ComboLBox always resets the focus to its
1286 		 * combo/edit counterpart */
1287 		 break;
1288        }
1289    }
1290    return 0;
1291 }
1292 
1293 /***********************************************************************
1294  *           COMBO_ItemOp
1295  *
1296  * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1297  */
1298 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1299 {
1300    HWND hWnd = lphc->self;
1301    UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1302 
1303    TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1304 
1305    switch( msg )
1306    {
1307    case WM_DELETEITEM:
1308        {
1309            DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1310            lpIS->CtlType  = ODT_COMBOBOX;
1311            lpIS->CtlID    = id;
1312            lpIS->hwndItem = hWnd;
1313            break;
1314        }
1315    case WM_DRAWITEM:
1316        {
1317            DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1318            lpIS->CtlType  = ODT_COMBOBOX;
1319            lpIS->CtlID    = id;
1320            lpIS->hwndItem = hWnd;
1321            break;
1322        }
1323    case WM_COMPAREITEM:
1324        {
1325            COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1326            lpIS->CtlType  = ODT_COMBOBOX;
1327            lpIS->CtlID    = id;
1328            lpIS->hwndItem = hWnd;
1329            break;
1330        }
1331    case WM_MEASUREITEM:
1332        {
1333            MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1334            lpIS->CtlType  = ODT_COMBOBOX;
1335            lpIS->CtlID    = id;
1336            break;
1337        }
1338    }
1339    return SendMessageW(lphc->owner, msg, id, lParam);
1340 }
1341 
1342 
1343 /***********************************************************************
1344  *           COMBO_GetTextW
1345  */
1346 static LRESULT COMBO_GetText( HEADCOMBO *lphc, INT count, LPWSTR buf )
1347 {
1348     INT length;
1349 
1350     if( lphc->wState & CBF_EDIT )
1351         return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1352 
1353     /* get it from the listbox */
1354 
1355     if (!count || !buf) return 0;
1356     if( lphc->hWndLBox )
1357     {
1358         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1359         if (idx == LB_ERR) goto error;
1360         length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1361         if (length == LB_ERR) goto error;
1362 
1363         /* 'length' is without the terminating character */
1364         if (length >= count)
1365         {
1366             WCHAR *lpBuffer = heap_alloc((length + 1) * sizeof(WCHAR));
1367             if (!lpBuffer) goto error;
1368             length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1369 
1370             /* truncate if buffer is too short */
1371             if (length != LB_ERR)
1372             {
1373                 lstrcpynW( buf, lpBuffer, count );
1374                 length = count;
1375             }
1376             heap_free( lpBuffer );
1377         }
1378         else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1379 
1380         if (length == LB_ERR) return 0;
1381         return length;
1382     }
1383 
1384  error:  /* error - truncate string, return zero */
1385     buf[0] = 0;
1386     return 0;
1387 }
1388 
1389 /***********************************************************************
1390  *           CBResetPos
1391  *
1392  * This function sets window positions according to the updated
1393  * component placement struct.
1394  */
1395 static void CBResetPos(
1396   LPHEADCOMBO lphc,
1397   const RECT  *rectEdit,
1398   const RECT  *rectLB,
1399   BOOL        bRedraw)
1400 {
1401    BOOL	bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1402 
1403    /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1404     * sizing messages */
1405 
1406    if( lphc->wState & CBF_EDIT )
1407      SetWindowPos( lphc->hWndEdit, 0,
1408 		   rectEdit->left, rectEdit->top,
1409 		   rectEdit->right - rectEdit->left,
1410 		   rectEdit->bottom - rectEdit->top,
1411                        SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1412 
1413    SetWindowPos( lphc->hWndLBox, 0,
1414 		 rectLB->left, rectLB->top,
1415                  rectLB->right - rectLB->left,
1416 		 rectLB->bottom - rectLB->top,
1417 		   SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1418 
1419    if( bDrop )
1420    {
1421        if( lphc->wState & CBF_DROPPED )
1422        {
1423            lphc->wState &= ~CBF_DROPPED;
1424            ShowWindow( lphc->hWndLBox, SW_HIDE );
1425        }
1426 
1427        if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1428            RedrawWindow( lphc->self, NULL, 0,
1429                            RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1430    }
1431 }
1432 
1433 
1434 /***********************************************************************
1435  *           COMBO_Size
1436  */
1437 static void COMBO_Size( LPHEADCOMBO lphc )
1438 {
1439   /*
1440    * Those controls are always the same height. So we have to make sure
1441    * they are not resized to another value.
1442    */
1443   if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1444   {
1445     int newComboHeight, curComboHeight, curComboWidth;
1446     RECT rc;
1447 
1448     GetWindowRect(lphc->self, &rc);
1449     curComboHeight = rc.bottom - rc.top;
1450     curComboWidth = rc.right - rc.left;
1451     newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE();
1452 
1453     /*
1454      * Resizing a combobox has another side effect, it resizes the dropped
1455      * rectangle as well. However, it does it only if the new height for the
1456      * combobox is more than the height it should have. In other words,
1457      * if the application resizing the combobox only had the intention to resize
1458      * the actual control, for example, to do the layout of a dialog that is
1459      * resized, the height of the dropdown is not changed.
1460      */
1461     if( curComboHeight > newComboHeight )
1462     {
1463       TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1464             curComboHeight, newComboHeight, lphc->droppedRect.bottom,
1465             lphc->droppedRect.top);
1466       lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight;
1467     }
1468     /*
1469      * Restore original height
1470      */
1471     if( curComboHeight != newComboHeight )
1472       SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight,
1473             SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW);
1474   }
1475 
1476   CBCalcPlacement(lphc->self,
1477 		  lphc,
1478 		  &lphc->textRect,
1479 		  &lphc->buttonRect,
1480 		  &lphc->droppedRect);
1481 
1482   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1483 }
1484 
1485 
1486 /***********************************************************************
1487  *           COMBO_Font
1488  */
1489 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1490 {
1491   /*
1492    * Set the font
1493    */
1494   lphc->hFont = hFont;
1495 
1496   /*
1497    * Propagate to owned windows.
1498    */
1499   if( lphc->wState & CBF_EDIT )
1500       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1501   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1502 
1503   /*
1504    * Redo the layout of the control.
1505    */
1506   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1507   {
1508     CBCalcPlacement(lphc->self,
1509 		    lphc,
1510 		    &lphc->textRect,
1511 		    &lphc->buttonRect,
1512 		    &lphc->droppedRect);
1513 
1514     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1515   }
1516   else
1517   {
1518     CBForceDummyResize(lphc);
1519   }
1520 }
1521 
1522 
1523 /***********************************************************************
1524  *           COMBO_SetItemHeight
1525  */
1526 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1527 {
1528    LRESULT	lRet = CB_ERR;
1529 
1530    if( index == -1 ) /* set text field height */
1531    {
1532        if( height < 32768 )
1533        {
1534            lphc->editHeight = height + 2;  /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1535 
1536 	 /*
1537 	  * Redo the layout of the control.
1538 	  */
1539 	 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1540 	 {
1541 	   CBCalcPlacement(lphc->self,
1542 			   lphc,
1543 			   &lphc->textRect,
1544 			   &lphc->buttonRect,
1545 			   &lphc->droppedRect);
1546 
1547 	   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1548 	 }
1549 	 else
1550 	 {
1551 	   CBForceDummyResize(lphc);
1552 	 }
1553 
1554 	   lRet = height;
1555        }
1556    }
1557    else if ( CB_OWNERDRAWN(lphc) )	/* set listbox item height */
1558        lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1559    return lRet;
1560 }
1561 
1562 /***********************************************************************
1563  *           COMBO_SelectString
1564  */
1565 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText)
1566 {
1567    INT index = SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1568    if( index >= 0 )
1569    {
1570      if( lphc->wState & CBF_EDIT )
1571        CBUpdateEdit( lphc, index );
1572      else
1573      {
1574        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1575      }
1576    }
1577    return (LRESULT)index;
1578 }
1579 
1580 /***********************************************************************
1581  *           COMBO_LButtonDown
1582  */
1583 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1584 {
1585    POINT     pt;
1586    BOOL      bButton;
1587    HWND      hWnd = lphc->self;
1588 
1589    pt.x = (short)LOWORD(lParam);
1590    pt.y = (short)HIWORD(lParam);
1591    bButton = PtInRect(&lphc->buttonRect, pt);
1592 
1593    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1594        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1595    {
1596        lphc->wState |= CBF_BUTTONDOWN;
1597        if( lphc->wState & CBF_DROPPED )
1598        {
1599 	   /* got a click to cancel selection */
1600 
1601            lphc->wState &= ~CBF_BUTTONDOWN;
1602            CBRollUp( lphc, TRUE, FALSE );
1603 	   if( !IsWindow( hWnd ) ) return;
1604 
1605            if( lphc->wState & CBF_CAPTURE )
1606            {
1607                lphc->wState &= ~CBF_CAPTURE;
1608                ReleaseCapture();
1609            }
1610        }
1611        else
1612        {
1613 	   /* drop down the listbox and start tracking */
1614 
1615            lphc->wState |= CBF_CAPTURE;
1616            SetCapture( hWnd );
1617            CBDropDown( lphc );
1618        }
1619        if( bButton ) CBRepaintButton( lphc );
1620    }
1621 }
1622 
1623 /***********************************************************************
1624  *           COMBO_LButtonUp
1625  *
1626  * Release capture and stop tracking if needed.
1627  */
1628 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1629 {
1630    if( lphc->wState & CBF_CAPTURE )
1631    {
1632        lphc->wState &= ~CBF_CAPTURE;
1633        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1634        {
1635 	   INT index = CBUpdateLBox( lphc, TRUE );
1636 	   /* Update edit only if item is in the list */
1637 	   if(index >= 0)
1638 	   {
1639 	       lphc->wState |= CBF_NOLBSELECT;
1640 	       CBUpdateEdit( lphc, index );
1641 	       lphc->wState &= ~CBF_NOLBSELECT;
1642 	   }
1643        }
1644        ReleaseCapture();
1645        SetCapture(lphc->hWndLBox);
1646    }
1647 
1648    if( lphc->wState & CBF_BUTTONDOWN )
1649    {
1650        lphc->wState &= ~CBF_BUTTONDOWN;
1651        CBRepaintButton( lphc );
1652    }
1653 }
1654 
1655 /***********************************************************************
1656  *           COMBO_MouseMove
1657  *
1658  * Two things to do - track combo button and release capture when
1659  * pointer goes into the listbox.
1660  */
1661 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1662 {
1663    POINT  pt;
1664    RECT   lbRect;
1665 
1666    pt.x = (short)LOWORD(lParam);
1667    pt.y = (short)HIWORD(lParam);
1668 
1669    if( lphc->wState & CBF_BUTTONDOWN )
1670    {
1671      BOOL bButton;
1672 
1673      bButton = PtInRect(&lphc->buttonRect, pt);
1674 
1675      if( !bButton )
1676      {
1677        lphc->wState &= ~CBF_BUTTONDOWN;
1678        CBRepaintButton( lphc );
1679      }
1680    }
1681 
1682    GetClientRect( lphc->hWndLBox, &lbRect );
1683    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1684    if( PtInRect(&lbRect, pt) )
1685    {
1686        lphc->wState &= ~CBF_CAPTURE;
1687        ReleaseCapture();
1688        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1689 
1690        /* hand over pointer tracking */
1691        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1692    }
1693 }
1694 
1695 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1696 {
1697     if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1698         return FALSE;
1699 
1700     pcbi->rcItem = lphc->textRect;
1701     pcbi->rcButton = lphc->buttonRect;
1702     pcbi->stateButton = 0;
1703     if (lphc->wState & CBF_BUTTONDOWN)
1704         pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1705     if (IsRectEmpty(&lphc->buttonRect))
1706         pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1707     pcbi->hwndCombo = lphc->self;
1708     pcbi->hwndItem = lphc->hWndEdit;
1709     pcbi->hwndList = lphc->hWndLBox;
1710     return TRUE;
1711 }
1712 
1713 static LRESULT CALLBACK COMBO_WindowProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
1714 {
1715     HEADCOMBO *lphc = (HEADCOMBO *)GetWindowLongPtrW( hwnd, 0 );
1716     HTHEME theme;
1717 
1718     TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd, message, wParam, lParam );
1719 
1720     if (!IsWindow(hwnd)) return 0;
1721 
1722     if (lphc || message == WM_NCCREATE)
1723     switch(message)
1724     {
1725     case WM_NCCREATE:
1726     {
1727         LONG style = ((CREATESTRUCTW *)lParam)->style;
1728         return COMBO_NCCreate(hwnd, style);
1729     }
1730 
1731     case WM_NCDESTROY:
1732         COMBO_NCDestroy(lphc);
1733         break;/* -> DefWindowProc */
1734 
1735     case WM_CREATE:
1736     {
1737         HWND hwndParent;
1738         LONG style;
1739 
1740         hwndParent = ((CREATESTRUCTW *)lParam)->hwndParent;
1741         style = ((CREATESTRUCTW *)lParam)->style;
1742         return COMBO_Create(hwnd, lphc, hwndParent, style);
1743     }
1744 
1745     case WM_DESTROY:
1746         theme = GetWindowTheme( hwnd );
1747         CloseThemeData( theme );
1748         break;
1749 
1750     case WM_THEMECHANGED:
1751         theme = GetWindowTheme( hwnd );
1752         CloseThemeData( theme );
1753         OpenThemeData( hwnd, WC_COMBOBOXW );
1754         break;
1755 
1756     case WM_PRINTCLIENT:
1757     case WM_PAINT:
1758     {
1759         LRESULT ret = 0;
1760         PAINTSTRUCT ps;
1761         HDC hdc;
1762 
1763         hdc = wParam ? (HDC)wParam : BeginPaint(hwnd, &ps);
1764 
1765         if (hdc && !(lphc->wState & CBF_NOREDRAW))
1766         {
1767             HTHEME theme = GetWindowTheme(hwnd);
1768 
1769             if (theme)
1770                 ret = COMBO_ThemedPaint(theme, lphc, hdc);
1771             else
1772                 ret = COMBO_Paint(lphc, hdc);
1773         }
1774 
1775         if (!wParam)
1776             EndPaint(hwnd, &ps);
1777 
1778         return ret;
1779     }
1780     case WM_ERASEBKGND:
1781         /* do all painting in WM_PAINT like Windows does */
1782         return 1;
1783 
1784     case WM_GETDLGCODE:
1785     {
1786         LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1787         if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1788         {
1789             int vk = (int)((LPMSG)lParam)->wParam;
1790 
1791             if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1792                 result |= DLGC_WANTMESSAGE;
1793         }
1794         return  result;
1795     }
1796 
1797     case WM_SIZE:
1798         if (lphc->hWndLBox && !(lphc->wState & CBF_NORESIZE))
1799             COMBO_Size( lphc );
1800         return  TRUE;
1801 
1802     case WM_SETFONT:
1803         COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1804         return TRUE;
1805 
1806     case WM_GETFONT:
1807         return (LRESULT)lphc->hFont;
1808 
1809     case WM_SETFOCUS:
1810         if (lphc->wState & CBF_EDIT)
1811         {
1812             SetFocus( lphc->hWndEdit );
1813             /* The first time focus is received, select all the text */
1814             if (!(lphc->wState & CBF_BEENFOCUSED))
1815             {
1816                 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1817                 lphc->wState |= CBF_BEENFOCUSED;
1818             }
1819         }
1820         else
1821             COMBO_SetFocus( lphc );
1822         return  TRUE;
1823 
1824     case WM_KILLFOCUS:
1825     {
1826         HWND hwndFocus = (HWND)wParam;
1827         if (!hwndFocus || (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox))
1828             COMBO_KillFocus( lphc );
1829         return  TRUE;
1830     }
1831 
1832     case WM_COMMAND:
1833         return COMBO_Command( lphc, wParam, (HWND)lParam );
1834 
1835     case WM_GETTEXT:
1836         return COMBO_GetText( lphc, wParam, (LPWSTR)lParam );
1837 
1838     case WM_SETTEXT:
1839     case WM_GETTEXTLENGTH:
1840     case WM_CLEAR:
1841         if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1842         {
1843             int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1844             if (j == -1) return 0;
1845             return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1846         }
1847         else if ( lphc->wState & CBF_EDIT )
1848         {
1849             LRESULT ret;
1850             lphc->wState |= CBF_NOEDITNOTIFY;
1851             ret = SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1852             lphc->wState &= ~CBF_NOEDITNOTIFY;
1853             return ret;
1854         }
1855         else
1856             return CB_ERR;
1857 
1858     case WM_CUT:
1859     case WM_PASTE:
1860     case WM_COPY:
1861         if (lphc->wState & CBF_EDIT)
1862             return SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1863         else return  CB_ERR;
1864 
1865     case WM_DRAWITEM:
1866     case WM_DELETEITEM:
1867     case WM_COMPAREITEM:
1868     case WM_MEASUREITEM:
1869         return COMBO_ItemOp(lphc, message, lParam);
1870 
1871     case WM_ENABLE:
1872         if (lphc->wState & CBF_EDIT)
1873             EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1874         EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1875 
1876         /* Force the control to repaint when the enabled state changes. */
1877         InvalidateRect(lphc->self, NULL, TRUE);
1878         return  TRUE;
1879 
1880     case WM_SETREDRAW:
1881         if (wParam)
1882             lphc->wState &= ~CBF_NOREDRAW;
1883         else
1884             lphc->wState |= CBF_NOREDRAW;
1885 
1886         if ( lphc->wState & CBF_EDIT )
1887             SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1888         SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1889         return  0;
1890 
1891     case WM_SYSKEYDOWN:
1892         if ( KEYDATA_ALT & HIWORD(lParam) )
1893             if( wParam == VK_UP || wParam == VK_DOWN )
1894                 COMBO_FlipListbox( lphc, FALSE, FALSE );
1895         return  0;
1896 
1897     case WM_KEYDOWN:
1898         if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1899                 (lphc->wState & CBF_DROPPED))
1900         {
1901             CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1902             return TRUE;
1903         }
1904         else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1905         {
1906             COMBO_FlipListbox( lphc, FALSE, FALSE );
1907             return TRUE;
1908         }
1909         /* fall through */
1910     case WM_CHAR:
1911     case WM_IME_CHAR:
1912         {
1913             HWND hwndTarget;
1914 
1915             if ( lphc->wState & CBF_EDIT )
1916                 hwndTarget = lphc->hWndEdit;
1917             else
1918                 hwndTarget = lphc->hWndLBox;
1919 
1920            return SendMessageW(hwndTarget, message, wParam, lParam);
1921         }
1922 
1923     case WM_LBUTTONDOWN:
1924         if ( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1925         if ( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1926         return  TRUE;
1927 
1928     case WM_LBUTTONUP:
1929         COMBO_LButtonUp( lphc );
1930         return  TRUE;
1931 
1932     case WM_MOUSEMOVE:
1933         if (!IsRectEmpty(&lphc->buttonRect))
1934         {
1935             POINT pt;
1936 
1937             pt.x = (short)LOWORD(lParam);
1938             pt.y = (short)HIWORD(lParam);
1939 
1940             if (PtInRect(&lphc->buttonRect, pt))
1941             {
1942                 if (!(lphc->wState & CBF_HOT))
1943                 {
1944                     lphc->wState |= CBF_HOT;
1945                     RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW);
1946                 }
1947             }
1948             else if (lphc->wState & CBF_HOT)
1949             {
1950                 lphc->wState &= ~CBF_HOT;
1951                 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW);
1952             }
1953         }
1954 
1955         if ( lphc->wState & CBF_CAPTURE )
1956             COMBO_MouseMove( lphc, wParam, lParam );
1957         return  TRUE;
1958 
1959     case WM_MOUSEWHEEL:
1960         if (wParam & (MK_SHIFT | MK_CONTROL))
1961             return DefWindowProcW(hwnd, message, wParam, lParam);
1962 
1963         if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
1964         if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
1965         return TRUE;
1966 
1967     case WM_CTLCOLOR:
1968     case WM_CTLCOLORMSGBOX:
1969     case WM_CTLCOLOREDIT:
1970     case WM_CTLCOLORLISTBOX:
1971     case WM_CTLCOLORBTN:
1972     case WM_CTLCOLORDLG:
1973     case WM_CTLCOLORSCROLLBAR:
1974     case WM_CTLCOLORSTATIC:
1975         return SendMessageW(lphc->owner, message, wParam, lParam);
1976 
1977     /* Combo messages */
1978     case CB_ADDSTRING:
1979         if (lphc->dwStyle & CBS_LOWERCASE)
1980             CharLowerW((LPWSTR)lParam);
1981         else if (lphc->dwStyle & CBS_UPPERCASE)
1982             CharUpperW((LPWSTR)lParam);
1983         return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
1984 
1985     case CB_INSERTSTRING:
1986         if (lphc->dwStyle & CBS_LOWERCASE)
1987             CharLowerW((LPWSTR)lParam);
1988         else if (lphc->dwStyle & CBS_UPPERCASE)
1989             CharUpperW((LPWSTR)lParam);
1990         return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1991 
1992     case CB_DELETESTRING:
1993         return SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
1994 
1995     case CB_SELECTSTRING:
1996         return COMBO_SelectString(lphc, (INT)wParam, lParam);
1997 
1998     case CB_FINDSTRING:
1999         return SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2000 
2001     case CB_FINDSTRINGEXACT:
2002         return SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2003 
2004     case CB_SETITEMHEIGHT:
2005         return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2006 
2007     case CB_GETITEMHEIGHT:
2008         if ((INT)wParam >= 0) /* listbox item */
2009             return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2010         return  CBGetTextAreaHeight(hwnd, lphc);
2011 
2012     case CB_RESETCONTENT:
2013         SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2014 
2015         if ((lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc))
2016         {
2017             static const WCHAR empty_stringW[] = { 0 };
2018             SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2019         }
2020         else
2021             InvalidateRect(lphc->self, NULL, TRUE);
2022         return  TRUE;
2023 
2024     case CB_INITSTORAGE:
2025         return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2026 
2027     case CB_GETHORIZONTALEXTENT:
2028         return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2029 
2030     case CB_SETHORIZONTALEXTENT:
2031         return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2032 
2033     case CB_GETTOPINDEX:
2034         return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2035 
2036     case CB_GETLOCALE:
2037         return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2038 
2039     case CB_SETLOCALE:
2040         return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2041 
2042     case CB_SETDROPPEDWIDTH:
2043         if ((CB_GETTYPE(lphc) == CBS_SIMPLE) || (INT)wParam >= 32768)
2044             return CB_ERR;
2045 
2046         /* new value must be higher than combobox width */
2047         if ((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2048             lphc->droppedWidth = wParam;
2049         else if (wParam)
2050             lphc->droppedWidth = 0;
2051 
2052         /* recalculate the combobox area */
2053         CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
2054 
2055         /* fall through */
2056     case CB_GETDROPPEDWIDTH:
2057         if (lphc->droppedWidth)
2058             return lphc->droppedWidth;
2059         return  lphc->droppedRect.right - lphc->droppedRect.left;
2060 
2061     case CB_GETDROPPEDCONTROLRECT:
2062         if (lParam)
2063             CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2064         return  CB_OKAY;
2065 
2066     case CB_GETDROPPEDSTATE:
2067         return (lphc->wState & CBF_DROPPED) != 0;
2068 
2069     case CB_DIR:
2070         return SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam);
2071 
2072     case CB_SHOWDROPDOWN:
2073         if (CB_GETTYPE(lphc) != CBS_SIMPLE)
2074         {
2075             if (wParam)
2076             {
2077                 if (!(lphc->wState & CBF_DROPPED))
2078                     CBDropDown( lphc );
2079             }
2080             else if (lphc->wState & CBF_DROPPED)
2081                 CBRollUp( lphc, FALSE, TRUE );
2082         }
2083         return  TRUE;
2084 
2085     case CB_GETCOUNT:
2086         return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2087 
2088     case CB_GETCURSEL:
2089         return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2090 
2091     case CB_SETCURSEL:
2092         lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2093         if (lParam >= 0)
2094             SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2095 
2096         /* no LBN_SELCHANGE in this case, update manually */
2097         CBPaintText(lphc, NULL);
2098         lphc->wState &= ~CBF_SELCHANGE;
2099         return lParam;
2100 
2101     case CB_GETLBTEXT:
2102         return SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2103 
2104     case CB_GETLBTEXTLEN:
2105         return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2106 
2107     case CB_GETITEMDATA:
2108         return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2109 
2110     case CB_SETITEMDATA:
2111         return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2112 
2113     case CB_GETEDITSEL:
2114         /* Edit checks passed parameters itself */
2115         if (lphc->wState & CBF_EDIT)
2116             return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2117         return  CB_ERR;
2118 
2119     case CB_SETEDITSEL:
2120         if (lphc->wState & CBF_EDIT)
2121             return SendMessageW(lphc->hWndEdit, EM_SETSEL, (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2122         return  CB_ERR;
2123 
2124     case CB_SETEXTENDEDUI:
2125         if (CB_GETTYPE(lphc) == CBS_SIMPLE )
2126             return  CB_ERR;
2127         if (wParam)
2128             lphc->wState |= CBF_EUI;
2129         else
2130             lphc->wState &= ~CBF_EUI;
2131         return  CB_OKAY;
2132 
2133     case CB_GETEXTENDEDUI:
2134         return (lphc->wState & CBF_EUI) != 0;
2135 
2136     case CB_GETCOMBOBOXINFO:
2137         return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2138 
2139     case CB_LIMITTEXT:
2140         if (lphc->wState & CBF_EDIT)
2141             return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2142         return  TRUE;
2143 
2144     case CB_GETMINVISIBLE:
2145         return lphc->visibleItems;
2146 
2147     case CB_SETMINVISIBLE:
2148         lphc->visibleItems = (INT)wParam;
2149         return TRUE;
2150 
2151     default:
2152         if (message >= WM_USER)
2153             WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message - WM_USER, wParam, lParam );
2154         break;
2155     }
2156 
2157     return DefWindowProcW(hwnd, message, wParam, lParam);
2158 }
2159 
2160 void COMBO_Register(void)
2161 {
2162     WNDCLASSW wndClass;
2163 
2164     memset(&wndClass, 0, sizeof(wndClass));
2165     wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
2166     wndClass.lpfnWndProc = COMBO_WindowProc;
2167     wndClass.cbClsExtra = 0;
2168     wndClass.cbWndExtra = sizeof(HEADCOMBO *);
2169     wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2170     wndClass.hbrBackground = NULL;
2171     wndClass.lpszClassName = WC_COMBOBOXW;
2172     RegisterClassW(&wndClass);
2173 }
2174 
2175 #ifdef __REACTOS__
2176 void COMBO_Unregister(void)
2177 {
2178     UnregisterClassW(WC_COMBOBOXW, NULL);
2179 }
2180 #endif
2181