1 /*
2 * Combo controls
3 *
4 * Copyright 1997 Alex Korobka
5 * Copyright (c) 2005 by Frank Richter
6 *
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.
11 *
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.
16 *
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
20 *
21 */
22
23 #include <stdarg.h>
24 #include <string.h>
25
26 #define OEMRESOURCE
27
28 #include "windef.h"
29 #include "winbase.h"
30 #include "wingdi.h"
31 #include "winuser.h"
32 #include "uxtheme.h"
33 #include "vssym32.h"
34 #include "commctrl.h"
35 #include "wine/debug.h"
36 #include "wine/heap.h"
37
38 #include "comctl32.h"
39
40 WINE_DEFAULT_DEBUG_CHANNEL(combo);
41
42 /* bits in the dwKeyData */
43 #define KEYDATA_ALT 0x2000
44 #define KEYDATA_PREVSTATE 0x4000
45
46 /*
47 * Additional combo box definitions
48 */
49
50 #define CB_NOTIFY( lphc, code ) \
51 (SendMessageW((lphc)->owner, WM_COMMAND, \
52 MAKEWPARAM(GetWindowLongPtrW((lphc)->self,GWLP_ID), (code)), (LPARAM)(lphc)->self))
53
54 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
55 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
56 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
57 #define CB_HWND( lphc ) ((lphc)->self)
58 #define CB_GETTYPE( lphc ) ((lphc)->dwStyle & (CBS_DROPDOWNLIST))
59
60 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
61
62 /*
63 * Drawing globals
64 */
65 static HBITMAP hComboBmp = 0;
66 static UINT CBitHeight, CBitWidth;
67
68 /*
69 * Look and feel dependent "constants"
70 */
71
72 #define COMBO_YBORDERGAP 5
73 #define COMBO_XBORDERSIZE() 2
74 #define COMBO_YBORDERSIZE() 2
75 #define COMBO_EDITBUTTONSPACE() 0
76 #define EDIT_CONTROL_PADDING() 1
77
78 #define ID_CB_LISTBOX 1000
79 #define ID_CB_EDIT 1001
80
81 static void CBCalcPlacement(HEADCOMBO *combo);
82 static void CBResetPos(HEADCOMBO *combo);
83
84 /***********************************************************************
85 * COMBO_Init
86 *
87 * Load combo button bitmap.
88 */
COMBO_Init(void)89 static BOOL COMBO_Init(void)
90 {
91 HDC hDC;
92
93 if( hComboBmp ) return TRUE;
94 if( (hDC = CreateCompatibleDC(0)) )
95 {
96 BOOL bRet = FALSE;
97 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
98 {
99 BITMAP bm;
100 HBITMAP hPrevB;
101 RECT r;
102
103 GetObjectW( hComboBmp, sizeof(bm), &bm );
104 CBitHeight = bm.bmHeight;
105 CBitWidth = bm.bmWidth;
106
107 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
108
109 hPrevB = SelectObject( hDC, hComboBmp);
110 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
111 InvertRect( hDC, &r );
112 SelectObject( hDC, hPrevB );
113 bRet = TRUE;
114 }
115 DeleteDC( hDC );
116 return bRet;
117 }
118 return FALSE;
119 }
120
121 /***********************************************************************
122 * COMBO_NCCreate
123 */
COMBO_NCCreate(HWND hwnd,LONG style)124 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style)
125 {
126 HEADCOMBO *lphc;
127
128 if (COMBO_Init() && (lphc = heap_alloc_zero(sizeof(*lphc))))
129 {
130 lphc->self = hwnd;
131 SetWindowLongPtrW( hwnd, 0, (LONG_PTR)lphc );
132
133 /* some braindead apps do try to use scrollbar/border flags */
134
135 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
136 SetWindowLongW( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) );
137
138 /*
139 * We also have to remove the client edge style to make sure
140 * we don't end-up with a non client area.
141 */
142 SetWindowLongW( hwnd, GWL_EXSTYLE,
143 GetWindowLongW( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
144
145 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
146 lphc->dwStyle |= CBS_HASSTRINGS;
147 if( !(GetWindowLongW( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
148 lphc->wState |= CBF_NOTIFY;
149
150 TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle );
151 return TRUE;
152 }
153 return FALSE;
154 }
155
156 /***********************************************************************
157 * COMBO_NCDestroy
158 */
COMBO_NCDestroy(HEADCOMBO * lphc)159 static LRESULT COMBO_NCDestroy( HEADCOMBO *lphc )
160 {
161 if (lphc)
162 {
163 TRACE("[%p]: freeing storage\n", lphc->self);
164
165 if ( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
166 DestroyWindow( lphc->hWndLBox );
167
168 SetWindowLongPtrW( lphc->self, 0, 0 );
169 heap_free( lphc );
170 }
171
172 return 0;
173 }
174
combo_get_text_height(const HEADCOMBO * combo)175 static INT combo_get_text_height(const HEADCOMBO *combo)
176 {
177 HDC hdc = GetDC(combo->self);
178 HFONT prev_font = 0;
179 TEXTMETRICW tm;
180
181 if (combo->hFont)
182 prev_font = SelectObject(hdc, combo->hFont);
183
184 GetTextMetricsW(hdc, &tm);
185
186 if (prev_font)
187 SelectObject(hdc, prev_font);
188
189 ReleaseDC(combo->self, hdc);
190
191 return tm.tmHeight + 4;
192 }
193
194 /***********************************************************************
195 * CBGetTextAreaHeight
196 *
197 * This method will calculate the height of the text area of the
198 * combobox.
199 * The height of the text area is set in two ways.
200 * It can be set explicitly through a combobox message or through a
201 * WM_MEASUREITEM callback.
202 * If this is not the case, the height is set to font height + 4px
203 * This height was determined through experimentation.
204 * CBCalcPlacement will add 2*COMBO_YBORDERSIZE pixels for the border
205 */
CBGetTextAreaHeight(HEADCOMBO * lphc,BOOL clip_item_height)206 static INT CBGetTextAreaHeight(HEADCOMBO *lphc, BOOL clip_item_height)
207 {
208 INT item_height, text_height;
209
210 if (clip_item_height && !CB_OWNERDRAWN(lphc))
211 {
212 text_height = combo_get_text_height(lphc);
213 if (lphc->item_height < text_height)
214 lphc->item_height = text_height;
215 }
216 item_height = lphc->item_height;
217
218
219 /*
220 * Check the ownerdraw case if we haven't asked the parent the size
221 * of the item yet.
222 */
223 if ( CB_OWNERDRAWN(lphc) &&
224 (lphc->wState & CBF_MEASUREITEM) )
225 {
226 MEASUREITEMSTRUCT measureItem;
227 RECT clientRect;
228 INT originalItemHeight = item_height;
229 UINT id = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
230
231 /*
232 * We use the client rect for the width of the item.
233 */
234 GetClientRect(lphc->self, &clientRect);
235
236 lphc->wState &= ~CBF_MEASUREITEM;
237
238 /*
239 * Send a first one to measure the size of the text area
240 */
241 measureItem.CtlType = ODT_COMBOBOX;
242 measureItem.CtlID = id;
243 measureItem.itemID = -1;
244 measureItem.itemWidth = clientRect.right;
245 measureItem.itemHeight = item_height - 6; /* ownerdrawn cb is taller */
246 measureItem.itemData = 0;
247 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
248 item_height = 6 + measureItem.itemHeight;
249
250 /*
251 * Send a second one in the case of a fixed ownerdraw list to calculate the
252 * size of the list items. (we basically do this on behalf of the listbox)
253 */
254 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
255 {
256 measureItem.CtlType = ODT_COMBOBOX;
257 measureItem.CtlID = id;
258 measureItem.itemID = 0;
259 measureItem.itemWidth = clientRect.right;
260 measureItem.itemHeight = originalItemHeight;
261 measureItem.itemData = 0;
262 SendMessageW(lphc->owner, WM_MEASUREITEM, id, (LPARAM)&measureItem);
263 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
264 }
265
266 /*
267 * Keep the size for the next time
268 */
269 lphc->item_height = item_height;
270 }
271
272 return item_height;
273 }
274
275 /***********************************************************************
276 * CBForceDummyResize
277 *
278 * The dummy resize is used for listboxes that have a popup to trigger
279 * a re-arranging of the contents of the combobox and the recalculation
280 * of the size of the "real" control window.
281 */
CBForceDummyResize(LPHEADCOMBO lphc)282 static void CBForceDummyResize(LPHEADCOMBO lphc)
283 {
284 RECT windowRect;
285 int newComboHeight;
286
287 newComboHeight = CBGetTextAreaHeight(lphc, FALSE) + 2*COMBO_YBORDERSIZE();
288
289 GetWindowRect(lphc->self, &windowRect);
290
291 /*
292 * We have to be careful, resizing a combobox also has the meaning that the
293 * dropped rect will be resized. In this case, we want to trigger a resize
294 * to recalculate layout but we don't want to change the dropped rectangle
295 * So, we pass the height of text area of control as the height.
296 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
297 * message.
298 */
299 lphc->wState |= CBF_NORESIZE;
300 SetWindowPos( lphc->self,
301 NULL,
302 0, 0,
303 windowRect.right - windowRect.left,
304 newComboHeight,
305 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
306 lphc->wState &= ~CBF_NORESIZE;
307
308 CBCalcPlacement(lphc);
309 CBResetPos(lphc);
310 }
311
312 /***********************************************************************
313 * CBCalcPlacement
314 *
315 * Set up component coordinates given valid lphc->RectCombo.
316 */
CBCalcPlacement(HEADCOMBO * combo)317 static void CBCalcPlacement(HEADCOMBO *combo)
318 {
319 /* Start with the client rectangle. */
320 GetClientRect(combo->self, &combo->textRect);
321
322 /* Remove the borders */
323 InflateRect(&combo->textRect, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
324
325 /* Chop off the bottom part to fit with the height of the text area. */
326 combo->textRect.bottom = combo->textRect.top + CBGetTextAreaHeight(combo, FALSE);
327
328 /* The button starts the same vertical position as the text area. */
329 combo->buttonRect = combo->textRect;
330
331 /* If the combobox is "simple" there is no button. */
332 if (CB_GETTYPE(combo) == CBS_SIMPLE)
333 combo->buttonRect.left = combo->buttonRect.right = combo->buttonRect.bottom = 0;
334 else
335 {
336 /*
337 * Let's assume the combobox button is the same width as the
338 * scrollbar button.
339 * size the button horizontally and cut-off the text area.
340 */
341 combo->buttonRect.left = combo->buttonRect.right - GetSystemMetrics(SM_CXVSCROLL);
342 combo->textRect.right = combo->buttonRect.left;
343 }
344
345 /* In the case of a dropdown, there is an additional spacing between the text area and the button. */
346 if (CB_GETTYPE(combo) == CBS_DROPDOWN)
347 combo->textRect.right -= COMBO_EDITBUTTONSPACE();
348
349 /* If we have an edit control, we space it away from the borders slightly. */
350 if (CB_GETTYPE(combo) != CBS_DROPDOWNLIST)
351 InflateRect(&combo->textRect, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
352
353 /* Adjust the size of the listbox popup. */
354 if (CB_GETTYPE(combo) == CBS_SIMPLE)
355 {
356 GetClientRect(combo->self, &combo->droppedRect);
357 combo->droppedRect.top = combo->textRect.bottom + COMBO_YBORDERSIZE();
358 }
359 else
360 {
361 /* Make sure the dropped width is as large as the combobox itself. */
362 if (combo->droppedWidth < (combo->buttonRect.right + COMBO_XBORDERSIZE()))
363 {
364 combo->droppedRect.right = combo->droppedRect.left + (combo->buttonRect.right + COMBO_XBORDERSIZE());
365
366 /* In the case of a dropdown, the popup listbox is offset to the right. We want to make sure it's flush
367 with the right side of the combobox */
368 if (CB_GETTYPE(combo) == CBS_DROPDOWN)
369 combo->droppedRect.right -= COMBO_EDITBUTTONSPACE();
370 }
371 else
372 combo->droppedRect.right = combo->droppedRect.left + combo->droppedWidth;
373 }
374
375 /* Disallow negative window width */
376 if (combo->textRect.right < combo->textRect.left)
377 combo->textRect.right = combo->textRect.left;
378
379 TRACE("text %s, button %s, lbox %s.\n", wine_dbgstr_rect(&combo->textRect), wine_dbgstr_rect(&combo->buttonRect),
380 wine_dbgstr_rect(&combo->droppedRect));
381 }
382
383 /***********************************************************************
384 * CBGetDroppedControlRect
385 */
CBGetDroppedControlRect(LPHEADCOMBO lphc,LPRECT lpRect)386 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
387 {
388 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
389 of the combo box and the lower right corner of the listbox */
390
391 GetWindowRect(lphc->self, lpRect);
392
393 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
394 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
395
396 }
397
398 /***********************************************************************
399 * COMBO_Create
400 */
COMBO_Create(HWND hwnd,LPHEADCOMBO lphc,HWND hwndParent,LONG style)401 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style )
402 {
403 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
404 static const WCHAR editName[] = {'E','d','i','t',0};
405
406 OpenThemeData( hwnd, WC_COMBOBOXW );
407 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
408 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
409
410 lphc->owner = hwndParent;
411
412 lphc->droppedWidth = 0;
413
414 lphc->item_height = combo_get_text_height(lphc);
415
416 /*
417 * The first time we go through, we want to measure the ownerdraw item
418 */
419 lphc->wState |= CBF_MEASUREITEM;
420
421 /*
422 * Per default the comctl32 version of combo shows up to 30 items
423 */
424 lphc->visibleItems = 30;
425
426 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
427
428 if( lphc->owner || !(style & WS_VISIBLE) )
429 {
430 UINT lbeStyle = 0;
431 UINT lbeExStyle = 0;
432
433 /*
434 * Initialize the dropped rect to the size of the client area of the
435 * control and then, force all the areas of the combobox to be
436 * recalculated.
437 */
438 GetClientRect( hwnd, &lphc->droppedRect );
439 CBCalcPlacement(lphc);
440
441 /*
442 * Adjust the position of the popup listbox if it's necessary
443 */
444 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
445 {
446 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
447
448 /*
449 * If it's a dropdown, the listbox is offset
450 */
451 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
452 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
453
454 if (lphc->droppedRect.bottom < lphc->droppedRect.top)
455 lphc->droppedRect.bottom = lphc->droppedRect.top;
456 if (lphc->droppedRect.right < lphc->droppedRect.left)
457 lphc->droppedRect.right = lphc->droppedRect.left;
458 MapWindowPoints( hwnd, 0, (LPPOINT)&lphc->droppedRect, 2 );
459 }
460
461 /* create listbox popup */
462
463 lbeStyle = (LBS_NOTIFY | LBS_COMBOBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
464 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
465
466 if( lphc->dwStyle & CBS_SORT )
467 lbeStyle |= LBS_SORT;
468 if( lphc->dwStyle & CBS_HASSTRINGS )
469 lbeStyle |= LBS_HASSTRINGS;
470 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
471 lbeStyle |= LBS_NOINTEGRALHEIGHT;
472 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
473 lbeStyle |= LBS_DISABLENOSCROLL;
474
475 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
476 {
477 lbeStyle |= WS_VISIBLE;
478
479 /*
480 * In win 95 look n feel, the listbox in the simple combobox has
481 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
482 */
483 lbeStyle &= ~WS_BORDER;
484 lbeExStyle |= WS_EX_CLIENTEDGE;
485 }
486 else
487 {
488 lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
489 }
490
491 lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
492 lphc->droppedRect.left, lphc->droppedRect.top, lphc->droppedRect.right - lphc->droppedRect.left,
493 lphc->droppedRect.bottom - lphc->droppedRect.top, hwnd, (HMENU)ID_CB_LISTBOX,
494 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), lphc );
495 if( lphc->hWndLBox )
496 {
497 BOOL bEdit = TRUE;
498 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
499
500 if( lphc->wState & CBF_EDIT )
501 {
502 if( lphc->dwStyle & CBS_OEMCONVERT )
503 lbeStyle |= ES_OEMCONVERT;
504 if( lphc->dwStyle & CBS_AUTOHSCROLL )
505 lbeStyle |= ES_AUTOHSCROLL;
506 if( lphc->dwStyle & CBS_LOWERCASE )
507 lbeStyle |= ES_LOWERCASE;
508 else if( lphc->dwStyle & CBS_UPPERCASE )
509 lbeStyle |= ES_UPPERCASE;
510
511 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
512
513 lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
514 lphc->textRect.left, lphc->textRect.top,
515 lphc->textRect.right - lphc->textRect.left,
516 lphc->textRect.bottom - lphc->textRect.top,
517 hwnd, (HMENU)ID_CB_EDIT,
518 (HINSTANCE)GetWindowLongPtrW( hwnd, GWLP_HINSTANCE ), NULL );
519 if( !lphc->hWndEdit )
520 bEdit = FALSE;
521 }
522
523 if( bEdit )
524 {
525 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
526 {
527 /* Now do the trick with parent */
528 SetParent(lphc->hWndLBox, HWND_DESKTOP);
529 /*
530 * If the combo is a dropdown, we must resize the control
531 * to fit only the text area and button. To do this,
532 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
533 * will take care of setting the height for us.
534 */
535 CBForceDummyResize(lphc);
536 }
537
538 TRACE("init done\n");
539 return 0;
540 }
541 ERR("edit control failure.\n");
542 } else ERR("listbox failure.\n");
543 } else ERR("no owner for visible combo.\n");
544
545 /* CreateWindow() will send WM_NCDESTROY to cleanup */
546
547 return -1;
548 }
549
550 /***********************************************************************
551 * CBPaintButton
552 *
553 * Paint combo button (normal, pressed, and disabled states).
554 */
CBPaintButton(HEADCOMBO * lphc,HDC hdc)555 static void CBPaintButton(HEADCOMBO *lphc, HDC hdc)
556 {
557 UINT buttonState = DFCS_SCROLLCOMBOBOX;
558
559 if (IsRectEmpty(&lphc->buttonRect))
560 return;
561
562 if( lphc->wState & CBF_NOREDRAW )
563 return;
564
565
566 if (lphc->wState & CBF_BUTTONDOWN)
567 buttonState |= DFCS_PUSHED;
568
569 if (CB_DISABLED(lphc))
570 buttonState |= DFCS_INACTIVE;
571
572 DrawFrameControl(hdc, &lphc->buttonRect, DFC_SCROLL, buttonState);
573 }
574
575 /***********************************************************************
576 * COMBO_PrepareColors
577 *
578 * This method will sent the appropriate WM_CTLCOLOR message to
579 * prepare and setup the colors for the combo's DC.
580 *
581 * It also returns the brush to use for the background.
582 */
COMBO_PrepareColors(LPHEADCOMBO lphc,HDC hDC)583 static HBRUSH COMBO_PrepareColors(
584 LPHEADCOMBO lphc,
585 HDC hDC)
586 {
587 HBRUSH hBkgBrush;
588
589 /*
590 * Get the background brush for this control.
591 */
592 if (CB_DISABLED(lphc))
593 {
594 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
595 (WPARAM)hDC, (LPARAM)lphc->self );
596
597 /*
598 * We have to change the text color since WM_CTLCOLORSTATIC will
599 * set it to the "enabled" color. This is the same behavior as the
600 * edit control
601 */
602 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
603 }
604 else
605 {
606 /* FIXME: In which cases WM_CTLCOLORLISTBOX should be sent? */
607 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
608 (WPARAM)hDC, (LPARAM)lphc->self );
609 }
610
611 /*
612 * Catch errors.
613 */
614 if( !hBkgBrush )
615 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
616
617 return hBkgBrush;
618 }
619
620 /***********************************************************************
621 * CBPaintText
622 *
623 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
624 */
CBPaintText(HEADCOMBO * lphc,HDC hdc_paint)625 static void CBPaintText(HEADCOMBO *lphc, HDC hdc_paint)
626 {
627 RECT rectEdit = lphc->textRect;
628 INT id, size = 0;
629 LPWSTR pText = NULL;
630
631 TRACE("\n");
632
633 /* follow Windows combobox that sends a bunch of text
634 * inquiries to its listbox while processing WM_PAINT. */
635
636 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
637 {
638 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
639 if (size == LB_ERR)
640 FIXME("LB_ERR probably not handled yet\n");
641 if ((pText = heap_alloc((size + 1) * sizeof(WCHAR))))
642 {
643 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
644 size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, id, (LPARAM)pText);
645 pText[size] = '\0'; /* just in case */
646 } else return;
647 }
648
649 if( lphc->wState & CBF_EDIT )
650 {
651 static const WCHAR empty_stringW[] = { 0 };
652 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
653 if( lphc->wState & CBF_FOCUSED )
654 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, MAXLONG);
655 }
656 else if(!(lphc->wState & CBF_NOREDRAW) && IsWindowVisible( lphc->self ))
657 {
658 /* paint text field ourselves */
659 HDC hdc = hdc_paint ? hdc_paint : GetDC(lphc->self);
660 UINT itemState = ODS_COMBOBOXEDIT;
661 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
662 HBRUSH hPrevBrush, hBkgBrush;
663
664 /*
665 * Give ourselves some space.
666 */
667 InflateRect( &rectEdit, -1, -1 );
668
669 hBkgBrush = COMBO_PrepareColors( lphc, hdc );
670 hPrevBrush = SelectObject( hdc, hBkgBrush );
671 FillRect( hdc, &rectEdit, hBkgBrush );
672
673 if( CB_OWNERDRAWN(lphc) )
674 {
675 DRAWITEMSTRUCT dis;
676 HRGN clipRegion;
677 UINT ctlid = (UINT)GetWindowLongPtrW( lphc->self, GWLP_ID );
678
679 /* setup state for DRAWITEM message. Owner will highlight */
680 if ( (lphc->wState & CBF_FOCUSED) &&
681 !(lphc->wState & CBF_DROPPED) )
682 itemState |= ODS_SELECTED | ODS_FOCUS;
683
684 if (!IsWindowEnabled(lphc->self)) itemState |= ODS_DISABLED;
685
686 dis.CtlType = ODT_COMBOBOX;
687 dis.CtlID = ctlid;
688 dis.hwndItem = lphc->self;
689 dis.itemAction = ODA_DRAWENTIRE;
690 dis.itemID = id;
691 dis.itemState = itemState;
692 dis.hDC = hdc;
693 dis.rcItem = rectEdit;
694 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, id, 0);
695
696 /*
697 * Clip the DC and have the parent draw the item.
698 */
699 clipRegion = set_control_clipping( hdc, &rectEdit );
700
701 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
702
703 SelectClipRgn( hdc, clipRegion );
704 if (clipRegion) DeleteObject( clipRegion );
705 }
706 else
707 {
708 static const WCHAR empty_stringW[] = { 0 };
709
710 if ( (lphc->wState & CBF_FOCUSED) &&
711 !(lphc->wState & CBF_DROPPED) ) {
712
713 /* highlight */
714 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
715 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
716 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
717 }
718
719 ExtTextOutW( hdc,
720 rectEdit.left + 1,
721 rectEdit.top + 1,
722 ETO_OPAQUE | ETO_CLIPPED,
723 &rectEdit,
724 pText ? pText : empty_stringW , size, NULL );
725
726 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
727 DrawFocusRect( hdc, &rectEdit );
728 }
729
730 if( hPrevFont )
731 SelectObject(hdc, hPrevFont );
732
733 if( hPrevBrush )
734 SelectObject( hdc, hPrevBrush );
735
736 if( !hdc_paint )
737 ReleaseDC( lphc->self, hdc );
738 }
739
740 heap_free(pText);
741 }
742
743 /***********************************************************************
744 * CBPaintBorder
745 */
CBPaintBorder(const HEADCOMBO * lphc,HDC hdc)746 static void CBPaintBorder(const HEADCOMBO *lphc, HDC hdc)
747 {
748 RECT clientRect;
749
750 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
751 {
752 GetClientRect(lphc->self, &clientRect);
753 }
754 else
755 {
756 clientRect = lphc->textRect;
757
758 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
759 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
760 }
761
762 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
763 }
764
COMBO_ThemedPaint(HTHEME theme,HEADCOMBO * lphc,HDC hdc)765 static LRESULT COMBO_ThemedPaint(HTHEME theme, HEADCOMBO *lphc, HDC hdc)
766 {
767 int button_state;
768 RECT frame;
769
770 /* paint border */
771 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
772 GetClientRect(lphc->self, &frame);
773 else
774 {
775 frame = lphc->textRect;
776 InflateRect(&frame, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
777 InflateRect(&frame, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
778 }
779
780 DrawThemeBackground(theme, hdc, 0, IsWindowEnabled(lphc->self) ? CBXS_NORMAL : CBXS_DISABLED, &frame, NULL);
781
782 /* Paint button */
783 if (!IsRectEmpty(&lphc->buttonRect))
784 {
785 if (!IsWindowEnabled(lphc->self))
786 button_state = CBXS_DISABLED;
787 else if (lphc->wState & CBF_BUTTONDOWN)
788 button_state = CBXS_PRESSED;
789 else if (lphc->wState & CBF_HOT)
790 button_state = CBXS_HOT;
791 else
792 button_state = CBXS_NORMAL;
793 DrawThemeBackground(theme, hdc, CP_DROPDOWNBUTTON, button_state, &lphc->buttonRect, NULL);
794 }
795
796 if ((lphc->dwStyle & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST)
797 CBPaintText(lphc, hdc);
798
799 return 0;
800 }
801
802 /***********************************************************************
803 * COMBO_Paint
804 */
COMBO_Paint(HEADCOMBO * lphc,HDC hdc)805 static LRESULT COMBO_Paint(HEADCOMBO *lphc, HDC hdc)
806 {
807 HBRUSH hPrevBrush, hBkgBrush;
808
809 TRACE("hdc=%p\n", hdc);
810
811 /*
812 * Retrieve the background brush and select it in the
813 * DC.
814 */
815 hBkgBrush = COMBO_PrepareColors(lphc, hdc);
816 hPrevBrush = SelectObject(hdc, hBkgBrush);
817 if (!(lphc->wState & CBF_EDIT))
818 FillRect(hdc, &lphc->textRect, hBkgBrush);
819
820 /*
821 * In non 3.1 look, there is a sunken border on the combobox
822 */
823 CBPaintBorder(lphc, hdc);
824
825 CBPaintButton(lphc, hdc);
826
827 /* paint the edit control padding area */
828 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
829 {
830 RECT rPadEdit = lphc->textRect;
831
832 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
833
834 FrameRect(hdc, &rPadEdit, GetSysColorBrush(COLOR_WINDOW));
835 }
836
837 if (!(lphc->wState & CBF_EDIT))
838 CBPaintText( lphc, hdc );
839
840 if (hPrevBrush)
841 SelectObject( hdc, hPrevBrush );
842
843 return 0;
844 }
845
846 /***********************************************************************
847 * CBUpdateLBox
848 *
849 * Select listbox entry according to the contents of the edit control.
850 */
CBUpdateLBox(LPHEADCOMBO lphc,BOOL bSelect)851 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
852 {
853 INT length, idx;
854 LPWSTR pText = NULL;
855
856 idx = LB_ERR;
857 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
858
859 if (length > 0)
860 pText = heap_alloc((length + 1) * sizeof(WCHAR));
861
862 TRACE("\t edit text length %i\n", length );
863
864 if( pText )
865 {
866 GetWindowTextW( lphc->hWndEdit, pText, length + 1);
867 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING, -1, (LPARAM)pText);
868 heap_free( pText );
869 }
870
871 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, bSelect ? idx : -1, 0);
872
873 /* probably superfluous but Windows sends this too */
874 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, idx < 0 ? 0 : idx, 0);
875 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, idx < 0 ? 0 : idx, 0);
876
877 return idx;
878 }
879
880 /***********************************************************************
881 * CBUpdateEdit
882 *
883 * Copy a listbox entry to the edit control.
884 */
CBUpdateEdit(LPHEADCOMBO lphc,INT index)885 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
886 {
887 INT length;
888 LPWSTR pText = NULL;
889 static const WCHAR empty_stringW[] = { 0 };
890
891 TRACE("\t %i\n", index );
892
893 if( index >= 0 ) /* got an entry */
894 {
895 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, index, 0);
896 if( length != LB_ERR)
897 {
898 if ((pText = heap_alloc((length + 1) * sizeof(WCHAR))))
899 SendMessageW(lphc->hWndLBox, LB_GETTEXT, index, (LPARAM)pText);
900 }
901 }
902
903 if( CB_HASSTRINGS(lphc) )
904 {
905 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
906 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
907 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
908 }
909
910 if( lphc->wState & CBF_FOCUSED )
911 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
912
913 heap_free( pText );
914 }
915
916 /***********************************************************************
917 * CBDropDown
918 *
919 * Show listbox popup.
920 */
CBDropDown(LPHEADCOMBO lphc)921 static void CBDropDown( LPHEADCOMBO lphc )
922 {
923 HMONITOR monitor;
924 MONITORINFO mon_info;
925 RECT rect,r;
926 int nItems;
927 int nDroppedHeight;
928
929 TRACE("[%p]: drop down\n", lphc->self);
930
931 CB_NOTIFY( lphc, CBN_DROPDOWN );
932
933 /* set selection */
934
935 lphc->wState |= CBF_DROPPED;
936 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
937 {
938 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
939
940 /* Update edit only if item is in the list */
941 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
942 CBUpdateEdit( lphc, lphc->droppedIndex );
943 }
944 else
945 {
946 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
947
948 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
949 lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex, 0);
950 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
951 }
952
953 /* now set popup position */
954 GetWindowRect( lphc->self, &rect );
955
956 /*
957 * If it's a dropdown, the listbox is offset
958 */
959 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
960 rect.left += COMBO_EDITBUTTONSPACE();
961
962 /* if the dropped height is greater than the total height of the dropped
963 items list, then force the drop down list height to be the total height
964 of the items in the dropped list */
965
966 /* And Remove any extra space (Best Fit) */
967 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
968 /* if listbox length has been set directly by its handle */
969 GetWindowRect(lphc->hWndLBox, &r);
970 if (nDroppedHeight < r.bottom - r.top)
971 nDroppedHeight = r.bottom - r.top;
972 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
973
974 if (nItems > 0)
975 {
976 int nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
977
978 if (lphc->dwStyle & CBS_NOINTEGRALHEIGHT)
979 {
980 nDroppedHeight -= 1;
981 }
982 else
983 {
984 if (nItems > lphc->visibleItems)
985 nItems = lphc->visibleItems;
986 nDroppedHeight = nItems * nIHeight + COMBO_YBORDERSIZE();
987 }
988 }
989
990 r.left = rect.left;
991 r.top = rect.bottom;
992 r.right = r.left + lphc->droppedRect.right - lphc->droppedRect.left;
993 r.bottom = r.top + nDroppedHeight;
994
995 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
996 monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
997 mon_info.cbSize = sizeof(mon_info);
998 GetMonitorInfoW( monitor, &mon_info );
999
1000 if (r.bottom > mon_info.rcWork.bottom)
1001 {
1002 r.top = max( rect.top - nDroppedHeight, mon_info.rcWork.top );
1003 r.bottom = min( r.top + nDroppedHeight, mon_info.rcWork.bottom );
1004 }
1005
1006 SetWindowPos( lphc->hWndLBox, HWND_TOPMOST, r.left, r.top, r.right - r.left, r.bottom - r.top,
1007 SWP_NOACTIVATE | SWP_SHOWWINDOW );
1008
1009
1010 if( !(lphc->wState & CBF_NOREDRAW) )
1011 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1012
1013 EnableWindow( lphc->hWndLBox, TRUE );
1014 if (GetCapture() != lphc->self)
1015 SetCapture(lphc->hWndLBox);
1016 }
1017
1018 /***********************************************************************
1019 * CBRollUp
1020 *
1021 * Hide listbox popup.
1022 */
CBRollUp(LPHEADCOMBO lphc,BOOL ok,BOOL bButton)1023 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1024 {
1025 HWND hWnd = lphc->self;
1026
1027 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1028 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1029
1030 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1031
1032 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1033 {
1034
1035 if( lphc->wState & CBF_DROPPED )
1036 {
1037 RECT rect;
1038
1039 lphc->wState &= ~CBF_DROPPED;
1040 ShowWindow( lphc->hWndLBox, SW_HIDE );
1041
1042 if(GetCapture() == lphc->hWndLBox)
1043 {
1044 ReleaseCapture();
1045 }
1046
1047 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1048 {
1049 rect = lphc->buttonRect;
1050 }
1051 else
1052 {
1053 if( bButton )
1054 {
1055 UnionRect( &rect,
1056 &lphc->buttonRect,
1057 &lphc->textRect);
1058 }
1059 else
1060 rect = lphc->textRect;
1061
1062 bButton = TRUE;
1063 }
1064
1065 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1066 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1067 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1068 CB_NOTIFY( lphc, CBN_CLOSEUP );
1069 }
1070 }
1071 }
1072
1073 /***********************************************************************
1074 * COMBO_FlipListbox
1075 *
1076 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1077 */
COMBO_FlipListbox(LPHEADCOMBO lphc,BOOL ok,BOOL bRedrawButton)1078 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1079 {
1080 if( lphc->wState & CBF_DROPPED )
1081 {
1082 CBRollUp( lphc, ok, bRedrawButton );
1083 return FALSE;
1084 }
1085
1086 CBDropDown( lphc );
1087 return TRUE;
1088 }
1089
1090 /***********************************************************************
1091 * CBRepaintButton
1092 */
CBRepaintButton(LPHEADCOMBO lphc)1093 static void CBRepaintButton( LPHEADCOMBO lphc )
1094 {
1095 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1096 UpdateWindow(lphc->self);
1097 }
1098
1099 /***********************************************************************
1100 * COMBO_SetFocus
1101 */
COMBO_SetFocus(LPHEADCOMBO lphc)1102 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1103 {
1104 if( !(lphc->wState & CBF_FOCUSED) )
1105 {
1106 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1107 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1108
1109 /* This is wrong. Message sequences seem to indicate that this
1110 is set *after* the notify. */
1111 /* lphc->wState |= CBF_FOCUSED; */
1112
1113 if( !(lphc->wState & CBF_EDIT) )
1114 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1115
1116 CB_NOTIFY( lphc, CBN_SETFOCUS );
1117 lphc->wState |= CBF_FOCUSED;
1118 }
1119 }
1120
1121 /***********************************************************************
1122 * COMBO_KillFocus
1123 */
COMBO_KillFocus(LPHEADCOMBO lphc)1124 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1125 {
1126 HWND hWnd = lphc->self;
1127
1128 if( lphc->wState & CBF_FOCUSED )
1129 {
1130 CBRollUp( lphc, FALSE, TRUE );
1131 if( IsWindow( hWnd ) )
1132 {
1133 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1134 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1135
1136 lphc->wState &= ~CBF_FOCUSED;
1137
1138 /* redraw text */
1139 if( !(lphc->wState & CBF_EDIT) )
1140 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1141
1142 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1143 }
1144 }
1145 }
1146
1147 /***********************************************************************
1148 * COMBO_Command
1149 */
COMBO_Command(LPHEADCOMBO lphc,WPARAM wParam,HWND hWnd)1150 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1151 {
1152 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1153 {
1154 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1155
1156 switch( HIWORD(wParam) >> 8 )
1157 {
1158 case (EN_SETFOCUS >> 8):
1159
1160 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1161
1162 COMBO_SetFocus( lphc );
1163 break;
1164
1165 case (EN_KILLFOCUS >> 8):
1166
1167 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1168
1169 /* NOTE: it seems that Windows' edit control sends an
1170 * undocumented message WM_USER + 0x1B instead of this
1171 * notification (only when it happens to be a part of
1172 * the combo). ?? - AK.
1173 */
1174
1175 COMBO_KillFocus( lphc );
1176 break;
1177
1178
1179 case (EN_CHANGE >> 8):
1180 /*
1181 * In some circumstances (when the selection of the combobox
1182 * is changed for example) we don't want the EN_CHANGE notification
1183 * to be forwarded to the parent of the combobox. This code
1184 * checks a flag that is set in these occasions and ignores the
1185 * notification.
1186 */
1187 if (lphc->wState & CBF_NOLBSELECT)
1188 {
1189 lphc->wState &= ~CBF_NOLBSELECT;
1190 }
1191 else
1192 {
1193 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1194 }
1195
1196 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1197 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1198 break;
1199
1200 case (EN_UPDATE >> 8):
1201 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1202 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1203 break;
1204
1205 case (EN_ERRSPACE >> 8):
1206 CB_NOTIFY( lphc, CBN_ERRSPACE );
1207 }
1208 }
1209 else if( lphc->hWndLBox == hWnd )
1210 {
1211 switch( (short)HIWORD(wParam) )
1212 {
1213 case LBN_ERRSPACE:
1214 CB_NOTIFY( lphc, CBN_ERRSPACE );
1215 break;
1216
1217 case LBN_DBLCLK:
1218 CB_NOTIFY( lphc, CBN_DBLCLK );
1219 break;
1220
1221 case LBN_SELCHANGE:
1222 case LBN_SELCANCEL:
1223
1224 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1225
1226 /* do not roll up if selection is being tracked
1227 * by arrow keys in the dropdown listbox */
1228 if (!(lphc->wState & CBF_NOROLLUP))
1229 {
1230 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1231 }
1232 else lphc->wState &= ~CBF_NOROLLUP;
1233
1234 CB_NOTIFY( lphc, CBN_SELCHANGE );
1235
1236 if( HIWORD(wParam) == LBN_SELCHANGE)
1237 {
1238 if( lphc->wState & CBF_EDIT )
1239 lphc->wState |= CBF_NOLBSELECT;
1240 CBPaintText( lphc, NULL );
1241 }
1242 break;
1243
1244 case LBN_SETFOCUS:
1245 case LBN_KILLFOCUS:
1246 /* nothing to do here since ComboLBox always resets the focus to its
1247 * combo/edit counterpart */
1248 break;
1249 }
1250 }
1251 return 0;
1252 }
1253
1254 /***********************************************************************
1255 * COMBO_ItemOp
1256 *
1257 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1258 */
COMBO_ItemOp(LPHEADCOMBO lphc,UINT msg,LPARAM lParam)1259 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1260 {
1261 HWND hWnd = lphc->self;
1262 UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1263
1264 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1265
1266 switch( msg )
1267 {
1268 case WM_DELETEITEM:
1269 {
1270 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1271 lpIS->CtlType = ODT_COMBOBOX;
1272 lpIS->CtlID = id;
1273 lpIS->hwndItem = hWnd;
1274 break;
1275 }
1276 case WM_DRAWITEM:
1277 {
1278 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1279 lpIS->CtlType = ODT_COMBOBOX;
1280 lpIS->CtlID = id;
1281 lpIS->hwndItem = hWnd;
1282 break;
1283 }
1284 case WM_COMPAREITEM:
1285 {
1286 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1287 lpIS->CtlType = ODT_COMBOBOX;
1288 lpIS->CtlID = id;
1289 lpIS->hwndItem = hWnd;
1290 break;
1291 }
1292 case WM_MEASUREITEM:
1293 {
1294 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1295 lpIS->CtlType = ODT_COMBOBOX;
1296 lpIS->CtlID = id;
1297 break;
1298 }
1299 }
1300 return SendMessageW(lphc->owner, msg, id, lParam);
1301 }
1302
1303
1304 /***********************************************************************
1305 * COMBO_GetTextW
1306 */
COMBO_GetText(HEADCOMBO * lphc,INT count,LPWSTR buf)1307 static LRESULT COMBO_GetText( HEADCOMBO *lphc, INT count, LPWSTR buf )
1308 {
1309 INT length;
1310
1311 if( lphc->wState & CBF_EDIT )
1312 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1313
1314 /* get it from the listbox */
1315
1316 if (!count || !buf) return 0;
1317 if( lphc->hWndLBox )
1318 {
1319 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1320 if (idx == LB_ERR) goto error;
1321 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1322 if (length == LB_ERR) goto error;
1323
1324 /* 'length' is without the terminating character */
1325 if (length >= count)
1326 {
1327 WCHAR *lpBuffer = heap_alloc((length + 1) * sizeof(WCHAR));
1328 if (!lpBuffer) goto error;
1329 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1330
1331 /* truncate if buffer is too short */
1332 if (length != LB_ERR)
1333 {
1334 lstrcpynW( buf, lpBuffer, count );
1335 length = count;
1336 }
1337 heap_free( lpBuffer );
1338 }
1339 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1340
1341 if (length == LB_ERR) return 0;
1342 return length;
1343 }
1344
1345 error: /* error - truncate string, return zero */
1346 buf[0] = 0;
1347 return 0;
1348 }
1349
1350 /***********************************************************************
1351 * CBResetPos
1352 *
1353 * This function sets window positions according to the updated
1354 * component placement struct.
1355 */
CBResetPos(HEADCOMBO * combo)1356 static void CBResetPos(HEADCOMBO *combo)
1357 {
1358 BOOL drop = CB_GETTYPE(combo) != CBS_SIMPLE;
1359
1360 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1361 * sizing messages */
1362 if (combo->wState & CBF_EDIT)
1363 SetWindowPos(combo->hWndEdit, 0, combo->textRect.left, combo->textRect.top,
1364 combo->textRect.right - combo->textRect.left,
1365 combo->textRect.bottom - combo->textRect.top,
1366 SWP_NOZORDER | SWP_NOACTIVATE | (drop ? SWP_NOREDRAW : 0));
1367
1368 SetWindowPos(combo->hWndLBox, 0, combo->droppedRect.left, combo->droppedRect.top,
1369 combo->droppedRect.right - combo->droppedRect.left,
1370 combo->droppedRect.bottom - combo->droppedRect.top,
1371 SWP_NOACTIVATE | SWP_NOZORDER | (drop ? SWP_NOREDRAW : 0));
1372
1373 if (drop)
1374 {
1375 if (combo->wState & CBF_DROPPED)
1376 {
1377 combo->wState &= ~CBF_DROPPED;
1378 ShowWindow(combo->hWndLBox, SW_HIDE);
1379 }
1380
1381 if (!(combo->wState & CBF_NOREDRAW))
1382 RedrawWindow(combo->self, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW);
1383 }
1384 }
1385
1386
1387 /***********************************************************************
1388 * COMBO_Size
1389 */
COMBO_Size(HEADCOMBO * lphc)1390 static void COMBO_Size( HEADCOMBO *lphc )
1391 {
1392 if (!lphc->hWndLBox || (lphc->wState & CBF_NORESIZE))
1393 return;
1394
1395 /*
1396 * Those controls are always the same height. So we have to make sure
1397 * they are not resized to another value.
1398 */
1399 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1400 {
1401 int newComboHeight, curComboHeight, curComboWidth;
1402 RECT rc;
1403
1404 GetWindowRect(lphc->self, &rc);
1405 curComboHeight = rc.bottom - rc.top;
1406 curComboWidth = rc.right - rc.left;
1407 newComboHeight = CBGetTextAreaHeight(lphc, TRUE) + 2*COMBO_YBORDERSIZE();
1408
1409 /*
1410 * Resizing a combobox has another side effect, it resizes the dropped
1411 * rectangle as well. However, it does it only if the new height for the
1412 * combobox is more than the height it should have. In other words,
1413 * if the application resizing the combobox only had the intention to resize
1414 * the actual control, for example, to do the layout of a dialog that is
1415 * resized, the height of the dropdown is not changed.
1416 */
1417 if( curComboHeight > newComboHeight )
1418 {
1419 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1420 curComboHeight, newComboHeight, lphc->droppedRect.bottom,
1421 lphc->droppedRect.top);
1422 lphc->droppedRect.bottom = lphc->droppedRect.top + curComboHeight - newComboHeight;
1423 }
1424 /*
1425 * Restore original height
1426 */
1427 if (curComboHeight != newComboHeight)
1428 {
1429 lphc->wState |= CBF_NORESIZE;
1430 SetWindowPos(lphc->self, 0, 0, 0, curComboWidth, newComboHeight,
1431 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOREDRAW);
1432 lphc->wState &= ~CBF_NORESIZE;
1433 }
1434 }
1435
1436 CBCalcPlacement(lphc);
1437
1438 CBResetPos(lphc);
1439 }
1440
1441
1442 /***********************************************************************
1443 * COMBO_Font
1444 */
COMBO_Font(LPHEADCOMBO lphc,HFONT hFont,BOOL bRedraw)1445 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1446 {
1447 lphc->hFont = hFont;
1448 lphc->item_height = combo_get_text_height(lphc);
1449
1450 /*
1451 * Propagate to owned windows.
1452 */
1453 if( lphc->wState & CBF_EDIT )
1454 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1455 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1456
1457 /*
1458 * Redo the layout of the control.
1459 */
1460 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1461 {
1462 CBCalcPlacement(lphc);
1463
1464 CBResetPos(lphc);
1465 }
1466 else
1467 {
1468 CBForceDummyResize(lphc);
1469 }
1470 }
1471
1472
1473 /***********************************************************************
1474 * COMBO_SetItemHeight
1475 */
COMBO_SetItemHeight(LPHEADCOMBO lphc,INT index,INT height)1476 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1477 {
1478 LRESULT lRet = CB_ERR;
1479
1480 if( index == -1 ) /* set text field height */
1481 {
1482 if( height < 32768 )
1483 {
1484 lphc->item_height = height + 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1485
1486 /*
1487 * Redo the layout of the control.
1488 */
1489 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1490 {
1491 CBCalcPlacement(lphc);
1492
1493 CBResetPos(lphc);
1494 }
1495 else
1496 {
1497 CBForceDummyResize(lphc);
1498 }
1499
1500 lRet = height;
1501 }
1502 }
1503 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1504 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT, index, height);
1505 return lRet;
1506 }
1507
1508 /***********************************************************************
1509 * COMBO_SelectString
1510 */
COMBO_SelectString(LPHEADCOMBO lphc,INT start,LPARAM pText)1511 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText)
1512 {
1513 INT index = SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, start, pText);
1514 if( index >= 0 )
1515 {
1516 if( lphc->wState & CBF_EDIT )
1517 CBUpdateEdit( lphc, index );
1518 else
1519 {
1520 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1521 }
1522 }
1523 return (LRESULT)index;
1524 }
1525
1526 /***********************************************************************
1527 * COMBO_LButtonDown
1528 */
COMBO_LButtonDown(LPHEADCOMBO lphc,LPARAM lParam)1529 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1530 {
1531 POINT pt;
1532 BOOL bButton;
1533 HWND hWnd = lphc->self;
1534
1535 pt.x = (short)LOWORD(lParam);
1536 pt.y = (short)HIWORD(lParam);
1537 bButton = PtInRect(&lphc->buttonRect, pt);
1538
1539 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1540 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1541 {
1542 lphc->wState |= CBF_BUTTONDOWN;
1543 if( lphc->wState & CBF_DROPPED )
1544 {
1545 /* got a click to cancel selection */
1546
1547 lphc->wState &= ~CBF_BUTTONDOWN;
1548 CBRollUp( lphc, TRUE, FALSE );
1549 if( !IsWindow( hWnd ) ) return;
1550
1551 if( lphc->wState & CBF_CAPTURE )
1552 {
1553 lphc->wState &= ~CBF_CAPTURE;
1554 ReleaseCapture();
1555 }
1556 }
1557 else
1558 {
1559 /* drop down the listbox and start tracking */
1560
1561 lphc->wState |= CBF_CAPTURE;
1562 SetCapture( hWnd );
1563 CBDropDown( lphc );
1564 }
1565 if( bButton ) CBRepaintButton( lphc );
1566 }
1567 }
1568
1569 /***********************************************************************
1570 * COMBO_LButtonUp
1571 *
1572 * Release capture and stop tracking if needed.
1573 */
COMBO_LButtonUp(LPHEADCOMBO lphc)1574 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1575 {
1576 if( lphc->wState & CBF_CAPTURE )
1577 {
1578 lphc->wState &= ~CBF_CAPTURE;
1579 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1580 {
1581 INT index = CBUpdateLBox( lphc, TRUE );
1582 /* Update edit only if item is in the list */
1583 if(index >= 0)
1584 {
1585 lphc->wState |= CBF_NOLBSELECT;
1586 CBUpdateEdit( lphc, index );
1587 lphc->wState &= ~CBF_NOLBSELECT;
1588 }
1589 }
1590 ReleaseCapture();
1591 SetCapture(lphc->hWndLBox);
1592 }
1593
1594 if( lphc->wState & CBF_BUTTONDOWN )
1595 {
1596 lphc->wState &= ~CBF_BUTTONDOWN;
1597 CBRepaintButton( lphc );
1598 }
1599 }
1600
1601 /***********************************************************************
1602 * COMBO_MouseMove
1603 *
1604 * Two things to do - track combo button and release capture when
1605 * pointer goes into the listbox.
1606 */
COMBO_MouseMove(LPHEADCOMBO lphc,WPARAM wParam,LPARAM lParam)1607 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1608 {
1609 POINT pt;
1610 RECT lbRect;
1611
1612 pt.x = (short)LOWORD(lParam);
1613 pt.y = (short)HIWORD(lParam);
1614
1615 if( lphc->wState & CBF_BUTTONDOWN )
1616 {
1617 BOOL bButton;
1618
1619 bButton = PtInRect(&lphc->buttonRect, pt);
1620
1621 if( !bButton )
1622 {
1623 lphc->wState &= ~CBF_BUTTONDOWN;
1624 CBRepaintButton( lphc );
1625 }
1626 }
1627
1628 GetClientRect( lphc->hWndLBox, &lbRect );
1629 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1630 if( PtInRect(&lbRect, pt) )
1631 {
1632 lphc->wState &= ~CBF_CAPTURE;
1633 ReleaseCapture();
1634 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1635
1636 /* hand over pointer tracking */
1637 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1638 }
1639 }
1640
COMBO_GetComboBoxInfo(const HEADCOMBO * lphc,COMBOBOXINFO * pcbi)1641 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1642 {
1643 if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1644 return FALSE;
1645
1646 pcbi->rcItem = lphc->textRect;
1647 pcbi->rcButton = lphc->buttonRect;
1648 pcbi->stateButton = 0;
1649 if (lphc->wState & CBF_BUTTONDOWN)
1650 pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1651 if (IsRectEmpty(&lphc->buttonRect))
1652 pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1653 pcbi->hwndCombo = lphc->self;
1654 pcbi->hwndItem = lphc->hWndEdit;
1655 pcbi->hwndList = lphc->hWndLBox;
1656 return TRUE;
1657 }
1658
COMBO_WindowProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)1659 static LRESULT CALLBACK COMBO_WindowProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
1660 {
1661 HEADCOMBO *lphc = (HEADCOMBO *)GetWindowLongPtrW( hwnd, 0 );
1662 HTHEME theme;
1663
1664 TRACE("[%p]: msg %#x wp %08lx lp %08lx\n", hwnd, message, wParam, lParam );
1665
1666 if (!IsWindow(hwnd)) return 0;
1667
1668 if (lphc || message == WM_NCCREATE)
1669 switch(message)
1670 {
1671 case WM_NCCREATE:
1672 {
1673 LONG style = ((CREATESTRUCTW *)lParam)->style;
1674 return COMBO_NCCreate(hwnd, style);
1675 }
1676
1677 case WM_NCDESTROY:
1678 COMBO_NCDestroy(lphc);
1679 break;/* -> DefWindowProc */
1680
1681 case WM_CREATE:
1682 {
1683 HWND hwndParent;
1684 LONG style;
1685
1686 hwndParent = ((CREATESTRUCTW *)lParam)->hwndParent;
1687 style = ((CREATESTRUCTW *)lParam)->style;
1688 return COMBO_Create(hwnd, lphc, hwndParent, style);
1689 }
1690
1691 case WM_DESTROY:
1692 theme = GetWindowTheme( hwnd );
1693 CloseThemeData( theme );
1694 break;
1695
1696 case WM_THEMECHANGED:
1697 theme = GetWindowTheme( hwnd );
1698 CloseThemeData( theme );
1699 OpenThemeData( hwnd, WC_COMBOBOXW );
1700 break;
1701
1702 case WM_PRINTCLIENT:
1703 case WM_PAINT:
1704 {
1705 LRESULT ret = 0;
1706 PAINTSTRUCT ps;
1707 HDC hdc;
1708
1709 hdc = wParam ? (HDC)wParam : BeginPaint(hwnd, &ps);
1710
1711 if (hdc && !(lphc->wState & CBF_NOREDRAW))
1712 {
1713 HTHEME theme = GetWindowTheme(hwnd);
1714
1715 if (theme)
1716 ret = COMBO_ThemedPaint(theme, lphc, hdc);
1717 else
1718 ret = COMBO_Paint(lphc, hdc);
1719 }
1720
1721 if (!wParam)
1722 EndPaint(hwnd, &ps);
1723
1724 return ret;
1725 }
1726 case WM_ERASEBKGND:
1727 /* do all painting in WM_PAINT like Windows does */
1728 return 1;
1729
1730 case WM_GETDLGCODE:
1731 {
1732 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1733 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1734 {
1735 int vk = (int)((LPMSG)lParam)->wParam;
1736
1737 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1738 result |= DLGC_WANTMESSAGE;
1739 }
1740 return result;
1741 }
1742
1743 case WM_SIZE:
1744 COMBO_Size( lphc );
1745 return TRUE;
1746
1747 case WM_SETFONT:
1748 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1749 return TRUE;
1750
1751 case WM_GETFONT:
1752 return (LRESULT)lphc->hFont;
1753
1754 case WM_SETFOCUS:
1755 if (lphc->wState & CBF_EDIT)
1756 {
1757 SetFocus( lphc->hWndEdit );
1758 /* The first time focus is received, select all the text */
1759 if (!(lphc->wState & CBF_BEENFOCUSED))
1760 {
1761 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1762 lphc->wState |= CBF_BEENFOCUSED;
1763 }
1764 }
1765 else
1766 COMBO_SetFocus( lphc );
1767 return TRUE;
1768
1769 case WM_KILLFOCUS:
1770 {
1771 HWND hwndFocus = (HWND)wParam;
1772 if (!hwndFocus || (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox))
1773 COMBO_KillFocus( lphc );
1774 return TRUE;
1775 }
1776
1777 case WM_COMMAND:
1778 return COMBO_Command( lphc, wParam, (HWND)lParam );
1779
1780 case WM_GETTEXT:
1781 return COMBO_GetText( lphc, wParam, (LPWSTR)lParam );
1782
1783 case WM_SETTEXT:
1784 case WM_GETTEXTLENGTH:
1785 case WM_CLEAR:
1786 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1787 {
1788 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1789 if (j == -1) return 0;
1790 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1791 }
1792 else if ( lphc->wState & CBF_EDIT )
1793 {
1794 LRESULT ret;
1795 lphc->wState |= CBF_NOEDITNOTIFY;
1796 ret = SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1797 lphc->wState &= ~CBF_NOEDITNOTIFY;
1798 return ret;
1799 }
1800 else
1801 return CB_ERR;
1802
1803 case WM_CUT:
1804 case WM_PASTE:
1805 case WM_COPY:
1806 if (lphc->wState & CBF_EDIT)
1807 return SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1808 else return CB_ERR;
1809
1810 case WM_DRAWITEM:
1811 case WM_DELETEITEM:
1812 case WM_COMPAREITEM:
1813 case WM_MEASUREITEM:
1814 return COMBO_ItemOp(lphc, message, lParam);
1815
1816 case WM_ENABLE:
1817 if (lphc->wState & CBF_EDIT)
1818 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1819 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1820
1821 /* Force the control to repaint when the enabled state changes. */
1822 InvalidateRect(lphc->self, NULL, TRUE);
1823 return TRUE;
1824
1825 case WM_SETREDRAW:
1826 if (wParam)
1827 lphc->wState &= ~CBF_NOREDRAW;
1828 else
1829 lphc->wState |= CBF_NOREDRAW;
1830
1831 if ( lphc->wState & CBF_EDIT )
1832 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1833 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1834 return 0;
1835
1836 case WM_SYSKEYDOWN:
1837 if ( KEYDATA_ALT & HIWORD(lParam) )
1838 if( wParam == VK_UP || wParam == VK_DOWN )
1839 #ifdef __REACTOS__
1840 {
1841 #endif
1842 COMBO_FlipListbox( lphc, FALSE, FALSE );
1843 return 0;
1844 #ifdef __REACTOS__
1845 }
1846 break;
1847 #endif
1848
1849 case WM_KEYDOWN:
1850 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1851 (lphc->wState & CBF_DROPPED))
1852 {
1853 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1854 return TRUE;
1855 }
1856 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1857 {
1858 COMBO_FlipListbox( lphc, FALSE, FALSE );
1859 return TRUE;
1860 }
1861 /* fall through */
1862 case WM_CHAR:
1863 case WM_IME_CHAR:
1864 {
1865 HWND hwndTarget;
1866
1867 #ifdef __REACTOS__
1868 if (lphc->wState & CBF_DROPPED)
1869 lphc->wState |= CBF_NOROLLUP;
1870 #endif
1871 if ( lphc->wState & CBF_EDIT )
1872 hwndTarget = lphc->hWndEdit;
1873 else
1874 hwndTarget = lphc->hWndLBox;
1875
1876 return SendMessageW(hwndTarget, message, wParam, lParam);
1877 }
1878
1879 case WM_LBUTTONDOWN:
1880 if ( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
1881 if ( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1882 return TRUE;
1883
1884 case WM_LBUTTONUP:
1885 COMBO_LButtonUp( lphc );
1886 return TRUE;
1887
1888 case WM_MOUSEMOVE:
1889 if (!IsRectEmpty(&lphc->buttonRect))
1890 {
1891 POINT pt;
1892
1893 pt.x = (short)LOWORD(lParam);
1894 pt.y = (short)HIWORD(lParam);
1895
1896 if (PtInRect(&lphc->buttonRect, pt))
1897 {
1898 if (!(lphc->wState & CBF_HOT))
1899 {
1900 lphc->wState |= CBF_HOT;
1901 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW);
1902 }
1903 }
1904 else if (lphc->wState & CBF_HOT)
1905 {
1906 lphc->wState &= ~CBF_HOT;
1907 RedrawWindow(hwnd, &lphc->buttonRect, 0, RDW_INVALIDATE | RDW_UPDATENOW);
1908 }
1909 }
1910
1911 if ( lphc->wState & CBF_CAPTURE )
1912 COMBO_MouseMove( lphc, wParam, lParam );
1913 return TRUE;
1914
1915 case WM_MOUSEWHEEL:
1916 if (wParam & (MK_SHIFT | MK_CONTROL))
1917 return DefWindowProcW(hwnd, message, wParam, lParam);
1918
1919 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
1920 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
1921 return TRUE;
1922
1923 case WM_CTLCOLOR:
1924 case WM_CTLCOLORMSGBOX:
1925 case WM_CTLCOLOREDIT:
1926 case WM_CTLCOLORLISTBOX:
1927 case WM_CTLCOLORBTN:
1928 case WM_CTLCOLORDLG:
1929 case WM_CTLCOLORSCROLLBAR:
1930 case WM_CTLCOLORSTATIC:
1931 return SendMessageW(lphc->owner, message, wParam, lParam);
1932
1933 /* Combo messages */
1934 case CB_ADDSTRING:
1935 if (lphc->dwStyle & CBS_LOWERCASE)
1936 CharLowerW((LPWSTR)lParam);
1937 else if (lphc->dwStyle & CBS_UPPERCASE)
1938 CharUpperW((LPWSTR)lParam);
1939 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
1940
1941 case CB_INSERTSTRING:
1942 if (lphc->dwStyle & CBS_LOWERCASE)
1943 CharLowerW((LPWSTR)lParam);
1944 else if (lphc->dwStyle & CBS_UPPERCASE)
1945 CharUpperW((LPWSTR)lParam);
1946 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1947
1948 case CB_DELETESTRING:
1949 return SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
1950
1951 case CB_SELECTSTRING:
1952 return COMBO_SelectString(lphc, (INT)wParam, lParam);
1953
1954 case CB_FINDSTRING:
1955 return SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
1956
1957 case CB_FINDSTRINGEXACT:
1958 return SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
1959
1960 case CB_SETITEMHEIGHT:
1961 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
1962
1963 case CB_GETITEMHEIGHT:
1964 if ((INT)wParam >= 0) /* listbox item */
1965 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
1966 return CBGetTextAreaHeight(lphc, FALSE);
1967
1968 case CB_RESETCONTENT:
1969 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
1970
1971 if ((lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc))
1972 {
1973 static const WCHAR empty_stringW[] = { 0 };
1974 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
1975 }
1976 else
1977 InvalidateRect(lphc->self, NULL, TRUE);
1978 return TRUE;
1979
1980 case CB_INITSTORAGE:
1981 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
1982
1983 case CB_GETHORIZONTALEXTENT:
1984 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
1985
1986 case CB_SETHORIZONTALEXTENT:
1987 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
1988
1989 case CB_GETTOPINDEX:
1990 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
1991
1992 case CB_GETLOCALE:
1993 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
1994
1995 case CB_SETLOCALE:
1996 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
1997
1998 case CB_SETDROPPEDWIDTH:
1999 if ((CB_GETTYPE(lphc) == CBS_SIMPLE) || (INT)wParam >= 32768)
2000 return CB_ERR;
2001
2002 /* new value must be higher than combobox width */
2003 if ((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2004 lphc->droppedWidth = wParam;
2005 else if (wParam)
2006 lphc->droppedWidth = 0;
2007
2008 /* recalculate the combobox area */
2009 CBCalcPlacement(lphc);
2010
2011 /* fall through */
2012 case CB_GETDROPPEDWIDTH:
2013 if (lphc->droppedWidth)
2014 return lphc->droppedWidth;
2015 return lphc->droppedRect.right - lphc->droppedRect.left;
2016
2017 case CB_GETDROPPEDCONTROLRECT:
2018 if (lParam)
2019 CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2020 return CB_OKAY;
2021
2022 case CB_GETDROPPEDSTATE:
2023 return (lphc->wState & CBF_DROPPED) != 0;
2024
2025 case CB_DIR:
2026 return SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam);
2027
2028 case CB_SHOWDROPDOWN:
2029 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
2030 {
2031 if (wParam)
2032 {
2033 if (!(lphc->wState & CBF_DROPPED))
2034 CBDropDown( lphc );
2035 }
2036 else if (lphc->wState & CBF_DROPPED)
2037 CBRollUp( lphc, FALSE, TRUE );
2038 }
2039 return TRUE;
2040
2041 case CB_GETCOUNT:
2042 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2043
2044 case CB_GETCURSEL:
2045 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2046
2047 case CB_SETCURSEL:
2048 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2049 if (lParam >= 0)
2050 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2051
2052 /* no LBN_SELCHANGE in this case, update manually */
2053 CBPaintText(lphc, NULL);
2054 lphc->wState &= ~CBF_SELCHANGE;
2055 return lParam;
2056
2057 case CB_GETLBTEXT:
2058 return SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2059
2060 case CB_GETLBTEXTLEN:
2061 return SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2062
2063 case CB_GETITEMDATA:
2064 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2065
2066 case CB_SETITEMDATA:
2067 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2068
2069 case CB_GETEDITSEL:
2070 /* Edit checks passed parameters itself */
2071 if (lphc->wState & CBF_EDIT)
2072 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2073 return CB_ERR;
2074
2075 case CB_SETEDITSEL:
2076 if (lphc->wState & CBF_EDIT)
2077 return SendMessageW(lphc->hWndEdit, EM_SETSEL, (INT)(SHORT)LOWORD(lParam), (INT)(SHORT)HIWORD(lParam) );
2078 return CB_ERR;
2079
2080 case CB_SETEXTENDEDUI:
2081 if (CB_GETTYPE(lphc) == CBS_SIMPLE )
2082 return CB_ERR;
2083 if (wParam)
2084 lphc->wState |= CBF_EUI;
2085 else
2086 lphc->wState &= ~CBF_EUI;
2087 return CB_OKAY;
2088
2089 case CB_GETEXTENDEDUI:
2090 return (lphc->wState & CBF_EUI) != 0;
2091
2092 case CB_GETCOMBOBOXINFO:
2093 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2094
2095 case CB_LIMITTEXT:
2096 if (lphc->wState & CBF_EDIT)
2097 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2098 return TRUE;
2099
2100 case CB_GETMINVISIBLE:
2101 return lphc->visibleItems;
2102
2103 case CB_SETMINVISIBLE:
2104 lphc->visibleItems = (INT)wParam;
2105 return TRUE;
2106
2107 default:
2108 if (message >= WM_USER)
2109 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n", message - WM_USER, wParam, lParam );
2110 break;
2111 }
2112
2113 return DefWindowProcW(hwnd, message, wParam, lParam);
2114 }
2115
COMBO_Register(void)2116 void COMBO_Register(void)
2117 {
2118 WNDCLASSW wndClass;
2119
2120 memset(&wndClass, 0, sizeof(wndClass));
2121 wndClass.style = CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS;
2122 wndClass.lpfnWndProc = COMBO_WindowProc;
2123 wndClass.cbClsExtra = 0;
2124 wndClass.cbWndExtra = sizeof(HEADCOMBO *);
2125 wndClass.hCursor = LoadCursorW(0, (LPWSTR)IDC_ARROW);
2126 wndClass.hbrBackground = NULL;
2127 wndClass.lpszClassName = WC_COMBOBOXW;
2128 RegisterClassW(&wndClass);
2129 }
2130
2131 #ifdef __REACTOS__
COMBO_Unregister(void)2132 void COMBO_Unregister(void)
2133 {
2134 UnregisterClassW(WC_COMBOBOXW, NULL);
2135 }
2136 #endif
2137