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