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
22 * - ComboBox_[GS]etMinVisible()
23 * - CB_GETMINVISIBLE, CB_SETMINVISIBLE
39 #include "wine/unicode.h"
40 #include "wine/debug.h"
41 #include "wine/heap.h"
45 WINE_DEFAULT_DEBUG_CHANNEL(combo
);
47 /* bits in the dwKeyData */
48 #define KEYDATA_ALT 0x2000
49 #define KEYDATA_PREVSTATE 0x4000
52 * Additional combo box definitions
55 #define CB_NOTIFY( lphc, code ) \
56 (SendMessageW((lphc)->owner, WM_COMMAND, \
57 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
59 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
60 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
61 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
62 #define CB_HWND( lphc ) ((lphc)->self)
63 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
65 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
70 static HBITMAP hComboBmp
= 0;
71 static UINT CBitHeight
, CBitWidth
;
74 * Look and feel dependent "constants"
77 #define COMBO_YBORDERGAP 5
78 #define COMBO_XBORDERSIZE() 2
79 #define COMBO_YBORDERSIZE() 2
80 #define COMBO_EDITBUTTONSPACE() 0
81 #define EDIT_CONTROL_PADDING() 1
83 #define ID_CB_LISTBOX 1000
84 #define ID_CB_EDIT 1001
86 /***********************************************************************
89 * Load combo button bitmap.
91 static BOOL
COMBO_Init(void)
95 if( hComboBmp
) return TRUE
;
96 if( (hDC
= CreateCompatibleDC(0)) )
99 if( (hComboBmp
= LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO
))) )
105 GetObjectW( hComboBmp
, sizeof(bm
), &bm
);
106 CBitHeight
= bm
.bmHeight
;
107 CBitWidth
= bm
.bmWidth
;
109 TRACE("combo bitmap [%i,%i]\n", CBitWidth
, CBitHeight
);
111 hPrevB
= SelectObject( hDC
, hComboBmp
);
112 SetRect( &r
, 0, 0, CBitWidth
, CBitHeight
);
113 InvertRect( hDC
, &r
);
114 SelectObject( hDC
, hPrevB
);
123 /***********************************************************************
126 static LRESULT
COMBO_NCCreate(HWND hwnd
, LONG style
)
130 if (COMBO_Init() && (lphc
= heap_alloc_zero(sizeof(*lphc
))))
133 SetWindowLongPtrW( hwnd
, 0, (LONG_PTR
)lphc
);
135 /* some braindead apps do try to use scrollbar/border flags */
137 lphc
->dwStyle
= style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
);
138 SetWindowLongW( hwnd
, GWL_STYLE
, style
& ~(WS_BORDER
| WS_HSCROLL
| WS_VSCROLL
) );
141 * We also have to remove the client edge style to make sure
142 * we don't end-up with a non client area.
144 SetWindowLongW( hwnd
, GWL_EXSTYLE
,
145 GetWindowLongW( hwnd
, GWL_EXSTYLE
) & ~WS_EX_CLIENTEDGE
);
147 if( !(style
& (CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
)) )
148 lphc
->dwStyle
|= CBS_HASSTRINGS
;
149 if( !(GetWindowLongW( hwnd
, GWL_EXSTYLE
) & WS_EX_NOPARENTNOTIFY
) )
150 lphc
->wState
|= CBF_NOTIFY
;
152 TRACE("[%p], style = %08x\n", lphc
, lphc
->dwStyle
);
158 /***********************************************************************
161 static LRESULT
COMBO_NCDestroy( HEADCOMBO
*lphc
)
165 TRACE("[%p]: freeing storage\n", lphc
->self
);
167 if ( (CB_GETTYPE(lphc
) != CBS_SIMPLE
) && lphc
->hWndLBox
)
168 DestroyWindow( lphc
->hWndLBox
);
170 SetWindowLongPtrW( lphc
->self
, 0, 0 );
177 /***********************************************************************
178 * CBGetTextAreaHeight
180 * This method will calculate the height of the text area of the
182 * The height of the text area is set in two ways.
183 * It can be set explicitly through a combobox message or through a
184 * WM_MEASUREITEM callback.
185 * If this is not the case, the height is set to font height + 4px
186 * This height was determined through experimentation.
187 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
189 static INT
CBGetTextAreaHeight(
195 if( lphc
->editHeight
) /* explicitly set height */
197 iTextItemHeight
= lphc
->editHeight
;
202 HDC hDC
= GetDC(hwnd
);
207 hPrevFont
= SelectObject( hDC
, lphc
->hFont
);
209 GetTextMetricsW(hDC
, &tm
);
211 baseUnitY
= tm
.tmHeight
;
214 SelectObject( hDC
, hPrevFont
);
216 ReleaseDC(hwnd
, hDC
);
218 iTextItemHeight
= baseUnitY
+ 4;
222 * Check the ownerdraw case if we haven't asked the parent the size
225 if ( CB_OWNERDRAWN(lphc
) &&
226 (lphc
->wState
& CBF_MEASUREITEM
) )
228 MEASUREITEMSTRUCT measureItem
;
230 INT originalItemHeight
= iTextItemHeight
;
231 UINT id
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
234 * We use the client rect for the width of the item.
236 GetClientRect(hwnd
, &clientRect
);
238 lphc
->wState
&= ~CBF_MEASUREITEM
;
241 * Send a first one to measure the size of the text area
243 measureItem
.CtlType
= ODT_COMBOBOX
;
244 measureItem
.CtlID
= id
;
245 measureItem
.itemID
= -1;
246 measureItem
.itemWidth
= clientRect
.right
;
247 measureItem
.itemHeight
= iTextItemHeight
- 6; /* ownerdrawn cb is taller */
248 measureItem
.itemData
= 0;
249 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
250 iTextItemHeight
= 6 + measureItem
.itemHeight
;
253 * Send a second one in the case of a fixed ownerdraw list to calculate the
254 * size of the list items. (we basically do this on behalf of the listbox)
256 if (lphc
->dwStyle
& CBS_OWNERDRAWFIXED
)
258 measureItem
.CtlType
= ODT_COMBOBOX
;
259 measureItem
.CtlID
= id
;
260 measureItem
.itemID
= 0;
261 measureItem
.itemWidth
= clientRect
.right
;
262 measureItem
.itemHeight
= originalItemHeight
;
263 measureItem
.itemData
= 0;
264 SendMessageW(lphc
->owner
, WM_MEASUREITEM
, id
, (LPARAM
)&measureItem
);
265 lphc
->fixedOwnerDrawHeight
= measureItem
.itemHeight
;
269 * Keep the size for the next time
271 lphc
->editHeight
= iTextItemHeight
;
274 return iTextItemHeight
;
277 /***********************************************************************
280 * The dummy resize is used for listboxes that have a popup to trigger
281 * a re-arranging of the contents of the combobox and the recalculation
282 * of the size of the "real" control window.
284 static void CBForceDummyResize(
290 newComboHeight
= CBGetTextAreaHeight(lphc
->self
,lphc
) + 2*COMBO_YBORDERSIZE();
292 GetWindowRect(lphc
->self
, &windowRect
);
295 * We have to be careful, resizing a combobox also has the meaning that the
296 * dropped rect will be resized. In this case, we want to trigger a resize
297 * to recalculate layout but we don't want to change the dropped rectangle
298 * So, we pass the height of text area of control as the height.
299 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
302 SetWindowPos( lphc
->self
,
305 windowRect
.right
- windowRect
.left
,
307 SWP_NOMOVE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
310 /***********************************************************************
313 * Set up component coordinates given valid lphc->RectCombo.
315 static void CBCalcPlacement(
323 * Again, start with the client rectangle.
325 GetClientRect(hwnd
, lprEdit
);
330 InflateRect(lprEdit
, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
333 * Chop off the bottom part to fit with the height of the text area.
335 lprEdit
->bottom
= lprEdit
->top
+ CBGetTextAreaHeight(hwnd
, lphc
);
338 * The button starts the same vertical position as the text area.
340 CopyRect(lprButton
, lprEdit
);
343 * If the combobox is "simple" there is no button.
345 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
346 lprButton
->left
= lprButton
->right
= lprButton
->bottom
= 0;
350 * Let's assume the combobox button is the same width as the
352 * size the button horizontally and cut-off the text area.
354 lprButton
->left
= lprButton
->right
- GetSystemMetrics(SM_CXVSCROLL
);
355 lprEdit
->right
= lprButton
->left
;
359 * In the case of a dropdown, there is an additional spacing between the
360 * text area and the button.
362 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
364 lprEdit
->right
-= COMBO_EDITBUTTONSPACE();
368 * If we have an edit control, we space it away from the borders slightly.
370 if (CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
)
372 InflateRect(lprEdit
, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
376 * Adjust the size of the listbox popup.
378 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
381 * Use the client rectangle to initialize the listbox rectangle
383 GetClientRect(hwnd
, lprLB
);
386 * Then, chop-off the top part.
388 lprLB
->top
= lprEdit
->bottom
+ COMBO_YBORDERSIZE();
393 * Make sure the dropped width is as large as the combobox itself.
395 if (lphc
->droppedWidth
< (lprButton
->right
+ COMBO_XBORDERSIZE()))
397 lprLB
->right
= lprLB
->left
+ (lprButton
->right
+ COMBO_XBORDERSIZE());
400 * In the case of a dropdown, the popup listbox is offset to the right.
401 * so, we want to make sure it's flush with the right side of the
404 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
405 lprLB
->right
-= COMBO_EDITBUTTONSPACE();
408 lprLB
->right
= lprLB
->left
+ lphc
->droppedWidth
;
411 /* don't allow negative window width */
412 if (lprEdit
->right
< lprEdit
->left
)
413 lprEdit
->right
= lprEdit
->left
;
415 TRACE("\ttext\t= (%s)\n", wine_dbgstr_rect(lprEdit
));
417 TRACE("\tbutton\t= (%s)\n", wine_dbgstr_rect(lprButton
));
419 TRACE("\tlbox\t= (%s)\n", wine_dbgstr_rect(lprLB
));
422 /***********************************************************************
423 * CBGetDroppedControlRect
425 static void CBGetDroppedControlRect( LPHEADCOMBO lphc
, LPRECT lpRect
)
427 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
428 of the combo box and the lower right corner of the listbox */
430 GetWindowRect(lphc
->self
, lpRect
);
432 lpRect
->right
= lpRect
->left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
433 lpRect
->bottom
= lpRect
->top
+ lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
437 /***********************************************************************
440 static LRESULT
COMBO_Create( HWND hwnd
, LPHEADCOMBO lphc
, HWND hwndParent
, LONG style
)
442 static const WCHAR clbName
[] = {'C','o','m','b','o','L','B','o','x',0};
443 static const WCHAR editName
[] = {'E','d','i','t',0};
445 OpenThemeData( hwnd
, WC_COMBOBOXW
);
446 if( !CB_GETTYPE(lphc
) ) lphc
->dwStyle
|= CBS_SIMPLE
;
447 if( CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
) lphc
->wState
|= CBF_EDIT
;
449 lphc
->owner
= hwndParent
;
452 * The item height and dropped width are not set when the control
455 lphc
->droppedWidth
= lphc
->editHeight
= 0;
458 * The first time we go through, we want to measure the ownerdraw item
460 lphc
->wState
|= CBF_MEASUREITEM
;
462 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
464 if( lphc
->owner
|| !(style
& WS_VISIBLE
) )
470 * Initialize the dropped rect to the size of the client area of the
471 * control and then, force all the areas of the combobox to be
474 GetClientRect( hwnd
, &lphc
->droppedRect
);
475 CBCalcPlacement(hwnd
, lphc
, &lphc
->textRect
, &lphc
->buttonRect
, &lphc
->droppedRect
);
478 * Adjust the position of the popup listbox if it's necessary
480 if ( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
482 lphc
->droppedRect
.top
= lphc
->textRect
.bottom
+ COMBO_YBORDERSIZE();
485 * If it's a dropdown, the listbox is offset
487 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
488 lphc
->droppedRect
.left
+= COMBO_EDITBUTTONSPACE();
490 if (lphc
->droppedRect
.bottom
< lphc
->droppedRect
.top
)
491 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
;
492 if (lphc
->droppedRect
.right
< lphc
->droppedRect
.left
)
493 lphc
->droppedRect
.right
= lphc
->droppedRect
.left
;
494 MapWindowPoints( hwnd
, 0, (LPPOINT
)&lphc
->droppedRect
, 2 );
497 /* create listbox popup */
499 lbeStyle
= (LBS_NOTIFY
| LBS_COMBOBOX
| WS_BORDER
| WS_CLIPSIBLINGS
| WS_CHILD
) |
500 (style
& (WS_VSCROLL
| CBS_OWNERDRAWFIXED
| CBS_OWNERDRAWVARIABLE
));
502 if( lphc
->dwStyle
& CBS_SORT
)
503 lbeStyle
|= LBS_SORT
;
504 if( lphc
->dwStyle
& CBS_HASSTRINGS
)
505 lbeStyle
|= LBS_HASSTRINGS
;
506 if( lphc
->dwStyle
& CBS_NOINTEGRALHEIGHT
)
507 lbeStyle
|= LBS_NOINTEGRALHEIGHT
;
508 if( lphc
->dwStyle
& CBS_DISABLENOSCROLL
)
509 lbeStyle
|= LBS_DISABLENOSCROLL
;
511 if( CB_GETTYPE(lphc
) == CBS_SIMPLE
) /* child listbox */
513 lbeStyle
|= WS_VISIBLE
;
516 * In win 95 look n feel, the listbox in the simple combobox has
517 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
519 lbeStyle
&= ~WS_BORDER
;
520 lbeExStyle
|= WS_EX_CLIENTEDGE
;
524 lbeExStyle
|= (WS_EX_TOPMOST
| WS_EX_TOOLWINDOW
);
527 lphc
->hWndLBox
= CreateWindowExW(lbeExStyle
, clbName
, NULL
, lbeStyle
,
528 lphc
->droppedRect
.left
, lphc
->droppedRect
.top
, lphc
->droppedRect
.right
- lphc
->droppedRect
.left
,
529 lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
, hwnd
, (HMENU
)ID_CB_LISTBOX
,
530 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), lphc
);
534 lbeStyle
= WS_CHILD
| WS_VISIBLE
| ES_NOHIDESEL
| ES_LEFT
| ES_COMBO
;
536 if( lphc
->wState
& CBF_EDIT
)
538 if( lphc
->dwStyle
& CBS_OEMCONVERT
)
539 lbeStyle
|= ES_OEMCONVERT
;
540 if( lphc
->dwStyle
& CBS_AUTOHSCROLL
)
541 lbeStyle
|= ES_AUTOHSCROLL
;
542 if( lphc
->dwStyle
& CBS_LOWERCASE
)
543 lbeStyle
|= ES_LOWERCASE
;
544 else if( lphc
->dwStyle
& CBS_UPPERCASE
)
545 lbeStyle
|= ES_UPPERCASE
;
547 if (!IsWindowEnabled(hwnd
)) lbeStyle
|= WS_DISABLED
;
549 lphc
->hWndEdit
= CreateWindowExW(0, editName
, NULL
, lbeStyle
,
550 lphc
->textRect
.left
, lphc
->textRect
.top
,
551 lphc
->textRect
.right
- lphc
->textRect
.left
,
552 lphc
->textRect
.bottom
- lphc
->textRect
.top
,
553 hwnd
, (HMENU
)ID_CB_EDIT
,
554 (HINSTANCE
)GetWindowLongPtrW( hwnd
, GWLP_HINSTANCE
), NULL
);
555 if( !lphc
->hWndEdit
)
561 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
563 /* Now do the trick with parent */
564 SetParent(lphc
->hWndLBox
, HWND_DESKTOP
);
566 * If the combo is a dropdown, we must resize the control
567 * to fit only the text area and button. To do this,
568 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
569 * will take care of setting the height for us.
571 CBForceDummyResize(lphc
);
574 TRACE("init done\n");
577 ERR("edit control failure.\n");
578 } else ERR("listbox failure.\n");
579 } else ERR("no owner for visible combo.\n");
581 /* CreateWindow() will send WM_NCDESTROY to cleanup */
586 /***********************************************************************
589 * Paint combo button (normal, pressed, and disabled states).
591 static void CBPaintButton( LPHEADCOMBO lphc
, HDC hdc
, RECT rectButton
)
593 UINT buttonState
= DFCS_SCROLLCOMBOBOX
;
595 if( lphc
->wState
& CBF_NOREDRAW
)
599 if (lphc
->wState
& CBF_BUTTONDOWN
)
600 buttonState
|= DFCS_PUSHED
;
602 if (CB_DISABLED(lphc
))
603 buttonState
|= DFCS_INACTIVE
;
605 DrawFrameControl(hdc
, &rectButton
, DFC_SCROLL
, buttonState
);
608 /***********************************************************************
609 * COMBO_PrepareColors
611 * This method will sent the appropriate WM_CTLCOLOR message to
612 * prepare and setup the colors for the combo's DC.
614 * It also returns the brush to use for the background.
616 static HBRUSH
COMBO_PrepareColors(
623 * Get the background brush for this control.
625 if (CB_DISABLED(lphc
))
627 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLORSTATIC
,
628 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
631 * We have to change the text color since WM_CTLCOLORSTATIC will
632 * set it to the "enabled" color. This is the same behavior as the
635 SetTextColor(hDC
, GetSysColor(COLOR_GRAYTEXT
));
639 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
640 hBkgBrush
= (HBRUSH
)SendMessageW(lphc
->owner
, WM_CTLCOLOREDIT
,
641 (WPARAM
)hDC
, (LPARAM
)lphc
->self
);
648 hBkgBrush
= GetSysColorBrush(COLOR_WINDOW
);
653 /***********************************************************************
656 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
658 static void CBPaintText(HEADCOMBO
*lphc
, HDC hdc_paint
)
660 RECT rectEdit
= lphc
->textRect
;
666 /* follow Windows combobox that sends a bunch of text
667 * inquiries to its listbox while processing WM_PAINT. */
669 if( (id
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0) ) != LB_ERR
)
671 size
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, id
, 0);
673 FIXME("LB_ERR probably not handled yet\n");
674 if ((pText
= heap_alloc((size
+ 1) * sizeof(WCHAR
))))
676 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
677 size
=SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, id
, (LPARAM
)pText
);
678 pText
[size
] = '\0'; /* just in case */
682 if( lphc
->wState
& CBF_EDIT
)
684 static const WCHAR empty_stringW
[] = { 0 };
685 if( CB_HASSTRINGS(lphc
) ) SetWindowTextW( lphc
->hWndEdit
, pText
? pText
: empty_stringW
);
686 if( lphc
->wState
& CBF_FOCUSED
)
687 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, MAXLONG
);
689 else if(!(lphc
->wState
& CBF_NOREDRAW
) && IsWindowVisible( lphc
->self
))
691 /* paint text field ourselves */
692 HDC hdc
= hdc_paint
? hdc_paint
: GetDC(lphc
->self
);
693 UINT itemState
= ODS_COMBOBOXEDIT
;
694 HFONT hPrevFont
= (lphc
->hFont
) ? SelectObject(hdc
, lphc
->hFont
) : 0;
695 HBRUSH hPrevBrush
, hBkgBrush
;
698 * Give ourselves some space.
700 InflateRect( &rectEdit
, -1, -1 );
702 hBkgBrush
= COMBO_PrepareColors( lphc
, hdc
);
703 hPrevBrush
= SelectObject( hdc
, hBkgBrush
);
704 FillRect( hdc
, &rectEdit
, hBkgBrush
);
706 if( CB_OWNERDRAWN(lphc
) )
710 UINT ctlid
= (UINT
)GetWindowLongPtrW( lphc
->self
, GWLP_ID
);
712 /* setup state for DRAWITEM message. Owner will highlight */
713 if ( (lphc
->wState
& CBF_FOCUSED
) &&
714 !(lphc
->wState
& CBF_DROPPED
) )
715 itemState
|= ODS_SELECTED
| ODS_FOCUS
;
717 if (!IsWindowEnabled(lphc
->self
)) itemState
|= ODS_DISABLED
;
719 dis
.CtlType
= ODT_COMBOBOX
;
721 dis
.hwndItem
= lphc
->self
;
722 dis
.itemAction
= ODA_DRAWENTIRE
;
724 dis
.itemState
= itemState
;
726 dis
.rcItem
= rectEdit
;
727 dis
.itemData
= SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, id
, 0);
730 * Clip the DC and have the parent draw the item.
732 clipRegion
= set_control_clipping( hdc
, &rectEdit
);
734 SendMessageW(lphc
->owner
, WM_DRAWITEM
, ctlid
, (LPARAM
)&dis
);
736 SelectClipRgn( hdc
, clipRegion
);
737 if (clipRegion
) DeleteObject( clipRegion
);
741 static const WCHAR empty_stringW
[] = { 0 };
743 if ( (lphc
->wState
& CBF_FOCUSED
) &&
744 !(lphc
->wState
& CBF_DROPPED
) ) {
747 FillRect( hdc
, &rectEdit
, GetSysColorBrush(COLOR_HIGHLIGHT
) );
748 SetBkColor( hdc
, GetSysColor( COLOR_HIGHLIGHT
) );
749 SetTextColor( hdc
, GetSysColor( COLOR_HIGHLIGHTTEXT
) );
755 ETO_OPAQUE
| ETO_CLIPPED
,
757 pText
? pText
: empty_stringW
, size
, NULL
);
759 if(lphc
->wState
& CBF_FOCUSED
&& !(lphc
->wState
& CBF_DROPPED
))
760 DrawFocusRect( hdc
, &rectEdit
);
764 SelectObject(hdc
, hPrevFont
);
767 SelectObject( hdc
, hPrevBrush
);
770 ReleaseDC( lphc
->self
, hdc
);
776 /***********************************************************************
779 static void CBPaintBorder(
781 const HEADCOMBO
*lphc
,
786 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
788 GetClientRect(hwnd
, &clientRect
);
792 clientRect
= lphc
->textRect
;
794 InflateRect(&clientRect
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
795 InflateRect(&clientRect
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
798 DrawEdge(hdc
, &clientRect
, EDGE_SUNKEN
, BF_RECT
);
801 static LRESULT
COMBO_ThemedPaint(HTHEME theme
, HEADCOMBO
*lphc
, HDC hdc
)
807 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
808 GetClientRect(lphc
->self
, &frame
);
811 frame
= lphc
->textRect
;
812 InflateRect(&frame
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
813 InflateRect(&frame
, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
816 DrawThemeBackground(theme
, hdc
, 0, IsWindowEnabled(lphc
->self
) ? CBXS_NORMAL
: CBXS_DISABLED
, &frame
, NULL
);
819 if (!IsRectEmpty(&lphc
->buttonRect
))
821 if (!IsWindowEnabled(lphc
->self
))
822 button_state
= CBXS_DISABLED
;
823 else if (lphc
->wState
& CBF_BUTTONDOWN
)
824 button_state
= CBXS_PRESSED
;
825 else if (lphc
->wState
& CBF_HOT
)
826 button_state
= CBXS_HOT
;
828 button_state
= CBXS_NORMAL
;
829 DrawThemeBackground(theme
, hdc
, CP_DROPDOWNBUTTON
, button_state
, &lphc
->buttonRect
, NULL
);
832 if ((lphc
->dwStyle
& CBS_DROPDOWNLIST
) == CBS_DROPDOWNLIST
)
833 CBPaintText(lphc
, hdc
);
838 /***********************************************************************
841 static LRESULT
COMBO_Paint(HEADCOMBO
*lphc
, HDC hdc
)
843 HBRUSH hPrevBrush
, hBkgBrush
;
845 TRACE("hdc=%p\n", hdc
);
848 * Retrieve the background brush and select it in the
851 hBkgBrush
= COMBO_PrepareColors(lphc
, hdc
);
852 hPrevBrush
= SelectObject(hdc
, hBkgBrush
);
853 if (!(lphc
->wState
& CBF_EDIT
))
854 FillRect(hdc
, &lphc
->textRect
, hBkgBrush
);
857 * In non 3.1 look, there is a sunken border on the combobox
859 CBPaintBorder(lphc
->self
, lphc
, hdc
);
861 if (!IsRectEmpty(&lphc
->buttonRect
))
862 CBPaintButton(lphc
, hdc
, lphc
->buttonRect
);
864 /* paint the edit control padding area */
865 if (CB_GETTYPE(lphc
) != CBS_DROPDOWNLIST
)
867 RECT rPadEdit
= lphc
->textRect
;
869 InflateRect(&rPadEdit
, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
871 FrameRect(hdc
, &rPadEdit
, GetSysColorBrush(COLOR_WINDOW
));
874 if (!(lphc
->wState
& CBF_EDIT
))
875 CBPaintText( lphc
, hdc
);
878 SelectObject( hdc
, hPrevBrush
);
883 /***********************************************************************
886 * Select listbox entry according to the contents of the edit control.
888 static INT
CBUpdateLBox( LPHEADCOMBO lphc
, BOOL bSelect
)
894 length
= SendMessageW( lphc
->hWndEdit
, WM_GETTEXTLENGTH
, 0, 0 );
897 pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
899 TRACE("\t edit text length %i\n", length
);
903 GetWindowTextW( lphc
->hWndEdit
, pText
, length
+ 1);
904 idx
= SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, -1, (LPARAM
)pText
);
908 SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, bSelect
? idx
: -1, 0);
910 /* probably superfluous but Windows sends this too */
911 SendMessageW(lphc
->hWndLBox
, LB_SETCARETINDEX
, idx
< 0 ? 0 : idx
, 0);
912 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, idx
< 0 ? 0 : idx
, 0);
917 /***********************************************************************
920 * Copy a listbox entry to the edit control.
922 static void CBUpdateEdit( LPHEADCOMBO lphc
, INT index
)
926 static const WCHAR empty_stringW
[] = { 0 };
928 TRACE("\t %i\n", index
);
930 if( index
>= 0 ) /* got an entry */
932 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, index
, 0);
933 if( length
!= LB_ERR
)
935 if ((pText
= heap_alloc((length
+ 1) * sizeof(WCHAR
))))
936 SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, index
, (LPARAM
)pText
);
940 if( CB_HASSTRINGS(lphc
) )
942 lphc
->wState
|= (CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
943 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, pText
? (LPARAM
)pText
: (LPARAM
)empty_stringW
);
944 lphc
->wState
&= ~(CBF_NOEDITNOTIFY
| CBF_NOLBSELECT
);
947 if( lphc
->wState
& CBF_FOCUSED
)
948 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
953 /***********************************************************************
956 * Show listbox popup.
958 static void CBDropDown( LPHEADCOMBO lphc
)
961 MONITORINFO mon_info
;
966 TRACE("[%p]: drop down\n", lphc
->self
);
968 CB_NOTIFY( lphc
, CBN_DROPDOWN
);
972 lphc
->wState
|= CBF_DROPPED
;
973 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
975 lphc
->droppedIndex
= CBUpdateLBox( lphc
, TRUE
);
977 /* Update edit only if item is in the list */
978 if( !(lphc
->wState
& CBF_CAPTURE
) && lphc
->droppedIndex
>= 0)
979 CBUpdateEdit( lphc
, lphc
->droppedIndex
);
983 lphc
->droppedIndex
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
985 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
,
986 lphc
->droppedIndex
== LB_ERR
? 0 : lphc
->droppedIndex
, 0);
987 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
990 /* now set popup position */
991 GetWindowRect( lphc
->self
, &rect
);
994 * If it's a dropdown, the listbox is offset
996 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
997 rect
.left
+= COMBO_EDITBUTTONSPACE();
999 /* if the dropped height is greater than the total height of the dropped
1000 items list, then force the drop down list height to be the total height
1001 of the items in the dropped list */
1003 /* And Remove any extra space (Best Fit) */
1004 nDroppedHeight
= lphc
->droppedRect
.bottom
- lphc
->droppedRect
.top
;
1005 /* if listbox length has been set directly by its handle */
1006 GetWindowRect(lphc
->hWndLBox
, &r
);
1007 if (nDroppedHeight
< r
.bottom
- r
.top
)
1008 nDroppedHeight
= r
.bottom
- r
.top
;
1009 nItems
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
1016 nIHeight
= (int)SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, 0, 0);
1018 nHeight
= nIHeight
*nItems
;
1020 if (nHeight
< nDroppedHeight
- COMBO_YBORDERSIZE())
1021 nDroppedHeight
= nHeight
+ COMBO_YBORDERSIZE();
1023 if (nDroppedHeight
< nHeight
)
1026 nDroppedHeight
= (nItems
+1)*nIHeight
;
1027 else if (nDroppedHeight
< 6*nIHeight
)
1028 nDroppedHeight
= 6*nIHeight
;
1033 r
.top
= rect
.bottom
;
1034 r
.right
= r
.left
+ lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
1035 r
.bottom
= r
.top
+ nDroppedHeight
;
1037 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1038 monitor
= MonitorFromRect( &rect
, MONITOR_DEFAULTTOPRIMARY
);
1039 mon_info
.cbSize
= sizeof(mon_info
);
1040 GetMonitorInfoW( monitor
, &mon_info
);
1042 if (r
.bottom
> mon_info
.rcWork
.bottom
)
1044 r
.top
= max( rect
.top
- nDroppedHeight
, mon_info
.rcWork
.top
);
1045 r
.bottom
= min( r
.top
+ nDroppedHeight
, mon_info
.rcWork
.bottom
);
1048 SetWindowPos( lphc
->hWndLBox
, HWND_TOPMOST
, r
.left
, r
.top
, r
.right
- r
.left
, r
.bottom
- r
.top
,
1049 SWP_NOACTIVATE
| SWP_SHOWWINDOW
);
1052 if( !(lphc
->wState
& CBF_NOREDRAW
) )
1053 RedrawWindow( lphc
->self
, NULL
, 0, RDW_INVALIDATE
|
1054 RDW_ERASE
| RDW_UPDATENOW
| RDW_NOCHILDREN
);
1056 EnableWindow( lphc
->hWndLBox
, TRUE
);
1057 if (GetCapture() != lphc
->self
)
1058 SetCapture(lphc
->hWndLBox
);
1061 /***********************************************************************
1064 * Hide listbox popup.
1066 static void CBRollUp( LPHEADCOMBO lphc
, BOOL ok
, BOOL bButton
)
1068 HWND hWnd
= lphc
->self
;
1070 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1071 lphc
->self
, ok
, (INT
)(lphc
->wState
& CBF_DROPPED
));
1073 CB_NOTIFY( lphc
, (ok
) ? CBN_SELENDOK
: CBN_SELENDCANCEL
);
1075 if( IsWindow( hWnd
) && CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1078 if( lphc
->wState
& CBF_DROPPED
)
1082 lphc
->wState
&= ~CBF_DROPPED
;
1083 ShowWindow( lphc
->hWndLBox
, SW_HIDE
);
1085 if(GetCapture() == lphc
->hWndLBox
)
1090 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1092 rect
= lphc
->buttonRect
;
1103 rect
= lphc
->textRect
;
1108 if( bButton
&& !(lphc
->wState
& CBF_NOREDRAW
) )
1109 RedrawWindow( hWnd
, &rect
, 0, RDW_INVALIDATE
|
1110 RDW_ERASE
| RDW_UPDATENOW
| RDW_NOCHILDREN
);
1111 CB_NOTIFY( lphc
, CBN_CLOSEUP
);
1116 /***********************************************************************
1119 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1121 BOOL
COMBO_FlipListbox( LPHEADCOMBO lphc
, BOOL ok
, BOOL bRedrawButton
)
1123 if( lphc
->wState
& CBF_DROPPED
)
1125 CBRollUp( lphc
, ok
, bRedrawButton
);
1133 /***********************************************************************
1136 static void CBRepaintButton( LPHEADCOMBO lphc
)
1138 InvalidateRect(lphc
->self
, &lphc
->buttonRect
, TRUE
);
1139 UpdateWindow(lphc
->self
);
1142 /***********************************************************************
1145 static void COMBO_SetFocus( LPHEADCOMBO lphc
)
1147 if( !(lphc
->wState
& CBF_FOCUSED
) )
1149 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1150 SendMessageW(lphc
->hWndLBox
, LB_CARETON
, 0, 0);
1152 /* This is wrong. Message sequences seem to indicate that this
1153 is set *after* the notify. */
1154 /* lphc->wState |= CBF_FOCUSED; */
1156 if( !(lphc
->wState
& CBF_EDIT
) )
1157 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1159 CB_NOTIFY( lphc
, CBN_SETFOCUS
);
1160 lphc
->wState
|= CBF_FOCUSED
;
1164 /***********************************************************************
1167 static void COMBO_KillFocus( LPHEADCOMBO lphc
)
1169 HWND hWnd
= lphc
->self
;
1171 if( lphc
->wState
& CBF_FOCUSED
)
1173 CBRollUp( lphc
, FALSE
, TRUE
);
1174 if( IsWindow( hWnd
) )
1176 if( CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
)
1177 SendMessageW(lphc
->hWndLBox
, LB_CARETOFF
, 0, 0);
1179 lphc
->wState
&= ~CBF_FOCUSED
;
1182 if( !(lphc
->wState
& CBF_EDIT
) )
1183 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1185 CB_NOTIFY( lphc
, CBN_KILLFOCUS
);
1190 /***********************************************************************
1193 static LRESULT
COMBO_Command( LPHEADCOMBO lphc
, WPARAM wParam
, HWND hWnd
)
1195 if ( lphc
->wState
& CBF_EDIT
&& lphc
->hWndEdit
== hWnd
)
1197 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1199 switch( HIWORD(wParam
) >> 8 )
1201 case (EN_SETFOCUS
>> 8):
1203 TRACE("[%p]: edit [%p] got focus\n", lphc
->self
, lphc
->hWndEdit
);
1205 COMBO_SetFocus( lphc
);
1208 case (EN_KILLFOCUS
>> 8):
1210 TRACE("[%p]: edit [%p] lost focus\n", lphc
->self
, lphc
->hWndEdit
);
1212 /* NOTE: it seems that Windows' edit control sends an
1213 * undocumented message WM_USER + 0x1B instead of this
1214 * notification (only when it happens to be a part of
1215 * the combo). ?? - AK.
1218 COMBO_KillFocus( lphc
);
1222 case (EN_CHANGE
>> 8):
1224 * In some circumstances (when the selection of the combobox
1225 * is changed for example) we don't want the EN_CHANGE notification
1226 * to be forwarded to the parent of the combobox. This code
1227 * checks a flag that is set in these occasions and ignores the
1230 if (lphc
->wState
& CBF_NOLBSELECT
)
1232 lphc
->wState
&= ~CBF_NOLBSELECT
;
1236 CBUpdateLBox( lphc
, lphc
->wState
& CBF_DROPPED
);
1239 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1240 CB_NOTIFY( lphc
, CBN_EDITCHANGE
);
1243 case (EN_UPDATE
>> 8):
1244 if (!(lphc
->wState
& CBF_NOEDITNOTIFY
))
1245 CB_NOTIFY( lphc
, CBN_EDITUPDATE
);
1248 case (EN_ERRSPACE
>> 8):
1249 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1252 else if( lphc
->hWndLBox
== hWnd
)
1254 switch( (short)HIWORD(wParam
) )
1257 CB_NOTIFY( lphc
, CBN_ERRSPACE
);
1261 CB_NOTIFY( lphc
, CBN_DBLCLK
);
1267 TRACE("[%p]: lbox selection change [%x]\n", lphc
->self
, lphc
->wState
);
1269 /* do not roll up if selection is being tracked
1270 * by arrow keys in the dropdown listbox */
1271 if (!(lphc
->wState
& CBF_NOROLLUP
))
1273 CBRollUp( lphc
, (HIWORD(wParam
) == LBN_SELCHANGE
), TRUE
);
1275 else lphc
->wState
&= ~CBF_NOROLLUP
;
1277 CB_NOTIFY( lphc
, CBN_SELCHANGE
);
1279 if( HIWORD(wParam
) == LBN_SELCHANGE
)
1281 if( lphc
->wState
& CBF_EDIT
)
1282 lphc
->wState
|= CBF_NOLBSELECT
;
1283 CBPaintText( lphc
, NULL
);
1289 /* nothing to do here since ComboLBox always resets the focus to its
1290 * combo/edit counterpart */
1297 /***********************************************************************
1300 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1302 static LRESULT
COMBO_ItemOp( LPHEADCOMBO lphc
, UINT msg
, LPARAM lParam
)
1304 HWND hWnd
= lphc
->self
;
1305 UINT id
= (UINT
)GetWindowLongPtrW( hWnd
, GWLP_ID
);
1307 TRACE("[%p]: ownerdraw op %04x\n", lphc
->self
, msg
);
1313 DELETEITEMSTRUCT
*lpIS
= (DELETEITEMSTRUCT
*)lParam
;
1314 lpIS
->CtlType
= ODT_COMBOBOX
;
1316 lpIS
->hwndItem
= hWnd
;
1321 DRAWITEMSTRUCT
*lpIS
= (DRAWITEMSTRUCT
*)lParam
;
1322 lpIS
->CtlType
= ODT_COMBOBOX
;
1324 lpIS
->hwndItem
= hWnd
;
1327 case WM_COMPAREITEM
:
1329 COMPAREITEMSTRUCT
*lpIS
= (COMPAREITEMSTRUCT
*)lParam
;
1330 lpIS
->CtlType
= ODT_COMBOBOX
;
1332 lpIS
->hwndItem
= hWnd
;
1335 case WM_MEASUREITEM
:
1337 MEASUREITEMSTRUCT
*lpIS
= (MEASUREITEMSTRUCT
*)lParam
;
1338 lpIS
->CtlType
= ODT_COMBOBOX
;
1343 return SendMessageW(lphc
->owner
, msg
, id
, lParam
);
1347 /***********************************************************************
1350 static LRESULT
COMBO_GetText( HEADCOMBO
*lphc
, INT count
, LPWSTR buf
)
1354 if( lphc
->wState
& CBF_EDIT
)
1355 return SendMessageW( lphc
->hWndEdit
, WM_GETTEXT
, count
, (LPARAM
)buf
);
1357 /* get it from the listbox */
1359 if (!count
|| !buf
) return 0;
1360 if( lphc
->hWndLBox
)
1362 INT idx
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1363 if (idx
== LB_ERR
) goto error
;
1364 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, idx
, 0 );
1365 if (length
== LB_ERR
) goto error
;
1367 /* 'length' is without the terminating character */
1368 if (length
>= count
)
1370 WCHAR
*lpBuffer
= heap_alloc((length
+ 1) * sizeof(WCHAR
));
1371 if (!lpBuffer
) goto error
;
1372 length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)lpBuffer
);
1374 /* truncate if buffer is too short */
1375 if (length
!= LB_ERR
)
1377 lstrcpynW( buf
, lpBuffer
, count
);
1380 heap_free( lpBuffer
);
1382 else length
= SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, idx
, (LPARAM
)buf
);
1384 if (length
== LB_ERR
) return 0;
1388 error
: /* error - truncate string, return zero */
1393 /***********************************************************************
1396 * This function sets window positions according to the updated
1397 * component placement struct.
1399 static void CBResetPos(
1401 const RECT
*rectEdit
,
1405 BOOL bDrop
= (CB_GETTYPE(lphc
) != CBS_SIMPLE
);
1407 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1408 * sizing messages */
1410 if( lphc
->wState
& CBF_EDIT
)
1411 SetWindowPos( lphc
->hWndEdit
, 0,
1412 rectEdit
->left
, rectEdit
->top
,
1413 rectEdit
->right
- rectEdit
->left
,
1414 rectEdit
->bottom
- rectEdit
->top
,
1415 SWP_NOZORDER
| SWP_NOACTIVATE
| ((bDrop
) ? SWP_NOREDRAW
: 0) );
1417 SetWindowPos( lphc
->hWndLBox
, 0,
1418 rectLB
->left
, rectLB
->top
,
1419 rectLB
->right
- rectLB
->left
,
1420 rectLB
->bottom
- rectLB
->top
,
1421 SWP_NOACTIVATE
| SWP_NOZORDER
| ((bDrop
) ? SWP_NOREDRAW
: 0) );
1425 if( lphc
->wState
& CBF_DROPPED
)
1427 lphc
->wState
&= ~CBF_DROPPED
;
1428 ShowWindow( lphc
->hWndLBox
, SW_HIDE
);
1431 if( bRedraw
&& !(lphc
->wState
& CBF_NOREDRAW
) )
1432 RedrawWindow( lphc
->self
, NULL
, 0,
1433 RDW_INVALIDATE
| RDW_ERASE
| RDW_UPDATENOW
);
1438 /***********************************************************************
1441 static void COMBO_Size( LPHEADCOMBO lphc
)
1444 * Those controls are always the same height. So we have to make sure
1445 * they are not resized to another value.
1447 if( CB_GETTYPE(lphc
) != CBS_SIMPLE
)
1449 int newComboHeight
, curComboHeight
, curComboWidth
;
1452 GetWindowRect(lphc
->self
, &rc
);
1453 curComboHeight
= rc
.bottom
- rc
.top
;
1454 curComboWidth
= rc
.right
- rc
.left
;
1455 newComboHeight
= CBGetTextAreaHeight(lphc
->self
, lphc
) + 2*COMBO_YBORDERSIZE();
1458 * Resizing a combobox has another side effect, it resizes the dropped
1459 * rectangle as well. However, it does it only if the new height for the
1460 * combobox is more than the height it should have. In other words,
1461 * if the application resizing the combobox only had the intention to resize
1462 * the actual control, for example, to do the layout of a dialog that is
1463 * resized, the height of the dropdown is not changed.
1465 if( curComboHeight
> newComboHeight
)
1467 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1468 curComboHeight
, newComboHeight
, lphc
->droppedRect
.bottom
,
1469 lphc
->droppedRect
.top
);
1470 lphc
->droppedRect
.bottom
= lphc
->droppedRect
.top
+ curComboHeight
- newComboHeight
;
1473 * Restore original height
1475 if( curComboHeight
!= newComboHeight
)
1476 SetWindowPos(lphc
->self
, 0, 0, 0, curComboWidth
, newComboHeight
,
1477 SWP_NOZORDER
|SWP_NOMOVE
|SWP_NOACTIVATE
|SWP_NOREDRAW
);
1480 CBCalcPlacement(lphc
->self
,
1484 &lphc
->droppedRect
);
1486 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1490 /***********************************************************************
1493 static void COMBO_Font( LPHEADCOMBO lphc
, HFONT hFont
, BOOL bRedraw
)
1498 lphc
->hFont
= hFont
;
1501 * Propagate to owned windows.
1503 if( lphc
->wState
& CBF_EDIT
)
1504 SendMessageW(lphc
->hWndEdit
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1505 SendMessageW(lphc
->hWndLBox
, WM_SETFONT
, (WPARAM
)hFont
, bRedraw
);
1508 * Redo the layout of the control.
1510 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1512 CBCalcPlacement(lphc
->self
,
1516 &lphc
->droppedRect
);
1518 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1522 CBForceDummyResize(lphc
);
1527 /***********************************************************************
1528 * COMBO_SetItemHeight
1530 static LRESULT
COMBO_SetItemHeight( LPHEADCOMBO lphc
, INT index
, INT height
)
1532 LRESULT lRet
= CB_ERR
;
1534 if( index
== -1 ) /* set text field height */
1536 if( height
< 32768 )
1538 lphc
->editHeight
= height
+ 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1541 * Redo the layout of the control.
1543 if ( CB_GETTYPE(lphc
) == CBS_SIMPLE
)
1545 CBCalcPlacement(lphc
->self
,
1549 &lphc
->droppedRect
);
1551 CBResetPos( lphc
, &lphc
->textRect
, &lphc
->droppedRect
, TRUE
);
1555 CBForceDummyResize(lphc
);
1561 else if ( CB_OWNERDRAWN(lphc
) ) /* set listbox item height */
1562 lRet
= SendMessageW(lphc
->hWndLBox
, LB_SETITEMHEIGHT
, index
, height
);
1566 /***********************************************************************
1567 * COMBO_SelectString
1569 static LRESULT
COMBO_SelectString( LPHEADCOMBO lphc
, INT start
, LPARAM pText
)
1571 INT index
= SendMessageW(lphc
->hWndLBox
, LB_SELECTSTRING
, start
, pText
);
1574 if( lphc
->wState
& CBF_EDIT
)
1575 CBUpdateEdit( lphc
, index
);
1578 InvalidateRect(lphc
->self
, &lphc
->textRect
, TRUE
);
1581 return (LRESULT
)index
;
1584 /***********************************************************************
1587 static void COMBO_LButtonDown( LPHEADCOMBO lphc
, LPARAM lParam
)
1591 HWND hWnd
= lphc
->self
;
1593 pt
.x
= (short)LOWORD(lParam
);
1594 pt
.y
= (short)HIWORD(lParam
);
1595 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1597 if( (CB_GETTYPE(lphc
) == CBS_DROPDOWNLIST
) ||
1598 (bButton
&& (CB_GETTYPE(lphc
) == CBS_DROPDOWN
)) )
1600 lphc
->wState
|= CBF_BUTTONDOWN
;
1601 if( lphc
->wState
& CBF_DROPPED
)
1603 /* got a click to cancel selection */
1605 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1606 CBRollUp( lphc
, TRUE
, FALSE
);
1607 if( !IsWindow( hWnd
) ) return;
1609 if( lphc
->wState
& CBF_CAPTURE
)
1611 lphc
->wState
&= ~CBF_CAPTURE
;
1617 /* drop down the listbox and start tracking */
1619 lphc
->wState
|= CBF_CAPTURE
;
1623 if( bButton
) CBRepaintButton( lphc
);
1627 /***********************************************************************
1630 * Release capture and stop tracking if needed.
1632 static void COMBO_LButtonUp( LPHEADCOMBO lphc
)
1634 if( lphc
->wState
& CBF_CAPTURE
)
1636 lphc
->wState
&= ~CBF_CAPTURE
;
1637 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
)
1639 INT index
= CBUpdateLBox( lphc
, TRUE
);
1640 /* Update edit only if item is in the list */
1643 lphc
->wState
|= CBF_NOLBSELECT
;
1644 CBUpdateEdit( lphc
, index
);
1645 lphc
->wState
&= ~CBF_NOLBSELECT
;
1649 SetCapture(lphc
->hWndLBox
);
1652 if( lphc
->wState
& CBF_BUTTONDOWN
)
1654 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1655 CBRepaintButton( lphc
);
1659 /***********************************************************************
1662 * Two things to do - track combo button and release capture when
1663 * pointer goes into the listbox.
1665 static void COMBO_MouseMove( LPHEADCOMBO lphc
, WPARAM wParam
, LPARAM lParam
)
1670 pt
.x
= (short)LOWORD(lParam
);
1671 pt
.y
= (short)HIWORD(lParam
);
1673 if( lphc
->wState
& CBF_BUTTONDOWN
)
1677 bButton
= PtInRect(&lphc
->buttonRect
, pt
);
1681 lphc
->wState
&= ~CBF_BUTTONDOWN
;
1682 CBRepaintButton( lphc
);
1686 GetClientRect( lphc
->hWndLBox
, &lbRect
);
1687 MapWindowPoints( lphc
->self
, lphc
->hWndLBox
, &pt
, 1 );
1688 if( PtInRect(&lbRect
, pt
) )
1690 lphc
->wState
&= ~CBF_CAPTURE
;
1692 if( CB_GETTYPE(lphc
) == CBS_DROPDOWN
) CBUpdateLBox( lphc
, TRUE
);
1694 /* hand over pointer tracking */
1695 SendMessageW(lphc
->hWndLBox
, WM_LBUTTONDOWN
, wParam
, lParam
);
1699 static LRESULT
COMBO_GetComboBoxInfo(const HEADCOMBO
*lphc
, COMBOBOXINFO
*pcbi
)
1701 if (!pcbi
|| (pcbi
->cbSize
< sizeof(COMBOBOXINFO
)))
1704 pcbi
->rcItem
= lphc
->textRect
;
1705 pcbi
->rcButton
= lphc
->buttonRect
;
1706 pcbi
->stateButton
= 0;
1707 if (lphc
->wState
& CBF_BUTTONDOWN
)
1708 pcbi
->stateButton
|= STATE_SYSTEM_PRESSED
;
1709 if (IsRectEmpty(&lphc
->buttonRect
))
1710 pcbi
->stateButton
|= STATE_SYSTEM_INVISIBLE
;
1711 pcbi
->hwndCombo
= lphc
->self
;
1712 pcbi
->hwndItem
= lphc
->hWndEdit
;
1713 pcbi
->hwndList
= lphc
->hWndLBox
;
1717 static LRESULT CALLBACK
COMBO_WindowProc( HWND hwnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1719 HEADCOMBO
*lphc
= (HEADCOMBO
*)GetWindowLongPtrW( hwnd
, 0 );
1722 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd
, message
, wParam
, lParam
);
1724 if (!IsWindow(hwnd
)) return 0;
1726 if (lphc
|| message
== WM_NCCREATE
)
1731 LONG style
= ((CREATESTRUCTW
*)lParam
)->style
;
1732 return COMBO_NCCreate(hwnd
, style
);
1736 COMBO_NCDestroy(lphc
);
1737 break;/* -> DefWindowProc */
1744 hwndParent
= ((CREATESTRUCTW
*)lParam
)->hwndParent
;
1745 style
= ((CREATESTRUCTW
*)lParam
)->style
;
1746 return COMBO_Create(hwnd
, lphc
, hwndParent
, style
);
1750 theme
= GetWindowTheme( hwnd
);
1751 CloseThemeData( theme
);
1754 case WM_THEMECHANGED
:
1755 theme
= GetWindowTheme( hwnd
);
1756 CloseThemeData( theme
);
1757 OpenThemeData( hwnd
, WC_COMBOBOXW
);
1760 case WM_PRINTCLIENT
:
1767 hdc
= wParam
? (HDC
)wParam
: BeginPaint(hwnd
, &ps
);
1769 if (hdc
&& !(lphc
->wState
& CBF_NOREDRAW
))
1771 HTHEME theme
= GetWindowTheme(hwnd
);
1774 ret
= COMBO_ThemedPaint(theme
, lphc
, hdc
);
1776 ret
= COMBO_Paint(lphc
, hdc
);
1780 EndPaint(hwnd
, &ps
);
1785 /* do all painting in WM_PAINT like Windows does */
1790 LRESULT result
= DLGC_WANTARROWS
| DLGC_WANTCHARS
;
1791 if (lParam
&& (((LPMSG
)lParam
)->message
== WM_KEYDOWN
))
1793 int vk
= (int)((LPMSG
)lParam
)->wParam
;
1795 if ((vk
== VK_RETURN
|| vk
== VK_ESCAPE
) && (lphc
->wState
& CBF_DROPPED
))
1796 result
|= DLGC_WANTMESSAGE
;
1802 if (lphc
->hWndLBox
&& !(lphc
->wState
& CBF_NORESIZE
))
1807 COMBO_Font( lphc
, (HFONT
)wParam
, (BOOL
)lParam
);
1811 return (LRESULT
)lphc
->hFont
;
1814 if (lphc
->wState
& CBF_EDIT
)
1816 SetFocus( lphc
->hWndEdit
);
1817 /* The first time focus is received, select all the text */
1818 if (!(lphc
->wState
& CBF_BEENFOCUSED
))
1820 SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, 0, -1);
1821 lphc
->wState
|= CBF_BEENFOCUSED
;
1825 COMBO_SetFocus( lphc
);
1830 HWND hwndFocus
= (HWND
)wParam
;
1831 if (!hwndFocus
|| (hwndFocus
!= lphc
->hWndEdit
&& hwndFocus
!= lphc
->hWndLBox
))
1832 COMBO_KillFocus( lphc
);
1837 return COMBO_Command( lphc
, wParam
, (HWND
)lParam
);
1840 return COMBO_GetText( lphc
, wParam
, (LPWSTR
)lParam
);
1843 case WM_GETTEXTLENGTH
:
1845 if ((message
== WM_GETTEXTLENGTH
) && !ISWIN31
&& !(lphc
->wState
& CBF_EDIT
))
1847 int j
= SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
1848 if (j
== -1) return 0;
1849 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, j
, 0);
1851 else if ( lphc
->wState
& CBF_EDIT
)
1854 lphc
->wState
|= CBF_NOEDITNOTIFY
;
1855 ret
= SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1856 lphc
->wState
&= ~CBF_NOEDITNOTIFY
;
1865 if (lphc
->wState
& CBF_EDIT
)
1866 return SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1871 case WM_COMPAREITEM
:
1872 case WM_MEASUREITEM
:
1873 return COMBO_ItemOp(lphc
, message
, lParam
);
1876 if (lphc
->wState
& CBF_EDIT
)
1877 EnableWindow( lphc
->hWndEdit
, (BOOL
)wParam
);
1878 EnableWindow( lphc
->hWndLBox
, (BOOL
)wParam
);
1880 /* Force the control to repaint when the enabled state changes. */
1881 InvalidateRect(lphc
->self
, NULL
, TRUE
);
1886 lphc
->wState
&= ~CBF_NOREDRAW
;
1888 lphc
->wState
|= CBF_NOREDRAW
;
1890 if ( lphc
->wState
& CBF_EDIT
)
1891 SendMessageW(lphc
->hWndEdit
, message
, wParam
, lParam
);
1892 SendMessageW(lphc
->hWndLBox
, message
, wParam
, lParam
);
1896 if ( KEYDATA_ALT
& HIWORD(lParam
) )
1897 if( wParam
== VK_UP
|| wParam
== VK_DOWN
)
1898 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1902 if ((wParam
== VK_RETURN
|| wParam
== VK_ESCAPE
) &&
1903 (lphc
->wState
& CBF_DROPPED
))
1905 CBRollUp( lphc
, wParam
== VK_RETURN
, FALSE
);
1908 else if ((wParam
== VK_F4
) && !(lphc
->wState
& CBF_EUI
))
1910 COMBO_FlipListbox( lphc
, FALSE
, FALSE
);
1919 if ( lphc
->wState
& CBF_EDIT
)
1920 hwndTarget
= lphc
->hWndEdit
;
1922 hwndTarget
= lphc
->hWndLBox
;
1924 return SendMessageW(hwndTarget
, message
, wParam
, lParam
);
1927 case WM_LBUTTONDOWN
:
1928 if ( !(lphc
->wState
& CBF_FOCUSED
) ) SetFocus( lphc
->self
);
1929 if ( lphc
->wState
& CBF_FOCUSED
) COMBO_LButtonDown( lphc
, lParam
);
1933 COMBO_LButtonUp( lphc
);
1937 if (!IsRectEmpty(&lphc
->buttonRect
))
1941 pt
.x
= (short)LOWORD(lParam
);
1942 pt
.y
= (short)HIWORD(lParam
);
1944 if (PtInRect(&lphc
->buttonRect
, pt
))
1946 if (!(lphc
->wState
& CBF_HOT
))
1948 lphc
->wState
|= CBF_HOT
;
1949 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1952 else if (lphc
->wState
& CBF_HOT
)
1954 lphc
->wState
&= ~CBF_HOT
;
1955 RedrawWindow(hwnd
, &lphc
->buttonRect
, 0, RDW_INVALIDATE
| RDW_UPDATENOW
);
1959 if ( lphc
->wState
& CBF_CAPTURE
)
1960 COMBO_MouseMove( lphc
, wParam
, lParam
);
1964 if (wParam
& (MK_SHIFT
| MK_CONTROL
))
1965 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
1967 if (GET_WHEEL_DELTA_WPARAM(wParam
) > 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_UP
, 0);
1968 if (GET_WHEEL_DELTA_WPARAM(wParam
) < 0) return SendMessageW(hwnd
, WM_KEYDOWN
, VK_DOWN
, 0);
1971 /* Combo messages */
1973 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1974 CharLowerW((LPWSTR
)lParam
);
1975 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1976 CharUpperW((LPWSTR
)lParam
);
1977 return SendMessageW(lphc
->hWndLBox
, LB_ADDSTRING
, 0, lParam
);
1979 case CB_INSERTSTRING
:
1980 if (lphc
->dwStyle
& CBS_LOWERCASE
)
1981 CharLowerW((LPWSTR
)lParam
);
1982 else if (lphc
->dwStyle
& CBS_UPPERCASE
)
1983 CharUpperW((LPWSTR
)lParam
);
1984 return SendMessageW(lphc
->hWndLBox
, LB_INSERTSTRING
, wParam
, lParam
);
1986 case CB_DELETESTRING
:
1987 return SendMessageW(lphc
->hWndLBox
, LB_DELETESTRING
, wParam
, 0);
1989 case CB_SELECTSTRING
:
1990 return COMBO_SelectString(lphc
, (INT
)wParam
, lParam
);
1993 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRING
, wParam
, lParam
);
1995 case CB_FINDSTRINGEXACT
:
1996 return SendMessageW(lphc
->hWndLBox
, LB_FINDSTRINGEXACT
, wParam
, lParam
);
1998 case CB_SETITEMHEIGHT
:
1999 return COMBO_SetItemHeight( lphc
, (INT
)wParam
, (INT
)lParam
);
2001 case CB_GETITEMHEIGHT
:
2002 if ((INT
)wParam
>= 0) /* listbox item */
2003 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMHEIGHT
, wParam
, 0);
2004 return CBGetTextAreaHeight(hwnd
, lphc
);
2006 case CB_RESETCONTENT
:
2007 SendMessageW(lphc
->hWndLBox
, LB_RESETCONTENT
, 0, 0);
2009 if ((lphc
->wState
& CBF_EDIT
) && CB_HASSTRINGS(lphc
))
2011 static const WCHAR empty_stringW
[] = { 0 };
2012 SendMessageW(lphc
->hWndEdit
, WM_SETTEXT
, 0, (LPARAM
)empty_stringW
);
2015 InvalidateRect(lphc
->self
, NULL
, TRUE
);
2018 case CB_INITSTORAGE
:
2019 return SendMessageW(lphc
->hWndLBox
, LB_INITSTORAGE
, wParam
, lParam
);
2021 case CB_GETHORIZONTALEXTENT
:
2022 return SendMessageW(lphc
->hWndLBox
, LB_GETHORIZONTALEXTENT
, 0, 0);
2024 case CB_SETHORIZONTALEXTENT
:
2025 return SendMessageW(lphc
->hWndLBox
, LB_SETHORIZONTALEXTENT
, wParam
, 0);
2027 case CB_GETTOPINDEX
:
2028 return SendMessageW(lphc
->hWndLBox
, LB_GETTOPINDEX
, 0, 0);
2031 return SendMessageW(lphc
->hWndLBox
, LB_GETLOCALE
, 0, 0);
2034 return SendMessageW(lphc
->hWndLBox
, LB_SETLOCALE
, wParam
, 0);
2036 case CB_SETDROPPEDWIDTH
:
2037 if ((CB_GETTYPE(lphc
) == CBS_SIMPLE
) || (INT
)wParam
>= 32768)
2040 /* new value must be higher than combobox width */
2041 if ((INT
)wParam
>= lphc
->droppedRect
.right
- lphc
->droppedRect
.left
)
2042 lphc
->droppedWidth
= wParam
;
2044 lphc
->droppedWidth
= 0;
2046 /* recalculate the combobox area */
2047 CBCalcPlacement(hwnd
, lphc
, &lphc
->textRect
, &lphc
->buttonRect
, &lphc
->droppedRect
);
2050 case CB_GETDROPPEDWIDTH
:
2051 if (lphc
->droppedWidth
)
2052 return lphc
->droppedWidth
;
2053 return lphc
->droppedRect
.right
- lphc
->droppedRect
.left
;
2055 case CB_GETDROPPEDCONTROLRECT
:
2057 CBGetDroppedControlRect(lphc
, (LPRECT
)lParam
);
2060 case CB_GETDROPPEDSTATE
:
2061 return (lphc
->wState
& CBF_DROPPED
) != 0;
2064 return SendMessageW(lphc
->hWndLBox
, LB_DIR
, wParam
, lParam
);
2066 case CB_SHOWDROPDOWN
:
2067 if (CB_GETTYPE(lphc
) != CBS_SIMPLE
)
2071 if (!(lphc
->wState
& CBF_DROPPED
))
2074 else if (lphc
->wState
& CBF_DROPPED
)
2075 CBRollUp( lphc
, FALSE
, TRUE
);
2080 return SendMessageW(lphc
->hWndLBox
, LB_GETCOUNT
, 0, 0);
2083 return SendMessageW(lphc
->hWndLBox
, LB_GETCURSEL
, 0, 0);
2086 lParam
= SendMessageW(lphc
->hWndLBox
, LB_SETCURSEL
, wParam
, 0);
2088 SendMessageW(lphc
->hWndLBox
, LB_SETTOPINDEX
, wParam
, 0);
2090 /* no LBN_SELCHANGE in this case, update manually */
2091 CBPaintText(lphc
, NULL
);
2092 lphc
->wState
&= ~CBF_SELCHANGE
;
2096 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXT
, wParam
, lParam
);
2098 case CB_GETLBTEXTLEN
:
2099 return SendMessageW(lphc
->hWndLBox
, LB_GETTEXTLEN
, wParam
, 0);
2101 case CB_GETITEMDATA
:
2102 return SendMessageW(lphc
->hWndLBox
, LB_GETITEMDATA
, wParam
, 0);
2104 case CB_SETITEMDATA
:
2105 return SendMessageW(lphc
->hWndLBox
, LB_SETITEMDATA
, wParam
, lParam
);
2108 /* Edit checks passed parameters itself */
2109 if (lphc
->wState
& CBF_EDIT
)
2110 return SendMessageW(lphc
->hWndEdit
, EM_GETSEL
, wParam
, lParam
);
2114 if (lphc
->wState
& CBF_EDIT
)
2115 return SendMessageW(lphc
->hWndEdit
, EM_SETSEL
, (INT
)(SHORT
)LOWORD(lParam
), (INT
)(SHORT
)HIWORD(lParam
) );
2118 case CB_SETEXTENDEDUI
:
2119 if (CB_GETTYPE(lphc
) == CBS_SIMPLE
)
2122 lphc
->wState
|= CBF_EUI
;
2124 lphc
->wState
&= ~CBF_EUI
;
2127 case CB_GETEXTENDEDUI
:
2128 return (lphc
->wState
& CBF_EUI
) != 0;
2130 case CB_GETCOMBOBOXINFO
:
2131 return COMBO_GetComboBoxInfo(lphc
, (COMBOBOXINFO
*)lParam
);
2134 if (lphc
->wState
& CBF_EDIT
)
2135 return SendMessageW(lphc
->hWndEdit
, EM_LIMITTEXT
, wParam
, lParam
);
2139 if (message
>= WM_USER
)
2140 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message
- WM_USER
, wParam
, lParam
);
2144 return DefWindowProcW(hwnd
, message
, wParam
, lParam
);
2147 void COMBO_Register(void)
2151 memset(&wndClass
, 0, sizeof(wndClass
));
2152 wndClass
.style
= CS_PARENTDC
| CS_DBLCLKS
| CS_HREDRAW
| CS_VREDRAW
| CS_GLOBALCLASS
;
2153 wndClass
.lpfnWndProc
= COMBO_WindowProc
;
2154 wndClass
.cbClsExtra
= 0;
2155 wndClass
.cbWndExtra
= sizeof(HEADCOMBO
*);
2156 wndClass
.hCursor
= LoadCursorW(0, (LPWSTR
)IDC_ARROW
);
2157 wndClass
.hbrBackground
= NULL
;
2158 wndClass
.lpszClassName
= WC_COMBOBOXW
;
2159 RegisterClassW(&wndClass
);
2163 void COMBO_Unregister(void)
2165 UnregisterClassW(WC_COMBOBOXW
, NULL
);