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