///////////////////////////////////////////////////////////////////////
//                     Microsoft Windows                             //
//              Copyright(c) Microsoft Corp., 1995                   //
///////////////////////////////////////////////////////////////////////
//
// LANG.CPP - "Language" property page for InetCpl
//

// HISTORY:
//
// 1/10/97  beomoh      created
//

#include "inetcplp.h"

#include <tchar.h>
#include <mlang.h>
#include "psapi.h"
#include "tlhelp32.h"
#include "process.h"
#include <mluisupp.h>
#include <shdocvw.h>

#define ARRAYSIZE(a)        (sizeof(a)/sizeof(a[0]))
#define FORMAT_STR          TEXT("%s [%s]")
#define MAX_LIST_STRING_LEN MAX_LOCALE_NAME + MAX_RFC1766_NAME + 3
#define MAX_ACCEPT_LANG_LEN 2048

#define CP_THAI     874
#define CP_ARABIC   1256
#define CP_HEBREW   1255

// used as the return value from setlang dialog
#define RETURN_SETLANG_ENDLANGDIALOG    2
#define RETURN_SETLANG_CLOSEDNORMAL     1
#define RETURN_SETLANG_CANCELED         0

typedef HRESULT (* PCOINIT) (LPVOID);
typedef VOID (* PCOUNINIT) (VOID);
typedef VOID (* PCOMEMFREE) (LPVOID);
typedef HRESULT (* PCOCREINST) (REFCLSID, LPUNKNOWN, DWORD,     REFIID, LPVOID * );

extern HMODULE hOLE32;
extern PCOINIT pCoInitialize;
extern PCOUNINIT pCoUninitialize;
extern PCOMEMFREE pCoTaskMemFree;
extern PCOCREINST pCoCreateInstance;

extern BOOL _StartOLE32();

class CUILangList;
INT_PTR KickSetLang(HWND hDlg, CUILangList * pLangList);

static const TCHAR s_szResourceLocale[] = TEXT("ResourceLocale");
// HKLM\Software\Microsoft\Internet Explorer\International used for url string
static const TCHAR s_szUrlSPK[] 
= TEXT("http://www.microsoft.com/isapi/redir.dll?prd=ie&pver=6&ar=plugui&sba=install");
static const TCHAR c_szInstall[] 
= TEXT("Software\\Microsoft\\Active Setup\\Installed Components\\{89820200-ECBD-11CF-8B85-00AA005B4383}");
static const TCHAR c_szLocale[] = TEXT("Locale");
static const TCHAR s_szLangPackPath[]   = TEXT("Software\\Microsoft\\Internet Explorer");
static const TCHAR s_szVersion[] = TEXT("LPKInstalled");

typedef struct 
{
    WORD wlangid;
    BOOL fValid;
    TCHAR szName[MAX_LOCALE_NAME];
} LANGLIST;

static LANGLIST s_arryLangList[] = 
{
    {0x0409, FALSE, {0}},
    {0x0407, FALSE, {0}},
    {0x0411, FALSE, {0}},
    {0x0412, FALSE, {0}},
    {0x0404, FALSE, {0}},
    {0x0804, FALSE, {0}},
    {0x040c, FALSE, {0}},
    {0x0c0a, FALSE, {0}},
    {0x0416, FALSE, {0}},
    {0x0410, FALSE, {0}},
    {0x0413, FALSE, {0}},
    {0x041d, FALSE, {0}},
    {0x0406, FALSE, {0}},
    {0x040b, FALSE, {0}},
    {0x040e, FALSE, {0}},
    {0x0414, FALSE, {0}},
    {0x0408, FALSE, {0}},
    {0x0415, FALSE, {0}},
    {0x0419, FALSE, {0}},
    {0x0405, FALSE, {0}},
    {0x0816, FALSE, {0}},
    {0x041f, FALSE, {0}},
    {0x041b, FALSE, {0}},
    {0x0424, FALSE, {0}},
    {0x0401, FALSE, {0}},
    {0x040d, FALSE, {0}},
    {0x042d, FALSE, {0}},
    {0x040f, FALSE, {0}},
};

//
//  ISO639 ID table
//
typedef struct tagISO639
{
    LPCTSTR ISO639;
    LANGID LangID;
}   ISO639, *LPISO639;

const ISO639 c_ISO639[] =
{
    { TEXT("EN"), 0x0409 },
    { TEXT("DE"), 0x0407 },
    { TEXT("JA"), 0x0411 },
    { TEXT("KO"), 0x0412 },
    { TEXT("TW"), 0x0404 },
    { TEXT("CN"), 0x0804 },
    { TEXT("FR"), 0x040C },
    { TEXT("ES"), 0x0C0A },
    { TEXT("BR"), 0x0416 },
    { TEXT("IT"), 0x0410 },
    { TEXT("NL"), 0x0413 },
    { TEXT("SV"), 0x041D },
    { TEXT("DA"), 0x0406 },
    { TEXT("FI"), 0x040B },
    { TEXT("HU"), 0x040E },
    { TEXT("NO"), 0x0414 },
    { TEXT("EL"), 0x0408 },
    { TEXT("PL"), 0x0415 },
    { TEXT("RU"), 0x0419 },
    { TEXT("CS"), 0x0405 },
    { TEXT("PT"), 0x0816 },
    { TEXT("TR"), 0x041F },
    { TEXT("SK"), 0x041B },
    { TEXT("SL"), 0x0424 },
    { TEXT("AR"), 0x0401 },
    { TEXT("HE"), 0x040D },
    { TEXT("EU"), 0x042D },
    { TEXT("IS"), 0x040F },
};

// GetInstallLanguage
//
// synopsis - borrowed this function from shlwapi. we can remove this
//            once we have it exported from shlwapi.dll
//
LANGID GetInstallLanguage(void)
{
    static LANGID LangID = 0;
    TCHAR szISO639[3];
    DWORD cb;

    if (0 == LangID)
    {
        cb = sizeof(szISO639);
        if (ERROR_SUCCESS == SHGetValue(HKEY_LOCAL_MACHINE, c_szInstall, c_szLocale, NULL, szISO639, &cb))
        {
            int i;

            for (i = 0; i < ARRAYSIZE(c_ISO639); i++)
            {
                if (!StrCmpNI(szISO639, c_ISO639[i].ISO639, ARRAYSIZE(szISO639)))
                {
                    LangID = c_ISO639[i].LangID;
                    break;
                }
            }
        }
    }
    return LangID;
}

// CUILangList
// 
// maintains the list of UI languages for user to choose
//
class CUILangList
{
public:
    CUILangList() {_iLangIdx = -1; lang = s_arryLangList; 
                   _nLangList = ARRAYSIZE(s_arryLangList);
                   _fOffice9Installed = -1;};
    
    void    ValidateLangList();
    BOOL    IsValidLang(int idx) { return (idx < _nLangList) ? lang[idx].fValid: FALSE; };
    int     GetCurrentLangIdx();
    void    SetCurrentLangIdx(int idx);
    LPCTSTR GetCurrentLangName();
    LPCTSTR GetLangNameOfIdx(int idx);
    WORD    GetLangIdOfIdx(int idx) { return (idx < _nLangList) ? lang[idx].wlangid:0; };
    UINT    GetIds(int idx); 
    int     GetListSize() {return _nLangList;};
    BOOL    IsOffice9Installed();
    static  HRESULT GetLangList(HWND hdlg, CUILangList ** ppLangList);
    static  HRESULT RemoveLangList(HWND hdlg);
private:
    int _iLangIdx;
    int _nLangList;
    int _fOffice9Installed;
    LANGLIST *lang;
};

// CShutDownProcInfo
// 
// manages information about processes we want
// to shutdown/restart.
//
typedef enum 
{
    PS_UNKNOWN=0, 
    PS_CANDIDATE, 
    PS_TO_BE_SHUTDOWN, 
    PS_IGNORE, 
    PS_SHUTDOWN_OK, 
    PS_WAITING, 
    PS_TO_BE_SHUTDOWN_WITH_NO_RELAUNCH, 
    PS_SHUTDOWN_OK_NO_RELAUNCH_NEEDED, 
} PROCSTATE; 

