/*
 * control - Dialog box property sheet for "control panel"
 */

#include "tweakui.h"

/*
 * cchFriendlyMax is used to hold control panel file descriptions.
 */
#define cchFriendlyMax 256

typedef struct CPL {            /* control panel */
    TCHAR tszFile[MAX_PATH];    /* File name */
} CPL, *PCPL;

#define icplPlvi(plvi) ((UINT)(plvi)->lParam)
#define pcplIcpl(icpl) (&pcpii->pcpl[icpl])
#define pcplPlvi(plvi) pcplIcpl(icplPlvi(plvi))

typedef struct CPII {
    Declare_Gxa(CPL, cpl);
} CPII, *PCPII;

CPII cpii;
#define pcpii (&cpii)

TCHAR g_tszBoring[64];

#pragma BEGIN_CONST_DATA

const static DWORD CODESEG rgdwHelp[] = {
        0,                      0,
};

/*****************************************************************************
 *
 *  Control_VerQueryDescription
 *
 *  pull out the file description if we have one.
 *
 *****************************************************************************/

BOOL PASCAL
Control_VerQueryDescription(LPVOID pvData, UINT uiLang, UINT uiCharSet,
                            LPTSTR ptszBuf, UINT ctch)
{
    TCHAR tsz[128];
    LPWSTR pwsz;
    BOOL fRc;
    UINT cb;

    wsprintf(tsz, TEXT("\\StringFileInfo\\%04X%04X\\")
                  TEXT("FileDescription"), uiLang, uiCharSet);
    fRc = VerQueryValue(pvData, tsz, (LPVOID *)&pwsz, &cb);

    if (fRc && cb > 0) {
#ifdef UNICODE
        lstrcpyn(ptszBuf, pwsz, ctch);
#else
        WideCharToMultiByte(CP_ACP, 0, pwsz, cb / cbX(WCHAR),
                            ptszBuf, MAX_PATH, NULL, NULL);
        lstrcpyn(ptszBuf, (LPSTR)pwsz, ctch);
#endif
    }

    return fRc;
}


/*****************************************************************************
 *
 *  Control_GetFileDescription
 *
 *  pull out the file description if we have one.
 *
 *****************************************************************************/

void PASCAL
Control_GetFileDescription(LPTSTR ptszFile, LPTSTR ptszBuf, UINT ctch)
{
    DWORD dwHandle;
    UINT cb;

    /*
     *  Assume it doesn't work.
     */
    ptszBuf[0] = TEXT('\0');

    cb = GetFileVersionInfoSize(ptszFile, &dwHandle);
    if (cb) {
        LPVOID pvData = lAlloc(cb);
        if (pvData) {
            LPWORD rgwTrans;
            UINT uiSize;

            if (GetFileVersionInfo(ptszFile, dwHandle, cb, pvData) &&
                VerQueryValue(pvData, TEXT("\\VarFileInfo\\Translation"),
                              (LPVOID *)&rgwTrans, &uiSize)) {
                if (Control_VerQueryDescription(pvData,
                                                rgwTrans[0], rgwTrans[1],
                                                ptszBuf, ctch) ||
                    /*
                     *  Lots of people forget to set the language properly,
                     *  so we will try English/USA regardless.  And if that
                     *  doesn't work, try English/NULL because some people
                     *  do that too.
                     */
                    Control_VerQueryDescription(pvData,
                                                0x0409, 0x040E,
                                                ptszBuf, ctch) ||
                    Control_VerQueryDescription(pvData,
                                                0x0409, 0x0000,
                                                ptszBuf, ctch)) {
                }
            }
            lFree(pvData);
        }
    }
}

/*****************************************************************************
 *
 *  Control_GetShowState
 *
 *  Determine whether a *.CPL file is currently shown in the
 *  Control Panel.
 *
 *****************************************************************************/

BOOL PASCAL
Control_GetShowState(LPCTSTR ptszName)
{
    TCHAR tsz[10];

    GetPrivateProfileString(c_tszDontLoad, ptszName,
                            c_tszNil, tsz, cA(tsz), c_tszControlIni);

    return tsz[0] ? FALSE : TRUE;

}

/*****************************************************************************
 *
 *  Control_SetShowState
 *
 *  Set the registry/INI flag that tells us to hide/show the *.CPL file.
 *
 *****************************************************************************/

