
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: menu.c
*
* Contains the main menu switching functions and the clipboard functions.
*
* Functions:
*   DialogMenu()
*   LoadMenuBitmaps()
*   FreeMenuBitmaps()
*   InitMenu()
*   MsgFilterHookFunc()
*   ShowHelp()
*   CopyToClipboard()
*   DlgInClipboard()
*   PasteFromClipboard()
*   GetHelpContext()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include "dialogs.h"

#include <string.h>


#define MM10PERINCH         254         // Tenths of a millimeter per inch.

typedef struct {
    WORD idm;
    INT idbm;
    HBITMAP hbm;
} BITMAPMENU;


STATICFN VOID CopyToClipboard(VOID);
STATICFN BOOL DlgInClipboard(VOID);
STATICFN VOID PasteFromClipboard(VOID);
STATICFN INT GetHelpContext(INT idSubject, PHELPMAP phmap);


static BITMAPMENU bmpmenuTable[] = {
    {MENU_ALIGNLEFT,        IDBM_ALEFT,         NULL },
    {MENU_ALIGNVERT,        IDBM_AVERT,         NULL },
    {MENU_ALIGNRIGHT,       IDBM_ARIGHT,        NULL },
    {MENU_ALIGNTOP,         IDBM_ATOP,          NULL },
    {MENU_ALIGNHORZ,        IDBM_AHORZ,         NULL },
    {MENU_ALIGNBOTTOM,      IDBM_ABOTTOM,       NULL },
    {MENU_SPACEVERT,        IDBM_ASPCVERT,      NULL },
    {MENU_SPACEHORZ,        IDBM_ASPCHORZ,      NULL },
    {MENU_ARRSIZEWIDTH,     IDBM_ASZWIDTH,      NULL },
    {MENU_ARRSIZEHEIGHT,    IDBM_ASZHGHT,       NULL },
    {MENU_ARRPUSHBOTTOM,    IDBM_APBBOTTM,      NULL },
    {MENU_ARRPUSHRIGHT,     IDBM_APBRIGHT,      NULL }
};



/************************************************************************
* DialogMenu
*
* This is the main switching function to send menu commands to the
* appropriate function.
*
* Arguments:
*   INT - the menu command
*
************************************************************************/

