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