/*
 * RoboCook     (C) 1994 Software Sculptors
 *                  All Rights Under Copyright Reserved
 *
 * Version 1.0
 *
 * Written using SAS/C 6.51
 * Using Magic User Interface (MUI) by Stefan Stuntz
 *
 * By John M. Haubrich, Jr.
 *
 *
 * Version~ Date~~~~~~~ Comments~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * -------  ----        --------
 *    0.0   JAN 01 1994 First MUI version.
 *             ;
 */

#define MODULE_MAIN
#include "common.h"

struct NewMenu Menu_MainMenu[] = {
   NM_TITLE, (STRPTR)"Project",                        NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"About...",                 (STRPTR)"?", 0, 0L, (APTR)ID_MENU_ABOUT,
   NM_ITEM, (STRPTR)NM_BARLABEL,                       NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Quit",                     (STRPTR)"Q", 0, 0L, (APTR)ID_MENU_QUIT,
   NM_TITLE, (STRPTR)"Find",                           NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Find...",                  (STRPTR)"F", 0, 0L, (APTR)ID_MENU_FIND,
   NM_ITEM, (STRPTR)"Find Next",                (STRPTR)"N", 0, 0L, (APTR)ID_MENU_FINDNEXT,
   NM_TITLE, (STRPTR)"Utilities",                      NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Conversion Calculator...", (STRPTR)"C", 0, 0L, (APTR)ID_MENU_CONVERSIONS,
   NM_ITEM, (STRPTR)"System Calculator...",     (STRPTR)"L", 0, 0L, (APTR)ID_MENU_SYSTEMCALC,
   NM_ITEM, (STRPTR)NM_BARLABEL,                       NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Speaker Exceptions...",           NULL, 0, 0L, (APTR)ID_MENU_SPEAKER,
   NM_ITEM, (STRPTR)NM_BARLABEL,                       NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Re-Organize Recipe File...",      NULL, 0, 0L, (APTR)ID_MENU_REORGANIZE,
   NM_TITLE, (STRPTR)"Settings",                       NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Show Timers...",           (STRPTR)"T", 0, 0L, (APTR)ID_MENU_TIMERS,
   NM_ITEM, (STRPTR)"Printer Setup...",         (STRPTR)"P", 0, 0L, (APTR)ID_MENU_PRINTERSETUP,
   NM_ITEM, (STRPTR)NM_BARLABEL,                       NULL, 0, 0L, NULL,
   NM_ITEM, (STRPTR)"Program Settings...",             NULL, 0, 0L, (APTR)ID_MENU_SETTINGS,
   NM_END, NULL, NULL, 0, 0L, NULL };


/*
 * GLOBAL VARIABLES ---------------------------------
 */
struct MyGlobals MG;

struct MainWindow                MAIN;
struct AddWindow                 ADD;
struct CategoryWindow            CAT;
struct MakeWindow                MAKE;
struct FindWindow                FIND;
struct ReOrgWindow               REORG;
struct TimerWindow               TIMER;
struct SpeakerExceptionsWindow   SPEAKXP;
struct GroceryListWindow         GROCLIST;
struct SettingsWindow            SETTINGS;
struct MakeLocalWindow           MAKELOCL;

APTR  dummy;    // for Popup() macro...
char  GL_szFault[ FAULT_MAX * 2 ];
char  GL_szReOrgStat[ 256 ];
char  GL_szBuf[ 256 ];
LONG  GL_lFindStart = 0;
char  GL_szNumSelectedStat[ 50 ];


/*
 * DEFINES ------------------------------------------
 */
#define NUM_ARGS        0
//#define ARG_SETTINGS    1


/*
 * LIBRARY VARIABLES ---------------------------------
 */
struct Library       *DosBase          = NULL;
struct IntuitionBase *IntuitionBase    = NULL;
struct Library       *WorkbenchBase    = NULL;
struct GfxBase       *GfxBase          = NULL;
struct Library       *GadToolsBase     = NULL;
struct Library       *DiskfontBase     = NULL;
struct Library       *AslBase          = NULL;
struct Library       *IconBase         = NULL;
struct Library       *UtilityBase      = NULL;
struct Library       *MUIMasterBase    = NULL;
struct Library       *NarratorBase     = NULL;
struct Library       *TranslatorBase   = NULL;

#ifdef LATTICE
int CXBRK(void) { return(0); }
void __regargs __chkabort(void) { return; }
#endif


/*
 * CONSTANTS -----------------------------------------
 */
UBYTE *Main_ViewByCYCLabels[] = {
   (UBYTE *)"Course",
   (UBYTE *)"Category",
   (UBYTE *)"Recipe Name",
   (UBYTE *)"Source",
   NULL };


/*
 * LIST HOOK FUNCTIONS ------------------------------
 */
SAVEDS ASM APTR fnMainConstructFunc(REG(a0) struct Hook *hook, REG(a2) APTR mempool, REG(a1) char *pszDiskRecord )
{
   struct RecipeMemNode *pNew;
   char                 *psz1, *psz2;
   UBYTE                 ubCourse, ubPrepRating, ubCostRating, ubTasteRating;
   UBYTE                 ubOven, ubAdjustTimer, ubReserved1, ubReserved2;
   UBYTE                 ubCourseLen;
//   UBYTE                 ubPicFilespecLen, ubCommentsLen;
   UBYTE                 ubCategoryLen, ubNameLen, ubSourceLen, ubServingsLen;
//   char                 *pszPicFilespec,    *pszComments;
   char                 *pszCategory,  *pszName,  *pszSource,  *pszServings;
//   char                 *pszReserved0,   *pszReserved1,   *pszReserved2,   *pszReserved3;
//   UBYTE                 ubReserved0Len,  ubReserved1Len,  ubReserved2Len,  ubReserved3Len;
   ULONG                 ulStringPoolLen;
   char                 *pStringPool;


   ubCourse      = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 0 );
   ubPrepRating  = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 1 );
   ubCostRating  = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 2 );
   ubTasteRating = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 3 );
   ubOven        = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 4 );
   ubAdjustTimer = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 5 );
   ubReserved1   = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 6 );
   ubReserved2   = (UBYTE)*( pszDiskRecord + sizeof( ULONG ) + 7 );

   // skip disk-addr & UBYTE array
   psz1 = pszDiskRecord + sizeof( ULONG ) + sizeof( ULONG )*2;

   // get category name...
   pszCategory = psz1;
   psz2 = strchr( psz1, LEVEL1BREAK );
   ubCategoryLen = psz2 - psz1;

   // get recipe name...
   pszName = psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
   ubNameLen = psz2 - psz1;

   // SKIP comments...
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );

   // get source name...
   pszSource = psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
   ubSourceLen = psz2 - psz1;

   // get number of servings...
   pszServings = psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
   ubServingsLen = psz2 - psz1;

   // SKIP pic filespec...
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );

//   // get reserved0 ...
//   pszReserved0 = psz1 = psz2+1;
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
//   ubReserved0Len = psz2 - psz1;

//   // get reserved1 ...
//   pszReserved1 = psz1 = psz2+1;
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
//   ubReserved1Len = psz2 - psz1;

//   // get reserved2 ...
//   pszReserved2 = psz1 = psz2+1;
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
//   ubReserved2Len = psz2 - psz1;

//   // get reserved3 ...
//   pszReserved3 = psz1 = psz2+1;
   psz1 = psz2+1;
   psz2 = strchr( psz1, LEVEL1BREAK );
//   ubReserved3Len = psz2 - psz1;

   ulStringPoolLen = sizeof(struct RecipeMemNode) + ubCategoryLen +
                     ubNameLen + ubSourceLen + ubServingsLen +
//                     ubReserved0Len +
//                     ubReserved1Len + ubReserved2Len + ubReserved3Len +
                     10;
   if ( pNew = AllocVec( ulStringPoolLen, MEMF_ANY | MEMF_CLEAR ) )
   {
      ubCourseLen = strlen( Details_CourseCYCLabels[ ubCourse ] );
      pNew->ubCourse      = ubCourse - 1;
      pNew->ubPrepRating  = ubPrepRating - 1;
      pNew->ubCostRating  = ubCostRating - 1;
      pNew->ubTasteRating = ubTasteRating - 1;
      pNew->ubOven        = ubOven - 1;
      pNew->ubAdjustTimer = ubAdjustTimer - 1;
      pNew->ubReserved1   = ubReserved1 - 1;
      pNew->ubReserved2   = ubReserved2 - 1;

      //
      // copy strings into stringpool...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      pStringPool = pNew->pStringPool = ( pNew + 1 );

      strncpy( pStringPool, pszCategory, ubCategoryLen );
      *( pStringPool+ubCategoryLen ) = 0;    // null-terminate
      pNew->pszCategoryName = pStringPool;
      pStringPool += (ubCategoryLen+1);

      strncpy( pStringPool, pszName, ubNameLen );
      *( pStringPool+ubNameLen ) = 0;        // null-terminate
      pNew->pszRecipeName   = pStringPool;
      pStringPool += (ubNameLen+1);

      strncpy( pStringPool, pszSource, ubSourceLen );
      *( pStringPool+ubSourceLen ) = 0;      // null-terminate
      pNew->pszSource       = pStringPool;
      pStringPool += (ubSourceLen+1);

      strncpy( pStringPool, pszServings, ubServingsLen );
      *( pStringPool+ubServingsLen ) = 0;    // null-terminate
      pNew->pszServings     = pStringPool;
      pStringPool += (ubServingsLen+1);

//      strncpy( pStringPool, pszReserved0, ubReserved0Len );
//      *( pStringPool+ubReserved0Len ) = 0;        // null-terminate
//      pNew->pszReserved0    = pStringPool;
//      pStringPool += (ubReserved0Len+1);

//      strncpy( pStringPool, pszReserved1, ubReserved1Len );
//      *( pStringPool+ubReserved1Len ) = 0;        // null-terminate
//      pNew->pszReserved1    = pStringPool;
//      pStringPool += (ubReserved1Len+1);

//      strncpy( pStringPool, pszReserved2, ubReserved2Len );
//      *( pStringPool+ubReserved2Len ) = 0;        // null-terminate
//      pNew->pszReserved2    = pStringPool;
//      pStringPool += (ubReserved2Len+1);

//      strncpy( pStringPool, pszReserved3, ubReserved3Len );
//      *( pStringPool+ubReserved3Len ) = 0;        // null-terminate
//      pNew->pszReserved3    = pStringPool;
//      pStringPool += (ubReserved3Len+1);

      //
      // get disk address for this record...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      pNew->ulDiskAddr = *( (ULONG *)pszDiskRecord );

      return( pNew );
   }
   else
   {
      return( NULL );
   }
}
static struct Hook hMainConstructHook =
{
   {NULL, NULL},
   (void *)fnMainConstructFunc,
   NULL, NULL
};