VOID DialogMenu(
    INT cmd)
{
    /*
     * Be sure any outstanding changes get applied without errors.
     */
    if (!StatusApplyChanges())
        return;

    switch (cmd) {

        /*
         * File menu ----------------------------------------------------
         */

        case MENU_NEWRES:
            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            FreeInclude();
            FreeRes();
            AddNewDialog();
            ShowFileStatus(TRUE);
            break;

        case MENU_OPEN:
            if (DoWeSave(FILE_INCLUDE) == IDCANCEL ||
                    DoWeSave(FILE_RESOURCE) == IDCANCEL)
                break;

            Open(FILE_RESOURCE);

            break;

        case MENU_SAVE:
            if (gfIncChged) {
                if (!Save(FILE_NOSHOW | FILE_INCLUDE))
                    break;
            }

            if (gfResChged)
                Save(FILE_NOSHOW | FILE_RESOURCE);

            break;

        case MENU_SAVEAS:
            /*
             * Save the include file, but only if there is one.
             */
            if (pszIncludeFile || plInclude) {
                if (!Save(FILE_NOSHOW | FILE_INCLUDE | FILE_SAVEAS))
                    break;
            }

            /*
             * Save the resource file.
             */
            Save(FILE_NOSHOW | FILE_RESOURCE | FILE_SAVEAS);

            break;

        case MENU_NEWCUST:
            DlgBox(DID_NEWCUST, (WNDPROC)NewCustDlgProc);
            break;

        case MENU_OPENCUST:
            OpenCustomDialog();
            break;

        case MENU_REMCUST:
            DlgBox(DID_REMCUST, (WNDPROC)RemCustDlgProc);
            break;

        case MENU_SETINCLUDE:
            if (DoWeSave(FILE_INCLUDE) != IDCANCEL)
                Open(FILE_INCLUDE);

            break;

        case MENU_EXIT:
            PostMessage(ghwndMain, WM_CLOSE, 0, 0L);
            break;

        /*
         * Edit menu ----------------------------------------------------
         */

        case MENU_RESTOREDIALOG:
            RestoreDialog();
            break;

        case MENU_CUT:
        case MENU_COPY:
            if (gfEditingDlg) {
                /*
                 * Save current stuff in clipboard.
                 */
                CopyToClipboard();

                /*
                 * Clear the selection if they did a "cut" instead of
                 * a "copy".
                 */
                if (cmd == MENU_CUT)
                    DeleteControl();
            }

            break;

        case MENU_PASTE:
            PasteFromClipboard();
            break;

        case MENU_DELETE:
            DeleteControl();
            break;

        case MENU_DUPLICATE:
            Duplicate();
            break;

        case MENU_SYMBOLS:
            ViewInclude();
            break;

        case MENU_STYLES:
            StylesDialog();
            break;

        case MENU_SIZETOTEXT:
            SizeToText();
            break;

        case MENU_NEWDIALOG:
            AddNewDialog();
            break;

        case MENU_SELECTDIALOG:
            SelectDialogDialog();
            break;

        /*
         * Arrange menu -------------------------------------------------
         */

        case MENU_ALIGNLEFT:
        case MENU_ALIGNVERT:
        case MENU_ALIGNRIGHT:
        case MENU_ALIGNTOP:
        case MENU_ALIGNHORZ:
        case MENU_ALIGNBOTTOM:
            AlignControls(cmd);
            break;

        case MENU_SPACEVERT:
        case MENU_SPACEHORZ:
            ArrangeSpacing(cmd);
            break;

        case MENU_ARRSIZEWIDTH:
        case MENU_ARRSIZEHEIGHT:
            ArrangeSize(cmd);
            break;

        case MENU_ARRPUSHBOTTOM:
        case MENU_ARRPUSHRIGHT:
            ArrangePushButtons(cmd);
            break;

        case MENU_ORDERGROUP:
            OrderGroupDialog();
            break;

        case MENU_ARRSETTINGS:
            ArrangeSettingsDialog();
            break;

        /*
         * Options menu -------------------------------------------------
         */

        case MENU_TESTMODE:
            if (gfTestMode)
                DestroyTestDialog();
            else
                CreateTestDialog();

            break;

        case MENU_HEXMODE:
            /*
             * Flip the flag, and update the status display so that
             * the id value will be displayed in the new format.
             */
            gfHexMode = gfHexMode ? FALSE : TRUE;
            StatusUpdate();
            break;

        case MENU_TRANSLATE:
            /*
             * Flip the flag, and set the enable state of the fields
             * in the status window.  Changing the translate mode can
             * effect whether they are allowed to change ids of controls.
             */
            gfTranslateMode = gfTranslateMode ? FALSE : TRUE;

            /*
             * If they turned on Translate mode, reset the tool and
             * hide the Toolbox.  Otherwise, show the toolbox if they
             * want it shown.
             */
            if (gfTranslateMode) {
                ToolboxShow(FALSE);
                ToolboxSelectTool(W_NOTHING, FALSE);
            }
            else if (gfShowToolbox) {
                ToolboxShow(TRUE);
            }

            StatusSetEnable();
            break;

        case MENU_USENEWKEYWORDS:
            /*
             * Flip the flag.
             */
            gfUseNewKeywords = gfUseNewKeywords ? FALSE : TRUE;
            break;

        case MENU_SHOWTOOLBOX:
            /*
             * Toggle the state of the Toolbox.
             */
            gfShowToolbox = gfShowToolbox ? FALSE : TRUE;
            ToolboxShow(gfShowToolbox);

            break;

        /*
         * Help menu ----------------------------------------------------
         */

        case MENU_CONTENTS:
            WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
            break;

        case MENU_SEARCH:
            /*
             * Tell winhelp to be sure this app's help file is current,
             * then invoke a search with an empty starting key.
             */
            WinHelp(ghwndMain, gszHelpFile, HELP_FORCEFILE, 0);
            WinHelp(ghwndMain, gszHelpFile, HELP_PARTIALKEY, (DWORD)szEmpty);
            break;

        case MENU_ABOUT:
            DlgBox(DID_ABOUT, (WNDPROC)AboutDlgProc);
            break;

        /*
         * Hidden menu commands (accessed by accelerators) --------------
         */

        case MENU_HIDDEN_TOTOOLBOX:
            if (ghwndToolbox && IsWindowVisible(ghwndToolbox))
                SetFocus(ghwndToolbox);

            break;

        case MENU_HIDDEN_TOPROPBAR:
            SetFocus(hwndStatus);
            break;
    }
}



