// Copyright (c) 1995, Microsoft Corporation, all rights reserved
//
// edit.c
// Remote Access Common Dialog APIs
// List editor, string editor dialog routines
//
// 08/28/95 Steve Cobb


#include "rasdlgp.h"


//-----------------------------------------------------------------------------
// Local datatypes (alphabetically)
//-----------------------------------------------------------------------------

// List editor dialog argument block.
//
typedef struct
_LEARGS
{
    // Caller's arguments to the stub API.
    //
    DTLLIST*     pList;
    BOOL*        pfCheck;
    DWORD        dwMaxItemLen;
    TCHAR*       pszTitle;
    TCHAR*       pszItemLabel;
    TCHAR*       pszListLabel;
    TCHAR*       pszCheckLabel;
    TCHAR*       pszDefaultItem;
    INT          iSelInitial;
    DWORD*       pdwHelp;
    DWORD        dwfFlags;
    PDESTROYNODE pDestroyId;
}
LEARGS;


// List editor dialog context block.
//
typedef struct
_LEINFO
{
    // Caller's arguments to the dialog.
    //
    LEARGS* pArgs;

    // Handle of this dialog and some of it's controls.
    //
    HWND hwndDlg;
    HWND hwndStItem;
    HWND hwndStList;
    HWND hwndPbAdd;
    HWND hwndPbReplace;
    HWND hwndPbUp;
    HWND hwndPbDown;
    HWND hwndPbDelete;
    HWND hwndPbOk;
    HWND hwndEb;
    HWND hwndLb;
    HWND hwndCb;

    // Convenient alternatives to (pInfo->pArgs->dwFlags & LEDFLAG_Sorted) and
    // (pInfo->pArgs->dwFlags & LEDFLAG_NoDeleteLastItem).
    //
    BOOL fSorted;
    BOOL fNoDeleteLast;

    // Button bitmaps.
    //
    HBITMAP hbmUp;
    HBITMAP hbmDown;

    // List of empty nodes whose node-IDs should be 'pDestroyId'ed if user
    // presses OK.
    //
    DTLLIST* pListDeletes;
}
LEINFO;


// String Editor dialog arument block.
//
typedef struct
_ZEARGS
{
    /* Caller's aruments to the stub API.
    */
    TCHAR*  pszIn;
    DWORD   dwSidTitle;
    DWORD   dwSidLabel;
    DWORD   cbMax;
    DWORD   dwHelpId;
    TCHAR** ppszOut;
}
ZEARGS;


// String Editor dialog context block.
//
typedef struct
_ZEINFO
{
    // Caller's arguments to the stub API.
    //
    ZEARGS* pArgs;

    // Dialog and control handles.
    //
    HWND hwndDlg;
    HWND hwndEb;
}
ZEINFO;


//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------

INT_PTR CALLBACK
LeDlgProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam );

VOID
LeAdd(
    IN LEINFO* pInfo );

BOOL
LeCommand(
    IN LEINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl );

VOID
LeDelete(
    IN LEINFO* pInfo );

VOID
LeDown(
    IN LEINFO* pInfo );

VOID
LeEnableUpAndDownButtons(
    IN LEINFO* pInfo );

VOID
LeExitNoMemory(
    IN LEINFO* pInfo );

BOOL
LeInit(
    IN HWND hwndDlg,
    IN LEARGS* pArgs );

VOID
LeItemTextFromListSelection(
    IN LEINFO* pInfo );

VOID
LeReplace(
    IN LEINFO* pInfo );

BOOL
LeSaveSettings(
    IN LEINFO* pInfo );

VOID
LeTerm(
    IN HWND hwndDlg );

VOID
LeUp(
    IN LEINFO* pInfo );

INT_PTR CALLBACK
ZeDlgProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam );

BOOL
ZeCommand(
    IN ZEINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl );

BOOL
ZeInit(
    IN HWND hwndDlg,
    IN ZEARGS* pArgs );

VOID
ZeTerm(
    IN HWND hwndDlg );


//-----------------------------------------------------------------------------
// List Editor dialog entry point
//-----------------------------------------------------------------------------


