d3drm: Just use RGBA_MAKE.
[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    r.left = rect.left;
1047    r.top = rect.bottom;
1048    r.right = r.left + lphc->droppedRect.right - lphc->droppedRect.left;
1049    r.bottom = r.top + nDroppedHeight;
1050
1051    /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1052    monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1053    mon_info.cbSize = sizeof(mon_info);
1054    GetMonitorInfoW( monitor, &mon_info );
1055
1056    if (r.bottom > mon_info.rcWork.bottom)
1057    {
1058        r.top = max( rect.top - nDroppedHeight, mon_info.rcWork.top );
1059        r.bottom = min( r.top + nDroppedHeight, mon_info.rcWork.bottom );
1060    }
1061
1062    SetWindowPos( lphc->hWndLBox, HWND_TOP, r.left, r.top, r.right - r.left, r.bottom - r.top,
1063                  SWP_NOACTIVATE | SWP_SHOWWINDOW );
1064
1065
1066    if( !(lphc->wState & CBF_NOREDRAW) )
1067      RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1068                            RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1069
1070    EnableWindow( lphc->hWndLBox, TRUE );
1071    if (GetCapture() != lphc->self)
1072       SetCapture(lphc->hWndLBox);
1073 }
1074
1075 /***********************************************************************
1076  *           CBRollUp
1077  *
1078  * Hide listbox popup.
1079  */
1080 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1081 {
1082    HWND hWnd = lphc->self;
1083
1084    TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1085          lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1086
1087    CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1088
1089    if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1090    {
1091
1092        if( lphc->wState & CBF_DROPPED )
1093        {
1094            RECT rect;
1095
1096            lphc->wState &= ~CBF_DROPPED;
1097            ShowWindow( lphc->hWndLBox, SW_HIDE );
1098
1099            if(GetCapture() == lphc->hWndLBox)
1100            {
1101                ReleaseCapture();
1102            }
1103
1104            if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1105            {
1106                rect = lphc->buttonRect;
1107            }
1108            else
1109            {
1110                if( bButton )
1111                {
1112                  UnionRect( &rect,
1113                             &lphc->buttonRect,
1114                             &lphc->textRect);
1115                }
1116                else
1117                  rect = lphc->textRect;
1118
1119                bButton = TRUE;
1120            }
1121
1122            if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1123                RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1124                                RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1125            CB_NOTIFY( lphc, CBN_CLOSEUP );
1126        }
1127    }
1128 }
1129
1130 /***********************************************************************
1131  *           COMBO_FlipListbox
1132  *
1133  * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1134  */
1135 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1136 {
1137    if( lphc->wState & CBF_DROPPED )
1138    {
1139        CBRollUp( lphc, ok, bRedrawButton );
1140        return FALSE;
1141    }
1142
1143    CBDropDown( lphc );
1144    return TRUE;
1145 }
1146
1147 /***********************************************************************
1148  *           CBRepaintButton
1149  */
1150 static void CBRepaintButton( LPHEADCOMBO lphc )
1151    {
1152   InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1153   UpdateWindow(lphc->self);
1154 }
1155
1156 /***********************************************************************
1157  *           COMBO_SetFocus
1158  */
1159 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1160 {
1161    if( !(lphc->wState & CBF_FOCUSED) )
1162    {
1163        if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1164            SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1165
1166        /* This is wrong. Message sequences seem to indicate that this
1167           is set *after* the notify. */
1168        /* lphc->wState |= CBF_FOCUSED;  */
1169
1170        if( !(lphc->wState & CBF_EDIT) )
1171          InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1172
1173        CB_NOTIFY( lphc, CBN_SETFOCUS );
1174        lphc->wState |= CBF_FOCUSED;
1175    }
1176 }
1177
1178 /***********************************************************************
1179  *           COMBO_KillFocus
1180  */
1181 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1182 {
1183    HWND hWnd = lphc->self;
1184
1185    if( lphc->wState & CBF_FOCUSED )
1186    {
1187        CBRollUp( lphc, FALSE, TRUE );
1188        if( IsWindow( hWnd ) )
1189        {
1190            if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1191                SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1192
1193            lphc->wState &= ~CBF_FOCUSED;
1194
1195            /* redraw text */
1196            if( !(lphc->wState & CBF_EDIT) )
1197              InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1198
1199            CB_NOTIFY( lphc, CBN_KILLFOCUS );
1200        }
1201    }
1202 }
1203
1204 /***********************************************************************
1205  *           COMBO_Command
1206  */
1207 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1208 {
1209    if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1210    {
1211        /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1212
1213        switch( HIWORD(wParam) >> 8 )
1214        {
1215            case (EN_SETFOCUS >> 8):
1216
1217                TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1218
1219                 COMBO_SetFocus( lphc );
1220                 break;
1221
1222            case (EN_KILLFOCUS >> 8):
1223
1224                TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1225
1226                 /* NOTE: it seems that Windows' edit control sends an
1227                  * undocumented message WM_USER + 0x1B instead of this
1228                  * notification (only when it happens to be a part of
1229                  * the combo). ?? - AK.
1230                  */
1231
1232                 COMBO_KillFocus( lphc );
1233                 break;
1234
1235
1236            case (EN_CHANGE >> 8):
1237                /*
1238                 * In some circumstances (when the selection of the combobox
1239                 * is changed for example) we don't want the EN_CHANGE notification
1240                 * to be forwarded to the parent of the combobox. This code
1241                 * checks a flag that is set in these occasions and ignores the
1242                 * notification.
1243                 */
1244                 if (lphc->wState & CBF_NOLBSELECT)
1245                 {
1246                   lphc->wState &= ~CBF_NOLBSELECT;
1247                 }
1248                 else
1249                 {
1250                   CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1251                 }
1252
1253                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1254                   CB_NOTIFY( lphc, CBN_EDITCHANGE );
1255                 break;
1256
1257            case (EN_UPDATE >> 8):
1258                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1259                   CB_NOTIFY( lphc, CBN_EDITUPDATE );
1260                 break;
1261
1262            case (EN_ERRSPACE >> 8):
1263                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1264        }
1265    }
1266    else if( lphc->hWndLBox == hWnd )
1267    {
1268        switch( (short)HIWORD(wParam) )
1269        {
1270            case LBN_ERRSPACE:
1271                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1272                 break;
1273
1274            case LBN_DBLCLK:
1275                 CB_NOTIFY( lphc, CBN_DBLCLK );
1276                 break;
1277
1278            case LBN_SELCHANGE:
1279            case LBN_SELCANCEL:
1280
1281                 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1282
1283                 /* do not roll up if selection is being tracked
1284                  * by arrow keys in the dropdown listbox */
1285                 if (!(lphc->wState & CBF_NOROLLUP))
1286                 {
1287                     CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1288                 }
1289                 else lphc->wState &= ~CBF_NOROLLUP;
1290
1291                 CB_NOTIFY( lphc, CBN_SELCHANGE );
1292
1293                 if( HIWORD(wParam) == LBN_SELCHANGE)
1294                 {
1295                    if( lphc->wState & CBF_EDIT )
1296                    {
1297                        INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1298                        lphc->wState |= CBF_NOLBSELECT;
1299                        CBUpdateEdit( lphc, index );
1300                        /* select text in edit, as Windows does */
1301                       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1302                    }
1303                    else
1304                    {
1305                        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1306                        UpdateWindow(lphc->self);
1307                    }
1308                 }
1309                 break;
1310
1311            case LBN_SETFOCUS:
1312            case LBN_KILLFOCUS:
1313                 /* nothing to do here since ComboLBox always resets the focus to its
1314                  * combo/edit counterpart */
1315                  break;
1316        }
1317    }
1318    return 0;
1319 }
1320
1321 /***********************************************************************
1322  *           COMBO_ItemOp
1323  *
1324  * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1325  */
1326 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1327 {
1328    HWND hWnd = lphc->self;
1329    UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1330
1331    TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1332
1333    switch( msg )
1334    {
1335    case WM_DELETEITEM:
1336        {
1337            DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1338            lpIS->CtlType  = ODT_COMBOBOX;
1339            lpIS->CtlID    = id;
1340            lpIS->hwndItem = hWnd;
1341            break;
1342        }
1343    case WM_DRAWITEM:
1344        {
1345            DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1346            lpIS->CtlType  = ODT_COMBOBOX;
1347            lpIS->CtlID    = id;
1348            lpIS->hwndItem = hWnd;
1349            break;
1350        }
1351    case WM_COMPAREITEM:
1352        {
1353            COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1354            lpIS->CtlType  = ODT_COMBOBOX;
1355            lpIS->CtlID    = id;
1356            lpIS->hwndItem = hWnd;
1357            break;
1358        }
1359    case WM_MEASUREITEM:
1360        {
1361            MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1362            lpIS->CtlType  = ODT_COMBOBOX;
1363            lpIS->CtlID    = id;
1364            break;
1365        }
1366    }
1367    return SendMessageW(lphc->owner, msg, id, lParam);
1368 }
1369
1370
1371 /***********************************************************************
1372  *           COMBO_GetTextW
1373  */
1374 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1375 {
1376     INT length;
1377
1378     if( lphc->wState & CBF_EDIT )
1379         return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1380
1381     /* get it from the listbox */
1382
1383     if (!count || !buf) return 0;
1384     if( lphc->hWndLBox )
1385     {
1386         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1387         if (idx == LB_ERR) goto error;
1388         length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1389         if (length == LB_ERR) goto error;
1390
1391         /* 'length' is without the terminating character */
1392         if (length >= count)
1393         {
1394             LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1395             if (!lpBuffer) goto error;
1396             length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1397
1398             /* truncate if buffer is too short */
1399             if (length != LB_ERR)
1400             {
1401                 lstrcpynW( buf, lpBuffer, count );
1402                 length = count;
1403             }
1404             HeapFree( GetProcessHeap(), 0, lpBuffer );
1405         }
1406         else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1407
1408         if (length == LB_ERR) return 0;
1409         return length;
1410     }
1411
1412  error:  /* error - truncate string, return zero */
1413     buf[0] = 0;
1414     return 0;
1415 }
1416
1417
1418 /***********************************************************************
1419  *           COMBO_GetTextA
1420  *
1421  * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1422  *       also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1423  */
1424 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1425 {
1426     INT length;
1427
1428     if( lphc->wState & CBF_EDIT )
1429         return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1430
1431     /* get it from the listbox */
1432
1433     if (!count || !buf) return 0;
1434     if( lphc->hWndLBox )
1435     {
1436         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1437         if (idx == LB_ERR) goto error;
1438         length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1439         if (length == LB_ERR) goto error;
1440
1441         /* 'length' is without the terminating character */
1442         if (length >= count)
1443         {
1444             LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1445             if (!lpBuffer) goto error;
1446             length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1447
1448             /* truncate if buffer is too short */
1449             if (length != LB_ERR)
1450             {
1451                 lstrcpynA( buf, lpBuffer, count );
1452                 length = count;
1453             }
1454             HeapFree( GetProcessHeap(), 0, lpBuffer );
1455         }
1456         else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1457
1458         if (length == LB_ERR) return 0;
1459         return length;
1460     }
1461
1462  error:  /* error - truncate string, return zero */
1463     buf[0] = 0;
1464     return 0;
1465 }
1466
1467
1468 /***********************************************************************
1469  *           CBResetPos
1470  *
1471  * This function sets window positions according to the updated
1472  * component placement struct.
1473  */
1474 static void CBResetPos(
1475   LPHEADCOMBO lphc,
1476   const RECT  *rectEdit,
1477   const RECT  *rectLB,
1478   BOOL        bRedraw)
1479 {
1480    BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1481
1482    /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1483     * sizing messages */
1484
1485    if( lphc->wState & CBF_EDIT )
1486      SetWindowPos( lphc->hWndEdit, 0,
1487                    rectEdit->left, rectEdit->top,
1488                    rectEdit->right - rectEdit->left,
1489                    rectEdit->bottom - rectEdit->top,
1490                        SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1491
1492    SetWindowPos( lphc->hWndLBox, 0,
1493                  rectLB->left, rectLB->top,
1494                  rectLB->right - rectLB->left,
1495                  rectLB->bottom - rectLB->top,
1496                    SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1497
1498    if( bDrop )
1499    {
1500        if( lphc->wState & CBF_DROPPED )
1501        {
1502            lphc->wState &= ~CBF_DROPPED;
1503            ShowWindow( lphc->hWndLBox, SW_HIDE );
1504        }
1505
1506        if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1507            RedrawWindow( lphc->self, NULL, 0,
1508                            RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1509    }
1510 }
1511
1512
1513 /***********************************************************************
1514  *           COMBO_Size
1515  */
1516 static void COMBO_Size( LPHEADCOMBO lphc )
1517 {
1518   /*
1519    * Those controls are always the same height. So we have to make sure
1520    * they are not resized to another value.
1521    */
1522   if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1523   {
1524     int newComboHeight, curComboHeight, curComboWidth;
1525     RECT rc;
1526
1527     GetWindowRect(lphc->self, &rc);
1528     curComboHeight = rc.bottom - rc.top;
1529     curComboWidth = rc.right - rc.left;
1530     newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE();
1531
1532     /*
1533      * Resizing a combobox has another side effect, it resizes the dropped
1534      * rectangle as well. However, it does it only if the new height for the
1535      * combobox is more than the height it should have. In other words,
1536      * if the application resizing the combobox only had the intention to resize
1537      * the actual control, for example, to do the layout of a dialog that is
1538      * resized, the height of the dropdown is not changed.
1539      */
1540     if( curComboHeight > newComboHeight )
1541     {
1542       TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1543             curComboHeight, newComboHeight, lphc->droppedRect.bottom,
1544             lphc->droppedRect.top);
1545       lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight;
1546     }
1547     /*
1548      * Restore original height
1549      */
1550     if( curComboHeight != newComboHeight )
1551       SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight,
1552             SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW);
1553   }
1554
1555   CBCalcPlacement(lphc->self,
1556                   lphc,
1557                   &lphc->textRect,
1558                   &lphc->buttonRect,
1559                   &lphc->droppedRect);
1560
1561   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1562 }
1563
1564
1565 /***********************************************************************
1566  *           COMBO_Font
1567  */
1568 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1569 {
1570   /*
1571    * Set the font
1572    */
1573   lphc->hFont = hFont;
1574
1575   /*
1576    * Propagate to owned windows.
1577    */
1578   if( lphc->wState & CBF_EDIT )
1579       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1580   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1581
1582   /*
1583    * Redo the layout of the control.
1584    */
1585   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1586   {
1587     CBCalcPlacement(lphc->self,
1588                     lphc,
1589                     &lphc->textRect,
1590                     &lphc->buttonRect,
1591                     &lphc->droppedRect);
1592
1593     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1594   }
1595   else
1596   {
1597     CBForceDummyResize(lphc);
1598   }
1599 }
1600
1601
1602 /***********************************************************************
1603  *           COMBO_SetItemHeight
1604  */
1605 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1606 {
1607    LRESULT      lRet = CB_ERR;
1608
1609    if( index == -1 ) /* set text field height */
1610    {
1611        if( height < 32768 )
1612        {
1613            lphc->editHeight = height + 2;  /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1614
1615          /*
1616           * Redo the layout of the control.
1617           */
1618          if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1619          {
1620            CBCalcPlacement(lphc->self,
1621                            lphc,
1622                            &lphc->textRect,
1623                            &lphc->buttonRect,
1624                            &lphc->droppedRect);
1625
1626            CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1627          }
1628          else
1629          {
1630            CBForceDummyResize(lphc);
1631          }
1632
1633            lRet = height;
1634        }
1635    }
1636    else if ( CB_OWNERDRAWN(lphc) )      /* set listbox item height */
1637        lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1638    return lRet;
1639 }
1640
1641 /***********************************************************************
1642  *           COMBO_SelectString
1643  */
1644 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1645 {
1646    INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText) :
1647                          SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1648    if( index >= 0 )
1649    {
1650      if( lphc->wState & CBF_EDIT )
1651        CBUpdateEdit( lphc, index );
1652      else
1653      {
1654        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1655      }
1656    }
1657    return (LRESULT)index;
1658 }
1659
1660 /***********************************************************************
1661  *           COMBO_LButtonDown
1662  */
1663 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1664 {
1665    POINT     pt;
1666    BOOL      bButton;
1667    HWND      hWnd = lphc->self;
1668
1669    pt.x = (short)LOWORD(lParam);
1670    pt.y = (short)HIWORD(lParam);
1671    bButton = PtInRect(&lphc->buttonRect, pt);
1672
1673    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1674        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1675    {
1676        lphc->wState |= CBF_BUTTONDOWN;
1677        if( lphc->wState & CBF_DROPPED )
1678        {
1679            /* got a click to cancel selection */
1680
1681            lphc->wState &= ~CBF_BUTTONDOWN;
1682            CBRollUp( lphc, TRUE, FALSE );
1683            if( !IsWindow( hWnd ) ) return;
1684
1685            if( lphc->wState & CBF_CAPTURE )
1686            {
1687                lphc->wState &= ~CBF_CAPTURE;
1688                ReleaseCapture();
1689            }
1690        }
1691        else
1692        {
1693            /* drop down the listbox and start tracking */
1694
1695            lphc->wState |= CBF_CAPTURE;
1696            SetCapture( hWnd );
1697            CBDropDown( lphc );
1698        }
1699        if( bButton ) CBRepaintButton( lphc );
1700    }
1701 }
1702
1703 /***********************************************************************
1704  *           COMBO_LButtonUp
1705  *
1706  * Release capture and stop tracking if needed.
1707  */
1708 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1709 {
1710    if( lphc->wState & CBF_CAPTURE )
1711    {
1712        lphc->wState &= ~CBF_CAPTURE;
1713        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1714        {
1715            INT index = CBUpdateLBox( lphc, TRUE );
1716            /* Update edit only if item is in the list */
1717            if(index >= 0)
1718            {
1719                lphc->wState |= CBF_NOLBSELECT;
1720                CBUpdateEdit( lphc, index );
1721                lphc->wState &= ~CBF_NOLBSELECT;
1722            }
1723        }
1724        ReleaseCapture();
1725        SetCapture(lphc->hWndLBox);
1726    }
1727
1728    if( lphc->wState & CBF_BUTTONDOWN )
1729    {
1730        lphc->wState &= ~CBF_BUTTONDOWN;
1731        CBRepaintButton( lphc );
1732    }
1733 }
1734
1735 /***********************************************************************
1736  *           COMBO_MouseMove
1737  *
1738  * Two things to do - track combo button and release capture when
1739  * pointer goes into the listbox.
1740  */
1741 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1742 {
1743    POINT  pt;
1744    RECT   lbRect;
1745
1746    pt.x = (short)LOWORD(lParam);
1747    pt.y = (short)HIWORD(lParam);
1748
1749    if( lphc->wState & CBF_BUTTONDOWN )
1750    {
1751      BOOL bButton;
1752
1753      bButton = PtInRect(&lphc->buttonRect, pt);
1754
1755      if( !bButton )
1756      {
1757        lphc->wState &= ~CBF_BUTTONDOWN;
1758        CBRepaintButton( lphc );
1759      }
1760    }
1761
1762    GetClientRect( lphc->hWndLBox, &lbRect );
1763    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1764    if( PtInRect(&lbRect, pt) )
1765    {
1766        lphc->wState &= ~CBF_CAPTURE;
1767        ReleaseCapture();
1768        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1769
1770        /* hand over pointer tracking */
1771        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1772    }
1773 }
1774
1775 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1776 {
1777     if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1778         return FALSE;
1779
1780     pcbi->rcItem = lphc->textRect;
1781     pcbi->rcButton = lphc->buttonRect;
1782     pcbi->stateButton = 0;
1783     if (lphc->wState & CBF_BUTTONDOWN)
1784         pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1785     if (IsRectEmpty(&lphc->buttonRect))
1786         pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1787     pcbi->hwndCombo = lphc->self;
1788     pcbi->hwndItem = lphc->hWndEdit;
1789     pcbi->hwndList = lphc->hWndLBox;
1790     return TRUE;
1791 }
1792
1793 static char *strdupA(LPCSTR str)
1794 {
1795     char *ret;
1796     DWORD len;
1797
1798     if(!str) return NULL;
1799
1800     len = strlen(str);
1801     ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1802     memcpy(ret, str, len + 1);
1803     return ret;
1804 }
1805
1806 /***********************************************************************
1807  *           ComboWndProc_common
1808  */
1809 LRESULT ComboWndProc_common( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, BOOL unicode )
1810 {
1811       LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1812
1813       TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1814             hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1815
1816       if (!IsWindow(hwnd)) return 0;
1817
1818       if( lphc || message == WM_NCCREATE )
1819       switch(message)
1820       {
1821
1822         /* System messages */
1823
1824         case WM_NCCREATE:
1825         {
1826                 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1827                                        ((LPCREATESTRUCTA)lParam)->style;
1828                 return COMBO_NCCreate(hwnd, style);
1829         }
1830         case WM_NCDESTROY:
1831                 COMBO_NCDestroy(lphc);
1832                 break;/* -> DefWindowProc */
1833
1834         case WM_CREATE:
1835         {
1836                 HWND hwndParent;
1837                 LONG style;
1838                 if(unicode)
1839                 {
1840                     hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1841                     style = ((LPCREATESTRUCTW)lParam)->style;
1842                 }
1843                 else
1844                 {
1845                     hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1846                     style = ((LPCREATESTRUCTA)lParam)->style;
1847                 }
1848                 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1849         }
1850
1851         case WM_PRINTCLIENT:
1852                 /* Fallthrough */
1853         case WM_PAINT:
1854                 /* wParam may contain a valid HDC! */
1855                 return  COMBO_Paint(lphc, (HDC)wParam);
1856
1857         case WM_ERASEBKGND:
1858                 /* do all painting in WM_PAINT like Windows does */
1859                 return 1;
1860
1861         case WM_GETDLGCODE:
1862         {
1863                 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1864                 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1865                 {
1866                    int vk = (int)((LPMSG)lParam)->wParam;
1867
1868                    if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1869                        result |= DLGC_WANTMESSAGE;
1870                 }
1871                 return  result;
1872         }
1873         case WM_SIZE:
1874                 if( lphc->hWndLBox &&
1875                   !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1876                 return  TRUE;
1877         case WM_SETFONT:
1878                 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1879                 return  TRUE;
1880         case WM_GETFONT:
1881                 return  (LRESULT)lphc->hFont;
1882         case WM_SETFOCUS:
1883                if( lphc->wState & CBF_EDIT ) {
1884                    SetFocus( lphc->hWndEdit );
1885                    /* The first time focus is received, select all the text */
1886                    if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1887                        SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1888                        lphc->wState |= CBF_BEENFOCUSED;
1889                    }
1890                }
1891                 else
1892                     COMBO_SetFocus( lphc );
1893                 return  TRUE;
1894         case WM_KILLFOCUS:
1895             {
1896                 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1897                 if( !hwndFocus ||
1898                     (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1899                     COMBO_KillFocus( lphc );
1900                 return  TRUE;
1901             }
1902         case WM_COMMAND:
1903                 return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1904         case WM_GETTEXT:
1905             return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1906                            : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1907         case WM_SETTEXT:
1908         case WM_GETTEXTLENGTH:
1909         case WM_CLEAR:
1910                 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1911                 {
1912                     int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1913                     if (j == -1) return 0;
1914                     return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1915                                      SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1916                 }
1917                 else if( lphc->wState & CBF_EDIT )
1918                 {
1919                     LRESULT ret;
1920                     lphc->wState |= CBF_NOEDITNOTIFY;
1921                     ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1922                                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1923                     lphc->wState &= ~CBF_NOEDITNOTIFY;
1924                     return ret;
1925                 }
1926                 else return CB_ERR;
1927         case WM_CUT:
1928         case WM_PASTE:
1929         case WM_COPY:
1930                 if( lphc->wState & CBF_EDIT )
1931                 {
1932                     return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1933                                      SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1934                 }
1935                 else return  CB_ERR;
1936
1937         case WM_DRAWITEM:
1938         case WM_DELETEITEM:
1939         case WM_COMPAREITEM:
1940         case WM_MEASUREITEM:
1941                 return COMBO_ItemOp(lphc, message, lParam);
1942         case WM_ENABLE:
1943                 if( lphc->wState & CBF_EDIT )
1944                     EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1945                 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1946
1947                 /* Force the control to repaint when the enabled state changes. */
1948                 InvalidateRect(lphc->self, NULL, TRUE);
1949                 return  TRUE;
1950         case WM_SETREDRAW:
1951                 if( wParam )
1952                     lphc->wState &= ~CBF_NOREDRAW;
1953                 else
1954                     lphc->wState |= CBF_NOREDRAW;
1955
1956                 if( lphc->wState & CBF_EDIT )
1957                     SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1958                 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1959                 return  0;
1960         case WM_SYSKEYDOWN:
1961                 if( KEYDATA_ALT & HIWORD(lParam) )
1962                     if( wParam == VK_UP || wParam == VK_DOWN )
1963                         COMBO_FlipListbox( lphc, FALSE, FALSE );
1964                 return  0;
1965
1966         case WM_KEYDOWN:
1967                 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1968                      (lphc->wState & CBF_DROPPED))
1969                 {
1970                    CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1971                    return TRUE;
1972                 }
1973                else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1974                {
1975                   COMBO_FlipListbox( lphc, FALSE, FALSE );
1976                   return TRUE;
1977                }
1978                /* fall through */
1979         case WM_CHAR:
1980         case WM_IME_CHAR:
1981         {
1982                 HWND hwndTarget;
1983
1984                 if( lphc->wState & CBF_EDIT )
1985                     hwndTarget = lphc->hWndEdit;
1986                 else
1987                     hwndTarget = lphc->hWndLBox;
1988
1989                 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
1990                                  SendMessageA(hwndTarget, message, wParam, lParam);
1991         }
1992         case WM_LBUTTONDOWN:
1993                 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1994                 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1995                 return  TRUE;
1996         case WM_LBUTTONUP:
1997                 COMBO_LButtonUp( lphc );
1998                 return  TRUE;
1999         case WM_MOUSEMOVE:
2000                 if( lphc->wState & CBF_CAPTURE )
2001                     COMBO_MouseMove( lphc, wParam, lParam );
2002                 return  TRUE;
2003
2004         case WM_MOUSEWHEEL:
2005                 if (wParam & (MK_SHIFT | MK_CONTROL))
2006                     return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2007                                      DefWindowProcA(hwnd, message, wParam, lParam);
2008
2009                 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2010                 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2011                 return TRUE;
2012
2013         /* Combo messages */
2014
2015         case CB_ADDSTRING:
2016                 if( unicode )
2017                 {
2018                     if( lphc->dwStyle & CBS_LOWERCASE )
2019                         CharLowerW((LPWSTR)lParam);
2020                     else if( lphc->dwStyle & CBS_UPPERCASE )
2021                         CharUpperW((LPWSTR)lParam);
2022                     return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2023                 }
2024                 else /* unlike the unicode version, the ansi version does not overwrite
2025                         the string if converting case */
2026                 {
2027                     char *string = NULL;
2028                     LRESULT ret;
2029                     if( lphc->dwStyle & CBS_LOWERCASE )
2030                     {
2031                         string = strdupA((LPSTR)lParam);
2032                         CharLowerA(string);
2033                     }
2034
2035                     else if( lphc->dwStyle & CBS_UPPERCASE )
2036                     {
2037                         string = strdupA((LPSTR)lParam);
2038                         CharUpperA(string);
2039                     }
2040
2041                     ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2042                     HeapFree(GetProcessHeap(), 0, string);
2043                     return ret;
2044                 }
2045         case CB_INSERTSTRING:
2046                 if( unicode )
2047                 {
2048                     if( lphc->dwStyle & CBS_LOWERCASE )
2049                         CharLowerW((LPWSTR)lParam);
2050                     else if( lphc->dwStyle & CBS_UPPERCASE )
2051                         CharUpperW((LPWSTR)lParam);
2052                     return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2053                 }
2054                 else
2055                 {
2056                     if( lphc->dwStyle & CBS_LOWERCASE )
2057                         CharLowerA((LPSTR)lParam);
2058                     else if( lphc->dwStyle & CBS_UPPERCASE )
2059                         CharUpperA((LPSTR)lParam);
2060
2061                     return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2062                 }
2063         case CB_DELETESTRING:
2064                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2065                                  SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2066         case CB_SELECTSTRING:
2067                 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2068         case CB_FINDSTRING:
2069                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2070                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2071         case CB_FINDSTRINGEXACT:
2072                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2073                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2074         case CB_SETITEMHEIGHT:
2075                 return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2076         case CB_GETITEMHEIGHT:
2077                 if( (INT)wParam >= 0 )  /* listbox item */
2078                     return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2079                 return  CBGetTextAreaHeight(hwnd, lphc);
2080         case CB_RESETCONTENT:
2081                 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2082                 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2083                 {
2084                     static const WCHAR empty_stringW[] = { 0 };
2085                     SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2086                 }
2087                 else
2088                     InvalidateRect(lphc->self, NULL, TRUE);
2089                 return  TRUE;
2090         case CB_INITSTORAGE:
2091                 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2092         case CB_GETHORIZONTALEXTENT:
2093                 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2094         case CB_SETHORIZONTALEXTENT:
2095                 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2096         case CB_GETTOPINDEX:
2097                 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2098         case CB_GETLOCALE:
2099                 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2100         case CB_SETLOCALE:
2101                 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2102         case CB_SETDROPPEDWIDTH:
2103                 if( (CB_GETTYPE(lphc) == CBS_SIMPLE) ||
2104                     (INT)wParam >= 32768 )
2105                     return CB_ERR;
2106                 /* new value must be higher than combobox width */
2107                 if((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2108                     lphc->droppedWidth = wParam;
2109                 else if(wParam)
2110                     lphc->droppedWidth = 0;
2111
2112                 /* recalculate the combobox area */
2113                 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
2114
2115                 /* fall through */
2116         case CB_GETDROPPEDWIDTH:
2117                 if( lphc->droppedWidth )
2118                     return  lphc->droppedWidth;
2119                 return  lphc->droppedRect.right - lphc->droppedRect.left;
2120         case CB_GETDROPPEDCONTROLRECT:
2121                 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2122                 return  CB_OKAY;
2123         case CB_GETDROPPEDSTATE:
2124                 return (lphc->wState & CBF_DROPPED) != 0;
2125         case CB_DIR:
2126                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2127                                  SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2128
2129         case CB_SHOWDROPDOWN:
2130                 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2131                 {
2132                     if( wParam )
2133                     {
2134                         if( !(lphc->wState & CBF_DROPPED) )
2135                             CBDropDown( lphc );
2136                     }
2137                     else
2138                         if( lphc->wState & CBF_DROPPED )
2139                             CBRollUp( lphc, FALSE, TRUE );
2140                 }
2141                 return  TRUE;
2142         case CB_GETCOUNT:
2143                 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2144         case CB_GETCURSEL:
2145                 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2146         case CB_SETCURSEL:
2147                 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2148                 if( lParam >= 0 )
2149                     SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2150
2151                 /* no LBN_SELCHANGE in this case, update manually */
2152                 if( lphc->wState & CBF_EDIT )
2153                     CBUpdateEdit( lphc, (INT)wParam );
2154                 else
2155                     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2156                 lphc->wState &= ~CBF_SELCHANGE;
2157                 return  lParam;
2158         case CB_GETLBTEXT:
2159                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2160                                  SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2161         case CB_GETLBTEXTLEN:
2162                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2163                                  SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2164         case CB_GETITEMDATA:
2165                 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2166         case CB_SETITEMDATA:
2167                 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2168         case CB_GETEDITSEL:
2169                 /* Edit checks passed parameters itself */
2170                 if( lphc->wState & CBF_EDIT )
2171                     return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2172                 return  CB_ERR;
2173         case CB_SETEDITSEL:
2174                 if( lphc->wState & CBF_EDIT )
2175                     return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2176                           (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2177                 return  CB_ERR;
2178         case CB_SETEXTENDEDUI:
2179                 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2180                     return  CB_ERR;
2181                 if( wParam )
2182                     lphc->wState |= CBF_EUI;
2183                 else lphc->wState &= ~CBF_EUI;
2184                 return  CB_OKAY;
2185         case CB_GETEXTENDEDUI:
2186                 return (lphc->wState & CBF_EUI) != 0;
2187         case CB_GETCOMBOBOXINFO:
2188                 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2189         case CB_LIMITTEXT:
2190                 if( lphc->wState & CBF_EDIT )
2191                         return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2192                 return  TRUE;
2193         default:
2194                 if (message >= WM_USER)
2195                     WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2196                         message - WM_USER, wParam, lParam );
2197                 break;
2198       }
2199       return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2200                        DefWindowProcA(hwnd, message, wParam, lParam);
2201 }
2202
2203 /*************************************************************************
2204  *           GetComboBoxInfo   (USER32.@)
2205  */
2206 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo,      /* [in] handle to combo box */
2207                             PCOMBOBOXINFO pcbi   /* [in/out] combo box information */)
2208 {
2209     TRACE("(%p, %p)\n", hwndCombo, pcbi);
2210     return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2211 }