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