SAVEDS ASM VOID fnMainDestructFunc(REG(a0) struct Hook *hook, REG(a2) APTR mempool, REG(a1) struct RecipeMemNode *pRMNode )
{
   FreeVec( pRMNode );
}
static struct Hook hMainDestructHook = {
   {NULL, NULL},
   (void *)fnMainDestructFunc,
   NULL, NULL
};

SAVEDS ASM LONG fnMainCompareFunc( REG(a0) struct Hook *hook, REG(a1) struct RecipeMemNode *pRMNode1, REG(a2) struct RecipeMemNode *pRMNode2 )
{
   LONG   rc = NULL;
   ULONG  ulStat;


   get( MAIN.CYC_ViewBy, MUIA_Cycle_Active, &ulStat );
   switch ( ulStat )
   {
      case 3:     // SOURCE
         rc = stricmp( pRMNode1->pszSource, pRMNode2->pszSource );
         break;
      case 2:     // RECIPE NAME
         rc = stricmp( pRMNode1->pszRecipeName, pRMNode2->pszRecipeName );
         break;
      case 1:     // CATEGORY
         rc = stricmp( pRMNode1->pszCategoryName, pRMNode2->pszCategoryName );
         break;
      default:
      case 0:     // COURSE
         rc = stricmp( Details_CourseCYCLabels[ pRMNode1->ubCourse ],
                       Details_CourseCYCLabels[ pRMNode2->ubCourse ] );
         break;
   }

   return( rc );
}
static struct Hook hMainCompareHook = {
   {NULL, NULL},
   (void *)fnMainCompareFunc,
   NULL, NULL
};

SAVEDS ASM LONG fnMainDisplayFunc(REG(a0) struct Hook *hook,REG(a2) char **array,REG(a1) struct RecipeMemNode *pRMNode)
{
   if ( pRMNode )
   {
      //
      // display data
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( pRMNode->ubSelected )
      {
         *array++ = "\33I[6:15]";      // MUII_CheckMark
      }
      else
      {
         *array++ = "";
      }
      if ( pRMNode->ubCourse > 6 )
      {
         *array++ = "None";
      }
      else
      {
         *array++ = Details_CourseCYCLabels[ pRMNode->ubCourse ];
      }
      *array++ = pRMNode->pszCategoryName;

      if ( pRMNode->ubOven > 2 )
      {
         *array++ = "None";
      }
      else
      {
         *array++ = Add_OvenCYCLabels[ pRMNode->ubOven ];
      }

      *array++ = pRMNode->pszRecipeName;
      *array   = pRMNode->pszSource;
   }
   else
   {
      //
      // display list title
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      *array++ = "\33bM?";
      *array++ = "\33bCourse";
      *array++ = "\33bCategory";
      *array++ = "\33bOven";
      *array++ = "\33bRecipe";
      *array   = "\33bSource";
   }

   return(0);
}
static struct Hook hMainDisplayHook = {
   {NULL, NULL},
   (void *)fnMainDisplayFunc,
   NULL, NULL
};


/*
 * HOOK FUNCTIONS -----------------------------------
 */


/*
 * FUNCTIONS ----------------------------------------
 */
//
// fnExtractValue(): get first 'word' from line; if decimal, convert
//                   elif fraction, call fnFrac2Float() else rc = 1.0
//    WARNING: Assumes max line length of 80.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
float fnExtractValue( UBYTE *psz )
{
   float  rc = 0.0;
   float  fIntPart = 0.0, fFracPart = 0.0;
   UBYTE *pszStart, *pszEnd, szBuf[80+1+1];


   strncpy( szBuf, psz, 80 );
   szBuf[80] = 0;
   strcat( szBuf, " " );

   pszStart = szBuf;
   while ( *pszStart == ' ' ) pszStart++;    // skip blanks at start of line
   pszEnd = strchr( pszStart, ' ' );         // scan for end of first 'word'
   if ( pszEnd )
   {
      *pszEnd = 0;
      if ( strchr( pszStart, '/' ) )
      {
         fIntPart = 0.0;
         fnFrac2Float( pszStart, &fFracPart );
      }
      else
      {
         fIntPart = atof( pszStart );
         *pszEnd = ' ';
         pszStart = pszEnd + 1;
         while ( *pszStart == ' ' ) pszStart++;    // skip to word 2

         if ( isdigit( *pszStart ) )
         {
            pszEnd = strchr( pszStart, ' ' );            // find end of word 2

            if ( pszEnd )
            {
               *pszEnd = 0;
               fnFrac2Float( pszStart, &fFracPart );
            }
         }
      }
   }
   else
   {
      fIntPart = 1.0;
   }

   rc = fIntPart + fFracPart;
   return( rc );
}

//
// fnFindRecordItem(): find a record item in a memory-loaded record
//    return 0 on error or memory-address if item is found
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ULONG fnFindRecordItem( UBYTE *pMemRecord, ULONG ulRecItemId, char *pszResult )
{
   ULONG rc = (ULONG)FALSE;
   char *psz, *pszStart, *pszEnd;


   switch( ulRecItemId )
   {
      case RECITEM_COMMENTS:
         // skip 2 LEVEL1BREAK's to get to comment...
         psz = (char *)pMemRecord;
         psz = strchr(   psz, LEVEL1BREAK );    // category
         psz = strchr( ++psz, LEVEL1BREAK );    // recipe name

         pszStart = psz+1;
         psz = strchr( ++psz, LEVEL1BREAK );    // comments
         pszEnd = psz;

         if ( pszStart && pszEnd )
         {
            strncpy( pszResult, pszStart, (pszEnd - pszStart) );
            *( pszResult+(pszEnd - pszStart) ) = 0;
            rc = (ULONG)pszStart;
         }
         break;
      case RECITEM_PICFILESPEC:
         // skip 5 LEVEL1BREAK's to get to picfilespec...
         psz = (char *)pMemRecord;
         psz = strchr(   psz, LEVEL1BREAK );    // category
         psz = strchr( ++psz, LEVEL1BREAK );    // recipe name
         psz = strchr( ++psz, LEVEL1BREAK );    // comments
         psz = strchr( ++psz, LEVEL1BREAK );    // source
         psz = strchr( ++psz, LEVEL1BREAK );    // serves

         pszStart = psz+1;
         psz = strchr( ++psz, LEVEL1BREAK );    // picfilespec
         pszEnd = psz;

         if ( pszStart && pszEnd )
         {
            strncpy( pszResult, pszStart, (pszEnd - pszStart) );
            *( pszResult+(pszEnd - pszStart) ) = 0;
            rc = (ULONG)pszStart;
         }
         break;
      case RECITEM_INGREDIENTS:
         // skip 10 LEVEL1BREAK's to get to ingredients list
         psz = (char *)pMemRecord;
         psz = strchr(   psz, LEVEL1BREAK );    // category
         psz = strchr( ++psz, LEVEL1BREAK );    // recipe name
         psz = strchr( ++psz, LEVEL1BREAK );    // comments
         psz = strchr( ++psz, LEVEL1BREAK );    // source
         psz = strchr( ++psz, LEVEL1BREAK );    // serves
         psz = strchr( ++psz, LEVEL1BREAK );    // picfilesize
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved0
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved1
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved2
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved3

         rc = (ULONG)( psz+1 );
         break;
      case RECITEM_DIRECTIONS:
         // skip 11 LEVEL1BREAK's to get to ingredients list
         psz = (char *)pMemRecord;
         psz = strchr(   psz, LEVEL1BREAK );    // category
         psz = strchr( ++psz, LEVEL1BREAK );    // recipe name
         psz = strchr( ++psz, LEVEL1BREAK );    // comments
         psz = strchr( ++psz, LEVEL1BREAK );    // source
         psz = strchr( ++psz, LEVEL1BREAK );    // serves
         psz = strchr( ++psz, LEVEL1BREAK );    // picfilesize
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved0
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved1
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved2
         psz = strchr( ++psz, LEVEL1BREAK );    // reserved3
         psz = strchr( ++psz, LEVEL1BREAK );    // ingredients

         rc = (ULONG)( psz+1 );
         break;
      case RECITEM_TIMERLIST:
         psz = (char *)pMemRecord;
         psz = strchr(   psz, LEVEL1BREAK );    // category
         psz = strchr( ++psz, LEVEL1BREAK );    // recipe name
         psz = strchr( ++psz, LEVEL1BREAK );    // comments
         psz = strchr( ++psz, LEVEL1BREAK );    // source
         psz = strchr( ++psz, LEVEL1BREAK );    // serves
         psz = strchr( ++psz, LEVEL1BREAK );    // picfilesize

         rc = (ULONG)( psz+1 );
         break;
      case RECITEM_PSZRESERVED1:
         break;
      case RECITEM_PSZRESERVED2:
         break;
      case RECITEM_PSZRESERVED3:
         break;
   }
   return( rc );
}

//
// fnFloat2Frac(): convert float to fraction using table
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BOOL fnFloat2Frac( float flFloat, char *pszFraction )
{
   float  flFraction;
   ULONG  ulFloor;


   strcpy( pszFraction, "" );

   ulFloor = (ULONG)floor( flFloat );
   flFraction = flFloat - (float)ulFloor;
   if ( ulFloor )
   {
      sprintf( pszFraction, "%lu ", ulFloor );
   }

   if      ( flFraction >= 0.96875 ) sprintf( pszFraction, "%lu", ulFloor+1 );
   else if ( flFraction >= 0.90625 )  strcat( pszFraction, "15/16" );
   else if ( flFraction >= 0.84375 )  strcat( pszFraction, " 7/8 " );
   else if ( flFraction >= 0.78125 )  strcat( pszFraction, "13/16" );
   else if ( flFraction >= 0.71875 )  strcat( pszFraction, " 3/4 " );
   else if ( flFraction >= 0.66    )  strcat( pszFraction, " 2/3 " );
   else if ( flFraction >= 0.65625 )  strcat( pszFraction, "11/16" );
   else if ( flFraction >= 0.59375 )  strcat( pszFraction, " 5/8 " );
   else if ( flFraction >= 0.53125 )  strcat( pszFraction, " 9/16" );
   else if ( flFraction >= 0.46875 )  strcat( pszFraction, " 1/2 " );
   else if ( flFraction >= 0.40625 )  strcat( pszFraction, " 7/16" );
   else if ( flFraction >= 0.34375 )  strcat( pszFraction, " 3/8 " );
   else if ( flFraction >= 0.33    )  strcat( pszFraction, " 1/3 " );
   else if ( flFraction >= 0.28125 )  strcat( pszFraction, " 5/16" );
   else if ( flFraction >= 0.21875 )  strcat( pszFraction, " 1/4 " );
   else if ( flFraction >= 0.15625 )  strcat( pszFraction, " 3/16" );
   else if ( flFraction >= 0.09375 )  strcat( pszFraction, " 1/8 " );
   else if ( flFraction >= 0.03125 )  strcat( pszFraction, " 1/16" );

   return( TRUE );
}