class CShutDownProcInfo : public CProcessInfo
{
public:
    CShutDownProcInfo(HWND hdlgParent);
    ~CShutDownProcInfo();
    HRESULT EnsureProcList();
    HRESULT IncreaseProcList();
    HRESULT NotifyShutDownToFolks(int *nProccess);
    HRESULT AddToProcList(HWND hwndShutDown);
    HRESULT WaitForOneProcess(int iProc);
    HRESULT WaitForFolksShutDown();
    HRESULT GetRestartAppPath(LPTSTR szPath, int cchPath, int iProc);
    HRESULT RestartFolks();
    static DWORD CALLBACK ShutDownThreadProc(void *pv);
protected:
    typedef struct
    {
        DWORD dwPID;
        TCHAR szExeName[32];
        PROCSTATE State; 
    } PROCLIST;
    PROCLIST *_pProcList;
    int _nAlloced;
    int _iProcList;
    HWND _hdlgParent;
    BOOL _fAllShutDown;
};
// this always fills '0' to empty digits
// caller has to make sure sz has cdigit+1 of buffer
void IntToHex(OUT LPTSTR sz, IN int cdigit, IN int value)
{
    int i, idigit;

    if (sz && value > 0 && cdigit > 0)
    {
        // nul terminate the buffer
        sz[cdigit] = TEXT('\0');
        
        for (i = cdigit-1; i >= 0; i--, value /= 16)
        {
            idigit = value%16;
            if (idigit < 10)
                sz[i] = (TCHAR)idigit + TEXT('0');
            else 
                sz[i] = (TCHAR)idigit - 10 + TEXT('A');
        }
    }
} 

// set valid flags for the lang list
// very expensive so expects to be called only once in a session
// from CUILangList::GetLangList
//
#define MAX_SATELLITEPACKS 30 // 30 must be a practical number for satellite packs
void CUILangList::ValidateLangList()
{
    HKEY hKey;
    HRESULT hr;
    TCHAR szValueName[32];
    WORD aryValidLang[MAX_SATELLITEPACKS +1+1] = {0}; // +1 for install lang, 
                                                      // +1 for terminator

    int  nMaxValidLang = ARRAYSIZE(aryValidLang)-1;   // -1 for terminator
    WORD *pwValid = aryValidLang;
    
    // make the install language always valid
    *pwValid = GetInstallLanguage();
    if (*pwValid != 0)
    {
       *(pwValid+1) = 0; // terminator
       pwValid++;
       nMaxValidLang--;
    }

    if (ERROR_SUCCESS == 
    RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_INTERNATIONAL, NULL, KEY_READ, &hKey))
    {
        int i = 0;
        do {
            // see if the value has a match in the list
            DWORD dwType;
            DWORD cb = ARRAYSIZE(szValueName)-2;

            hr = SHEnumValue(hKey, i++, szValueName+2, &cb, &dwType, NULL, NULL);
            if (SUCCEEDED(hr) && dwType == REG_SZ)
            {
                UINT uiInstalled ;

                szValueName[0] = TEXT('0');
                szValueName[1] = TEXT('x');
                StrToIntEx(szValueName, STIF_SUPPORT_HEX, (LPINT)&uiInstalled);
                if (uiInstalled > 0)
                {
                    *pwValid     = (unsigned short) uiInstalled;
                    *(pwValid+1) = 0; // terminator
                    pwValid++;
                }
            }
        } while(hr == ERROR_SUCCESS && i < nMaxValidLang);
        RegCloseKey(hKey);
    }

    // this assumes we can use StrChrW to search a value in 
    // a word array, it also assumes we never have 0 as a langid
    //
    Assert(sizeof(WORD) == sizeof(WCHAR)); // unix?

    int nValidLang = (int)(pwValid-aryValidLang);
    for(int idx = 0; idx < GetListSize(); idx++ )
    {
        // abusing the string function but this is a fast way
        if (StrChrW((WCHAR *)aryValidLang, (WCHAR)lang[idx].wlangid))
        {
            lang[idx].fValid = TRUE;
            if(--nValidLang <= 0)
                break;
        }
    }
}

static const TCHAR s_szPropLangList[] = TEXT("langlist");
HRESULT CUILangList::GetLangList(HWND hdlg, CUILangList ** ppLangList)
{
    HRESULT hr=S_OK;
    
    CUILangList *pLangList = (CUILangList *)GetProp(hdlg, s_szPropLangList);
    if (!pLangList)
    {
        pLangList = new CUILangList();
        if (pLangList)
        {
            pLangList->ValidateLangList();
            SetProp(hdlg, s_szPropLangList, (HANDLE)pLangList);
        }
        else
            hr = E_FAIL;
    }
    
    ASSERT(ppLangList);
    if (ppLangList)
        *ppLangList = pLangList;
    
    return hr;
}

HRESULT CUILangList::RemoveLangList(HWND hdlg)
{
    HRESULT hr = S_OK;
    CUILangList *pLangList = (CUILangList *)GetProp(hdlg, s_szPropLangList);

    if (pLangList)
    {
        delete pLangList;
        RemoveProp(hdlg, s_szPropLangList);    
    }
    else
        hr = S_FALSE;

    return hr;
}

void CUILangList::SetCurrentLangIdx(int idx)
{
    TCHAR sz[4+1];
    if (idx != _iLangIdx)
    {
        // the resource id is always 4 digit
        IntToHex(sz, 4, lang[idx].wlangid);
        SHSetValue(HKEY_CURRENT_USER, REGSTR_PATH_INTERNATIONAL, 
                   s_szResourceLocale, REG_SZ, (void *)sz, sizeof(sz));
        _iLangIdx = idx;
    }
}
// returns idx to the lang array
int CUILangList::GetCurrentLangIdx()
{
    // show the current selection
    TCHAR sz[64];
    DWORD dwType;
    int   isel;
    
    // see if it's cached already
    if (_iLangIdx == -1)
    {
        // We basically wants what we've set in the registry,
        // but if Office9 is installed we'll show whatever
        // Office sets, and we can't change the Office setting anyway
        // MLGetUILanguage returns Office's setting if its there
        // Also I suppose we want to show NT5's UI language here
        //
        if (IsOffice9Installed() || IsOS(OS_WIN2000ORGREATER))
            isel = INETCPL_GetUILanguage();
        else
        {
            DWORD dwcbData = sizeof(sz);

            HRESULT hr =  SHGetValue(HKEY_CURRENT_USER, REGSTR_PATH_INTERNATIONAL, 
                                  s_szResourceLocale, &dwType, (void *)&sz[2], &dwcbData);
                   
            if (hr == ERROR_SUCCESS && dwType == REG_SZ)
            {
                sz[0] = TEXT('0');
                sz[1] = TEXT('x');
                StrToIntEx(sz, STIF_SUPPORT_HEX, (LPINT)&isel);
            }
            else
            {
                isel = GetInstallLanguage();
            }
        }
        
        for(int i = 0; i < GetListSize(); i++ )
        {
            if (isel == lang[i].wlangid)
            {
                _iLangIdx = i;
                break;
            }
        }
            
        // english for error case
        if (_iLangIdx < 0) 
            _iLangIdx = 0;
    }
    return _iLangIdx;
}

LPCTSTR CUILangList::GetLangNameOfIdx(int idx)
{
    LPCTSTR pszRet = NULL;
    IMultiLanguage2 *pML2;
    HRESULT hr;
    RFC1766INFO Rfc1766Info={0};

    if(!hOLE32)
    {
        if(!_StartOLE32())
        {
            ASSERT(FALSE);
            return NULL;
        }
    }
    hr = pCoInitialize(NULL);

    if (FAILED(hr))
        return NULL;

    hr = pCoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, IID_IMultiLanguage2, (LPVOID *) &pML2);

    if (SUCCEEDED(hr))
    {
        if (idx >= 0)
        {
            if (!lang[idx].szName[0])
            {
                pML2->GetRfc1766Info(lang[idx].wlangid, INETCPL_GetUILanguage(), &Rfc1766Info);
                StrCpyNW(lang[idx].szName, Rfc1766Info.wszLocaleName, ARRAYSIZE(lang[0].szName));            
            }
            pszRet = lang[idx].szName;        
        }
        pML2->Release();
    }

    pCoUninitialize();
    return pszRet;
}
 
