/*
 *  Microsoft Confidential
 *  Copyright (C) Microsoft Corporation 1991
 *  All Rights Reserved.
 *
 *
 *  PIFLIB.C
 *  User interface routines for PIFMGR.DLL
 *
 *  History:
 *  Created 31-Jul-1992 3:30pm by Jeff Parsons
 */

#include "shellprv.h"
#pragma hdrstop


#define LIB_SIG                 0x504A

#define LIB_DEFER               LOADPROPLIB_DEFER

typedef struct LIBLINK {        /* ll */
    struct  LIBLINK *pllNext;   //
    struct  LIBLINK *pllPrev;   //
    int     iSig;               // liblink signature
    int     flLib;              // proplink flags (LIB_*)
    HINSTANCE hDLL;             // if NULL, then load has been deferred
    TCHAR    achDLL[80];        // name of DLL
} LIBLINK;
typedef LIBLINK *PLIBLINK;


#define SHEET_SIG               0x504A

typedef struct SHEETLINK {      /* sl */
    struct  SHEETLINK *pslNext;
    struct  SHEETLINK *pslPrev;
    int     iSig;
    int     iType;
    PROPSHEETPAGE psi;
} SHEETLINK;
typedef SHEETLINK *PSHEETLINK;


UINT    cEdits;                 // number of edit sessions in progress

PLIBLINK pllHead;               // pointer to first lib link
HANDLE   offHighestLibLink;      // highest offset of a lib link thus far recorded

PSHEETLINK pslHead;             // pointer to first sheet link
UINT    cSheetLinks;            // number of sheet links
HANDLE  offHighestSheetLink;    // highest offset of a sheet link thus far recorded


struct {                        // built-in property sheet info
    LPCTSTR  lpTemplateName;
    DLGPROC lpfnDlgProc;
    int     iType;
} const aPSInfo[] = {
    { MAKEINTRESOURCE(IDD_PROGRAM), DlgPrgProc, SHEETTYPE_SIMPLE},
    { MAKEINTRESOURCE(IDD_FONT),    DlgFntProc, SHEETTYPE_SIMPLE},
    { MAKEINTRESOURCE(IDD_MEMORY),  DlgMemProc, SHEETTYPE_SIMPLE},
    { MAKEINTRESOURCE(IDD_SCREEN),  DlgVidProc, SHEETTYPE_SIMPLE},
    { MAKEINTRESOURCE(IDD_MISC),    DlgMscProc, SHEETTYPE_SIMPLE},
};


/** LoadPropertyLib - load new property library
 *
 * INPUT
 *  lpszDLL -> name of DLL
 *  fLoad == flags (see LOADPROPLIB_*)
 *
 * OUTPUT
 *  handle to property library if loaded, FALSE if not
 */

HANDLE WINAPI LoadPropertyLib(LPCTSTR lpszDLL, int fLoad)
{
    HINSTANCE hDLL;
    register PLIBLINK pll;
    FunctionName(LoadPropertyLib);

    hDLL = NULL;
    if (!(fLoad & LOADPROPLIB_DEFER)) {

        hDLL = LoadLibrary(lpszDLL);

        if (hDLL < (HINSTANCE)HINSTANCE_ERROR)
            return FALSE;
    }

    // Allocate new lib link

    if (!(pll = (PLIBLINK)LocalAlloc(LPTR, SIZEOF(LIBLINK))))
        return FALSE;

    if ((HANDLE) pll > offHighestLibLink)
        offHighestLibLink = pll;

    // Initialize the lib link

    pll->pllPrev = NULL;
    pll->iSig = LIB_SIG;
    pll->hDLL = hDLL;
    pll->flLib = 0;
    if (!hDLL)
        pll->flLib |= LIB_DEFER;
    lstrcpyn(pll->achDLL, lpszDLL, ARRAYSIZE(pll->achDLL));

    // Link into the global lib list

    if (NULL != (pll->pllNext = pllHead))
        pllHead->pllPrev = pll;
    pllHead = pll;

    return pll;
}


