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