LPCTSTR CUILangList::GetCurrentLangName()
{
    int idx = GetCurrentLangIdx();
    return GetLangNameOfIdx(idx);
}

BOOL CUILangList::IsOffice9Installed()
{
    DWORD dwVersion;
    DWORD cb = sizeof(dwVersion);
    if (_fOffice9Installed < 0)
    {
        _fOffice9Installed ++;
        if (ERROR_SUCCESS ==
            SHGetValue(HKEY_LOCAL_MACHINE, s_szLangPackPath, s_szVersion, NULL, &dwVersion, &cb)
          && dwVersion > 0) // magic number - christw tells me so
            _fOffice9Installed ++;
    }
    return (BOOL)_fOffice9Installed;
}

void InitCurrentUILang(HWND hDlg)
{
    BOOL fChanged = FALSE;
    CUILangList *pLangList;  
    LPCTSTR pszLangSel = NULL;
    HRESULT hr;
    
    hr = CUILangList::GetLangList(hDlg, &pLangList);
    
    if (SUCCEEDED(hr))
        pszLangSel = pLangList->GetCurrentLangName();
    
    if (pszLangSel)
    {
        TCHAR szBig[1024], szSmall[256];

        GetDlgItemText(hDlg, IDC_LANG_CURSEL, szBig, ARRAYSIZE(szBig));
        if (szBig[0])
            fChanged = (StrStr(szBig, pszLangSel) == NULL); 

        if (MLLoadString((fChanged)? IDS_LANG_FUTUREUSE: IDS_LANG_CURRENTUSE, szSmall, ARRAYSIZE(szSmall)) > 0)
        {
            wnsprintf(szBig, ARRAYSIZE(szBig), szSmall, pszLangSel);
            Static_SetText(GetDlgItem(hDlg, IDC_LANG_CURSEL), szBig);
        }
    }
}


//
// FillAcceptListBox()
//
// Fills the accept language listbox with names of selected language
//
void FillAcceptListBox(IN HWND hDlg)
{
    IMultiLanguage2 *pML2;
    HRESULT hr;
    HKEY hKey;
    DWORD cb;
    TCHAR sz[MAX_LIST_STRING_LEN], szBuf[MAX_ACCEPT_LANG_LEN], *p1, *p2, *p3;
    HWND hwndList = GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST);

    if(!hOLE32)
    {
        if(!_StartOLE32())
        {
            ASSERT(FALSE);
            return;
        }
    }
    hr = pCoInitialize(NULL);
    if (FAILED(hr))
        return;

    hr = pCoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, IID_IMultiLanguage2, (LPVOID *) &pML2);
    if (SUCCEEDED(hr))
    {
        if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_INTERNATIONAL, NULL, NULL, NULL, KEY_SET_VALUE|KEY_READ, NULL, &hKey, NULL))
        {
            LCID lcid;
            RFC1766INFO Rfc1766Info;
            TCHAR sz1[MAX_LIST_STRING_LEN], sz2[MAX_RFC1766_NAME];

            cb = sizeof(szBuf);
            if (ERROR_SUCCESS == RegQueryValueEx(hKey, REGSTR_VAL_ACCEPT_LANGUAGE, NULL, NULL, (LPBYTE)szBuf, &cb))
            {
                p1 = p2 = szBuf;
                while (NULL != *p1)
                {
                    WCHAR wsz[MAX_LIST_STRING_LEN];
                    BOOL bEnd = FALSE;

                    while (TEXT(',') != *p2 && NULL != *p2)
                        p2 = CharNext(p2);
                    if (NULL != *p2)
                        *p2 = NULL;
                    else
                        bEnd = TRUE;
                    p3 = p1;
                    while (TEXT(';') != *p3 && NULL != *p3)
                        p3 = CharNext(p3);
                    if (NULL != *p3)
                        *p3 = NULL;
#ifdef UNICODE
                    StrCpyN(wsz, p1, ARRAYSIZE(wsz));
#else
                    MultiByteToWideChar(CP_ACP, 0, p1, -1, wsz, MAX_RFC1766_NAME);
#endif
                    hr = pML2->GetLcidFromRfc1766(&lcid, wsz);
                    if (SUCCEEDED(hr))
                    {
                        hr = pML2->GetRfc1766Info(lcid, INETCPL_GetUILanguage(), &Rfc1766Info);
                        if (SUCCEEDED(hr))
                        {
#ifdef UNICODE
                            StrCpyN(sz1, Rfc1766Info.wszLocaleName, ARRAYSIZE(sz1));
#else
                            WideCharToMultiByte(CP_ACP, 0, Rfc1766Info.wszLocaleName, -1, sz1, MAX_LIST_STRING_LEN, NULL, NULL);
#endif
                            wnsprintf(sz, ARRAYSIZE(sz), FORMAT_STR, sz1, p1);
                        }
                    }
                    else
                    {
                        MLLoadString(IDS_USER_DEFINED, sz1, ARRAYSIZE(sz1));
                        wnsprintf(sz, ARRAYSIZE(sz), FORMAT_STR, sz1, p1);
                    }
                    ListBox_AddString(hwndList, sz);
                    if (TRUE == bEnd)
                        p1 = p2;
                    else
                        p1 = p2 = p2 + 1;
                }
            }
            else
            {
                lcid = GetUserDefaultLCID();

                hr = pML2->GetRfc1766Info(lcid, INETCPL_GetUILanguage(), &Rfc1766Info);
                if (SUCCEEDED(hr))
                {
#ifdef UNICODE
                    StrCpyN(sz1, Rfc1766Info.wszLocaleName,  ARRAYSIZE(sz1));
                    StrCpyN(sz2, Rfc1766Info.wszRfc1766,  ARRAYSIZE(sz2));
#else
                    WideCharToMultiByte(CP_ACP, 0, Rfc1766Info.wszLocaleName, -1, sz1, MAX_LIST_STRING_LEN, NULL, NULL);
                    WideCharToMultiByte(CP_ACP, 0, Rfc1766Info.wszRfc1766, -1, sz2, MAX_RFC1766_NAME, NULL, NULL);
#endif
                    wnsprintf(sz, ARRAYSIZE(sz), FORMAT_STR, sz1, sz2);
                    ListBox_AddString(hwndList, sz);
                }
            }
            RegCloseKey(hKey);
        }
        pML2->Release();
    }
    pCoUninitialize();
}

//
// LanguageDlgInit()
//
// Initializes the Language dialog.
//
BOOL LanguageDlgInit(IN HWND hDlg)
{
    if (!hDlg)
        return FALSE;   // nothing to initialize

    FillAcceptListBox(hDlg);

    EnableWindow(GetDlgItem(hDlg, IDC_LANG_REMOVE_BUTTON), FALSE);
    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_UP_BUTTON), FALSE);
    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_DOWN_BUTTON), FALSE);
    EnableWindow(GetDlgItem(hDlg, IDC_LANG_ADD_BUTTON), !g_restrict.fInternational);
    
    // On NT5, we use NT5's MUI feature instead of IE5 plugui
    if (IsOS(OS_WIN2000ORGREATER))
        ShowWindow(GetDlgItem(hDlg, IDC_LANG_UI_PREF), SW_HIDE);
    else
    {
        UINT uiACP = GetACP();

        // We don't support PlugUI on these platforms
        if (uiACP == CP_ARABIC || uiACP == CP_HEBREW || uiACP == CP_THAI)
            ShowWindow(GetDlgItem(hDlg, IDC_LANG_UI_PREF), SW_HIDE);
        else
            EnableWindow(GetDlgItem(hDlg, IDC_LANG_UI_PREF), !g_restrict.fInternational);
    }

    // show the current UI lang
    InitCurrentUILang(hDlg);
    
    // everything ok
    return TRUE;
}

