CodeWeavers
[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("[%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             /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
751             size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
752             pText[size] = '\0'; /* just in case */
753         } else return;
754    }
755    else
756        if( !CB_OWNERDRAWN(lphc) )
757            return;
758
759    if( lphc->wState & CBF_EDIT )
760    {
761         static const WCHAR empty_stringW[] = { 0 };
762         if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
763         if( lphc->wState & CBF_FOCUSED ) 
764             SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
765    }
766    else /* paint text field ourselves */
767    {
768      UINT       itemState = ODS_COMBOBOXEDIT;
769      HFONT      hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
770
771      /*
772       * Give ourselves some space.
773       */
774      InflateRect( &rectEdit, -1, -1 );
775      
776      if( CB_OWNERDRAWN(lphc) )
777      {
778        DRAWITEMSTRUCT dis;
779        HRGN           clipRegion;
780        UINT ctlid = GetWindowLongA( lphc->self, GWL_ID );
781
782        /* setup state for DRAWITEM message. Owner will highlight */
783        if ( (lphc->wState & CBF_FOCUSED) && 
784             !(lphc->wState & CBF_DROPPED) )
785            itemState |= ODS_SELECTED | ODS_FOCUS;
786
787        /*
788         * Save the current clip region.
789         * To retrieve the clip region, we need to create one "dummy"
790         * clip region.
791         */
792        clipRegion = CreateRectRgnIndirect(&rectEdit);
793        
794        if (GetClipRgn(hdc, clipRegion)!=1)
795        {
796          DeleteObject(clipRegion);
797          clipRegion=(HRGN)NULL;
798        }
799
800        if (!IsWindowEnabled(lphc->self) & WS_DISABLED) itemState |= ODS_DISABLED;
801
802        dis.CtlType      = ODT_COMBOBOX;
803        dis.CtlID        = ctlid;
804        dis.hwndItem     = lphc->self;
805        dis.itemAction   = ODA_DRAWENTIRE;
806        dis.itemID       = id;
807        dis.itemState    = itemState;
808        dis.hDC          = hdc;
809        dis.rcItem       = rectEdit;
810        dis.itemData     = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, 
811                                         (WPARAM)id, 0 );
812        
813        /*
814         * Clip the DC and have the parent draw the item.
815         */
816        IntersectClipRect(hdc,
817                          rectEdit.left,  rectEdit.top,
818                          rectEdit.right, rectEdit.bottom);
819
820        SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
821
822        /*
823         * Reset the clipping region.
824         */
825        SelectClipRgn(hdc, clipRegion);          
826      }
827      else
828      {
829        static const WCHAR empty_stringW[] = { 0 };
830
831        if ( (lphc->wState & CBF_FOCUSED) && 
832             !(lphc->wState & CBF_DROPPED) ) {
833
834            /* highlight */
835            FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
836            SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
837            SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
838        } 
839      
840        ExtTextOutW( hdc, 
841                     rectEdit.left + 1, 
842                     rectEdit.top + 1,
843                     ETO_OPAQUE | ETO_CLIPPED, 
844                     &rectEdit,
845                     pText ? pText : empty_stringW , size, NULL );
846        
847        if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
848          DrawFocusRect( hdc, &rectEdit );
849      }
850      
851      if( hPrevFont ) 
852        SelectObject(hdc, hPrevFont );
853    }
854    if (pText)
855         HeapFree( GetProcessHeap(), 0, pText );
856 }
857
858 /***********************************************************************
859  *           CBPaintBorder
860  */
861 static void CBPaintBorder(
862   HWND        hwnd,
863   LPHEADCOMBO lphc,   
864   HDC         hdc)
865 {
866   RECT clientRect;
867
868   if (CB_GETTYPE(lphc) != CBS_SIMPLE)
869   {
870     GetClientRect(hwnd, &clientRect);
871   }
872   else
873   {
874     CopyRect(&clientRect, &lphc->textRect);
875
876     InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
877     InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
878   }
879
880   DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
881 }
882
883 /***********************************************************************
884  *           COMBO_PrepareColors
885  *
886  * This method will sent the appropriate WM_CTLCOLOR message to
887  * prepare and setup the colors for the combo's DC.
888  *
889  * It also returns the brush to use for the background.
890  */
891 static HBRUSH COMBO_PrepareColors(
892   LPHEADCOMBO lphc,
893   HDC         hDC)
894 {
895   HBRUSH  hBkgBrush;
896
897   /*
898    * Get the background brush for this control.
899    */
900   if (CB_DISABLED(lphc))
901   {
902     hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORSTATIC, hDC, (LPARAM)lphc->self );
903
904     /*
905      * We have to change the text color since WM_CTLCOLORSTATIC will
906      * set it to the "enabled" color. This is the same behavior as the
907      * edit control
908      */
909     SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
910   }
911   else
912   {
913     if (lphc->wState & CBF_EDIT)
914     {
915       hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLOREDIT, hDC, (LPARAM)lphc->self );
916     }
917     else
918     {
919       hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX, hDC, (LPARAM)lphc->self );
920     }
921   }
922
923   /*
924    * Catch errors.
925    */
926   if( !hBkgBrush ) 
927     hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
928
929   return hBkgBrush;
930 }
931
932 /***********************************************************************
933  *           COMBO_EraseBackground
934  */
935 static LRESULT COMBO_EraseBackground(
936   HWND        hwnd, 
937   LPHEADCOMBO lphc,
938   HDC         hParamDC)
939 {
940   HBRUSH  hBkgBrush;
941   HDC     hDC;
942
943   if(lphc->wState & CBF_EDIT)
944       return TRUE;
945   
946   hDC = (hParamDC) ? hParamDC
947                    : GetDC(hwnd);
948   /*
949    * Retrieve the background brush
950    */
951   hBkgBrush = COMBO_PrepareColors(lphc, hDC);
952   
953   FillRect(hDC, &lphc->textRect, hBkgBrush);
954
955   if (!hParamDC)
956     ReleaseDC(hwnd, hDC);
957
958   return TRUE;
959 }
960
961 /***********************************************************************
962  *           COMBO_Paint
963  */
964 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
965 {
966   PAINTSTRUCT ps;
967   HDC   hDC;
968   
969   hDC = (hParamDC) ? hParamDC
970                    : BeginPaint( lphc->self, &ps);
971
972   TRACE("hdc=%04x\n", hDC);
973
974   if( hDC && !(lphc->wState & CBF_NOREDRAW) )
975   {
976       HBRUSH    hPrevBrush, hBkgBrush;
977
978       /*
979        * Retrieve the background brush and select it in the
980        * DC.
981        */
982       hBkgBrush = COMBO_PrepareColors(lphc, hDC);
983
984       hPrevBrush = SelectObject( hDC, hBkgBrush );
985
986       /*
987        * In non 3.1 look, there is a sunken border on the combobox
988        */
989       if (TWEAK_WineLook != WIN31_LOOK)
990       {
991         CBPaintBorder(lphc->self, lphc, hDC);
992       }
993
994       if( !IsRectEmpty(&lphc->buttonRect) )
995       {
996         CBPaintButton(lphc, hDC, lphc->buttonRect);
997       }
998
999       /* paint the edit control padding area */
1000       if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
1001       {
1002           RECT rPadEdit = lphc->textRect;
1003
1004           InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
1005
1006           FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
1007       }
1008  
1009       if( !(lphc->wState & CBF_EDIT) )
1010       {
1011         /*
1012          * The text area has a border only in Win 3.1 look.
1013          */
1014         if (TWEAK_WineLook == WIN31_LOOK)
1015         {
1016           HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
1017           
1018           Rectangle( hDC, 
1019                      lphc->textRect.left, lphc->textRect.top,
1020                      lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1021
1022           SelectObject( hDC, hPrevPen );
1023         }
1024
1025         CBPaintText( lphc, hDC, lphc->textRect);
1026       }
1027
1028       if( hPrevBrush )
1029         SelectObject( hDC, hPrevBrush );
1030   }
1031
1032   if( !hParamDC ) 
1033     EndPaint(lphc->self, &ps);
1034
1035   return 0;
1036 }
1037
1038 /***********************************************************************
1039  *           CBUpdateLBox
1040  *
1041  * Select listbox entry according to the contents of the edit control.
1042  */
1043 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1044 {
1045    INT  length, idx;
1046    LPWSTR pText = NULL;
1047    
1048    idx = LB_ERR;
1049    length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
1050
1051    if( length > 0 ) 
1052        pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1053
1054    TRACE("\t edit text length %i\n", length );
1055
1056    if( pText )
1057    {
1058        if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1059        else pText[0] = '\0';
1060        idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1061                              (WPARAM)(-1), (LPARAM)pText );
1062        HeapFree( GetProcessHeap(), 0, pText );
1063    }
1064
1065    SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1066
1067    /* probably superfluous but Windows sends this too */
1068    SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1069    SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1070
1071    return idx;
1072 }
1073
1074 /***********************************************************************
1075  *           CBUpdateEdit
1076  *
1077  * Copy a listbox entry to the edit control.
1078  */
1079 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1080 {
1081    INT  length;
1082    LPWSTR pText = NULL;
1083    static const WCHAR empty_stringW[] = { 0 };
1084
1085    TRACE("\t %i\n", index );
1086
1087    if( index >= 0 ) /* got an entry */
1088    {
1089        length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1090        if( length )
1091        {
1092            if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1093            {
1094                 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1095                                 (WPARAM)index, (LPARAM)pText );
1096            }
1097        }
1098    }
1099
1100    lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1101    SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1102    lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1103
1104    if( lphc->wState & CBF_FOCUSED )
1105       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1106
1107    if( pText )
1108        HeapFree( GetProcessHeap(), 0, pText );
1109 }
1110
1111 /***********************************************************************
1112  *           CBDropDown
1113  * 
1114  * Show listbox popup.
1115  */
1116 static void CBDropDown( LPHEADCOMBO lphc )
1117 {
1118    RECT rect,r;
1119    int nItems = 0;
1120    int nDroppedHeight;
1121
1122    TRACE("[%04x]: drop down\n", lphc->self);
1123
1124    CB_NOTIFY( lphc, CBN_DROPDOWN );
1125
1126    /* set selection */
1127
1128    lphc->wState |= CBF_DROPPED;
1129    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1130    {
1131        lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1132
1133        /* Update edit only if item is in the list */
1134        if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1135          CBUpdateEdit( lphc, lphc->droppedIndex );
1136    }
1137    else
1138    {
1139        lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1140
1141        SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1142                      (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1143        SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1144    }
1145
1146    /* now set popup position */
1147    GetWindowRect( lphc->self, &rect );
1148    
1149    /*
1150     * If it's a dropdown, the listbox is offset
1151     */
1152    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1153      rect.left += COMBO_EDITBUTTONSPACE();
1154
1155   /* if the dropped height is greater than the total height of the dropped
1156      items list, then force the drop down list height to be the total height
1157      of the items in the dropped list */
1158
1159   /* And Remove any extra space (Best Fit) */
1160    nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1161   /* if listbox length has been set directly by its handle */
1162    GetWindowRect(lphc->hWndLBox, &r);
1163    if (nDroppedHeight < r.bottom - r.top)
1164        nDroppedHeight = r.bottom - r.top;
1165    nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1166
1167    if (nItems > 0)
1168    {
1169       int nHeight;
1170
1171       nHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1172       nHeight *= nItems;
1173
1174       if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1175          nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1176    }
1177
1178    /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1179    if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1180       rect.bottom = rect.top - nDroppedHeight;
1181
1182    SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom, 
1183                  lphc->droppedRect.right - lphc->droppedRect.left,
1184                  nDroppedHeight,
1185                  SWP_NOACTIVATE | SWP_SHOWWINDOW);
1186
1187
1188    if( !(lphc->wState & CBF_NOREDRAW) )
1189      RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE | 
1190                            RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1191
1192    EnableWindow( lphc->hWndLBox, TRUE );
1193    if (GetCapture() != lphc->self)
1194       SetCapture(lphc->hWndLBox);
1195 }
1196
1197 /***********************************************************************
1198  *           CBRollUp
1199  *
1200  * Hide listbox popup.
1201  */
1202 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1203 {
1204    HWND hWnd = lphc->self;
1205
1206    TRACE("[%04x]: sel ok? [%i] dropped? [%i]\n", 
1207          lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED));
1208
1209    CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1210
1211    if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1212    {
1213
1214        if( lphc->wState & CBF_DROPPED ) 
1215        {
1216            RECT rect;
1217
1218            lphc->wState &= ~CBF_DROPPED;
1219            ShowWindow( lphc->hWndLBox, SW_HIDE );
1220
1221            if(GetCapture() == lphc->hWndLBox)
1222            {
1223                ReleaseCapture();
1224            }
1225
1226            if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1227            {
1228                rect = lphc->buttonRect;
1229            }
1230            else 
1231            {
1232                if( bButton )
1233                {
1234                  UnionRect( &rect,
1235                             &lphc->buttonRect,
1236                             &lphc->textRect);
1237                }
1238                else
1239                  rect = lphc->textRect;
1240
1241                bButton = TRUE;
1242            }
1243
1244            if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1245                RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE | 
1246                                RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1247            CB_NOTIFY( lphc, CBN_CLOSEUP );
1248        }
1249    }
1250 }
1251
1252 /***********************************************************************
1253  *           COMBO_FlipListbox
1254  *
1255  * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1256  */
1257 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1258 {
1259    if( lphc->wState & CBF_DROPPED )
1260    {
1261        CBRollUp( lphc, ok, bRedrawButton );
1262        return FALSE;
1263    }
1264
1265    CBDropDown( lphc );
1266    return TRUE;
1267 }
1268
1269 /***********************************************************************
1270  *           CBRepaintButton
1271  */
1272 static void CBRepaintButton( LPHEADCOMBO lphc )
1273    {
1274   InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1275   UpdateWindow(lphc->self);
1276 }
1277
1278 /***********************************************************************
1279  *           COMBO_SetFocus
1280  */
1281 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1282 {
1283    if( !(lphc->wState & CBF_FOCUSED) )
1284    {
1285        if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1286            SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1287
1288        /* This is wrong. Message sequences seem to indicate that this
1289           is set *after* the notify. */
1290        /* lphc->wState |= CBF_FOCUSED;  */
1291
1292        if( !(lphc->wState & CBF_EDIT) )
1293          InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1294
1295        CB_NOTIFY( lphc, CBN_SETFOCUS );
1296        lphc->wState |= CBF_FOCUSED;
1297    }
1298 }
1299
1300 /***********************************************************************
1301  *           COMBO_KillFocus
1302  */
1303 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1304 {
1305    HWND hWnd = lphc->self;
1306
1307    if( lphc->wState & CBF_FOCUSED )
1308    {
1309        CBRollUp( lphc, FALSE, TRUE );
1310        if( IsWindow( hWnd ) )
1311        {
1312            if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1313                SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1314
1315            lphc->wState &= ~CBF_FOCUSED;
1316
1317            /* redraw text */
1318            if( !(lphc->wState & CBF_EDIT) )
1319              InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1320
1321            CB_NOTIFY( lphc, CBN_KILLFOCUS );
1322        }
1323    }
1324 }
1325
1326 /***********************************************************************
1327  *           COMBO_Command
1328  */
1329 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1330 {
1331    if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1332    {
1333        /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1334
1335        switch( HIWORD(wParam) >> 8 )
1336        {   
1337            case (EN_SETFOCUS >> 8):
1338
1339                 TRACE("[%04x]: edit [%04x] got focus\n", 
1340                              lphc->self, lphc->hWndEdit );
1341
1342                 COMBO_SetFocus( lphc );
1343                 break;
1344
1345            case (EN_KILLFOCUS >> 8):
1346
1347                 TRACE("[%04x]: edit [%04x] lost focus\n",
1348                              lphc->self, lphc->hWndEdit );
1349
1350                 /* NOTE: it seems that Windows' edit control sends an
1351                  * undocumented message WM_USER + 0x1B instead of this
1352                  * notification (only when it happens to be a part of 
1353                  * the combo). ?? - AK.
1354                  */
1355
1356                 COMBO_KillFocus( lphc );
1357                 break;
1358
1359
1360            case (EN_CHANGE >> 8):
1361                /*
1362                 * In some circumstances (when the selection of the combobox
1363                 * is changed for example) we don't wans the EN_CHANGE notification
1364                 * to be forwarded to the parent of the combobox. This code
1365                 * checks a flag that is set in these occasions and ignores the 
1366                 * notification.
1367                 */
1368                 if (lphc->wState & CBF_NOLBSELECT)
1369                 {
1370                   lphc->wState &= ~CBF_NOLBSELECT;
1371                 }
1372                 else
1373                 {
1374                   CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1375                 }
1376
1377                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1378                   CB_NOTIFY( lphc, CBN_EDITCHANGE );
1379                 break;
1380
1381            case (EN_UPDATE >> 8):
1382                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1383                   CB_NOTIFY( lphc, CBN_EDITUPDATE );
1384                 break;
1385
1386            case (EN_ERRSPACE >> 8):
1387                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1388        }
1389    }
1390    else if( lphc->hWndLBox == hWnd )
1391    {
1392        switch( HIWORD(wParam) )
1393        {
1394            case LBN_ERRSPACE:
1395                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1396                 break;
1397
1398            case LBN_DBLCLK:
1399                 CB_NOTIFY( lphc, CBN_DBLCLK );
1400                 break;
1401
1402            case LBN_SELCHANGE:
1403            case LBN_SELCANCEL:
1404
1405                 TRACE("[%04x]: lbox selection change [%04x]\n", 
1406                              lphc->self, lphc->wState );
1407
1408                 if( HIWORD(wParam) == LBN_SELCHANGE)
1409                 {
1410                    if( lphc->wState & CBF_EDIT )
1411                    {
1412                        INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1413                        lphc->wState |= CBF_NOLBSELECT;
1414                        CBUpdateEdit( lphc, index );
1415                        /* select text in edit, as Windows does */
1416                        SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1417                    }
1418                    else
1419                        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1420                 }
1421
1422                 /* do not roll up if selection is being tracked 
1423                  * by arrowkeys in the dropdown listbox */
1424                 if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1425                 {
1426                    CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1427                 }
1428                 else lphc->wState &= ~CBF_NOROLLUP;
1429
1430                 CB_NOTIFY( lphc, CBN_SELCHANGE );
1431
1432                 /* fall through */
1433
1434            case LBN_SETFOCUS:
1435            case LBN_KILLFOCUS:
1436                 /* nothing to do here since ComboLBox always resets the focus to its
1437                  * combo/edit counterpart */
1438                  break;
1439        }
1440    }
1441    return 0;
1442 }
1443
1444 /***********************************************************************
1445  *           COMBO_ItemOp
1446  *
1447  * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1448  */
1449 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1450 {
1451    HWND hWnd = lphc->self;
1452    UINT id = GetWindowLongA( hWnd, GWL_ID );
1453
1454    TRACE("[%04x]: ownerdraw op %04x\n", lphc->self, msg );
1455
1456    switch( msg )
1457    {
1458    case WM_DELETEITEM:
1459        {
1460            DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1461            lpIS->CtlType  = ODT_COMBOBOX;
1462            lpIS->CtlID    = id;
1463            lpIS->hwndItem = hWnd;
1464            break;
1465        }
1466    case WM_DRAWITEM:
1467        {
1468            DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1469            lpIS->CtlType  = ODT_COMBOBOX;
1470            lpIS->CtlID    = id;
1471            lpIS->hwndItem = hWnd;
1472            break;
1473        }
1474    case WM_COMPAREITEM:
1475        {
1476            COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1477            lpIS->CtlType  = ODT_COMBOBOX;
1478            lpIS->CtlID    = id;
1479            lpIS->hwndItem = hWnd;
1480            break;
1481        }
1482    case WM_MEASUREITEM:
1483        {
1484            MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1485            lpIS->CtlType  = ODT_COMBOBOX;
1486            lpIS->CtlID    = id;
1487            break;
1488        }
1489    }
1490    return SendMessageW(lphc->owner, msg, id, lParam);
1491 }
1492
1493 /***********************************************************************
1494  *           COMBO_GetText
1495  *
1496  * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1497  *       also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1498  */
1499 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, INT N, LPARAM lParam, BOOL unicode)
1500 {
1501    if( lphc->wState & CBF_EDIT )
1502        return unicode ? SendMessageW(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam) :
1503                         SendMessageA(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam);
1504
1505    /* get it from the listbox */
1506
1507    if( lphc->hWndLBox )
1508    {
1509        INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1510        if( idx != LB_ERR )
1511        {
1512            INT n = 0;
1513            INT length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN,
1514                                                 (WPARAM)idx, 0 );
1515
1516             if(unicode)
1517             {
1518                 LPWSTR lpBuffer, lpText = (LPWSTR)lParam;
1519
1520                 /* 'length' is without the terminating character */
1521                 if(length >= N)
1522                     lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1523                 else
1524                     lpBuffer = lpText;
1525
1526                 if(lpBuffer)
1527                 {
1528                     n = SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1529
1530                     /* truncate if buffer is too short */
1531                     if(length >= N)
1532                     {
1533                         if(N && lpText)
1534                         {
1535                             if(n != LB_ERR)
1536                                 strncpyW(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1537                             lpText[N - 1] = '\0';
1538                         }
1539                         HeapFree( GetProcessHeap(), 0, lpBuffer );
1540                    }
1541                }
1542            }
1543            else
1544            {
1545                 LPSTR lpBuffer, lpText = (LPSTR)lParam;
1546
1547                 /* 'length' is without the terminating character */
1548                 if(length >= N)
1549                     lpBuffer = HeapAlloc(GetProcessHeap(), 0, length + 1);
1550                 else
1551                     lpBuffer = lpText;
1552
1553                 if(lpBuffer)
1554                 {
1555                     n = SendMessageA(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1556
1557                     /* truncate if buffer is too short */
1558                     if(length >= N)
1559                     {
1560                         if(N && lpText)
1561                         {
1562                             if(n != LB_ERR)
1563                                 strncpy(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1564                             lpText[N - 1] = '\0';
1565                         }
1566                         HeapFree( GetProcessHeap(), 0, lpBuffer );
1567                    }
1568                }
1569            }
1570            if (n<0)
1571                n=0;
1572            else
1573                n++;
1574            return (LRESULT)n;
1575        }
1576    }
1577    return 0;
1578 }
1579
1580
1581 /***********************************************************************
1582  *           CBResetPos
1583  *
1584  * This function sets window positions according to the updated 
1585  * component placement struct.
1586  */
1587 static void CBResetPos(
1588   LPHEADCOMBO lphc, 
1589   LPRECT      rectEdit,
1590   LPRECT      rectLB,
1591   BOOL        bRedraw)
1592 {
1593    BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1594
1595    /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1596     * sizing messages */
1597
1598    if( lphc->wState & CBF_EDIT )
1599      SetWindowPos( lphc->hWndEdit, 0, 
1600                    rectEdit->left, rectEdit->top,
1601                    rectEdit->right - rectEdit->left,
1602                    rectEdit->bottom - rectEdit->top,
1603                        SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1604
1605    SetWindowPos( lphc->hWndLBox, 0,
1606                  rectLB->left, rectLB->top,
1607                  rectLB->right - rectLB->left, 
1608                  rectLB->bottom - rectLB->top, 
1609                    SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1610
1611    if( bDrop )
1612    {
1613        if( lphc->wState & CBF_DROPPED )
1614        {
1615            lphc->wState &= ~CBF_DROPPED;
1616            ShowWindow( lphc->hWndLBox, SW_HIDE );
1617        }
1618
1619        if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1620            RedrawWindow( lphc->self, NULL, 0,
1621                            RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1622    }
1623 }
1624
1625
1626 /***********************************************************************
1627  *           COMBO_Size
1628  */
1629 static void COMBO_Size( LPHEADCOMBO lphc )
1630   {
1631   CBCalcPlacement(lphc->self,
1632                   lphc, 
1633                   &lphc->textRect, 
1634                   &lphc->buttonRect, 
1635                   &lphc->droppedRect);
1636
1637   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1638 }
1639
1640
1641 /***********************************************************************
1642  *           COMBO_Font
1643  */
1644 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1645 {
1646   /*
1647    * Set the font
1648    */
1649   lphc->hFont = hFont;
1650
1651   /*
1652    * Propagate to owned windows.
1653    */
1654   if( lphc->wState & CBF_EDIT )
1655       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1656   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1657
1658   /*
1659    * Redo the layout of the control.
1660    */
1661   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1662   {
1663     CBCalcPlacement(lphc->self,
1664                     lphc, 
1665                     &lphc->textRect, 
1666                     &lphc->buttonRect, 
1667                     &lphc->droppedRect);
1668     
1669     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1670   }
1671   else
1672   {
1673     CBForceDummyResize(lphc);
1674   }
1675 }
1676
1677
1678 /***********************************************************************
1679  *           COMBO_SetItemHeight
1680  */
1681 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1682 {
1683    LRESULT      lRet = CB_ERR;
1684
1685    if( index == -1 ) /* set text field height */
1686    {
1687        if( height < 32768 )
1688        {
1689            lphc->editHeight = height;
1690
1691          /*
1692           * Redo the layout of the control.
1693           */
1694          if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1695          {
1696            CBCalcPlacement(lphc->self,
1697                            lphc, 
1698                            &lphc->textRect, 
1699                            &lphc->buttonRect, 
1700                            &lphc->droppedRect);
1701            
1702            CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1703          }
1704          else
1705          {
1706            CBForceDummyResize(lphc);
1707          }
1708                  
1709            lRet = height;
1710        }
1711    } 
1712    else if ( CB_OWNERDRAWN(lphc) )      /* set listbox item height */
1713         lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, 
1714                               (WPARAM)index, (LPARAM)height );
1715    return lRet;
1716 }
1717
1718 /***********************************************************************
1719  *           COMBO_SelectString
1720  */
1721 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1722 {
1723    INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1724                          SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1725    if( index >= 0 )
1726    {
1727      if( lphc->wState & CBF_EDIT )
1728        CBUpdateEdit( lphc, index );
1729      else
1730      {
1731        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1732      }
1733    }
1734    return (LRESULT)index;
1735 }
1736
1737 /***********************************************************************
1738  *           COMBO_LButtonDown
1739  */
1740 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1741 {
1742    POINT     pt;
1743    BOOL      bButton;
1744    HWND      hWnd = lphc->self;
1745
1746    pt.x = LOWORD(lParam);
1747    pt.y = HIWORD(lParam);
1748    bButton = PtInRect(&lphc->buttonRect, pt);
1749
1750    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1751        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1752    {
1753        lphc->wState |= CBF_BUTTONDOWN;
1754        if( lphc->wState & CBF_DROPPED )
1755        {
1756            /* got a click to cancel selection */
1757
1758            lphc->wState &= ~CBF_BUTTONDOWN;
1759            CBRollUp( lphc, TRUE, FALSE );
1760            if( !IsWindow( hWnd ) ) return;
1761
1762            if( lphc->wState & CBF_CAPTURE )
1763            {
1764                lphc->wState &= ~CBF_CAPTURE;
1765                ReleaseCapture();
1766            }
1767        }
1768        else
1769        {
1770            /* drop down the listbox and start tracking */
1771
1772            lphc->wState |= CBF_CAPTURE;
1773            SetCapture( hWnd );
1774            CBDropDown( lphc );
1775        }
1776        if( bButton ) CBRepaintButton( lphc );
1777    }
1778 }
1779
1780 /***********************************************************************
1781  *           COMBO_LButtonUp
1782  *
1783  * Release capture and stop tracking if needed.
1784  */
1785 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1786 {
1787    if( lphc->wState & CBF_CAPTURE )
1788    {
1789        lphc->wState &= ~CBF_CAPTURE;
1790        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1791        {
1792            INT index = CBUpdateLBox( lphc, TRUE );
1793            /* Update edit only if item is in the list */
1794            if(index >= 0)
1795            {
1796                lphc->wState |= CBF_NOLBSELECT;
1797                CBUpdateEdit( lphc, index );
1798                lphc->wState &= ~CBF_NOLBSELECT;
1799            }
1800        }
1801        ReleaseCapture();
1802        SetCapture(lphc->hWndLBox);
1803    }
1804
1805    if( lphc->wState & CBF_BUTTONDOWN )
1806    {
1807        lphc->wState &= ~CBF_BUTTONDOWN;
1808        CBRepaintButton( lphc );
1809    }
1810 }
1811
1812 /***********************************************************************
1813  *           COMBO_MouseMove
1814  *
1815  * Two things to do - track combo button and release capture when
1816  * pointer goes into the listbox.
1817  */
1818 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1819 {
1820    POINT  pt;
1821    RECT   lbRect;
1822    
1823    pt.x = LOWORD(lParam);
1824    pt.y = HIWORD(lParam);
1825    
1826    if( lphc->wState & CBF_BUTTONDOWN )
1827    {
1828      BOOL bButton;
1829
1830      bButton = PtInRect(&lphc->buttonRect, pt);
1831
1832      if( !bButton )
1833      {
1834        lphc->wState &= ~CBF_BUTTONDOWN;
1835        CBRepaintButton( lphc );
1836      }
1837    }
1838
1839    GetClientRect( lphc->hWndLBox, &lbRect );
1840    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1841    if( PtInRect(&lbRect, pt) )
1842    {
1843        lphc->wState &= ~CBF_CAPTURE;
1844        ReleaseCapture();
1845        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1846
1847        /* hand over pointer tracking */
1848        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1849    }
1850 }
1851
1852
1853 /***********************************************************************
1854  *           ComboWndProc_common
1855  *
1856  * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1857  */
1858 static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
1859                                     WPARAM wParam, LPARAM lParam, BOOL unicode )
1860 {
1861       LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongA( hwnd, 0 );
1862
1863       TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1864             hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1865
1866       if( lphc || message == WM_NCCREATE )
1867       switch(message) 
1868       { 
1869
1870         /* System messages */
1871
1872         case WM_NCCREATE: 
1873         {
1874                 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1875                                        ((LPCREATESTRUCTA)lParam)->style;
1876                 return COMBO_NCCreate(hwnd, style);
1877         }
1878         case WM_NCDESTROY: 
1879                 COMBO_NCDestroy(lphc);
1880                 break;/* -> DefWindowProc */
1881
1882         case WM_CREATE: 
1883         { 
1884                 HWND hwndParent;
1885                 LONG style;
1886                 if(unicode)
1887                 {
1888                     hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1889                     style = ((LPCREATESTRUCTW)lParam)->style;
1890                 }
1891                 else
1892                 {
1893                     hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1894                     style = ((LPCREATESTRUCTA)lParam)->style;
1895                 }
1896                 return COMBO_Create(hwnd, lphc, hwndParent, style);
1897         }
1898
1899         case WM_PRINTCLIENT:
1900                 if (lParam & PRF_ERASEBKGND)
1901                   COMBO_EraseBackground(hwnd, lphc, wParam);
1902
1903                 /* Fallthrough */
1904         case WM_PAINT:
1905                 /* wParam may contain a valid HDC! */
1906                 return  COMBO_Paint(lphc, wParam);
1907         case WM_ERASEBKGND:
1908                 return  COMBO_EraseBackground(hwnd, lphc, wParam);
1909         case WM_GETDLGCODE:
1910         {
1911                 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1912                 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1913                 {
1914                    int vk = (int)((LPMSG)lParam)->wParam;
1915
1916                    if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1917                        result |= DLGC_WANTMESSAGE;
1918                 }
1919                 return  result;
1920         }
1921         case WM_WINDOWPOSCHANGING:
1922                 return  COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1923     case WM_WINDOWPOSCHANGED:
1924         /* SetWindowPos can be called on a Combobox to resize its Listbox.
1925          * In that case, the Combobox itself will not be resized, so we won't
1926          * get a WM_SIZE. Since we still want to update the Listbox, we have to
1927          * do it here.
1928          */
1929         /* fall through */
1930         case WM_SIZE:
1931                 if( lphc->hWndLBox && 
1932                   !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1933                 return  TRUE;
1934         case WM_SETFONT:
1935                 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1936                 return  TRUE;
1937         case WM_GETFONT:
1938                 return  (LRESULT)lphc->hFont;
1939         case WM_SETFOCUS:
1940                 if( lphc->wState & CBF_EDIT )
1941                     SetFocus( lphc->hWndEdit );
1942                 else
1943                     COMBO_SetFocus( lphc );
1944                 return  TRUE;
1945         case WM_KILLFOCUS:
1946             {
1947                 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1948                 if( !hwndFocus ||
1949                     (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1950                     COMBO_KillFocus( lphc );
1951                 return  TRUE;
1952             }
1953         case WM_COMMAND:
1954                 return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1955         case WM_GETTEXT:
1956                 return COMBO_GetText( lphc, (INT)wParam, lParam, unicode );
1957         case WM_SETTEXT:
1958         case WM_GETTEXTLENGTH:
1959         case WM_CLEAR:
1960                 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1961                 {
1962                 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1963                 if (j == -1) return 0;
1964                 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1965                 }
1966                 else if( lphc->wState & CBF_EDIT ) 
1967                 {
1968                     LRESULT ret;
1969                     lphc->wState |= CBF_NOEDITNOTIFY;
1970                     ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1971                                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1972                     lphc->wState &= ~CBF_NOEDITNOTIFY;
1973                     return ret;
1974                 }
1975                 else return CB_ERR;
1976         case WM_CUT:
1977         case WM_PASTE:
1978         case WM_COPY:
1979                 if( lphc->wState & CBF_EDIT ) 
1980                 {
1981                     return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1982                                      SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1983                 }
1984                 else return  CB_ERR;
1985
1986         case WM_DRAWITEM:
1987         case WM_DELETEITEM:
1988         case WM_COMPAREITEM:
1989         case WM_MEASUREITEM:
1990                 return COMBO_ItemOp(lphc, message, lParam);
1991         case WM_ENABLE:
1992                 if( lphc->wState & CBF_EDIT )
1993                     EnableWindow( lphc->hWndEdit, (BOOL)wParam ); 
1994                 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1995
1996                 /* Force the control to repaint when the enabled state changes. */
1997                 InvalidateRect(lphc->self, NULL, TRUE);
1998                 return  TRUE;
1999         case WM_SETREDRAW:
2000                 if( wParam )
2001                     lphc->wState &= ~CBF_NOREDRAW;
2002                 else
2003                     lphc->wState |= CBF_NOREDRAW;
2004
2005                 if( lphc->wState & CBF_EDIT )
2006                     SendMessageW(lphc->hWndEdit, message, wParam, lParam);
2007                 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
2008                 return  0;
2009         case WM_SYSKEYDOWN:
2010                 if( KEYDATA_ALT & HIWORD(lParam) )
2011                     if( wParam == VK_UP || wParam == VK_DOWN )
2012                         COMBO_FlipListbox( lphc, FALSE, FALSE );
2013                 return  0;
2014
2015         case WM_CHAR:
2016         case WM_KEYDOWN:
2017         {
2018                 HWND hwndTarget;
2019
2020                 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2021                      (lphc->wState & CBF_DROPPED))
2022                 {
2023                    CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2024                    return TRUE;
2025                 }
2026
2027                 if( lphc->wState & CBF_EDIT )
2028                     hwndTarget = lphc->hWndEdit;
2029                 else
2030                     hwndTarget = lphc->hWndLBox;
2031
2032                 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2033                                  SendMessageA(hwndTarget, message, wParam, lParam);
2034         }
2035         case WM_LBUTTONDOWN: 
2036                 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2037                 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2038                 return  TRUE;
2039         case WM_LBUTTONUP:
2040                 COMBO_LButtonUp( lphc );
2041                 return  TRUE;
2042         case WM_MOUSEMOVE: 
2043                 if( lphc->wState & CBF_CAPTURE ) 
2044                     COMBO_MouseMove( lphc, wParam, lParam );
2045                 return  TRUE;
2046
2047         case WM_MOUSEWHEEL:
2048                 if (wParam & (MK_SHIFT | MK_CONTROL))
2049                     return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2050                                      DefWindowProcA(hwnd, message, wParam, lParam);
2051                 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2052                 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2053                 return TRUE;
2054
2055         /* Combo messages */
2056
2057         case CB_ADDSTRING16:
2058                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2059                 /* fall through */
2060         case CB_ADDSTRING:
2061                 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2062                                  SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2063         case CB_INSERTSTRING16:
2064                 wParam = (INT)(INT16)wParam;
2065                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2066                 /* fall through */
2067         case CB_INSERTSTRING:
2068                 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2069                                  SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2070         case CB_DELETESTRING16:
2071         case CB_DELETESTRING:
2072                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2073                                  SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2074         case CB_SELECTSTRING16:
2075                 wParam = (INT)(INT16)wParam;
2076                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2077                 /* fall through */
2078         case CB_SELECTSTRING:
2079                 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2080         case CB_FINDSTRING16:
2081                 wParam = (INT)(INT16)wParam;
2082                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2083                 /* fall through */
2084         case CB_FINDSTRING:
2085                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2086                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2087         case CB_FINDSTRINGEXACT16:
2088                 wParam = (INT)(INT16)wParam;
2089                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2090                 /* fall through */
2091         case CB_FINDSTRINGEXACT:
2092                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2093                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2094         case CB_SETITEMHEIGHT16:
2095                 wParam = (INT)(INT16)wParam;    /* signed integer */
2096                 /* fall through */
2097         case CB_SETITEMHEIGHT:
2098                 return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2099         case CB_GETITEMHEIGHT16:
2100                 wParam = (INT)(INT16)wParam;
2101                 /* fall through */
2102         case CB_GETITEMHEIGHT:
2103                 if( (INT)wParam >= 0 )  /* listbox item */
2104                     return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2105                 return  CBGetTextAreaHeight(hwnd, lphc);
2106         case CB_RESETCONTENT16: 
2107         case CB_RESETCONTENT:
2108                 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2109                 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2110                 {
2111                     static const WCHAR empty_stringW[] = { 0 };
2112                     SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2113                 }
2114                 else
2115                     InvalidateRect(lphc->self, NULL, TRUE);
2116                 return  TRUE;
2117         case CB_INITSTORAGE:
2118                 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2119         case CB_GETHORIZONTALEXTENT:
2120                 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2121         case CB_SETHORIZONTALEXTENT:
2122                 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2123         case CB_GETTOPINDEX:
2124                 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2125         case CB_GETLOCALE:
2126                 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2127         case CB_SETLOCALE:
2128                 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2129         case CB_GETDROPPEDWIDTH:
2130                 if( lphc->droppedWidth )
2131                     return  lphc->droppedWidth;
2132                 return  lphc->droppedRect.right - lphc->droppedRect.left;
2133         case CB_SETDROPPEDWIDTH:
2134                 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2135                     (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2136                 return  CB_ERR;
2137         case CB_GETDROPPEDCONTROLRECT16:
2138                 lParam = (LPARAM)MapSL(lParam);
2139                 if( lParam ) 
2140                 {
2141                     RECT        r;
2142                     CBGetDroppedControlRect( lphc, &r );
2143                     CONV_RECT32TO16( &r, (LPRECT16)lParam );
2144                 }
2145                 return  CB_OKAY;
2146         case CB_GETDROPPEDCONTROLRECT:
2147                 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2148                 return  CB_OKAY;
2149         case CB_GETDROPPEDSTATE16:
2150         case CB_GETDROPPEDSTATE:
2151                 return  (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2152         case CB_DIR16: 
2153                 lParam = (LPARAM)MapSL(lParam);
2154                 message = LB_DIR16;
2155                 /* fall through */
2156         case CB_DIR:
2157                 if(message == CB_DIR) message = LB_DIR;
2158                 return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) :
2159                                  SendMessageA(lphc->hWndLBox, message, wParam, lParam);
2160
2161         case CB_SHOWDROPDOWN16:
2162         case CB_SHOWDROPDOWN:
2163                 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2164                 {
2165                     if( wParam )
2166                     {
2167                         if( !(lphc->wState & CBF_DROPPED) )
2168                             CBDropDown( lphc );
2169                     }
2170                     else 
2171                         if( lphc->wState & CBF_DROPPED ) 
2172                             CBRollUp( lphc, FALSE, TRUE );
2173                 }
2174                 return  TRUE;
2175         case CB_GETCOUNT16: 
2176         case CB_GETCOUNT:
2177                 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2178         case CB_GETCURSEL16: 
2179         case CB_GETCURSEL:
2180                 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2181         case CB_SETCURSEL16:
2182                 wParam = (INT)(INT16)wParam;
2183                 /* fall through */
2184         case CB_SETCURSEL:
2185                 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2186                 if( lParam >= 0 )
2187                     SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2188
2189                 /* no LBN_SELCHANGE in this case, update manually */
2190                 if( lphc->wState & CBF_EDIT )
2191                     CBUpdateEdit( lphc, (INT)wParam );
2192                 else
2193                     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2194                 lphc->wState &= ~CBF_SELCHANGE;
2195                 return  lParam;
2196         case CB_GETLBTEXT16: 
2197                 wParam = (INT)(INT16)wParam;
2198                 lParam = (LPARAM)MapSL(lParam);
2199                 /* fall through */
2200         case CB_GETLBTEXT:
2201                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2202                                  SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2203         case CB_GETLBTEXTLEN16: 
2204                 wParam = (INT)(INT16)wParam;
2205                 /* fall through */
2206         case CB_GETLBTEXTLEN:
2207                 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2208         case CB_GETITEMDATA16:
2209                 wParam = (INT)(INT16)wParam;
2210                 /* fall through */
2211         case CB_GETITEMDATA:
2212                 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2213         case CB_SETITEMDATA16:
2214                 wParam = (INT)(INT16)wParam;
2215                 /* fall through */
2216         case CB_SETITEMDATA:
2217                 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2218         case CB_GETEDITSEL16: 
2219                 wParam = lParam = 0;   /* just in case */
2220                 /* fall through */
2221         case CB_GETEDITSEL:
2222                 /* Edit checks passed parameters itself */
2223                 if( lphc->wState & CBF_EDIT )
2224                     return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2225                 return  CB_ERR;
2226         case CB_SETEDITSEL16: 
2227         case CB_SETEDITSEL:
2228                 if( lphc->wState & CBF_EDIT ) 
2229                     return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2230                           (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2231                 return  CB_ERR;
2232         case CB_SETEXTENDEDUI16:
2233         case CB_SETEXTENDEDUI:
2234                 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2235                     return  CB_ERR;
2236                 if( wParam )
2237                     lphc->wState |= CBF_EUI;
2238                 else lphc->wState &= ~CBF_EUI;
2239                 return  CB_OKAY;
2240         case CB_GETEXTENDEDUI16:
2241         case CB_GETEXTENDEDUI:
2242                 return  (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2243
2244         default:
2245                 if (message >= WM_USER)
2246                     WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2247                         message - WM_USER, wParam, lParam );
2248                 break;
2249       }
2250       return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2251                        DefWindowProcA(hwnd, message, wParam, lParam);
2252 }
2253
2254 /***********************************************************************
2255  *           ComboWndProcA
2256  *
2257  * This is just a wrapper for the real ComboWndProc which locks/unlocks
2258  * window structs.
2259  */
2260 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2261 {
2262     if (!IsWindow(hwnd)) return 0;
2263     return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2264 }
2265
2266 /***********************************************************************
2267  *           ComboWndProcW
2268  */
2269 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2270 {
2271     if (!IsWindow(hwnd)) return 0;
2272     return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2273 }