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