/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    oemui.c

Abstract:

    Support for OEM plugin UI modules

Environment:

        Windows NT printer driver

Revision History:

        02/13/97 -davidx-
                Created it.

--*/

#include "precomp.h"

//
// User mode helper functions for OEM plugins
//

const OEMUIPROCS OemUIHelperFuncs = {
    (PFN_DrvGetDriverSetting) BGetDriverSettingForOEM,
    (PFN_DrvUpdateUISetting)  BUpdateUISettingForOEM,
};



BOOL
BPackOemPluginItems(
    PUIDATA pUiData
    )

/*++

Routine Description:

    Call OEM plugin UI modules to let them add their OPTITEMs

Arguments:

    pUiData - Points to UIDATA structure

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    PCOMMONINFO         pci;
    PFN_OEMCommonUIProp pfnOEMCommonUIProp;
    POEMCUIPPARAM       pOemCUIPParam;
    POPTITEM            pOptItem;
    DWORD               dwOptItem;

    //
    // Check if we're being called for the first time.
    // We assume all OEM plugin items are always packed at the end.
    //

    if (pUiData->pOptItem == NULL)
        pUiData->dwDrvOptItem = pUiData->dwOptItem;
    else if (pUiData->dwDrvOptItem != pUiData->dwOptItem)
    {
        RIP(("Inconsistent OPTITEM count for driver items\n"));
        return FALSE;
    }

    //
    // Quick exit for no OEM plugin case
    //

    pci = (PCOMMONINFO) pUiData;

    if (pci->pOemPlugins->dwCount == 0)
        return TRUE;

    pOptItem = pUiData->pOptItem;

    FOREACH_OEMPLUGIN_LOOP(pci)

        if (!HAS_COM_INTERFACE(pOemEntry) &&
            !(pfnOEMCommonUIProp = GET_OEM_ENTRYPOINT(pOemEntry, OEMCommonUIProp)))
                continue;

        //
        // Compose the input parameter for calling OEMCommonUI
        //

        pOemCUIPParam = pOemEntry->pParam;

        if (pOemCUIPParam == NULL)
        {
            //
            // Allocate memory for an OEMUI_PARAM structure
            // during the first pass
            //

            if (pOptItem != NULL)
                continue;

            if (! (pOemCUIPParam = HEAPALLOC(pci->hHeap, sizeof(OEMCUIPPARAM))))
            {
                ERR(("Memory allocation failed\n"));
                return FALSE;
            }

            pOemEntry->pParam = pOemCUIPParam;
            pOemCUIPParam->cbSize = sizeof(OEMCUIPPARAM);
            pOemCUIPParam->poemuiobj = pci->pOemPlugins->pdriverobj;
            pOemCUIPParam->hPrinter = pci->hPrinter;
            pOemCUIPParam->pPrinterName = pci->pPrinterName;
            pOemCUIPParam->hModule = pOemEntry->hInstance;
            pOemCUIPParam->hOEMHeap = pci->hHeap;
            pOemCUIPParam->pPublicDM = pci->pdm;
            pOemCUIPParam->pOEMDM = pOemEntry->pOEMDM;
        }

        pOemCUIPParam->pDrvOptItems = pUiData->pDrvOptItem;
        pOemCUIPParam->cDrvOptItems = pUiData->dwDrvOptItem;
        pOemCUIPParam->pOEMOptItems = pOptItem;
        dwOptItem = pOemCUIPParam->cOEMOptItems;

        //
        // Actually call OEMCommonUI entrypoint
        //

        if (HAS_COM_INTERFACE(pOemEntry))
        {
            HRESULT hr;

            hr = HComOEMCommonUIProp(
                    pOemEntry,
                    (pUiData->iMode == MODE_DOCUMENT_STICKY) ? OEMCUIP_DOCPROP : OEMCUIP_PRNPROP,
                    pOemCUIPParam);

            if (hr == E_NOTIMPL)
            {
                HeapFree(pci->hHeap, 0, pOemCUIPParam);
                pOemEntry->pParam = NULL;
                continue;
            }

            if (FAILED(hr))
            {
                ERR(("OEMCommonUI failed for '%ws': %d\n",
                    CURRENT_OEM_MODULE_NAME(pOemEntry),
                    GetLastError()));

                //
                // OEM failure during the first pass is recoverable:
                // we'll simply ignore OEM plugin items
                //

                if (pOptItem == NULL)
                {
                    HeapFree(pci->hHeap, 0, pOemCUIPParam);
                    pOemEntry->pParam = NULL;
                    continue;
                }
                return FALSE;
            }
        }
        else
        {
            if (!pfnOEMCommonUIProp(
                    (pUiData->iMode == MODE_DOCUMENT_STICKY) ? OEMCUIP_DOCPROP : OEMCUIP_PRNPROP,
                    pOemCUIPParam))
            {
                ERR(("OEMCommonUI failed for '%ws': %d\n",
                    CURRENT_OEM_MODULE_NAME(pOemEntry),
                    GetLastError()));
    #if 0
                (VOID) IDisplayErrorMessageBox(
                                NULL,
                                0,
                                IDS_OEMERR_DLGTITLE,
                                IDS_OEMERR_OPTITEM,
                                CURRENT_OEM_MODULE_NAME(pOemEntry));
    #endif
                //
                // OEM failure during the first pass is recoverable:
                // we'll simply ignore OEM plugin items
                //

                if (pOptItem == NULL)
                {
                    HeapFree(pci->hHeap, 0, pOemCUIPParam);
                    pOemEntry->pParam = NULL;
                    continue;
                }

                return FALSE;
            }
        }

        if (pOptItem != NULL)
        {
            //
            // second pass - ensure the number of items is consistent
            //

            if (dwOptItem != pOemCUIPParam->cOEMOptItems)
            {
                RIP(("Inconsistent OPTITEM count reported by OEM plugin: %ws\n",
                     CURRENT_OEM_MODULE_NAME(pOemEntry),
                     GetLastError()));

                return FALSE;
            }

            pOptItem += pOemCUIPParam->cOEMOptItems;
            pUiData->pOptItem += pOemCUIPParam->cOEMOptItems;
        }

        pUiData->dwOptItem += pOemCUIPParam->cOEMOptItems;

    END_OEMPLUGIN_LOOP

    return TRUE;
}



LONG
LInvokeOemPluginCallbacks(
    PUIDATA         pUiData,
    PCPSUICBPARAM   pCallbackParam,
    LONG            lRet
    )

/*++

Routine Description:

    Call OEM plugin module's callback function

Arguments:

    pUiData - Points to UIDATA structure
    pCallbackParam - Points to callback parameter from compstui
    lRet - Return value after the driver has processed the callback

Return Value:

    Return value for compstui

--*/

