4 * Copyright 1997 Alex Korobka
6 * FIXME: roll up in Netscape 3.01.
15 #include "wine/winuser16.h"
21 #include "debugtools.h"
24 DEFAULT_DEBUG_CHANNEL(combo);
26 /* bits in the dwKeyData */
27 #define KEYDATA_ALT 0x2000
28 #define KEYDATA_PREVSTATE 0x4000
31 * Additional combo box definitions
34 #define CB_GETPTR( wnd ) (*(LPHEADCOMBO*)((wnd)->wExtra))
35 #define CB_NOTIFY( lphc, code ) \
36 (SendMessageW((lphc)->owner, WM_COMMAND, \
37 MAKEWPARAM((lphc)->self->wIDmenu, (code)), (lphc)->self->hwndSelf))
38 #define CB_GETEDITTEXTLENGTH( lphc ) \
39 (SendMessageW((lphc)->hWndEdit, WM_GETTEXTLENGTH, 0, 0 ))
41 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
46 static HBITMAP hComboBmp = 0;
47 static UINT CBitHeight, CBitWidth;
50 * Look and feel dependant "constants"
53 #define COMBO_YBORDERGAP 5
54 #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
55 #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
56 #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 )
57 #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 )
59 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
60 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
62 /*********************************************************************
63 * combo class descriptor
65 const struct builtin_class_descr COMBO_builtin_class =
67 "ComboBox", /* name */
68 CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS, /* style */
69 ComboWndProcA, /* procA */
70 ComboWndProcW, /* procW */
71 sizeof(HEADCOMBO *), /* extra */
72 IDC_ARROWA, /* cursor */
77 /***********************************************************************
80 * Load combo button bitmap.
82 static BOOL COMBO_Init()
86 if( hComboBmp ) return TRUE;
87 if( (hDC = CreateCompatibleDC(0)) )
90 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
96 GetObjectW( hComboBmp, sizeof(bm), &bm );
97 CBitHeight = bm.bmHeight;
98 CBitWidth = bm.bmWidth;
100 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
102 hPrevB = SelectObject( hDC, hComboBmp);
103 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
104 InvertRect( hDC, &r );
105 SelectObject( hDC, hPrevB );
114 /***********************************************************************
117 static LRESULT COMBO_NCCreate(WND* wnd, LONG style)
121 if ( wnd && COMBO_Init() &&
122 (lphc = HeapAlloc(GetProcessHeap(), 0, sizeof(HEADCOMBO))) )
124 memset( lphc, 0, sizeof(HEADCOMBO) );
125 *(LPHEADCOMBO*)wnd->wExtra = lphc;
127 /* some braindead apps do try to use scrollbar/border flags */
129 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
130 wnd->dwStyle &= ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
133 * We also have to remove the client edge style to make sure
134 * we don't end-up with a non client area.
136 wnd->dwExStyle &= ~(WS_EX_CLIENTEDGE);
138 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
139 lphc->dwStyle |= CBS_HASSTRINGS;
140 if( !(wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) )
141 lphc->wState |= CBF_NOTIFY;
143 TRACE("[0x%08x], style = %08x\n",
144 (UINT)lphc, lphc->dwStyle );
146 return (LRESULT)(UINT)wnd->hwndSelf;
148 return (LRESULT)FALSE;
151 /***********************************************************************
154 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
159 WND* wnd = lphc->self;
161 TRACE("[%04x]: freeing storage\n", CB_HWND(lphc));
163 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
164 DestroyWindow( lphc->hWndLBox );
166 HeapFree( GetProcessHeap(), 0, lphc );
172 /***********************************************************************
173 * CBGetTextAreaHeight
175 * This method will calculate the height of the text area of the
177 * The height of the text area is set in two ways.
178 * It can be set explicitely through a combobox message or through a
179 * WM_MEASUREITEM callback.
180 * If this is not the case, the height is set to 13 dialog units.
181 * This height was determined through experimentation.
183 static INT CBGetTextAreaHeight(
189 if( lphc->editHeight ) /* explicitly set height */
191 iTextItemHeight = lphc->editHeight;
196 HDC hDC = GetDC(hwnd);
201 hPrevFont = SelectObject( hDC, lphc->hFont );
203 GetTextMetricsW(hDC, &tm);
205 baseUnitY = tm.tmHeight;
208 SelectObject( hDC, hPrevFont );
210 ReleaseDC(hwnd, hDC);
212 iTextItemHeight = ((13 * baseUnitY) / 8);
215 * This "formula" calculates the height of the complete control.
216 * To calculate the height of the text area, we have to remove the
219 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
223 * Check the ownerdraw case if we haven't asked the parent the size
226 if ( CB_OWNERDRAWN(lphc) &&
227 (lphc->wState & CBF_MEASUREITEM) )
229 MEASUREITEMSTRUCT measureItem;
231 INT originalItemHeight = iTextItemHeight;
234 * We use the client rect for the width of the item.
236 GetClientRect(hwnd, &clientRect);
238 lphc->wState &= ~CBF_MEASUREITEM;
241 * Send a first one to measure the size of the text area
243 measureItem.CtlType = ODT_COMBOBOX;
244 measureItem.CtlID = lphc->self->wIDmenu;
245 measureItem.itemID = -1;
246 measureItem.itemWidth = clientRect.right;
247 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
248 measureItem.itemData = 0;
249 SendMessageW(lphc->owner, WM_MEASUREITEM,
250 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
251 iTextItemHeight = 6 + measureItem.itemHeight;
254 * Send a second one in the case of a fixed ownerdraw list to calculate the
255 * size of the list items. (we basically do this on behalf of the listbox)
257 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
259 measureItem.CtlType = ODT_COMBOBOX;
260 measureItem.CtlID = lphc->self->wIDmenu;
261 measureItem.itemID = 0;
262 measureItem.itemWidth = clientRect.right;
263 measureItem.itemHeight = originalItemHeight;
264 measureItem.itemData = 0;
265 SendMessageW(lphc->owner, WM_MEASUREITEM,
266 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
267 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
271 * Keep the size for the next time
273 lphc->editHeight = iTextItemHeight;
276 return iTextItemHeight;
279 /***********************************************************************
282 * The dummy resize is used for listboxes that have a popup to trigger
283 * a re-arranging of the contents of the combobox and the recalculation
284 * of the size of the "real" control window.
286 static void CBForceDummyResize(
292 newComboHeight = CBGetTextAreaHeight(CB_HWND(lphc),lphc) + 2*COMBO_YBORDERSIZE();
294 GetWindowRect(CB_HWND(lphc), &windowRect);
297 * We have to be careful, resizing a combobox also has the meaning that the
298 * dropped rect will be resized. In this case, we want to trigger a resize
299 * to recalculate layout but we don't want to change the dropped rectangle
300 * So, we pass the height of text area of control as the height.
301 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
304 SetWindowPos( CB_HWND(lphc),
307 windowRect.right - windowRect.left,
309 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
312 /***********************************************************************
315 * Set up component coordinates given valid lphc->RectCombo.
317 static void CBCalcPlacement(
325 * Again, start with the client rectangle.
327 GetClientRect(hwnd, lprEdit);
332 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
335 * Chop off the bottom part to fit with the height of the text area.
337 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
340 * The button starts the same vertical position as the text area.
342 CopyRect(lprButton, lprEdit);
345 * If the combobox is "simple" there is no button.
347 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
348 lprButton->left = lprButton->right = lprButton->bottom = 0;
352 * Let's assume the combobox button is the same width as the
354 * size the button horizontally and cut-off the text area.
356 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
357 lprEdit->right = lprButton->left;
361 * In the case of a dropdown, there is an additional spacing between the
362 * text area and the button.
364 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
366 lprEdit->right -= COMBO_EDITBUTTONSPACE();
370 * If we have an edit control, we space it away from the borders slightly.
372 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
374 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
378 * Adjust the size of the listbox popup.
380 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
383 * Use the client rectangle to initialize the listbox rectangle
385 GetClientRect(hwnd, lprLB);
388 * Then, chop-off the top part.
390 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
395 * Make sure the dropped width is as large as the combobox itself.
397 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
399 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
402 * In the case of a dropdown, the popup listbox is offset to the right.
403 * so, we want to make sure it's flush with the right side of the
406 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
407 lprLB->right -= COMBO_EDITBUTTONSPACE();
410 lprLB->right = lprLB->left + lphc->droppedWidth;
413 TRACE("\ttext\t= (%i,%i-%i,%i)\n",
414 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
416 TRACE("\tbutton\t= (%i,%i-%i,%i)\n",
417 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
419 TRACE("\tlbox\t= (%i,%i-%i,%i)\n",
420 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
423 /***********************************************************************
424 * CBGetDroppedControlRect
426 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
428 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
429 of the combo box and the lower right corner of the listbox */
431 GetWindowRect(lphc->self->hwndSelf, lpRect);
433 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
434 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
438 /***********************************************************************
439 * COMBO_WindowPosChanging
441 static LRESULT COMBO_WindowPosChanging(
444 WINDOWPOS* posChanging)
447 * We need to override the WM_WINDOWPOSCHANGING method to handle all
448 * the non-simple comboboxes. The problem is that those controls are
449 * always the same height. We have to make sure they are not resized
452 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
453 ((posChanging->flags & SWP_NOSIZE) == 0) )
457 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
458 2*COMBO_YBORDERSIZE();
461 * Resizing a combobox has another side effect, it resizes the dropped
462 * rectangle as well. However, it does it only if the new height for the
463 * combobox is different than the height it should have. In other words,
464 * if the application resizing the combobox only had the intention to resize
465 * the actual control, for example, to do the layout of a dialog that is
466 * resized, the height of the dropdown is not changed.
468 if (posChanging->cy != newComboHeight)
470 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
472 posChanging->cy = newComboHeight;
479 /***********************************************************************
482 static LRESULT COMBO_Create( LPHEADCOMBO lphc, WND* wnd, HWND hwndParent, LONG style )
484 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
485 static const WCHAR editName[] = {'E','d','i','t',0};
487 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
488 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
491 lphc->owner = hwndParent;
494 * The item height and dropped width are not set when the control
497 lphc->droppedWidth = lphc->editHeight = 0;
500 * The first time we go through, we want to measure the ownerdraw item
502 lphc->wState |= CBF_MEASUREITEM;
504 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
506 if( lphc->owner || !(style & WS_VISIBLE) )
512 * Initialize the dropped rect to the size of the client area of the
513 * control and then, force all the areas of the combobox to be
516 GetClientRect( wnd->hwndSelf, &lphc->droppedRect );
518 CBCalcPlacement(wnd->hwndSelf,
522 &lphc->droppedRect );
525 * Adjust the position of the popup listbox if it's necessary
527 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
529 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
532 * If it's a dropdown, the listbox is offset
534 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
535 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
537 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect);
538 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect.right);
541 /* create listbox popup */
543 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
544 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
546 if( lphc->dwStyle & CBS_SORT )
547 lbeStyle |= LBS_SORT;
548 if( lphc->dwStyle & CBS_HASSTRINGS )
549 lbeStyle |= LBS_HASSTRINGS;
550 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
551 lbeStyle |= LBS_NOINTEGRALHEIGHT;
552 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
553 lbeStyle |= LBS_DISABLENOSCROLL;
555 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
557 lbeStyle |= WS_VISIBLE;
560 * In win 95 look n feel, the listbox in the simple combobox has
561 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
563 if (TWEAK_WineLook > WIN31_LOOK)
565 lbeStyle &= ~WS_BORDER;
566 lbeExStyle |= WS_EX_CLIENTEDGE;
570 lphc->hWndLBox = CreateWindowExW(lbeExStyle,
574 lphc->droppedRect.left,
575 lphc->droppedRect.top,
576 lphc->droppedRect.right - lphc->droppedRect.left,
577 lphc->droppedRect.bottom - lphc->droppedRect.top,
578 lphc->self->hwndSelf,
579 (HMENU)ID_CB_LISTBOX,
580 lphc->self->hInstance,
586 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
589 * In Win95 look, the border fo the edit control is
590 * provided by the combobox
592 if (TWEAK_WineLook == WIN31_LOOK)
593 lbeStyle |= WS_BORDER;
595 if( lphc->wState & CBF_EDIT )
597 if( lphc->dwStyle & CBS_OEMCONVERT )
598 lbeStyle |= ES_OEMCONVERT;
599 if( lphc->dwStyle & CBS_AUTOHSCROLL )
600 lbeStyle |= ES_AUTOHSCROLL;
601 if( lphc->dwStyle & CBS_LOWERCASE )
602 lbeStyle |= ES_LOWERCASE;
603 else if( lphc->dwStyle & CBS_UPPERCASE )
604 lbeStyle |= ES_UPPERCASE;
606 lphc->hWndEdit = CreateWindowExW(0,
610 lphc->textRect.left, lphc->textRect.top,
611 lphc->textRect.right - lphc->textRect.left,
612 lphc->textRect.bottom - lphc->textRect.top,
613 lphc->self->hwndSelf,
615 lphc->self->hInstance,
618 if( !lphc->hWndEdit )
624 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
626 /* Now do the trick with parent */
627 SetParent(lphc->hWndLBox, HWND_DESKTOP);
629 * If the combo is a dropdown, we must resize the control
630 * to fit only the text area and button. To do this,
631 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
632 * will take care of setting the height for us.
634 CBForceDummyResize(lphc);
637 TRACE("init done\n");
638 return wnd->hwndSelf;
640 ERR("edit control failure.\n");
641 } else ERR("listbox failure.\n");
642 } else ERR("no owner for visible combo.\n");
644 /* CreateWindow() will send WM_NCDESTROY to cleanup */
649 /***********************************************************************
652 * Paint combo button (normal, pressed, and disabled states).
654 static void CBPaintButton(
659 if( lphc->wState & CBF_NOREDRAW )
662 if (TWEAK_WineLook == WIN31_LOOK)
668 COLORREF oldTextColor, oldBkColor;
671 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
674 * Draw the button background
679 rectButton.right-rectButton.left,
680 rectButton.bottom-rectButton.top,
683 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
685 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
689 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
693 * Remove the edge of the button from the rectangle
694 * and calculate the position of the bitmap.
696 InflateRect( &rectButton, -2, -2);
698 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
699 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
702 hMemDC = CreateCompatibleDC( hdc );
703 SelectObject( hMemDC, hComboBmp );
704 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
705 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
707 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
708 SetBkColor( hdc, oldBkColor );
709 SetTextColor( hdc, oldTextColor );
711 SelectObject( hdc, hPrevBrush );
715 UINT buttonState = DFCS_SCROLLCOMBOBOX;
717 if (lphc->wState & CBF_BUTTONDOWN)
719 buttonState |= DFCS_PUSHED;
722 if (CB_DISABLED(lphc))
724 buttonState |= DFCS_INACTIVE;
727 DrawFrameControl(hdc,
734 /***********************************************************************
737 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
739 static void CBPaintText(
747 if( lphc->wState & CBF_NOREDRAW ) return;
749 /* follow Windows combobox that sends a bunch of text
750 * inquiries to its listbox while processing WM_PAINT. */
752 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
754 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
755 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
757 SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
758 pText[size] = '\0'; /* just in case */
764 if( lphc->wState & CBF_EDIT )
766 static const WCHAR empty_stringW[] = { 0 };
767 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
768 if( lphc->wState & CBF_FOCUSED )
769 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
771 else /* paint text field ourselves */
774 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
777 * Give ourselves some space.
779 InflateRect( &rectEdit, -1, -1 );
781 if ( (lphc->wState & CBF_FOCUSED) &&
782 !(lphc->wState & CBF_DROPPED) )
786 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
787 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
788 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
789 itemState = ODS_SELECTED | ODS_FOCUS;
794 if( CB_OWNERDRAWN(lphc) )
800 * Save the current clip region.
801 * To retrieve the clip region, we need to create one "dummy"
804 clipRegion = CreateRectRgnIndirect(&rectEdit);
806 if (GetClipRgn(hdc, clipRegion)!=1)
808 DeleteObject(clipRegion);
809 clipRegion=(HRGN)NULL;
812 if ( lphc->self->dwStyle & WS_DISABLED )
813 itemState |= ODS_DISABLED;
815 dis.CtlType = ODT_COMBOBOX;
816 dis.CtlID = lphc->self->wIDmenu;
817 dis.hwndItem = lphc->self->hwndSelf;
818 dis.itemAction = ODA_DRAWENTIRE;
820 dis.itemState = itemState;
822 dis.rcItem = rectEdit;
823 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA,
827 * Clip the DC and have the parent draw the item.
829 IntersectClipRect(hdc,
830 rectEdit.left, rectEdit.top,
831 rectEdit.right, rectEdit.bottom);
833 SendMessageW(lphc->owner, WM_DRAWITEM,
834 lphc->self->wIDmenu, (LPARAM)&dis );
837 * Reset the clipping region.
839 SelectClipRgn(hdc, clipRegion);
843 static const WCHAR empty_stringW[] = { 0 };
847 ETO_OPAQUE | ETO_CLIPPED,
849 pText ? pText : empty_stringW , size, NULL );
851 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
852 DrawFocusRect( hdc, &rectEdit );
856 SelectObject(hdc, hPrevFont );
859 HeapFree( GetProcessHeap(), 0, pText );
862 /***********************************************************************
865 static void CBPaintBorder(
872 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
874 GetClientRect(hwnd, &clientRect);
878 CopyRect(&clientRect, &lphc->textRect);
880 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
881 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
884 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
887 /***********************************************************************
888 * COMBO_PrepareColors
890 * This method will sent the appropriate WM_CTLCOLOR message to
891 * prepare and setup the colors for the combo's DC.
893 * It also returns the brush to use for the background.
895 static HBRUSH COMBO_PrepareColors(
902 * Get the background brush for this control.
904 if (CB_DISABLED(lphc))
906 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
907 hDC, lphc->self->hwndSelf );
910 * We have to change the text color since WM_CTLCOLORSTATIC will
911 * set it to the "enabled" color. This is the same behavior as the
914 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
918 if (lphc->wState & CBF_EDIT)
920 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
921 hDC, lphc->self->hwndSelf );
925 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX,
926 hDC, lphc->self->hwndSelf );
934 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
939 /***********************************************************************
940 * COMBO_EraseBackground
942 static LRESULT COMBO_EraseBackground(
950 if(lphc->wState & CBF_EDIT)
953 hDC = (hParamDC) ? hParamDC
956 * Retrieve the background brush
958 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
960 FillRect(hDC, &lphc->textRect, hBkgBrush);
963 ReleaseDC(hwnd, hDC);
968 /***********************************************************************
971 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
976 hDC = (hParamDC) ? hParamDC
977 : BeginPaint( lphc->self->hwndSelf, &ps);
980 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
982 HBRUSH hPrevBrush, hBkgBrush;
985 * Retrieve the background brush and select it in the
988 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
990 hPrevBrush = SelectObject( hDC, hBkgBrush );
993 * In non 3.1 look, there is a sunken border on the combobox
995 if (TWEAK_WineLook != WIN31_LOOK)
997 CBPaintBorder(CB_HWND(lphc), lphc, hDC);
1000 if( !IsRectEmpty(&lphc->buttonRect) )
1002 CBPaintButton(lphc, hDC, lphc->buttonRect);
1005 if( !(lphc->wState & CBF_EDIT) )
1008 * The text area has a border only in Win 3.1 look.
1010 if (TWEAK_WineLook == WIN31_LOOK)
1012 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
1015 lphc->textRect.left, lphc->textRect.top,
1016 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1018 SelectObject( hDC, hPrevPen );
1021 CBPaintText( lphc, hDC, lphc->textRect);
1025 SelectObject( hDC, hPrevBrush );
1029 EndPaint(lphc->self->hwndSelf, &ps);
1034 /***********************************************************************
1037 * Select listbox entry according to the contents of the edit control.
1039 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1042 LPWSTR pText = NULL;
1045 length = CB_GETEDITTEXTLENGTH( lphc );
1048 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1050 TRACE("\t edit text length %i\n", length );
1054 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1055 else pText[0] = '\0';
1056 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1057 (WPARAM)(-1), (LPARAM)pText );
1058 HeapFree( GetProcessHeap(), 0, pText );
1061 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1063 /* probably superfluous but Windows sends this too */
1064 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1065 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1070 /***********************************************************************
1073 * Copy a listbox entry to the edit control.
1075 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1078 LPWSTR pText = NULL;
1079 static const WCHAR empty_stringW[] = { 0 };
1081 TRACE("\t %i\n", index );
1083 if( index >= 0 ) /* got an entry */
1085 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1088 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1090 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1091 (WPARAM)index, (LPARAM)pText );
1096 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1097 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1098 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1100 if( lphc->wState & CBF_FOCUSED )
1101 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1104 HeapFree( GetProcessHeap(), 0, pText );
1107 /***********************************************************************
1110 * Show listbox popup.
1112 static void CBDropDown( LPHEADCOMBO lphc )
1118 TRACE("[%04x]: drop down\n", CB_HWND(lphc));
1120 CB_NOTIFY( lphc, CBN_DROPDOWN );
1124 lphc->wState |= CBF_DROPPED;
1125 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1127 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1129 /* Update edit only if item is in the list */
1130 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1131 CBUpdateEdit( lphc, lphc->droppedIndex );
1135 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1137 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1138 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1139 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1142 /* now set popup position */
1143 GetWindowRect( lphc->self->hwndSelf, &rect );
1146 * If it's a dropdown, the listbox is offset
1148 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1149 rect.left += COMBO_EDITBUTTONSPACE();
1151 /* if the dropped height is greater than the total height of the dropped
1152 items list, then force the drop down list height to be the total height
1153 of the items in the dropped list */
1155 /* And Remove any extra space (Best Fit) */
1156 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1157 /* if listbox length has been set directly by its handle */
1158 GetWindowRect(lphc->hWndLBox, &r);
1159 if (nDroppedHeight < r.bottom - r.top)
1160 nDroppedHeight = r.bottom - r.top;
1161 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1167 nHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1170 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1171 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1174 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1175 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1176 rect.bottom = rect.top - nDroppedHeight;
1178 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1179 lphc->droppedRect.right - lphc->droppedRect.left,
1181 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1184 if( !(lphc->wState & CBF_NOREDRAW) )
1185 RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE |
1186 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1188 EnableWindow( lphc->hWndLBox, TRUE );
1189 if (GetCapture() != lphc->self->hwndSelf)
1190 SetCapture(lphc->hWndLBox);
1193 /***********************************************************************
1196 * Hide listbox popup.
1198 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1200 HWND hWnd = lphc->self->hwndSelf;
1202 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1204 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1207 TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok );
1209 if( lphc->wState & CBF_DROPPED )
1213 lphc->wState &= ~CBF_DROPPED;
1214 ShowWindow( lphc->hWndLBox, SW_HIDE );
1215 if(GetCapture() == lphc->hWndLBox)
1220 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1222 rect = lphc->buttonRect;
1233 rect = lphc->textRect;
1238 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1239 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1240 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1241 CB_NOTIFY( lphc, CBN_CLOSEUP );
1246 /***********************************************************************
1249 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1251 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1253 if( lphc->wState & CBF_DROPPED )
1255 CBRollUp( lphc, ok, bRedrawButton );
1263 /***********************************************************************
1266 static void CBRepaintButton( LPHEADCOMBO lphc )
1268 InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE);
1269 UpdateWindow(CB_HWND(lphc));
1272 /***********************************************************************
1275 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1277 if( !(lphc->wState & CBF_FOCUSED) )
1279 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1280 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1282 lphc->wState |= CBF_FOCUSED;
1284 if( !(lphc->wState & CBF_EDIT) )
1285 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1287 CB_NOTIFY( lphc, CBN_SETFOCUS );
1291 /***********************************************************************
1294 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1296 HWND hWnd = lphc->self->hwndSelf;
1298 if( lphc->wState & CBF_FOCUSED )
1300 CBRollUp( lphc, FALSE, TRUE );
1301 if( IsWindow( hWnd ) )
1303 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1304 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1306 lphc->wState &= ~CBF_FOCUSED;
1309 if( !(lphc->wState & CBF_EDIT) )
1310 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1312 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1317 /***********************************************************************
1320 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1322 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1324 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1326 switch( HIWORD(wParam) >> 8 )
1328 case (EN_SETFOCUS >> 8):
1330 TRACE("[%04x]: edit [%04x] got focus\n",
1331 CB_HWND(lphc), lphc->hWndEdit );
1333 COMBO_SetFocus( lphc );
1336 case (EN_KILLFOCUS >> 8):
1338 TRACE("[%04x]: edit [%04x] lost focus\n",
1339 CB_HWND(lphc), lphc->hWndEdit );
1341 /* NOTE: it seems that Windows' edit control sends an
1342 * undocumented message WM_USER + 0x1B instead of this
1343 * notification (only when it happens to be a part of
1344 * the combo). ?? - AK.
1347 COMBO_KillFocus( lphc );
1351 case (EN_CHANGE >> 8):
1353 * In some circumstances (when the selection of the combobox
1354 * is changed for example) we don't wans the EN_CHANGE notification
1355 * to be forwarded to the parent of the combobox. This code
1356 * checks a flag that is set in these occasions and ignores the
1359 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1360 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1362 if (lphc->wState & CBF_NOLBSELECT)
1364 lphc->wState &= ~CBF_NOLBSELECT;
1368 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1372 case (EN_UPDATE >> 8):
1373 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1374 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1377 case (EN_ERRSPACE >> 8):
1378 CB_NOTIFY( lphc, CBN_ERRSPACE );
1381 else if( lphc->hWndLBox == hWnd )
1383 switch( HIWORD(wParam) )
1386 CB_NOTIFY( lphc, CBN_ERRSPACE );
1390 CB_NOTIFY( lphc, CBN_DBLCLK );
1396 TRACE("[%04x]: lbox selection change [%04x]\n",
1397 CB_HWND(lphc), lphc->wState );
1399 if( HIWORD(wParam) == LBN_SELCHANGE)
1401 if( lphc->wState & CBF_EDIT )
1403 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1404 lphc->wState |= CBF_NOLBSELECT;
1405 CBUpdateEdit( lphc, index );
1406 /* select text in edit, as Windows does */
1407 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1410 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1413 /* do not roll up if selection is being tracked
1414 * by arrowkeys in the dropdown listbox */
1416 if( (lphc->dwStyle & CBS_SIMPLE) ||
1417 ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1419 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1421 else lphc->wState &= ~CBF_NOROLLUP;
1423 CB_NOTIFY( lphc, CBN_SELCHANGE );
1429 /* nothing to do here since ComboLBox always resets the focus to its
1430 * combo/edit counterpart */
1437 /***********************************************************************
1440 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1442 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1444 HWND hWnd = lphc->self->hwndSelf;
1446 TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg );
1448 #define lpIS ((LPDELETEITEMSTRUCT)lParam)
1450 /* two first items are the same in all 4 structs */
1451 lpIS->CtlType = ODT_COMBOBOX;
1452 lpIS->CtlID = lphc->self->wIDmenu;
1454 switch( msg ) /* patch window handle */
1457 lpIS->hwndItem = hWnd;
1461 #define lpIS ((LPDRAWITEMSTRUCT)lParam)
1462 lpIS->hwndItem = hWnd;
1465 case WM_COMPAREITEM:
1466 #define lpIS ((LPCOMPAREITEMSTRUCT)lParam)
1467 lpIS->hwndItem = hWnd;
1472 return SendMessageW(lphc->owner, msg, lphc->self->wIDmenu, lParam);
1475 /***********************************************************************
1478 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, INT N, LPARAM lParam, BOOL unicode)
1480 if( lphc->wState & CBF_EDIT )
1481 return unicode ? SendMessageW(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam) :
1482 SendMessageA(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam);
1484 /* get it from the listbox */
1486 if( lphc->hWndLBox )
1488 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1492 INT length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN,
1497 LPWSTR lpBuffer, lpText = (LPWSTR)lParam;
1499 /* 'length' is without the terminating character */
1501 lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1507 n = SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1509 /* truncate if buffer is too short */
1515 strncpyW(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1516 lpText[N - 1] = '\0';
1519 HeapFree( GetProcessHeap(), 0, lpBuffer );
1524 LPSTR lpBuffer, lpText = (LPSTR)lParam;
1526 /* 'length' is without the terminating character */
1528 lpBuffer = HeapAlloc(GetProcessHeap(), 0, length + 1);
1534 n = SendMessageA(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1536 /* truncate if buffer is too short */
1542 strncpy(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1543 lpText[N - 1] = '\0';
1546 HeapFree( GetProcessHeap(), 0, lpBuffer );
1556 /***********************************************************************
1559 * This function sets window positions according to the updated
1560 * component placement struct.
1562 static void CBResetPos(
1568 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1570 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1571 * sizing messages */
1573 if( lphc->wState & CBF_EDIT )
1574 SetWindowPos( lphc->hWndEdit, 0,
1575 rectEdit->left, rectEdit->top,
1576 rectEdit->right - rectEdit->left,
1577 rectEdit->bottom - rectEdit->top,
1578 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1580 SetWindowPos( lphc->hWndLBox, 0,
1581 rectLB->left, rectLB->top,
1582 rectLB->right - rectLB->left,
1583 rectLB->bottom - rectLB->top,
1584 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1588 if( lphc->wState & CBF_DROPPED )
1590 lphc->wState &= ~CBF_DROPPED;
1591 ShowWindow( lphc->hWndLBox, SW_HIDE );
1594 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1595 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
1596 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1601 /***********************************************************************
1604 static void COMBO_Size( LPHEADCOMBO lphc )
1606 CBCalcPlacement(lphc->self->hwndSelf,
1610 &lphc->droppedRect);
1612 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1616 /***********************************************************************
1619 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1624 lphc->hFont = hFont;
1627 * Propagate to owned windows.
1629 if( lphc->wState & CBF_EDIT )
1630 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1631 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1634 * Redo the layout of the control.
1636 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1638 CBCalcPlacement(lphc->self->hwndSelf,
1642 &lphc->droppedRect);
1644 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1648 CBForceDummyResize(lphc);
1653 /***********************************************************************
1654 * COMBO_SetItemHeight
1656 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1658 LRESULT lRet = CB_ERR;
1660 if( index == -1 ) /* set text field height */
1662 if( height < 32768 )
1664 lphc->editHeight = height;
1667 * Redo the layout of the control.
1669 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1671 CBCalcPlacement(lphc->self->hwndSelf,
1675 &lphc->droppedRect);
1677 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1681 CBForceDummyResize(lphc);
1687 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1688 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1689 (WPARAM)index, (LPARAM)height );
1693 /***********************************************************************
1694 * COMBO_SelectString
1696 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1698 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1699 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1702 if( lphc->wState & CBF_EDIT )
1703 CBUpdateEdit( lphc, index );
1706 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1709 return (LRESULT)index;
1712 /***********************************************************************
1715 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1719 HWND hWnd = lphc->self->hwndSelf;
1721 pt.x = LOWORD(lParam);
1722 pt.y = HIWORD(lParam);
1723 bButton = PtInRect(&lphc->buttonRect, pt);
1725 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1726 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1728 lphc->wState |= CBF_BUTTONDOWN;
1729 if( lphc->wState & CBF_DROPPED )
1731 /* got a click to cancel selection */
1733 lphc->wState &= ~CBF_BUTTONDOWN;
1734 CBRollUp( lphc, TRUE, FALSE );
1735 if( !IsWindow( hWnd ) ) return;
1737 if( lphc->wState & CBF_CAPTURE )
1739 lphc->wState &= ~CBF_CAPTURE;
1745 /* drop down the listbox and start tracking */
1747 lphc->wState |= CBF_CAPTURE;
1751 if( bButton ) CBRepaintButton( lphc );
1755 /***********************************************************************
1758 * Release capture and stop tracking if needed.
1760 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1762 if( lphc->wState & CBF_CAPTURE )
1764 lphc->wState &= ~CBF_CAPTURE;
1765 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1767 INT index = CBUpdateLBox( lphc, TRUE );
1768 /* Update edit only if item is in the list */
1771 lphc->wState |= CBF_NOLBSELECT;
1772 CBUpdateEdit( lphc, index );
1773 lphc->wState &= ~CBF_NOLBSELECT;
1777 SetCapture(lphc->hWndLBox);
1780 if( lphc->wState & CBF_BUTTONDOWN )
1782 lphc->wState &= ~CBF_BUTTONDOWN;
1783 CBRepaintButton( lphc );
1787 /***********************************************************************
1790 * Two things to do - track combo button and release capture when
1791 * pointer goes into the listbox.
1793 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1798 pt.x = LOWORD(lParam);
1799 pt.y = HIWORD(lParam);
1801 if( lphc->wState & CBF_BUTTONDOWN )
1805 bButton = PtInRect(&lphc->buttonRect, pt);
1809 lphc->wState &= ~CBF_BUTTONDOWN;
1810 CBRepaintButton( lphc );
1814 GetClientRect( lphc->hWndLBox, &lbRect );
1815 MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 );
1816 if( PtInRect(&lbRect, pt) )
1818 lphc->wState &= ~CBF_CAPTURE;
1820 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1822 /* hand over pointer tracking */
1823 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1828 /***********************************************************************
1829 * ComboWndProc_locked
1831 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1833 static LRESULT ComboWndProc_locked( WND* pWnd, UINT message,
1834 WPARAM wParam, LPARAM lParam, BOOL unicode )
1837 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1838 HWND hwnd = pWnd->hwndSelf;
1840 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1841 pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam );
1843 if( lphc || message == WM_NCCREATE )
1847 /* System messages */
1851 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1852 ((LPCREATESTRUCTA)lParam)->style;
1853 return COMBO_NCCreate(pWnd, style);
1856 COMBO_NCDestroy(lphc);
1857 break;/* -> DefWindowProc */
1865 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1866 style = ((LPCREATESTRUCTW)lParam)->style;
1870 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1871 style = ((LPCREATESTRUCTA)lParam)->style;
1873 return COMBO_Create(lphc, pWnd, hwndParent, style);
1876 case WM_PRINTCLIENT:
1877 if (lParam & PRF_ERASEBKGND)
1878 COMBO_EraseBackground(hwnd, lphc, wParam);
1882 /* wParam may contain a valid HDC! */
1883 return COMBO_Paint(lphc, wParam);
1885 return COMBO_EraseBackground(hwnd, lphc, wParam);
1888 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1889 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1891 int vk = (int)((LPMSG)lParam)->wParam;
1893 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1894 result |= DLGC_WANTMESSAGE;
1898 case WM_WINDOWPOSCHANGING:
1899 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1900 case WM_WINDOWPOSCHANGED:
1901 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1902 * In that case, the Combobox itself will not be resized, so we won't
1903 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1908 if( lphc->hWndLBox &&
1909 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1912 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1915 return (LRESULT)lphc->hFont;
1917 if( lphc->wState & CBF_EDIT )
1918 SetFocus( lphc->hWndEdit );
1920 COMBO_SetFocus( lphc );
1923 #define hwndFocus ((HWND16)wParam)
1925 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1926 COMBO_KillFocus( lphc );
1930 return COMBO_Command( lphc, wParam, (HWND)lParam );
1932 return COMBO_GetText( lphc, (INT)wParam, lParam, unicode );
1934 case WM_GETTEXTLENGTH:
1936 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1938 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1939 if (j == -1) return 0;
1940 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1942 else if( lphc->wState & CBF_EDIT )
1945 lphc->wState |= CBF_NOEDITNOTIFY;
1946 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1947 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1948 lphc->wState &= ~CBF_NOEDITNOTIFY;
1955 if( lphc->wState & CBF_EDIT )
1957 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1958 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1964 case WM_COMPAREITEM:
1965 case WM_MEASUREITEM:
1966 return COMBO_ItemOp(lphc, message, lParam);
1968 if( lphc->wState & CBF_EDIT )
1969 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1970 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1972 /* Force the control to repaint when the enabled state changes. */
1973 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
1977 lphc->wState &= ~CBF_NOREDRAW;
1979 lphc->wState |= CBF_NOREDRAW;
1981 if( lphc->wState & CBF_EDIT )
1982 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1983 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1986 if( KEYDATA_ALT & HIWORD(lParam) )
1987 if( wParam == VK_UP || wParam == VK_DOWN )
1988 COMBO_FlipListbox( lphc, FALSE, FALSE );
1996 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1997 (lphc->wState & CBF_DROPPED))
1999 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2003 if( lphc->wState & CBF_EDIT )
2004 hwndTarget = lphc->hWndEdit;
2006 hwndTarget = lphc->hWndLBox;
2008 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2009 SendMessageA(hwndTarget, message, wParam, lParam);
2011 case WM_LBUTTONDOWN:
2012 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf );
2013 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2016 COMBO_LButtonUp( lphc );
2019 if( lphc->wState & CBF_CAPTURE )
2020 COMBO_MouseMove( lphc, wParam, lParam );
2024 if (wParam & (MK_SHIFT | MK_CONTROL))
2025 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2026 DefWindowProcA(hwnd, message, wParam, lParam);
2027 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2028 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2031 /* Combo messages */
2033 case CB_ADDSTRING16:
2034 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2037 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2038 SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2039 case CB_INSERTSTRING16:
2040 wParam = (INT)(INT16)wParam;
2041 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2043 case CB_INSERTSTRING:
2044 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2045 SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2046 case CB_DELETESTRING16:
2047 case CB_DELETESTRING:
2048 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2049 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2050 case CB_SELECTSTRING16:
2051 wParam = (INT)(INT16)wParam;
2052 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2054 case CB_SELECTSTRING:
2055 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2056 case CB_FINDSTRING16:
2057 wParam = (INT)(INT16)wParam;
2058 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2061 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2062 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2063 case CB_FINDSTRINGEXACT16:
2064 wParam = (INT)(INT16)wParam;
2065 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2067 case CB_FINDSTRINGEXACT:
2068 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2069 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2070 case CB_SETITEMHEIGHT16:
2071 wParam = (INT)(INT16)wParam; /* signed integer */
2073 case CB_SETITEMHEIGHT:
2074 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2075 case CB_GETITEMHEIGHT16:
2076 wParam = (INT)(INT16)wParam;
2078 case CB_GETITEMHEIGHT:
2079 if( (INT)wParam >= 0 ) /* listbox item */
2080 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2081 return CBGetTextAreaHeight(hwnd, lphc);
2082 case CB_RESETCONTENT16:
2083 case CB_RESETCONTENT:
2084 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2085 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2087 static const WCHAR empty_stringW[] = { 0 };
2088 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2091 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
2093 case CB_INITSTORAGE:
2094 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2095 case CB_GETHORIZONTALEXTENT:
2096 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2097 case CB_SETHORIZONTALEXTENT:
2098 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2099 case CB_GETTOPINDEX:
2100 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2102 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2104 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2105 case CB_GETDROPPEDWIDTH:
2106 if( lphc->droppedWidth )
2107 return lphc->droppedWidth;
2108 return lphc->droppedRect.right - lphc->droppedRect.left;
2109 case CB_SETDROPPEDWIDTH:
2110 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2111 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2113 case CB_GETDROPPEDCONTROLRECT16:
2114 lParam = (LPARAM)MapSL(lParam);
2118 CBGetDroppedControlRect( lphc, &r );
2119 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2122 case CB_GETDROPPEDCONTROLRECT:
2123 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2125 case CB_GETDROPPEDSTATE16:
2126 case CB_GETDROPPEDSTATE:
2127 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2129 lParam = (LPARAM)MapSL(lParam);
2133 if(message == CB_DIR) message = LB_DIR;
2134 return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) :
2135 SendMessageA(lphc->hWndLBox, message, wParam, lParam);
2137 case CB_SHOWDROPDOWN16:
2138 case CB_SHOWDROPDOWN:
2139 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2143 if( !(lphc->wState & CBF_DROPPED) )
2147 if( lphc->wState & CBF_DROPPED )
2148 CBRollUp( lphc, FALSE, TRUE );
2153 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2154 case CB_GETCURSEL16:
2156 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2157 case CB_SETCURSEL16:
2158 wParam = (INT)(INT16)wParam;
2161 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2163 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2165 /* no LBN_SELCHANGE in this case, update manually */
2166 if( lphc->wState & CBF_EDIT )
2167 CBUpdateEdit( lphc, (INT)wParam );
2169 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
2170 lphc->wState &= ~CBF_SELCHANGE;
2172 case CB_GETLBTEXT16:
2173 wParam = (INT)(INT16)wParam;
2174 lParam = (LPARAM)MapSL(lParam);
2177 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2178 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2179 case CB_GETLBTEXTLEN16:
2180 wParam = (INT)(INT16)wParam;
2182 case CB_GETLBTEXTLEN:
2183 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2184 case CB_GETITEMDATA16:
2185 wParam = (INT)(INT16)wParam;
2187 case CB_GETITEMDATA:
2188 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2189 case CB_SETITEMDATA16:
2190 wParam = (INT)(INT16)wParam;
2192 case CB_SETITEMDATA:
2193 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2194 case CB_GETEDITSEL16:
2195 wParam = lParam = 0; /* just in case */
2198 /* Edit checks passed parameters itself */
2199 if( lphc->wState & CBF_EDIT )
2200 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2202 case CB_SETEDITSEL16:
2204 if( lphc->wState & CBF_EDIT )
2205 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2206 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2208 case CB_SETEXTENDEDUI16:
2209 case CB_SETEXTENDEDUI:
2210 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2213 lphc->wState |= CBF_EUI;
2214 else lphc->wState &= ~CBF_EUI;
2216 case CB_GETEXTENDEDUI16:
2217 case CB_GETEXTENDEDUI:
2218 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2221 if (message >= WM_USER)
2222 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2223 message - WM_USER, wParam, lParam );
2226 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2227 DefWindowProcA(hwnd, message, wParam, lParam);
2232 /***********************************************************************
2235 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2238 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2240 WND* pWnd = WIN_FindWndPtr(hwnd);
2241 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, FALSE);
2243 WIN_ReleaseWndPtr(pWnd);
2247 /***********************************************************************
2250 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2252 WND* pWnd = WIN_FindWndPtr(hwnd);
2253 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, TRUE);
2255 WIN_ReleaseWndPtr(pWnd);