user32: Use window height/width instead of client for combo resizing.
[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_Create
457  */
458 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style,
459                              BOOL unicode )
460 {
461   static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
462   static const WCHAR editName[] = {'E','d','i','t',0};
463
464   if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
465   if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
466
467   lphc->owner = hwndParent;
468
469   /*
470    * The item height and dropped width are not set when the control
471    * is created.
472    */
473   lphc->droppedWidth = lphc->editHeight = 0;
474
475   /*
476    * The first time we go through, we want to measure the ownerdraw item
477    */
478   lphc->wState |= CBF_MEASUREITEM;
479
480   /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
481
482   if( lphc->owner || !(style & WS_VISIBLE) )
483   {
484       UINT lbeStyle   = 0;
485       UINT lbeExStyle = 0;
486
487       /*
488        * Initialize the dropped rect to the size of the client area of the
489        * control and then, force all the areas of the combobox to be
490        * recalculated.
491        */
492       GetClientRect( hwnd, &lphc->droppedRect );
493       CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
494
495       /*
496        * Adjust the position of the popup listbox if it's necessary
497        */
498       if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
499       {
500         lphc->droppedRect.top   = lphc->textRect.bottom + COMBO_YBORDERSIZE();
501
502         /*
503          * If it's a dropdown, the listbox is offset
504          */
505         if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
506           lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
507
508         if (lphc->droppedRect.bottom < lphc->droppedRect.top)
509             lphc->droppedRect.bottom = lphc->droppedRect.top;
510         if (lphc->droppedRect.right < lphc->droppedRect.left)
511             lphc->droppedRect.right = lphc->droppedRect.left;
512         MapWindowPoints( hwnd, 0, (LPPOINT)&lphc->droppedRect, 2 );
513       }
514
515       /* create listbox popup */
516
517       lbeStyle = (LBS_NOTIFY | LBS_COMBOBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
518                  (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
519
520       if( lphc->dwStyle & CBS_SORT )
521         lbeStyle |= LBS_SORT;
522       if( lphc->dwStyle & CBS_HASSTRINGS )
523         lbeStyle |= LBS_HASSTRINGS;
524       if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
525         lbeStyle |= LBS_NOINTEGRALHEIGHT;
526       if( lphc->dwStyle & CBS_DISABLENOSCROLL )
527         lbeStyle |= LBS_DISABLENOSCROLL;
528
529       if( CB_GETTYPE(lphc) == CBS_SIMPLE )      /* child listbox */
530       {
531         lbeStyle |= WS_VISIBLE;
532
533         /*
534          * In win 95 look n feel, the listbox in the simple combobox has
535          * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
536          */
537         lbeStyle   &= ~WS_BORDER;
538         lbeExStyle |= WS_EX_CLIENTEDGE;
539       }
540       else
541       {
542         lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
543       }
544
545       if (unicode)
546           lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
547                                            lphc->droppedRect.left,
548                                            lphc->droppedRect.top,
549                                            lphc->droppedRect.right - lphc->droppedRect.left,
550                                            lphc->droppedRect.bottom - lphc->droppedRect.top,
551                                            hwnd, (HMENU)ID_CB_LISTBOX,
552                                            (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
553       else
554           lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle,
555                                            lphc->droppedRect.left,
556                                            lphc->droppedRect.top,
557                                            lphc->droppedRect.right - lphc->droppedRect.left,
558                                            lphc->droppedRect.bottom - lphc->droppedRect.top,
559                                            hwnd, (HMENU)ID_CB_LISTBOX,
560                                            (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
561
562       if( lphc->hWndLBox )
563       {
564           BOOL  bEdit = TRUE;
565           lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
566
567           if( lphc->wState & CBF_EDIT )
568           {
569               if( lphc->dwStyle & CBS_OEMCONVERT )
570                   lbeStyle |= ES_OEMCONVERT;
571               if( lphc->dwStyle & CBS_AUTOHSCROLL )
572                   lbeStyle |= ES_AUTOHSCROLL;
573               if( lphc->dwStyle & CBS_LOWERCASE )
574                   lbeStyle |= ES_LOWERCASE;
575               else if( lphc->dwStyle & CBS_UPPERCASE )
576                   lbeStyle |= ES_UPPERCASE;
577
578               if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
579
580               if (unicode)
581                   lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
582                                                    lphc->textRect.left, lphc->textRect.top,
583                                                    lphc->textRect.right - lphc->textRect.left,
584                                                    lphc->textRect.bottom - lphc->textRect.top,
585                                                    hwnd, (HMENU)ID_CB_EDIT,
586                                                    (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
587               else
588                   lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle,
589                                                    lphc->textRect.left, lphc->textRect.top,
590                                                    lphc->textRect.right - lphc->textRect.left,
591                                                    lphc->textRect.bottom - lphc->textRect.top,
592                                                    hwnd, (HMENU)ID_CB_EDIT,
593                                                    (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
594
595               if( !lphc->hWndEdit )
596                 bEdit = FALSE;
597           }
598
599           if( bEdit )
600           {
601             if( CB_GETTYPE(lphc) != CBS_SIMPLE )
602             {
603               /* Now do the trick with parent */
604               SetParent(lphc->hWndLBox, HWND_DESKTOP);
605               /*
606                * If the combo is a dropdown, we must resize the control
607                * to fit only the text area and button. To do this,
608                * we send a dummy resize and the WM_WINDOWPOSCHANGING message
609                * will take care of setting the height for us.
610                */
611               CBForceDummyResize(lphc);
612             }
613
614             TRACE("init done\n");
615             return 0;
616           }
617           ERR("edit control failure.\n");
618       } else ERR("listbox failure.\n");
619   } else ERR("no owner for visible combo.\n");
620
621   /* CreateWindow() will send WM_NCDESTROY to cleanup */
622
623   return -1;
624 }
625
626 /***********************************************************************
627  *           CBPaintButton
628  *
629  * Paint combo button (normal, pressed, and disabled states).
630  */
631 static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton)
632 {
633     UINT buttonState = DFCS_SCROLLCOMBOBOX;
634
635     if( lphc->wState & CBF_NOREDRAW )
636       return;
637
638
639     if (lphc->wState & CBF_BUTTONDOWN)
640         buttonState |= DFCS_PUSHED;
641
642     if (CB_DISABLED(lphc))
643         buttonState |= DFCS_INACTIVE;
644
645     DrawFrameControl(hdc, &rectButton, DFC_SCROLL, buttonState);
646 }
647
648 /***********************************************************************
649  *           CBPaintText
650  *
651  * Paint CBS_DROPDOWNLIST text field / update edit control contents.
652  */
653 static void CBPaintText(
654   LPHEADCOMBO lphc,
655   HDC         hdc,
656   RECT        rectEdit)
657 {
658    INT  id, size = 0;
659    LPWSTR pText = NULL;
660
661    if( lphc->wState & CBF_NOREDRAW ) return;
662
663    TRACE("\n");
664
665    /* follow Windows combobox that sends a bunch of text
666     * inquiries to its listbox while processing WM_PAINT. */
667
668    if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
669    {
670         size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
671         if (size == LB_ERR)
672           FIXME("LB_ERR probably not handled yet\n");
673         if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
674         {
675             /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
676            size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, id, (LPARAM)pText);
677             pText[size] = '\0'; /* just in case */
678         } else return;
679    }
680    else
681        if( !CB_OWNERDRAWN(lphc) )
682            return;
683
684    if( lphc->wState & CBF_EDIT )
685    {
686         static const WCHAR empty_stringW[] = { 0 };
687         if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
688         if( lphc->wState & CBF_FOCUSED )
689            SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
690    }
691    else /* paint text field ourselves */
692    {
693      UINT       itemState = ODS_COMBOBOXEDIT;
694      HFONT      hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
695
696      /*
697       * Give ourselves some space.
698       */
699      InflateRect( &rectEdit, -1, -1 );
700
701      if( CB_OWNERDRAWN(lphc) )
702      {
703        DRAWITEMSTRUCT dis;
704        HRGN           clipRegion;
705        UINT ctlid = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
706
707        /* setup state for DRAWITEM message. Owner will highlight */
708        if ( (lphc->wState & CBF_FOCUSED) &&
709             !(lphc->wState & CBF_DROPPED) )
710            itemState |= ODS_SELECTED | ODS_FOCUS;
711
712        if (!IsWindowEnabled(lphc->self)) itemState |= ODS_DISABLED;
713
714        dis.CtlType      = ODT_COMBOBOX;
715        dis.CtlID        = ctlid;
716        dis.hwndItem     = lphc->self;
717        dis.itemAction   = ODA_DRAWENTIRE;
718        dis.itemID       = id;
719        dis.itemState    = itemState;
720        dis.hDC          = hdc;
721        dis.rcItem       = rectEdit;
722        dis.itemData     = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, id, 0);
723
724        /*
725         * Clip the DC and have the parent draw the item.
726         */
727        clipRegion = set_control_clipping( hdc, &rectEdit );
728
729        SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
730
731        SelectClipRgn( hdc, clipRegion );
732        if (clipRegion) DeleteObject( clipRegion );
733      }
734      else
735      {
736        static const WCHAR empty_stringW[] = { 0 };
737
738        if ( (lphc->wState & CBF_FOCUSED) &&
739             !(lphc->wState & CBF_DROPPED) ) {
740
741            /* highlight */
742            FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
743            SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
744            SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
745        }
746
747        ExtTextOutW( hdc,
748                     rectEdit.left + 1,
749                     rectEdit.top + 1,
750                     ETO_OPAQUE | ETO_CLIPPED,
751                     &rectEdit,
752                     pText ? pText : empty_stringW , size, NULL );
753
754        if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
755          DrawFocusRect( hdc, &rectEdit );
756      }
757
758      if( hPrevFont )
759        SelectObject(hdc, hPrevFont );
760    }
761    HeapFree( GetProcessHeap(), 0, pText );
762 }
763
764 /***********************************************************************
765  *           CBPaintBorder
766  */
767 static void CBPaintBorder(
768   HWND            hwnd,
769   const HEADCOMBO *lphc,
770   HDC             hdc)
771 {
772   RECT clientRect;
773
774   if (CB_GETTYPE(lphc) != CBS_SIMPLE)
775   {
776     GetClientRect(hwnd, &clientRect);
777   }
778   else
779   {
780     CopyRect(&clientRect, &lphc->textRect);
781
782     InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
783     InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
784   }
785
786   DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
787 }
788
789 /***********************************************************************
790  *           COMBO_PrepareColors
791  *
792  * This method will sent the appropriate WM_CTLCOLOR message to
793  * prepare and setup the colors for the combo's DC.
794  *
795  * It also returns the brush to use for the background.
796  */
797 static HBRUSH COMBO_PrepareColors(
798   LPHEADCOMBO lphc,
799   HDC         hDC)
800 {
801   HBRUSH  hBkgBrush;
802
803   /*
804    * Get the background brush for this control.
805    */
806   if (CB_DISABLED(lphc))
807   {
808     hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
809                                      (WPARAM)hDC, (LPARAM)lphc->self );
810
811     /*
812      * We have to change the text color since WM_CTLCOLORSTATIC will
813      * set it to the "enabled" color. This is the same behavior as the
814      * edit control
815      */
816     SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
817   }
818   else
819   {
820       /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
821       hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
822                                        (WPARAM)hDC, (LPARAM)lphc->self );
823   }
824
825   /*
826    * Catch errors.
827    */
828   if( !hBkgBrush )
829     hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
830
831   return hBkgBrush;
832 }
833
834
835 /***********************************************************************
836  *           COMBO_Paint
837  */
838 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
839 {
840   PAINTSTRUCT ps;
841   HDC   hDC;
842
843   hDC = (hParamDC) ? hParamDC
844                    : BeginPaint( lphc->self, &ps);
845
846   TRACE("hdc=%p\n", hDC);
847
848   if( hDC && !(lphc->wState & CBF_NOREDRAW) )
849   {
850       HBRUSH    hPrevBrush, hBkgBrush;
851
852       /*
853        * Retrieve the background brush and select it in the
854        * DC.
855        */
856       hBkgBrush = COMBO_PrepareColors(lphc, hDC);
857
858       hPrevBrush = SelectObject( hDC, hBkgBrush );
859       if (!(lphc->wState & CBF_EDIT))
860         FillRect(hDC, &lphc->textRect, hBkgBrush);
861
862       /*
863        * In non 3.1 look, there is a sunken border on the combobox
864        */
865       CBPaintBorder(lphc->self, lphc, hDC);
866
867       if( !IsRectEmpty(&lphc->buttonRect) )
868       {
869         CBPaintButton(lphc, hDC, lphc->buttonRect);
870       }
871
872       /* paint the edit control padding area */
873       if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
874       {
875           RECT rPadEdit = lphc->textRect;
876
877           InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
878
879           FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
880       }
881
882       if( !(lphc->wState & CBF_EDIT) )
883         CBPaintText( lphc, hDC, lphc->textRect);
884
885       if( hPrevBrush )
886         SelectObject( hDC, hPrevBrush );
887   }
888
889   if( !hParamDC )
890     EndPaint(lphc->self, &ps);
891
892   return 0;
893 }
894
895 /***********************************************************************
896  *           CBUpdateLBox
897  *
898  * Select listbox entry according to the contents of the edit control.
899  */
900 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
901 {
902    INT  length, idx;
903    LPWSTR pText = NULL;
904
905    idx = LB_ERR;
906    length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
907
908    if( length > 0 )
909        pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
910
911    TRACE("\t edit text length %i\n", length );
912
913    if( pText )
914    {
915        GetWindowTextW( lphc->hWndEdit, pText, length + 1);
916        idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, -1, (LPARAM)pText);
917        HeapFree( GetProcessHeap(), 0, pText );
918    }
919
920    SendMessageW(lphc->hWndLBox, LB_SETCURSEL, bSelect ? idx : -1, 0);
921
922    /* probably superfluous but Windows sends this too */
923    SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, idx < 0 ? 0 : idx, 0);
924    SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, idx < 0 ? 0 : idx, 0);
925
926    return idx;
927 }
928
929 /***********************************************************************
930  *           CBUpdateEdit
931  *
932  * Copy a listbox entry to the edit control.
933  */
934 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
935 {
936    INT  length;
937    LPWSTR pText = NULL;
938    static const WCHAR empty_stringW[] = { 0 };
939
940    TRACE("\t %i\n", index );
941
942    if( index >= 0 ) /* got an entry */
943    {
944        length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, index, 0);
945        if( length != LB_ERR)
946        {
947            if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
948            {
949                SendMessageW(lphc->hWndLBox, LB_GETTEXT, index, (LPARAM)pText);
950            }
951        }
952    }
953
954    if( CB_HASSTRINGS(lphc) )
955    {
956       lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
957       SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
958       lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
959    }
960
961    if( lphc->wState & CBF_FOCUSED )
962       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
963
964    HeapFree( GetProcessHeap(), 0, pText );
965 }
966
967 /***********************************************************************
968  *           CBDropDown
969  *
970  * Show listbox popup.
971  */
972 static void CBDropDown( LPHEADCOMBO lphc )
973 {
974     HMONITOR monitor;
975     MONITORINFO mon_info;
976    RECT rect,r;
977    int nItems = 0;
978    int nDroppedHeight;
979
980    TRACE("[%p]: drop down\n", lphc->self);
981
982    CB_NOTIFY( lphc, CBN_DROPDOWN );
983
984    /* set selection */
985
986    lphc->wState |= CBF_DROPPED;
987    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
988    {
989        lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
990
991        /* Update edit only if item is in the list */
992        if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
993          CBUpdateEdit( lphc, lphc->droppedIndex );
994    }
995    else
996    {
997        lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
998
999        SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1000                     lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex, 0);
1001        SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1002    }
1003
1004    /* now set popup position */
1005    GetWindowRect( lphc->self, &rect );
1006
1007    /*
1008     * If it's a dropdown, the listbox is offset
1009     */
1010    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1011      rect.left += COMBO_EDITBUTTONSPACE();
1012
1013   /* if the dropped height is greater than the total height of the dropped
1014      items list, then force the drop down list height to be the total height
1015      of the items in the dropped list */
1016
1017   /* And Remove any extra space (Best Fit) */
1018    nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1019   /* if listbox length has been set directly by its handle */
1020    GetWindowRect(lphc->hWndLBox, &r);
1021    if (nDroppedHeight < r.bottom - r.top)
1022        nDroppedHeight = r.bottom - r.top;
1023    nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1024
1025    if (nItems > 0)
1026    {
1027       int nHeight;
1028       int nIHeight;
1029
1030       nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1031
1032       nHeight = nIHeight*nItems;
1033
1034       if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1035          nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1036
1037       if (nDroppedHeight < nHeight)
1038       {
1039             if (nItems < 5)
1040                 nDroppedHeight = (nItems+1)*nIHeight;
1041             else if (nDroppedHeight < 6*nIHeight)
1042                 nDroppedHeight = 6*nIHeight;
1043       }
1044    }
1045
1046    /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1047    monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1048    mon_info.cbSize = sizeof(mon_info);
1049    GetMonitorInfoW( monitor, &mon_info );
1050
1051    if( (rect.bottom + nDroppedHeight) >= mon_info.rcWork.bottom )
1052       rect.bottom = rect.top - nDroppedHeight;
1053
1054    SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1055                  lphc->droppedRect.right - lphc->droppedRect.left,
1056                  nDroppedHeight,
1057                  SWP_NOACTIVATE | SWP_SHOWWINDOW);
1058
1059
1060    if( !(lphc->wState & CBF_NOREDRAW) )
1061      RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1062                            RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1063
1064    EnableWindow( lphc->hWndLBox, TRUE );
1065    if (GetCapture() != lphc->self)
1066       SetCapture(lphc->hWndLBox);
1067 }
1068
1069 /***********************************************************************
1070  *           CBRollUp
1071  *
1072  * Hide listbox popup.
1073  */
1074 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1075 {
1076    HWND hWnd = lphc->self;
1077
1078    TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1079          lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1080
1081    CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1082
1083    if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1084    {
1085
1086        if( lphc->wState & CBF_DROPPED )
1087        {
1088            RECT rect;
1089
1090            lphc->wState &= ~CBF_DROPPED;
1091            ShowWindow( lphc->hWndLBox, SW_HIDE );
1092
1093            if(GetCapture() == lphc->hWndLBox)
1094            {
1095                ReleaseCapture();
1096            }
1097
1098            if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1099            {
1100                rect = lphc->buttonRect;
1101            }
1102            else
1103            {
1104                if( bButton )
1105                {
1106                  UnionRect( &rect,
1107                             &lphc->buttonRect,
1108                             &lphc->textRect);
1109                }
1110                else
1111                  rect = lphc->textRect;
1112
1113                bButton = TRUE;
1114            }
1115
1116            if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1117                RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1118                                RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1119            CB_NOTIFY( lphc, CBN_CLOSEUP );
1120        }
1121    }
1122 }
1123
1124 /***********************************************************************
1125  *           COMBO_FlipListbox
1126  *
1127  * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1128  */
1129 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1130 {
1131    if( lphc->wState & CBF_DROPPED )
1132    {
1133        CBRollUp( lphc, ok, bRedrawButton );
1134        return FALSE;
1135    }
1136
1137    CBDropDown( lphc );
1138    return TRUE;
1139 }
1140
1141 /***********************************************************************
1142  *           CBRepaintButton
1143  */
1144 static void CBRepaintButton( LPHEADCOMBO lphc )
1145    {
1146   InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1147   UpdateWindow(lphc->self);
1148 }
1149
1150 /***********************************************************************
1151  *           COMBO_SetFocus
1152  */
1153 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1154 {
1155    if( !(lphc->wState & CBF_FOCUSED) )
1156    {
1157        if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1158            SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1159
1160        /* This is wrong. Message sequences seem to indicate that this
1161           is set *after* the notify. */
1162        /* lphc->wState |= CBF_FOCUSED;  */
1163
1164        if( !(lphc->wState & CBF_EDIT) )
1165          InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1166
1167        CB_NOTIFY( lphc, CBN_SETFOCUS );
1168        lphc->wState |= CBF_FOCUSED;
1169    }
1170 }
1171
1172 /***********************************************************************
1173  *           COMBO_KillFocus
1174  */
1175 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1176 {
1177    HWND hWnd = lphc->self;
1178
1179    if( lphc->wState & CBF_FOCUSED )
1180    {
1181        CBRollUp( lphc, FALSE, TRUE );
1182        if( IsWindow( hWnd ) )
1183        {
1184            if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1185                SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1186
1187            lphc->wState &= ~CBF_FOCUSED;
1188
1189            /* redraw text */
1190            if( !(lphc->wState & CBF_EDIT) )
1191              InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1192
1193            CB_NOTIFY( lphc, CBN_KILLFOCUS );
1194        }
1195    }
1196 }
1197
1198 /***********************************************************************
1199  *           COMBO_Command
1200  */
1201 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1202 {
1203    if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1204    {
1205        /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1206
1207        switch( HIWORD(wParam) >> 8 )
1208        {
1209            case (EN_SETFOCUS >> 8):
1210
1211                TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1212
1213                 COMBO_SetFocus( lphc );
1214                 break;
1215
1216            case (EN_KILLFOCUS >> 8):
1217
1218                TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1219
1220                 /* NOTE: it seems that Windows' edit control sends an
1221                  * undocumented message WM_USER + 0x1B instead of this
1222                  * notification (only when it happens to be a part of
1223                  * the combo). ?? - AK.
1224                  */
1225
1226                 COMBO_KillFocus( lphc );
1227                 break;
1228
1229
1230            case (EN_CHANGE >> 8):
1231                /*
1232                 * In some circumstances (when the selection of the combobox
1233                 * is changed for example) we don't want the EN_CHANGE notification
1234                 * to be forwarded to the parent of the combobox. This code
1235                 * checks a flag that is set in these occasions and ignores the
1236                 * notification.
1237                 */
1238                 if (lphc->wState & CBF_NOLBSELECT)
1239                 {
1240                   lphc->wState &= ~CBF_NOLBSELECT;
1241                 }
1242                 else
1243                 {
1244                   CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1245                 }
1246
1247                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1248                   CB_NOTIFY( lphc, CBN_EDITCHANGE );
1249                 break;
1250
1251            case (EN_UPDATE >> 8):
1252                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1253                   CB_NOTIFY( lphc, CBN_EDITUPDATE );
1254                 break;
1255
1256            case (EN_ERRSPACE >> 8):
1257                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1258        }
1259    }
1260    else if( lphc->hWndLBox == hWnd )
1261    {
1262        switch( (short)HIWORD(wParam) )
1263        {
1264            case LBN_ERRSPACE:
1265                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1266                 break;
1267
1268            case LBN_DBLCLK:
1269                 CB_NOTIFY( lphc, CBN_DBLCLK );
1270                 break;
1271
1272            case LBN_SELCHANGE:
1273            case LBN_SELCANCEL:
1274
1275                 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1276
1277                 /* do not roll up if selection is being tracked
1278                  * by arrow keys in the dropdown listbox */
1279                 if (!(lphc->wState & CBF_NOROLLUP))
1280                 {
1281                     CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1282                 }
1283                 else lphc->wState &= ~CBF_NOROLLUP;
1284
1285                 CB_NOTIFY( lphc, CBN_SELCHANGE );
1286
1287                 if( HIWORD(wParam) == LBN_SELCHANGE)
1288                 {
1289                    if( lphc->wState & CBF_EDIT )
1290                    {
1291                        INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1292                        lphc->wState |= CBF_NOLBSELECT;
1293                        CBUpdateEdit( lphc, index );
1294                        /* select text in edit, as Windows does */
1295                       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1296                    }
1297                    else
1298                    {
1299                        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1300                        UpdateWindow(lphc->self);
1301                    }
1302                 }
1303                 break;
1304
1305            case LBN_SETFOCUS:
1306            case LBN_KILLFOCUS:
1307                 /* nothing to do here since ComboLBox always resets the focus to its
1308                  * combo/edit counterpart */
1309                  break;
1310        }
1311    }
1312    return 0;
1313 }
1314
1315 /***********************************************************************
1316  *           COMBO_ItemOp
1317  *
1318  * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1319  */
1320 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1321 {
1322    HWND hWnd = lphc->self;
1323    UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1324
1325    TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1326
1327    switch( msg )
1328    {
1329    case WM_DELETEITEM:
1330        {
1331            DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1332            lpIS->CtlType  = ODT_COMBOBOX;
1333            lpIS->CtlID    = id;
1334            lpIS->hwndItem = hWnd;
1335            break;
1336        }
1337    case WM_DRAWITEM:
1338        {
1339            DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1340            lpIS->CtlType  = ODT_COMBOBOX;
1341            lpIS->CtlID    = id;
1342            lpIS->hwndItem = hWnd;
1343            break;
1344        }
1345    case WM_COMPAREITEM:
1346        {
1347            COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1348            lpIS->CtlType  = ODT_COMBOBOX;
1349            lpIS->CtlID    = id;
1350            lpIS->hwndItem = hWnd;
1351            break;
1352        }
1353    case WM_MEASUREITEM:
1354        {
1355            MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1356            lpIS->CtlType  = ODT_COMBOBOX;
1357            lpIS->CtlID    = id;
1358            break;
1359        }
1360    }
1361    return SendMessageW(lphc->owner, msg, id, lParam);
1362 }
1363
1364
1365 /***********************************************************************
1366  *           COMBO_GetTextW
1367  */
1368 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1369 {
1370     INT length;
1371
1372     if( lphc->wState & CBF_EDIT )
1373         return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1374
1375     /* get it from the listbox */
1376
1377     if (!count || !buf) return 0;
1378     if( lphc->hWndLBox )
1379     {
1380         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1381         if (idx == LB_ERR) goto error;
1382         length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1383         if (length == LB_ERR) goto error;
1384
1385         /* 'length' is without the terminating character */
1386         if (length >= count)
1387         {
1388             LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1389             if (!lpBuffer) goto error;
1390             length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1391
1392             /* truncate if buffer is too short */
1393             if (length != LB_ERR)
1394             {
1395                 lstrcpynW( buf, lpBuffer, count );
1396                 length = count;
1397             }
1398             HeapFree( GetProcessHeap(), 0, lpBuffer );
1399         }
1400         else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1401
1402         if (length == LB_ERR) return 0;
1403         return length;
1404     }
1405
1406  error:  /* error - truncate string, return zero */
1407     buf[0] = 0;
1408     return 0;
1409 }
1410
1411
1412 /***********************************************************************
1413  *           COMBO_GetTextA
1414  *
1415  * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1416  *       also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1417  */
1418 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1419 {
1420     INT length;
1421
1422     if( lphc->wState & CBF_EDIT )
1423         return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1424
1425     /* get it from the listbox */
1426
1427     if (!count || !buf) return 0;
1428     if( lphc->hWndLBox )
1429     {
1430         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1431         if (idx == LB_ERR) goto error;
1432         length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1433         if (length == LB_ERR) goto error;
1434
1435         /* 'length' is without the terminating character */
1436         if (length >= count)
1437         {
1438             LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1439             if (!lpBuffer) goto error;
1440             length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1441
1442             /* truncate if buffer is too short */
1443             if (length != LB_ERR)
1444             {
1445                 lstrcpynA( buf, lpBuffer, count );
1446                 length = count;
1447             }
1448             HeapFree( GetProcessHeap(), 0, lpBuffer );
1449         }
1450         else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1451
1452         if (length == LB_ERR) return 0;
1453         return length;
1454     }
1455
1456  error:  /* error - truncate string, return zero */
1457     buf[0] = 0;
1458     return 0;
1459 }
1460
1461
1462 /***********************************************************************
1463  *           CBResetPos
1464  *
1465  * This function sets window positions according to the updated
1466  * component placement struct.
1467  */
1468 static void CBResetPos(
1469   LPHEADCOMBO lphc,
1470   const RECT  *rectEdit,
1471   const RECT  *rectLB,
1472   BOOL        bRedraw)
1473 {
1474    BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1475
1476    /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1477     * sizing messages */
1478
1479    if( lphc->wState & CBF_EDIT )
1480      SetWindowPos( lphc->hWndEdit, 0,
1481                    rectEdit->left, rectEdit->top,
1482                    rectEdit->right - rectEdit->left,
1483                    rectEdit->bottom - rectEdit->top,
1484                        SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1485
1486    SetWindowPos( lphc->hWndLBox, 0,
1487                  rectLB->left, rectLB->top,
1488                  rectLB->right - rectLB->left,
1489                  rectLB->bottom - rectLB->top,
1490                    SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1491
1492    if( bDrop )
1493    {
1494        if( lphc->wState & CBF_DROPPED )
1495        {
1496            lphc->wState &= ~CBF_DROPPED;
1497            ShowWindow( lphc->hWndLBox, SW_HIDE );
1498        }
1499
1500        if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1501            RedrawWindow( lphc->self, NULL, 0,
1502                            RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1503    }
1504 }
1505
1506
1507 /***********************************************************************
1508  *           COMBO_Size
1509  */
1510 static void COMBO_Size( LPHEADCOMBO lphc )
1511 {
1512   /*
1513    * Those controls are always the same height. So we have to make sure
1514    * they are not resized to another value.
1515    */
1516   if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1517   {
1518     int newComboHeight, curComboHeight, curComboWidth;
1519     RECT rc;
1520
1521     GetWindowRect(lphc->self, &rc);
1522     curComboHeight = rc.bottom - rc.top;
1523     curComboWidth = rc.right - rc.left;
1524     newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE();
1525
1526     /*
1527      * Resizing a combobox has another side effect, it resizes the dropped
1528      * rectangle as well. However, it does it only if the new height for the
1529      * combobox is more than the height it should have. In other words,
1530      * if the application resizing the combobox only had the intention to resize
1531      * the actual control, for example, to do the layout of a dialog that is
1532      * resized, the height of the dropdown is not changed.
1533      */
1534     if( curComboHeight > newComboHeight )
1535     {
1536       TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1537             curComboHeight, newComboHeight, lphc->droppedRect.bottom,
1538             lphc->droppedRect.top);
1539       lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight;
1540     }
1541     /*
1542      * Restore original height
1543      */
1544     if( curComboHeight != newComboHeight )
1545       SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight,
1546             SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW);
1547   }
1548
1549   CBCalcPlacement(lphc->self,
1550                   lphc,
1551                   &lphc->textRect,
1552                   &lphc->buttonRect,
1553                   &lphc->droppedRect);
1554
1555   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1556 }
1557
1558
1559 /***********************************************************************
1560  *           COMBO_Font
1561  */
1562 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1563 {
1564   /*
1565    * Set the font
1566    */
1567   lphc->hFont = hFont;
1568
1569   /*
1570    * Propagate to owned windows.
1571    */
1572   if( lphc->wState & CBF_EDIT )
1573       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1574   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1575
1576   /*
1577    * Redo the layout of the control.
1578    */
1579   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1580   {
1581     CBCalcPlacement(lphc->self,
1582                     lphc,
1583                     &lphc->textRect,
1584                     &lphc->buttonRect,
1585                     &lphc->droppedRect);
1586
1587     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1588   }
1589   else
1590   {
1591     CBForceDummyResize(lphc);
1592   }
1593 }
1594
1595
1596 /***********************************************************************
1597  *           COMBO_SetItemHeight
1598  */
1599 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1600 {
1601    LRESULT      lRet = CB_ERR;
1602
1603    if( index == -1 ) /* set text field height */
1604    {
1605        if( height < 32768 )
1606        {
1607            lphc->editHeight = height + 2;  /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1608
1609          /*
1610           * Redo the layout of the control.
1611           */
1612          if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1613          {
1614            CBCalcPlacement(lphc->self,
1615                            lphc,
1616                            &lphc->textRect,
1617                            &lphc->buttonRect,
1618                            &lphc->droppedRect);
1619
1620            CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1621          }
1622          else
1623          {
1624            CBForceDummyResize(lphc);
1625          }
1626
1627            lRet = height;
1628        }
1629    }
1630    else if ( CB_OWNERDRAWN(lphc) )      /* set listbox item height */
1631        lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1632    return lRet;
1633 }
1634
1635 /***********************************************************************
1636  *           COMBO_SelectString
1637  */
1638 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1639 {
1640    INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText) :
1641                          SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1642    if( index >= 0 )
1643    {
1644      if( lphc->wState & CBF_EDIT )
1645        CBUpdateEdit( lphc, index );
1646      else
1647      {
1648        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1649      }
1650    }
1651    return (LRESULT)index;
1652 }
1653
1654 /***********************************************************************
1655  *           COMBO_LButtonDown
1656  */
1657 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1658 {
1659    POINT     pt;
1660    BOOL      bButton;
1661    HWND      hWnd = lphc->self;
1662
1663    pt.x = (short)LOWORD(lParam);
1664    pt.y = (short)HIWORD(lParam);
1665    bButton = PtInRect(&lphc->buttonRect, pt);
1666
1667    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1668        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1669    {
1670        lphc->wState |= CBF_BUTTONDOWN;
1671        if( lphc->wState & CBF_DROPPED )
1672        {
1673            /* got a click to cancel selection */
1674
1675            lphc->wState &= ~CBF_BUTTONDOWN;
1676            CBRollUp( lphc, TRUE, FALSE );
1677            if( !IsWindow( hWnd ) ) return;
1678
1679            if( lphc->wState & CBF_CAPTURE )
1680            {
1681                lphc->wState &= ~CBF_CAPTURE;
1682                ReleaseCapture();
1683            }
1684        }
1685        else
1686        {
1687            /* drop down the listbox and start tracking */
1688
1689            lphc->wState |= CBF_CAPTURE;
1690            SetCapture( hWnd );
1691            CBDropDown( lphc );
1692        }
1693        if( bButton ) CBRepaintButton( lphc );
1694    }
1695 }
1696
1697 /***********************************************************************
1698  *           COMBO_LButtonUp
1699  *
1700  * Release capture and stop tracking if needed.
1701  */
1702 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1703 {
1704    if( lphc->wState & CBF_CAPTURE )
1705    {
1706        lphc->wState &= ~CBF_CAPTURE;
1707        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1708        {
1709            INT index = CBUpdateLBox( lphc, TRUE );
1710            /* Update edit only if item is in the list */
1711            if(index >= 0)
1712            {
1713                lphc->wState |= CBF_NOLBSELECT;
1714                CBUpdateEdit( lphc, index );
1715                lphc->wState &= ~CBF_NOLBSELECT;
1716            }
1717        }
1718        ReleaseCapture();
1719        SetCapture(lphc->hWndLBox);
1720    }
1721
1722    if( lphc->wState & CBF_BUTTONDOWN )
1723    {
1724        lphc->wState &= ~CBF_BUTTONDOWN;
1725        CBRepaintButton( lphc );
1726    }
1727 }
1728
1729 /***********************************************************************
1730  *           COMBO_MouseMove
1731  *
1732  * Two things to do - track combo button and release capture when
1733  * pointer goes into the listbox.
1734  */
1735 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1736 {
1737    POINT  pt;
1738    RECT   lbRect;
1739
1740    pt.x = (short)LOWORD(lParam);
1741    pt.y = (short)HIWORD(lParam);
1742
1743    if( lphc->wState & CBF_BUTTONDOWN )
1744    {
1745      BOOL bButton;
1746
1747      bButton = PtInRect(&lphc->buttonRect, pt);
1748
1749      if( !bButton )
1750      {
1751        lphc->wState &= ~CBF_BUTTONDOWN;
1752        CBRepaintButton( lphc );
1753      }
1754    }
1755
1756    GetClientRect( lphc->hWndLBox, &lbRect );
1757    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1758    if( PtInRect(&lbRect, pt) )
1759    {
1760        lphc->wState &= ~CBF_CAPTURE;
1761        ReleaseCapture();
1762        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1763
1764        /* hand over pointer tracking */
1765        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1766    }
1767 }
1768
1769 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1770 {
1771     if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1772         return FALSE;
1773
1774     pcbi->rcItem = lphc->textRect;
1775     pcbi->rcButton = lphc->buttonRect;
1776     pcbi->stateButton = 0;
1777     if (lphc->wState & CBF_BUTTONDOWN)
1778         pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1779     if (IsRectEmpty(&lphc->buttonRect))
1780         pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1781     pcbi->hwndCombo = lphc->self;
1782     pcbi->hwndItem = lphc->hWndEdit;
1783     pcbi->hwndList = lphc->hWndLBox;
1784     return TRUE;
1785 }
1786
1787 static char *strdupA(LPCSTR str)
1788 {
1789     char *ret;
1790     DWORD len;
1791
1792     if(!str) return NULL;
1793
1794     len = strlen(str);
1795     ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1796     memcpy(ret, str, len + 1);
1797     return ret;
1798 }
1799
1800 /***********************************************************************
1801  *           ComboWndProc_common
1802  */
1803 LRESULT ComboWndProc_common( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, BOOL unicode )
1804 {
1805       LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1806
1807       TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1808             hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1809
1810       if (!IsWindow(hwnd)) return 0;
1811
1812       if( lphc || message == WM_NCCREATE )
1813       switch(message)
1814       {
1815
1816         /* System messages */
1817
1818         case WM_NCCREATE:
1819         {
1820                 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1821                                        ((LPCREATESTRUCTA)lParam)->style;
1822                 return COMBO_NCCreate(hwnd, style);
1823         }
1824         case WM_NCDESTROY:
1825                 COMBO_NCDestroy(lphc);
1826                 break;/* -> DefWindowProc */
1827
1828         case WM_CREATE:
1829         {
1830                 HWND hwndParent;
1831                 LONG style;
1832                 if(unicode)
1833                 {
1834                     hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1835                     style = ((LPCREATESTRUCTW)lParam)->style;
1836                 }
1837                 else
1838                 {
1839                     hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1840                     style = ((LPCREATESTRUCTA)lParam)->style;
1841                 }
1842                 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1843         }
1844
1845         case WM_PRINTCLIENT:
1846                 /* Fallthrough */
1847         case WM_PAINT:
1848                 /* wParam may contain a valid HDC! */
1849                 return  COMBO_Paint(lphc, (HDC)wParam);
1850
1851         case WM_ERASEBKGND:
1852                 /* do all painting in WM_PAINT like Windows does */
1853                 return 1;
1854
1855         case WM_GETDLGCODE:
1856         {
1857                 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1858                 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1859                 {
1860                    int vk = (int)((LPMSG)lParam)->wParam;
1861
1862                    if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1863                        result |= DLGC_WANTMESSAGE;
1864                 }
1865                 return  result;
1866         }
1867         case WM_SIZE:
1868                 if( lphc->hWndLBox &&
1869                   !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1870                 return  TRUE;
1871         case WM_SETFONT:
1872                 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1873                 return  TRUE;
1874         case WM_GETFONT:
1875                 return  (LRESULT)lphc->hFont;
1876         case WM_SETFOCUS:
1877                if( lphc->wState & CBF_EDIT ) {
1878                    SetFocus( lphc->hWndEdit );
1879                    /* The first time focus is received, select all the text */
1880                    if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1881                        SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1882                        lphc->wState |= CBF_BEENFOCUSED;
1883                    }
1884                }
1885                 else
1886                     COMBO_SetFocus( lphc );
1887                 return  TRUE;
1888         case WM_KILLFOCUS:
1889             {
1890                 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1891                 if( !hwndFocus ||
1892                     (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1893                     COMBO_KillFocus( lphc );
1894                 return  TRUE;
1895             }
1896         case WM_COMMAND:
1897                 return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1898         case WM_GETTEXT:
1899             return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1900                            : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1901         case WM_SETTEXT:
1902         case WM_GETTEXTLENGTH:
1903         case WM_CLEAR:
1904                 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1905                 {
1906                     int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1907                     if (j == -1) return 0;
1908                     return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1909                                      SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1910                 }
1911                 else if( lphc->wState & CBF_EDIT )
1912                 {
1913                     LRESULT ret;
1914                     lphc->wState |= CBF_NOEDITNOTIFY;
1915                     ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1916                                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1917                     lphc->wState &= ~CBF_NOEDITNOTIFY;
1918                     return ret;
1919                 }
1920                 else return CB_ERR;
1921         case WM_CUT:
1922         case WM_PASTE:
1923         case WM_COPY:
1924                 if( lphc->wState & CBF_EDIT )
1925                 {
1926                     return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1927                                      SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1928                 }
1929                 else return  CB_ERR;
1930
1931         case WM_DRAWITEM:
1932         case WM_DELETEITEM:
1933         case WM_COMPAREITEM:
1934         case WM_MEASUREITEM:
1935                 return COMBO_ItemOp(lphc, message, lParam);
1936         case WM_ENABLE:
1937                 if( lphc->wState & CBF_EDIT )
1938                     EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1939                 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1940
1941                 /* Force the control to repaint when the enabled state changes. */
1942                 InvalidateRect(lphc->self, NULL, TRUE);
1943                 return  TRUE;
1944         case WM_SETREDRAW:
1945                 if( wParam )
1946                     lphc->wState &= ~CBF_NOREDRAW;
1947                 else
1948                     lphc->wState |= CBF_NOREDRAW;
1949
1950                 if( lphc->wState & CBF_EDIT )
1951                     SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1952                 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1953                 return  0;
1954         case WM_SYSKEYDOWN:
1955                 if( KEYDATA_ALT & HIWORD(lParam) )
1956                     if( wParam == VK_UP || wParam == VK_DOWN )
1957                         COMBO_FlipListbox( lphc, FALSE, FALSE );
1958                 return  0;
1959
1960         case WM_KEYDOWN:
1961                 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1962                      (lphc->wState & CBF_DROPPED))
1963                 {
1964                    CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1965                    return TRUE;
1966                 }
1967                else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1968                {
1969                   COMBO_FlipListbox( lphc, FALSE, FALSE );
1970                   return TRUE;
1971                }
1972                /* fall through */
1973         case WM_CHAR:
1974         case WM_IME_CHAR:
1975         {
1976                 HWND hwndTarget;
1977
1978                 if( lphc->wState & CBF_EDIT )
1979                     hwndTarget = lphc->hWndEdit;
1980                 else
1981                     hwndTarget = lphc->hWndLBox;
1982
1983                 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
1984                                  SendMessageA(hwndTarget, message, wParam, lParam);
1985         }
1986         case WM_LBUTTONDOWN:
1987                 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1988                 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1989                 return  TRUE;
1990         case WM_LBUTTONUP:
1991                 COMBO_LButtonUp( lphc );
1992                 return  TRUE;
1993         case WM_MOUSEMOVE:
1994                 if( lphc->wState & CBF_CAPTURE )
1995                     COMBO_MouseMove( lphc, wParam, lParam );
1996                 return  TRUE;
1997
1998         case WM_MOUSEWHEEL:
1999                 if (wParam & (MK_SHIFT | MK_CONTROL))
2000                     return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2001                                      DefWindowProcA(hwnd, message, wParam, lParam);
2002
2003                 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2004                 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2005                 return TRUE;
2006
2007         /* Combo messages */
2008
2009         case CB_ADDSTRING:
2010                 if( unicode )
2011                 {
2012                     if( lphc->dwStyle & CBS_LOWERCASE )
2013                         CharLowerW((LPWSTR)lParam);
2014                     else if( lphc->dwStyle & CBS_UPPERCASE )
2015                         CharUpperW((LPWSTR)lParam);
2016                     return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2017                 }
2018                 else /* unlike the unicode version, the ansi version does not overwrite
2019                         the string if converting case */
2020                 {
2021                     char *string = NULL;
2022                     LRESULT ret;
2023                     if( lphc->dwStyle & CBS_LOWERCASE )
2024                     {
2025                         string = strdupA((LPSTR)lParam);
2026                         CharLowerA(string);
2027                     }
2028
2029                     else if( lphc->dwStyle & CBS_UPPERCASE )
2030                     {
2031                         string = strdupA((LPSTR)lParam);
2032                         CharUpperA(string);
2033                     }
2034
2035                     ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2036                     HeapFree(GetProcessHeap(), 0, string);
2037                     return ret;
2038                 }
2039         case CB_INSERTSTRING:
2040                 if( unicode )
2041                 {
2042                     if( lphc->dwStyle & CBS_LOWERCASE )
2043                         CharLowerW((LPWSTR)lParam);
2044                     else if( lphc->dwStyle & CBS_UPPERCASE )
2045                         CharUpperW((LPWSTR)lParam);
2046                     return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2047                 }
2048                 else
2049                 {
2050                     if( lphc->dwStyle & CBS_LOWERCASE )
2051                         CharLowerA((LPSTR)lParam);
2052                     else if( lphc->dwStyle & CBS_UPPERCASE )
2053                         CharUpperA((LPSTR)lParam);
2054
2055                     return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2056                 }
2057         case CB_DELETESTRING:
2058                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2059                                  SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2060         case CB_SELECTSTRING:
2061                 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2062         case CB_FINDSTRING:
2063                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2064                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2065         case CB_FINDSTRINGEXACT:
2066                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2067                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2068         case CB_SETITEMHEIGHT:
2069                 return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2070         case CB_GETITEMHEIGHT:
2071                 if( (INT)wParam >= 0 )  /* listbox item */
2072                     return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2073                 return  CBGetTextAreaHeight(hwnd, lphc);
2074         case CB_RESETCONTENT:
2075                 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2076                 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2077                 {
2078                     static const WCHAR empty_stringW[] = { 0 };
2079                     SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2080                 }
2081                 else
2082                     InvalidateRect(lphc->self, NULL, TRUE);
2083                 return  TRUE;
2084         case CB_INITSTORAGE:
2085                 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2086         case CB_GETHORIZONTALEXTENT:
2087                 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2088         case CB_SETHORIZONTALEXTENT:
2089                 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2090         case CB_GETTOPINDEX:
2091                 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2092         case CB_GETLOCALE:
2093                 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2094         case CB_SETLOCALE:
2095                 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2096         case CB_SETDROPPEDWIDTH:
2097                 if( (CB_GETTYPE(lphc) == CBS_SIMPLE) ||
2098                     (INT)wParam >= 32768 )
2099                     return CB_ERR;
2100                 /* new value must be higher than combobox width */
2101                 if((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2102                     lphc->droppedWidth = wParam;
2103                 else if(wParam)
2104                     lphc->droppedWidth = 0;
2105
2106                 /* recalculate the combobox area */
2107                 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
2108
2109                 /* fall through */
2110         case CB_GETDROPPEDWIDTH:
2111                 if( lphc->droppedWidth )
2112                     return  lphc->droppedWidth;
2113                 return  lphc->droppedRect.right - lphc->droppedRect.left;
2114         case CB_GETDROPPEDCONTROLRECT:
2115                 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2116                 return  CB_OKAY;
2117         case CB_GETDROPPEDSTATE:
2118                 return (lphc->wState & CBF_DROPPED) != 0;
2119         case CB_DIR:
2120                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2121                                  SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2122
2123         case CB_SHOWDROPDOWN:
2124                 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2125                 {
2126                     if( wParam )
2127                     {
2128                         if( !(lphc->wState & CBF_DROPPED) )
2129                             CBDropDown( lphc );
2130                     }
2131                     else
2132                         if( lphc->wState & CBF_DROPPED )
2133                             CBRollUp( lphc, FALSE, TRUE );
2134                 }
2135                 return  TRUE;
2136         case CB_GETCOUNT:
2137                 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2138         case CB_GETCURSEL:
2139                 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2140         case CB_SETCURSEL:
2141                 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2142                 if( lParam >= 0 )
2143                     SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2144
2145                 /* no LBN_SELCHANGE in this case, update manually */
2146                 if( lphc->wState & CBF_EDIT )
2147                     CBUpdateEdit( lphc, (INT)wParam );
2148                 else
2149                     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2150                 lphc->wState &= ~CBF_SELCHANGE;
2151                 return  lParam;
2152         case CB_GETLBTEXT:
2153                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2154                                  SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2155         case CB_GETLBTEXTLEN:
2156                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2157                                  SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2158         case CB_GETITEMDATA:
2159                 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2160         case CB_SETITEMDATA:
2161                 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2162         case CB_GETEDITSEL:
2163                 /* Edit checks passed parameters itself */
2164                 if( lphc->wState & CBF_EDIT )
2165                     return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2166                 return  CB_ERR;
2167         case CB_SETEDITSEL:
2168                 if( lphc->wState & CBF_EDIT )
2169                     return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2170                           (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2171                 return  CB_ERR;
2172         case CB_SETEXTENDEDUI:
2173                 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2174                     return  CB_ERR;
2175                 if( wParam )
2176                     lphc->wState |= CBF_EUI;
2177                 else lphc->wState &= ~CBF_EUI;
2178                 return  CB_OKAY;
2179         case CB_GETEXTENDEDUI:
2180                 return (lphc->wState & CBF_EUI) != 0;
2181         case CB_GETCOMBOBOXINFO:
2182                 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2183         case CB_LIMITTEXT:
2184                 if( lphc->wState & CBF_EDIT )
2185                         return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2186                 return  TRUE;
2187         default:
2188                 if (message >= WM_USER)
2189                     WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2190                         message - WM_USER, wParam, lParam );
2191                 break;
2192       }
2193       return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2194                        DefWindowProcA(hwnd, message, wParam, lParam);
2195 }
2196
2197 /*************************************************************************
2198  *           GetComboBoxInfo   (USER32.@)
2199  */
2200 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo,      /* [in] handle to combo box */
2201                             PCOMBOBOXINFO pcbi   /* [in/out] combo box information */)
2202 {
2203     TRACE("(%p, %p)\n", hwndCombo, pcbi);
2204     return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2205 }