{
    PCOMMONINFO     pci = (PCOMMONINFO) pUiData;
    POEMCUIPPARAM   pOemCUIPParam;
    LONG            lNewResult;

    //
    // Quick exit for no OEM plugin case
    //

    if (pci->pOemPlugins->dwCount == 0)
        return lRet;

    //
    // Go through each OEM plugin UI module
    //

    FOREACH_OEMPLUGIN_LOOP(pci)

        //
        // Stop when anyone says don't exit
        //

        if (lRet == CPSUICB_ACTION_NO_APPLY_EXIT)
        {
            ASSERT(pCallbackParam->Reason == CPSUICB_REASON_APPLYNOW);
            break;
        }

        //
        // Get the address of OEM callback function and call it
        //

        pOemCUIPParam = pOemEntry->pParam;

        if (pOemCUIPParam == NULL || pOemCUIPParam->OEMCUIPCallback == NULL)
            continue;

        lNewResult = pOemCUIPParam->OEMCUIPCallback(pCallbackParam, pOemCUIPParam);

        //
        // Merge the new result with the existing result
        //

        switch (lNewResult)
        {
        case CPSUICB_ACTION_ITEMS_APPLIED:
        case CPSUICB_ACTION_NO_APPLY_EXIT:

            ASSERT(pCallbackParam->Reason == CPSUICB_REASON_APPLYNOW);
            lRet = lNewResult;
            break;

        case CPSUICB_ACTION_REINIT_ITEMS:

            ASSERT(pCallbackParam->Reason != CPSUICB_REASON_APPLYNOW);
            lRet = lNewResult;
            break;

        case CPSUICB_ACTION_OPTIF_CHANGED:

            ASSERT(pCallbackParam->Reason != CPSUICB_REASON_APPLYNOW);
            if (lRet == CPSUICB_ACTION_NONE)
                lRet = lNewResult;
            break;

        case CPSUICB_ACTION_NONE:
            break;

        default:

            RIP(("Invalid return value from OEM callback: '%ws'\n",
                 CURRENT_OEM_MODULE_NAME(pOemEntry),
                 GetLastError()));
            break;
        }

    END_OEMPLUGIN_LOOP

    return lRet;
}


LRESULT
OEMDocumentPropertySheets(
    PPROPSHEETUI_INFO   pPSUIInfo,
    LPARAM              lParam
    )
{
    HRESULT hr;

    POEM_PLUGIN_ENTRY pOemEntry;

    pOemEntry = ((POEMUIPSPARAM)(pPSUIInfo->lParamInit))->pOemEntry;

    hr = HComOEMDocumentPropertySheets(pOemEntry,
                                       pPSUIInfo,
                                       lParam);

    if (SUCCEEDED(hr))
        return 1;

    return -1;
}

LRESULT
OEMDevicePropertySheets(
    PPROPSHEETUI_INFO   pPSUIInfo,
    LPARAM              lParam
    )
{
    HRESULT hr;

    POEM_PLUGIN_ENTRY pOemEntry;

    pOemEntry = ((POEMUIPSPARAM)(pPSUIInfo->lParamInit))->pOemEntry;

    hr = HComOEMDevicePropertySheets(pOemEntry,
                                     pPSUIInfo,
                                     lParam);

    if (SUCCEEDED(hr))
        return 1;

    return -1;
}


