4 * Copyright 1997 Alex Korobka
6 * FIXME: roll up in Netscape 3.01.
15 #include "wine/winuser16.h"
21 #include "debugtools.h"
23 DEFAULT_DEBUG_CHANNEL(combo);
25 /* bits in the dwKeyData */
26 #define KEYDATA_ALT 0x2000
27 #define KEYDATA_PREVSTATE 0x4000
30 * Additional combo box definitions
33 #define CB_GETPTR( wnd ) (*(LPHEADCOMBO*)((wnd)->wExtra))
34 #define CB_NOTIFY( lphc, code ) \
35 (SendMessageW((lphc)->owner, WM_COMMAND, \
36 MAKEWPARAM((lphc)->self->wIDmenu, (code)), (lphc)->self->hwndSelf))
37 #define CB_GETEDITTEXTLENGTH( lphc ) \
38 (SendMessageW((lphc)->hWndEdit, WM_GETTEXTLENGTH, 0, 0 ))
40 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
45 static HBITMAP hComboBmp = 0;
46 static UINT CBitHeight, CBitWidth;
49 * Look and feel dependant "constants"
52 #define COMBO_YBORDERGAP 5
53 #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
54 #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
55 #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 )
56 #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 )
58 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
59 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
61 /*********************************************************************
62 * combo class descriptor
64 const struct builtin_class_descr COMBO_builtin_class =
66 "ComboBox", /* name */
67 CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS, /* style */
68 ComboWndProcA, /* procA */
69 ComboWndProcW, /* procW */
70 sizeof(HEADCOMBO *), /* extra */
71 IDC_ARROWA, /* cursor */
76 /***********************************************************************
79 * Load combo button bitmap.
81 static BOOL COMBO_Init()
85 if( hComboBmp ) return TRUE;
86 if( (hDC = CreateCompatibleDC(0)) )
89 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
95 GetObjectW( hComboBmp, sizeof(bm), &bm );
96 CBitHeight = bm.bmHeight;
97 CBitWidth = bm.bmWidth;
99 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
101 hPrevB = SelectObject( hDC, hComboBmp);
102 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
103 InvertRect( hDC, &r );
104 SelectObject( hDC, hPrevB );
113 /***********************************************************************
116 static LRESULT COMBO_NCCreate(WND* wnd, LONG style)
120 if ( wnd && COMBO_Init() &&
121 (lphc = HeapAlloc(GetProcessHeap(), 0, sizeof(HEADCOMBO))) )
123 memset( lphc, 0, sizeof(HEADCOMBO) );
124 *(LPHEADCOMBO*)wnd->wExtra = lphc;
126 /* some braindead apps do try to use scrollbar/border flags */
128 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
129 wnd->dwStyle &= ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
132 * We also have to remove the client edge style to make sure
133 * we don't end-up with a non client area.
135 wnd->dwExStyle &= ~(WS_EX_CLIENTEDGE);
137 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
138 lphc->dwStyle |= CBS_HASSTRINGS;
139 if( !(wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) )
140 lphc->wState |= CBF_NOTIFY;
142 TRACE("[0x%08x], style = %08x\n",
143 (UINT)lphc, lphc->dwStyle );
145 return (LRESULT)(UINT)wnd->hwndSelf;
147 return (LRESULT)FALSE;
150 /***********************************************************************
153 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
158 WND* wnd = lphc->self;
160 TRACE("[%04x]: freeing storage\n", CB_HWND(lphc));
162 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
163 DestroyWindow( lphc->hWndLBox );
165 HeapFree( GetProcessHeap(), 0, lphc );
171 /***********************************************************************
172 * CBGetTextAreaHeight
174 * This method will calculate the height of the text area of the
176 * The height of the text area is set in two ways.
177 * It can be set explicitely through a combobox message or through a
178 * WM_MEASUREITEM callback.
179 * If this is not the case, the height is set to 13 dialog units.
180 * This height was determined through experimentation.
182 static INT CBGetTextAreaHeight(
188 if( lphc->editHeight ) /* explicitly set height */
190 iTextItemHeight = lphc->editHeight;
195 HDC hDC = GetDC(hwnd);
200 hPrevFont = SelectObject( hDC, lphc->hFont );
202 GetTextMetricsW(hDC, &tm);
204 baseUnitY = tm.tmHeight;
207 SelectObject( hDC, hPrevFont );
209 ReleaseDC(hwnd, hDC);
211 iTextItemHeight = ((13 * baseUnitY) / 8);
214 * This "formula" calculates the height of the complete control.
215 * To calculate the height of the text area, we have to remove the
218 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
222 * Check the ownerdraw case if we haven't asked the parent the size
225 if ( CB_OWNERDRAWN(lphc) &&
226 (lphc->wState & CBF_MEASUREITEM) )
228 MEASUREITEMSTRUCT measureItem;
230 INT originalItemHeight = iTextItemHeight;
233 * We use the client rect for the width of the item.
235 GetClientRect(hwnd, &clientRect);
237 lphc->wState &= ~CBF_MEASUREITEM;
240 * Send a first one to measure the size of the text area
242 measureItem.CtlType = ODT_COMBOBOX;
243 measureItem.CtlID = lphc->self->wIDmenu;
244 measureItem.itemID = -1;
245 measureItem.itemWidth = clientRect.right;
246 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
247 measureItem.itemData = 0;
248 SendMessageW(lphc->owner, WM_MEASUREITEM,
249 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
250 iTextItemHeight = 6 + measureItem.itemHeight;
253 * Send a second one in the case of a fixed ownerdraw list to calculate the
254 * size of the list items. (we basically do this on behalf of the listbox)
256 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
258 measureItem.CtlType = ODT_COMBOBOX;
259 measureItem.CtlID = lphc->self->wIDmenu;
260 measureItem.itemID = 0;
261 measureItem.itemWidth = clientRect.right;
262 measureItem.itemHeight = originalItemHeight;
263 measureItem.itemData = 0;
264 SendMessageW(lphc->owner, WM_MEASUREITEM,
265 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
266 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
270 * Keep the size for the next time
272 lphc->editHeight = iTextItemHeight;
275 return iTextItemHeight;
278 /***********************************************************************
281 * The dummy resize is used for listboxes that have a popup to trigger
282 * a re-arranging of the contents of the combobox and the recalculation
283 * of the size of the "real" control window.
285 static void CBForceDummyResize(
291 newComboHeight = CBGetTextAreaHeight(CB_HWND(lphc),lphc) + 2*COMBO_YBORDERSIZE();
293 GetWindowRect(CB_HWND(lphc), &windowRect);
296 * We have to be careful, resizing a combobox also has the meaning that the
297 * dropped rect will be resized. In this case, we want to trigger a resize
298 * to recalculate layout but we don't want to change the dropped rectangle
299 * So, we pass the height of text area of control as the height.
300 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
303 SetWindowPos( CB_HWND(lphc),
306 windowRect.right - windowRect.left,
308 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
311 /***********************************************************************
314 * Set up component coordinates given valid lphc->RectCombo.
316 static void CBCalcPlacement(
324 * Again, start with the client rectangle.
326 GetClientRect(hwnd, lprEdit);
331 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
334 * Chop off the bottom part to fit with the height of the text area.
336 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
339 * The button starts the same vertical position as the text area.
341 CopyRect(lprButton, lprEdit);
344 * If the combobox is "simple" there is no button.
346 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
347 lprButton->left = lprButton->right = lprButton->bottom = 0;
351 * Let's assume the combobox button is the same width as the
353 * size the button horizontally and cut-off the text area.
355 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
356 lprEdit->right = lprButton->left;
360 * In the case of a dropdown, there is an additional spacing between the
361 * text area and the button.
363 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
365 lprEdit->right -= COMBO_EDITBUTTONSPACE();
369 * If we have an edit control, we space it away from the borders slightly.
371 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
373 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
377 * Adjust the size of the listbox popup.
379 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
382 * Use the client rectangle to initialize the listbox rectangle
384 GetClientRect(hwnd, lprLB);
387 * Then, chop-off the top part.
389 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
394 * Make sure the dropped width is as large as the combobox itself.
396 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
398 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
401 * In the case of a dropdown, the popup listbox is offset to the right.
402 * so, we want to make sure it's flush with the right side of the
405 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
406 lprLB->right -= COMBO_EDITBUTTONSPACE();
409 lprLB->right = lprLB->left + lphc->droppedWidth;
412 TRACE("\ttext\t= (%i,%i-%i,%i)\n",
413 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
415 TRACE("\tbutton\t= (%i,%i-%i,%i)\n",
416 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
418 TRACE("\tlbox\t= (%i,%i-%i,%i)\n",
419 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
422 /***********************************************************************
423 * CBGetDroppedControlRect
425 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
427 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
428 of the combo box and the lower right corner of the listbox */
430 GetWindowRect(lphc->self->hwndSelf, lpRect);
432 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
433 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
437 /***********************************************************************
438 * COMBO_WindowPosChanging
440 static LRESULT COMBO_WindowPosChanging(
443 WINDOWPOS* posChanging)
446 * We need to override the WM_WINDOWPOSCHANGING method to handle all
447 * the non-simple comboboxes. The problem is that those controls are
448 * always the same height. We have to make sure they are not resized
451 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
452 ((posChanging->flags & SWP_NOSIZE) == 0) )
456 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
457 2*COMBO_YBORDERSIZE();
460 * Resizing a combobox has another side effect, it resizes the dropped
461 * rectangle as well. However, it does it only if the new height for the
462 * combobox is different from the height it should have. In other words,
463 * if the application resizing the combobox only had the intention to resize
464 * the actual control, for example, to do the layout of a dialog that is
465 * resized, the height of the dropdown is not changed.
467 if (posChanging->cy != newComboHeight)
469 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
471 posChanging->cy = newComboHeight;
478 /***********************************************************************
481 static LRESULT COMBO_Create( LPHEADCOMBO lphc, WND* wnd, HWND hwndParent, LONG style )
483 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
484 static const WCHAR editName[] = {'E','d','i','t',0};
486 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
487 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
490 lphc->owner = hwndParent;
493 * The item height and dropped width are not set when the control
496 lphc->droppedWidth = lphc->editHeight = 0;
499 * The first time we go through, we want to measure the ownerdraw item
501 lphc->wState |= CBF_MEASUREITEM;
503 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
505 if( lphc->owner || !(style & WS_VISIBLE) )
511 * Initialize the dropped rect to the size of the client area of the
512 * control and then, force all the areas of the combobox to be
515 GetClientRect( wnd->hwndSelf, &lphc->droppedRect );
517 CBCalcPlacement(wnd->hwndSelf,
521 &lphc->droppedRect );
524 * Adjust the position of the popup listbox if it's necessary
526 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
528 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
531 * If it's a dropdown, the listbox is offset
533 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
534 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
536 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect);
537 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect.right);
540 /* create listbox popup */
542 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
543 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
545 if( lphc->dwStyle & CBS_SORT )
546 lbeStyle |= LBS_SORT;
547 if( lphc->dwStyle & CBS_HASSTRINGS )
548 lbeStyle |= LBS_HASSTRINGS;
549 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
550 lbeStyle |= LBS_NOINTEGRALHEIGHT;
551 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
552 lbeStyle |= LBS_DISABLENOSCROLL;
554 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
556 lbeStyle |= WS_VISIBLE;
559 * In win 95 look n feel, the listbox in the simple combobox has
560 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
562 if (TWEAK_WineLook > WIN31_LOOK)
564 lbeStyle &= ~WS_BORDER;
565 lbeExStyle |= WS_EX_CLIENTEDGE;
569 lphc->hWndLBox = CreateWindowExW(lbeExStyle,
573 lphc->droppedRect.left,
574 lphc->droppedRect.top,
575 lphc->droppedRect.right - lphc->droppedRect.left,
576 lphc->droppedRect.bottom - lphc->droppedRect.top,
577 lphc->self->hwndSelf,
578 (HMENU)ID_CB_LISTBOX,
579 lphc->self->hInstance,
585 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
588 * In Win95 look, the border fo the edit control is
589 * provided by the combobox
591 if (TWEAK_WineLook == WIN31_LOOK)
592 lbeStyle |= WS_BORDER;
594 if( lphc->wState & CBF_EDIT )
596 if( lphc->dwStyle & CBS_OEMCONVERT )
597 lbeStyle |= ES_OEMCONVERT;
598 if( lphc->dwStyle & CBS_AUTOHSCROLL )
599 lbeStyle |= ES_AUTOHSCROLL;
600 if( lphc->dwStyle & CBS_LOWERCASE )
601 lbeStyle |= ES_LOWERCASE;
602 else if( lphc->dwStyle & CBS_UPPERCASE )
603 lbeStyle |= ES_UPPERCASE;
605 lphc->hWndEdit = CreateWindowExW(0,
609 lphc->textRect.left, lphc->textRect.top,
610 lphc->textRect.right - lphc->textRect.left,
611 lphc->textRect.bottom - lphc->textRect.top,
612 lphc->self->hwndSelf,
614 lphc->self->hInstance,
617 if( !lphc->hWndEdit )
623 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
625 /* Now do the trick with parent */
626 SetParent(lphc->hWndLBox, HWND_DESKTOP);
628 * If the combo is a dropdown, we must resize the control
629 * to fit only the text area and button. To do this,
630 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
631 * will take care of setting the height for us.
633 CBForceDummyResize(lphc);
636 TRACE("init done\n");
637 return wnd->hwndSelf;
639 ERR("edit control failure.\n");
640 } else ERR("listbox failure.\n");
641 } else ERR("no owner for visible combo.\n");
643 /* CreateWindow() will send WM_NCDESTROY to cleanup */
648 /***********************************************************************
651 * Paint combo button (normal, pressed, and disabled states).
653 static void CBPaintButton(
658 if( lphc->wState & CBF_NOREDRAW )
661 if (TWEAK_WineLook == WIN31_LOOK)
667 COLORREF oldTextColor, oldBkColor;
670 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
673 * Draw the button background
678 rectButton.right-rectButton.left,
679 rectButton.bottom-rectButton.top,
682 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
684 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
688 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
692 * Remove the edge of the button from the rectangle
693 * and calculate the position of the bitmap.
695 InflateRect( &rectButton, -2, -2);
697 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
698 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
701 hMemDC = CreateCompatibleDC( hdc );
702 SelectObject( hMemDC, hComboBmp );
703 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
704 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
706 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
707 SetBkColor( hdc, oldBkColor );
708 SetTextColor( hdc, oldTextColor );
710 SelectObject( hdc, hPrevBrush );
714 UINT buttonState = DFCS_SCROLLCOMBOBOX;
716 if (lphc->wState & CBF_BUTTONDOWN)
718 buttonState |= DFCS_PUSHED;
721 if (CB_DISABLED(lphc))
723 buttonState |= DFCS_INACTIVE;
726 DrawFrameControl(hdc,
733 /***********************************************************************
736 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
738 static void CBPaintText(
746 if( lphc->wState & CBF_NOREDRAW ) return;
748 /* follow Windows combobox that sends a bunch of text
749 * inquiries to its listbox while processing WM_PAINT. */
751 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
753 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
754 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
756 SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
757 pText[size] = '\0'; /* just in case */
761 if( !CB_OWNERDRAWN(lphc) )
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 */
773 UINT itemState = ODS_COMBOBOXEDIT;
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;
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(
948 if(lphc->wState & CBF_EDIT)
951 hDC = (hParamDC) ? hParamDC
954 * Retrieve the background brush
956 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
958 FillRect(hDC, &lphc->textRect, hBkgBrush);
961 ReleaseDC(hwnd, hDC);
966 /***********************************************************************
969 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
974 hDC = (hParamDC) ? hParamDC
975 : BeginPaint( lphc->self->hwndSelf, &ps);
978 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
980 HBRUSH hPrevBrush, hBkgBrush;
983 * Retrieve the background brush and select it in the
986 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
988 hPrevBrush = SelectObject( hDC, hBkgBrush );
991 * In non 3.1 look, there is a sunken border on the combobox
993 if (TWEAK_WineLook != WIN31_LOOK)
995 CBPaintBorder(CB_HWND(lphc), lphc, hDC);
998 if( !IsRectEmpty(&lphc->buttonRect) )
1000 CBPaintButton(lphc, hDC, lphc->buttonRect);
1003 if( !(lphc->wState & CBF_EDIT) )
1006 * The text area has a border only in Win 3.1 look.
1008 if (TWEAK_WineLook == WIN31_LOOK)
1010 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
1013 lphc->textRect.left, lphc->textRect.top,
1014 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1016 SelectObject( hDC, hPrevPen );
1019 CBPaintText( lphc, hDC, lphc->textRect);
1023 SelectObject( hDC, hPrevBrush );
1027 EndPaint(lphc->self->hwndSelf, &ps);
1032 /***********************************************************************
1035 * Select listbox entry according to the contents of the edit control.
1037 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1040 LPWSTR pText = NULL;
1043 length = CB_GETEDITTEXTLENGTH( lphc );
1046 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1048 TRACE("\t edit text length %i\n", length );
1052 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1053 else pText[0] = '\0';
1054 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1055 (WPARAM)(-1), (LPARAM)pText );
1056 HeapFree( GetProcessHeap(), 0, pText );
1059 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1061 /* probably superfluous but Windows sends this too */
1062 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1063 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1068 /***********************************************************************
1071 * Copy a listbox entry to the edit control.
1073 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1076 LPWSTR pText = NULL;
1077 static const WCHAR empty_stringW[] = { 0 };
1079 TRACE("\t %i\n", index );
1081 if( index >= 0 ) /* got an entry */
1083 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1086 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1088 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1089 (WPARAM)index, (LPARAM)pText );
1094 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1095 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1096 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1098 if( lphc->wState & CBF_FOCUSED )
1099 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1102 HeapFree( GetProcessHeap(), 0, pText );
1105 /***********************************************************************
1108 * Show listbox popup.
1110 static void CBDropDown( LPHEADCOMBO lphc )
1116 TRACE("[%04x]: drop down\n", CB_HWND(lphc));
1118 CB_NOTIFY( lphc, CBN_DROPDOWN );
1122 lphc->wState |= CBF_DROPPED;
1123 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1125 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1127 /* Update edit only if item is in the list */
1128 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1129 CBUpdateEdit( lphc, lphc->droppedIndex );
1133 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1135 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1136 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1137 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1140 /* now set popup position */
1141 GetWindowRect( lphc->self->hwndSelf, &rect );
1144 * If it's a dropdown, the listbox is offset
1146 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1147 rect.left += COMBO_EDITBUTTONSPACE();
1149 /* if the dropped height is greater than the total height of the dropped
1150 items list, then force the drop down list height to be the total height
1151 of the items in the dropped list */
1153 /* And Remove any extra space (Best Fit) */
1154 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1155 /* if listbox length has been set directly by its handle */
1156 GetWindowRect(lphc->hWndLBox, &r);
1157 if (nDroppedHeight < r.bottom - r.top)
1158 nDroppedHeight = r.bottom - r.top;
1159 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1165 nHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1168 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1169 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1172 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1173 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1174 rect.bottom = rect.top - nDroppedHeight;
1176 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1177 lphc->droppedRect.right - lphc->droppedRect.left,
1179 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1182 if( !(lphc->wState & CBF_NOREDRAW) )
1183 RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE |
1184 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1186 EnableWindow( lphc->hWndLBox, TRUE );
1187 if (GetCapture() != lphc->self->hwndSelf)
1188 SetCapture(lphc->hWndLBox);
1191 /***********************************************************************
1194 * Hide listbox popup.
1196 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1198 HWND hWnd = lphc->self->hwndSelf;
1200 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1202 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1205 TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok );
1207 if( lphc->wState & CBF_DROPPED )
1211 lphc->wState &= ~CBF_DROPPED;
1212 ShowWindow( lphc->hWndLBox, SW_HIDE );
1213 if(GetCapture() == lphc->hWndLBox)
1218 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1220 rect = lphc->buttonRect;
1231 rect = lphc->textRect;
1236 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1237 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1238 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1239 CB_NOTIFY( lphc, CBN_CLOSEUP );
1244 /***********************************************************************
1247 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1249 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1251 if( lphc->wState & CBF_DROPPED )
1253 CBRollUp( lphc, ok, bRedrawButton );
1261 /***********************************************************************
1264 static void CBRepaintButton( LPHEADCOMBO lphc )
1266 InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE);
1267 UpdateWindow(CB_HWND(lphc));
1270 /***********************************************************************
1273 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1275 if( !(lphc->wState & CBF_FOCUSED) )
1277 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1278 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1280 lphc->wState |= CBF_FOCUSED;
1282 if( !(lphc->wState & CBF_EDIT) )
1283 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1285 CB_NOTIFY( lphc, CBN_SETFOCUS );
1286 lphc->wState |= CBF_FOCUSED;
1290 /***********************************************************************
1293 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1295 HWND hWnd = lphc->self->hwndSelf;
1297 if( lphc->wState & CBF_FOCUSED )
1299 CBRollUp( lphc, FALSE, TRUE );
1300 if( IsWindow( hWnd ) )
1302 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1303 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1305 lphc->wState &= ~CBF_FOCUSED;
1308 if( !(lphc->wState & CBF_EDIT) )
1309 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1311 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1316 /***********************************************************************
1319 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1321 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1323 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1325 switch( HIWORD(wParam) >> 8 )
1327 case (EN_SETFOCUS >> 8):
1329 TRACE("[%04x]: edit [%04x] got focus\n",
1330 CB_HWND(lphc), lphc->hWndEdit );
1332 COMBO_SetFocus( lphc );
1335 case (EN_KILLFOCUS >> 8):
1337 TRACE("[%04x]: edit [%04x] lost focus\n",
1338 CB_HWND(lphc), lphc->hWndEdit );
1340 /* NOTE: it seems that Windows' edit control sends an
1341 * undocumented message WM_USER + 0x1B instead of this
1342 * notification (only when it happens to be a part of
1343 * the combo). ?? - AK.
1346 COMBO_KillFocus( lphc );
1350 case (EN_CHANGE >> 8):
1352 * In some circumstances (when the selection of the combobox
1353 * is changed for example) we don't wans the EN_CHANGE notification
1354 * to be forwarded to the parent of the combobox. This code
1355 * checks a flag that is set in these occasions and ignores the
1358 if (lphc->wState & CBF_NOLBSELECT)
1360 lphc->wState &= ~CBF_NOLBSELECT;
1364 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1367 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1368 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1371 case (EN_UPDATE >> 8):
1372 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1373 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1376 case (EN_ERRSPACE >> 8):
1377 CB_NOTIFY( lphc, CBN_ERRSPACE );
1380 else if( lphc->hWndLBox == hWnd )
1382 switch( HIWORD(wParam) )
1385 CB_NOTIFY( lphc, CBN_ERRSPACE );
1389 CB_NOTIFY( lphc, CBN_DBLCLK );
1395 TRACE("[%04x]: lbox selection change [%04x]\n",
1396 CB_HWND(lphc), lphc->wState );
1398 if( HIWORD(wParam) == LBN_SELCHANGE)
1400 if( lphc->wState & CBF_EDIT )
1402 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1403 lphc->wState |= CBF_NOLBSELECT;
1404 CBUpdateEdit( lphc, index );
1405 /* select text in edit, as Windows does */
1406 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1409 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1412 /* do not roll up if selection is being tracked
1413 * by arrowkeys in the dropdown listbox */
1415 if( (lphc->dwStyle & CBS_SIMPLE) ||
1416 ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1418 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1420 else lphc->wState &= ~CBF_NOROLLUP;
1422 CB_NOTIFY( lphc, CBN_SELCHANGE );
1428 /* nothing to do here since ComboLBox always resets the focus to its
1429 * combo/edit counterpart */
1436 /***********************************************************************
1439 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1441 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1443 HWND hWnd = lphc->self->hwndSelf;
1445 TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg );
1447 #define lpIS ((LPDELETEITEMSTRUCT)lParam)
1449 /* two first items are the same in all 4 structs */
1450 lpIS->CtlType = ODT_COMBOBOX;
1451 lpIS->CtlID = lphc->self->wIDmenu;
1453 switch( msg ) /* patch window handle */
1456 lpIS->hwndItem = hWnd;
1460 #define lpIS ((LPDRAWITEMSTRUCT)lParam)
1461 lpIS->hwndItem = hWnd;
1464 case WM_COMPAREITEM:
1465 #define lpIS ((LPCOMPAREITEMSTRUCT)lParam)
1466 lpIS->hwndItem = hWnd;
1471 return SendMessageW(lphc->owner, msg, lphc->self->wIDmenu, lParam);
1474 /***********************************************************************
1477 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, INT N, LPARAM lParam, BOOL unicode)
1479 if( lphc->wState & CBF_EDIT )
1480 return unicode ? SendMessageW(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam) :
1481 SendMessageA(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam);
1483 /* get it from the listbox */
1485 if( lphc->hWndLBox )
1487 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1491 INT length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN,
1496 LPWSTR lpBuffer, lpText = (LPWSTR)lParam;
1498 /* 'length' is without the terminating character */
1500 lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1506 n = SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1508 /* truncate if buffer is too short */
1514 strncpyW(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1515 lpText[N - 1] = '\0';
1518 HeapFree( GetProcessHeap(), 0, lpBuffer );
1523 LPSTR lpBuffer, lpText = (LPSTR)lParam;
1525 /* 'length' is without the terminating character */
1527 lpBuffer = HeapAlloc(GetProcessHeap(), 0, length + 1);
1533 n = SendMessageA(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1535 /* truncate if buffer is too short */
1541 strncpy(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1542 lpText[N - 1] = '\0';
1545 HeapFree( GetProcessHeap(), 0, lpBuffer );
1555 /***********************************************************************
1558 * This function sets window positions according to the updated
1559 * component placement struct.
1561 static void CBResetPos(
1567 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1569 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1570 * sizing messages */
1572 if( lphc->wState & CBF_EDIT )
1573 SetWindowPos( lphc->hWndEdit, 0,
1574 rectEdit->left, rectEdit->top,
1575 rectEdit->right - rectEdit->left,
1576 rectEdit->bottom - rectEdit->top,
1577 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1579 SetWindowPos( lphc->hWndLBox, 0,
1580 rectLB->left, rectLB->top,
1581 rectLB->right - rectLB->left,
1582 rectLB->bottom - rectLB->top,
1583 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1587 if( lphc->wState & CBF_DROPPED )
1589 lphc->wState &= ~CBF_DROPPED;
1590 ShowWindow( lphc->hWndLBox, SW_HIDE );
1593 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1594 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
1595 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1600 /***********************************************************************
1603 static void COMBO_Size( LPHEADCOMBO lphc )
1605 CBCalcPlacement(lphc->self->hwndSelf,
1609 &lphc->droppedRect);
1611 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1615 /***********************************************************************
1618 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1623 lphc->hFont = hFont;
1626 * Propagate to owned windows.
1628 if( lphc->wState & CBF_EDIT )
1629 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1630 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1633 * Redo the layout of the control.
1635 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1637 CBCalcPlacement(lphc->self->hwndSelf,
1641 &lphc->droppedRect);
1643 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1647 CBForceDummyResize(lphc);
1652 /***********************************************************************
1653 * COMBO_SetItemHeight
1655 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1657 LRESULT lRet = CB_ERR;
1659 if( index == -1 ) /* set text field height */
1661 if( height < 32768 )
1663 lphc->editHeight = height;
1666 * Redo the layout of the control.
1668 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1670 CBCalcPlacement(lphc->self->hwndSelf,
1674 &lphc->droppedRect);
1676 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1680 CBForceDummyResize(lphc);
1686 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1687 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1688 (WPARAM)index, (LPARAM)height );
1692 /***********************************************************************
1693 * COMBO_SelectString
1695 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1697 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1698 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1701 if( lphc->wState & CBF_EDIT )
1702 CBUpdateEdit( lphc, index );
1705 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1708 return (LRESULT)index;
1711 /***********************************************************************
1714 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1718 HWND hWnd = lphc->self->hwndSelf;
1720 pt.x = LOWORD(lParam);
1721 pt.y = HIWORD(lParam);
1722 bButton = PtInRect(&lphc->buttonRect, pt);
1724 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1725 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1727 lphc->wState |= CBF_BUTTONDOWN;
1728 if( lphc->wState & CBF_DROPPED )
1730 /* got a click to cancel selection */
1732 lphc->wState &= ~CBF_BUTTONDOWN;
1733 CBRollUp( lphc, TRUE, FALSE );
1734 if( !IsWindow( hWnd ) ) return;
1736 if( lphc->wState & CBF_CAPTURE )
1738 lphc->wState &= ~CBF_CAPTURE;
1744 /* drop down the listbox and start tracking */
1746 lphc->wState |= CBF_CAPTURE;
1750 if( bButton ) CBRepaintButton( lphc );
1754 /***********************************************************************
1757 * Release capture and stop tracking if needed.
1759 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1761 if( lphc->wState & CBF_CAPTURE )
1763 lphc->wState &= ~CBF_CAPTURE;
1764 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1766 INT index = CBUpdateLBox( lphc, TRUE );
1767 /* Update edit only if item is in the list */
1770 lphc->wState |= CBF_NOLBSELECT;
1771 CBUpdateEdit( lphc, index );
1772 lphc->wState &= ~CBF_NOLBSELECT;
1776 SetCapture(lphc->hWndLBox);
1779 if( lphc->wState & CBF_BUTTONDOWN )
1781 lphc->wState &= ~CBF_BUTTONDOWN;
1782 CBRepaintButton( lphc );
1786 /***********************************************************************
1789 * Two things to do - track combo button and release capture when
1790 * pointer goes into the listbox.
1792 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1797 pt.x = LOWORD(lParam);
1798 pt.y = HIWORD(lParam);
1800 if( lphc->wState & CBF_BUTTONDOWN )
1804 bButton = PtInRect(&lphc->buttonRect, pt);
1808 lphc->wState &= ~CBF_BUTTONDOWN;
1809 CBRepaintButton( lphc );
1813 GetClientRect( lphc->hWndLBox, &lbRect );
1814 MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 );
1815 if( PtInRect(&lbRect, pt) )
1817 lphc->wState &= ~CBF_CAPTURE;
1819 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1821 /* hand over pointer tracking */
1822 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1827 /***********************************************************************
1828 * ComboWndProc_locked
1830 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1832 static LRESULT ComboWndProc_locked( WND* pWnd, UINT message,
1833 WPARAM wParam, LPARAM lParam, BOOL unicode )
1836 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1837 HWND hwnd = pWnd->hwndSelf;
1839 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1840 pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam );
1842 if( lphc || message == WM_NCCREATE )
1846 /* System messages */
1850 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1851 ((LPCREATESTRUCTA)lParam)->style;
1852 return COMBO_NCCreate(pWnd, style);
1855 COMBO_NCDestroy(lphc);
1856 break;/* -> DefWindowProc */
1864 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1865 style = ((LPCREATESTRUCTW)lParam)->style;
1869 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1870 style = ((LPCREATESTRUCTA)lParam)->style;
1872 return COMBO_Create(lphc, pWnd, hwndParent, style);
1875 case WM_PRINTCLIENT:
1876 if (lParam & PRF_ERASEBKGND)
1877 COMBO_EraseBackground(hwnd, lphc, wParam);
1881 /* wParam may contain a valid HDC! */
1882 return COMBO_Paint(lphc, wParam);
1884 return COMBO_EraseBackground(hwnd, lphc, wParam);
1887 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1888 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1890 int vk = (int)((LPMSG)lParam)->wParam;
1892 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1893 result |= DLGC_WANTMESSAGE;
1897 case WM_WINDOWPOSCHANGING:
1898 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1899 case WM_WINDOWPOSCHANGED:
1900 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1901 * In that case, the Combobox itself will not be resized, so we won't
1902 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1907 if( lphc->hWndLBox &&
1908 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1911 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1914 return (LRESULT)lphc->hFont;
1916 if( lphc->wState & CBF_EDIT )
1917 SetFocus( lphc->hWndEdit );
1919 COMBO_SetFocus( lphc );
1922 #define hwndFocus ((HWND16)wParam)
1924 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1925 COMBO_KillFocus( lphc );
1929 return COMBO_Command( lphc, wParam, (HWND)lParam );
1931 return COMBO_GetText( lphc, (INT)wParam, lParam, unicode );
1933 case WM_GETTEXTLENGTH:
1935 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1937 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1938 if (j == -1) return 0;
1939 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1941 else if( lphc->wState & CBF_EDIT )
1944 lphc->wState |= CBF_NOEDITNOTIFY;
1945 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1946 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1947 lphc->wState &= ~CBF_NOEDITNOTIFY;
1954 if( lphc->wState & CBF_EDIT )
1956 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1957 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1963 case WM_COMPAREITEM:
1964 case WM_MEASUREITEM:
1965 return COMBO_ItemOp(lphc, message, lParam);
1967 if( lphc->wState & CBF_EDIT )
1968 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1969 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1971 /* Force the control to repaint when the enabled state changes. */
1972 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
1976 lphc->wState &= ~CBF_NOREDRAW;
1978 lphc->wState |= CBF_NOREDRAW;
1980 if( lphc->wState & CBF_EDIT )
1981 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1982 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1985 if( KEYDATA_ALT & HIWORD(lParam) )
1986 if( wParam == VK_UP || wParam == VK_DOWN )
1987 COMBO_FlipListbox( lphc, FALSE, FALSE );
1995 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1996 (lphc->wState & CBF_DROPPED))
1998 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2002 if( lphc->wState & CBF_EDIT )
2003 hwndTarget = lphc->hWndEdit;
2005 hwndTarget = lphc->hWndLBox;
2007 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2008 SendMessageA(hwndTarget, message, wParam, lParam);
2010 case WM_LBUTTONDOWN:
2011 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf );
2012 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2015 COMBO_LButtonUp( lphc );
2018 if( lphc->wState & CBF_CAPTURE )
2019 COMBO_MouseMove( lphc, wParam, lParam );
2023 if (wParam & (MK_SHIFT | MK_CONTROL))
2024 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2025 DefWindowProcA(hwnd, message, wParam, lParam);
2026 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2027 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2030 /* Combo messages */
2032 case CB_ADDSTRING16:
2033 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2036 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2037 SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2038 case CB_INSERTSTRING16:
2039 wParam = (INT)(INT16)wParam;
2040 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2042 case CB_INSERTSTRING:
2043 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2044 SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2045 case CB_DELETESTRING16:
2046 case CB_DELETESTRING:
2047 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2048 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2049 case CB_SELECTSTRING16:
2050 wParam = (INT)(INT16)wParam;
2051 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2053 case CB_SELECTSTRING:
2054 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2055 case CB_FINDSTRING16:
2056 wParam = (INT)(INT16)wParam;
2057 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2060 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2061 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2062 case CB_FINDSTRINGEXACT16:
2063 wParam = (INT)(INT16)wParam;
2064 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2066 case CB_FINDSTRINGEXACT:
2067 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2068 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2069 case CB_SETITEMHEIGHT16:
2070 wParam = (INT)(INT16)wParam; /* signed integer */
2072 case CB_SETITEMHEIGHT:
2073 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2074 case CB_GETITEMHEIGHT16:
2075 wParam = (INT)(INT16)wParam;
2077 case CB_GETITEMHEIGHT:
2078 if( (INT)wParam >= 0 ) /* listbox item */
2079 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2080 return CBGetTextAreaHeight(hwnd, lphc);
2081 case CB_RESETCONTENT16:
2082 case CB_RESETCONTENT:
2083 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2084 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2086 static const WCHAR empty_stringW[] = { 0 };
2087 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2090 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
2092 case CB_INITSTORAGE:
2093 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2094 case CB_GETHORIZONTALEXTENT:
2095 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2096 case CB_SETHORIZONTALEXTENT:
2097 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2098 case CB_GETTOPINDEX:
2099 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2101 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2103 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2104 case CB_GETDROPPEDWIDTH:
2105 if( lphc->droppedWidth )
2106 return lphc->droppedWidth;
2107 return lphc->droppedRect.right - lphc->droppedRect.left;
2108 case CB_SETDROPPEDWIDTH:
2109 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2110 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2112 case CB_GETDROPPEDCONTROLRECT16:
2113 lParam = (LPARAM)MapSL(lParam);
2117 CBGetDroppedControlRect( lphc, &r );
2118 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2121 case CB_GETDROPPEDCONTROLRECT:
2122 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2124 case CB_GETDROPPEDSTATE16:
2125 case CB_GETDROPPEDSTATE:
2126 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2128 lParam = (LPARAM)MapSL(lParam);
2132 if(message == CB_DIR) message = LB_DIR;
2133 return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) :
2134 SendMessageA(lphc->hWndLBox, message, wParam, lParam);
2136 case CB_SHOWDROPDOWN16:
2137 case CB_SHOWDROPDOWN:
2138 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2142 if( !(lphc->wState & CBF_DROPPED) )
2146 if( lphc->wState & CBF_DROPPED )
2147 CBRollUp( lphc, FALSE, TRUE );
2152 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2153 case CB_GETCURSEL16:
2155 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2156 case CB_SETCURSEL16:
2157 wParam = (INT)(INT16)wParam;
2160 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2162 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2164 /* no LBN_SELCHANGE in this case, update manually */
2165 if( lphc->wState & CBF_EDIT )
2166 CBUpdateEdit( lphc, (INT)wParam );
2168 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
2169 lphc->wState &= ~CBF_SELCHANGE;
2171 case CB_GETLBTEXT16:
2172 wParam = (INT)(INT16)wParam;
2173 lParam = (LPARAM)MapSL(lParam);
2176 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2177 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2178 case CB_GETLBTEXTLEN16:
2179 wParam = (INT)(INT16)wParam;
2181 case CB_GETLBTEXTLEN:
2182 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2183 case CB_GETITEMDATA16:
2184 wParam = (INT)(INT16)wParam;
2186 case CB_GETITEMDATA:
2187 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2188 case CB_SETITEMDATA16:
2189 wParam = (INT)(INT16)wParam;
2191 case CB_SETITEMDATA:
2192 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2193 case CB_GETEDITSEL16:
2194 wParam = lParam = 0; /* just in case */
2197 /* Edit checks passed parameters itself */
2198 if( lphc->wState & CBF_EDIT )
2199 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2201 case CB_SETEDITSEL16:
2203 if( lphc->wState & CBF_EDIT )
2204 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2205 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2207 case CB_SETEXTENDEDUI16:
2208 case CB_SETEXTENDEDUI:
2209 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2212 lphc->wState |= CBF_EUI;
2213 else lphc->wState &= ~CBF_EUI;
2215 case CB_GETEXTENDEDUI16:
2216 case CB_GETEXTENDEDUI:
2217 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2220 if (message >= WM_USER)
2221 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2222 message - WM_USER, wParam, lParam );
2225 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2226 DefWindowProcA(hwnd, message, wParam, lParam);
2231 /***********************************************************************
2234 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2237 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2239 WND* pWnd = WIN_FindWndPtr(hwnd);
2240 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, FALSE);
2242 WIN_ReleaseWndPtr(pWnd);
2246 /***********************************************************************
2249 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2251 WND* pWnd = WIN_FindWndPtr(hwnd);
2252 LRESULT retvalue = ComboWndProc_locked(pWnd, message, wParam, lParam, TRUE);
2254 WIN_ReleaseWndPtr(pWnd);