BOOL
ListEditorDlg(
    IN HWND hwndOwner,
    IN OUT DTLLIST* pList,
    IN OUT BOOL* pfCheck,
    IN DWORD dwMaxItemLen,
    IN TCHAR* pszTitle,
    IN TCHAR* pszItemLabel,
    IN TCHAR* pszListLabel,
    IN TCHAR* pszCheckLabel,
    IN TCHAR* pszDefaultItem,
    IN INT iSelInitial,
    IN DWORD* pdwHelp,
    IN DWORD dwfFlags,
    IN PDESTROYNODE pDestroyId )

    // Pops-up the List Editor dialog.
    //
    // 'HwndOwner' is the owner of the dialog.  'PList' is, on entry, the Psz
    // list to display initially, and on successful exit, the result list.
    // 'PfCheck' is the state of the check box or NULL for the non-checkbox
    // style.  'DwMaxItemLen' is the maximum length of an individual list
    // item.  'PszTitle' is the dialog title.  'PszItemLabel' is the label
    // (and hotkey) associated with the item box.  'PszListLabel' is the label
    // (and hotkey) associated with the list.  'PszCheckLabel' is the label
    // (and hotkey) associated with the checkbox.  'PszDefaultItem' is the
    // default contents of the edit box or for the selected list text.
    // 'ISelInitial' is the item the list to initally select.  'PdwHelp' is
    // the array of CID_LE_* help contexts to use.  'DwfFlags' indicates
    // LEDFLAG_* behavior options.  'PDestroyId' is the routine to use to
    // destroy node IDs when they are deleted or NULL if none.
    //
    // Returns true if user pressed OK and succeeded, false if he pressed
    // Cancel or encountered an error.
    //
{
    INT_PTR nStatus;
    LEARGS args;

    TRACE( "ListEditorDlg" );

    args.pList = pList;
    args.pfCheck = pfCheck;
    args.dwMaxItemLen = dwMaxItemLen;
    args.pszTitle = pszTitle;
    args.pszItemLabel = pszItemLabel;
    args.pszListLabel = pszListLabel;
    args.pszCheckLabel = pszCheckLabel;
    args.pszDefaultItem = pszDefaultItem;
    args.iSelInitial = iSelInitial;
    args.pdwHelp = pdwHelp;
    args.dwfFlags = dwfFlags;
    args.pDestroyId = pDestroyId;

    nStatus =
        DialogBoxParam(
            g_hinstDll,
            (pfCheck)
                ? MAKEINTRESOURCE( DID_LE_ListEditor2 )
                : ((dwfFlags & LEDFLAG_Sorted)
                       ? MAKEINTRESOURCE( DID_LE_ListEditor3 )
                       : MAKEINTRESOURCE( DID_LE_ListEditor )),
            hwndOwner,
            LeDlgProc,
            (LPARAM )&args );

    if (nStatus == -1)
    {
        ErrorDlg( hwndOwner, SID_OP_LoadDlg, ERROR_UNKNOWN, NULL );
        nStatus = FALSE;
    }

    return (nStatus) ? TRUE : FALSE;
}


//----------------------------------------------------------------------------
// List Editor dialog routines
// Listed alphabetically following dialog proc
//----------------------------------------------------------------------------

