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