4 * Copyright 1997 Alex Korobka
5 * Copyright (c) 2005 by Frank Richter
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
35 #include "wine/debug.h"
36 #include "wine/heap.h"
40 WINE_DEFAULT_DEBUG_CHANNEL(combo
);
42 /* bits in the dwKeyData */
43 #define KEYDATA_ALT 0x2000
44 #define KEYDATA_PREVSTATE 0x4000
47 * Additional combo box definitions
50 #define CB_NOTIFY( lphc, code ) \
51 (SendMessageW((lphc)->owner, WM_COMMAND, \
52 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
54 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
55 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
56 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
57 #define CB_HWND( lphc ) ((lphc)->self)
58 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
60 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
65 static HBITMAP hComboBmp
= 0;
66 static UINT CBitHeight
, CBitWidth
;
69 * Look and feel dependent "constants"
72 #define COMBO_YBORDERGAP 5
73 #define COMBO_XBORDERSIZE() 2
74 #define COMBO_YBORDERSIZE() 2
75 #define COMBO_EDITBUTTONSPACE() 0
76 #define EDIT_CONTROL_PADDING() 1
78 #define ID_CB_LISTBOX 1000
79 #define ID_CB_EDIT 1001
81 static void CBCalcPlacement(HEADCOMBO
*combo
);
82 static void CBResetPos(HEADCOMBO
*combo
);
84 /***********************************************************************
87 * Load combo button bitmap.
89 static BOOL
COMBO_Init(void)
93 if( hComboBmp
) return TRUE
;
94 if( (hDC
= CreateCompatibleDC(0)) )
97 if( (hComboBmp
= LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO
))) )
103 GetObjectW( hComboBmp
, sizeof(bm
), &bm
);
104 CBitHeight
= bm
.bmHeight
;
105 CBitWidth
= bm
.bmWidth
;
107 TRACE("combo bitmap [%i,%i]\n", CBitWidth
, CBitHeight
);
109 hPrevB
= SelectObject( hDC
, hComboBmp
);
110 SetRect( &r
, 0, 0, CBitWidth
, CBitHeight
);
111 InvertRect( hDC
, &r
);
112 SelectObject( hDC
, hPrevB
);
121 /***********************************************************************
124 static LRESULT
COMBO_NCCreate(HWND hwnd
, LONG style
)
128 if (COMBO_Init() && (lphc
= heap_alloc_zero(sizeof(*lphc
))))
131 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)lphc
);
133 /* some braindead apps do try to use scrollbar/border flags */
135 lphc
->dwStyle
= style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
);
136 SetWindowLongW( hwnd
, GWL_STYLE
, style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
) );
139 * We also have to remove the client edge style to make sure
140 * we don't end-up with a non client area.
142 SetWindowLongW( hwnd
, GWL_EXSTYLE
,
143 GetWindowLongW( hwnd
, GWL_EXSTYLE
) & ~WS_EX_CLIENTEDGE
);
145 if( !(style
& (CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
)) )
146 lphc
->dwStyle
|= CBS_HASSTRINGS
;
147 if( !(GetWindowLongW( hwnd
, GWL_EXSTYLE
) & WS_EX_NOPARENTNOTIFY
) )
148 lphc
->wState
|= CBF_NOTIFY
;
150 TRACE("[%p], style = %08x\n", lphc
, lphc
->dwStyle
);
156 /***********************************************************************
159 static LRESULT
COMBO_NCDestroy( HEADCOMBO
*lphc
)
163 TRACE("[%p]: freeing storage\n", lphc
->self
);
165 if ( (CB_GETTYPE(lphc
) != CBS_SIMPLE
) && lphc
->hWndLBox
)
166 DestroyWindow( lphc
->hWndLBox
);
168 SetWindowLongPtrW( lphc
->self
, 0, 0 );
175 static INT
combo_get_text_height(const HEADCOMBO
*combo
)
177 HDC hdc
= GetDC(combo
->self
);
182 prev_font
= SelectObject(hdc
, combo
->hFont
);
184 GetTextMetricsW(hdc
, &tm
);
187 SelectObject(hdc
, prev_font
);
189 ReleaseDC(combo
->self
, hdc
);
191 return tm
.tmHeight
+ 4;
194 /***********************************************************************
195 * CBGetTextAreaHeight
197 * This method will calculate the height of the text area of the
199 * The height of the text area is set in two ways.
200 * It can be set explicitly through a combobox message or through a
201 * WM_MEASUREITEM callback.
202 * If this is not the case, the height is set to font height + 4px
203 * This height was determined through experimentation.
204 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
206 static INT
CBGetTextAreaHeight(HEADCOMBO
*lphc
, BOOL clip_item_height
)
208 INT item_height
, text_height
;
210 if (clip_item_height
&& !CB_OWNERDRAWN(lphc
))
212 text_height
= combo_get_text_height(lphc
);
213 if (lphc
->item_height
< text_height
)
214 lphc
->item_height
= text_height
;
216 item_height
= lphc
->item_height
;
220 * Check the ownerdraw case if we haven't asked the parent the size
223 if ( CB_OWNERDRAWN(lphc
) &&
224 (lphc
->wState
& CBF_MEASUREITEM
) )
226 MEASUREITEMSTRUCT measureItem
;
228 INT originalItemHeight
= item_height
;
229 UINT id
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
232 * We use the client rect for the width of the item.
234 GetClientRect(lphc
->self
, &clientRect
);
236 lphc
->wState
&= ~CBF_MEASUREITEM
;
239 * Send a first one to measure the size of the text area
241 measureItem
.CtlType
= ODT_COMBOBOX
;
242 measureItem
.CtlID
= id
;
243 measureItem
.itemID
= -1;
244 measureItem
.itemWidth
= clientRect
.right
;
245 measureItem
.itemHeight
= item_height
- 6; /* ownerdrawn cb is taller */
246 measureItem
.itemData
= 0;
247 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
248 item_height
= 6 + measureItem
.itemHeight
;
251 * Send a second one in the case of a fixed ownerdraw list to calculate the
252 * size of the list items. (we basically do this on behalf of the listbox)
254 if (lphc
->dwStyle
& CBS_OWNERDRAWFIXED
)
256 measureItem
.CtlType
= ODT_COMBOBOX
;
257 measureItem
.CtlID
= id
;
258 measureItem
.itemID
= 0;
259 measureItem
.itemWidth
= clientRect
.right
;
260 measureItem
.itemHeight
= originalItemHeight
;
261 measureItem
.itemData
= 0;
262 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
263 lphc
->fixedOwnerDrawHeight
= measureItem
.itemHeight
;
267 * Keep the size for the next time
269 lphc
->item_height
= item_height
;
275 /***********************************************************************
278 * The dummy resize is used for listboxes that have a popup to trigger
279 * a re-arranging of the contents of the combobox and the recalculation
280 * of the size of the "real" control window.
282 static void CBForceDummyResize(LPHEADCOMBO lphc
)
287 newComboHeight
= CBGetTextAreaHeight(lphc
, FALSE
) + 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 lphc
->wState
|= CBF_NORESIZE
;
300 SetWindowPos( lphc
->self
,
303 windowRect
.right
- windowRect
.left
,
305 SWP_NOMOVE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
306 lphc
->wState
&= ~CBF_NORESIZE
;
308 CBCalcPlacement(lphc
);
312 /***********************************************************************
315 * Set up component coordinates given valid lphc->RectCombo.
317 static void CBCalcPlacement(HEADCOMBO
*combo
)
319 /* Start with the client rectangle. */
320 GetClientRect(combo
->self
, &combo
->textRect
);
322 /* Remove the borders */
323 InflateRect(&combo
->textRect
, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
325 /* Chop off the bottom part to fit with the height of the text area. */
326 combo
->textRect
.bottom
= combo
->textRect
.top
+ CBGetTextAreaHeight(combo
, FALSE
);
328 /* The button starts the same vertical position as the text area. */
329 combo
->buttonRect
= combo
->textRect
;
331 /* If the combobox is "simple" there is no button. */
332 if (CB_GETTYPE(combo
) == CBS_SIMPLE
)
333 combo
->buttonRect
.left
= combo
->buttonRect
.right
= combo
->buttonRect
.bottom
= 0;
337 * Let's assume the combobox button is the same width as the
339 * size the button horizontally and cut-off the text area.
341 combo
->buttonRect
.left
= combo
->buttonRect
.right
- GetSystemMetrics(SM_CXVSCROLL
);
342 combo
->textRect
.right
= combo
->buttonRect
.left
;
345 /* In the case of a dropdown, there is an additional spacing between the text area and the button. */
346 if (CB_GETTYPE(combo
) == CBS_DROPDOWN
)
347 combo
->textRect
.right
-= COMBO_EDITBUTTONSPACE();
349 /* If we have an edit control, we space it away from the borders slightly. */
350 if (CB_GETTYPE(combo
) != CBS_DROPDOWNLIST
)
351 InflateRect(&combo
->textRect
, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
353 /* Adjust the size of the listbox popup. */
354 if (CB_GETTYPE(combo
) == CBS_SIMPLE
)
356 GetClientRect(combo
->self
, &combo
->droppedRect
);
357 combo
->droppedRect
.top
= combo
->textRect
.bottom
+ COMBO_YBORDERSIZE();
361 /* Make sure the dropped width is as large as the combobox itself. */
362 if (combo
->droppedWidth
< (combo
->buttonRect
.right
+ COMBO_XBORDERSIZE()))
364 combo
->droppedRect
.right
= combo
->droppedRect
.left
+ (combo
->buttonRect
.right
+ COMBO_XBORDERSIZE());
366 /* In the case of a dropdown, the popup listbox is offset to the right. We want to make sure it's flush
367 with the right side of the combobox */
368 if (CB_GETTYPE(combo
) == CBS_DROPDOWN
)
369 combo
->droppedRect
.right
-= COMBO_EDITBUTTONSPACE();
372 combo
->droppedRect
.right
= combo
->droppedRect
.left
+ combo
->droppedWidth
;
375 /* Disallow negative window width */
376 if (combo
->textRect
.right
< combo
->textRect
.left
)
377 combo
->textRect
.right
= combo
->textRect
.left
;
379 TRACE("text %s, button %s, lbox %s.\n", wine_dbgstr_rect(&combo
->textRect
), wine_dbgstr_rect(&combo
->buttonRect
),
380 wine_dbgstr_rect(&combo
->droppedRect
));
383 /***********************************************************************
384 * CBGetDroppedControlRect
386 static void CBGetDroppedControlRect( LPHEADCOMBO lphc
, LPRECT lpRect
)
388 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
389 of the combo box and the lower right corner of the listbox */
391 GetWindowRect(lphc
->self
, lpRect
);
393 lpRect
->right
= lpRect
->left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
394 lpRect
->bottom
= lpRect
->top
+ lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
398 /***********************************************************************
401 static LRESULT
COMBO_Create( HWND hwnd
, LPHEADCOMBO lphc
, HWND hwndParent
, LONG style
)
403 OpenThemeData( hwnd
, WC_COMBOBOXW
);
404 if( !CB_GETTYPE(lphc
) ) lphc
->dwStyle
|= CBS_SIMPLE
;
405 if( CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
) lphc
->wState
|= CBF_EDIT
;
407 lphc
->owner
= hwndParent
;
409 lphc
->droppedWidth
= 0;
411 lphc
->item_height
= combo_get_text_height(lphc
);
414 * The first time we go through, we want to measure the ownerdraw item
416 lphc
->wState
|= CBF_MEASUREITEM
;
419 * Per default the comctl32 version of combo shows up to 30 items
421 lphc
->visibleItems
= 30;
423 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
425 if( lphc
->owner
|| !(style
& WS_VISIBLE
) )
431 * Initialize the dropped rect to the size of the client area of the
432 * control and then, force all the areas of the combobox to be
435 GetClientRect( hwnd
, &lphc
->droppedRect
);
436 CBCalcPlacement(lphc
);
439 * Adjust the position of the popup listbox if it's necessary
441 if ( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
443 lphc
->droppedRect
.top
= lphc
->textRect
.bottom
+ COMBO_YBORDERSIZE();
446 * If it's a dropdown, the listbox is offset
448 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
449 lphc
->droppedRect
.left
+= COMBO_EDITBUTTONSPACE();
451 if (lphc
->droppedRect
.bottom
< lphc
->droppedRect
.top
)
452 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
;
453 if (lphc
->droppedRect
.right
< lphc
->droppedRect
.left
)
454 lphc
->droppedRect
.right
= lphc
->droppedRect
.left
;
455 MapWindowPoints( hwnd
, 0, (LPPOINT
)&lphc
->droppedRect
, 2 );
458 /* create listbox popup */
460 lbeStyle
= (LBS_NOTIFY
| LBS_COMBOBOX
| WS_BORDER
| WS_CLIPSIBLINGS
| WS_CHILD
) |
461 (style
& (WS_VSCROLL
| CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
));
463 if( lphc
->dwStyle
& CBS_SORT
)
464 lbeStyle
|= LBS_SORT
;
465 if( lphc
->dwStyle
& CBS_HASSTRINGS
)
466 lbeStyle
|= LBS_HASSTRINGS
;
467 if( lphc
->dwStyle
& CBS_NOINTEGRALHEIGHT
)
468 lbeStyle
|= LBS_NOINTEGRALHEIGHT
;
469 if( lphc
->dwStyle
& CBS_DISABLENOSCROLL
)
470 lbeStyle
|= LBS_DISABLENOSCROLL
;
472 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
) /* child listbox */
474 lbeStyle
|= WS_VISIBLE
;
477 * In win 95 look n feel, the listbox in the simple combobox has
478 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
480 lbeStyle
&= ~WS_BORDER
;
481 lbeExStyle
|= WS_EX_CLIENTEDGE
;
485 lbeExStyle
|= (WS_EX_TOPMOST
| WS_EX_TOOLWINDOW
);
488 lphc
->hWndLBox
= CreateWindowExW(lbeExStyle
, L
"ComboLBox", NULL
, lbeStyle
,
489 lphc
->droppedRect
.left
, lphc
->droppedRect
.top
, lphc
->droppedRect
.right
- lphc
->droppedRect
.left
,
490 lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
, hwnd
, (HMENU
)ID_CB_LISTBOX
,
491 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), lphc
);
495 lbeStyle
= WS_CHILD
| WS_VISIBLE
| ES_NOHIDESEL
| ES_LEFT
| ES_COMBO
;
497 if( lphc
->wState
& CBF_EDIT
)
499 if( lphc
->dwStyle
& CBS_OEMCONVERT
)
500 lbeStyle
|= ES_OEMCONVERT
;
501 if( lphc
->dwStyle
& CBS_AUTOHSCROLL
)
502 lbeStyle
|= ES_AUTOHSCROLL
;
503 if( lphc
->dwStyle
& CBS_LOWERCASE
)
504 lbeStyle
|= ES_LOWERCASE
;
505 else if( lphc
->dwStyle
& CBS_UPPERCASE
)
506 lbeStyle
|= ES_UPPERCASE
;
508 if (!IsWindowEnabled(hwnd
)) lbeStyle
|= WS_DISABLED
;
510 lphc
->hWndEdit
= CreateWindowExW(0, WC_EDITW
, NULL
, lbeStyle
,
511 lphc
->textRect
.left
, lphc
->textRect
.top
,
512 lphc
->textRect
.right
- lphc
->textRect
.left
,
513 lphc
->textRect
.bottom
- lphc
->textRect
.top
,
514 hwnd
, (HMENU
)ID_CB_EDIT
,
515 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), NULL
);
516 if( !lphc
->hWndEdit
)
522 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
524 /* Now do the trick with parent */
525 SetParent(lphc
->hWndLBox
, HWND_DESKTOP
);
527 * If the combo is a dropdown, we must resize the control
528 * to fit only the text area and button. To do this,
529 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
530 * will take care of setting the height for us.
532 CBForceDummyResize(lphc
);
535 TRACE("init done\n");
538 ERR("edit control failure.\n");
539 } else ERR("listbox failure.\n");
540 } else ERR("no owner for visible combo.\n");
542 /* CreateWindow() will send WM_NCDESTROY to cleanup */
547 /***********************************************************************
550 * Paint combo button (normal, pressed, and disabled states).
552 static void CBPaintButton(HEADCOMBO
*lphc
, HDC hdc
)
554 UINT buttonState
= DFCS_SCROLLCOMBOBOX
;
556 if (IsRectEmpty(&lphc
->buttonRect
))
559 if( lphc
->wState
& CBF_NOREDRAW
)
563 if (lphc
->wState
& CBF_BUTTONDOWN
)
564 buttonState
|= DFCS_PUSHED
;
566 if (CB_DISABLED(lphc
))
567 buttonState
|= DFCS_INACTIVE
;
569 DrawFrameControl(hdc
, &lphc
->buttonRect
, DFC_SCROLL
, buttonState
);
572 /***********************************************************************
573 * COMBO_PrepareColors
575 * This method will sent the appropriate WM_CTLCOLOR message to
576 * prepare and setup the colors for the combo's DC.
578 * It also returns the brush to use for the background.
580 static HBRUSH
COMBO_PrepareColors(
587 * Get the background brush for this control.
589 if (CB_DISABLED(lphc
))
591 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLORSTATIC
,
592 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
595 * We have to change the text color since WM_CTLCOLORSTATIC will
596 * set it to the "enabled" color. This is the same behavior as the
599 SetTextColor(hDC
, GetSysColor(COLOR_GRAYTEXT
));
603 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
604 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLOREDIT
,
605 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
612 hBkgBrush
= GetSysColorBrush(COLOR_WINDOW
);
617 /***********************************************************************
620 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
622 static void CBPaintText(HEADCOMBO
*lphc
, HDC hdc_paint
)
624 RECT rectEdit
= lphc
->textRect
;
630 /* follow Windows combobox that sends a bunch of text
631 * inquiries to its listbox while processing WM_PAINT. */
633 if( (id
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0) ) != LB_ERR
)
635 size
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, id
, 0);
637 FIXME("LB_ERR probably not handled yet\n");
638 if ((pText
= heap_alloc((size
+ 1) * sizeof(WCHAR
))))
640 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
641 size
=SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, id
, (LPARAM
)pText
);
642 pText
[size
] = '\0'; /* just in case */
646 if( lphc
->wState
& CBF_EDIT
)
648 if( CB_HASSTRINGS(lphc
) ) SetWindowTextW( lphc
->hWndEdit
, pText
? pText
: L
"" );
649 if( lphc
->wState
& CBF_FOCUSED
)
650 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, MAXLONG
);
652 else if(!(lphc
->wState
& CBF_NOREDRAW
) && IsWindowVisible( lphc
->self
))
654 /* paint text field ourselves */
655 HDC hdc
= hdc_paint
? hdc_paint
: GetDC(lphc
->self
);
656 UINT itemState
= ODS_COMBOBOXEDIT
;
657 HFONT hPrevFont
= (lphc
->hFont
) ? SelectObject(hdc
, lphc
->hFont
) : 0;
658 HBRUSH hPrevBrush
, hBkgBrush
;
661 * Give ourselves some space.
663 InflateRect( &rectEdit
, -1, -1 );
665 hBkgBrush
= COMBO_PrepareColors( lphc
, hdc
);
666 hPrevBrush
= SelectObject( hdc
, hBkgBrush
);
667 FillRect( hdc
, &rectEdit
, hBkgBrush
);
669 if( CB_OWNERDRAWN(lphc
) )
673 UINT ctlid
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
675 /* setup state for DRAWITEM message. Owner will highlight */
676 if ( (lphc
->wState
& CBF_FOCUSED
) &&
677 !(lphc
->wState
& CBF_DROPPED
) )
678 itemState
|= ODS_SELECTED
| ODS_FOCUS
;
680 if (!IsWindowEnabled(lphc
->self
)) itemState
|= ODS_DISABLED
;
682 dis
.CtlType
= ODT_COMBOBOX
;
684 dis
.hwndItem
= lphc
->self
;
685 dis
.itemAction
= ODA_DRAWENTIRE
;
687 dis
.itemState
= itemState
;
689 dis
.rcItem
= rectEdit
;
690 dis
.itemData
= SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, id
, 0);
693 * Clip the DC and have the parent draw the item.
695 clipRegion
= set_control_clipping( hdc
, &rectEdit
);
697 SendMessageW(lphc
->owner
, WM_DRAWITEM
, ctlid
, (LPARAM
)&dis
);
699 SelectClipRgn( hdc
, clipRegion
);
700 if (clipRegion
) DeleteObject( clipRegion
);
704 if ( (lphc
->wState
& CBF_FOCUSED
) &&
705 !(lphc
->wState
& CBF_DROPPED
) ) {
708 FillRect( hdc
, &rectEdit
, GetSysColorBrush(COLOR_HIGHLIGHT
) );
709 SetBkColor( hdc
, GetSysColor( COLOR_HIGHLIGHT
) );
710 SetTextColor( hdc
, GetSysColor( COLOR_HIGHLIGHTTEXT
) );
716 ETO_OPAQUE
| ETO_CLIPPED
,
718 pText
? pText
: L
"" , size
, NULL
);
720 if(lphc
->wState
& CBF_FOCUSED
&& !(lphc
->wState
& CBF_DROPPED
))
721 DrawFocusRect( hdc
, &rectEdit
);
725 SelectObject(hdc
, hPrevFont
);
728 SelectObject( hdc
, hPrevBrush
);
731 ReleaseDC( lphc
->self
, hdc
);
737 /***********************************************************************
740 static void CBPaintBorder(const HEADCOMBO
*lphc
, HDC hdc
)
744 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
746 GetClientRect(lphc
->self
, &clientRect
);
750 clientRect
= lphc
->textRect
;
752 InflateRect(&clientRect
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
753 InflateRect(&clientRect
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
756 DrawEdge(hdc
, &clientRect
, EDGE_SUNKEN
, BF_RECT
);
759 static LRESULT
COMBO_ThemedPaint(HTHEME theme
, HEADCOMBO
*lphc
, HDC hdc
)
765 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
766 GetClientRect(lphc
->self
, &frame
);
769 frame
= lphc
->textRect
;
770 InflateRect(&frame
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
771 InflateRect(&frame
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
774 DrawThemeBackground(theme
, hdc
, 0, IsWindowEnabled(lphc
->self
) ? CBXS_NORMAL
: CBXS_DISABLED
, &frame
, NULL
);
777 if (!IsRectEmpty(&lphc
->buttonRect
))
779 if (!IsWindowEnabled(lphc
->self
))
780 button_state
= CBXS_DISABLED
;
781 else if (lphc
->wState
& CBF_BUTTONDOWN
)
782 button_state
= CBXS_PRESSED
;
783 else if (lphc
->wState
& CBF_HOT
)
784 button_state
= CBXS_HOT
;
786 button_state
= CBXS_NORMAL
;
787 DrawThemeBackground(theme
, hdc
, CP_DROPDOWNBUTTON
, button_state
, &lphc
->buttonRect
, NULL
);
790 if ((lphc
->dwStyle
& CBS_DROPDOWNLIST
) == CBS_DROPDOWNLIST
)
791 CBPaintText(lphc
, hdc
);
793 InvalidateRect(lphc
->hWndEdit
, NULL
, TRUE
);
798 /***********************************************************************
801 static LRESULT
COMBO_Paint(HEADCOMBO
*lphc
, HDC hdc
)
803 HBRUSH hPrevBrush
, hBkgBrush
;
805 TRACE("hdc=%p\n", hdc
);
808 * Retrieve the background brush and select it in the
811 hBkgBrush
= COMBO_PrepareColors(lphc
, hdc
);
812 hPrevBrush
= SelectObject(hdc
, hBkgBrush
);
813 if (!(lphc
->wState
& CBF_EDIT
))
814 FillRect(hdc
, &lphc
->textRect
, hBkgBrush
);
817 * In non 3.1 look, there is a sunken border on the combobox
819 CBPaintBorder(lphc
, hdc
);
821 CBPaintButton(lphc
, hdc
);
823 /* paint the edit control padding area */
824 if (CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
)
826 RECT rPadEdit
= lphc
->textRect
;
828 InflateRect(&rPadEdit
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
830 FrameRect(hdc
, &rPadEdit
, GetSysColorBrush(COLOR_WINDOW
));
833 if (!(lphc
->wState
& CBF_EDIT
))
834 CBPaintText( lphc
, hdc
);
837 SelectObject( hdc
, hPrevBrush
);
842 /***********************************************************************
845 * Select listbox entry according to the contents of the edit control.
847 static INT
CBUpdateLBox( LPHEADCOMBO lphc
, BOOL bSelect
)
853 length
= SendMessageW( lphc
->hWndEdit
, WM_GETTEXTLENGTH
, 0, 0 );
856 pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
858 TRACE("\t edit text length %i\n", length
);
862 GetWindowTextW( lphc
->hWndEdit
, pText
, length
+ 1);
863 idx
= SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, -1, (LPARAM
)pText
);
867 SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, bSelect
? idx
: -1, 0);
869 /* probably superfluous but Windows sends this too */
870 SendMessageW(lphc
->hWndLBox
, LB_SETCARETINDEX
, idx
< 0 ? 0 : idx
, 0);
871 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, idx
< 0 ? 0 : idx
, 0);
876 /***********************************************************************
879 * Copy a listbox entry to the edit control.
881 static void CBUpdateEdit( LPHEADCOMBO lphc
, INT index
)
886 TRACE("\t %i\n", index
);
888 if( index
>= 0 ) /* got an entry */
890 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, index
, 0);
891 if( length
!= LB_ERR
)
893 if ((pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
))))
894 SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, index
, (LPARAM
)pText
);
898 if( CB_HASSTRINGS(lphc
) )
900 lphc
->wState
|= (CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
901 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, pText
? (LPARAM
)pText
: (LPARAM
)L
"");
902 lphc
->wState
&= ~(CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
905 if( lphc
->wState
& CBF_FOCUSED
)
906 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
911 /***********************************************************************
914 * Show listbox popup.
916 static void CBDropDown( LPHEADCOMBO lphc
)
919 MONITORINFO mon_info
;
924 TRACE("[%p]: drop down\n", lphc
->self
);
926 CB_NOTIFY( lphc
, CBN_DROPDOWN
);
930 lphc
->wState
|= CBF_DROPPED
;
931 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
933 lphc
->droppedIndex
= CBUpdateLBox( lphc
, TRUE
);
935 /* Update edit only if item is in the list */
936 if( !(lphc
->wState
& CBF_CAPTURE
) && lphc
->droppedIndex
>= 0)
937 CBUpdateEdit( lphc
, lphc
->droppedIndex
);
941 lphc
->droppedIndex
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
943 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
,
944 lphc
->droppedIndex
== LB_ERR
? 0 : lphc
->droppedIndex
, 0);
945 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
948 /* now set popup position */
949 GetWindowRect( lphc
->self
, &rect
);
952 * If it's a dropdown, the listbox is offset
954 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
955 rect
.left
+= COMBO_EDITBUTTONSPACE();
957 /* if the dropped height is greater than the total height of the dropped
958 items list, then force the drop down list height to be the total height
959 of the items in the dropped list */
961 /* And Remove any extra space (Best Fit) */
962 nDroppedHeight
= lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
963 /* if listbox length has been set directly by its handle */
964 GetWindowRect(lphc
->hWndLBox
, &r
);
965 if (nDroppedHeight
< r
.bottom
- r
.top
)
966 nDroppedHeight
= r
.bottom
- r
.top
;
967 nItems
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
971 int nIHeight
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, 0, 0);
973 if (lphc
->dwStyle
& CBS_NOINTEGRALHEIGHT
)
979 if (nItems
> lphc
->visibleItems
)
980 nItems
= lphc
->visibleItems
;
981 nDroppedHeight
= nItems
* nIHeight
+ COMBO_YBORDERSIZE();
987 r
.right
= r
.left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
988 r
.bottom
= r
.top
+ nDroppedHeight
;
990 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
991 monitor
= MonitorFromRect( &rect
, MONITOR_DEFAULTTOPRIMARY
);
992 mon_info
.cbSize
= sizeof(mon_info
);
993 GetMonitorInfoW( monitor
, &mon_info
);
995 if (r
.bottom
> mon_info
.rcWork
.bottom
)
997 r
.top
= max( rect
.top
- nDroppedHeight
, mon_info
.rcWork
.top
);
998 r
.bottom
= min( r
.top
+ nDroppedHeight
, mon_info
.rcWork
.bottom
);
1001 SetWindowPos( lphc
->hWndLBox
, HWND_TOPMOST
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
,
1002 SWP_NOACTIVATE
| SWP_SHOWWINDOW
);
1005 if( !(lphc
->wState
& CBF_NOREDRAW
) )
1006 RedrawWindow( lphc
->self
, NULL
, 0, RDW_INVALIDATE
| RDW_ERASE
| RDW_UPDATENOW
);
1008 EnableWindow( lphc
->hWndLBox
, TRUE
);
1009 if (GetCapture() != lphc
->self
)
1010 SetCapture(lphc
->hWndLBox
);
1013 /***********************************************************************
1016 * Hide listbox popup.
1018 static void CBRollUp( LPHEADCOMBO lphc
, BOOL ok
, BOOL bButton
)
1020 HWND hWnd
= lphc
->self
;
1022 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1023 lphc
->self
, ok
, (INT
)(lphc
->wState
& CBF_DROPPED
));
1025 CB_NOTIFY( lphc
, (ok
) ? CBN_SELENDOK
: CBN_SELENDCANCEL
);
1027 if( IsWindow( hWnd
) && CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1030 if( lphc
->wState
& CBF_DROPPED
)
1034 lphc
->wState
&= ~CBF_DROPPED
;
1035 ShowWindow( lphc
->hWndLBox
, SW_HIDE
);
1037 if(GetCapture() == lphc
->hWndLBox
)
1042 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1044 rect
= lphc
->buttonRect
;
1055 rect
= lphc
->textRect
;
1060 if( bButton
&& !(lphc
->wState
& CBF_NOREDRAW
) )
1061 RedrawWindow( hWnd
, &rect
, 0, RDW_INVALIDATE
|
1062 RDW_ERASE
| RDW_UPDATENOW
| RDW_NOCHILDREN
);
1063 CB_NOTIFY( lphc
, CBN_CLOSEUP
);
1068 /***********************************************************************
1071 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1073 BOOL
COMBO_FlipListbox( LPHEADCOMBO lphc
, BOOL ok
, BOOL bRedrawButton
)
1075 if( lphc
->wState
& CBF_DROPPED
)
1077 CBRollUp( lphc
, ok
, bRedrawButton
);
1085 /***********************************************************************
1088 static void CBRepaintButton( LPHEADCOMBO lphc
)
1090 InvalidateRect(lphc
->self
, &lphc
->buttonRect
, TRUE
);
1091 UpdateWindow(lphc
->self
);
1094 /***********************************************************************
1097 static void COMBO_SetFocus( LPHEADCOMBO lphc
)
1099 if( !(lphc
->wState
& CBF_FOCUSED
) )
1101 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1102 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
1104 /* This is wrong. Message sequences seem to indicate that this
1105 is set *after* the notify. */
1106 /* lphc->wState |= CBF_FOCUSED; */
1108 if( !(lphc
->wState
& CBF_EDIT
) )
1109 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1111 CB_NOTIFY( lphc
, CBN_SETFOCUS
);
1112 lphc
->wState
|= CBF_FOCUSED
;
1116 /***********************************************************************
1119 static void COMBO_KillFocus( LPHEADCOMBO lphc
)
1121 HWND hWnd
= lphc
->self
;
1123 if( lphc
->wState
& CBF_FOCUSED
)
1125 CBRollUp( lphc
, FALSE
, TRUE
);
1126 if( IsWindow( hWnd
) )
1128 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1129 SendMessageW(lphc
->hWndLBox
, LB_CARETOFF
, 0, 0);
1131 lphc
->wState
&= ~CBF_FOCUSED
;
1134 if( !(lphc
->wState
& CBF_EDIT
) )
1135 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1137 CB_NOTIFY( lphc
, CBN_KILLFOCUS
);
1142 /***********************************************************************
1145 static LRESULT
COMBO_Command( LPHEADCOMBO lphc
, WPARAM wParam
, HWND hWnd
)
1147 if ( lphc
->wState
& CBF_EDIT
&& lphc
->hWndEdit
== hWnd
)
1149 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1151 switch( HIWORD(wParam
) >> 8 )
1153 case (EN_SETFOCUS
>> 8):
1155 TRACE("[%p]: edit [%p] got focus\n", lphc
->self
, lphc
->hWndEdit
);
1157 COMBO_SetFocus( lphc
);
1160 case (EN_KILLFOCUS
>> 8):
1162 TRACE("[%p]: edit [%p] lost focus\n", lphc
->self
, lphc
->hWndEdit
);
1164 /* NOTE: it seems that Windows' edit control sends an
1165 * undocumented message WM_USER + 0x1B instead of this
1166 * notification (only when it happens to be a part of
1167 * the combo). ?? - AK.
1170 COMBO_KillFocus( lphc
);
1174 case (EN_CHANGE
>> 8):
1176 * In some circumstances (when the selection of the combobox
1177 * is changed for example) we don't want the EN_CHANGE notification
1178 * to be forwarded to the parent of the combobox. This code
1179 * checks a flag that is set in these occasions and ignores the
1182 if (lphc
->wState
& CBF_NOLBSELECT
)
1184 lphc
->wState
&= ~CBF_NOLBSELECT
;
1188 CBUpdateLBox( lphc
, lphc
->wState
& CBF_DROPPED
);
1191 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1192 CB_NOTIFY( lphc
, CBN_EDITCHANGE
);
1195 case (EN_UPDATE
>> 8):
1196 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1197 CB_NOTIFY( lphc
, CBN_EDITUPDATE
);
1200 case (EN_ERRSPACE
>> 8):
1201 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1204 else if( lphc
->hWndLBox
== hWnd
)
1206 switch( (short)HIWORD(wParam
) )
1209 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1213 CB_NOTIFY( lphc
, CBN_DBLCLK
);
1219 TRACE("[%p]: lbox selection change [%x]\n", lphc
->self
, lphc
->wState
);
1221 /* do not roll up if selection is being tracked
1222 * by arrow keys in the dropdown listbox */
1223 if (!(lphc
->wState
& CBF_NOROLLUP
))
1225 CBRollUp( lphc
, (HIWORD(wParam
) == LBN_SELCHANGE
), TRUE
);
1227 else lphc
->wState
&= ~CBF_NOROLLUP
;
1229 CB_NOTIFY( lphc
, CBN_SELCHANGE
);
1231 if( HIWORD(wParam
) == LBN_SELCHANGE
)
1233 if( lphc
->wState
& CBF_EDIT
)
1234 lphc
->wState
|= CBF_NOLBSELECT
;
1235 CBPaintText( lphc
, NULL
);
1241 /* nothing to do here since ComboLBox always resets the focus to its
1242 * combo/edit counterpart */
1249 /***********************************************************************
1252 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1254 static LRESULT
COMBO_ItemOp( LPHEADCOMBO lphc
, UINT msg
, LPARAM lParam
)
1256 HWND hWnd
= lphc
->self
;
1257 UINT id
= (UINT
)GetWindowLongPtrW( hWnd
, GWLP_ID
);
1259 TRACE("[%p]: ownerdraw op %04x\n", lphc
->self
, msg
);
1265 DELETEITEMSTRUCT
*lpIS
= (DELETEITEMSTRUCT
*)lParam
;
1266 lpIS
->CtlType
= ODT_COMBOBOX
;
1268 lpIS
->hwndItem
= hWnd
;
1273 DRAWITEMSTRUCT
*lpIS
= (DRAWITEMSTRUCT
*)lParam
;
1274 lpIS
->CtlType
= ODT_COMBOBOX
;
1276 lpIS
->hwndItem
= hWnd
;
1279 case WM_COMPAREITEM
:
1281 COMPAREITEMSTRUCT
*lpIS
= (COMPAREITEMSTRUCT
*)lParam
;
1282 lpIS
->CtlType
= ODT_COMBOBOX
;
1284 lpIS
->hwndItem
= hWnd
;
1287 case WM_MEASUREITEM
:
1289 MEASUREITEMSTRUCT
*lpIS
= (MEASUREITEMSTRUCT
*)lParam
;
1290 lpIS
->CtlType
= ODT_COMBOBOX
;
1295 return SendMessageW(lphc
->owner
, msg
, id
, lParam
);
1299 /***********************************************************************
1302 static LRESULT
COMBO_GetText( HEADCOMBO
*lphc
, INT count
, LPWSTR buf
)
1306 if( lphc
->wState
& CBF_EDIT
)
1307 return SendMessageW( lphc
->hWndEdit
, WM_GETTEXT
, count
, (LPARAM
)buf
);
1309 /* get it from the listbox */
1311 if (!count
|| !buf
) return 0;
1312 if( lphc
->hWndLBox
)
1314 INT idx
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1315 if (idx
== LB_ERR
) goto error
;
1316 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, idx
, 0 );
1317 if (length
== LB_ERR
) goto error
;
1319 /* 'length' is without the terminating character */
1320 if (length
>= count
)
1322 WCHAR
*lpBuffer
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
1323 if (!lpBuffer
) goto error
;
1324 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)lpBuffer
);
1326 /* truncate if buffer is too short */
1327 if (length
!= LB_ERR
)
1329 lstrcpynW( buf
, lpBuffer
, count
);
1332 heap_free( lpBuffer
);
1334 else length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)buf
);
1336 if (length
== LB_ERR
) return 0;
1340 error
: /* error - truncate string, return zero */
1345 /***********************************************************************
1348 * This function sets window positions according to the updated
1349 * component placement struct.
1351 static void CBResetPos(HEADCOMBO
*combo
)
1353 BOOL drop
= CB_GETTYPE(combo
) != CBS_SIMPLE
;
1355 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1356 * sizing messages */
1357 if (combo
->wState
& CBF_EDIT
)
1358 SetWindowPos(combo
->hWndEdit
, 0, combo
->textRect
.left
, combo
->textRect
.top
,
1359 combo
->textRect
.right
- combo
->textRect
.left
,
1360 combo
->textRect
.bottom
- combo
->textRect
.top
,
1361 SWP_NOZORDER
| SWP_NOACTIVATE
| (drop
? SWP_NOREDRAW
: 0));
1363 SetWindowPos(combo
->hWndLBox
, 0, combo
->droppedRect
.left
, combo
->droppedRect
.top
,
1364 combo
->droppedRect
.right
- combo
->droppedRect
.left
,
1365 combo
->droppedRect
.bottom
- combo
->droppedRect
.top
,
1366 SWP_NOACTIVATE
| SWP_NOZORDER
| (drop
? SWP_NOREDRAW
: 0));
1370 if (combo
->wState
& CBF_DROPPED
)
1372 combo
->wState
&= ~CBF_DROPPED
;
1373 ShowWindow(combo
->hWndLBox
, SW_HIDE
);
1376 if (!(combo
->wState
& CBF_NOREDRAW
))
1377 RedrawWindow(combo
->self
, NULL
, 0, RDW_INVALIDATE
| RDW_ERASE
| RDW_UPDATENOW
);
1382 /***********************************************************************
1385 static void COMBO_Size( HEADCOMBO
*lphc
)
1387 if (!lphc
->hWndLBox
|| (lphc
->wState
& CBF_NORESIZE
))
1391 * Those controls are always the same height. So we have to make sure
1392 * they are not resized to another value.
1394 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1396 int newComboHeight
, curComboHeight
, curComboWidth
;
1399 GetWindowRect(lphc
->self
, &rc
);
1400 curComboHeight
= rc
.bottom
- rc
.top
;
1401 curComboWidth
= rc
.right
- rc
.left
;
1402 newComboHeight
= CBGetTextAreaHeight(lphc
, TRUE
) + 2*COMBO_YBORDERSIZE();
1405 * Resizing a combobox has another side effect, it resizes the dropped
1406 * rectangle as well. However, it does it only if the new height for the
1407 * combobox is more than the height it should have. In other words,
1408 * if the application resizing the combobox only had the intention to resize
1409 * the actual control, for example, to do the layout of a dialog that is
1410 * resized, the height of the dropdown is not changed.
1412 if( curComboHeight
> newComboHeight
)
1414 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%ld, oldDropTop=%ld\n",
1415 curComboHeight
, newComboHeight
, lphc
->droppedRect
.bottom
,
1416 lphc
->droppedRect
.top
);
1417 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
+ curComboHeight
- newComboHeight
;
1420 * Restore original height
1422 if (curComboHeight
!= newComboHeight
)
1424 lphc
->wState
|= CBF_NORESIZE
;
1425 SetWindowPos(lphc
->self
, 0, 0, 0, curComboWidth
, newComboHeight
,
1426 SWP_NOZORDER
| SWP_NOMOVE
| SWP_NOACTIVATE
| SWP_NOREDRAW
);
1427 lphc
->wState
&= ~CBF_NORESIZE
;
1431 CBCalcPlacement(lphc
);
1437 /***********************************************************************
1440 static void COMBO_Font( LPHEADCOMBO lphc
, HFONT hFont
, BOOL bRedraw
)
1442 lphc
->hFont
= hFont
;
1443 lphc
->item_height
= combo_get_text_height(lphc
);
1446 * Propagate to owned windows.
1448 if( lphc
->wState
& CBF_EDIT
)
1449 SendMessageW(lphc
->hWndEdit
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1450 SendMessageW(lphc
->hWndLBox
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1453 * Redo the layout of the control.
1455 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1457 CBCalcPlacement(lphc
);
1463 CBForceDummyResize(lphc
);
1468 /***********************************************************************
1469 * COMBO_SetItemHeight
1471 static LRESULT
COMBO_SetItemHeight( LPHEADCOMBO lphc
, INT index
, INT height
)
1473 LRESULT lRet
= CB_ERR
;
1475 if( index
== -1 ) /* set text field height */
1477 if( height
< 32768 )
1479 lphc
->item_height
= height
+ 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1482 * Redo the layout of the control.
1484 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1486 CBCalcPlacement(lphc
);
1492 CBForceDummyResize(lphc
);
1498 else if ( CB_OWNERDRAWN(lphc
) ) /* set listbox item height */
1499 lRet
= SendMessageW(lphc
->hWndLBox
, LB_SETITEMHEIGHT
, index
, height
);
1503 /***********************************************************************
1504 * COMBO_SelectString
1506 static LRESULT
COMBO_SelectString( LPHEADCOMBO lphc
, INT start
, LPARAM pText
)
1508 INT index
= SendMessageW(lphc
->hWndLBox
, LB_SELECTSTRING
, start
, pText
);
1511 if( lphc
->wState
& CBF_EDIT
)
1512 CBUpdateEdit( lphc
, index
);
1515 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1518 return (LRESULT
)index
;
1521 /***********************************************************************
1524 static void COMBO_LButtonDown( LPHEADCOMBO lphc
, LPARAM lParam
)
1528 HWND hWnd
= lphc
->self
;
1530 pt
.x
= (short)LOWORD(lParam
);
1531 pt
.y
= (short)HIWORD(lParam
);
1532 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1534 if( (CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
) ||
1535 (bButton
&& (CB_GETTYPE(lphc
) == CBS_DROPDOWN
)) )
1537 lphc
->wState
|= CBF_BUTTONDOWN
;
1538 if( lphc
->wState
& CBF_DROPPED
)
1540 /* got a click to cancel selection */
1542 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1543 CBRollUp( lphc
, TRUE
, FALSE
);
1544 if( !IsWindow( hWnd
) ) return;
1546 if( lphc
->wState
& CBF_CAPTURE
)
1548 lphc
->wState
&= ~CBF_CAPTURE
;
1554 /* drop down the listbox and start tracking */
1556 lphc
->wState
|= CBF_CAPTURE
;
1560 if( bButton
) CBRepaintButton( lphc
);
1564 /***********************************************************************
1567 * Release capture and stop tracking if needed.
1569 static void COMBO_LButtonUp( LPHEADCOMBO lphc
)
1571 if( lphc
->wState
& CBF_CAPTURE
)
1573 lphc
->wState
&= ~CBF_CAPTURE
;
1574 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1576 INT index
= CBUpdateLBox( lphc
, TRUE
);
1577 /* Update edit only if item is in the list */
1580 lphc
->wState
|= CBF_NOLBSELECT
;
1581 CBUpdateEdit( lphc
, index
);
1582 lphc
->wState
&= ~CBF_NOLBSELECT
;
1586 SetCapture(lphc
->hWndLBox
);
1589 if( lphc
->wState
& CBF_BUTTONDOWN
)
1591 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1592 CBRepaintButton( lphc
);
1596 /***********************************************************************
1599 * Two things to do - track combo button and release capture when
1600 * pointer goes into the listbox.
1602 static void COMBO_MouseMove( LPHEADCOMBO lphc
, WPARAM wParam
, LPARAM lParam
)
1607 pt
.x
= (short)LOWORD(lParam
);
1608 pt
.y
= (short)HIWORD(lParam
);
1610 if( lphc
->wState
& CBF_BUTTONDOWN
)
1614 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1618 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1619 CBRepaintButton( lphc
);
1623 GetClientRect( lphc
->hWndLBox
, &lbRect
);
1624 MapWindowPoints( lphc
->self
, lphc
->hWndLBox
, &pt
, 1 );
1625 if( PtInRect(&lbRect
, pt
) )
1627 lphc
->wState
&= ~CBF_CAPTURE
;
1629 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
) CBUpdateLBox( lphc
, TRUE
);
1631 /* hand over pointer tracking */
1632 SendMessageW(lphc
->hWndLBox
, WM_LBUTTONDOWN
, wParam
, lParam
);
1636 static LRESULT
COMBO_MouseLeave(LPHEADCOMBO lphc
)
1638 lphc
->wState
&= ~CBF_HOT
;
1639 RedrawWindow(lphc
->self
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1643 static LRESULT
COMBO_GetComboBoxInfo(const HEADCOMBO
*lphc
, COMBOBOXINFO
*pcbi
)
1645 if (!pcbi
|| (pcbi
->cbSize
< sizeof(COMBOBOXINFO
)))
1648 pcbi
->rcItem
= lphc
->textRect
;
1649 pcbi
->rcButton
= lphc
->buttonRect
;
1650 pcbi
->stateButton
= 0;
1651 if (lphc
->wState
& CBF_BUTTONDOWN
)
1652 pcbi
->stateButton
|= STATE_SYSTEM_PRESSED
;
1653 if (IsRectEmpty(&lphc
->buttonRect
))
1654 pcbi
->stateButton
|= STATE_SYSTEM_INVISIBLE
;
1655 pcbi
->hwndCombo
= lphc
->self
;
1656 pcbi
->hwndItem
= lphc
->hWndEdit
;
1657 pcbi
->hwndList
= lphc
->hWndLBox
;
1661 static LRESULT CALLBACK
COMBO_WindowProc( HWND hwnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1663 HEADCOMBO
*lphc
= (HEADCOMBO
*)GetWindowLongPtrW( hwnd
, 0 );
1666 TRACE("[%p]: msg %#x, wp %Ix, lp %Ix\n", hwnd
, message
, wParam
, lParam
);
1668 if (!IsWindow(hwnd
)) return 0;
1670 if (lphc
|| message
== WM_NCCREATE
)
1675 LONG style
= ((CREATESTRUCTW
*)lParam
)->style
;
1676 return COMBO_NCCreate(hwnd
, style
);
1680 COMBO_NCDestroy(lphc
);
1681 break;/* -> DefWindowProc */
1688 hwndParent
= ((CREATESTRUCTW
*)lParam
)->hwndParent
;
1689 style
= ((CREATESTRUCTW
*)lParam
)->style
;
1690 return COMBO_Create(hwnd
, lphc
, hwndParent
, style
);
1694 theme
= GetWindowTheme( hwnd
);
1695 CloseThemeData( theme
);
1698 case WM_THEMECHANGED
:
1699 theme
= GetWindowTheme( hwnd
);
1700 CloseThemeData( theme
);
1701 OpenThemeData( hwnd
, WC_COMBOBOXW
);
1702 InvalidateRect( hwnd
, NULL
, TRUE
);
1705 case WM_PRINTCLIENT
:
1712 hdc
= wParam
? (HDC
)wParam
: BeginPaint(hwnd
, &ps
);
1714 if (hdc
&& !(lphc
->wState
& CBF_NOREDRAW
))
1716 HTHEME theme
= GetWindowTheme(hwnd
);
1719 ret
= COMBO_ThemedPaint(theme
, lphc
, hdc
);
1721 ret
= COMBO_Paint(lphc
, hdc
);
1725 EndPaint(hwnd
, &ps
);
1730 /* do all painting in WM_PAINT like Windows does */
1735 LRESULT result
= DLGC_WANTARROWS
| DLGC_WANTCHARS
;
1736 if (lParam
&& (((LPMSG
)lParam
)->message
== WM_KEYDOWN
))
1738 int vk
= (int)((LPMSG
)lParam
)->wParam
;
1740 if ((vk
== VK_RETURN
|| vk
== VK_ESCAPE
) && (lphc
->wState
& CBF_DROPPED
))
1741 result
|= DLGC_WANTMESSAGE
;
1751 COMBO_Font( lphc
, (HFONT
)wParam
, (BOOL
)lParam
);
1755 return (LRESULT
)lphc
->hFont
;
1758 if (lphc
->wState
& CBF_EDIT
)
1760 SetFocus( lphc
->hWndEdit
);
1761 /* The first time focus is received, select all the text */
1762 if (!(lphc
->wState
& CBF_BEENFOCUSED
))
1764 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
1765 lphc
->wState
|= CBF_BEENFOCUSED
;
1769 COMBO_SetFocus( lphc
);
1774 HWND hwndFocus
= (HWND
)wParam
;
1775 if (!hwndFocus
|| (hwndFocus
!= lphc
->hWndEdit
&& hwndFocus
!= lphc
->hWndLBox
))
1776 COMBO_KillFocus( lphc
);
1781 return COMBO_Command( lphc
, wParam
, (HWND
)lParam
);
1784 return COMBO_GetText( lphc
, wParam
, (LPWSTR
)lParam
);
1787 case WM_GETTEXTLENGTH
:
1789 if ((message
== WM_GETTEXTLENGTH
) && !ISWIN31
&& !(lphc
->wState
& CBF_EDIT
))
1791 int j
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1792 if (j
== -1) return 0;
1793 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, j
, 0);
1795 else if ( lphc
->wState
& CBF_EDIT
)
1798 lphc
->wState
|= CBF_NOEDITNOTIFY
;
1799 ret
= SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1800 lphc
->wState
&= ~CBF_NOEDITNOTIFY
;
1809 if (lphc
->wState
& CBF_EDIT
)
1810 return SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1815 case WM_COMPAREITEM
:
1816 case WM_MEASUREITEM
:
1817 return COMBO_ItemOp(lphc
, message
, lParam
);
1820 if (lphc
->wState
& CBF_EDIT
)
1821 EnableWindow( lphc
->hWndEdit
, (BOOL
)wParam
);
1822 EnableWindow( lphc
->hWndLBox
, (BOOL
)wParam
);
1824 /* Force the control to repaint when the enabled state changes. */
1825 InvalidateRect(lphc
->self
, NULL
, TRUE
);
1830 lphc
->wState
&= ~CBF_NOREDRAW
;
1832 lphc
->wState
|= CBF_NOREDRAW
;
1834 if ( lphc
->wState
& CBF_EDIT
)
1835 SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1836 SendMessageW(lphc
->hWndLBox
, message
, wParam
, lParam
);
1840 if ( KEYDATA_ALT
& HIWORD(lParam
) )
1841 if( wParam
== VK_UP
|| wParam
== VK_DOWN
)
1842 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1846 if ((wParam
== VK_RETURN
|| wParam
== VK_ESCAPE
) &&
1847 (lphc
->wState
& CBF_DROPPED
))
1849 CBRollUp( lphc
, wParam
== VK_RETURN
, FALSE
);
1852 else if ((wParam
== VK_F4
) && !(lphc
->wState
& CBF_EUI
))
1854 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1863 if ( lphc
->wState
& CBF_EDIT
)
1864 hwndTarget
= lphc
->hWndEdit
;
1866 hwndTarget
= lphc
->hWndLBox
;
1868 return SendMessageW(hwndTarget
, message
, wParam
, lParam
);
1871 case WM_LBUTTONDOWN
:
1872 if ( !(lphc
->wState
& CBF_FOCUSED
) ) SetFocus( lphc
->self
);
1873 if ( lphc
->wState
& CBF_FOCUSED
) COMBO_LButtonDown( lphc
, lParam
);
1877 COMBO_LButtonUp( lphc
);
1881 if (!IsRectEmpty(&lphc
->buttonRect
))
1883 TRACKMOUSEEVENT event
;
1886 pt
.x
= (short)LOWORD(lParam
);
1887 pt
.y
= (short)HIWORD(lParam
);
1889 if (PtInRect(&lphc
->buttonRect
, pt
))
1891 if (!(lphc
->wState
& CBF_HOT
))
1893 lphc
->wState
|= CBF_HOT
;
1894 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1896 event
.cbSize
= sizeof(TRACKMOUSEEVENT
);
1897 event
.dwFlags
= TME_QUERY
;
1898 if (!TrackMouseEvent(&event
) || event
.hwndTrack
!= hwnd
|| !(event
.dwFlags
& TME_LEAVE
))
1900 event
.hwndTrack
= hwnd
;
1901 event
.dwFlags
= TME_LEAVE
;
1902 TrackMouseEvent(&event
);
1906 else if (lphc
->wState
& CBF_HOT
)
1908 lphc
->wState
&= ~CBF_HOT
;
1909 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1913 if ( lphc
->wState
& CBF_CAPTURE
)
1914 COMBO_MouseMove( lphc
, wParam
, lParam
);
1918 return COMBO_MouseLeave(lphc
);
1921 if (wParam
& (MK_SHIFT
| MK_CONTROL
))
1922 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
1924 if (GET_WHEEL_DELTA_WPARAM(wParam
) > 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_UP
, 0);
1925 if (GET_WHEEL_DELTA_WPARAM(wParam
) < 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_DOWN
, 0);
1929 case WM_CTLCOLORMSGBOX
:
1930 case WM_CTLCOLOREDIT
:
1931 case WM_CTLCOLORLISTBOX
:
1932 case WM_CTLCOLORBTN
:
1933 case WM_CTLCOLORDLG
:
1934 case WM_CTLCOLORSCROLLBAR
:
1935 case WM_CTLCOLORSTATIC
:
1936 return SendMessageW(lphc
->owner
, message
, wParam
, lParam
);
1938 /* Combo messages */
1940 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1941 CharLowerW((LPWSTR
)lParam
);
1942 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1943 CharUpperW((LPWSTR
)lParam
);
1944 return SendMessageW(lphc
->hWndLBox
, LB_ADDSTRING
, 0, lParam
);
1946 case CB_INSERTSTRING
:
1947 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1948 CharLowerW((LPWSTR
)lParam
);
1949 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1950 CharUpperW((LPWSTR
)lParam
);
1951 return SendMessageW(lphc
->hWndLBox
, LB_INSERTSTRING
, wParam
, lParam
);
1953 case CB_DELETESTRING
:
1954 return SendMessageW(lphc
->hWndLBox
, LB_DELETESTRING
, wParam
, 0);
1956 case CB_SELECTSTRING
:
1957 return COMBO_SelectString(lphc
, (INT
)wParam
, lParam
);
1960 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, wParam
, lParam
);
1962 case CB_FINDSTRINGEXACT
:
1963 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRINGEXACT
, wParam
, lParam
);
1965 case CB_SETITEMHEIGHT
:
1966 return COMBO_SetItemHeight( lphc
, (INT
)wParam
, (INT
)lParam
);
1968 case CB_GETITEMHEIGHT
:
1969 if ((INT
)wParam
>= 0) /* listbox item */
1970 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, wParam
, 0);
1971 return CBGetTextAreaHeight(lphc
, FALSE
);
1973 case CB_RESETCONTENT
:
1974 SendMessageW(lphc
->hWndLBox
, LB_RESETCONTENT
, 0, 0);
1976 if ((lphc
->wState
& CBF_EDIT
) && CB_HASSTRINGS(lphc
))
1977 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, (LPARAM
)L
"");
1979 InvalidateRect(lphc
->self
, NULL
, TRUE
);
1982 case CB_INITSTORAGE
:
1983 return SendMessageW(lphc
->hWndLBox
, LB_INITSTORAGE
, wParam
, lParam
);
1985 case CB_GETHORIZONTALEXTENT
:
1986 return SendMessageW(lphc
->hWndLBox
, LB_GETHORIZONTALEXTENT
, 0, 0);
1988 case CB_SETHORIZONTALEXTENT
:
1989 return SendMessageW(lphc
->hWndLBox
, LB_SETHORIZONTALEXTENT
, wParam
, 0);
1991 case CB_GETTOPINDEX
:
1992 return SendMessageW(lphc
->hWndLBox
, LB_GETTOPINDEX
, 0, 0);
1995 return SendMessageW(lphc
->hWndLBox
, LB_GETLOCALE
, 0, 0);
1998 return SendMessageW(lphc
->hWndLBox
, LB_SETLOCALE
, wParam
, 0);
2000 case CB_SETDROPPEDWIDTH
:
2001 if ((CB_GETTYPE(lphc
) == CBS_SIMPLE
) || (INT
)wParam
>= 32768)
2004 /* new value must be higher than combobox width */
2005 if ((INT
)wParam
>= lphc
->droppedRect
.right
- lphc
->droppedRect
.left
)
2006 lphc
->droppedWidth
= wParam
;
2008 lphc
->droppedWidth
= 0;
2010 /* recalculate the combobox area */
2011 CBCalcPlacement(lphc
);
2014 case CB_GETDROPPEDWIDTH
:
2015 if (lphc
->droppedWidth
)
2016 return lphc
->droppedWidth
;
2017 return lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
2019 case CB_GETDROPPEDCONTROLRECT
:
2021 CBGetDroppedControlRect(lphc
, (LPRECT
)lParam
);
2024 case CB_GETDROPPEDSTATE
:
2025 return (lphc
->wState
& CBF_DROPPED
) != 0;
2028 return SendMessageW(lphc
->hWndLBox
, LB_DIR
, wParam
, lParam
);
2030 case CB_SHOWDROPDOWN
:
2031 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
2035 if (!(lphc
->wState
& CBF_DROPPED
))
2038 else if (lphc
->wState
& CBF_DROPPED
)
2039 CBRollUp( lphc
, FALSE
, TRUE
);
2044 return SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
2047 return SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
2050 lParam
= SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, wParam
, 0);
2052 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, wParam
, 0);
2054 /* no LBN_SELCHANGE in this case, update manually */
2055 CBPaintText(lphc
, NULL
);
2056 lphc
->wState
&= ~CBF_SELCHANGE
;
2060 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, wParam
, lParam
);
2062 case CB_GETLBTEXTLEN
:
2063 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, wParam
, 0);
2065 case CB_GETITEMDATA
:
2066 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, wParam
, 0);
2068 case CB_SETITEMDATA
:
2069 return SendMessageW(lphc
->hWndLBox
, LB_SETITEMDATA
, wParam
, lParam
);
2072 /* Edit checks passed parameters itself */
2073 if (lphc
->wState
& CBF_EDIT
)
2074 return SendMessageW(lphc
->hWndEdit
, EM_GETSEL
, wParam
, lParam
);
2078 if (lphc
->wState
& CBF_EDIT
)
2079 return SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, (INT
)(SHORT
)LOWORD(lParam
), (INT
)(SHORT
)HIWORD(lParam
) );
2082 case CB_SETEXTENDEDUI
:
2083 if (CB_GETTYPE(lphc
) == CBS_SIMPLE
)
2086 lphc
->wState
|= CBF_EUI
;
2088 lphc
->wState
&= ~CBF_EUI
;
2091 case CB_GETEXTENDEDUI
:
2092 return (lphc
->wState
& CBF_EUI
) != 0;
2094 case CB_GETCOMBOBOXINFO
:
2095 return COMBO_GetComboBoxInfo(lphc
, (COMBOBOXINFO
*)lParam
);
2098 if (lphc
->wState
& CBF_EDIT
)
2099 return SendMessageW(lphc
->hWndEdit
, EM_LIMITTEXT
, wParam
, lParam
);
2102 case CB_GETMINVISIBLE
:
2103 return lphc
->visibleItems
;
2105 case CB_SETMINVISIBLE
:
2106 lphc
->visibleItems
= (INT
)wParam
;
2110 if (message
>= WM_USER
)
2111 WARN("unknown msg WM_USER+%04x, wp %Ix, lp %Ix\n", message
- WM_USER
, wParam
, lParam
);
2115 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
2118 void COMBO_Register(void)
2122 memset(&wndClass
, 0, sizeof(wndClass
));
2123 wndClass
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
2124 wndClass
.lpfnWndProc
= COMBO_WindowProc
;
2125 wndClass
.cbClsExtra
= 0;
2126 wndClass
.cbWndExtra
= sizeof(HEADCOMBO
*);
2127 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
2128 wndClass
.hbrBackground
= NULL
;
2129 wndClass
.lpszClassName
= WC_COMBOBOXW
;
2130 RegisterClassW(&wndClass
);