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