BOOL
BAddOemPluginPages(
    PUIDATA pUiData,
    DWORD   dwFlags
    )

/*++

Routine Description:

    Call OEM plugin UI modules to let them add their own property sheet pages

Arguments:

    pUiData - Points to UIDATA structure
    dwFlags - Flags from DOCUMENTPROPERTYHEADER or DEVICEPROPERTYHEADER

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    PCOMMONINFO     pci = (PCOMMONINFO) pUiData;
    FARPROC         pfnOEMPropertySheets;
    POEMUIPSPARAM   pOemUIPSParam;

    //
    // Quick exit for no OEM plugin case
    //

    if (pci->pOemPlugins->dwCount == 0)
        return TRUE;

    //
    // Add the property sheet for each OEM plugin UI module
    //

    FOREACH_OEMPLUGIN_LOOP(pci)

        //
        // get the address of appropriate OEM entrypoint
        //


        if (HAS_COM_INTERFACE(pOemEntry))
        {
            if (pUiData->iMode == MODE_DOCUMENT_STICKY)
                pfnOEMPropertySheets = (FARPROC)OEMDocumentPropertySheets;
            else
                pfnOEMPropertySheets = (FARPROC)OEMDevicePropertySheets;
        }
        else
        {
            if (pUiData->iMode == MODE_DOCUMENT_STICKY)
            {
                pfnOEMPropertySheets = (FARPROC)
                    GET_OEM_ENTRYPOINT(pOemEntry, OEMDocumentPropertySheets);
            }
            else
            {
                pfnOEMPropertySheets = (FARPROC)
                    GET_OEM_ENTRYPOINT(pOemEntry, OEMDevicePropertySheets);
            }

            if (pfnOEMPropertySheets == NULL)
                continue;
        }

        //
        // Collect input parameters to be passed to OEM plugin
        //

        if ((pOemUIPSParam = HEAPALLOC(pci->hHeap, sizeof(OEMUIPSPARAM))) == NULL)
        {
            ERR(("Memory allocation failed\n"));
            return FALSE;
        }

        pOemUIPSParam->cbSize = sizeof(OEMUIPSPARAM);
        pOemUIPSParam->poemuiobj = pci->pOemPlugins->pdriverobj;
        pOemUIPSParam->hPrinter = pci->hPrinter;
        pOemUIPSParam->pPrinterName = pci->pPrinterName;
        pOemUIPSParam->hModule = pOemEntry->hInstance;
        pOemUIPSParam->hOEMHeap = pci->hHeap;
        pOemUIPSParam->pPublicDM = pci->pdm;
        pOemUIPSParam->pOEMDM = pOemEntry->pOEMDM;
        pOemUIPSParam->dwFlags = dwFlags;
        pOemUIPSParam->pOemEntry = pOemEntry;

        //
        // call compstui to add the OEM plugin property sheets
        //

        if (pUiData->pfnComPropSheet(pUiData->hComPropSheet,
                                     CPSFUNC_ADD_PFNPROPSHEETUI,
                                     (LPARAM) pfnOEMPropertySheets,
                                     (LPARAM) pOemUIPSParam) <= 0)
        {
            VERBOSE(("Couldn't add property sheet pages for '%ws'\n",
                     CURRENT_OEM_MODULE_NAME(pOemEntry),
                     GetLastError()));
#if 0
            (VOID) IDisplayErrorMessageBox(
                            NULL,
                            0,
                            IDS_OEMERR_DLGTITLE,
                            IDS_OEMERR_PROPSHEET,
                            CURRENT_OEM_MODULE_NAME(pOemEntry));
#endif
        }

    END_OEMPLUGIN_LOOP

    return TRUE;
}



BOOL
APIENTRY
BGetDriverSettingForOEM(
    PCOMMONINFO pci,
    PCSTR       pFeatureKeyword,
    PVOID       pOutput,
    DWORD       cbSize,
    PDWORD      pcbNeeded,
    PDWORD      pdwOptionsReturned
    )

/*++

Routine Description:

    Provide OEM plugins access to driver private settings

Arguments:

    pci - Points to basic printer information
    pFeatureKeyword - Specifies the keyword the caller is interested in
    pOutput - Points to output buffer
    cbSize - Size of output buffer
    pcbNeeded - Returns the expected size of output buffer
    pdwOptionsReturned - Returns the number of options selected

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{
    ULONG_PTR dwIndex;
    BOOL     bResult;

    ASSERT(pci->pvStartSign == pci);

    //
    // This is not very portable: If the pointer value for pFeatureKeyword
    // is less than 0x10000, we assume that the pointer value actually
    // specifies a predefined index.
    //

    //
    // Following ASSERT is removed for Win64
    //
    // ASSERT(sizeof(pFeatureKeyword) == sizeof(DWORD));
    //

    dwIndex = (ULONG_PTR)pFeatureKeyword;

    if (dwIndex >= OEMGDS_MIN_DOCSTICKY && dwIndex < OEMGDS_MIN_PRINTERSTICKY)
    {
        if (pci->pdm == NULL)
            goto setting_not_available;

        bResult = BGetDevmodeSettingForOEM(
                        pci->pdm,
                        (DWORD)dwIndex,
                        pOutput,
                        cbSize,
                        pcbNeeded);

        if (bResult)
            *pdwOptionsReturned = 1;
    }
    else if (dwIndex >= OEMGDS_MIN_PRINTERSTICKY && dwIndex < OEMGDS_MAX)
    {
        if (pci->pPrinterData == NULL)
            goto setting_not_available;

        bResult = BGetPrinterDataSettingForOEM(
                        pci->pPrinterData,
                        (DWORD)dwIndex,
                        pOutput,
                        cbSize,
                        pcbNeeded);

        if (bResult)
            *pdwOptionsReturned = 1;
    }
    else
    {
        if (pci->pCombinedOptions == NULL)
            goto setting_not_available;

        bResult = BGetGenericOptionSettingForOEM(
                        pci->pUIInfo,
                        pci->pCombinedOptions,
                        pFeatureKeyword,
                        pOutput,
                        cbSize,
                        pcbNeeded,
                        pdwOptionsReturned);
    }

    return bResult;

setting_not_available:

    WARNING(("Requested driver setting not available: %d\n", pFeatureKeyword));
    SetLastError(ERROR_NOT_SUPPORTED);
    return FALSE;
}


BOOL
BUpdateUISettingForOEM(
    PCOMMONINFO pci,
    PVOID       pOptItem,
    DWORD       dwPreviousSelection,
    DWORD       dwMode
    )

/*++

Routine Description:

    Update the UI settings in optionsarray for OEM.

Arguments:

    pci - Points to basic printer information
    pOptItem - Points to the current OPTITEM

Return Value:

    TRUE if successful, FALSE if there is an error such as conflict and
    user wants to cancel.

--*/