//
// fnFrac2Float(): convert fraction to float
//                 assumes a simple fraction (NN/DD)
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void fnFrac2Float( char *pszFraction, float *flFloat )
{
   static char  szFraction[100];
   char        *pszFracEnd = NULL, *pszSlash = NULL;
   float        fl;
   ULONG        ulNumerator = 0, ulDenominator = 1;
   USHORT       us;


   strncpy( szFraction, pszFraction, 98 );
   szFraction[98] = 0;
   strcat( szFraction, " " );

   fl = 0.0;

   pszSlash = strchr( pszFraction, '/' );
   if ( pszSlash )
   {
      *pszSlash = 0;
      ulNumerator = atol( pszFraction );
      us = strspn( pszSlash+1, "0123456789" );
      pszFracEnd = pszSlash+1 + us;
      if ( pszFracEnd )
      {
         *pszFracEnd = 0;
         ulDenominator = atol( pszSlash+1 );
         if ( ulDenominator != 0 )
         {
            fl = (float)( (float)ulNumerator / (float)ulDenominator );
         }
      }
   }

   *flFloat = fl;
}

//
// fnSkipQuantity(): convert fraction to float
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char *fnSkipQuantity( char *pszFraction )
{
   static char  szFraction[100];
   char        *rc = NULL;
   char        *psz1 = NULL;
   USHORT       us;


   strncpy( szFraction, pszFraction, 98 );
   szFraction[98] = 0;
   strcat( szFraction, " " );

   rc = szFraction;
   us = strspn( szFraction, "0123456789 /." );
   psz1 = &szFraction[ us - ( (us > 0) ? 1 : 0 ) ];
   if ( psz1 )
   {
      rc = pszFraction + ( ( psz1 - szFraction ) +1 );
   }

   return( rc );
}


// ***********************************************************************
//
//    MAIN WINDOW
// struct MainWindow {
//    APTR  CYC_ViewBy,
//          LIS_RecipeList,
//          TXT_NumSelected,
//          BUT_Mark,
//          BUT_UnMark,
//          BUT_Add,
//          BUT_Find,
//          BUT_FindNext,
//          BUT_Change,
//          BUT_Show,
//          BUT_Make;
// } MAIN;
//
// ***********************************************************************
//
BOOL fnConstructMainWindow( void )
{
   MG.WIN_Main = WindowObject,
      MUIA_Window_ID, MAKE_ID('M','A','I','N'),
      MUIA_Window_Title, "Robo Cook",
      MUIA_Window_Menu, (APTR)Menu_MainMenu,
      WindowContents, VGroup,
         Child, VGroup,
            Child, HGroup,
               Child, HSpace(0),
               Child, KeyLabel1("View by",'v'),
               Child, MAIN.CYC_ViewBy = KeyCycle(Main_ViewByCYCLabels,'v'),
               Child, HSpace(0),
               End,
            Child, MAIN.LIS_RecipeList = ListviewObject,
               MUIA_Listview_MultiSelect, MUIV_Listview_MultiSelect_Default,
               MUIA_Listview_Input, TRUE,
               MUIA_Listview_List, ListObject,
                  InputListFrame,
                  MUIA_List_CompareHook,   &hMainCompareHook,
                  MUIA_List_ConstructHook, &hMainConstructHook,
                  MUIA_List_DestructHook,  &hMainDestructHook,
                  MUIA_List_DisplayHook,   &hMainDisplayHook,
                  MUIA_List_Format, "D=10 COL=0,D=10 COL=1,D=10 COL=2,D=10 COL=3,D=10 COL=4,D=10 COL=5",
                  MUIA_List_Title, TRUE,
                  End,
               End,
            Child, MAIN.TXT_NumSelected = TextObject, TextFrame,
               MUIA_Background, MUII_TextBack,
               End,
            Child, VGroup,
               Child, HGroup, MUIA_Group_SameSize, TRUE,
                  Child, MAIN.BUT_Mark     = KeyButton("Mark",'m'),
                  Child, MAIN.BUT_UnMark   = KeyButton("Unmark",'u'),
                  Child, MAIN.BUT_Delete   = KeyButton("Delete",'e'),
                  End,
               Child, HGroup, MUIA_Group_SameSize, TRUE,
                  Child, MAIN.BUT_Add      = KeyButton("Add...",'a'),
                  Child, MAIN.BUT_Find     = KeyButton("Find...",'f'),
                  Child, MAIN.BUT_FindNext = KeyButton("Find Next", 'n' ),
                  End,
               Child, HGroup, MUIA_Group_SameSize, TRUE,
                  Child, MAIN.BUT_Change   = KeyButton("Change...",'c'),
                  Child, MAIN.BUT_Show     = KeyButton("Show...",'h'),
                  Child, MAIN.BUT_Make     = KeyButton("Make...",'k'),
                  End,
               End,
            End,
         End,
      End;

   // window failed?
   if ( !MG.WIN_Main )
   {
      return( FAILURE );
   }
   else
   {
      // make gadget connections
      DoMethod( MG.WIN_Main, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
                MG.App, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit );

      DoMethod( MAIN.LIS_RecipeList, MUIM_Notify, MUIA_Listview_DoubleClick, TRUE,
                MG.App, 2, MUIM_Application_ReturnID, ID_LIS_MAINRECIPELIST );
      DoMethod( MAIN.CYC_ViewBy, MUIM_Notify, MUIA_Cycle_Active, MUIV_EveryTime,
                MG.App, 2, MUIM_Application_ReturnID, ID_CYC_MAINVIEWBYCYCLE );
      DoMethod( MAIN.BUT_Add, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINADD );
      DoMethod( MAIN.BUT_Find, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINFIND );
      DoMethod( MAIN.BUT_FindNext, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINFINDNEXT );
      DoMethod( MAIN.BUT_Change, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINCHANGE );
      DoMethod( MAIN.BUT_Show, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINSHOW );
      DoMethod( MAIN.BUT_Make, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINMAKE );
      DoMethod( MAIN.BUT_Mark, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINMARK );
      DoMethod( MAIN.BUT_UnMark, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINUNMARK );
      DoMethod( MAIN.BUT_Delete, MUIM_Notify, MUIA_Pressed, FALSE,
                MG.App, 2, MUIM_Application_ReturnID, ID_BUT_MAINDELETE );

      // set tab cycle chain
      DoMethod( MG.WIN_Main, MUIM_Window_SetCycleChain,
                MAIN.CYC_ViewBy,
                MAIN.LIS_RecipeList,
                MAIN.BUT_Mark,
                MAIN.BUT_UnMark,
                MAIN.BUT_Delete,
                MAIN.BUT_Add,
                MAIN.BUT_Find,
                MAIN.BUT_FindNext,
                MAIN.BUT_Change,
                MAIN.BUT_Show,
                MAIN.BUT_Make,
                NULL );

      // add window to Application object
      DoMethod( MG.App, OM_ADDMEMBER, MG.WIN_Main );

      return( SUCCESS );
   }
}

BOOL fnInitMainWindow( void )
{
   set( MAIN.CYC_ViewBy, MUIA_Cycle_Active, 0 );
   set( MAIN.LIS_RecipeList, MUIA_List_Active, MUIV_List_Active_Top );
   strcpy( GL_szNumSelectedStat, " 0 marked." );
   set( MAIN.TXT_NumSelected, MUIA_Text_Contents, GL_szNumSelectedStat );

   set( MG.WIN_Main, MUIA_Window_ActiveObject,  MAIN.LIS_RecipeList );
   set( MG.WIN_Main, MUIA_Window_DefaultObject, MAIN.LIS_RecipeList );
   set( MG.WIN_Main, MUIA_Window_Open, TRUE );

   return( SUCCESS );
}

//BOOL fnDestructMainWindow( void );



/**
 ** M A I N   P R O G R A M -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 **/
