#include "alfpriv.h" typedef struct { ALFAPP app; DWORD comboStyle; } ALFComboCreateParams; static int ALF__ComboItemHeight(HWND hwnd) { int height = 0; 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)) { height = tm.tmHeight; } SelectFont(hDc, oldfont); } ReleaseDC(hwnd, hDc); return height; } static LRESULT CALLBACK ALF__ComboWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_NCCREATE) { ALFComboCreateParams *params = (ALFComboCreateParams*)((CREATESTRUCT*)lParam)->lpCreateParams; SetWindowLongPtr(hwnd, 0, (LONG_PTR)params->app); } ALFAPP app = (ALFAPP)GetWindowLongPtr(hwnd, 0); HWND hwndChild = (HWND)GetWindowLongPtr(hwnd, sizeof(void*)); if (uMsg == WM_CREATE) { ALFComboCreateParams *params = (ALFComboCreateParams*)((CREATESTRUCT*)lParam)->lpCreateParams; HWND hwndCombo = CreateWindowEx(0, TEXT("COMBOBOX"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_OWNERDRAWFIXED | CBS_HASSTRINGS | params->comboStyle, 0, 0, 0, 200 /* FIXME needed for commctl32 v5, what is the best value here? */, hwnd, (HMENU) GetWindowLongPtrW(hwnd, GWLP_ID), ((CREATESTRUCT*)lParam)->hInstance, NULL); SetWindowLongPtr(hwnd, sizeof(void*), (LONG_PTR)hwndCombo); } if (uMsg == WM_ENABLE && hwndChild) { EnableWindow(hwndChild, (BOOL)wParam); return 0; } if (uMsg == WM_SETTEXT && hwndChild) { LRESULT index = SendMessage(hwndChild, CB_FINDSTRINGEXACT, (WPARAM)-1, lParam); if (index >= 0) { SendMessage(hwnd, CB_SETCURSEL, (WPARAM)index, 0); } else { return SendMessage(hwndChild, WM_SETTEXT, wParam, lParam); } } if (uMsg == WM_GETTEXTLENGTH && hwndChild) { if ((GetWindowLong(hwndChild, GWL_STYLE) & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) { int index = SendMessage(hwndChild, CB_GETCURSEL, 0, 0); if (index != CB_ERR) { return SendMessage(hwndChild, CB_GETLBTEXTLEN, (WPARAM)index, 0); } } return SendMessage(hwndChild, WM_GETTEXTLENGTH, wParam, lParam); } if (uMsg == WM_GETTEXT && hwndChild) { if ((GetWindowLong(hwndChild, GWL_STYLE) & CBS_DROPDOWNLIST) == CBS_DROPDOWNLIST) { int index = SendMessage(hwndChild, CB_GETCURSEL, 0, 0); if (index != CB_ERR) { return SendMessage(hwndChild, CB_GETLBTEXT, (WPARAM)index, lParam); } } return SendMessage(hwndChild, WM_GETTEXT, wParam, lParam); } if (uMsg == WM_SETFONT && hwndChild) { SendMessage(hwndChild, WM_SETFONT, wParam, lParam); int h = ALF__ComboItemHeight(hwnd); if (h) SendMessage(hwndChild, CB_SETITEMHEIGHT, (WPARAM)0, h); return 0; } if (uMsg == WM_GETFONT && hwndChild) { return SendMessage(hwndChild, WM_GETFONT, wParam, lParam); } if (uMsg == CB_ADDSTRING && hwndChild) { return SendMessage(hwndChild, CB_ADDSTRING, wParam, lParam); } if (uMsg == CB_INSERTSTRING && hwndChild) { return SendMessage(hwndChild, CB_INSERTSTRING, wParam, lParam); } if (uMsg == CB_GETCURSEL && hwndChild) { return SendMessage(hwndChild, CB_GETCURSEL, wParam, lParam); } if (uMsg == CB_SETCURSEL && hwndChild) { LRESULT curSel = SendMessage(hwndChild, CB_GETCURSEL, wParam, lParam); if ((WPARAM)curSel != wParam) { LRESULT newSel = SendMessage(hwndChild, CB_SETCURSEL, wParam, lParam); SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(GetWindowLong(hwndChild, GWL_ID), CBN_SELCHANGE), (LPARAM)hwndChild); return newSel; } } if (uMsg == CB_GETLBTEXTLEN && hwndChild) { return SendMessage(hwndChild, CB_GETLBTEXTLEN, wParam, lParam); } if (uMsg == CB_GETLBTEXT && hwndChild) { return SendMessage(hwndChild, CB_GETLBTEXT, wParam, lParam); } if (uMsg == WM_COMMAND && (HWND)lParam == hwndChild) { return SendMessage(GetParent(hwnd), WM_COMMAND, 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; if (!ps->cx) { ps->cx = ALF_CentipointsToPixels(GetParent(hwnd), 12000); } if (!ps->cy) { ps->cy = tm.tmHeight + 2*app->compatFn->GetSystemMetricsForDpi( SM_CYEDGE, ALF_CentipointsToPixels(GetParent(hwnd), 7200)) + 4 /* padding internal to the edit control */ + 2 /* external padding to line up with themed button */; } } SelectFont(hDc, oldfont); } ReleaseDC(hwnd, hDc); return 0; } if (uMsg == WM_MEASUREITEM) { PMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam; int h = ALF__ComboItemHeight(hwnd); if (h) lpmis->itemHeight = h; 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; // 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, (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, 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 && hwndChild) { WINDOWPOS *pos = (WINDOWPOS *)lParam; if (!(pos->flags & SWP_NOSIZE)) { RECT r; GetWindowRect(hwndChild, &r); SetWindowPos(hwndChild, NULL, 0, 1, pos->cx, r.bottom - r.top, SWP_NOZORDER|SWP_NOACTIVATE); SendMessage(hwndChild, CB_SETITEMHEIGHT, (WPARAM)-1, pos->cy - 8); int h = ALF__ComboItemHeight(hwnd); if (h) SendMessage(hwndChild, CB_SETITEMHEIGHT, (WPARAM)0, h); } } return DefWindowProc(hwnd, uMsg, wParam, lParam); } void ALF_RegisterComboClass(ALFAPP app) { WNDCLASS cls; ZeroMemory(&cls, sizeof(cls)); TCHAR classNameBuf[256]; ALF_BuildRandomClassName(TEXT("ALFComboBox."), classNameBuf); cls.hInstance = app->hInstance; cls.hCursor = LoadCursor(NULL, (LPTSTR)IDC_ARROW); cls.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); cls.lpszClassName = classNameBuf; cls.cbWndExtra = sizeof(void*)*2; cls.lpfnWndProc = ALF__ComboWindowProc; ATOM classatom = RegisterClass(&cls); if (!classatom) MessageBox(NULL, TEXT("FATAL: Could not register Combo class"), NULL, MB_OK); app->comboClass = MAKEINTATOM(classatom); } static HWND ALF_InternalAddComboBox(HWND win, WORD id, UINT x, UINT y, DWORD style, const TCHAR *defaultText) { ALFComboCreateParams cp; cp.app = ALF_ApplicationFromWindow(win); cp.comboStyle = style; HWND hwndCombo = CreateWindowEx(WS_EX_CONTROLPARENT, cp.app->comboClass, TEXT(""), WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN, 0, 0, 0, 0, win, (HMENU)(int)id, (HINSTANCE)GetWindowLongPtr(win, GWLP_HINSTANCE), &cp); if (defaultText) SetWindowText(hwndCombo, defaultText); ALFAddWidgetParams p; ZeroMemory(&p, sizeof(p)); p.hwnd = hwndCombo; p.x = x; p.y = y; p.width = 0; p.height = 0; p.flags = ALF_QUERYSIZE | ALF_MESSAGEFONT; ALF_AddWidgetEx(win, &p); return hwndCombo; } HWND ALF_AddEditableComboBox(HWND win, WORD id, UINT x, UINT y, const TCHAR *defaultText) { return ALF_InternalAddComboBox(win, id, x, y, CBS_DROPDOWN, defaultText); } HWND ALF_AddSelectionOnlyComboBox(HWND win, WORD id, UINT x, UINT y) { return ALF_InternalAddComboBox(win, id, x, y, CBS_DROPDOWNLIST, NULL); } int /* index */ ALF_ComboBoxAddString(HWND combo, const TCHAR *text) { return (int)SendMessage(combo, CB_ADDSTRING, 0, (LPARAM)text); } TCHAR * ALF_ComboBoxCurrentText(HWND combo) { return ALF_Text(combo); } void ALF_ComboBoxSetText(HWND combo, const TCHAR *text) { return ALF_SetText(combo, text); } int ALF_ComboBoxCurrentIndex(HWND combo) { return (int)SendMessage(combo, CB_GETCURSEL, 0, 0); } void ALF_ComboBoxSetCurrentIndex(HWND combo, int index) { SendMessage(combo, CB_SETCURSEL, (WPARAM)index, 0); } TCHAR * // free with HeapFree ALF_ComboBoxStringForIndex(HWND combo, int index) { int len = (int)SendMessage(combo, CB_GETLBTEXTLEN, (WPARAM)index, 0); if (len > 0) { TCHAR* buf = (TCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY|HEAP_GENERATE_EXCEPTIONS, (len + 1)*sizeof(WCHAR)); if (SendMessage(combo, CB_GETLBTEXT, (WPARAM)index, (LPARAM)buf)) { return buf; } else { HeapFree(GetProcessHeap(), 0, buf); } } return NULL; } void ALF_ComboBoxInsertString(HWND combo, int index, const TCHAR *text) { SendMessage(combo, CB_INSERTSTRING, (WPARAM)index, (LPARAM)text); } void ALF_ComboBoxRemoveString(HWND combo, int index) { SendMessage(combo, CB_DELETESTRING, (WPARAM)index, 0); }