MAINWND.C

/***************************************************************************** 
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
* A PARTICULAR PURPOSE.
*
* Copyright 1993 - 1998 Microsoft Corporation. All Rights Reserved.
*
******************************************************************************
*
* MainWnd.C
*
* Message handlers and UI for the main window and associated controls
*
*****************************************************************************/

#pragma warning(disable:4756)

#define _INC_SHELLAPI
#include <windows.h>
#undef _INC_SHELLAPI

#include <shellapi.h>
#include <windowsx.h>
#include <mmsystem.h>
#include <commdlg.h>
#include <commctrl.h>
#include <ctype.h>

#include "debug.h"

#include "MIDIPlyr.H"

#define BITMAP_COUNT 6 /* Number of buttons in bitmap */

PRIVATE HWND ghWndToolbar = NULL;
PRIVATE HWND ghWndStatus = NULL;
PRIVATE HWND ghWndTime = NULL;
PRIVATE char gszAppTitle[80] = "";
PRIVATE int gnSB_TFPaneSize = 0;
PRIVATE char gszOpenName[MAX_FILEPATH] = "";
PRIVATE char gszOpenTitle[MAX_FILEPATH] = "";
PRIVATE char BCODE gszFilter[] =
"MIDI File (*.MID;*.RMI)\0*.MID;*.RMI\0"
"All Files (*.*)\0*.*\0";

PRIVATE char BCODE gszDefExtension[] = "MID";
PRIVATE BOOL gbAutoPlay = TRUE;
PRIVATE UINT guDevice = 0;

PRIVATE TBBUTTON gatbButton[] =
{
{0, -1, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, -1},
{0, IDM_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0, 0, 0, -1},
{0, -1, TBSTATE_ENABLED, TBSTYLE_SEP, 0, 0, 0, -1},
{2, IDM_PLAY, 0, TBSTYLE_BUTTON, 0, 0, 0, -1},
{3, IDM_STOP, 0, TBSTYLE_BUTTON, 0, 0, 0, -1},
{4, IDM_PAUSE, 0, TBSTYLE_BUTTON, 0, 0, 0, -1},
};

#define BUTTON_COUNT (sizeof(gatbButton)/sizeof(gatbButton[0]))

PRIVATE VOID FNLOCAL InitToolbar(HWND hWnd);
PRIVATE VOID FNLOCAL InitStatusBar(HWND hWnd);
PRIVATE VOID FNLOCAL ResizeStatusBar(HWND hWnd);
PRIVATE VOID FNLOCAL SyncUI(HWND hWnd);
PRIVATE VOID FNLOCAL SetOneAction(HWND hWnd, int nMenuID, BOOL fEnable);
PRIVATE VOID FNLOCAL AttemptFileOpen(HWND hWnd);
PRIVATE BOOL FNLOCAL PrerollAndWait(HWND hWnd);

PRIVATE BOOL FNLOCAL MWnd_OnCreate(HWND hWnd, CREATESTRUCT FAR* lpCreateStruct);
PRIVATE VOID FNLOCAL MWnd_OnGetMinMaxInfo(HWND hWnd, MINMAXINFO FAR* lpMinMaxInfo);
PRIVATE VOID FNLOCAL MWnd_OnSize(HWND hWnd, UINT state, int cx, int cy);
PRIVATE VOID FNLOCAL MWnd_OnPaint(HWND hWnd);
PRIVATE VOID FNLOCAL MWnd_OnDropFiles(HWND hWnd, HDROP hDrop);
PRIVATE VOID FNLOCAL MWnd_OnFileOpen(HWND hWnd);
PRIVATE VOID FNLOCAL MWnd_OnCommandToggleChild(HWND hWnd, UINT id);
PRIVATE VOID FNLOCAL MWnd_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify);
PRIVATE VOID FNLOCAL MWnd_OnDestroy(HWND hWnd);

