
/*****************************************************/
/* -- Demonstration of how to achieve MDI-like menu  */
/*    navigation using left and right arrows to      */
/*    include modeless dialog boxes.                 */
/*****************************************************/
#include <stdio.h>  /* for sprintf */
#include <windows.h>
#include "mdimenu.h"

/* Tell main window to drop next menu. */
#define wmNextMenu      WM_USER

/* Menu item "indices". */
#define imnuMDI         -2 /* MDI child system menu. */
#define imnuFrame       -1 /* Frame's system menu. */
#define imnuFirst       0  /* First frame menu item. */
#define imnuOther       1  /* None of the above. */

/* Frame window class name. */
#define szFrame         "frame"

/* MDI child window class name. */
#define szChild         "child"

/* Linked window list tags. */
#define szPropNext      "Next window"
#define szPropPrev      "Previous window"

/* Key constants for faking presses. */
#define lfSysUp     0xc0380001

HHOOK       hhook;
HINSTANCE   hins;           /* App. instance handle. */
HWND        hwndMain;       /* Main window. */
HWND        hwndMDI;        /* MDI client window. */
HWND        hwndMDIActive;  /* Active MDI window. */
HWND        hwndDlg;        /* Active dialog window. */
BOOL        fIgnoreMessage; /* Disable hook. */
HMENU       hmnuFirst;      /* First popup menu. */
int         iwndMdi;        /* MDI child number. */

/* Currently dropped menu. */
int         imnu    = imnuFrame;

HOOKPROC    lpfnMenuHook;   /* Hook proc. */
DLGPROC     lpfnModeless;   /* Dialog proc. */

LRESULT CALLBACK    FrameWndProc(HWND, UINT, WPARAM,
                      LPARAM);
LRESULT CALLBACK    MDIChildWndProc(HWND, UINT, WPARAM,
                      LPARAM);
BOOL                FInit(BOOL, int);
HWND                MakeNewChild(VOID);
LRESULT CALLBACK    MenuHookProc(int, WPARAM, LPARAM);
LRESULT CALLBACK    ModelessDlgProc(HWND, UINT, WPARAM,
                      LPARAM);
VOID                NextMenu(HWND, BOOL);

int PASCAL

WinMain(HINSTANCE hinsThis, HINSTANCE hinsPrev,
  LPSTR lpszCmdLine, int wShow)
/*****************************************************/
/* -- Entry point.                                   */
/*****************************************************/
    {
    MSG    msg;

    hins = hinsThis;

    if (!FInit(hinsPrev == NULL, wShow))
        return 0;

    /* Enter main message loop. */
    while (GetMessage(&msg, NULL, 0, 0))
        {
        if (TranslateMDISysAccel(hwndMDI, &msg))
            continue;

        /* Is this message for a dialog? */
        if (hwndDlg != NULL &&
          IsDialogMessage(hwndDlg, &msg))
            continue;

        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }

    if (lpfnModeless != NULL)
        FreeProcInstance((FARPROC)lpfnModeless);
    if (hhook != NULL)
        UnhookWindowsHook(WH_MSGFILTER, lpfnMenuHook);
    if (lpfnMenuHook != NULL)
        FreeProcInstance((FARPROC)lpfnMenuHook);

    return 0;
    }

BOOL
FInit(BOOL fFirst, int wShow)
/*****************************************************/
/* -- Register classes if first instance.            */
/*****************************************************/
    {
    if (fFirst)
        {
        WNDCLASS    wc;

        /* Register the frame class. */
        wc.style = 0;
        wc.lpfnWndProc = FrameWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hins;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground =
          (HBRUSH)(COLOR_APPWORKSPACE + 1);
        wc.lpszMenuName = "MdiMenu";
        wc.lpszClassName = szFrame;

        if (!RegisterClass(&wc))
            return FALSE;

        /* Register the MDI child class. */
        wc.lpfnWndProc = MDIChildWndProc;
        wc.hIcon = NULL;
        wc.lpszMenuName = NULL;
        wc.cbWndExtra = 0;
        wc.lpszClassName = szChild;
        if (!RegisterClass(&wc))
            return FALSE;
        }

    /* Create the frame.  The MDI Client window is */
    /* created in the frame's WM_CREATE case. */
    if ((hwndMain = CreateWindow(szFrame,
      "Custom MDI Menus",
      WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL,
      hins, NULL)) == NULL)
        return FALSE;

    if (hwndMDI == NULL)
        return FALSE;

    /* Display the frame window. */
    ShowWindow(hwndMain, wShow);
    UpdateWindow(hwndMain);

    /* Make the first MDI child window. */
    MakeNewChild();

    /* Get some procedure instance handles for */
    /* dialog and hook procs. */
    if ((lpfnModeless = (DLGPROC)MakeProcInstance(
      (FARPROC)ModelessDlgProc, hins)) == NULL)
        return FALSE;

    if ((lpfnMenuHook = (HOOKPROC)MakeProcInstance(
      (FARPROC)MenuHookProc, hins)) == NULL)
        return FALSE;

    /* Install the hook proc. */
    if ((hhook = SetWindowsHook(WH_MSGFILTER,
      lpfnMenuHook)) == NULL)
        return FALSE;

    return TRUE;
    }