/** EnumPropertyLibs - enumerate property libraries
 *
 * INPUT
 *  iLib    == 0 to begin enumeration, or result of previous call
 *  lphDLL  -> where to store handle (NULL if don't care)
 *  lpszDLL -> where to store name of library (NULL if don't care)
 *  cchszDLL == size of space (in chars) to store name
 *
 * OUTPUT
 *  lphDLL and lpszDLL filled in as appropriate, 0 if no more libs (or error)
 */

HANDLE WINAPI EnumPropertyLibs(HANDLE iLib, LPHANDLE lphDLL, LPTSTR lpszDLL, int cchszDLL)
{
    register PLIBLINK pll;
    FunctionName(EnumPropertyLibs);

    if (!iLib)
        pll = pllHead;
    else
        pll = ((PLIBLINK)iLib)->pllNext;

    // Validate the handle

    if (!pll)
        return 0;

    if ((HANDLE) pll > offHighestLibLink)
        return 0;

    if (pll->iSig != LIB_SIG)
        return 0;

    if (lphDLL)
        *lphDLL = pll->hDLL;

    if (lpszDLL)
        lstrcpyn(lpszDLL, pll->achDLL, min(cchszDLL, ARRAYSIZE(pll->achDLL)));

    return pll;
}


/** FreePropertyLib - free installable property library
 *
 * INPUT
 *  hLib == handle to property library
 *
 * OUTPUT
 *  TRUE if successful, FALSE otherwise
 */

BOOL WINAPI FreePropertyLib(HANDLE hLib)
{
    register PLIBLINK pll;
    FunctionName(FreePropertyLib);

    // Validate the handle

    if (!hLib)
        return FALSE;

    if ((HANDLE)hLib > offHighestLibLink)
        return FALSE;

    pll = (PLIBLINK)hLib;

    if (pll->iSig != LIB_SIG)
        return FALSE;

    // Free the associated library

    if (pll->hDLL)
        FreeLibrary(pll->hDLL);

    // Unlink from the global list

    if (pll->pllPrev)
        pll->pllPrev->pllNext = pll->pllNext;
    else
        pllHead = pll->pllNext;

    if (pll->pllNext)
        pll->pllNext->pllPrev = pll->pllPrev;

    EVAL(LocalFree(pll) == NULL);

    return TRUE;
}


/** AddPropertySheet - install new property sheet
 *
 * INPUT
 *  lppsi -> property sheet info structure
 *  iType  == sheet type (see SHEETTYPE_* constants)
 *
 * OUTPUT
 *  handle to property sheet link, or NULL if failure
 */

HANDLE WINAPI AddPropertySheet(const PROPSHEETPAGE *lppsi, int iType)
{
    register PSHEETLINK psl;
    FunctionName(AddPropertySheet);

    // Allocate new sheet link

    if (!(psl = (PSHEETLINK)LocalAlloc(LPTR, SIZEOF(SHEETLINK))))
        return FALSE;

    if ((HANDLE) psl > offHighestSheetLink)
        offHighestSheetLink = psl;

    // Initialize the sheet link

    psl->pslPrev = NULL;
    psl->iSig = SHEET_SIG;
    psl->psi = *lppsi;
    psl->iType = iType;

    // Link into the global sheet list

    if (NULL != (psl->pslNext = pslHead))
        pslHead->pslPrev = psl;
    pslHead = psl;

    cSheetLinks++;

    return psl;
}


/** RemovePropertySheet - remove installable property sheet
 *
 * INPUT
 *  hSheet == handle to sheet link
 *
 * OUTPUT
 *  TRUE if successful, FALSE otherwise
 */