{
    POPTITEM    pCurItem = pOptItem;
    PUIDATA     pUiData = (PUIDATA)pci;

    if (ICheckConstraintsDlg(pUiData, pCurItem, 1, FALSE) == CONFLICT_CANCEL)
    {
        //
        // If there is a conflict and the user clicked
        // CANCEL to restore the original selection.
        // CONFLICT_CANCEL, restore the old setting
        //

        return FALSE;
    }

    if (dwMode == OEMCUIP_DOCPROP)
    {
        //
        // We use FLAG_WITHIN_PLUGINCALL to indicate we are within the UI helper
        // function call issued by OEM plugin. This is needed to fix bug #90923.
        //

        pUiData->ci.dwFlags |= FLAG_WITHIN_PLUGINCALL;
        VUnpackDocumentPropertiesItems(pUiData, pCurItem, 1);
        pUiData->ci.dwFlags &= ~FLAG_WITHIN_PLUGINCALL;

        VPropShowConstraints(pUiData, MODE_DOCANDPRINTER_STICKY);
    }
    else
    {
        VUpdateOptionsArrayWithSelection(pUiData, pCurItem);
        VPropShowConstraints(pUiData, MODE_PRINTER_STICKY);
    }

    //
    // Record the fact that one of our OPTITEM selection has been changed by plugin's
    // call of helper function DrvUpdateUISetting. This is necessary so that at the
    // APPLYNOW time we know constraints could exist even though user hasn't touched
    // any of our OPTITEMs.
    //

    pUiData->ci.dwFlags |= FLAG_PLUGIN_CHANGED_OPTITEM;

    return TRUE;
}

BOOL
BUpgradeRegistrySettingForOEM(
    HANDLE      hPrinter,
    PCSTR       pFeatureKeyword,
    PCSTR       pOptionKeyword
    )

/*++

Routine Description:

    Set the Feature.Option request to our options array. OEM will only
    call this function at OEMUpgradeDriver to upgrade their registry setttings
    into our optionsarray saved in our PRINTERDATA

Arguments:

    hPrinter - Handle of the Printer
    pFeatureKeyword - Specifies the keyword the caller is interested in
    pOptionKeyword - Specifies the keyword the caller is interested in

Return Value:

    TRUE if successful, FALSE if there is an error

--*/

