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