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