user32: Avoid TRUE:FALSE conditional expressions.
[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, LPARAM lParam )
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;
1519
1520     newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE();
1521
1522     /*
1523      * Resizing a combobox has another side effect, it resizes the dropped
1524      * rectangle as well. However, it does it only if the new height for the
1525      * combobox is more than the height it should have. In other words,
1526      * if the application resizing the combobox only had the intention to resize
1527      * the actual control, for example, to do the layout of a dialog that is
1528      * resized, the height of the dropdown is not changed.
1529      */
1530     if( HIWORD(lParam) > newComboHeight )
1531     {
1532       TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1533             HIWORD(lParam), newComboHeight, lphc->droppedRect.bottom,
1534             lphc->droppedRect.top);
1535       lphc->droppedRect.bottom = lphc->droppedRect.top + HIWORD(lParam) - newComboHeight;
1536     }
1537     /*
1538      * Restore original height
1539      */
1540     if( HIWORD(lParam) != newComboHeight )
1541       SetWindowPos(lphc->self, 0, 0, 0, LOWORD(lParam), newComboHeight,
1542             SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW);
1543   }
1544
1545   CBCalcPlacement(lphc->self,
1546                   lphc,
1547                   &lphc->textRect,
1548                   &lphc->buttonRect,
1549                   &lphc->droppedRect);
1550
1551   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1552 }
1553
1554
1555 /***********************************************************************
1556  *           COMBO_Font
1557  */
1558 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1559 {
1560   /*
1561    * Set the font
1562    */
1563   lphc->hFont = hFont;
1564
1565   /*
1566    * Propagate to owned windows.
1567    */
1568   if( lphc->wState & CBF_EDIT )
1569       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1570   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1571
1572   /*
1573    * Redo the layout of the control.
1574    */
1575   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1576   {
1577     CBCalcPlacement(lphc->self,
1578                     lphc,
1579                     &lphc->textRect,
1580                     &lphc->buttonRect,
1581                     &lphc->droppedRect);
1582
1583     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1584   }
1585   else
1586   {
1587     CBForceDummyResize(lphc);
1588   }
1589 }
1590
1591
1592 /***********************************************************************
1593  *           COMBO_SetItemHeight
1594  */
1595 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1596 {
1597    LRESULT      lRet = CB_ERR;
1598
1599    if( index == -1 ) /* set text field height */
1600    {
1601        if( height < 32768 )
1602        {
1603            lphc->editHeight = height + 2;  /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1604
1605          /*
1606           * Redo the layout of the control.
1607           */
1608          if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1609          {
1610            CBCalcPlacement(lphc->self,
1611                            lphc,
1612                            &lphc->textRect,
1613                            &lphc->buttonRect,
1614                            &lphc->droppedRect);
1615
1616            CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1617          }
1618          else
1619          {
1620            CBForceDummyResize(lphc);
1621          }
1622
1623            lRet = height;
1624        }
1625    }
1626    else if ( CB_OWNERDRAWN(lphc) )      /* set listbox item height */
1627        lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1628    return lRet;
1629 }
1630
1631 /***********************************************************************
1632  *           COMBO_SelectString
1633  */
1634 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1635 {
1636    INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText) :
1637                          SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1638    if( index >= 0 )
1639    {
1640      if( lphc->wState & CBF_EDIT )
1641        CBUpdateEdit( lphc, index );
1642      else
1643      {
1644        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1645      }
1646    }
1647    return (LRESULT)index;
1648 }
1649
1650 /***********************************************************************
1651  *           COMBO_LButtonDown
1652  */
1653 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1654 {
1655    POINT     pt;
1656    BOOL      bButton;
1657    HWND      hWnd = lphc->self;
1658
1659    pt.x = (short)LOWORD(lParam);
1660    pt.y = (short)HIWORD(lParam);
1661    bButton = PtInRect(&lphc->buttonRect, pt);
1662
1663    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1664        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1665    {
1666        lphc->wState |= CBF_BUTTONDOWN;
1667        if( lphc->wState & CBF_DROPPED )
1668        {
1669            /* got a click to cancel selection */
1670
1671            lphc->wState &= ~CBF_BUTTONDOWN;
1672            CBRollUp( lphc, TRUE, FALSE );
1673            if( !IsWindow( hWnd ) ) return;
1674
1675            if( lphc->wState & CBF_CAPTURE )
1676            {
1677                lphc->wState &= ~CBF_CAPTURE;
1678                ReleaseCapture();
1679            }
1680        }
1681        else
1682        {
1683            /* drop down the listbox and start tracking */
1684
1685            lphc->wState |= CBF_CAPTURE;
1686            SetCapture( hWnd );
1687            CBDropDown( lphc );
1688        }
1689        if( bButton ) CBRepaintButton( lphc );
1690    }
1691 }
1692
1693 /***********************************************************************
1694  *           COMBO_LButtonUp
1695  *
1696  * Release capture and stop tracking if needed.
1697  */
1698 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1699 {
1700    if( lphc->wState & CBF_CAPTURE )
1701    {
1702        lphc->wState &= ~CBF_CAPTURE;
1703        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1704        {
1705            INT index = CBUpdateLBox( lphc, TRUE );
1706            /* Update edit only if item is in the list */
1707            if(index >= 0)
1708            {
1709                lphc->wState |= CBF_NOLBSELECT;
1710                CBUpdateEdit( lphc, index );
1711                lphc->wState &= ~CBF_NOLBSELECT;
1712            }
1713        }
1714        ReleaseCapture();
1715        SetCapture(lphc->hWndLBox);
1716    }
1717
1718    if( lphc->wState & CBF_BUTTONDOWN )
1719    {
1720        lphc->wState &= ~CBF_BUTTONDOWN;
1721        CBRepaintButton( lphc );
1722    }
1723 }
1724
1725 /***********************************************************************
1726  *           COMBO_MouseMove
1727  *
1728  * Two things to do - track combo button and release capture when
1729  * pointer goes into the listbox.
1730  */
1731 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1732 {
1733    POINT  pt;
1734    RECT   lbRect;
1735
1736    pt.x = (short)LOWORD(lParam);
1737    pt.y = (short)HIWORD(lParam);
1738
1739    if( lphc->wState & CBF_BUTTONDOWN )
1740    {
1741      BOOL bButton;
1742
1743      bButton = PtInRect(&lphc->buttonRect, pt);
1744
1745      if( !bButton )
1746      {
1747        lphc->wState &= ~CBF_BUTTONDOWN;
1748        CBRepaintButton( lphc );
1749      }
1750    }
1751
1752    GetClientRect( lphc->hWndLBox, &lbRect );
1753    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1754    if( PtInRect(&lbRect, pt) )
1755    {
1756        lphc->wState &= ~CBF_CAPTURE;
1757        ReleaseCapture();
1758        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1759
1760        /* hand over pointer tracking */
1761        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1762    }
1763 }
1764
1765 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1766 {
1767     if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1768         return FALSE;
1769
1770     pcbi->rcItem = lphc->textRect;
1771     pcbi->rcButton = lphc->buttonRect;
1772     pcbi->stateButton = 0;
1773     if (lphc->wState & CBF_BUTTONDOWN)
1774         pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1775     if (IsRectEmpty(&lphc->buttonRect))
1776         pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1777     pcbi->hwndCombo = lphc->self;
1778     pcbi->hwndItem = lphc->hWndEdit;
1779     pcbi->hwndList = lphc->hWndLBox;
1780     return TRUE;
1781 }
1782
1783 static char *strdupA(LPCSTR str)
1784 {
1785     char *ret;
1786     DWORD len;
1787
1788     if(!str) return NULL;
1789
1790     len = strlen(str);
1791     ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1792     memcpy(ret, str, len + 1);
1793     return ret;
1794 }
1795
1796 /***********************************************************************
1797  *           ComboWndProc_common
1798  */
1799 LRESULT ComboWndProc_common( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, BOOL unicode )
1800 {
1801       LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1802
1803       TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1804             hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1805
1806       if (!IsWindow(hwnd)) return 0;
1807
1808       if( lphc || message == WM_NCCREATE )
1809       switch(message)
1810       {
1811
1812         /* System messages */
1813
1814         case WM_NCCREATE:
1815         {
1816                 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1817                                        ((LPCREATESTRUCTA)lParam)->style;
1818                 return COMBO_NCCreate(hwnd, style);
1819         }
1820         case WM_NCDESTROY:
1821                 COMBO_NCDestroy(lphc);
1822                 break;/* -> DefWindowProc */
1823
1824         case WM_CREATE:
1825         {
1826                 HWND hwndParent;
1827                 LONG style;
1828                 if(unicode)
1829                 {
1830                     hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1831                     style = ((LPCREATESTRUCTW)lParam)->style;
1832                 }
1833                 else
1834                 {
1835                     hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1836                     style = ((LPCREATESTRUCTA)lParam)->style;
1837                 }
1838                 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1839         }
1840
1841         case WM_PRINTCLIENT:
1842                 /* Fallthrough */
1843         case WM_PAINT:
1844                 /* wParam may contain a valid HDC! */
1845                 return  COMBO_Paint(lphc, (HDC)wParam);
1846
1847         case WM_ERASEBKGND:
1848                 /* do all painting in WM_PAINT like Windows does */
1849                 return 1;
1850
1851         case WM_GETDLGCODE:
1852         {
1853                 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1854                 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1855                 {
1856                    int vk = (int)((LPMSG)lParam)->wParam;
1857
1858                    if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1859                        result |= DLGC_WANTMESSAGE;
1860                 }
1861                 return  result;
1862         }
1863         case WM_SIZE:
1864                 if( lphc->hWndLBox &&
1865                   !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc, lParam );
1866                 return  TRUE;
1867         case WM_SETFONT:
1868                 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1869                 return  TRUE;
1870         case WM_GETFONT:
1871                 return  (LRESULT)lphc->hFont;
1872         case WM_SETFOCUS:
1873                if( lphc->wState & CBF_EDIT ) {
1874                    SetFocus( lphc->hWndEdit );
1875                    /* The first time focus is received, select all the text */
1876                    if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1877                        SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1878                        lphc->wState |= CBF_BEENFOCUSED;
1879                    }
1880                }
1881                 else
1882                     COMBO_SetFocus( lphc );
1883                 return  TRUE;
1884         case WM_KILLFOCUS:
1885             {
1886                 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1887                 if( !hwndFocus ||
1888                     (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1889                     COMBO_KillFocus( lphc );
1890                 return  TRUE;
1891             }
1892         case WM_COMMAND:
1893                 return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1894         case WM_GETTEXT:
1895             return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1896                            : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1897         case WM_SETTEXT:
1898         case WM_GETTEXTLENGTH:
1899         case WM_CLEAR:
1900                 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1901                 {
1902                     int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1903                     if (j == -1) return 0;
1904                     return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1905                                      SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1906                 }
1907                 else if( lphc->wState & CBF_EDIT )
1908                 {
1909                     LRESULT ret;
1910                     lphc->wState |= CBF_NOEDITNOTIFY;
1911                     ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1912                                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1913                     lphc->wState &= ~CBF_NOEDITNOTIFY;
1914                     return ret;
1915                 }
1916                 else return CB_ERR;
1917         case WM_CUT:
1918         case WM_PASTE:
1919         case WM_COPY:
1920                 if( lphc->wState & CBF_EDIT )
1921                 {
1922                     return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1923                                      SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1924                 }
1925                 else return  CB_ERR;
1926
1927         case WM_DRAWITEM:
1928         case WM_DELETEITEM:
1929         case WM_COMPAREITEM:
1930         case WM_MEASUREITEM:
1931                 return COMBO_ItemOp(lphc, message, lParam);
1932         case WM_ENABLE:
1933                 if( lphc->wState & CBF_EDIT )
1934                     EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1935                 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1936
1937                 /* Force the control to repaint when the enabled state changes. */
1938                 InvalidateRect(lphc->self, NULL, TRUE);
1939                 return  TRUE;
1940         case WM_SETREDRAW:
1941                 if( wParam )
1942                     lphc->wState &= ~CBF_NOREDRAW;
1943                 else
1944                     lphc->wState |= CBF_NOREDRAW;
1945
1946                 if( lphc->wState & CBF_EDIT )
1947                     SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1948                 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1949                 return  0;
1950         case WM_SYSKEYDOWN:
1951                 if( KEYDATA_ALT & HIWORD(lParam) )
1952                     if( wParam == VK_UP || wParam == VK_DOWN )
1953                         COMBO_FlipListbox( lphc, FALSE, FALSE );
1954                 return  0;
1955
1956         case WM_KEYDOWN:
1957                 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1958                      (lphc->wState & CBF_DROPPED))
1959                 {
1960                    CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1961                    return TRUE;
1962                 }
1963                else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1964                {
1965                   COMBO_FlipListbox( lphc, FALSE, FALSE );
1966                   return TRUE;
1967                }
1968                /* fall through */
1969         case WM_CHAR:
1970         case WM_IME_CHAR:
1971         {
1972                 HWND hwndTarget;
1973
1974                 if( lphc->wState & CBF_EDIT )
1975                     hwndTarget = lphc->hWndEdit;
1976                 else
1977                     hwndTarget = lphc->hWndLBox;
1978
1979                 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
1980                                  SendMessageA(hwndTarget, message, wParam, lParam);
1981         }
1982         case WM_LBUTTONDOWN:
1983                 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1984                 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1985                 return  TRUE;
1986         case WM_LBUTTONUP:
1987                 COMBO_LButtonUp( lphc );
1988                 return  TRUE;
1989         case WM_MOUSEMOVE:
1990                 if( lphc->wState & CBF_CAPTURE )
1991                     COMBO_MouseMove( lphc, wParam, lParam );
1992                 return  TRUE;
1993
1994         case WM_MOUSEWHEEL:
1995                 if (wParam & (MK_SHIFT | MK_CONTROL))
1996                     return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
1997                                      DefWindowProcA(hwnd, message, wParam, lParam);
1998
1999                 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2000                 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2001                 return TRUE;
2002
2003         /* Combo messages */
2004
2005         case CB_ADDSTRING:
2006                 if( unicode )
2007                 {
2008                     if( lphc->dwStyle & CBS_LOWERCASE )
2009                         CharLowerW((LPWSTR)lParam);
2010                     else if( lphc->dwStyle & CBS_UPPERCASE )
2011                         CharUpperW((LPWSTR)lParam);
2012                     return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2013                 }
2014                 else /* unlike the unicode version, the ansi version does not overwrite
2015                         the string if converting case */
2016                 {
2017                     char *string = NULL;
2018                     LRESULT ret;
2019                     if( lphc->dwStyle & CBS_LOWERCASE )
2020                     {
2021                         string = strdupA((LPSTR)lParam);
2022                         CharLowerA(string);
2023                     }
2024
2025                     else if( lphc->dwStyle & CBS_UPPERCASE )
2026                     {
2027                         string = strdupA((LPSTR)lParam);
2028                         CharUpperA(string);
2029                     }
2030
2031                     ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2032                     HeapFree(GetProcessHeap(), 0, string);
2033                     return ret;
2034                 }
2035         case CB_INSERTSTRING:
2036                 if( unicode )
2037                 {
2038                     if( lphc->dwStyle & CBS_LOWERCASE )
2039                         CharLowerW((LPWSTR)lParam);
2040                     else if( lphc->dwStyle & CBS_UPPERCASE )
2041                         CharUpperW((LPWSTR)lParam);
2042                     return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2043                 }
2044                 else
2045                 {
2046                     if( lphc->dwStyle & CBS_LOWERCASE )
2047                         CharLowerA((LPSTR)lParam);
2048                     else if( lphc->dwStyle & CBS_UPPERCASE )
2049                         CharUpperA((LPSTR)lParam);
2050
2051                     return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2052                 }
2053         case CB_DELETESTRING:
2054                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2055                                  SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2056         case CB_SELECTSTRING:
2057                 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2058         case CB_FINDSTRING:
2059                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2060                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2061         case CB_FINDSTRINGEXACT:
2062                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2063                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2064         case CB_SETITEMHEIGHT:
2065                 return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2066         case CB_GETITEMHEIGHT:
2067                 if( (INT)wParam >= 0 )  /* listbox item */
2068                     return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2069                 return  CBGetTextAreaHeight(hwnd, lphc);
2070         case CB_RESETCONTENT:
2071                 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2072                 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2073                 {
2074                     static const WCHAR empty_stringW[] = { 0 };
2075                     SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2076                 }
2077                 else
2078                     InvalidateRect(lphc->self, NULL, TRUE);
2079                 return  TRUE;
2080         case CB_INITSTORAGE:
2081                 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2082         case CB_GETHORIZONTALEXTENT:
2083                 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2084         case CB_SETHORIZONTALEXTENT:
2085                 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2086         case CB_GETTOPINDEX:
2087                 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2088         case CB_GETLOCALE:
2089                 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2090         case CB_SETLOCALE:
2091                 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2092         case CB_SETDROPPEDWIDTH:
2093                 if( (CB_GETTYPE(lphc) == CBS_SIMPLE) ||
2094                     (INT)wParam >= 32768 )
2095                     return CB_ERR;
2096                 /* new value must be higher than combobox width */
2097                 if((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2098                     lphc->droppedWidth = wParam;
2099                 else if(wParam)
2100                     lphc->droppedWidth = 0;
2101
2102                 /* recalculate the combobox area */
2103                 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
2104
2105                 /* fall through */
2106         case CB_GETDROPPEDWIDTH:
2107                 if( lphc->droppedWidth )
2108                     return  lphc->droppedWidth;
2109                 return  lphc->droppedRect.right - lphc->droppedRect.left;
2110         case CB_GETDROPPEDCONTROLRECT:
2111                 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2112                 return  CB_OKAY;
2113         case CB_GETDROPPEDSTATE:
2114                 return (lphc->wState & CBF_DROPPED) != 0;
2115         case CB_DIR:
2116                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2117                                  SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2118
2119         case CB_SHOWDROPDOWN:
2120                 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2121                 {
2122                     if( wParam )
2123                     {
2124                         if( !(lphc->wState & CBF_DROPPED) )
2125                             CBDropDown( lphc );
2126                     }
2127                     else
2128                         if( lphc->wState & CBF_DROPPED )
2129                             CBRollUp( lphc, FALSE, TRUE );
2130                 }
2131                 return  TRUE;
2132         case CB_GETCOUNT:
2133                 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2134         case CB_GETCURSEL:
2135                 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2136         case CB_SETCURSEL:
2137                 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2138                 if( lParam >= 0 )
2139                     SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2140
2141                 /* no LBN_SELCHANGE in this case, update manually */
2142                 if( lphc->wState & CBF_EDIT )
2143                     CBUpdateEdit( lphc, (INT)wParam );
2144                 else
2145                     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2146                 lphc->wState &= ~CBF_SELCHANGE;
2147                 return  lParam;
2148         case CB_GETLBTEXT:
2149                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2150                                  SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2151         case CB_GETLBTEXTLEN:
2152                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2153                                  SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2154         case CB_GETITEMDATA:
2155                 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2156         case CB_SETITEMDATA:
2157                 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2158         case CB_GETEDITSEL:
2159                 /* Edit checks passed parameters itself */
2160                 if( lphc->wState & CBF_EDIT )
2161                     return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2162                 return  CB_ERR;
2163         case CB_SETEDITSEL:
2164                 if( lphc->wState & CBF_EDIT )
2165                     return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2166                           (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2167                 return  CB_ERR;
2168         case CB_SETEXTENDEDUI:
2169                 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2170                     return  CB_ERR;
2171                 if( wParam )
2172                     lphc->wState |= CBF_EUI;
2173                 else lphc->wState &= ~CBF_EUI;
2174                 return  CB_OKAY;
2175         case CB_GETEXTENDEDUI:
2176                 return (lphc->wState & CBF_EUI) != 0;
2177         case CB_GETCOMBOBOXINFO:
2178                 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2179         case CB_LIMITTEXT:
2180                 if( lphc->wState & CBF_EDIT )
2181                         return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2182                 return  TRUE;
2183         default:
2184                 if (message >= WM_USER)
2185                     WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2186                         message - WM_USER, wParam, lParam );
2187                 break;
2188       }
2189       return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2190                        DefWindowProcA(hwnd, message, wParam, lParam);
2191 }
2192
2193 /*************************************************************************
2194  *           GetComboBoxInfo   (USER32.@)
2195  */
2196 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo,      /* [in] handle to combo box */
2197                             PCOMBOBOXINFO pcbi   /* [in/out] combo box information */)
2198 {
2199     TRACE("(%p, %p)\n", hwndCombo, pcbi);
2200     return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2201 }