/*****************************************************************************
*
* InitToolbar
*
* Called to create the toolbar
*
* HWND hWnd - Application window which toolbar is a child of
*
* Create and show the toolbar window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL InitToolbar(
HWND hWnd)
{
ghWndToolbar = CreateToolbarEx(hWnd,
WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN,
IDC_TOOLBAR,
BITMAP_COUNT,
ghInst,
IDB_TOOLBAR,
gatbButton,
BUTTON_COUNT,
0, 0,
0, 0,
sizeof(TBBUTTON));

if (ghWndToolbar)
ShowWindow(ghWndToolbar, SW_RESTORE);
}

/*****************************************************************************
*
* InitStatusBar
*
* Called to create the status bar
*
* HWND hWnd - Application window which status bar is a child of
*
* Create and show the status window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL InitStatusBar(
HWND hWnd)
{
HWND hWndDesktop;
HFONT hFontStat;
HDC hDC;
UINT idx;
SIZE size;

ghWndStatus = CreateStatusWindow(WS_CHILD|SBARS_SIZEGRIP,
"",
hWnd,
IDC_STATBAR);

if (ghWndStatus)
{
hWndDesktop = GetDesktopWindow();
hFontStat = (HFONT)SendMessage(ghWndStatus, WM_GETFONT, 0, 0L);
hDC = GetDC(hWndDesktop);

if (hFontStat != (HFONT)NULL && hDC != (HDC)NULL)
{
hFontStat = (HFONT)SelectObject(hDC, hFontStat);

gnSB_TFPaneSize = 0;
for (idx = 0; idx < N_TIME_FORMATS; idx++)
{
GetTextExtentPoint(hDC,
grgszTimeFormats[idx],
lstrlen(grgszTimeFormats[idx]),
&size);

gnSB_TFPaneSize = max(gnSB_TFPaneSize, size.cx);
}

SelectObject(hDC, hFontStat);

gnSB_TFPaneSize *= 2;
}

if (hDC != (HDC)NULL)
ReleaseDC(hWndDesktop, hDC);

ResizeStatusBar(hWnd);

FORWARD_WM_COMMAND(hWnd, gnTimeFormat, 0, 0, SendMessage);
ShowWindow(ghWndStatus, SW_RESTORE);
}
}

/*****************************************************************************
*
* ResizeStatusBar
*
* Force the status bar to resize to fit in the main window
*
* HWND hWnd - Application window which status bar is a child of
*
* Figure out the pane sizes and send a message to the status bar to set them.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL ResizeStatusBar(
HWND hWnd)
{
RECT rc;
int rnPaneEdge[SB_N_PANES];

GetClientRect(hWnd, &rc);

/* SB_SETPARTS expects:
** wParam == Number of panes in status bar.
** lParam == Pointer to an array of int's indicating the right-hand
** coordinate of each pane.
*/
rnPaneEdge[SB_PANE_STATE] = rc.right - gnSB_TFPaneSize;
rnPaneEdge[SB_PANE_TFMT] = -1;

SendMessage(ghWndStatus,
SB_SETPARTS,
SB_N_PANES,
(DWORD)(LPINT)(rnPaneEdge));
}

/*****************************************************************************
*
* SyncUI
*
* Bring all UI elements into sync with the state of the sequencer
*
* HWND hWnd - Application main window
*
* Build a flag word of the actions which are allowed from the current state.
* Set the menu items and toolbar buttons for each action appropriately.
* Show the current state as a string in the status bar.
* Depress the pause button if the sequencer is paused.
* Cause the time window to update.
*
*****************************************************************************/
#define SUI_F_CANPLAY 0x0001
#define SUI_F_CANPAUSE 0x0002
#define SUI_F_CANSTOP 0x0004
#define SUI_F_CANSELDEVICE 0x0008

