|
|
#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; }
|