Merge trunk HEAD (r46369)
[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
1841 TRACE("[%p]: msg %s wp %08lx lp %08lx\n",
1842 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1843
1844 if( lphc || message == WM_NCCREATE )
1845 switch(message)
1846 {
1847
1848 /* System messages */
1849
1850 case WM_NCCREATE:
1851 {
1852 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1853 ((LPCREATESTRUCTA)lParam)->style;
1854 return COMBO_NCCreate(hwnd, style);
1855 }
1856 case WM_NCDESTROY:
1857 COMBO_NCDestroy(lphc);
1858 break;/* -> DefWindowProc */
1859
1860 case WM_CREATE:
1861 {
1862 HWND hwndParent;
1863 LONG style;
1864 if(unicode)
1865 {
1866 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1867 style = ((LPCREATESTRUCTW)lParam)->style;
1868 }
1869 else
1870 {
1871 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1872 style = ((LPCREATESTRUCTA)lParam)->style;
1873 }
1874 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1875 }
1876
1877 case WM_PRINTCLIENT:
1878 /* Fallthrough */
1879 case WM_PAINT:
1880 /* wParam may contain a valid HDC! */
1881 return COMBO_Paint(lphc, (HDC)wParam);
1882
1883 case WM_ERASEBKGND:
1884 /* do all painting in WM_PAINT like Windows does */
1885 return 1;
1886
1887 case WM_GETDLGCODE:
1888 {
1889 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1890 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1891 {
1892 int vk = (int)((LPMSG)lParam)->wParam;
1893
1894 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1895 result |= DLGC_WANTMESSAGE;
1896 }
1897 return result;
1898 }
1899 case WM_WINDOWPOSCHANGING:
1900 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1901 case WM_WINDOWPOSCHANGED:
1902 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1903 * In that case, the Combobox itself will not be resized, so we won't
1904 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1905 * do it here.
1906 */
1907 /* we should not force repainting on WM_WINDOWPOSCHANGED, it breaks
1908 * Z-order based painting.
1909 */
1910 /* fall through */
1911 case WM_SIZE:
1912 if( lphc->hWndLBox &&
1913 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc, message == WM_SIZE );
1914 return TRUE;
1915 case WM_SETFONT:
1916 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1917 return TRUE;
1918 case WM_GETFONT:
1919 return (LRESULT)lphc->hFont;
1920 case WM_SETFOCUS:
1921 if( lphc->wState & CBF_EDIT ) {
1922 SetFocus( lphc->hWndEdit );
1923 /* The first time focus is received, select all the text */
1924 if( !(lphc->wState & CBF_BEENFOCUSED) ) {
1925 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, -1);
1926 lphc->wState |= CBF_BEENFOCUSED;
1927 }
1928 }
1929 else
1930 COMBO_SetFocus( lphc );
1931 return TRUE;
1932 case WM_KILLFOCUS:
1933 {
1934 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1935 if( !hwndFocus ||
1936 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1937 COMBO_KillFocus( lphc );
1938 return TRUE;
1939 }
1940 case WM_COMMAND:
1941 return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1942 case WM_GETTEXT:
1943 return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1944 : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1945 case WM_SETTEXT:
1946 case WM_GETTEXTLENGTH:
1947 case WM_CLEAR:
1948 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1949 {
1950 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1951 if (j == -1) return 0;
1952 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1953 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1954 }
1955 else if( lphc->wState & CBF_EDIT )
1956 {
1957 LRESULT ret;
1958 lphc->wState |= CBF_NOEDITNOTIFY;
1959 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1960 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1961 lphc->wState &= ~CBF_NOEDITNOTIFY;
1962 return ret;
1963 }
1964 else return CB_ERR;
1965 case WM_CUT:
1966 case WM_PASTE:
1967 case WM_COPY:
1968 if( lphc->wState & CBF_EDIT )
1969 {
1970 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1971 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1972 }
1973 else return CB_ERR;
1974
1975 case WM_DRAWITEM:
1976 case WM_DELETEITEM:
1977 case WM_COMPAREITEM:
1978 case WM_MEASUREITEM:
1979 return COMBO_ItemOp(lphc, message, lParam);
1980 case WM_ENABLE:
1981 if( lphc->wState & CBF_EDIT )
1982 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1983 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1984
1985 /* Force the control to repaint when the enabled state changes. */
1986 InvalidateRect(lphc->self, NULL, TRUE);
1987 return TRUE;
1988 case WM_SETREDRAW:
1989 if( wParam )
1990 lphc->wState &= ~CBF_NOREDRAW;
1991 else
1992 lphc->wState |= CBF_NOREDRAW;
1993
1994 if( lphc->wState & CBF_EDIT )
1995 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1996 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1997 return 0;
1998 case WM_SYSKEYDOWN:
1999 if( KEYDATA_ALT & HIWORD(lParam) )
2000 if( wParam == VK_UP || wParam == VK_DOWN )
2001 COMBO_FlipListbox( lphc, FALSE, FALSE );
2002 return 0;
2003
2004 case WM_KEYDOWN:
2005 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2006 (lphc->wState & CBF_DROPPED))
2007 {
2008 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2009 return TRUE;
2010 }
2011 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
2012 {
2013 COMBO_FlipListbox( lphc, FALSE, FALSE );
2014 return TRUE;
2015 }
2016 /* fall through */
2017 case WM_CHAR:
2018 case WM_IME_CHAR:
2019 {
2020 HWND hwndTarget;
2021
2022 if( lphc->wState & CBF_EDIT )
2023 hwndTarget = lphc->hWndEdit;
2024 else
2025 hwndTarget = lphc->hWndLBox;
2026
2027 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2028 SendMessageA(hwndTarget, message, wParam, lParam);
2029 }
2030 case WM_LBUTTONDOWN:
2031 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2032 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2033 return TRUE;
2034 case WM_LBUTTONUP:
2035 COMBO_LButtonUp( lphc );
2036 return TRUE;
2037 case WM_MOUSEMOVE:
2038 if( lphc->wState & CBF_CAPTURE )
2039 COMBO_MouseMove( lphc, wParam, lParam );
2040 return TRUE;
2041
2042 case WM_MOUSEWHEEL:
2043 if (wParam & (MK_SHIFT | MK_CONTROL))
2044 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2045 DefWindowProcA(hwnd, message, wParam, lParam);
2046
2047 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2048 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2049 return TRUE;
2050
2051 /* Combo messages */
2052
2053 case CB_ADDSTRING:
2054 if( unicode )
2055 {
2056 if( lphc->dwStyle & CBS_LOWERCASE )
2057 CharLowerW((LPWSTR)lParam);
2058 else if( lphc->dwStyle & CBS_UPPERCASE )
2059 CharUpperW((LPWSTR)lParam);
2060 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2061 }
2062 else /* unlike the unicode version, the ansi version does not overwrite
2063 the string if converting case */
2064 {
2065 char *string = NULL;
2066 LRESULT ret;
2067 if( lphc->dwStyle & CBS_LOWERCASE )
2068 {
2069 string = strdupA((LPSTR)lParam);
2070 CharLowerA(string);
2071 }
2072
2073 else if( lphc->dwStyle & CBS_UPPERCASE )
2074 {
2075 string = strdupA((LPSTR)lParam);
2076 CharUpperA(string);
2077 }
2078
2079 ret = SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, string ? (LPARAM)string : lParam);
2080 HeapFree(GetProcessHeap(), 0, string);
2081 return ret;
2082 }
2083 case CB_INSERTSTRING:
2084 if( unicode )
2085 {
2086 if( lphc->dwStyle & CBS_LOWERCASE )
2087 CharLowerW((LPWSTR)lParam);
2088 else if( lphc->dwStyle & CBS_UPPERCASE )
2089 CharUpperW((LPWSTR)lParam);
2090 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2091 }
2092 else
2093 {
2094 if( lphc->dwStyle & CBS_LOWERCASE )
2095 CharLowerA((LPSTR)lParam);
2096 else if( lphc->dwStyle & CBS_UPPERCASE )
2097 CharUpperA((LPSTR)lParam);
2098
2099 return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2100 }
2101 case CB_DELETESTRING:
2102 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2103 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2104 case CB_SELECTSTRING:
2105 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2106 case CB_FINDSTRING:
2107 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2108 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2109 case CB_FINDSTRINGEXACT:
2110 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2111 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2112 case CB_SETITEMHEIGHT:
2113 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2114 case CB_GETITEMHEIGHT:
2115 if( (INT)wParam >= 0 ) /* listbox item */
2116 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2117 return CBGetTextAreaHeight(hwnd, lphc);
2118 case CB_RESETCONTENT:
2119 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2120 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2121 {
2122 static const WCHAR empty_stringW[] = { 0 };
2123 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2124 }
2125 else
2126 InvalidateRect(lphc->self, NULL, TRUE);
2127 return TRUE;
2128 case CB_INITSTORAGE:
2129 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2130 case CB_GETHORIZONTALEXTENT:
2131 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2132 case CB_SETHORIZONTALEXTENT:
2133 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2134 case CB_GETTOPINDEX:
2135 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2136 case CB_GETLOCALE:
2137 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2138 case CB_SETLOCALE:
2139 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2140 case CB_GETDROPPEDWIDTH:
2141 if( lphc->droppedWidth )
2142 return lphc->droppedWidth;
2143 return lphc->droppedRect.right - lphc->droppedRect.left;
2144 case CB_SETDROPPEDWIDTH:
2145 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2146 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2147 return CB_ERR;
2148 case CB_GETDROPPEDCONTROLRECT:
2149 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2150 return CB_OKAY;
2151 case CB_GETDROPPEDSTATE:
2152 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2153 case CB_DIR:
2154 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2155 SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2156
2157 case CB_SHOWDROPDOWN:
2158 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2159 {
2160 if( wParam )
2161 {
2162 if( !(lphc->wState & CBF_DROPPED) )
2163 CBDropDown( lphc );
2164 }
2165 else
2166 if( lphc->wState & CBF_DROPPED )
2167 CBRollUp( lphc, FALSE, TRUE );
2168 }
2169 return TRUE;
2170 case CB_GETCOUNT:
2171 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2172 case CB_GETCURSEL:
2173 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2174 case CB_SETCURSEL:
2175 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2176 if( lParam >= 0 )
2177 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2178
2179 /* no LBN_SELCHANGE in this case, update manually */
2180 if( lphc->wState & CBF_EDIT )
2181 CBUpdateEdit( lphc, (INT)wParam );
2182 else
2183 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2184 lphc->wState &= ~CBF_SELCHANGE;
2185 return lParam;
2186 case CB_GETLBTEXT:
2187 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2188 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2189 case CB_GETLBTEXTLEN:
2190 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2191 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2192 case CB_GETITEMDATA:
2193 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2194 case CB_SETITEMDATA:
2195 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2196 case CB_GETEDITSEL:
2197 /* Edit checks passed parameters itself */
2198 if( lphc->wState & CBF_EDIT )
2199 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2200 return CB_ERR;
2201 case CB_SETEDITSEL:
2202 if( lphc->wState & CBF_EDIT )
2203 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2204 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2205 return CB_ERR;
2206 case CB_SETEXTENDEDUI:
2207 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2208 return CB_ERR;
2209 if( wParam )
2210 lphc->wState |= CBF_EUI;
2211 else lphc->wState &= ~CBF_EUI;
2212 return CB_OKAY;
2213 case CB_GETEXTENDEDUI:
2214 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2215 case CB_GETCOMBOBOXINFO:
2216 return COMBO_GetComboBoxInfo(lphc, (COMBOBOXINFO *)lParam);
2217 case CB_LIMITTEXT:
2218 if( lphc->wState & CBF_EDIT )
2219 return SendMessageW(lphc->hWndEdit, EM_LIMITTEXT, wParam, lParam);
2220
2221 case WM_UPDATEUISTATE:
2222 if (unicode)
2223 DefWindowProcW(lphc->self, message, wParam, lParam);
2224 else
2225 DefWindowProcA(lphc->self, message, wParam, lParam);
2226
2227 if (COMBO_update_uistate(lphc))
2228 {
2229 /* redraw text */
2230 if( !(lphc->wState & CBF_EDIT) )
2231 NtUserInvalidateRect(lphc->self, &lphc->textRect, TRUE);
2232 }
2233 break;
2234
2235 default:
2236 if (message >= WM_USER)
2237 WARN("unknown msg WM_USER+%04x wp=%04lx lp=%08lx\n",
2238 message - WM_USER, wParam, lParam );
2239 break;
2240 }
2241 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2242 DefWindowProcA(hwnd, message, wParam, lParam);
2243 }
2244
2245 /***********************************************************************
2246 * ComboWndProcA
2247 *
2248 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2249 * window structs.
2250 */
2251 LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2252 {
2253 if (!IsWindow(hwnd)) return 0;
2254 return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2255 }
2256
2257 /***********************************************************************
2258 * ComboWndProcW
2259 */
2260 LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2261 {
2262 if (!IsWindow(hwnd)) return 0;
2263 return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2264 }
2265
2266 /*************************************************************************
2267 * GetComboBoxInfo (USER32.@)
2268 */
2269 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */
2270 PCOMBOBOXINFO pcbi /* [in/out] combo box information */)
2271 {
2272 TRACE("(%p, %p)\n", hwndCombo, pcbi);
2273 #ifndef __REACTOS__
2274 return SendMessageW(hwndCombo, CB_GETCOMBOBOXINFO, 0, (LPARAM)pcbi);
2275 #else
2276 return NtUserGetComboBoxInfo(hwndCombo, pcbi);
2277 #endif
2278 }