PRIVATE VOID FNLOCAL SyncUI(
HWND hWnd)
{
WORD wActionFlags;
UINT uState;
char szState[40];
BOOL fPress;

wActionFlags = 0;
uState = SEQ_S_NOFILE;

if (gpSeq != NULL)
{
uState = gpSeq->uState;
switch (uState)
{
case SEQ_S_NOFILE:
wActionFlags = SUI_F_CANSELDEVICE;
break;

case SEQ_S_OPENED:
case SEQ_S_PREROLLED:
wActionFlags = SUI_F_CANPLAY|SUI_F_CANSELDEVICE;
break;

case SEQ_S_PAUSED:
case SEQ_S_PLAYING:
wActionFlags = SUI_F_CANPAUSE|SUI_F_CANSTOP;
break;


case SEQ_S_PREROLLING:
case SEQ_S_STOPPING:
// assert(0);
wActionFlags = 0;
break;
}
}

fPress = (gpSeq->uState == SEQ_S_PAUSED);
SendMessage(ghWndToolbar,
TB_PRESSBUTTON,
IDM_PAUSE,
fPress);

SetOneAction(hWnd, IDM_PLAY, wActionFlags & SUI_F_CANPLAY);
SetOneAction(hWnd, IDM_PAUSE, wActionFlags & SUI_F_CANPAUSE);
SetOneAction(hWnd, IDM_STOP, wActionFlags & SUI_F_CANSTOP);

EnableMenuItem(GetMenu(hWnd),
POS_PLAYTHRU,
MF_BYPOSITION|((wActionFlags & SUI_F_CANSELDEVICE) ? MF_ENABLED : MF_DISABLED));

DrawMenuBar(hWnd);

szState[0] = '\0';
LoadString(ghInst, IDS_STATES + uState, szState, sizeof(szState));
SendMessage(ghWndStatus, SB_SETTEXT, SB_PANE_STATE, (LPARAM)(LPSTR)szState);

InvalidateRect(ghWndTime, NULL, TRUE);
}

/*****************************************************************************
*
* SetOneAction
*
* Update the state of one action in both the toolbar and action menu
*
* HWND hWnd - Application main window
* int nMenuID - Menu ID of action
* BOOL fEnable - Enable or disable this action
*
*****************************************************************************/
PRIVATE VOID FNLOCAL SetOneAction(
HWND hWnd,
int nMenuID,
BOOL fEnable)
{
EnableMenuItem(GetMenu(hWnd),
nMenuID,
MF_BYCOMMAND|(fEnable ? MF_ENABLED : MF_DISABLED));

SendMessage(ghWndToolbar,
TB_ENABLEBUTTON,
nMenuID,
(DWORD)fEnable);
}

/*****************************************************************************
*
* AttemptFileOpen
*
* Try to open the given file in the sequencer.
*
* HWND hWnd - Application main window
*
* Stop and close the current file.
* Open the new file.
* Preroll the new file.
* Set the title test for the main window.
* Call SyncUI to update available actions.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL AttemptFileOpen(
HWND hWnd)
{
MMRESULT mmrc;
PSTR pStrFile = gszUntitled;

/* Stop, close, etc. if we're still playing
*/
DPF(1, "AttemptFileOpen: Calling seqStop(); state = %u", gpSeq->uState);

mmrc = seqStop(gpSeq);
if (mmrc != MMSYSERR_NOERROR)
{
Error(hWnd, IDS_STOPFAILED, mmrc);
return;
}

DPF(1, "Calling seqCloseFile(); state = %u", gpSeq->uState);
seqCloseFile(gpSeq);

DPF(1, "Calling seqOpenFile(); state = %u", gpSeq->uState);
/* Open new file
*/

gpSeq->pstrFile = gszOpenName;
mmrc = seqOpenFile(gpSeq);
if (mmrc != MMSYSERR_NOERROR)
{
Error(hWnd, IDS_OPENFAILED, mmrc);
return;
}

pStrFile = gszOpenTitle;

wsprintf(gszAppTitle, gszAppTitleMask, (LPSTR)pStrFile);
SetWindowText(hWnd, gszAppTitle);

SyncUI(hWnd);
}

/*****************************************************************************
*
* PrerollAndWait
*
* Prerolls the sequencer using the current device ID and file.
*
* HWND hWnd - Current window
*
* Just call preroll and loop processing messages until done.
*
*****************************************************************************/
PRIVATE BOOL FNLOCAL PrerollAndWait(
HWND hWnd)
{
PREROLL preroll;
MMRESULT mmrc;

preroll.tkBase = 0;
preroll.tkEnd = gpSeq->tkLength;

gpSeq->uDeviceID = guDevice;

if (MMSYSERR_NOERROR != (mmrc = seqPreroll(gpSeq, &preroll)))
{
Error(hWnd, IDS_PREROLLFAILED, mmrc);
return FALSE;
}

return TRUE;
}