//
// SaveLanguageData()
//
// Save the new language settings into regestry
//
void SaveLanguageData(IN HWND hDlg)
{
    HKEY hKey;
    DWORD dw;
    int i, iNumItems, iQ, n;
    TCHAR szBuf[MAX_ACCEPT_LANG_LEN];

    if (ERROR_SUCCESS == RegCreateKeyEx(HKEY_CURRENT_USER, REGSTR_PATH_INTERNATIONAL, NULL, NULL, NULL, KEY_WRITE, NULL, &hKey, &dw ))
    {
        HWND hwndList = GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST);

        iNumItems = ListBox_GetCount(hwndList);

        for (n = 1, iQ = 10; iQ < iNumItems; iQ *= 10, n++)
            ;

        szBuf[0] = NULL;
        for (i = 0; i < iNumItems; i++)
        {
            TCHAR sz[MAX_LIST_STRING_LEN], *p1, *p2;

            ListBox_GetText(hwndList, i, sz);
            p1 = sz;
            // We can assume safely there is '[' and ']' in this string.
            while (TEXT('[') != *p1)
                p1 = CharNext(p1);
            p1 = p2 = p1 + 1;
            while (TEXT(']') != *p2)
                p2 = CharNext(p2);
            *p2 = NULL;
            if (0 == i)
                StrCpyN(szBuf, p1, ARRAYSIZE(szBuf));
            else
            {
                TCHAR szF[MAX_ACCEPT_LANG_LEN], szQ[MAX_ACCEPT_LANG_LEN];

                int len = lstrlen(szBuf);
                StrCpyN(szBuf + len, TEXT(","), ARRAYSIZE(szBuf) - len);
                len++;
                StrCpyN(szBuf + len, p1, ARRAYSIZE(szBuf) - len);
                wnsprintf(szF, ARRAYSIZE(szF), TEXT(";q=0.%%0%dd"), n);
                wnsprintf(szQ, ARRAYSIZE(szQ), szF, ((iNumItems - i) * iQ + (iNumItems / 2)) / iNumItems);
                len = lstrlen(szBuf);
                StrCpyN(szBuf + len , szQ, ARRAYSIZE(szBuf) - len);
            }
        }
        RegSetValueEx(hKey, REGSTR_VAL_ACCEPT_LANGUAGE, NULL, REG_SZ, (LPBYTE)szBuf, (lstrlen(szBuf)+1)*sizeof(TCHAR));
        RegCloseKey(hKey);
    }
}

// MoveUpDownListItem()
//
// Move selected list item up or down
//
void MoveUpDownListItem(HWND hDlg, HWND hwndList, BOOL bUp)
{
    int i, iNumItems;
    TCHAR sz[MAX_LIST_STRING_LEN];

    i = ListBox_GetCurSel(hwndList);
    iNumItems = ListBox_GetCount(hwndList);
    ListBox_GetText(hwndList, i, sz);
    ListBox_DeleteString(hwndList, i);

    i += (bUp)? -1: 1;
    if (i < 0)
        i = 0;
    else if (i >= iNumItems)
        i = iNumItems - 1;
    ListBox_InsertString(hwndList, i, sz);
    ListBox_SetSel(hwndList, TRUE, i);
    ListBox_SetCurSel(hwndList, i);

    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_UP_BUTTON), i != 0);
    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_DOWN_BUTTON), i < iNumItems - 1);

    if (NULL == GetFocus()) // This prevent keyboard access disable
        SetFocus(hwndList);
}


//
// FillLanguageListBox()
//
// Fills the language listbox with the names of available languages
//
BOOL FillLanguageListBox(IN HWND hDlg)
{
    IMultiLanguage2 *pML2;
    HRESULT hr;
    TCHAR sz[MAX_LIST_STRING_LEN], sz1[MAX_LOCALE_NAME], sz2[MAX_RFC1766_NAME];
    HWND hwndEdit = GetDlgItem(hDlg, IDC_LANG_USER_DEFINED_EDIT);
    HWND hwndList = GetDlgItem(hDlg, IDC_LANG_AVAILABLE_LIST);
    HWND hwndAccept = GetDlgItem(GetParent(hDlg), IDC_LANG_ACCEPT_LIST);
    
    SendMessage(hwndEdit, EM_SETLIMITTEXT, 16, 0L); // Set Limit text as 16 characters

    if(!hOLE32)
    {
        if(!_StartOLE32())
        {
            ASSERT(FALSE);
            return FALSE;
        }
    }
    hr = pCoInitialize(NULL);
    if (FAILED(hr))
        return FALSE;

    hr = pCoCreateInstance(CLSID_CMultiLanguage, NULL, CLSCTX_INPROC_SERVER, IID_IMultiLanguage2, (LPVOID *) &pML2);
    if (SUCCEEDED(hr))
    {
        IEnumRfc1766 *pEnumRfc1766;
        RFC1766INFO Rfc1766Info;

        if (SUCCEEDED(pML2->EnumRfc1766(INETCPL_GetUILanguage(), &pEnumRfc1766)))
        {
            while (S_OK == pEnumRfc1766->Next(1, &Rfc1766Info, NULL))
            {
#ifdef UNICODE
                StrCpyN(sz1, Rfc1766Info.wszLocaleName, ARRAYSIZE(sz1));
                StrCpyN(sz2, Rfc1766Info.wszRfc1766,  ARRAYSIZE(sz2));
#else
                WideCharToMultiByte(CP_ACP, 0, Rfc1766Info.wszLocaleName, -1, sz1, MAX_LOCALE_NAME, NULL, NULL);
                WideCharToMultiByte(CP_ACP, 0, Rfc1766Info.wszRfc1766, -1, sz2, MAX_RFC1766_NAME, NULL, NULL);
#endif
                wnsprintf(sz, ARRAYSIZE(sz), FORMAT_STR, sz1, sz2);
                if (LB_ERR == ListBox_FindStringExact(hwndAccept, -1, sz))
                    ListBox_AddString(hwndList, sz);
            }
            pEnumRfc1766->Release();
        }
        pML2->Release();
    }
    pCoUninitialize();
    
    // everything ok
    return TRUE;
}

//
// AddLanguage()
//
// Add selected language to accept language listbox.
//
void AddLanguage(IN HWND hDlg)
{
    int i, j, *pItems, iNumItems, iIndex;
    TCHAR sz[MAX_LIST_STRING_LEN];
    HWND hdlgParent = GetParent(hDlg);
    HWND hwndFrom = GetDlgItem(hDlg, IDC_LANG_AVAILABLE_LIST);
    HWND hwndTo = GetDlgItem(hdlgParent, IDC_LANG_ACCEPT_LIST);

    i = ListBox_GetSelCount(hwndFrom);
    if (0 < i && (pItems = (PINT)LocalAlloc(LPTR, sizeof(int)*i)))
    {
        ListBox_GetSelItems(hwndFrom, i, pItems);
        for (j = 0; j < i; j++)
        {
            ListBox_GetText(hwndFrom, pItems[j], sz);
            ListBox_AddString(hwndTo, sz);
        }
        LocalFree(pItems);
    }
    if (GetWindowTextLength(GetDlgItem(hDlg, IDC_LANG_USER_DEFINED_EDIT)))
    {
        TCHAR *p, sz1[MAX_LIST_STRING_LEN], sz2[MAX_LIST_STRING_LEN];
        BOOL fValid = TRUE;

        GetWindowText(GetDlgItem(hDlg, IDC_LANG_USER_DEFINED_EDIT), sz2, ARRAYSIZE(sz2));
        p = sz2;
        while (NULL != *p && TRUE == fValid)
        {
            switch (*p)
            {
                // Invalid characters for user-defined string
                case TEXT(','):
                case TEXT(';'):
                case TEXT('['):
                case TEXT(']'):
                case TEXT('='):
                    fValid = FALSE;
                    break;

                default:
                    p = CharNext(p);
            }
        }
        if (FALSE == fValid)
        {
            TCHAR szTitle[256], szErr[1024];

            MLLoadShellLangString(IDS_USER_DEFINED_ERR, szErr, ARRAYSIZE(szErr));
            GetWindowText(hDlg, szTitle, ARRAYSIZE(szTitle));
            MessageBox(hDlg, szErr, szTitle, MB_OK | MB_ICONHAND);
        }
        else
        {
            MLLoadString(IDS_USER_DEFINED, sz1, ARRAYSIZE(sz1));
            wnsprintf(sz, ARRAYSIZE(sz), FORMAT_STR, sz1, sz2);
            ListBox_AddString(hwndTo, sz);
        }
    }
    iIndex = ListBox_GetCurSel(hwndTo);
    if (LB_ERR != iIndex)
    {
        iNumItems = ListBox_GetCount(hwndTo);
        EnableWindow(GetDlgItem(hdlgParent, IDC_LANG_REMOVE_BUTTON), iNumItems > 0);
        EnableWindow(GetDlgItem(hdlgParent, IDC_LANG_MOVE_UP_BUTTON), iIndex > 0);
        EnableWindow(GetDlgItem(hdlgParent, IDC_LANG_MOVE_DOWN_BUTTON), iIndex < iNumItems - 1);
    }
}

