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