BOOL WINAPI RemovePropertySheet(HANDLE hSheet)
{
    register PSHEETLINK psl;
    FunctionName(RemovePropertySheet);

    // Validate the handle

    if (!hSheet)
        return FALSE;

    if ((HANDLE)hSheet > offHighestSheetLink)
        return FALSE;

    psl = (PSHEETLINK)hSheet;

    if (psl->iSig != SHEET_SIG)
        return FALSE;

    // Unlink from the global list

    cSheetLinks--;

    if (psl->pslPrev)
        psl->pslPrev->pslNext = psl->pslNext;
    else
        pslHead = psl->pslNext;

    if (psl->pslNext)
        psl->pslNext->pslPrev = psl->pslPrev;

    EVAL(LocalFree(psl) == NULL);

    return TRUE;
}


/** LoadPropertySheets - load property sheets
 *
 * INPUT
 *  hProps = property handle
 *  flags = 0 (reserved)
 *
 * OUTPUT
 *  # of sheets loaded, 0 if error
 */

int WINAPI LoadPropertySheets(HANDLE hProps, int flags)
{
    register PLIBLINK pll;
    FunctionName(LoadPropertySheets);

    // If this is the first edit session, do global init now

    if (cEdits++ == 0)
        if (!LoadGlobalEditData())
            return 0;

    pll = NULL;
    while (NULL != (pll = (PLIBLINK)EnumPropertyLibs(pll, NULL, NULL, 0))) {
        if (!pll->hDLL && (pll->flLib & LIB_DEFER)) {

            pll->hDLL = LoadLibrary(pll->achDLL);

            // If the load failed, to us that simply means those sheets
            // will not be available; the particular error is not interesting,
            // so nullify the handle

            if (pll->hDLL < (HINSTANCE)HINSTANCE_ERROR)
                pll->hDLL = NULL;
        }
    }
    return cSheetLinks + ARRAYSIZE(aPSInfo);
}


/** EnumPropertySheets - enumerate property sheets
 *
 * INPUT
 *  hProps == property handle
 *  iType  == sheet type (see SHEETTYPE_* constants)
 *  iSheet == 0 to begin enumeration, or result of previous call
 *  lppsi -> property sheet info structure to be filled in
 *
 * OUTPUT
 *  lppsi filled in as appropriate, 0 if no more sheets (or error)
 */

INT_PTR WINAPI EnumPropertySheets(HANDLE hProps, int iType, INT_PTR iSheet, LPPROPSHEETPAGE lppsp)
{
    register PSHEETLINK psl;
    FunctionName(EnumPropertySheets);

    while (iSheet < ARRAYSIZE(aPSInfo)) {
        if (aPSInfo[iSheet].iType <= iType) {
            if (lppsp) {
                lppsp->dwSize      = SIZEOF(PROPSHEETPAGE);
                lppsp->dwFlags     = PSP_DEFAULT;
                lppsp->hInstance   = HINST_THISDLL;
                lppsp->pszTemplate = aPSInfo[iSheet].lpTemplateName;
                lppsp->pfnDlgProc  = aPSInfo[iSheet].lpfnDlgProc;
                // lppsp->pszTitle    = NULL;
                lppsp->lParam      = (LONG_PTR)hProps;
            }
            return ++iSheet;
        }
        ++iSheet;
    }
    if (iSheet == ARRAYSIZE(aPSInfo))
        psl = pslHead;
    else
        psl = ((PSHEETLINK)iSheet)->pslNext;

    // Validate the handle

    while (psl && (HANDLE) psl <= offHighestSheetLink && psl->iSig == SHEET_SIG) {

        if (psl->iType <= iType) {

            *lppsp = psl->psi;
            lppsp->lParam = (LONG_PTR)hProps;

            return (INT_PTR) psl;
        }
        psl = psl->pslNext;
    }
    return 0;                   // no more matching sheets
}


/** FreePropertySheets - free property sheets
 *
 * INPUT
 *  hProps = property handle
 *  flags = 0 (reserved)
 *
 * OUTPUT
 *  Nothing
 */

