(Re)move USER32 headers from include/user32.
[reactos.git] / reactos / lib / 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 * FIXME: roll up in Netscape 3.01.
21 */
22
23 #include "user32.h"
24 #include <stdarg.h>
25 #include <string.h>
26
27 #define NDEBUG
28 #include <string.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include "controls.h"
32 #include "wine/debug.h"
33 #include "wine/unicode.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(combo);
36
37 /* bits in the dwKeyData */
38 #define KEYDATA_ALT 0x2000
39 #define KEYDATA_PREVSTATE 0x4000
40
41 /*
42 * Additional combo box definitions
43 */
44
45 #define CB_NOTIFY( lphc, code ) \
46 (SendMessageW((lphc)->owner, WM_COMMAND, \
47 MAKEWPARAM(GetWindowLongA((lphc)->self,GWL_ID), (code)), (LPARAM)(lphc)->self))
48
49 #define CB_DISABLED( lphc ) (!IsWindowEnabled((lphc)->self))
50 #define CB_OWNERDRAWN( lphc ) ((lphc)->dwStyle & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE))
51 #define CB_HASSTRINGS( lphc ) ((lphc)->dwStyle & CBS_HASSTRINGS)
52 #define CB_HWND( lphc ) ((lphc)->self)
53
54 #define ISWIN31 (LOWORD(GetVersion()) == 0x0a03)
55
56 /*
57 * Drawing globals
58 */
59 static HBITMAP hComboBmp = 0;
60 static UINT CBitHeight, CBitWidth;
61
62 /*
63 * Look and feel dependent "constants"
64 */
65
66 #define COMBO_YBORDERGAP 5
67 #define COMBO_XBORDERSIZE() 2
68 #define COMBO_YBORDERSIZE() 2
69 #define COMBO_EDITBUTTONSPACE() 0
70 #define EDIT_CONTROL_PADDING() 1
71
72 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
73 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam );
74
75 /*********************************************************************
76 * combo class descriptor
77 */
78 const struct builtin_class_descr COMBO_builtin_class =
79 {
80 #ifdef __REACTOS__
81 L"ComboBox", /* name */
82 CS_PARENTDC | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS, /* style */
83 (WNDPROC) ComboWndProcW, /* procW */
84 (WNDPROC) ComboWndProcA, /* procA */
85 sizeof(HEADCOMBO *), /* extra */
86 (LPCWSTR) IDC_ARROW, /* cursor */
87 0 /* brush */
88 #else
89 "ComboBox", /* name */
90 CS_PARENTDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, /* style */
91 ComboWndProcA, /* procA */
92 ComboWndProcW, /* procW */
93 sizeof(HEADCOMBO *), /* extra */
94 IDC_ARROW, /* cursor */
95 0 /* brush */
96 #endif
97 };
98
99
100 /***********************************************************************
101 * COMBO_Init
102 *
103 * Load combo button bitmap.
104 */
105 static BOOL COMBO_Init()
106 {
107 HDC hDC;
108
109 if( hComboBmp ) return TRUE;
110 if( (hDC = CreateCompatibleDC(0)) )
111 {
112 BOOL bRet = FALSE;
113 if( (hComboBmp = LoadBitmapW(0, MAKEINTRESOURCEW(OBM_COMBO))) )
114 {
115 BITMAP bm;
116 HBITMAP hPrevB;
117 RECT r;
118
119 GetObjectW( hComboBmp, sizeof(bm), &bm );
120 CBitHeight = bm.bmHeight;
121 CBitWidth = bm.bmWidth;
122
123 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
124
125 hPrevB = SelectObject( hDC, hComboBmp);
126 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
127 InvertRect( hDC, &r );
128 SelectObject( hDC, hPrevB );
129 bRet = TRUE;
130 }
131 DeleteDC( hDC );
132 return bRet;
133 }
134 return FALSE;
135 }
136
137 /***********************************************************************
138 * COMBO_NCCreate
139 */
140 static LRESULT COMBO_NCCreate(HWND hwnd, LONG style)
141 {
142 LPHEADCOMBO lphc;
143
144 if (COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(HEADCOMBO))) )
145 {
146 lphc->self = hwnd;
147 SetWindowLongA( hwnd, 0, (LONG)lphc );
148
149 /* some braindead apps do try to use scrollbar/border flags */
150
151 lphc->dwStyle = style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
152 SetWindowLongA( hwnd, GWL_STYLE, style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL) );
153
154 /*
155 * We also have to remove the client edge style to make sure
156 * we don't end-up with a non client area.
157 */
158 SetWindowLongA( hwnd, GWL_EXSTYLE,
159 GetWindowLongA( hwnd, GWL_EXSTYLE ) & ~WS_EX_CLIENTEDGE );
160
161 if( !(style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
162 lphc->dwStyle |= CBS_HASSTRINGS;
163 if( !(GetWindowLongA( hwnd, GWL_EXSTYLE ) & WS_EX_NOPARENTNOTIFY) )
164 lphc->wState |= CBF_NOTIFY;
165
166 TRACE("[%p], style = %08x\n", lphc, lphc->dwStyle );
167 return TRUE;
168 }
169 return FALSE;
170 }
171
172 /***********************************************************************
173 * COMBO_NCDestroy
174 */
175 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
176 {
177
178 if( lphc )
179 {
180 TRACE("[%p]: freeing storage\n", lphc->self);
181
182 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
183 DestroyWindow( lphc->hWndLBox );
184
185 SetWindowLongA( lphc->self, 0, 0 );
186 HeapFree( GetProcessHeap(), 0, lphc );
187 }
188 return 0;
189 }
190
191 /***********************************************************************
192 * CBGetTextAreaHeight
193 *
194 * This method will calculate the height of the text area of the
195 * combobox.
196 * The height of the text area is set in two ways.
197 * It can be set explicitly through a combobox message or through a
198 * WM_MEASUREITEM callback.
199 * If this is not the case, the height is set to 13 dialog units.
200 * This height was determined through experimentation.
201 */
202 static INT CBGetTextAreaHeight(
203 HWND hwnd,
204 LPHEADCOMBO lphc)
205 {
206 INT iTextItemHeight;
207
208 if( lphc->editHeight ) /* explicitly set height */
209 {
210 iTextItemHeight = lphc->editHeight;
211 }
212 else
213 {
214 TEXTMETRICW tm;
215 HDC hDC = GetDC(hwnd);
216 HFONT hPrevFont = 0;
217 INT baseUnitY;
218
219 if (lphc->hFont)
220 hPrevFont = SelectObject( hDC, lphc->hFont );
221
222 GetTextMetricsW(hDC, &tm);
223
224 baseUnitY = tm.tmHeight;
225
226 if( hPrevFont )
227 SelectObject( hDC, hPrevFont );
228
229 ReleaseDC(hwnd, hDC);
230
231 iTextItemHeight = ((13 * baseUnitY) / 8);
232
233 /*
234 * This "formula" calculates the height of the complete control.
235 * To calculate the height of the text area, we have to remove the
236 * borders.
237 */
238 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
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 = GetWindowLongA( lphc->self, GWL_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 TRACE("\ttext\t= (%ld,%ld-%ld,%ld)\n",
432 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
433
434 TRACE("\tbutton\t= (%ld,%ld-%ld,%ld)\n",
435 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
436
437 TRACE("\tlbox\t= (%ld,%ld-%ld,%ld)\n",
438 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
439 }
440
441 /***********************************************************************
442 * CBGetDroppedControlRect
443 */
444 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
445 {
446 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
447 of the combo box and the lower right corner of the listbox */
448
449 GetWindowRect(lphc->self, lpRect);
450
451 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
452 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
453
454 }
455
456 /***********************************************************************
457 * COMBO_WindowPosChanging
458 */
459 static LRESULT COMBO_WindowPosChanging(
460 HWND hwnd,
461 LPHEADCOMBO lphc,
462 WINDOWPOS* posChanging)
463 {
464 /*
465 * We need to override the WM_WINDOWPOSCHANGING method to handle all
466 * the non-simple comboboxes. The problem is that those controls are
467 * always the same height. We have to make sure they are not resized
468 * to another value.
469 */
470 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
471 ((posChanging->flags & SWP_NOSIZE) == 0) )
472 {
473 int newComboHeight;
474
475 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
476 2*COMBO_YBORDERSIZE();
477
478 /*
479 * Resizing a combobox has another side effect, it resizes the dropped
480 * rectangle as well. However, it does it only if the new height for the
481 * combobox is different from the height it should have. In other words,
482 * if the application resizing the combobox only had the intention to resize
483 * the actual control, for example, to do the layout of a dialog that is
484 * resized, the height of the dropdown is not changed.
485 */
486 if (posChanging->cy != newComboHeight)
487 {
488 TRACE("posChanging->cy=%d, newComboHeight=%d, oldbot=%ld, oldtop=%ld\n",
489 posChanging->cy, newComboHeight, lphc->droppedRect.bottom,
490 lphc->droppedRect.top);
491 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
492
493 posChanging->cy = newComboHeight;
494 }
495 }
496
497 return 0;
498 }
499
500 /***********************************************************************
501 * COMBO_Create
502 */
503 static LRESULT COMBO_Create( HWND hwnd, LPHEADCOMBO lphc, HWND hwndParent, LONG style,
504 BOOL unicode )
505 {
506 static const WCHAR clbName[] = {'C','o','m','b','o','L','B','o','x',0};
507 static const WCHAR editName[] = {'E','d','i','t',0};
508
509 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
510 if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
511
512 lphc->owner = hwndParent;
513
514 /*
515 * The item height and dropped width are not set when the control
516 * is created.
517 */
518 lphc->droppedWidth = lphc->editHeight = 0;
519
520 /*
521 * The first time we go through, we want to measure the ownerdraw item
522 */
523 lphc->wState |= CBF_MEASUREITEM;
524
525 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
526
527 if( lphc->owner || !(style & WS_VISIBLE) )
528 {
529 UINT lbeStyle = 0;
530 UINT lbeExStyle = 0;
531
532 /*
533 * Initialize the dropped rect to the size of the client area of the
534 * control and then, force all the areas of the combobox to be
535 * recalculated.
536 */
537 GetClientRect( hwnd, &lphc->droppedRect );
538 CBCalcPlacement(hwnd, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect );
539
540 /*
541 * Adjust the position of the popup listbox if it's necessary
542 */
543 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
544 {
545 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
546
547 /*
548 * If it's a dropdown, the listbox is offset
549 */
550 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
551 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
552
553 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect);
554 ClientToScreen(hwnd, (LPPOINT)&lphc->droppedRect.right);
555 }
556
557 /* create listbox popup */
558
559 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS | WS_CHILD) |
560 (style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
561
562 if( lphc->dwStyle & CBS_SORT )
563 lbeStyle |= LBS_SORT;
564 if( lphc->dwStyle & CBS_HASSTRINGS )
565 lbeStyle |= LBS_HASSTRINGS;
566 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
567 lbeStyle |= LBS_NOINTEGRALHEIGHT;
568 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
569 lbeStyle |= LBS_DISABLENOSCROLL;
570
571 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
572 {
573 lbeStyle |= WS_VISIBLE;
574
575 /*
576 * In win 95 look n feel, the listbox in the simple combobox has
577 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
578 */
579 lbeStyle &= ~WS_BORDER;
580 lbeExStyle |= WS_EX_CLIENTEDGE;
581 }
582 else
583 {
584 lbeExStyle |= (WS_EX_TOPMOST | WS_EX_TOOLWINDOW);
585 }
586
587 if (unicode)
588 lphc->hWndLBox = CreateWindowExW(lbeExStyle, clbName, NULL, lbeStyle,
589 lphc->droppedRect.left,
590 lphc->droppedRect.top,
591 lphc->droppedRect.right - lphc->droppedRect.left,
592 lphc->droppedRect.bottom - lphc->droppedRect.top,
593 hwnd, (HMENU)ID_CB_LISTBOX,
594 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc );
595 else
596 lphc->hWndLBox = CreateWindowExA(lbeExStyle, "ComboLBox", NULL, lbeStyle,
597 lphc->droppedRect.left,
598 lphc->droppedRect.top,
599 lphc->droppedRect.right - lphc->droppedRect.left,
600 lphc->droppedRect.bottom - lphc->droppedRect.top,
601 hwnd, (HMENU)ID_CB_LISTBOX,
602 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), lphc );
603
604 if( lphc->hWndLBox )
605 {
606 BOOL bEdit = TRUE;
607 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT | ES_COMBO;
608
609 if( lphc->wState & CBF_EDIT )
610 {
611 if( lphc->dwStyle & CBS_OEMCONVERT )
612 lbeStyle |= ES_OEMCONVERT;
613 if( lphc->dwStyle & CBS_AUTOHSCROLL )
614 lbeStyle |= ES_AUTOHSCROLL;
615 if( lphc->dwStyle & CBS_LOWERCASE )
616 lbeStyle |= ES_LOWERCASE;
617 else if( lphc->dwStyle & CBS_UPPERCASE )
618 lbeStyle |= ES_UPPERCASE;
619
620 if (!IsWindowEnabled(hwnd)) lbeStyle |= WS_DISABLED;
621
622 if (unicode)
623 lphc->hWndEdit = CreateWindowExW(0, editName, NULL, lbeStyle,
624 lphc->textRect.left, lphc->textRect.top,
625 lphc->textRect.right - lphc->textRect.left,
626 lphc->textRect.bottom - lphc->textRect.top,
627 hwnd, (HMENU)ID_CB_EDIT,
628 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL );
629 else
630 lphc->hWndEdit = CreateWindowExA(0, "Edit", NULL, lbeStyle,
631 lphc->textRect.left, lphc->textRect.top,
632 lphc->textRect.right - lphc->textRect.left,
633 lphc->textRect.bottom - lphc->textRect.top,
634 hwnd, (HMENU)ID_CB_EDIT,
635 (HINSTANCE)GetWindowLongA( hwnd, GWL_HINSTANCE ), NULL );
636
637 if( !lphc->hWndEdit )
638 bEdit = FALSE;
639 }
640
641 if( bEdit )
642 {
643 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
644 {
645 /* Now do the trick with parent */
646 SetParent(lphc->hWndLBox, HWND_DESKTOP);
647 /*
648 * If the combo is a dropdown, we must resize the control
649 * to fit only the text area and button. To do this,
650 * we send a dummy resize and the WM_WINDOWPOSCHANGING message
651 * will take care of setting the height for us.
652 */
653 CBForceDummyResize(lphc);
654 }
655
656 TRACE("init done\n");
657 return 0;
658 }
659 ERR("edit control failure.\n");
660 } else ERR("listbox failure.\n");
661 } else ERR("no owner for visible combo.\n");
662
663 /* CreateWindow() will send WM_NCDESTROY to cleanup */
664
665 return -1;
666 }
667
668 /***********************************************************************
669 * CBPaintButton
670 *
671 * Paint combo button (normal, pressed, and disabled states).
672 */
673 static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton)
674 {
675 UINT buttonState = DFCS_SCROLLCOMBOBOX;
676
677 if( lphc->wState & CBF_NOREDRAW )
678 return;
679
680
681 if (lphc->wState & CBF_BUTTONDOWN)
682 buttonState |= DFCS_PUSHED;
683
684 if (CB_DISABLED(lphc))
685 buttonState |= DFCS_INACTIVE;
686
687 DrawFrameControl(hdc, &rectButton, DFC_SCROLL, buttonState);
688 }
689
690 /***********************************************************************
691 * CBPaintText
692 *
693 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
694 */
695 static void CBPaintText(
696 LPHEADCOMBO lphc,
697 HDC hdc,
698 RECT rectEdit)
699 {
700 INT id, size = 0;
701 LPWSTR pText = NULL;
702
703 if( lphc->wState & CBF_NOREDRAW ) return;
704
705 TRACE("\n");
706
707 /* follow Windows combobox that sends a bunch of text
708 * inquiries to its listbox while processing WM_PAINT. */
709
710 if( (id = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
711 {
712 size = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
713 if (size == LB_ERR)
714 FIXME("LB_ERR probably not handled yet\n");
715 if( (pText = HeapAlloc( GetProcessHeap(), 0, (size + 1) * sizeof(WCHAR))) )
716 {
717 /* size from LB_GETTEXTLEN may be too large, from LB_GETTEXT is accurate */
718 size=SendMessageW(lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText);
719 pText[size] = '\0'; /* just in case */
720 } else return;
721 }
722 else
723 if( !CB_OWNERDRAWN(lphc) )
724 return;
725
726 if( lphc->wState & CBF_EDIT )
727 {
728 static const WCHAR empty_stringW[] = { 0 };
729 if( CB_HASSTRINGS(lphc) ) SetWindowTextW( lphc->hWndEdit, pText ? pText : empty_stringW );
730 if( lphc->wState & CBF_FOCUSED )
731 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
732 }
733 else /* paint text field ourselves */
734 {
735 UINT itemState = ODS_COMBOBOXEDIT;
736 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hdc, lphc->hFont) : 0;
737
738 /*
739 * Give ourselves some space.
740 */
741 InflateRect( &rectEdit, -1, -1 );
742
743 if( CB_OWNERDRAWN(lphc) )
744 {
745 DRAWITEMSTRUCT dis;
746 HRGN clipRegion;
747 UINT ctlid = GetWindowLongA( lphc->self, GWL_ID );
748
749 /* setup state for DRAWITEM message. Owner will highlight */
750 if ( (lphc->wState & CBF_FOCUSED) &&
751 !(lphc->wState & CBF_DROPPED) )
752 itemState |= ODS_SELECTED | ODS_FOCUS;
753
754 /*
755 * Save the current clip region.
756 * To retrieve the clip region, we need to create one "dummy"
757 * clip region.
758 */
759 clipRegion = CreateRectRgnIndirect(&rectEdit);
760
761 if (GetClipRgn(hdc, clipRegion)!=1)
762 {
763 DeleteObject(clipRegion);
764 clipRegion=NULL;
765 }
766
767 if (!IsWindowEnabled(lphc->self) & WS_DISABLED) itemState |= ODS_DISABLED;
768
769 dis.CtlType = ODT_COMBOBOX;
770 dis.CtlID = ctlid;
771 dis.hwndItem = lphc->self;
772 dis.itemAction = ODA_DRAWENTIRE;
773 dis.itemID = id;
774 dis.itemState = itemState;
775 dis.hDC = hdc;
776 dis.rcItem = rectEdit;
777 dis.itemData = SendMessageW(lphc->hWndLBox, LB_GETITEMDATA,
778 (WPARAM)id, 0 );
779
780 /*
781 * Clip the DC and have the parent draw the item.
782 */
783 IntersectClipRect(hdc,
784 rectEdit.left, rectEdit.top,
785 rectEdit.right, rectEdit.bottom);
786
787 SendMessageW(lphc->owner, WM_DRAWITEM, ctlid, (LPARAM)&dis );
788
789 /*
790 * Reset the clipping region.
791 */
792 SelectClipRgn(hdc, clipRegion);
793 }
794 else
795 {
796 static const WCHAR empty_stringW[] = { 0 };
797
798 if ( (lphc->wState & CBF_FOCUSED) &&
799 !(lphc->wState & CBF_DROPPED) ) {
800
801 /* highlight */
802 FillRect( hdc, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
803 SetBkColor( hdc, GetSysColor( COLOR_HIGHLIGHT ) );
804 SetTextColor( hdc, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
805 }
806
807 ExtTextOutW( hdc,
808 rectEdit.left + 1,
809 rectEdit.top + 1,
810 ETO_OPAQUE | ETO_CLIPPED,
811 &rectEdit,
812 pText ? pText : empty_stringW , size, NULL );
813
814 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
815 DrawFocusRect( hdc, &rectEdit );
816 }
817
818 if( hPrevFont )
819 SelectObject(hdc, hPrevFont );
820 }
821 if (pText)
822 HeapFree( GetProcessHeap(), 0, pText );
823 }
824
825 /***********************************************************************
826 * CBPaintBorder
827 */
828 static void CBPaintBorder(
829 HWND hwnd,
830 LPHEADCOMBO lphc,
831 HDC hdc)
832 {
833 RECT clientRect;
834
835 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
836 {
837 GetClientRect(hwnd, &clientRect);
838 }
839 else
840 {
841 CopyRect(&clientRect, &lphc->textRect);
842
843 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
844 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
845 }
846
847 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
848 }
849
850 /***********************************************************************
851 * COMBO_PrepareColors
852 *
853 * This method will sent the appropriate WM_CTLCOLOR message to
854 * prepare and setup the colors for the combo's DC.
855 *
856 * It also returns the brush to use for the background.
857 */
858 static HBRUSH COMBO_PrepareColors(
859 LPHEADCOMBO lphc,
860 HDC hDC)
861 {
862 HBRUSH hBkgBrush;
863
864 /*
865 * Get the background brush for this control.
866 */
867 if (CB_DISABLED(lphc))
868 {
869 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORSTATIC,
870 (WPARAM)hDC, (LPARAM)lphc->self );
871
872 /*
873 * We have to change the text color since WM_CTLCOLORSTATIC will
874 * set it to the "enabled" color. This is the same behavior as the
875 * edit control
876 */
877 SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT));
878 }
879 else
880 {
881 if (lphc->wState & CBF_EDIT)
882 {
883 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLOREDIT,
884 (WPARAM)hDC, (LPARAM)lphc->self );
885 }
886 else
887 {
888 hBkgBrush = (HBRUSH)SendMessageW(lphc->owner, WM_CTLCOLORLISTBOX,
889 (WPARAM)hDC, (LPARAM)lphc->self );
890 }
891 }
892
893 /*
894 * Catch errors.
895 */
896 if( !hBkgBrush )
897 hBkgBrush = GetSysColorBrush(COLOR_WINDOW);
898
899 return hBkgBrush;
900 }
901
902 /***********************************************************************
903 * COMBO_EraseBackground
904 */
905 static LRESULT COMBO_EraseBackground(
906 HWND hwnd,
907 LPHEADCOMBO lphc,
908 HDC hParamDC)
909 {
910 HBRUSH hBkgBrush;
911 HDC hDC;
912
913 if(lphc->wState & CBF_EDIT)
914 return TRUE;
915
916 hDC = (hParamDC) ? hParamDC
917 : GetDC(hwnd);
918 /*
919 * Retrieve the background brush
920 */
921 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
922
923 FillRect(hDC, &lphc->textRect, hBkgBrush);
924
925 if (!hParamDC)
926 ReleaseDC(hwnd, hDC);
927
928 return TRUE;
929 }
930
931 /***********************************************************************
932 * COMBO_Paint
933 */
934 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
935 {
936 PAINTSTRUCT ps;
937 HDC hDC;
938
939 hDC = (hParamDC) ? hParamDC
940 : BeginPaint( lphc->self, &ps);
941
942 TRACE("hdc=%p\n", hDC);
943
944 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
945 {
946 HBRUSH hPrevBrush, hBkgBrush;
947
948 /*
949 * Retrieve the background brush and select it in the
950 * DC.
951 */
952 hBkgBrush = COMBO_PrepareColors(lphc, hDC);
953
954 hPrevBrush = SelectObject( hDC, hBkgBrush );
955
956 /*
957 * In non 3.1 look, there is a sunken border on the combobox
958 */
959 CBPaintBorder(lphc->self, lphc, hDC);
960
961 if( !IsRectEmpty(&lphc->buttonRect) )
962 {
963 CBPaintButton(lphc, hDC, lphc->buttonRect);
964 }
965
966 /* paint the edit control padding area */
967 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
968 {
969 RECT rPadEdit = lphc->textRect;
970
971 InflateRect(&rPadEdit, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
972
973 FrameRect( hDC, &rPadEdit, GetSysColorBrush(COLOR_WINDOW) );
974 }
975
976 if( !(lphc->wState & CBF_EDIT) )
977 CBPaintText( lphc, hDC, lphc->textRect);
978
979 if( hPrevBrush )
980 SelectObject( hDC, hPrevBrush );
981 }
982
983 if( !hParamDC )
984 EndPaint(lphc->self, &ps);
985
986 return 0;
987 }
988
989 /***********************************************************************
990 * CBUpdateLBox
991 *
992 * Select listbox entry according to the contents of the edit control.
993 */
994 static INT CBUpdateLBox( LPHEADCOMBO lphc, BOOL bSelect )
995 {
996 INT length, idx;
997 LPWSTR pText = NULL;
998
999 idx = LB_ERR;
1000 length = SendMessageW( lphc->hWndEdit, WM_GETTEXTLENGTH, 0, 0 );
1001
1002 if( length > 0 )
1003 pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR));
1004
1005 TRACE("\t edit text length %i\n", length );
1006
1007 if( pText )
1008 {
1009 if( length ) GetWindowTextW( lphc->hWndEdit, pText, length + 1);
1010 else pText[0] = '\0';
1011 idx = SendMessageW(lphc->hWndLBox, LB_FINDSTRING,
1012 (WPARAM)(-1), (LPARAM)pText );
1013 HeapFree( GetProcessHeap(), 0, pText );
1014 }
1015
1016 SendMessageW(lphc->hWndLBox, LB_SETCURSEL, (WPARAM)(bSelect ? idx : -1), 0);
1017
1018 /* probably superfluous but Windows sends this too */
1019 SendMessageW(lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1020 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)(idx < 0 ? 0 : idx), 0);
1021
1022 return idx;
1023 }
1024
1025 /***********************************************************************
1026 * CBUpdateEdit
1027 *
1028 * Copy a listbox entry to the edit control.
1029 */
1030 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1031 {
1032 INT length;
1033 LPWSTR pText = NULL;
1034 static const WCHAR empty_stringW[] = { 0 };
1035
1036 TRACE("\t %i\n", index );
1037
1038 if( index >= 0 ) /* got an entry */
1039 {
1040 length = SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1041 if( length != LB_ERR)
1042 {
1043 if( (pText = HeapAlloc( GetProcessHeap(), 0, (length + 1) * sizeof(WCHAR))) )
1044 {
1045 SendMessageW(lphc->hWndLBox, LB_GETTEXT,
1046 (WPARAM)index, (LPARAM)pText );
1047 }
1048 }
1049 }
1050
1051 lphc->wState |= (CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1052 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, pText ? (LPARAM)pText : (LPARAM)empty_stringW);
1053 lphc->wState &= ~(CBF_NOEDITNOTIFY | CBF_NOLBSELECT);
1054
1055 if( lphc->wState & CBF_FOCUSED )
1056 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1057
1058 if( pText )
1059 HeapFree( GetProcessHeap(), 0, pText );
1060 }
1061
1062 /***********************************************************************
1063 * CBDropDown
1064 *
1065 * Show listbox popup.
1066 */
1067 static void CBDropDown( LPHEADCOMBO lphc )
1068 {
1069 RECT rect,r;
1070 int nItems = 0;
1071 int nDroppedHeight;
1072
1073 TRACE("[%p]: drop down\n", lphc->self);
1074
1075 CB_NOTIFY( lphc, CBN_DROPDOWN );
1076
1077 /* set selection */
1078
1079 lphc->wState |= CBF_DROPPED;
1080 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1081 {
1082 lphc->droppedIndex = CBUpdateLBox( lphc, TRUE );
1083
1084 /* Update edit only if item is in the list */
1085 if( !(lphc->wState & CBF_CAPTURE) && lphc->droppedIndex >= 0)
1086 CBUpdateEdit( lphc, lphc->droppedIndex );
1087 }
1088 else
1089 {
1090 lphc->droppedIndex = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1091
1092 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX,
1093 (WPARAM)(lphc->droppedIndex == LB_ERR ? 0 : lphc->droppedIndex), 0 );
1094 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1095 }
1096
1097 /* now set popup position */
1098 GetWindowRect( lphc->self, &rect );
1099
1100 /*
1101 * If it's a dropdown, the listbox is offset
1102 */
1103 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1104 rect.left += COMBO_EDITBUTTONSPACE();
1105
1106 /* if the dropped height is greater than the total height of the dropped
1107 items list, then force the drop down list height to be the total height
1108 of the items in the dropped list */
1109
1110 /* And Remove any extra space (Best Fit) */
1111 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1112 /* if listbox length has been set directly by its handle */
1113 GetWindowRect(lphc->hWndLBox, &r);
1114 if (nDroppedHeight < r.bottom - r.top)
1115 nDroppedHeight = r.bottom - r.top;
1116 nItems = (int)SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1117
1118 if (nItems > 0)
1119 {
1120 int nHeight;
1121 int nIHeight;
1122
1123 nIHeight = (int)SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, 0, 0);
1124
1125 nHeight = nIHeight*nItems;
1126
1127 if (nHeight < nDroppedHeight - COMBO_YBORDERSIZE())
1128 nDroppedHeight = nHeight + COMBO_YBORDERSIZE();
1129
1130 if (nDroppedHeight < nIHeight)
1131 {
1132 if (nItems < 5)
1133 nDroppedHeight = (nItems+1)*nIHeight;
1134 else
1135 nDroppedHeight = 6*nIHeight;
1136 }
1137 }
1138
1139 /*If height of dropped rectangle gets beyond a screen size it should go up, otherwise down.*/
1140 if( (rect.bottom + nDroppedHeight) >= GetSystemMetrics( SM_CYSCREEN ) )
1141 rect.bottom = rect.top - nDroppedHeight;
1142
1143 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1144 lphc->droppedRect.right - lphc->droppedRect.left,
1145 nDroppedHeight,
1146 SWP_NOACTIVATE | SWP_SHOWWINDOW);
1147
1148
1149 if( !(lphc->wState & CBF_NOREDRAW) )
1150 RedrawWindow( lphc->self, NULL, 0, RDW_INVALIDATE |
1151 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1152
1153 EnableWindow( lphc->hWndLBox, TRUE );
1154 if (GetCapture() != lphc->self)
1155 SetCapture(lphc->hWndLBox);
1156 }
1157
1158 /***********************************************************************
1159 * CBRollUp
1160 *
1161 * Hide listbox popup.
1162 */
1163 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1164 {
1165 HWND hWnd = lphc->self;
1166
1167 TRACE("[%p]: sel ok? [%i] dropped? [%i]\n",
1168 lphc->self, (INT)ok, (INT)(lphc->wState & CBF_DROPPED));
1169
1170 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1171
1172 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1173 {
1174
1175 if( lphc->wState & CBF_DROPPED )
1176 {
1177 RECT rect;
1178
1179 lphc->wState &= ~CBF_DROPPED;
1180 ShowWindow( lphc->hWndLBox, SW_HIDE );
1181
1182 if(GetCapture() == lphc->hWndLBox)
1183 {
1184 ReleaseCapture();
1185 }
1186
1187 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1188 {
1189 rect = lphc->buttonRect;
1190 }
1191 else
1192 {
1193 if( bButton )
1194 {
1195 UnionRect( &rect,
1196 &lphc->buttonRect,
1197 &lphc->textRect);
1198 }
1199 else
1200 rect = lphc->textRect;
1201
1202 bButton = TRUE;
1203 }
1204
1205 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1206 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1207 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1208 CB_NOTIFY( lphc, CBN_CLOSEUP );
1209 }
1210 }
1211 }
1212
1213 /***********************************************************************
1214 * COMBO_FlipListbox
1215 *
1216 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1217 */
1218 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL ok, BOOL bRedrawButton )
1219 {
1220 if( lphc->wState & CBF_DROPPED )
1221 {
1222 CBRollUp( lphc, ok, bRedrawButton );
1223 return FALSE;
1224 }
1225
1226 CBDropDown( lphc );
1227 return TRUE;
1228 }
1229
1230 /***********************************************************************
1231 * CBRepaintButton
1232 */
1233 static void CBRepaintButton( LPHEADCOMBO lphc )
1234 {
1235 InvalidateRect(lphc->self, &lphc->buttonRect, TRUE);
1236 UpdateWindow(lphc->self);
1237 }
1238
1239 /***********************************************************************
1240 * COMBO_SetFocus
1241 */
1242 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1243 {
1244 if( !(lphc->wState & CBF_FOCUSED) )
1245 {
1246 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1247 SendMessageW(lphc->hWndLBox, LB_CARETON, 0, 0);
1248
1249 /* This is wrong. Message sequences seem to indicate that this
1250 is set *after* the notify. */
1251 /* lphc->wState |= CBF_FOCUSED; */
1252
1253 if( !(lphc->wState & CBF_EDIT) )
1254 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
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 InvalidateRect(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 if( HIWORD(wParam) == LBN_SELCHANGE)
1367 {
1368 if( lphc->wState & CBF_EDIT )
1369 {
1370 INT index = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1371 lphc->wState |= CBF_NOLBSELECT;
1372 CBUpdateEdit( lphc, index );
1373 /* select text in edit, as Windows does */
1374 SendMessageW(lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
1375 }
1376 else
1377 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
1378 }
1379
1380 /* do not roll up if selection is being tracked
1381 * by arrowkeys in the dropdown listbox */
1382 if( ((lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP)) )
1383 {
1384 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1385 }
1386 else lphc->wState &= ~CBF_NOROLLUP;
1387
1388 CB_NOTIFY( lphc, CBN_SELCHANGE );
1389
1390 /* fall through */
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 = GetWindowLongA( hWnd, GWL_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 = SendMessageA(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 )
1598 {
1599 CBCalcPlacement(lphc->self,
1600 lphc,
1601 &lphc->textRect,
1602 &lphc->buttonRect,
1603 &lphc->droppedRect);
1604
1605 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
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;
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 InvalidateRect(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
1821 /***********************************************************************
1822 * ComboWndProc_common
1823 *
1824 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1825 */
1826 static LRESULT ComboWndProc_common( HWND hwnd, UINT message,
1827 WPARAM wParam, LPARAM lParam, BOOL unicode )
1828 {
1829 LPHEADCOMBO lphc = (LPHEADCOMBO)GetWindowLongA( hwnd, 0 );
1830
1831 TRACE("[%p]: msg %s wp %08x lp %08lx\n",
1832 hwnd, SPY_GetMsgName(message, hwnd), wParam, lParam );
1833
1834 if( lphc || message == WM_NCCREATE )
1835 switch(message)
1836 {
1837
1838 /* System messages */
1839
1840 case WM_NCCREATE:
1841 {
1842 LONG style = unicode ? ((LPCREATESTRUCTW)lParam)->style :
1843 ((LPCREATESTRUCTA)lParam)->style;
1844 return COMBO_NCCreate(hwnd, style);
1845 }
1846 case WM_NCDESTROY:
1847 COMBO_NCDestroy(lphc);
1848 break;/* -> DefWindowProc */
1849
1850 case WM_CREATE:
1851 {
1852 HWND hwndParent;
1853 LONG style;
1854 if(unicode)
1855 {
1856 hwndParent = ((LPCREATESTRUCTW)lParam)->hwndParent;
1857 style = ((LPCREATESTRUCTW)lParam)->style;
1858 }
1859 else
1860 {
1861 hwndParent = ((LPCREATESTRUCTA)lParam)->hwndParent;
1862 style = ((LPCREATESTRUCTA)lParam)->style;
1863 }
1864 return COMBO_Create(hwnd, lphc, hwndParent, style, unicode);
1865 }
1866
1867 case WM_PRINTCLIENT:
1868 if (lParam & PRF_ERASEBKGND)
1869 COMBO_EraseBackground(hwnd, lphc, (HDC)wParam);
1870
1871 /* Fallthrough */
1872 case WM_PAINT:
1873 /* wParam may contain a valid HDC! */
1874 return COMBO_Paint(lphc, (HDC)wParam);
1875 case WM_ERASEBKGND:
1876 return COMBO_EraseBackground(hwnd, lphc, (HDC)wParam);
1877 case WM_GETDLGCODE:
1878 {
1879 LRESULT result = DLGC_WANTARROWS | DLGC_WANTCHARS;
1880 if (lParam && (((LPMSG)lParam)->message == WM_KEYDOWN))
1881 {
1882 int vk = (int)((LPMSG)lParam)->wParam;
1883
1884 if ((vk == VK_RETURN || vk == VK_ESCAPE) && (lphc->wState & CBF_DROPPED))
1885 result |= DLGC_WANTMESSAGE;
1886 }
1887 return result;
1888 }
1889 case WM_WINDOWPOSCHANGING:
1890 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1891 case WM_WINDOWPOSCHANGED:
1892 /* SetWindowPos can be called on a Combobox to resize its Listbox.
1893 * In that case, the Combobox itself will not be resized, so we won't
1894 * get a WM_SIZE. Since we still want to update the Listbox, we have to
1895 * do it here.
1896 */
1897 /* fall through */
1898 case WM_SIZE:
1899 if( lphc->hWndLBox &&
1900 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1901 return TRUE;
1902 case WM_SETFONT:
1903 COMBO_Font( lphc, (HFONT)wParam, (BOOL)lParam );
1904 return TRUE;
1905 case WM_GETFONT:
1906 return (LRESULT)lphc->hFont;
1907 case WM_SETFOCUS:
1908 if( lphc->wState & CBF_EDIT )
1909 SetFocus( lphc->hWndEdit );
1910 else
1911 COMBO_SetFocus( lphc );
1912 return TRUE;
1913 case WM_KILLFOCUS:
1914 {
1915 #ifdef __REACTOS__
1916 HWND hwndFocus = (HWND)wParam;
1917 #else
1918 HWND hwndFocus = WIN_GetFullHandle( (HWND)wParam );
1919 #endif
1920 if( !hwndFocus ||
1921 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1922 COMBO_KillFocus( lphc );
1923 return TRUE;
1924 }
1925 case WM_COMMAND:
1926 #ifdef __REACTOS__
1927 return COMBO_Command( lphc, wParam, (HWND)lParam);
1928 #else
1929 return COMBO_Command( lphc, wParam, WIN_GetFullHandle( (HWND)lParam ) );
1930 #endif
1931 case WM_GETTEXT:
1932 return unicode ? COMBO_GetTextW( lphc, wParam, (LPWSTR)lParam )
1933 : COMBO_GetTextA( lphc, wParam, (LPSTR)lParam );
1934 case WM_SETTEXT:
1935 case WM_GETTEXTLENGTH:
1936 case WM_CLEAR:
1937 if ((message == WM_GETTEXTLENGTH) && !ISWIN31 && !(lphc->wState & CBF_EDIT))
1938 {
1939 int j = SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1940 if (j == -1) return 0;
1941 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, j, 0) :
1942 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, j, 0);
1943 }
1944 else if( lphc->wState & CBF_EDIT )
1945 {
1946 LRESULT ret;
1947 lphc->wState |= CBF_NOEDITNOTIFY;
1948 ret = unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1949 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1950 lphc->wState &= ~CBF_NOEDITNOTIFY;
1951 return ret;
1952 }
1953 else return CB_ERR;
1954 case WM_CUT:
1955 case WM_PASTE:
1956 case WM_COPY:
1957 if( lphc->wState & CBF_EDIT )
1958 {
1959 return unicode ? SendMessageW(lphc->hWndEdit, message, wParam, lParam) :
1960 SendMessageA(lphc->hWndEdit, message, wParam, lParam);
1961 }
1962 else return CB_ERR;
1963
1964 case WM_DRAWITEM:
1965 case WM_DELETEITEM:
1966 case WM_COMPAREITEM:
1967 case WM_MEASUREITEM:
1968 return COMBO_ItemOp(lphc, message, lParam);
1969 case WM_ENABLE:
1970 if( lphc->wState & CBF_EDIT )
1971 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1972 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1973
1974 /* Force the control to repaint when the enabled state changes. */
1975 InvalidateRect(lphc->self, NULL, TRUE);
1976 return TRUE;
1977 case WM_SETREDRAW:
1978 if( wParam )
1979 lphc->wState &= ~CBF_NOREDRAW;
1980 else
1981 lphc->wState |= CBF_NOREDRAW;
1982
1983 if( lphc->wState & CBF_EDIT )
1984 SendMessageW(lphc->hWndEdit, message, wParam, lParam);
1985 SendMessageW(lphc->hWndLBox, message, wParam, lParam);
1986 return 0;
1987 case WM_SYSKEYDOWN:
1988 if( KEYDATA_ALT & HIWORD(lParam) )
1989 if( wParam == VK_UP || wParam == VK_DOWN )
1990 COMBO_FlipListbox( lphc, FALSE, FALSE );
1991 return 0;
1992
1993 case WM_CHAR:
1994 case WM_IME_CHAR:
1995 case WM_KEYDOWN:
1996 {
1997 HWND hwndTarget;
1998
1999 if ((wParam == VK_RETURN || wParam == VK_ESCAPE) &&
2000 (lphc->wState & CBF_DROPPED))
2001 {
2002 CBRollUp( lphc, wParam == VK_RETURN, FALSE );
2003 return TRUE;
2004 }
2005 else if ((wParam == VK_F4) && !(lphc->wState & CBF_EUI))
2006 {
2007 COMBO_FlipListbox( lphc, FALSE, FALSE );
2008 return TRUE;
2009 }
2010
2011 if( lphc->wState & CBF_EDIT )
2012 hwndTarget = lphc->hWndEdit;
2013 else
2014 hwndTarget = lphc->hWndLBox;
2015
2016 return unicode ? SendMessageW(hwndTarget, message, wParam, lParam) :
2017 SendMessageA(hwndTarget, message, wParam, lParam);
2018 }
2019 case WM_LBUTTONDOWN:
2020 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self );
2021 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
2022 return TRUE;
2023 case WM_LBUTTONUP:
2024 COMBO_LButtonUp( lphc );
2025 return TRUE;
2026 case WM_MOUSEMOVE:
2027 if( lphc->wState & CBF_CAPTURE )
2028 COMBO_MouseMove( lphc, wParam, lParam );
2029 return TRUE;
2030
2031 case WM_MOUSEWHEEL:
2032 if (wParam & (MK_SHIFT | MK_CONTROL))
2033 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2034 DefWindowProcA(hwnd, message, wParam, lParam);
2035
2036 if (GET_WHEEL_DELTA_WPARAM(wParam) > 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_UP, 0);
2037 if (GET_WHEEL_DELTA_WPARAM(wParam) < 0) return SendMessageW(hwnd, WM_KEYDOWN, VK_DOWN, 0);
2038 return TRUE;
2039
2040 /* Combo messages */
2041
2042 #ifndef __REACTOS__
2043 case CB_ADDSTRING16:
2044 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2045 /* fall through */
2046 #endif
2047 case CB_ADDSTRING:
2048 if( unicode )
2049 {
2050 if( lphc->dwStyle & CBS_LOWERCASE )
2051 strlwrW((LPWSTR)lParam);
2052 else if( lphc->dwStyle & CBS_UPPERCASE )
2053 struprW((LPWSTR)lParam);
2054 return SendMessageW(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2055 }
2056 else
2057 {
2058 if( lphc->dwStyle & CBS_LOWERCASE )
2059 _strlwr((LPSTR)lParam);
2060 else if( lphc->dwStyle & CBS_UPPERCASE )
2061 _strupr((LPSTR)lParam);
2062 return SendMessageA(lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
2063 }
2064 #ifndef __REACTOS__
2065 case CB_INSERTSTRING16:
2066 wParam = (INT)(INT16)wParam;
2067 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2068 /* fall through */
2069 #endif
2070 case CB_INSERTSTRING:
2071 if( unicode )
2072 {
2073 if( lphc->dwStyle & CBS_LOWERCASE )
2074 strlwrW((LPWSTR)lParam);
2075 else if( lphc->dwStyle & CBS_UPPERCASE )
2076 struprW((LPWSTR)lParam);
2077 return SendMessageW(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2078 }
2079 else
2080 {
2081 if( lphc->dwStyle & CBS_LOWERCASE )
2082 _strlwr((LPSTR)lParam);
2083 else if( lphc->dwStyle & CBS_UPPERCASE )
2084 _strupr((LPSTR)lParam);
2085 return SendMessageA(lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
2086 }
2087 #ifndef __REACTOS__
2088 case CB_DELETESTRING16:
2089 #endif
2090 case CB_DELETESTRING:
2091 return unicode ? SendMessageW(lphc->hWndLBox, LB_DELETESTRING, wParam, 0) :
2092 SendMessageA(lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
2093 #ifndef __REACTOS__
2094 case CB_SELECTSTRING16:
2095 wParam = (INT)(INT16)wParam;
2096 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2097 /* fall through */
2098 #endif
2099 case CB_SELECTSTRING:
2100 return COMBO_SelectString(lphc, (INT)wParam, lParam, unicode);
2101 #ifndef __REACTOS__
2102 case CB_FINDSTRING16:
2103 wParam = (INT)(INT16)wParam;
2104 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2105 /* fall through */
2106 #endif
2107 case CB_FINDSTRING:
2108 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam) :
2109 SendMessageA(lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
2110 #ifndef __REACTOS__
2111 case CB_FINDSTRINGEXACT16:
2112 wParam = (INT)(INT16)wParam;
2113 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)MapSL(lParam);
2114 /* fall through */
2115 #endif
2116 case CB_FINDSTRINGEXACT:
2117 return unicode ? SendMessageW(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam) :
2118 SendMessageA(lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam);
2119 #ifndef __REACTOS__
2120 case CB_SETITEMHEIGHT16:
2121 wParam = (INT)(INT16)wParam; /* signed integer */
2122 /* fall through */
2123 #endif
2124 case CB_SETITEMHEIGHT:
2125 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
2126 #ifndef __REACTOS__
2127 case CB_GETITEMHEIGHT16:
2128 wParam = (INT)(INT16)wParam;
2129 /* fall through */
2130 #endif
2131 case CB_GETITEMHEIGHT:
2132 if( (INT)wParam >= 0 ) /* listbox item */
2133 return SendMessageW(lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
2134 return CBGetTextAreaHeight(hwnd, lphc);
2135 #ifndef __REACTOS__
2136 case CB_RESETCONTENT16:
2137 #endif
2138 case CB_RESETCONTENT:
2139 SendMessageW(lphc->hWndLBox, LB_RESETCONTENT, 0, 0);
2140 if( (lphc->wState & CBF_EDIT) && CB_HASSTRINGS(lphc) )
2141 {
2142 static const WCHAR empty_stringW[] = { 0 };
2143 SendMessageW(lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)empty_stringW);
2144 }
2145 else
2146 InvalidateRect(lphc->self, NULL, TRUE);
2147 return TRUE;
2148 case CB_INITSTORAGE:
2149 return SendMessageW(lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
2150 case CB_GETHORIZONTALEXTENT:
2151 return SendMessageW(lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
2152 case CB_SETHORIZONTALEXTENT:
2153 return SendMessageW(lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
2154 case CB_GETTOPINDEX:
2155 return SendMessageW(lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
2156 case CB_GETLOCALE:
2157 return SendMessageW(lphc->hWndLBox, LB_GETLOCALE, 0, 0);
2158 case CB_SETLOCALE:
2159 return SendMessageW(lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
2160 case CB_GETDROPPEDWIDTH:
2161 if( lphc->droppedWidth )
2162 return lphc->droppedWidth;
2163 return lphc->droppedRect.right - lphc->droppedRect.left;
2164 case CB_SETDROPPEDWIDTH:
2165 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
2166 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
2167 return CB_ERR;
2168 #ifndef __REACTOS__
2169 case CB_GETDROPPEDCONTROLRECT16:
2170 lParam = (LPARAM)MapSL(lParam);
2171 if( lParam )
2172 {
2173 RECT r;
2174 CBGetDroppedControlRect( lphc, &r );
2175 CONV_RECT32TO16( &r, (LPRECT16)lParam );
2176 }
2177 return CB_OKAY;
2178 #endif
2179 case CB_GETDROPPEDCONTROLRECT:
2180 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
2181 return CB_OKAY;
2182 #ifndef __REACTOS__
2183 case CB_GETDROPPEDSTATE16:
2184 #endif
2185 case CB_GETDROPPEDSTATE:
2186 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
2187 #ifndef __REACTOS__
2188 case CB_DIR16:
2189 return SendMessageA(lphc->hWndLBox, LB_DIR16, wParam, lParam);
2190 #endif
2191 case CB_DIR:
2192 return unicode ? SendMessageW(lphc->hWndLBox, LB_DIR, wParam, lParam) :
2193 SendMessageA(lphc->hWndLBox, LB_DIR, wParam, lParam);
2194
2195 #ifndef __REACTOS__
2196 case CB_SHOWDROPDOWN16:
2197 #endif
2198 case CB_SHOWDROPDOWN:
2199 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
2200 {
2201 if( wParam )
2202 {
2203 if( !(lphc->wState & CBF_DROPPED) )
2204 CBDropDown( lphc );
2205 }
2206 else
2207 if( lphc->wState & CBF_DROPPED )
2208 CBRollUp( lphc, FALSE, TRUE );
2209 }
2210 return TRUE;
2211 #ifndef __REACTOS__
2212 case CB_GETCOUNT16:
2213 #endif
2214 case CB_GETCOUNT:
2215 return SendMessageW(lphc->hWndLBox, LB_GETCOUNT, 0, 0);
2216 #ifndef __REACTOS__
2217 case CB_GETCURSEL16:
2218 #endif
2219 case CB_GETCURSEL:
2220 return SendMessageW(lphc->hWndLBox, LB_GETCURSEL, 0, 0);
2221 #ifndef __REACTOS__
2222 case CB_SETCURSEL16:
2223 wParam = (INT)(INT16)wParam;
2224 /* fall through */
2225 #endif
2226 case CB_SETCURSEL:
2227 lParam = SendMessageW(lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2228 if( lParam >= 0 )
2229 SendMessageW(lphc->hWndLBox, LB_SETTOPINDEX, wParam, 0);
2230
2231 /* no LBN_SELCHANGE in this case, update manually */
2232 if( lphc->wState & CBF_EDIT )
2233 CBUpdateEdit( lphc, (INT)wParam );
2234 else
2235 InvalidateRect(lphc->self, &lphc->textRect, TRUE);
2236 lphc->wState &= ~CBF_SELCHANGE;
2237 return lParam;
2238 #ifndef __REACTOS__
2239 case CB_GETLBTEXT16:
2240 wParam = (INT)(INT16)wParam;
2241 lParam = (LPARAM)MapSL(lParam);
2242 /* fall through */
2243 #endif
2244 case CB_GETLBTEXT:
2245 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXT, wParam, lParam) :
2246 SendMessageA(lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2247 #ifndef __REACTOS__
2248 case CB_GETLBTEXTLEN16:
2249 wParam = (INT)(INT16)wParam;
2250 /* fall through */
2251 #endif
2252 case CB_GETLBTEXTLEN:
2253 return unicode ? SendMessageW(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0) :
2254 SendMessageA(lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2255 #ifndef __REACTOS__
2256 case CB_GETITEMDATA16:
2257 wParam = (INT)(INT16)wParam;
2258 /* fall through */
2259 #endif
2260 case CB_GETITEMDATA:
2261 return SendMessageW(lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2262 #ifndef __REACTOS__
2263 case CB_SETITEMDATA16:
2264 wParam = (INT)(INT16)wParam;
2265 /* fall through */
2266 #endif
2267 case CB_SETITEMDATA:
2268 return SendMessageW(lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2269 #ifndef __REACTOS__
2270 case CB_GETEDITSEL16:
2271 wParam = lParam = 0; /* just in case */
2272 /* fall through */
2273 #endif
2274 case CB_GETEDITSEL:
2275 /* Edit checks passed parameters itself */
2276 if( lphc->wState & CBF_EDIT )
2277 return SendMessageW(lphc->hWndEdit, EM_GETSEL, wParam, lParam);
2278 return CB_ERR;
2279 #ifndef __REACTOS__
2280 case CB_SETEDITSEL16:
2281 #endif
2282 case CB_SETEDITSEL:
2283 if( lphc->wState & CBF_EDIT )
2284 return SendMessageW(lphc->hWndEdit, EM_SETSEL,
2285 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2286 return CB_ERR;
2287 #ifndef __REACTOS__
2288 case CB_SETEXTENDEDUI16:
2289 #endif
2290 case CB_SETEXTENDEDUI:
2291 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2292 return CB_ERR;
2293 if( wParam )
2294 lphc->wState |= CBF_EUI;
2295 else lphc->wState &= ~CBF_EUI;
2296 return CB_OKAY;
2297 #ifndef __REACTOS__
2298 case CB_GETEXTENDEDUI16:
2299 #endif
2300 case CB_GETEXTENDEDUI:
2301 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2302
2303 default:
2304 if (message >= WM_USER)
2305 WARN("unknown msg WM_USER+%04x wp=%04x lp=%08lx\n",
2306 message - WM_USER, wParam, lParam );
2307 break;
2308 }
2309 return unicode ? DefWindowProcW(hwnd, message, wParam, lParam) :
2310 DefWindowProcA(hwnd, message, wParam, lParam);
2311 }
2312
2313 /***********************************************************************
2314 * ComboWndProcA
2315 *
2316 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2317 * window structs.
2318 */
2319 static LRESULT WINAPI ComboWndProcA( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2320 {
2321 if (!IsWindow(hwnd)) return 0;
2322 return ComboWndProc_common( hwnd, message, wParam, lParam, FALSE );
2323 }
2324
2325 /***********************************************************************
2326 * ComboWndProcW
2327 */
2328 static LRESULT WINAPI ComboWndProcW( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
2329 {
2330 if (!IsWindow(hwnd)) return 0;
2331 return ComboWndProc_common( hwnd, message, wParam, lParam, TRUE );
2332 }
2333
2334 /*************************************************************************
2335 * GetComboBoxInfo (USER32.@)
2336 */
2337 BOOL WINAPI GetComboBoxInfo(HWND hwndCombo, /* [in] handle to combo box */
2338 PCOMBOBOXINFO pcbi /* [in/out] combo box information */)
2339 {
2340 FIXME("\n");
2341 return FALSE;
2342
2343 }