//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1997.
//
//  File:       N C U I . C P P
//
//  Contents:   Common user interface routines.
//
//  Notes:
//
//  Author:     shaunco   24 Mar 1997
//
//----------------------------------------------------------------------------

#include <pch.h>
#pragma hdrstop

#include "ncerror.h"
#include "ncstring.h"
#include "ncui.h"
#include "ncperms.h"
#include "netconp.h"

//+---------------------------------------------------------------------------
//
//  Function:   EnableOrDisableDialogControls
//
//  Purpose:    Enable or disable a group of controls all at once.
//
//  Arguments:
//      hDlg    [in] Window handle of parent dialog.
//      ccid    [in] Count of elements in array pointed to by acid.
//      acid    [in] Array of control ids.
//      fEnable [in] TRUE to enable controls, FALSE to disable.
//
//  Returns:    nothing
//
//  Author:     shaunco   24 Mar 1997
//
//  Notes:      Don't forget to declare your array as 'static const'.
//
NOTHROW
VOID
EnableOrDisableDialogControls (
    IN HWND        hDlg,
    IN INT         ccid,
    IN const INT*  acid,
    IN BOOL        fEnable)
{
    Assert (IsWindow (hDlg));
    Assert (FImplies (ccid, acid));

    while (ccid--)
    {
        EnableWindow (GetDlgItem (hDlg, *acid++), fEnable);
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   FMapRadioButtonToValue
//
//  Purpose:    Maps the current state of a set of radio buttons to a DWORD
//              value based on a mapping table.
//
//  Arguments:
//      hDlg     [in]  Window handle of parent dialog.
//      crbm     [in]  Count of elements in array pointed to by arbm.
//      arbm     [in]  Array of elements that map a radio button control id to
//                     its associated value.
//      pdwValue [out] The returned value.
//
//  Returns:    TRUE if a radio button was set and the value returned.
//              FALSE otherwise.
//
//  Author:     shaunco   24 Mar 1997
//
//  Notes:      Don't forget to declare your array as 'static const'.
//
NOTHROW
BOOL
FMapRadioButtonToValue (
    IN HWND                    hDlg,
    IN INT                     crbm,
    IN const RADIO_BUTTON_MAP* arbm,
    OUT DWORD*                  pdwValue)
{
    Assert (IsWindow (hDlg));
    Assert (FImplies (crbm, arbm));
    Assert (pdwValue);

    while (crbm--)
    {
        // If it is set, return the corresponding value.
        if (BST_CHECKED & IsDlgButtonChecked (hDlg, arbm->cid))
        {
            *pdwValue = arbm->dwValue;
            return TRUE;
        }

        arbm++;
    }
    *pdwValue = 0;
    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Function:   FMapValueToRadioButton
//
//  Purpose:    Set the state of a set of radio buttons based on the value of
//              of a DWORD and a mapping table.
//
//  Arguments:
//      hDlg    [in] Window handle of parent dialog.
//      crbm    [in] Count of elements in array pointed to by arbm.
//      arbm    [in] Array of elements that map a radio button control id to
//                   its associated value.
//      dwValue [in] value which gets mapped to set the appropriate radio
//                   button.
//
//  Returns:    TRUE if dwValue was found in the map.  FALSE otherwise.
//
//  Author:     shaunco   24 Mar 1997
//
//  Notes:      Don't forget to declare your array as 'static const'.
//
NOTHROW
BOOL
FMapValueToRadioButton (
    IN HWND                    hDlg,
    IN INT                     crbm,
    IN const RADIO_BUTTON_MAP* arbm,
    IN DWORD                   dwValue,
    IN INT*                    pncid)
{
    Assert (IsWindow (hDlg));
    Assert (FImplies (crbm, arbm));

    while (crbm--)
    {
        if (dwValue == arbm->dwValue)
        {
            // Set the radio button.
            CheckDlgButton (hDlg, arbm->cid, BST_CHECKED);

            // Return the control id if requested.
            if (pncid)
            {
                *pncid = arbm->cid;
            }

            return TRUE;
        }

        arbm++;
    }
    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Function:   GetIntegerFormat
//
//  Purpose:    Uses GetNumberFormat to format an integer number.
//
//  Arguments:
//      Locale            [in]  See Win32 API description of GetNumberFormat.
//      pszValue          [in]
//      pszFormattedValue [out]
//      cchFormattedValue [in]
//
//  Returns:    return value from GetNumberFormat
//
//  Author:     shaunco   4 May 1998
//
//  Notes:
//
INT
GetIntegerFormat (
    IN LCID   Locale,
    IN PCWSTR pszValue,
    OUT PWSTR pszFormattedValue,
    IN INT    cchFormattedValue)
{
    // Format the number for the user's locale and preferences.
    //
    WCHAR szGrouping [16];
    GetLocaleInfo (Locale, LOCALE_SGROUPING,
                   szGrouping, celems(szGrouping));

    WCHAR szDecimalSep [16];
    GetLocaleInfo (Locale, LOCALE_SDECIMAL,
                   szDecimalSep, celems(szDecimalSep));

    WCHAR szThousandSep [16];
    GetLocaleInfo (Locale, LOCALE_STHOUSAND,
                   szThousandSep, celems(szThousandSep));

    NUMBERFMT nf;
    ZeroMemory (&nf, sizeof(nf));
    nf.Grouping      = wcstoul (szGrouping, NULL, 10);
    nf.lpDecimalSep  = szDecimalSep;
    nf.lpThousandSep = szThousandSep;

    return GetNumberFormat (
                    Locale,
                    0,
                    pszValue,
                    &nf,
                    pszFormattedValue,
                    cchFormattedValue);
}

INT
Format32bitInteger (
    IN UINT32  unValue,
    IN BOOL    fSigned,
    OUT PWSTR  pszFormattedValue,
    IN INT     cchFormattedValue)
{
    // Convert the number to a string.
    //
    WCHAR szValue [33];

    *szValue = 0;

    if (fSigned)
    {
        _itow ((INT)unValue, szValue, 10);
    }
    else
    {
        _ultow (unValue, szValue, 10);
    }

    // Format the number according to user locale settings.
    //
    INT cch = GetIntegerFormat (
                LOCALE_USER_DEFAULT,
                szValue,
                pszFormattedValue,
                cchFormattedValue);
    if (!cch)
    {
        TraceHr(ttidError, FAL, HrFromLastWin32Error(), FALSE,
            "GetIntegerFormat failed in Format32bitInteger");

        lstrcpynW (pszFormattedValue, szValue, cchFormattedValue);
        cch = lstrlenW (pszFormattedValue);
    }
    return cch;
}

INT
Format64bitInteger (
    IN UINT64  ulValue,
    IN BOOL    fSigned,
    OUT PWSTR  pszFormattedValue,
    IN INT     cchFormattedValue)
{
    // Convert the number to a string.
    //
    WCHAR szValue [32];

    *szValue = 0;

    if (fSigned)
    {
        _i64tow ((INT64)ulValue, szValue, 10);
    }
    else
    {
        _ui64tow (ulValue, szValue, 10);
    }

    // Format the number according to user locale settings.
    //
    INT cch = GetIntegerFormat (
                LOCALE_USER_DEFAULT,
                szValue,
                pszFormattedValue,
                cchFormattedValue);
    if (!cch)
    {
        TraceHr(ttidError, FAL, HrFromLastWin32Error(), FALSE,
            "GetIntegerFormat failed in Format64bitInteger");

        lstrcpynW (pszFormattedValue, szValue, cchFormattedValue);
        cch = lstrlenW (pszFormattedValue);
    }
    return cch;
}


BOOL
SetDlgItemFormatted32bitInteger (
    IN HWND    hDlg,
    IN INT     nIdDlgItem,
    IN UINT32  unValue,
    IN BOOL    fSigned)
{
    // Format the number according to user locale settings.
    //
    WCHAR szFormattedValue[64];

    Format32bitInteger(
        unValue,
        fSigned,
        szFormattedValue,
        celems(szFormattedValue));

    // Display the number.
    //
    return SetDlgItemText (hDlg, nIdDlgItem, szFormattedValue);
}

BOOL
SetDlgItemFormatted64bitInteger (
    IN HWND    hDlg,
    IN INT     nIdDlgItem,
    IN UINT64  ulValue,
    IN BOOL    fSigned)
{
    // Format the number according to user locale settings.
    //
    WCHAR szFormattedValue[64];

    Format64bitInteger(
        ulValue,
        fSigned,
        szFormattedValue,
        celems(szFormattedValue));

    // Display the number.
    //
    return SetDlgItemText (hDlg, nIdDlgItem, szFormattedValue);
}

//+---------------------------------------------------------------------------
//
//  Function:   HrNcQueryUserForRebootEx
//
//  Purpose:    Query the user to reboot.  If he/she chooses yes, a reboot
//              is initiated.
//
//  Arguments:
//      hwndParent  [in] Parent window handle.
//      pszCaption  [in] Caption text.
//      pszText     [in] Message text.
//      dwFlags     [in] Control flags (QUFR_PROMPT | QUFR_REBOOT)
//
//  Returns:    S_OK if a reboot was requested, S_FALSE if the user
//              didn't want to, or an error code otherwise.
//
//  Author:     danielwe   29 Oct 1997
//
//  Notes:
//
HRESULT
HrNcQueryUserForRebootEx (
    IN HWND       hwndParent,
    IN PCWSTR     pszCaption,
    IN PCWSTR     pszText,
    IN DWORD      dwFlags)
{
    HRESULT hr   = S_FALSE;
    INT     nRet = IDYES;

    if (dwFlags & QUFR_PROMPT)
    {
        nRet = MessageBox (hwndParent, pszText, pszCaption,
                           MB_YESNO | MB_ICONEXCLAMATION);
    }

    if (nRet == IDYES)
    {
        if (dwFlags & QUFR_REBOOT)
        {
            TOKEN_PRIVILEGES* ptpOld;
            hr = HrEnableAllPrivileges (&ptpOld);
            if (S_OK == hr)
            {
                if (!ExitWindowsEx (EWX_REBOOT, 10))
                {
                    hr = HrFromLastWin32Error();
                }

                MemFree (ptpOld);
            }
        }
        else
        {
            hr = S_OK;
        }
    }

    TraceError("HrNcQueryUserForRebootEx", (hr == S_FALSE) ? S_OK : hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   HrNcQueryUserForReboot
//
//  Purpose:    Query the user to reboot.  If he/she chooses yes, a reboot
//              is initiated.
//
//  Arguments:
//      hinst       [in] Module instance with string ids.
//      hwndParent  [in] Parent window handle.
//      unIdCaption [in] String id of caption text.
//      unIdText    [in] String id of message text.
//      dwFlags     [in] Control flags (QUFR_PROMPT | QUFR_REBOOT)
//
//  Returns:    S_OK if a reboot is initiated, S_FALSE if the user
//              didn't want to, or an error code otherwise.
//
//  Author:     shaunco   2 Jan 1998
//
//  Notes:
//
HRESULT
HrNcQueryUserForReboot (
    IN HINSTANCE   hinst,
    IN HWND        hwndParent,
    IN UINT        unIdCaption,
    IN UINT        unIdText,
    IN DWORD       dwFlags)
{
    PCWSTR pszCaption = SzLoadString (hinst, unIdCaption);
    PCWSTR pszText    = SzLoadString (hinst, unIdText);

    HRESULT hr = HrNcQueryUserForRebootEx (hwndParent, pszCaption,
                                           pszText, dwFlags);

    TraceError("HrNcQueryUserForReboot", (hr == S_FALSE) ? S_OK : hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   HrShell_NotifyIcon
//
//  Purpose:    HRESULT returning wrapper for Shell_NotifyIcon.
//
//  Arguments:
//      dwMessage [in]
//      pData     [in]
//
//  Returns:    S_OK or an error code.
//
//  Author:     shaunco   11 Nov 1998
//
//  Notes:
//
HRESULT
HrShell_NotifyIcon (
    IN DWORD dwMessage,
    IN PNOTIFYICONDATA pData)
{
    HRESULT hr              = E_FAIL; // First time through, this will succeed
    BOOL    fr              = FALSE;
    BOOL    fRetriedAlready = FALSE;
    BOOL    fAttemptRetry   = FALSE;
    INT     iRetries = 0;

    // Attempt the first time, and attempt again after an attempted correction
    //
    while ((hr == E_FAIL) || fAttemptRetry)
    {
        if (fAttemptRetry)
            fRetriedAlready = TRUE;

        fr = Shell_NotifyIcon(dwMessage, pData);
        if (!fr)
        {
            if (dwMessage == NIM_ADD && !fRetriedAlready)
            {
                NOTIFYICONDATA nidDelete;

                ZeroMemory (&nidDelete, sizeof(nidDelete));
                nidDelete.cbSize  = sizeof(NOTIFYICONDATA);
                nidDelete.hWnd    = pData->hWnd;
                nidDelete.uID     = pData->uID;

                Shell_NotifyIcon(NIM_DELETE, &nidDelete);

                fAttemptRetry = TRUE;
                hr = E_FAIL;
            }
            else
            {
                // We should not attempt [a|another] retry
                //
                fAttemptRetry = FALSE;
                hr = S_FALSE;
            }
        }
        else
        {
            fAttemptRetry = FALSE;
            hr = S_OK;
        }
    }

    // At this point, if hr == S_FALSE, it means that we tried to retry, and even that failed
    // We need to convert this to E_FAIL so we still return what we did before
    //
    if (S_FALSE == hr)
    {
        hr = E_FAIL;
    }

    // If we successfully swapped an icon, we should assert and figure out why that
    // went wrong.
    //
    if ((S_OK == hr) && fRetriedAlready)
    {
        TraceTag(ttidShellFolder, "We should debug this. We worked around a duplicate icon by removing "
            "the old one and putting the new one in place");
    }

    TraceError("HrShell_NotifyIcon", hr);
    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   LresFromHr
//
//  Purpose:    Translates an HRESULT into a valid LRESULT to be returned by
//              a dialog handler function.
//
//  Arguments:
//      hr [in] HRESULT to be translated.
//
//  Returns:    LRESULT
//
//  Author:     danielwe   24 Mar 1997
//
//  Notes:
//
LRESULT
LresFromHr (
    IN HRESULT hr)
{
    AssertSz (((LRESULT)hr) != PSNRET_INVALID, "Don't pass PSNRET_INVALID to "
              "LresFromHr! Use equivalent NETCFG_E_* value instead!");
    AssertSz (((LRESULT)hr) != PSNRET_INVALID_NOCHANGEPAGE, "Don't pass "
              "PSNRET_INVALID_NOCHANGEPAGE to "
              "LresFromHr! Use equivalent NETCFG_E_* value instead!");

    if (NETCFG_E_PSNRET_INVALID == hr)
    {
        return PSNRET_INVALID;
    }

    if (NETCFG_E_PSNRET_INVALID_NCPAGE == hr)
    {
        return PSNRET_INVALID_NOCHANGEPAGE;
    }

    return (SUCCEEDED(hr)) ? PSNRET_NOERROR : (LRESULT)hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   NcMsgBox
//
//  Purpose:    Displays a message box using resource strings and replaceable
//              parameters.
//
//  Arguments:
//      hinst       [in] hinstance for resource strings
//      hwnd        [in] parent window handle
//      unIdCaption [in] resource id of caption string
//      unIdFormat  [in] resource id of text string (with %1, %2, etc.)
//      unStyle     [in] standard message box styles
//      ...         [in] replaceable parameters (optional)
//                          (these must be PCWSTRs as that is all
//                          FormatMessage handles.)
//
//  Returns:    the return value of MessageBox()
//
//  Author:     shaunco   24 Mar 1997
//
//  Notes:      FormatMessage is used to do the parameter substitution.
//
INT
WINAPIV
NcMsgBox (
    IN HINSTANCE   hinst,
    IN HWND        hwnd,
    IN UINT        unIdCaption,
    IN UINT        unIdFormat,
    IN UINT        unStyle,
    IN ...)
{
    PCWSTR pszCaption = SzLoadString (hinst, unIdCaption);
    PCWSTR pszFormat  = SzLoadString (hinst, unIdFormat);

    PWSTR  pszText = NULL;
    va_list val;
    va_start (val, unStyle);
    FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                   pszFormat, 0, 0, (PWSTR)&pszText, 0, &val);
    va_end (val);

    INT nRet = MessageBox (hwnd, pszText, pszCaption, unStyle);
    LocalFree (pszText);

    return nRet;
}

//+---------------------------------------------------------------------------
//
//  Function:   NcMsgBoxWithVarCaption
//
//  Purpose:    Same as NcMsgBox, but allows a string parameter to be used
//              for the caption.
//
//  Arguments:
//      hinst           [in] hinstance for resource strings
//      hwnd            [in] parent window handle
//      unIdCaption     [in] resource id of caption string
//      pszCaptionParam [in] string parameter to use for caption
//      unIdFormat      [in] resource id of text string (with %1, %2, etc.)
//      unStyle         [in] standard message box styles
//      ...             [in] replaceable parameters (optional)
//                              (these must be PCWSTRs as that is all
//                              FormatMessage handles.)
//
//  Returns:    the return value of MessageBox()
//
//  Author:     danielwe   29 Oct 1997
//
//  Notes:      FormatMessage is used to do the parameter substitution.
//
INT
WINAPIV
NcMsgBoxWithVarCaption (
    HINSTANCE   hinst,
    HWND        hwnd,
    UINT        unIdCaption,
    PCWSTR      pszCaptionParam,
    UINT        unIdFormat,
    UINT        unStyle,
    ...)
{
    PCWSTR pszCaption = SzLoadString (hinst, unIdCaption);
    PCWSTR pszFormat  = SzLoadString (hinst, unIdFormat);

    PWSTR  pszNewCaption = NULL;
    DwFormatStringWithLocalAlloc (pszCaption, &pszNewCaption, pszCaptionParam);

    PWSTR  pszText = NULL;
    va_list val;
    va_start (val, unStyle);
    FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                   pszFormat, 0, 0, (PWSTR)&pszText, 0, &val);
    va_end (val);

    INT nRet = MessageBox (hwnd, pszText, pszNewCaption, unStyle);
    LocalFree (pszText);
    LocalFree (pszNewCaption);

    return nRet;
}

//+---------------------------------------------------------------------------
//
//  Function:   NcMsgBoxWithWin32ErrorText
//
//  Purpose:    Displays a message box using a Win32 error code, resource
//              strings and replaceable parameters.
//              The output text is a combination of the user's format
//              string (with parameter's replaced) and the Win32 error
//              text as returned from FormatMessage.  These two strings
//              are combined using the IDS_TEXT_WITH_WIN32_ERROR resource.
//
//  Arguments:
//      dwError     [in] Win32 error code
//      hinst       [in] Module instance where string resources live.
//      hwnd        [in] parent window handle
//      unIdCaption [in] resource id of caption string
//      unIdCombineFormat [in] resource id of format string to combine
//                              error text with unIdFormat text.
//      unIdFormat  [in] resource id of text string (with %1, %2, etc.)
//      unStyle     [in] standard message box styles
//      ...         [in] replaceable parameters (optional)
//                          (these must be PCWSTRs as that is all
//                          FormatMessage handles.)
//
//  Returns:    the return value of MessageBox()
//
//  Author:     shaunco   3 May 1997
//
//  Notes:      FormatMessage is used to do the parameter substitution.
//
NOTHROW
INT
WINAPIV
NcMsgBoxWithWin32ErrorText (
    IN DWORD       dwError,
    IN HINSTANCE   hinst,
    IN HWND        hwnd,
    IN UINT        unIdCaption,
    IN UINT        unIdCombineFormat,
    IN UINT        unIdFormat,
    IN UINT        unStyle,
    IN ...)
{
    // Get the user's text with parameter's replaced.
    //
    PCWSTR pszFormat = SzLoadString (hinst, unIdFormat);
    PWSTR  pszText;
    va_list val;
    va_start (val, unStyle);
    FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_STRING,
                   pszFormat, 0, 0, (PWSTR)&pszText, 0, &val);
    va_end(val);

    // Get the error text for the Win32 error.
    //
    PWSTR pszError;
    FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                   NULL, dwError, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                   (PWSTR)&pszError, 0, NULL);

    // Combine the user's text with the error text using IDS_TEXT_WITH_WIN32_ERROR.
    //
    PCWSTR pszTextWithErrorFmt = SzLoadString (hinst, unIdCombineFormat);
    PWSTR  pszTextWithError;
    DwFormatStringWithLocalAlloc (pszTextWithErrorFmt, &pszTextWithError,
                                  pszText, pszError);

    PCWSTR pszCaption = SzLoadString (hinst, unIdCaption);
    INT nRet = MessageBox (hwnd, pszTextWithError, pszCaption, unStyle);

    LocalFree (pszTextWithError);
    LocalFree (pszError);
    LocalFree (pszText);

    return nRet;
}

//+---------------------------------------------------------------------------
//
//  Function:   SendDlgItemsMessage
//
//  Purpose:    Send a group of dialog items the same message.
//
//  Arguments:
//      hDlg    [in] Window handle of parent dialog.
//      ccid    [in] Count of elements in array pointed to by acid.
//      acid    [in] Array of control ids.
//      unMsg   [in] Message to send
//      wParam  [in] First message parameter
//      lParam  [in] Second message parameter
//
//  Returns:    nothing
//
//  Author:     shaunco   11 Jun 1997
//
//  Notes:
//
VOID
SendDlgItemsMessage (
    IN HWND        hDlg,
    IN INT         ccid,
    IN const INT*  acid,
    IN UINT        unMsg,
    IN WPARAM      wParam,
    IN LPARAM      lParam)
{
    Assert (IsWindow (hDlg));
    Assert (FImplies (ccid, acid));

    while (ccid--)
    {
        Assert (IsWindow (GetDlgItem (hDlg, *acid)));

        SendDlgItemMessage (hDlg, *acid++, unMsg, wParam, lParam);
    }
}

//
// Function:    SetDefaultButton
//
// Purpose:     Set the new default pushbutton on a dialog
//
// Params:      hdlg  [in] - Dialog HWND
//              iddef [in] - Id of new default pushbutton
//
// Returns:     nothing
//
VOID
SetDefaultButton(
    IN HWND hdlg,
    IN INT iddef)
{
    HWND hwnd;
    DWORD dwData;

    Assert(hdlg);

    dwData = (DWORD)SendMessage (hdlg, DM_GETDEFID, 0, 0L);
    if ((HIWORD(dwData) == DC_HASDEFID) && LOWORD(dwData))
    {
        hwnd = GetDlgItem (hdlg, (INT)LOWORD(dwData));
        if ((LOWORD(dwData) != iddef) && (hwnd))
        {
            SendMessage (hwnd, BM_SETSTYLE, (WPARAM)BS_PUSHBUTTON, TRUE);
        }
    }

    SendMessage (hdlg, DM_SETDEFID,(WPARAM)iddef, 0L);
    if (iddef)
    {
        hwnd = GetDlgItem (hdlg, iddef);
        Assert(hwnd);
        SendMessage (hwnd, BM_SETSTYLE, (WPARAM)BS_DEFPUSHBUTTON, TRUE);
    }
}

static const CONTEXTIDMAP c_adwContextIdMap[] =
{
    { IDOK,                   IDH_OK,     IDH_OK  },
    { IDCANCEL,               IDH_CANCEL, IDH_CANCEL },
    { 0,                      0,          0 },      // end marker
};

//+---------------------------------------------------------------------------
//
//  Function:   DwContextIdFromIdc
//
//  Purpose:    Converts the given control ID to a context help ID
//
//  Arguments:
//      idControl [in]  Control ID to convert
//
//  Returns:    Context help ID for that control (mapping comes from help
//              authors)
//
//  Author:     danielwe   27 May 1998
//
//  Notes:
//
DWORD DwContextIdFromIdc(
    PCCONTEXTIDMAP lpContextIdMap,
    BOOL bJpn,
    INT idControl)
{
    DWORD   idw;

    Assert(lpContextIdMap);

    for (idw = 0; lpContextIdMap[idw].idControl; idw++)
    {
        if (idControl == lpContextIdMap[idw].idControl)
        {
            if (!bJpn)
            {
                return lpContextIdMap[idw].dwContextId;
            }
            else
            {
                return lpContextIdMap[idw].dwContextIdJapan;
            }
        }
    }

    // Not found, just return 0
    return 0;
}

//+---------------------------------------------------------------------------
//
//  Function:   OnHelpGeneric
//
//  Purpose:    Handles help generically
//
//  Arguments:
//      hwnd   [in]     HWND of parent window
//      lParam [in]     lParam of the WM_HELP message
//
//  Returns:    Nothing
//
//  Author:     danielwe   27 May 1998
//              anbrad     18 May 1999 moved to common.  common control id's added.
//
//  Notes:
//
VOID OnHelpGeneric(
    HWND hwnd,
    LPHELPINFO lphi,
    PCCONTEXTIDMAP pContextMap,
    BOOL bJpn,
    PCWSTR pszHelpFile)
{
    static const TCHAR c_szWindowsHelpFile[] = TEXT("windows.hlp");

    Assert(lphi);

    if (lphi->iContextType == HELPINFO_WINDOW)
    {
        switch(lphi->iCtrlId)
        {
        case -1:        // IDC_STATIC
            break;
        case IDOK:
        case IDCANCEL:
        case IDABORT:
        case IDRETRY:
        case IDIGNORE:
        case IDYES:
        case IDNO:
        case IDCLOSE:
        case IDHELP:
            WinHelp(hwnd, c_szWindowsHelpFile, HELP_CONTEXTPOPUP,
                    DwContextIdFromIdc(c_adwContextIdMap, bJpn, lphi->iCtrlId));
            break;
        default:
            WinHelp(hwnd, pszHelpFile, HELP_CONTEXTPOPUP,
                    DwContextIdFromIdc(pContextMap, bJpn, lphi->iCtrlId));
        }
    }
}