/* * Combo controls * * Copyright 1997 Alex Korobka * * FIXME: roll up in Netscape 3.01. */ #include #include "winuser.h" #include "wine/winuser16.h" #include "sysmetrics.h" #include "win.h" #include "spy.h" #include "user.h" #include "heap.h" #include "combo.h" #include "drive.h" #include "debugtools.h" #include "tweak.h" DEFAULT_DEBUG_CHANNEL(combo) /* bits in the dwKeyData */ #define KEYDATA_ALT 0x2000 #define KEYDATA_PREVSTATE 0x4000 /* * Additional combo box definitions */ #define CB_GETPTR( wnd ) (*(LPHEADCOMBO*)((wnd)->wExtra)) #define CB_NOTIFY( lphc, code ) \ (SendMessageA( (lphc)->owner, WM_COMMAND, \ MAKEWPARAM((lphc)->self->wIDmenu, (code)), (lphc)->self->hwndSelf)) #define CB_GETEDITTEXTLENGTH( lphc ) \ (SendMessageA( (lphc)->hWndEdit, WM_GETTEXTLENGTH, 0, 0 )) /* * Drawing globals */ static HBITMAP hComboBmp = 0; static UINT CBitHeight, CBitWidth; /* * Look and feel dependant "constants" */ #define COMBO_XBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 ) #define COMBO_YBORDERSIZE() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 2 ) #define COMBO_EDITBUTTONSPACE() ( (TWEAK_WineLook == WIN31_LOOK) ? 8 : 0 ) #define EDIT_CONTROL_PADDING() ( (TWEAK_WineLook == WIN31_LOOK) ? 0 : 1 ) /*********************************************************************** * COMBO_Init * * Load combo button bitmap. */ static BOOL COMBO_Init() { HDC hDC; if( hComboBmp ) return TRUE; if( (hDC = CreateCompatibleDC(0)) ) { BOOL bRet = FALSE; if( (hComboBmp = LoadBitmapA(0, MAKEINTRESOURCEA(OBM_COMBO))) ) { BITMAP bm; HBITMAP hPrevB; RECT r; GetObjectA( hComboBmp, sizeof(bm), &bm ); CBitHeight = bm.bmHeight; CBitWidth = bm.bmWidth; TRACE("combo bitmap [%i,%i]\n", CBitWidth, CBitHeight ); hPrevB = SelectObject16( hDC, hComboBmp); SetRect( &r, 0, 0, CBitWidth, CBitHeight ); InvertRect( hDC, &r ); SelectObject( hDC, hPrevB ); bRet = TRUE; } DeleteDC( hDC ); return bRet; } return FALSE; } /*********************************************************************** * COMBO_NCCreate */ static LRESULT COMBO_NCCreate(WND* wnd, LPARAM lParam) { LPHEADCOMBO lphc; if ( wnd && COMBO_Init() && (lphc = HeapAlloc(GetProcessHeap(), 0, sizeof(HEADCOMBO))) ) { LPCREATESTRUCTA lpcs = (CREATESTRUCTA*)lParam; memset( lphc, 0, sizeof(HEADCOMBO) ); *(LPHEADCOMBO*)wnd->wExtra = lphc; /* some braindead apps do try to use scrollbar/border flags */ lphc->dwStyle = (lpcs->style & ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL)); wnd->dwStyle &= ~(WS_BORDER | WS_HSCROLL | WS_VSCROLL); /* * We also have to remove the client edge style to make sure * we don't end-up with a non client area. */ wnd->dwExStyle &= ~(WS_EX_CLIENTEDGE); if( !(lpcs->style & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) lphc->dwStyle |= CBS_HASSTRINGS; if( !(wnd->dwExStyle & WS_EX_NOPARENTNOTIFY) ) lphc->wState |= CBF_NOTIFY; TRACE("[0x%08x], style = %08x\n", (UINT)lphc, lphc->dwStyle ); return (LRESULT)(UINT)wnd->hwndSelf; } return (LRESULT)FALSE; } /*********************************************************************** * COMBO_NCDestroy */ static LRESULT COMBO_NCDestroy( LPHEADCOMBO lphc ) { if( lphc ) { WND* wnd = lphc->self; TRACE("[%04x]: freeing storage\n", CB_HWND(lphc)); if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && lphc->hWndLBox ) DestroyWindow( lphc->hWndLBox ); HeapFree( GetProcessHeap(), 0, lphc ); wnd->wExtra[0] = 0; } return 0; } /*********************************************************************** * CBForceDummyResize * * The dummy resize is used for listboxes that have a popup to trigger * a re-arranging of the contents of the combobox and the recalculation * of the size of the "real" control window. */ static void CBForceDummyResize( LPHEADCOMBO lphc) { RECT windowRect; GetWindowRect(CB_HWND(lphc), &windowRect); /* * We have to be careful, resizing a combobox also has the meaning that the * dropped rect will be resized. In this case, we want to trigger a resize * to recalculate layout but we don't want to change the dropped rectangle * So, we add the size of the dropped rectangle to the size of the control. * this will cancel-out in the processing of the WM_WINDOWPOSCHANGING * message. */ SetWindowPos( CB_HWND(lphc), (HWND)NULL, 0, 0, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top + lphc->droppedRect.bottom - lphc->droppedRect.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE ); } /*********************************************************************** * CBGetTextAreaHeight * * This method will calculate the height of the text area of the * combobox. * The height of the text area is set in two ways. * It can be set explicitely through a combobox message of through a * WM_MEASUREITEM callback. * If this is not the case, the height is set to 13 dialog units. * This height was determined through experimentation. */ static INT CBGetTextAreaHeight( HWND hwnd, LPHEADCOMBO lphc) { INT iTextItemHeight; if( lphc->editHeight ) /* explicitly set height */ { iTextItemHeight = lphc->editHeight; } else { TEXTMETRICA tm; HDC hDC = GetDC(hwnd); HFONT hPrevFont = 0; INT baseUnitY; if (lphc->hFont) hPrevFont = SelectObject( hDC, lphc->hFont ); GetTextMetricsA(hDC, &tm); baseUnitY = tm.tmHeight; if( hPrevFont ) SelectObject( hDC, hPrevFont ); ReleaseDC(hwnd, hDC); iTextItemHeight = ((13 * baseUnitY) / 8); /* * This "formula" calculates the height of the complete control. * To calculate the height of the text area, we have to remove the * borders. */ iTextItemHeight -= 2*COMBO_YBORDERSIZE(); } /* * Check the ownerdraw case if we haven't asked the parent the size * of the item yet. */ if ( CB_OWNERDRAWN(lphc) && (lphc->wState & CBF_MEASUREITEM) ) { MEASUREITEMSTRUCT measureItem; RECT clientRect; INT originalItemHeight = iTextItemHeight; /* * We use the client rect for the width of the item. */ GetClientRect(hwnd, &clientRect); lphc->wState &= ~CBF_MEASUREITEM; /* * Send a first one to measure the size of the text area */ measureItem.CtlType = ODT_COMBOBOX; measureItem.CtlID = lphc->self->wIDmenu; measureItem.itemID = -1; measureItem.itemWidth = clientRect.right; measureItem.itemHeight = iTextItemHeight - 6; /* ownerdrawn cb is taller */ measureItem.itemData = 0; SendMessageA(lphc->owner, WM_MEASUREITEM, (WPARAM)measureItem.CtlID, (LPARAM)&measureItem); iTextItemHeight = 6 + measureItem.itemHeight; /* * Send a second one in the case of a fixed ownerdraw list to calculate the * size of the list items. (we basically do this on behalf of the listbox) */ if (lphc->dwStyle & CBS_OWNERDRAWFIXED) { measureItem.CtlType = ODT_COMBOBOX; measureItem.CtlID = lphc->self->wIDmenu; measureItem.itemID = 0; measureItem.itemWidth = clientRect.right; measureItem.itemHeight = originalItemHeight; measureItem.itemData = 0; SendMessageA(lphc->owner, WM_MEASUREITEM, (WPARAM)measureItem.CtlID, (LPARAM)&measureItem); lphc->fixedOwnerDrawHeight = measureItem.itemHeight; } /* * Keep the size for the next time */ lphc->editHeight = iTextItemHeight; } return iTextItemHeight; } /*********************************************************************** * CBCalcPlacement * * Set up component coordinates given valid lphc->RectCombo. */ static void CBCalcPlacement( HWND hwnd, LPHEADCOMBO lphc, LPRECT lprEdit, LPRECT lprButton, LPRECT lprLB) { /* * Again, start with the client rectangle. */ GetClientRect(hwnd, lprEdit); /* * Remove the borders */ InflateRect(lprEdit, -COMBO_XBORDERSIZE(), -COMBO_YBORDERSIZE()); /* * Chop off the bottom part to fit with the height of the text area. */ lprEdit->bottom = lprEdit->top + CBGetTextAreaHeight(hwnd, lphc); /* * The button starts the same vertical position as the text area. */ CopyRect(lprButton, lprEdit); /* * If the combobox is "simple" there is no button. */ if( CB_GETTYPE(lphc) == CBS_SIMPLE ) lprButton->left = lprButton->right = lprButton->bottom = 0; else { /* * Let's assume the combobox button is the same width as the * scrollbar button. * size the button horizontally and cut-off the text area. */ lprButton->left = lprButton->right - GetSystemMetrics(SM_CXVSCROLL); lprEdit->right = lprButton->left; } /* * In the case of a dropdown, there is an additional spacing between the * text area and the button. */ if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) { lprEdit->right -= COMBO_EDITBUTTONSPACE(); } /* * If we have an edit control, we space it away from the borders slightly. */ if (CB_GETTYPE(lphc) != CBS_DROPDOWNLIST) { InflateRect(lprEdit, -EDIT_CONTROL_PADDING(), -EDIT_CONTROL_PADDING()); } /* * Adjust the size of the listbox popup. */ if( CB_GETTYPE(lphc) == CBS_SIMPLE ) { /* * Use the client rectangle to initialize the listbox rectangle */ GetClientRect(hwnd, lprLB); /* * Then, chop-off the top part. */ lprLB->top = lprEdit->bottom + COMBO_YBORDERSIZE(); } else { /* * Make sure the dropped width is as large as the combobox itself. */ if (lphc->droppedWidth < (lprButton->right + COMBO_XBORDERSIZE())) { lprLB->right = lprLB->left + (lprButton->right + COMBO_XBORDERSIZE()); /* * In the case of a dropdown, the popup listbox is offset to the right. * so, we want to make sure it's flush with the right side of the * combobox */ if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) lprLB->right -= COMBO_EDITBUTTONSPACE(); } else lprLB->right = lprLB->left + lphc->droppedWidth; } TRACE("\ttext\t= (%i,%i-%i,%i)\n", lprEdit->left, lprEdit->top, lprEdit->right, lprEdit->bottom); TRACE("\tbutton\t= (%i,%i-%i,%i)\n", lprButton->left, lprButton->top, lprButton->right, lprButton->bottom); TRACE("\tlbox\t= (%i,%i-%i,%i)\n", lprLB->left, lprLB->top, lprLB->right, lprLB->bottom ); } /*********************************************************************** * CBGetDroppedControlRect */ static void CBGetDroppedControlRect( LPHEADCOMBO lphc, LPRECT lpRect) { CopyRect(lpRect, &lphc->droppedRect); } /*********************************************************************** * COMBO_WindowPosChanging */ static LRESULT COMBO_WindowPosChanging( HWND hwnd, LPHEADCOMBO lphc, WINDOWPOS* posChanging) { /* * We need to override the WM_WINDOWPOSCHANGING method to handle all * the non-simple comboboxes. The problem is that those controls are * always the same height. We have to make sure they are not resized * to another value. */ if ( ( CB_GETTYPE(lphc) != CBS_SIMPLE ) && ((posChanging->flags & SWP_NOSIZE) == 0) ) { int newComboHeight; newComboHeight = CBGetTextAreaHeight(hwnd,lphc) + 2*COMBO_YBORDERSIZE(); /* * Resizing a combobox has another side effect, it resizes the dropped * rectangle as well. However, it does it only if the new height for the * combobox is different than the height it should have. In other words, * if the application resizing the combobox only had the intention to resize * the actual control, for example, to do the layout of a dialog that is * resized, the height of the dropdown is not changed. */ if (posChanging->cy != newComboHeight) { lphc->droppedRect.bottom = lphc->droppedRect.top + posChanging->cy - newComboHeight; posChanging->cy = newComboHeight; } } return 0; } /*********************************************************************** * COMBO_Create */ static LRESULT COMBO_Create( LPHEADCOMBO lphc, WND* wnd, LPARAM lParam) { static char clbName[] = "ComboLBox"; static char editName[] = "Edit"; LPCREATESTRUCTA lpcs = (CREATESTRUCTA*)lParam; if( !CB_GETTYPE(lphc) ) lphc->dwStyle |= CBS_SIMPLE; else if( CB_GETTYPE(lphc) != CBS_DROPDOWNLIST ) lphc->wState |= CBF_EDIT; lphc->self = wnd; lphc->owner = lpcs->hwndParent; /* * The item height and dropped width are not set when the control * is created. */ lphc->droppedWidth = lphc->editHeight = 0; /* * The first time we go through, we want to measure the ownerdraw item */ lphc->wState |= CBF_MEASUREITEM; /* M$ IE 3.01 actually creates (and rapidly destroys) an ownerless combobox */ if( lphc->owner || !(lpcs->style & WS_VISIBLE) ) { UINT lbeStyle; /* * Initialize the dropped rect to the size of the client area of the * control and then, force all the areas of the combobox to be * recalculated. */ GetClientRect( wnd->hwndSelf, &lphc->droppedRect ); CBCalcPlacement(wnd->hwndSelf, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect ); /* * Adjust the position of the popup listbox if it's necessary */ if ( CB_GETTYPE(lphc) != CBS_SIMPLE ) { lphc->droppedRect.top = lphc->textRect.bottom + COMBO_YBORDERSIZE(); /* * If it's a dropdown, the listbox is offset */ if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) lphc->droppedRect.left += COMBO_EDITBUTTONSPACE(); ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect); ClientToScreen(wnd->hwndSelf, (LPPOINT)&lphc->droppedRect.right); } /* create listbox popup */ lbeStyle = (LBS_NOTIFY | WS_BORDER | WS_CLIPSIBLINGS) | (lpcs->style & (WS_VSCROLL | CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)); if( lphc->dwStyle & CBS_SORT ) lbeStyle |= LBS_SORT; if( lphc->dwStyle & CBS_HASSTRINGS ) lbeStyle |= LBS_HASSTRINGS; if( lphc->dwStyle & CBS_NOINTEGRALHEIGHT ) lbeStyle |= LBS_NOINTEGRALHEIGHT; if( lphc->dwStyle & CBS_DISABLENOSCROLL ) lbeStyle |= LBS_DISABLENOSCROLL; if( CB_GETTYPE(lphc) == CBS_SIMPLE ) /* child listbox */ lbeStyle |= WS_CHILD | WS_VISIBLE; else /* popup listbox */ lbeStyle |= WS_POPUP; /* Dropdown ComboLBox is not a child window and we cannot pass * ID_CB_LISTBOX directly because it will be treated as a menu handle. */ lphc->hWndLBox = CreateWindowExA(0, clbName, NULL, lbeStyle, lphc->droppedRect.left, lphc->droppedRect.top, lphc->droppedRect.right - lphc->droppedRect.left, lphc->droppedRect.bottom - lphc->droppedRect.top, lphc->self->hwndSelf, (lphc->dwStyle & CBS_DROPDOWN)? (HMENU)0 : (HMENU)ID_CB_LISTBOX, lphc->self->hInstance, (LPVOID)lphc ); if( lphc->hWndLBox ) { BOOL bEdit = TRUE; lbeStyle = WS_CHILD | WS_VISIBLE | ES_NOHIDESEL | ES_LEFT; /* * In Win95 look, the border fo the edit control is * provided by the combobox */ if (TWEAK_WineLook == WIN31_LOOK) lbeStyle |= WS_BORDER; if( lphc->wState & CBF_EDIT ) { if( lphc->dwStyle & CBS_OEMCONVERT ) lbeStyle |= ES_OEMCONVERT; if( lphc->dwStyle & CBS_AUTOHSCROLL ) lbeStyle |= ES_AUTOHSCROLL; if( lphc->dwStyle & CBS_LOWERCASE ) lbeStyle |= ES_LOWERCASE; else if( lphc->dwStyle & CBS_UPPERCASE ) lbeStyle |= ES_UPPERCASE; lphc->hWndEdit = CreateWindowExA(0, editName, NULL, lbeStyle, lphc->textRect.left, lphc->textRect.top, lphc->textRect.right - lphc->textRect.left, lphc->textRect.bottom - lphc->textRect.top, lphc->self->hwndSelf, (HMENU)ID_CB_EDIT, lphc->self->hInstance, NULL ); if( !lphc->hWndEdit ) bEdit = FALSE; } if( bEdit ) { /* * If the combo is a dropdown, we must resize the control to fit only * the text area and button. To do this, we send a dummy resize and the * WM_WINDOWPOSCHANGING message will take care of setting the height for * us. */ if( CB_GETTYPE(lphc) != CBS_SIMPLE ) { CBForceDummyResize(lphc); } TRACE("init done\n"); return wnd->hwndSelf; } ERR("edit control failure.\n"); } else ERR("listbox failure.\n"); } else ERR("no owner for visible combo.\n"); /* CreateWindow() will send WM_NCDESTROY to cleanup */ return -1; } /*********************************************************************** * CBPaintButton * * Paint combo button (normal, pressed, and disabled states). */ static void CBPaintButton( LPHEADCOMBO lphc, HDC hdc, RECT rectButton) { UINT x, y; BOOL bBool; HDC hMemDC; HBRUSH hPrevBrush; COLORREF oldTextColor, oldBkColor; if( lphc->wState & CBF_NOREDRAW ) return; hPrevBrush = SelectObject(hdc, GetSysColorBrush(COLOR_BTNFACE)); /* * Draw the button background */ PatBlt( hdc, rectButton.left, rectButton.top, rectButton.right-rectButton.left, rectButton.bottom-rectButton.top, PATCOPY ); if( (bBool = lphc->wState & CBF_BUTTONDOWN) ) { DrawEdge( hdc, &rectButton, EDGE_SUNKEN, BF_RECT ); } else { DrawEdge( hdc, &rectButton, EDGE_RAISED, BF_RECT ); } /* * Remove the edge of the button from the rectangle * and calculate the position of the bitmap. */ InflateRect( &rectButton, -2, -2); x = (rectButton.left + rectButton.right - CBitWidth) >> 1; y = (rectButton.top + rectButton.bottom - CBitHeight) >> 1; hMemDC = CreateCompatibleDC( hdc ); SelectObject( hMemDC, hComboBmp ); oldTextColor = SetTextColor( hdc, GetSysColor(COLOR_BTNFACE) ); oldBkColor = SetBkColor( hdc, CB_DISABLED(lphc) ? RGB(128,128,128) : RGB(0,0,0) ); BitBlt( hdc, x, y, CBitWidth, CBitHeight, hMemDC, 0, 0, SRCCOPY ); SetBkColor( hdc, oldBkColor ); SetTextColor( hdc, oldTextColor ); DeleteDC( hMemDC ); SelectObject( hdc, hPrevBrush ); } /*********************************************************************** * CBPaintText * * Paint CBS_DROPDOWNLIST text field / update edit control contents. */ static void CBPaintText( LPHEADCOMBO lphc, HDC hdc, RECT rectEdit) { INT id, size = 0; LPSTR pText = NULL; if( lphc->wState & CBF_NOREDRAW ) return; /* follow Windows combobox that sends a bunch of text * inquiries to its listbox while processing WM_PAINT. */ if( (id = SendMessageA(lphc->hWndLBox, LB_GETCURSEL, 0, 0) ) != LB_ERR ) { size = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, id, 0); if( (pText = HeapAlloc( GetProcessHeap(), 0, size + 1)) ) { SendMessageA( lphc->hWndLBox, LB_GETTEXT, (WPARAM)id, (LPARAM)pText ); pText[size] = '\0'; /* just in case */ } else return; } if( lphc->wState & CBF_EDIT ) { if( CB_HASSTRINGS(lphc) ) SetWindowTextA( lphc->hWndEdit, pText ? pText : "" ); if( lphc->wState & CBF_FOCUSED ) SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1)); } else /* paint text field ourselves */ { HBRUSH hPrevBrush = 0; HDC hDC = hdc; if( !hDC ) { if ((hDC = GetDC(lphc->self->hwndSelf))) { HBRUSH hBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX, hDC, lphc->self->hwndSelf ); hPrevBrush = SelectObject( hDC, (hBrush) ? hBrush : GetStockObject(WHITE_BRUSH) ); } } if( hDC ) { UINT itemState; HFONT hPrevFont = (lphc->hFont) ? SelectObject(hDC, lphc->hFont) : 0; /* * Give ourselves some space. */ InflateRect( &rectEdit, -1, -1 ); if ( (lphc->wState & CBF_FOCUSED) && !(lphc->wState & CBF_DROPPED) ) { /* highlight */ FillRect( hDC, &rectEdit, GetSysColorBrush(COLOR_HIGHLIGHT) ); SetBkColor( hDC, GetSysColor( COLOR_HIGHLIGHT ) ); SetTextColor( hDC, GetSysColor( COLOR_HIGHLIGHTTEXT ) ); itemState = ODS_SELECTED | ODS_FOCUS; } else itemState = 0; if( CB_OWNERDRAWN(lphc) ) { DRAWITEMSTRUCT dis; HRGN clipRegion; /* * Save the current clip region. * To retrieve the clip region, we need to create one "dummy" * clip region. */ clipRegion = CreateRectRgnIndirect(&rectEdit); if (GetClipRgn(hDC, clipRegion)!=1) { DeleteObject(clipRegion); clipRegion=(HRGN)NULL; } if ( lphc->self->dwStyle & WS_DISABLED ) itemState |= ODS_DISABLED; dis.CtlType = ODT_COMBOBOX; dis.CtlID = lphc->self->wIDmenu; dis.hwndItem = lphc->self->hwndSelf; dis.itemAction = ODA_DRAWENTIRE; dis.itemID = id; dis.itemState = itemState; dis.hDC = hDC; dis.rcItem = rectEdit; dis.itemData = SendMessageA( lphc->hWndLBox, LB_GETITEMDATA, (WPARAM)id, 0 ); /* * Clip the DC and have the parent draw the item. */ IntersectClipRect(hDC, rectEdit.left, rectEdit.top, rectEdit.right, rectEdit.bottom); SendMessageA(lphc->owner, WM_DRAWITEM, lphc->self->wIDmenu, (LPARAM)&dis ); /* * Reset the clipping region. */ SelectClipRgn(hDC, clipRegion); } else { ExtTextOutA( hDC, rectEdit.left + 1, rectEdit.top + 1, ETO_OPAQUE | ETO_CLIPPED, &rectEdit, pText ? pText : "" , size, NULL ); if(lphc->wState & CBF_FOCUSED && !(lphc->wState & CBF_DROPPED)) DrawFocusRect( hDC, &rectEdit ); } if( hPrevFont ) SelectObject(hDC, hPrevFont ); if( !hdc ) { if( hPrevBrush ) SelectObject( hDC, hPrevBrush ); ReleaseDC( lphc->self->hwndSelf, hDC ); } } } if (pText) HeapFree( GetProcessHeap(), 0, pText ); } /*********************************************************************** * CBPaintBorder */ static void CBPaintBorder( HWND hwnd, LPHEADCOMBO lphc, HDC hdc) { RECT clientRect; if (CB_GETTYPE(lphc) != CBS_SIMPLE) { GetClientRect(hwnd, &clientRect); } else { CopyRect(&clientRect, &lphc->textRect); InflateRect(&clientRect, EDIT_CONTROL_PADDING(), EDIT_CONTROL_PADDING()); InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE()); } DrawEdge(hdc, &clientRect, EDGE_SUNKEN, BF_RECT); } /*********************************************************************** * COMBO_EraseBackground */ static LRESULT COMBO_EraseBackground( HWND hwnd, LPHEADCOMBO lphc, HDC hParamDC) { HBRUSH hBkgBrush; RECT clientRect; HDC hDC; hDC = (hParamDC) ? hParamDC : GetDC(hwnd); /* * Calculate the area that we want to erase. */ if (CB_GETTYPE(lphc) != CBS_SIMPLE) { GetClientRect(hwnd, &clientRect); } else { CopyRect(&clientRect, &lphc->textRect); InflateRect(&clientRect, COMBO_XBORDERSIZE(), COMBO_YBORDERSIZE()); } hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX, hDC, hwnd); if( !hBkgBrush ) hBkgBrush = GetStockObject(WHITE_BRUSH); FillRect(hDC, &clientRect, hBkgBrush); if (!hParamDC) ReleaseDC(hwnd, hDC); return TRUE; } /*********************************************************************** * COMBO_Paint */ static LRESULT COMBO_Paint(LPHEADCOMBO lphc, HDC hParamDC) { PAINTSTRUCT ps; HDC hDC; hDC = (hParamDC) ? hParamDC : BeginPaint( lphc->self->hwndSelf, &ps); if( hDC && !(lphc->wState & CBF_NOREDRAW) ) { HBRUSH hPrevBrush, hBkgBrush; hBkgBrush = SendMessageA( lphc->owner, WM_CTLCOLORLISTBOX, hDC, lphc->self->hwndSelf ); if( !hBkgBrush ) hBkgBrush = GetStockObject(WHITE_BRUSH); hPrevBrush = SelectObject( hDC, hBkgBrush ); /* * In non 3.1 look, there is a sunken border on the combobox */ if (TWEAK_WineLook != WIN31_LOOK) { CBPaintBorder(CB_HWND(lphc), lphc, hDC); } if( !IsRectEmpty(&lphc->buttonRect) ) { CBPaintButton(lphc, hDC, lphc->buttonRect); } if( !(lphc->wState & CBF_EDIT) ) { /* * The text area has a border only in Win 3.1 look. */ if (TWEAK_WineLook == WIN31_LOOK) { HPEN hPrevPen = SelectObject( hDC, GetSysColorPen(COLOR_WINDOWFRAME) ); Rectangle( hDC, lphc->textRect.left, lphc->textRect.top, lphc->textRect.right - 1, lphc->textRect.bottom - 1); SelectObject( hDC, hPrevPen ); } CBPaintText( lphc, hDC, lphc->textRect); } if( hPrevBrush ) SelectObject( hDC, hPrevBrush ); } if( !hParamDC ) EndPaint(lphc->self->hwndSelf, &ps); return 0; } /*********************************************************************** * CBUpdateLBox * * Select listbox entry according to the contents of the edit control. */ static INT CBUpdateLBox( LPHEADCOMBO lphc ) { INT length, idx, ret; LPSTR pText = NULL; idx = ret = LB_ERR; length = CB_GETEDITTEXTLENGTH( lphc ); if( length > 0 ) pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1); TRACE("\t edit text length %i\n", length ); if( pText ) { if( length ) GetWindowTextA( lphc->hWndEdit, pText, length + 1); else pText[0] = '\0'; idx = SendMessageA( lphc->hWndLBox, LB_FINDSTRING, (WPARAM)(-1), (LPARAM)pText ); if( idx == LB_ERR ) idx = 0; /* select first item */ else ret = idx; HeapFree( GetProcessHeap(), 0, pText ); } /* select entry */ SendMessageA( lphc->hWndLBox, LB_SETCURSEL, (WPARAM)idx, 0 ); if( idx >= 0 ) { SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)idx, 0 ); /* probably superfluous but Windows sends this too */ SendMessageA( lphc->hWndLBox, LB_SETCARETINDEX, (WPARAM)idx, 0 ); } return ret; } /*********************************************************************** * CBUpdateEdit * * Copy a listbox entry to the edit control. */ static void CBUpdateEdit( LPHEADCOMBO lphc , INT index ) { INT length; LPSTR pText = NULL; TRACE("\t %i\n", index ); if( index == -1 ) { length = CB_GETEDITTEXTLENGTH( lphc ); if( length ) { if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) ) { GetWindowTextA( lphc->hWndEdit, pText, length + 1 ); index = SendMessageA( lphc->hWndLBox, LB_FINDSTRING, (WPARAM)(-1), (LPARAM)pText ); HeapFree( GetProcessHeap(), 0, pText ); } } } if( index >= 0 ) /* got an entry */ { length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)index, 0); if( length ) { if( (pText = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1)) ) { SendMessageA( lphc->hWndLBox, LB_GETTEXT, (WPARAM)index, (LPARAM)pText ); SendMessageA( lphc->hWndEdit, WM_SETTEXT, 0, (LPARAM)pText ); SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) ); HeapFree( GetProcessHeap(), 0, pText ); } } } } /*********************************************************************** * CBDropDown * * Show listbox popup. */ static void CBDropDown( LPHEADCOMBO lphc ) { INT index; RECT rect; TRACE("[%04x]: drop down\n", CB_HWND(lphc)); CB_NOTIFY( lphc, CBN_DROPDOWN ); /* set selection */ lphc->wState |= CBF_DROPPED; if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) { index = CBUpdateLBox( lphc ); if( !(lphc->wState & CBF_CAPTURE) ) CBUpdateEdit( lphc, index ); } else { index = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 ); if( index == LB_ERR ) index = 0; SendMessageA( lphc->hWndLBox, LB_SETTOPINDEX, (WPARAM)index, 0 ); SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 ); } /* now set popup position */ GetWindowRect( lphc->self->hwndSelf, &rect ); /* * If it's a dropdown, the listbox is offset */ if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) rect.left += COMBO_EDITBUTTONSPACE(); SetWindowPos( lphc->hWndLBox, HWND_TOP, rect.left, rect.bottom, lphc->droppedRect.right - lphc->droppedRect.left, lphc->droppedRect.bottom - lphc->droppedRect.top, SWP_NOACTIVATE | SWP_NOREDRAW); if( !(lphc->wState & CBF_NOREDRAW) ) RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); ShowWindow( lphc->hWndLBox, SW_SHOWNA ); } /*********************************************************************** * CBRollUp * * Hide listbox popup. */ static void CBRollUp( LPHEADCOMBO lphc, BOOL ok, BOOL bButton ) { HWND hWnd = lphc->self->hwndSelf; CB_NOTIFY( lphc, (ok) ? CBN_SELENDOK : CBN_SELENDCANCEL ); if( IsWindow( hWnd ) && CB_GETTYPE(lphc) != CBS_SIMPLE ) { TRACE("[%04x]: roll up [%i]\n", CB_HWND(lphc), (INT)ok ); /* always send WM_LBUTTONUP? */ SendMessageA( lphc->hWndLBox, WM_LBUTTONUP, 0, (LPARAM)(-1) ); if( lphc->wState & CBF_DROPPED ) { RECT rect; lphc->wState &= ~CBF_DROPPED; ShowWindow( lphc->hWndLBox, SW_HIDE ); if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) { INT index = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 ); CBUpdateEdit( lphc, index ); rect = lphc->buttonRect; } else { if( bButton ) { UnionRect( &rect, &lphc->buttonRect, &lphc->textRect); } else rect = lphc->textRect; bButton = TRUE; } if( bButton && !(lphc->wState & CBF_NOREDRAW) ) RedrawWindow( hWnd, &rect, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW | RDW_NOCHILDREN ); CB_NOTIFY( lphc, CBN_CLOSEUP ); } } } /*********************************************************************** * COMBO_FlipListbox * * Used by the ComboLBox to show/hide itself in response to VK_F4, etc... */ BOOL COMBO_FlipListbox( LPHEADCOMBO lphc, BOOL bRedrawButton ) { if( lphc->wState & CBF_DROPPED ) { CBRollUp( lphc, TRUE, bRedrawButton ); return FALSE; } CBDropDown( lphc ); return TRUE; } /*********************************************************************** * COMBO_GetLBWindow * * Edit control helper. */ HWND COMBO_GetLBWindow( WND* pWnd ) { LPHEADCOMBO lphc = CB_GETPTR(pWnd); if( lphc ) return lphc->hWndLBox; return 0; } /*********************************************************************** * CBRepaintButton */ static void CBRepaintButton( LPHEADCOMBO lphc ) { InvalidateRect(CB_HWND(lphc), &lphc->buttonRect, TRUE); UpdateWindow(CB_HWND(lphc)); } /*********************************************************************** * COMBO_SetFocus */ static void COMBO_SetFocus( LPHEADCOMBO lphc ) { if( !(lphc->wState & CBF_FOCUSED) ) { if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) SendMessageA( lphc->hWndLBox, LB_CARETON, 0, 0 ); if( lphc->wState & CBF_EDIT ) SendMessageA( lphc->hWndEdit, EM_SETSEL, 0, (LPARAM)(-1) ); lphc->wState |= CBF_FOCUSED; if( !(lphc->wState & CBF_EDIT) ) { InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE); } CB_NOTIFY( lphc, CBN_SETFOCUS ); } } /*********************************************************************** * COMBO_KillFocus */ static void COMBO_KillFocus( LPHEADCOMBO lphc ) { HWND hWnd = lphc->self->hwndSelf; if( lphc->wState & CBF_FOCUSED ) { SendMessageA( hWnd, WM_LBUTTONUP, 0, (LPARAM)(-1) ); CBRollUp( lphc, FALSE, TRUE ); if( IsWindow( hWnd ) ) { if( CB_GETTYPE(lphc) == CBS_DROPDOWNLIST ) SendMessageA( lphc->hWndLBox, LB_CARETOFF, 0, 0 ); lphc->wState &= ~CBF_FOCUSED; /* redraw text */ if( lphc->wState & CBF_EDIT ) SendMessageA( lphc->hWndEdit, EM_SETSEL, (WPARAM)(-1), 0 ); else { InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE); } CB_NOTIFY( lphc, CBN_KILLFOCUS ); } } } /*********************************************************************** * COMBO_Command */ static LRESULT COMBO_Command( LPHEADCOMBO lphc, WPARAM wParam, HWND hWnd ) { if( lphc->wState & CBF_EDIT && lphc->hWndEdit == hWnd ) { /* ">> 8" makes gcc generate jump-table instead of cmp ladder */ switch( HIWORD(wParam) >> 8 ) { case (EN_SETFOCUS >> 8): TRACE("[%04x]: edit [%04x] got focus\n", CB_HWND(lphc), lphc->hWndEdit ); if( !(lphc->wState & CBF_FOCUSED) ) COMBO_SetFocus( lphc ); break; case (EN_KILLFOCUS >> 8): TRACE("[%04x]: edit [%04x] lost focus\n", CB_HWND(lphc), lphc->hWndEdit ); /* NOTE: it seems that Windows' edit control sends an * undocumented message WM_USER + 0x1B instead of this * notification (only when it happens to be a part of * the combo). ?? - AK. */ COMBO_KillFocus( lphc ); break; case (EN_CHANGE >> 8): CB_NOTIFY( lphc, CBN_EDITCHANGE ); CBUpdateLBox( lphc ); break; case (EN_UPDATE >> 8): CB_NOTIFY( lphc, CBN_EDITUPDATE ); break; case (EN_ERRSPACE >> 8): CB_NOTIFY( lphc, CBN_ERRSPACE ); } } else if( lphc->hWndLBox == hWnd ) { switch( HIWORD(wParam) ) { case LBN_ERRSPACE: CB_NOTIFY( lphc, CBN_ERRSPACE ); break; case LBN_DBLCLK: CB_NOTIFY( lphc, CBN_DBLCLK ); break; case LBN_SELCHANGE: case LBN_SELCANCEL: TRACE("[%04x]: lbox selection change [%04x]\n", CB_HWND(lphc), lphc->wState ); /* do not roll up if selection is being tracked * by arrowkeys in the dropdown listbox */ if( (lphc->wState & CBF_DROPPED) && !(lphc->wState & CBF_NOROLLUP) ) CBRollUp( lphc, (HIWORD(wParam) == LBN_SELCHANGE), TRUE ); else lphc->wState &= ~CBF_NOROLLUP; CB_NOTIFY( lphc, CBN_SELCHANGE ); InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE); /* fall through */ case LBN_SETFOCUS: case LBN_KILLFOCUS: /* nothing to do here since ComboLBox always resets the focus to its * combo/edit counterpart */ break; } } return 0; } /*********************************************************************** * COMBO_ItemOp * * Fixup an ownerdrawn item operation and pass it up to the combobox owner. */ static LRESULT COMBO_ItemOp( LPHEADCOMBO lphc, UINT msg, WPARAM wParam, LPARAM lParam ) { HWND hWnd = lphc->self->hwndSelf; TRACE("[%04x]: ownerdraw op %04x\n", CB_HWND(lphc), msg ); #define lpIS ((LPDELETEITEMSTRUCT)lParam) /* two first items are the same in all 4 structs */ lpIS->CtlType = ODT_COMBOBOX; lpIS->CtlID = lphc->self->wIDmenu; switch( msg ) /* patch window handle */ { case WM_DELETEITEM: lpIS->hwndItem = hWnd; #undef lpIS break; case WM_DRAWITEM: #define lpIS ((LPDRAWITEMSTRUCT)lParam) lpIS->hwndItem = hWnd; #undef lpIS break; case WM_COMPAREITEM: #define lpIS ((LPCOMPAREITEMSTRUCT)lParam) lpIS->hwndItem = hWnd; #undef lpIS break; } return SendMessageA( lphc->owner, msg, lphc->self->wIDmenu, lParam ); } /*********************************************************************** * COMBO_GetText */ static LRESULT COMBO_GetText( LPHEADCOMBO lphc, UINT N, LPSTR lpText) { if( lphc->wState & CBF_EDIT ) return SendMessageA( lphc->hWndEdit, WM_GETTEXT, (WPARAM)N, (LPARAM)lpText ); /* get it from the listbox */ if( lphc->hWndLBox ) { INT idx = SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0 ); if( idx != LB_ERR ) { LPSTR lpBuffer; INT length = SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, (WPARAM)idx, 0 ); /* 'length' is without the terminating character */ if( length >= N ) lpBuffer = (LPSTR) HeapAlloc( GetProcessHeap(), 0, length + 1 ); else lpBuffer = lpText; if( lpBuffer ) { INT n = SendMessageA( lphc->hWndLBox, LB_GETTEXT, (WPARAM)idx, (LPARAM)lpBuffer ); /* truncate if buffer is too short */ if( length >= N ) { if (N && lpText) { if( n != LB_ERR ) memcpy( lpText, lpBuffer, (N>n) ? n+1 : N-1 ); lpText[N - 1] = '\0'; } HeapFree( GetProcessHeap(), 0, lpBuffer ); } return (LRESULT)n; } } } return 0; } /*********************************************************************** * CBResetPos * * This function sets window positions according to the updated * component placement struct. */ static void CBResetPos( LPHEADCOMBO lphc, LPRECT rectEdit, LPRECT rectLB, BOOL bRedraw) { BOOL bDrop = (CB_GETTYPE(lphc) != CBS_SIMPLE); /* NOTE: logs sometimes have WM_LBUTTONUP before a cascade of * sizing messages */ if( lphc->wState & CBF_EDIT ) SetWindowPos( lphc->hWndEdit, 0, rectEdit->left, rectEdit->top, rectEdit->right - rectEdit->left, rectEdit->bottom - rectEdit->top, SWP_NOZORDER | SWP_NOACTIVATE | ((bDrop) ? SWP_NOREDRAW : 0) ); SetWindowPos( lphc->hWndLBox, 0, rectLB->left, rectLB->top, rectLB->right - rectLB->left, rectLB->bottom - rectLB->top, SWP_NOACTIVATE | SWP_NOZORDER | ((bDrop) ? SWP_NOREDRAW : 0) ); if( bDrop ) { if( lphc->wState & CBF_DROPPED ) { lphc->wState &= ~CBF_DROPPED; ShowWindow( lphc->hWndLBox, SW_HIDE ); } if( bRedraw && !(lphc->wState & CBF_NOREDRAW) ) RedrawWindow( lphc->self->hwndSelf, NULL, 0, RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW ); } } /*********************************************************************** * COMBO_Size */ static void COMBO_Size( LPHEADCOMBO lphc ) { CBCalcPlacement(lphc->self->hwndSelf, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect); CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); } /*********************************************************************** * COMBO_Font */ static void COMBO_Font( LPHEADCOMBO lphc, HFONT hFont, BOOL bRedraw ) { /* * Set the font */ lphc->hFont = hFont; /* * Propagate to owned windows. */ if( lphc->wState & CBF_EDIT ) SendMessageA( lphc->hWndEdit, WM_SETFONT, (WPARAM)hFont, bRedraw ); SendMessageA( lphc->hWndLBox, WM_SETFONT, (WPARAM)hFont, bRedraw ); /* * Redo the layout of the control. */ if ( CB_GETTYPE(lphc) == CBS_SIMPLE) { CBCalcPlacement(lphc->self->hwndSelf, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect); CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); } else { CBForceDummyResize(lphc); } } /*********************************************************************** * COMBO_SetItemHeight */ static LRESULT COMBO_SetItemHeight( LPHEADCOMBO lphc, INT index, INT height ) { LRESULT lRet = CB_ERR; if( index == -1 ) /* set text field height */ { if( height < 32768 ) { lphc->editHeight = height; /* * Redo the layout of the control. */ if ( CB_GETTYPE(lphc) == CBS_SIMPLE) { CBCalcPlacement(lphc->self->hwndSelf, lphc, &lphc->textRect, &lphc->buttonRect, &lphc->droppedRect); CBResetPos( lphc, &lphc->textRect, &lphc->droppedRect, TRUE ); } else { CBForceDummyResize(lphc); } lRet = height; } } else if ( CB_OWNERDRAWN(lphc) ) /* set listbox item height */ lRet = SendMessageA( lphc->hWndLBox, LB_SETITEMHEIGHT, (WPARAM)index, (LPARAM)height ); return lRet; } /*********************************************************************** * COMBO_SelectString */ static LRESULT COMBO_SelectString( LPHEADCOMBO lphc, INT start, LPCSTR pText ) { INT index = SendMessageA( lphc->hWndLBox, LB_SELECTSTRING, (WPARAM)start, (LPARAM)pText ); if( index >= 0 ) { if( lphc->wState & CBF_EDIT ) CBUpdateEdit( lphc, index ); else { InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE); } } return (LRESULT)index; } /*********************************************************************** * COMBO_LButtonDown */ static void COMBO_LButtonDown( LPHEADCOMBO lphc, LPARAM lParam ) { POINT pt; BOOL bButton; HWND hWnd = lphc->self->hwndSelf; pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); bButton = PtInRect(&lphc->buttonRect, pt); if( (CB_GETTYPE(lphc) == CBS_DROPDOWNLIST) || (bButton && (CB_GETTYPE(lphc) == CBS_DROPDOWN)) ) { lphc->wState |= CBF_BUTTONDOWN; if( lphc->wState & CBF_DROPPED ) { /* got a click to cancel selection */ lphc->wState &= ~CBF_BUTTONDOWN; CBRollUp( lphc, TRUE, FALSE ); if( !IsWindow( hWnd ) ) return; if( lphc->wState & CBF_CAPTURE ) { lphc->wState &= ~CBF_CAPTURE; ReleaseCapture(); } } else { /* drop down the listbox and start tracking */ lphc->wState |= CBF_CAPTURE; CBDropDown( lphc ); SetCapture( hWnd ); } if( bButton ) CBRepaintButton( lphc ); } } /*********************************************************************** * COMBO_LButtonUp * * Release capture and stop tracking if needed. */ static void COMBO_LButtonUp( LPHEADCOMBO lphc, LPARAM lParam ) { if( lphc->wState & CBF_CAPTURE ) { lphc->wState &= ~CBF_CAPTURE; if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) { INT index = CBUpdateLBox( lphc ); CBUpdateEdit( lphc, index ); } ReleaseCapture(); } if( lphc->wState & CBF_BUTTONDOWN ) { lphc->wState &= ~CBF_BUTTONDOWN; CBRepaintButton( lphc ); } } /*********************************************************************** * COMBO_MouseMove * * Two things to do - track combo button and release capture when * pointer goes into the listbox. */ static void COMBO_MouseMove( LPHEADCOMBO lphc, WPARAM wParam, LPARAM lParam ) { POINT pt; RECT lbRect; pt.x = LOWORD(lParam); pt.y = HIWORD(lParam); if( lphc->wState & CBF_BUTTONDOWN ) { BOOL bButton; bButton = PtInRect(&lphc->buttonRect, pt); if( !bButton ) { lphc->wState &= ~CBF_BUTTONDOWN; CBRepaintButton( lphc ); } } GetClientRect( lphc->hWndLBox, &lbRect ); MapWindowPoints( lphc->self->hwndSelf, lphc->hWndLBox, &pt, 1 ); if( PtInRect(&lbRect, pt) ) { lphc->wState &= ~CBF_CAPTURE; ReleaseCapture(); if( CB_GETTYPE(lphc) == CBS_DROPDOWN ) CBUpdateLBox( lphc ); /* hand over pointer tracking */ SendMessageA( lphc->hWndLBox, WM_LBUTTONDOWN, wParam, lParam ); } } /*********************************************************************** * ComboWndProc_locked * * http://www.microsoft.com/msdn/sdk/platforms/doc/sdk/win32/ctrl/src/combobox_15.htm */ static inline LRESULT WINAPI ComboWndProc_locked( WND* pWnd, UINT message, WPARAM wParam, LPARAM lParam ) { if( pWnd ) { LPHEADCOMBO lphc = CB_GETPTR(pWnd); HWND hwnd = pWnd->hwndSelf; TRACE("[%04x]: msg %s wp %08x lp %08lx\n", pWnd->hwndSelf, SPY_GetMsgName(message), wParam, lParam ); if( lphc || message == WM_NCCREATE ) switch(message) { /* System messages */ case WM_NCCREATE: return COMBO_NCCreate(pWnd, lParam); case WM_NCDESTROY: COMBO_NCDestroy(lphc); break;/* -> DefWindowProc */ case WM_CREATE: return COMBO_Create(lphc, pWnd, lParam); case WM_PRINTCLIENT: if (lParam & PRF_ERASEBKGND) COMBO_EraseBackground(hwnd, lphc, wParam); /* Fallthrough */ case WM_PAINT: /* wParam may contain a valid HDC! */ return COMBO_Paint(lphc, wParam); case WM_ERASEBKGND: return COMBO_EraseBackground(hwnd, lphc, wParam); case WM_GETDLGCODE: return (LRESULT)(DLGC_WANTARROWS | DLGC_WANTCHARS); case WM_WINDOWPOSCHANGING: return COMBO_WindowPosChanging(hwnd, lphc, (LPWINDOWPOS)lParam); case WM_SIZE: if( lphc->hWndLBox && !(lphc->wState & CBF_NORESIZE) ) COMBO_Size( lphc ); return TRUE; case WM_SETFONT: COMBO_Font( lphc, (HFONT16)wParam, (BOOL)lParam ); return TRUE; case WM_GETFONT: return (LRESULT)lphc->hFont; case WM_SETFOCUS: if( lphc->wState & CBF_EDIT ) SetFocus( lphc->hWndEdit ); else COMBO_SetFocus( lphc ); return TRUE; case WM_KILLFOCUS: #define hwndFocus ((HWND16)wParam) if( !hwndFocus || (hwndFocus != lphc->hWndEdit && hwndFocus != lphc->hWndLBox )) COMBO_KillFocus( lphc ); #undef hwndFocus return TRUE; case WM_COMMAND: return COMBO_Command( lphc, wParam, (HWND)lParam ); case WM_GETTEXT: return COMBO_GetText( lphc, (UINT)wParam, (LPSTR)lParam ); case WM_SETTEXT: case WM_GETTEXTLENGTH: case WM_CLEAR: case WM_CUT: case WM_PASTE: case WM_COPY: if( lphc->wState & CBF_EDIT ) return SendMessageA( lphc->hWndEdit, message, wParam, lParam ); return CB_ERR; case WM_DRAWITEM: case WM_DELETEITEM: case WM_COMPAREITEM: case WM_MEASUREITEM: return COMBO_ItemOp( lphc, message, wParam, lParam ); case WM_ENABLE: if( lphc->wState & CBF_EDIT ) EnableWindow( lphc->hWndEdit, (BOOL)wParam ); EnableWindow( lphc->hWndLBox, (BOOL)wParam ); return TRUE; case WM_SETREDRAW: if( wParam ) lphc->wState &= ~CBF_NOREDRAW; else lphc->wState |= CBF_NOREDRAW; if( lphc->wState & CBF_EDIT ) SendMessageA( lphc->hWndEdit, message, wParam, lParam ); SendMessageA( lphc->hWndLBox, message, wParam, lParam ); return 0; case WM_SYSKEYDOWN: if( KEYDATA_ALT & HIWORD(lParam) ) if( wParam == VK_UP || wParam == VK_DOWN ) COMBO_FlipListbox( lphc, TRUE ); break;/* -> DefWindowProc */ case WM_CHAR: case WM_KEYDOWN: if( lphc->wState & CBF_EDIT ) return SendMessageA( lphc->hWndEdit, message, wParam, lParam ); else return SendMessageA( lphc->hWndLBox, message, wParam, lParam ); case WM_LBUTTONDOWN: if( !(lphc->wState & CBF_FOCUSED) ) SetFocus( lphc->self->hwndSelf ); if( lphc->wState & CBF_FOCUSED ) COMBO_LButtonDown( lphc, lParam ); return TRUE; case WM_LBUTTONUP: COMBO_LButtonUp( lphc, lParam ); return TRUE; case WM_MOUSEMOVE: if( lphc->wState & CBF_CAPTURE ) COMBO_MouseMove( lphc, wParam, lParam ); return TRUE; /* Combo messages */ case CB_ADDSTRING16: if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_ADDSTRING: return SendMessageA( lphc->hWndLBox, LB_ADDSTRING, 0, lParam); case CB_INSERTSTRING16: wParam = (INT)(INT16)wParam; if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_INSERTSTRING: return SendMessageA( lphc->hWndLBox, LB_INSERTSTRING, wParam, lParam); case CB_DELETESTRING16: case CB_DELETESTRING: return SendMessageA( lphc->hWndLBox, LB_DELETESTRING, wParam, 0); case CB_SELECTSTRING16: wParam = (INT)(INT16)wParam; if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_SELECTSTRING: return COMBO_SelectString( lphc, (INT)wParam, (LPSTR)lParam ); case CB_FINDSTRING16: wParam = (INT)(INT16)wParam; if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_FINDSTRING: return SendMessageA( lphc->hWndLBox, LB_FINDSTRING, wParam, lParam); case CB_FINDSTRINGEXACT16: wParam = (INT)(INT16)wParam; if( CB_HASSTRINGS(lphc) ) lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_FINDSTRINGEXACT: return SendMessageA( lphc->hWndLBox, LB_FINDSTRINGEXACT, wParam, lParam ); case CB_SETITEMHEIGHT16: wParam = (INT)(INT16)wParam; /* signed integer */ case CB_SETITEMHEIGHT: return COMBO_SetItemHeight( lphc, (INT)wParam, (INT)lParam); case CB_GETITEMHEIGHT16: wParam = (INT)(INT16)wParam; case CB_GETITEMHEIGHT: if( (INT)wParam >= 0 ) /* listbox item */ return SendMessageA( lphc->hWndLBox, LB_GETITEMHEIGHT, wParam, 0); return CBGetTextAreaHeight(hwnd, lphc); case CB_RESETCONTENT16: case CB_RESETCONTENT: SendMessageA( lphc->hWndLBox, LB_RESETCONTENT, 0, 0 ); InvalidateRect(CB_HWND(lphc), NULL, TRUE); return TRUE; case CB_INITSTORAGE: return SendMessageA( lphc->hWndLBox, LB_INITSTORAGE, wParam, lParam); case CB_GETHORIZONTALEXTENT: return SendMessageA( lphc->hWndLBox, LB_GETHORIZONTALEXTENT, 0, 0); case CB_SETHORIZONTALEXTENT: return SendMessageA( lphc->hWndLBox, LB_SETHORIZONTALEXTENT, wParam, 0); case CB_GETTOPINDEX: return SendMessageA( lphc->hWndLBox, LB_GETTOPINDEX, 0, 0); case CB_GETLOCALE: return SendMessageA( lphc->hWndLBox, LB_GETLOCALE, 0, 0); case CB_SETLOCALE: return SendMessageA( lphc->hWndLBox, LB_SETLOCALE, wParam, 0); case CB_GETDROPPEDWIDTH: if( lphc->droppedWidth ) return lphc->droppedWidth; return lphc->droppedRect.right - lphc->droppedRect.left; case CB_SETDROPPEDWIDTH: if( (CB_GETTYPE(lphc) != CBS_SIMPLE) && (INT)wParam < 32768 ) lphc->droppedWidth = (INT)wParam; return CB_ERR; case CB_GETDROPPEDCONTROLRECT16: lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); if( lParam ) { RECT r; CBGetDroppedControlRect( lphc, &r ); CONV_RECT32TO16( &r, (LPRECT16)lParam ); } return CB_OKAY; case CB_GETDROPPEDCONTROLRECT: if( lParam ) CBGetDroppedControlRect(lphc, (LPRECT)lParam ); return CB_OKAY; case CB_GETDROPPEDSTATE16: case CB_GETDROPPEDSTATE: return (lphc->wState & CBF_DROPPED) ? TRUE : FALSE; case CB_DIR16: lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); /* fall through */ case CB_DIR: return COMBO_Directory( lphc, (UINT)wParam, (LPSTR)lParam, (message == CB_DIR)); case CB_SHOWDROPDOWN16: case CB_SHOWDROPDOWN: if( CB_GETTYPE(lphc) != CBS_SIMPLE ) { if( wParam ) { if( !(lphc->wState & CBF_DROPPED) ) CBDropDown( lphc ); } else if( lphc->wState & CBF_DROPPED ) CBRollUp( lphc, FALSE, TRUE ); } return TRUE; case CB_GETCOUNT16: case CB_GETCOUNT: return SendMessageA( lphc->hWndLBox, LB_GETCOUNT, 0, 0); case CB_GETCURSEL16: case CB_GETCURSEL: return SendMessageA( lphc->hWndLBox, LB_GETCURSEL, 0, 0); case CB_SETCURSEL16: wParam = (INT)(INT16)wParam; case CB_SETCURSEL: lParam = SendMessageA( lphc->hWndLBox, LB_SETCURSEL, wParam, 0); if( lphc->wState & CBF_SELCHANGE ) { /* no LBN_SELCHANGE in this case, update manually */ InvalidateRect(CB_HWND(lphc), &lphc->textRect, TRUE); lphc->wState &= ~CBF_SELCHANGE; } return lParam; case CB_GETLBTEXT16: wParam = (INT)(INT16)wParam; lParam = (LPARAM)PTR_SEG_TO_LIN(lParam); case CB_GETLBTEXT: return SendMessageA( lphc->hWndLBox, LB_GETTEXT, wParam, lParam); case CB_GETLBTEXTLEN16: wParam = (INT)(INT16)wParam; case CB_GETLBTEXTLEN: return SendMessageA( lphc->hWndLBox, LB_GETTEXTLEN, wParam, 0); case CB_GETITEMDATA16: wParam = (INT)(INT16)wParam; case CB_GETITEMDATA: return SendMessageA( lphc->hWndLBox, LB_GETITEMDATA, wParam, 0); case CB_SETITEMDATA16: wParam = (INT)(INT16)wParam; case CB_SETITEMDATA: return SendMessageA( lphc->hWndLBox, LB_SETITEMDATA, wParam, lParam); case CB_GETEDITSEL16: wParam = lParam = 0; /* just in case */ case CB_GETEDITSEL: if( lphc->wState & CBF_EDIT ) { INT a, b; return SendMessageA( lphc->hWndEdit, EM_GETSEL, (wParam) ? wParam : (WPARAM)&a, (lParam) ? lParam : (LPARAM)&b ); } return CB_ERR; case CB_SETEDITSEL16: case CB_SETEDITSEL: if( lphc->wState & CBF_EDIT ) return SendMessageA( lphc->hWndEdit, EM_SETSEL, (INT)(INT16)LOWORD(lParam), (INT)(INT16)HIWORD(lParam) ); return CB_ERR; case CB_SETEXTENDEDUI16: case CB_SETEXTENDEDUI: if( CB_GETTYPE(lphc) == CBS_SIMPLE ) return CB_ERR; if( wParam ) lphc->wState |= CBF_EUI; else lphc->wState &= ~CBF_EUI; return CB_OKAY; case CB_GETEXTENDEDUI16: case CB_GETEXTENDEDUI: return (lphc->wState & CBF_EUI) ? TRUE : FALSE; case (WM_USER + 0x1B): WARN("[%04x]: undocumented msg!\n", hwnd ); } return DefWindowProcA(hwnd, message, wParam, lParam); } return CB_ERR; } /*********************************************************************** * ComboWndProc * * This is just a wrapper for the real ComboWndProc which locks/unlocks * window structs. */ LRESULT WINAPI ComboWndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { WND* pWnd = WIN_FindWndPtr(hwnd); LRESULT retvalue = ComboWndProc_locked(pWnd,message,wParam,lParam); WIN_ReleaseWndPtr(pWnd); return retvalue; }