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