void main( int argc, char *argv[] )
{
   ULONG   signals;
   BOOL    running = TRUE;
   ULONG   ulInputEvent;
   BOOL    fSuccess;

   // generic status vars...
   ULONG   ulStat;
   LONG    lStat, lDiskStat;
   UBYTE  *pszStart, *pszEnd, *pszStat, szBuf[FMSIZE+20];
   USHORT  usStat;
   BOOL    fPrintError = FALSE;

   // specific to certain routines... need to remain in scope!
   struct RecipeMemNode *pRMNode = NULL;
   struct MakeNode      *pMNode  = NULL;
   struct TimersNode    *pTNode  = NULL;
   struct TimerDefNode  *pTDNode = NULL;
   struct Window        *pWindow = NULL;
   UBYTE                *pMemRecord = NULL;
   static char           szTool[ FMSIZE*2 + 50 ];
   static char          *pszNewNew = "new,new";
   BPTR                  fhPRT;
   ULONG                 ulFormFeed;


   //
   // setup variables, read startup params, etc...
   // also create application object
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   JMH_Startup( argc, argv );

   //
   // construct all windows
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( fnConstructMainWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Main window." );
   }
   if ( fnConstructAddWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Add window." );
   }
   if ( fnConstructCatWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Category window." );
   }
   if ( fnConstructMakeWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Make window." );
   }
   if ( fnConstructFindWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Find window." );
   }
   if ( fnConstructReOrgWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create ReOrg window." );
   }
   if ( fnConstructTimerWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Timer window." );
   }
   if ( fnConstructSpeakXPWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Speaker Exceptions window." );
   }
   if ( fnConstructGrocListWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Grocery List window." );
   }
   if ( fnConstructSettingsWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create Settings window." );
   }
   if ( fnConstructMakeLocalWindow() == FAILURE )
   {
      JMH_Quit( "Failed to create MakeLocal window." );
   }

   //
   // Load application settings; populate global vars
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   DoMethod( MG.App, MUIM_Application_Load, MUIV_Application_Load_ENV );
   get( SETTINGS.POP_MeasureListspec, MUIA_String_Contents, &pszStat );
   strcpy( MG.szMeasureList, pszStat );
   get( SETTINGS.POP_GfxViewer, MUIA_String_Contents, &pszStat );
   strcpy( MG.szGfxViewer, pszStat );
   get( SETTINGS.POP_PrinterPrefsProg, MUIA_String_Contents, &pszStat );
   strcpy( MG.szPrinterPrefsProg, pszStat );
   get( SETTINGS.POP_SysCalculator, MUIA_String_Contents, &pszStat );
   strcpy( MG.szSysCalculator, pszStat );
   get( SETTINGS.POP_SpeakXPList, MUIA_String_Contents, &pszStat );
   strcpy( MG.szSpeakXPList, pszStat );
   get( SETTINGS.POP_CategoryList, MUIA_String_Contents, &pszStat );
   strcpy( MG.szCategoryList, pszStat );

   //
   // init listview text lists
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( fnReadListFromDisk( MG.szCategoryList, CAT.LIS_Categories,
                            MUIV_List_Insert_Sorted, "ROBOCOOK_CATEGORYLIST" ) == FAILURE )
      JMH_Quit( NULL );

   //
   // open main window
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   fnInitMainWindow();

   //
   // disable SPEECH controls if can't lock translator and narrator libs!
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( (NarratorBase) && (TranslatorBase) )
   {
      fnReadListFromDisk( MG.szSpeakXPList, SPEAKXP.LIS_SpeakerExceptions,
                          MUIV_List_Insert_Sorted, "ROBOCOOK_SPEAKXPLIST" );
   }
   else
   {
      // warn user...
//      JMH_Error( "Narrator and Translator libraries could not be found.\nSpeech will not be available.", MG.WIN_Main, NULL, NULL, 0 );

      // disable speakxp menuitem, speechRB(alarm), MAKE/SpeechCHE
      get( MG.WIN_Main, MUIA_Window_Window, &pWindow );
      if ( pWindow )
      {
         DoMethod( MG.WIN_Main, MUIM_Window_SetMenuState, ID_MENU_SPEAKER, FALSE );
      }

      set( SETTINGS.CYC_SpeakerSex,  MUIA_Disabled, TRUE );
      set( SETTINGS.CYC_SpeakerType, MUIA_Disabled, TRUE );
      set( SETTINGS.SLI_Pitch,       MUIA_Disabled, TRUE );
      set( SETTINGS.SLI_Speed,       MUIA_Disabled, TRUE );
      set( SETTINGS.STR_SpeakString, MUIA_Disabled, TRUE );
//      set( MAKE.CHE_Speech,          MUIA_Disabled, TRUE );
   }

   //
   // set window to sleep while loading recipe data...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   set( MG.WIN_Main, MUIA_Window_Sleep, TRUE );
   set( MAIN.LIS_RecipeList, MUIA_List_Quiet, TRUE );
   fSuccess = fnReadRecipeData( TRUE );
   if ( fSuccess == FALSE )
   {
      JMH_Quit( "Couldn't read recipe data file." );
   }
   set( MAIN.LIS_RecipeList, MUIA_List_Quiet, FALSE );
   set( MG.WIN_Main, MUIA_Window_Sleep, FALSE );

   //
   // activate first item in listview
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   set( MAIN.LIS_RecipeList, MUIA_List_Active, MUIV_List_Active_Top );

   //
   // show 'em what it's all 'About'
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   JMH_About();

   //
   //   MAIN LOOP          MAIN LOOP         MAIN LOOP         MAIN LOOP
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   while (running)
   {
      ulInputEvent = DoMethod( MG.App, MUIM_Application_Input, &signals );
      switch ( ulInputEvent )
      {
         case MUIV_Application_ReturnID_Quit:
         case ID_MENU_QUIT:
            if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                              "Are you sure you want to quit?", NULL ) == 1 )
            {
               running = FALSE;
            }
            break;

         // MENUS ---------------------------------------------
            case ID_MENU_CONVERSIONS:
MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "*OK",
             "MAIN/CONVERSIONSLIST goes here...", NULL );
               break;
            case ID_MENU_SYSTEMCALC:
               sprintf( szTool, "\"%s\"", MG.szSysCalculator );
               lStat = SystemTags( szTool, SYS_Asynch, TRUE, TAG_DONE, TAG_DONE );
               if ( lStat != 0 )
               {
                  if ( lStat > 0 )
                     Fault( IoErr(),
                            "Cannot launch Calculator tool because",
                            GL_szFault,
                            sizeof( GL_szFault ) );
                  else
                     strcpy( GL_szFault, "Couldn't launch Calculator tool!" );

                  JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
               }
               break;
            case ID_MENU_SPEAKER:
               fnInitSpeakXPWindow();
               break;
            case ID_MENU_ABOUT:
               JMH_About();
               break;
            case ID_MENU_REORGANIZE:
               fnInitReOrgWindow();
               break;
            case ID_MENU_TIMERS:
               fnInitTimerWindow();
               break;
            case ID_MENU_PRINTERSETUP:
               set( MG.App, MUIA_Application_Sleep, TRUE );
               sprintf( szTool, "\"%s\"", MG.szPrinterPrefsProg );
               lStat = SystemTags( szTool, SYS_Asynch, FALSE, TAG_DONE, TAG_DONE );
               if ( lStat != 0 )
               {
                  if ( lStat > 0 )
                     Fault( IoErr(),
                            "Cannot launch Printer Preferences tool because",
                            GL_szFault,
                            sizeof( GL_szFault ) );
                  else
                     strcpy( GL_szFault, "Unable to launch Printer Preferences tool!" );

                  JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
               }
               set( MG.App, MUIA_Application_Sleep, FALSE );
               break;
            case ID_MENU_SETTINGS:
               fnInitSettingsWindow();
               break;

         // GADGETS -------------------------------------------
         // MAIN WINDOW
            case ID_BUT_MAINCHANGE:
               get( MAIN.LIS_RecipeList, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a recipe to change first!", MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  fnInitDetWindow();
               }
               break;
            case ID_LIS_MAINRECIPELIST:
               fnInitDetWindow();
               break;
            case ID_CYC_MAINVIEWBYCYCLE:
               get( MAIN.CYC_ViewBy, MUIA_Cycle_Active, &ulStat );
               set( MAIN.LIS_RecipeList, MUIA_List_Quiet, TRUE );
               switch( ulStat )
               {
                  case 3:
                     set( MAIN.LIS_RecipeList, MUIA_List_Format,
                          "D=10 COL=0,D=10 COL=5,D=10 COL=1,D=10 COL=2,D=10 COL=3,D=10 COL=4" );
                     break;
                  case 2:
                     set( MAIN.LIS_RecipeList, MUIA_List_Format,
                          "D=10 COL=0,D=10 COL=4,D=10 COL=1,D=10 COL=2,D=10 COL=3,D=10 COL=5" );
                     break;
                  case 1:
                     set( MAIN.LIS_RecipeList, MUIA_List_Format,
                          "D=10 COL=0,D=10 COL=2,D=10 COL=1,D=10 COL=3,D=10 COL=4,D=10 COL=5" );
                     break;
                  case 0:
                  default:
                     set( MAIN.LIS_RecipeList, MUIA_List_Format,
                          "D=10 COL=0,D=10 COL=1,D=10 COL=2,D=10 COL=3,D=10 COL=4,D=10 COL=5" );
                     break;
               }
               DoMethod( MAIN.LIS_RecipeList, MUIM_List_Sort, TRUE );
               set( MAIN.LIS_RecipeList, MUIA_List_Quiet, FALSE );
               break;
            case ID_BUT_MAINADD:
               fnInitAddWindow();
               break;
            case ID_BUT_MAINFIND:
            case ID_MENU_FIND:
               fnInitFindWindow();
               break;
            case ID_BUT_MAINFINDNEXT:
            case ID_MENU_FINDNEXT:
            case ID_BUT_FINDFINDNEXT:
               lStat = fnFind( GL_lFindStart+1 );
               if ( lStat != -1 )
               {
                  GL_lFindStart = lStat;
               }
               else
               {
                  JMH_Error( "No match for search criteria.", MG.WIN_Main, NULL, NULL, 0 );
               }
               break;
            case ID_BUT_MAINSHOW:
               get( MAIN.LIS_RecipeList, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a recipe to show first!", MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry,
                            MUIV_List_GetEntry_Active, &pRMNode );
                  pMemRecord = fnFetchRecord( pRMNode->ulDiskAddr );
                  if ( pMemRecord )
                  {
                     // find picfilespec...
                     if ( fnFindRecordItem( pMemRecord, RECITEM_PICFILESPEC, szBuf ) )
                     {
                        if ( strcmp( szBuf, "" ) == 0 )
                        {
                           JMH_Error( "This recipe has no picture.", MG.WIN_Main, NULL, NULL, 0 );
                        }
                        else
                        {
                           set( MG.App, MUIA_Application_Sleep, TRUE );
                           sprintf( szTool, "\"%s\" \"%s\"", MG.szGfxViewer, szBuf );
                           lStat = SystemTags( szTool, SYS_Asynch, FALSE, TAG_DONE, TAG_DONE );
                           if ( lStat != 0 )
                           {
                              if ( lStat > 0 )
                                 Fault( IoErr(),
                                        "Cannot launch graphics viewer because",
                                        GL_szFault,
                                        sizeof( GL_szFault ) );
                              else
                                 strcpy( GL_szFault, "Couldn't launch graphics viewer!" );

                              JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
                           }
                        }
                        set( MG.App, MUIA_Application_Sleep, FALSE );
                     }
                     else
                     {
                        JMH_Error( "Couldn't get the picture file name.", MG.WIN_Main, NULL, NULL, 0 );
                     }
                     FreeVec( pMemRecord );
                  }
                  else
                  {
                     JMH_Error( "Couldn't get the picture file name.", MG.WIN_Main, NULL, NULL, 0 );
                  }
               }
               break;
            case ID_BUT_MAINMAKE:
               fnInitMakeWindow();
               break;
            case ID_BUT_MAINMARK:
               lStat = -1;
               DoMethod( MAIN.LIS_RecipeList, MUIM_List_NextSelected, &lStat );
               while ( lStat != -1 )
               {
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry, lStat, &pRMNode );
                  if ( pRMNode ) pRMNode->ubSelected = 1;
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_NextSelected, &lStat );
               }
               DoMethod( MAIN.LIS_RecipeList, MUIM_List_Redraw, MUIV_List_Redraw_All );

               // show number of items selected...
               get( MAIN.LIS_RecipeList, MUIA_List_Entries, &ulStat );
               for ( usStat = 0, lStat = 0; lStat < ulStat; lStat++ )
               {
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry, lStat, &pRMNode );
                  if ( pRMNode )
                     if ( pRMNode->ubSelected == 1 ) usStat++;
               }
               sprintf( GL_szNumSelectedStat, " %u marked.", usStat );
               set( MAIN.TXT_NumSelected, MUIA_Text_Contents, GL_szNumSelectedStat );
               break;
            case ID_BUT_MAINUNMARK:
               lStat = -1;
               DoMethod( MAIN.LIS_RecipeList, MUIM_List_NextSelected, &lStat );
               while ( lStat != -1 )
               {
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry, lStat, &pRMNode );
                  if ( pRMNode ) pRMNode->ubSelected = 0;
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_NextSelected, &lStat );
               }
               DoMethod( MAIN.LIS_RecipeList, MUIM_List_Redraw, MUIV_List_Redraw_All );

               // show number of items selected...
               get( MAIN.LIS_RecipeList, MUIA_List_Entries, &ulStat );
               for ( usStat = 0, lStat = 0; lStat < ulStat; lStat++ )
               {
                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry, lStat, &pRMNode );
                  if ( pRMNode )
                     if ( pRMNode->ubSelected == 1 ) usStat++;
               }
               sprintf( GL_szNumSelectedStat, " %u marked.", usStat );
               set( MAIN.TXT_NumSelected, MUIA_Text_Contents, GL_szNumSelectedStat );
               break;
            case ID_BUT_MAINDELETE:
               get( MAIN.LIS_RecipeList, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a recipe to delete first!", MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                                    "Are you sure you want to delete this item?", NULL ) == 1 )
                  {
                     fnDeleteRecord( lStat );
                  }
               }
               break;

         // ADD WINDOW
            case ID_BUT_ADDADD:
               fnDestructAddWindow( ENDWINDOW_OK );
               break;
            case ID_WIN_CLOSE_ADD:
            case ID_BUT_ADDCANCEL:
               fnDestructAddWindow( ENDWINDOW_CANCEL );
               break;
            case ID_BUT_ADDCATEGORY:
               fnInitCatWindow();
               break;
            case ID_STR_ADDINGREDIENTCHANGE:
               DoMethod( ADD.LIS_Ingredients, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pszStat );
               set( ADD.STR_Item, MUIA_String_Contents, pszStat );
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Item );
               break;
            case ID_STR_ADDITEM:
               get( ADD.LIS_Ingredients, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  lStat = MUIV_List_Insert_Bottom;
               }

               get( ADD.STR_Item, MUIA_String_Contents, &pszStat );
               if ( *pszStat )
               {
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_Remove, lStat );
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_Insert, &pszStat, 1, lStat );
                  set( ADD.STR_Item, MUIA_String_Contents, NULL );
                  set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Item );
               }
               else
               {
                  set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Direction );
               }
               break;
            case ID_BUT_ADDINGREDIENTNEW:
               get( ADD.LIS_Ingredients, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_InsertSingle, "New", MUIV_List_Insert_Bottom );
                  set( ADD.LIS_Ingredients, MUIA_List_Active, MUIV_List_Active_Bottom );
               }
               else
               {
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_InsertSingle, "New", lStat+1 );
                  set( ADD.LIS_Ingredients, MUIA_List_Active, lStat+1 );
               }
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Item );
               break;
            case ID_BUT_ADDINGREDIENTDEL:
               get( ADD.LIS_Ingredients, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a line to delete first!", MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_Remove, MUIV_List_Remove_Active );
               }
               break;
            case ID_BUT_ADDINGREDIENTCLEAR:
               if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                                 "Are you sure you want to clear this list?\n(All items will be erased.)", NULL ) == 1 )
               {
                  DoMethod( ADD.LIS_Ingredients, MUIM_List_Clear, TRUE );
               }
               break;
            case ID_STR_ADDDIRECTIONCHANGE:
               DoMethod( ADD.LIS_Directions, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pszStat );
               set( ADD.STR_Direction, MUIA_String_Contents, pszStat );
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Direction );
               break;
            case ID_STR_ADDDIRECTION:
               get( ADD.LIS_Directions, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  lStat = MUIV_List_Insert_Bottom;
               }

               get( ADD.STR_Direction, MUIA_String_Contents, &pszStat );
               if ( *pszStat )
               {
                  DoMethod( ADD.LIS_Directions, MUIM_List_Remove, lStat );
                  DoMethod( ADD.LIS_Directions, MUIM_List_Insert, &pszStat, 1, lStat );
                  set( ADD.STR_Direction, MUIA_String_Contents, NULL );
                  set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Direction );
               }
               else
               {
                  set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.BUT_Add );
               }
               break;
            case ID_BUT_ADDDIRECTIONNEW:
               get( ADD.LIS_Directions, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  DoMethod( ADD.LIS_Directions, MUIM_List_InsertSingle, "New", MUIV_List_Insert_Bottom );
                  set( ADD.LIS_Directions, MUIA_List_Active, MUIV_List_Active_Bottom );
               }
               else
               {
                  DoMethod( ADD.LIS_Directions, MUIM_List_InsertSingle, "New", lStat+1 );
                  set( ADD.LIS_Directions, MUIA_List_Active, lStat+1 );
               }
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_Direction );
               break;
            case ID_BUT_ADDDIRECTIONDEL:
               get( ADD.LIS_Directions, MUIA_List_Active, &ulStat );
               if ( ulStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a line to delete first!", MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  DoMethod( ADD.LIS_Directions, MUIM_List_Remove, MUIV_List_Remove_Active );
               }
               break;
            case ID_BUT_ADDDIRECTIONCLEAR:
               if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                                 "Are you sure you want to clear this list?\n(All items will be erased.)", NULL ) == 1 )
               {
                  DoMethod( ADD.LIS_Directions, MUIM_List_Clear, TRUE );
               }
               break;
            case ID_BUT_ADDTIMERNEW:
               get( ADD.CYC_TimerType, MUIA_Cycle_Active, &ulStat );
               if ( ulStat == 0 )
               {
                  DoMethod( ADD.LIS_Timers, MUIM_List_InsertSingle,
                            "New                                /PREP/00:00:00", MUIV_List_Insert_Bottom );
               }
               else
               {
                  DoMethod( ADD.LIS_Timers, MUIM_List_InsertSingle,
                            "New                                /BAKE/00:00:00", MUIV_List_Insert_Bottom );
               }
               DoMethod( MG.WIN_Add, MUIM_MultiSet, MUIA_Disabled, FALSE,
                         ADD.BUT_TimerDel,      ADD.BUT_TimerClear,
                         ADD.CHE_AdjustTimer,   ADD.STR_TimerName,
                         ADD.CYC_TimerType,     ADD.SLI_TimerHours,
                         ADD.SLI_TimerMinutes,  ADD.SLI_TimerSeconds,
                         ADD.BUT_TimerUpdate,   NULL );
               set( ADD.LIS_Timers, MUIA_List_Active, MUIV_List_Active_Bottom );
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.STR_TimerName );
               fnRecalcOverallTime();
               break;
            case ID_BUT_ADDTIMERDEL:
               get( ADD.LIS_Timers, MUIA_List_Active, &ulStat );
               if ( ulStat == MUIV_List_Active_Off )
               {
                  JMH_Error( "You must select a line to delete first!", MG.WIN_Add, NULL, NULL, 0 );
               }
               else
               {
                  DoMethod( ADD.LIS_Timers, MUIM_List_Remove, MUIV_List_Remove_Active );
                  get( ADD.LIS_Timers, MUIA_List_Entries, &lStat );
                  if ( lStat == 0 )
                  {
                     DoMethod( MG.WIN_Add, MUIM_MultiSet, MUIA_Disabled, TRUE,
                               ADD.BUT_TimerDel,      ADD.BUT_TimerClear,
                               ADD.CHE_AdjustTimer,   ADD.STR_TimerName,
                               ADD.CYC_TimerType,     ADD.SLI_TimerHours,
                               ADD.SLI_TimerMinutes,  ADD.SLI_TimerSeconds,
                               ADD.BUT_TimerUpdate,   NULL );
                  }
               }
               fnRecalcOverallTime();
               break;
            case ID_BUT_ADDTIMERCLEAR:
               if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                                 "Are you sure you want to clear this list?\n(All items will be erased.)", NULL ) == 1 )
               {
                  // setup timer list
                  DoMethod( ADD.LIS_Timers, MUIM_List_Clear, NULL );
                  set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.BUT_TimerNew );
                  DoMethod( MG.WIN_Add, MUIM_MultiSet, MUIA_Disabled, TRUE,
                            ADD.BUT_TimerDel,      ADD.BUT_TimerClear,
                            ADD.CHE_AdjustTimer,   ADD.STR_TimerName,
                            ADD.CYC_TimerType,     ADD.SLI_TimerHours,
                            ADD.SLI_TimerMinutes,  ADD.SLI_TimerSeconds,
                            ADD.BUT_TimerUpdate,   NULL );
               }
               fnRecalcOverallTime();
               break;
            case ID_BUT_ADDTIMERUPDATE:
            {
               ULONG  ulHours, ulMinutes, ulSeconds, ulType;


               get( ADD.STR_TimerName, MUIA_String_Contents, &pszStat );
               get( ADD.SLI_TimerHours, MUIA_Slider_Level, &ulHours );
               get( ADD.SLI_TimerMinutes, MUIA_Slider_Level, &ulMinutes );
               get( ADD.SLI_TimerSeconds, MUIA_Slider_Level, &ulSeconds );
               get( ADD.CYC_TimerType, MUIA_Cycle_Active, &ulType );
               sprintf( szBuf, "%-35.35s %-4.4s %02u:%02u:%02u",
                               pszStat,
                               Add_PrepOrBakeCYCLabels[ ulType ],
                               ulHours, ulMinutes, ulSeconds );
               get( ADD.LIS_Timers, MUIA_List_Active, &lStat );
               if ( lStat == MUIV_List_Active_Off )
               {
                  lStat = MUIV_List_Insert_Bottom;
               }
               DoMethod( ADD.LIS_Timers, MUIM_List_Remove, lStat );
               DoMethod( ADD.LIS_Timers, MUIM_List_InsertSingle, szBuf, lStat );
               if ( lStat == MUIV_List_Insert_Bottom )
               {
                  set( ADD.LIS_Timers, MUIA_List_Active, MUIV_List_Active_Bottom );
               }
               else
               {
                  set( ADD.LIS_Timers, MUIA_List_Active, lStat );
               }
               set( MG.WIN_Add, MUIA_Window_ActiveObject, ADD.BUT_TimerNew );
               fnRecalcOverallTime();
               break;
            }
            case ID_STR_ADDTIMERLISTCHANGE:
            {
               char   szHours[3], szMinutes[3], szSeconds[3];
               char   szTName[35+1];
               SHORT  s;


               // update sliders and timer name EF and type-cycle
               DoMethod( ADD.LIS_Timers, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pTNode );

               // strip spaces off right end of timer name
               strcpy( szTName, pTNode->szTimerName );
               for ( s = strlen( szTName ) - 1;
                     ( ( s > 0 ) && ( szTName[s] == ' ' ) ); s-- )
               szTName[ s ] = 0;
               set( ADD.STR_TimerName, MUIA_String_Contents, szTName );

               if ( strcmpi( pTNode->szPrepOrBake, "PREP" ) == 0 )
               {
                  set( ADD.CYC_TimerType, MUIA_Cycle_Active, 0 );
               }
               else
               {
                  set( ADD.CYC_TimerType, MUIA_Cycle_Active, 1 );
               }

               strncpy( szHours,   &(pTNode->szHMSTime[0]), 2 );
               szHours[2] = 0;
               strncpy( szMinutes, &(pTNode->szHMSTime[3]), 2 );
               szMinutes[2] = 0;
               strncpy( szSeconds, &(pTNode->szHMSTime[6]), 2 );
               szSeconds[2] = 0;
               set( ADD.SLI_TimerHours,   MUIA_Slider_Level, atol( szHours   ) );
               set( ADD.SLI_TimerMinutes, MUIA_Slider_Level, atol( szMinutes ) );
               set( ADD.SLI_TimerSeconds, MUIA_Slider_Level, atol( szSeconds ) );
               break;
            }

         // CATEGORY WINDOW
            case ID_WIN_CLOSE_CATEGORY:
            case ID_BUT_CATCANCEL:
               fnDestructCatWindow( ENDWINDOW_CANCEL );
               break;
            case ID_LIS_CATLISTCHANGE:
               DoMethod( CAT.LIS_Categories, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pszStat );
               set( CAT.STR_Category, MUIA_Text_Contents, pszStat );
               break;
            case ID_BUT_CATSELECT:
            case ID_LIS_CATCATEGORIES:
               fnDestructCatWindow( ENDWINDOW_OK );
               break;

         // MAKE WINDOW
            case ID_LIS_MAKERECIPECHANGE:
               DoMethod( MAKE.LIS_Recipes, MUIM_List_GetEntry, MUIV_List_GetEntry_Active, &pMNode );
               set( MAKE.STR_Servings, MUIA_String_Integer, atol( pMNode->szNewServings ) );
               set( MG.WIN_Make, MUIA_Window_ActiveObject, MAKE.STR_Servings );
               break;
            case ID_BUT_MAKEGROCLIST:
               fnInitGrocListWindow();
               break;
            case ID_STR_MAKENEWSERVINGS:
               get( MAKE.STR_Servings, MUIA_String_Contents, &pszStat );
               if ( ( strcmp( pszStat, "" ) == 0 ) || ( atoi( pszStat ) == 0 ) )
               {
                  JMH_Error( "Servings must be 1 or greater.", MG.WIN_Make, NULL, NULL, 0 );
               }
               else
               {
                  DoMethod( MAKE.LIS_Recipes, MUIM_List_GetEntry, MUIV_List_GetEntry_Active, &pMNode );
                  sprintf( pMNode->szNewServings, "%3u", atoi( pszStat ) );
                  set( MG.WIN_Make, MUIA_Window_ActiveObject, MAKE.STR_Servings );
                  if ( atoi( pMNode->szNewServings ) != atoi( pMNode->szStdServings ) )
                  {
                     char  szQuery[200];


                     sprintf( szQuery, "The original recipe called for %s servings.\nYou have specified %s servings.\n\nDo you wish to adjust the cooking time for this recipe?",
                                       pMNode->szStdServings,
                                       pMNode->szNewServings );
                     if ( MUI_Request( MG.App, MG.WIN_Make, 0, NULL, "Yes|*No",
                                       szQuery, NULL ) == 1 )
                     {
                        pMNode->fAdjustTimes = TRUE;
                     }
                  }
                  DoMethod( MAKE.LIS_Recipes, MUIM_List_Redraw, MUIV_List_Redraw_Active );
               }
               break;
            case ID_BUT_MAKEOUTPUT:
               get( MAKE.MX_Output, MUIA_Radio_Active, &ulStat );
               switch( ulStat )
               {
                  case 0:  // Printer
                     ulStat = MUI_Request( MG.App, MG.WIN_Make, 0, NULL, "*Selected|All",
                                           "Print selected recipe or all recipes?", NULL );
                     fhPRT = NULL;
                     fhPRT = Open( "PRT:", MODE_OLDFILE );
                     if ( fhPRT )
                     {
                        set( MG.App, MUIA_Application_Sleep, TRUE );

                        if ( ulStat == 1 )
                        {
                           get( MAKE.LIS_Recipes, MUIA_List_Active, &lStat );
                           ulStat = lStat + 1;
                        }
                        else
                        {
                           get( MAKE.LIS_Recipes, MUIA_List_Entries, &ulStat );
                        }

                        for ( lStat = 0; lStat < ulStat; lStat++ )
                        {
                           DoMethod( MAKE.LIS_Recipes, MUIM_List_GetEntry, lStat, &pMNode );
                           if ( pMNode )
                           {
                              // get ingredients list
                              DoMethod( MAIN.LIS_RecipeList, MUIM_List_GetEntry, pMNode->ulMainListIndex, &pRMNode );
                              pMemRecord = fnFetchRecord( pRMNode->ulDiskAddr );
                              if ( pMemRecord )
                              {
                                 char   szPrintBuf[161], szFractionBuf[20];
                                 UBYTE *psz, *psz2;
                                 float  fl;


                                 sprintf( szPrintBuf, "%-30.30s   %-30.30s   %-12.12s\n",
                                                      pRMNode->pszRecipeName,
                                                      pRMNode->pszCategoryName,
                                                      Details_CourseCYCLabels[ pRMNode->ubCourse ] );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }
                                 sprintf( szPrintBuf, "Source: %-15.15s                                       Servings: %3u\n\n",
                                                      pRMNode->pszSource,
                                                      atoi( pMNode->szNewServings ) );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }
                                 fnFindRecordItem( pMemRecord, RECITEM_COMMENTS, szBuf );
                                 sprintf( szPrintBuf, "%-70.70s\n--------------------------------------------------------------------------\n\n", szBuf );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }
                                 sprintf( szPrintBuf, "Preparation: %-13.13s              Cost:  %-11.11s\n",
                                                      Find_PrepCYCLabels[ pRMNode->ubPrepRating ],
                                                      Find_CostCYCLabels[ pRMNode->ubCostRating ] );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }
                                 sprintf( szPrintBuf, "       Oven: %-13.13s              Taste: %-13.13s\n\nIngredients:\n",
                                                      Add_OvenCYCLabels[ pRMNode->ubOven ],
                                                      Find_TasteCYCLabels[ pRMNode->ubTasteRating ] );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }

                                 // find start of ingredients list...
                                 if ( pszStart = (char *)fnFindRecordItem( pMemRecord, RECITEM_INGREDIENTS, NULL ) )
                                 {
                                    pszEnd = strchr( pszStart, LEVEL1BREAK );

                                    //
                                    // read ingredients and print
                                    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                    for ( psz = pszStart; psz < ( pszEnd-1 ); )
                                    {
                                       psz2 = strchr( psz+1, LEVEL2BREAK );
                                       if ( psz2 )
                                       {
                                          *psz2 = 0;

                                          //
                                          // multiplier
                                          // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                          fl = fnExtractValue( pMNode->szNewServings );
                                          fl *= fnExtractValue( psz );
                                          fl /= fnExtractValue( pMNode->szStdServings );
                                          fnFloat2Frac( fl, szFractionBuf );
                                          sprintf( szPrintBuf, "%s %s\n", szFractionBuf, fnSkipQuantity( psz ) );

                                          lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                          if ( lDiskStat != strlen( szPrintBuf ) )
                                          {
                                             fPrintError = TRUE;
                                             break;
                                          }

                                          *psz2 = LEVEL2BREAK;
                                          psz = psz2+1;
                                       }
                                    }
                                 }

                                 strcpy( szPrintBuf, "\nDirections:\n" );
                                 lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                 if ( lDiskStat != strlen( szPrintBuf ) )
                                 {
                                    fPrintError = TRUE;
                                    break;
                                 }

                                 // find start of directions list...
                                 if ( pszStart = (char *)fnFindRecordItem( pMemRecord, RECITEM_DIRECTIONS, NULL ) )
                                 {
                                    pszEnd = strchr( pszStart, LEVEL1BREAK );

                                    //
                                    // read directions and print
                                    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                    for ( psz = pszStart; psz < ( pszEnd-1 ); )
                                    {
                                       psz2 = strchr( psz+1, LEVEL2BREAK );
                                       if ( psz2 )
                                       {
                                          *psz2 = 0;

                                          sprintf( szPrintBuf, "%s\n", psz );
                                          lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                          if ( lDiskStat != strlen( szPrintBuf ) )
                                          {
                                             fPrintError = TRUE;
                                             break;
                                          }

                                          *psz2 = LEVEL2BREAK;
                                          psz = psz2+1;
                                       }
                                    }
                                 }

                                 get( SETTINGS.CHE_PrinterFormFeed, MUIA_Selected, &ulFormFeed );
                                 if ( ulFormFeed == TRUE )
                                 {
                                    sprintf( szPrintBuf, "%c", 12 );
                                    lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                                    if ( lDiskStat != strlen( szPrintBuf ) )
                                    {
                                       fPrintError = TRUE;
                                       break;
                                    }
                                 }

                                 FreeVec( pMemRecord );
                              }
                           }
                        }

                        //
                        // printer error?
                        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        if ( fPrintError )
                        {
                           Fault( IoErr(),
                                  "Printer Error!\n",
                                  GL_szFault,
                                  sizeof( GL_szFault ) );
                           JMH_Error( GL_szFault, MG.WIN_Make, NULL, NULL, 0 );
                        }

                        //
                        // close printer 'channel': C64!!!
                        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        if ( fhPRT )
                        {
                           Close( fhPRT );
                           fhPRT = NULL;
                        }

                        set( MG.App, MUIA_Application_Sleep, FALSE );
                     }
                     else
                     {
                        JMH_Error( "Printer Error!\nCouldn't get access to printer.", MG.WIN_Make, NULL, NULL, 0 );
                     }
                     break;
