#include "ctlspriv.h" #define EDIT_SELECTALL( hwnd ) Edit_SetSel(hwnd, 0, 0); \ Edit_SetSel(hwnd, 0, -1); const TCHAR c_szComboBox[] = TEXT("combobox"); const TCHAR c_szComboBoxEx[] = WC_COMBOBOXEX; #ifdef DPITEST #define ComboEx_IsDPIScaled() TRUE #else #define ComboEx_IsDPIScaled() (CCDPIScale(pce->ci)) #endif #define COMBO_MARGIN 4 #define COMBO_WIDTH g_cxSmIcon #define COMBO_HEIGHT g_cySmIcon #define COMBO_BORDER 3 typedef struct { LPTSTR pszText; int iImage; int iSelectedImage; int iOverlay; int iIndent; LPARAM lParam; } CEITEM, *PCEITEM; typedef struct { CCONTROLINFO ci; HWND hwndCombo; HWND hwndEdit; DWORD dwExStyle; HIMAGELIST himl; HFONT hFont; int cxIndent; WPARAM iSel; CEITEM cei; BOOL fEditItemSet :1; BOOL fEditChanged :1; BOOL fFontCreated :1; BOOL fInEndEdit :1; BOOL fInDrop :1; } COMBOEX, *PCOMBOBOXEX; void ComboEx_OnWindowPosChanging(PCOMBOBOXEX pce, LPWINDOWPOS pwp); HFONT ComboEx_GetFont(PCOMBOBOXEX pce); BOOL ComboEx_OnGetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem); int ComboEx_ComputeItemHeight(PCOMBOBOXEX pce, BOOL); int ComboEx_OnFindStringExact(PCOMBOBOXEX pce, int iStart, LPCTSTR lpsz); int WINAPI ShellEditWordBreakProc(LPTSTR lpch, int ichCurrent, int cch, int code); LRESULT CALLBACK ComboSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); int ComboEx_StrCmp(PCOMBOBOXEX pce, LPCTSTR psz1, LPCTSTR psz2); #define ComboEx_Editable(pce) (((pce)->ci.style & CBS_DROPDOWNLIST) == CBS_DROPDOWN) void ComboEx_OnSetFont(PCOMBOBOXEX pce, HFONT hFont, BOOL fRedraw) { int iHeight; HFONT hfontOld = NULL; if (pce->fFontCreated) hfontOld = ComboEx_GetFont(pce); if (!hFont) { LOGFONT lf; SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE); hFont = CreateFontIndirect(&lf); pce->fFontCreated = TRUE; } else { pce->fFontCreated = FALSE; } pce->ci.uiCodePage = GetCodePageForFont(hFont); SendMessage(pce->hwndCombo, WM_SETFONT, (WPARAM)hFont, fRedraw); if (pce->hwndEdit) { SendMessage(pce->hwndEdit, WM_SETFONT, (WPARAM)hFont, fRedraw); SendMessage(pce->hwndEdit, EM_SETMARGINS, EC_USEFONTINFO, 0L); } iHeight = ComboEx_ComputeItemHeight(pce, FALSE); SendMessage(pce->ci.hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, (LPARAM)iHeight); SendMessage(pce->hwndCombo, CB_SETITEMHEIGHT, 0, (LPARAM)iHeight); // do this last so that we don't have a nuked font as we try to create the new one if (hfontOld) DeleteObject(hfontOld); } void ComboEx_OnDestroy(PCOMBOBOXEX pce) { // don't need do destroy hwndCombo.. it will be destroyed along with us. SendMessage(pce->hwndCombo, CB_RESETCONTENT, 0, 0); // we may still have string allocated for the item in the edit box so free it if (pce->cei.pszText) Str_Set(&(pce->cei.pszText), NULL); if (pce->fFontCreated) { DeleteObject(ComboEx_GetFont(pce)); } if (pce->hwndEdit) RemoveWindowSubclass(pce->hwndEdit, EditSubclassProc, 0); if (pce->hwndCombo) RemoveWindowSubclass(pce->hwndCombo, ComboSubclassProc, 0); SetWindowPtr(pce->ci.hwnd, 0, 0); LocalFree(pce); } // this gets the client rect without the scrollbar part and the border void ComboEx_GetComboClientRect(PCOMBOBOXEX pce, LPRECT lprc) { GetClientRect(pce->hwndCombo, lprc); InflateRect(lprc, -g_cxEdge, -g_cyEdge); lprc->right -= g_cxScrollbar; } // returns the edit box (creating it if necessary) or NULL if the combo does // not have an edit box HWND ComboEx_GetEditBox(PCOMBOBOXEX pce) { HFONT hfont; DWORD dwStyle; DWORD dwExStyle = 0; if (pce->hwndEdit) return(pce->hwndEdit); if (!ComboEx_Editable(pce)) return(NULL); dwStyle = WS_VISIBLE | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT; if (pce->ci.style & CBS_AUTOHSCROLL) { dwStyle |= ES_AUTOHSCROLL; } if (pce->ci.style & CBS_OEMCONVERT) { dwStyle |= ES_OEMCONVERT; } dwExStyle = pce->ci.dwExStyle & (WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR); pce->hwndEdit = CreateWindowEx(dwExStyle, WC_EDIT, c_szNULL, dwStyle, 0, 0, 0, 0, pce->hwndCombo, IntToPtr_(HMENU, GetDlgCtrlID(pce->ci.hwnd)), HINST_THISDLL, 0); if (!pce->hwndEdit || !SetWindowSubclass(pce->hwndEdit, EditSubclassProc, 0, (DWORD_PTR)pce)) { return NULL; } // Override the edit's theme with combobox SetWindowTheme(pce->hwndEdit, L"Combobox", NULL); hfont = ComboEx_GetFont(pce); if (hfont) FORWARD_WM_SETFONT(pce->hwndEdit, hfont, FALSE, SendMessage); return(pce->hwndEdit); } /// /// the edit box handling... /* we want the edit box up on CBN_SETFOCUS and CBN_CLOSEUP remove it on CBN_DROPDOWN and on CBN_KILLFOCUS this assumes that CBN_SETFOCUS and CBN_KILLFOCUS will come before and after CBN_DROPDOWN and CBN_CLOSEUP respectively */ // Really a BOOL return value LRESULT ComboEx_EndEdit(PCOMBOBOXEX pce, int iWhy) { NMCBEENDEDIT nm; LRESULT fRet; if (!ComboEx_GetEditBox(pce)) return(FALSE); pce->fInEndEdit = TRUE; GetWindowText(pce->hwndEdit, nm.szText, ARRAYSIZE(nm.szText)); nm.fChanged = pce->fEditChanged; nm.iWhy = iWhy; nm.iNewSelection = ComboEx_OnFindStringExact(pce, ComboBox_GetCurSel(pce->hwndCombo) - 1, nm.szText); fRet = BOOLFROMPTR(CCSendNotify(&pce->ci, CBEN_ENDEDIT, &nm.hdr)); pce->fInEndEdit = FALSE; if (!fRet) { if (nm.iNewSelection != ComboBox_GetCurSel(pce->hwndCombo)) { if (nm.iNewSelection != -1) { SendMessage(pce->ci.hwnd, CB_SETCURSEL, nm.iNewSelection, 0); } else { //if the selection is -1 and if we do a CB_SETCURSEL on comboboxex then it nukes the text in //the edit window. Which is not the desired behavior. We need to update the Current Selection in the //child combobox but leave the text as it is. SendMessage(pce->hwndCombo, CB_SETCURSEL, nm.iNewSelection,0); } } pce->fEditChanged = FALSE; } InvalidateRect(pce->hwndCombo, NULL, FALSE); return(fRet); } void ComboEx_SizeEditBox(PCOMBOBOXEX pce) { RECT rc; int cxIcon = 0, cyIcon = 0; ComboEx_GetComboClientRect(pce, &rc); InvalidateRect(pce->hwndCombo, &rc, TRUE); // erase so that the selection highlight is erased if (pce->himl && !(pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT)) { // Make room for icons. CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon); if (cxIcon) cxIcon += COMBO_MARGIN; } // combobox edit field is one border in from the entire combobox client // rect -- thus add one border to edit control's left side rc.left += g_cxBorder + cxIcon; rc.bottom -= g_cyBorder; rc.top = rc.bottom - ComboEx_ComputeItemHeight(pce, TRUE) - g_cyBorder; SetWindowPos(pce->hwndEdit, NULL, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc), SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW); } BOOL ComboEx_GetCurSelText(PCOMBOBOXEX pce, LPTSTR pszText, int cchText) { COMBOBOXEXITEM cei; BOOL bRet = TRUE; cei.mask = CBEIF_TEXT; cei.pszText = pszText; cei.cchTextMax = cchText; cei.iItem = (INT)ComboBox_GetCurSel(pce->hwndCombo); if (cei.iItem == -1 ) { pszText[0] = 0; bRet = FALSE; } else { ComboEx_OnGetItem(pce, &cei); } return bRet; } void ComboEx_UpdateEditText(PCOMBOBOXEX pce, BOOL fClearOnNoSel) { if (!pce->fInEndEdit) { TCHAR szText[CBEMAXSTRLEN]; HWND hwndEdit = ComboEx_Editable(pce) ? pce->hwndEdit : pce->hwndCombo; if (ComboEx_GetCurSelText(pce, szText, ARRAYSIZE(szText)) || fClearOnNoSel) { SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)szText); EDIT_SELECTALL( hwndEdit ); } } } BOOL ComboEx_BeginEdit(PCOMBOBOXEX pce) { if (!ComboEx_GetEditBox(pce)) return(FALSE); SetFocus(pce->hwndEdit); return(TRUE); } BOOL ComboSubclass_HandleButton(PCOMBOBOXEX pce, UINT uMsg, WPARAM wParam, LPARAM lParam) { RECT rc; POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; ComboEx_GetComboClientRect(pce, &rc); InflateRect(&rc, g_cxEdge, g_cyEdge); if (PtInRect(&rc, pt)) { // // CheckForDragBegin yields, so we must revalidate on the way back. // HWND hwndCombo = pce->hwndCombo; if (CheckForDragBegin(pce->hwndCombo, LOWORD(lParam), HIWORD(lParam))) { NMCBEDRAGBEGIN nmcbebd; LRESULT fRet; nmcbebd.iItemid = -1; GetWindowText(pce->hwndEdit, nmcbebd.szText, ARRAYSIZE(nmcbebd.szText)); fRet = CCSendNotify(&pce->ci, CBEN_DRAGBEGIN, &nmcbebd.hdr); return TRUE; } // CheckForDragBegin yields, so revalidate before continuing else if (IsWindow(hwndCombo)) { if (uMsg == WM_LBUTTONDOWN) { // Post a fake WM_LBUTTONUP message because CheckForDragBegin may have // removed it from the combo's message queue but the combo expects to // get it. PostMessage(hwndCombo, WM_LBUTTONUP, 0, 0); } // a click on our border should start edit mode as well if (ComboEx_Editable(pce)) { if (!ComboEx_BeginEdit(pce)) SetFocus(pce->hwndCombo); return TRUE; } return FALSE; } } return FALSE; } BOOL ComboSubclass_HandleCommand(PCOMBOBOXEX pce, WPARAM wParam, LPARAM lParam) { UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam); UINT uCmd = GET_WM_COMMAND_CMD(wParam, lParam); HWND hwnd = GET_WM_COMMAND_HWND(wParam, lParam); switch (uCmd) { case EN_SETFOCUS: if (!pce->fInDrop) { EDIT_SELECTALL( pce->hwndEdit ); CCSendNotify(&pce->ci, CBEN_BEGINEDIT, NULL); pce->fEditChanged = FALSE; } break; case EN_KILLFOCUS: { HWND hwndFocus; hwndFocus = GetFocus(); if (hwndFocus != pce->hwndCombo) { ComboEx_EndEdit(pce, CBENF_KILLFOCUS); SendMessage(pce->hwndCombo, WM_KILLFOCUS, (WPARAM)hwndFocus, 0); } break; } case EN_CHANGE: { TCHAR szTextOrig[CBEMAXSTRLEN]; TCHAR szTextNow[CBEMAXSTRLEN]; WPARAM iItem; iItem = ComboBox_GetCurSel(pce->hwndCombo); if(iItem == -1) { if (pce->fEditItemSet && pce->cei.pszText) { Str_GetPtr(pce->cei.pszText, szTextOrig, ARRAYSIZE(szTextOrig)); } else { szTextOrig[0] = TEXT('\0'); } } else { ComboEx_GetCurSelText(pce,szTextOrig, ARRAYSIZE(szTextOrig)); } GetWindowText(pce->hwndEdit, szTextNow, ARRAYSIZE(szTextNow)); pce->fEditChanged = (ComboEx_StrCmp(pce, szTextOrig, szTextNow) != 0); SendMessage(pce->ci.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(idCmd, pce->ci.hwnd, CBN_EDITCHANGE)); break; } } return(hwnd == pce->hwndEdit); } LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { PCOMBOBOXEX pce = (PCOMBOBOXEX)dwRefData; if (uMsg == WM_SETFONT || uMsg == WM_WININICHANGE) { return DefSubclassProc(hwnd, uMsg, wParam, lParam); } switch(uMsg) { case WM_DESTROY: RemoveWindowSubclass(hwnd, EditSubclassProc, 0); break; case WM_CHAR: switch ((TCHAR)wParam) { case TEXT('\n'): case TEXT('\r'): // return... don't go to wndproc because // the edit control beeps on enter return 0; } break; case WM_SIZE: if (GetFocus() != hwnd) { Edit_SetSel(pce->hwndEdit, 0, 0); // makesure everything is scrolled over first } break; case WM_KEYDOWN: switch(wParam) { case VK_RETURN: if (!ComboEx_EndEdit(pce, CBENF_RETURN)) // we know we have an edit window, so FALSE return means // app returned FALSE to CBEN_ENDEDIT notification ComboEx_BeginEdit(pce); break; case VK_ESCAPE: pce->fEditChanged = FALSE; if (!ComboEx_EndEdit(pce, CBENF_ESCAPE)) { if(pce->fEditItemSet) { if(pce->cei.pszText) { SendMessage(pce->hwndEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)pce->cei.pszText); EDIT_SELECTALL( pce->hwndEdit ); } RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); }else { ComboEx_BeginEdit(pce); } } break; // Pass these to the combobox itself to make it work properly... case VK_HOME: case VK_END: if (!pce->fInDrop) break; case VK_F4: case VK_UP: case VK_DOWN: case VK_PRIOR: case VK_NEXT: if (pce->hwndCombo) return SendMessage(pce->hwndCombo, uMsg, wParam, lParam); break; } break; case WM_LBUTTONDOWN: if (GetFocus() != pce->hwndEdit) { SetFocus(pce->hwndEdit); // since we disabled autoselection on first click in address bar, // we should not eat this message. This allows the dragging to begin with // the first click. return(0L); // eat this message } break; case WM_SYSKEYDOWN: switch(wParam) { // Pass these to the combobox itself to make it work properly... case VK_UP: case VK_DOWN: { LRESULT lR; if (pce->hwndCombo) { lR=SendMessage(pce->hwndCombo, uMsg, wParam, lParam); //notify of navigation key usage CCNotifyNavigationKeyUsage(&(pce->ci), UISF_HIDEFOCUS); return lR; } } } } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } LRESULT ComboEx_GetLBText(PCOMBOBOXEX pce, UINT uMsg, WPARAM wParam, LPARAM lParam) { DWORD cchResult = CB_ERR; TCHAR szText[CBEMAXSTRLEN]; COMBOBOXEXITEM cei; cei.mask = CBEIF_TEXT; cei.pszText = szText; cei.cchTextMax = ARRAYSIZE(szText); cei.iItem = (INT)wParam; if (ComboEx_OnGetItem(pce, &cei)) { cchResult = lstrlen(szText); if (lParam && uMsg == CB_GETLBTEXT) { // REVIEW: trusts that the lParam points to a buffer of sufficient // size to support the string and null terminator. StringCchCopy((LPTSTR)lParam, cchResult+1, szText); } } return cchResult; } LRESULT CALLBACK ComboSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) { PCOMBOBOXEX pce = (PCOMBOBOXEX)dwRefData; switch (uMsg) { case CB_GETLBTEXT: case CB_GETLBTEXTLEN: return ComboEx_GetLBText(pce, uMsg, wParam, lParam); case WM_RBUTTONDOWN: case WM_LBUTTONDOWN: if (ComboSubclass_HandleButton(pce, uMsg, wParam, lParam)) { return 0; } break; case WM_PAINT: if (pce->hwndEdit) { RedrawWindow(pce->hwndEdit, NULL, NULL, RDW_INVALIDATE); } break; case WM_COMMAND: if (ComboSubclass_HandleCommand(pce, wParam, lParam)) { return 0; } break; case WM_DESTROY: RemoveWindowSubclass(hwnd, ComboSubclassProc, 0); break; case WM_SETCURSOR: if (pce) { NMMOUSE nm = {0}; nm.dwHitInfo = lParam; if (CCSendNotify(&pce->ci, NM_SETCURSOR, &nm.hdr)) { return 0; } } break; } return DefSubclassProc(hwnd, uMsg, wParam, lParam); } BOOL ComboEx_OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) { PCOMBOBOXEX pce; DWORD dwStyle; DWORD dwExStyle = 0; pce = (PCOMBOBOXEX)LocalAlloc(LPTR, sizeof(COMBOEX)); if (!pce) return FALSE; SetWindowPtr(hwnd, 0, pce); // force off borders off ourself lpcs->style &= ~(WS_BORDER | WS_VSCROLL | WS_HSCROLL | CBS_UPPERCASE | CBS_LOWERCASE); SetWindowLong(hwnd, GWL_STYLE, lpcs->style); CIInitialize(&pce->ci, hwnd, lpcs); // or in CBS_SIMPLE because we can never allow the sub combo box // to have just drop down.. it's either all simple or dropdownlist dwStyle = CBS_OWNERDRAWFIXED | CBS_SIMPLE | CBS_NOINTEGRALHEIGHT | WS_VISIBLE |WS_VSCROLL | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; dwStyle |= (lpcs->style & (CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD)); if ((lpcs->style & CBS_DROPDOWNLIST) == CBS_SIMPLE) dwStyle |= (lpcs->style & (CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_UPPERCASE | CBS_LOWERCASE)); dwExStyle = lpcs->dwExStyle & (WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR); pce->hwndCombo = CreateWindowEx(dwExStyle, WC_COMBOBOX, lpcs->lpszName, dwStyle, 0, 0, lpcs->cx, lpcs->cy, hwnd, lpcs->hMenu, lpcs->hInstance, 0); if (!pce->hwndCombo || !SetWindowSubclass(pce->hwndCombo, ComboSubclassProc, 0, (DWORD_PTR)pce) || (!ComboEx_GetEditBox(pce) && ComboEx_Editable(pce))) { ComboEx_OnDestroy(pce); return FALSE; } ComboEx_OnSetFont(pce, NULL, FALSE); pce->cxIndent = 10; pce->iSel = -1; ComboEx_OnWindowPosChanging(pce, NULL); return TRUE; } HIMAGELIST ComboEx_OnSetImageList(PCOMBOBOXEX pce, HIMAGELIST himl) { int iHeight; HIMAGELIST himlOld = pce->himl; pce->himl = himl; iHeight = ComboEx_ComputeItemHeight(pce, FALSE); SendMessage(pce->ci.hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, iHeight); SendMessage(pce->hwndCombo, CB_SETITEMHEIGHT, 0, iHeight); InvalidateRect(pce->hwndCombo, NULL, TRUE); if (pce->hwndEdit) ComboEx_SizeEditBox(pce); return himlOld; } void ComboEx_OnDrawItem(PCOMBOBOXEX pce, LPDRAWITEMSTRUCT pdis) { HDC hdc = pdis->hDC; RECT rc = pdis->rcItem; TCHAR szText[CBEMAXSTRLEN]; int offset = 0; int xString, yString, xCombo; int cxIcon = 0, cyIcon = 0; int iLen; BOOL fSelected = FALSE; SIZE sizeText; COMBOBOXEXITEM cei; BOOL fNoText = FALSE; BOOL fEnabled = IsWindowEnabled(pce->hwndCombo); BOOL fRTLReading = FALSE; UINT OldTextAlign; // Setup the dc before we use it. fRTLReading = GetWindowLong(pdis->hwndItem, GWL_EXSTYLE) & WS_EX_RTLREADING; if (fRTLReading) { OldTextAlign = GetTextAlign(hdc); SetTextAlign(hdc, OldTextAlign|TA_RTLREADING); } rc.top += g_cyBorder; szText[0] = 0; if (pdis->itemID != -1) { cei.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_OVERLAY | CBEIF_SELECTEDIMAGE| CBEIF_INDENT; cei.pszText = szText; cei.cchTextMax = ARRAYSIZE(szText); cei.iItem = (INT)pdis->itemID; ComboEx_OnGetItem(pce, &cei); if (pce->iSel == (int)pdis->itemID || ((pce->iSel == -1) && ((int)pdis->itemID == ComboBox_GetCurSel(pce->hwndCombo)))) fSelected = TRUE; } else { if (pce->fEditItemSet) { cei.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_OVERLAY | CBEIF_SELECTEDIMAGE| CBEIF_INDENT; cei.pszText = szText; cei.cchTextMax = ARRAYSIZE(szText); cei.iItem = (INT)pdis->itemID; ComboEx_OnGetItem(pce, &cei); } } if (pce->himl && !(pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT)) { CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon); if (cxIcon) cxIcon += COMBO_MARGIN; } // if we're not drawing the edit box, figure out how far to indent // over if (!(pdis->itemState & ODS_COMBOBOXEDIT)) { offset = (pce->cxIndent * cei.iIndent) + COMBO_BORDER; } else { if (pce->hwndEdit) fNoText = TRUE; if (pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT) cxIcon = 0; } xCombo = rc.left + offset; rc.left = xString = xCombo + cxIcon; iLen = lstrlen(szText); GetTextExtentPoint(hdc, szText, iLen, &sizeText); rc.right = rc.left + sizeText.cx; rc.left--; rc.right++; if (pdis->itemAction != ODA_FOCUS) { int yMid; BOOL fTextHighlight = FALSE;; yMid = (rc.top + rc.bottom) / 2; // center the string within rc yString = yMid - (sizeText.cy/2); if (pdis->itemState & ODS_SELECTED) { if (!(pdis->itemState & ODS_COMBOBOXEDIT) || !ComboEx_Editable(pce)) { fTextHighlight = TRUE; } } if ( !fEnabled ) { SetBkColor(hdc, g_clrBtnFace); SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); } else { SetBkColor(hdc, GetSysColor(fTextHighlight ? COLOR_HIGHLIGHT : COLOR_WINDOW)); SetTextColor(hdc, GetSysColor(fTextHighlight ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT)); } if ((pdis->itemState & ODS_COMBOBOXEDIT) && (rc.right > pdis->rcItem.right)) { // Need to clip as user does not! rc.right = pdis->rcItem.right; } if (!fNoText) { ExtTextOut(hdc, xString, yString, ETO_OPAQUE | ETO_CLIPPED, &rc, szText, iLen, NULL); } if (pce->himl && (pdis->itemID != -1 || pce->fEditItemSet) && !((pce->dwExStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT)) && (pdis->itemState & ODS_COMBOBOXEDIT))) { DWORD dwDrawFlags = ILD_NORMAL; if ((pdis->itemState & ODS_COMBOBOXEDIT) && !fEnabled) { dwDrawFlags = ILD_TRANSPARENT; } if (pce->himl && (pdis->itemID != -1 || pce->fEditItemSet) && !((pce->dwExStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT)))) { if (pdis->itemState & ODS_SELECTED) dwDrawFlags |= ILD_SELECTED | ILD_FOCUS; if (ComboEx_IsDPIScaled()) { dwDrawFlags |= ILD_DPISCALE; } ImageList_Draw(pce->himl, (fSelected) ? cei.iSelectedImage : cei.iImage, hdc, xCombo, yMid - (cyIcon/2), INDEXTOOVERLAYMASK(cei.iOverlay) | dwDrawFlags); } } } if ((pdis->itemAction == ODA_FOCUS || (pdis->itemState & ODS_FOCUS)) && !(CCGetUIState(&(pce->ci)) & UISF_HIDEFOCUS)) { if (!fNoText) { DrawFocusRect(hdc, &rc); } } // Restore the text align in the dc. if (fRTLReading) { SetTextAlign(hdc, OldTextAlign); } } int ComboEx_ComputeItemHeight(PCOMBOBOXEX pce, BOOL fTextOnly) { HDC hdc; HFONT hfontOld; int dyDriveItem; SIZE siz; hdc = GetDC(NULL); hfontOld = ComboEx_GetFont(pce); if (hfontOld) hfontOld = SelectObject(hdc, hfontOld); GetTextExtentPoint(hdc, TEXT("W"), 1, &siz); dyDriveItem = siz.cy; if (hfontOld) SelectObject(hdc, hfontOld); ReleaseDC(NULL, hdc); if (fTextOnly) return dyDriveItem; dyDriveItem += COMBO_BORDER; // now take into account the icon if (pce->himl) { int cxIcon = 0, cyIcon = 0; CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon); if (dyDriveItem < cyIcon) dyDriveItem = cyIcon; } return dyDriveItem; } void ComboEx_OnMeasureItem(PCOMBOBOXEX pce, LPMEASUREITEMSTRUCT pmi) { pmi->itemHeight = ComboEx_ComputeItemHeight(pce, FALSE); } void ComboEx_ISetItem(PCOMBOBOXEX pce, PCEITEM pcei, PCOMBOBOXEXITEM pceItem) { if (pceItem->mask & CBEIF_INDENT) pcei->iIndent = pceItem->iIndent; if (pceItem->mask & CBEIF_IMAGE) pcei->iImage = pceItem->iImage; if (pceItem->mask & CBEIF_SELECTEDIMAGE) pcei->iSelectedImage = pceItem->iSelectedImage; if (pceItem->mask & CBEIF_OVERLAY) pcei->iOverlay = pceItem->iOverlay; if (pceItem->mask & CBEIF_TEXT) { Str_Set(&pcei->pszText, pceItem->pszText); } if (pceItem->mask & CBEIF_LPARAM) { pcei->lParam = pceItem->lParam; } } #define ComboEx_GetItemPtr(pce, iItem) \ ((PCEITEM)SendMessage((pce)->hwndCombo, CB_GETITEMDATA, iItem, 0)) #define ComboEx_Count(pce) \ ((int)SendMessage((pce)->hwndCombo, CB_GETCOUNT, 0, 0)) BOOL ComboEx_OnGetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem) { PCEITEM pcei; NMCOMBOBOXEX nm; if(pceItem->iItem != -1) { pcei = ComboEx_GetItemPtr(pce, pceItem->iItem); } else { pcei = &(pce->cei); } if ((!pcei) || (pcei == (PCEITEM)-1)) return FALSE; nm.ceItem.mask = 0; if (pceItem->mask & CBEIF_TEXT) { if (pcei->pszText == LPSTR_TEXTCALLBACK) { nm.ceItem.mask |= CBEIF_TEXT; } else { if(pceItem->iItem != -1) { Str_GetPtr(pcei->pszText, pceItem->pszText, pceItem->cchTextMax); } else if (pce->hwndEdit) { SendMessage(pce->hwndEdit, WM_GETTEXT, (WPARAM)pceItem->cchTextMax, (LPARAM)pceItem->pszText); } } } if (pceItem->mask & CBEIF_IMAGE) { if (pcei->iImage == I_IMAGECALLBACK) { nm.ceItem.mask |= CBEIF_IMAGE; } pceItem->iImage = pcei->iImage; } if (pceItem->mask & CBEIF_SELECTEDIMAGE) { if (pcei->iSelectedImage == I_IMAGECALLBACK) { nm.ceItem.mask |= CBEIF_SELECTEDIMAGE; } pceItem->iSelectedImage = pcei->iSelectedImage; } if (pceItem->mask & CBEIF_OVERLAY) { if (pcei->iOverlay == I_IMAGECALLBACK) { nm.ceItem.mask |= CBEIF_OVERLAY; } pceItem->iOverlay = pcei->iOverlay; } if (pceItem->mask & CBEIF_INDENT) { if (pcei->iIndent == I_INDENTCALLBACK) { nm.ceItem.mask |= CBEIF_INDENT; pceItem->iIndent = 0; } else { pceItem->iIndent = pcei->iIndent; } } if (pceItem->mask & CBEIF_LPARAM) { pceItem->lParam = pcei->lParam; } // is there anything to call back for? if (nm.ceItem.mask) { UINT uMask = nm.ceItem.mask; nm.ceItem = *pceItem; nm.ceItem.lParam = pcei->lParam; nm.ceItem.mask = uMask; if ((nm.ceItem.mask & CBEIF_TEXT) && nm.ceItem.cchTextMax) { // null terminate just in case they don't respond *nm.ceItem.pszText = 0; } CCSendNotify(&pce->ci, CBEN_GETDISPINFO, &nm.hdr); if (nm.ceItem.mask & CBEIF_INDENT) pceItem->iIndent = nm.ceItem.iIndent; if (nm.ceItem.mask & CBEIF_IMAGE) pceItem->iImage = nm.ceItem.iImage; if (nm.ceItem.mask & CBEIF_SELECTEDIMAGE) pceItem->iSelectedImage = nm.ceItem.iSelectedImage; if (nm.ceItem.mask & CBEIF_OVERLAY) pceItem->iOverlay = nm.ceItem.iOverlay; if (nm.ceItem.mask & CBEIF_TEXT) pceItem->pszText = CCReturnDispInfoText(nm.ceItem.pszText, pceItem->pszText, pceItem->cchTextMax); if (nm.ceItem.mask & CBEIF_DI_SETITEM) { ComboEx_ISetItem(pce, pcei, &nm.ceItem); } } return TRUE; } BOOL ComboEx_OnGetItemA(PCOMBOBOXEX pce, PCOMBOBOXEXITEMA pceItem) { LPWSTR pwszText; LPSTR pszTextSave; BOOL fRet; if (!(pceItem->mask & CBEIF_TEXT)) { return ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)pceItem); } pwszText = (LPWSTR)LocalAlloc(LPTR, (pceItem->cchTextMax+1)*sizeof(WCHAR)); if (!pwszText) return FALSE; pszTextSave = pceItem->pszText; ((PCOMBOBOXEXITEM)pceItem)->pszText = pwszText; fRet = ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)pceItem); pceItem->pszText = pszTextSave; if (fRet) { // WCTMB failes w/ ERROR_INSUFFICIENT_BUFFER whereas the native-A implementation truncates WideCharToMultiByte(CP_ACP, 0, pwszText, -1, (LPSTR)pszTextSave, pceItem->cchTextMax, NULL, NULL); } LocalFree(pwszText); return fRet; } BOOL ComboEx_OnSetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem) { if(pceItem->iItem != -1) { PCEITEM pcei = ComboEx_GetItemPtr(pce, pceItem->iItem); UINT rdwFlags = 0; if (pcei == (PCEITEM)-1) return FALSE; ComboEx_ISetItem(pce, pcei, pceItem); if (rdwFlags & (CBEIF_INDENT | CBEIF_IMAGE |CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_OVERLAY)) { rdwFlags = RDW_ERASE | RDW_INVALIDATE; } if (rdwFlags) { RedrawWindow(pce->hwndCombo, NULL, NULL, rdwFlags); } if (pceItem->iItem == (INT)ComboBox_GetCurSel(pce->hwndCombo)) ComboEx_UpdateEditText(pce, FALSE); // FEATURE: notify item changed return TRUE; } else { pce->cei.iImage = -1; pce->cei.iSelectedImage = -1; ComboEx_ISetItem(pce, &(pce->cei), pceItem); pce->fEditItemSet = TRUE; if (!pce->hwndEdit){ Str_Set(&pce->cei.pszText, NULL); pce->fEditItemSet = FALSE; return(CB_ERR); } if(pce->cei.pszText) { SendMessage(pce->hwndEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)pce->cei.pszText); EDIT_SELECTALL( pce->hwndEdit ); } RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); return TRUE; } } void ComboEx_HandleDeleteItem(PCOMBOBOXEX pce, LPDELETEITEMSTRUCT pdis) { PCEITEM pcei = (PCEITEM)pdis->itemData; if (pcei) { NMCOMBOBOXEX nm; Str_Set(&pcei->pszText, NULL); nm.ceItem.iItem = (INT)pdis->itemID; nm.ceItem.mask = CBEIF_LPARAM; nm.ceItem.lParam = pcei->lParam; CCSendNotify(&pce->ci, CBEN_DELETEITEM, &nm.hdr); LocalFree(pcei); } } LRESULT ComboEx_OnInsertItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem) { LRESULT iRet; PCEITEM pcei = (PCEITEM)LocalAlloc(LPTR, sizeof(CEITEM)); if (!pcei) return -1; pcei->iImage = -1; pcei->iSelectedImage = -1; //pcei->iOverlay = 0; //pcei->iIndent = 0; ComboEx_ISetItem(pce, pcei, pceItem); iRet = ComboBox_InsertString(pce->hwndCombo, pceItem->iItem, pcei); if (iRet != -1) { NMCOMBOBOXEX nm; nm.ceItem = *pceItem; CCSendNotify(&pce->ci, CBEN_INSERTITEM, &nm.hdr); } return iRet; } void ComboEx_OnWindowPosChanging(PCOMBOBOXEX pce, LPWINDOWPOS pwp) { RECT rcWindow, rcClient; RECT rc; int cxInner; int cy; GetWindowRect(pce->ci.hwnd, &rcWindow); if (pwp) { // check to see if our size & position aren't actually changing (rebar, for one, // does lots of DeferWindowPos calls that don't actually change our size or position // but still generate WM_WINDOWPOSCHANGING msgs). we avoid flicker by bailing here. RECT rcWp; SetRect(&rcWp, pwp->x, pwp->y, pwp->x + pwp->cx, pwp->y + pwp->cy); MapWindowRect(GetParent(pce->ci.hwnd), HWND_DESKTOP, (LPPOINT)&rcWp); if (EqualRect(&rcWp, &rcWindow)) { // this is a noop, so bail return; } } GetClientRect(pce->ci.hwnd, &rcClient); if (pwp) cxInner = pwp->cx + RECTWIDTH(rcWindow) - RECTWIDTH(rcClient); else cxInner = RECTWIDTH(rcClient); GetWindowRect(pce->hwndCombo, &rc); if (cxInner) { // don't size the inner combo if width is 0; otherwise, the below // computation will make the comboEX the height of the inner combo // top + inner combo dropdown instead of JUST the inner combo top cy = (pwp && ((pce->ci.style & CBS_DROPDOWNLIST) == CBS_SIMPLE)) ? pwp->cy : RECTHEIGHT(rc); SetWindowPos(pce->hwndCombo, NULL, 0, 0, cxInner, cy, SWP_NOACTIVATE | (pce->hwndEdit ? SWP_NOREDRAW : 0)); } GetWindowRect(pce->hwndCombo, &rc); cy = RECTHEIGHT(rc) + (RECTHEIGHT(rcWindow) - RECTHEIGHT(rcClient)); if (pwp) { if (cy < pwp->cy || !(pce->dwExStyle & CBES_EX_NOSIZELIMIT)) { pwp->cy = cy; } } else { if (cy < RECTHEIGHT(rcWindow) || !(pce->dwExStyle & CBES_EX_NOSIZELIMIT)) { SetWindowPos(pce->ci.hwnd, NULL, 0, 0, RECTWIDTH(rcWindow), cy, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER); } } if (pce->hwndEdit) { ComboEx_SizeEditBox(pce); InvalidateRect(pce->hwndCombo, NULL, TRUE); } } LRESULT ComboEx_HandleCommand(PCOMBOBOXEX pce, WPARAM wParam, LPARAM lParam) { LRESULT lres; UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam); UINT uCmd = GET_WM_COMMAND_CMD(wParam, lParam); if (!pce) return 0; if (uCmd == CBN_SELCHANGE) // update the edit text before forwarding this notification 'cause in // a normal combobox, the edit control will have already been updated // upon receipt of this notification ComboEx_UpdateEditText(pce, FALSE); lres = SendMessage(pce->ci.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(idCmd, pce->ci.hwnd, uCmd)); switch (uCmd) { case CBN_DROPDOWN: pce->iSel = ComboBox_GetCurSel(pce->hwndCombo); ComboEx_EndEdit(pce, CBENF_DROPDOWN); if (GetFocus() == pce->hwndEdit) SetFocus(pce->hwndCombo); pce->fInDrop = TRUE; break; case CBN_KILLFOCUS: ComboEx_EndEdit(pce, CBENF_KILLFOCUS); break; case CBN_CLOSEUP: pce->iSel = -1; ComboEx_BeginEdit(pce); pce->fInDrop = FALSE; break; case CBN_SETFOCUS: ComboEx_BeginEdit(pce); break; } return lres; } LRESULT ComboEx_OnGetItemData(PCOMBOBOXEX pce, WPARAM i) { PCEITEM pcei = (PCEITEM)SendMessage(pce->hwndCombo, CB_GETITEMDATA, i, 0); if (pcei == NULL || pcei == (PCEITEM)CB_ERR) { return CB_ERR; } return pcei->lParam; } LRESULT ComboEx_OnSetItemData(PCOMBOBOXEX pce, int i, LPARAM lParam) { PCEITEM pcei = (PCEITEM)SendMessage(pce->hwndCombo, CB_GETITEMDATA, i, 0); if (pcei == NULL || pcei == (PCEITEM)CB_ERR) { return CB_ERR; } pcei->lParam = lParam; return 0; } int ComboEx_OnFindStringExact(PCOMBOBOXEX pce, int iStart, LPCTSTR lpsz) { int i; int iMax = ComboEx_Count(pce); TCHAR szText[CBEMAXSTRLEN]; COMBOBOXEXITEM cei; if (iStart < 0) iStart = -1; cei.mask = CBEIF_TEXT; cei.pszText = szText; cei.cchTextMax = ARRAYSIZE(szText); for (i = iStart + 1 ; i < iMax; i++) { cei.iItem = i; if (ComboEx_OnGetItem(pce, &cei)) { if (!ComboEx_StrCmp(pce, lpsz, szText)) { return i; } } } for (i = 0; i <= iStart; i++) { cei.iItem = i; if (ComboEx_OnGetItem(pce, &cei)) { if (!ComboEx_StrCmp(pce, lpsz, szText)) { return i; } } } return CB_ERR; } int ComboEx_StrCmp(PCOMBOBOXEX pce, LPCTSTR psz1, LPCTSTR psz2) { if (pce->dwExStyle & CBES_EX_CASESENSITIVE) { return lstrcmp(psz1, psz2); } return lstrcmpi(psz1, psz2); } DWORD ComboEx_OnSetExStyle(PCOMBOBOXEX pce, DWORD dwExStyle, DWORD dwExMask) { DWORD dwRet; DWORD dwChange; if (dwExMask) dwExStyle = (pce->dwExStyle & ~ dwExMask) | (dwExStyle & dwExMask); dwRet = pce->dwExStyle; dwChange = (pce->dwExStyle ^ dwExStyle); pce->dwExStyle = dwExStyle; if (dwChange & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT)) { InvalidateRect(pce->ci.hwnd, NULL, TRUE); if (pce->hwndEdit) { ComboEx_SizeEditBox(pce); InvalidateRect(pce->hwndEdit, NULL, TRUE); } } if (dwChange & CBES_EX_PATHWORDBREAKPROC) SetPathWordBreakProc(pce->hwndEdit, (pce->dwExStyle & CBES_EX_PATHWORDBREAKPROC)); return dwRet; } HFONT ComboEx_GetFont(PCOMBOBOXEX pce) { if (pce->hwndCombo) return (HFONT)SendMessage(pce->hwndCombo, WM_GETFONT, 0, 0); return NULL; } LRESULT CALLBACK ComboExWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LRESULT lres = 0; PCOMBOBOXEX pce = (PCOMBOBOXEX)GetWindowPtr(hwnd, 0); if (!pce) { if (uMsg != WM_NCCREATE && uMsg != WM_CREATE) goto DoDefault; } switch (uMsg) { HANDLE_MSG(pce, WM_SETFONT, ComboEx_OnSetFont); case WM_ENABLE: if (pce->hwndCombo) EnableWindow(pce->hwndCombo, (BOOL) wParam); if (pce->hwndEdit) EnableWindow(pce->hwndEdit, (BOOL) wParam); break; case WM_WININICHANGE: InitGlobalMetrics(wParam); // only need to re-create this font if we created it in the first place // and somebody changed the font (or did a wildcard change) // // NOTE: Some people broadcast a nonclient metrics change when they // change the icon title logfont, so watch for both. // if (pce && pce->fFontCreated && ((wParam == 0 && lParam == 0) || wParam == SPI_SETICONTITLELOGFONT || wParam == SPI_SETNONCLIENTMETRICS)) { ComboEx_OnSetFont(pce, NULL, TRUE); } break; case WM_SYSCOLORCHANGE: InitGlobalColors(); break; case WM_NOTIFYFORMAT: return CIHandleNotifyFormat(&pce->ci, lParam); break; case WM_NCCREATE: // strip off the scroll bits SetWindowBits(hwnd, GWL_STYLE, WS_BORDER | WS_VSCROLL | WS_HSCROLL, 0); goto DoDefault; case WM_CREATE: if (!ComboEx_OnCreate(hwnd, (LPCREATESTRUCT)lParam)) lres = -1; // OnCreate falied. Fail WM_CREATE break; case WM_PRINTCLIENT: CCSendPrint(&pce->ci, (HDC)wParam); break; case WM_DESTROY: ASSERT(pce); ComboEx_OnDestroy(pce); break; case WM_WINDOWPOSCHANGING: ComboEx_OnWindowPosChanging(pce, (LPWINDOWPOS)lParam); break; case WM_DRAWITEM: ComboEx_OnDrawItem(pce, (LPDRAWITEMSTRUCT)lParam); break; case WM_MEASUREITEM: ComboEx_OnMeasureItem(pce, (LPMEASUREITEMSTRUCT)lParam); break; case WM_COMMAND: return ComboEx_HandleCommand(pce, wParam, lParam); case WM_GETFONT: return (LRESULT)ComboEx_GetFont(pce); case WM_SETFOCUS: if (pce->hwndCombo) SetFocus(pce->hwndCombo); break; case WM_DELETEITEM: ComboEx_HandleDeleteItem(pce, (LPDELETEITEMSTRUCT)lParam); return TRUE; case WM_UPDATEUISTATE: //not sure need to set bit, will probably not use it, on the other hand this // is consistent with remaining of common controls and not very expensive CCOnUIState(&(pce->ci), WM_UPDATEUISTATE, wParam, lParam); goto DoDefault; // this is for backcompat only. case CBEM_SETEXSTYLE: return ComboEx_OnSetExStyle(pce, (DWORD)wParam, 0); case CBEM_SETEXTENDEDSTYLE: return ComboEx_OnSetExStyle(pce, (DWORD)lParam, (DWORD)wParam); case CBEM_GETEXTENDEDSTYLE: return pce->dwExStyle; case CBEM_GETCOMBOCONTROL: return (LRESULT)pce->hwndCombo; case CBEM_SETIMAGELIST: return (LRESULT)ComboEx_OnSetImageList(pce, (HIMAGELIST)lParam); case CBEM_GETIMAGELIST: return (LRESULT)pce->himl; case CBEM_GETITEMA: return ComboEx_OnGetItemA(pce, (PCOMBOBOXEXITEMA)lParam); case CBEM_GETITEM: return ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)lParam); case CBEM_SETITEMA: { LRESULT lResult; LPWSTR lpStrings; UINT uiCount; LPSTR lpAnsiString = (LPSTR) ((PCOMBOBOXEXITEM)lParam)->pszText; if ((((PCOMBOBOXEXITEM)lParam)->mask & CBEIF_TEXT) && (((PCOMBOBOXEXITEM)lParam)->pszText != LPSTR_TEXTCALLBACK)) { uiCount = lstrlenA(lpAnsiString)+1; lpStrings = LocalAlloc(LPTR, (uiCount) * sizeof(TCHAR)); if (!lpStrings) return -1; MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpAnsiString, uiCount, lpStrings, uiCount); ((PCOMBOBOXEXITEMA)lParam)->pszText = (LPSTR)lpStrings; lResult = ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam); ((PCOMBOBOXEXITEMA)lParam)->pszText = lpAnsiString; LocalFree(lpStrings); return lResult; } else { return ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam); } } case CBEM_SETITEM: return ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam); case CBEM_INSERTITEMA: { LRESULT lResult; LPWSTR lpStrings; UINT uiCount; LPSTR lpAnsiString = (LPSTR) ((PCOMBOBOXEXITEM)lParam)->pszText; if (!lpAnsiString || lpAnsiString == (LPSTR)LPSTR_TEXTCALLBACK) return ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam); uiCount = lstrlenA(lpAnsiString)+1; lpStrings = LocalAlloc(LPTR, (uiCount) * sizeof(TCHAR)); if (!lpStrings) return -1; MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpAnsiString, uiCount, lpStrings, uiCount); ((PCOMBOBOXEXITEMA)lParam)->pszText = (LPSTR)lpStrings; lResult = ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam); ((PCOMBOBOXEXITEMA)lParam)->pszText = lpAnsiString; LocalFree(lpStrings); return lResult; } case CBEM_INSERTITEM: return ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam); case CBEM_GETEDITCONTROL: return (LRESULT)pce->hwndEdit; case CBEM_HASEDITCHANGED: return pce->fEditChanged; case CBEM_SETWINDOWTHEME: if (lParam) { SetWindowTheme(hwnd, (LPWSTR)lParam, NULL); if (pce->hwndCombo) SetWindowTheme(pce->hwndCombo, (LPWSTR)lParam, NULL); if (pce->hwndEdit) SetWindowTheme(pce->hwndEdit, (LPWSTR)lParam, NULL); } break; case CB_GETITEMDATA: return ComboEx_OnGetItemData(pce, (int)wParam); case CB_SETITEMDATA: return ComboEx_OnSetItemData(pce, (int)wParam, lParam); case CB_LIMITTEXT: if (ComboEx_GetEditBox(pce)) Edit_LimitText(pce->hwndEdit, wParam); break; case CB_FINDSTRINGEXACT: { LPCTSTR psz = (LPCTSTR)lParam; return ComboEx_OnFindStringExact(pce, (int)wParam, psz); } case CB_SETITEMHEIGHT: lres = SendMessage(pce->hwndCombo, uMsg, wParam, lParam); if (wParam == (WPARAM)-1) { RECT rcWindow, rcClient; int cy; GetWindowRect(pce->hwndCombo, &rcWindow); cy = RECTHEIGHT(rcWindow); GetWindowRect(pce->ci.hwnd, &rcWindow); GetClientRect(pce->ci.hwnd, &rcClient); cy = cy + (RECTHEIGHT(rcWindow) - RECTHEIGHT(rcClient)); SetWindowPos(pce->ci.hwnd, NULL, 0, 0, RECTWIDTH(rcWindow), cy, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER); } break; case CB_INSERTSTRING: case CB_ADDSTRING: case CB_SETEDITSEL: case CB_FINDSTRING: case CB_DIR: // override to do nothing break; case CB_SETCURSEL: case CB_RESETCONTENT: case CB_DELETESTRING: lres = SendMessage(pce->hwndCombo, uMsg, wParam, lParam); ComboEx_UpdateEditText(pce, uMsg == CB_SETCURSEL); break; case WM_SETTEXT: if (!pce->hwndEdit) return(CB_ERR); lres = SendMessage(pce->hwndEdit, uMsg, wParam, lParam); EDIT_SELECTALL( pce->hwndEdit ); RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE); return(lres); case WM_CUT: case WM_COPY: case WM_PASTE: case WM_GETTEXT: case WM_GETTEXTLENGTH: if (!pce->hwndEdit) return 0; return(SendMessage(pce->hwndEdit, uMsg, wParam, lParam)); case WM_SETREDRAW: if (pce->hwndEdit) SendMessage(pce->hwndEdit, uMsg, wParam, lParam); break; case CB_GETEDITSEL: if (pce->hwndEdit) return SendMessage(pce->hwndEdit, EM_GETSEL, wParam, lParam); // else fall through // Handle it being in a dialog... // May want to handle it differently when edit control has // focus... case WM_GETDLGCODE: case CB_SHOWDROPDOWN: case CB_SETEXTENDEDUI: case CB_GETEXTENDEDUI: case CB_GETDROPPEDSTATE: case CB_GETDROPPEDCONTROLRECT: case CB_GETCURSEL: case CB_GETCOUNT: case CB_SELECTSTRING: case CB_GETITEMHEIGHT: case CB_SETDROPPEDWIDTH: return SendMessage(pce->hwndCombo, uMsg, wParam, lParam); case CB_GETLBTEXT: case CB_GETLBTEXTLEN: return ComboEx_GetLBText(pce, uMsg, wParam, lParam); default: if (CCWndProc(&pce->ci, uMsg, wParam, lParam, &lres)) return lres; DoDefault: return DefWindowProc(hwnd, uMsg, wParam, lParam); } return lres; } BOOL InitComboExClass(HINSTANCE hinst) { WNDCLASS wc; wc.lpfnWndProc = ComboExWndProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hIcon = NULL; wc.lpszMenuName = NULL; wc.hInstance = hinst; wc.lpszClassName = c_szComboBoxEx; wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // NULL; wc.style = CS_GLOBALCLASS; wc.cbWndExtra = sizeof(PCOMBOBOXEX); wc.cbClsExtra = 0; return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS)); } //--------------------------------------------------------------------------- // SetPathWordBreakProc does special break processing for edit controls. // // The word break proc is called when ctrl-(left or right) arrow is pressed in the // edit control. Normal processing provided by USER breaks words at spaces or tabs, // but for us it would be nice to break words at slashes, backslashes, & periods too // since it may be common to have paths or url's typed in. void WINAPI SetPathWordBreakProc(HWND hwndEdit, BOOL fSet) { PROC lpfnOld; // Don't shaft folks who set their own break proc - leave it alone. lpfnOld = (FARPROC)SendMessage(hwndEdit, EM_GETWORDBREAKPROC, 0, 0L); if (fSet) { if (!lpfnOld) SendMessage(hwndEdit, EM_SETWORDBREAKPROC, 0, (LPARAM)ShellEditWordBreakProc); } else { if (lpfnOld == (FARPROC)ShellEditWordBreakProc) SendMessage(hwndEdit, EM_SETWORDBREAKPROC, 0, 0L); } } BOOL IsDelimiter(TCHAR ch) { return (ch == TEXT(' ') || ch == TEXT('\t') || ch == TEXT('.') || ch == TEXT('/') || ch == TEXT('\\')); } int WINAPI ShellEditWordBreakProc(LPTSTR lpch, int ichCurrent, int cch, int code) { LPTSTR lpchT = lpch + ichCurrent; int iIndex; BOOL fFoundNonDelimiter = FALSE; static BOOL fRight = FALSE; // hack due to bug in USER switch (code) { case WB_ISDELIMITER: fRight = TRUE; // Simple case - is the current character a delimiter? iIndex = (int)IsDelimiter(*lpchT); break; case WB_LEFT: // Move to the left to find the first delimiter. If we are // currently at a delimiter, then skip delimiters until we // find the first non-delimiter, then start from there. // // Special case for fRight - if we are currently at a delimiter // then just return the current word! while ((lpchT = CharPrev(lpch, lpchT)) != lpch) { if (IsDelimiter(*lpchT)) { if (fRight || fFoundNonDelimiter) break; } else { fFoundNonDelimiter = TRUE; fRight = FALSE; } } iIndex = (int) (lpchT - lpch); // We are currently pointing at the delimiter, next character // is the beginning of the next word. if (iIndex > 0 && iIndex < cch) iIndex++; break; case WB_RIGHT: fRight = FALSE; // If we are not at a delimiter, then skip to the right until // we find the first delimiter. If we started at a delimiter, or // we have just finished scanning to the first delimiter, then // skip all delimiters until we find the first non delimiter. // // Careful - the string passed in to us may not be NULL terminated! fFoundNonDelimiter = !IsDelimiter(*lpchT); if (lpchT != (lpch + cch)) { while ((lpchT = FastCharNext(lpchT)) != (lpch + cch)) { if (IsDelimiter(*lpchT)) { fFoundNonDelimiter = FALSE; } else { if (!fFoundNonDelimiter) break; } } } // We are currently pointing at the next word. iIndex = (int) (lpchT - lpch); break; } return iIndex; }