4 * Copyright 1997 Alex Korobka
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * FIXME: roll up in Netscape 3.01.
29 #include "wine/winuser16.h"
30 #include "wine/unicode.h"
35 #include "wine/debug.h"
37 WINE_DEFAULT_DEBUG_CHANNEL(combo);
39 /* bits in the dwKeyData */
40 #define KEYDATA_ALT 0x2000
41 #define KEYDATA_PREVSTATE 0x4000
44 * Additional combo box definitions
47 #define CB_NOTIFY( lphc, code ) \
48 (SendMessageW((lphc)->owner, WM_COMMAND, \
49 MAKEWPARAM(GetWindowLongA((lphc)->self,GWL_ID), (code)), (LPARAM)(lphc)->self))
51 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
52 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
53 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
54 #define CB_HWND( lphc ) ((lphc)->self)
56 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
61 static HBITMAP hComboBmp = 0;
62 static UINT CBitHeight, CBitWidth;
65 * Look and feel dependant "constants"
68 #define COMBO_YBORDERGAP 5
69 #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
70 #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
71 #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 )
72 #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 )
74 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
75 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
77 /*********************************************************************
78 * combo class descriptor
80 const struct builtin_class_descr COMBO_builtin_class =
82 "ComboBox", /* name */
83 CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS, /* style */
84 ComboWndProcA, /* procA */
85 ComboWndProcW, /* procW */
86 sizeof(HEADCOMBO *), /* extra */
87 IDC_ARROWA, /* cursor */
92 /***********************************************************************
95 * Load combo button bitmap.
97 static BOOL COMBO_Init()
101 if( hComboBmp ) return TRUE;
102 if( (hDC = CreateCompatibleDC(0)) )
105 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
111 GetObjectW( hComboBmp, sizeof(bm), &bm );
112 CBitHeight = bm.bmHeight;
113 CBitWidth = bm.bmWidth;
115 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
117 hPrevB = SelectObject( hDC, hComboBmp);
118 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
119 InvertRect( hDC, &r );
120 SelectObject( hDC, hPrevB );
129 /***********************************************************************
132 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style)
136 if (COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEADCOMBO))) )
139 SetWindowLongA( hwnd, 0, (LONG)lphc );
141 /* some braindead apps do try to use scrollbar/border flags */
143 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
144 SetWindowLongA( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) );
147 * We also have to remove the client edge style to make sure
148 * we don't end-up with a non client area.
150 SetWindowLongA( hwnd, GWL_EXSTYLE,
151 GetWindowLongA( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
153 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
154 lphc->dwStyle |= CBS_HASSTRINGS;
155 if( !(GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
156 lphc->wState |= CBF_NOTIFY;
158 TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle );
164 /***********************************************************************
167 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
172 TRACE("[%p]: freeing storage\n", lphc->self);
174 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
175 DestroyWindow( lphc->hWndLBox );
177 SetWindowLongA( lphc->self, 0, 0 );
178 HeapFree( GetProcessHeap(), 0, lphc );
183 /***********************************************************************
184 * CBGetTextAreaHeight
186 * This method will calculate the height of the text area of the
188 * The height of the text area is set in two ways.
189 * It can be set explicitly through a combobox message or through a
190 * WM_MEASUREITEM callback.
191 * If this is not the case, the height is set to 13 dialog units.
192 * This height was determined through experimentation.
194 static INT CBGetTextAreaHeight(
200 if( lphc->editHeight ) /* explicitly set height */
202 iTextItemHeight = lphc->editHeight;
207 HDC hDC = GetDC(hwnd);
212 hPrevFont = SelectObject( hDC, lphc->hFont );
214 GetTextMetricsW(hDC, &tm);
216 baseUnitY = tm.tmHeight;
219 SelectObject( hDC, hPrevFont );
221 ReleaseDC(hwnd, hDC);
223 iTextItemHeight = ((13 * baseUnitY) / 8);
226 * This "formula" calculates the height of the complete control.
227 * To calculate the height of the text area, we have to remove the
230 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
234 * Check the ownerdraw case if we haven't asked the parent the size
237 if ( CB_OWNERDRAWN(lphc) &&
238 (lphc->wState & CBF_MEASUREITEM) )
240 MEASUREITEMSTRUCT measureItem;
242 INT originalItemHeight = iTextItemHeight;
243 UINT id = GetWindowLongA( lphc->self, GWL_ID );
246 * We use the client rect for the width of the item.
248 GetClientRect(hwnd, &clientRect);
250 lphc->wState &= ~CBF_MEASUREITEM;
253 * Send a first one to measure the size of the text area
255 measureItem.CtlType = ODT_COMBOBOX;
256 measureItem.CtlID = id;
257 measureItem.itemID = -1;
258 measureItem.itemWidth = clientRect.right;
259 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
260 measureItem.itemData = 0;
261 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
262 iTextItemHeight = 6 + measureItem.itemHeight;
265 * Send a second one in the case of a fixed ownerdraw list to calculate the
266 * size of the list items. (we basically do this on behalf of the listbox)
268 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
270 measureItem.CtlType = ODT_COMBOBOX;
271 measureItem.CtlID = id;
272 measureItem.itemID = 0;
273 measureItem.itemWidth = clientRect.right;
274 measureItem.itemHeight = originalItemHeight;
275 measureItem.itemData = 0;
276 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
277 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
281 * Keep the size for the next time
283 lphc->editHeight = iTextItemHeight;
286 return iTextItemHeight;
289 /***********************************************************************
292 * The dummy resize is used for listboxes that have a popup to trigger
293 * a re-arranging of the contents of the combobox and the recalculation
294 * of the size of the "real" control window.
296 static void CBForceDummyResize(
302 newComboHeight = CBGetTextAreaHeight(lphc->self,lphc) + 2*COMBO_YBORDERSIZE();
304 GetWindowRect(lphc->self, &windowRect);
307 * We have to be careful, resizing a combobox also has the meaning that the
308 * dropped rect will be resized. In this case, we want to trigger a resize
309 * to recalculate layout but we don't want to change the dropped rectangle
310 * So, we pass the height of text area of control as the height.
311 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
314 SetWindowPos( lphc->self,
317 windowRect.right - windowRect.left,
319 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
322 /***********************************************************************
325 * Set up component coordinates given valid lphc->RectCombo.
327 static void CBCalcPlacement(
335 * Again, start with the client rectangle.
337 GetClientRect(hwnd, lprEdit);
342 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
345 * Chop off the bottom part to fit with the height of the text area.
347 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
350 * The button starts the same vertical position as the text area.
352 CopyRect(lprButton, lprEdit);
355 * If the combobox is "simple" there is no button.
357 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
358 lprButton->left = lprButton->right = lprButton->bottom = 0;
362 * Let's assume the combobox button is the same width as the
364 * size the button horizontally and cut-off the text area.
366 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
367 lprEdit->right = lprButton->left;
371 * In the case of a dropdown, there is an additional spacing between the
372 * text area and the button.
374 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
376 lprEdit->right -= COMBO_EDITBUTTONSPACE();
380 * If we have an edit control, we space it away from the borders slightly.
382 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
384 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
388 * Adjust the size of the listbox popup.
390 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
393 * Use the client rectangle to initialize the listbox rectangle
395 GetClientRect(hwnd, lprLB);
398 * Then, chop-off the top part.
400 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
405 * Make sure the dropped width is as large as the combobox itself.
407 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
409 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
412 * In the case of a dropdown, the popup listbox is offset to the right.
413 * so, we want to make sure it's flush with the right side of the
416 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
417 lprLB->right -= COMBO_EDITBUTTONSPACE();
420 lprLB->right = lprLB->left + lphc->droppedWidth;
423 TRACE("\ttext\t= (%i,%i-%i,%i)\n",
424 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
426 TRACE("\tbutton\t= (%i,%i-%i,%i)\n",
427 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
429 TRACE("\tlbox\t= (%i,%i-%i,%i)\n",
430 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
433 /***********************************************************************
434 * CBGetDroppedControlRect
436 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
438 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
439 of the combo box and the lower right corner of the listbox */
441 GetWindowRect(lphc->self, lpRect);
443 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
444 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
448 /***********************************************************************
449 * COMBO_WindowPosChanging
451 static LRESULT COMBO_WindowPosChanging(
454 WINDOWPOS* posChanging)
457 * We need to override the WM_WINDOWPOSCHANGING method to handle all
458 * the non-simple comboboxes. The problem is that those controls are
459 * always the same height. We have to make sure they are not resized
462 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
463 ((posChanging->flags & SWP_NOSIZE) == 0) )
467 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
468 2*COMBO_YBORDERSIZE();
471 * Resizing a combobox has another side effect, it resizes the dropped
472 * rectangle as well. However, it does it only if the new height for the
473 * combobox is different from the height it should have. In other words,
474 * if the application resizing the combobox only had the intention to resize
475 * the actual control, for example, to do the layout of a dialog that is
476 * resized, the height of the dropdown is not changed.
478 if (posChanging->cy != newComboHeight)
480 TRACE("posChanging->cy=%d, newComboHeight=%d, oldbot=%d, oldtop=%d\n",
481 posChanging->cy, newComboHeight, lphc->droppedRect.bottom,
482 lphc->droppedRect.top);
483 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
485 posChanging->cy = newComboHeight;
492 /***********************************************************************
495 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style,
498 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
499 static const WCHAR editName[] = {'E','d','i','t',0};
501 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
502 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
504 lphc->owner = hwndParent;
507 * The item height and dropped width are not set when the control
510 lphc->droppedWidth = lphc->editHeight = 0;
513 * The first time we go through, we want to measure the ownerdraw item
515 lphc->wState |= CBF_MEASUREITEM;
517 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
519 if( lphc->owner || !(style & WS_VISIBLE) )
525 * Initialize the dropped rect to the size of the client area of the
526 * control and then, force all the areas of the combobox to be
529 GetClientRect( hwnd, &lphc->droppedRect );
530 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
533 * Adjust the position of the popup listbox if it's necessary
535 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
537 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
540 * If it's a dropdown, the listbox is offset
542 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
543 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
545 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect);
546 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect.right);
549 /* create listbox popup */
551 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
552 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
554 if( lphc->dwStyle & CBS_SORT )
555 lbeStyle |= LBS_SORT;
556 if( lphc->dwStyle & CBS_HASSTRINGS )
557 lbeStyle |= LBS_HASSTRINGS;
558 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
559 lbeStyle |= LBS_NOINTEGRALHEIGHT;
560 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
561 lbeStyle |= LBS_DISABLENOSCROLL;
563 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
565 lbeStyle |= WS_VISIBLE;
568 * In win 95 look n feel, the listbox in the simple combobox has
569 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
571 if (TWEAK_WineLook > WIN31_LOOK)
573 lbeStyle &= ~WS_BORDER;
574 lbeExStyle |= WS_EX_CLIENTEDGE;
579 lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
580 lphc->droppedRect.left,
581 lphc->droppedRect.top,
582 lphc->droppedRect.right - lphc->droppedRect.left,
583 lphc->droppedRect.bottom - lphc->droppedRect.top,
584 hwnd, (HMENU)ID_CB_LISTBOX,
585 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc );
587 lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle,
588 lphc->droppedRect.left,
589 lphc->droppedRect.top,
590 lphc->droppedRect.right - lphc->droppedRect.left,
591 lphc->droppedRect.bottom - lphc->droppedRect.top,
592 hwnd, (HMENU)ID_CB_LISTBOX,
593 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc );
598 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
601 * In Win95 look, the border fo the edit control is
602 * provided by the combobox
604 if (TWEAK_WineLook == WIN31_LOOK)
605 lbeStyle |= WS_BORDER;
607 if( lphc->wState & CBF_EDIT )
609 if( lphc->dwStyle & CBS_OEMCONVERT )
610 lbeStyle |= ES_OEMCONVERT;
611 if( lphc->dwStyle & CBS_AUTOHSCROLL )
612 lbeStyle |= ES_AUTOHSCROLL;
613 if( lphc->dwStyle & CBS_LOWERCASE )
614 lbeStyle |= ES_LOWERCASE;
615 else if( lphc->dwStyle & CBS_UPPERCASE )
616 lbeStyle |= ES_UPPERCASE;
618 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
621 lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
622 lphc->textRect.left, lphc->textRect.top,
623 lphc->textRect.right - lphc->textRect.left,
624 lphc->textRect.bottom - lphc->textRect.top,
625 hwnd, (HMENU)ID_CB_EDIT,
626 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL );
628 lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle,
629 lphc->textRect.left, lphc->textRect.top,
630 lphc->textRect.right - lphc->textRect.left,
631 lphc->textRect.bottom - lphc->textRect.top,
632 hwnd, (HMENU)ID_CB_EDIT,
633 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL );
635 if( !lphc->hWndEdit )
641 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
643 /* Now do the trick with parent */
644 SetParent(lphc->hWndLBox, HWND_DESKTOP);
646 * If the combo is a dropdown, we must resize the control
647 * to fit only the text area and button. To do this,
648 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
649 * will take care of setting the height for us.
651 CBForceDummyResize(lphc);
654 TRACE("init done\n");
657 ERR("edit control failure.\n");
658 } else ERR("listbox failure.\n");
659 } else ERR("no owner for visible combo.\n");
661 /* CreateWindow() will send WM_NCDESTROY to cleanup */
666 /***********************************************************************
669 * Paint combo button (normal, pressed, and disabled states).
671 static void CBPaintButton(
676 if( lphc->wState & CBF_NOREDRAW )
679 if (TWEAK_WineLook == WIN31_LOOK)
685 COLORREF oldTextColor, oldBkColor;
688 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
691 * Draw the button background
696 rectButton.right-rectButton.left,
697 rectButton.bottom-rectButton.top,
700 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
702 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
706 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
710 * Remove the edge of the button from the rectangle
711 * and calculate the position of the bitmap.
713 InflateRect( &rectButton, -2, -2);
715 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
716 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
719 hMemDC = CreateCompatibleDC( hdc );
720 SelectObject( hMemDC, hComboBmp );
721 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
722 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
724 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
725 SetBkColor( hdc, oldBkColor );
726 SetTextColor( hdc, oldTextColor );
728 SelectObject( hdc, hPrevBrush );
732 UINT buttonState = DFCS_SCROLLCOMBOBOX;
734 if (lphc->wState & CBF_BUTTONDOWN)
736 buttonState |= DFCS_PUSHED;
739 if (CB_DISABLED(lphc))
741 buttonState |= DFCS_INACTIVE;
744 DrawFrameControl(hdc,
751 /***********************************************************************
754 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
756 static void CBPaintText(
764 if( lphc->wState & CBF_NOREDRAW ) return;
768 /* follow Windows combobox that sends a bunch of text
769 * inquiries to its listbox while processing WM_PAINT. */
771 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
773 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
775 FIXME("LB_ERR probably not handled yet\n");
776 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
778 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
779 size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
780 pText[size] = '\0'; /* just in case */
784 if( !CB_OWNERDRAWN(lphc) )
787 if( lphc->wState & CBF_EDIT )
789 static const WCHAR empty_stringW[] = { 0 };
790 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
791 if( lphc->wState & CBF_FOCUSED )
792 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
794 else /* paint text field ourselves */
796 UINT itemState = ODS_COMBOBOXEDIT;
797 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
800 * Give ourselves some space.
802 InflateRect( &rectEdit, -1, -1 );
804 if( CB_OWNERDRAWN(lphc) )
808 UINT ctlid = GetWindowLongA( lphc->self, GWL_ID );
810 /* setup state for DRAWITEM message. Owner will highlight */
811 if ( (lphc->wState & CBF_FOCUSED) &&
812 !(lphc->wState & CBF_DROPPED) )
813 itemState |= ODS_SELECTED | ODS_FOCUS;
816 * Save the current clip region.
817 * To retrieve the clip region, we need to create one "dummy"
820 clipRegion = CreateRectRgnIndirect(&rectEdit);
822 if (GetClipRgn(hdc, clipRegion)!=1)
824 DeleteObject(clipRegion);
828 if (!IsWindowEnabled(lphc->self) & WS_DISABLED) itemState |= ODS_DISABLED;
830 dis.CtlType = ODT_COMBOBOX;
832 dis.hwndItem = lphc->self;
833 dis.itemAction = ODA_DRAWENTIRE;
835 dis.itemState = itemState;
837 dis.rcItem = rectEdit;
838 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA,
842 * Clip the DC and have the parent draw the item.
844 IntersectClipRect(hdc,
845 rectEdit.left, rectEdit.top,
846 rectEdit.right, rectEdit.bottom);
848 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
851 * Reset the clipping region.
853 SelectClipRgn(hdc, clipRegion);
857 static const WCHAR empty_stringW[] = { 0 };
859 if ( (lphc->wState & CBF_FOCUSED) &&
860 !(lphc->wState & CBF_DROPPED) ) {
863 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
864 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
865 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
871 ETO_OPAQUE | ETO_CLIPPED,
873 pText ? pText : empty_stringW , size, NULL );
875 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
876 DrawFocusRect( hdc, &rectEdit );
880 SelectObject(hdc, hPrevFont );
883 HeapFree( GetProcessHeap(), 0, pText );
886 /***********************************************************************
889 static void CBPaintBorder(
896 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
898 GetClientRect(hwnd, &clientRect);
902 CopyRect(&clientRect, &lphc->textRect);
904 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
905 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
908 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
911 /***********************************************************************
912 * COMBO_PrepareColors
914 * This method will sent the appropriate WM_CTLCOLOR message to
915 * prepare and setup the colors for the combo's DC.
917 * It also returns the brush to use for the background.
919 static HBRUSH COMBO_PrepareColors(
926 * Get the background brush for this control.
928 if (CB_DISABLED(lphc))
930 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
931 (WPARAM)hDC, (LPARAM)lphc->self );
934 * We have to change the text color since WM_CTLCOLORSTATIC will
935 * set it to the "enabled" color. This is the same behavior as the
938 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
942 if (lphc->wState & CBF_EDIT)
944 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
945 (WPARAM)hDC, (LPARAM)lphc->self );
949 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX,
950 (WPARAM)hDC, (LPARAM)lphc->self );
958 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
963 /***********************************************************************
964 * COMBO_EraseBackground
966 static LRESULT COMBO_EraseBackground(
974 if(lphc->wState & CBF_EDIT)
977 hDC = (hParamDC) ? hParamDC
980 * Retrieve the background brush
982 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
984 FillRect(hDC, &lphc->textRect, hBkgBrush);
987 ReleaseDC(hwnd, hDC);
992 /***********************************************************************
995 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
1000 hDC = (hParamDC) ? hParamDC
1001 : BeginPaint( lphc->self, &ps);
1003 TRACE("hdc=%p\n", hDC);
1005 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
1007 HBRUSH hPrevBrush, hBkgBrush;
1010 * Retrieve the background brush and select it in the
1013 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
1015 hPrevBrush = SelectObject( hDC, hBkgBrush );
1018 * In non 3.1 look, there is a sunken border on the combobox
1020 if (TWEAK_WineLook != WIN31_LOOK)
1022 CBPaintBorder(lphc->self, lphc, hDC);
1025 if( !IsRectEmpty(&lphc->buttonRect) )
1027 CBPaintButton(lphc, hDC, lphc->buttonRect);
1030 /* paint the edit control padding area */
1031 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
1033 RECT rPadEdit = lphc->textRect;
1035 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
1037 FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
1040 if( !(lphc->wState & CBF_EDIT) )
1043 * The text area has a border only in Win 3.1 look.
1045 if (TWEAK_WineLook == WIN31_LOOK)
1047 HPEN hPrevPen = SelectObject( hDC, SYSCOLOR_GetPen(COLOR_WINDOWFRAME) );
1050 lphc->textRect.left, lphc->textRect.top,
1051 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
1053 SelectObject( hDC, hPrevPen );
1056 CBPaintText( lphc, hDC, lphc->textRect);
1060 SelectObject( hDC, hPrevBrush );
1064 EndPaint(lphc->self, &ps);
1069 /***********************************************************************
1072 * Select listbox entry according to the contents of the edit control.
1074 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
1077 LPWSTR pText = NULL;
1080 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
1083 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1085 TRACE("\t edit text length %i\n", length );
1089 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1090 else pText[0] = '\0';
1091 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1092 (WPARAM)(-1), (LPARAM)pText );
1093 HeapFree( GetProcessHeap(), 0, pText );
1096 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1098 /* probably superfluous but Windows sends this too */
1099 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1100 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1105 /***********************************************************************
1108 * Copy a listbox entry to the edit control.
1110 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1113 LPWSTR pText = NULL;
1114 static const WCHAR empty_stringW[] = { 0 };
1116 TRACE("\t %i\n", index );
1118 if( index >= 0 ) /* got an entry */
1120 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1121 if( length != LB_ERR)
1123 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1125 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1126 (WPARAM)index, (LPARAM)pText );
1131 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1132 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1133 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1135 if( lphc->wState & CBF_FOCUSED )
1136 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1139 HeapFree( GetProcessHeap(), 0, pText );
1142 /***********************************************************************
1145 * Show listbox popup.
1147 static void CBDropDown( LPHEADCOMBO lphc )
1153 TRACE("[%p]: drop down\n", lphc->self);
1155 CB_NOTIFY( lphc, CBN_DROPDOWN );
1159 lphc->wState |= CBF_DROPPED;
1160 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1162 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1164 /* Update edit only if item is in the list */
1165 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1166 CBUpdateEdit( lphc, lphc->droppedIndex );
1170 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1172 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1173 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1174 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1177 /* now set popup position */
1178 GetWindowRect( lphc->self, &rect );
1181 * If it's a dropdown, the listbox is offset
1183 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1184 rect.left += COMBO_EDITBUTTONSPACE();
1186 /* if the dropped height is greater than the total height of the dropped
1187 items list, then force the drop down list height to be the total height
1188 of the items in the dropped list */
1190 /* And Remove any extra space (Best Fit) */
1191 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1192 /* if listbox length has been set directly by its handle */
1193 GetWindowRect(lphc->hWndLBox, &r);
1194 if (nDroppedHeight < r.bottom - r.top)
1195 nDroppedHeight = r.bottom - r.top;
1196 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1203 nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1205 nHeight = nIHeight*nItems;
1207 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1208 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1210 if (nDroppedHeight < nIHeight)
1213 nDroppedHeight = (nItems+1)*nIHeight;
1215 nDroppedHeight = 6*nIHeight;
1219 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1220 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1221 rect.bottom = rect.top - nDroppedHeight;
1223 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1224 lphc->droppedRect.right - lphc->droppedRect.left,
1226 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1229 if( !(lphc->wState & CBF_NOREDRAW) )
1230 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1231 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1233 EnableWindow( lphc->hWndLBox, TRUE );
1234 if (GetCapture() != lphc->self)
1235 SetCapture(lphc->hWndLBox);
1238 /***********************************************************************
1241 * Hide listbox popup.
1243 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1245 HWND hWnd = lphc->self;
1247 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1248 lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED));
1250 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1252 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1255 if( lphc->wState & CBF_DROPPED )
1259 lphc->wState &= ~CBF_DROPPED;
1260 ShowWindow( lphc->hWndLBox, SW_HIDE );
1262 if(GetCapture() == lphc->hWndLBox)
1267 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1269 rect = lphc->buttonRect;
1280 rect = lphc->textRect;
1285 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1286 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1287 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1288 CB_NOTIFY( lphc, CBN_CLOSEUP );
1293 /***********************************************************************
1296 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1298 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1300 if( lphc->wState & CBF_DROPPED )
1302 CBRollUp( lphc, ok, bRedrawButton );
1310 /***********************************************************************
1313 static void CBRepaintButton( LPHEADCOMBO lphc )
1315 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1316 UpdateWindow(lphc->self);
1319 /***********************************************************************
1322 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1324 if( !(lphc->wState & CBF_FOCUSED) )
1326 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1327 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1329 /* This is wrong. Message sequences seem to indicate that this
1330 is set *after* the notify. */
1331 /* lphc->wState |= CBF_FOCUSED; */
1333 if( !(lphc->wState & CBF_EDIT) )
1334 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1336 CB_NOTIFY( lphc, CBN_SETFOCUS );
1337 lphc->wState |= CBF_FOCUSED;
1341 /***********************************************************************
1344 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1346 HWND hWnd = lphc->self;
1348 if( lphc->wState & CBF_FOCUSED )
1350 CBRollUp( lphc, FALSE, TRUE );
1351 if( IsWindow( hWnd ) )
1353 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1354 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1356 lphc->wState &= ~CBF_FOCUSED;
1359 if( !(lphc->wState & CBF_EDIT) )
1360 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1362 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1367 /***********************************************************************
1370 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1372 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1374 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1376 switch( HIWORD(wParam) >> 8 )
1378 case (EN_SETFOCUS >> 8):
1380 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1382 COMBO_SetFocus( lphc );
1385 case (EN_KILLFOCUS >> 8):
1387 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1389 /* NOTE: it seems that Windows' edit control sends an
1390 * undocumented message WM_USER + 0x1B instead of this
1391 * notification (only when it happens to be a part of
1392 * the combo). ?? - AK.
1395 COMBO_KillFocus( lphc );
1399 case (EN_CHANGE >> 8):
1401 * In some circumstances (when the selection of the combobox
1402 * is changed for example) we don't wans the EN_CHANGE notification
1403 * to be forwarded to the parent of the combobox. This code
1404 * checks a flag that is set in these occasions and ignores the
1407 if (lphc->wState & CBF_NOLBSELECT)
1409 lphc->wState &= ~CBF_NOLBSELECT;
1413 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1416 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1417 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1420 case (EN_UPDATE >> 8):
1421 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1422 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1425 case (EN_ERRSPACE >> 8):
1426 CB_NOTIFY( lphc, CBN_ERRSPACE );
1429 else if( lphc->hWndLBox == hWnd )
1431 switch( HIWORD(wParam) )
1434 CB_NOTIFY( lphc, CBN_ERRSPACE );
1438 CB_NOTIFY( lphc, CBN_DBLCLK );
1444 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1446 if( HIWORD(wParam) == LBN_SELCHANGE)
1448 if( lphc->wState & CBF_EDIT )
1450 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1451 lphc->wState |= CBF_NOLBSELECT;
1452 CBUpdateEdit( lphc, index );
1453 /* select text in edit, as Windows does */
1454 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1457 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1460 /* do not roll up if selection is being tracked
1461 * by arrowkeys in the dropdown listbox */
1462 if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1464 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1466 else lphc->wState &= ~CBF_NOROLLUP;
1468 CB_NOTIFY( lphc, CBN_SELCHANGE );
1474 /* nothing to do here since ComboLBox always resets the focus to its
1475 * combo/edit counterpart */
1482 /***********************************************************************
1485 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1487 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1489 HWND hWnd = lphc->self;
1490 UINT id = GetWindowLongA( hWnd, GWL_ID );
1492 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1498 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1499 lpIS->CtlType = ODT_COMBOBOX;
1501 lpIS->hwndItem = hWnd;
1506 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1507 lpIS->CtlType = ODT_COMBOBOX;
1509 lpIS->hwndItem = hWnd;
1512 case WM_COMPAREITEM:
1514 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1515 lpIS->CtlType = ODT_COMBOBOX;
1517 lpIS->hwndItem = hWnd;
1520 case WM_MEASUREITEM:
1522 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1523 lpIS->CtlType = ODT_COMBOBOX;
1528 return SendMessageW(lphc->owner, msg, id, lParam);
1532 /***********************************************************************
1535 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1539 if( lphc->wState & CBF_EDIT )
1540 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1542 /* get it from the listbox */
1544 if (!count || !buf) return 0;
1545 if( lphc->hWndLBox )
1547 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1548 if (idx == LB_ERR) goto error;
1549 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1550 if (length == LB_ERR) goto error;
1552 /* 'length' is without the terminating character */
1553 if (length >= count)
1555 LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1556 if (!lpBuffer) goto error;
1557 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1559 /* truncate if buffer is too short */
1560 if (length != LB_ERR)
1562 lstrcpynW( buf, lpBuffer, count );
1565 HeapFree( GetProcessHeap(), 0, lpBuffer );
1567 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1569 if (length == LB_ERR) return 0;
1573 error: /* error - truncate string, return zero */
1579 /***********************************************************************
1582 * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1583 * also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1585 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1589 if( lphc->wState & CBF_EDIT )
1590 return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1592 /* get it from the listbox */
1594 if (!count || !buf) return 0;
1595 if( lphc->hWndLBox )
1597 INT idx = SendMessageA(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1598 if (idx == LB_ERR) goto error;
1599 length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1600 if (length == LB_ERR) goto error;
1602 /* 'length' is without the terminating character */
1603 if (length >= count)
1605 LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1606 if (!lpBuffer) goto error;
1607 length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1609 /* truncate if buffer is too short */
1610 if (length != LB_ERR)
1612 lstrcpynA( buf, lpBuffer, count );
1615 HeapFree( GetProcessHeap(), 0, lpBuffer );
1617 else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1619 if (length == LB_ERR) return 0;
1623 error: /* error - truncate string, return zero */
1629 /***********************************************************************
1632 * This function sets window positions according to the updated
1633 * component placement struct.
1635 static void CBResetPos(
1641 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1643 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1644 * sizing messages */
1646 if( lphc->wState & CBF_EDIT )
1647 SetWindowPos( lphc->hWndEdit, 0,
1648 rectEdit->left, rectEdit->top,
1649 rectEdit->right - rectEdit->left,
1650 rectEdit->bottom - rectEdit->top,
1651 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1653 SetWindowPos( lphc->hWndLBox, 0,
1654 rectLB->left, rectLB->top,
1655 rectLB->right - rectLB->left,
1656 rectLB->bottom - rectLB->top,
1657 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1661 if( lphc->wState & CBF_DROPPED )
1663 lphc->wState &= ~CBF_DROPPED;
1664 ShowWindow( lphc->hWndLBox, SW_HIDE );
1667 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1668 RedrawWindow( lphc->self, NULL, 0,
1669 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1674 /***********************************************************************
1677 static void COMBO_Size( LPHEADCOMBO lphc )
1679 CBCalcPlacement(lphc->self,
1683 &lphc->droppedRect);
1685 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1689 /***********************************************************************
1692 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1697 lphc->hFont = hFont;
1700 * Propagate to owned windows.
1702 if( lphc->wState & CBF_EDIT )
1703 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1704 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1707 * Redo the layout of the control.
1709 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1711 CBCalcPlacement(lphc->self,
1715 &lphc->droppedRect);
1717 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1721 CBForceDummyResize(lphc);
1726 /***********************************************************************
1727 * COMBO_SetItemHeight
1729 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1731 LRESULT lRet = CB_ERR;
1733 if( index == -1 ) /* set text field height */
1735 if( height < 32768 )
1737 lphc->editHeight = height;
1740 * Redo the layout of the control.
1742 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1744 CBCalcPlacement(lphc->self,
1748 &lphc->droppedRect);
1750 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1754 CBForceDummyResize(lphc);
1760 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1761 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1762 (WPARAM)index, (LPARAM)height );
1766 /***********************************************************************
1767 * COMBO_SelectString
1769 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1771 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1772 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1775 if( lphc->wState & CBF_EDIT )
1776 CBUpdateEdit( lphc, index );
1779 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1782 return (LRESULT)index;
1785 /***********************************************************************
1788 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1792 HWND hWnd = lphc->self;
1794 pt.x = LOWORD(lParam);
1795 pt.y = HIWORD(lParam);
1796 bButton = PtInRect(&lphc->buttonRect, pt);
1798 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1799 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1801 lphc->wState |= CBF_BUTTONDOWN;
1802 if( lphc->wState & CBF_DROPPED )
1804 /* got a click to cancel selection */
1806 lphc->wState &= ~CBF_BUTTONDOWN;
1807 CBRollUp( lphc, TRUE, FALSE );
1808 if( !IsWindow( hWnd ) ) return;
1810 if( lphc->wState & CBF_CAPTURE )
1812 lphc->wState &= ~CBF_CAPTURE;
1818 /* drop down the listbox and start tracking */
1820 lphc->wState |= CBF_CAPTURE;
1824 if( bButton ) CBRepaintButton( lphc );
1828 /***********************************************************************
1831 * Release capture and stop tracking if needed.
1833 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1835 if( lphc->wState & CBF_CAPTURE )
1837 lphc->wState &= ~CBF_CAPTURE;
1838 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1840 INT index = CBUpdateLBox( lphc, TRUE );
1841 /* Update edit only if item is in the list */
1844 lphc->wState |= CBF_NOLBSELECT;
1845 CBUpdateEdit( lphc, index );
1846 lphc->wState &= ~CBF_NOLBSELECT;
1850 SetCapture(lphc->hWndLBox);
1853 if( lphc->wState & CBF_BUTTONDOWN )
1855 lphc->wState &= ~CBF_BUTTONDOWN;
1856 CBRepaintButton( lphc );
1860 /***********************************************************************
1863 * Two things to do - track combo button and release capture when
1864 * pointer goes into the listbox.
1866 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1871 pt.x = LOWORD(lParam);
1872 pt.y = HIWORD(lParam);
1874 if( lphc->wState & CBF_BUTTONDOWN )
1878 bButton = PtInRect(&lphc->buttonRect, pt);
1882 lphc->wState &= ~CBF_BUTTONDOWN;
1883 CBRepaintButton( lphc );
1887 GetClientRect( lphc->hWndLBox, &lbRect );
1888 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1889 if( PtInRect(&lbRect, pt) )
1891 lphc->wState &= ~CBF_CAPTURE;
1893 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1895 /* hand over pointer tracking */
1896 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1901 /***********************************************************************
1902 * ComboWndProc_common
1904 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1906 static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
1907 WPARAM wParam, LPARAM lParam, BOOL unicode )
1909 LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongA( hwnd, 0 );
1911 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
1912 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1914 if( lphc || message == WM_NCCREATE )
1918 /* System messages */
1922 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1923 ((LPCREATESTRUCTA)lParam)->style;
1924 return COMBO_NCCreate(hwnd, style);
1927 COMBO_NCDestroy(lphc);
1928 break;/* -> DefWindowProc */
1936 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1937 style = ((LPCREATESTRUCTW)lParam)->style;
1941 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1942 style = ((LPCREATESTRUCTA)lParam)->style;
1944 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1947 case WM_PRINTCLIENT:
1948 if (lParam & PRF_ERASEBKGND)
1949 COMBO_EraseBackground(hwnd, lphc, (HDC)wParam);
1953 /* wParam may contain a valid HDC! */
1954 return COMBO_Paint(lphc, (HDC)wParam);
1956 return COMBO_EraseBackground(hwnd, lphc, (HDC)wParam);
1959 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1960 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1962 int vk = (int)((LPMSG)lParam)->wParam;
1964 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1965 result |= DLGC_WANTMESSAGE;
1969 case WM_WINDOWPOSCHANGING:
1970 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1971 case WM_WINDOWPOSCHANGED:
1972 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1973 * In that case, the Combobox itself will not be resized, so we won't
1974 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1979 if( lphc->hWndLBox &&
1980 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1983 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1986 return (LRESULT)lphc->hFont;
1988 if( lphc->wState & CBF_EDIT )
1989 SetFocus( lphc->hWndEdit );
1991 COMBO_SetFocus( lphc );
1995 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1997 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1998 COMBO_KillFocus( lphc );
2002 return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
2004 return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
2005 : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
2007 case WM_GETTEXTLENGTH:
2009 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
2011 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2012 if (j == -1) return 0;
2013 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
2014 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
2016 else if( lphc->wState & CBF_EDIT )
2019 lphc->wState |= CBF_NOEDITNOTIFY;
2020 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
2021 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
2022 lphc->wState &= ~CBF_NOEDITNOTIFY;
2029 if( lphc->wState & CBF_EDIT )
2031 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
2032 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
2038 case WM_COMPAREITEM:
2039 case WM_MEASUREITEM:
2040 return COMBO_ItemOp(lphc, message, lParam);
2042 if( lphc->wState & CBF_EDIT )
2043 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
2044 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
2046 /* Force the control to repaint when the enabled state changes. */
2047 InvalidateRect(lphc->self, NULL, TRUE);
2051 lphc->wState &= ~CBF_NOREDRAW;
2053 lphc->wState |= CBF_NOREDRAW;
2055 if( lphc->wState & CBF_EDIT )
2056 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
2057 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
2060 if( KEYDATA_ALT & HIWORD(lParam) )
2061 if( wParam == VK_UP || wParam == VK_DOWN )
2062 COMBO_FlipListbox( lphc, FALSE, FALSE );
2070 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2071 (lphc->wState & CBF_DROPPED))
2073 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2077 if( lphc->wState & CBF_EDIT )
2078 hwndTarget = lphc->hWndEdit;
2080 hwndTarget = lphc->hWndLBox;
2082 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2083 SendMessageA(hwndTarget, message, wParam, lParam);
2085 case WM_LBUTTONDOWN:
2086 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2087 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2090 COMBO_LButtonUp( lphc );
2093 if( lphc->wState & CBF_CAPTURE )
2094 COMBO_MouseMove( lphc, wParam, lParam );
2098 if (wParam & (MK_SHIFT | MK_CONTROL))
2099 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2100 DefWindowProcA(hwnd, message, wParam, lParam);
2101 if (SHIWORD(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2102 if (SHIWORD(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2105 /* Combo messages */
2107 case CB_ADDSTRING16:
2108 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2111 return unicode ? SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam) :
2112 SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2113 case CB_INSERTSTRING16:
2114 wParam = (INT)(INT16)wParam;
2115 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2117 case CB_INSERTSTRING:
2118 return unicode ? SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam) :
2119 SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2120 case CB_DELETESTRING16:
2121 case CB_DELETESTRING:
2122 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2123 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2124 case CB_SELECTSTRING16:
2125 wParam = (INT)(INT16)wParam;
2126 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2128 case CB_SELECTSTRING:
2129 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2130 case CB_FINDSTRING16:
2131 wParam = (INT)(INT16)wParam;
2132 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2135 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2136 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2137 case CB_FINDSTRINGEXACT16:
2138 wParam = (INT)(INT16)wParam;
2139 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2141 case CB_FINDSTRINGEXACT:
2142 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2143 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2144 case CB_SETITEMHEIGHT16:
2145 wParam = (INT)(INT16)wParam; /* signed integer */
2147 case CB_SETITEMHEIGHT:
2148 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2149 case CB_GETITEMHEIGHT16:
2150 wParam = (INT)(INT16)wParam;
2152 case CB_GETITEMHEIGHT:
2153 if( (INT)wParam >= 0 ) /* listbox item */
2154 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2155 return CBGetTextAreaHeight(hwnd, lphc);
2156 case CB_RESETCONTENT16:
2157 case CB_RESETCONTENT:
2158 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2159 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2161 static const WCHAR empty_stringW[] = { 0 };
2162 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2165 InvalidateRect(lphc->self, NULL, TRUE);
2167 case CB_INITSTORAGE:
2168 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2169 case CB_GETHORIZONTALEXTENT:
2170 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2171 case CB_SETHORIZONTALEXTENT:
2172 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2173 case CB_GETTOPINDEX:
2174 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2176 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2178 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2179 case CB_GETDROPPEDWIDTH:
2180 if( lphc->droppedWidth )
2181 return lphc->droppedWidth;
2182 return lphc->droppedRect.right - lphc->droppedRect.left;
2183 case CB_SETDROPPEDWIDTH:
2184 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2185 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2187 case CB_GETDROPPEDCONTROLRECT16:
2188 lParam = (LPARAM)MapSL(lParam);
2192 CBGetDroppedControlRect( lphc, &r );
2193 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2196 case CB_GETDROPPEDCONTROLRECT:
2197 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2199 case CB_GETDROPPEDSTATE16:
2200 case CB_GETDROPPEDSTATE:
2201 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2203 lParam = (LPARAM)MapSL(lParam);
2207 if(message == CB_DIR) message = LB_DIR;
2208 return unicode ? SendMessageW(lphc->hWndLBox, message, wParam, lParam) :
2209 SendMessageA(lphc->hWndLBox, message, wParam, lParam);
2211 case CB_SHOWDROPDOWN16:
2212 case CB_SHOWDROPDOWN:
2213 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2217 if( !(lphc->wState & CBF_DROPPED) )
2221 if( lphc->wState & CBF_DROPPED )
2222 CBRollUp( lphc, FALSE, TRUE );
2227 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2228 case CB_GETCURSEL16:
2230 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2231 case CB_SETCURSEL16:
2232 wParam = (INT)(INT16)wParam;
2235 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2237 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2239 /* no LBN_SELCHANGE in this case, update manually */
2240 if( lphc->wState & CBF_EDIT )
2241 CBUpdateEdit( lphc, (INT)wParam );
2243 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2244 lphc->wState &= ~CBF_SELCHANGE;
2246 case CB_GETLBTEXT16:
2247 wParam = (INT)(INT16)wParam;
2248 lParam = (LPARAM)MapSL(lParam);
2251 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2252 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2253 case CB_GETLBTEXTLEN16:
2254 wParam = (INT)(INT16)wParam;
2256 case CB_GETLBTEXTLEN:
2257 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2258 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2259 case CB_GETITEMDATA16:
2260 wParam = (INT)(INT16)wParam;
2262 case CB_GETITEMDATA:
2263 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2264 case CB_SETITEMDATA16:
2265 wParam = (INT)(INT16)wParam;
2267 case CB_SETITEMDATA:
2268 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2269 case CB_GETEDITSEL16:
2270 wParam = lParam = 0; /* just in case */
2273 /* Edit checks passed parameters itself */
2274 if( lphc->wState & CBF_EDIT )
2275 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2277 case CB_SETEDITSEL16:
2279 if( lphc->wState & CBF_EDIT )
2280 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2281 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2283 case CB_SETEXTENDEDUI16:
2284 case CB_SETEXTENDEDUI:
2285 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2288 lphc->wState |= CBF_EUI;
2289 else lphc->wState &= ~CBF_EUI;
2291 case CB_GETEXTENDEDUI16:
2292 case CB_GETEXTENDEDUI:
2293 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2296 if (message >= WM_USER)
2297 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2298 message - WM_USER, wParam, lParam );
2301 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2302 DefWindowProcA(hwnd, message, wParam, lParam);
2305 /***********************************************************************
2308 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2311 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2313 if (!IsWindow(hwnd)) return 0;
2314 return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2317 /***********************************************************************
2320 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2322 if (!IsWindow(hwnd)) return 0;
2323 return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2326 /*************************************************************************
2327 * GetComboBoxInfo (USER32.@)
2329 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */
2330 PCOMBOBOXINFO pcbi /* [in/out] combo box information */)