//                  case 2:  // Remote
//MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "*OK",
//             "MAKE/OUTPUT-REMOTE goes here...", NULL );
//                     break;
                  default:
                  case 1:  // Local
                     fnInitMakeLocalWindow();
                     break;
               }
               break;
            case ID_WIN_CLOSE_MAKE:
            case ID_BUT_MAKECANCEL:
               fnDestructMakeWindow( ENDWINDOW_CANCEL );
               break;

         // FIND WINDOW
            case ID_BUT_FINDFIND:
               lStat = fnFind( 0 );
               if ( lStat != -1 )
               {
                  GL_lFindStart = lStat;
                  set( MG.WIN_Find, MUIA_Window_ActiveObject, FIND.BUT_FindNext );
                  set( MG.WIN_Find, MUIA_Window_DefaultObject, FIND.BUT_FindNext );
               }
               else
               {
                  JMH_Error( "No match for search criteria.", MG.WIN_Main, NULL, NULL, 0 );
               }
               break;
            case ID_BUT_FINDCLEAR:
               set( FIND.CHE_RecipeName,  MUIA_Selected, FALSE );
               set( FIND.CHE_Comment,     MUIA_Selected, FALSE );
               set( FIND.CHE_Course,      MUIA_Selected, FALSE );
               set( FIND.CHE_Category,    MUIA_Selected, FALSE );
               set( FIND.CHE_Source,      MUIA_Selected, FALSE );
               set( FIND.CHE_Serves,      MUIA_Selected, FALSE );
               set( FIND.CHE_Preparation, MUIA_Selected, FALSE );
               set( FIND.CHE_Cost,        MUIA_Selected, FALSE );
               set( FIND.CHE_Taste,       MUIA_Selected, FALSE );
               set( FIND.CHE_Oven,        MUIA_Selected, FALSE );
               set( FIND.CHE_Ingredient,  MUIA_Selected, FALSE );
               set( FIND.CHE_Time,        MUIA_Selected, FALSE );
               set( MG.WIN_Find, MUIA_Window_ActiveObject, FIND.CHE_RecipeName );
               break;
            case ID_WIN_CLOSE_FIND:
            case ID_BUT_FINDCANCEL:
               fnDestructFindWindow( ENDWINDOW_CANCEL );
               break;

         // REORG WINDOW
            case ID_BUT_REORGREORG:
            {
               BPTR   fhNew = NULL, fhOld = NULL;
               char   szOldFile[FMSIZE+1];
               UBYTE *pDiskBufferIn = NULL, *pDiskBufferOut = NULL;
               LONG   l1, l2, lDoneSoFar, lReadSize = 0;
               BOOL   fReOrgSuccess = FALSE;


               set( REORG.BUT_ReOrg, MUIA_Disabled, TRUE );
               set( REORG.BUT_Cancel, MUIA_Disabled, TRUE );

               pDiskBufferIn = AllocVec( 16384, MEMF_ANY );
               if ( !pDiskBufferIn )
               {
                  JMH_Error( "Cannot allocate backup memory buffer.\nReorganization failed.", MG.WIN_Main, NULL, NULL, 0 );
                  break;
               }

               pDiskBufferOut = AllocVec( 16384, MEMF_ANY );
               if ( !pDiskBufferOut )
               {
                  JMH_Error( "Cannot allocate backup memory buffer.\nReorganization failed.", MG.WIN_Main, NULL, NULL, 0 );
                  FreeVec( pDiskBufferIn );
                  break;
               }

               // close the data file first...
               Close( MG.fhRecipes );  MG.fhRecipes = NULL;

               get( REORG.POP_ToReOrg, MUIA_String_Contents, &pszStat );
               strcpy( szBuf, pszStat );

               // get name of backup copy (destination)
               strcpy( szOldFile, szBuf );
               AddPart( szOldFile, "robocook.data", sizeof( szOldFile ) );

               sprintf( szTool, "copy \"%s\" TO \"%s\"", MG.szRecipeFilespec, szBuf );
               lStat = SystemTags( szTool, SYS_Asynch, FALSE, TAG_DONE, TAG_DONE );
               if ( lStat != 0 )
               {
                  if ( lStat > 0 )
                     Fault( IoErr(),
                            "Cannot make a backup copy of recipe data file because",
                            GL_szFault,
                            sizeof( GL_szFault ) );
                  else
                     strcpy( GL_szFault, "Couldn't make a backup copy of recipe data file.\nReorganization cancelled." );

                  JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
               }
               else
               {
                  if ( !DeleteFile( MG.szRecipeFilespec ) )
                  {
                     JMH_Error( "File error!\nCould not delete old data file.\nReorganization failed.", MG.WIN_Main, NULL, NULL, 0 );
                  }
                  else
                  {
                     fhOld = Open( szOldFile, MODE_OLDFILE );
                     fhNew = Open( MG.szRecipeFilespec, MODE_NEWFILE );
                     if ( !fhOld || !fhNew )
                     {
                        if ( fhOld )
                        {
                           Close( fhOld );   fhOld = NULL;
                        }
                        if ( fhNew )
                        {
                           Close( fhNew );   fhNew = NULL;
                        }

                        JMH_Error( "File error!\nCould not open new data files.\nReorganization failed.", MG.WIN_Main, NULL, NULL, 0 );

                        //
                        // copy it back; if can't, warn user that data
                        //               file is in backup location...
                        // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                        sprintf( szTool, "copy \"%s\" TO \"%s\"", szOldFile, MG.szRecipeFilespec );
                        lStat = SystemTags( szTool, SYS_Asynch, FALSE, TAG_DONE, TAG_DONE );
                        if ( lStat != 0 )
                        {
                           JMH_Error( "Couldn't restore data file to original location.\nFile is located on destination you specified.\nReorganization failed.", MG.WIN_Main, NULL, NULL, 0 );
                        }
                     }
                     else
                     {
                        for( lDoneSoFar = 0, lDiskStat = 1; lDiskStat > 0; )
                        {
                           lDiskStat = Read( fhOld, pDiskBufferIn, 16384 );
                           if ( lDiskStat <= 0 )
                           {
                              break;
                           }
                           else
                           {
                              lReadSize = lDiskStat;
                              for ( l1=0, l2=0; l1 < lReadSize; l1++ )
                              {
                                 if ( *( pDiskBufferIn+l1) != NULLSPACE )
                                 {
                                    *( pDiskBufferOut+l2 ) = *( pDiskBufferIn+l1 );
                                    l2++;
                                 }
                              }
                              if ( l2 > 0 )
                              {
                                 lDiskStat = Write( fhNew, pDiskBufferOut, l2 );
                                 if ( lDiskStat != l2 )
                                 {
                                    JMH_Error( "Cannot write data to new file.\nReorganization failed.\nOld file is in the backup location.", MG.WIN_Main, NULL, NULL, 0 );
                                    break;
                                 }
                                 else
                                 {
                                    lDoneSoFar += lReadSize;
                                    ulStat = 100 * (float)( (float)lDoneSoFar / (float)MG.ulTotalFileSize );
                                    set( REORG.GAU_Progress, MUIA_Gauge_Current, ulStat );
                                 }
                              }
                           }
                        }
                        if ( lDiskStat < 0 )
                        {
                           Fault( IoErr(),
                                  "Could not write file status because",
                                  GL_szFault,
                                  sizeof( GL_szFault ) );
                           JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
                        }
                        else
                        {
                           //
                           // mark backup file as 'OLD'
                           // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                           Seek( fhOld, strlen( FILEID_HEADER ), OFFSET_BEGINNING );
                           lDiskStat = Write( fhOld, FILEID_OLD, strlen( FILEID_OLD ) );
                           if ( lDiskStat != strlen( FILEID_OLD ) )
                           {
                              Fault( IoErr(),
                                     "Could not mark backup file as 'OLD' because",
                                     GL_szFault,
                                     sizeof( GL_szFault ) );
                              JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
                           }
                           else
                           {
                              fReOrgSuccess = TRUE;
                           }
                        }
                     }

                     MG.ulTotalFileSize = Seek( fhNew, 0, OFFSET_END );
                     MG.ulNullSpaceSize = 0;

                     if ( fhOld )
                     {
                        Close( fhOld );   fhOld = NULL;
                     }
                     if ( fhNew )
                     {
                        Close( fhNew );   fhNew = NULL;
                     }
                  }
               }

               FreeVec( pDiskBufferOut );
               FreeVec( pDiskBufferIn );

               set( REORG.BUT_ReOrg, MUIA_Disabled, FALSE );
               set( REORG.BUT_Cancel, MUIA_Disabled, FALSE );
               set( MG.App, MUIA_Application_Sleep, FALSE );

               if ( fReOrgSuccess == TRUE )
               {
                  MUI_Request( MG.App, MG.WIN_ReOrg, 0, NULL, "*OK",
                               "File reorganization successful!", NULL );

                  DoMethod( MAIN.LIS_RecipeList, MUIM_List_Clear, TRUE );

                  //
                  // set window to sleep while loading recipe data...
                  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  set( MG.WIN_Main, MUIA_Window_Sleep, TRUE );
                  set( MAIN.LIS_RecipeList, MUIA_List_Quiet, TRUE );
                  fSuccess = fnReadRecipeData( TRUE );
                  if ( fSuccess == FALSE )
                  {
                     JMH_Quit( "Couldn't read recipe data file." );
                  }
                  set( MAIN.LIS_RecipeList, MUIA_List_Quiet, FALSE );
                  set( MG.WIN_Main, MUIA_Window_Sleep, FALSE );

                  //
                  // activate first item in listview
                  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  set( MAIN.LIS_RecipeList, MUIA_List_Active, MUIV_List_Active_Top );

                  fnDestructReOrgWindow( ENDWINDOW_OK );
               }
               break;
            }
            case ID_WIN_CLOSE_REORG:
            case ID_BUT_REORGCANCEL:
               fnDestructReOrgWindow( ENDWINDOW_CANCEL );
               break;

         // TIMER WINDOW
            case ID_CHE_TIMERPAUSE:
               pTDNode = NULL;
               DoMethod( TIMER.LIS_TimerList, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pTDNode );
               if ( pTDNode )
               {
                  get( TIMER.CHE_Pause, MUIA_Selected, &ulStat );
                  pTDNode->ubPaused = ulStat;
               }
               break;
            case ID_BUT_TIMERREMOVE:
               if ( MUI_Request( MG.App, MG.WIN_Timer, 0, NULL, "Yes|*No",
                                 "Are you sure you want to delete this item?", NULL ) == 1 )
               {
                  DoMethod( TIMER.LIS_TimerList, MUIM_List_Remove, MUIV_List_Remove_Active );
               }