int ComboBoxEx_AddString(IN HWND hwndCtl, IN LPCTSTR sz)
{
    COMBOBOXEXITEM cbexItem = {0};
    
    int csz = _tcslen(sz);

    cbexItem.mask = CBEIF_TEXT;
    cbexItem.pszText = (LPTSTR)sz;
    cbexItem.cchTextMax = csz;
    
    // sort the string based on the current locale
    // we don't bother to use binary search because
    // the list is up to 25 item
    TCHAR szItem[MAX_LOCALE_NAME];
    int i, itemCount = ComboBox_GetCount(hwndCtl);
    for (i = 0; i < itemCount; i++)
    {
        ComboBox_GetLBText(hwndCtl, i, szItem);
        if (CompareString(INETCPL_GetUILanguage(), 
                          0,
                          sz,
                          csz,
                          szItem,
                          ARRAYSIZE(szItem)) == CSTR_LESS_THAN)
        {
            break;
        }
    }
    cbexItem.iItem = i;
    
    SendMessage(hwndCtl, CBEM_INSERTITEM, (WPARAM)0, (LPARAM)(LPVOID)&cbexItem);
    return i;
}

BOOL FillUILangListBox(IN HWND hDlg, CUILangList *pLangList)
{
    HWND hwndCombo = GetDlgItem(hDlg, IDC_COMBO_UILANG);
    BOOL bNT5 = IsOS(OS_WIN2000ORGREATER);
    DWORD dwAcp = GetACP();
    LPCTSTR pszLangName;
    
    if (!pLangList)
        return FALSE;

    // fill the list up.
    for (int i = 0; i < pLangList->GetListSize(); i++)
    {
        if (!pLangList->IsValidLang(i))
            continue;

        if (!bNT5)
        {
            LANGID lid = pLangList->GetLangIdOfIdx(i);

            if (dwAcp == CP_THAI || dwAcp == CP_ARABIC || dwAcp == CP_HEBREW)
            {
                // do not support cross codepage PlugUI
                // on Thai or Middle East platform(Arabic/Hebrew)
                static DWORD dwDefCP = 0;

                if (dwDefCP == 0)
                {
                    TCHAR szLcData[6+1]; // +2 for '0x' +1 for terminator

                    GetLocaleInfo( MAKELCID(lid, SUBLANG_NEUTRAL),
                        LOCALE_IDEFAULTANSICODEPAGE, szLcData, ARRAYSIZE(szLcData));
                                       
                    dwDefCP = StrToInt(szLcData);
                }
                if (dwDefCP != dwAcp && lid != 0x0409 && lid != GetInstallLanguage())
                    continue;
            }
            else
            {
                // skip Arabic and Hebrew on non-supporting platform
                if (lid == 0x401 || lid == 0x40d)
                    continue;
            }
        }

        pszLangName = pLangList->GetLangNameOfIdx(i);

        // ComboBox_FindStringExact has problems to handle DBCS Unicode characters 
        if (pszLangName)
        {
            int ipos = ComboBoxEx_AddString(hwndCombo, pszLangName);
            if (ipos >= 0)
            {
                ComboBox_SetItemData(hwndCombo, ipos, i);
            }
        }
    }

    // show the current selection
    int iLangIdx = pLangList->GetCurrentLangIdx();
    if (iLangIdx >= 0)
    {
        int iCBPos;
        int iCBSize = ComboBox_GetCount(hwndCombo);
        for (iCBPos = 0; iCBPos < iCBSize; iCBPos++)
        {
            if (iLangIdx == ComboBox_GetItemData(hwndCombo, iCBPos))
                break;
        }

        if (iCBPos < iCBSize)
            ComboBox_SetCurSel(hwndCombo, iCBPos);
    }
    return TRUE;
}

//
// Shutdown/reboot procedures implementation
//
// synopsis: CShutDownInfo class implements the method and the process list
//           which handle the sequence.
//           s_arryClsNames[] holds the list of target application
//           ChangeLanguage() (global) triggers the sequence being called from
//           LangChangeDlgProc().
//
static const LPTSTR s_arryClsNames[] =  
{
    TEXT("IEFrame"),                       // browser instance
    TEXT("ThorBrowserWndClass"),           // OE 
    TEXT("HH Parent"),                     // Html Help
    TEXT("MPWClass"),                      // 
    TEXT("Outlook Express Browser Class"), // OE
    TEXT("ATH_Note"),                      // OE?
    TEXT("WABBrowseView"),                 // WAB
    TEXT("Afx:400000:8:10008:0:900d6"),
    TEXT("Media Player 2"),
    TEXT("FrontPageExpressWindow"), 
    TEXT("MSBLUIManager"),                 // Messenger
};

//
// CShutDownInfo
// class methods implementation
//
#define SHUTDOWN_TIMEOUT 2000 // 2 sec
#define RELAUNCH_TIMEOUT 1000 // 1 sec
CShutDownProcInfo::CShutDownProcInfo(HWND hDlg)
{
    _pProcList = NULL;
    _nAlloced = 0;
    _iProcList = 0;
    _hdlgParent = hDlg;
    _fAllShutDown = FALSE;
}

CShutDownProcInfo::~CShutDownProcInfo()
{
    if (_pProcList)
        LocalFree(_pProcList);
}


HRESULT CShutDownProcInfo::EnsureProcList()
{
    HRESULT hr = S_OK;
    if (!_pProcList)
    {
        // alloc mem for practical # of processes
        _nAlloced = ARRAYSIZE(s_arryClsNames);
        _pProcList = (PROCLIST *)LocalAlloc(LPTR, sizeof(PROCLIST)*_nAlloced);
    }
    if (!_pProcList) 
    {
        _nAlloced = 0;
        hr = E_FAIL;
    }

    return hr;
}
HRESULT CShutDownProcInfo::IncreaseProcList()
{
    HRESULT hr = S_OK;
    PROCLIST * pl = NULL;
    // realloc mem every so often
    if (_iProcList+1 > _nAlloced)
    {
        pl = (PROCLIST *)LocalReAlloc(_pProcList, sizeof(PROCLIST)*(ARRAYSIZE(s_arryClsNames)+_nAlloced), 
                                      LMEM_MOVEABLE | LMEM_ZEROINIT);
        if (pl)
        {
            _nAlloced += ARRAYSIZE(s_arryClsNames);
            _pProcList =  pl;
        }
        else
           hr = E_FAIL;
    }

    if (hr == S_OK)
        _iProcList++;

    return hr;
}
// CShutDownProcInfo::AddToProcList()
//
// synopsis: Get process info from given window handle
//           store it for shutdown procedure
//
//
//
HRESULT CShutDownProcInfo::AddToProcList(HWND hwnd)
{
    HRESULT hr = S_OK;

    hr = EnsureProcList();
    if (SUCCEEDED(hr) && hwnd)
    {
        DWORD dwPID;
        BOOL  fFoundDup = FALSE;

        GetWindowThreadProcessId(hwnd, &dwPID);
        
        // check to see if we already have the PID in the list
        for (int i=0; i < _iProcList; i++)
        {
            if (_pProcList[i].dwPID == dwPID)
            {
                fFoundDup = TRUE;
                break;
            }
        }

        // add proccess info only if we don't have it already
        if (!fFoundDup)
        {
            hr = IncreaseProcList();
            if (SUCCEEDED(hr))
            {
                int iCur = _iProcList-1;

                GetExeNameFromPID(dwPID, 
                    _pProcList[iCur].szExeName, 
                    ARRAYSIZE(_pProcList[iCur].szExeName));

                _pProcList[iCur].dwPID = dwPID;
                _pProcList[iCur].State = PS_UNKNOWN;
            }
        }
    }
    return hr;    
}