HANDLE WINAPI FreePropertySheets(HANDLE hProps, int flags)
{
    register PLIBLINK pll;
    FunctionName(FreePropertySheets);

    pll = NULL;
    while (NULL != (pll = (PLIBLINK)EnumPropertyLibs(pll, NULL, NULL, 0))) {
        if (pll->hDLL && (pll->flLib & LIB_DEFER)) {
            FreeLibrary(pll->hDLL);
            pll->hDLL = NULL;
        }
    }
    // If this is the last edit session, do global un-init now

    if (--cEdits == 0)
        FreeGlobalEditData();

    return 0;
}


/** InitRealModeFlag - Initialize PROP_REALMODE
 *
 * INPUT
 *  ppl = properties
 *
 * OUTPUT
 *  ppl->flProp PROP_REALMODE bit set if sheet is for real-mode app,
 *  else clear.
 */

void InitRealModeFlag(PPROPLINK ppl)
{
    PROPPRG prg;

    if (!PifMgr_GetProperties(ppl, MAKELP(0,GROUP_PRG),
                        &prg, SIZEOF(prg), GETPROPS_NONE)) {
        return;                 /* Weird */
    }
    if (prg.flPrgInit & PRGINIT_REALMODE) {
        ppl->flProp |= PROP_REALMODE;
    } else {
        ppl->flProp &= ~PROP_REALMODE;
    }
}


/** EditProperties - edit property info
 *
 * INPUT
 *  hProps = handle to properties
 *  lpszTitle = pointer to title to use (NULL if none)
 *  uStartPage = starting property sheet #
 *  hwnd = handle to window of parent (NULL if none)
 *  uMsgPost = msg # to post to hwnd with change notification (0 if none)
 *
 * OUTPUT
 *  TRUE if successful, FALSE otherwise
 */

int WINAPI EditProperties(HANDLE hProps, LPCTSTR lpszTitle, UINT uStartPage, HWND hwnd, UINT uMsgPost)
{
    int cSheets;
    INT_PTR iSheet;
    PPROPLINK ppl;
    PROPSHEETHEADER psh;
    PROPSHEETPAGE *ppsp;
    register PSHEETLINK psl;
    FunctionName(EditProperties);

    if (!(ppl = ValidPropHandle(hProps)))
        return FALSE;

    if (hwnd && uMsgPost) {
        ppl->hwndNotify = hwnd;
        ppl->uMsgNotify = uMsgPost;
    }
    cSheets = LoadPropertySheets(hProps, 0);

    psl = pslHead;
    if (!(ppsp = (PROPSHEETPAGE *)LocalAlloc(LPTR, cSheets*SIZEOF(PROPSHEETPAGE))))
        return FALSE;

    psh.dwSize = SIZEOF(psh);
    psh.dwFlags = PSH_PROPTITLE | PSH_PROPSHEETPAGE;
    psh.hwndParent = hwnd;
    if (!lpszTitle)
        psh.pszCaption = ppl->szPathName+ppl->iFileName;
    else
        psh.pszCaption = ppl->lpszTitle = lpszTitle;
    psh.nPages = 0;
    psh.nStartPage = uStartPage;
    psh.ppsp = ppsp;

    iSheet = cSheets = 0;

    while (0 != (iSheet = EnumPropertySheets(hProps, SHEETTYPE_SIMPLE, iSheet, ppsp))) {
        cSheets++;
        ppsp++;
    }
    psh.nPages = cSheets;

    // Since the user wishes to *explicitly* change settings for this app
    // we make sure that the DONTWRITE flag isn't going to get in his way...

    ppl->flProp &= ~PROP_DONTWRITE;

    InitRealModeFlag(ppl);

    PropertySheet(&psh);

    VERIFYFALSE(LocalFree((HLOCAL)psh.ppsp));

    FreePropertySheets(hProps, 0);

    if (ppl->flProp & PROP_NOTIFY) {
        ppl->flProp &= ~PROP_NOTIFY;
        PostMessage(ppl->hwndNotify, ppl->uMsgNotify, 0, 0);
    }
    ppl->hwndNotify = NULL;
    ppl->uMsgNotify = 0;
    ppl->lpszTitle = NULL;

    return TRUE;
}