INT_PTR CALLBACK
LeDlgProc(
    IN HWND hwnd,
    IN UINT unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam )

    // DialogProc callback for the List Editor dialog.  Parameters and return
    // value are as described for standard windows 'DialogProc's.
    //
{
#if 0
    TRACE4( "LeDlgProc(h=$%x,m=$%x,w=$%x,l=$%x)",
           (DWORD )hwnd, (DWORD )unMsg, (DWORD )wparam, (DWORD )lparam );
#endif

    switch (unMsg)
    {
        case WM_INITDIALOG:
        {
            return LeInit( hwnd, (LEARGS* )lparam );
        }

        case WM_HELP:
        case WM_CONTEXTMENU:
        {
            LEINFO* pInfo = (LEINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            ASSERT( pInfo );

            ContextHelp( pInfo->pArgs->pdwHelp, hwnd, unMsg, wparam, lparam );
            break;
        }

        case WM_COMMAND:
        {
            LEINFO* pInfo = (LEINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            ASSERT( pInfo );

            return LeCommand(
                pInfo, HIWORD( wparam ), LOWORD( wparam ), (HWND )lparam );
        }

        case WM_DESTROY:
        {
            LeTerm( hwnd );
            break;
        }
    }

    return FALSE;
}


VOID
LeAdd(
    IN LEINFO* pInfo )

    // Add button click handler.  'PInfo' is the dialog context.
    //
{
    TCHAR* psz;

    psz = GetText( pInfo->hwndEb );
    if (!psz)
    {
        LeExitNoMemory( pInfo );
        return;
    }

    if (pInfo->pArgs->dwfFlags & LEDFLAG_Unique)
    {
        if (ListBox_IndexFromString( pInfo->hwndLb, psz ) >= 0)
        {
            MSGARGS msgargs;

            ZeroMemory( &msgargs, sizeof(msgargs) );
            msgargs.apszArgs[ 0 ] = psz;
            MsgDlg( pInfo->hwndDlg, SID_NotUnique, &msgargs );
            Edit_SetSel( pInfo->hwndEb, 0, -1 );
            SetFocus( pInfo->hwndEb );
            Free( psz );
            return;
        }
    }

    ListBox_SetCurSel( pInfo->hwndLb,
        ListBox_AddItem( pInfo->hwndLb, psz, 0 ) );
    Free( psz );
    LeEnableUpAndDownButtons( pInfo );
    EnableWindow( pInfo->hwndPbReplace, FALSE );

    if (!pInfo->fNoDeleteLast || ListBox_GetCount( pInfo->hwndLb ) > 1)
    {
        EnableWindow( pInfo->hwndPbDelete, TRUE );
    }

    SetWindowText( pInfo->hwndEb, TEXT("") );
    SetFocus( pInfo->hwndEb );
}


BOOL
LeCommand(
    IN LEINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl )

    // Called on WM_COMMAND.  'PInfo' is the dialog context.  'WNotification'
    // is the notification code of the command.  'wId' is the control/menu
    // identifier of the command.  'HwndCtrl' is the control window handle of
    // the command.
    //
    // Returns true if processed message, false otherwise.
    //
{
    TRACE3( "LeCommand(n=%d,i=%d,c=$%x)",
        (DWORD )wNotification, (DWORD )wId, (ULONG_PTR )hwndCtrl );

    switch (wId)
    {
        case CID_LE_PB_Add:
        {
            LeAdd( pInfo );
            return TRUE;
        }

        case CID_LE_PB_Replace:
        {
            LeReplace( pInfo );
            return TRUE;
        }

        case CID_LE_PB_Up:
        {
            LeUp( pInfo );
            return TRUE;
        }

        case CID_LE_PB_Down:
        {
            LeDown( pInfo );
            return TRUE;
        }

        case CID_LE_PB_Delete:
        {
            LeDelete( pInfo );
            return TRUE;
        }

        case CID_LE_EB_Item:
        {
            if (wNotification == EN_SETFOCUS || wNotification == EN_UPDATE)
            {
                TCHAR* psz = GetText( pInfo->hwndEb );

                if (psz && lstrlen( psz ) > 0 && !IsAllWhite( psz ))
                {
                    EnableWindow( pInfo->hwndPbAdd, TRUE );
                    EnableWindow( pInfo->hwndPbReplace, TRUE );
                    Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbAdd );
                }
                else
                {
                    EnableWindow( pInfo->hwndPbAdd, FALSE );
                    EnableWindow( pInfo->hwndPbReplace, FALSE );
                    Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbOk );
                }

                Free0( psz );
            }
            return TRUE;
        }

        case CID_LE_LB_List:
        {
            if (wNotification == LBN_SELCHANGE)
            {
                LeEnableUpAndDownButtons( pInfo );
                if (ListBox_GetCurSel( pInfo->hwndLb ) >= 0)
                {
                    LeItemTextFromListSelection( pInfo );
                }
            }
            return TRUE;
        }

        case CID_LE_CB_Promote:
        {
            if (wNotification == BN_SETFOCUS)
            {
                Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbOk );
            }
            return TRUE;
        }

        case IDOK:
        {
            EndDialog( pInfo->hwndDlg, LeSaveSettings( pInfo ) );
            return TRUE;
        }

        case IDCANCEL:
        {
            DTLLIST* pList;
            DTLNODE* pNode;

            TRACE( "Cancel pressed" );
            EndDialog( pInfo->hwndDlg, FALSE );
            return TRUE;
        }
    }

    return FALSE;
}


VOID
LeDelete(
    IN LEINFO* pInfo )

    // Delete button click handler.  'PInfo' is the dialog context.
    //
{
    INT i;
    INT c;

    i = ListBox_GetCurSel( pInfo->hwndLb );
    if (pInfo->pArgs->pDestroyId)
    {
        LONG_PTR lId = ListBox_GetItemData( pInfo->hwndLb, i );
        if (lId != 0)
        {
            DTLNODE* pNode;

            pNode = DtlCreateNode( NULL, lId );
            if (!pNode)
            {
                ErrorDlg( pInfo->hwndDlg, SID_OP_DisplayData,
                    ERROR_NOT_ENOUGH_MEMORY, NULL );
                EndDialog( pInfo->hwndDlg, FALSE );
                return;
            }

            DtlAddNodeFirst( pInfo->pListDeletes, pNode );
        }
    }
    ListBox_DeleteString( pInfo->hwndLb, i );
    c = ListBox_GetCount( pInfo->hwndLb );

    if (c == 0)
    {
        EnableWindow( pInfo->hwndPbReplace, FALSE );
        EnableWindow( pInfo->hwndPbDelete, FALSE );
        SetFocus( pInfo->hwndEb );
        Edit_SetSel( pInfo->hwndEb, 0, -1 );
    }
    else
    {
        if (c == 1 && pInfo->fNoDeleteLast)
        {
            EnableWindow( pInfo->hwndPbDelete, FALSE );
        }

        if (i >= c)
        {
            i = c - 1;
        }

        ListBox_SetCurSel( pInfo->hwndLb, i );
    }

    LeEnableUpAndDownButtons( pInfo );

    if (IsWindowEnabled( GetFocus() ))
    {
        SetFocus( pInfo->hwndEb );
        Edit_SetSel( pInfo->hwndEb, 0, -1 );
    }
}