/************************************************************************
* LoadMenuBitmaps
*
* This function loads and inserts the menu items that are bitmaps.
* This has to be done at runtime because windows does not have a
* way to specify bitmap menu items in the rc file.
*
* Arguments:
*   HMENU hMenu - The menu handle.
*
************************************************************************/

VOID LoadMenuBitmaps(
    HMENU hMenu)
{
    INT i;

    for (i = 0; i < sizeof(bmpmenuTable) / sizeof(BITMAPMENU); i++) {
        bmpmenuTable[i].hbm =
                LoadBitmap(ghInst, MAKEINTRESOURCE(bmpmenuTable[i].idbm));

        ModifyMenu(hMenu, bmpmenuTable[i].idm,
                MF_BYCOMMAND | MF_BITMAP, bmpmenuTable[i].idm,
                (LPTSTR)(DWORD)bmpmenuTable[i].hbm);
    }
}



/************************************************************************
* FreeMenuBitmaps
*
* This function frees the menu bitmaps that were loaded by
* LoadMenuBitmaps.  This function should be called only when
* the application is exiting, because it frees the bitmaps
* without removing them from the menu first.
*
************************************************************************/

VOID FreeMenuBitmaps(VOID)
{
    INT i;

    for (i = 0; i < sizeof(bmpmenuTable) / sizeof(BITMAPMENU); i++)
        if (bmpmenuTable[i].hbm)
            DeleteObject(bmpmenuTable[i].hbm);
}



/************************************************************************
* InitMenu
*
* This function grays/enables and checks/unchecks the menu items
* appropriately for the given state.
*
* Arguments:
*   HMENU hMenu - The menu handle.
*
************************************************************************/

