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