// CShutDownProcInfo::WaitForOneProcess
//
// synopsis: ensures the given process 
//           has terminated
//
//
HRESULT CShutDownProcInfo::WaitForOneProcess(int iProc)
{
    HRESULT hr = S_OK;
    if (iProc < _iProcList && _pProcList[iProc].State != PS_SHUTDOWN_OK)
    {
        DWORD dwProcessFlags = PROCESS_ALL_ACCESS | 
                               (_fNT ? SYNCHRONIZE : 0 );

        HANDLE hProc = OpenProcess(dwProcessFlags,
                                   FALSE,    
                                   _pProcList[iProc].dwPID);

        // pressume it has terminated, get it marked so
        _pProcList[iProc].State = PS_SHUTDOWN_OK;

        if (hProc) 
        {
            // if the proccess in query is still alive,
            // we'll wait with time out here
            //
            DWORD dwRet = WaitForSingleObject (hProc, SHUTDOWN_TIMEOUT);
            if (dwRet == WAIT_TIMEOUT)
            {
                _pProcList[iProc].State = PS_WAITING;
            }
            
            CloseHandle(hProc);
        }
    }
    return hr;
}

// CShutDownProcInfo::WaitForFolksShutDown
//
// synopsis: ensure the nominated processes terminate. If anyone 
//           doesn't want to terminate, wait for her retrying a couple of
//           times and note her name so we can show it to the user. 
//          
//
#define MAXSHUTDOWNTRY 10
HRESULT CShutDownProcInfo::WaitForFolksShutDown()
{
    HRESULT hr = S_OK;
    int    iTry = 0;
    do
    {
        // pressume all will be fine
        _fAllShutDown = TRUE;
        // waiting loop
        for (int i = 0; i < _iProcList; i++)
        {
            WaitForOneProcess(i);
            if (_pProcList[i].State != PS_SHUTDOWN_OK)
                _fAllShutDown = FALSE;
        }
    }
    while( !_fAllShutDown && iTry++ < MAXSHUTDOWNTRY  );
    // FEATURE: here we should put up a dialog
    //         to ask user if they want to wait
    //         for the apps 

    return hr;
}

// CShutDownProcInfo::NotifyShutDownToFolks
//
// synopsis: send POI_OFFICE_COMMAND to possible candidates on the desktop
//           if a candidate replies with valid value, save the proccess
//           information for the later restart procedure.
//
HRESULT CShutDownProcInfo::NotifyShutDownToFolks(int *pnProcess)
{
    HWND hwndShutDown, hwndAfter;
    PLUGUI_QUERY pq;
    HRESULT hr = S_OK;
    int     nProcToShutDown = 0;

    for (int i = 0; i < ARRAYSIZE(s_arryClsNames); i++)
    {
        hwndAfter = NULL; 
        while (hwndShutDown = FindWindowEx(NULL, hwndAfter, s_arryClsNames[i], NULL))
        {
            pq.uQueryVal = (UINT)SendMessage(hwndShutDown, PUI_OFFICE_COMMAND, PLUGUI_CMD_QUERY, 0);
            if (pq.uQueryVal)
            {
                if(pq.PlugUIInfo.uMajorVersion == OFFICE_VERSION_9)
                {
                    PostMessage(hwndShutDown, PUI_OFFICE_COMMAND, (WPARAM)PLUGUI_CMD_SHUTDOWN, 0);

                    // store the information about the process which this window belongs to
                    // we only need to remember non OLE processes here for re-starting.
                    if (!pq.PlugUIInfo.uOleServer)
                    {
                        AddToProcList(hwndShutDown);
                        nProcToShutDown ++;
                    }
                }
            }
            hwndAfter = hwndShutDown;
        }
    }
    if (!nProcToShutDown)
        hr = S_FALSE;

    if (pnProcess)
        *pnProcess = nProcToShutDown;

    return hr;
}

const TCHAR c_szRegAppPaths[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\");
HRESULT CShutDownProcInfo::GetRestartAppPath(LPTSTR szPath, int cchPath, int iProc)
{
    HRESULT hr = S_OK;
    TCHAR szAppPath[MAX_PATH];
    TCHAR szRegKey[MAX_PATH];

    ASSERT(szPath && cchPath > 0);

    if (iProc < _iProcList)
    {
        _tcscpy(szRegKey, c_szRegAppPaths);
        _tcscat(szRegKey, _pProcList[iProc].szExeName);
        
        DWORD cb = sizeof(szAppPath);
        if (ERROR_SUCCESS != SHGetValue(HKEY_LOCAL_MACHINE, szRegKey, NULL, NULL, szAppPath, &cb))
        {
            szPath[0] = TEXT('0');
            hr = E_FAIL;
        }
        else
            _tcsncpy(szPath, szAppPath, cchPath);
    }
    return hr;
}

HRESULT CShutDownProcInfo::RestartFolks()
{
    PROCESS_INFORMATION pi;
    for (int i = 0; i < _iProcList; i++)
    {
        STARTUPINFO si = {0};
        si.cb = sizeof(si);
        if (_pProcList[i].State == PS_SHUTDOWN_OK)
        {
            TCHAR szAppPath[MAX_PATH];    
            HRESULT hr = GetRestartAppPath(szAppPath, ARRAYSIZE(szAppPath), i);
            if (hr == S_OK)
            {
                BOOL fLaunchedOK = 
                CreateProcess (szAppPath,               // name of app to launch
                                NULL,                   // lpCmdLine
                                NULL,                   // lpProcessAttributes
                                NULL,                   // lpThreadAttributes
                                TRUE,                   // bInheritHandles
                                NORMAL_PRIORITY_CLASS,  // dwCreationFlags
                                NULL,                   // lpEnvironment
                                NULL,                   // lpCurrentDirectory
                                &si,                    // lpStartupInfo
                                &pi);                   // lpProcessInformation

                if (fLaunchedOK)
                { 
                    DWORD dwRet = WaitForInputIdle (pi.hProcess,
                                                    RELAUNCH_TIMEOUT);
                    CloseHandle(pi.hProcess);
                    CloseHandle(pi.hThread);
                }
            }
        }
    }
    return S_OK;
}



// 
//   CShutDownProcInfo::ShutDownThreadProc
//
//   synopsis: launched from changelang dialog so the dialog
//             wouldn't get blocked when we're waiting for our apps
//             to shutdown/restart. this is a static proc
//             so we should be able to delete the class instance
//             in this proc.
//
DWORD CALLBACK CShutDownProcInfo::ShutDownThreadProc(void *pv)
{
    CShutDownProcInfo *pspi = (CShutDownProcInfo *)pv;
    
    if (pspi)
    {
        HRESULT hr;
        int     nToShutDown;
        // send PUI_OFFICE_COMMAND to corresponding folks...
        hr = pspi->NotifyShutDownToFolks(&nToShutDown);

        // and wait until all processes shutdown
        if (SUCCEEDED(hr) && nToShutDown > 0)
        {
            hr = pspi->WaitForFolksShutDown();

            // then restart here
            if (SUCCEEDED(hr))
               pspi->RestartFolks();
        }
    
        // now the parent dialog should go away
        int iret = (nToShutDown > 0) ? 
                   RETURN_SETLANG_ENDLANGDIALOG: RETURN_SETLANG_CLOSEDNORMAL;
        
        EndDialog(pspi->_hdlgParent, iret);
    
        // delete this class instance
        delete pspi;
    }
    return 0;
}

void OpenSatelliteDownloadUrl(HWND hDlg)
{
    // get the default Url from registry
    TCHAR szSatelliteUrl[INTERNET_MAX_URL_LENGTH];

    // reg api needs size in byte
    DWORD dwType, dwcbData = sizeof(szSatelliteUrl);
    
    DWORD dwRet =  SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_PATH_INTERNATIONAL, 
                             NULL, &dwType, (void *)szSatelliteUrl, &dwcbData);
    if (dwRet != ERROR_SUCCESS || !szSatelliteUrl[0])
    {
       // use the hard coded Url instead
       _tcscpy(szSatelliteUrl, s_szUrlSPK);
    }

    if(!hOLE32)
    {
        if(!_StartOLE32())
        {
            ASSERT(FALSE);
            return;
        }
    }

    HRESULT hr = pCoInitialize(NULL);
    if (SUCCEEDED(hr))
    {
        NavToUrlUsingIE(szSatelliteUrl, TRUE);
        pCoUninitialize();
    }
}

