4 * Copyright 1997 Alex Korobka
6 * FIXME: roll up in Netscape 3.01.
15 #include "wine/winuser16.h"
16 #include "wine/unicode.h"
20 #include "debugtools.h"
22 DEFAULT_DEBUG_CHANNEL(combo);
24 /* bits in the dwKeyData */
25 #define KEYDATA_ALT 0x2000
26 #define KEYDATA_PREVSTATE 0x4000
29 * Additional combo box definitions
32 #define CB_NOTIFY( lphc, code ) \
33 (SendMessageW((lphc)->owner, WM_COMMAND, \
34 MAKEWPARAM(GetWindowLongA((lphc)->self,GWL_ID), (code)), (lphc)->self))
36 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
37 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
38 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
39 #define CB_HWND( lphc ) ((lphc)->self)
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(HWND hwnd, LONG style)
121 if (COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEADCOMBO))) )
124 SetWindowLongA( hwnd, 0, (LONG)lphc );
126 /* some braindead apps do try to use scrollbar/border flags */
128 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
129 SetWindowLongA( hwnd, GWL_STYLE, style & ~(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 SetWindowLongA( hwnd, GWL_EXSTYLE,
136 GetWindowLongA( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
138 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
139 lphc->dwStyle |= CBS_HASSTRINGS;
140 if( !(GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
141 lphc->wState |= CBF_NOTIFY;
143 TRACE("[0x%p], style = %08x\n", lphc, lphc->dwStyle );
149 /***********************************************************************
152 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
157 TRACE("[%04x]: freeing storage\n", lphc->self);
159 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
160 DestroyWindow( lphc->hWndLBox );
162 SetWindowLongA( lphc->self, 0, 0 );
163 HeapFree( GetProcessHeap(), 0, lphc );
168 /***********************************************************************
169 * CBGetTextAreaHeight
171 * This method will calculate the height of the text area of the
173 * The height of the text area is set in two ways.
174 * It can be set explicitly through a combobox message or through a
175 * WM_MEASUREITEM callback.
176 * If this is not the case, the height is set to 13 dialog units.
177 * This height was determined through experimentation.
179 static INT CBGetTextAreaHeight(
185 if( lphc->editHeight ) /* explicitly set height */
187 iTextItemHeight = lphc->editHeight;
192 HDC hDC = GetDC(hwnd);
197 hPrevFont = SelectObject( hDC, lphc->hFont );
199 GetTextMetricsW(hDC, &tm);
201 baseUnitY = tm.tmHeight;
204 SelectObject( hDC, hPrevFont );
206 ReleaseDC(hwnd, hDC);
208 iTextItemHeight = ((13 * baseUnitY) / 8);
211 * This "formula" calculates the height of the complete control.
212 * To calculate the height of the text area, we have to remove the
215 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
219 * Check the ownerdraw case if we haven't asked the parent the size
222 if ( CB_OWNERDRAWN(lphc) &&
223 (lphc->wState & CBF_MEASUREITEM) )
225 MEASUREITEMSTRUCT measureItem;
227 INT originalItemHeight = iTextItemHeight;
228 UINT id = GetWindowLongA( lphc->self, GWL_ID );
231 * We use the client rect for the width of the item.
233 GetClientRect(hwnd, &clientRect);
235 lphc->wState &= ~CBF_MEASUREITEM;
238 * Send a first one to measure the size of the text area
240 measureItem.CtlType = ODT_COMBOBOX;
241 measureItem.CtlID = id;
242 measureItem.itemID = -1;
243 measureItem.itemWidth = clientRect.right;
244 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
245 measureItem.itemData = 0;
246 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
247 iTextItemHeight = 6 + measureItem.itemHeight;
250 * Send a second one in the case of a fixed ownerdraw list to calculate the
251 * size of the list items. (we basically do this on behalf of the listbox)
253 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
255 measureItem.CtlType = ODT_COMBOBOX;
256 measureItem.CtlID = id;
257 measureItem.itemID = 0;
258 measureItem.itemWidth = clientRect.right;
259 measureItem.itemHeight = originalItemHeight;
260 measureItem.itemData = 0;
261 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
262 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
266 * Keep the size for the next time
268 lphc->editHeight = iTextItemHeight;
271 return iTextItemHeight;
274 /***********************************************************************
277 * The dummy resize is used for listboxes that have a popup to trigger
278 * a re-arranging of the contents of the combobox and the recalculation
279 * of the size of the "real" control window.
281 static void CBForceDummyResize(
287 newComboHeight = CBGetTextAreaHeight(lphc->self,lphc) + 2*COMBO_YBORDERSIZE();
289 GetWindowRect(lphc->self, &windowRect);
292 * We have to be careful, resizing a combobox also has the meaning that the
293 * dropped rect will be resized. In this case, we want to trigger a resize
294 * to recalculate layout but we don't want to change the dropped rectangle
295 * So, we pass the height of text area of control as the height.
296 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
299 SetWindowPos( lphc->self,
302 windowRect.right - windowRect.left,
304 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
307 /***********************************************************************
310 * Set up component coordinates given valid lphc->RectCombo.
312 static void CBCalcPlacement(
320 * Again, start with the client rectangle.
322 GetClientRect(hwnd, lprEdit);
327 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
330 * Chop off the bottom part to fit with the height of the text area.
332 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
335 * The button starts the same vertical position as the text area.
337 CopyRect(lprButton, lprEdit);
340 * If the combobox is "simple" there is no button.
342 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
343 lprButton->left = lprButton->right = lprButton->bottom = 0;
347 * Let's assume the combobox button is the same width as the
349 * size the button horizontally and cut-off the text area.
351 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
352 lprEdit->right = lprButton->left;
356 * In the case of a dropdown, there is an additional spacing between the
357 * text area and the button.
359 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
361 lprEdit->right -= COMBO_EDITBUTTONSPACE();
365 * If we have an edit control, we space it away from the borders slightly.
367 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
369 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
373 * Adjust the size of the listbox popup.
375 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
378 * Use the client rectangle to initialize the listbox rectangle
380 GetClientRect(hwnd, lprLB);
383 * Then, chop-off the top part.
385 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
390 * Make sure the dropped width is as large as the combobox itself.
392 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
394 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
397 * In the case of a dropdown, the popup listbox is offset to the right.
398 * so, we want to make sure it's flush with the right side of the
401 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
402 lprLB->right -= COMBO_EDITBUTTONSPACE();
405 lprLB->right = lprLB->left + lphc->droppedWidth;
408 TRACE("\ttext\t= (%i,%i-%i,%i)\n",
409 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
411 TRACE("\tbutton\t= (%i,%i-%i,%i)\n",
412 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
414 TRACE("\tlbox\t= (%i,%i-%i,%i)\n",
415 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
418 /***********************************************************************
419 * CBGetDroppedControlRect
421 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
423 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
424 of the combo box and the lower right corner of the listbox */
426 GetWindowRect(lphc->self, lpRect);
428 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
429 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
433 /***********************************************************************
434 * COMBO_WindowPosChanging
436 static LRESULT COMBO_WindowPosChanging(
439 WINDOWPOS* posChanging)
442 * We need to override the WM_WINDOWPOSCHANGING method to handle all
443 * the non-simple comboboxes. The problem is that those controls are
444 * always the same height. We have to make sure they are not resized
447 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
448 ((posChanging->flags & SWP_NOSIZE) == 0) )
452 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
453 2*COMBO_YBORDERSIZE();
456 * Resizing a combobox has another side effect, it resizes the dropped
457 * rectangle as well. However, it does it only if the new height for the
458 * combobox is different from the height it should have. In other words,
459 * if the application resizing the combobox only had the intention to resize
460 * the actual control, for example, to do the layout of a dialog that is
461 * resized, the height of the dropdown is not changed.
463 if (posChanging->cy != newComboHeight)
465 TRACE("posChanging->cy=%d, newComboHeight=%d, oldbot=%d, oldtop=%d\n",
466 posChanging->cy, newComboHeight, lphc->droppedRect.bottom,
467 lphc->droppedRect.top);
468 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
470 posChanging->cy = newComboHeight;
477 /***********************************************************************
480 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style )
482 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
483 static const WCHAR editName[] = {'E','d','i','t',0};
485 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
486 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
488 lphc->owner = hwndParent;
491 * The item height and dropped width are not set when the control
494 lphc->droppedWidth = lphc->editHeight = 0;
497 * The first time we go through, we want to measure the ownerdraw item
499 lphc->wState |= CBF_MEASUREITEM;
501 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
503 if( lphc->owner || !(style & WS_VISIBLE) )
509 * Initialize the dropped rect to the size of the client area of the
510 * control and then, force all the areas of the combobox to be
513 GetClientRect( hwnd, &lphc->droppedRect );
514 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
517 * Adjust the position of the popup listbox if it's necessary
519 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
521 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
524 * If it's a dropdown, the listbox is offset
526 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
527 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
529 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect);
530 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect.right);
533 /* create listbox popup */
535 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
536 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
538 if( lphc->dwStyle & CBS_SORT )
539 lbeStyle |= LBS_SORT;
540 if( lphc->dwStyle & CBS_HASSTRINGS )
541 lbeStyle |= LBS_HASSTRINGS;
542 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
543 lbeStyle |= LBS_NOINTEGRALHEIGHT;
544 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
545 lbeStyle |= LBS_DISABLENOSCROLL;
547 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
549 lbeStyle |= WS_VISIBLE;
552 * In win 95 look n feel, the listbox in the simple combobox has
553 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
555 if (TWEAK_WineLook > WIN31_LOOK)
557 lbeStyle &= ~WS_BORDER;
558 lbeExStyle |= WS_EX_CLIENTEDGE;
562 lphc->hWndLBox = CreateWindowExW(lbeExStyle,
566 lphc->droppedRect.left,
567 lphc->droppedRect.top,
568 lphc->droppedRect.right - lphc->droppedRect.left,
569 lphc->droppedRect.bottom - lphc->droppedRect.top,
570 hwnd, (HMENU)ID_CB_LISTBOX,
571 GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc );
576 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
579 * In Win95 look, the border fo the edit control is
580 * provided by the combobox
582 if (TWEAK_WineLook == WIN31_LOOK)
583 lbeStyle |= WS_BORDER;
585 if( lphc->wState & CBF_EDIT )
587 if( lphc->dwStyle & CBS_OEMCONVERT )
588 lbeStyle |= ES_OEMCONVERT;
589 if( lphc->dwStyle & CBS_AUTOHSCROLL )
590 lbeStyle |= ES_AUTOHSCROLL;
591 if( lphc->dwStyle & CBS_LOWERCASE )
592 lbeStyle |= ES_LOWERCASE;
593 else if( lphc->dwStyle & CBS_UPPERCASE )
594 lbeStyle |= ES_UPPERCASE;
596 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
598 lphc->hWndEdit = CreateWindowExW(0,
602 lphc->textRect.left, lphc->textRect.top,
603 lphc->textRect.right - lphc->textRect.left,
604 lphc->textRect.bottom - lphc->textRect.top,
605 hwnd, (HMENU)ID_CB_EDIT,
606 GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL );
608 if( !lphc->hWndEdit )
614 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
616 /* Now do the trick with parent */
617 SetParent(lphc->hWndLBox, HWND_DESKTOP);
619 * If the combo is a dropdown, we must resize the control
620 * to fit only the text area and button. To do this,
621 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
622 * will take care of setting the height for us.
624 CBForceDummyResize(lphc);
627 TRACE("init done\n");
630 ERR("edit control failure.\n");
631 } else ERR("listbox failure.\n");
632 } else ERR("no owner for visible combo.\n");
634 /* CreateWindow() will send WM_NCDESTROY to cleanup */
639 /***********************************************************************
642 * Paint combo button (normal, pressed, and disabled states).
644 static void CBPaintButton(
649 if( lphc->wState & CBF_NOREDRAW )
652 if (TWEAK_WineLook == WIN31_LOOK)
658 COLORREF oldTextColor, oldBkColor;
661 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
664 * Draw the button background
669 rectButton.right-rectButton.left,
670 rectButton.bottom-rectButton.top,
673 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
675 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
679 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
683 * Remove the edge of the button from the rectangle
684 * and calculate the position of the bitmap.
686 InflateRect( &rectButton, -2, -2);
688 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
689 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
692 hMemDC = CreateCompatibleDC( hdc );
693 SelectObject( hMemDC, hComboBmp );
694 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
695 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
697 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
698 SetBkColor( hdc, oldBkColor );
699 SetTextColor( hdc, oldTextColor );
701 SelectObject( hdc, hPrevBrush );
705 UINT buttonState = DFCS_SCROLLCOMBOBOX;
707 if (lphc->wState & CBF_BUTTONDOWN)
709 buttonState |= DFCS_PUSHED;
712 if (CB_DISABLED(lphc))
714 buttonState |= DFCS_INACTIVE;
717 DrawFrameControl(hdc,
724 /***********************************************************************
727 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
729 static void CBPaintText(
737 if( lphc->wState & CBF_NOREDRAW ) return;
741 /* follow Windows combobox that sends a bunch of text
742 * inquiries to its listbox while processing WM_PAINT. */
744 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
746 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
747 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
749 SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
750 pText[size] = '\0'; /* just in case */
754 if( !CB_OWNERDRAWN(lphc) )
757 if( lphc->wState & CBF_EDIT )
759 static const WCHAR empty_stringW[] = { 0 };
760 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
761 if( lphc->wState & CBF_FOCUSED )
762 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
764 else /* paint text field ourselves */
766 UINT itemState = ODS_COMBOBOXEDIT;
767 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
770 * Give ourselves some space.
772 InflateRect( &rectEdit, -1, -1 );
774 if( CB_OWNERDRAWN(lphc) )
778 UINT ctlid = GetWindowLongA( lphc->self, GWL_ID );
780 /* setup state for DRAWITEM message. Owner will highlight */
781 if ( (lphc->wState & CBF_FOCUSED) &&
782 !(lphc->wState & CBF_DROPPED) )
783 itemState |= ODS_SELECTED | ODS_FOCUS;
786 * Save the current clip region.
787 * To retrieve the clip region, we need to create one "dummy"
790 clipRegion = CreateRectRgnIndirect(&rectEdit);
792 if (GetClipRgn(hdc, clipRegion)!=1)
794 DeleteObject(clipRegion);
795 clipRegion=(HRGN)NULL;
798 if (!IsWindowEnabled(lphc->self) & WS_DISABLED) itemState |= ODS_DISABLED;
800 dis.CtlType = ODT_COMBOBOX;
802 dis.hwndItem = lphc->self;
803 dis.itemAction = ODA_DRAWENTIRE;
805 dis.itemState = itemState;
807 dis.rcItem = rectEdit;
808 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA,
812 * Clip the DC and have the parent draw the item.
814 IntersectClipRect(hdc,
815 rectEdit.left, rectEdit.top,
816 rectEdit.right, rectEdit.bottom);
818 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
821 * Reset the clipping region.
823 SelectClipRgn(hdc, clipRegion);
827 static const WCHAR empty_stringW[] = { 0 };
829 if ( (lphc->wState & CBF_FOCUSED) &&
830 !(lphc->wState & CBF_DROPPED) ) {
833 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
834 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
835 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
841 ETO_OPAQUE | ETO_CLIPPED,
843 pText ? pText : empty_stringW , size, NULL );
845 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
846 DrawFocusRect( hdc, &rectEdit );
850 SelectObject(hdc, hPrevFont );
853 HeapFree( GetProcessHeap(), 0, pText );
856 /***********************************************************************
859 static void CBPaintBorder(
866 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
868 GetClientRect(hwnd, &clientRect);
872 CopyRect(&clientRect, &lphc->textRect);
874 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
875 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
878 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
881 /***********************************************************************
882 * COMBO_PrepareColors
884 * This method will sent the appropriate WM_CTLCOLOR message to
885 * prepare and setup the colors for the combo's DC.
887 * It also returns the brush to use for the background.
889 static HBRUSH COMBO_PrepareColors(
896 * Get the background brush for this control.
898 if (CB_DISABLED(lphc))
900 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORSTATIC, hDC, lphc->self );
903 * We have to change the text color since WM_CTLCOLORSTATIC will
904 * set it to the "enabled" color. This is the same behavior as the
907 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
911 if (lphc->wState & CBF_EDIT)
913 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLOREDIT, hDC, lphc->self );
917 hBkgBrush = SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX, hDC, lphc->self );
925 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
930 /***********************************************************************
931 * COMBO_EraseBackground
933 static LRESULT COMBO_EraseBackground(
941 if(lphc->wState & CBF_EDIT)
944 hDC = (hParamDC) ? hParamDC
947 * Retrieve the background brush
949 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
951 FillRect(hDC, &lphc->textRect, hBkgBrush);
954 ReleaseDC(hwnd, hDC);
959 /***********************************************************************
962 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
967 hDC = (hParamDC) ? hParamDC
968 : BeginPaint( lphc->self, &ps);
970 TRACE("hdc=%04x\n", hDC);
972 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
974 HBRUSH hPrevBrush, hBkgBrush;
977 * Retrieve the background brush and select it in the
980 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
982 hPrevBrush = SelectObject( hDC, hBkgBrush );
985 * In non 3.1 look, there is a sunken border on the combobox
987 if (TWEAK_WineLook != WIN31_LOOK)
989 CBPaintBorder(lphc->self, lphc, hDC);
992 if( !IsRectEmpty(&lphc->buttonRect) )
994 CBPaintButton(lphc, hDC, lphc->buttonRect);
997 /* paint the edit control padding area */
998 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
1000 RECT rPadEdit = lphc->textRect;
1002 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
1004 FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
1007 if( !(lphc->wState & CBF_EDIT) )
1010 * The text area has a border only in Win 3.1 look.
1012 if (TWEAK_WineLook == WIN31_LOOK)
1014 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
1017 lphc->textRect.left, lphc->textRect.top,
1018 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1020 SelectObject( hDC, hPrevPen );
1023 CBPaintText( lphc, hDC, lphc->textRect);
1027 SelectObject( hDC, hPrevBrush );
1031 EndPaint(lphc->self, &ps);
1036 /***********************************************************************
1039 * Select listbox entry according to the contents of the edit control.
1041 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1044 LPWSTR pText = NULL;
1047 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
1050 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1052 TRACE("\t edit text length %i\n", length );
1056 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1057 else pText[0] = '\0';
1058 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1059 (WPARAM)(-1), (LPARAM)pText );
1060 HeapFree( GetProcessHeap(), 0, pText );
1063 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1065 /* probably superfluous but Windows sends this too */
1066 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1067 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1072 /***********************************************************************
1075 * Copy a listbox entry to the edit control.
1077 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1080 LPWSTR pText = NULL;
1081 static const WCHAR empty_stringW[] = { 0 };
1083 TRACE("\t %i\n", index );
1085 if( index >= 0 ) /* got an entry */
1087 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1090 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1092 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1093 (WPARAM)index, (LPARAM)pText );
1098 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1099 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1100 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1102 if( lphc->wState & CBF_FOCUSED )
1103 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1106 HeapFree( GetProcessHeap(), 0, pText );
1109 /***********************************************************************
1112 * Show listbox popup.
1114 static void CBDropDown( LPHEADCOMBO lphc )
1120 TRACE("[%04x]: drop down\n", lphc->self);
1122 CB_NOTIFY( lphc, CBN_DROPDOWN );
1126 lphc->wState |= CBF_DROPPED;
1127 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1129 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1131 /* Update edit only if item is in the list */
1132 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1133 CBUpdateEdit( lphc, lphc->droppedIndex );
1137 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1139 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1140 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1141 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1144 /* now set popup position */
1145 GetWindowRect( lphc->self, &rect );
1148 * If it's a dropdown, the listbox is offset
1150 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1151 rect.left += COMBO_EDITBUTTONSPACE();
1153 /* if the dropped height is greater than the total height of the dropped
1154 items list, then force the drop down list height to be the total height
1155 of the items in the dropped list */
1157 /* And Remove any extra space (Best Fit) */
1158 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1159 /* if listbox length has been set directly by its handle */
1160 GetWindowRect(lphc->hWndLBox, &r);
1161 if (nDroppedHeight < r.bottom - r.top)
1162 nDroppedHeight = r.bottom - r.top;
1163 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1169 nHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1172 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1173 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1176 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1177 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1178 rect.bottom = rect.top - nDroppedHeight;
1180 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1181 lphc->droppedRect.right - lphc->droppedRect.left,
1183 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1186 if( !(lphc->wState & CBF_NOREDRAW) )
1187 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1188 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1190 EnableWindow( lphc->hWndLBox, TRUE );
1191 if (GetCapture() != lphc->self)
1192 SetCapture(lphc->hWndLBox);
1195 /***********************************************************************
1198 * Hide listbox popup.
1200 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1202 HWND hWnd = lphc->self;
1204 TRACE("[%04x]: sel ok? [%i] dropped? [%i]\n",
1205 lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED));
1207 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1209 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1212 if( lphc->wState & CBF_DROPPED )
1216 lphc->wState &= ~CBF_DROPPED;
1217 ShowWindow( lphc->hWndLBox, SW_HIDE );
1219 if(GetCapture() == lphc->hWndLBox)
1224 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1226 rect = lphc->buttonRect;
1237 rect = lphc->textRect;
1242 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1243 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1244 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1245 CB_NOTIFY( lphc, CBN_CLOSEUP );
1250 /***********************************************************************
1253 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1255 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1257 if( lphc->wState & CBF_DROPPED )
1259 CBRollUp( lphc, ok, bRedrawButton );
1267 /***********************************************************************
1270 static void CBRepaintButton( LPHEADCOMBO lphc )
1272 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1273 UpdateWindow(lphc->self);
1276 /***********************************************************************
1279 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1281 if( !(lphc->wState & CBF_FOCUSED) )
1283 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1284 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1286 /* This is wrong. Message sequences seem to indicate that this
1287 is set *after* the notify. */
1288 /* lphc->wState |= CBF_FOCUSED; */
1290 if( !(lphc->wState & CBF_EDIT) )
1291 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1293 CB_NOTIFY( lphc, CBN_SETFOCUS );
1294 lphc->wState |= CBF_FOCUSED;
1298 /***********************************************************************
1301 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1303 HWND hWnd = lphc->self;
1305 if( lphc->wState & CBF_FOCUSED )
1307 CBRollUp( lphc, FALSE, TRUE );
1308 if( IsWindow( hWnd ) )
1310 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1311 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1313 lphc->wState &= ~CBF_FOCUSED;
1316 if( !(lphc->wState & CBF_EDIT) )
1317 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1319 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1324 /***********************************************************************
1327 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1329 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1331 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1333 switch( HIWORD(wParam) >> 8 )
1335 case (EN_SETFOCUS >> 8):
1337 TRACE("[%04x]: edit [%04x] got focus\n",
1338 lphc->self, lphc->hWndEdit );
1340 COMBO_SetFocus( lphc );
1343 case (EN_KILLFOCUS >> 8):
1345 TRACE("[%04x]: edit [%04x] lost focus\n",
1346 lphc->self, lphc->hWndEdit );
1348 /* NOTE: it seems that Windows' edit control sends an
1349 * undocumented message WM_USER + 0x1B instead of this
1350 * notification (only when it happens to be a part of
1351 * the combo). ?? - AK.
1354 COMBO_KillFocus( lphc );
1358 case (EN_CHANGE >> 8):
1360 * In some circumstances (when the selection of the combobox
1361 * is changed for example) we don't wans the EN_CHANGE notification
1362 * to be forwarded to the parent of the combobox. This code
1363 * checks a flag that is set in these occasions and ignores the
1366 if (lphc->wState & CBF_NOLBSELECT)
1368 lphc->wState &= ~CBF_NOLBSELECT;
1372 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1375 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1376 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1379 case (EN_UPDATE >> 8):
1380 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1381 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1384 case (EN_ERRSPACE >> 8):
1385 CB_NOTIFY( lphc, CBN_ERRSPACE );
1388 else if( lphc->hWndLBox == hWnd )
1390 switch( HIWORD(wParam) )
1393 CB_NOTIFY( lphc, CBN_ERRSPACE );
1397 CB_NOTIFY( lphc, CBN_DBLCLK );
1403 TRACE("[%04x]: lbox selection change [%04x]\n",
1404 lphc->self, lphc->wState );
1406 if( HIWORD(wParam) == LBN_SELCHANGE)
1408 if( lphc->wState & CBF_EDIT )
1410 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1411 lphc->wState |= CBF_NOLBSELECT;
1412 CBUpdateEdit( lphc, index );
1413 /* select text in edit, as Windows does */
1414 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1417 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1420 /* do not roll up if selection is being tracked
1421 * by arrowkeys in the dropdown listbox */
1422 if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1424 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1426 else lphc->wState &= ~CBF_NOROLLUP;
1428 CB_NOTIFY( lphc, CBN_SELCHANGE );
1434 /* nothing to do here since ComboLBox always resets the focus to its
1435 * combo/edit counterpart */
1442 /***********************************************************************
1445 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1447 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1449 HWND hWnd = lphc->self;
1450 UINT id = GetWindowLongA( hWnd, GWL_ID );
1452 TRACE("[%04x]: ownerdraw op %04x\n", lphc->self, msg );
1458 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1459 lpIS->CtlType = ODT_COMBOBOX;
1461 lpIS->hwndItem = hWnd;
1466 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1467 lpIS->CtlType = ODT_COMBOBOX;
1469 lpIS->hwndItem = hWnd;
1472 case WM_COMPAREITEM:
1474 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1475 lpIS->CtlType = ODT_COMBOBOX;
1477 lpIS->hwndItem = hWnd;
1480 case WM_MEASUREITEM:
1482 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1483 lpIS->CtlType = ODT_COMBOBOX;
1488 return SendMessageW(lphc->owner, msg, id, lParam);
1491 /***********************************************************************
1494 * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1495 * also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1497 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, INT N, LPARAM lParam, BOOL unicode)
1499 if( lphc->wState & CBF_EDIT )
1500 return unicode ? SendMessageW(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam) :
1501 SendMessageA(lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, lParam);
1503 /* get it from the listbox */
1505 if( lphc->hWndLBox )
1507 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1511 INT length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN,
1516 LPWSTR lpBuffer, lpText = (LPWSTR)lParam;
1518 /* 'length' is without the terminating character */
1520 lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1526 n = SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1528 /* truncate if buffer is too short */
1534 strncpyW(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1535 lpText[N - 1] = '\0';
1537 HeapFree( GetProcessHeap(), 0, lpBuffer );
1543 LPSTR lpBuffer, lpText = (LPSTR)lParam;
1545 /* 'length' is without the terminating character */
1547 lpBuffer = HeapAlloc(GetProcessHeap(), 0, length + 1);
1553 n = SendMessageA(lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer);
1555 /* truncate if buffer is too short */
1561 strncpy(lpText, lpBuffer, (N > n) ? n+1 : N-1);
1562 lpText[N - 1] = '\0';
1564 HeapFree( GetProcessHeap(), 0, lpBuffer );
1579 /***********************************************************************
1582 * This function sets window positions according to the updated
1583 * component placement struct.
1585 static void CBResetPos(
1591 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1593 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1594 * sizing messages */
1596 if( lphc->wState & CBF_EDIT )
1597 SetWindowPos( lphc->hWndEdit, 0,
1598 rectEdit->left, rectEdit->top,
1599 rectEdit->right - rectEdit->left,
1600 rectEdit->bottom - rectEdit->top,
1601 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1603 SetWindowPos( lphc->hWndLBox, 0,
1604 rectLB->left, rectLB->top,
1605 rectLB->right - rectLB->left,
1606 rectLB->bottom - rectLB->top,
1607 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1611 if( lphc->wState & CBF_DROPPED )
1613 lphc->wState &= ~CBF_DROPPED;
1614 ShowWindow( lphc->hWndLBox, SW_HIDE );
1617 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1618 RedrawWindow( lphc->self, NULL, 0,
1619 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1624 /***********************************************************************
1627 static void COMBO_Size( LPHEADCOMBO lphc )
1629 CBCalcPlacement(lphc->self,
1633 &lphc->droppedRect);
1635 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1639 /***********************************************************************
1642 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1647 lphc->hFont = hFont;
1650 * Propagate to owned windows.
1652 if( lphc->wState & CBF_EDIT )
1653 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1654 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1657 * Redo the layout of the control.
1659 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1661 CBCalcPlacement(lphc->self,
1665 &lphc->droppedRect);
1667 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1671 CBForceDummyResize(lphc);
1676 /***********************************************************************
1677 * COMBO_SetItemHeight
1679 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1681 LRESULT lRet = CB_ERR;
1683 if( index == -1 ) /* set text field height */
1685 if( height < 32768 )
1687 lphc->editHeight = height;
1690 * Redo the layout of the control.
1692 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1694 CBCalcPlacement(lphc->self,
1698 &lphc->droppedRect);
1700 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1704 CBForceDummyResize(lphc);
1710 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1711 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1712 (WPARAM)index, (LPARAM)height );
1716 /***********************************************************************
1717 * COMBO_SelectString
1719 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1721 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1722 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1725 if( lphc->wState & CBF_EDIT )
1726 CBUpdateEdit( lphc, index );
1729 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1732 return (LRESULT)index;
1735 /***********************************************************************
1738 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1742 HWND hWnd = lphc->self;
1744 pt.x = LOWORD(lParam);
1745 pt.y = HIWORD(lParam);
1746 bButton = PtInRect(&lphc->buttonRect, pt);
1748 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1749 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1751 lphc->wState |= CBF_BUTTONDOWN;
1752 if( lphc->wState & CBF_DROPPED )
1754 /* got a click to cancel selection */
1756 lphc->wState &= ~CBF_BUTTONDOWN;
1757 CBRollUp( lphc, TRUE, FALSE );
1758 if( !IsWindow( hWnd ) ) return;
1760 if( lphc->wState & CBF_CAPTURE )
1762 lphc->wState &= ~CBF_CAPTURE;
1768 /* drop down the listbox and start tracking */
1770 lphc->wState |= CBF_CAPTURE;
1774 if( bButton ) CBRepaintButton( lphc );
1778 /***********************************************************************
1781 * Release capture and stop tracking if needed.
1783 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1785 if( lphc->wState & CBF_CAPTURE )
1787 lphc->wState &= ~CBF_CAPTURE;
1788 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1790 INT index = CBUpdateLBox( lphc, TRUE );
1791 /* Update edit only if item is in the list */
1794 lphc->wState |= CBF_NOLBSELECT;
1795 CBUpdateEdit( lphc, index );
1796 lphc->wState &= ~CBF_NOLBSELECT;
1800 SetCapture(lphc->hWndLBox);
1803 if( lphc->wState & CBF_BUTTONDOWN )
1805 lphc->wState &= ~CBF_BUTTONDOWN;
1806 CBRepaintButton( lphc );
1810 /***********************************************************************
1813 * Two things to do - track combo button and release capture when
1814 * pointer goes into the listbox.
1816 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1821 pt.x = LOWORD(lParam);
1822 pt.y = HIWORD(lParam);
1824 if( lphc->wState & CBF_BUTTONDOWN )
1828 bButton = PtInRect(&lphc->buttonRect, pt);
1832 lphc->wState &= ~CBF_BUTTONDOWN;
1833 CBRepaintButton( lphc );
1837 GetClientRect( lphc->hWndLBox, &lbRect );
1838 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1839 if( PtInRect(&lbRect, pt) )
1841 lphc->wState &= ~CBF_CAPTURE;
1843 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1845 /* hand over pointer tracking */
1846 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1851 /***********************************************************************
1852 * ComboWndProc_common
1854 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1856 static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
1857 WPARAM wParam, LPARAM lParam, BOOL unicode )
1859 LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongA( hwnd, 0 );
1861 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1862 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1864 if( lphc || message == WM_NCCREATE )
1868 /* System messages */
1872 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1873 ((LPCREATESTRUCTA)lParam)->style;
1874 return COMBO_NCCreate(hwnd, style);
1877 COMBO_NCDestroy(lphc);
1878 break;/* -> DefWindowProc */
1886 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1887 style = ((LPCREATESTRUCTW)lParam)->style;
1891 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1892 style = ((LPCREATESTRUCTA)lParam)->style;
1894 return COMBO_Create(hwnd, lphc, hwndParent, style);
1897 case WM_PRINTCLIENT:
1898 if (lParam & PRF_ERASEBKGND)
1899 COMBO_EraseBackground(hwnd, lphc, wParam);
1903 /* wParam may contain a valid HDC! */
1904 return COMBO_Paint(lphc, wParam);
1906 return COMBO_EraseBackground(hwnd, lphc, wParam);
1909 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1910 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1912 int vk = (int)((LPMSG)lParam)->wParam;
1914 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1915 result |= DLGC_WANTMESSAGE;
1919 case WM_WINDOWPOSCHANGING:
1920 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1921 case WM_WINDOWPOSCHANGED:
1922 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1923 * In that case, the Combobox itself will not be resized, so we won't
1924 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1929 if( lphc->hWndLBox &&
1930 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1933 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1936 return (LRESULT)lphc->hFont;
1938 if( lphc->wState & CBF_EDIT )
1939 SetFocus( lphc->hWndEdit );
1941 COMBO_SetFocus( lphc );
1944 #define hwndFocus ((HWND16)wParam)
1946 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1947 COMBO_KillFocus( lphc );
1951 return COMBO_Command( lphc, wParam, (HWND)lParam );
1953 return COMBO_GetText( lphc, (INT)wParam, lParam, unicode );
1955 case WM_GETTEXTLENGTH:
1957 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1959 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1960 if (j == -1) return 0;
1961 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1963 else if( lphc->wState & CBF_EDIT )
1966 lphc->wState |= CBF_NOEDITNOTIFY;
1967 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1968 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1969 lphc->wState &= ~CBF_NOEDITNOTIFY;
1976 if( lphc->wState & CBF_EDIT )
1978 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1979 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1985 case WM_COMPAREITEM:
1986 case WM_MEASUREITEM:
1987 return COMBO_ItemOp(lphc, message, lParam);
1989 if( lphc->wState & CBF_EDIT )
1990 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1991 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1993 /* Force the control to repaint when the enabled state changes. */
1994 InvalidateRect(lphc->self, NULL, TRUE);
1998 lphc->wState &= ~CBF_NOREDRAW;
2000 lphc->wState |= CBF_NOREDRAW;
2002 if( lphc->wState & CBF_EDIT )
2003 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
2004 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
2007 if( KEYDATA_ALT & HIWORD(lParam) )
2008 if( wParam == VK_UP || wParam == VK_DOWN )
2009 COMBO_FlipListbox( lphc, FALSE, FALSE );
2017 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2018 (lphc->wState & CBF_DROPPED))
2020 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2024 if( lphc->wState & CBF_EDIT )
2025 hwndTarget = lphc->hWndEdit;
2027 hwndTarget = lphc->hWndLBox;
2029 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2030 SendMessageA(hwndTarget, message, wParam, lParam);
2032 case WM_LBUTTONDOWN:
2033 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2034 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2037 COMBO_LButtonUp( lphc );
2040 if( lphc->wState & CBF_CAPTURE )
2041 COMBO_MouseMove( lphc, wParam, lParam );
2045 if (wParam & (MK_SHIFT | MK_CONTROL))
2046 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2047 DefWindowProcA(hwnd, message, wParam, lParam);
2048 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2049 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2052 /* Combo messages */
2054 case CB_ADDSTRING16:
2055 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2058 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2059 SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2060 case CB_INSERTSTRING16:
2061 wParam = (INT)(INT16)wParam;
2062 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2064 case CB_INSERTSTRING:
2065 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2066 SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2067 case CB_DELETESTRING16:
2068 case CB_DELETESTRING:
2069 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2070 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2071 case CB_SELECTSTRING16:
2072 wParam = (INT)(INT16)wParam;
2073 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2075 case CB_SELECTSTRING:
2076 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2077 case CB_FINDSTRING16:
2078 wParam = (INT)(INT16)wParam;
2079 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2082 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2083 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2084 case CB_FINDSTRINGEXACT16:
2085 wParam = (INT)(INT16)wParam;
2086 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2088 case CB_FINDSTRINGEXACT:
2089 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2090 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2091 case CB_SETITEMHEIGHT16:
2092 wParam = (INT)(INT16)wParam; /* signed integer */
2094 case CB_SETITEMHEIGHT:
2095 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2096 case CB_GETITEMHEIGHT16:
2097 wParam = (INT)(INT16)wParam;
2099 case CB_GETITEMHEIGHT:
2100 if( (INT)wParam >= 0 ) /* listbox item */
2101 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2102 return CBGetTextAreaHeight(hwnd, lphc);
2103 case CB_RESETCONTENT16:
2104 case CB_RESETCONTENT:
2105 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2106 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2108 static const WCHAR empty_stringW[] = { 0 };
2109 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2112 InvalidateRect(lphc->self, NULL, TRUE);
2114 case CB_INITSTORAGE:
2115 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2116 case CB_GETHORIZONTALEXTENT:
2117 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2118 case CB_SETHORIZONTALEXTENT:
2119 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2120 case CB_GETTOPINDEX:
2121 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2123 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2125 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2126 case CB_GETDROPPEDWIDTH:
2127 if( lphc->droppedWidth )
2128 return lphc->droppedWidth;
2129 return lphc->droppedRect.right - lphc->droppedRect.left;
2130 case CB_SETDROPPEDWIDTH:
2131 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2132 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2134 case CB_GETDROPPEDCONTROLRECT16:
2135 lParam = (LPARAM)MapSL(lParam);
2139 CBGetDroppedControlRect( lphc, &r );
2140 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2143 case CB_GETDROPPEDCONTROLRECT:
2144 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2146 case CB_GETDROPPEDSTATE16:
2147 case CB_GETDROPPEDSTATE:
2148 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2150 lParam = (LPARAM)MapSL(lParam);
2154 if(message == CB_DIR) message = LB_DIR;
2155 return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) :
2156 SendMessageA(lphc->hWndLBox, message, wParam, lParam);
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);
2186 /* no LBN_SELCHANGE in this case, update manually */
2187 if( lphc->wState & CBF_EDIT )
2188 CBUpdateEdit( lphc, (INT)wParam );
2190 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2191 lphc->wState &= ~CBF_SELCHANGE;
2193 case CB_GETLBTEXT16:
2194 wParam = (INT)(INT16)wParam;
2195 lParam = (LPARAM)MapSL(lParam);
2198 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2199 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2200 case CB_GETLBTEXTLEN16:
2201 wParam = (INT)(INT16)wParam;
2203 case CB_GETLBTEXTLEN:
2204 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2205 case CB_GETITEMDATA16:
2206 wParam = (INT)(INT16)wParam;
2208 case CB_GETITEMDATA:
2209 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2210 case CB_SETITEMDATA16:
2211 wParam = (INT)(INT16)wParam;
2213 case CB_SETITEMDATA:
2214 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2215 case CB_GETEDITSEL16:
2216 wParam = lParam = 0; /* just in case */
2219 /* Edit checks passed parameters itself */
2220 if( lphc->wState & CBF_EDIT )
2221 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2223 case CB_SETEDITSEL16:
2225 if( lphc->wState & CBF_EDIT )
2226 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2227 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2229 case CB_SETEXTENDEDUI16:
2230 case CB_SETEXTENDEDUI:
2231 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2234 lphc->wState |= CBF_EUI;
2235 else lphc->wState &= ~CBF_EUI;
2237 case CB_GETEXTENDEDUI16:
2238 case CB_GETEXTENDEDUI:
2239 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2242 if (message >= WM_USER)
2243 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2244 message - WM_USER, wParam, lParam );
2247 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2248 DefWindowProcA(hwnd, message, wParam, lParam);
2251 /***********************************************************************
2254 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2257 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2259 if (!IsWindow(hwnd)) return 0;
2260 return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2263 /***********************************************************************
2266 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2268 if (!IsWindow(hwnd)) return 0;
2269 return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );