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