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