VOID
LeDown(
    IN LEINFO* pInfo )

    // Down button click handler.  'PInfo' is the dialog context.
    //
{
    TCHAR* psz;
    INT i;
    LONG_PTR lId;

    ASSERT( !pInfo->fSorted );

    i = ListBox_GetCurSel( pInfo->hwndLb );
    psz = ListBox_GetPsz( pInfo->hwndLb, i );
    if (!psz)
    {
        LeExitNoMemory( pInfo );
        return;
    }

    lId = ListBox_GetItemData( pInfo->hwndLb, i );
    ListBox_InsertString( pInfo->hwndLb, i + 2, psz );
    ListBox_SetItemData( pInfo->hwndLb, i + 2, lId );
    Free( psz );
    ListBox_DeleteString( pInfo->hwndLb, i );
    ListBox_SetCurSel( pInfo->hwndLb, i + 1 );

    if (i == ListBox_GetCount( pInfo->hwndLb ))
    {
        Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbUp );
        SetFocus( pInfo->hwndPbUp );
    }

    LeEnableUpAndDownButtons( pInfo );
}


VOID
LeEnableUpAndDownButtons(
    IN LEINFO* pInfo )

    // Determine if the Up and Down operations make sense and enable/disable
    // the buttons as appropriate.  'PInfo' is the dialog context.
    //
{
    INT i;
    INT c;

    if (pInfo->fSorted)
    {
        return;
    }

    i = ListBox_GetCurSel( pInfo->hwndLb );
    c = ListBox_GetCount( pInfo->hwndLb );

    EnableWindow( pInfo->hwndPbDown, (i < c - 1 ) );
    EnableWindow( pInfo->hwndPbUp, (i > 0) );
}


VOID
LeExitNoMemory(
    IN LEINFO* pInfo )

    // End the dialog reporting a memory.  'PInfo' is the dialog context.
    //
{
    ErrorDlg( pInfo->hwndDlg,
        SID_OP_DisplayData, ERROR_NOT_ENOUGH_MEMORY, NULL );
    EndDialog( pInfo->hwndDlg, FALSE );
}