void PASCAL
Control_SetShowState(LPCTSTR ptszName, BOOL fShow)
{
    WritePrivateProfileString(c_tszDontLoad, ptszName,
                              fShow ? NULL : c_tszNo, c_tszControlIni);
}

/*****************************************************************************
 *
 *  Control_AddCpl
 *
 *  Add a single *.CPL to the list.
 *
 *  The display name is the CPL filename, followed by an optional
 *  parenthesized version description string.  For control panels
 *  that ship with Windows 95 whose version strings isn't very good, we substitute
 *  our own.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

LPCTSTR c_rgpszCpl[] = {
    c_tszAppWizCpl,
    c_tszDeskCpl,
    c_tszIntlCpl,
    c_tszMainCpl,
    c_tszTimeDateCpl,
};

int PASCAL
Control_AddCpl(HWND hwnd, LPTSTR ptszName)
{
    //
    //  On Windows 2000, ncpa.cpl is a stub that exists only for legacy
    //  purposes.  It does exactly the same thing as the "Network
    //  Connections" icon that already exists in the Control Panel.
    //
    if (g_fNT5 && lstrcmpi(ptszName, TEXT("ncpa.cpl")) == 0) {
        return -1;
    }

    PCPL pcpl = (PCPL)Misc_AllocPx(&pcpii->gxa);
    TCHAR tszDesc[MAX_PATH];
    TCHAR tszFull[MAX_PATH + 3 + MAX_PATH]; /* 3 = " - " */

    if (pcpl) {
        lstrcpy(pcpl->tszFile, ptszFilenameCqn(ptszName));
        CharLower(pcpl->tszFile);

        Control_GetFileDescription(ptszName, tszDesc, MAX_PATH);

        /*
         *  Now clean up various weird cases.
         */
        if (lstrcmpi(tszDesc, g_tszBoring) == 0) {
            int i;
            for (i = 0; i < cA(c_rgpszCpl); i++) {
                if (lstrcmpi(pcpl->tszFile, c_rgpszCpl[i]) == 0) {
                    LoadString(hinstCur, IDS_CPL_ADDRM + i, tszDesc, cA(tszDesc));
                    break;
                }
            }
        }

        wsprintf(tszFull, TEXT("%s - %s"), pcpl->tszFile, tszDesc);

        return LV_AddItem(hwnd, pcpii->ccpl++, tszFull, -1,
                          Control_GetShowState(pcpl->tszFile));
    } else {
        return -1;
    }
}

/*****************************************************************************
 *
 *  Control_InitControls2
 *
 *  Enumerate all the special DLLs in the MMCPL section and add them
 *  to the list.  Note that the buffer sizes are exactly the same sizes
 *  that SHELL32 uses, so we won't miss anything that the shell itself
 *  doesn't.
 *
 *****************************************************************************/

void PASCAL
Control_InitControls2(HWND hwnd)
{
    LPTSTR ptsz;
    TCHAR tszKeys[512];
    TCHAR tszName[64];

    GetPrivateProfileString(TEXT("MMCPL"), NULL, c_tszNil,
                            tszKeys, cA(tszKeys), c_tszControlIni);

    for (ptsz = tszKeys; ptsz[0]; ptsz += lstrlen(ptsz) + 1) {
        /*
         *  Some legacy keys to ignore:
         *
         *  NumApps=        <number of applets>
         *  H=              <height>
         *  W=              <width>
         *  X=              <x coordinate>
         *  Y=              <y coordinate>
         */
        if (lstrcmpi(ptsz, TEXT("NumApps")) == 0) {
            continue;
        }
        if (ptsz[1] == TEXT('\0')) {
            switch (ptsz[0]) {
            case 'H':
            case 'W':
            case 'X':
            case 'Y': continue;
            }
        }

        /*
         *  End of wacky legacy keys.
         */

        GetPrivateProfileString(TEXT("MMCPL"), ptsz, c_tszNil,
                                tszName, cA(tszName), c_tszControlIni);
        Control_AddCpl(hwnd, tszName);
    }
}

/*****************************************************************************
 *
 *  Control_InitControls3
 *
 *  Enumerate all the *.CPL files in the System folder
 *  and add them to the list.
 *
 *****************************************************************************/