/*****************************************************************************
*
* MWnd_OnCreate
*
* Handle WM_CREATE message to main application window.
*
* HWND hWnd - Window handle
* CREATESTRUCT FAR* lpCreateStruct
* - Pointer to creation parameters for the window.
*
* Returns TRUE on success. Returning FALSE will cause the window to be
* destroyed and the application will exit.
*
* Set the default time format.
* Create the tool and status bars.
* Create the time window as a child of the main app window and show it.
* Set the main window's title to show no document ('Untitled').
* Accept drag/drop files.
* Call SyncUI to update the enable status of the toolbar and menu items.
*
*****************************************************************************/
PRIVATE BOOL FNLOCAL MWnd_OnCreate(
HWND hWnd,
CREATESTRUCT FAR* lpCreateStruct)
{
HMENU hMenu;
HMENU hMenuOptions;
HMENU hMenuPlayThru;
UINT cDevs;
UINT idx;
RECT rc;
MIDIOUTCAPS moutCaps;

gnTimeFormat = IDS_TF_FIRST;

InitToolbar(hWnd);
InitStatusBar(hWnd);

hMenu = GetMenu(hWnd);
hMenuOptions = GetSubMenu(hMenu, POS_OPTIONS);
hMenuPlayThru = GetSubMenu(hMenu, POS_PLAYTHRU);

AppendMenu(hMenuOptions, MF_SEPARATOR, 0, NULL);

for (idx = 0; idx < N_TIME_FORMATS; idx++)
{
AppendMenu(hMenuOptions,
MF_ENABLED|MF_STRING,
IDS_TF_FIRST + idx,
grgszTimeFormats[idx]);
}

cDevs = midiOutGetNumDevs();
if (cDevs)
AppendMenu(hMenuPlayThru, MF_SEPARATOR, 0, NULL);

for (idx = 0; idx < cDevs; idx++)
{
if (midiOutGetDevCaps(idx, &moutCaps, sizeof(moutCaps)) == MMSYSERR_NOERROR)
{
AppendMenu(hMenuPlayThru,
MF_ENABLED|MF_STRING,
IDM_DEVICES + idx,
moutCaps.szPname);
}
}

CheckMenuItem(hMenu, IDM_TOOLBAR, MF_BYCOMMAND|MF_CHECKED);
CheckMenuItem(hMenu, IDM_STATUS, MF_BYCOMMAND|MF_CHECKED);
CheckMenuItem(hMenu, IDM_AUTOPLAY,MF_BYCOMMAND|MF_CHECKED);
CheckMenuItem(hMenu, gnTimeFormat,MF_BYCOMMAND|MF_CHECKED);
CheckMenuItem(hMenu, IDM_DEVICES, MF_BYCOMMAND|MF_CHECKED);

GetClientRect(hWnd, &rc);

ghWndTime = CreateWindow(
gszTWndClass,
NULL,
WS_CHILD,
rc.left, rc.top,
rc.right-rc.left, rc.bottom-rc.top,
hWnd,
NULL,
lpCreateStruct->hInstance,
NULL);

ShowWindow(ghWndTime, SW_RESTORE);

wsprintf(gszAppTitle, gszAppTitleMask, (LPSTR)gszUntitled);
SetWindowText(hWnd, gszAppTitle);

DragAcceptFiles(hWnd, TRUE);

SyncUI(hWnd);

return TRUE;
}

/*****************************************************************************
*
* MWnd_OnGetMinMaxSize
*
* Handle WM_GETMINMAXSIZE message to main application window.
*
* HWND hWnd - Window handle
* MINMAXINFO FAR* lpMinMaxInfo
* - Pointer to min/max tracking information
*
* This message is sent to a window before resize tracking begins. The
* lpMinMaxInfo structure contains the minimum and maximum x and y values
* the window can be resized to.
*
* We don't allow resizing small enough to cause the status bar and toolbar
* to overlap so they don't try to draw over each other.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnGetMinMaxInfo(
HWND hWnd,
MINMAXINFO FAR* lpMinMaxInfo)
{
RECT rc;

GetWindowRect(hWnd, &rc);

/* Only go small enough that our client area after children is zero.
*/
lpMinMaxInfo->ptMinTrackSize.y =
(rc.bottom - rc.top) -
(grcTWnd.bottom - grcTWnd.top);
}