VOID InitMenu(
    HMENU hMenu)
{
    register INT i;
    BOOL fEnable;
    NPCTYPE npc;
    HMENU hMenuArrange;

    MyEnableMenuItem(hMenu, MENU_NEWRES, !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SAVE,
            (gfEditingDlg || gprlHead) && (gfResChged || gfIncChged));

    MyEnableMenuItem(hMenu, MENU_SAVEAS, gfEditingDlg || gprlHead);

    MyEnableMenuItem(hMenu, MENU_SETINCLUDE,
            (gfEditingDlg || gprlHead) && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_REMCUST, gpclHead);

    MyEnableMenuItem(hMenu, MENU_RESTOREDIALOG, gfDlgChanged && gcd.prl);

    MyEnableMenuItem(hMenu, MENU_CUT,
            gnpcSel && gfEditingDlg && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_COPY, gnpcSel && gfEditingDlg);

    MyEnableMenuItem(hMenu, MENU_PASTE, !gfTranslateMode &&
            IsClipboardFormatAvailable(fmtDlg) &&
            (gfEditingDlg || DlgInClipboard()));

    MyEnableMenuItem(hMenu, MENU_DELETE, gnpcSel && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_DUPLICATE, gnpcSel && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SYMBOLS,
            (gfEditingDlg || gprlHead) && !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_STYLES, gnpcSel && !gfTranslateMode);

    /*
     * For the "Size to text" menu command to be enabled, there
     * must be at least one control selected, and one of the
     * controls selected has to be able to be sized to its text.
     */
    fEnable = FALSE;
    if (gcSelected) {
        for (npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected && npc->pwcd->fSizeToText) {
                fEnable = TRUE;
                break;
            }
        }
    }

    MyEnableMenuItem(hMenu, MENU_SIZETOTEXT, fEnable);

    MyEnableMenuItem(hMenu, MENU_NEWDIALOG, !gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_SELECTDIALOG, gfEditingDlg || gprlHead);

    hMenuArrange = GetSubMenu(hMenu, MENUPOS_ARRANGE);

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGEALIGN, gcSelected > 1);

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGESPACE, gcSelected > 1);

    /*
     * For the "Same size" menu option to be enabled, there
     * must be more than one control selected, and they
     * must be sizeable controls.
     */
    fEnable = FALSE;
    if (gcSelected > 1) {
        for (i = 0, npc = npcHead; npc; npc = npc->npcNext) {
            if (npc->fSelected && npc->pwcd->fSizeable) {
                i++;

                if (i > 1) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
    }

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGESIZE, fEnable);

    /*
     * For the Arrange/Push buttons menu item to be enabled,
     * there must be a dialog up and it must have at least one
     * push button.  In addition, if there are control(s) other
     * than the dialog selected, at least one of the selected
     * controls must be a push button.
     */
    fEnable = FALSE;
    if (gfEditingDlg || gprlHead) {
        if (!gcSelected || gfDlgSelected) {
            for (npc = npcHead; npc; npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
        else {
            for (npc = npcHead; npc; npc = npc->npcNext) {
                if (npc->pwcd->iType == W_PUSHBUTTON && npc->fSelected) {
                    fEnable = TRUE;
                    break;
                }
            }
        }
    }

    MyEnableMenuItemByPos(hMenuArrange, MENUPOS_ARRANGEPUSH, fEnable);

    MyEnableMenuItem(hMenu, MENU_ORDERGROUP,
            npcHead && !gfTranslateMode && !gfTestMode && cWindows > 1);

    MyEnableMenuItem(hMenu, MENU_TESTMODE, gfEditingDlg);
    MyCheckMenuItem(hMenu, MENU_TESTMODE, gfTestMode);

    MyEnableMenuItem(hMenu, MENU_HEXMODE, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_HEXMODE, gfHexMode);

    MyEnableMenuItem(hMenu, MENU_TRANSLATE, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_TRANSLATE, gfTranslateMode);

    MyEnableMenuItem(hMenu, MENU_USENEWKEYWORDS, !gfTestMode);
    MyCheckMenuItem(hMenu, MENU_USENEWKEYWORDS, gfUseNewKeywords);

    MyEnableMenuItem(hMenu, MENU_SHOWTOOLBOX, !gfTestMode && !gfTranslateMode);
    MyCheckMenuItem(hMenu, MENU_SHOWTOOLBOX, gfShowToolbox);
}



/************************************************************************
* CopyToClipboard
*
* Puts the current dialog and a bitmap image of it into the clipboard.
* Gives a dialog box resource to the Clipboard.
* Gives a bit map to the clipboard.
* Clears everything else out of clipboard.
*
************************************************************************/

STATICFN VOID CopyToClipboard(VOID)
{
    INT cbRes;
    HANDLE hResClip;
    PRES lpRes;
    PRES pRes;
    HDC hdcSrc;
    HDC hdcDst;
    RECT rc;
    HBITMAP hbm;

    /*
     * Store the current selection in a dialog resource.
     */
    if (!(pRes = AllocDialogResource(FALSE, TRUE)))
        return;

    /*
     * Allocate global memory for it.
     */
    cbRes = ResourceSize(pRes);
    if (!cbRes || !(hResClip = GlobalAlloc(GHND | GMEM_DDESHARE, cbRes))) {
        MyFree(pRes);
        Message(MSG_OUTOFMEMORY);
        return;
    }

    /*
     * Copy it to the global memory.
     */
    lpRes = (PRES)GlobalLock(hResClip);
    memcpy(lpRes, pRes, cbRes);
    GlobalUnlock(hResClip);
    MyFree(pRes);

    /*
     * Now place it in the clipboard.
     */
    if (OpenClipboard(ghwndMain)) {
        EmptyClipboard();
        SetClipboardData(fmtDlg, hResClip);

        /*
         * If the dialog is selected, place a bitmap image of it
         * in the clipboard also.  The drag handles will be removed
         * first and the dialog will be activated so that the
         * image looks proper.
         */
        if (gfDlgSelected) {
            CancelSelection(FALSE);
            SetActiveWindow(gcd.npc->hwnd);
            UpdateWindow(gcd.npc->hwnd);

            if (hdcSrc = GetDC(NULL)) {
                GetWindowRect(gcd.npc->hwnd, &rc);
                if (hbm = CreateCompatibleBitmap(hdcSrc,
                        rc.right - rc.left, rc.bottom - rc.top)) {
                    if (hdcDst = CreateCompatibleDC(hdcSrc)) {
                        /*
                         * Calculate the dimensions of the bitmap and
                         * convert them to tenths of a millimeter for
                         * setting the size with the SetBitmapDimensionEx
                         * call.  This allows programs like WinWord to
                         * retrieve the bitmap and know what size to
                         * display it as.
                         */
                        SetBitmapDimensionEx(hbm,
                                ((rc.right - rc.left) * MM10PERINCH) /
                                GetDeviceCaps(hdcSrc, LOGPIXELSX),
                                ((rc.bottom - rc.top) * MM10PERINCH) /
                                GetDeviceCaps(hdcSrc, LOGPIXELSY),
                                NULL);

                        SelectObject(hdcDst, hbm);
                        BitBlt(hdcDst, 0, 0,
                                rc.right - rc.left, rc.bottom - rc.top,
                                hdcSrc, rc.left, rc.top, SRCCOPY);
                        DeleteDC(hdcDst);
                        SetClipboardData(CF_BITMAP, hbm);
                    }
                    else {
                        DeleteObject(hbm);
                    }
                }

                ReleaseDC(NULL, hdcSrc);
            }

            SetActiveWindow(ghwndMain);
            SelectControl(gcd.npc, FALSE);
        }

        CloseClipboard();
    }
    else {
        Message(MSG_NOCLIP);
    }
}



/************************************************************************
* DlgInClipboard
*
* This function returns TRUE if there is data in the clipboard in the
* dialog format, and this data is for a complete dialog, not just for
* a group of controls.
*
************************************************************************/

STATICFN BOOL DlgInClipboard(VOID)
{
    HANDLE hClip;
    PRES pRes;
    PDIALOGBOXHEADER pdbh;
    BOOL fDlgResFound = FALSE;

    if (!OpenClipboard(ghwndMain))
        return FALSE;

    if (hClip = GetClipboardData(fmtDlg)) {
        pRes = (PRES)GlobalLock(hClip);

        /*
         * If cx is CONTROLS_ONLY, then we know that we only
         * want to copy the controls in the template, not the
         * entire dialog plus controls.
         */
        pdbh = (PDIALOGBOXHEADER)SkipResHeader(pRes);
        if (pdbh->cx != CONTROLS_ONLY)
            fDlgResFound = TRUE;

        GlobalUnlock(hClip);
    }

    CloseClipboard();

    return fDlgResFound;
}



/************************************************************************
* PasteFromClipboard
*
* This routine pastes any data from the clipboard into the current
* resource file.  If the data represents a complete dialog, a new dialog
* is added.  If it represents some controls, the operation to drop them
* into the current dialog is begun.
*
************************************************************************/

STATICFN VOID PasteFromClipboard(VOID)
{
    HANDLE hClip;
    PRES pResClip;
    PRES pResCopy;
    INT cbRes;

    if (!OpenClipboard(ghwndMain)) {
        Message(MSG_NOCLIP);
        return;
    }

    if (hClip = GetClipboardData(fmtDlg)) {
        pResClip = (PRES)GlobalLock(hClip);
        cbRes = ResourceSize(pResClip);

        /*
         * Make a copy of the clipboard data.  This needs to be done
         * because we may need to drag the new controls for a while,
         * and it is rude to leave the clipboard open that long.
         */
        if (pResCopy = (PRES)MyAlloc(cbRes)) {
            memcpy((PBYTE)pResCopy, (PBYTE)pResClip, cbRes);

            /*
             * Now duplicate the dialog or controls in the clipboard.
             * The pResCopy buffer does NOT need to be freed here, because
             * it will be freed after the drag operation is complete.
             */
            MakeCopyFromRes(pResCopy);
        }

        GlobalUnlock(hClip);
    }

    CloseClipboard();
}



/************************************************************************
* MsgFilterHookFunc
*
* This is the exported message filter function that is hooked into
* the message stream for detecting the pressing of the F1 key, at
* which time it calls up the appropriate help.
*
************************************************************************/

BOOL APIENTRY MsgFilterHookFunc(
    INT nCode,
    WPARAM wParam,
    LPMSG lpMsg)
{
    if ((nCode == MSGF_MENU || nCode == MSGF_DIALOGBOX) &&
            (lpMsg->message == WM_KEYDOWN && lpMsg->wParam == VK_F1)) {
        /*
         * Display help.
         */
        ShowHelp((nCode == MSGF_MENU) ? TRUE : FALSE);

        /*
         * Tell Windows to swallow this message.
         */
        return 1;
    }

    return CallNextHookEx(ghhkMsgFilter, nCode, wParam, (LONG)lpMsg);
}



/************************************************************************
* ShowHelp
*
* This function is called when the user has requested help.  It will
* look at the menu state (if fMenuHelp is TRUE) or which dialog
* is currently up to determine the help topic, then it calls WinHelp.
*
* Arguments:
*   BOOL fMenuHelp - TRUE if this help is for a menu (help was requested
*                    in the menu modal loop).  If FALSE, general help
*                    or help for a dialog is assumed.
*
************************************************************************/

VOID ShowHelp(
    BOOL fMenuHelp)
{
    INT nHelpContext = 0;
    HWND hwndFocus;

    if (fMenuHelp) {
        nHelpContext = GetHelpContext(gMenuSelected, gahmapMenu);
    }
    else {
        /*
         * Look for help for the current dialog.
         */
        if (gidCurrentDlg) {
            nHelpContext = GetHelpContext(gidCurrentDlg, gahmapDialog);
        }
        else {
            /*
             * There is no current dialog.  Is the window with the
             * focus a control on the Properties Bar?
             */
            if ((hwndFocus = GetFocus()) && IsChild(hwndStatus, hwndFocus))
                nHelpContext = GetHelpContext(DID_STATUS, gahmapDialog);
        }
    }

    /*
     * If there is help context, display it.  Otherwise display
     * the Contents screen.
     */
    if (nHelpContext)
        WinHelp(ghwndMain, gszHelpFile, HELP_CONTEXT, nHelpContext);
    else
        WinHelp(ghwndMain, gszHelpFile, HELP_CONTENTS, 0L);
}



/************************************************************************
* GetHelpContext
*
* This function takes a subject and returns its matching help
* context id from the given HELPMAP table.
*
* Arguments:
*   INT idSubject   - ID of the subject to find the help context for.
*   PHELPMAP phmap  - The help map table.  It is assumed that the
*                     last entry in the table has a NULL subject id.
*
************************************************************************/

STATICFN INT GetHelpContext(
    INT idSubject,
    PHELPMAP phmap)
{
    while (phmap->idSubject) {
        if (phmap->idSubject == idSubject)
            return phmap->HelpContext;

        phmap++;
    }

    return 0;
}
