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