/*****************************************************************************
*
* MWnd_OnSize
*
* Handle WM_SIZE message to main application window.
*
* HWND hWnd - Window handle
* UINT state - Some SIZE_xxx code indicating what type of
* size operation this is.
* int cx, cy - New x and y size of the window's client area.
*
* Get the new client area.
* Adjust the client area for the toolbar and status bar if they exist.
* Make sure the client area isn't a negative height and adjust if it is.
* Resize the time window to fit in our new client area.
* Forward the WM_SIZE to the time window so it can resize its font.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnSize(
HWND hWnd,
UINT state,
int cx,
int cy)
{
RECT rc;
RECT rcClient;

GetClientRect(hWnd, &rcClient);
if (ghWndToolbar != NULL)
{
/* Cause the toolbar to be aware of the size change
*/
FORWARD_WM_SIZE(ghWndToolbar, SIZE_RESTORED, 0, 0, SendMessage);

GetWindowRect(ghWndToolbar, &rc);
rcClient.top += (rc.bottom - rc.top);
}

if (ghWndStatus != NULL)
{
ResizeStatusBar(hWnd);

/* Cause the status bar to be aware of the size change
*/
FORWARD_WM_SIZE(ghWndStatus, SIZE_RESTORED, 0, 0, SendMessage);

GetWindowRect(ghWndStatus, &rc);
rcClient.bottom -= (rc.bottom - rc.top);
}

/* Do we need to resize entire window so the tool/status bars
** don't overlap? (The only case where this can happen is
** on creation of one of the two -- we set minimum tracking so
** a user can't manually resize the window to cause this
** condition).
*/
if (rcClient.bottom < rcClient.top)
{
GetWindowRect(hWnd, &rc);
SetWindowPos(hWnd,
(HWND)NULL,
0, 0,
rc.right - rc.left + 1,
rc.bottom - rc.top + 1 - rcClient.top - rcClient.bottom,
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
}

SetWindowPos(ghWndTime,
(HWND)NULL,
rcClient.left,
rcClient.top,
rcClient.right - rcClient.left,
rcClient.bottom - rcClient.top,
SWP_NOACTIVATE|SWP_NOZORDER);

FORWARD_WM_SIZE(ghWndTime, SIZE_RESTORED, 0, 0, SendMessage);
}

/*****************************************************************************
*
* MWnd_OnPaint
*
* Handle WM_PAINT message to main application window.
*
* HWND hWnd - Window handle
*
* Just do a BeginPaint/EndPaint pair so USER will mark the area
* as valid. All the real work of painting the time is done
* by the WM_PAINT handler for the time window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnPaint(
HWND hWnd)
{
PAINTSTRUCT ps;
HDC hDC;

hDC = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}

/*****************************************************************************
*
* MWnd_OnDropFiles
*
* Handle WM_DROPFILES message to main application window.
*
* HWND hWnd - Window handle
* HDROP hDrop - Handle to dropped file information
*
* Get the 0th filename and free the drop handle.
* Extract the file title from the full pathname.
* Open the file.
* If we opened successfully, start playback by forwarding a WM_COMMAND
* of IDM_PLAY to the main window.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnDropFiles(
HWND hWnd,
HDROP hDrop)
{
PSTR pStr;

/* For multiple selections, we only accept the first file
*/
DragQueryFile(hDrop, 0, gszOpenName, sizeof(gszOpenName));
DragFinish(hDrop);

/* We don't get OpenTitle like we do from GetOpenFileName; need to
** figure this out for ourselves
*/
pStr = gszOpenName + lstrlen(gszOpenName) - 1;

while (pStr >= gszOpenName && *pStr != '/' && *pStr != '\\' && *pStr != ':')
pStr--;

pStr++;
lstrcpy(gszOpenTitle, pStr);

AttemptFileOpen(hWnd);

if (gbAutoPlay && gpSeq->uState == SEQ_S_OPENED)
FORWARD_WM_COMMAND(hWnd, IDM_PLAY, (HWND)NULL, 0, SendMessage);
}


