You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1829 lines
52 KiB
1829 lines
52 KiB
#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;
|
|
}
|