user32: Avoid sending unwanted DM_GETDEFID/DM_SETDEFID messages.
[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        GetWindowTextW( lphc->hWndEdit, pText, length + 1);
999        idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1000                              (WPARAM)(-1), (LPARAM)pText );
1001        HeapFree( GetProcessHeap(), 0, pText );
1002    }
1003
1004    SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1005
1006    /* probably superfluous but Windows sends this too */
1007    SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1008    SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1009
1010    return idx;
1011 }
1012
1013 /***********************************************************************
1014  *           CBUpdateEdit
1015  *
1016  * Copy a listbox entry to the edit control.
1017  */
1018 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1019 {
1020    INT  length;
1021    LPWSTR pText = NULL;
1022    static const WCHAR empty_stringW[] = { 0 };
1023
1024    TRACE("\t %i\n", index );
1025
1026    if( index >= 0 ) /* got an entry */
1027    {
1028        length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1029        if( length != LB_ERR)
1030        {
1031            if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1032            {
1033                 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1034                                 (WPARAM)index, (LPARAM)pText );
1035            }
1036        }
1037    }
1038
1039    lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1040    SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1041    lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1042
1043    if( lphc->wState & CBF_FOCUSED )
1044       SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1045
1046    HeapFree( GetProcessHeap(), 0, pText );
1047 }
1048
1049 /***********************************************************************
1050  *           CBDropDown
1051  *
1052  * Show listbox popup.
1053  */
1054 static void CBDropDown( LPHEADCOMBO lphc )
1055 {
1056     HMONITOR monitor;
1057     MONITORINFO mon_info;
1058    RECT rect,r;
1059    int nItems = 0;
1060    int nDroppedHeight;
1061
1062    TRACE("[%p]: drop down\n", lphc->self);
1063
1064    CB_NOTIFY( lphc, CBN_DROPDOWN );
1065
1066    /* set selection */
1067
1068    lphc->wState |= CBF_DROPPED;
1069    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1070    {
1071        lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1072
1073        /* Update edit only if item is in the list */
1074        if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1075          CBUpdateEdit( lphc, lphc->droppedIndex );
1076    }
1077    else
1078    {
1079        lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1080
1081        SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1082                      (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1083        SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1084    }
1085
1086    /* now set popup position */
1087    GetWindowRect( lphc->self, &rect );
1088
1089    /*
1090     * If it's a dropdown, the listbox is offset
1091     */
1092    if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1093      rect.left += COMBO_EDITBUTTONSPACE();
1094
1095   /* if the dropped height is greater than the total height of the dropped
1096      items list, then force the drop down list height to be the total height
1097      of the items in the dropped list */
1098
1099   /* And Remove any extra space (Best Fit) */
1100    nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1101   /* if listbox length has been set directly by its handle */
1102    GetWindowRect(lphc->hWndLBox, &r);
1103    if (nDroppedHeight < r.bottom - r.top)
1104        nDroppedHeight = r.bottom - r.top;
1105    nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1106
1107    if (nItems > 0)
1108    {
1109       int nHeight;
1110       int nIHeight;
1111
1112       nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1113
1114       nHeight = nIHeight*nItems;
1115
1116       if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1117          nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1118
1119       if (nDroppedHeight < nHeight)
1120       {
1121             if (nItems < 5)
1122                 nDroppedHeight = (nItems+1)*nIHeight;
1123             else
1124                 nDroppedHeight = 6*nIHeight;
1125       }
1126    }
1127
1128    /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1129    monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1130    mon_info.cbSize = sizeof(mon_info);
1131    GetMonitorInfoW( monitor, &mon_info );
1132
1133    if( (rect.bottom + nDroppedHeight) >= mon_info.rcWork.bottom )
1134       rect.bottom = rect.top - nDroppedHeight;
1135
1136    SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1137                  lphc->droppedRect.right - lphc->droppedRect.left,
1138                  nDroppedHeight,
1139                  SWP_NOACTIVATE | SWP_SHOWWINDOW);
1140
1141
1142    if( !(lphc->wState & CBF_NOREDRAW) )
1143      RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1144                            RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1145
1146    EnableWindow( lphc->hWndLBox, TRUE );
1147    if (GetCapture() != lphc->self)
1148       SetCapture(lphc->hWndLBox);
1149 }
1150
1151 /***********************************************************************
1152  *           CBRollUp
1153  *
1154  * Hide listbox popup.
1155  */
1156 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1157 {
1158    HWND hWnd = lphc->self;
1159
1160    TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1161          lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED));
1162
1163    CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1164
1165    if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1166    {
1167
1168        if( lphc->wState & CBF_DROPPED )
1169        {
1170            RECT rect;
1171
1172            lphc->wState &= ~CBF_DROPPED;
1173            ShowWindow( lphc->hWndLBox, SW_HIDE );
1174
1175            if(GetCapture() == lphc->hWndLBox)
1176            {
1177                ReleaseCapture();
1178            }
1179
1180            if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1181            {
1182                rect = lphc->buttonRect;
1183            }
1184            else
1185            {
1186                if( bButton )
1187                {
1188                  UnionRect( &rect,
1189                             &lphc->buttonRect,
1190                             &lphc->textRect);
1191                }
1192                else
1193                  rect = lphc->textRect;
1194
1195                bButton = TRUE;
1196            }
1197
1198            if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1199                RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1200                                RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1201            CB_NOTIFY( lphc, CBN_CLOSEUP );
1202        }
1203    }
1204 }
1205
1206 /***********************************************************************
1207  *           COMBO_FlipListbox
1208  *
1209  * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1210  */
1211 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1212 {
1213    if( lphc->wState & CBF_DROPPED )
1214    {
1215        CBRollUp( lphc, ok, bRedrawButton );
1216        return FALSE;
1217    }
1218
1219    CBDropDown( lphc );
1220    return TRUE;
1221 }
1222
1223 /***********************************************************************
1224  *           CBRepaintButton
1225  */
1226 static void CBRepaintButton( LPHEADCOMBO lphc )
1227    {
1228   InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1229   UpdateWindow(lphc->self);
1230 }
1231
1232 /***********************************************************************
1233  *           COMBO_SetFocus
1234  */
1235 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1236 {
1237    if( !(lphc->wState & CBF_FOCUSED) )
1238    {
1239        if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1240            SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1241
1242        /* This is wrong. Message sequences seem to indicate that this
1243           is set *after* the notify. */
1244        /* lphc->wState |= CBF_FOCUSED;  */
1245
1246        if( !(lphc->wState & CBF_EDIT) )
1247          InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1248
1249        CB_NOTIFY( lphc, CBN_SETFOCUS );
1250        lphc->wState |= CBF_FOCUSED;
1251    }
1252 }
1253
1254 /***********************************************************************
1255  *           COMBO_KillFocus
1256  */
1257 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1258 {
1259    HWND hWnd = lphc->self;
1260
1261    if( lphc->wState & CBF_FOCUSED )
1262    {
1263        CBRollUp( lphc, FALSE, TRUE );
1264        if( IsWindow( hWnd ) )
1265        {
1266            if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1267                SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1268
1269            lphc->wState &= ~CBF_FOCUSED;
1270
1271            /* redraw text */
1272            if( !(lphc->wState & CBF_EDIT) )
1273              InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1274
1275            CB_NOTIFY( lphc, CBN_KILLFOCUS );
1276        }
1277    }
1278 }
1279
1280 /***********************************************************************
1281  *           COMBO_Command
1282  */
1283 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1284 {
1285    if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1286    {
1287        /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1288
1289        switch( HIWORD(wParam) >> 8 )
1290        {
1291            case (EN_SETFOCUS >> 8):
1292
1293                TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1294
1295                 COMBO_SetFocus( lphc );
1296                 break;
1297
1298            case (EN_KILLFOCUS >> 8):
1299
1300                TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1301
1302                 /* NOTE: it seems that Windows' edit control sends an
1303                  * undocumented message WM_USER + 0x1B instead of this
1304                  * notification (only when it happens to be a part of
1305                  * the combo). ?? - AK.
1306                  */
1307
1308                 COMBO_KillFocus( lphc );
1309                 break;
1310
1311
1312            case (EN_CHANGE >> 8):
1313                /*
1314                 * In some circumstances (when the selection of the combobox
1315                 * is changed for example) we don't want the EN_CHANGE notification
1316                 * to be forwarded to the parent of the combobox. This code
1317                 * checks a flag that is set in these occasions and ignores the
1318                 * notification.
1319                 */
1320                 if (lphc->wState & CBF_NOLBSELECT)
1321                 {
1322                   lphc->wState &= ~CBF_NOLBSELECT;
1323                 }
1324                 else
1325                 {
1326                   CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1327                 }
1328
1329                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1330                   CB_NOTIFY( lphc, CBN_EDITCHANGE );
1331                 break;
1332
1333            case (EN_UPDATE >> 8):
1334                 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1335                   CB_NOTIFY( lphc, CBN_EDITUPDATE );
1336                 break;
1337
1338            case (EN_ERRSPACE >> 8):
1339                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1340        }
1341    }
1342    else if( lphc->hWndLBox == hWnd )
1343    {
1344        switch( (short)HIWORD(wParam) )
1345        {
1346            case LBN_ERRSPACE:
1347                 CB_NOTIFY( lphc, CBN_ERRSPACE );
1348                 break;
1349
1350            case LBN_DBLCLK:
1351                 CB_NOTIFY( lphc, CBN_DBLCLK );
1352                 break;
1353
1354            case LBN_SELCHANGE:
1355            case LBN_SELCANCEL:
1356
1357                TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1358
1359                 if( HIWORD(wParam) == LBN_SELCHANGE)
1360                 {
1361                    if( lphc->wState & CBF_EDIT )
1362                    {
1363                        INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1364                        lphc->wState |= CBF_NOLBSELECT;
1365                        CBUpdateEdit( lphc, index );
1366                        /* select text in edit, as Windows does */
1367                        SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1368                    }
1369                    else
1370                        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1371                 }
1372
1373                 /* do not roll up if selection is being tracked
1374                  * by arrowkeys in the dropdown listbox */
1375                 if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1376                 {
1377                    CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1378                 }
1379                 else lphc->wState &= ~CBF_NOROLLUP;
1380
1381                 CB_NOTIFY( lphc, CBN_SELCHANGE );
1382
1383                 /* fall through */
1384
1385            case LBN_SETFOCUS:
1386            case LBN_KILLFOCUS:
1387                 /* nothing to do here since ComboLBox always resets the focus to its
1388                  * combo/edit counterpart */
1389                  break;
1390        }
1391    }
1392    return 0;
1393 }
1394
1395 /***********************************************************************
1396  *           COMBO_ItemOp
1397  *
1398  * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1399  */
1400 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1401 {
1402    HWND hWnd = lphc->self;
1403    UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1404
1405    TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1406
1407    switch( msg )
1408    {
1409    case WM_DELETEITEM:
1410        {
1411            DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1412            lpIS->CtlType  = ODT_COMBOBOX;
1413            lpIS->CtlID    = id;
1414            lpIS->hwndItem = hWnd;
1415            break;
1416        }
1417    case WM_DRAWITEM:
1418        {
1419            DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1420            lpIS->CtlType  = ODT_COMBOBOX;
1421            lpIS->CtlID    = id;
1422            lpIS->hwndItem = hWnd;
1423            break;
1424        }
1425    case WM_COMPAREITEM:
1426        {
1427            COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1428            lpIS->CtlType  = ODT_COMBOBOX;
1429            lpIS->CtlID    = id;
1430            lpIS->hwndItem = hWnd;
1431            break;
1432        }
1433    case WM_MEASUREITEM:
1434        {
1435            MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1436            lpIS->CtlType  = ODT_COMBOBOX;
1437            lpIS->CtlID    = id;
1438            break;
1439        }
1440    }
1441    return SendMessageW(lphc->owner, msg, id, lParam);
1442 }
1443
1444
1445 /***********************************************************************
1446  *           COMBO_GetTextW
1447  */
1448 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1449 {
1450     INT length;
1451
1452     if( lphc->wState & CBF_EDIT )
1453         return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1454
1455     /* get it from the listbox */
1456
1457     if (!count || !buf) return 0;
1458     if( lphc->hWndLBox )
1459     {
1460         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1461         if (idx == LB_ERR) goto error;
1462         length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1463         if (length == LB_ERR) goto error;
1464
1465         /* 'length' is without the terminating character */
1466         if (length >= count)
1467         {
1468             LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1469             if (!lpBuffer) goto error;
1470             length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1471
1472             /* truncate if buffer is too short */
1473             if (length != LB_ERR)
1474             {
1475                 lstrcpynW( buf, lpBuffer, count );
1476                 length = count;
1477             }
1478             HeapFree( GetProcessHeap(), 0, lpBuffer );
1479         }
1480         else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1481
1482         if (length == LB_ERR) return 0;
1483         return length;
1484     }
1485
1486  error:  /* error - truncate string, return zero */
1487     buf[0] = 0;
1488     return 0;
1489 }
1490
1491
1492 /***********************************************************************
1493  *           COMBO_GetTextA
1494  *
1495  * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1496  *       also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1497  */
1498 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1499 {
1500     INT length;
1501
1502     if( lphc->wState & CBF_EDIT )
1503         return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1504
1505     /* get it from the listbox */
1506
1507     if (!count || !buf) return 0;
1508     if( lphc->hWndLBox )
1509     {
1510         INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1511         if (idx == LB_ERR) goto error;
1512         length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1513         if (length == LB_ERR) goto error;
1514
1515         /* 'length' is without the terminating character */
1516         if (length >= count)
1517         {
1518             LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1519             if (!lpBuffer) goto error;
1520             length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1521
1522             /* truncate if buffer is too short */
1523             if (length != LB_ERR)
1524             {
1525                 lstrcpynA( buf, lpBuffer, count );
1526                 length = count;
1527             }
1528             HeapFree( GetProcessHeap(), 0, lpBuffer );
1529         }
1530         else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1531
1532         if (length == LB_ERR) return 0;
1533         return length;
1534     }
1535
1536  error:  /* error - truncate string, return zero */
1537     buf[0] = 0;
1538     return 0;
1539 }
1540
1541
1542 /***********************************************************************
1543  *           CBResetPos
1544  *
1545  * This function sets window positions according to the updated
1546  * component placement struct.
1547  */
1548 static void CBResetPos(
1549   LPHEADCOMBO lphc,
1550   LPRECT      rectEdit,
1551   LPRECT      rectLB,
1552   BOOL        bRedraw)
1553 {
1554    BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1555
1556    /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1557     * sizing messages */
1558
1559    if( lphc->wState & CBF_EDIT )
1560      SetWindowPos( lphc->hWndEdit, 0,
1561                    rectEdit->left, rectEdit->top,
1562                    rectEdit->right - rectEdit->left,
1563                    rectEdit->bottom - rectEdit->top,
1564                        SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1565
1566    SetWindowPos( lphc->hWndLBox, 0,
1567                  rectLB->left, rectLB->top,
1568                  rectLB->right - rectLB->left,
1569                  rectLB->bottom - rectLB->top,
1570                    SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1571
1572    if( bDrop )
1573    {
1574        if( lphc->wState & CBF_DROPPED )
1575        {
1576            lphc->wState &= ~CBF_DROPPED;
1577            ShowWindow( lphc->hWndLBox, SW_HIDE );
1578        }
1579
1580        if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1581            RedrawWindow( lphc->self, NULL, 0,
1582                            RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1583    }
1584 }
1585
1586
1587 /***********************************************************************
1588  *           COMBO_Size
1589  */
1590 static void COMBO_Size( LPHEADCOMBO lphc, BOOL bRedraw )
1591   {
1592   CBCalcPlacement(lphc->self,
1593                   lphc,
1594                   &lphc->textRect,
1595                   &lphc->buttonRect,
1596                   &lphc->droppedRect);
1597
1598   CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, bRedraw );
1599 }
1600
1601
1602 /***********************************************************************
1603  *           COMBO_Font
1604  */
1605 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1606 {
1607   /*
1608    * Set the font
1609    */
1610   lphc->hFont = hFont;
1611
1612   /*
1613    * Propagate to owned windows.
1614    */
1615   if( lphc->wState & CBF_EDIT )
1616       SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1617   SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1618
1619   /*
1620    * Redo the layout of the control.
1621    */
1622   if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1623   {
1624     CBCalcPlacement(lphc->self,
1625                     lphc,
1626                     &lphc->textRect,
1627                     &lphc->buttonRect,
1628                     &lphc->droppedRect);
1629
1630     CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1631   }
1632   else
1633   {
1634     CBForceDummyResize(lphc);
1635   }
1636 }
1637
1638
1639 /***********************************************************************
1640  *           COMBO_SetItemHeight
1641  */
1642 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1643 {
1644    LRESULT      lRet = CB_ERR;
1645
1646    if( index == -1 ) /* set text field height */
1647    {
1648        if( height < 32768 )
1649        {
1650            lphc->editHeight = height;
1651
1652          /*
1653           * Redo the layout of the control.
1654           */
1655          if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1656          {
1657            CBCalcPlacement(lphc->self,
1658                            lphc,
1659                            &lphc->textRect,
1660                            &lphc->buttonRect,
1661                            &lphc->droppedRect);
1662
1663            CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1664          }
1665          else
1666          {
1667            CBForceDummyResize(lphc);
1668          }
1669
1670            lRet = height;
1671        }
1672    }
1673    else if ( CB_OWNERDRAWN(lphc) )      /* set listbox item height */
1674         lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1675                               (WPARAM)index, (LPARAM)height );
1676    return lRet;
1677 }
1678
1679 /***********************************************************************
1680  *           COMBO_SelectString
1681  */
1682 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1683 {
1684    INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1685                          SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1686    if( index >= 0 )
1687    {
1688      if( lphc->wState & CBF_EDIT )
1689        CBUpdateEdit( lphc, index );
1690      else
1691      {
1692        InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1693      }
1694    }
1695    return (LRESULT)index;
1696 }
1697
1698 /***********************************************************************
1699  *           COMBO_LButtonDown
1700  */
1701 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1702 {
1703    POINT     pt;
1704    BOOL      bButton;
1705    HWND      hWnd = lphc->self;
1706
1707    pt.x = (short)LOWORD(lParam);
1708    pt.y = (short)HIWORD(lParam);
1709    bButton = PtInRect(&lphc->buttonRect, pt);
1710
1711    if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1712        (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1713    {
1714        lphc->wState |= CBF_BUTTONDOWN;
1715        if( lphc->wState & CBF_DROPPED )
1716        {
1717            /* got a click to cancel selection */
1718
1719            lphc->wState &= ~CBF_BUTTONDOWN;
1720            CBRollUp( lphc, TRUE, FALSE );
1721            if( !IsWindow( hWnd ) ) return;
1722
1723            if( lphc->wState & CBF_CAPTURE )
1724            {
1725                lphc->wState &= ~CBF_CAPTURE;
1726                ReleaseCapture();
1727            }
1728        }
1729        else
1730        {
1731            /* drop down the listbox and start tracking */
1732
1733            lphc->wState |= CBF_CAPTURE;
1734            SetCapture( hWnd );
1735            CBDropDown( lphc );
1736        }
1737        if( bButton ) CBRepaintButton( lphc );
1738    }
1739 }
1740
1741 /***********************************************************************
1742  *           COMBO_LButtonUp
1743  *
1744  * Release capture and stop tracking if needed.
1745  */
1746 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1747 {
1748    if( lphc->wState & CBF_CAPTURE )
1749    {
1750        lphc->wState &= ~CBF_CAPTURE;
1751        if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1752        {
1753            INT index = CBUpdateLBox( lphc, TRUE );
1754            /* Update edit only if item is in the list */
1755            if(index >= 0)
1756            {
1757                lphc->wState |= CBF_NOLBSELECT;
1758                CBUpdateEdit( lphc, index );
1759                lphc->wState &= ~CBF_NOLBSELECT;
1760            }
1761        }
1762        ReleaseCapture();
1763        SetCapture(lphc->hWndLBox);
1764    }
1765
1766    if( lphc->wState & CBF_BUTTONDOWN )
1767    {
1768        lphc->wState &= ~CBF_BUTTONDOWN;
1769        CBRepaintButton( lphc );
1770    }
1771 }
1772
1773 /***********************************************************************
1774  *           COMBO_MouseMove
1775  *
1776  * Two things to do - track combo button and release capture when
1777  * pointer goes into the listbox.
1778  */
1779 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1780 {
1781    POINT  pt;
1782    RECT   lbRect;
1783
1784    pt.x = (short)LOWORD(lParam);
1785    pt.y = (short)HIWORD(lParam);
1786
1787    if( lphc->wState & CBF_BUTTONDOWN )
1788    {
1789      BOOL bButton;
1790
1791      bButton = PtInRect(&lphc->buttonRect, pt);
1792
1793      if( !bButton )
1794      {
1795        lphc->wState &= ~CBF_BUTTONDOWN;
1796        CBRepaintButton( lphc );
1797      }
1798    }
1799
1800    GetClientRect( lphc->hWndLBox, &lbRect );
1801    MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1802    if( PtInRect(&lbRect, pt) )
1803    {
1804        lphc->wState &= ~CBF_CAPTURE;
1805        ReleaseCapture();
1806        if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1807
1808        /* hand over pointer tracking */
1809        SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1810    }
1811 }
1812
1813 static LRESULT COMBO_GetComboBoxInfo(LPHEADCOMBO lphc, COMBOBOXINFO *pcbi)
1814 {
1815     if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1816         return FALSE;
1817
1818     pcbi->rcItem = lphc->textRect;
1819     pcbi->rcButton = lphc->buttonRect;
1820     pcbi->stateButton = 0;
1821     if (lphc->wState & CBF_BUTTONDOWN)
1822         pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1823     if (IsRectEmpty(&lphc->buttonRect))
1824         pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1825     pcbi->hwndCombo = lphc->self;
1826     pcbi->hwndItem = lphc->hWndEdit;
1827     pcbi->hwndList = lphc->hWndLBox;
1828     return TRUE;
1829 }
1830
1831 static char *strdupA(LPCSTR str)
1832 {
1833     char *ret;
1834     DWORD len;
1835
1836     if(!str) return NULL;
1837
1838     len = strlen(str);
1839     ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1840     memcpy(ret, str, len + 1);
1841     return ret;
1842 }
1843
1844 /***********************************************************************
1845  *           ComboWndProc_common
1846  *
1847  * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/comboboxes/comboboxes.asp
1848  */
1849 static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
1850                                     WPARAM wParam, LPARAM lParam, BOOL unicode )
1851 {
1852       LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1853
1854       TRACE("[%p]: msg %s wp %08x lp %08lx\n",
1855             hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1856
1857       if( lphc || message == WM_NCCREATE )
1858       switch(message)
1859       {
1860
1861         /* System messages */
1862
1863         case WM_NCCREATE:
1864         {
1865                 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1866                                        ((LPCREATESTRUCTA)lParam)->style;
1867                 return COMBO_NCCreate(hwnd, style);
1868         }
1869         case WM_NCDESTROY:
1870                 COMBO_NCDestroy(lphc);
1871                 break;/* -> DefWindowProc */
1872
1873         case WM_CREATE:
1874         {
1875                 HWND hwndParent;
1876                 LONG style;
1877                 if(unicode)
1878                 {
1879                     hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1880                     style = ((LPCREATESTRUCTW)lParam)->style;
1881                 }
1882                 else
1883                 {
1884                     hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1885                     style = ((LPCREATESTRUCTA)lParam)->style;
1886                 }
1887                 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1888         }
1889
1890         case WM_PRINTCLIENT:
1891                 /* Fallthrough */
1892         case WM_PAINT:
1893                 /* wParam may contain a valid HDC! */
1894                 return  COMBO_Paint(lphc, (HDC)wParam);
1895
1896         case WM_ERASEBKGND:
1897                 /* do all painting in WM_PAINT like Windows does */
1898                 return 1;
1899
1900         case WM_GETDLGCODE:
1901         {
1902                 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1903                 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1904                 {
1905                    int vk = (int)((LPMSG)lParam)->wParam;
1906
1907                    if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1908                        result |= DLGC_WANTMESSAGE;
1909                 }
1910                 return  result;
1911         }
1912         case WM_WINDOWPOSCHANGING:
1913                 return  COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1914     case WM_WINDOWPOSCHANGED:
1915         /* SetWindowPos can be called on a Combobox to resize its Listbox.
1916          * In that case, the Combobox itself will not be resized, so we won't
1917          * get a WM_SIZE. Since we still want to update the Listbox, we have to
1918          * do it here.
1919          */
1920         /* we should not force repainting on WM_WINDOWPOSCHANGED, it breaks
1921          * Z-order based painting.
1922          */
1923         /* fall through */
1924         case WM_SIZE:
1925                 if( lphc->hWndLBox &&
1926                   !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc, message == WM_SIZE );
1927                 return  TRUE;
1928         case WM_SETFONT:
1929                 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1930                 return  TRUE;
1931         case WM_GETFONT:
1932                 return  (LRESULT)lphc->hFont;
1933         case WM_SETFOCUS:
1934                 if( lphc->wState & CBF_EDIT )
1935                     SetFocus( lphc->hWndEdit );
1936                 else
1937                     COMBO_SetFocus( lphc );
1938                 return  TRUE;
1939         case WM_KILLFOCUS:
1940             {
1941                 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1942                 if( !hwndFocus ||
1943                     (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1944                     COMBO_KillFocus( lphc );
1945                 return  TRUE;
1946             }
1947         case WM_COMMAND:
1948                 return  COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1949         case WM_GETTEXT:
1950             return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1951                            : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1952         case WM_SETTEXT:
1953         case WM_GETTEXTLENGTH:
1954         case WM_CLEAR:
1955                 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1956                 {
1957                     int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1958                     if (j == -1) return 0;
1959                     return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1960                                      SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1961                 }
1962                 else if( lphc->wState & CBF_EDIT )
1963                 {
1964                     LRESULT ret;
1965                     lphc->wState |= CBF_NOEDITNOTIFY;
1966                     ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1967                                     SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1968                     lphc->wState &= ~CBF_NOEDITNOTIFY;
1969                     return ret;
1970                 }
1971                 else return CB_ERR;
1972         case WM_CUT:
1973         case WM_PASTE:
1974         case WM_COPY:
1975                 if( lphc->wState & CBF_EDIT )
1976                 {
1977                     return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1978                                      SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1979                 }
1980                 else return  CB_ERR;
1981
1982         case WM_DRAWITEM:
1983         case WM_DELETEITEM:
1984         case WM_COMPAREITEM:
1985         case WM_MEASUREITEM:
1986                 return COMBO_ItemOp(lphc, message, lParam);
1987         case WM_ENABLE:
1988                 if( lphc->wState & CBF_EDIT )
1989                     EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1990                 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1991
1992                 /* Force the control to repaint when the enabled state changes. */
1993                 InvalidateRect(lphc->self, NULL, TRUE);
1994                 return  TRUE;
1995         case WM_SETREDRAW:
1996                 if( wParam )
1997                     lphc->wState &= ~CBF_NOREDRAW;
1998                 else
1999                     lphc->wState |= CBF_NOREDRAW;
2000
2001                 if( lphc->wState & CBF_EDIT )
2002                     SendMessageW(lphc->hWndEdit, message, wParam, lParam);
2003                 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
2004                 return  0;
2005         case WM_SYSKEYDOWN:
2006                 if( KEYDATA_ALT & HIWORD(lParam) )
2007                     if( wParam == VK_UP || wParam == VK_DOWN )
2008                         COMBO_FlipListbox( lphc, FALSE, FALSE );
2009                 return  0;
2010
2011         case WM_CHAR:
2012         case WM_IME_CHAR:
2013         case WM_KEYDOWN:
2014         {
2015                 HWND hwndTarget;
2016
2017                 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2018                      (lphc->wState & CBF_DROPPED))
2019                 {
2020                    CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2021                    return TRUE;
2022                 }
2023                else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
2024                {
2025                   COMBO_FlipListbox( lphc, FALSE, FALSE );
2026                   return TRUE;
2027                }
2028
2029                 if( lphc->wState & CBF_EDIT )
2030                     hwndTarget = lphc->hWndEdit;
2031                 else
2032                     hwndTarget = lphc->hWndLBox;
2033
2034                 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2035                                  SendMessageA(hwndTarget, message, wParam, lParam);
2036         }
2037         case WM_LBUTTONDOWN:
2038                 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2039                 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2040                 return  TRUE;
2041         case WM_LBUTTONUP:
2042                 COMBO_LButtonUp( lphc );
2043                 return  TRUE;
2044         case WM_MOUSEMOVE:
2045                 if( lphc->wState & CBF_CAPTURE )
2046                     COMBO_MouseMove( lphc, wParam, lParam );
2047                 return  TRUE;
2048
2049         case WM_MOUSEWHEEL:
2050                 if (wParam & (MK_SHIFT | MK_CONTROL))
2051                     return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2052                                      DefWindowProcA(hwnd, message, wParam, lParam);
2053
2054                 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2055                 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2056                 return TRUE;
2057
2058         /* Combo messages */
2059
2060         case CB_ADDSTRING16:
2061                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2062                 /* fall through */
2063         case CB_ADDSTRING:
2064                 if( unicode )
2065                 {
2066                     if( lphc->dwStyle & CBS_LOWERCASE )
2067                         CharLowerW((LPWSTR)lParam);
2068                     else if( lphc->dwStyle & CBS_UPPERCASE )
2069                         CharUpperW((LPWSTR)lParam);
2070                     return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2071                 }
2072                 else /* unlike the unicode version, the ansi version does not overwrite
2073                         the string if converting case */
2074                 {
2075                     char *string = NULL;
2076                     LRESULT ret;
2077                     if( lphc->dwStyle & CBS_LOWERCASE )
2078                     {
2079                         string = strdupA((LPSTR)lParam);
2080                         CharLowerA(string);
2081                     }
2082
2083                     else if( lphc->dwStyle & CBS_UPPERCASE )
2084                     {
2085                         string = strdupA((LPSTR)lParam);
2086                         CharUpperA(string);
2087                     }
2088
2089                     ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2090                     HeapFree(GetProcessHeap(), 0, string);
2091                     return ret;
2092                 }
2093         case CB_INSERTSTRING16:
2094                 wParam = (INT)(INT16)wParam;
2095                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2096                 /* fall through */
2097         case CB_INSERTSTRING:
2098                 if( unicode )
2099                 {
2100                     if( lphc->dwStyle & CBS_LOWERCASE )
2101                         CharLowerW((LPWSTR)lParam);
2102                     else if( lphc->dwStyle & CBS_UPPERCASE )
2103                         CharUpperW((LPWSTR)lParam);
2104                     return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2105                 }
2106                 else
2107                 {
2108                     if( lphc->dwStyle & CBS_LOWERCASE )
2109                         CharLowerA((LPSTR)lParam);
2110                     else if( lphc->dwStyle & CBS_UPPERCASE )
2111                         CharUpperA((LPSTR)lParam);
2112
2113                     return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2114                 }
2115         case CB_DELETESTRING16:
2116         case CB_DELETESTRING:
2117                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2118                                  SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2119         case CB_SELECTSTRING16:
2120                 wParam = (INT)(INT16)wParam;
2121                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2122                 /* fall through */
2123         case CB_SELECTSTRING:
2124                 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2125         case CB_FINDSTRING16:
2126                 wParam = (INT)(INT16)wParam;
2127                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2128                 /* fall through */
2129         case CB_FINDSTRING:
2130                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2131                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2132         case CB_FINDSTRINGEXACT16:
2133                 wParam = (INT)(INT16)wParam;
2134                 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2135                 /* fall through */
2136         case CB_FINDSTRINGEXACT:
2137                 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2138                                  SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2139         case CB_SETITEMHEIGHT16:
2140                 wParam = (INT)(INT16)wParam;    /* signed integer */
2141                 /* fall through */
2142         case CB_SETITEMHEIGHT:
2143                 return  COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2144         case CB_GETITEMHEIGHT16:
2145                 wParam = (INT)(INT16)wParam;
2146                 /* fall through */
2147         case CB_GETITEMHEIGHT:
2148                 if( (INT)wParam >= 0 )  /* listbox item */
2149                     return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2150                 return  CBGetTextAreaHeight(hwnd, lphc);
2151         case CB_RESETCONTENT16:
2152         case CB_RESETCONTENT:
2153                 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2154                 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2155                 {
2156                     static const WCHAR empty_stringW[] = { 0 };
2157                     SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2158                 }
2159                 else
2160                     InvalidateRect(lphc->self, NULL, TRUE);
2161                 return  TRUE;
2162         case CB_INITSTORAGE:
2163                 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2164         case CB_GETHORIZONTALEXTENT:
2165                 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2166         case CB_SETHORIZONTALEXTENT:
2167                 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2168         case CB_GETTOPINDEX:
2169                 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2170         case CB_GETLOCALE:
2171                 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2172         case CB_SETLOCALE:
2173                 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2174         case CB_GETDROPPEDWIDTH:
2175                 if( lphc->droppedWidth )
2176                     return  lphc->droppedWidth;
2177                 return  lphc->droppedRect.right - lphc->droppedRect.left;
2178         case CB_SETDROPPEDWIDTH:
2179                 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2180                     (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2181                 return  CB_ERR;
2182         case CB_GETDROPPEDCONTROLRECT16:
2183                 lParam = (LPARAM)MapSL(lParam);
2184                 if( lParam )
2185                 {
2186                     RECT r;
2187                     RECT16 *r16 = (RECT16 *)lParam;
2188                     CBGetDroppedControlRect( lphc, &r );
2189                     r16->left   = r.left;
2190                     r16->top    = r.top;
2191                     r16->right  = r.right;
2192                     r16->bottom = r.bottom;
2193                 }
2194                 return  CB_OKAY;
2195         case CB_GETDROPPEDCONTROLRECT:
2196                 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2197                 return  CB_OKAY;
2198         case CB_GETDROPPEDSTATE16:
2199         case CB_GETDROPPEDSTATE:
2200                 return  (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2201         case CB_DIR16:
2202                 return SendMessageA(lphc->hWndLBox, LB_DIR16, wParam, lParam);
2203         case CB_DIR:
2204                 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2205                                  SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2206
2207         case CB_SHOWDROPDOWN16:
2208         case CB_SHOWDROPDOWN:
2209                 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2210                 {
2211                     if( wParam )
2212                     {
2213                         if( !(lphc->wState & CBF_DROPPED) )
2214                             CBDropDown( lphc );
2215                     }
2216                     else
2217                         if( lphc->wState & CBF_DROPPED )
2218                             CBRollUp( lphc, FALSE, TRUE );
2219                 }
2220                 return  TRUE;
2221         case CB_GETCOUNT16:
2222         case CB_GETCOUNT:
2223                 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2224         case CB_GETCURSEL16:
2225         case CB_GETCURSEL:
2226                 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2227         case CB_SETCURSEL16:
2228                 wParam = (INT)(INT16)wParam;
2229                 /* fall through */
2230         case CB_SETCURSEL:
2231                 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2232                 if( lParam >= 0 )
2233                     SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2234
2235                 /* no LBN_SELCHANGE in this case, update manually */
2236                 if( lphc->wState & CBF_EDIT )
2237                     CBUpdateEdit( lphc, (INT)wParam );
2238                 else
2239                     InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2240                 lphc->wState &= ~CBF_SELCHANGE;
2241                 return  lParam;
2242         case CB_GETLBTEXT16:
2243                 wParam = (INT)(INT16)wParam;
2244                 lParam = (LPARAM)MapSL(lParam);
2245                 /* fall through */
2246         case CB_GETLBTEXT:
2247                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2248                                  SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2249         case CB_GETLBTEXTLEN16:
2250                 wParam = (INT)(INT16)wParam;
2251                 /* fall through */
2252         case CB_GETLBTEXTLEN:
2253                 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2254                                  SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2255         case CB_GETITEMDATA16:
2256                 wParam = (INT)(INT16)wParam;
2257                 /* fall through */
2258         case CB_GETITEMDATA:
2259                 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2260         case CB_SETITEMDATA16:
2261                 wParam = (INT)(INT16)wParam;
2262                 /* fall through */
2263         case CB_SETITEMDATA:
2264                 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2265         case CB_GETEDITSEL16:
2266                 wParam = lParam = 0;   /* just in case */
2267                 /* fall through */
2268         case CB_GETEDITSEL:
2269                 /* Edit checks passed parameters itself */
2270                 if( lphc->wState & CBF_EDIT )
2271                     return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2272                 return  CB_ERR;
2273         case CB_SETEDITSEL16:
2274         case CB_SETEDITSEL:
2275                 if( lphc->wState & CBF_EDIT )
2276                     return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2277                           (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2278                 return  CB_ERR;
2279         case CB_SETEXTENDEDUI16:
2280         case CB_SETEXTENDEDUI:
2281                 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2282                     return  CB_ERR;
2283                 if( wParam )
2284                     lphc->wState |= CBF_EUI;
2285                 else lphc->wState &= ~CBF_EUI;
2286                 return  CB_OKAY;
2287         case CB_GETEXTENDEDUI16:
2288         case CB_GETEXTENDEDUI:
2289                 return  (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2290         case CB_GETCOMBOBOXINFO:
2291                 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2292         case CB_LIMITTEXT:
2293                 if( lphc->wState & CBF_EDIT )
2294                         return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2295         default:
2296                 if (message >= WM_USER)
2297                     WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2298                         message - WM_USER, wParam, lParam );
2299                 break;
2300       }
2301       return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2302                        DefWindowProcA(hwnd, message, wParam, lParam);
2303 }
2304
2305 /***********************************************************************
2306  *           ComboWndProcA
2307  *
2308  * This is just a wrapper for the real ComboWndProc which locks/unlocks
2309  * window structs.
2310  */
2311 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2312 {
2313     if (!IsWindow(hwnd)) return 0;
2314     return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2315 }
2316
2317 /***********************************************************************
2318  *           ComboWndProcW
2319  */
2320 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2321 {
2322     if (!IsWindow(hwnd)) return 0;
2323     return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2324 }
2325
2326 /*************************************************************************
2327  *           GetComboBoxInfo   (USER32.@)
2328  */
2329 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo,      /* [in] handle to combo box */
2330                             PCOMBOBOXINFO pcbi   /* [in/out] combo box information */)
2331 {
2332     TRACE("(%p, %p)\n", hwndCombo, pcbi);
2333     return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2334 }