Synchronize with trunk's revision r57629.
[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 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1046 monitor = MonitorFromRect( &rect, MONITOR_DEFAULTTOPRIMARY );
1047 mon_info.cbSize = sizeof(mon_info);
1048 GetMonitorInfoW( monitor, &mon_info );
1049
1050 if( (rect.bottom + nDroppedHeight) >= mon_info.rcWork.bottom )
1051 rect.bottom = rect.top - nDroppedHeight;
1052
1053 SetWindowPos( lphc->hWndLBox, HWND_TOPMOST, rect.left, rect.bottom,
1054 lphc->droppedRect.right - lphc->droppedRect.left,
1055 nDroppedHeight,
1056 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1057
1058
1059 if( !(lphc->wState & CBF_NOREDRAW) )
1060 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1061 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1062
1063 EnableWindow( lphc->hWndLBox, TRUE );
1064 if (GetCapture() != lphc->self)
1065 SetCapture(lphc->hWndLBox);
1066 }
1067
1068 /***********************************************************************
1069 * CBRollUp
1070 *
1071 * Hide listbox popup.
1072 */
1073 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1074 {
1075 HWND hWnd = lphc->self;
1076
1077 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1078 lphc->self, ok, (INT)(lphc->wState & CBF_DROPPED));
1079
1080 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1081
1082 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1083 {
1084
1085 if( lphc->wState & CBF_DROPPED )
1086 {
1087 RECT rect;
1088
1089 lphc->wState &= ~CBF_DROPPED;
1090 ShowWindow( lphc->hWndLBox, SW_HIDE );
1091
1092 if(GetCapture() == lphc->hWndLBox)
1093 {
1094 ReleaseCapture();
1095 }
1096
1097 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1098 {
1099 rect = lphc->buttonRect;
1100 }
1101 else
1102 {
1103 if( bButton )
1104 {
1105 UnionRect( &rect,
1106 &lphc->buttonRect,
1107 &lphc->textRect);
1108 }
1109 else
1110 rect = lphc->textRect;
1111
1112 bButton = TRUE;
1113 }
1114
1115 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1116 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1117 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1118 CB_NOTIFY( lphc, CBN_CLOSEUP );
1119 }
1120 }
1121 }
1122
1123 /***********************************************************************
1124 * COMBO_FlipListbox
1125 *
1126 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1127 */
1128 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1129 {
1130 if( lphc->wState & CBF_DROPPED )
1131 {
1132 CBRollUp( lphc, ok, bRedrawButton );
1133 return FALSE;
1134 }
1135
1136 CBDropDown( lphc );
1137 return TRUE;
1138 }
1139
1140 /***********************************************************************
1141 * CBRepaintButton
1142 */
1143 static void CBRepaintButton( LPHEADCOMBO lphc )
1144 {
1145 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1146 UpdateWindow(lphc->self);
1147 }
1148
1149 /***********************************************************************
1150 * COMBO_SetFocus
1151 */
1152 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1153 {
1154 if( !(lphc->wState & CBF_FOCUSED) )
1155 {
1156 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1157 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1158
1159 /* This is wrong. Message sequences seem to indicate that this
1160 is set *after* the notify. */
1161 /* lphc->wState |= CBF_FOCUSED; */
1162
1163 if( !(lphc->wState & CBF_EDIT) )
1164 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1165
1166 CB_NOTIFY( lphc, CBN_SETFOCUS );
1167 lphc->wState |= CBF_FOCUSED;
1168 }
1169 }
1170
1171 /***********************************************************************
1172 * COMBO_KillFocus
1173 */
1174 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1175 {
1176 HWND hWnd = lphc->self;
1177
1178 if( lphc->wState & CBF_FOCUSED )
1179 {
1180 CBRollUp( lphc, FALSE, TRUE );
1181 if( IsWindow( hWnd ) )
1182 {
1183 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1184 SendMessageW(lphc->hWndLBox, LB_CARETOFF, 0, 0);
1185
1186 lphc->wState &= ~CBF_FOCUSED;
1187
1188 /* redraw text */
1189 if( !(lphc->wState & CBF_EDIT) )
1190 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1191
1192 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1193 }
1194 }
1195 }
1196
1197 /***********************************************************************
1198 * COMBO_Command
1199 */
1200 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1201 {
1202 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1203 {
1204 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1205
1206 switch( HIWORD(wParam) >> 8 )
1207 {
1208 case (EN_SETFOCUS >> 8):
1209
1210 TRACE("[%p]: edit [%p] got focus\n", lphc->self, lphc->hWndEdit );
1211
1212 COMBO_SetFocus( lphc );
1213 break;
1214
1215 case (EN_KILLFOCUS >> 8):
1216
1217 TRACE("[%p]: edit [%p] lost focus\n", lphc->self, lphc->hWndEdit );
1218
1219 /* NOTE: it seems that Windows' edit control sends an
1220 * undocumented message WM_USER + 0x1B instead of this
1221 * notification (only when it happens to be a part of
1222 * the combo). ?? - AK.
1223 */
1224
1225 COMBO_KillFocus( lphc );
1226 break;
1227
1228
1229 case (EN_CHANGE >> 8):
1230 /*
1231 * In some circumstances (when the selection of the combobox
1232 * is changed for example) we don't want the EN_CHANGE notification
1233 * to be forwarded to the parent of the combobox. This code
1234 * checks a flag that is set in these occasions and ignores the
1235 * notification.
1236 */
1237 if (lphc->wState & CBF_NOLBSELECT)
1238 {
1239 lphc->wState &= ~CBF_NOLBSELECT;
1240 }
1241 else
1242 {
1243 CBUpdateLBox( lphc, lphc->wState & CBF_DROPPED );
1244 }
1245
1246 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1247 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1248 break;
1249
1250 case (EN_UPDATE >> 8):
1251 if (!(lphc->wState & CBF_NOEDITNOTIFY))
1252 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1253 break;
1254
1255 case (EN_ERRSPACE >> 8):
1256 CB_NOTIFY( lphc, CBN_ERRSPACE );
1257 }
1258 }
1259 else if( lphc->hWndLBox == hWnd )
1260 {
1261 switch( (short)HIWORD(wParam) )
1262 {
1263 case LBN_ERRSPACE:
1264 CB_NOTIFY( lphc, CBN_ERRSPACE );
1265 break;
1266
1267 case LBN_DBLCLK:
1268 CB_NOTIFY( lphc, CBN_DBLCLK );
1269 break;
1270
1271 case LBN_SELCHANGE:
1272 case LBN_SELCANCEL:
1273
1274 TRACE("[%p]: lbox selection change [%x]\n", lphc->self, lphc->wState );
1275
1276 /* do not roll up if selection is being tracked
1277 * by arrow keys in the dropdown listbox */
1278 if (!(lphc->wState & CBF_NOROLLUP))
1279 {
1280 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1281 }
1282 else lphc->wState &= ~CBF_NOROLLUP;
1283
1284 CB_NOTIFY( lphc, CBN_SELCHANGE );
1285
1286 if( HIWORD(wParam) == LBN_SELCHANGE)
1287 {
1288 if( lphc->wState & CBF_EDIT )
1289 {
1290 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1291 lphc->wState |= CBF_NOLBSELECT;
1292 CBUpdateEdit( lphc, index );
1293 /* select text in edit, as Windows does */
1294 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1295 }
1296 else
1297 {
1298 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1299 UpdateWindow(lphc->self);
1300 }
1301 }
1302 break;
1303
1304 case LBN_SETFOCUS:
1305 case LBN_KILLFOCUS:
1306 /* nothing to do here since ComboLBox always resets the focus to its
1307 * combo/edit counterpart */
1308 break;
1309 }
1310 }
1311 return 0;
1312 }
1313
1314 /***********************************************************************
1315 * COMBO_ItemOp
1316 *
1317 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1318 */
1319 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, LPARAM lParam )
1320 {
1321 HWND hWnd = lphc->self;
1322 UINT id = (UINT)GetWindowLongPtrW( hWnd, GWLP_ID );
1323
1324 TRACE("[%p]: ownerdraw op %04x\n", lphc->self, msg );
1325
1326 switch( msg )
1327 {
1328 case WM_DELETEITEM:
1329 {
1330 DELETEITEMSTRUCT *lpIS = (DELETEITEMSTRUCT *)lParam;
1331 lpIS->CtlType = ODT_COMBOBOX;
1332 lpIS->CtlID = id;
1333 lpIS->hwndItem = hWnd;
1334 break;
1335 }
1336 case WM_DRAWITEM:
1337 {
1338 DRAWITEMSTRUCT *lpIS = (DRAWITEMSTRUCT *)lParam;
1339 lpIS->CtlType = ODT_COMBOBOX;
1340 lpIS->CtlID = id;
1341 lpIS->hwndItem = hWnd;
1342 break;
1343 }
1344 case WM_COMPAREITEM:
1345 {
1346 COMPAREITEMSTRUCT *lpIS = (COMPAREITEMSTRUCT *)lParam;
1347 lpIS->CtlType = ODT_COMBOBOX;
1348 lpIS->CtlID = id;
1349 lpIS->hwndItem = hWnd;
1350 break;
1351 }
1352 case WM_MEASUREITEM:
1353 {
1354 MEASUREITEMSTRUCT *lpIS = (MEASUREITEMSTRUCT *)lParam;
1355 lpIS->CtlType = ODT_COMBOBOX;
1356 lpIS->CtlID = id;
1357 break;
1358 }
1359 }
1360 return SendMessageW(lphc->owner, msg, id, lParam);
1361 }
1362
1363
1364 /***********************************************************************
1365 * COMBO_GetTextW
1366 */
1367 static LRESULT COMBO_GetTextW( LPHEADCOMBO lphc, INT count, LPWSTR buf )
1368 {
1369 INT length;
1370
1371 if( lphc->wState & CBF_EDIT )
1372 return SendMessageW( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1373
1374 /* get it from the listbox */
1375
1376 if (!count || !buf) return 0;
1377 if( lphc->hWndLBox )
1378 {
1379 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1380 if (idx == LB_ERR) goto error;
1381 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1382 if (length == LB_ERR) goto error;
1383
1384 /* 'length' is without the terminating character */
1385 if (length >= count)
1386 {
1387 LPWSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1388 if (!lpBuffer) goto error;
1389 length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1390
1391 /* truncate if buffer is too short */
1392 if (length != LB_ERR)
1393 {
1394 lstrcpynW( buf, lpBuffer, count );
1395 length = count;
1396 }
1397 HeapFree( GetProcessHeap(), 0, lpBuffer );
1398 }
1399 else length = SendMessageW(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1400
1401 if (length == LB_ERR) return 0;
1402 return length;
1403 }
1404
1405 error: /* error - truncate string, return zero */
1406 buf[0] = 0;
1407 return 0;
1408 }
1409
1410
1411 /***********************************************************************
1412 * COMBO_GetTextA
1413 *
1414 * NOTE! LB_GETTEXT does not count terminating \0, WM_GETTEXT does.
1415 * also LB_GETTEXT might return values < 0, WM_GETTEXT doesn't.
1416 */
1417 static LRESULT COMBO_GetTextA( LPHEADCOMBO lphc, INT count, LPSTR buf )
1418 {
1419 INT length;
1420
1421 if( lphc->wState & CBF_EDIT )
1422 return SendMessageA( lphc->hWndEdit, WM_GETTEXT, count, (LPARAM)buf );
1423
1424 /* get it from the listbox */
1425
1426 if (!count || !buf) return 0;
1427 if( lphc->hWndLBox )
1428 {
1429 INT idx = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1430 if (idx == LB_ERR) goto error;
1431 length = SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, idx, 0 );
1432 if (length == LB_ERR) goto error;
1433
1434 /* 'length' is without the terminating character */
1435 if (length >= count)
1436 {
1437 LPSTR lpBuffer = HeapAlloc(GetProcessHeap(), 0, (length + 1) );
1438 if (!lpBuffer) goto error;
1439 length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)lpBuffer);
1440
1441 /* truncate if buffer is too short */
1442 if (length != LB_ERR)
1443 {
1444 lstrcpynA( buf, lpBuffer, count );
1445 length = count;
1446 }
1447 HeapFree( GetProcessHeap(), 0, lpBuffer );
1448 }
1449 else length = SendMessageA(lphc->hWndLBox, LB_GETTEXT, idx, (LPARAM)buf);
1450
1451 if (length == LB_ERR) return 0;
1452 return length;
1453 }
1454
1455 error: /* error - truncate string, return zero */
1456 buf[0] = 0;
1457 return 0;
1458 }
1459
1460
1461 /***********************************************************************
1462 * CBResetPos
1463 *
1464 * This function sets window positions according to the updated
1465 * component placement struct.
1466 */
1467 static void CBResetPos(
1468 LPHEADCOMBO lphc,
1469 const RECT *rectEdit,
1470 const RECT *rectLB,
1471 BOOL bRedraw)
1472 {
1473 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1474
1475 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1476 * sizing messages */
1477
1478 if( lphc->wState & CBF_EDIT )
1479 SetWindowPos( lphc->hWndEdit, 0,
1480 rectEdit->left, rectEdit->top,
1481 rectEdit->right - rectEdit->left,
1482 rectEdit->bottom - rectEdit->top,
1483 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1484
1485 SetWindowPos( lphc->hWndLBox, 0,
1486 rectLB->left, rectLB->top,
1487 rectLB->right - rectLB->left,
1488 rectLB->bottom - rectLB->top,
1489 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1490
1491 if( bDrop )
1492 {
1493 if( lphc->wState & CBF_DROPPED )
1494 {
1495 lphc->wState &= ~CBF_DROPPED;
1496 ShowWindow( lphc->hWndLBox, SW_HIDE );
1497 }
1498
1499 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1500 RedrawWindow( lphc->self, NULL, 0,
1501 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1502 }
1503 }
1504
1505
1506 /***********************************************************************
1507 * COMBO_Size
1508 */
1509 static void COMBO_Size( LPHEADCOMBO lphc, LPARAM lParam )
1510 {
1511 /*
1512 * Those controls are always the same height. So we have to make sure
1513 * they are not resized to another value.
1514 */
1515 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1516 {
1517 int newComboHeight;
1518
1519 newComboHeight = CBGetTextAreaHeight(lphc->self, lphc) + 2*COMBO_YBORDERSIZE();
1520
1521 /*
1522 * Resizing a combobox has another side effect, it resizes the dropped
1523 * rectangle as well. However, it does it only if the new height for the
1524 * combobox is more than the height it should have. In other words,
1525 * if the application resizing the combobox only had the intention to resize
1526 * the actual control, for example, to do the layout of a dialog that is
1527 * resized, the height of the dropdown is not changed.
1528 */
1529 if( HIWORD(lParam) > newComboHeight )
1530 {
1531 TRACE("oldComboHeight=%d, newComboHeight=%d, oldDropBottom=%d, oldDropTop=%d\n",
1532 HIWORD(lParam), newComboHeight, lphc->droppedRect.bottom,
1533 lphc->droppedRect.top);
1534 lphc->droppedRect.bottom = lphc->droppedRect.top + HIWORD(lParam) - newComboHeight;
1535 }
1536 /*
1537 * Restore original height
1538 */
1539 if( HIWORD(lParam) != newComboHeight )
1540 SetWindowPos(lphc->self, 0, 0, 0, LOWORD(lParam), newComboHeight,
1541 SWP_NOZORDER|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOREDRAW);
1542 }
1543
1544 CBCalcPlacement(lphc->self,
1545 lphc,
1546 &lphc->textRect,
1547 &lphc->buttonRect,
1548 &lphc->droppedRect);
1549
1550 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1551 }
1552
1553
1554 /***********************************************************************
1555 * COMBO_Font
1556 */
1557 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1558 {
1559 /*
1560 * Set the font
1561 */
1562 lphc->hFont = hFont;
1563
1564 /*
1565 * Propagate to owned windows.
1566 */
1567 if( lphc->wState & CBF_EDIT )
1568 SendMessageW(lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw);
1569 SendMessageW(lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw);
1570
1571 /*
1572 * Redo the layout of the control.
1573 */
1574 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1575 {
1576 CBCalcPlacement(lphc->self,
1577 lphc,
1578 &lphc->textRect,
1579 &lphc->buttonRect,
1580 &lphc->droppedRect);
1581
1582 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1583 }
1584 else
1585 {
1586 CBForceDummyResize(lphc);
1587 }
1588 }
1589
1590
1591 /***********************************************************************
1592 * COMBO_SetItemHeight
1593 */
1594 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1595 {
1596 LRESULT lRet = CB_ERR;
1597
1598 if( index == -1 ) /* set text field height */
1599 {
1600 if( height < 32768 )
1601 {
1602 lphc->editHeight = height + 2; /* Is the 2 for 2*EDIT_CONTROL_PADDING? */
1603
1604 /*
1605 * Redo the layout of the control.
1606 */
1607 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1608 {
1609 CBCalcPlacement(lphc->self,
1610 lphc,
1611 &lphc->textRect,
1612 &lphc->buttonRect,
1613 &lphc->droppedRect);
1614
1615 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1616 }
1617 else
1618 {
1619 CBForceDummyResize(lphc);
1620 }
1621
1622 lRet = height;
1623 }
1624 }
1625 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1626 lRet = SendMessageW(lphc->hWndLBox, LB_SETITEMHEIGHT,
1627 (WPARAM)index, (LPARAM)height );
1628 return lRet;
1629 }
1630
1631 /***********************************************************************
1632 * COMBO_SelectString
1633 */
1634 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPARAM pText, BOOL unicode )
1635 {
1636 INT index = unicode ? SendMessageW(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText) :
1637 SendMessageA(lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, pText);
1638 if( index >= 0 )
1639 {
1640 if( lphc->wState & CBF_EDIT )
1641 CBUpdateEdit( lphc, index );
1642 else
1643 {
1644 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1645 }
1646 }
1647 return (LRESULT)index;
1648 }
1649
1650 /***********************************************************************
1651 * COMBO_LButtonDown
1652 */
1653 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1654 {
1655 POINT pt;
1656 BOOL bButton;
1657 HWND hWnd = lphc->self;
1658
1659 pt.x = (short)LOWORD(lParam);
1660 pt.y = (short)HIWORD(lParam);
1661 bButton = PtInRect(&lphc->buttonRect, pt);
1662
1663 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1664 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1665 {
1666 lphc->wState |= CBF_BUTTONDOWN;
1667 if( lphc->wState & CBF_DROPPED )
1668 {
1669 /* got a click to cancel selection */
1670
1671 lphc->wState &= ~CBF_BUTTONDOWN;
1672 CBRollUp( lphc, TRUE, FALSE );
1673 if( !IsWindow( hWnd ) ) return;
1674
1675 if( lphc->wState & CBF_CAPTURE )
1676 {
1677 lphc->wState &= ~CBF_CAPTURE;
1678 ReleaseCapture();
1679 }
1680 }
1681 else
1682 {
1683 /* drop down the listbox and start tracking */
1684
1685 lphc->wState |= CBF_CAPTURE;
1686 SetCapture( hWnd );
1687 CBDropDown( lphc );
1688 }
1689 if( bButton ) CBRepaintButton( lphc );
1690 }
1691 }
1692
1693 /***********************************************************************
1694 * COMBO_LButtonUp
1695 *
1696 * Release capture and stop tracking if needed.
1697 */
1698 static void COMBO_LButtonUp( LPHEADCOMBO lphc )
1699 {
1700 if( lphc->wState & CBF_CAPTURE )
1701 {
1702 lphc->wState &= ~CBF_CAPTURE;
1703 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1704 {
1705 INT index = CBUpdateLBox( lphc, TRUE );
1706 /* Update edit only if item is in the list */
1707 if(index >= 0)
1708 {
1709 lphc->wState |= CBF_NOLBSELECT;
1710 CBUpdateEdit( lphc, index );
1711 lphc->wState &= ~CBF_NOLBSELECT;
1712 }
1713 }
1714 ReleaseCapture();
1715 SetCapture(lphc->hWndLBox);
1716 }
1717
1718 if( lphc->wState & CBF_BUTTONDOWN )
1719 {
1720 lphc->wState &= ~CBF_BUTTONDOWN;
1721 CBRepaintButton( lphc );
1722 }
1723 }
1724
1725 /***********************************************************************
1726 * COMBO_MouseMove
1727 *
1728 * Two things to do - track combo button and release capture when
1729 * pointer goes into the listbox.
1730 */
1731 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1732 {
1733 POINT pt;
1734 RECT lbRect;
1735
1736 pt.x = (short)LOWORD(lParam);
1737 pt.y = (short)HIWORD(lParam);
1738
1739 if( lphc->wState & CBF_BUTTONDOWN )
1740 {
1741 BOOL bButton;
1742
1743 bButton = PtInRect(&lphc->buttonRect, pt);
1744
1745 if( !bButton )
1746 {
1747 lphc->wState &= ~CBF_BUTTONDOWN;
1748 CBRepaintButton( lphc );
1749 }
1750 }
1751
1752 GetClientRect( lphc->hWndLBox, &lbRect );
1753 MapWindowPoints( lphc->self, lphc->hWndLBox, &pt, 1 );
1754 if( PtInRect(&lbRect, pt) )
1755 {
1756 lphc->wState &= ~CBF_CAPTURE;
1757 ReleaseCapture();
1758 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc, TRUE );
1759
1760 /* hand over pointer tracking */
1761 SendMessageW(lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam);
1762 }
1763 }
1764
1765 static LRESULT COMBO_GetComboBoxInfo(const HEADCOMBO *lphc, COMBOBOXINFO *pcbi)
1766 {
1767 if (!pcbi || (pcbi->cbSize < sizeof(COMBOBOXINFO)))
1768 return FALSE;
1769
1770 pcbi->rcItem = lphc->textRect;
1771 pcbi->rcButton = lphc->buttonRect;
1772 pcbi->stateButton = 0;
1773 if (lphc->wState & CBF_BUTTONDOWN)
1774 pcbi->stateButton |= STATE_SYSTEM_PRESSED;
1775 if (IsRectEmpty(&lphc->buttonRect))
1776 pcbi->stateButton |= STATE_SYSTEM_INVISIBLE;
1777 pcbi->hwndCombo = lphc->self;
1778 pcbi->hwndItem = lphc->hWndEdit;
1779 pcbi->hwndList = lphc->hWndLBox;
1780 return TRUE;
1781 }
1782
1783 static char *strdupA(LPCSTR str)
1784 {
1785 char *ret;
1786 DWORD len;
1787
1788 if(!str) return NULL;
1789
1790 len = strlen(str);
1791 ret = HeapAlloc(GetProcessHeap(), 0, len + 1);
1792 if (ret != NULL)
1793 memcpy(ret, str, len + 1);
1794 return ret;
1795 }
1796
1797 /***********************************************************************
1798 * ComboWndProc_common
1799 */
1800 LRESULT WINAPI ComboWndProc_common( HWND hwnd, UINT message,
1801 WPARAM wParam, LPARAM lParam, BOOL unicode )
1802 {
1803 LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongPtrW( hwnd, 0 );
1804 #ifdef __REACTOS__
1805 PWND pWnd;
1806
1807 pWnd = ValidateHwnd(hwnd);
1808 if (pWnd)
1809 {
1810 if (!pWnd->fnid)
1811 {
1812 NtUserSetWindowFNID(hwnd, FNID_COMBOBOX);
1813 }
1814 else
1815 {
1816 if (pWnd->fnid != FNID_COMBOBOX)
1817 {
1818 ERR("Wrong window class for ComboBox! fnId 0x%x\n",pWnd->fnid);
1819 return 0;
1820 }
1821 }
1822 }
1823 #endif
1824
1825 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1826 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1827
1828 if( lphc || message == WM_NCCREATE )
1829 switch(message)
1830 {
1831
1832 /* System messages */
1833
1834 case WM_NCCREATE:
1835 {
1836 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1837 ((LPCREATESTRUCTA)lParam)->style;
1838 return COMBO_NCCreate(hwnd, style);
1839 }
1840 case WM_NCDESTROY:
1841 COMBO_NCDestroy(lphc);
1842 #ifdef __REACTOS__
1843 NtUserSetWindowFNID(hwnd, FNID_DESTROY);
1844 #endif
1845 break;/* -> DefWindowProc */
1846
1847 case WM_CREATE:
1848 {
1849 HWND hwndParent;
1850 LONG style;
1851 if(unicode)
1852 {
1853 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1854 style = ((LPCREATESTRUCTW)lParam)->style;
1855 }
1856 else
1857 {
1858 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1859 style = ((LPCREATESTRUCTA)lParam)->style;
1860 }
1861 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1862 }
1863
1864 case WM_PRINTCLIENT:
1865 /* Fallthrough */
1866 case WM_PAINT:
1867 /* wParam may contain a valid HDC! */
1868 return COMBO_Paint(lphc, (HDC)wParam);
1869
1870 case WM_ERASEBKGND:
1871 /* do all painting in WM_PAINT like Windows does */
1872 return 1;
1873
1874 case WM_GETDLGCODE:
1875 {
1876 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1877 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1878 {
1879 int vk = (int)((LPMSG)lParam)->wParam;
1880
1881 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1882 result |= DLGC_WANTMESSAGE;
1883 }
1884 return result;
1885 }
1886 case WM_SIZE:
1887 if( lphc->hWndLBox &&
1888 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc, lParam );
1889 return TRUE;
1890 case WM_SETFONT:
1891 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1892 return TRUE;
1893 case WM_GETFONT:
1894 return (LRESULT)lphc->hFont;
1895 case WM_SETFOCUS:
1896 if( lphc->wState & CBF_EDIT ) {
1897 SetFocus( lphc->hWndEdit );
1898 /* The first time focus is received, select all the text */
1899 if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1900 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1901 lphc->wState |= CBF_BEENFOCUSED;
1902 }
1903 }
1904 else
1905 COMBO_SetFocus( lphc );
1906 return TRUE;
1907 case WM_KILLFOCUS:
1908 {
1909 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1910 if( !hwndFocus ||
1911 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1912 COMBO_KillFocus( lphc );
1913 return TRUE;
1914 }
1915 case WM_COMMAND:
1916 return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1917 case WM_GETTEXT:
1918 return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1919 : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1920 case WM_SETTEXT:
1921 case WM_GETTEXTLENGTH:
1922 case WM_CLEAR:
1923 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1924 {
1925 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1926 if (j == -1) return 0;
1927 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1928 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1929 }
1930 else if( lphc->wState & CBF_EDIT )
1931 {
1932 LRESULT ret;
1933 lphc->wState |= CBF_NOEDITNOTIFY;
1934 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1935 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1936 lphc->wState &= ~CBF_NOEDITNOTIFY;
1937 return ret;
1938 }
1939 else return CB_ERR;
1940 case WM_CUT:
1941 case WM_PASTE:
1942 case WM_COPY:
1943 if( lphc->wState & CBF_EDIT )
1944 {
1945 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1946 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1947 }
1948 else return CB_ERR;
1949
1950 case WM_DRAWITEM:
1951 case WM_DELETEITEM:
1952 case WM_COMPAREITEM:
1953 case WM_MEASUREITEM:
1954 return COMBO_ItemOp(lphc, message, lParam);
1955 case WM_ENABLE:
1956 if( lphc->wState & CBF_EDIT )
1957 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1958 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1959
1960 /* Force the control to repaint when the enabled state changes. */
1961 InvalidateRect(lphc->self, NULL, TRUE);
1962 return TRUE;
1963 case WM_SETREDRAW:
1964 if( wParam )
1965 lphc->wState &= ~CBF_NOREDRAW;
1966 else
1967 lphc->wState |= CBF_NOREDRAW;
1968
1969 if( lphc->wState & CBF_EDIT )
1970 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1971 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1972 return 0;
1973 case WM_SYSKEYDOWN:
1974 if( KF_ALTDOWN & HIWORD(lParam) ) // ReactOS (wine) KEYDATA_ALT
1975 if( wParam == VK_UP || wParam == VK_DOWN )
1976 COMBO_FlipListbox( lphc, FALSE, FALSE );
1977 return 0;
1978
1979 case WM_KEYDOWN:
1980 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
1981 (lphc->wState & CBF_DROPPED))
1982 {
1983 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
1984 return TRUE;
1985 }
1986 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
1987 {
1988 COMBO_FlipListbox( lphc, FALSE, FALSE );
1989 return TRUE;
1990 }
1991 /* fall through */
1992 case WM_CHAR:
1993 case WM_IME_CHAR:
1994 {
1995 HWND hwndTarget;
1996
1997 if( lphc->wState & CBF_EDIT )
1998 hwndTarget = lphc->hWndEdit;
1999 else
2000 hwndTarget = lphc->hWndLBox;
2001
2002 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2003 SendMessageA(hwndTarget, message, wParam, lParam);
2004 }
2005 case WM_LBUTTONDOWN:
2006 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2007 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2008 return TRUE;
2009 case WM_LBUTTONUP:
2010 COMBO_LButtonUp( lphc );
2011 return TRUE;
2012 case WM_MOUSEMOVE:
2013 if( lphc->wState & CBF_CAPTURE )
2014 COMBO_MouseMove( lphc, wParam, lParam );
2015 return TRUE;
2016
2017 case WM_MOUSEWHEEL:
2018 if (wParam & (MK_SHIFT | MK_CONTROL))
2019 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2020 DefWindowProcA(hwnd, message, wParam, lParam);
2021
2022 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2023 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2024 return TRUE;
2025
2026 /* Combo messages */
2027
2028 case CB_ADDSTRING:
2029 if( unicode )
2030 {
2031 if( lphc->dwStyle & CBS_LOWERCASE )
2032 CharLowerW((LPWSTR)lParam);
2033 else if( lphc->dwStyle & CBS_UPPERCASE )
2034 CharUpperW((LPWSTR)lParam);
2035 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2036 }
2037 else /* unlike the unicode version, the ansi version does not overwrite
2038 the string if converting case */
2039 {
2040 char *string = NULL;
2041 LRESULT ret;
2042 if( lphc->dwStyle & CBS_LOWERCASE )
2043 {
2044 string = strdupA((LPSTR)lParam);
2045 CharLowerA(string);
2046 }
2047
2048 else if( lphc->dwStyle & CBS_UPPERCASE )
2049 {
2050 string = strdupA((LPSTR)lParam);
2051 CharUpperA(string);
2052 }
2053
2054 ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2055 HeapFree(GetProcessHeap(), 0, string);
2056 return ret;
2057 }
2058 case CB_INSERTSTRING:
2059 if( unicode )
2060 {
2061 if( lphc->dwStyle & CBS_LOWERCASE )
2062 CharLowerW((LPWSTR)lParam);
2063 else if( lphc->dwStyle & CBS_UPPERCASE )
2064 CharUpperW((LPWSTR)lParam);
2065 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2066 }
2067 else
2068 {
2069 if( lphc->dwStyle & CBS_LOWERCASE )
2070 CharLowerA((LPSTR)lParam);
2071 else if( lphc->dwStyle & CBS_UPPERCASE )
2072 CharUpperA((LPSTR)lParam);
2073
2074 return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2075 }
2076 case CB_DELETESTRING:
2077 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2078 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2079 case CB_SELECTSTRING:
2080 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2081 case CB_FINDSTRING:
2082 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2083 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2084 case CB_FINDSTRINGEXACT:
2085 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2086 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2087 case CB_SETITEMHEIGHT:
2088 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2089 case CB_GETITEMHEIGHT:
2090 if( (INT)wParam >= 0 ) /* listbox item */
2091 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2092 return CBGetTextAreaHeight(hwnd, lphc);
2093 case CB_RESETCONTENT:
2094 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2095 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2096 {
2097 static const WCHAR empty_stringW[] = { 0 };
2098 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2099 }
2100 else
2101 InvalidateRect(lphc->self, NULL, TRUE);
2102 return TRUE;
2103 case CB_INITSTORAGE:
2104 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2105 case CB_GETHORIZONTALEXTENT:
2106 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2107 case CB_SETHORIZONTALEXTENT:
2108 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2109 case CB_GETTOPINDEX:
2110 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2111 case CB_GETLOCALE:
2112 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2113 case CB_SETLOCALE:
2114 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2115 case CB_SETDROPPEDWIDTH:
2116 if( (CB_GETTYPE(lphc) == CBS_SIMPLE) ||
2117 (INT)wParam >= 32768 )
2118 return CB_ERR;
2119 /* new value must be higher than combobox width */
2120 if((INT)wParam >= lphc->droppedRect.right - lphc->droppedRect.left)
2121 lphc->droppedWidth = wParam;
2122 else if(wParam)
2123 lphc->droppedWidth = 0;
2124
2125 /* recalculate the combobox area */
2126 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
2127
2128 /* fall through */
2129 case CB_GETDROPPEDWIDTH:
2130 if( lphc->droppedWidth )
2131 return lphc->droppedWidth;
2132 return lphc->droppedRect.right - lphc->droppedRect.left;
2133 case CB_GETDROPPEDCONTROLRECT:
2134 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2135 return CB_OKAY;
2136 case CB_GETDROPPEDSTATE:
2137 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2138 case CB_DIR:
2139 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2140 SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2141
2142 case CB_SHOWDROPDOWN:
2143 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2144 {
2145 if( wParam )
2146 {
2147 if( !(lphc->wState & CBF_DROPPED) )
2148 CBDropDown( lphc );
2149 }
2150 else
2151 if( lphc->wState & CBF_DROPPED )
2152 CBRollUp( lphc, FALSE, TRUE );
2153 }
2154 return TRUE;
2155 case CB_GETCOUNT:
2156 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2157 case CB_GETCURSEL:
2158 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2159 case CB_SETCURSEL:
2160 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2161 if( lParam >= 0 )
2162 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2163
2164 /* no LBN_SELCHANGE in this case, update manually */
2165 if( lphc->wState & CBF_EDIT )
2166 CBUpdateEdit( lphc, (INT)wParam );
2167 else
2168 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2169 lphc->wState &= ~CBF_SELCHANGE;
2170 return lParam;
2171 case CB_GETLBTEXT:
2172 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2173 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2174 case CB_GETLBTEXTLEN:
2175 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2176 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2177 case CB_GETITEMDATA:
2178 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2179 case CB_SETITEMDATA:
2180 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2181 case CB_GETEDITSEL:
2182 /* Edit checks passed parameters itself */
2183 if( lphc->wState & CBF_EDIT )
2184 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2185 return CB_ERR;
2186 case CB_SETEDITSEL:
2187 if( lphc->wState & CBF_EDIT )
2188 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2189 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2190 return CB_ERR;
2191 case CB_SETEXTENDEDUI:
2192 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2193 return CB_ERR;
2194 if( wParam )
2195 lphc->wState |= CBF_EUI;
2196 else lphc->wState &= ~CBF_EUI;
2197 return CB_OKAY;
2198 case CB_GETEXTENDEDUI:
2199 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2200 case CB_GETCOMBOBOXINFO:
2201 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2202 case CB_LIMITTEXT:
2203 if( lphc->wState & CBF_EDIT )
2204 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2205 return TRUE;
2206
2207 case WM_UPDATEUISTATE:
2208 if (unicode)
2209 DefWindowProcW(lphc->self, message, wParam, lParam);
2210 else
2211 DefWindowProcA(lphc->self, message, wParam, lParam);
2212
2213 if (COMBO_update_uistate(lphc))
2214 {
2215 /* redraw text */
2216 if( !(lphc->wState & CBF_EDIT) )
2217 NtUserInvalidateRect(lphc->self, &lphc->textRect, TRUE);
2218 }
2219 break;
2220
2221 default:
2222 if (message >= WM_USER)
2223 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2224 message - WM_USER, wParam, lParam );
2225 break;
2226 }
2227 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2228 DefWindowProcA(hwnd, message, wParam, lParam);
2229 }
2230
2231 /***********************************************************************
2232 * ComboWndProcA
2233 *
2234 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2235 * window structs.
2236 */
2237 LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2238 {
2239 if (!IsWindow(hwnd)) return 0;
2240 return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2241 }
2242
2243 /***********************************************************************
2244 * ComboWndProcW
2245 */
2246 LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2247 {
2248 if (!IsWindow(hwnd)) return 0;
2249 return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2250 }
2251
2252 /*************************************************************************
2253 * GetComboBoxInfo (USER32.@)
2254 */
2255 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */
2256 PCOMBOBOXINFO pcbi /* [in/out] combo box information */)
2257 {
2258 TRACE("(%p, %p)\n", hwndCombo, pcbi);
2259 #ifndef __REACTOS__
2260 return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2261 #else
2262 return NtUserGetComboBoxInfo(hwndCombo, pcbi);
2263 #endif
2264 }