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