INT_PTR CALLBACK LangMsgDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_COMMAND:
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDYES:
                case IDNO:
                case IDOK:
                case IDCANCEL:
                    EndDialog(hDlg, GET_WM_COMMAND_ID(wParam, lParam));
                    break;
            }
            return TRUE;
        }

        case WM_HELP:           // F1
            ResWinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle, IDS_HELPFILE,
                HELP_WM_HELP, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);
            break;

        case WM_CONTEXTMENU:    // right mouse click
            ResWinHelp( (HWND) wParam, IDS_HELPFILE,
                HELP_CONTEXTMENU, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);        
            break;
    }
    return FALSE;
}

BOOL ChangeLanguage(IN HWND hDlg, CUILangList *pLangList)
{
    HWND hwndCombo = GetDlgItem(hDlg, IDC_COMBO_UILANG);
    int iSel = ComboBox_GetCurSel(hwndCombo);
    INT_PTR idxSel = 0;
    int idxCur;
    
    if (iSel != CB_ERR)
        idxSel = ComboBox_GetItemData(hwndCombo, iSel);

    if ( idxSel != CB_ERR 
        && idxSel < pLangList->GetListSize())
    {
        idxCur = pLangList->GetCurrentLangIdx();

        if (idxCur != idxSel)
        {
            INT_PTR iRet = DialogBox(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG_WARNING), hDlg, LangMsgDlgProc);

            if (IDCANCEL != iRet)
            {
                pLangList->SetCurrentLangIdx((int)idxSel);

                if (IDYES == iRet)
                {
                    CShutDownProcInfo  *pspi = new CShutDownProcInfo(hDlg);
                    if (!SHCreateThread(pspi->ShutDownThreadProc, (void *)pspi, 0, NULL))
                        delete pspi;

                    // returning TRUE to indicate that we do shutdown/restart
                    return TRUE;
                }
                else
                {
                    DialogBox(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG_INFO), hDlg, LangMsgDlgProc);
                }
            }
        }
    }
    // returning FALSE to indicate that we haven't changed the language
    return FALSE;
}

//
// LangChangeDlgProc()
//
// Message handler for the "Change Language" subdialog.
//
INT_PTR CALLBACK LangChangeDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CUILangList *pLangList;
    switch (uMsg)
    {
        case WM_INITDIALOG:
            CUILangList::GetLangList(GetParent(hDlg), &pLangList);
            return FillUILangListBox(hDlg, pLangList);
    
        case WM_DESTROY:
            break;

        case WM_COMMAND:
            switch(GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDC_LANG_ADDSPK:
                    // open url from resource
                    OpenSatelliteDownloadUrl(hDlg);
                    EndDialog(hDlg, RETURN_SETLANG_ENDLANGDIALOG);
                    break;
                case IDOK:
                    if(!SUCCEEDED(CUILangList::GetLangList(GetParent(hDlg), &pLangList))
                      || !ChangeLanguage(hDlg, pLangList))
                      EndDialog(hDlg, 0);

                    // EndDialog() is called in separate thread 
                    // when shutdown/restart is done
                    // 
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    break;
            }
            break;

        case WM_HELP:           // F1
            ResWinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle, IDS_HELPFILE,
                HELP_WM_HELP, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);
            break;

        case WM_CONTEXTMENU:    // right mouse click
            ResWinHelp( (HWND) wParam, IDS_HELPFILE,
                HELP_CONTEXTMENU, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);        
            break;

        default:
            return FALSE;
    }
    return TRUE;
}

//
// LangAddDlgProc()
//
// Message handler for the "Add Language" subdialog.
//
INT_PTR CALLBACK LangAddDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_INITDIALOG:
            return FillLanguageListBox(hDlg);
    
        case WM_DESTROY:
            break;

        case WM_COMMAND:
            switch(GET_WM_COMMAND_ID(wParam, lParam))
            {
                case IDOK:
                    AddLanguage(hDlg);
                    EndDialog(hDlg, 0);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    break;
            }
            break;

        case WM_HELP:           // F1
            ResWinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle, IDS_HELPFILE,
                HELP_WM_HELP, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);
            break;

        case WM_CONTEXTMENU:    // right mouse click
            ResWinHelp( (HWND) wParam, IDS_HELPFILE,
                HELP_CONTEXTMENU, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);        
            break;

        default:
            return FALSE;
    }
    return TRUE;
}

// put any cleanup procedures for language dialog here
void LangDlgCleanup(HWND hDlg)
{
    // also delete and remove the instance of
    // UI language list from window prop
    CUILangList::RemoveLangList(hDlg);
}
//
// LanguageDlgProc()
//
// Message handler for the "Language Preference" subdialog.
//
INT_PTR CALLBACK LanguageDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CUILangList *pLangList;
    switch (uMsg)
    {
        case WM_INITDIALOG:
            return LanguageDlgInit(hDlg);
    
        case WM_DESTROY:
            LangDlgCleanup(hDlg);
            break;

        case WM_COMMAND:
            switch(GET_WM_COMMAND_ID(wParam, lParam))
            {
                HWND hwndList;
                int iIndex, iNumItems;
                INT_PTR iret;

                case IDOK:
                    SaveLanguageData(hDlg);
                    EndDialog(hDlg, 0);
                    break;

                case IDCANCEL:
                    EndDialog(hDlg, 0);
                    break;

                case IDC_LANG_ADD_BUTTON:
                    DialogBox(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG_ADD), hDlg, LangAddDlgProc);
                    break;

                case IDC_LANG_UI_PREF:
                    CUILangList::GetLangList(hDlg, &pLangList);
                    iret = KickSetLang(hDlg, pLangList);
                    if (iret == RETURN_SETLANG_ENDLANGDIALOG)
                    {
                        // we're outa job
                        EndDialog(hDlg, 0);
                    }
                    else
                    {
                        InitCurrentUILang(hDlg);
                    }
                    break;

                case IDC_LANG_REMOVE_BUTTON:
                    hwndList = GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST);
                    iIndex = ListBox_GetCurSel(hwndList);
                    ListBox_DeleteString(hwndList, iIndex);
                    iNumItems = ListBox_GetCount(hwndList);
                    if (iNumItems == iIndex)
                        iIndex--;
                    ListBox_SetCurSel(hwndList, iIndex);
                    EnableWindow(GetDlgItem(hDlg, IDC_LANG_REMOVE_BUTTON), (iNumItems > 0) && !g_restrict.fInternational);
                    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_UP_BUTTON), (iIndex > 0) && !g_restrict.fInternational);
                    EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_DOWN_BUTTON), (iIndex < iNumItems - 1) && !g_restrict.fInternational);

                    if (NULL == GetFocus()) // This prevent keyboard access disable
                        SetFocus(hwndList);
                    break;

                case IDC_LANG_ACCEPT_LIST:
                    hwndList = GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST);
                    iIndex = ListBox_GetCurSel(hwndList);
                    if (0 <= iIndex)
                    {
                        iNumItems = ListBox_GetCount(hwndList);
                        EnableWindow(GetDlgItem(hDlg, IDC_LANG_REMOVE_BUTTON), (iNumItems > 0) && !g_restrict.fInternational);
                        EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_UP_BUTTON), (iIndex > 0) && !g_restrict.fInternational);
                        EnableWindow(GetDlgItem(hDlg, IDC_LANG_MOVE_DOWN_BUTTON), (iIndex < iNumItems - 1) && !g_restrict.fInternational);
                    }
                    break;

                case IDC_LANG_MOVE_UP_BUTTON:
                    MoveUpDownListItem(hDlg, GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST), TRUE);
                    break;

                case IDC_LANG_MOVE_DOWN_BUTTON:
                    MoveUpDownListItem(hDlg, GetDlgItem(hDlg, IDC_LANG_ACCEPT_LIST), FALSE);
                    break;
            }
            break;

        case WM_HELP:           // F1
            ResWinHelp( (HWND)((LPHELPINFO)lParam)->hItemHandle, IDS_HELPFILE,
                HELP_WM_HELP, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);
            break;

        case WM_CONTEXTMENU:    // right mouse click
            ResWinHelp( (HWND) wParam, IDS_HELPFILE,
                HELP_CONTEXTMENU, (DWORD_PTR)(LPSTR)mapIDCsToIDHs);        
            break;

        default:
            return FALSE;
    }
    return TRUE;
}


