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/unicode.h"
36 #include "wine/debug.h"
37 #include "wine/heap.h"
41 WINE_DEFAULT_DEBUG_CHANNEL(combo
);
43 /* bits in the dwKeyData */
44 #define KEYDATA_ALT 0x2000
45 #define KEYDATA_PREVSTATE 0x4000
48 * Additional combo box definitions
51 #define CB_NOTIFY( lphc, code ) \
52 (SendMessageW((lphc)->owner, WM_COMMAND, \
53 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
55 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
56 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
57 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
58 #define CB_HWND( lphc ) ((lphc)->self)
59 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
61 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
66 static HBITMAP hComboBmp
= 0;
67 static UINT CBitHeight
, CBitWidth
;
70 * Look and feel dependent "constants"
73 #define COMBO_YBORDERGAP 5
74 #define COMBO_XBORDERSIZE() 2
75 #define COMBO_YBORDERSIZE() 2
76 #define COMBO_EDITBUTTONSPACE() 0
77 #define EDIT_CONTROL_PADDING() 1
79 #define ID_CB_LISTBOX 1000
80 #define ID_CB_EDIT 1001
82 /***********************************************************************
85 * Load combo button bitmap.
87 static BOOL
COMBO_Init(void)
91 if( hComboBmp
) return TRUE
;
92 if( (hDC
= CreateCompatibleDC(0)) )
95 if( (hComboBmp
= LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO
))) )
101 GetObjectW( hComboBmp
, sizeof(bm
), &bm
);
102 CBitHeight
= bm
.bmHeight
;
103 CBitWidth
= bm
.bmWidth
;
105 TRACE("combo bitmap [%i,%i]\n", CBitWidth
, CBitHeight
);
107 hPrevB
= SelectObject( hDC
, hComboBmp
);
108 SetRect( &r
, 0, 0, CBitWidth
, CBitHeight
);
109 InvertRect( hDC
, &r
);
110 SelectObject( hDC
, hPrevB
);
119 /***********************************************************************
122 static LRESULT
COMBO_NCCreate(HWND hwnd
, LONG style
)
126 if (COMBO_Init() && (lphc
= heap_alloc_zero(sizeof(*lphc
))))
129 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)lphc
);
131 /* some braindead apps do try to use scrollbar/border flags */
133 lphc
->dwStyle
= style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
);
134 SetWindowLongW( hwnd
, GWL_STYLE
, style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
) );
137 * We also have to remove the client edge style to make sure
138 * we don't end-up with a non client area.
140 SetWindowLongW( hwnd
, GWL_EXSTYLE
,
141 GetWindowLongW( hwnd
, GWL_EXSTYLE
) & ~WS_EX_CLIENTEDGE
);
143 if( !(style
& (CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
)) )
144 lphc
->dwStyle
|= CBS_HASSTRINGS
;
145 if( !(GetWindowLongW( hwnd
, GWL_EXSTYLE
) & WS_EX_NOPARENTNOTIFY
) )
146 lphc
->wState
|= CBF_NOTIFY
;
148 TRACE("[%p], style = %08x\n", lphc
, lphc
->dwStyle
);
154 /***********************************************************************
157 static LRESULT
COMBO_NCDestroy( HEADCOMBO
*lphc
)
161 TRACE("[%p]: freeing storage\n", lphc
->self
);
163 if ( (CB_GETTYPE(lphc
) != CBS_SIMPLE
) && lphc
->hWndLBox
)
164 DestroyWindow( lphc
->hWndLBox
);
166 SetWindowLongPtrW( lphc
->self
, 0, 0 );
173 /***********************************************************************
174 * CBGetTextAreaHeight
176 * This method will calculate the height of the text area of the
178 * The height of the text area is set in two ways.
179 * It can be set explicitly through a combobox message or through a
180 * WM_MEASUREITEM callback.
181 * If this is not the case, the height is set to font height + 4px
182 * This height was determined through experimentation.
183 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
185 static INT
CBGetTextAreaHeight(
191 if( lphc
->editHeight
) /* explicitly set height */
193 iTextItemHeight
= lphc
->editHeight
;
198 HDC hDC
= GetDC(hwnd
);
203 hPrevFont
= SelectObject( hDC
, lphc
->hFont
);
205 GetTextMetricsW(hDC
, &tm
);
207 baseUnitY
= tm
.tmHeight
;
210 SelectObject( hDC
, hPrevFont
);
212 ReleaseDC(hwnd
, hDC
);
214 iTextItemHeight
= baseUnitY
+ 4;
218 * Check the ownerdraw case if we haven't asked the parent the size
221 if ( CB_OWNERDRAWN(lphc
) &&
222 (lphc
->wState
& CBF_MEASUREITEM
) )
224 MEASUREITEMSTRUCT measureItem
;
226 INT originalItemHeight
= iTextItemHeight
;
227 UINT id
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
230 * We use the client rect for the width of the item.
232 GetClientRect(hwnd
, &clientRect
);
234 lphc
->wState
&= ~CBF_MEASUREITEM
;
237 * Send a first one to measure the size of the text area
239 measureItem
.CtlType
= ODT_COMBOBOX
;
240 measureItem
.CtlID
= id
;
241 measureItem
.itemID
= -1;
242 measureItem
.itemWidth
= clientRect
.right
;
243 measureItem
.itemHeight
= iTextItemHeight
- 6; /* ownerdrawn cb is taller */
244 measureItem
.itemData
= 0;
245 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
246 iTextItemHeight
= 6 + measureItem
.itemHeight
;
249 * Send a second one in the case of a fixed ownerdraw list to calculate the
250 * size of the list items. (we basically do this on behalf of the listbox)
252 if (lphc
->dwStyle
& CBS_OWNERDRAWFIXED
)
254 measureItem
.CtlType
= ODT_COMBOBOX
;
255 measureItem
.CtlID
= id
;
256 measureItem
.itemID
= 0;
257 measureItem
.itemWidth
= clientRect
.right
;
258 measureItem
.itemHeight
= originalItemHeight
;
259 measureItem
.itemData
= 0;
260 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
261 lphc
->fixedOwnerDrawHeight
= measureItem
.itemHeight
;
265 * Keep the size for the next time
267 lphc
->editHeight
= iTextItemHeight
;
270 return iTextItemHeight
;
273 /***********************************************************************
276 * The dummy resize is used for listboxes that have a popup to trigger
277 * a re-arranging of the contents of the combobox and the recalculation
278 * of the size of the "real" control window.
280 static void CBForceDummyResize(
286 newComboHeight
= CBGetTextAreaHeight(lphc
->self
,lphc
) + 2*COMBO_YBORDERSIZE();
288 GetWindowRect(lphc
->self
, &windowRect
);
291 * We have to be careful, resizing a combobox also has the meaning that the
292 * dropped rect will be resized. In this case, we want to trigger a resize
293 * to recalculate layout but we don't want to change the dropped rectangle
294 * So, we pass the height of text area of control as the height.
295 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
298 SetWindowPos( lphc
->self
,
301 windowRect
.right
- windowRect
.left
,
303 SWP_NOMOVE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
306 /***********************************************************************
309 * Set up component coordinates given valid lphc->RectCombo.
311 static void CBCalcPlacement(
319 * Again, start with the client rectangle.
321 GetClientRect(hwnd
, lprEdit
);
326 InflateRect(lprEdit
, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
329 * Chop off the bottom part to fit with the height of the text area.
331 lprEdit
->bottom
= lprEdit
->top
+ CBGetTextAreaHeight(hwnd
, lphc
);
334 * The button starts the same vertical position as the text area.
336 CopyRect(lprButton
, lprEdit
);
339 * If the combobox is "simple" there is no button.
341 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
342 lprButton
->left
= lprButton
->right
= lprButton
->bottom
= 0;
346 * Let's assume the combobox button is the same width as the
348 * size the button horizontally and cut-off the text area.
350 lprButton
->left
= lprButton
->right
- GetSystemMetrics(SM_CXVSCROLL
);
351 lprEdit
->right
= lprButton
->left
;
355 * In the case of a dropdown, there is an additional spacing between the
356 * text area and the button.
358 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
360 lprEdit
->right
-= COMBO_EDITBUTTONSPACE();
364 * If we have an edit control, we space it away from the borders slightly.
366 if (CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
)
368 InflateRect(lprEdit
, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
372 * Adjust the size of the listbox popup.
374 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
377 * Use the client rectangle to initialize the listbox rectangle
379 GetClientRect(hwnd
, lprLB
);
382 * Then, chop-off the top part.
384 lprLB
->top
= lprEdit
->bottom
+ COMBO_YBORDERSIZE();
389 * Make sure the dropped width is as large as the combobox itself.
391 if (lphc
->droppedWidth
< (lprButton
->right
+ COMBO_XBORDERSIZE()))
393 lprLB
->right
= lprLB
->left
+ (lprButton
->right
+ COMBO_XBORDERSIZE());
396 * In the case of a dropdown, the popup listbox is offset to the right.
397 * so, we want to make sure it's flush with the right side of the
400 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
401 lprLB
->right
-= COMBO_EDITBUTTONSPACE();
404 lprLB
->right
= lprLB
->left
+ lphc
->droppedWidth
;
407 /* don't allow negative window width */
408 if (lprEdit
->right
< lprEdit
->left
)
409 lprEdit
->right
= lprEdit
->left
;
411 TRACE("\ttext\t= (%s)\n", wine_dbgstr_rect(lprEdit
));
413 TRACE("\tbutton\t= (%s)\n", wine_dbgstr_rect(lprButton
));
415 TRACE("\tlbox\t= (%s)\n", wine_dbgstr_rect(lprLB
));
418 /***********************************************************************
419 * CBGetDroppedControlRect
421 static void CBGetDroppedControlRect( LPHEADCOMBO lphc
, LPRECT lpRect
)
423 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
424 of the combo box and the lower right corner of the listbox */
426 GetWindowRect(lphc
->self
, lpRect
);
428 lpRect
->right
= lpRect
->left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
429 lpRect
->bottom
= lpRect
->top
+ lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
433 /***********************************************************************
436 static LRESULT
COMBO_Create( HWND hwnd
, LPHEADCOMBO lphc
, HWND hwndParent
, LONG style
)
438 static const WCHAR clbName
[] = {'C','o','m','b','o','L','B','o','x',0};
439 static const WCHAR editName
[] = {'E','d','i','t',0};
441 OpenThemeData( hwnd
, WC_COMBOBOXW
);
442 if( !CB_GETTYPE(lphc
) ) lphc
->dwStyle
|= CBS_SIMPLE
;
443 if( CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
) lphc
->wState
|= CBF_EDIT
;
445 lphc
->owner
= hwndParent
;
448 * The item height and dropped width are not set when the control
451 lphc
->droppedWidth
= lphc
->editHeight
= 0;
454 * The first time we go through, we want to measure the ownerdraw item
456 lphc
->wState
|= CBF_MEASUREITEM
;
459 * Per default the comctl32 version of combo shows up to 30 items
461 lphc
->visibleItems
= 30;
463 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
465 if( lphc
->owner
|| !(style
& WS_VISIBLE
) )
471 * Initialize the dropped rect to the size of the client area of the
472 * control and then, force all the areas of the combobox to be
475 GetClientRect( hwnd
, &lphc
->droppedRect
);
476 CBCalcPlacement(hwnd
, lphc
, &lphc
->textRect
, &lphc
->buttonRect
, &lphc
->droppedRect
);
479 * Adjust the position of the popup listbox if it's necessary
481 if ( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
483 lphc
->droppedRect
.top
= lphc
->textRect
.bottom
+ COMBO_YBORDERSIZE();
486 * If it's a dropdown, the listbox is offset
488 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
489 lphc
->droppedRect
.left
+= COMBO_EDITBUTTONSPACE();
491 if (lphc
->droppedRect
.bottom
< lphc
->droppedRect
.top
)
492 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
;
493 if (lphc
->droppedRect
.right
< lphc
->droppedRect
.left
)
494 lphc
->droppedRect
.right
= lphc
->droppedRect
.left
;
495 MapWindowPoints( hwnd
, 0, (LPPOINT
)&lphc
->droppedRect
, 2 );
498 /* create listbox popup */
500 lbeStyle
= (LBS_NOTIFY
| LBS_COMBOBOX
| WS_BORDER
| WS_CLIPSIBLINGS
| WS_CHILD
) |
501 (style
& (WS_VSCROLL
| CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
));
503 if( lphc
->dwStyle
& CBS_SORT
)
504 lbeStyle
|= LBS_SORT
;
505 if( lphc
->dwStyle
& CBS_HASSTRINGS
)
506 lbeStyle
|= LBS_HASSTRINGS
;
507 if( lphc
->dwStyle
& CBS_NOINTEGRALHEIGHT
)
508 lbeStyle
|= LBS_NOINTEGRALHEIGHT
;
509 if( lphc
->dwStyle
& CBS_DISABLENOSCROLL
)
510 lbeStyle
|= LBS_DISABLENOSCROLL
;
512 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
) /* child listbox */
514 lbeStyle
|= WS_VISIBLE
;
517 * In win 95 look n feel, the listbox in the simple combobox has
518 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
520 lbeStyle
&= ~WS_BORDER
;
521 lbeExStyle
|= WS_EX_CLIENTEDGE
;
525 lbeExStyle
|= (WS_EX_TOPMOST
| WS_EX_TOOLWINDOW
);
528 lphc
->hWndLBox
= CreateWindowExW(lbeExStyle
, clbName
, NULL
, lbeStyle
,
529 lphc
->droppedRect
.left
, lphc
->droppedRect
.top
, lphc
->droppedRect
.right
- lphc
->droppedRect
.left
,
530 lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
, hwnd
, (HMENU
)ID_CB_LISTBOX
,
531 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), lphc
);
535 lbeStyle
= WS_CHILD
| WS_VISIBLE
| ES_NOHIDESEL
| ES_LEFT
| ES_COMBO
;
537 if( lphc
->wState
& CBF_EDIT
)
539 if( lphc
->dwStyle
& CBS_OEMCONVERT
)
540 lbeStyle
|= ES_OEMCONVERT
;
541 if( lphc
->dwStyle
& CBS_AUTOHSCROLL
)
542 lbeStyle
|= ES_AUTOHSCROLL
;
543 if( lphc
->dwStyle
& CBS_LOWERCASE
)
544 lbeStyle
|= ES_LOWERCASE
;
545 else if( lphc
->dwStyle
& CBS_UPPERCASE
)
546 lbeStyle
|= ES_UPPERCASE
;
548 if (!IsWindowEnabled(hwnd
)) lbeStyle
|= WS_DISABLED
;
550 lphc
->hWndEdit
= CreateWindowExW(0, editName
, NULL
, lbeStyle
,
551 lphc
->textRect
.left
, lphc
->textRect
.top
,
552 lphc
->textRect
.right
- lphc
->textRect
.left
,
553 lphc
->textRect
.bottom
- lphc
->textRect
.top
,
554 hwnd
, (HMENU
)ID_CB_EDIT
,
555 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), NULL
);
556 if( !lphc
->hWndEdit
)
562 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
564 /* Now do the trick with parent */
565 SetParent(lphc
->hWndLBox
, HWND_DESKTOP
);
567 * If the combo is a dropdown, we must resize the control
568 * to fit only the text area and button. To do this,
569 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
570 * will take care of setting the height for us.
572 CBForceDummyResize(lphc
);
575 TRACE("init done\n");
578 ERR("edit control failure.\n");
579 } else ERR("listbox failure.\n");
580 } else ERR("no owner for visible combo.\n");
582 /* CreateWindow() will send WM_NCDESTROY to cleanup */
587 /***********************************************************************
590 * Paint combo button (normal, pressed, and disabled states).
592 static void CBPaintButton( LPHEADCOMBO lphc
, HDC hdc
, RECT rectButton
)
594 UINT buttonState
= DFCS_SCROLLCOMBOBOX
;
596 if( lphc
->wState
& CBF_NOREDRAW
)
600 if (lphc
->wState
& CBF_BUTTONDOWN
)
601 buttonState
|= DFCS_PUSHED
;
603 if (CB_DISABLED(lphc
))
604 buttonState
|= DFCS_INACTIVE
;
606 DrawFrameControl(hdc
, &rectButton
, DFC_SCROLL
, buttonState
);
609 /***********************************************************************
610 * COMBO_PrepareColors
612 * This method will sent the appropriate WM_CTLCOLOR message to
613 * prepare and setup the colors for the combo's DC.
615 * It also returns the brush to use for the background.
617 static HBRUSH
COMBO_PrepareColors(
624 * Get the background brush for this control.
626 if (CB_DISABLED(lphc
))
628 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLORSTATIC
,
629 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
632 * We have to change the text color since WM_CTLCOLORSTATIC will
633 * set it to the "enabled" color. This is the same behavior as the
636 SetTextColor(hDC
, GetSysColor(COLOR_GRAYTEXT
));
640 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
641 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLOREDIT
,
642 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
649 hBkgBrush
= GetSysColorBrush(COLOR_WINDOW
);
654 /***********************************************************************
657 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
659 static void CBPaintText(HEADCOMBO
*lphc
, HDC hdc_paint
)
661 RECT rectEdit
= lphc
->textRect
;
667 /* follow Windows combobox that sends a bunch of text
668 * inquiries to its listbox while processing WM_PAINT. */
670 if( (id
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0) ) != LB_ERR
)
672 size
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, id
, 0);
674 FIXME("LB_ERR probably not handled yet\n");
675 if ((pText
= heap_alloc((size
+ 1) * sizeof(WCHAR
))))
677 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
678 size
=SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, id
, (LPARAM
)pText
);
679 pText
[size
] = '\0'; /* just in case */
683 if( lphc
->wState
& CBF_EDIT
)
685 static const WCHAR empty_stringW
[] = { 0 };
686 if( CB_HASSTRINGS(lphc
) ) SetWindowTextW( lphc
->hWndEdit
, pText
? pText
: empty_stringW
);
687 if( lphc
->wState
& CBF_FOCUSED
)
688 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, MAXLONG
);
690 else if(!(lphc
->wState
& CBF_NOREDRAW
) && IsWindowVisible( lphc
->self
))
692 /* paint text field ourselves */
693 HDC hdc
= hdc_paint
? hdc_paint
: GetDC(lphc
->self
);
694 UINT itemState
= ODS_COMBOBOXEDIT
;
695 HFONT hPrevFont
= (lphc
->hFont
) ? SelectObject(hdc
, lphc
->hFont
) : 0;
696 HBRUSH hPrevBrush
, hBkgBrush
;
699 * Give ourselves some space.
701 InflateRect( &rectEdit
, -1, -1 );
703 hBkgBrush
= COMBO_PrepareColors( lphc
, hdc
);
704 hPrevBrush
= SelectObject( hdc
, hBkgBrush
);
705 FillRect( hdc
, &rectEdit
, hBkgBrush
);
707 if( CB_OWNERDRAWN(lphc
) )
711 UINT ctlid
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
713 /* setup state for DRAWITEM message. Owner will highlight */
714 if ( (lphc
->wState
& CBF_FOCUSED
) &&
715 !(lphc
->wState
& CBF_DROPPED
) )
716 itemState
|= ODS_SELECTED
| ODS_FOCUS
;
718 if (!IsWindowEnabled(lphc
->self
)) itemState
|= ODS_DISABLED
;
720 dis
.CtlType
= ODT_COMBOBOX
;
722 dis
.hwndItem
= lphc
->self
;
723 dis
.itemAction
= ODA_DRAWENTIRE
;
725 dis
.itemState
= itemState
;
727 dis
.rcItem
= rectEdit
;
728 dis
.itemData
= SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, id
, 0);
731 * Clip the DC and have the parent draw the item.
733 clipRegion
= set_control_clipping( hdc
, &rectEdit
);
735 SendMessageW(lphc
->owner
, WM_DRAWITEM
, ctlid
, (LPARAM
)&dis
);
737 SelectClipRgn( hdc
, clipRegion
);
738 if (clipRegion
) DeleteObject( clipRegion
);
742 static const WCHAR empty_stringW
[] = { 0 };
744 if ( (lphc
->wState
& CBF_FOCUSED
) &&
745 !(lphc
->wState
& CBF_DROPPED
) ) {
748 FillRect( hdc
, &rectEdit
, GetSysColorBrush(COLOR_HIGHLIGHT
) );
749 SetBkColor( hdc
, GetSysColor( COLOR_HIGHLIGHT
) );
750 SetTextColor( hdc
, GetSysColor( COLOR_HIGHLIGHTTEXT
) );
756 ETO_OPAQUE
| ETO_CLIPPED
,
758 pText
? pText
: empty_stringW
, size
, NULL
);
760 if(lphc
->wState
& CBF_FOCUSED
&& !(lphc
->wState
& CBF_DROPPED
))
761 DrawFocusRect( hdc
, &rectEdit
);
765 SelectObject(hdc
, hPrevFont
);
768 SelectObject( hdc
, hPrevBrush
);
771 ReleaseDC( lphc
->self
, hdc
);
777 /***********************************************************************
780 static void CBPaintBorder(
782 const HEADCOMBO
*lphc
,
787 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
789 GetClientRect(hwnd
, &clientRect
);
793 clientRect
= lphc
->textRect
;
795 InflateRect(&clientRect
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
796 InflateRect(&clientRect
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
799 DrawEdge(hdc
, &clientRect
, EDGE_SUNKEN
, BF_RECT
);
802 static LRESULT
COMBO_ThemedPaint(HTHEME theme
, HEADCOMBO
*lphc
, HDC hdc
)
808 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
809 GetClientRect(lphc
->self
, &frame
);
812 frame
= lphc
->textRect
;
813 InflateRect(&frame
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
814 InflateRect(&frame
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
817 DrawThemeBackground(theme
, hdc
, 0, IsWindowEnabled(lphc
->self
) ? CBXS_NORMAL
: CBXS_DISABLED
, &frame
, NULL
);
820 if (!IsRectEmpty(&lphc
->buttonRect
))
822 if (!IsWindowEnabled(lphc
->self
))
823 button_state
= CBXS_DISABLED
;
824 else if (lphc
->wState
& CBF_BUTTONDOWN
)
825 button_state
= CBXS_PRESSED
;
826 else if (lphc
->wState
& CBF_HOT
)
827 button_state
= CBXS_HOT
;
829 button_state
= CBXS_NORMAL
;
830 DrawThemeBackground(theme
, hdc
, CP_DROPDOWNBUTTON
, button_state
, &lphc
->buttonRect
, NULL
);
833 if ((lphc
->dwStyle
& CBS_DROPDOWNLIST
) == CBS_DROPDOWNLIST
)
834 CBPaintText(lphc
, hdc
);
839 /***********************************************************************
842 static LRESULT
COMBO_Paint(HEADCOMBO
*lphc
, HDC hdc
)
844 HBRUSH hPrevBrush
, hBkgBrush
;
846 TRACE("hdc=%p\n", hdc
);
849 * Retrieve the background brush and select it in the
852 hBkgBrush
= COMBO_PrepareColors(lphc
, hdc
);
853 hPrevBrush
= SelectObject(hdc
, hBkgBrush
);
854 if (!(lphc
->wState
& CBF_EDIT
))
855 FillRect(hdc
, &lphc
->textRect
, hBkgBrush
);
858 * In non 3.1 look, there is a sunken border on the combobox
860 CBPaintBorder(lphc
->self
, lphc
, hdc
);
862 if (!IsRectEmpty(&lphc
->buttonRect
))
863 CBPaintButton(lphc
, hdc
, lphc
->buttonRect
);
865 /* paint the edit control padding area */
866 if (CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
)
868 RECT rPadEdit
= lphc
->textRect
;
870 InflateRect(&rPadEdit
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
872 FrameRect(hdc
, &rPadEdit
, GetSysColorBrush(COLOR_WINDOW
));
875 if (!(lphc
->wState
& CBF_EDIT
))
876 CBPaintText( lphc
, hdc
);
879 SelectObject( hdc
, hPrevBrush
);
884 /***********************************************************************
887 * Select listbox entry according to the contents of the edit control.
889 static INT
CBUpdateLBox( LPHEADCOMBO lphc
, BOOL bSelect
)
895 length
= SendMessageW( lphc
->hWndEdit
, WM_GETTEXTLENGTH
, 0, 0 );
898 pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
900 TRACE("\t edit text length %i\n", length
);
904 GetWindowTextW( lphc
->hWndEdit
, pText
, length
+ 1);
905 idx
= SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, -1, (LPARAM
)pText
);
909 SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, bSelect
? idx
: -1, 0);
911 /* probably superfluous but Windows sends this too */
912 SendMessageW(lphc
->hWndLBox
, LB_SETCARETINDEX
, idx
< 0 ? 0 : idx
, 0);
913 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, idx
< 0 ? 0 : idx
, 0);
918 /***********************************************************************
921 * Copy a listbox entry to the edit control.
923 static void CBUpdateEdit( LPHEADCOMBO lphc
, INT index
)
927 static const WCHAR empty_stringW
[] = { 0 };
929 TRACE("\t %i\n", index
);
931 if( index
>= 0 ) /* got an entry */
933 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, index
, 0);
934 if( length
!= LB_ERR
)
936 if ((pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
))))
937 SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, index
, (LPARAM
)pText
);
941 if( CB_HASSTRINGS(lphc
) )
943 lphc
->wState
|= (CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
944 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, pText
? (LPARAM
)pText
: (LPARAM
)empty_stringW
);
945 lphc
->wState
&= ~(CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
948 if( lphc
->wState
& CBF_FOCUSED
)
949 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
954 /***********************************************************************
957 * Show listbox popup.
959 static void CBDropDown( LPHEADCOMBO lphc
)
962 MONITORINFO mon_info
;
967 TRACE("[%p]: drop down\n", lphc
->self
);
969 CB_NOTIFY( lphc
, CBN_DROPDOWN
);
973 lphc
->wState
|= CBF_DROPPED
;
974 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
976 lphc
->droppedIndex
= CBUpdateLBox( lphc
, TRUE
);
978 /* Update edit only if item is in the list */
979 if( !(lphc
->wState
& CBF_CAPTURE
) && lphc
->droppedIndex
>= 0)
980 CBUpdateEdit( lphc
, lphc
->droppedIndex
);
984 lphc
->droppedIndex
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
986 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
,
987 lphc
->droppedIndex
== LB_ERR
? 0 : lphc
->droppedIndex
, 0);
988 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
991 /* now set popup position */
992 GetWindowRect( lphc
->self
, &rect
);
995 * If it's a dropdown, the listbox is offset
997 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
998 rect
.left
+= COMBO_EDITBUTTONSPACE();
1000 /* if the dropped height is greater than the total height of the dropped
1001 items list, then force the drop down list height to be the total height
1002 of the items in the dropped list */
1004 /* And Remove any extra space (Best Fit) */
1005 nDroppedHeight
= lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
1006 /* if listbox length has been set directly by its handle */
1007 GetWindowRect(lphc
->hWndLBox
, &r
);
1008 if (nDroppedHeight
< r
.bottom
- r
.top
)
1009 nDroppedHeight
= r
.bottom
- r
.top
;
1010 nItems
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
1014 int nIHeight
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, 0, 0);
1016 if (lphc
->dwStyle
& CBS_NOINTEGRALHEIGHT
)
1018 nDroppedHeight
-= 1;
1022 if (nItems
> lphc
->visibleItems
)
1023 nItems
= lphc
->visibleItems
;
1024 nDroppedHeight
= nItems
* nIHeight
+ COMBO_YBORDERSIZE();
1029 r
.top
= rect
.bottom
;
1030 r
.right
= r
.left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
1031 r
.bottom
= r
.top
+ nDroppedHeight
;
1033 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1034 monitor
= MonitorFromRect( &rect
, MONITOR_DEFAULTTOPRIMARY
);
1035 mon_info
.cbSize
= sizeof(mon_info
);
1036 GetMonitorInfoW( monitor
, &mon_info
);
1038 if (r
.bottom
> mon_info
.rcWork
.bottom
)
1040 r
.top
= max( rect
.top
- nDroppedHeight
, mon_info
.rcWork
.top
);
1041 r
.bottom
= min( r
.top
+ nDroppedHeight
, mon_info
.rcWork
.bottom
);
1044 SetWindowPos( lphc
->hWndLBox
, HWND_TOPMOST
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
,
1045 SWP_NOACTIVATE
| SWP_SHOWWINDOW
);
1048 if( !(lphc
->wState
& CBF_NOREDRAW
) )
1049 RedrawWindow( lphc
->self
, NULL
, 0, RDW_INVALIDATE
|
1050 RDW_ERASE
| RDW_UPDATENOW
| RDW_NOCHILDREN
);
1052 EnableWindow( lphc
->hWndLBox
, TRUE
);
1053 if (GetCapture() != lphc
->self
)
1054 SetCapture(lphc
->hWndLBox
);
1057 /***********************************************************************
1060 * Hide listbox popup.
1062 static void CBRollUp( LPHEADCOMBO lphc
, BOOL ok
, BOOL bButton
)
1064 HWND hWnd
= lphc
->self
;
1066 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1067 lphc
->self
, ok
, (INT
)(lphc
->wState
& CBF_DROPPED
));
1069 CB_NOTIFY( lphc
, (ok
) ? CBN_SELENDOK
: CBN_SELENDCANCEL
);
1071 if( IsWindow( hWnd
) && CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1074 if( lphc
->wState
& CBF_DROPPED
)
1078 lphc
->wState
&= ~CBF_DROPPED
;
1079 ShowWindow( lphc
->hWndLBox
, SW_HIDE
);
1081 if(GetCapture() == lphc
->hWndLBox
)
1086 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1088 rect
= lphc
->buttonRect
;
1099 rect
= lphc
->textRect
;
1104 if( bButton
&& !(lphc
->wState
& CBF_NOREDRAW
) )
1105 RedrawWindow( hWnd
, &rect
, 0, RDW_INVALIDATE
|
1106 RDW_ERASE
| RDW_UPDATENOW
| RDW_NOCHILDREN
);
1107 CB_NOTIFY( lphc
, CBN_CLOSEUP
);
1112 /***********************************************************************
1115 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1117 BOOL
COMBO_FlipListbox( LPHEADCOMBO lphc
, BOOL ok
, BOOL bRedrawButton
)
1119 if( lphc
->wState
& CBF_DROPPED
)
1121 CBRollUp( lphc
, ok
, bRedrawButton
);
1129 /***********************************************************************
1132 static void CBRepaintButton( LPHEADCOMBO lphc
)
1134 InvalidateRect(lphc
->self
, &lphc
->buttonRect
, TRUE
);
1135 UpdateWindow(lphc
->self
);
1138 /***********************************************************************
1141 static void COMBO_SetFocus( LPHEADCOMBO lphc
)
1143 if( !(lphc
->wState
& CBF_FOCUSED
) )
1145 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1146 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
1148 /* This is wrong. Message sequences seem to indicate that this
1149 is set *after* the notify. */
1150 /* lphc->wState |= CBF_FOCUSED; */
1152 if( !(lphc
->wState
& CBF_EDIT
) )
1153 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1155 CB_NOTIFY( lphc
, CBN_SETFOCUS
);
1156 lphc
->wState
|= CBF_FOCUSED
;
1160 /***********************************************************************
1163 static void COMBO_KillFocus( LPHEADCOMBO lphc
)
1165 HWND hWnd
= lphc
->self
;
1167 if( lphc
->wState
& CBF_FOCUSED
)
1169 CBRollUp( lphc
, FALSE
, TRUE
);
1170 if( IsWindow( hWnd
) )
1172 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1173 SendMessageW(lphc
->hWndLBox
, LB_CARETOFF
, 0, 0);
1175 lphc
->wState
&= ~CBF_FOCUSED
;
1178 if( !(lphc
->wState
& CBF_EDIT
) )
1179 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1181 CB_NOTIFY( lphc
, CBN_KILLFOCUS
);
1186 /***********************************************************************
1189 static LRESULT
COMBO_Command( LPHEADCOMBO lphc
, WPARAM wParam
, HWND hWnd
)
1191 if ( lphc
->wState
& CBF_EDIT
&& lphc
->hWndEdit
== hWnd
)
1193 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1195 switch( HIWORD(wParam
) >> 8 )
1197 case (EN_SETFOCUS
>> 8):
1199 TRACE("[%p]: edit [%p] got focus\n", lphc
->self
, lphc
->hWndEdit
);
1201 COMBO_SetFocus( lphc
);
1204 case (EN_KILLFOCUS
>> 8):
1206 TRACE("[%p]: edit [%p] lost focus\n", lphc
->self
, lphc
->hWndEdit
);
1208 /* NOTE: it seems that Windows' edit control sends an
1209 * undocumented message WM_USER + 0x1B instead of this
1210 * notification (only when it happens to be a part of
1211 * the combo). ?? - AK.
1214 COMBO_KillFocus( lphc
);
1218 case (EN_CHANGE
>> 8):
1220 * In some circumstances (when the selection of the combobox
1221 * is changed for example) we don't want the EN_CHANGE notification
1222 * to be forwarded to the parent of the combobox. This code
1223 * checks a flag that is set in these occasions and ignores the
1226 if (lphc
->wState
& CBF_NOLBSELECT
)
1228 lphc
->wState
&= ~CBF_NOLBSELECT
;
1232 CBUpdateLBox( lphc
, lphc
->wState
& CBF_DROPPED
);
1235 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1236 CB_NOTIFY( lphc
, CBN_EDITCHANGE
);
1239 case (EN_UPDATE
>> 8):
1240 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1241 CB_NOTIFY( lphc
, CBN_EDITUPDATE
);
1244 case (EN_ERRSPACE
>> 8):
1245 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1248 else if( lphc
->hWndLBox
== hWnd
)
1250 switch( (short)HIWORD(wParam
) )
1253 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1257 CB_NOTIFY( lphc
, CBN_DBLCLK
);
1263 TRACE("[%p]: lbox selection change [%x]\n", lphc
->self
, lphc
->wState
);
1265 /* do not roll up if selection is being tracked
1266 * by arrow keys in the dropdown listbox */
1267 if (!(lphc
->wState
& CBF_NOROLLUP
))
1269 CBRollUp( lphc
, (HIWORD(wParam
) == LBN_SELCHANGE
), TRUE
);
1271 else lphc
->wState
&= ~CBF_NOROLLUP
;
1273 CB_NOTIFY( lphc
, CBN_SELCHANGE
);
1275 if( HIWORD(wParam
) == LBN_SELCHANGE
)
1277 if( lphc
->wState
& CBF_EDIT
)
1278 lphc
->wState
|= CBF_NOLBSELECT
;
1279 CBPaintText( lphc
, NULL
);
1285 /* nothing to do here since ComboLBox always resets the focus to its
1286 * combo/edit counterpart */
1293 /***********************************************************************
1296 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1298 static LRESULT
COMBO_ItemOp( LPHEADCOMBO lphc
, UINT msg
, LPARAM lParam
)
1300 HWND hWnd
= lphc
->self
;
1301 UINT id
= (UINT
)GetWindowLongPtrW( hWnd
, GWLP_ID
);
1303 TRACE("[%p]: ownerdraw op %04x\n", lphc
->self
, msg
);
1309 DELETEITEMSTRUCT
*lpIS
= (DELETEITEMSTRUCT
*)lParam
;
1310 lpIS
->CtlType
= ODT_COMBOBOX
;
1312 lpIS
->hwndItem
= hWnd
;
1317 DRAWITEMSTRUCT
*lpIS
= (DRAWITEMSTRUCT
*)lParam
;
1318 lpIS
->CtlType
= ODT_COMBOBOX
;
1320 lpIS
->hwndItem
= hWnd
;
1323 case WM_COMPAREITEM
:
1325 COMPAREITEMSTRUCT
*lpIS
= (COMPAREITEMSTRUCT
*)lParam
;
1326 lpIS
->CtlType
= ODT_COMBOBOX
;
1328 lpIS
->hwndItem
= hWnd
;
1331 case WM_MEASUREITEM
:
1333 MEASUREITEMSTRUCT
*lpIS
= (MEASUREITEMSTRUCT
*)lParam
;
1334 lpIS
->CtlType
= ODT_COMBOBOX
;
1339 return SendMessageW(lphc
->owner
, msg
, id
, lParam
);
1343 /***********************************************************************
1346 static LRESULT
COMBO_GetText( HEADCOMBO
*lphc
, INT count
, LPWSTR buf
)
1350 if( lphc
->wState
& CBF_EDIT
)
1351 return SendMessageW( lphc
->hWndEdit
, WM_GETTEXT
, count
, (LPARAM
)buf
);
1353 /* get it from the listbox */
1355 if (!count
|| !buf
) return 0;
1356 if( lphc
->hWndLBox
)
1358 INT idx
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1359 if (idx
== LB_ERR
) goto error
;
1360 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, idx
, 0 );
1361 if (length
== LB_ERR
) goto error
;
1363 /* 'length' is without the terminating character */
1364 if (length
>= count
)
1366 WCHAR
*lpBuffer
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
1367 if (!lpBuffer
) goto error
;
1368 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)lpBuffer
);
1370 /* truncate if buffer is too short */
1371 if (length
!= LB_ERR
)
1373 lstrcpynW( buf
, lpBuffer
, count
);
1376 heap_free( lpBuffer
);
1378 else length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)buf
);
1380 if (length
== LB_ERR
) return 0;
1384 error
: /* error - truncate string, return zero */
1389 /***********************************************************************
1392 * This function sets window positions according to the updated
1393 * component placement struct.
1395 static void CBResetPos(
1397 const RECT
*rectEdit
,
1401 BOOL bDrop
= (CB_GETTYPE(lphc
) != CBS_SIMPLE
);
1403 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1404 * sizing messages */
1406 if( lphc
->wState
& CBF_EDIT
)
1407 SetWindowPos( lphc
->hWndEdit
, 0,
1408 rectEdit
->left
, rectEdit
->top
,
1409 rectEdit
->right
- rectEdit
->left
,
1410 rectEdit
->bottom
- rectEdit
->top
,
1411 SWP_NOZORDER
| SWP_NOACTIVATE
| ((bDrop
) ? SWP_NOREDRAW
: 0) );
1413 SetWindowPos( lphc
->hWndLBox
, 0,
1414 rectLB
->left
, rectLB
->top
,
1415 rectLB
->right
- rectLB
->left
,
1416 rectLB
->bottom
- rectLB
->top
,
1417 SWP_NOACTIVATE
| SWP_NOZORDER
| ((bDrop
) ? SWP_NOREDRAW
: 0) );
1421 if( lphc
->wState
& CBF_DROPPED
)
1423 lphc
->wState
&= ~CBF_DROPPED
;
1424 ShowWindow( lphc
->hWndLBox
, SW_HIDE
);
1427 if( bRedraw
&& !(lphc
->wState
& CBF_NOREDRAW
) )
1428 RedrawWindow( lphc
->self
, NULL
, 0,
1429 RDW_INVALIDATE
| RDW_ERASE
| RDW_UPDATENOW
);
1434 /***********************************************************************
1437 static void COMBO_Size( LPHEADCOMBO lphc
)
1440 * Those controls are always the same height. So we have to make sure
1441 * they are not resized to another value.
1443 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1445 int newComboHeight
, curComboHeight
, curComboWidth
;
1448 GetWindowRect(lphc
->self
, &rc
);
1449 curComboHeight
= rc
.bottom
- rc
.top
;
1450 curComboWidth
= rc
.right
- rc
.left
;
1451 newComboHeight
= CBGetTextAreaHeight(lphc
->self
, lphc
) + 2*COMBO_YBORDERSIZE();
1454 * Resizing a combobox has another side effect, it resizes the dropped
1455 * rectangle as well. However, it does it only if the new height for the
1456 * combobox is more than the height it should have. In other words,
1457 * if the application resizing the combobox only had the intention to resize
1458 * the actual control, for example, to do the layout of a dialog that is
1459 * resized, the height of the dropdown is not changed.
1461 if( curComboHeight
> newComboHeight
)
1463 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1464 curComboHeight
, newComboHeight
, lphc
->droppedRect
.bottom
,
1465 lphc
->droppedRect
.top
);
1466 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
+ curComboHeight
- newComboHeight
;
1469 * Restore original height
1471 if( curComboHeight
!= newComboHeight
)
1472 SetWindowPos(lphc
->self
, 0, 0, 0, curComboWidth
, newComboHeight
,
1473 SWP_NOZORDER
|SWP_NOMOVE
|SWP_NOACTIVATE
|SWP_NOREDRAW
);
1476 CBCalcPlacement(lphc
->self
,
1480 &lphc
->droppedRect
);
1482 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1486 /***********************************************************************
1489 static void COMBO_Font( LPHEADCOMBO lphc
, HFONT hFont
, BOOL bRedraw
)
1494 lphc
->hFont
= hFont
;
1497 * Propagate to owned windows.
1499 if( lphc
->wState
& CBF_EDIT
)
1500 SendMessageW(lphc
->hWndEdit
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1501 SendMessageW(lphc
->hWndLBox
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1504 * Redo the layout of the control.
1506 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1508 CBCalcPlacement(lphc
->self
,
1512 &lphc
->droppedRect
);
1514 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1518 CBForceDummyResize(lphc
);
1523 /***********************************************************************
1524 * COMBO_SetItemHeight
1526 static LRESULT
COMBO_SetItemHeight( LPHEADCOMBO lphc
, INT index
, INT height
)
1528 LRESULT lRet
= CB_ERR
;
1530 if( index
== -1 ) /* set text field height */
1532 if( height
< 32768 )
1534 lphc
->editHeight
= height
+ 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1537 * Redo the layout of the control.
1539 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1541 CBCalcPlacement(lphc
->self
,
1545 &lphc
->droppedRect
);
1547 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1551 CBForceDummyResize(lphc
);
1557 else if ( CB_OWNERDRAWN(lphc
) ) /* set listbox item height */
1558 lRet
= SendMessageW(lphc
->hWndLBox
, LB_SETITEMHEIGHT
, index
, height
);
1562 /***********************************************************************
1563 * COMBO_SelectString
1565 static LRESULT
COMBO_SelectString( LPHEADCOMBO lphc
, INT start
, LPARAM pText
)
1567 INT index
= SendMessageW(lphc
->hWndLBox
, LB_SELECTSTRING
, start
, pText
);
1570 if( lphc
->wState
& CBF_EDIT
)
1571 CBUpdateEdit( lphc
, index
);
1574 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1577 return (LRESULT
)index
;
1580 /***********************************************************************
1583 static void COMBO_LButtonDown( LPHEADCOMBO lphc
, LPARAM lParam
)
1587 HWND hWnd
= lphc
->self
;
1589 pt
.x
= (short)LOWORD(lParam
);
1590 pt
.y
= (short)HIWORD(lParam
);
1591 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1593 if( (CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
) ||
1594 (bButton
&& (CB_GETTYPE(lphc
) == CBS_DROPDOWN
)) )
1596 lphc
->wState
|= CBF_BUTTONDOWN
;
1597 if( lphc
->wState
& CBF_DROPPED
)
1599 /* got a click to cancel selection */
1601 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1602 CBRollUp( lphc
, TRUE
, FALSE
);
1603 if( !IsWindow( hWnd
) ) return;
1605 if( lphc
->wState
& CBF_CAPTURE
)
1607 lphc
->wState
&= ~CBF_CAPTURE
;
1613 /* drop down the listbox and start tracking */
1615 lphc
->wState
|= CBF_CAPTURE
;
1619 if( bButton
) CBRepaintButton( lphc
);
1623 /***********************************************************************
1626 * Release capture and stop tracking if needed.
1628 static void COMBO_LButtonUp( LPHEADCOMBO lphc
)
1630 if( lphc
->wState
& CBF_CAPTURE
)
1632 lphc
->wState
&= ~CBF_CAPTURE
;
1633 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1635 INT index
= CBUpdateLBox( lphc
, TRUE
);
1636 /* Update edit only if item is in the list */
1639 lphc
->wState
|= CBF_NOLBSELECT
;
1640 CBUpdateEdit( lphc
, index
);
1641 lphc
->wState
&= ~CBF_NOLBSELECT
;
1645 SetCapture(lphc
->hWndLBox
);
1648 if( lphc
->wState
& CBF_BUTTONDOWN
)
1650 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1651 CBRepaintButton( lphc
);
1655 /***********************************************************************
1658 * Two things to do - track combo button and release capture when
1659 * pointer goes into the listbox.
1661 static void COMBO_MouseMove( LPHEADCOMBO lphc
, WPARAM wParam
, LPARAM lParam
)
1666 pt
.x
= (short)LOWORD(lParam
);
1667 pt
.y
= (short)HIWORD(lParam
);
1669 if( lphc
->wState
& CBF_BUTTONDOWN
)
1673 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1677 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1678 CBRepaintButton( lphc
);
1682 GetClientRect( lphc
->hWndLBox
, &lbRect
);
1683 MapWindowPoints( lphc
->self
, lphc
->hWndLBox
, &pt
, 1 );
1684 if( PtInRect(&lbRect
, pt
) )
1686 lphc
->wState
&= ~CBF_CAPTURE
;
1688 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
) CBUpdateLBox( lphc
, TRUE
);
1690 /* hand over pointer tracking */
1691 SendMessageW(lphc
->hWndLBox
, WM_LBUTTONDOWN
, wParam
, lParam
);
1695 static LRESULT
COMBO_GetComboBoxInfo(const HEADCOMBO
*lphc
, COMBOBOXINFO
*pcbi
)
1697 if (!pcbi
|| (pcbi
->cbSize
< sizeof(COMBOBOXINFO
)))
1700 pcbi
->rcItem
= lphc
->textRect
;
1701 pcbi
->rcButton
= lphc
->buttonRect
;
1702 pcbi
->stateButton
= 0;
1703 if (lphc
->wState
& CBF_BUTTONDOWN
)
1704 pcbi
->stateButton
|= STATE_SYSTEM_PRESSED
;
1705 if (IsRectEmpty(&lphc
->buttonRect
))
1706 pcbi
->stateButton
|= STATE_SYSTEM_INVISIBLE
;
1707 pcbi
->hwndCombo
= lphc
->self
;
1708 pcbi
->hwndItem
= lphc
->hWndEdit
;
1709 pcbi
->hwndList
= lphc
->hWndLBox
;
1713 static LRESULT CALLBACK
COMBO_WindowProc( HWND hwnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1715 HEADCOMBO
*lphc
= (HEADCOMBO
*)GetWindowLongPtrW( hwnd
, 0 );
1718 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd
, message
, wParam
, lParam
);
1720 if (!IsWindow(hwnd
)) return 0;
1722 if (lphc
|| message
== WM_NCCREATE
)
1727 LONG style
= ((CREATESTRUCTW
*)lParam
)->style
;
1728 return COMBO_NCCreate(hwnd
, style
);
1732 COMBO_NCDestroy(lphc
);
1733 break;/* -> DefWindowProc */
1740 hwndParent
= ((CREATESTRUCTW
*)lParam
)->hwndParent
;
1741 style
= ((CREATESTRUCTW
*)lParam
)->style
;
1742 return COMBO_Create(hwnd
, lphc
, hwndParent
, style
);
1746 theme
= GetWindowTheme( hwnd
);
1747 CloseThemeData( theme
);
1750 case WM_THEMECHANGED
:
1751 theme
= GetWindowTheme( hwnd
);
1752 CloseThemeData( theme
);
1753 OpenThemeData( hwnd
, WC_COMBOBOXW
);
1756 case WM_PRINTCLIENT
:
1763 hdc
= wParam
? (HDC
)wParam
: BeginPaint(hwnd
, &ps
);
1765 if (hdc
&& !(lphc
->wState
& CBF_NOREDRAW
))
1767 HTHEME theme
= GetWindowTheme(hwnd
);
1770 ret
= COMBO_ThemedPaint(theme
, lphc
, hdc
);
1772 ret
= COMBO_Paint(lphc
, hdc
);
1776 EndPaint(hwnd
, &ps
);
1781 /* do all painting in WM_PAINT like Windows does */
1786 LRESULT result
= DLGC_WANTARROWS
| DLGC_WANTCHARS
;
1787 if (lParam
&& (((LPMSG
)lParam
)->message
== WM_KEYDOWN
))
1789 int vk
= (int)((LPMSG
)lParam
)->wParam
;
1791 if ((vk
== VK_RETURN
|| vk
== VK_ESCAPE
) && (lphc
->wState
& CBF_DROPPED
))
1792 result
|= DLGC_WANTMESSAGE
;
1798 if (lphc
->hWndLBox
&& !(lphc
->wState
& CBF_NORESIZE
))
1803 COMBO_Font( lphc
, (HFONT
)wParam
, (BOOL
)lParam
);
1807 return (LRESULT
)lphc
->hFont
;
1810 if (lphc
->wState
& CBF_EDIT
)
1812 SetFocus( lphc
->hWndEdit
);
1813 /* The first time focus is received, select all the text */
1814 if (!(lphc
->wState
& CBF_BEENFOCUSED
))
1816 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
1817 lphc
->wState
|= CBF_BEENFOCUSED
;
1821 COMBO_SetFocus( lphc
);
1826 HWND hwndFocus
= (HWND
)wParam
;
1827 if (!hwndFocus
|| (hwndFocus
!= lphc
->hWndEdit
&& hwndFocus
!= lphc
->hWndLBox
))
1828 COMBO_KillFocus( lphc
);
1833 return COMBO_Command( lphc
, wParam
, (HWND
)lParam
);
1836 return COMBO_GetText( lphc
, wParam
, (LPWSTR
)lParam
);
1839 case WM_GETTEXTLENGTH
:
1841 if ((message
== WM_GETTEXTLENGTH
) && !ISWIN31
&& !(lphc
->wState
& CBF_EDIT
))
1843 int j
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1844 if (j
== -1) return 0;
1845 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, j
, 0);
1847 else if ( lphc
->wState
& CBF_EDIT
)
1850 lphc
->wState
|= CBF_NOEDITNOTIFY
;
1851 ret
= SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1852 lphc
->wState
&= ~CBF_NOEDITNOTIFY
;
1861 if (lphc
->wState
& CBF_EDIT
)
1862 return SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1867 case WM_COMPAREITEM
:
1868 case WM_MEASUREITEM
:
1869 return COMBO_ItemOp(lphc
, message
, lParam
);
1872 if (lphc
->wState
& CBF_EDIT
)
1873 EnableWindow( lphc
->hWndEdit
, (BOOL
)wParam
);
1874 EnableWindow( lphc
->hWndLBox
, (BOOL
)wParam
);
1876 /* Force the control to repaint when the enabled state changes. */
1877 InvalidateRect(lphc
->self
, NULL
, TRUE
);
1882 lphc
->wState
&= ~CBF_NOREDRAW
;
1884 lphc
->wState
|= CBF_NOREDRAW
;
1886 if ( lphc
->wState
& CBF_EDIT
)
1887 SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1888 SendMessageW(lphc
->hWndLBox
, message
, wParam
, lParam
);
1892 if ( KEYDATA_ALT
& HIWORD(lParam
) )
1893 if( wParam
== VK_UP
|| wParam
== VK_DOWN
)
1894 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1898 if ((wParam
== VK_RETURN
|| wParam
== VK_ESCAPE
) &&
1899 (lphc
->wState
& CBF_DROPPED
))
1901 CBRollUp( lphc
, wParam
== VK_RETURN
, FALSE
);
1904 else if ((wParam
== VK_F4
) && !(lphc
->wState
& CBF_EUI
))
1906 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1915 if ( lphc
->wState
& CBF_EDIT
)
1916 hwndTarget
= lphc
->hWndEdit
;
1918 hwndTarget
= lphc
->hWndLBox
;
1920 return SendMessageW(hwndTarget
, message
, wParam
, lParam
);
1923 case WM_LBUTTONDOWN
:
1924 if ( !(lphc
->wState
& CBF_FOCUSED
) ) SetFocus( lphc
->self
);
1925 if ( lphc
->wState
& CBF_FOCUSED
) COMBO_LButtonDown( lphc
, lParam
);
1929 COMBO_LButtonUp( lphc
);
1933 if (!IsRectEmpty(&lphc
->buttonRect
))
1937 pt
.x
= (short)LOWORD(lParam
);
1938 pt
.y
= (short)HIWORD(lParam
);
1940 if (PtInRect(&lphc
->buttonRect
, pt
))
1942 if (!(lphc
->wState
& CBF_HOT
))
1944 lphc
->wState
|= CBF_HOT
;
1945 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1948 else if (lphc
->wState
& CBF_HOT
)
1950 lphc
->wState
&= ~CBF_HOT
;
1951 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1955 if ( lphc
->wState
& CBF_CAPTURE
)
1956 COMBO_MouseMove( lphc
, wParam
, lParam
);
1960 if (wParam
& (MK_SHIFT
| MK_CONTROL
))
1961 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
1963 if (GET_WHEEL_DELTA_WPARAM(wParam
) > 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_UP
, 0);
1964 if (GET_WHEEL_DELTA_WPARAM(wParam
) < 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_DOWN
, 0);
1967 /* Combo messages */
1969 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1970 CharLowerW((LPWSTR
)lParam
);
1971 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1972 CharUpperW((LPWSTR
)lParam
);
1973 return SendMessageW(lphc
->hWndLBox
, LB_ADDSTRING
, 0, lParam
);
1975 case CB_INSERTSTRING
:
1976 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1977 CharLowerW((LPWSTR
)lParam
);
1978 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1979 CharUpperW((LPWSTR
)lParam
);
1980 return SendMessageW(lphc
->hWndLBox
, LB_INSERTSTRING
, wParam
, lParam
);
1982 case CB_DELETESTRING
:
1983 return SendMessageW(lphc
->hWndLBox
, LB_DELETESTRING
, wParam
, 0);
1985 case CB_SELECTSTRING
:
1986 return COMBO_SelectString(lphc
, (INT
)wParam
, lParam
);
1989 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, wParam
, lParam
);
1991 case CB_FINDSTRINGEXACT
:
1992 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRINGEXACT
, wParam
, lParam
);
1994 case CB_SETITEMHEIGHT
:
1995 return COMBO_SetItemHeight( lphc
, (INT
)wParam
, (INT
)lParam
);
1997 case CB_GETITEMHEIGHT
:
1998 if ((INT
)wParam
>= 0) /* listbox item */
1999 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, wParam
, 0);
2000 return CBGetTextAreaHeight(hwnd
, lphc
);
2002 case CB_RESETCONTENT
:
2003 SendMessageW(lphc
->hWndLBox
, LB_RESETCONTENT
, 0, 0);
2005 if ((lphc
->wState
& CBF_EDIT
) && CB_HASSTRINGS(lphc
))
2007 static const WCHAR empty_stringW
[] = { 0 };
2008 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, (LPARAM
)empty_stringW
);
2011 InvalidateRect(lphc
->self
, NULL
, TRUE
);
2014 case CB_INITSTORAGE
:
2015 return SendMessageW(lphc
->hWndLBox
, LB_INITSTORAGE
, wParam
, lParam
);
2017 case CB_GETHORIZONTALEXTENT
:
2018 return SendMessageW(lphc
->hWndLBox
, LB_GETHORIZONTALEXTENT
, 0, 0);
2020 case CB_SETHORIZONTALEXTENT
:
2021 return SendMessageW(lphc
->hWndLBox
, LB_SETHORIZONTALEXTENT
, wParam
, 0);
2023 case CB_GETTOPINDEX
:
2024 return SendMessageW(lphc
->hWndLBox
, LB_GETTOPINDEX
, 0, 0);
2027 return SendMessageW(lphc
->hWndLBox
, LB_GETLOCALE
, 0, 0);
2030 return SendMessageW(lphc
->hWndLBox
, LB_SETLOCALE
, wParam
, 0);
2032 case CB_SETDROPPEDWIDTH
:
2033 if ((CB_GETTYPE(lphc
) == CBS_SIMPLE
) || (INT
)wParam
>= 32768)
2036 /* new value must be higher than combobox width */
2037 if ((INT
)wParam
>= lphc
->droppedRect
.right
- lphc
->droppedRect
.left
)
2038 lphc
->droppedWidth
= wParam
;
2040 lphc
->droppedWidth
= 0;
2042 /* recalculate the combobox area */
2043 CBCalcPlacement(hwnd
, lphc
, &lphc
->textRect
, &lphc
->buttonRect
, &lphc
->droppedRect
);
2046 case CB_GETDROPPEDWIDTH
:
2047 if (lphc
->droppedWidth
)
2048 return lphc
->droppedWidth
;
2049 return lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
2051 case CB_GETDROPPEDCONTROLRECT
:
2053 CBGetDroppedControlRect(lphc
, (LPRECT
)lParam
);
2056 case CB_GETDROPPEDSTATE
:
2057 return (lphc
->wState
& CBF_DROPPED
) != 0;
2060 return SendMessageW(lphc
->hWndLBox
, LB_DIR
, wParam
, lParam
);
2062 case CB_SHOWDROPDOWN
:
2063 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
2067 if (!(lphc
->wState
& CBF_DROPPED
))
2070 else if (lphc
->wState
& CBF_DROPPED
)
2071 CBRollUp( lphc
, FALSE
, TRUE
);
2076 return SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
2079 return SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
2082 lParam
= SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, wParam
, 0);
2084 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, wParam
, 0);
2086 /* no LBN_SELCHANGE in this case, update manually */
2087 CBPaintText(lphc
, NULL
);
2088 lphc
->wState
&= ~CBF_SELCHANGE
;
2092 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, wParam
, lParam
);
2094 case CB_GETLBTEXTLEN
:
2095 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, wParam
, 0);
2097 case CB_GETITEMDATA
:
2098 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, wParam
, 0);
2100 case CB_SETITEMDATA
:
2101 return SendMessageW(lphc
->hWndLBox
, LB_SETITEMDATA
, wParam
, lParam
);
2104 /* Edit checks passed parameters itself */
2105 if (lphc
->wState
& CBF_EDIT
)
2106 return SendMessageW(lphc
->hWndEdit
, EM_GETSEL
, wParam
, lParam
);
2110 if (lphc
->wState
& CBF_EDIT
)
2111 return SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, (INT
)(SHORT
)LOWORD(lParam
), (INT
)(SHORT
)HIWORD(lParam
) );
2114 case CB_SETEXTENDEDUI
:
2115 if (CB_GETTYPE(lphc
) == CBS_SIMPLE
)
2118 lphc
->wState
|= CBF_EUI
;
2120 lphc
->wState
&= ~CBF_EUI
;
2123 case CB_GETEXTENDEDUI
:
2124 return (lphc
->wState
& CBF_EUI
) != 0;
2126 case CB_GETCOMBOBOXINFO
:
2127 return COMBO_GetComboBoxInfo(lphc
, (COMBOBOXINFO
*)lParam
);
2130 if (lphc
->wState
& CBF_EDIT
)
2131 return SendMessageW(lphc
->hWndEdit
, EM_LIMITTEXT
, wParam
, lParam
);
2134 case CB_GETMINVISIBLE
:
2135 return lphc
->visibleItems
;
2137 case CB_SETMINVISIBLE
:
2138 lphc
->visibleItems
= (INT
)wParam
;
2142 if (message
>= WM_USER
)
2143 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message
- WM_USER
, wParam
, lParam
);
2147 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
2150 void COMBO_Register(void)
2154 memset(&wndClass
, 0, sizeof(wndClass
));
2155 wndClass
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
2156 wndClass
.lpfnWndProc
= COMBO_WindowProc
;
2157 wndClass
.cbClsExtra
= 0;
2158 wndClass
.cbWndExtra
= sizeof(HEADCOMBO
*);
2159 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
2160 wndClass
.hbrBackground
= NULL
;
2161 wndClass
.lpszClassName
= WC_COMBOBOXW
;
2162 RegisterClassW(&wndClass
);