#include "alfpriv.h" // Win32s doesn't like using the original message numbers for custom messages #define ALF_CB_INSERTSTRING (ALF_WM__BASE + 200) #define ALF_CB_ADDSTRING (ALF_WM__BASE + 201) #define ALF_CB_GETCURSEL (ALF_WM__BASE + 202) #define ALF_CB_SETCURSEL (ALF_WM__BASE + 203) #define ALF_CB_GETLBTEXTLEN (ALF_WM__BASE + 204) #define ALF_CB_GETLBTEXT (ALF_WM__BASE + 205) #define ALF_CB_DELETESTRING (ALF_WM__BASE + 206) #define ALF_CB_GETSTYLE (ALF_WM__BASE + 207) #define ALF_CB_FINDSTRINGEXACT (ALF_WM__BASE + 208) typedef struct { DWORD comboStyle; } ALFComboCreateParams; typedef struct { HWND hwndChild; int dpi; } ALFComboPriv; static ALFComboPriv * ALF_Combo_InitializePriv(HWND hwnd, ALFComboCreateParams *params) { ALFComboPriv *priv = ALF_New(ALFComboPriv, 1); priv->dpi = 96; DWORD comboStyle = params->comboStyle | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS; priv->hwndChild = CreateWindowEx(0, TEXT("COMBOBOX"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | comboStyle, 0, 0, 100, 200 /* FIXME needed for commctl32 v5, what is the best value here? */, hwnd, (HMENU) GetWindowLongPtrW(hwnd, GWLP_ID), ALF_HINSTANCE, NULL); return priv; } static void ALF_Combo_FreePriv(ALFComboPriv *priv) { ALF_Free(priv); } static int ALF__ComboItemHeight(HWND hwnd) { int height = 0; HDC hDc = GetDC(hwnd); HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); HFONT oldfont = SelectFont(hDc, font); TEXTMETRIC tm; ZeroMemory(&tm, sizeof(tm)); if (GetTextMetrics(hDc, &tm)) { height = tm.tmHeight; } SelectFont(hDc, oldfont); if (!ALF_Compat_IsMinWindowsVersion(4, 0)) { // baseline alignment for NT3.x/Win32s // ideally, this would only be applied for the ODS_COMOBOXEDIT state, // but that state is not sent pre-Win95. height += 2; } ReleaseDC(hwnd, hDc); return height; } static LRESULT CALLBACK ALF__ComboWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ALFComboPriv *priv = (ALFComboPriv *)GetWindowLongPtr(hwnd, 0); if (uMsg == WM_CREATE) { ALFComboCreateParams *params = (ALFComboCreateParams*)((ALFControlCreateParams *)((CREATESTRUCT*)lParam)->lpCreateParams)->param; priv = ALF_Combo_InitializePriv(hwnd, params); SetWindowLongPtr(hwnd, 0, (LONG_PTR)priv); } if (uMsg == WM_DESTROY) { ALF_Combo_FreePriv(priv); priv = NULL; SetWindowLongPtr(hwnd, 0, 0); } if (uMsg == WM_ENABLE && priv) { EnableWindow(priv->hwndChild, (BOOL)wParam); return 0; } if (uMsg == WM_SETTEXT && priv) { SetWindowText(priv->hwndChild, (TCHAR*)lParam); ALF_InvalidateLayout(GetParent(hwnd)); } if (uMsg == WM_GETTEXTLENGTH && priv) { return (LRESULT)GetWindowTextLength(priv->hwndChild); } if (uMsg == WM_GETTEXT && priv) { return GetWindowText(priv->hwndChild, (TCHAR*)lParam, (int)wParam); } if (uMsg == WM_SETFONT && priv) { SendMessage(priv->hwndChild, WM_SETFONT, wParam, lParam); SendMessage(priv->hwndChild, CB_SETITEMHEIGHT, (WPARAM)0, (LPARAM)ALF__ComboItemHeight(hwnd)); ALF_InvalidateLayout(GetParent(hwnd)); return 0; } if (uMsg == WM_GETFONT && priv) { return SendMessage(priv->hwndChild, WM_GETFONT, wParam, lParam); } if (uMsg == ALF_CB_GETSTYLE && priv) { return GetWindowLong(priv->hwndChild, GWL_STYLE); } if ((uMsg == ALF_CB_FINDSTRINGEXACT || uMsg == CB_FINDSTRINGEXACT) && priv) { return SendMessage(priv->hwndChild, CB_FINDSTRINGEXACT, wParam, lParam); } if ((uMsg == ALF_CB_ADDSTRING || uMsg == CB_ADDSTRING) && priv) { return SendMessage(priv->hwndChild, CB_ADDSTRING, wParam, lParam); } if ((uMsg == ALF_CB_INSERTSTRING || uMsg == CB_INSERTSTRING) && priv) { return SendMessage(priv->hwndChild, CB_INSERTSTRING, wParam, lParam); } if ((uMsg == ALF_CB_DELETESTRING || uMsg == CB_DELETESTRING) && priv) { return SendMessage(priv->hwndChild, CB_DELETESTRING, wParam, lParam); } if ((uMsg == ALF_CB_GETCURSEL || uMsg == CB_GETCURSEL) && priv) { return SendMessage(priv->hwndChild, CB_GETCURSEL, wParam, lParam); } if ((uMsg == ALF_CB_SETCURSEL || uMsg == CB_SETCURSEL) && priv) { LRESULT curSel = SendMessage(priv->hwndChild, CB_GETCURSEL, 0, 0); if ((WPARAM)curSel != wParam) { LRESULT newSel = SendMessage(priv->hwndChild, CB_SETCURSEL, wParam, 0); SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(GetWindowLong(priv->hwndChild, GWL_ID), CBN_SELCHANGE), (LPARAM)priv->hwndChild); return newSel; } } if ((uMsg == ALF_CB_GETLBTEXTLEN || uMsg == CB_GETLBTEXTLEN) && priv) { return SendMessage(priv->hwndChild, CB_GETLBTEXTLEN, wParam, lParam); } if ((uMsg == ALF_CB_GETLBTEXT || uMsg == CB_GETLBTEXT) && priv) { return SendMessage(priv->hwndChild, CB_GETLBTEXT, wParam, lParam); } if (uMsg == WM_COMMAND && priv && (HWND)lParam == priv->hwndChild) { // XXX: for whatever reason, Win95 (and only win95 - doesn't happen on NT) // sends a wrong ID value in WPARAM. We fix it by replacing it with our own id. return SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetWindowLong(hwnd, GWL_ID), HIWORD(wParam)), (LPARAM)hwnd); } if (uMsg == ALF_WM_QUERYSIZE) { HDC hDc = GetDC(hwnd); HFONT font = (HFONT)SendMessage(hwnd, WM_GETFONT, 0, 0); if (font) { HFONT oldfont = SelectFont(hDc, font); TEXTMETRIC tm; ZeroMemory(&tm, sizeof(tm)); if (GetTextMetrics(hDc, &tm)) { SIZE *ps = (SIZE*)lParam; int cx = ALF_CentipointsToPixels(12000, priv->dpi); int cy = ps->cy = tm.tmHeight + 2*ALF_Compat_GetSystemMetricsForDpi( SM_CYEDGE, (UINT)priv->dpi) + 4 /* padding internal to the edit control */; if (ps->cx < cx) ps->cx = cx; if (ps->cy < cy) ps->cy = cy; } SelectFont(hDc, oldfont); } ReleaseDC(hwnd, hDc); return 0; } if (uMsg == WM_MEASUREITEM) { PMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam; lpmis->itemHeight = (UINT)ALF__ComboItemHeight(hwnd); return TRUE; } if (uMsg == WM_DRAWITEM) { LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam; if (lpdis->itemID != (UINT)-1) { // Empty item DWORD clrForeground = SetTextColor(lpdis->hDC, GetSysColor(lpdis->itemState & (ODS_SELECTED | ODS_FOCUS) ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); DWORD clrBackground = SetBkColor(lpdis->hDC, GetSysColor(lpdis->itemState & (ODS_SELECTED | ODS_FOCUS) ? COLOR_HIGHLIGHT : COLOR_WINDOW)); LONG y = lpdis->rcItem.top; LONG x = lpdis->rcItem.left; if (!ALF_Compat_IsMinWindowsVersion(4, 0)) { // baseline alignment for NT3.x/Win32s // ideally, this would only be applied for the ODS_COMOBOXEDIT state, // but that state is not sent pre-Win95. y += 1; } // add left padding like an edit control TEXTMETRIC tm; ZeroMemory(&tm, sizeof(tm)); if (GetTextMetrics(lpdis->hDC, &tm)) { if (tm.tmPitchAndFamily & (TMPF_VECTOR | TMPF_TRUETYPE)) x += ALF_GetAveCharWidth(lpdis->hDC) / 2; } // Get and display the text for the list item. int len = (int)SendMessage(lpdis->hwndItem, CB_GETLBTEXTLEN, (WPARAM)lpdis->itemID, 0); if (len != CB_ERR) { TCHAR *buf = (TCHAR*)LocalAlloc(LPTR, (SIZE_T)(len+1) * sizeof(TCHAR)); SendMessage(lpdis->hwndItem, CB_GETLBTEXT, lpdis->itemID, (LPARAM)buf); ExtTextOut(lpdis->hDC, x, y, ETO_CLIPPED | ETO_OPAQUE, &lpdis->rcItem, buf, (UINT)lstrlen(buf), NULL); LocalFree(buf); } // Restore the previous colors. SetTextColor(lpdis->hDC, clrForeground); SetBkColor(lpdis->hDC, clrBackground); } // If the item has the focus, draw the focus rectangle. if ((lpdis->itemState & ODS_FOCUS) && !(lpdis->itemState & ODS_NOFOCUSRECT)) DrawFocusRect(lpdis->hDC, &lpdis->rcItem); return TRUE; } if (uMsg == WM_WINDOWPOSCHANGED && priv) { WINDOWPOS *pos = (WINDOWPOS *)lParam; if (!(pos->flags & SWP_NOSIZE)) { // XXX: When resizing the combo box, it will improperly draw a selection. // this appears to be a well-known bug that is still not fixed, even in Win10. // workaround based on https://stackoverflow.com/questions/49603893/how-to-deal-with-invalidly-painted-combobox-control-in-win32-winapi LRESULT sel = SendMessage(priv->hwndChild, CB_GETEDITSEL, 0, 0); SendMessage(priv->hwndChild, CB_SETEDITSEL, 0, -1); // SWP_NOCOPYBITS because NT 3.51 doesn't properly repaint the drop-down button SetWindowPos(priv->hwndChild, NULL, 0, 0, pos->cx, 200, SWP_NOZORDER|SWP_NOACTIVATE|SWP_NOCOPYBITS); SendMessage(priv->hwndChild, CB_SETEDITSEL, 0, (LPARAM)sel); int heightOffset = 0; if (ALF_Compat_IsMinWindowsVersion(4, 0)) { heightOffset = - 2*ALF_Compat_GetSystemMetricsForDpi( SM_CYEDGE, (UINT)priv->dpi) - 2; } SendMessage(priv->hwndChild, CB_SETITEMHEIGHT, (WPARAM)-1, pos->cy + heightOffset); SendMessage(priv->hwndChild, CB_SETITEMHEIGHT, (WPARAM)0, (LPARAM)ALF__ComboItemHeight(hwnd)); } } if (uMsg == ALF_WM_DPICHANGE && priv) { priv->dpi = (int)lParam; ALF_InvalidateLayout(GetParent(hwnd)); return TRUE; } if (!ALF_Compat_IsMinWindowsVersion(4, 0)) { // HACK to draw the space between edit control and button as COLOR_BTNFACE if (uMsg == WM_CTLCOLORLISTBOX && priv && !(GetWindowLong(priv->hwndChild, GWL_STYLE) & 1)) { return (LRESULT)GetSysColorBrush(COLOR_BTNFACE); } } if (ALF_ShouldMessageBubble(hwnd, uMsg, wParam, lParam)) { return SendMessage(GetParent(hwnd), uMsg, wParam, lParam); } return DefWindowProc(hwnd, uMsg, wParam, lParam); } static HWND ALF_InternalAddComboBox(HWND win, WORD id, int x, int y, DWORD style, const TCHAR *defaultText) { ALFComboCreateParams cp; cp.comboStyle = style; HWND hwndCombo = ALF_CreateControlWindow(WS_EX_CONTROLPARENT, TEXT(""), WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0, win, (HMENU)(ULONG_PTR)id, ALF__ComboWindowProc, &cp); if (defaultText) SetWindowText(hwndCombo, defaultText); ALF_AddWidget(win, x, y, hwndCombo, 0, 0, ALF_LAYOUT_SIZE_QUERY | ALF_LAYOUT_INHERITFONT | ALF_LAYOUT_SENDDPICHANGE); return hwndCombo; } HWND ALF_AddEditableComboBox(HWND win, WORD id, int x, int y, const TCHAR *defaultText) { return ALF_InternalAddComboBox(win, id, x, y, CBS_DROPDOWN, defaultText); } HWND ALF_AddSelectionOnlyComboBox(HWND win, WORD id, int x, int y) { return ALF_InternalAddComboBox(win, id, x, y, CBS_DROPDOWNLIST, NULL); } int /* index */ ALF_ComboBoxAddString(HWND combo, const TCHAR *text) { return (int)SendMessage(combo, ALF_CB_ADDSTRING, 0, (LPARAM)text); } TCHAR * ALF_ComboBoxCurrentText(HWND combo) { LRESULT style = SendMessage(combo, ALF_CB_GETSTYLE, 0, 0); if (style & CBS_DROPDOWNLIST) { int i = ALF_ComboBoxCurrentIndex(combo); if (i >= 0) { return ALF_ComboBoxStringForIndex(combo, i); } } return ALF_Text(combo); } void ALF_ComboBoxSetText(HWND combo, const TCHAR *text) { LRESULT index = SendMessage(combo, ALF_CB_FINDSTRINGEXACT, (WPARAM)-1, (LPARAM)text); if (index >= 0) { SendMessage(combo, ALF_CB_SETCURSEL, (WPARAM)index, 0); } else { SetWindowText(combo, text); } } int ALF_ComboBoxCurrentIndex(HWND combo) { return (int)SendMessage(combo, ALF_CB_GETCURSEL, 0, 0); } void ALF_ComboBoxSetCurrentIndex(HWND combo, int index) { SendMessage(combo, ALF_CB_SETCURSEL, (WPARAM)index, 0); } TCHAR * // free with ALF_Free ALF_ComboBoxStringForIndex(HWND combo, int index) { int len = (int)SendMessage(combo, ALF_CB_GETLBTEXTLEN, (WPARAM)index, 0); if (len > 0) { TCHAR* buf = ALF_New(TCHAR, (SIZE_T)len + 1); if (SendMessage(combo, ALF_CB_GETLBTEXT, (WPARAM)index, (LPARAM)buf)) { return buf; } else { ALF_Free(buf); } } return NULL; } void ALF_ComboBoxInsertString(HWND combo, int index, const TCHAR *text) { SendMessage(combo, ALF_CB_INSERTSTRING, (WPARAM)index, (LPARAM)text); } void ALF_ComboBoxRemoveString(HWND combo, int index) { SendMessage(combo, ALF_CB_DELETESTRING, (WPARAM)index, 0); }