//
// KickLanguageDialog
//
// synopsis : used for launching Language Preference sub dialog.
//            we need to launch the dialogbox as a separate process if inetcpl is 
//            invoked from Tools->Internet options. 
//            The reason: we shutdown every browser instances on desktop
//                        user chooses different UI language than the current,
//                        including the browser that launched inetcpl.
//
static const TCHAR  s_szRunDll32[] = TEXT("RunDll32.exe");
static const TCHAR  s_szKickLangDialog[] = TEXT(" inetcpl.cpl,OpenLanguageDialog");
void KickLanguageDialog(HWND hDlg)
{
    // 1: here we want to check to see if inetcpl was launched 
    //         as a rundll32 process already, which would happen if user 
    //         clicks on it at control panel folder
    //
    //
    BOOL fLaunchedOnBrowser = FALSE;
    
    // this tells me whether we got invoked from Tools->Internet Options...
    if (g_szCurrentURL[0])
    {
        fLaunchedOnBrowser = TRUE;
    }
    
    if (fLaunchedOnBrowser)
    {
        TCHAR szCommandLine[MAX_PATH];
        TCHAR szTitle[MAX_PATH];

        HWND hwndParent = GetParent(hDlg);
        
        StrCpy(szCommandLine, s_szRunDll32);
        StrCat(szCommandLine, s_szKickLangDialog);
        
        if (GetWindowText(hwndParent, szTitle, ARRAYSIZE(szTitle)) > 0)
        {
            StrCat(szCommandLine, TEXT(" "));
            StrCat(szCommandLine, szTitle);
        }
        
#ifdef USE_CREATE_PROCESS
        PROCESS_INFORMATION pi;
        STARTUPINFO si = {0};

        si.cb = sizeof(si);
        BOOL fLaunchedOK = 
        CreateProcess (szCommandLine,          // name of app to launch
                       NULL,                   // lpCmdLine
                       NULL,                   // lpProcessAttributes
                       NULL,                   // lpThreadAttributes
                       TRUE,                   // bInheritHandles
                       NORMAL_PRIORITY_CLASS,  // dwCreationFlags
                       NULL,                   // lpEnvironment
                       NULL,                   // lpCurrentDirectory
                       &si,                    // lpStartupInfo
                       &pi);                   // lpProcessInformation
#else
        char szAnsiPath[MAX_PATH];
        SHUnicodeToAnsi(szCommandLine, szAnsiPath, ARRAYSIZE(szAnsiPath));
        WinExec(szAnsiPath, SW_SHOWNORMAL);
#endif
    }
    else
    {
        DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG), hDlg, LanguageDlgProc, NULL);
    }
}

//
// KickSetLang
//
// synopsis : tries to find setlang.exe of Office9 first, if found it'll be kicked
//            if not, it uses our own setlang dialog.
//
//
static const TCHAR s_szOfficeInstallRoot[] = TEXT("Software\\Microsoft\\Office\\9.0\\Common\\InstallRoot");
static const TCHAR s_szOffice10InstallRoot[] = TEXT("Software\\Microsoft\\Shared");
static const TCHAR s_szPath[] = TEXT("Path");
static const TCHAR s_szOffice10Path[] = TEXT("OfficeSetLangInstallLocation");
static const TCHAR s_szSetLangExe[] = TEXT("setlang.exe");

INT_PTR KickSetLang(HWND hDlg, CUILangList *pLangList)
{
    BOOL fOfficeSetLangInstalled = FALSE;
    INT_PTR iret;
    
    TCHAR szSetLangPath[MAX_PATH];    
    
    //  deleting the key this way makes the key invalid for this process
    //  this way the inetcpl doesnt get bogus cached values
    SHDeleteKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\ShellNoRoam\\MUICache"));

    // try to get Office's setlang path
    if(pLangList && pLangList->IsOffice9Installed()) 
    {
        DWORD cb = sizeof(szSetLangPath);
        DWORD dwRet = SHGetValue(HKEY_LOCAL_MACHINE, s_szOffice10InstallRoot, s_szOffice10Path, NULL, szSetLangPath, &cb);

        // fall back to Office9 langpack setting if Office10 langpack setting isn't there
        if (ERROR_SUCCESS != dwRet)
        {
            cb = sizeof(szSetLangPath);            
            dwRet = SHGetValue(HKEY_LOCAL_MACHINE, s_szOfficeInstallRoot, s_szPath, NULL, szSetLangPath, &cb);
        }

        if (ERROR_SUCCESS == dwRet)
        {
            // If last character is a backslash
            if (szSetLangPath[lstrlen(szSetLangPath)-1] == TEXT('\\'))
            {
                // Then concatenate the exe name
                //
                StrCat(szSetLangPath, s_szSetLangExe);
            }
            if (PathFileExists(szSetLangPath) == TRUE)
                fOfficeSetLangInstalled = TRUE;
        }
    }
    
    if (fOfficeSetLangInstalled)
    {
        PROCESS_INFORMATION pi;
        STARTUPINFO si = {0};

        si.cb = sizeof(si);
        BOOL fLaunchedOK = CreateProcess(
                              szSetLangPath,     // name of app to launch
                                       NULL,     // lpCmdLine
                                       NULL,     // lpProcessAttributes
                                       NULL,     // lpThreadAttributes
                                       TRUE,     // bInheritHandles
                      NORMAL_PRIORITY_CLASS,     // dwCreationFlags
                                       NULL,     // lpEnvironment
                                       NULL,     // lpCurrentDirectory
                                       &si,      // lpStartupInfo
                                       &pi);     // lpProcessInformation
        // just wait a while
        if (fLaunchedOK)
        { 
            WaitForInputIdle (pi.hProcess, RELAUNCH_TIMEOUT);
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        iret = RETURN_SETLANG_ENDLANGDIALOG;
    }
    else
    {
        iret = DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG_CHANGE), hDlg, LangChangeDlgProc, NULL);
    }

    return iret;
}

//
// entry point for rundll32
// NOTE: the following function was written intentionally as non-Unicode
//       mainly because we don't have Wide wrapper mechanism for rundll32
//       function on win95
//
extern void GetRestrictFlags(RESTRICT_FLAGS *pRestrict);
void CALLBACK OpenLanguageDialog(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
{
    // hinst is ignored because we set it at our LibMain()
    INITCOMMONCONTROLSEX icex;

    GetRestrictFlags(&g_restrict);
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC  = ICC_USEREX_CLASSES|ICC_NATIVEFNTCTL_CLASS;
    InitCommonControlsEx(&icex);
    
    if (lpszCmdLine && *lpszCmdLine)
    {
        HWND hwndParent = FindWindowA(NULL, lpszCmdLine);
        if (hwndParent)
            hwnd = hwndParent;
    }
    DialogBoxParam(MLGetHinst(), MAKEINTRESOURCE(IDD_LANG), hwnd, LanguageDlgProc, NULL);
}

// MLGetUILanguage in shlwapi returns current MUI language regardless version.
// MUI architecture doesn't display string correctly when main dll and satellite
// pack versions are mismatched.
// A good example is IE version upgrade without upgrading satellite.
// So here is more clever way to get the MUI language.
//
// 1. Get MLGetUILangauge from shlwapi
// 2. Compare it with current installed language.
// 3. if those are different, try to get resource dll.
// 4. if the resource dll is not in correct path just return current installed
//    language.
// 5. Or return the langid of MLGetUILanguage.
LANGID INETCPL_GetUILanguage()
{
    HINSTANCE hMLInst;
    TCHAR szPath[MAX_PATH], szMUI[16];
    LANGID lidUI = MLGetUILanguage();

    if (IsOS(OS_WIN2000ORGREATER))
        return lidUI;

    if (lidUI != GetInstallLanguage())
    {
        hMLInst = MLGetHinst();
        if (GetModuleFileName(hMLInst, szPath, ARRAYSIZE(szPath)))
        {
            IntToHex(szMUI, 4, lidUI);
            if (StrStrI(szPath, szMUI) == NULL)
                lidUI = GetInstallLanguage();
        }
    }

    return lidUI;
}
