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