LRESULT CALLBACK
FrameWndProc(HWND hwnd, UINT wm, WPARAM wParam,
  LPARAM lParam)
/*****************************************************/
/* -- Frame window proc.                             */
/*****************************************************/
    {
    switch (wm)
        {
    default:

        break;
        
    /* Next two cases track the last menuitem */
    /* activated. */
    case WM_MENUSELECT:
        if (LOWORD(lParam) & MF_SYSMENU)
            imnu = imnuFrame;
        else if (hmnuFirst == (HMENU)wParam)
            imnu = imnuFirst;
        else
            imnu = imnuOther;
        break;

    case WM_CREATE:
        {
        CLIENTCREATESTRUCT  ccs;
        HMENU               hmnuMain;

        /* Get first submenu handle. */
        hmnuMain = GetMenu(hwnd);
        hmnuFirst = GetSubMenu(hmnuMain, 0);

        /* Window menu to list children. */
        ccs.hWindowMenu =
          GetSubMenu(hmnuMain, cmnu - 1);
        ccs.idFirstChild = idmModeless + 1;

        /* Create the MDI client. */
        hwndMDI = CreateWindow("mdiclient", NULL,
          WS_CHILD | WS_CLIPCHILDREN, 0, 0, 0, 0, hwnd,
          0, hins, (LPSTR)&ccs);
        ShowWindow(hwndMDI, SW_SHOW);

        /* Initialize doubly linked window list. */
        SetProp(hwnd, szPropNext, (HANDLE)hwnd);
        SetProp(hwnd, szPropPrev, (HANDLE)hwnd);
        }
        return 0;

    case WM_COMMAND:
        switch (wParam)
                {
        default:
            break;

        case idmNew:
            /* Make an empty MDI child window. */
            MakeNewChild();
            return 0;

        case idmModeless:
            CreateDialog(hins, "MdiDialog", hwndMain,
              lpfnModeless);
            return 0;
            }
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        return 0;


    case WM_DESTROY:
        RemoveProp(hwnd, szPropNext);
        RemoveProp(hwnd, szPropPrev);
        PostQuitMessage(0);
        return 0;

    case wmNextMenu:
        /* This is where we do all the work. */
        NextMenu((HWND)wParam, (BOOL)lParam);
        return 0;
        }

    return
      DefFrameProc(hwnd, hwndMDI, wm, wParam, lParam);
    }

LRESULT CALLBACK
MDIChildWndProc(HWND hwnd, UINT wm,
  WPARAM wParam, LPARAM lParam)
/*****************************************************/
/* -- MDI child window proc.                         */
/*****************************************************/
    {
    switch (wm)
        {
    default:
        break;

    /* Keep track of active MDI window. */
    case WM_MDIACTIVATE:
        hwndMDIActive = wParam ? hwnd : NULL;
        break;

    /* Keep track of active menuitem. */
    case WM_MENUSELECT:
        imnu = imnuMDI;
        break;
        }

    return DefMDIChildProc(hwnd, wm, wParam, lParam);
    }

LRESULT CALLBACK
ModelessDlgProc(HWND hwnd, UINT wm, WPARAM wParam,
  LPARAM lParam)
/*****************************************************/
/* -- Modeless dialog proc.                          */
/*****************************************************/
    {
    switch (wm)
        {
    default:
        break;

    case WM_INITDIALOG:
        {
        HWND    hwndPrev;

        /* Insert into doubly linked list. */

        hwndPrev = (HWND)GetProp(hwndMain, szPropPrev);
        SetProp(hwnd, szPropNext, (HANDLE)hwndMain);
        SetProp(hwnd, szPropPrev, (HANDLE)hwndPrev);
        SetProp(hwndPrev, szPropNext, (HANDLE)hwnd);
        SetProp(hwndMain, szPropPrev, (HANDLE)hwnd);
        }
        break;

    case WM_ACTIVATE:
        hwndDlg = wParam ? hwnd : NULL;
        break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        return TRUE;

    case WM_DESTROY:
        {
        HWND    hwndNext, hwndPrev;

        /* Unlink. */
        hwndNext = (HWND)GetProp(hwnd, szPropNext);
        hwndPrev = (HWND)GetProp(hwnd, szPropPrev);
        SetProp(hwndPrev, szPropNext,
          (HANDLE)hwndNext);
        SetProp(hwndNext, szPropPrev,
          (HANDLE)hwndPrev);
        RemoveProp(hwnd, szPropNext);
        RemoveProp(hwnd, szPropPrev);
        }
        break;
        }

    return FALSE;
    }