{

    PFEATURE    pFeature;
    POPTION     pOption;
    DWORD       dwFeatureCount, i, j;
    BOOL        bFeatureFound, bOptionFound, bResult = FALSE;
    PCSTR       pKeywordName;
    POPTSELECT      pOptionsArray = NULL;
    PDRIVER_INFO_3  pDriverInfo3 = NULL;
    PRAWBINARYDATA  pRawData = NULL;
    PINFOHEADER     pInfoHeader = NULL;
    PUIINFO         pUIInfo = NULL;
    PPRINTERDATA    pPrinterData = NULL;
    OPTSELECT       DocOptions[MAX_PRINTER_OPTIONS];

    //
    // Get information about the printer driver
    //

    bResult = bFeatureFound = bOptionFound = FALSE;

    if ((pDriverInfo3 = MyGetPrinterDriver(hPrinter, NULL, 3)) == NULL)
    {
        ERR(("Cannot get printer driver info: %d\n", GetLastError()));
        goto upgrade_registry_exit;
    }

//    ENTER_CRITICAL_SECTION();

    pRawData = LoadRawBinaryData(pDriverInfo3->pDataFile);

//    LEAVE_CRITICAL_SECTION();

    if (pRawData == NULL)
        goto upgrade_registry_exit;

    if (!(pPrinterData = MemAllocZ(sizeof(PRINTERDATA)))  ||
        !( BGetPrinterProperties(hPrinter, pRawData, pPrinterData)))
    {

        ERR(("Cannot get printer data info: %d\n", GetLastError()));
        goto upgrade_registry_exit;
    }

    //
    // Allocate memory for combined optionsarray
    //

    if (!(pOptionsArray = MemAllocZ(MAX_COMBINED_OPTIONS * sizeof (OPTSELECT))))
        goto upgrade_registry_exit;

    if (! InitDefaultOptions(pRawData,
                             DocOptions,
                             MAX_PRINTER_OPTIONS,
                             MODE_DOCUMENT_STICKY))
    {
        goto upgrade_registry_exit;
    }

    //
    // Combine doc sticky options with printer sticky items
    //

    CombineOptionArray(pRawData, pOptionsArray, MAX_COMBINED_OPTIONS, DocOptions, pPrinterData->aOptions);

    //
    // Get an updated instance of printer description data
    //

    pInfoHeader = InitBinaryData(pRawData,
                                 NULL,
                                 pOptionsArray);

    if (pInfoHeader == NULL)
    {
        ERR(("InitBinaryData failed\n"));
        goto upgrade_registry_exit;
    }

    if (!(pUIInfo = OFFSET_TO_POINTER(pInfoHeader, pInfoHeader->loUIInfoOffset)))
        goto upgrade_registry_exit;

    //
    // Look for feature.option index
    //

    pFeature = PGetIndexedFeature(pUIInfo, 0);
    dwFeatureCount = pRawData->dwDocumentFeatures + pRawData->dwPrinterFeatures;

    if (pFeature && dwFeatureCount)
    {
        for (i = 0; i < dwFeatureCount; i++)
        {
            pKeywordName = OFFSET_TO_POINTER(pUIInfo->pubResourceData, pFeature->loKeywordName);
            if (strcmp(pKeywordName, pFeatureKeyword) == EQUAL_STRING)
            {
                bFeatureFound = TRUE;
                break;
            }
            pFeature++;
        }
    }

    if (bFeatureFound)
    {
        pOption = PGetIndexedOption(pUIInfo, pFeature, 0);

        for (j = 0; j < pFeature->Options.dwCount; j++)
        {
            pKeywordName = OFFSET_TO_POINTER(pUIInfo->pubResourceData, pOption->loKeywordName);
            if (strcmp(pKeywordName, pOptionKeyword) == EQUAL_STRING)
            {
                bOptionFound = TRUE;
                break;
            }
            pOption++;
        }
    }

    if (bFeatureFound && bOptionFound)
    {
        pOptionsArray[i].ubCurOptIndex = (BYTE)j;

        //
        // Resolve conflicts
        //

        if (!ResolveUIConflicts( pRawData,
                                 pOptionsArray,
                                 MAX_COMBINED_OPTIONS,
                                 MODE_DOCANDPRINTER_STICKY))
        {
            VERBOSE(("Resolved conflicting printer feature selections.\n"));
        }


        SeparateOptionArray(pRawData,
                            pOptionsArray,
                            pPrinterData->aOptions,
                            MAX_PRINTER_OPTIONS,
                            MODE_PRINTER_STICKY
                           );

        if (!BSavePrinterProperties(hPrinter, pRawData, pPrinterData, sizeof(PRINTERDATA)))
        {
            ERR(("BSavePrinterProperties failed\n"));
            bResult = FALSE;
        }
        else
            bResult = TRUE;
    }

upgrade_registry_exit:

    if (pInfoHeader)
        FreeBinaryData(pInfoHeader);

    if (pRawData)
        UnloadRawBinaryData(pRawData);

    if (pPrinterData)
        MemFree(pPrinterData);

    if (pDriverInfo3)
        MemFree(pDriverInfo3);

    if (pOptionsArray)
        MemFree(pOptionsArray);

    return bResult;
}

#ifdef PSCRIPT

#ifndef WINNT_40


