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