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