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