/*++

Routine Name:

    HQuerySimulationSupport

Routine Description:

    In the case of UI replacement, we allows IHV to query for print processor simulation
    support so they can provide simulated features on their UI.

    We won't enforce hooking out QueryJobAttribute w/o UI replacement here. We will do it
    at DrvQueryJobAttributes.

Arguments:

    hPrinter - printer handle
    dwLevel - interested level of spooler simulation capability info structure
    pCaps - pointer to output buffer
    cbSize - size in bytes of output buffer
    pcbNeeded - buffer size in bytes needed to store the interested info structure

Return Value:

    S_OK            if succeeded
    E_OUTOFMEMORY   if output buffer is not big enough
    E_NOTIMPL       if the interested level is not supported
    E_FAIL          if encountered other internal error

Last Error:

    None

--*/
HRESULT
HQuerySimulationSupport(
    IN  HANDLE  hPrinter,
    IN  DWORD   dwLevel,
    OUT PBYTE   pCaps,
    IN  DWORD   cbSize,
    OUT PDWORD  pcbNeeded
    )
{
    PRINTPROCESSOR_CAPS_1 SplCaps;
    PSIMULATE_CAPS_1      pSimCaps;
    DWORD cbNeeded;

    //
    // currently only Level 1 is supported
    //

    if (dwLevel != 1)
    {
        return E_NOTIMPL;
    }

    cbNeeded = sizeof(SIMULATE_CAPS_1);

    if (pcbNeeded)
    {
        *pcbNeeded = cbNeeded;
    }

    if (!pCaps || cbSize < cbNeeded)
    {
        return E_OUTOFMEMORY;
    }

    //
    // Since VGetSpoolerEmfCaps doesn't return error code, we
    // are using the dwLevel field to detect if the call succeeds.
    // If succeeds, dwLevel should be set as 1.
    //

    SplCaps.dwLevel = 0;

    VGetSpoolerEmfCaps(hPrinter,
                       NULL,
                       NULL,
                       sizeof(PRINTPROCESSOR_CAPS_1),
                       &SplCaps
                       );

    if (SplCaps.dwLevel != 1)
    {
        ERR(("VGetSpoolerEmfCaps failed\n"));
        return E_FAIL;
    }

    //
    // BUGBUG, we should get a new PRINTPROCESSOR_CAPS level to include all
    // these information instead of filling it out here. Need
    // new PRINTPROCESSOR_CAPS
    //

    pSimCaps = (PSIMULATE_CAPS_1)pCaps;

    pSimCaps->dwLevel = 1;
    pSimCaps->dwPageOrderFlags = SplCaps.dwPageOrderFlags;
    pSimCaps->dwNumberOfCopies = SplCaps.dwNumberOfCopies;
    pSimCaps->dwNupOptions = SplCaps.dwNupOptions;

    //
    // PRINTPROCESSOR_CAPS_1 is designed without an explicit field for
    // collate simulation. So before its CAPS_2 is introduced, we have
    // to assume that if reverse printing is supported, then collate
    // simulation is also supported.
    //

    if (SplCaps.dwPageOrderFlags & REVERSE_PRINT)
    {
        pSimCaps->dwCollate = 1;
    }
    else
    {
        pSimCaps->dwCollate = 0;
    }

    return S_OK;
}

#endif // !WINNT_40


