#include "ctlspriv.h"

#ifdef UNIX

#define EDIT_SELECTALL( hwnd )
#define GetTextExtentPoint GetTextExtentPoint32

#else

#define EDIT_SELECTALL( hwnd )  Edit_SetSel(hwnd, 0, 0);  \
                                Edit_SetSel(hwnd, 0, -1);

#endif

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;
#if 0
    if (pce->ci.style & CBS_UPPERCASE)
        dwStyle |= ES_UPPERCASE;
    if (pce->ci.style & CBS_LOWERCASE)
        dwStyle |= ES_LOWERCASE;
#endif

    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;
#ifndef UNIX
    POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
#else
    POINT pt;
    pt.x = GET_X_LPARAM(lParam);
    pt.y = GET_Y_LPARAM(lParam);
#endif

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

#ifndef UNIX
            GetWindowText(pce->hwndEdit, szTextNow, ARRAYSIZE(szTextNow));
#else
            GetWindowText(pce->hwndEdit, szTextNow, ARRAYSIZE(szTextNow)-1);
#endif
            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);
#ifndef UNIX
            // IEUNIX : 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
#endif
        }
        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)
{
    COMBOBOXEXITEM cei;
    TCHAR szText[CBEMAXSTRLEN];
    cei.mask = CBEIF_TEXT;
    cei.pszText = szText;
    cei.cchTextMax = ARRAYSIZE(szText);
    cei.iItem = (INT)wParam;
    if (!ComboEx_OnGetItem(pce, &cei))
        return CB_ERR;

    if (lParam && uMsg == CB_GETLBTEXT) {
#ifdef UNICODE_WIN9x
        if (pce->ci.bUnicode) {
            lstrcpy((LPTSTR)lParam, szText);
        } else {
            WideCharToMultiByte(pce->ci.uiCodePage, 0, szText, -1, (LPSTR)lParam, CBEMAXSTRLEN, NULL,NULL);
        }
#else
        lstrcpy((LPTSTR)lParam, szText);
#endif
    }
    return lstrlen(szText);
}

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;

}

#ifdef UNICODE
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;

}
#endif

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;

    if (!RegisterClass(&wc) && !GetClassInfo(hinst, c_szComboBoxEx, &wc))
        return FALSE;

    return TRUE;

}

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