/*****************************************************************************
*
* MWnd_OnFileOpen
*
* Handle WM_COMMAND/IDM_OPEN message to main application window.
*
* HWND hWnd - Window handle
*
* Fill in the OPENFILENAME struct and call GetOpenFileName.
* If not canceled, try to open the file.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnFileOpen(
HWND hWnd)
{
OPENFILENAME ofn;

*gszOpenName = '\0';

ofn.lStructSize= sizeof(OPENFILENAME);
ofn.hwndOwner= hWnd;
ofn.lpstrFilter= gszFilter;
ofn.lpstrCustomFilter= (LPSTR)NULL;
ofn.nMaxCustFilter= 0L;
ofn.nFilterIndex= 1L;
ofn.lpstrFile= gszOpenName;
ofn.nMaxFile= MAX_FILEPATH;
ofn.lpstrFileTitle= gszOpenTitle;
ofn.nMaxFileTitle= MAX_FILEPATH;
ofn.lpstrTitle= (LPSTR)NULL;
ofn.lpstrInitialDir= (LPSTR)NULL;
ofn.Flags= OFN_HIDEREADONLY|OFN_FILEMUSTEXIST;
ofn.nFileOffset= 0;
ofn.nFileExtension= 0;
ofn.lpstrDefExt= gszDefExtension;

if (!GetOpenFileName(&ofn))
return;

AttemptFileOpen(hWnd);
}

/*****************************************************************************
*
* MWnd_OnCommandToggleChild
*
* Handle WM_COMMAND message of toggle tool or status bar to main application
* window.
*
* HWND hWnd - Window handle
* UINT id - Control id of menu selection; either
* IDM_TOOLBAR or IDM_STATUS
*
* Get the current menu item check state.
* Destroy or create the child as needed.
* Send a WM_SIZE to the main window so client area will be recalculated.
* Toggle the menu item check state.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnCommandToggleChild(
HWND hWnd,
UINT id)
{
HMENU hMenu;
UINT uState;
HWND* phWnd;

phWnd = (id == IDM_TOOLBAR) ? &ghWndToolbar : &ghWndStatus;

hMenu = GetMenu(hWnd);
uState = GetMenuState(hMenu, id, MF_BYCOMMAND);
if (uState & MF_CHECKED)
{
DestroyWindow(*phWnd);
*phWnd = NULL;
}
else
{
if (id == IDM_TOOLBAR)
InitToolbar(hWnd);
else
InitStatusBar(hWnd);
}

SendMessage(hWnd, WM_SIZE, 0, 0L);

uState ^= MF_CHECKED;
uState &= MF_CHECKED;
CheckMenuItem(hMenu, id, MF_BYCOMMAND|uState);

SyncUI(hWnd);
}


/*****************************************************************************
*
* MWnd_OnCommand
*
* Handle WM_COMMAND message to main application window.
*
* HWND hWnd - Window handle
* int id - id of control or menu causing WM_COMMAND
* HWND hwndCtl - Window handle of child control, if any
* UINT codeNotify - Notification code if this message is from a
* control.
*
* For a press of the toolbar buttons or their menu equivalents, just load
* a resource string and display it in the status bar.
*
* For an exit request, send ourselves a WM_CLOSE message.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnCommand(
HWND hWnd,
int id,
HWND hWndCtl,
UINT codeNotify)
{
HMENU hMenu;
int nIdxFormat;
LPSTR lpstr;

if (id >= IDS_TF_FIRST && id <= IDS_TF_LAST)
{
if (NULL != ghWndStatus)
{
nIdxFormat = id - IDS_TF_FIRST;

lpstr = (LPSTR)(grgszTimeFormats[nIdxFormat]);

SendMessage(ghWndStatus,
SB_SETTEXT,
SB_PANE_TFMT,
(LPARAM)lpstr);

}

hMenu = GetMenu(hWnd);

CheckMenuItem(hMenu, gnTimeFormat, MF_UNCHECKED|MF_BYCOMMAND);
CheckMenuItem(hMenu, id, MF_CHECKED|MF_BYCOMMAND);

gnTimeFormat = id;

/* Force time window to update font and repaint entire time string
*/