//fnRemoveTimerSignal();
               break;
            case ID_BUT_TIMERALARMCONFIG:
               set( SETTINGS.REG_Page, MUIA_Group_ActivePage, 0 );
               fnInitSettingsWindow();
               break;
            case ID_LIS_TIMERLISTCHANGE:
               pTDNode = NULL;
               DoMethod( TIMER.LIS_TimerList, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pTDNode );
               if ( pTDNode )
               {
                  set( TIMER.CHE_Pause, MUIA_Selected, pTDNode->ubPaused );
               }
               break;
            case ID_WIN_CLOSE_TIMER:
            case ID_BUT_TIMERCANCEL:
               set( MAKELOCL.CHE_OpenTimerWindow, MUIA_Selected, FALSE );
               fnDestructTimerWindow( ENDWINDOW_CANCEL );
               break;

         // SPEAKER EXCEPTIONS WINDOW
            case ID_LIS_SPEAKXPLIST:
               DoMethod( SPEAKXP.LIS_SpeakerExceptions, MUIM_List_GetEntry,
                         MUIV_List_GetEntry_Active, &pszStart );
               pszEnd = strchr( pszStart, ',' );
               if ( pszEnd )
               {
                  *pszEnd = 0;
                  set( SPEAKXP.STR_From, MUIA_String_Contents, pszStart );
                  set( SPEAKXP.STR_To, MUIA_String_Contents, pszEnd+1 );
                  *pszEnd = ',';
               }
               break;
            case ID_BUT_SPEAKXPNEW:
               DoMethod( SPEAKXP.LIS_SpeakerExceptions, MUIM_List_Insert, &pszNewNew, 1, MUIV_List_Insert_Bottom );
               set( SPEAKXP.LIS_SpeakerExceptions, MUIA_List_Active, MUIV_List_Active_Bottom );

               set( SPEAKXP.BUT_Delete, MUIA_Disabled, FALSE );
               set( SPEAKXP.BUT_Test,   MUIA_Disabled, FALSE );
               set( SPEAKXP.BUT_Save,   MUIA_Disabled, FALSE );
               set( SPEAKXP.STR_From,   MUIA_Disabled, FALSE );
               set( SPEAKXP.STR_To,     MUIA_Disabled, FALSE );

               set( MG.WIN_SpeakXP, MUIA_Window_ActiveObject, SPEAKXP.STR_From );
               break;
            case ID_BUT_SPEAKXPDELETE:
               if ( MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "Yes|*No",
                                 "Are you sure you want to delete this item?", NULL ) == 1 )
               {
                  DoMethod( SPEAKXP.LIS_SpeakerExceptions, MUIM_List_Remove, MUIV_List_Remove_Active );

                  // do we have any items left in the list?
                  get( SPEAKXP.LIS_SpeakerExceptions, MUIA_List_Entries, &ulStat );
                  if ( ulStat == 0 )
                  {
                     // disable Delete, Test, Save and Use
                     set( SPEAKXP.BUT_Delete, MUIA_Disabled, TRUE );
                     set( SPEAKXP.BUT_Test,   MUIA_Disabled, TRUE );
                     set( SPEAKXP.BUT_Save,   MUIA_Disabled, TRUE );
                     set( SPEAKXP.STR_From,   MUIA_Disabled, TRUE );
                     set( SPEAKXP.STR_To,     MUIA_Disabled, TRUE );
                  }
               }
               break;
            case ID_BUT_SPEAKXPTEST:
               get( SPEAKXP.LIS_SpeakerExceptions, MUIA_List_Active, MUIV_List_Active_Top );
MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "*OK",
             "SPEAKXP/TEST goes here...", NULL );
               break;
            case ID_STR_SPEAKXPFROM:
            case ID_STR_SPEAKXPTO:
               get( SPEAKXP.STR_From, MUIA_String_Contents, &pszStat );
               get( SPEAKXP.LIS_SpeakerExceptions, MUIA_List_Active, &lStat );
               strcpy( GL_szBuf, pszStat );
               strcat( GL_szBuf, "," );
               get( SPEAKXP.STR_To, MUIA_String_Contents, &pszStat );
               strcat( GL_szBuf, pszStat );
               pszStat = (char *)&GL_szBuf;
               DoMethod( SPEAKXP.LIS_SpeakerExceptions, MUIM_List_Remove, lStat );
               DoMethod( SPEAKXP.LIS_SpeakerExceptions, MUIM_List_Insert, &pszStat, 1, lStat );
               set( SPEAKXP.LIS_SpeakerExceptions, MUIA_List_Active, lStat );
               break;
            case ID_BUT_SPEAKXPSAVE:
               fnWriteListToDisk( "speakxp.list", SPEAKXP.LIS_SpeakerExceptions );
               fnDestructSpeakXPWindow( ENDWINDOW_OK );
               break;
            case ID_WIN_CLOSE_SPEAKXP:
            case ID_BUT_SPEAKXPCANCEL:
               fnDestructSpeakXPWindow( ENDWINDOW_CANCEL );
               break;

         // GROCERY LIST WINDOW
            case ID_BUT_GROCLISTPRINT:
               fhPRT = Open( "PRT:", MODE_OLDFILE );
               if ( fhPRT )
               {
                  char   szPrintBuf[81];


                  set( MG.App, MUIA_Application_Sleep, TRUE );

                  strcpy( szPrintBuf, "Grocery List\n------------------\n\n" );
                  lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                  if ( lDiskStat != strlen( szPrintBuf ) )
                  {
                     Fault( IoErr(),
                            "Printer Error!\n",
                            GL_szFault,
                            sizeof( GL_szFault ) );
                     JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
                  }
                  else
                  {
                     get( GROCLIST.LIS_GroceryList, MUIA_List_Entries, &ulStat );
                     for ( lStat = 0; lStat < ulStat; lStat++ )
                     {
                        DoMethod( GROCLIST.LIS_GroceryList, MUIM_List_GetEntry, lStat, &pszStat );
                        sprintf( szPrintBuf, "%s\n", pszStat );
                        lDiskStat = Write( fhPRT, szPrintBuf, strlen( szPrintBuf ) );
                        if ( lDiskStat != strlen( szPrintBuf ) )
                        {
                           Fault( IoErr(),
                                  "Printer Error!\n",
                                  GL_szFault,
                                  sizeof( GL_szFault ) );
                           JMH_Error( GL_szFault, MG.WIN_Main, NULL, NULL, 0 );
                        }
                     }
                  }
                  Close( fhPRT );

                  set( MG.App, MUIA_Application_Sleep, FALSE );
               }
               else
               {
                  JMH_Error( "Printer Error!\nCouldn't get access to printer.", MG.WIN_Main, NULL, NULL, 0 );
               }
               break;
            case ID_WIN_CLOSE_GROCLIST:
            case ID_BUT_GROCLISTCANCEL:
               fnDestructGrocListWindow( ENDWINDOW_CANCEL );
               break;

         // SETTINGS WINDOW
            case ID_BUT_SETTINGSSAVE:
               DoMethod( MG.App, MUIM_Application_Save, MUIV_Application_Save_ENVARC );
               fnDestructSettingsWindow( ENDWINDOW_OK );
               break;
            case ID_BUT_SETTINGSUSE:
               DoMethod( MG.App, MUIM_Application_Save, MUIV_Application_Save_ENV );
               fnDestructSettingsWindow( ENDWINDOW_OK );
               break;
            case ID_WIN_CLOSE_SETTINGS:
            case ID_BUT_SETTINGSCANCEL:
               fnDestructSettingsWindow( ENDWINDOW_CANCEL );
               break;

         // MAKELOCL WINDOW
            case ID_BUT_MAKELOCLPREVRECIPE:
               get( MAKE.LIS_Recipes, MUIA_List_Active, &lStat );
               set( MAKE.LIS_Recipes, MUIA_List_Active, lStat-1 );
               set( MG.WIN_Make, MUIA_Window_Sleep, FALSE );
               set( MG.WIN_Main, MUIA_Window_Sleep, FALSE );
               fnInitMakeLocalWindow();
               break;
            case ID_BUT_MAKELOCLNEXTRECIPE:
               get( MAKE.LIS_Recipes, MUIA_List_Active, &lStat );
               set( MAKE.LIS_Recipes, MUIA_List_Active, lStat+1 );
               set( MG.WIN_Make, MUIA_Window_Sleep, FALSE );
               set( MG.WIN_Main, MUIA_Window_Sleep, FALSE );
               fnInitMakeLocalWindow();
               break;
            case ID_BUT_MAKELOCLSTARTTIMER:
MUI_Request( MG.App, MG.WIN_Main, 0, NULL, "*OK",
             "MAKELOCL/STARTTIMER goes here...", NULL );
               break;
            case ID_WIN_CLOSE_MAKELOCL:
            case ID_BUT_MAKELOCLCANCEL:
               fnDestructMakeLocalWindow( ENDWINDOW_CANCEL );
               break;
      }
      if (running && signals) Wait(signals);
   }

   set( MG.WIN_Main, MUIA_Window_Open, FALSE );
   JMH_Quit( NULL );
}