/*++

Routine Name:

    HEnumConstrainedOptions

Routine Description:

    enumerate the constrained option keyword name list in the specified feature

Arguments:

    poemuiobj - pointer to driver context object
    dwFlags - flags for the enumeration operation
    pszFeatureKeyword - feature keyword name
    pmszConstrainedOptionList - pointer to output data buffer
    cbSize - output data buffer size in bytes
    pcbNeeded - buffer size in bytes needed to store the output data

Return Value:

    S_OK            if succeeds
    E_OUTOFMEMORY   if output data buffer size is not big enough
    E_INVALIDARG    if feature keyword name is not recognized, or the feature's
                    stickiness doesn't match current sticky-mode
    E_FAIL          if other internal failures are encountered

Last Error:

    None

--*/
HRESULT
HEnumConstrainedOptions(
    IN  POEMUIOBJ  poemuiobj,
    IN  DWORD      dwFlags,
    IN  PCSTR      pszFeatureKeyword,
    OUT PSTR       pmszConstrainedOptionList,
    IN  DWORD      cbSize,
    OUT PDWORD     pcbNeeded
    )
{
    PCOMMONINFO pci = (PCOMMONINFO)poemuiobj;
    PUIDATA     pUiData;
    PFEATURE    pFeature;
    POPTION     pOption;
    DWORD       dwFeatureIndex, dwIndex;
    PBOOL       pabEnabledOptions = NULL;
    PSTR        pCurrentOut;
    DWORD       cbNeeded;
    INT         cbRemain;
    HRESULT     hr;

    pUiData = (PUIDATA)pci;

    if (!pszFeatureKeyword ||
        (pFeature = PGetNamedFeature(pci->pUIInfo, pszFeatureKeyword, &dwFeatureIndex)) == NULL)
    {
        WARNING(("HEnumConstrainedOptions: invalid feature\n"));

        //
        // Even though we could return right here, we still use goto to maintain single exit point.
        //

        hr = E_INVALIDARG;
        goto exit;
    }

    //
    // pUiData->iMode can have 2 modes: MODE_DOCUMENT_STICKY and MODE_PRINTER_STICKY. See PFillUiData().
    // In MODE_DOCUMENT_STICKY mode, we only support doc-sticky features.
    // In MODE_PRINTER_STICKY mode, we only support printer-sticky features.
    //
    // This is because in function PFillUiData(), it only fills devmode in MODE_DOCUMENT_STICKY mode.
    // Then in BCombineCommonInfoOptionsArray(), if devmode option array is not available, the PPD parser
    // will use OPTION_INDEX_ANY for any doc-sticky features.
    //

    if ((pUiData->iMode == MODE_DOCUMENT_STICKY && pFeature->dwFeatureType == FEATURETYPE_PRINTERPROPERTY) ||
        (pUiData->iMode == MODE_PRINTER_STICKY && pFeature->dwFeatureType != FEATURETYPE_PRINTERPROPERTY))
    {
        VERBOSE(("HEnumConstrainedOptions: mismatch iMode=%d, dwFeatureType=%d\n",
                pUiData->iMode, pFeature->dwFeatureType)) ;

        hr = E_INVALIDARG;
        goto exit;
    }

    if (pFeature->Options.dwCount)
    {
        if ((pabEnabledOptions = MemAllocZ(pFeature->Options.dwCount * sizeof(BOOL))) == NULL)
        {
            ERR(("HEnumConstrainedOptions: memory alloc failed\n"));
            hr = E_FAIL;
            goto exit;
        }

        //
        // Get the feature's enabled option list.
        //
        // See VPropShowConstraints() in docprop.c and prnprop.c for using different
        // modes to call EnumEnabledOptions().
        //

        if (pUiData->iMode == MODE_DOCUMENT_STICKY)
        {
            EnumEnabledOptions(pci->pRawData, pci->pCombinedOptions, dwFeatureIndex,
                               pabEnabledOptions, MODE_DOCANDPRINTER_STICKY);
        }
        else
        {
            EnumEnabledOptions(pci->pRawData, pci->pCombinedOptions, dwFeatureIndex,
                               pabEnabledOptions, MODE_PRINTER_STICKY);
        }
    }
    else
    {
        RIP(("HEnumConstrainedOptions: feature %s has no options\n", pszFeatureKeyword));

        //
        // continue so we will output the empty string with only the NUL character
        //
    }

    pCurrentOut = pmszConstrainedOptionList;
    cbNeeded = 0;
    cbRemain = (INT)cbSize;

    pOption = OFFSET_TO_POINTER(pci->pInfoHeader, pFeature->Options.loOffset);

    ASSERT(pOption || pFeature->Options.dwCount == 0);

    if (pOption == NULL && pFeature->Options.dwCount != 0)
    {
        hr = E_FAIL;
        goto exit;
    }

    for (dwIndex = 0; dwIndex < pFeature->Options.dwCount; dwIndex++)
    {
        if (!pabEnabledOptions[dwIndex])
        {
            DWORD  dwNameSize;
            PSTR   pszKeywordName;

            pszKeywordName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pOption->loKeywordName);

            ASSERT(pszKeywordName);

            if (pszKeywordName == NULL)
            {
                hr = E_FAIL;
                goto exit;
            }

            //
            // count in the NUL character between constrained option keywords
            //

            dwNameSize = strlen(pszKeywordName) + 1;

            if (pCurrentOut && cbRemain >= (INT)dwNameSize)
            {
                CopyMemory(pCurrentOut, pszKeywordName, dwNameSize);
                pCurrentOut += dwNameSize;
            }

            cbRemain -= dwNameSize;
            cbNeeded += dwNameSize;
        }

        pOption = (POPTION)((PBYTE)pOption + pFeature->dwOptionSize);
    }

    //
    // remember the last NUL terminator for the MULTI_SZ output string
    //

    cbNeeded++;

    if (pcbNeeded)
    {
        *pcbNeeded = cbNeeded;
    }

    if (!pCurrentOut || cbRemain < 1)
    {
        hr = E_OUTOFMEMORY;
        goto exit;
    }

    *pCurrentOut = NUL;

    //
    // Succeeded
    //

    hr = S_OK;

    exit:

    MemFree(pabEnabledOptions);
    return hr;
}