BOOL LoadGlobalEditData()
{
    FunctionName(LoadGlobalEditData);

    if (!LoadGlobalFontEditData())
        return FALSE;

    return TRUE;
}


void FreeGlobalEditData()
{
    FunctionName(FreeGlobalEditData);
    FreeGlobalFontEditData();
}


UINT CALLBACK PifPropPageRelease(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE lppsp)
{
    FunctionName(PifPropPageRelease);

    if (uMsg == PSPCB_RELEASE) {
        PPROPLINK ppl = (PPROPLINK)(INT_PTR)lppsp->lParam;

        if ((--ppl->iSheetUsage) == 0) {

            FreePropertySheets(ppl, 0);

            PifMgr_CloseProperties(ppl, CLOSEPROPS_NONE);
        }
    }
    return 1;
}

#define MZMAGIC      ((WORD)'M'+((WORD)'Z'<<8))

//
// call SHELL.DLL to get the EXE type.
//
BOOL IsWinExe(LPCTSTR lpszFile)
{
    DWORD dw = (DWORD) SHGetFileInfo(lpszFile, 0, NULL, 0, SHGFI_EXETYPE);

    return dw && LOWORD(dw) != MZMAGIC;
}

BOOL WINAPI PifPropGetPages(LPVOID lpv,
                            LPFNADDPROPSHEETPAGE lpfnAddPage,
                            LPARAM lParam)
{
#define hDrop   (HDROP)lpv
    PPROPLINK ppl;
    PROPSHEETPAGE psp;
    int iType, cSheets;
    INT_PTR iSheet;
    HPROPSHEETPAGE hpage;
    TCHAR szFileName[MAXPATHNAME];
    FunctionName(PifPropGetPages);

    // only process things if hDrop contains only one file
    if (DragQueryFile(hDrop, (UINT)-1, NULL, 0) != 1)
    {
        return TRUE;
    }

    // get the name of the file
    DragQueryFile(hDrop, 0, szFileName, ARRAYSIZE(szFileName));

    if (GetFileAttributes( szFileName) & FILE_ATTRIBUTE_OFFLINE)
    {
        return FALSE;
    }

    // if this is a windows app, don't do no properties
    if (IsWinExe(szFileName))
        return TRUE;

    // if we can't get a property handle, don't do no properties either
    if (!(ppl = (PPROPLINK)PifMgr_OpenProperties(szFileName, NULL, 0, OPENPROPS_NONE)))
        return TRUE;

    InitRealModeFlag(ppl);

    if (!(cSheets = LoadPropertySheets(ppl, 0)))
        goto CloseProps;

    // Since the user wishes to *explicitly* change settings for this app
    // we make sure that the DONTWRITE flag isn't going to get in his way...

    ppl->flProp &= ~PROP_DONTWRITE;

    iSheet = cSheets = 0;
    iType = (GetKeyState(VK_CONTROL) >= 0? SHEETTYPE_SIMPLE : SHEETTYPE_ADVANCED);

    while (TRUE) {

        if (!(iSheet = EnumPropertySheets(ppl, iType, iSheet, &psp))) {
            // done with enumeration
            break;
        }
        psp.dwFlags |= PSP_USECALLBACK;
        psp.pfnCallback = PifPropPageRelease;
        psp.pcRefParent = 0;

        hpage = CreatePropertySheetPage(&psp);
        if (hpage)
        {
            // the PROPLINK is now being used by this property sheet as well

            if (lpfnAddPage(hpage, lParam))
            {
                ppl->iSheetUsage++;
                cSheets++;
            }
            else
            {
                PifPropPageRelease(NULL, PSPCB_RELEASE, &psp);
            }
        }
    }

    if (!cSheets) {
        FreePropertySheets(ppl, 0);

CloseProps:
        PifMgr_CloseProperties(ppl, CLOSEPROPS_NONE);
    }
    return TRUE;
}
#undef hDrop
