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 */
762 if( lphc->wState & CBF_EDIT )
764 static const WCHAR empty_stringW[] = { 0 };
765 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
766 if( lphc->wState & CBF_FOCUSED )
767 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
769 else /* paint text field ourselves */
772 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
775 * Give ourselves some space.
777 InflateRect( &rectEdit, -1, -1 );
779 if ( (lphc->wState & CBF_FOCUSED) &&
780 !(lphc->wState & CBF_DROPPED) )
784 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
785 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
786 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
787 itemState = ODS_SELECTED | ODS_FOCUS;
792 if( CB_OWNERDRAWN(lphc) )
798 * Save the current clip region.
799 * To retrieve the clip region, we need to create one "dummy"
802 clipRegion = CreateRectRgnIndirect(&rectEdit);
804 if (GetClipRgn(hdc, clipRegion)!=1)
806 DeleteObject(clipRegion);
807 clipRegion=(HRGN)NULL;
810 if ( lphc->self->dwStyle & WS_DISABLED )
811 itemState |= ODS_DISABLED;
813 dis.CtlType = ODT_COMBOBOX;
814 dis.CtlID = lphc->self->wIDmenu;
815 dis.hwndItem = lphc->self->hwndSelf;
816 dis.itemAction = ODA_DRAWENTIRE;
818 dis.itemState = itemState;
820 dis.rcItem = rectEdit;
821 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA,
825 * Clip the DC and have the parent draw the item.
827 IntersectClipRect(hdc,
828 rectEdit.left, rectEdit.top,
829 rectEdit.right, rectEdit.bottom);
831 SendMessageW(lphc->owner, WM_DRAWITEM,
832 lphc->self->wIDmenu, (LPARAM)&dis );
835 * Reset the clipping region.
837 SelectClipRgn(hdc, clipRegion);
841 static const WCHAR empty_stringW[] = { 0 };
845 ETO_OPAQUE | ETO_CLIPPED,
847 pText ? pText : empty_stringW , size, NULL );
849 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
850 DrawFocusRect( hdc, &rectEdit );
854 SelectObject(hdc, hPrevFont );
857 HeapFree( GetProcessHeap(), 0, pText );
860 /***********************************************************************
863 static void CBPaintBorder(
870 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
872 GetClientRect(hwnd, &clientRect);
876 CopyRect(&clientRect, &lphc->textRect);
878 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
879 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
882 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
885 /***********************************************************************
886 * COMBO_PrepareColors
888 * This method will sent the appropriate WM_CTLCOLOR message to
889 * prepare and setup the colors for the combo's DC.
891 * It also returns the brush to use for the background.
893 static HBRUSH COMBO_PrepareColors(
900 * Get the background brush for this control.
902 if (CB_DISABLED(lphc))
904 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
905 hDC, lphc->self->hwndSelf );
908 * We have to change the text color since WM_CTLCOLORSTATIC will
909 * set it to the "enabled" color. This is the same behavior as the
912 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
916 if (lphc->wState & CBF_EDIT)
918 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
919 hDC, lphc->self->hwndSelf );
923 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX,
924 hDC, lphc->self->hwndSelf );
932 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
937 /***********************************************************************
938 * COMBO_EraseBackground
940 static LRESULT COMBO_EraseBackground(
949 hDC = (hParamDC) ? hParamDC
953 * Calculate the area that we want to erase.
955 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
957 GetClientRect(hwnd, &clientRect);
961 CopyRect(&clientRect, &lphc->textRect);
963 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
967 * Retrieve the background brush
969 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
971 FillRect(hDC, &clientRect, hBkgBrush);
974 ReleaseDC(hwnd, hDC);
979 /***********************************************************************
982 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
987 hDC = (hParamDC) ? hParamDC
988 : BeginPaint( lphc->self->hwndSelf, &ps);
991 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
993 HBRUSH hPrevBrush, hBkgBrush;
996 * Retrieve the background brush and select it in the
999 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
1001 hPrevBrush = SelectObject( hDC, hBkgBrush );
1004 * In non 3.1 look, there is a sunken border on the combobox
1006 if (TWEAK_WineLook != WIN31_LOOK)
1008 CBPaintBorder(CB_HWND(lphc), lphc, hDC);
1011 if( !IsRectEmpty(&lphc->buttonRect) )
1013 CBPaintButton(lphc, hDC, lphc->buttonRect);
1016 if( !(lphc->wState & CBF_EDIT) )
1019 * The text area has a border only in Win 3.1 look.
1021 if (TWEAK_WineLook == WIN31_LOOK)
1023 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
1026 lphc->textRect.left, lphc->textRect.top,
1027 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1029 SelectObject( hDC, hPrevPen );
1032 CBPaintText( lphc, hDC, lphc->textRect);
1036 SelectObject( hDC, hPrevBrush );
1040 EndPaint(lphc->self->hwndSelf, &ps);
1045 /***********************************************************************
1048 * Select listbox entry according to the contents of the edit control.
1050 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1053 LPWSTR pText = NULL;
1056 length = CB_GETEDITTEXTLENGTH( lphc );
1059 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1061 TRACE("\t edit text length %i\n", length );
1065 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1066 else pText[0] = '\0';
1067 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1068 (WPARAM)(-1), (LPARAM)pText );
1069 HeapFree( GetProcessHeap(), 0, pText );
1074 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1076 /* probably superfluous but Windows sends this too */
1077 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1078 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1083 /***********************************************************************
1086 * Copy a listbox entry to the edit control.
1088 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1091 LPWSTR pText = NULL;
1092 static const WCHAR empty_stringW[] = { 0 };
1094 TRACE("\t %i\n", index );
1096 if( index >= 0 ) /* got an entry */
1098 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1101 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1103 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1104 (WPARAM)index, (LPARAM)pText );
1109 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1110 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1111 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1113 if( lphc->wState & CBF_FOCUSED )
1114 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1117 HeapFree( GetProcessHeap(), 0, pText );
1120 /***********************************************************************
1123 * Show listbox popup.
1125 static void CBDropDown( LPHEADCOMBO lphc )
1131 TRACE("[%04x]: drop down\n", CB_HWND(lphc));
1133 CB_NOTIFY( lphc, CBN_DROPDOWN );
1137 lphc->wState |= CBF_DROPPED;
1138 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1140 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1142 if( !(lphc->wState & CBF_CAPTURE) )
1143 CBUpdateEdit( lphc, lphc->droppedIndex );
1147 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1149 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1150 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1151 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1154 /* now set popup position */
1155 GetWindowRect( lphc->self->hwndSelf, &rect );
1158 * If it's a dropdown, the listbox is offset
1160 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1161 rect.left += COMBO_EDITBUTTONSPACE();
1163 /* if the dropped height is greater than the total height of the dropped
1164 items list, then force the drop down list height to be the total height
1165 of the items in the dropped list */
1167 /* And Remove any extra space (Best Fit) */
1168 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1169 /* if listbox length has been set directly by its handle */
1170 GetWindowRect(lphc->hWndLBox, &r);
1171 if (nDroppedHeight < r.bottom - r.top)
1172 nDroppedHeight = r.bottom - r.top;
1173 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1179 nHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1182 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1183 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1186 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1187 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1188 rect.bottom = rect.top - nDroppedHeight;
1190 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1191 lphc->droppedRect.right - lphc->droppedRect.left,
1193 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1196 if( !(lphc->wState & CBF_NOREDRAW) )
1197 RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE |
1198 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1200 EnableWindow( lphc->hWndLBox, TRUE );
1201 if (GetCapture() != lphc->self->hwndSelf)
1202 SetCapture(lphc->hWndLBox);
1205 /***********************************************************************
1208 * Hide listbox popup.
1210 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1212 HWND hWnd = lphc->self->hwndSelf;
1214 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1216 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1219 TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok );
1221 if( lphc->wState & CBF_DROPPED )
1225 lphc->wState &= ~CBF_DROPPED;
1226 ShowWindow( lphc->hWndLBox, SW_HIDE );
1227 if(GetCapture() == lphc->hWndLBox)
1232 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1234 rect = lphc->buttonRect;
1245 rect = lphc->textRect;
1250 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1251 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1252 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1253 CB_NOTIFY( lphc, CBN_CLOSEUP );
1258 /***********************************************************************
1261 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1263 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1265 if( lphc->wState & CBF_DROPPED )
1267 CBRollUp( lphc, ok, bRedrawButton );
1275 /***********************************************************************
1278 static void CBRepaintButton( LPHEADCOMBO lphc )
1280 InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE);
1281 UpdateWindow(CB_HWND(lphc));
1284 /***********************************************************************
1287 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1289 if( !(lphc->wState & CBF_FOCUSED) )
1291 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1292 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1294 lphc->wState |= CBF_FOCUSED;
1296 if( !(lphc->wState & CBF_EDIT) )
1297 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1299 CB_NOTIFY( lphc, CBN_SETFOCUS );
1303 /***********************************************************************
1306 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1308 HWND hWnd = lphc->self->hwndSelf;
1310 if( lphc->wState & CBF_FOCUSED )
1312 CBRollUp( lphc, FALSE, TRUE );
1313 if( IsWindow( hWnd ) )
1315 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1316 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1318 lphc->wState &= ~CBF_FOCUSED;
1321 if( !(lphc->wState & CBF_EDIT) )
1322 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1324 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1329 /***********************************************************************
1332 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1334 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1336 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1338 switch( HIWORD(wParam) >> 8 )
1340 case (EN_SETFOCUS >> 8):
1342 TRACE("[%04x]: edit [%04x] got focus\n",
1343 CB_HWND(lphc), lphc->hWndEdit );
1345 COMBO_SetFocus( lphc );
1348 case (EN_KILLFOCUS >> 8):
1350 TRACE("[%04x]: edit [%04x] lost focus\n",
1351 CB_HWND(lphc), lphc->hWndEdit );
1353 /* NOTE: it seems that Windows' edit control sends an
1354 * undocumented message WM_USER + 0x1B instead of this
1355 * notification (only when it happens to be a part of
1356 * the combo). ?? - AK.
1359 COMBO_KillFocus( lphc );
1363 case (EN_CHANGE >> 8):
1365 * In some circumstances (when the selection of the combobox
1366 * is changed for example) we don't wans the EN_CHANGE notification
1367 * to be forwarded to the parent of the combobox. This code
1368 * checks a flag that is set in these occasions and ignores the
1371 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1372 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1374 if (lphc->wState & CBF_NOLBSELECT)
1376 lphc->wState &= ~CBF_NOLBSELECT;
1380 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1384 case (EN_UPDATE >> 8):
1385 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1386 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1389 case (EN_ERRSPACE >> 8):
1390 CB_NOTIFY( lphc, CBN_ERRSPACE );
1393 else if( lphc->hWndLBox == hWnd )
1395 switch( HIWORD(wParam) )
1398 CB_NOTIFY( lphc, CBN_ERRSPACE );
1402 CB_NOTIFY( lphc, CBN_DBLCLK );
1408 TRACE("[%04x]: lbox selection change [%04x]\n",
1409 CB_HWND(lphc), lphc->wState );
1411 if( HIWORD(wParam) == LBN_SELCHANGE)
1413 if( lphc->wState & CBF_EDIT )
1415 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1416 lphc->wState |= CBF_NOLBSELECT;
1417 CBUpdateEdit( lphc, index );
1418 /* select text in edit, as Windows does */
1419 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1422 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1425 /* do not roll up if selection is being tracked
1426 * by arrowkeys in the dropdown listbox */
1428 if( (lphc->dwStyle & CBS_SIMPLE) ||
1429 ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1431 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1433 else lphc->wState &= ~CBF_NOROLLUP;
1435 CB_NOTIFY( lphc, CBN_SELCHANGE );
1441 /* nothing to do here since ComboLBox always resets the focus to its
1442 * combo/edit counterpart */
1449 /***********************************************************************
1452 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1454 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1456 HWND hWnd = lphc->self->hwndSelf;
1458 TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg );
1460 #define lpIS ((LPDELETEITEMSTRUCT)lParam)
1462 /* two first items are the same in all 4 structs */
1463 lpIS->CtlType = ODT_COMBOBOX;
1464 lpIS->CtlID = lphc->self->wIDmenu;
1466 switch( msg ) /* patch window handle */
1469 lpIS->hwndItem = hWnd;
1473 #define lpIS ((LPDRAWITEMSTRUCT)lParam)
1474 lpIS->hwndItem = hWnd;
1477 case WM_COMPAREITEM:
1478 #define lpIS ((LPCOMPAREITEMSTRUCT)lParam)
1479 lpIS->hwndItem = hWnd;
1484 return SendMessageW(lphc->owner, msg, lphc->self->wIDmenu, lParam);
1487 /***********************************************************************
1490 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, INT N, LPARAM lParam, BOOL unicode)
1492 if( lphc->wState & CBF_EDIT )
1493 return unicode ? SendMessageW(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam) :
1494 SendMessageA(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam);
1496 /* get it from the listbox */
1498 if( lphc->hWndLBox )
1500 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1504 INT length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN,
1509 LPWSTR lpBuffer, lpText = (LPWSTR)lParam;
1511 /* 'length' is without the terminating character */
1513 lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1519 n = SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1521 /* truncate if buffer is too short */
1527 strncpyW(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1528 lpText[N - 1] = '\0';
1531 HeapFree( GetProcessHeap(), 0, lpBuffer );
1536 LPSTR lpBuffer, lpText = (LPSTR)lParam;
1538 /* 'length' is without the terminating character */
1540 lpBuffer = HeapAlloc(GetProcessHeap(), 0, length + 1);
1546 n = SendMessageA(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1548 /* truncate if buffer is too short */
1554 strncpy(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1555 lpText[N - 1] = '\0';
1558 HeapFree( GetProcessHeap(), 0, lpBuffer );
1568 /***********************************************************************
1571 * This function sets window positions according to the updated
1572 * component placement struct.
1574 static void CBResetPos(
1580 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1582 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1583 * sizing messages */
1585 if( lphc->wState & CBF_EDIT )
1586 SetWindowPos( lphc->hWndEdit, 0,
1587 rectEdit->left, rectEdit->top,
1588 rectEdit->right - rectEdit->left,
1589 rectEdit->bottom - rectEdit->top,
1590 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1592 SetWindowPos( lphc->hWndLBox, 0,
1593 rectLB->left, rectLB->top,
1594 rectLB->right - rectLB->left,
1595 rectLB->bottom - rectLB->top,
1596 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1600 if( lphc->wState & CBF_DROPPED )
1602 lphc->wState &= ~CBF_DROPPED;
1603 ShowWindow( lphc->hWndLBox, SW_HIDE );
1606 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1607 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
1608 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1613 /***********************************************************************
1616 static void COMBO_Size( LPHEADCOMBO lphc )
1618 CBCalcPlacement(lphc->self->hwndSelf,
1622 &lphc->droppedRect);
1624 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1628 /***********************************************************************
1631 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1636 lphc->hFont = hFont;
1639 * Propagate to owned windows.
1641 if( lphc->wState & CBF_EDIT )
1642 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1643 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1646 * Redo the layout of the control.
1648 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1650 CBCalcPlacement(lphc->self->hwndSelf,
1654 &lphc->droppedRect);
1656 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1660 CBForceDummyResize(lphc);
1665 /***********************************************************************
1666 * COMBO_SetItemHeight
1668 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1670 LRESULT lRet = CB_ERR;
1672 if( index == -1 ) /* set text field height */
1674 if( height < 32768 )
1676 lphc->editHeight = height;
1679 * Redo the layout of the control.
1681 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1683 CBCalcPlacement(lphc->self->hwndSelf,
1687 &lphc->droppedRect);
1689 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1693 CBForceDummyResize(lphc);
1699 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1700 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1701 (WPARAM)index, (LPARAM)height );
1705 /***********************************************************************
1706 * COMBO_SelectString
1708 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1710 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1711 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1714 if( lphc->wState & CBF_EDIT )
1715 CBUpdateEdit( lphc, index );
1718 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1721 return (LRESULT)index;
1724 /***********************************************************************
1727 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1731 HWND hWnd = lphc->self->hwndSelf;
1733 pt.x = LOWORD(lParam);
1734 pt.y = HIWORD(lParam);
1735 bButton = PtInRect(&lphc->buttonRect, pt);
1737 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1738 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1740 lphc->wState |= CBF_BUTTONDOWN;
1741 if( lphc->wState & CBF_DROPPED )
1743 /* got a click to cancel selection */
1745 lphc->wState &= ~CBF_BUTTONDOWN;
1746 CBRollUp( lphc, TRUE, FALSE );
1747 if( !IsWindow( hWnd ) ) return;
1749 if( lphc->wState & CBF_CAPTURE )
1751 lphc->wState &= ~CBF_CAPTURE;
1757 /* drop down the listbox and start tracking */
1759 lphc->wState |= CBF_CAPTURE;
1763 if( bButton ) CBRepaintButton( lphc );
1767 /***********************************************************************
1770 * Release capture and stop tracking if needed.
1772 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1774 if( lphc->wState & CBF_CAPTURE )
1776 lphc->wState &= ~CBF_CAPTURE;
1777 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1779 INT index = CBUpdateLBox( lphc, TRUE );
1780 lphc->wState |= CBF_NOLBSELECT;
1781 CBUpdateEdit( lphc, index );
1782 lphc->wState &= ~CBF_NOLBSELECT;
1785 SetCapture(lphc->hWndLBox);
1789 if( lphc->wState & CBF_BUTTONDOWN )
1791 lphc->wState &= ~CBF_BUTTONDOWN;
1792 CBRepaintButton( lphc );
1796 /***********************************************************************
1799 * Two things to do - track combo button and release capture when
1800 * pointer goes into the listbox.
1802 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1807 pt.x = LOWORD(lParam);
1808 pt.y = HIWORD(lParam);
1810 if( lphc->wState & CBF_BUTTONDOWN )
1814 bButton = PtInRect(&lphc->buttonRect, pt);
1818 lphc->wState &= ~CBF_BUTTONDOWN;
1819 CBRepaintButton( lphc );
1823 GetClientRect( lphc->hWndLBox, &lbRect );
1824 MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 );
1825 if( PtInRect(&lbRect, pt) )
1827 lphc->wState &= ~CBF_CAPTURE;
1829 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1831 /* hand over pointer tracking */
1832 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1837 /***********************************************************************
1838 * ComboWndProc_locked
1840 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1842 static LRESULT ComboWndProc_locked( WND* pWnd, UINT message,
1843 WPARAM wParam, LPARAM lParam, BOOL unicode )
1846 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1847 HWND hwnd = pWnd->hwndSelf;
1849 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1850 pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam );
1852 if( lphc || message == WM_NCCREATE )
1856 /* System messages */
1860 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1861 ((LPCREATESTRUCTA)lParam)->style;
1862 return COMBO_NCCreate(pWnd, style);
1865 COMBO_NCDestroy(lphc);
1866 break;/* -> DefWindowProc */
1874 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1875 style = ((LPCREATESTRUCTW)lParam)->style;
1879 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1880 style = ((LPCREATESTRUCTA)lParam)->style;
1882 return COMBO_Create(lphc, pWnd, hwndParent, style);
1885 case WM_PRINTCLIENT:
1886 if (lParam & PRF_ERASEBKGND)
1887 COMBO_EraseBackground(hwnd, lphc, wParam);
1891 /* wParam may contain a valid HDC! */
1892 return COMBO_Paint(lphc, wParam);
1894 return COMBO_EraseBackground(hwnd, lphc, wParam);
1897 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1898 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1900 int vk = (int)((LPMSG)lParam)->wParam;
1902 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1903 result |= DLGC_WANTMESSAGE;
1907 case WM_WINDOWPOSCHANGING:
1908 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1909 case WM_WINDOWPOSCHANGED:
1910 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1911 * In that case, the Combobox itself will not be resized, so we won't
1912 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1917 if( lphc->hWndLBox &&
1918 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1921 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1924 return (LRESULT)lphc->hFont;
1926 if( lphc->wState & CBF_EDIT )
1927 SetFocus( lphc->hWndEdit );
1929 COMBO_SetFocus( lphc );
1932 #define hwndFocus ((HWND16)wParam)
1934 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1935 COMBO_KillFocus( lphc );
1939 return COMBO_Command( lphc, wParam, (HWND)lParam );
1941 return COMBO_GetText( lphc, (INT)wParam, lParam, unicode );
1943 case WM_GETTEXTLENGTH:
1945 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1947 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1948 if (j == -1) return 0;
1949 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1951 else if( lphc->wState & CBF_EDIT )
1954 lphc->wState |= CBF_NOEDITNOTIFY;
1955 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1956 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1957 lphc->wState &= ~CBF_NOEDITNOTIFY;
1964 if( lphc->wState & CBF_EDIT )
1966 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1967 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1973 case WM_COMPAREITEM:
1974 case WM_MEASUREITEM:
1975 return COMBO_ItemOp(lphc, message, lParam);
1977 if( lphc->wState & CBF_EDIT )
1978 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1979 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1981 /* Force the control to repaint when the enabled state changes. */
1982 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
1986 lphc->wState &= ~CBF_NOREDRAW;
1988 lphc->wState |= CBF_NOREDRAW;
1990 if( lphc->wState & CBF_EDIT )
1991 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1992 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1995 if( KEYDATA_ALT & HIWORD(lParam) )
1996 if( wParam == VK_UP || wParam == VK_DOWN )
1997 COMBO_FlipListbox( lphc, FALSE, FALSE );
2005 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2006 (lphc->wState & CBF_DROPPED))
2008 CBRollUp( lphc, (CHAR)wParam == VK_RETURN, FALSE );
2012 if( lphc->wState & CBF_EDIT )
2013 hwndTarget = lphc->hWndEdit;
2015 hwndTarget = lphc->hWndLBox;
2017 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2018 SendMessageA(hwndTarget, message, wParam, lParam);
2020 case WM_LBUTTONDOWN:
2021 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf );
2022 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2025 COMBO_LButtonUp( lphc );
2028 if( lphc->wState & CBF_CAPTURE )
2029 COMBO_MouseMove( lphc, wParam, lParam );
2033 if (wParam & (MK_SHIFT | MK_CONTROL))
2034 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2035 DefWindowProcA(hwnd, message, wParam, lParam);
2036 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2037 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2040 /* Combo messages */
2042 case CB_ADDSTRING16:
2043 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2046 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2047 SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2048 case CB_INSERTSTRING16:
2049 wParam = (INT)(INT16)wParam;
2050 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2052 case CB_INSERTSTRING:
2053 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2054 SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2055 case CB_DELETESTRING16:
2056 case CB_DELETESTRING:
2057 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2058 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2059 case CB_SELECTSTRING16:
2060 wParam = (INT)(INT16)wParam;
2061 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2063 case CB_SELECTSTRING:
2064 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2065 case CB_FINDSTRING16:
2066 wParam = (INT)(INT16)wParam;
2067 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2070 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2071 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2072 case CB_FINDSTRINGEXACT16:
2073 wParam = (INT)(INT16)wParam;
2074 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2076 case CB_FINDSTRINGEXACT:
2077 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2078 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2079 case CB_SETITEMHEIGHT16:
2080 wParam = (INT)(INT16)wParam; /* signed integer */
2082 case CB_SETITEMHEIGHT:
2083 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2084 case CB_GETITEMHEIGHT16:
2085 wParam = (INT)(INT16)wParam;
2087 case CB_GETITEMHEIGHT:
2088 if( (INT)wParam >= 0 ) /* listbox item */
2089 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2090 return CBGetTextAreaHeight(hwnd, lphc);
2091 case CB_RESETCONTENT16:
2092 case CB_RESETCONTENT:
2093 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2094 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2096 static const WCHAR empty_stringW[] = { 0 };
2097 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2100 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
2102 case CB_INITSTORAGE:
2103 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2104 case CB_GETHORIZONTALEXTENT:
2105 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2106 case CB_SETHORIZONTALEXTENT:
2107 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2108 case CB_GETTOPINDEX:
2109 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2111 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2113 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2114 case CB_GETDROPPEDWIDTH:
2115 if( lphc->droppedWidth )
2116 return lphc->droppedWidth;
2117 return lphc->droppedRect.right - lphc->droppedRect.left;
2118 case CB_SETDROPPEDWIDTH:
2119 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2120 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2122 case CB_GETDROPPEDCONTROLRECT16:
2123 lParam = (LPARAM)MapSL(lParam);
2127 CBGetDroppedControlRect( lphc, &r );
2128 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2131 case CB_GETDROPPEDCONTROLRECT:
2132 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2134 case CB_GETDROPPEDSTATE16:
2135 case CB_GETDROPPEDSTATE:
2136 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2138 lParam = (LPARAM)MapSL(lParam);
2143 LPSTR dir; /* temporary, until listbox is unicoded */
2146 LPWSTR textW = (LPWSTR)lParam;
2147 INT countA = WideCharToMultiByte(CP_ACP, 0, textW, -1, NULL, 0, NULL, NULL);
2148 dir = HeapAlloc(GetProcessHeap(), 0, countA);
2149 WideCharToMultiByte(CP_ACP, 0, textW, -1, dir, countA, NULL, NULL);
2152 dir = (LPSTR)lParam;
2153 res = COMBO_Directory(lphc, (UINT)wParam, dir, (message == CB_DIR));
2155 HeapFree(GetProcessHeap(), 0, dir);
2158 case CB_SHOWDROPDOWN16:
2159 case CB_SHOWDROPDOWN:
2160 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2164 if( !(lphc->wState & CBF_DROPPED) )
2168 if( lphc->wState & CBF_DROPPED )
2169 CBRollUp( lphc, FALSE, TRUE );
2174 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2175 case CB_GETCURSEL16:
2177 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2178 case CB_SETCURSEL16:
2179 wParam = (INT)(INT16)wParam;
2182 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2184 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2185 if( lphc->wState & CBF_SELCHANGE )
2187 /* no LBN_SELCHANGE in this case, update manually */
2188 if( lphc->wState & CBF_EDIT )
2189 CBUpdateEdit( lphc, (INT)wParam );
2191 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
2192 lphc->wState &= ~CBF_SELCHANGE;
2195 case CB_GETLBTEXT16:
2196 wParam = (INT)(INT16)wParam;
2197 lParam = (LPARAM)MapSL(lParam);
2200 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2201 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2202 case CB_GETLBTEXTLEN16:
2203 wParam = (INT)(INT16)wParam;
2205 case CB_GETLBTEXTLEN:
2206 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2207 case CB_GETITEMDATA16:
2208 wParam = (INT)(INT16)wParam;
2210 case CB_GETITEMDATA:
2211 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2212 case CB_SETITEMDATA16:
2213 wParam = (INT)(INT16)wParam;
2215 case CB_SETITEMDATA:
2216 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2217 case CB_GETEDITSEL16:
2218 wParam = lParam = 0; /* just in case */
2221 /* Edit checks passed parameters itself */
2222 if( lphc->wState & CBF_EDIT )
2223 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2225 case CB_SETEDITSEL16:
2227 if( lphc->wState & CBF_EDIT )
2228 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2229 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2231 case CB_SETEXTENDEDUI16:
2232 case CB_SETEXTENDEDUI:
2233 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2236 lphc->wState |= CBF_EUI;
2237 else lphc->wState &= ~CBF_EUI;
2239 case CB_GETEXTENDEDUI16:
2240 case CB_GETEXTENDEDUI:
2241 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2244 if (message >= WM_USER)
2245 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2246 message - WM_USER, wParam, lParam );
2249 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2250 DefWindowProcA(hwnd, message, wParam, lParam);
2255 /***********************************************************************
2258 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2261 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2263 WND* pWnd = WIN_FindWndPtr(hwnd);
2264 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, FALSE);
2266 WIN_ReleaseWndPtr(pWnd);
2270 /***********************************************************************
2273 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2275 WND* pWnd = WIN_FindWndPtr(hwnd);
2276 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, TRUE);
2278 WIN_ReleaseWndPtr(pWnd);