/*++

Routine Name:

    HWhyConstrained

Routine Description:

    get feature/option keyword pair that constrains the given
    feature/option pair

Arguments:

    poemuiobj - pointer to driver context object
    dwFlags - flags for this operation
    pszFeatureKeyword - feature keyword name
    pszOptionKeyword - option keyword name
    pmszReasonList - pointer to output data buffer
    cbSize - output data buffer size in bytes
    pcbNeeded - buffer size in bytes needed to store the output data

Return Value:

    S_OK            if succeeds
    E_OUTOFMEMORY   if output data buffer size is not big enough
    E_INVALIDARG    if the feature keyword name or option keyword name
                    is not recognized, or the feature's stickiness
                    doesn't match current sticky-mode

Last Error:

    None

--*/
HRESULT
HWhyConstrained(
    IN  POEMUIOBJ  poemuiobj,
    IN  DWORD      dwFlags,
    IN  PCSTR      pszFeatureKeyword,
    IN  PCSTR      pszOptionKeyword,
    OUT PSTR       pmszReasonList,
    IN  DWORD      cbSize,
    OUT PDWORD     pcbNeeded
    )
{
    PCOMMONINFO   pci = (PCOMMONINFO)poemuiobj;
    PUIDATA       pUiData;
    PFEATURE      pFeature;
    POPTION       pOption;
    DWORD         dwFeatureIndex, dwOptionIndex;
    CONFLICTPAIR  ConflictPair;
    BOOL          bConflictFound;
    PSTR          pszConfFeatureName = NULL, pszConfOptionName = NULL;
    CHAR          emptyString[1] = {0};
    DWORD         cbConfFeatureKeySize = 0, cbConfOptionKeySize = 0;
    DWORD         cbNeeded = 0;

    pUiData = (PUIDATA)pci;

    if (!pszFeatureKeyword ||
        (pFeature = PGetNamedFeature(pci->pUIInfo, pszFeatureKeyword, &dwFeatureIndex)) == NULL)
    {
        WARNING(("HWhyConstrained: invalid feature\n"));
        return E_INVALIDARG;
    }

    if (!pszOptionKeyword ||
        (pOption = PGetNamedOption(pci->pUIInfo, pFeature, pszOptionKeyword, &dwOptionIndex)) == NULL)
    {
        WARNING(("HWhyConstrained: invalid option\n"));
        return E_INVALIDARG;
    }

    //
    // See comments in HEnumConstrainedOptions() for following stickiness mode check.
    //

    if ((pUiData->iMode == MODE_DOCUMENT_STICKY && pFeature->dwFeatureType == FEATURETYPE_PRINTERPROPERTY) ||
        (pUiData->iMode == MODE_PRINTER_STICKY && pFeature->dwFeatureType != FEATURETYPE_PRINTERPROPERTY))
    {
        VERBOSE(("HWhyConstrained: mismatch iMode=%d, dwFeatureType=%d\n",pUiData->iMode, pFeature->dwFeatureType));
        return E_INVALIDARG;
    }

    //
    // Get the feature/option pair that constrains the feature/option pair client is querying for.
    //

    bConflictFound = EnumNewPickOneUIConflict(pci->pRawData,
                                              pci->pCombinedOptions,
                                              dwFeatureIndex,
                                              dwOptionIndex,
                                              &ConflictPair);

    if (bConflictFound)
    {
        PFEATURE  pConfFeature;
        POPTION   pConfOption;
        DWORD     dwConfFeatureIndex, dwConfOptionIndex;

        //
        // ConflictPair has the feature with higher priority as dwFeatureIndex1.
        //

        if (dwFeatureIndex == ConflictPair.dwFeatureIndex1)
        {
            dwConfFeatureIndex = ConflictPair.dwFeatureIndex2;
            dwConfOptionIndex = ConflictPair.dwOptionIndex2;
        }
        else
        {
            dwConfFeatureIndex = ConflictPair.dwFeatureIndex1;
            dwConfOptionIndex = ConflictPair.dwOptionIndex1;
        }

        pConfFeature = PGetIndexedFeature(pci->pUIInfo, dwConfFeatureIndex);
        ASSERT(pConfFeature);

        pConfOption = PGetIndexedOption(pci->pUIInfo, pConfFeature, dwConfOptionIndex);

        //
        // We don't expect pConfOption to be NULL here. Use the ASSERT to catch cases we missed.
        //

        ASSERT(pConfOption);

        pszConfFeatureName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pConfFeature->loKeywordName);
        ASSERT(pszConfFeatureName);

        if (pConfOption)
        {
            pszConfOptionName = OFFSET_TO_POINTER(pci->pUIInfo->pubResourceData, pConfOption->loKeywordName);
            ASSERT(pszConfOptionName);
        }
        else
        {
            pszConfOptionName = &(emptyString[0]);
        }

        //
        // count in the 2 NUL characters: one after feature name, one after option name.
        //

        cbConfFeatureKeySize = strlen(pszConfFeatureName) + 1;
        cbConfOptionKeySize = strlen(pszConfOptionName) + 1;
    }

    //
    // count in the last NUL characters at the end.
    //

    cbNeeded = cbConfFeatureKeySize + cbConfOptionKeySize + 1;

    if (pcbNeeded)
    {
        *pcbNeeded = cbNeeded;
    }

    if (!pmszReasonList || cbSize < cbNeeded)
    {
        return E_OUTOFMEMORY;
    }

    if (bConflictFound)
    {
        ASSERT(pszConfFeatureName && pszConfOptionName);

        CopyMemory(pmszReasonList, pszConfFeatureName, cbConfFeatureKeySize);
        pmszReasonList += cbConfFeatureKeySize;

        CopyMemory(pmszReasonList, pszConfOptionName, cbConfOptionKeySize);
        pmszReasonList += cbConfOptionKeySize;
    }

    //
    // Now the NUL at the end to finish the MULTI_SZ output string.
    //

    *pmszReasonList = NUL;

    return S_OK;
}

#endif // PSCRIPT