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