if(ghWndTime)// for when WM_COMMAND is called before WM_CREATE
FORWARD_WM_SIZE(ghWndTime, SIZE_RESTORED, 0, 0, SendMessage);
}
else if (id >= IDM_DEVMIN && id <= IDM_DEVMAX)
{
hMenu = GetMenu(hWnd);

CheckMenuItem(hMenu, guDevice + IDM_DEVICES, MF_UNCHECKED|MF_BYCOMMAND);
guDevice = id - IDM_DEVICES;
CheckMenuItem(hMenu, guDevice + IDM_DEVICES, MF_CHECKED|MF_BYCOMMAND);
}
else switch(id)
{
case IDM_OPEN:
MWnd_OnFileOpen(hWnd);
break;

case IDM_TOOLBAR:
case IDM_STATUS:
MWnd_OnCommandToggleChild(hWnd, id);
break;

case IDM_AUTOPLAY:
gbAutoPlay = !gbAutoPlay;
CheckMenuItem(GetMenu(hWnd),
IDM_AUTOPLAY,
MF_BYCOMMAND|(gbAutoPlay ? MF_CHECKED : MF_UNCHECKED));
break;

case IDM_PLAY:
FORWARD_WM_COMMAND(ghWndTime, IDM_PLAY, 0, 0, SendMessage);

if (gpSeq->uState != SEQ_S_OPENED)
DPF(1, "IDM_PLAY: State %u", gpSeq->uState);

if (PrerollAndWait(hWnd))
seqStart(gpSeq);

SyncUI(hWnd);
break;

case IDM_STOP:
FORWARD_WM_COMMAND(ghWndTime, IDM_STOP, 0, 0, SendMessage);

seqStop(gpSeq);
SyncUI(hWnd);
break;

case IDM_PAUSE:
if (gpSeq->uState == SEQ_S_PAUSED)
{
seqRestart(gpSeq);
}
else
{
seqPause(gpSeq);
}

SyncUI(hWnd);
break;

case IDM_SYNCUI:
SyncUI(hWnd);
break;

case IDM_EXIT:
SendMessage(hWnd, WM_CLOSE, 0, 0L);
break;
}
}

/*****************************************************************************
*
* MWnd_OnDestroy
*
* Handle WM_DESTROY message to main application window.
*
* HWND hWnd - Window handle
*
* Our main application window has been closed. PostQuitMessage so the main
* message loop will exit and the app can terminate.
*
*****************************************************************************/
PRIVATE VOID FNLOCAL MWnd_OnDestroy(
HWND hWnd)
{
seqStop(gpSeq);
PostQuitMessage(0);
}

/*****************************************************************************
*
* MWnd_WndProc
*
* Window procedure for main application window.
*
* HWND hWnd - Window handle
* UINT msg - Message code
* WPARAM wParam - Message specific parameter
* LPARAM lParam - Message specific parameter

* 
* Dispatch messages we care about to the appropriate handler, else just
* call DefWindowProc.
*
* Note this use of message cracker macros from windowsx.h. Using these
* macros will shield you from the differences between Win16 and Win32;
* if your app is cross-compilable, you should use these and save yourself
* some headaches!
*
*****************************************************************************/
LRESULT CALLBACK MWnd_WndProc(
HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
switch( msg )
{
HANDLE_MSG(hWnd, WM_CREATE, MWnd_OnCreate);
HANDLE_MSG(hWnd, WM_GETMINMAXINFO, MWnd_OnGetMinMaxInfo);
HANDLE_MSG(hWnd, WM_SIZE, MWnd_OnSize);
HANDLE_MSG(hWnd, WM_PAINT, MWnd_OnPaint);
HANDLE_MSG(hWnd, WM_DROPFILES, MWnd_OnDropFiles);
HANDLE_MSG(hWnd, WM_COMMAND, MWnd_OnCommand);
HANDLE_MSG(hWnd, WM_DESTROY, MWnd_OnDestroy);

case MMSG_DONE:
FORWARD_WM_COMMAND(ghWndTime, IDM_STOP, 0, 0, SendMessage);

if (gpSeq->uState != SEQ_S_OPENED)
DPF(1, "MMSG_DONE and state %u", gpSeq->uState);

SyncUI(hWnd);
break;

default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}

return 0;
}