void PASCAL
Control_InitControls3(HWND hwnd)
{
    WIN32_FIND_DATA wfd;
    HANDLE h;
    TCHAR tszSystemDir[MAX_PATH];
    TCHAR tszPrevDir[MAX_PATH];

    if (GetCurrentDirectory(cA(tszPrevDir), tszPrevDir) && /* For restore */
        GetSystemDirectory(tszSystemDir, MAX_PATH) &&
        SetCurrentDirectory(tszSystemDir)) {

        h = FindFirstFile(c_tszStarCpl, &wfd);
        if (h != INVALID_HANDLE_VALUE) {
            do {
                Control_AddCpl(hwnd, wfd.cFileName);
            } while (FindNextFile(h, &wfd)) ;
            FindClose(h);
        }
        SetCurrentDirectory(tszPrevDir);
    }
}

/*****************************************************************************
 *
 *  Control_OnInitDialog
 *
 *  Enumerate all the *.CPL files and add them to the list.
 *
 *****************************************************************************/

BOOL PASCAL
Control_OnInitDialog(HWND hwnd)
{
    HCURSOR hcurPrev = GetCursor();
    SetCursor(LoadCursor(0, IDC_WAIT));

    ZeroMemory(pcpii, cbX(*pcpii));

    LoadString(hinstCur, IDS_CPL_BORING, g_tszBoring, cA(g_tszBoring));

    if (Misc_InitPgxa(&pcpii->gxa, cbX(CPL))) {
        Control_InitControls2(hwnd);
        Control_InitControls3(hwnd);
    }

    SetCursor(hcurPrev);
    return 1;
}

/*****************************************************************************
 *
 *  Control_OnDestroy
 *
 *  Free the memory we allocated.
 *
 *****************************************************************************/

void PASCAL
Control_OnDestroy(HWND hdlg)
{
    Misc_FreePgxa(&pcpii->gxa);
}


/*****************************************************************************
 *
 *  Control_LV_Dirtify
 *
 *      Mark this item as having been changed during the property sheet
 *	page's lifetime.
 *
 *****************************************************************************/

void PASCAL
Control_LV_Dirtify(LPARAM icpl)
{
//    pcpii->pcpl[icpl].cplfl |= cplflEdited;
}

/*****************************************************************************
 *
 *  Control_OnApply
 *
 *      Write the changes out.
 *
 *****************************************************************************/

void PASCAL
Control_OnApply(HWND hdlg)
{
    HWND hwnd = GetDlgItem(hdlg, IDC_ICONLV);
    int cItems = ListView_GetItemCount(hwnd);
    BOOL fChanged = 0;
    LV_ITEM lvi;

    for (lvi.iItem = 0; lvi.iItem < cItems; lvi.iItem++) {
        PCPL pcpl;
        lvi.stateMask = LVIS_STATEIMAGEMASK;
        Misc_LV_GetItemInfo(hwnd, &lvi, lvi.iItem, LVIF_PARAM | LVIF_STATE);
        pcpl = pcplPlvi(&lvi);

        if (Control_GetShowState(pcpl->tszFile) != LV_IsChecked(&lvi)) {
            fChanged = 1;
            Control_SetShowState(pcpl->tszFile, LV_IsChecked(&lvi));
        }
    }

    if (fChanged) {
        ChangeNotifyCsidl(hdlg, CSIDL_CONTROLS, SHCNE_UPDATEDIR);
    }
}

/*****************************************************************************
 *
 *  Oh yeah, we need this too.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

LVV lvvControl = {
    0,                          /* Control_OnCommand */
    0,                          /* Control_LV_OnInitContextMenu */
    Control_LV_Dirtify,
    0,                          /* Control_LV_GetIcon */
    Control_OnInitDialog,
    Control_OnApply,
    Control_OnDestroy,
    0,                          /* Control_OnSelChange */
    7,                          /* iMenu */
    rgdwHelp,
    0,				/* Double-click action */
    lvvflCanCheck,              /* We need check boxes */
    NULL,
};

#pragma END_CONST_DATA


/*****************************************************************************
 *
 *  Our window procedure.
 *
 *****************************************************************************/

INT_PTR EXPORT
Control_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
{
    return LV_DlgProc(&lvvControl, hdlg, wm, wParam, lParam);
}