BOOL
LeInit(
    IN HWND hwndDlg,
    IN LEARGS* pArgs )

    // Called on WM_INITDIALOG.  'HwndDlg' is the handle of the phonebook
    // dialog window.  'PArgs' is caller's arguments as passed to the stub
    // API.
    //
    // Return false if focus was set, true otherwise, i.e. as defined for
    // WM_INITDIALOG.
    //
{
    DWORD dwErr;
    LEINFO* pInfo;
    DTLNODE* pNode;
    INT c;

    TRACE( "LeInit" );

    // Allocate the dialog context block.  Initialize minimally for proper
    // cleanup, then attach to the dialog window.
    //
    {
        pInfo = Malloc( sizeof(*pInfo) );
        if (!pInfo)
        {
            ErrorDlg( hwndDlg, SID_OP_LoadDlg, ERROR_NOT_ENOUGH_MEMORY, NULL );
            EndDialog( hwndDlg, FALSE );
            return TRUE;
        }

        ZeroMemory( pInfo, sizeof(*pInfo) );
        pInfo->pArgs = pArgs;
        pInfo->hwndDlg = hwndDlg;

        SetWindowLongPtr( hwndDlg, DWLP_USER, (ULONG_PTR )pInfo );
        TRACE( "Context set" );
    }

    // Set up convenient shortcuts.
    //
    if (pArgs->dwfFlags & LEDFLAG_Sorted)
    {
        pInfo->fSorted = TRUE;
    }

    if (pArgs->dwfFlags & LEDFLAG_NoDeleteLastItem)
    {
        pInfo->fNoDeleteLast = TRUE;
    }

    pInfo->hwndStItem = GetDlgItem( hwndDlg, CID_LE_ST_Item );
    ASSERT( pInfo->hwndStItem );
    pInfo->hwndStList = GetDlgItem( hwndDlg, CID_LE_ST_List );
    ASSERT( pInfo->hwndStList );
    pInfo->hwndPbAdd = GetDlgItem( hwndDlg, CID_LE_PB_Add );
    ASSERT( pInfo->hwndPbAdd );
    pInfo->hwndPbReplace = GetDlgItem( hwndDlg, CID_LE_PB_Replace );
    ASSERT( pInfo->hwndPbReplace );
    pInfo->hwndPbDelete = GetDlgItem( hwndDlg, CID_LE_PB_Delete );
    ASSERT( pInfo->hwndPbDelete );
    pInfo->hwndPbOk = GetDlgItem( hwndDlg, IDOK );
    ASSERT( pInfo->hwndPbOk );
    pInfo->hwndEb = GetDlgItem( hwndDlg, CID_LE_EB_Item );
    ASSERT( pInfo->hwndEb );
    pInfo->hwndLb = GetDlgItem( hwndDlg, CID_LE_LB_List );
    ASSERT( pInfo->hwndLb );

    if (pArgs->pDestroyId)
    {
        // Create the empty list of deletions.
        //
        pInfo->pListDeletes = DtlCreateList( 0L );
        if (!pInfo->pListDeletes)
        {
            ErrorDlg( hwndDlg, SID_OP_LoadDlg, ERROR_NOT_ENOUGH_MEMORY, NULL );
            EndDialog( hwndDlg, FALSE );
            return TRUE;
        }
    }

    if (!pInfo->fSorted)
    {
        pInfo->hwndPbUp = GetDlgItem( hwndDlg, CID_LE_PB_Up );
        ASSERT( pInfo->hwndPbUp );
        pInfo->hwndPbDown = GetDlgItem( hwndDlg, CID_LE_PB_Down );
        ASSERT( pInfo->hwndPbDown );

        // Draw the graphical up and down arrow indicators.
        //
        pInfo->hbmUp = Button_CreateBitmap(
            pInfo->hwndPbUp, BMS_UpArrowOnRight );
        if (pInfo->hbmUp)
        {
            SendMessage( pInfo->hwndPbUp, BM_SETIMAGE, 0,
                (LPARAM )pInfo->hbmUp );
        }

        pInfo->hbmDown = Button_CreateBitmap(
            pInfo->hwndPbDown, BMS_DownArrowOnRight );
        if (pInfo->hbmDown)
        {
            SendMessage( pInfo->hwndPbDown, BM_SETIMAGE, 0,
                (LPARAM )pInfo->hbmDown );
        }
    }

    if (pArgs->pfCheck)
    {
        pInfo->hwndCb = GetDlgItem( hwndDlg, CID_LE_CB_Promote );
        ASSERT( pInfo->hwndCb );
        SetWindowText( pInfo->hwndCb, pArgs->pszCheckLabel );
        Button_SetCheck( pInfo->hwndCb, *pArgs->pfCheck );
    }

    Edit_LimitText( pInfo->hwndEb, pArgs->dwMaxItemLen );

    // Set caller-defined dialog title and labels.
    //
    SetWindowText( pInfo->hwndDlg, pArgs->pszTitle );
    SetWindowText( pInfo->hwndStItem, pArgs->pszItemLabel );
    SetWindowText( pInfo->hwndStList, pArgs->pszListLabel );

    // Fill the listbox.
    //
    for (pNode = DtlGetFirstNode( pArgs->pList );
         pNode;
         pNode = DtlGetNextNode( pNode ))
    {
        TCHAR* psz = (TCHAR* )DtlGetData( pNode );
        ASSERT( psz );

        ListBox_AddItem( pInfo->hwndLb, psz, (VOID* ) DtlGetNodeId( pNode ) );
    }

    c = ListBox_GetCount( pInfo->hwndLb );
    if (c > 0)
    {
        // Select item selected by caller.
        //
        ListBox_SetCurSelNotify( pInfo->hwndLb, pArgs->iSelInitial );
        LeEnableUpAndDownButtons( pInfo );

        if (c == 1 && pInfo->fNoDeleteLast)
        {
            EnableWindow( pInfo->hwndPbDelete, FALSE );
        }
    }
    else
    {
        // Empty list.
        //
        if (!pInfo->fSorted)
        {
            EnableWindow( pInfo->hwndPbUp, FALSE );
            EnableWindow( pInfo->hwndPbDown, FALSE );
        }
        EnableWindow( pInfo->hwndPbDelete, FALSE );
    }

    // Set default edit box contents, if any.
    //
    if (pArgs->pszDefaultItem)
    {
        SetWindowText( pInfo->hwndEb, pArgs->pszDefaultItem );
        Edit_SetSel( pInfo->hwndEb, 0, -1 );
    }
    else
    {
        EnableWindow( pInfo->hwndPbAdd, FALSE );
        EnableWindow( pInfo->hwndPbReplace, FALSE );
    }

    // Center dialog on the owner window.
    //
    CenterWindow( hwndDlg, GetParent( hwndDlg ) );

    // Add context help button to title bar.  Dlgedit.exe doesn't currently
    // support this at resource edit time.  When that's fixed set
    // DS_CONTEXTHELP there and remove this call.
    //
    AddContextHelpButton( hwndDlg );

    return TRUE;
}


VOID
LeItemTextFromListSelection(
    IN LEINFO* pInfo )

    // Copies the currently selected item in the list to the edit box.
    // 'PInfo' is the dialog context.
    //
{
    TCHAR* psz;
    INT iSel;

    iSel = ListBox_GetCurSel( pInfo->hwndLb );
    if (iSel >= 0)
    {
        psz = ListBox_GetPsz( pInfo->hwndLb, iSel );
        if (psz)
        {
            SetWindowText( pInfo->hwndEb, psz );
            Free( psz );
            return;
        }
    }

    SetWindowText( pInfo->hwndEb, TEXT("") );
}


