Started implementing date/time common control.
[wine.git] / controls / combo.c
blobd4daf140d58ba11ee7550ac530d15d5501f7c9a8
1 /*
2 * Combo controls
3 *
4 * Copyright 1997 Alex Korobka
5 *
6 * FIXME: roll up in Netscape 3.01.
7 */
9 #include <string.h>
11 #include "winbase.h"
12 #include "winuser.h"
13 #include "wingdi.h"
14 #include "wine/winuser16.h"
15 #include "win.h"
16 #include "spy.h"
17 #include "user.h"
18 #include "heap.h"
19 #include "combo.h"
20 #include "drive.h"
21 #include "debugtools.h"
22 #include "tweak.h"
24 DEFAULT_DEBUG_CHANNEL(combo)
26 /* bits in the dwKeyData */
27 #define KEYDATA_ALT 0x2000
28 #define KEYDATA_PREVSTATE 0x4000
31 * Additional combo box definitions
34 #define CB_GETPTR( wnd ) (*(LPHEADCOMBO*)((wnd)->wExtra))
35 #define CB_NOTIFY( lphc, code ) \
36 (SendMessageA( (lphc)->owner, WM_COMMAND, \
37 MAKEWPARAM((lphc)->self->wIDmenu, (code)), (lphc)->self->hwndSelf))
38 #define CB_GETEDITTEXTLENGTH( lphc ) \
39 (SendMessageA( (lphc)->hWndEdit, WM_GETTEXTLENGTH, 0, 0 ))
42 * Drawing globals
44 static HBITMAP hComboBmp = 0;
45 static UINT CBitHeight, CBitWidth;
48 * Look and feel dependant "constants"
51 #define COMBO_YBORDERGAP 5
52 #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
53 #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 )
54 #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 )
55 #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 )
57 /***********************************************************************
58 * COMBO_Init
60 * Load combo button bitmap.
62 static BOOL COMBO_Init()
64 HDC hDC;
66 if( hComboBmp ) return TRUE;
67 if( (hDC = CreateCompatibleDC(0)) )
69 BOOL bRet = FALSE;
70 if( (hComboBmp = LoadBitmapA(0, MAKEINTRESOURCEA(OBM_COMBO))) )
72 BITMAP bm;
73 HBITMAP hPrevB;
74 RECT r;
76 GetObjectA( hComboBmp, sizeof(bm), &bm );
77 CBitHeight = bm.bmHeight;
78 CBitWidth = bm.bmWidth;
80 TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight );
82 hPrevB = SelectObject16( hDC, hComboBmp);
83 SetRect( &r, 0, 0, CBitWidth, CBitHeight );
84 InvertRect( hDC, &r );
85 SelectObject( hDC, hPrevB );
86 bRet = TRUE;
88 DeleteDC( hDC );
89 return bRet;
91 return FALSE;
94 /***********************************************************************
95 * COMBO_NCCreate
97 static LRESULT COMBO_NCCreate(WND* wnd, LPARAM lParam)
99 LPHEADCOMBO lphc;
101 if ( wnd && COMBO_Init() &&
102 (lphc = HeapAlloc(GetProcessHeap(), 0, sizeof(HEADCOMBO))) )
104 LPCREATESTRUCTA lpcs = (CREATESTRUCTA*)lParam;
106 memset( lphc, 0, sizeof(HEADCOMBO) );
107 *(LPHEADCOMBO*)wnd->wExtra = lphc;
109 /* some braindead apps do try to use scrollbar/border flags */
111 lphc->dwStyle = (lpcs->style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL));
112 wnd->dwStyle &= ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL);
115 * We also have to remove the client edge style to make sure
116 * we don't end-up with a non client area.
118 wnd->dwExStyle &= ~(WS_EX_CLIENTEDGE);
120 if( !(lpcs->style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) )
121 lphc->dwStyle |= CBS_HASSTRINGS;
122 if( !(wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) )
123 lphc->wState |= CBF_NOTIFY;
125 TRACE("[0x%08x], style = %08x\n",
126 (UINT)lphc, lphc->dwStyle );
128 return (LRESULT)(UINT)wnd->hwndSelf;
130 return (LRESULT)FALSE;
133 /***********************************************************************
134 * COMBO_NCDestroy
136 static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc )
139 if( lphc )
141 WND* wnd = lphc->self;
143 TRACE("[%04x]: freeing storage\n", CB_HWND(lphc));
145 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox )
146 DestroyWindow( lphc->hWndLBox );
148 HeapFree( GetProcessHeap(), 0, lphc );
149 wnd->wExtra[0] = 0;
151 return 0;
154 /***********************************************************************
155 * CBForceDummyResize
157 * The dummy resize is used for listboxes that have a popup to trigger
158 * a re-arranging of the contents of the combobox and the recalculation
159 * of the size of the "real" control window.
161 static void CBForceDummyResize(
162 LPHEADCOMBO lphc)
164 RECT windowRect;
166 GetWindowRect(CB_HWND(lphc), &windowRect);
169 * We have to be careful, resizing a combobox also has the meaning that the
170 * dropped rect will be resized. In this case, we want to trigger a resize
171 * to recalculate layout but we don't want to change the dropped rectangle
172 * So, we add the size of the dropped rectangle to the size of the control.
173 * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING
174 * message.
176 SetWindowPos( CB_HWND(lphc),
177 (HWND)NULL,
178 0, 0,
179 windowRect.right - windowRect.left,
180 windowRect.bottom - windowRect.top +
181 lphc->droppedRect.bottom - lphc->droppedRect.top,
182 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );
185 /***********************************************************************
186 * CBGetTextAreaHeight
188 * This method will calculate the height of the text area of the
189 * combobox.
190 * The height of the text area is set in two ways.
191 * It can be set explicitely through a combobox message of through a
192 * WM_MEASUREITEM callback.
193 * If this is not the case, the height is set to 13 dialog units.
194 * This height was determined through experimentation.
196 static INT CBGetTextAreaHeight(
197 HWND hwnd,
198 LPHEADCOMBO lphc)
200 INT iTextItemHeight;
202 if( lphc->editHeight ) /* explicitly set height */
204 iTextItemHeight = lphc->editHeight;
206 else
208 TEXTMETRICA tm;
209 HDC hDC = GetDC(hwnd);
210 HFONT hPrevFont = 0;
211 INT baseUnitY;
213 if (lphc->hFont)
214 hPrevFont = SelectObject( hDC, lphc->hFont );
216 GetTextMetricsA(hDC, &tm);
218 baseUnitY = tm.tmHeight;
220 if( hPrevFont )
221 SelectObject( hDC, hPrevFont );
223 ReleaseDC(hwnd, hDC);
225 iTextItemHeight = ((13 * baseUnitY) / 8);
228 * This "formula" calculates the height of the complete control.
229 * To calculate the height of the text area, we have to remove the
230 * borders.
232 iTextItemHeight -= 2*COMBO_YBORDERSIZE();
236 * Check the ownerdraw case if we haven't asked the parent the size
237 * of the item yet.
239 if ( CB_OWNERDRAWN(lphc) &&
240 (lphc->wState & CBF_MEASUREITEM) )
242 MEASUREITEMSTRUCT measureItem;
243 RECT clientRect;
244 INT originalItemHeight = iTextItemHeight;
247 * We use the client rect for the width of the item.
249 GetClientRect(hwnd, &clientRect);
251 lphc->wState &= ~CBF_MEASUREITEM;
254 * Send a first one to measure the size of the text area
256 measureItem.CtlType = ODT_COMBOBOX;
257 measureItem.CtlID = lphc->self->wIDmenu;
258 measureItem.itemID = -1;
259 measureItem.itemWidth = clientRect.right;
260 measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */
261 measureItem.itemData = 0;
262 SendMessageA(lphc->owner, WM_MEASUREITEM,
263 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
264 iTextItemHeight = 6 + measureItem.itemHeight;
267 * Send a second one in the case of a fixed ownerdraw list to calculate the
268 * size of the list items. (we basically do this on behalf of the listbox)
270 if (lphc->dwStyle & CBS_OWNERDRAWFIXED)
272 measureItem.CtlType = ODT_COMBOBOX;
273 measureItem.CtlID = lphc->self->wIDmenu;
274 measureItem.itemID = 0;
275 measureItem.itemWidth = clientRect.right;
276 measureItem.itemHeight = originalItemHeight;
277 measureItem.itemData = 0;
278 SendMessageA(lphc->owner, WM_MEASUREITEM,
279 (WPARAM)measureItem.CtlID, (LPARAM)&measureItem);
280 lphc->fixedOwnerDrawHeight = measureItem.itemHeight;
284 * Keep the size for the next time
286 lphc->editHeight = iTextItemHeight;
289 return iTextItemHeight;
293 /***********************************************************************
294 * CBCalcPlacement
296 * Set up component coordinates given valid lphc->RectCombo.
298 static void CBCalcPlacement(
299 HWND hwnd,
300 LPHEADCOMBO lphc,
301 LPRECT lprEdit,
302 LPRECT lprButton,
303 LPRECT lprLB)
306 * Again, start with the client rectangle.
308 GetClientRect(hwnd, lprEdit);
311 * Remove the borders
313 InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE());
316 * Chop off the bottom part to fit with the height of the text area.
318 lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc);
321 * The button starts the same vertical position as the text area.
323 CopyRect(lprButton, lprEdit);
326 * If the combobox is "simple" there is no button.
328 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
329 lprButton->left = lprButton->right = lprButton->bottom = 0;
330 else
333 * Let's assume the combobox button is the same width as the
334 * scrollbar button.
335 * size the button horizontally and cut-off the text area.
337 lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL);
338 lprEdit->right = lprButton->left;
342 * In the case of a dropdown, there is an additional spacing between the
343 * text area and the button.
345 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
347 lprEdit->right -= COMBO_EDITBUTTONSPACE();
351 * If we have an edit control, we space it away from the borders slightly.
353 if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST)
355 InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING());
359 * Adjust the size of the listbox popup.
361 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
364 * Use the client rectangle to initialize the listbox rectangle
366 GetClientRect(hwnd, lprLB);
369 * Then, chop-off the top part.
371 lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE();
373 else
376 * Make sure the dropped width is as large as the combobox itself.
378 if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE()))
380 lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE());
383 * In the case of a dropdown, the popup listbox is offset to the right.
384 * so, we want to make sure it's flush with the right side of the
385 * combobox
387 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
388 lprLB->right -= COMBO_EDITBUTTONSPACE();
390 else
391 lprLB->right = lprLB->left + lphc->droppedWidth;
394 TRACE("\ttext\t= (%i,%i-%i,%i)\n",
395 lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom);
397 TRACE("\tbutton\t= (%i,%i-%i,%i)\n",
398 lprButton->left, lprButton->top, lprButton->right, lprButton->bottom);
400 TRACE("\tlbox\t= (%i,%i-%i,%i)\n",
401 lprLB->left, lprLB->top, lprLB->right, lprLB->bottom );
404 /***********************************************************************
405 * CBGetDroppedControlRect
407 static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect)
409 /* In windows, CB_GETDROPPEDCONTROLRECT returns the upper left corner
410 of the combo box and the lower right corner of the listbox */
412 GetWindowRect(lphc->self->hwndSelf, lpRect);
414 lpRect->right = lpRect->left + lphc->droppedRect.right - lphc->droppedRect.left;
415 lpRect->bottom = lpRect->top + lphc->droppedRect.bottom - lphc->droppedRect.top;
419 /***********************************************************************
420 * COMBO_WindowPosChanging
422 static LRESULT COMBO_WindowPosChanging(
423 HWND hwnd,
424 LPHEADCOMBO lphc,
425 WINDOWPOS* posChanging)
428 * We need to override the WM_WINDOWPOSCHANGING method to handle all
429 * the non-simple comboboxes. The problem is that those controls are
430 * always the same height. We have to make sure they are not resized
431 * to another value.
433 if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) &&
434 ((posChanging->flags & SWP_NOSIZE) == 0) )
436 int newComboHeight;
438 newComboHeight = CBGetTextAreaHeight(hwnd,lphc) +
439 2*COMBO_YBORDERSIZE();
442 * Resizing a combobox has another side effect, it resizes the dropped
443 * rectangle as well. However, it does it only if the new height for the
444 * combobox is different than the height it should have. In other words,
445 * if the application resizing the combobox only had the intention to resize
446 * the actual control, for example, to do the layout of a dialog that is
447 * resized, the height of the dropdown is not changed.
449 if (posChanging->cy != newComboHeight)
451 lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight;
453 posChanging->cy = newComboHeight;
457 return 0;
460 /***********************************************************************
461 * COMBO_Create
463 static LRESULT COMBO_Create( LPHEADCOMBO lphc, WND* wnd, LPARAM lParam)
465 static char clbName[] = "ComboLBox";
466 static char editName[] = "Edit";
468 LPCREATESTRUCTA lpcs = (CREATESTRUCTA*)lParam;
470 if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE;
471 else if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT;
473 lphc->self = wnd;
474 lphc->owner = lpcs->hwndParent;
477 * The item height and dropped width are not set when the control
478 * is created.
480 lphc->droppedWidth = lphc->editHeight = 0;
483 * The first time we go through, we want to measure the ownerdraw item
485 lphc->wState |= CBF_MEASUREITEM;
487 /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */
489 if( lphc->owner || !(lpcs->style & WS_VISIBLE) )
491 UINT lbeStyle = 0;
492 UINT lbeExStyle = 0;
495 * Initialize the dropped rect to the size of the client area of the
496 * control and then, force all the areas of the combobox to be
497 * recalculated.
499 GetClientRect( wnd->hwndSelf, &lphc->droppedRect );
501 CBCalcPlacement(wnd->hwndSelf,
502 lphc,
503 &lphc->textRect,
504 &lphc->buttonRect,
505 &lphc->droppedRect );
508 * Adjust the position of the popup listbox if it's necessary
510 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
512 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
515 * If it's a dropdown, the listbox is offset
517 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
518 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
520 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect);
521 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect.right);
524 /* create listbox popup */
526 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS) |
527 (lpcs->style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
529 if( lphc->dwStyle & CBS_SORT )
530 lbeStyle |= LBS_SORT;
531 if( lphc->dwStyle & CBS_HASSTRINGS )
532 lbeStyle |= LBS_HASSTRINGS;
533 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
534 lbeStyle |= LBS_NOINTEGRALHEIGHT;
535 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
536 lbeStyle |= LBS_DISABLENOSCROLL;
538 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
540 lbeStyle |= WS_CHILD | WS_VISIBLE;
543 * In win 95 look n feel, the listbox in the simple combobox has
544 * the WS_EXCLIENTEDGE style instead of the WS_BORDER style.
546 if (TWEAK_WineLook > WIN31_LOOK)
548 lbeStyle &= ~WS_BORDER;
549 lbeExStyle |= WS_EX_CLIENTEDGE;
552 else /* popup listbox */
553 lbeStyle |= WS_POPUP;
555 /* Dropdown ComboLBox is not a child window and we cannot pass
556 * ID_CB_LISTBOX directly because it will be treated as a menu handle.
558 lphc->hWndLBox = CreateWindowExA(lbeExStyle,
559 clbName,
560 NULL,
561 lbeStyle,
562 lphc->droppedRect.left,
563 lphc->droppedRect.top,
564 lphc->droppedRect.right - lphc->droppedRect.left,
565 lphc->droppedRect.bottom - lphc->droppedRect.top,
566 lphc->self->hwndSelf,
567 (lphc->dwStyle & CBS_DROPDOWN)? (HMENU)0 : (HMENU)ID_CB_LISTBOX,
568 lphc->self->hInstance,
569 (LPVOID)lphc );
572 * The ComboLBox is a strange little beast (when it's not a CBS_SIMPLE)...
573 * It's a popup window but, when you get the window style, you get WS_CHILD.
574 * When created, it's parent is the combobox but, when you ask for it's parent
575 * after that, you're supposed to get the desktop. (see MFC code function
576 * AfxCancelModes)
577 * To achieve this in Wine, we have to create it as a popup and change
578 * it's style to child after the creation.
580 if ( (lphc->hWndLBox!= 0) &&
581 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
583 SetWindowLongA(lphc->hWndLBox,
584 GWL_STYLE,
585 (GetWindowLongA(lphc->hWndLBox, GWL_STYLE) | WS_CHILD) & ~WS_POPUP);
588 if( lphc->hWndLBox )
590 BOOL bEdit = TRUE;
591 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT;
594 * In Win95 look, the border fo the edit control is
595 * provided by the combobox
597 if (TWEAK_WineLook == WIN31_LOOK)
598 lbeStyle |= WS_BORDER;
600 if( lphc->wState & CBF_EDIT )
602 if( lphc->dwStyle & CBS_OEMCONVERT )
603 lbeStyle |= ES_OEMCONVERT;
604 if( lphc->dwStyle & CBS_AUTOHSCROLL )
605 lbeStyle |= ES_AUTOHSCROLL;
606 if( lphc->dwStyle & CBS_LOWERCASE )
607 lbeStyle |= ES_LOWERCASE;
608 else if( lphc->dwStyle & CBS_UPPERCASE )
609 lbeStyle |= ES_UPPERCASE;
611 lphc->hWndEdit = CreateWindowExA(0,
612 editName,
613 NULL,
614 lbeStyle,
615 lphc->textRect.left, lphc->textRect.top,
616 lphc->textRect.right - lphc->textRect.left,
617 lphc->textRect.bottom - lphc->textRect.top,
618 lphc->self->hwndSelf,
619 (HMENU)ID_CB_EDIT,
620 lphc->self->hInstance,
621 NULL );
623 if( !lphc->hWndEdit )
624 bEdit = FALSE;
627 if( bEdit )
630 * If the combo is a dropdown, we must resize the control to fit only
631 * the text area and button. To do this, we send a dummy resize and the
632 * WM_WINDOWPOSCHANGING message will take care of setting the height for
633 * us.
635 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
637 CBForceDummyResize(lphc);
640 TRACE("init done\n");
641 return wnd->hwndSelf;
643 ERR("edit control failure.\n");
644 } else ERR("listbox failure.\n");
645 } else ERR("no owner for visible combo.\n");
647 /* CreateWindow() will send WM_NCDESTROY to cleanup */
649 return -1;
652 /***********************************************************************
653 * CBPaintButton
655 * Paint combo button (normal, pressed, and disabled states).
657 static void CBPaintButton(
658 LPHEADCOMBO lphc,
659 HDC hdc,
660 RECT rectButton)
662 UINT x, y;
663 BOOL bBool;
664 HDC hMemDC;
665 HBRUSH hPrevBrush;
666 COLORREF oldTextColor, oldBkColor;
668 if( lphc->wState & CBF_NOREDRAW )
669 return;
671 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
674 * Draw the button background
676 PatBlt( hdc,
677 rectButton.left,
678 rectButton.top,
679 rectButton.right-rectButton.left,
680 rectButton.bottom-rectButton.top,
681 PATCOPY );
683 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
685 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
687 else
689 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
693 * Remove the edge of the button from the rectangle
694 * and calculate the position of the bitmap.
696 InflateRect( &rectButton, -2, -2);
698 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
699 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
702 hMemDC = CreateCompatibleDC( hdc );
703 SelectObject( hMemDC, hComboBmp );
704 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
705 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
706 RGB(0,0,0) );
707 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
708 SetBkColor( hdc, oldBkColor );
709 SetTextColor( hdc, oldTextColor );
710 DeleteDC( hMemDC );
711 SelectObject( hdc, hPrevBrush );
714 /***********************************************************************
715 * CBPaintText
717 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
719 static void CBPaintText(
720 LPHEADCOMBO lphc,
721 HDC hdc,
722 RECT rectEdit)
724 INT id, size = 0;
725 LPSTR pText = NULL;
727 if( lphc->wState & CBF_NOREDRAW ) return;
729 /* follow Windows combobox that sends a bunch of text
730 * inquiries to its listbox while processing WM_PAINT. */
732 if( (id = SendMessageA(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
734 size = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
735 if( (pText = HeapAlloc( GetProcessHeap(), 0, size + 1)) )
737 SendMessageA( lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText );
738 pText[size] = '\0'; /* just in case */
739 } else return;
742 if( lphc->wState & CBF_EDIT )
744 if( CB_HASSTRINGS(lphc) ) SetWindowTextA( lphc->hWndEdit, pText ? pText : "" );
745 if( lphc->wState & CBF_FOCUSED )
746 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
748 else /* paint text field ourselves */
750 HBRUSH hPrevBrush = 0;
751 HDC hDC = hdc;
753 if( !hDC )
755 if ((hDC = GetDC(lphc->self->hwndSelf)))
757 HBRUSH hBrush = SendMessageA( lphc->owner,
758 WM_CTLCOLORLISTBOX,
759 hDC, lphc->self->hwndSelf );
760 hPrevBrush = SelectObject( hDC,
761 (hBrush) ? hBrush : GetStockObject(WHITE_BRUSH) );
764 if( hDC )
766 UINT itemState;
767 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hDC, lphc->hFont) : 0;
770 * Give ourselves some space.
772 InflateRect( &rectEdit, -1, -1 );
774 if ( (lphc->wState & CBF_FOCUSED) &&
775 !(lphc->wState & CBF_DROPPED) )
777 /* highlight */
779 FillRect( hDC, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
780 SetBkColor( hDC, GetSysColor( COLOR_HIGHLIGHT ) );
781 SetTextColor( hDC, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
782 itemState = ODS_SELECTED | ODS_FOCUS;
784 else
785 itemState = 0;
787 if( CB_OWNERDRAWN(lphc) )
789 DRAWITEMSTRUCT dis;
790 HRGN clipRegion;
793 * Save the current clip region.
794 * To retrieve the clip region, we need to create one "dummy"
795 * clip region.
797 clipRegion = CreateRectRgnIndirect(&rectEdit);
799 if (GetClipRgn(hDC, clipRegion)!=1)
801 DeleteObject(clipRegion);
802 clipRegion=(HRGN)NULL;
805 if ( lphc->self->dwStyle & WS_DISABLED )
806 itemState |= ODS_DISABLED;
808 dis.CtlType = ODT_COMBOBOX;
809 dis.CtlID = lphc->self->wIDmenu;
810 dis.hwndItem = lphc->self->hwndSelf;
811 dis.itemAction = ODA_DRAWENTIRE;
812 dis.itemID = id;
813 dis.itemState = itemState;
814 dis.hDC = hDC;
815 dis.rcItem = rectEdit;
816 dis.itemData = SendMessageA( lphc->hWndLBox, LB_GETITEMDATA,
817 (WPARAM)id, 0 );
820 * Clip the DC and have the parent draw the item.
822 IntersectClipRect(hDC,
823 rectEdit.left, rectEdit.top,
824 rectEdit.right, rectEdit.bottom);
826 SendMessageA(lphc->owner, WM_DRAWITEM,
827 lphc->self->wIDmenu, (LPARAM)&dis );
830 * Reset the clipping region.
832 SelectClipRgn(hDC, clipRegion);
834 else
836 ExtTextOutA( hDC,
837 rectEdit.left + 1,
838 rectEdit.top + 1,
839 ETO_OPAQUE | ETO_CLIPPED,
840 &rectEdit,
841 pText ? pText : "" , size, NULL );
843 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
844 DrawFocusRect( hDC, &rectEdit );
847 if( hPrevFont )
848 SelectObject(hDC, hPrevFont );
850 if( !hdc )
852 if( hPrevBrush )
853 SelectObject( hDC, hPrevBrush );
855 ReleaseDC( lphc->self->hwndSelf, hDC );
859 if (pText)
860 HeapFree( GetProcessHeap(), 0, pText );
863 /***********************************************************************
864 * CBPaintBorder
866 static void CBPaintBorder(
867 HWND hwnd,
868 LPHEADCOMBO lphc,
869 HDC hdc)
871 RECT clientRect;
873 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
875 GetClientRect(hwnd, &clientRect);
877 else
879 CopyRect(&clientRect, &lphc->textRect);
881 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
882 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
885 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
888 /***********************************************************************
889 * COMBO_EraseBackground
891 static LRESULT COMBO_EraseBackground(
892 HWND hwnd,
893 LPHEADCOMBO lphc,
894 HDC hParamDC)
896 HBRUSH hBkgBrush;
897 RECT clientRect;
898 HDC hDC;
900 hDC = (hParamDC) ? hParamDC
901 : GetDC(hwnd);
904 * Calculate the area that we want to erase.
906 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
908 GetClientRect(hwnd, &clientRect);
910 else
912 CopyRect(&clientRect, &lphc->textRect);
914 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
917 hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX,
918 hDC, hwnd);
920 if( !hBkgBrush )
921 hBkgBrush = GetStockObject(WHITE_BRUSH);
923 FillRect(hDC, &clientRect, hBkgBrush);
925 if (!hParamDC)
926 ReleaseDC(hwnd, hDC);
928 return TRUE;
931 /***********************************************************************
932 * COMBO_Paint
934 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
936 PAINTSTRUCT ps;
937 HDC hDC;
939 hDC = (hParamDC) ? hParamDC
940 : BeginPaint( lphc->self->hwndSelf, &ps);
943 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
945 HBRUSH hPrevBrush, hBkgBrush;
947 hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX,
948 hDC, lphc->self->hwndSelf );
950 if( !hBkgBrush )
951 hBkgBrush = GetStockObject(WHITE_BRUSH);
953 hPrevBrush = SelectObject( hDC, hBkgBrush );
956 * In non 3.1 look, there is a sunken border on the combobox
958 if (TWEAK_WineLook != WIN31_LOOK)
960 CBPaintBorder(CB_HWND(lphc), lphc, hDC);
963 if( !IsRectEmpty(&lphc->buttonRect) )
965 CBPaintButton(lphc, hDC, lphc->buttonRect);
968 if( !(lphc->wState & CBF_EDIT) )
971 * The text area has a border only in Win 3.1 look.
973 if (TWEAK_WineLook == WIN31_LOOK)
975 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
977 Rectangle( hDC,
978 lphc->textRect.left, lphc->textRect.top,
979 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
981 SelectObject( hDC, hPrevPen );
984 CBPaintText( lphc, hDC, lphc->textRect);
987 if( hPrevBrush )
988 SelectObject( hDC, hPrevBrush );
991 if( !hParamDC )
992 EndPaint(lphc->self->hwndSelf, &ps);
994 return 0;
997 /***********************************************************************
998 * CBUpdateLBox
1000 * Select listbox entry according to the contents of the edit control.
1002 static INT CBUpdateLBox( LPHEADCOMBO lphc )
1004 INT length, idx, ret;
1005 LPSTR pText = NULL;
1007 idx = ret = LB_ERR;
1008 length = CB_GETEDITTEXTLENGTH( lphc );
1010 if( length > 0 )
1011 pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1);
1013 TRACE("\t edit text length %i\n", length );
1015 if( pText )
1017 if( length ) GetWindowTextA( lphc->hWndEdit, pText, length + 1);
1018 else pText[0] = '\0';
1019 idx = SendMessageA( lphc->hWndLBox, LB_FINDSTRING,
1020 (WPARAM)(-1), (LPARAM)pText );
1021 if( idx == LB_ERR ) idx = 0; /* select first item */
1022 else ret = idx;
1023 HeapFree( GetProcessHeap(), 0, pText );
1026 /* select entry */
1028 SendMessageA( lphc->hWndLBox, LB_SETCURSEL, (WPARAM)idx, 0 );
1030 if( idx >= 0 )
1032 SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)idx, 0 );
1033 /* probably superfluous but Windows sends this too */
1034 SendMessageA( lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)idx, 0 );
1036 return ret;
1039 /***********************************************************************
1040 * CBUpdateEdit
1042 * Copy a listbox entry to the edit control.
1044 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1046 INT length;
1047 LPSTR pText = NULL;
1049 TRACE("\t %i\n", index );
1051 if( index == -1 )
1053 length = CB_GETEDITTEXTLENGTH( lphc );
1054 if( length )
1056 if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) )
1058 GetWindowTextA( lphc->hWndEdit, pText, length + 1 );
1059 index = SendMessageA( lphc->hWndLBox, LB_FINDSTRING,
1060 (WPARAM)(-1), (LPARAM)pText );
1061 HeapFree( GetProcessHeap(), 0, pText );
1066 if( index >= 0 ) /* got an entry */
1068 length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1069 if( length )
1071 if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) )
1073 SendMessageA( lphc->hWndLBox, LB_GETTEXT,
1074 (WPARAM)index, (LPARAM)pText );
1076 lphc->wState |= CBF_NOEDITNOTIFY;
1078 SendMessageA( lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)pText );
1079 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) );
1080 HeapFree( GetProcessHeap(), 0, pText );
1086 /***********************************************************************
1087 * CBDropDown
1089 * Show listbox popup.
1091 static void CBDropDown( LPHEADCOMBO lphc )
1093 RECT rect;
1094 int nItems = 0;
1095 int i;
1096 int nHeight;
1097 int nDroppedHeight;
1099 TRACE("[%04x]: drop down\n", CB_HWND(lphc));
1101 CB_NOTIFY( lphc, CBN_DROPDOWN );
1103 /* set selection */
1105 lphc->wState |= CBF_DROPPED;
1106 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1108 lphc->droppedIndex = CBUpdateLBox( lphc );
1110 if( !(lphc->wState & CBF_CAPTURE) )
1111 CBUpdateEdit( lphc, lphc->droppedIndex );
1113 else
1115 lphc->droppedIndex = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1117 if( lphc->droppedIndex == LB_ERR )
1118 lphc->droppedIndex = 0;
1120 SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)lphc->droppedIndex, 0 );
1121 SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 );
1124 /* now set popup position */
1125 GetWindowRect( lphc->self->hwndSelf, &rect );
1128 * If it's a dropdown, the listbox is offset
1130 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1131 rect.left += COMBO_EDITBUTTONSPACE();
1133 /* if the dropped height is greater than the total height of the dropped
1134 items list, then force the drop down list height to be the total height
1135 of the items in the dropped list */
1137 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1138 nItems = (int)SendMessageA (lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1139 nHeight = COMBO_YBORDERGAP;
1140 for (i = 0; i < nItems; i++)
1142 nHeight += (int)SendMessageA (lphc->hWndLBox, LB_GETITEMHEIGHT, i, 0);
1144 if (nHeight >= nDroppedHeight)
1145 break;
1148 if (nHeight < nDroppedHeight)
1149 nDroppedHeight = nHeight;
1151 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1152 lphc->droppedRect.right - lphc->droppedRect.left,
1153 nDroppedHeight,
1154 SWP_NOACTIVATE | SWP_NOREDRAW);
1156 if( !(lphc->wState & CBF_NOREDRAW) )
1157 RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE |
1158 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1160 ShowWindow( lphc->hWndLBox, SW_SHOWNA );
1163 /***********************************************************************
1164 * CBRollUp
1166 * Hide listbox popup.
1168 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1170 HWND hWnd = lphc->self->hwndSelf;
1172 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1174 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1177 TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok );
1179 if( lphc->wState & CBF_DROPPED )
1181 RECT rect;
1184 * It seems useful to send the WM_LBUTTONUP with (-1,-1) when cancelling
1185 * and with (0,0) (anywhere in the listbox) when Oking.
1187 SendMessageA( lphc->hWndLBox, WM_LBUTTONUP, 0, ok ? (LPARAM)0 : (LPARAM)(-1) );
1189 lphc->wState &= ~CBF_DROPPED;
1190 ShowWindow( lphc->hWndLBox, SW_HIDE );
1192 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1194 INT index = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1195 CBUpdateEdit( lphc, index );
1196 rect = lphc->buttonRect;
1198 else
1200 if( bButton )
1202 UnionRect( &rect,
1203 &lphc->buttonRect,
1204 &lphc->textRect);
1206 else
1207 rect = lphc->textRect;
1209 bButton = TRUE;
1212 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1213 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1214 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1215 CB_NOTIFY( lphc, CBN_CLOSEUP );
1220 /***********************************************************************
1221 * COMBO_FlipListbox
1223 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1225 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL bRedrawButton )
1227 if( lphc->wState & CBF_DROPPED )
1229 CBRollUp( lphc, TRUE, bRedrawButton );
1230 return FALSE;
1233 CBDropDown( lphc );
1234 return TRUE;
1237 /***********************************************************************
1238 * COMBO_GetLBWindow
1240 * Edit control helper.
1242 HWND COMBO_GetLBWindow( WND* pWnd )
1244 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1245 if( lphc ) return lphc->hWndLBox;
1246 return 0;
1250 /***********************************************************************
1251 * CBRepaintButton
1253 static void CBRepaintButton( LPHEADCOMBO lphc )
1255 InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE);
1256 UpdateWindow(CB_HWND(lphc));
1259 /***********************************************************************
1260 * COMBO_SetFocus
1262 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1264 if( !(lphc->wState & CBF_FOCUSED) )
1266 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1267 SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 );
1269 if( lphc->wState & CBF_EDIT )
1270 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) );
1271 lphc->wState |= CBF_FOCUSED;
1272 if( !(lphc->wState & CBF_EDIT) )
1274 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1277 CB_NOTIFY( lphc, CBN_SETFOCUS );
1281 /***********************************************************************
1282 * COMBO_KillFocus
1284 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1286 HWND hWnd = lphc->self->hwndSelf;
1288 if( lphc->wState & CBF_FOCUSED )
1290 SendMessageA( hWnd, WM_LBUTTONUP, 0, (LPARAM)(-1) );
1292 CBRollUp( lphc, FALSE, TRUE );
1293 if( IsWindow( hWnd ) )
1295 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1296 SendMessageA( lphc->hWndLBox, LB_CARETOFF, 0, 0 );
1298 lphc->wState &= ~CBF_FOCUSED;
1300 /* redraw text */
1301 if( lphc->wState & CBF_EDIT )
1302 SendMessageA( lphc->hWndEdit, EM_SETSEL, (WPARAM)(-1), 0 );
1303 else
1305 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1308 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1313 /***********************************************************************
1314 * COMBO_Command
1316 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1318 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1320 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1322 switch( HIWORD(wParam) >> 8 )
1324 case (EN_SETFOCUS >> 8):
1326 TRACE("[%04x]: edit [%04x] got focus\n",
1327 CB_HWND(lphc), lphc->hWndEdit );
1329 if( !(lphc->wState & CBF_FOCUSED) ) COMBO_SetFocus( lphc );
1330 break;
1332 case (EN_KILLFOCUS >> 8):
1334 TRACE("[%04x]: edit [%04x] lost focus\n",
1335 CB_HWND(lphc), lphc->hWndEdit );
1337 /* NOTE: it seems that Windows' edit control sends an
1338 * undocumented message WM_USER + 0x1B instead of this
1339 * notification (only when it happens to be a part of
1340 * the combo). ?? - AK.
1343 COMBO_KillFocus( lphc );
1344 break;
1347 case (EN_CHANGE >> 8):
1349 * In some circumstances (when the selection of the combobox
1350 * is changed for example) we don't wans the EN_CHANGE notification
1351 * to be forwarded to the parent of the combobox. This code
1352 * checks a flag that is set in these occasions and ignores the
1353 * notification.
1355 if (lphc->wState & CBF_NOEDITNOTIFY)
1357 lphc->wState &= ~CBF_NOEDITNOTIFY;
1359 else
1361 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1364 CBUpdateLBox( lphc );
1365 break;
1367 case (EN_UPDATE >> 8):
1368 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1369 break;
1371 case (EN_ERRSPACE >> 8):
1372 CB_NOTIFY( lphc, CBN_ERRSPACE );
1375 else if( lphc->hWndLBox == hWnd )
1377 switch( HIWORD(wParam) )
1379 case LBN_ERRSPACE:
1380 CB_NOTIFY( lphc, CBN_ERRSPACE );
1381 break;
1383 case LBN_DBLCLK:
1384 CB_NOTIFY( lphc, CBN_DBLCLK );
1385 break;
1387 case LBN_SELCHANGE:
1388 case LBN_SELCANCEL:
1390 TRACE("[%04x]: lbox selection change [%04x]\n",
1391 CB_HWND(lphc), lphc->wState );
1393 /* do not roll up if selection is being tracked
1394 * by arrowkeys in the dropdown listbox */
1396 if( (lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP) )
1397 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1398 else lphc->wState &= ~CBF_NOROLLUP;
1400 CB_NOTIFY( lphc, CBN_SELCHANGE );
1401 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1402 /* fall through */
1404 case LBN_SETFOCUS:
1405 case LBN_KILLFOCUS:
1406 /* nothing to do here since ComboLBox always resets the focus to its
1407 * combo/edit counterpart */
1408 break;
1411 return 0;
1414 /***********************************************************************
1415 * COMBO_ItemOp
1417 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1419 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg,
1420 WPARAM wParam, LPARAM lParam )
1422 HWND hWnd = lphc->self->hwndSelf;
1424 TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg );
1426 #define lpIS ((LPDELETEITEMSTRUCT)lParam)
1428 /* two first items are the same in all 4 structs */
1429 lpIS->CtlType = ODT_COMBOBOX;
1430 lpIS->CtlID = lphc->self->wIDmenu;
1432 switch( msg ) /* patch window handle */
1434 case WM_DELETEITEM:
1435 lpIS->hwndItem = hWnd;
1436 #undef lpIS
1437 break;
1438 case WM_DRAWITEM:
1439 #define lpIS ((LPDRAWITEMSTRUCT)lParam)
1440 lpIS->hwndItem = hWnd;
1441 #undef lpIS
1442 break;
1443 case WM_COMPAREITEM:
1444 #define lpIS ((LPCOMPAREITEMSTRUCT)lParam)
1445 lpIS->hwndItem = hWnd;
1446 #undef lpIS
1447 break;
1450 return SendMessageA( lphc->owner, msg, lphc->self->wIDmenu, lParam );
1453 /***********************************************************************
1454 * COMBO_GetText
1456 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, UINT N, LPSTR lpText)
1458 if( lphc->wState & CBF_EDIT )
1459 return SendMessageA( lphc->hWndEdit, WM_GETTEXT,
1460 (WPARAM)N, (LPARAM)lpText );
1462 /* get it from the listbox */
1464 if( lphc->hWndLBox )
1466 INT idx = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1467 if( idx != LB_ERR )
1469 LPSTR lpBuffer;
1470 INT length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN,
1471 (WPARAM)idx, 0 );
1473 /* 'length' is without the terminating character */
1474 if( length >= N )
1475 lpBuffer = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1 );
1476 else
1477 lpBuffer = lpText;
1479 if( lpBuffer )
1481 INT n = SendMessageA( lphc->hWndLBox, LB_GETTEXT,
1482 (WPARAM)idx, (LPARAM)lpBuffer );
1484 /* truncate if buffer is too short */
1486 if( length >= N )
1488 if (N && lpText) {
1489 if( n != LB_ERR ) memcpy( lpText, lpBuffer, (N>n) ? n+1 : N-1 );
1490 lpText[N - 1] = '\0';
1492 HeapFree( GetProcessHeap(), 0, lpBuffer );
1494 return (LRESULT)n;
1498 return 0;
1502 /***********************************************************************
1503 * CBResetPos
1505 * This function sets window positions according to the updated
1506 * component placement struct.
1508 static void CBResetPos(
1509 LPHEADCOMBO lphc,
1510 LPRECT rectEdit,
1511 LPRECT rectLB,
1512 BOOL bRedraw)
1514 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1516 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1517 * sizing messages */
1519 if( lphc->wState & CBF_EDIT )
1520 SetWindowPos( lphc->hWndEdit, 0,
1521 rectEdit->left, rectEdit->top,
1522 rectEdit->right - rectEdit->left,
1523 rectEdit->bottom - rectEdit->top,
1524 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1526 SetWindowPos( lphc->hWndLBox, 0,
1527 rectLB->left, rectLB->top,
1528 rectLB->right - rectLB->left,
1529 rectLB->bottom - rectLB->top,
1530 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1532 if( bDrop )
1534 if( lphc->wState & CBF_DROPPED )
1536 lphc->wState &= ~CBF_DROPPED;
1537 ShowWindow( lphc->hWndLBox, SW_HIDE );
1540 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1541 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
1542 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1547 /***********************************************************************
1548 * COMBO_Size
1550 static void COMBO_Size( LPHEADCOMBO lphc )
1552 CBCalcPlacement(lphc->self->hwndSelf,
1553 lphc,
1554 &lphc->textRect,
1555 &lphc->buttonRect,
1556 &lphc->droppedRect);
1558 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1562 /***********************************************************************
1563 * COMBO_Font
1565 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1568 * Set the font
1570 lphc->hFont = hFont;
1573 * Propagate to owned windows.
1575 if( lphc->wState & CBF_EDIT )
1576 SendMessageA( lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw );
1577 SendMessageA( lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw );
1580 * Redo the layout of the control.
1582 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1584 CBCalcPlacement(lphc->self->hwndSelf,
1585 lphc,
1586 &lphc->textRect,
1587 &lphc->buttonRect,
1588 &lphc->droppedRect);
1590 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1592 else
1594 CBForceDummyResize(lphc);
1599 /***********************************************************************
1600 * COMBO_SetItemHeight
1602 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1604 LRESULT lRet = CB_ERR;
1606 if( index == -1 ) /* set text field height */
1608 if( height < 32768 )
1610 lphc->editHeight = height;
1613 * Redo the layout of the control.
1615 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1617 CBCalcPlacement(lphc->self->hwndSelf,
1618 lphc,
1619 &lphc->textRect,
1620 &lphc->buttonRect,
1621 &lphc->droppedRect);
1623 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1625 else
1627 CBForceDummyResize(lphc);
1630 lRet = height;
1633 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1634 lRet = SendMessageA( lphc->hWndLBox, LB_SETITEMHEIGHT,
1635 (WPARAM)index, (LPARAM)height );
1636 return lRet;
1639 /***********************************************************************
1640 * COMBO_SelectString
1642 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPCSTR pText )
1644 INT index = SendMessageA( lphc->hWndLBox, LB_SELECTSTRING,
1645 (WPARAM)start, (LPARAM)pText );
1646 if( index >= 0 )
1648 if( lphc->wState & CBF_EDIT )
1649 CBUpdateEdit( lphc, index );
1650 else
1652 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1655 return (LRESULT)index;
1658 /***********************************************************************
1659 * COMBO_LButtonDown
1661 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1663 POINT pt;
1664 BOOL bButton;
1665 HWND hWnd = lphc->self->hwndSelf;
1667 pt.x = LOWORD(lParam);
1668 pt.y = HIWORD(lParam);
1669 bButton = PtInRect(&lphc->buttonRect, pt);
1671 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1672 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1674 lphc->wState |= CBF_BUTTONDOWN;
1675 if( lphc->wState & CBF_DROPPED )
1677 /* got a click to cancel selection */
1679 lphc->wState &= ~CBF_BUTTONDOWN;
1680 CBRollUp( lphc, TRUE, FALSE );
1681 if( !IsWindow( hWnd ) ) return;
1683 if( lphc->wState & CBF_CAPTURE )
1685 lphc->wState &= ~CBF_CAPTURE;
1686 ReleaseCapture();
1689 else
1691 /* drop down the listbox and start tracking */
1693 lphc->wState |= CBF_CAPTURE;
1694 CBDropDown( lphc );
1695 SetCapture( hWnd );
1697 if( bButton ) CBRepaintButton( lphc );
1701 /***********************************************************************
1702 * COMBO_LButtonUp
1704 * Release capture and stop tracking if needed.
1706 static void COMBO_LButtonUp( LPHEADCOMBO lphc, LPARAM lParam )
1708 if( lphc->wState & CBF_CAPTURE )
1710 lphc->wState &= ~CBF_CAPTURE;
1711 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1713 INT index = CBUpdateLBox( lphc );
1714 CBUpdateEdit( lphc, index );
1716 ReleaseCapture();
1719 if( lphc->wState & CBF_BUTTONDOWN )
1721 lphc->wState &= ~CBF_BUTTONDOWN;
1722 CBRepaintButton( lphc );
1726 /***********************************************************************
1727 * COMBO_MouseMove
1729 * Two things to do - track combo button and release capture when
1730 * pointer goes into the listbox.
1732 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1734 POINT pt;
1735 RECT lbRect;
1737 pt.x = LOWORD(lParam);
1738 pt.y = HIWORD(lParam);
1740 if( lphc->wState & CBF_BUTTONDOWN )
1742 BOOL bButton;
1744 bButton = PtInRect(&lphc->buttonRect, pt);
1746 if( !bButton )
1748 lphc->wState &= ~CBF_BUTTONDOWN;
1749 CBRepaintButton( lphc );
1753 GetClientRect( lphc->hWndLBox, &lbRect );
1754 MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 );
1755 if( PtInRect(&lbRect, pt) )
1757 lphc->wState &= ~CBF_CAPTURE;
1758 ReleaseCapture();
1759 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc );
1761 /* hand over pointer tracking */
1762 SendMessageA( lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam );
1767 /***********************************************************************
1768 * ComboWndProc_locked
1770 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1772 static inline LRESULT WINAPI ComboWndProc_locked( WND* pWnd, UINT message,
1773 WPARAM wParam, LPARAM lParam )
1775 if( pWnd ) {
1776 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1777 HWND hwnd = pWnd->hwndSelf;
1779 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1780 pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam );
1782 if( lphc || message == WM_NCCREATE )
1783 switch(message)
1786 /* System messages */
1788 case WM_NCCREATE:
1789 return COMBO_NCCreate(pWnd, lParam);
1790 case WM_NCDESTROY:
1791 COMBO_NCDestroy(lphc);
1792 break;/* -> DefWindowProc */
1794 case WM_CREATE:
1795 return COMBO_Create(lphc, pWnd, lParam);
1797 case WM_PRINTCLIENT:
1798 if (lParam & PRF_ERASEBKGND)
1799 COMBO_EraseBackground(hwnd, lphc, wParam);
1801 /* Fallthrough */
1802 case WM_PAINT:
1803 /* wParam may contain a valid HDC! */
1804 return COMBO_Paint(lphc, wParam);
1805 case WM_ERASEBKGND:
1806 return COMBO_EraseBackground(hwnd, lphc, wParam);
1807 case WM_GETDLGCODE:
1808 return (LRESULT)(DLGC_WANTARROWS | DLGC_WANTCHARS);
1809 case WM_WINDOWPOSCHANGING:
1810 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1811 case WM_SIZE:
1812 if( lphc->hWndLBox &&
1813 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1814 return TRUE;
1815 case WM_SETFONT:
1816 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1817 return TRUE;
1818 case WM_GETFONT:
1819 return (LRESULT)lphc->hFont;
1820 case WM_SETFOCUS:
1821 if( lphc->wState & CBF_EDIT )
1822 SetFocus( lphc->hWndEdit );
1823 else
1824 COMBO_SetFocus( lphc );
1825 return TRUE;
1826 case WM_KILLFOCUS:
1827 #define hwndFocus ((HWND16)wParam)
1828 if( !hwndFocus ||
1829 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1830 COMBO_KillFocus( lphc );
1831 #undef hwndFocus
1832 return TRUE;
1833 case WM_COMMAND:
1834 return COMBO_Command( lphc, wParam, (HWND)lParam );
1835 case WM_GETTEXT:
1836 return COMBO_GetText( lphc, (UINT)wParam, (LPSTR)lParam );
1837 case WM_SETTEXT:
1838 case WM_GETTEXTLENGTH:
1839 case WM_CLEAR:
1840 case WM_CUT:
1841 case WM_PASTE:
1842 case WM_COPY:
1843 if( lphc->wState & CBF_EDIT )
1845 lphc->wState |= CBF_NOEDITNOTIFY;
1847 return SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1849 return CB_ERR;
1850 case WM_DRAWITEM:
1851 case WM_DELETEITEM:
1852 case WM_COMPAREITEM:
1853 case WM_MEASUREITEM:
1854 return COMBO_ItemOp( lphc, message, wParam, lParam );
1855 case WM_ENABLE:
1856 if( lphc->wState & CBF_EDIT )
1857 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1858 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1859 return TRUE;
1860 case WM_SETREDRAW:
1861 if( wParam )
1862 lphc->wState &= ~CBF_NOREDRAW;
1863 else
1864 lphc->wState |= CBF_NOREDRAW;
1866 if( lphc->wState & CBF_EDIT )
1867 SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1868 SendMessageA( lphc->hWndLBox, message, wParam, lParam );
1869 return 0;
1870 case WM_SYSKEYDOWN:
1871 if( KEYDATA_ALT & HIWORD(lParam) )
1872 if( wParam == VK_UP || wParam == VK_DOWN )
1873 COMBO_FlipListbox( lphc, TRUE );
1874 break;/* -> DefWindowProc */
1876 case WM_CHAR:
1877 case WM_KEYDOWN:
1878 if( lphc->wState & CBF_EDIT )
1879 return SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1880 else
1881 return SendMessageA( lphc->hWndLBox, message, wParam, lParam );
1882 case WM_LBUTTONDOWN:
1883 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf );
1884 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1885 return TRUE;
1886 case WM_LBUTTONUP:
1887 COMBO_LButtonUp( lphc, lParam );
1888 return TRUE;
1889 case WM_MOUSEMOVE:
1890 if( lphc->wState & CBF_CAPTURE )
1891 COMBO_MouseMove( lphc, wParam, lParam );
1892 return TRUE;
1893 /* Combo messages */
1895 case CB_ADDSTRING16:
1896 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1897 case CB_ADDSTRING:
1898 return SendMessageA( lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
1899 case CB_INSERTSTRING16:
1900 wParam = (INT)(INT16)wParam;
1901 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1902 case CB_INSERTSTRING:
1903 return SendMessageA( lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1904 case CB_DELETESTRING16:
1905 case CB_DELETESTRING:
1906 return SendMessageA( lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
1907 case CB_SELECTSTRING16:
1908 wParam = (INT)(INT16)wParam;
1909 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1910 case CB_SELECTSTRING:
1911 return COMBO_SelectString( lphc, (INT)wParam, (LPSTR)lParam );
1912 case CB_FINDSTRING16:
1913 wParam = (INT)(INT16)wParam;
1914 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1915 case CB_FINDSTRING:
1916 return SendMessageA( lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
1917 case CB_FINDSTRINGEXACT16:
1918 wParam = (INT)(INT16)wParam;
1919 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1920 case CB_FINDSTRINGEXACT:
1921 return SendMessageA( lphc->hWndLBox, LB_FINDSTRINGEXACT,
1922 wParam, lParam );
1923 case CB_SETITEMHEIGHT16:
1924 wParam = (INT)(INT16)wParam; /* signed integer */
1925 case CB_SETITEMHEIGHT:
1926 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
1927 case CB_GETITEMHEIGHT16:
1928 wParam = (INT)(INT16)wParam;
1929 case CB_GETITEMHEIGHT:
1930 if( (INT)wParam >= 0 ) /* listbox item */
1931 return SendMessageA( lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
1932 return CBGetTextAreaHeight(hwnd, lphc);
1933 case CB_RESETCONTENT16:
1934 case CB_RESETCONTENT:
1935 SendMessageA( lphc->hWndLBox, LB_RESETCONTENT, 0, 0 );
1936 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
1937 return TRUE;
1938 case CB_INITSTORAGE:
1939 return SendMessageA( lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
1940 case CB_GETHORIZONTALEXTENT:
1941 return SendMessageA( lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
1942 case CB_SETHORIZONTALEXTENT:
1943 return SendMessageA( lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
1944 case CB_GETTOPINDEX:
1945 return SendMessageA( lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
1946 case CB_GETLOCALE:
1947 return SendMessageA( lphc->hWndLBox, LB_GETLOCALE, 0, 0);
1948 case CB_SETLOCALE:
1949 return SendMessageA( lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
1950 case CB_GETDROPPEDWIDTH:
1951 if( lphc->droppedWidth )
1952 return lphc->droppedWidth;
1953 return lphc->droppedRect.right - lphc->droppedRect.left;
1954 case CB_SETDROPPEDWIDTH:
1955 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
1956 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
1957 return CB_ERR;
1958 case CB_GETDROPPEDCONTROLRECT16:
1959 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1960 if( lParam )
1962 RECT r;
1963 CBGetDroppedControlRect( lphc, &r );
1964 CONV_RECT32TO16( &r, (LPRECT16)lParam );
1966 return CB_OKAY;
1967 case CB_GETDROPPEDCONTROLRECT:
1968 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
1969 return CB_OKAY;
1970 case CB_GETDROPPEDSTATE16:
1971 case CB_GETDROPPEDSTATE:
1972 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
1973 case CB_DIR16:
1974 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1975 /* fall through */
1976 case CB_DIR:
1977 return COMBO_Directory( lphc, (UINT)wParam,
1978 (LPSTR)lParam, (message == CB_DIR));
1979 case CB_SHOWDROPDOWN16:
1980 case CB_SHOWDROPDOWN:
1981 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1983 if( wParam )
1985 if( !(lphc->wState & CBF_DROPPED) )
1986 CBDropDown( lphc );
1988 else
1989 if( lphc->wState & CBF_DROPPED )
1990 CBRollUp( lphc, FALSE, TRUE );
1992 return TRUE;
1993 case CB_GETCOUNT16:
1994 case CB_GETCOUNT:
1995 return SendMessageA( lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1996 case CB_GETCURSEL16:
1997 case CB_GETCURSEL:
1998 return SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1999 case CB_SETCURSEL16:
2000 wParam = (INT)(INT16)wParam;
2001 case CB_SETCURSEL:
2002 lParam = SendMessageA( lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
2003 if( lphc->wState & CBF_SELCHANGE )
2005 /* no LBN_SELCHANGE in this case, update manually */
2006 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
2007 lphc->wState &= ~CBF_SELCHANGE;
2009 return lParam;
2010 case CB_GETLBTEXT16:
2011 wParam = (INT)(INT16)wParam;
2012 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2013 case CB_GETLBTEXT:
2014 return SendMessageA( lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2015 case CB_GETLBTEXTLEN16:
2016 wParam = (INT)(INT16)wParam;
2017 case CB_GETLBTEXTLEN:
2018 return SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2019 case CB_GETITEMDATA16:
2020 wParam = (INT)(INT16)wParam;
2021 case CB_GETITEMDATA:
2022 return SendMessageA( lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2023 case CB_SETITEMDATA16:
2024 wParam = (INT)(INT16)wParam;
2025 case CB_SETITEMDATA:
2026 return SendMessageA( lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2027 case CB_GETEDITSEL16:
2028 wParam = lParam = 0; /* just in case */
2029 case CB_GETEDITSEL:
2030 if( lphc->wState & CBF_EDIT )
2032 INT a, b;
2034 return SendMessageA( lphc->hWndEdit, EM_GETSEL,
2035 (wParam) ? wParam : (WPARAM)&a,
2036 (lParam) ? lParam : (LPARAM)&b );
2038 return CB_ERR;
2039 case CB_SETEDITSEL16:
2040 case CB_SETEDITSEL:
2041 if( lphc->wState & CBF_EDIT )
2042 return SendMessageA( lphc->hWndEdit, EM_SETSEL,
2043 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2044 return CB_ERR;
2045 case CB_SETEXTENDEDUI16:
2046 case CB_SETEXTENDEDUI:
2047 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2048 return CB_ERR;
2049 if( wParam )
2050 lphc->wState |= CBF_EUI;
2051 else lphc->wState &= ~CBF_EUI;
2052 return CB_OKAY;
2053 case CB_GETEXTENDEDUI16:
2054 case CB_GETEXTENDEDUI:
2055 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2056 case (WM_USER + 0x1B):
2057 WARN("[%04x]: undocumented msg!\n", hwnd );
2059 return DefWindowProcA(hwnd, message, wParam, lParam);
2061 return CB_ERR;
2064 /***********************************************************************
2065 * ComboWndProc
2067 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2068 * window structs.
2070 LRESULT WINAPI ComboWndProc( HWND hwnd, UINT message,
2071 WPARAM wParam, LPARAM lParam )
2073 WND* pWnd = WIN_FindWndPtr(hwnd);
2074 LRESULT retvalue = ComboWndProc_locked(pWnd,message,wParam,lParam);
2077 WIN_ReleaseWndPtr(pWnd);
2078 return retvalue;