HWND
MakeNewChild(VOID)
/*****************************************************/
/* -- Creates a new MDI child window.                */
/* -- Returns a handle to the new window.            */
/*****************************************************/
    {
    HWND            hwnd;
    MDICREATESTRUCT mcs;
    char            szBuf[50];

    sprintf(szBuf, "Untitled %d", ++iwndMdi);
    mcs.szTitle = szBuf;
    mcs.szClass = szChild;
    mcs.hOwner = hins;
    mcs.x = mcs.cx = CW_USEDEFAULT;
    mcs.y = mcs.cy = CW_USEDEFAULT;
    mcs.style = 0;

    /* Tell the MDI Client to create the child. */
    hwnd = (HWND)SendMessage(hwndMDI, WM_MDICREATE, 0,
        (LONG)(LPMDICREATESTRUCT)&mcs);
    ShowWindow(hwnd, SW_SHOW);
    return hwnd;

    }

LRESULT CALLBACK
MenuHookProc(int wCode, WPARAM wParam, LPARAM lParam)
/*****************************************************/
/* -- Examine the message to see if we need to move  */
/*    the menu to a new top-level window.            */
/*****************************************************/
    {
    HWND    hwnd, hwndFirst, hwndLast;
    LPMSG   lpmsg   = (LPMSG)lParam;
    BOOL    fRight;

    if (fIgnoreMessage)
        goto MenuHookProcExit;

    if (lpmsg->message != WM_KEYDOWN)
        goto MenuHookProcExit;

    if (!(fRight = lpmsg->wParam == VK_RIGHT) &&
      lpmsg->wParam != VK_LEFT)
        goto MenuHookProcExit;

    /* Any modeless dialogs present? */
    hwndFirst = (HWND)GetProp(hwndMain, szPropNext);
    hwndLast = (HWND)GetProp(hwndMain, szPropPrev);
    if (hwndFirst == hwndMain)
        goto MenuHookProcExit;  /* Nope. */

    if ((hwnd = GetActiveWindow()) == hwndMain)
        {
        switch (imnu)
            {
        default:
            goto MenuHookProcExit;

        case imnuFrame:
            if (!fRight)
                goto MenuHookProcExit;
            hwnd = hwndFirst;
            break;

        case imnuMDI:
            if (fRight)
                goto MenuHookProcExit;
            hwnd = hwndLast;
            break;

        case imnuFirst: /* First non-system menu. */
            if (fRight || hwndMDIActive != NULL)
                goto MenuHookProcExit;
            hwnd = hwndLast;
            break;
            }
        }
    else if ((HINSTANCE)GetWindowWord(hwnd,
      GWW_HINSTANCE) == hins)
        {
        hwnd = (HWND)GetProp(hwnd,
          fRight ? szPropNext : szPropPrev);

        }
    else
        {
        goto MenuHookProcExit;
        }

    PostMessage(lpmsg->hwnd, WM_KEYDOWN, VK_ESCAPE, 0L);
    PostMessage(hwndMain, wmNextMenu, (WPARAM)hwnd,
      fRight);
    lpmsg->wParam = VK_ESCAPE;

MenuHookProcExit:
    return CallNextHookEx(hhook, wCode, wParam,
      lParam);
    }

VOID
NextMenu(HWND hwnd, BOOL fRight)
/*****************************************************/
/* -- Drop the next menu.                            */
/* -- hwnd  : Drop menu for this window.             */
/* -- fRight: If user used right-arrow.              */
/*****************************************************/
    {
    BOOL    fIgnoreMessageSav;
    UINT    vk  = VK_DOWN;

    if (!IsWindow(hwnd))
        return;

    /* Prevent hook proc from going re-entrant. */
    fIgnoreMessageSav = fIgnoreMessage;
    fIgnoreMessage = TRUE;

    SetFocus(hwnd);

    /* Highlight first menu icon (enter menu mode). */
    PostMessage(hwnd, WM_SYSKEYDOWN, VK_MENU,
      0x20000001);
    PostMessage(hwnd, WM_SYSKEYUP, VK_MENU,
      0xc0000001);

    if (fRight)
        {
        /* User hit right arrow. */
        if (hwnd == hwndMain && hwndMDIActive != NULL)
            {
            if (IsZoomed(hwndMDIActive))
                {
                /* Default behavior is to highlight */
                /* the MDI's system menu icon that  */
                /* has been placed in the frame's   */
                /* menu bar, so we're done.         */
                goto NextMenuExit;
                }
            /* User has arrowed off last dialog. */
            /* There is an active MDI child, so */
            /* activate its system menu icon. */
            PostMessage(hwnd, WM_KEYDOWN, VK_LEFT,
              0x00000001);
            PostMessage(hwnd, WM_KEYUP, VK_LEFT,
              0xc0000001);
            }
        }
    else
        {
        /* User hit left arrow. */
        if (hwnd == hwndMain)

            {
            /* User has arrowed off last dialog. */
            /* Simulate a <space> to drop the system */
            /* menu. */
            vk = VK_SPACE;
            }
        }

NextMenuExit:
    PostMessage(hwnd, WM_KEYDOWN, vk, 0x00000001);
    PostMessage(hwnd, WM_KEYUP, vk, 0xc0000001);

    /* Allow the hook proc to work again. */
    fIgnoreMessage = fIgnoreMessageSav;
    }

/* End of File */ 