VOID
LeReplace(
    IN LEINFO* pInfo )

    // Replace button click handler.  'PInfo' is the dialog context.
    //
{
    TCHAR* psz;
    INT i;
    LONG_PTR lId;

    psz = GetText( pInfo->hwndEb );
    if (!psz)
    {
        LeExitNoMemory( pInfo );
        return;
    }

    if (pInfo->pArgs->dwfFlags & LEDFLAG_Unique)
    {
        if (ListBox_IndexFromString( pInfo->hwndLb, psz ) >= 0)
        {
            MSGARGS msgargs;

            ZeroMemory( &msgargs, sizeof(msgargs) );
            msgargs.apszArgs[ 0 ] = psz;
            MsgDlg( pInfo->hwndDlg, SID_NotUnique, &msgargs );
            Edit_SetSel( pInfo->hwndEb, 0, -1 );
            SetFocus( pInfo->hwndEb );
            Free( psz );
            return;
        }
    }

    i = ListBox_GetCurSel( pInfo->hwndLb );
    lId = ListBox_GetItemData( pInfo->hwndLb, i );
    ListBox_DeleteString( pInfo->hwndLb, i );

    if (pInfo->fSorted)
    {
        i = ListBox_AddItem( pInfo->hwndLb, psz, (VOID* )lId );
    }
    else
    {
        ListBox_InsertString( pInfo->hwndLb, i, psz );
        ListBox_SetItemData( pInfo->hwndLb, i, lId );
    }

    Free( psz );
    ListBox_SetCurSel( pInfo->hwndLb, i );
    SetFocus( pInfo->hwndEb );
    SetWindowText( pInfo->hwndEb, TEXT("") );
}


