Several focus and selection problems in listboxes.
[wine.git] / controls / combo.c
blob672e13a3c74da782cabd9a340d4bdf731b8499cc
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;
494 * Initialize the dropped rect to the size of the client area of the
495 * control and then, force all the areas of the combobox to be
496 * recalculated.
498 GetClientRect( wnd->hwndSelf, &lphc->droppedRect );
500 CBCalcPlacement(wnd->hwndSelf,
501 lphc,
502 &lphc->textRect,
503 &lphc->buttonRect,
504 &lphc->droppedRect );
507 * Adjust the position of the popup listbox if it's necessary
509 if ( CB_GETTYPE(lphc) != CBS_SIMPLE )
511 lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE();
514 * If it's a dropdown, the listbox is offset
516 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
517 lphc->droppedRect.left += COMBO_EDITBUTTONSPACE();
519 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect);
520 ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect.right);
523 /* create listbox popup */
525 lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS) |
526 (lpcs->style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE));
528 if( lphc->dwStyle & CBS_SORT )
529 lbeStyle |= LBS_SORT;
530 if( lphc->dwStyle & CBS_HASSTRINGS )
531 lbeStyle |= LBS_HASSTRINGS;
532 if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT )
533 lbeStyle |= LBS_NOINTEGRALHEIGHT;
534 if( lphc->dwStyle & CBS_DISABLENOSCROLL )
535 lbeStyle |= LBS_DISABLENOSCROLL;
537 if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */
538 lbeStyle |= WS_CHILD | WS_VISIBLE;
539 else /* popup listbox */
540 lbeStyle |= WS_POPUP;
542 /* Dropdown ComboLBox is not a child window and we cannot pass
543 * ID_CB_LISTBOX directly because it will be treated as a menu handle.
545 lphc->hWndLBox = CreateWindowExA(0,
546 clbName,
547 NULL,
548 lbeStyle,
549 lphc->droppedRect.left,
550 lphc->droppedRect.top,
551 lphc->droppedRect.right - lphc->droppedRect.left,
552 lphc->droppedRect.bottom - lphc->droppedRect.top,
553 lphc->self->hwndSelf,
554 (lphc->dwStyle & CBS_DROPDOWN)? (HMENU)0 : (HMENU)ID_CB_LISTBOX,
555 lphc->self->hInstance,
556 (LPVOID)lphc );
559 * The ComboLBox is a strange little beast (when it's not a CBS_SIMPLE)...
560 * It's a popup window but, when you get the window style, you get WS_CHILD.
561 * When created, it's parent is the combobox but, when you ask for it's parent
562 * after that, you're supposed to get the desktop. (see MFC code function
563 * AfxCancelModes)
564 * To achieve this in Wine, we have to create it as a popup and change
565 * it's style to child after the creation.
567 if ( (lphc->hWndLBox!= 0) &&
568 (CB_GETTYPE(lphc) != CBS_SIMPLE) )
570 SetWindowLongA(lphc->hWndLBox,
571 GWL_STYLE,
572 (GetWindowLongA(lphc->hWndLBox, GWL_STYLE) | WS_CHILD) & ~WS_POPUP);
575 if( lphc->hWndLBox )
577 BOOL bEdit = TRUE;
578 lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT;
581 * In Win95 look, the border fo the edit control is
582 * provided by the combobox
584 if (TWEAK_WineLook == WIN31_LOOK)
585 lbeStyle |= WS_BORDER;
587 if( lphc->wState & CBF_EDIT )
589 if( lphc->dwStyle & CBS_OEMCONVERT )
590 lbeStyle |= ES_OEMCONVERT;
591 if( lphc->dwStyle & CBS_AUTOHSCROLL )
592 lbeStyle |= ES_AUTOHSCROLL;
593 if( lphc->dwStyle & CBS_LOWERCASE )
594 lbeStyle |= ES_LOWERCASE;
595 else if( lphc->dwStyle & CBS_UPPERCASE )
596 lbeStyle |= ES_UPPERCASE;
598 lphc->hWndEdit = CreateWindowExA(0,
599 editName,
600 NULL,
601 lbeStyle,
602 lphc->textRect.left, lphc->textRect.top,
603 lphc->textRect.right - lphc->textRect.left,
604 lphc->textRect.bottom - lphc->textRect.top,
605 lphc->self->hwndSelf,
606 (HMENU)ID_CB_EDIT,
607 lphc->self->hInstance,
608 NULL );
610 if( !lphc->hWndEdit )
611 bEdit = FALSE;
614 if( bEdit )
617 * If the combo is a dropdown, we must resize the control to fit only
618 * the text area and button. To do this, we send a dummy resize and the
619 * WM_WINDOWPOSCHANGING message will take care of setting the height for
620 * us.
622 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
624 CBForceDummyResize(lphc);
627 TRACE("init done\n");
628 return wnd->hwndSelf;
630 ERR("edit control failure.\n");
631 } else ERR("listbox failure.\n");
632 } else ERR("no owner for visible combo.\n");
634 /* CreateWindow() will send WM_NCDESTROY to cleanup */
636 return -1;
639 /***********************************************************************
640 * CBPaintButton
642 * Paint combo button (normal, pressed, and disabled states).
644 static void CBPaintButton(
645 LPHEADCOMBO lphc,
646 HDC hdc,
647 RECT rectButton)
649 UINT x, y;
650 BOOL bBool;
651 HDC hMemDC;
652 HBRUSH hPrevBrush;
653 COLORREF oldTextColor, oldBkColor;
655 if( lphc->wState & CBF_NOREDRAW )
656 return;
658 hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE));
661 * Draw the button background
663 PatBlt( hdc,
664 rectButton.left,
665 rectButton.top,
666 rectButton.right-rectButton.left,
667 rectButton.bottom-rectButton.top,
668 PATCOPY );
670 if( (bBool = lphc->wState & CBF_BUTTONDOWN) )
672 DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT );
674 else
676 DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT );
680 * Remove the edge of the button from the rectangle
681 * and calculate the position of the bitmap.
683 InflateRect( &rectButton, -2, -2);
685 x = (rectButton.left + rectButton.right - CBitWidth) >> 1;
686 y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1;
689 hMemDC = CreateCompatibleDC( hdc );
690 SelectObject( hMemDC, hComboBmp );
691 oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) );
692 oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) :
693 RGB(0,0,0) );
694 BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY );
695 SetBkColor( hdc, oldBkColor );
696 SetTextColor( hdc, oldTextColor );
697 DeleteDC( hMemDC );
698 SelectObject( hdc, hPrevBrush );
701 /***********************************************************************
702 * CBPaintText
704 * Paint CBS_DROPDOWNLIST text field / update edit control contents.
706 static void CBPaintText(
707 LPHEADCOMBO lphc,
708 HDC hdc,
709 RECT rectEdit)
711 INT id, size = 0;
712 LPSTR pText = NULL;
714 if( lphc->wState & CBF_NOREDRAW ) return;
716 /* follow Windows combobox that sends a bunch of text
717 * inquiries to its listbox while processing WM_PAINT. */
719 if( (id = SendMessageA(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR )
721 size = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, id, 0);
722 if( (pText = HeapAlloc( GetProcessHeap(), 0, size + 1)) )
724 SendMessageA( lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText );
725 pText[size] = '\0'; /* just in case */
726 } else return;
729 if( lphc->wState & CBF_EDIT )
731 if( CB_HASSTRINGS(lphc) ) SetWindowTextA( lphc->hWndEdit, pText ? pText : "" );
732 if( lphc->wState & CBF_FOCUSED )
733 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1));
735 else /* paint text field ourselves */
737 HBRUSH hPrevBrush = 0;
738 HDC hDC = hdc;
740 if( !hDC )
742 if ((hDC = GetDC(lphc->self->hwndSelf)))
744 HBRUSH hBrush = SendMessageA( lphc->owner,
745 WM_CTLCOLORLISTBOX,
746 hDC, lphc->self->hwndSelf );
747 hPrevBrush = SelectObject( hDC,
748 (hBrush) ? hBrush : GetStockObject(WHITE_BRUSH) );
751 if( hDC )
753 UINT itemState;
754 HFONT hPrevFont = (lphc->hFont) ? SelectObject(hDC, lphc->hFont) : 0;
757 * Give ourselves some space.
759 InflateRect( &rectEdit, -1, -1 );
761 if ( (lphc->wState & CBF_FOCUSED) &&
762 !(lphc->wState & CBF_DROPPED) )
764 /* highlight */
766 FillRect( hDC, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) );
767 SetBkColor( hDC, GetSysColor( COLOR_HIGHLIGHT ) );
768 SetTextColor( hDC, GetSysColor( COLOR_HIGHLIGHTTEXT ) );
769 itemState = ODS_SELECTED | ODS_FOCUS;
771 else
772 itemState = 0;
774 if( CB_OWNERDRAWN(lphc) )
776 DRAWITEMSTRUCT dis;
777 HRGN clipRegion;
780 * Save the current clip region.
781 * To retrieve the clip region, we need to create one "dummy"
782 * clip region.
784 clipRegion = CreateRectRgnIndirect(&rectEdit);
786 if (GetClipRgn(hDC, clipRegion)!=1)
788 DeleteObject(clipRegion);
789 clipRegion=(HRGN)NULL;
792 if ( lphc->self->dwStyle & WS_DISABLED )
793 itemState |= ODS_DISABLED;
795 dis.CtlType = ODT_COMBOBOX;
796 dis.CtlID = lphc->self->wIDmenu;
797 dis.hwndItem = lphc->self->hwndSelf;
798 dis.itemAction = ODA_DRAWENTIRE;
799 dis.itemID = id;
800 dis.itemState = itemState;
801 dis.hDC = hDC;
802 dis.rcItem = rectEdit;
803 dis.itemData = SendMessageA( lphc->hWndLBox, LB_GETITEMDATA,
804 (WPARAM)id, 0 );
807 * Clip the DC and have the parent draw the item.
809 IntersectClipRect(hDC,
810 rectEdit.left, rectEdit.top,
811 rectEdit.right, rectEdit.bottom);
813 SendMessageA(lphc->owner, WM_DRAWITEM,
814 lphc->self->wIDmenu, (LPARAM)&dis );
817 * Reset the clipping region.
819 SelectClipRgn(hDC, clipRegion);
821 else
823 ExtTextOutA( hDC,
824 rectEdit.left + 1,
825 rectEdit.top + 1,
826 ETO_OPAQUE | ETO_CLIPPED,
827 &rectEdit,
828 pText ? pText : "" , size, NULL );
830 if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED))
831 DrawFocusRect( hDC, &rectEdit );
834 if( hPrevFont )
835 SelectObject(hDC, hPrevFont );
837 if( !hdc )
839 if( hPrevBrush )
840 SelectObject( hDC, hPrevBrush );
842 ReleaseDC( lphc->self->hwndSelf, hDC );
846 if (pText)
847 HeapFree( GetProcessHeap(), 0, pText );
850 /***********************************************************************
851 * CBPaintBorder
853 static void CBPaintBorder(
854 HWND hwnd,
855 LPHEADCOMBO lphc,
856 HDC hdc)
858 RECT clientRect;
860 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
862 GetClientRect(hwnd, &clientRect);
864 else
866 CopyRect(&clientRect, &lphc->textRect);
868 InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING());
869 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
872 DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT);
875 /***********************************************************************
876 * COMBO_EraseBackground
878 static LRESULT COMBO_EraseBackground(
879 HWND hwnd,
880 LPHEADCOMBO lphc,
881 HDC hParamDC)
883 HBRUSH hBkgBrush;
884 RECT clientRect;
885 HDC hDC;
887 hDC = (hParamDC) ? hParamDC
888 : GetDC(hwnd);
891 * Calculate the area that we want to erase.
893 if (CB_GETTYPE(lphc) != CBS_SIMPLE)
895 GetClientRect(hwnd, &clientRect);
897 else
899 CopyRect(&clientRect, &lphc->textRect);
901 InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE());
904 hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX,
905 hDC, hwnd);
907 if( !hBkgBrush )
908 hBkgBrush = GetStockObject(WHITE_BRUSH);
910 FillRect(hDC, &clientRect, hBkgBrush);
912 if (!hParamDC)
913 ReleaseDC(hwnd, hDC);
915 return TRUE;
918 /***********************************************************************
919 * COMBO_Paint
921 static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC)
923 PAINTSTRUCT ps;
924 HDC hDC;
926 hDC = (hParamDC) ? hParamDC
927 : BeginPaint( lphc->self->hwndSelf, &ps);
930 if( hDC && !(lphc->wState & CBF_NOREDRAW) )
932 HBRUSH hPrevBrush, hBkgBrush;
934 hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX,
935 hDC, lphc->self->hwndSelf );
937 if( !hBkgBrush )
938 hBkgBrush = GetStockObject(WHITE_BRUSH);
940 hPrevBrush = SelectObject( hDC, hBkgBrush );
943 * In non 3.1 look, there is a sunken border on the combobox
945 if (TWEAK_WineLook != WIN31_LOOK)
947 CBPaintBorder(CB_HWND(lphc), lphc, hDC);
950 if( !IsRectEmpty(&lphc->buttonRect) )
952 CBPaintButton(lphc, hDC, lphc->buttonRect);
955 if( !(lphc->wState & CBF_EDIT) )
958 * The text area has a border only in Win 3.1 look.
960 if (TWEAK_WineLook == WIN31_LOOK)
962 HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) );
964 Rectangle( hDC,
965 lphc->textRect.left, lphc->textRect.top,
966 lphc->textRect.right - 1, lphc->textRect.bottom - 1);
968 SelectObject( hDC, hPrevPen );
971 CBPaintText( lphc, hDC, lphc->textRect);
974 if( hPrevBrush )
975 SelectObject( hDC, hPrevBrush );
978 if( !hParamDC )
979 EndPaint(lphc->self->hwndSelf, &ps);
981 return 0;
984 /***********************************************************************
985 * CBUpdateLBox
987 * Select listbox entry according to the contents of the edit control.
989 static INT CBUpdateLBox( LPHEADCOMBO lphc )
991 INT length, idx, ret;
992 LPSTR pText = NULL;
994 idx = ret = LB_ERR;
995 length = CB_GETEDITTEXTLENGTH( lphc );
997 if( length > 0 )
998 pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1);
1000 TRACE("\t edit text length %i\n", length );
1002 if( pText )
1004 if( length ) GetWindowTextA( lphc->hWndEdit, pText, length + 1);
1005 else pText[0] = '\0';
1006 idx = SendMessageA( lphc->hWndLBox, LB_FINDSTRING,
1007 (WPARAM)(-1), (LPARAM)pText );
1008 if( idx == LB_ERR ) idx = 0; /* select first item */
1009 else ret = idx;
1010 HeapFree( GetProcessHeap(), 0, pText );
1013 /* select entry */
1015 SendMessageA( lphc->hWndLBox, LB_SETCURSEL, (WPARAM)idx, 0 );
1017 if( idx >= 0 )
1019 SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)idx, 0 );
1020 /* probably superfluous but Windows sends this too */
1021 SendMessageA( lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)idx, 0 );
1023 return ret;
1026 /***********************************************************************
1027 * CBUpdateEdit
1029 * Copy a listbox entry to the edit control.
1031 static void CBUpdateEdit( LPHEADCOMBO lphc , INT index )
1033 INT length;
1034 LPSTR pText = NULL;
1036 TRACE("\t %i\n", index );
1038 if( index == -1 )
1040 length = CB_GETEDITTEXTLENGTH( lphc );
1041 if( length )
1043 if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) )
1045 GetWindowTextA( lphc->hWndEdit, pText, length + 1 );
1046 index = SendMessageA( lphc->hWndLBox, LB_FINDSTRING,
1047 (WPARAM)(-1), (LPARAM)pText );
1048 HeapFree( GetProcessHeap(), 0, pText );
1053 if( index >= 0 ) /* got an entry */
1055 length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0);
1056 if( length )
1058 if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) )
1060 SendMessageA( lphc->hWndLBox, LB_GETTEXT,
1061 (WPARAM)index, (LPARAM)pText );
1063 lphc->wState |= CBF_NOEDITNOTIFY;
1065 SendMessageA( lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)pText );
1066 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) );
1067 HeapFree( GetProcessHeap(), 0, pText );
1073 /***********************************************************************
1074 * CBDropDown
1076 * Show listbox popup.
1078 static void CBDropDown( LPHEADCOMBO lphc )
1080 RECT rect;
1081 int nItems = 0;
1082 int i;
1083 int nHeight;
1084 int nDroppedHeight;
1086 TRACE("[%04x]: drop down\n", CB_HWND(lphc));
1088 CB_NOTIFY( lphc, CBN_DROPDOWN );
1090 /* set selection */
1092 lphc->wState |= CBF_DROPPED;
1093 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1095 lphc->droppedIndex = CBUpdateLBox( lphc );
1097 if( !(lphc->wState & CBF_CAPTURE) )
1098 CBUpdateEdit( lphc, lphc->droppedIndex );
1100 else
1102 lphc->droppedIndex = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1104 if( lphc->droppedIndex == LB_ERR )
1105 lphc->droppedIndex = 0;
1107 SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)lphc->droppedIndex, 0 );
1108 SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 );
1111 /* now set popup position */
1112 GetWindowRect( lphc->self->hwndSelf, &rect );
1115 * If it's a dropdown, the listbox is offset
1117 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1118 rect.left += COMBO_EDITBUTTONSPACE();
1120 /* if the dropped height is greater than the total height of the dropped
1121 items list, then force the drop down list height to be the total height
1122 of the items in the dropped list */
1124 nDroppedHeight = lphc->droppedRect.bottom - lphc->droppedRect.top;
1125 nItems = (int)SendMessageA (lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1126 nHeight = COMBO_YBORDERGAP;
1127 for (i = 0; i < nItems; i++)
1129 nHeight += (int)SendMessageA (lphc->hWndLBox, LB_GETITEMHEIGHT, i, 0);
1131 if (nHeight >= nDroppedHeight)
1132 break;
1135 if (nHeight < nDroppedHeight)
1136 nDroppedHeight = nHeight;
1138 SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom,
1139 lphc->droppedRect.right - lphc->droppedRect.left,
1140 nDroppedHeight,
1141 SWP_NOACTIVATE | SWP_NOREDRAW);
1143 if( !(lphc->wState & CBF_NOREDRAW) )
1144 RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE |
1145 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1147 ShowWindow( lphc->hWndLBox, SW_SHOWNA );
1150 /***********************************************************************
1151 * CBRollUp
1153 * Hide listbox popup.
1155 static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton )
1157 HWND hWnd = lphc->self->hwndSelf;
1159 CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL );
1161 if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE )
1164 TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok );
1166 if( lphc->wState & CBF_DROPPED )
1168 RECT rect;
1171 * It seems useful to send the WM_LBUTTONUP with (-1,-1) when cancelling
1172 * and with (0,0) (anywhere in the listbox) when Oking.
1174 SendMessageA( lphc->hWndLBox, WM_LBUTTONUP, 0, ok ? (LPARAM)0 : (LPARAM)(-1) );
1176 lphc->wState &= ~CBF_DROPPED;
1177 ShowWindow( lphc->hWndLBox, SW_HIDE );
1179 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1181 INT index = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1182 CBUpdateEdit( lphc, index );
1183 rect = lphc->buttonRect;
1185 else
1187 if( bButton )
1189 UnionRect( &rect,
1190 &lphc->buttonRect,
1191 &lphc->textRect);
1193 else
1194 rect = lphc->textRect;
1196 bButton = TRUE;
1199 if( bButton && !(lphc->wState & CBF_NOREDRAW) )
1200 RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE |
1201 RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN );
1202 CB_NOTIFY( lphc, CBN_CLOSEUP );
1207 /***********************************************************************
1208 * COMBO_FlipListbox
1210 * Used by the ComboLBox to show/hide itself in response to VK_F4, etc...
1212 BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL bRedrawButton )
1214 if( lphc->wState & CBF_DROPPED )
1216 CBRollUp( lphc, TRUE, bRedrawButton );
1217 return FALSE;
1220 CBDropDown( lphc );
1221 return TRUE;
1224 /***********************************************************************
1225 * COMBO_GetLBWindow
1227 * Edit control helper.
1229 HWND COMBO_GetLBWindow( WND* pWnd )
1231 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1232 if( lphc ) return lphc->hWndLBox;
1233 return 0;
1237 /***********************************************************************
1238 * CBRepaintButton
1240 static void CBRepaintButton( LPHEADCOMBO lphc )
1242 InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE);
1243 UpdateWindow(CB_HWND(lphc));
1246 /***********************************************************************
1247 * COMBO_SetFocus
1249 static void COMBO_SetFocus( LPHEADCOMBO lphc )
1251 if( !(lphc->wState & CBF_FOCUSED) )
1253 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1254 SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 );
1256 if( lphc->wState & CBF_EDIT )
1257 SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) );
1258 lphc->wState |= CBF_FOCUSED;
1259 if( !(lphc->wState & CBF_EDIT) )
1261 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1264 CB_NOTIFY( lphc, CBN_SETFOCUS );
1268 /***********************************************************************
1269 * COMBO_KillFocus
1271 static void COMBO_KillFocus( LPHEADCOMBO lphc )
1273 HWND hWnd = lphc->self->hwndSelf;
1275 if( lphc->wState & CBF_FOCUSED )
1277 SendMessageA( hWnd, WM_LBUTTONUP, 0, (LPARAM)(-1) );
1279 CBRollUp( lphc, FALSE, TRUE );
1280 if( IsWindow( hWnd ) )
1282 if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST )
1283 SendMessageA( lphc->hWndLBox, LB_CARETOFF, 0, 0 );
1285 lphc->wState &= ~CBF_FOCUSED;
1287 /* redraw text */
1288 if( lphc->wState & CBF_EDIT )
1289 SendMessageA( lphc->hWndEdit, EM_SETSEL, (WPARAM)(-1), 0 );
1290 else
1292 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1295 CB_NOTIFY( lphc, CBN_KILLFOCUS );
1300 /***********************************************************************
1301 * COMBO_Command
1303 static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd )
1305 if ( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd )
1307 /* ">> 8" makes gcc generate jump-table instead of cmp ladder */
1309 switch( HIWORD(wParam) >> 8 )
1311 case (EN_SETFOCUS >> 8):
1313 TRACE("[%04x]: edit [%04x] got focus\n",
1314 CB_HWND(lphc), lphc->hWndEdit );
1316 if( !(lphc->wState & CBF_FOCUSED) ) COMBO_SetFocus( lphc );
1317 break;
1319 case (EN_KILLFOCUS >> 8):
1321 TRACE("[%04x]: edit [%04x] lost focus\n",
1322 CB_HWND(lphc), lphc->hWndEdit );
1324 /* NOTE: it seems that Windows' edit control sends an
1325 * undocumented message WM_USER + 0x1B instead of this
1326 * notification (only when it happens to be a part of
1327 * the combo). ?? - AK.
1330 COMBO_KillFocus( lphc );
1331 break;
1334 case (EN_CHANGE >> 8):
1336 * In some circumstances (when the selection of the combobox
1337 * is changed for example) we don't wans the EN_CHANGE notification
1338 * to be forwarded to the parent of the combobox. This code
1339 * checks a flag that is set in these occasions and ignores the
1340 * notification.
1342 if (lphc->wState & CBF_NOEDITNOTIFY)
1344 lphc->wState &= ~CBF_NOEDITNOTIFY;
1346 else
1348 CB_NOTIFY( lphc, CBN_EDITCHANGE );
1351 CBUpdateLBox( lphc );
1352 break;
1354 case (EN_UPDATE >> 8):
1355 CB_NOTIFY( lphc, CBN_EDITUPDATE );
1356 break;
1358 case (EN_ERRSPACE >> 8):
1359 CB_NOTIFY( lphc, CBN_ERRSPACE );
1362 else if( lphc->hWndLBox == hWnd )
1364 switch( HIWORD(wParam) )
1366 case LBN_ERRSPACE:
1367 CB_NOTIFY( lphc, CBN_ERRSPACE );
1368 break;
1370 case LBN_DBLCLK:
1371 CB_NOTIFY( lphc, CBN_DBLCLK );
1372 break;
1374 case LBN_SELCHANGE:
1375 case LBN_SELCANCEL:
1377 TRACE("[%04x]: lbox selection change [%04x]\n",
1378 CB_HWND(lphc), lphc->wState );
1380 /* do not roll up if selection is being tracked
1381 * by arrowkeys in the dropdown listbox */
1383 if( (lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP) )
1384 CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE );
1385 else lphc->wState &= ~CBF_NOROLLUP;
1387 CB_NOTIFY( lphc, CBN_SELCHANGE );
1388 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1389 /* fall through */
1391 case LBN_SETFOCUS:
1392 case LBN_KILLFOCUS:
1393 /* nothing to do here since ComboLBox always resets the focus to its
1394 * combo/edit counterpart */
1395 break;
1398 return 0;
1401 /***********************************************************************
1402 * COMBO_ItemOp
1404 * Fixup an ownerdrawn item operation and pass it up to the combobox owner.
1406 static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg,
1407 WPARAM wParam, LPARAM lParam )
1409 HWND hWnd = lphc->self->hwndSelf;
1411 TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg );
1413 #define lpIS ((LPDELETEITEMSTRUCT)lParam)
1415 /* two first items are the same in all 4 structs */
1416 lpIS->CtlType = ODT_COMBOBOX;
1417 lpIS->CtlID = lphc->self->wIDmenu;
1419 switch( msg ) /* patch window handle */
1421 case WM_DELETEITEM:
1422 lpIS->hwndItem = hWnd;
1423 #undef lpIS
1424 break;
1425 case WM_DRAWITEM:
1426 #define lpIS ((LPDRAWITEMSTRUCT)lParam)
1427 lpIS->hwndItem = hWnd;
1428 #undef lpIS
1429 break;
1430 case WM_COMPAREITEM:
1431 #define lpIS ((LPCOMPAREITEMSTRUCT)lParam)
1432 lpIS->hwndItem = hWnd;
1433 #undef lpIS
1434 break;
1437 return SendMessageA( lphc->owner, msg, lphc->self->wIDmenu, lParam );
1440 /***********************************************************************
1441 * COMBO_GetText
1443 static LRESULT COMBO_GetText( LPHEADCOMBO lphc, UINT N, LPSTR lpText)
1445 if( lphc->wState & CBF_EDIT )
1446 return SendMessageA( lphc->hWndEdit, WM_GETTEXT,
1447 (WPARAM)N, (LPARAM)lpText );
1449 /* get it from the listbox */
1451 if( lphc->hWndLBox )
1453 INT idx = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 );
1454 if( idx != LB_ERR )
1456 LPSTR lpBuffer;
1457 INT length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN,
1458 (WPARAM)idx, 0 );
1460 /* 'length' is without the terminating character */
1461 if( length >= N )
1462 lpBuffer = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1 );
1463 else
1464 lpBuffer = lpText;
1466 if( lpBuffer )
1468 INT n = SendMessageA( lphc->hWndLBox, LB_GETTEXT,
1469 (WPARAM)idx, (LPARAM)lpBuffer );
1471 /* truncate if buffer is too short */
1473 if( length >= N )
1475 if (N && lpText) {
1476 if( n != LB_ERR ) memcpy( lpText, lpBuffer, (N>n) ? n+1 : N-1 );
1477 lpText[N - 1] = '\0';
1479 HeapFree( GetProcessHeap(), 0, lpBuffer );
1481 return (LRESULT)n;
1485 return 0;
1489 /***********************************************************************
1490 * CBResetPos
1492 * This function sets window positions according to the updated
1493 * component placement struct.
1495 static void CBResetPos(
1496 LPHEADCOMBO lphc,
1497 LPRECT rectEdit,
1498 LPRECT rectLB,
1499 BOOL bRedraw)
1501 BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE);
1503 /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of
1504 * sizing messages */
1506 if( lphc->wState & CBF_EDIT )
1507 SetWindowPos( lphc->hWndEdit, 0,
1508 rectEdit->left, rectEdit->top,
1509 rectEdit->right - rectEdit->left,
1510 rectEdit->bottom - rectEdit->top,
1511 SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) );
1513 SetWindowPos( lphc->hWndLBox, 0,
1514 rectLB->left, rectLB->top,
1515 rectLB->right - rectLB->left,
1516 rectLB->bottom - rectLB->top,
1517 SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) );
1519 if( bDrop )
1521 if( lphc->wState & CBF_DROPPED )
1523 lphc->wState &= ~CBF_DROPPED;
1524 ShowWindow( lphc->hWndLBox, SW_HIDE );
1527 if( bRedraw && !(lphc->wState & CBF_NOREDRAW) )
1528 RedrawWindow( lphc->self->hwndSelf, NULL, 0,
1529 RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW );
1534 /***********************************************************************
1535 * COMBO_Size
1537 static void COMBO_Size( LPHEADCOMBO lphc )
1539 CBCalcPlacement(lphc->self->hwndSelf,
1540 lphc,
1541 &lphc->textRect,
1542 &lphc->buttonRect,
1543 &lphc->droppedRect);
1545 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1549 /***********************************************************************
1550 * COMBO_Font
1552 static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw )
1555 * Set the font
1557 lphc->hFont = hFont;
1560 * Propagate to owned windows.
1562 if( lphc->wState & CBF_EDIT )
1563 SendMessageA( lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw );
1564 SendMessageA( lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw );
1567 * Redo the layout of the control.
1569 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1571 CBCalcPlacement(lphc->self->hwndSelf,
1572 lphc,
1573 &lphc->textRect,
1574 &lphc->buttonRect,
1575 &lphc->droppedRect);
1577 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1579 else
1581 CBForceDummyResize(lphc);
1586 /***********************************************************************
1587 * COMBO_SetItemHeight
1589 static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height )
1591 LRESULT lRet = CB_ERR;
1593 if( index == -1 ) /* set text field height */
1595 if( height < 32768 )
1597 lphc->editHeight = height;
1600 * Redo the layout of the control.
1602 if ( CB_GETTYPE(lphc) == CBS_SIMPLE)
1604 CBCalcPlacement(lphc->self->hwndSelf,
1605 lphc,
1606 &lphc->textRect,
1607 &lphc->buttonRect,
1608 &lphc->droppedRect);
1610 CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE );
1612 else
1614 CBForceDummyResize(lphc);
1617 lRet = height;
1620 else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */
1621 lRet = SendMessageA( lphc->hWndLBox, LB_SETITEMHEIGHT,
1622 (WPARAM)index, (LPARAM)height );
1623 return lRet;
1626 /***********************************************************************
1627 * COMBO_SelectString
1629 static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPCSTR pText )
1631 INT index = SendMessageA( lphc->hWndLBox, LB_SELECTSTRING,
1632 (WPARAM)start, (LPARAM)pText );
1633 if( index >= 0 )
1635 if( lphc->wState & CBF_EDIT )
1636 CBUpdateEdit( lphc, index );
1637 else
1639 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1642 return (LRESULT)index;
1645 /***********************************************************************
1646 * COMBO_LButtonDown
1648 static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam )
1650 POINT pt;
1651 BOOL bButton;
1652 HWND hWnd = lphc->self->hwndSelf;
1654 pt.x = LOWORD(lParam);
1655 pt.y = HIWORD(lParam);
1656 bButton = PtInRect(&lphc->buttonRect, pt);
1658 if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) ||
1659 (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) )
1661 lphc->wState |= CBF_BUTTONDOWN;
1662 if( lphc->wState & CBF_DROPPED )
1664 /* got a click to cancel selection */
1666 lphc->wState &= ~CBF_BUTTONDOWN;
1667 CBRollUp( lphc, TRUE, FALSE );
1668 if( !IsWindow( hWnd ) ) return;
1670 if( lphc->wState & CBF_CAPTURE )
1672 lphc->wState &= ~CBF_CAPTURE;
1673 ReleaseCapture();
1676 else
1678 /* drop down the listbox and start tracking */
1680 lphc->wState |= CBF_CAPTURE;
1681 CBDropDown( lphc );
1682 SetCapture( hWnd );
1684 if( bButton ) CBRepaintButton( lphc );
1688 /***********************************************************************
1689 * COMBO_LButtonUp
1691 * Release capture and stop tracking if needed.
1693 static void COMBO_LButtonUp( LPHEADCOMBO lphc, LPARAM lParam )
1695 if( lphc->wState & CBF_CAPTURE )
1697 lphc->wState &= ~CBF_CAPTURE;
1698 if( CB_GETTYPE(lphc) == CBS_DROPDOWN )
1700 INT index = CBUpdateLBox( lphc );
1701 CBUpdateEdit( lphc, index );
1703 ReleaseCapture();
1706 if( lphc->wState & CBF_BUTTONDOWN )
1708 lphc->wState &= ~CBF_BUTTONDOWN;
1709 CBRepaintButton( lphc );
1713 /***********************************************************************
1714 * COMBO_MouseMove
1716 * Two things to do - track combo button and release capture when
1717 * pointer goes into the listbox.
1719 static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam )
1721 POINT pt;
1722 RECT lbRect;
1724 pt.x = LOWORD(lParam);
1725 pt.y = HIWORD(lParam);
1727 if( lphc->wState & CBF_BUTTONDOWN )
1729 BOOL bButton;
1731 bButton = PtInRect(&lphc->buttonRect, pt);
1733 if( !bButton )
1735 lphc->wState &= ~CBF_BUTTONDOWN;
1736 CBRepaintButton( lphc );
1740 GetClientRect( lphc->hWndLBox, &lbRect );
1741 MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 );
1742 if( PtInRect(&lbRect, pt) )
1744 lphc->wState &= ~CBF_CAPTURE;
1745 ReleaseCapture();
1746 if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc );
1748 /* hand over pointer tracking */
1749 SendMessageA( lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam );
1754 /***********************************************************************
1755 * ComboWndProc_locked
1757 * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm
1759 static inline LRESULT WINAPI ComboWndProc_locked( WND* pWnd, UINT message,
1760 WPARAM wParam, LPARAM lParam )
1762 if( pWnd ) {
1763 LPHEADCOMBO lphc = CB_GETPTR(pWnd);
1764 HWND hwnd = pWnd->hwndSelf;
1766 TRACE("[%04x]: msg %s wp %08x lp %08lx\n",
1767 pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam );
1769 if( lphc || message == WM_NCCREATE )
1770 switch(message)
1773 /* System messages */
1775 case WM_NCCREATE:
1776 return COMBO_NCCreate(pWnd, lParam);
1777 case WM_NCDESTROY:
1778 COMBO_NCDestroy(lphc);
1779 break;/* -> DefWindowProc */
1781 case WM_CREATE:
1782 return COMBO_Create(lphc, pWnd, lParam);
1784 case WM_PRINTCLIENT:
1785 if (lParam & PRF_ERASEBKGND)
1786 COMBO_EraseBackground(hwnd, lphc, wParam);
1788 /* Fallthrough */
1789 case WM_PAINT:
1790 /* wParam may contain a valid HDC! */
1791 return COMBO_Paint(lphc, wParam);
1792 case WM_ERASEBKGND:
1793 return COMBO_EraseBackground(hwnd, lphc, wParam);
1794 case WM_GETDLGCODE:
1795 return (LRESULT)(DLGC_WANTARROWS | DLGC_WANTCHARS);
1796 case WM_WINDOWPOSCHANGING:
1797 return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam);
1798 case WM_SIZE:
1799 if( lphc->hWndLBox &&
1800 !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc );
1801 return TRUE;
1802 case WM_SETFONT:
1803 COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam );
1804 return TRUE;
1805 case WM_GETFONT:
1806 return (LRESULT)lphc->hFont;
1807 case WM_SETFOCUS:
1808 if( lphc->wState & CBF_EDIT )
1809 SetFocus( lphc->hWndEdit );
1810 else
1811 COMBO_SetFocus( lphc );
1812 return TRUE;
1813 case WM_KILLFOCUS:
1814 #define hwndFocus ((HWND16)wParam)
1815 if( !hwndFocus ||
1816 (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox ))
1817 COMBO_KillFocus( lphc );
1818 #undef hwndFocus
1819 return TRUE;
1820 case WM_COMMAND:
1821 return COMBO_Command( lphc, wParam, (HWND)lParam );
1822 case WM_GETTEXT:
1823 return COMBO_GetText( lphc, (UINT)wParam, (LPSTR)lParam );
1824 case WM_SETTEXT:
1825 case WM_GETTEXTLENGTH:
1826 case WM_CLEAR:
1827 case WM_CUT:
1828 case WM_PASTE:
1829 case WM_COPY:
1830 if( lphc->wState & CBF_EDIT )
1832 lphc->wState |= CBF_NOEDITNOTIFY;
1834 return SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1836 return CB_ERR;
1837 case WM_DRAWITEM:
1838 case WM_DELETEITEM:
1839 case WM_COMPAREITEM:
1840 case WM_MEASUREITEM:
1841 return COMBO_ItemOp( lphc, message, wParam, lParam );
1842 case WM_ENABLE:
1843 if( lphc->wState & CBF_EDIT )
1844 EnableWindow( lphc->hWndEdit, (BOOL)wParam );
1845 EnableWindow( lphc->hWndLBox, (BOOL)wParam );
1846 return TRUE;
1847 case WM_SETREDRAW:
1848 if( wParam )
1849 lphc->wState &= ~CBF_NOREDRAW;
1850 else
1851 lphc->wState |= CBF_NOREDRAW;
1853 if( lphc->wState & CBF_EDIT )
1854 SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1855 SendMessageA( lphc->hWndLBox, message, wParam, lParam );
1856 return 0;
1857 case WM_SYSKEYDOWN:
1858 if( KEYDATA_ALT & HIWORD(lParam) )
1859 if( wParam == VK_UP || wParam == VK_DOWN )
1860 COMBO_FlipListbox( lphc, TRUE );
1861 break;/* -> DefWindowProc */
1863 case WM_CHAR:
1864 case WM_KEYDOWN:
1865 if( lphc->wState & CBF_EDIT )
1866 return SendMessageA( lphc->hWndEdit, message, wParam, lParam );
1867 else
1868 return SendMessageA( lphc->hWndLBox, message, wParam, lParam );
1869 case WM_LBUTTONDOWN:
1870 if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf );
1871 if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam );
1872 return TRUE;
1873 case WM_LBUTTONUP:
1874 COMBO_LButtonUp( lphc, lParam );
1875 return TRUE;
1876 case WM_MOUSEMOVE:
1877 if( lphc->wState & CBF_CAPTURE )
1878 COMBO_MouseMove( lphc, wParam, lParam );
1879 return TRUE;
1880 /* Combo messages */
1882 case CB_ADDSTRING16:
1883 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1884 case CB_ADDSTRING:
1885 return SendMessageA( lphc->hWndLBox, LB_ADDSTRING, 0, lParam);
1886 case CB_INSERTSTRING16:
1887 wParam = (INT)(INT16)wParam;
1888 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1889 case CB_INSERTSTRING:
1890 return SendMessageA( lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam);
1891 case CB_DELETESTRING16:
1892 case CB_DELETESTRING:
1893 return SendMessageA( lphc->hWndLBox, LB_DELETESTRING, wParam, 0);
1894 case CB_SELECTSTRING16:
1895 wParam = (INT)(INT16)wParam;
1896 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1897 case CB_SELECTSTRING:
1898 return COMBO_SelectString( lphc, (INT)wParam, (LPSTR)lParam );
1899 case CB_FINDSTRING16:
1900 wParam = (INT)(INT16)wParam;
1901 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1902 case CB_FINDSTRING:
1903 return SendMessageA( lphc->hWndLBox, LB_FINDSTRING, wParam, lParam);
1904 case CB_FINDSTRINGEXACT16:
1905 wParam = (INT)(INT16)wParam;
1906 if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1907 case CB_FINDSTRINGEXACT:
1908 return SendMessageA( lphc->hWndLBox, LB_FINDSTRINGEXACT,
1909 wParam, lParam );
1910 case CB_SETITEMHEIGHT16:
1911 wParam = (INT)(INT16)wParam; /* signed integer */
1912 case CB_SETITEMHEIGHT:
1913 return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam);
1914 case CB_GETITEMHEIGHT16:
1915 wParam = (INT)(INT16)wParam;
1916 case CB_GETITEMHEIGHT:
1917 if( (INT)wParam >= 0 ) /* listbox item */
1918 return SendMessageA( lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0);
1919 return CBGetTextAreaHeight(hwnd, lphc);
1920 case CB_RESETCONTENT16:
1921 case CB_RESETCONTENT:
1922 SendMessageA( lphc->hWndLBox, LB_RESETCONTENT, 0, 0 );
1923 InvalidateRect(CB_HWND(lphc), NULL, TRUE);
1924 return TRUE;
1925 case CB_INITSTORAGE:
1926 return SendMessageA( lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam);
1927 case CB_GETHORIZONTALEXTENT:
1928 return SendMessageA( lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0);
1929 case CB_SETHORIZONTALEXTENT:
1930 return SendMessageA( lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0);
1931 case CB_GETTOPINDEX:
1932 return SendMessageA( lphc->hWndLBox, LB_GETTOPINDEX, 0, 0);
1933 case CB_GETLOCALE:
1934 return SendMessageA( lphc->hWndLBox, LB_GETLOCALE, 0, 0);
1935 case CB_SETLOCALE:
1936 return SendMessageA( lphc->hWndLBox, LB_SETLOCALE, wParam, 0);
1937 case CB_GETDROPPEDWIDTH:
1938 if( lphc->droppedWidth )
1939 return lphc->droppedWidth;
1940 return lphc->droppedRect.right - lphc->droppedRect.left;
1941 case CB_SETDROPPEDWIDTH:
1942 if( (CB_GETTYPE(lphc) != CBS_SIMPLE) &&
1943 (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam;
1944 return CB_ERR;
1945 case CB_GETDROPPEDCONTROLRECT16:
1946 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1947 if( lParam )
1949 RECT r;
1950 CBGetDroppedControlRect( lphc, &r );
1951 CONV_RECT32TO16( &r, (LPRECT16)lParam );
1953 return CB_OKAY;
1954 case CB_GETDROPPEDCONTROLRECT:
1955 if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam );
1956 return CB_OKAY;
1957 case CB_GETDROPPEDSTATE16:
1958 case CB_GETDROPPEDSTATE:
1959 return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE;
1960 case CB_DIR16:
1961 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
1962 /* fall through */
1963 case CB_DIR:
1964 return COMBO_Directory( lphc, (UINT)wParam,
1965 (LPSTR)lParam, (message == CB_DIR));
1966 case CB_SHOWDROPDOWN16:
1967 case CB_SHOWDROPDOWN:
1968 if( CB_GETTYPE(lphc) != CBS_SIMPLE )
1970 if( wParam )
1972 if( !(lphc->wState & CBF_DROPPED) )
1973 CBDropDown( lphc );
1975 else
1976 if( lphc->wState & CBF_DROPPED )
1977 CBRollUp( lphc, FALSE, TRUE );
1979 return TRUE;
1980 case CB_GETCOUNT16:
1981 case CB_GETCOUNT:
1982 return SendMessageA( lphc->hWndLBox, LB_GETCOUNT, 0, 0);
1983 case CB_GETCURSEL16:
1984 case CB_GETCURSEL:
1985 return SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0);
1986 case CB_SETCURSEL16:
1987 wParam = (INT)(INT16)wParam;
1988 case CB_SETCURSEL:
1989 lParam = SendMessageA( lphc->hWndLBox, LB_SETCURSEL, wParam, 0);
1990 if( lphc->wState & CBF_SELCHANGE )
1992 /* no LBN_SELCHANGE in this case, update manually */
1993 InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE);
1994 lphc->wState &= ~CBF_SELCHANGE;
1996 return lParam;
1997 case CB_GETLBTEXT16:
1998 wParam = (INT)(INT16)wParam;
1999 lParam = (LPARAM)PTR_SEG_TO_LIN(lParam);
2000 case CB_GETLBTEXT:
2001 return SendMessageA( lphc->hWndLBox, LB_GETTEXT, wParam, lParam);
2002 case CB_GETLBTEXTLEN16:
2003 wParam = (INT)(INT16)wParam;
2004 case CB_GETLBTEXTLEN:
2005 return SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0);
2006 case CB_GETITEMDATA16:
2007 wParam = (INT)(INT16)wParam;
2008 case CB_GETITEMDATA:
2009 return SendMessageA( lphc->hWndLBox, LB_GETITEMDATA, wParam, 0);
2010 case CB_SETITEMDATA16:
2011 wParam = (INT)(INT16)wParam;
2012 case CB_SETITEMDATA:
2013 return SendMessageA( lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam);
2014 case CB_GETEDITSEL16:
2015 wParam = lParam = 0; /* just in case */
2016 case CB_GETEDITSEL:
2017 if( lphc->wState & CBF_EDIT )
2019 INT a, b;
2021 return SendMessageA( lphc->hWndEdit, EM_GETSEL,
2022 (wParam) ? wParam : (WPARAM)&a,
2023 (lParam) ? lParam : (LPARAM)&b );
2025 return CB_ERR;
2026 case CB_SETEDITSEL16:
2027 case CB_SETEDITSEL:
2028 if( lphc->wState & CBF_EDIT )
2029 return SendMessageA( lphc->hWndEdit, EM_SETSEL,
2030 (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) );
2031 return CB_ERR;
2032 case CB_SETEXTENDEDUI16:
2033 case CB_SETEXTENDEDUI:
2034 if( CB_GETTYPE(lphc) == CBS_SIMPLE )
2035 return CB_ERR;
2036 if( wParam )
2037 lphc->wState |= CBF_EUI;
2038 else lphc->wState &= ~CBF_EUI;
2039 return CB_OKAY;
2040 case CB_GETEXTENDEDUI16:
2041 case CB_GETEXTENDEDUI:
2042 return (lphc->wState & CBF_EUI) ? TRUE : FALSE;
2043 case (WM_USER + 0x1B):
2044 WARN("[%04x]: undocumented msg!\n", hwnd );
2046 return DefWindowProcA(hwnd, message, wParam, lParam);
2048 return CB_ERR;
2051 /***********************************************************************
2052 * ComboWndProc
2054 * This is just a wrapper for the real ComboWndProc which locks/unlocks
2055 * window structs.
2057 LRESULT WINAPI ComboWndProc( HWND hwnd, UINT message,
2058 WPARAM wParam, LPARAM lParam )
2060 WND* pWnd = WIN_FindWndPtr(hwnd);
2061 LRESULT retvalue = ComboWndProc_locked(pWnd,message,wParam,lParam);
2064 WIN_ReleaseWndPtr(pWnd);
2065 return retvalue;