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