BOOL
LeSaveSettings(
    IN LEINFO* pInfo )

    // Saves dialog settings in the stub API caller's list.  'PInfo' is the
    // dialog context.
    //
    // Returns true if successful, false if does not validate.
    //
{
    DWORD dwErr;
    DTLNODE* pNode;
    DTLLIST* pList;
    DTLLIST* pListNew;
    TCHAR* psz;
    LONG_PTR lId;
    INT c;
    INT i;

    // Make new list from list box contents.
    //
    do
    {
        pListNew = DtlCreateList( 0L );
        if (!pListNew)
        {
            dwErr = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        dwErr = 0;
        c = ListBox_GetCount( pInfo->hwndLb );

        for (i = 0; i < c; ++i)
        {
            psz = ListBox_GetPsz( pInfo->hwndLb, i );
            if (!psz)
            {
                dwErr = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            lId = ListBox_GetItemData( pInfo->hwndLb, i );
            ASSERT( lId>=0 );

            pNode = DtlCreateNode( psz, lId );
            if (!pNode)
            {
                Free( psz );
                dwErr = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }

            DtlAddNodeLast( pListNew, pNode );
        }
    }
    while (FALSE);

    if (dwErr != 0)
    {
        ErrorDlg( pInfo->hwndDlg, SID_OP_DisplayData, dwErr, NULL );
        DtlDestroyList( pListNew, DestroyPszNode );
        return FALSE;
    }

    // Free all data in the old list.
    //
    while (pNode = DtlGetFirstNode( pInfo->pArgs->pList ))
    {
        Free( (TCHAR* )DtlGetData( pNode ) );
        DtlDeleteNode( pInfo->pArgs->pList, pNode );
    }

    // Free the node-IDs in the list of deletions.
    //
    if (pInfo->pListDeletes)
    {
        while (pNode = DtlGetFirstNode( pInfo->pListDeletes ))
        {
            pInfo->pArgs->pDestroyId( (DTLNODE* )DtlGetNodeId( pNode ) );
            DtlDeleteNode( pInfo->pListDeletes, pNode );
        }
    }

    // Move the new list onto caller's list.
    //
    while (pNode = DtlGetFirstNode( pListNew ))
    {
        DtlRemoveNode( pListNew, pNode );
        DtlAddNodeLast( pInfo->pArgs->pList, pNode );
    }
    DtlDestroyList( pListNew, DestroyPszNode );

    // Tell caller what the checkbox setting is.
    //
    if (pInfo->pArgs->pfCheck)
    {
        *pInfo->pArgs->pfCheck = Button_GetCheck( pInfo->hwndCb );
    }

    return TRUE;
}


VOID
LeTerm(
    IN HWND hwndDlg )

    // Called on WM_DESTROY.  'HwndDlg' is that handle of the dialog window.
    //
{
    LEINFO* pInfo = (LEINFO* )GetWindowLongPtr( hwndDlg, DWLP_USER );

    TRACE( "LeTerm" );

    if (pInfo)
    {
        if (pInfo->hbmUp)
        {
            DeleteObject( pInfo->hbmUp );
        }

        if (pInfo->hbmDown)
        {
            DeleteObject( pInfo->hbmDown );
        }

        DtlDestroyList( pInfo->pListDeletes, NULL );
        Free( pInfo );
    }
}


VOID
LeUp(
    IN LEINFO* pInfo )

    // Up button click handler.  'PInfo' is the dialog context.
    //
{
    TCHAR* psz;
    INT i;
    LONG_PTR lId;

    ASSERT( !pInfo->fSorted );

    i = ListBox_GetCurSel( pInfo->hwndLb );
    psz = ListBox_GetPsz( pInfo->hwndLb, i );
    if (!psz)
    {
        LeExitNoMemory( pInfo );
        return;
    }

    ListBox_InsertString( pInfo->hwndLb, i - 1, psz );
    Free( psz );
    lId = ListBox_GetItemData( pInfo->hwndLb, i + 1 );
    ListBox_DeleteString( pInfo->hwndLb, i + 1 );
    ListBox_SetItemData( pInfo->hwndLb, i - 1, lId );
    ListBox_SetCurSel( pInfo->hwndLb, i - 1 );

    if (i == 1)
    {
        Button_MakeDefault( pInfo->hwndDlg, pInfo->hwndPbDown );
        SetFocus( pInfo->hwndPbDown );
    }

    LeEnableUpAndDownButtons( pInfo );
}


//-----------------------------------------------------------------------------
// String Editor dialog entry point
//-----------------------------------------------------------------------------

BOOL
StringEditorDlg(
    IN HWND hwndOwner,
    IN TCHAR* pszIn,
    IN DWORD dwSidTitle,
    IN DWORD dwSidLabel,
    IN DWORD cbMax,
    IN DWORD dwHelpId,
    IN OUT TCHAR** ppszOut )

    // Pops-up the String Editor dialog.  'PszIn' is the initial setting of
    // the edit box or NULL for blank.  'DwSidTitle' and 'dwSidLabel' are the
    // string resource IDs of the dialog title and edit box label.  'CbMax' is
    // the maximum length of the to allow or 0 for no limit.  'DwHelpId' is
    // the HID_* constant to associate with the label and edit field or -1 if
    // none.
    //
    // Returns true if user pressed OK and succeeded, false if he pressed
    // Cancel or encountered an error.  If true, '*ppszNumber' is a heap block
    // with the edited result.  It is caller's responsibility to Free the
    // returned block.
    //
{
    INT_PTR nStatus;
    ZEARGS args;

    TRACE( "StringEditorDlg" );

    args.pszIn = pszIn;
    args.dwSidTitle = dwSidTitle;
    args.dwSidLabel = dwSidLabel;
    args.cbMax = cbMax;
    args.dwHelpId = dwHelpId;
    args.ppszOut = ppszOut;

    nStatus =
        (BOOL )DialogBoxParam(
            g_hinstDll,
            MAKEINTRESOURCE( DID_ZE_StringEditor ),
            hwndOwner,
            ZeDlgProc,
            (LPARAM )&args );

    if (nStatus == -1)
    {
        ErrorDlg( hwndOwner, SID_OP_LoadDlg, ERROR_UNKNOWN, NULL );
        nStatus = FALSE;
    }

    return (nStatus) ? TRUE : FALSE;
}


//----------------------------------------------------------------------------
// String Editor dialog routines
// Listed alphabetically following dialog proc
//----------------------------------------------------------------------------

INT_PTR CALLBACK
ZeDlgProc(
    IN HWND   hwnd,
    IN UINT   unMsg,
    IN WPARAM wparam,
    IN LPARAM lparam )

    // DialogProc callback for the Edit Phone Number dialog.  Parameters and
    // return value are as described for standard windows 'DialogProc's.
    //
{
#if 0
    TRACE4( "ZeDlgProc(h=$%x,m=$%x,w=$%x,l=$%x)",
        (DWORD )hwnd, (DWORD )unMsg, (DWORD )wparam, (DWORD )lparam );
#endif

    switch (unMsg)
    {
        case WM_INITDIALOG:
        {
            return ZeInit( hwnd, (ZEARGS* )lparam );
        }

        case WM_HELP:
        case WM_CONTEXTMENU:
        {
            ZEINFO* pInfo;

            pInfo = (ZEINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            if (pInfo && pInfo->pArgs->dwHelpId != (DWORD )-1)
            {
                DWORD adwZeHelp[ (2 + 1) * 2 ];

                ZeroMemory( adwZeHelp, sizeof(adwZeHelp) );
                adwZeHelp[ 0 ] = CID_ZE_ST_String;
                adwZeHelp[ 2 ] = CID_ZE_EB_String;
                adwZeHelp[ 1 ] = adwZeHelp[ 3 ] = pInfo->pArgs->dwHelpId;

                ContextHelp( adwZeHelp, hwnd, unMsg, wparam, lparam );
                break;
            }
        }

        case WM_COMMAND:
        {
            ZEINFO* pInfo = (ZEINFO* )GetWindowLongPtr( hwnd, DWLP_USER );
            ASSERT( pInfo );

            return ZeCommand(
                pInfo, HIWORD( wparam ), LOWORD( wparam ), (HWND )lparam );
        }

        case WM_DESTROY:
        {
            ZeTerm( hwnd );
            break;
        }
    }

    return FALSE;
}


BOOL
ZeCommand(
    IN ZEINFO* pInfo,
    IN WORD wNotification,
    IN WORD wId,
    IN HWND hwndCtrl )

    // Called on WM_COMMAND.  'PInfo' is the dialog context.  'WNotification'
    // is the notification code of the command.  'wId' is the control/menu
    // identifier of the command.  'HwndCtrl' is the control window handle of
    // the command.
    //
    // Returns true if processed message, false otherwise.
    //
{
    TRACE3( "ZeCommand(n=%d,i=%d,c=$%x)",
        (DWORD )wNotification, (DWORD )wId, (ULONG_PTR )hwndCtrl );

    switch (wId)
    {
        case IDOK:
        {
            TRACE( "OK pressed" );
            *pInfo->pArgs->ppszOut = GetText( pInfo->hwndEb );
            EndDialog( pInfo->hwndDlg, (*pInfo->pArgs->ppszOut != NULL) );
            return TRUE;
        }

        case IDCANCEL:
        {
            TRACE( "Cancel pressed" );
            EndDialog( pInfo->hwndDlg, FALSE );
            return TRUE;
        }
    }

    return FALSE;
}


BOOL
ZeInit(
    IN HWND hwndDlg,
    IN ZEARGS* pArgs )

    // Called on WM_INITDIALOG.  'hwndDlg' is the handle of the owning window.
    // 'PArgs' is caller's arguments as passed to the stub API.
    //
    // Return false if focus was set, true otherwise, i.e. as defined for
    // WM_INITDIALOG.
    //
{
    DWORD dwErr;
    TCHAR* psz;
    ZEINFO* pInfo;

    TRACE( "ZeInit" );

    // Allocate the dialog context block.  Initialize minimally for proper
    // cleanup, then attach to the dialog window.
    //
    {
        pInfo = Malloc( sizeof(*pInfo) );
        if (!pInfo)
        {
            ErrorDlg( hwndDlg, SID_OP_LoadDlg, ERROR_NOT_ENOUGH_MEMORY, NULL );
            EndDialog( hwndDlg, FALSE );
            return TRUE;
        }

        ZeroMemory( pInfo, sizeof(*pInfo) );
        pInfo->pArgs = pArgs;
        pInfo->hwndDlg = hwndDlg;

        SetWindowLongPtr( hwndDlg, DWLP_USER, (ULONG_PTR )pInfo );
        TRACE( "Context set" );
    }

    pInfo->hwndEb = GetDlgItem( hwndDlg, CID_ZE_EB_String );
    ASSERT( pInfo->hwndEb );

    if (pArgs->cbMax > 0)
    {
        Edit_LimitText( pInfo->hwndEb, pArgs->cbMax );
    }

    psz = PszFromId( g_hinstDll, pArgs->dwSidTitle );
    if (psz)
    {
        SetWindowText( hwndDlg, psz );
        Free( psz );
    }

    psz = PszFromId( g_hinstDll, pArgs->dwSidLabel );
    if (psz)
    {
        HWND hwndSt = GetDlgItem( hwndDlg, CID_ZE_ST_String );
        ASSERT( hwndSt );
        SetWindowText( hwndSt, psz );
        Free( psz );
    }

    if (pArgs->pszIn)
    {
        SetWindowText( pInfo->hwndEb, pArgs->pszIn );
        Edit_SetSel( pInfo->hwndEb, 0, -1 );
    }

    // Center dialog on the owner window.
    //
    CenterWindow( hwndDlg, GetParent( hwndDlg ) );

    // Add context help button to title bar.  Dlgedit.exe doesn't currently
    // support this at resource edit time.  When that's fixed set
    // DS_CONTEXTHELP there and remove this call.
    //
    AddContextHelpButton( hwndDlg );

    return TRUE;
}


VOID
ZeTerm(
    IN HWND hwndDlg )

    // Called on WM_DESTROY.  'HwndDlg' is that handle of the dialog window.
    //
{
    ZEINFO* pInfo = (ZEINFO* )GetWindowLongPtr( hwndDlg, DWLP_USER );

    TRACE( "ZeTerm" );

    if (pInfo)
    {
        Free( pInfo );
    }
}