/*
 * HHDL: Half-Half DataList class
 *       (C) 1994 John M Haubrich Jr
 *                All Rights Under Copyright Reserved
 *
 * MUI List subclass which handles data storage in memory and on disk.
 *
 */

#include "common.h"


/** BELOW: for demo program **/
struct HHdata {
   char  *pszString;
   UWORD  uw;
   BYTE   byte;
};


/*************************/
/* Init & Fail Functions */
/*************************/
VOID fail(APTR app2,char *str)
{
	if (app2)
		MUI_DisposeObject(app2);

	if (MUIMasterBase)
		CloseLibrary(MUIMasterBase);

	if (str)
	{
		puts(str);
		exit(20);
	}
	exit(0);
}

VOID init(VOID)
{
	if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME,MUIMASTER_VMIN)))
		fail(NULL,"Failed to open "MUIMASTER_NAME".");
}
/** ABOVE: for demo program **/


struct Library       *DosBase;
struct IntuitionBase *IntuitionBase;
struct GfxBase       *GfxBase;
struct Library       *GadToolsBase;
struct Library       *AslBase;
struct Library       *UtilityBase;
struct Library       *MUIMasterBase;


/*
 * LIST HOOK FUNCTIONS ------------------------------
 */
//
// Since we can construct from objects or a string, we'll assume objects
// are translated by insertion routine into a suitable string first...
//
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SAVEDS ASM APTR fnMainConstructFunc(REG(a0) struct Hook *hook, REG(a2) APTR mempool, REG(a1) char *pszDiskRecord )
{
   ULONG   ul, ulSize, ulLen, ulStringPos, ulOffset, ulStore;
   ULONG   lPointersArray[256];  // store pointers to strings to be copied
                                 // to end of node structure... limit = 256
   ULONG   lNodeIndexArray[256]; // corresponding index into node
   UBYTE   ubPArrayIndex;        // index into lPointersArray[].
   char   *pNew;
   char   *pszStart, *pszEnd;

   struct MyData *data;


   data   = (struct MyData *)hook->h_Data;

   //
   // start at size field and run along ListSpec to get total MEMORY size
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   ulStringPos = 4;  // position inside insertion string;
                     // skip over disk addr
   ulLen = 0;        // total size of memory needed for this node
   for ( ul = 10; ul < strlen( data->pszListSpec ); ul += 13 )
   {
      ulSize = ( *( (data->pszListSpec)+ul ) - '0' );
      switch ( *( (data->pszListSpec)+ul-9 ) )
      {
         case 'A':
            if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
            {
               switch( ulSize )
               {
                  case 1:  ulLen +=  4;   break;  //  3 digits+NULL
                  case 2:  ulLen +=  6;   break;  //  5 digits+NULL
                  case 4:  ulLen += 11;   break;  // 10 digits+NULL
               }
            }
            ulStringPos += ulSize;
            break;
         case 'S':
            if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
            {
               switch( ulSize )
               {
                  case 1:  ulLen +=  5;   break;  // sign+ 3 digits+NULL
                  case 2:  ulLen +=  7;   break;  // sign+ 5 digits+NULL
                  case 4:  ulLen += 12;   break;  // sign+10 digits+NULL
               }
            }
            ulStringPos += ulSize;
            break;
         case 'P':
            if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
            {
               ulLen += ulSize;
               pszStart = pszDiskRecord + ulStringPos;
               pszEnd = strchr( pszStart, LEVEL1BREAK );
               if ( pszEnd )
               {
                  ulLen += ( pszEnd - pszStart + 1 );
                  ulStringPos += ( pszEnd - pszStart + 1 );
               }
               else
               {
                  // =====================================================
                  // ERROR!   There should have been a LEVEL1BREAK
                  //          at the end of this string!
                  // =====================================================
                  return( NULL );
               }
            }
            break;
         case 'I':
         case 'N':
            if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
               ulLen += ulSize;
            ulStringPos += ulSize;
            break;
      }
   }

   if ( pNew = AllocVec( ulLen, MEMF_ANY | MEMF_CLEAR ) )
   {
      //
      // Run through ListSpec again and collect data into this memory area
      // If data is a pointer-to-string, store pointer for later insertion
      // at tail of structure since we don't know where the end of the
      // structure is yet!!!
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      memset( lPointersArray, -1, sizeof( lPointersArray ) );
      ulStringPos = 4;  // position inside insertion string;
                        // skip over disk addr
      ulOffset = 4;     // BYTE offset into NEW data area...
                        // skip first four bytes; this is for disk addr
      ubPArrayIndex = 0;
      for ( ul = 10; ul < strlen( data->pszListSpec ); ul += 13 )
      {
         ulSize = ( *( (data->pszListSpec)+ul ) - '0' );
         switch ( *( (data->pszListSpec)+ul-9 ) )
         {
            case 'A':
               if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
               {
                  switch ( ulSize )
                  {
                     case 1:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%04.4d", *( pszDiskRecord + ulStringPos ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( pszDiskRecord + ulStringPos ) );
                        }
                        ulOffset += 4;
                        break;
                     case 2:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%06.6d", *( (UWORD *)(pszDiskRecord + ulStringPos) ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( (UWORD *)(pszDiskRecord + ulStringPos) ) );
                        }
                        ulOffset += 6;
                        break;
                     case 4:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%011.11d", *( (ULONG *)(pszDiskRecord + ulStringPos) ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( (ULONG *)(pszDiskRecord + ulStringPos) ) );
                        }
                        ulOffset += 11;
                        break;
                  }
               }
               ulStringPos += ulSize;
               break;
            case 'S':
               if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
               {
                  switch ( ulSize )
                  {
                     case 1:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%05.5d", *( pszDiskRecord + ulStringPos ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( pszDiskRecord + ulStringPos ) );
                        }
                        ulOffset += 5;
                        break;
                     case 2:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%07.7d", *( (UWORD *)(pszDiskRecord + ulStringPos) ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( (UWORD *)(pszDiskRecord + ulStringPos) ) );
                        }
                        ulOffset += 7;
                        break;
                     case 4:
                        if ( *( (data->pszListSpec)+ul+0 ) == '0' )
                        {
                           sprintf( (pNew + ulOffset), "%012.12d", *( (ULONG *)(pszDiskRecord + ulStringPos) ) );
                        }
                        else
                        {
                           sprintf( (pNew + ulOffset), "%d", *( (ULONG *)(pszDiskRecord + ulStringPos) ) );
                        }
                        ulOffset += 12;
                        break;
                  }
               }
               ulStringPos += ulSize;
               break;
            case 'P':
               if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
               {
                  // size MUST be 4
                  *( (ULONG *)(pNew + ulOffset) ) = 0;

                  lPointersArray[ ubPArrayIndex ] = ulStringPos;
                  lNodeIndexArray[ ubPArrayIndex++ ] = ulOffset;

                  pszStart = pszDiskRecord + ulStringPos;
                  pszEnd = strchr( pszStart, LEVEL1BREAK );
                  if ( pszEnd )
                  {
                     ulStringPos += ( pszEnd - pszStart + 1 );
                  }
                  else
                  {
                     // =====================================================
                     // ERROR!   There should have been a LEVEL1BREAK
                     //          at the end of this string!
                     // =====================================================
                     return( NULL );
                  }
               }
               ulOffset += ulSize;
               break;
            case 'I':
            case 'N':
               if ( *( (data->pszListSpec)+ul+1 ) == 'M' )
               {
                  switch ( ulSize )
                  {
                     case 1:
                        *( pNew + ulOffset ) = *( pszDiskRecord + ulStringPos );
                        break;
                     case 2:
                        *( (UWORD *)(pNew + ulOffset) ) = *( (UWORD *)(pszDiskRecord + ulStringPos) );
                        break;
                     case 4:
                        *( (ULONG *)(pNew + ulOffset) ) = *( (ULONG *)(pszDiskRecord + ulStringPos) );
                        break;
                  }
               }
               ulStringPos += ulSize;
               ulOffset += ulSize;
               break;
         }
      }

      //
      // now insert the strings into the NEW node and link pointers
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ul = 0; ul < (ULONG)ubPArrayIndex; ul++ )
      {
         pszStart = ( pszDiskRecord + lPointersArray[ ul ] );
         pszEnd   = strchr( pszStart, LEVEL1BREAK );
         strncpy( (pNew + ulOffset), pszStart, (pszEnd - pszStart) );
         ulStore = ulOffset;
         ulOffset += (pszEnd - pszStart);
         *( pNew + ulOffset++ ) = 0;
         *( (ULONG *)( pNew + lNodeIndexArray[ ul ] ) ) = ulStore;
      }

      //
      // get disk address for this record...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      *( (ULONG *)pNew ) = *( (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) char *pszNode1, REG(a2) char *pszNode2 )
{
   LONG   rc = 0;    // assume equality for now...
   ULONG  ul, ul2, ulFieldNum, ulSize;
   UBYTE  ubType, ubLen;
   char   szBuf[5];

   struct MyData *data;


   data   = (struct MyData *)hook->h_Data;

   for ( ul = 0; ul < strlen( data->pszSortSpec ); ul += 3 )
   {
      sprintf( szBuf, "%-3.3s", (data->pszSortSpec)+ul );
      ulFieldNum = atol( &szBuf[1] );
      ubType = *( data->pszListSpec + 13*ulFieldNum +  1 );
      ubLen  = *( data->pszListSpec + 13*ulFieldNum + 10 ) - '0';

      //
      // get size of all previous data; i.e. find *THIS* field's offset!
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ulSize = 0, ul2 = 10; ul2 < 13*ulFieldNum; ul2 += 13 )
      {
         switch( ubType )
         {
            case 'A':
               switch( ubLen )
               {
                  case 1:  ulSize +=  4;   break;  //  3 digits+NULL
                  case 2:  ulSize +=  6;   break;  //  5 digits+NULL
                  case 4:  ulSize += 11;   break;  // 10 digits+NULL
               }
               break;
            case 'S':
               switch( ubLen )
               {
                  case 1:  ulSize +=  5;   break;  // sign+ 3 digits+NULL
                  case 2:  ulSize +=  7;   break;  // sign+ 5 digits+NULL
                  case 4:  ulSize += 12;   break;  // sign+10 digits+NULL
               }
               break;
            case 'P':
               ulSize += 4;
               break;
            case 'I':
            case 'N':
               ulSize += ( *( (data->pszListSpec)+ul2 ) - '0' );
               break;
         }
      }

      if ( ubType == 'P' )
      {
         rc = stricmp( pszNode1+ulSize, pszNode2+ulSize );
      }
      else if ( ubType == 'A' )
      {
         if ( (ULONG)atol( pszNode1+ulSize ) < (ULONG)atol( pszNode2+ulSize ) )
            rc = -1;
         else if ( (ULONG)atol( pszNode1+ulSize ) < (ULONG)atol( pszNode2+ulSize ) )
            rc =  1;
         else
            rc =  0;
      }
      else if ( ubType == 'S' )
      {
         if ( (LONG)atol( pszNode1+ulSize ) < (LONG)atol( pszNode2+ulSize ) )
            rc = -1;
         else if ( (LONG)atol( pszNode1+ulSize ) < (LONG)atol( pszNode2+ulSize ) )
            rc =  1;
         else
            rc =  0;
      }
      else
      {
         switch( ubLen )
         {
            case 1:
               if ( *( pszNode1+ulSize ) < *( pszNode2+ulSize ) )      rc = -1;
               else if ( *( pszNode1+ulSize ) > *( pszNode2+ulSize ) ) rc =  1;
               else                                                    rc =  0;
               break;
            case 2:
               if ( *( (UWORD *)(pszNode1+ulSize) ) < *( (UWORD *)(pszNode2)+ulSize ) )
                  rc = -1;
               else if ( *( (UWORD *)(pszNode1+ulSize) ) > *( (UWORD *)(pszNode2)+ulSize ) )
                  rc =  1;
               else
                  rc =  0;
               break;
            case 4:
               if ( *( (ULONG *)(pszNode1+ulSize) ) < *( (ULONG *)(pszNode2)+ulSize ) )
                  rc = -1;
               else if ( *( (ULONG *)(pszNode1+ulSize) ) > *( (ULONG *)(pszNode2)+ulSize ) )
                  rc =  1;
               else
                  rc =  0;
               break;
         }
      }

      //
      // if we're going in descending order, reverse the above result
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( szBuf[0] == 'D' )
      {
         rc = -rc;
      }
   }

   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) char *pszNode )
{
   BOOL           f;
   ULONG          ul, ulOffset, ulLen;
   struct MyData *data;
   char           szArrayPtr[11];
   char         **ppszArrayAddr = NULL;


   data = (struct MyData *)hook->h_Data;

   if ( pszNode )
   {
      //
      // display data
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ul = 0; ul < strlen(data->pszListSpec); ul += 13 )
      {
         ulLen = *( data->pszListSpec + ul + 1 ) - '0';

         if ( *( data->pszListSpec + ul + 1 ) == 'Y' )
         {
            ulOffset = fnFindNodeOffset( ul, data );

            switch ( *( data->pszListSpec + ul + 1 ) )
            {
               case 'A':
               case 'S':
                  *array++ = ( pszNode + ulOffset );
                  break;
               case 'P':
                  *array++ = ( pszNode + ulOffset );
                  break;
               case 'I':
               case 'N':
                  sprintf( szArrayPtr, "0x%-8.8s", *( data->pszListSpec + ul - 8 ) );
                  ppszArrayAddr = (char **)atol( szArrayPtr );
                  switch( ulLen )
                  {
                     case 1:  *array++ = (char *)( ppszArrayAddr + *( (UBYTE *)( pszNode+ulOffset ) ) );   break;
                     case 2:  *array++ = (char *)( ppszArrayAddr + *( (UWORD *)( pszNode+ulOffset ) ) );   break;
                     case 4:  *array++ = (char *)( ppszArrayAddr + *( (ULONG *)( pszNode+ulOffset ) ) );   break;
                  }
                  break;
            }
         }
      }
   }
   else if ( data->ppszListTitle )
   {
      //
      // display list titles
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( f = FALSE, ul = 0; ul < ( strlen(data->pszListSpec) / 13 ); ul++ )
      {
         f = TRUE;
         *array++ = *( ( data->ppszListTitle )+ul );     // list column titles
      }
      if ( f )    array--;
   }

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


//
// FUNCTIONS --------------------------------------------------
//
ULONG fnFindNodeOffset( ULONG ulFieldNum, struct MyData *data )
{
   ULONG  ul2, ulSize;
   UBYTE  ubType, ubLen;


   //
   // get size of all previous data; i.e. find *THIS* field's offset!
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   for ( ulSize = 0, ul2 = 10; ul2 < 13*ulFieldNum; ul2 += 13 )
   {
      ubType = *( data->pszListSpec + ul2 - 9 );
      ubLen  = *( data->pszListSpec + ul2 ) - '0';

      switch( ubType )
      {
         case 'A':
            switch( ubLen )
            {
               case 1:  ulSize +=  4;   break;  //  3 digits+NULL
               case 2:  ulSize +=  6;   break;  //  5 digits+NULL
               case 4:  ulSize += 11;   break;  // 10 digits+NULL
            }
            break;
         case 'S':
            switch( ubLen )
            {
               case 1:  ulSize +=  5;   break;  // sign+ 3 digits+NULL
               case 2:  ulSize +=  7;   break;  // sign+ 5 digits+NULL
               case 4:  ulSize += 12;   break;  // sign+10 digits+NULL
            }
            break;
         case 'P':
            ulSize += 4;
            break;
         case 'I':
         case 'N':
            ulSize += (ULONG)ubLen;
            break;
      }
   }

   return( ulSize );
}

char *fnMakeRecStringFromObjects( struct MyData *data,
                                  struct HHDataList_IOArray *pPutArray,
                                  struct ErrorReport *pErrRpt )
{
   struct HHDataList_IOArray *pIO = NULL;

   APTR   pValue;
   ULONG  ul2, ulSize, ulNumEntries, ulIndex, ul;
   UBYTE  ubType, ubLen;
   char  *psz, *pszTmpRecord = NULL;

   UBYTE *pubTmpRecNdx;    // pointers inside tmprecord
   UWORD *puwTmpRecNdx;
   ULONG *pulTmpRecNdx;


   //
   // get total size of node including actual string data and BREAKs
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   for ( ulSize = 0, ul2 = 10; ul2 < strlen( data->pszListSpec ); ul2 += 13 )
   {
      ubType = *( data->pszListSpec + ul2 - 9 );
      ubLen  = *( data->pszListSpec + ul2 ) - '0';

      switch( ubType )
      {
         case 'A':
            switch( ubLen )
            {
               case 1:  ulSize +=  4;   break;  //  3 digits+NULL
               case 2:  ulSize +=  6;   break;  //  5 digits+NULL
               case 4:  ulSize += 11;   break;  // 10 digits+NULL
            }
            break;
         case 'S':
            switch( ubLen )
            {
               case 1:  ulSize +=  5;   break;  // sign+ 3 digits+NULL
               case 2:  ulSize +=  7;   break;  // sign+ 5 digits+NULL
               case 4:  ulSize += 12;   break;  // sign+10 digits+NULL
            }
            break;
         case 'P':
            ulSize += 4;

            // add string length
            pIO = ( pPutArray + ( ul2/13 ) );
            if ( pIO )
            {
               if ( pIO->ulAttr == MUIV_HHDataList_ObjectContents )
               {
                  // do for each item
                  get( pIO->pObj, MUIA_List_Entries, &ulNumEntries );
                  for ( ul = 0; ul < ulNumEntries; ul++ )
                  {
                     DoMethod( pIO->pObj, MUIM_List_GetEntry, ul, &psz );
                     ulSize += ( strlen( psz ) + 1 );
                  }
                  ulSize++;
               }
               else
               {
                  get( pIO->pObj, pIO->ulAttr, &pValue );
                  ulSize += ( strlen( pValue ) + 1 );
               }
            }
            break;
         case 'I':
         case 'N':
            ulSize += (ULONG)ubLen;
            break;
      }
   }

   pszTmpRecord = AllocVec( ulSize+100, MEMF_ANY | MEMF_CLEAR );
   if ( pszTmpRecord )
   {
      //
      // get contents of each object and stuff into tmprecord...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ulIndex = 0, ul2 = 10; ul2 < strlen( data->pszListSpec ); ul2 += 13 )
      {
         pubTmpRecNdx = (UBYTE *)( pszTmpRecord + ulIndex );
         puwTmpRecNdx = (UWORD *)( pszTmpRecord + ulIndex );
         pulTmpRecNdx = (ULONG *)( pszTmpRecord + ulIndex );

         ubType = *( data->pszListSpec + ul2 - 9 );
         ubLen  = *( data->pszListSpec + ul2 ) - '0';

         pIO = ( pPutArray + ( ul2/13 ) );

         switch( ubType )
         {
            case 'A':
               get( pIO->pObj, pIO->ulAttr, &pValue );
               switch( ubLen )
               {
                  case 1:  *pubTmpRecNdx = (UBYTE)pValue;   break;
                  case 2:  *puwTmpRecNdx = (UWORD)pValue;   break;
                  case 4:  *pulTmpRecNdx = (ULONG)pValue;   break;
               }
               ulIndex += ubLen;
               break;
            case 'S':
               get( pIO->pObj, pIO->ulAttr, &pValue );
               switch( ubLen )
               {
                  case 1:  *pubTmpRecNdx = (BYTE)pValue;    break;
                  case 2:  *puwTmpRecNdx = (WORD)pValue;    break;
                  case 4:  *pulTmpRecNdx = (LONG)pValue;    break;
               }
               ulIndex += ubLen;
               break;
            case 'P':
               pIO = ( pPutArray + ( ul2/13 ) );
               if ( pIO )
               {
                  if ( pIO->ulAttr == MUIV_HHDataList_ObjectContents )
                  {
                     // do for each item
                     get( pIO->pObj, MUIA_List_Entries, &ulNumEntries );
                     for ( ul = 0; ul < ulNumEntries; ul++ )
                     {
                        pubTmpRecNdx = (UBYTE *)( pszTmpRecord + ulIndex );

                        DoMethod( pIO->pObj, MUIM_List_GetEntry, ul, &psz );
                        strncpy( pubTmpRecNdx, psz, strlen( psz ) );
                        *( pubTmpRecNdx + strlen( psz ) ) = LEVEL2BREAK;
                        ulIndex += ( strlen( psz ) + 1 );
                     }
                     *( pubTmpRecNdx + strlen( psz ) ) = LEVEL1BREAK;
                     ulIndex += 1;
                  }
                  else
                  {
                     get( pIO->pObj, pIO->ulAttr, &psz );
                     strncpy( pubTmpRecNdx, psz, strlen( psz ) );
                     *( pubTmpRecNdx + strlen( psz ) ) = LEVEL1BREAK;
                     ulIndex += ( strlen( psz ) + 1 );
                  }
               }
               break;
            case 'I':
            case 'N':
               get( pIO->pObj, pIO->ulAttr, &pValue );
               switch( ubLen )
               {
                  case 1:  *pubTmpRecNdx = (UBYTE)pValue;   break;
                  case 2:  *puwTmpRecNdx = (UWORD)pValue;   break;
                  case 4:  *pulTmpRecNdx = (ULONG)pValue;   break;
               }
               ulIndex += ubLen;
               break;
         }
      }
      return( pszTmpRecord );
   }
   else
   {
      pErrRpt->lError = -1;
      strcpy( pErrRpt->szError, "Cannot allocate memory for temporary record." );
      return( NULL );
   }
}

//
// fnSkipNullSpaces(): Read NULLSPACEs until no more or EOF
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SHORT fnSkipNullSpaces( struct MyData *data )
{
   LONG  lDiskStat;
   UBYTE ubChar;


   lDiskStat = Read( data->fhListFile, &ubChar, 1 );
   if ( lDiskStat == 0 )     // if we hit EOF, just return success
   {
      return( 2 );
   }
   else if ( lDiskStat != 1 )
   {
      return( 0 );
   }

   while ( ( ubChar == 252 ) && ( lDiskStat == 1 ) )
   {
      lDiskStat = Read( data->fhListFile, &ubChar, 1 );
   }
   switch( lDiskStat )
   {
      case 1:
         // we've passed the NULLSPACE... reverse direction 1 byte and return
         Seek( data->fhListFile, -1, OFFSET_CURRENT );
         return( 1 );
      case 0:
         // we've hit EOF.
         return( 2 );
      default:
         return( 0 );      // disk error
   }
}

//
// fnReadFileIntoList(): verify legitimacy of file; then read into HHDL
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LONG fnReadFileIntoList( Object *obj, struct MyData *data, struct ErrorReport *pErrRpt )
{
   LONG   lDiskStat;
   char   szVerifyBuf[ HEADERSIZE+1 ];
   ULONG  ulBufSize = 2048;
   ULONG *pulBuf, ulRecordSize;
   ULONG  ulStat;
   SHORT  sOK;



   // verify this file is legit
   lDiskStat = Read( data->fhListFile, &szVerifyBuf, HEADERSIZE );
   if ( ( lDiskStat == HEADERSIZE ) &&
        ( strncmp( szVerifyBuf, HEADERSTRING, strlen( HEADERSTRING ) ) == 0 )
      )
   {
      // file is legit... set file size ULONGs...
      data->ulUnusedSpace = *((ULONG *)&(szVerifyBuf[12]));
      data->ulTotalSpace  = Seek( data->fhListFile, 0, OFFSET_END ) - HEADERSIZE;

      //
      // ok, read the file into memory...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Seek( data->fhListFile, HEADERSIZE, OFFSET_BEGINNING );
      pulBuf = AllocVec( ulBufSize, MEMF_ANY );
      if ( !pulBuf )
      {
         pErrRpt->lError = -1;
         strcpy( pErrRpt->szError, "Cannot get memory for disk buffer." );
         return( FALSE );
      }

      ulStat = 0;
      sOK = fnSkipNullSpaces( data );
      switch( sOK )
      {
         case 0:     // disk error
            pErrRpt->lError = IoErr();
            Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
            FreeVec( pulBuf );
            return( FALSE );
         case 1:     // skipped NULLSPACE; more data exists...
            break;
         case 2:     // skipped NULLSPACE; we're at the EOF.
            FreeVec( pulBuf );
            return( TRUE );
      }

      lDiskStat = Read( data->fhListFile, pulBuf, 4 );
      if ( lDiskStat != 4 )
      {
         pErrRpt->lError = IoErr();
         Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
         FreeVec( pulBuf );
         return( FALSE );
      }

      ulRecordSize = *( (ULONG *)pulBuf );
      while ( ulRecordSize != 0 )
      {
         // skip any leading NULLSPACEs...
         if ( ulRecordSize == 0xFCFCFCFC )
         {
            ulStat = 0;
            sOK = fnSkipNullSpaces( data );
            switch( sOK )
            {
               case 0:     // disk error
                  pErrRpt->lError = IoErr();
                  Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
                  FreeVec( pulBuf );
                  return( FALSE );
               case 1:     // skipped NULLSPACE; more data exists...
                  lDiskStat = Read( data->fhListFile, pulBuf, 4 );
                  if ( lDiskStat != 4 )
                  {
                     pErrRpt->lError = IoErr();
                     Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
                     FreeVec( pulBuf );
                     return( FALSE );
                  }
                  ulRecordSize = *( (ULONG *)pulBuf );
                  break;
               case 2:     // skipped NULLSPACE; we're at the EOF.
                  FreeVec( pulBuf );
                  return( TRUE );
            }
         }

         if ( ulRecordSize > ulBufSize )
         {
            FreeVec( pulBuf );
            ulBufSize = ulRecordSize + 4 + 1;
            pulBuf = AllocVec( ulBufSize, MEMF_ANY );
            if ( pulBuf == NULL )
            {
               pErrRpt->lError = IoErr();
               strcpy( pErrRpt->szError, "Memory Error!\nNot enough memory to read all recipes." );
               return( FALSE );
            }
         }

         *pulBuf = Seek( data->fhListFile, 0, OFFSET_CURRENT ) - 4;
         lDiskStat = Read( data->fhListFile, pulBuf+1, ulRecordSize );
         if ( lDiskStat != ulRecordSize )
         {
            pErrRpt->lError = IoErr();
            Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
            DoMethod( obj, MUIM_List_Clear, TRUE );
            FreeVec( pulBuf );
            return( FALSE );
         }

         DoMethod( obj, MUIM_List_Insert,
                   (char *)&pulBuf, 1, MUIV_List_Insert_Sorted );

         lDiskStat = Read( data->fhListFile, pulBuf, 4 );
         if ( lDiskStat == 0 )
         {
            // END OF FILE -- BREAK THE LOOP
            ulRecordSize = 0;
            break;
         }
         else if ( lDiskStat != 4 )
         {
            pErrRpt->lError = IoErr();
            Fault( IoErr(), "Disk read error!\n", pErrRpt->szError, sizeof( pErrRpt->szError ) );
            DoMethod( obj, MUIM_List_Clear, TRUE );
            FreeVec( pulBuf );
            return( FALSE );
         }
         ulRecordSize = *( (ULONG *)pulBuf );
      }

      FreeVec( pulBuf );
      return( TRUE );      // success
   }

   return( FALSE );
}

//
// fnReadDiskRecord(): write a record string to disk
//    RETURN pointer or NULL on failure
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char *fnReadDiskRecord( Object *obj,     struct MyData *data,
                        LONG lListIndex, struct ErrorReport *pErrRpt )
{
   ULONG *rc    = NULL;
   char  *pNode = NULL;
   ULONG  ulDiskAddr, ulRecSize;
   LONG   lDiskStat;


   DoMethod( obj, MUIM_List_GetEntry, lListIndex, &pNode );
   if ( pNode )
   {
      ulDiskAddr = *( (ULONG *)pNode );
      Seek( data->fhListFile, ulDiskAddr, OFFSET_BEGINNING );
      lDiskStat = Read( data->fhListFile, &ulRecSize, 4 );
      if ( lDiskStat != 4 )
      {
         pErrRpt->lError = IoErr();
         Fault( IoErr(), "Disk read error!  ", pErrRpt->szError, sizeof( pErrRpt->szError ) );
         return( FALSE );
      }
      if ( ulRecSize != 0 )
      {
         rc = AllocVec( ulRecSize, MEMF_ANY | MEMF_CLEAR );
         if ( rc )
         {
            *rc = Seek( data->fhListFile, 0, OFFSET_CURRENT ) - 4;
            lDiskStat = Read( data->fhListFile, rc+1, ulRecSize );
            if ( lDiskStat != ulRecSize )
            {
               pErrRpt->lError = IoErr();
               Fault( IoErr(), "Disk read error!  ", pErrRpt->szError, sizeof( pErrRpt->szError ) );
               FreeVec( rc );
               return( FALSE );
            }
         }
      }
   }

   return( (char *)rc );
}

//
// fnWriteDiskRecord(): write a record string to disk
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BOOL  fnWriteDiskRecord( Object *obj,                 struct MyData *data,
                         char   *pszRecord,           LONG lListIndex,
                         struct ErrorReport *pErrRpt, UBYTE ubAddOrChangeMode )
{
   BOOL  rc = FALSE;      // assume failure
   char  *pNode = NULL;
   LONG   lOldRecAddr = 0;
   LONG   lDiskStat;
   ULONG  ulRecSize, ulBlockSize, ulDiskBlockSize, ulRemainder;
   UBYTE  ubNullSpace = NULLSPACE;
   BOOL   fError;
   ULONG  ul;


   ulBlockSize     = 4 + strlen( pszRecord );
   ulDiskBlockSize = strlen( pszRecord );

   switch( ubAddOrChangeMode )
   {
      case CHANGE_MODE:
         //
         // look at disk address; get rec size; if big enough,
         // replace ... else write NULLSPACEs over record and append
         // to end of file.
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         DoMethod( obj, MUIM_List_GetEntry, lListIndex, &pNode );
         Seek( data->fhListFile, *((ULONG *)pNode), OFFSET_BEGINNING );
         lDiskStat = Read( data->fhListFile, &ulRecSize, 4 );
         if ( lDiskStat != 4 )
         {
            pErrRpt->lError = IoErr();
            Fault( IoErr(), "Disk read error!  ", pErrRpt->szError, sizeof( pErrRpt->szError ) );
            strcat( pErrRpt->szError, "\nThe data was not added to the file." );
            return( FALSE );
         }

         lOldRecAddr = Seek( data->fhListFile, -4, OFFSET_CURRENT );

         // is old record of sufficient size?
         if ( ulRecSize >= ulDiskBlockSize )
         {
            // add here... blitz remainder with NULLSPACEs...
            ulRemainder = ulRecSize - ulDiskBlockSize;
            *( (ULONG *)pszRecord ) = Seek( data->fhListFile, 0, OFFSET_CURRENT );
            if ( Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
            {
               if ( Write( data->fhListFile, pszRecord+4, ulDiskBlockSize ) )
               {
                  for ( fError = FALSE, ul = 0; ul < ulRemainder; ul++ )
                  {
                     lDiskStat = Write( data->fhListFile, &ubNullSpace, 1 );
                     if ( lDiskStat != 1 ) fError = TRUE;
                  }
                  if ( fError == TRUE )
                  {
                     rc = FALSE;
                  }
                  else
                  {
                     data->ulUnusedSpace += ulRemainder;
                     rc = TRUE;  // SUCCESS!
                  }
               }
               else
               {
                  rc = FALSE;
               }
            }
            else
            {
               rc = FALSE;
            }
         }
         else
         {
            // write to end of file
            Seek( data->fhListFile, 0, OFFSET_END );
            *( (ULONG *)pszRecord ) = Seek( data->fhListFile, 0, OFFSET_CURRENT );
            if ( Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
            {
               data->ulTotalSpace += 4;
               if ( Write( data->fhListFile, pszRecord+4, ulDiskBlockSize ) )
               {
                  data->ulTotalSpace += ulDiskBlockSize;

                  // blitz old record with NULLSPACEs...
                  Seek( data->fhListFile, lOldRecAddr-4, OFFSET_BEGINNING );
                  for ( fError = FALSE, ul = 0; ul < ulRecSize+4; ul++ )
                  {
                     lDiskStat = Write( data->fhListFile, &ubNullSpace, 1 );
                     if ( lDiskStat != 1 ) fError = TRUE;
                  }
                  if ( fError == TRUE )
                  {
                     rc = FALSE;
                  }
                  else
                  {
                     data->ulUnusedSpace += ( ulRecSize+4 );
                     rc = TRUE;     // SUCCESS!
                  }
               }
               else
               {
                  rc = FALSE;
               }
            }
            else
            {
               rc = FALSE;
            }
         }
         break;
      case ADD_MODE:
         // write to end of file
         Seek( data->fhListFile, 0, OFFSET_END );
         *( (ULONG *)pszRecord ) = Seek( data->fhListFile, 0, OFFSET_CURRENT );
         if ( Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
         {
            data->ulTotalSpace += 4;
            if ( Write( data->fhListFile, pszRecord+4, ulDiskBlockSize ) )
            {
               data->ulTotalSpace += ulDiskBlockSize;
               rc = TRUE;  // SUCCESS!
            }
         }
         break;
      default:
         rc = FALSE;
         break;
   }

   if ( rc == TRUE )
   {
      //
      // finally, (if no error) add to listview...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( ubAddOrChangeMode == CHANGE_MODE )
      {
         DoMethod( obj, MUIM_List_Remove, lListIndex );
      }
      DoMethod( obj, MUIM_List_Insert, &pszRecord, 1, lListIndex );
      set( obj, MUIA_List_Active, lListIndex );
   }
   else
   {
      pErrRpt->lError = -1;
      strcpy( pErrRpt->szError, "There was an error writing to the data file.  The file may be corrupt." );
   }

   return( rc );
}

SAVEDS ULONG mNew(struct IClass *cl,Object *obj,struct opSet *msg)
{
   struct MyData   *data;
   struct TagItem  *params;
   APTR             tmp;
   LONG             lDiskStat;


   if (!(obj = (Object *)DoSuperMethodA(cl,obj,(Msg)msg)))
      return(0);

   data   = INST_DATA(cl,obj);

   //
   // store data pointer in some of the Hook structures we call...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   hMainConstructHook.h_Data = (APTR)data;
   hMainCompareHook.h_Data   = (APTR)data;
   hMainDisplayHook.h_Data   = (APTR)data;

   params = msg->ops_AttrList;

   // init MyData storage to zeros...
   memset( &data, 0, sizeof( struct MyData ) );

   // parse initial taglist
   data->pMUIListviewObj = (Object *)GetTagData(MUIA_HHDataList_Listview, NULL, params);
   if ( ! data->pMUIListviewObj )
   {
      return( FALSE );
   }

   tmp = (APTR)GetTagData( MUIA_HHDataList_FileSpec, NULL, params );
   if ( tmp )
   {
      data->pszListFile = AllocVec( strlen( tmp )+1, MEMF_ANY );
      if ( data->pszListFile )
      {
         memcpy( data->pszListFile, tmp, strlen( tmp )+1 );

         // attempt to open exclusive access
         data->fhListFile = Open( data->pszListFile, MODE_OLDFILE );
         if ( !data->fhListFile )
         {
            // if file doesn't exist, create it...
            data->fhListFile = Open( data->pszListFile, MODE_NEWFILE );
            if ( !data->fhListFile )
            {
               // error: cannot create requested file
// need to scan taglist and find MUIA_Error_ReturnInt and MUIA_Error_ReturnString
//x               params->pErrRpt->lError = IoErr();
//x               strcpy( params->pErrRpt->szError, "Cannot create requested data file." );
               return( FALSE );
            }
            else
            {
               // write initial stuff to file... skip rest of loading sequence
               lDiskStat = Write( data->fhListFile, "HHDATALISTV1    ", HEADERSIZE );
               data->ulUnusedSpace = 0;
               if ( lDiskStat != HEADERSIZE )
               {
                  // error: file not legit
// need to scan taglist and find MUIA_Error_ReturnInt and MUIA_Error_ReturnString
//x                  params->pErrRpt->lError = IoErr();
//x                  strcpy( params->pErrRpt->szError, "This file is not a valid data file." );
                  return( FALSE );
               }
            }
         }
// Pass struct ErrorReport via MUIA_HHDL_ErrorStruct tag!
//         if ( fnReadFileIntoList( obj, data, ---errorstruct--- ) == FALSE )
         if ( fnReadFileIntoList( obj, data, NULL ) == FALSE )
         {
            // error reading file
            return( NULL );
         }
      }
      else
      {
         return( NULL );
      }
   }
   else
   {
      return( NULL );
   }

   tmp = (APTR)GetTagData( MUIA_HHDataList_ListSpec, NULL, params );
   if ( tmp )
   {
      data->pszListSpec = tmp;
   }
   else
   {
      return( NULL );
   }

   tmp = (APTR)GetTagData( MUIA_HHDataList_SortSpec, NULL, params );
   if ( tmp )
   {
      data->pszSortSpec = AllocVec( strlen( tmp )+1, MEMF_ANY );
      if ( data->pszSortSpec )
      {
         memcpy( data->pszSortSpec, tmp, strlen( tmp )+1 );
      }
      else
      {
         return( NULL );
      }
   }
   else
   {
      return( NULL );
   }

   set( obj, MUIA_List_CompareHook,   hMainConstructHook );
   set( obj, MUIA_List_ConstructHook, hMainDestructHook  );
   set( obj, MUIA_List_DestructHook,  hMainCompareHook   );
   set( obj, MUIA_List_DisplayHook,   hMainDisplayHook   );

   return((ULONG)obj);
}


SAVEDS ULONG mDispose(struct IClass *cl,Object *obj,Msg msg)
{
   struct MyData *data = INST_DATA(cl,obj);

   if ( data->pszSortSpec )      FreeVec( data->pszSortSpec );

   if ( data->fhListFile )
   {
      // write unused back to file
      Seek( data->fhListFile, 12, OFFSET_BEGINNING );
      if ( IoErr() == 0 )
      {
         Write( data->fhListFile, &(data->ulUnusedSpace), 4 );
      }
      Close( data->fhListFile );
   }

   return(DoSuperMethodA(cl,obj,msg));
}


/*
** OM_SET method, we need to see if someone changed the pen attribute.
*/

SAVEDS ULONG mSet( struct IClass *cl, Object *obj, struct opSet *msg)
{
   struct MyData *data = INST_DATA(cl,obj);
   struct TagItem *tags,*tag;

   for ( tags = msg->ops_AttrList; tag = NextTagItem( &tags ); )
   {
      switch ( tag->ti_Tag )
      {
         case MUIA_HHDataList_SortSpec:
            if ( data->pszSortSpec )   FreeVec( data->pszSortSpec );
            data->pszSortSpec = AllocVec( strlen( (char*)(tag->ti_Data) )+1, MEMF_ANY );
            if ( data->pszSortSpec )
            {
               memcpy( data->pszSortSpec, (char*)( tag->ti_Data ), strlen( (char *)(tag->ti_Data) )+1 );
               DoMethod( obj, MUIM_List_Sort, TRUE );
            }
            else
            {
               return(0);
            }
            MUI_Redraw( obj, MADF_DRAWOBJECT );    // redraw completely
            break;
      }
   }

   return( DoSuperMethodA( cl, obj, (Msg)msg ) );
}


/*
** OM_GET method, see if someone wants to read the color.
*/
static ULONG mGet(struct IClass *cl,Object *obj,Msg msg)
{
   struct MyData *data = INST_DATA(cl,obj);
   ULONG *store = ((struct opGet *)msg)->opg_Storage;

   switch (((struct opGet *)msg)->opg_AttrID)
   {
      case MUIA_HHDataList_FileSpec:
         *store = (ULONG)( data->pszListFile );
         return( TRUE );
      case MUIA_HHDataList_FileSize:
         *store = (ULONG)( data->ulTotalSpace );
         return( TRUE );
      case MUIA_HHDataList_ReOrgGainBytes:
         *store = (ULONG)( data->ulUnusedSpace );
         return( TRUE );
      case MUIA_HHDataList_SortSpec:
         *store = (ULONG)( data->pszSortSpec );
         return( TRUE );
   }

   return( DoSuperMethodA( cl, obj, msg ) );
}


SAVEDS ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg)
{
   //
   // let our superclass first fill in what it thinks about sizes.
   // this will e.g. add the size of frame and inner spacing.
   //

   DoSuperMethodA( cl, obj, (Msg)msg );

   //
   // now add the values specific to our object. note that we
   // indeed need to *add* these values, not just set them!
   //
   // nothing to adjust... we'll let MUI Listview take care of that.
   //

   return(0);
}


/*
** Draw method is called whenever MUI feels we should render
** our object. This usually happens after layout is finished
** or when we need to refresh in a simplerefresh window.
** Note: You may only render within the rectangle
**       _mleft(obj), _mtop(obj), _mwidth(obj), _mheight(obj).
*/
SAVEDS ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg)
{
   /*
   ** let our superclass draw itself first, area class would
   ** e.g. draw the frame and clear the whole region. What
   ** it does exactly depends on msg->flags.
   **
   ** we'll just let the parent object, draw it all... we don't
   ** do any rendering!
   */

   DoSuperMethodA( cl, obj, (Msg)msg );
   return(0);
}

SAVEDS ULONG mInsertObj(struct IClass *cl,Object *obj,struct MUIP_HHDLInsertObj *msg)
{
   struct MyData             *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLInsertObj *params = msg;

   char  *pszTmpRecord = NULL;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   pszTmpRecord = fnMakeRecStringFromObjects( data, params->pPutArray, params->pErrRpt );
   if ( pszTmpRecord )
   {
      DoMethod( obj, MUIM_HHDataList_InsertFromPointer, pszTmpRecord,
                params->pos, params->pErrRpt );
      FreeVec( pszTmpRecord );
      return( TRUE );
   }
   else
   {
      return( FALSE );
   }
}
SAVEDS ULONG mInsertPtr(struct IClass *cl,Object *obj,struct MUIP_HHDLInsertPtr *msg)
{
   struct MyData             *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLInsertPtr *params = msg;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   return( fnWriteDiskRecord( obj, data, params->pszRecord, params->pos,
                              params->pErrRpt, ADD_MODE ) );
}
SAVEDS ULONG mRemove(struct IClass *cl,Object *obj,struct MUIP_HHDLRemove *msg)
{
   struct MyData          *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLRemove *params = msg;

   LONG   lActive, lNumItems;
   APTR   pNode;
   ULONG  ul, ulRecordLen;
   UBYTE  ubNullSpace = NULLSPACE;
   LONG   lDiskStat;
   BOOL   fError;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   if ( params->pos == MUIV_HHDataList_Remove_Active )
   {
      get( obj, MUIA_List_Active, &lActive );
   }
   else
   {
      lActive = params->pos;
   }
   get( obj, MUIA_List_Entries, &lNumItems );
   if ( lActive <= lNumItems-1 )
   {
      // del from list
//      DoMethod( data->pMUIListviewObj, MUIM_List_Remove, lActive );
//---can I do this? (below):
DoMethod( obj, MUIM_List_Remove, lActive );

      // del from disk: from robocook/storage.c
      DoMethod( obj, MUIM_List_GetEntry, lActive, &pNode );

      // first entry in (memory) node is disk addr of record
      Seek( data->fhListFile, *((ULONG *)pNode), OFFSET_BEGINNING );

      lDiskStat = Read( data->fhListFile, &ulRecordLen, 4 );
      if ( lDiskStat != 4 )
      {
         params->pErrRpt->lError = IoErr();
         Fault( IoErr(), "Disk read error!  ", params->pErrRpt->szError, sizeof( params->pErrRpt->szError ) );
         strcat( params->pErrRpt->szError, "  The recipe was not deleted from the file." );
         return( FALSE );
      }

      Seek( data->fhListFile, -4, OFFSET_CURRENT );
      for ( fError = FALSE, ul = 0; ul < ulRecordLen+4; ul++ )
      {
         lDiskStat = Write( data->fhListFile, &ubNullSpace, 1 );
         if ( lDiskStat != 1 ) fError = TRUE;
      }
      if ( fError == TRUE )
      {
         params->pErrRpt->lError = IoErr();
         strcpy( params->pErrRpt->szError, "There was an error writing to the data file.  The file may be corrupt." );
      }
      else
      {
         data->ulUnusedSpace += ( ulRecordLen+4 );
      }
   }
   else
   {
      params->pErrRpt->lError = -1;
      strcpy( params->pErrRpt->szError, "There was an error writing to the data file.  The file may be corrupt." );
      return( FALSE );
   }

   return( TRUE );
}
SAVEDS ULONG mReOrganize(struct IClass *cl,Object *obj,struct MUIP_HHDLReOrganize *msg)
{
   struct MyData              *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLReOrganize *params = msg;

   BPTR   fhNew = NULL, fhOld = NULL;
   char   szTool[ FMSIZE*2 ];
   UBYTE *pDiskBufferIn = NULL, *pDiskBufferOut = NULL;
   LONG   l1, l2, lDoneSoFar, lReadSize = 0;
   BOOL   fReOrgSuccess = FALSE;

   LONG   lStat, lDiskStat;
   ULONG  ulStat;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   // quiet list
   set( obj, MUIA_List_Quiet, TRUE );

   // reorg
   pDiskBufferIn = AllocVec( 16384, MEMF_ANY );
   if ( !pDiskBufferIn )
   {
      params->pErrRpt->lError = -1;
      strcpy( params->pErrRpt->szError, "Cannot allocate backup memory buffer.  Reorganization failed." );
      return( FALSE );
   }

   pDiskBufferOut = AllocVec( 16384, MEMF_ANY );
   if ( !pDiskBufferOut )
   {
      params->pErrRpt->lError = -1;
      strcpy( params->pErrRpt->szError, "Cannot allocate backup memory buffer.  Reorganization failed." );
      FreeVec( pDiskBufferIn );
      return( FALSE );
   }

   // close the data file first...
   Close( data->fhListFile );  data->fhListFile = NULL;

   sprintf( szTool, "copy \"%s\" TO \"%s\"", data->pszListFile, params->pszBackupFile );
   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",
                params->pErrRpt->szError,
                sizeof( params->pErrRpt->szError ) );
      else
         strcpy( params->pErrRpt->szError, "Couldn't make a backup copy of recipe data file.  Reorganization cancelled." );

      params->pErrRpt->lError = IoErr();
   }
   else
   {
      if ( !DeleteFile( data->pszListFile ) )
      {
         params->pErrRpt->lError = -1;
         strcpy( params->pErrRpt->szError, "Could not delete old data file.  Reorganization failed." );
      }
      else
      {
         fhOld = Open( params->pszBackupFile, MODE_OLDFILE );
         fhNew = Open( data->pszListFile, MODE_NEWFILE );
         if ( !fhOld || !fhNew )
         {
            if ( fhOld )
            {
               Close( fhOld );   fhOld = NULL;
            }
            if ( fhNew )
            {
               Close( fhNew );   fhNew = NULL;
            }

            params->pErrRpt->lError = -1;
            strcpy( params->pErrRpt->szError, "File error!  Could not open new data files.  Reorganization failed." );

            //
            // copy it back; if can't, warn user that data
            //               file is in backup location...
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            sprintf( szTool, "copy \"%s\" TO \"%s\"", params->pszBackupFile, data->pszListFile );
            lStat = SystemTags( szTool, SYS_Asynch, FALSE, TAG_DONE, TAG_DONE );
            if ( lStat != 0 )
            {
               params->pErrRpt->lError = -1;
               strcpy( params->pErrRpt->szError, "Couldn't restore data file to original location.  File is located on destination you specified.  Reorganization failed." );
            }
         }
         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 )
                     {
                        params->pErrRpt->lError = -1;
                        strcpy( params->pErrRpt->szError, "Cannot write data to new file.  Reorganization failed.  Old file is in the backup location." );
                        break;
                     }
                     else if ( params->pGauge )
                     {
                        lDoneSoFar += lReadSize;
                        ulStat = 100 * (float)( (float)lDoneSoFar / (float)(data->ulTotalSpace+HEADERSIZE) );
                        set( params->pGauge, MUIA_Gauge_Current, ulStat );
                     }
                  }
               }
            }
            if ( lDiskStat < 0 )
            {
               params->pErrRpt->lError = -1;
               strcpy( params->pErrRpt->szError, "Cannot write data to new file.  Reorganization failed.  Old file is in the backup location." );
               Fault( IoErr(),
                      "Could not write file status because",
                      params->pErrRpt->szError,
                      sizeof( params->pErrRpt->szError ) );
            }
            else
            {
               //
               // write unusedspace-stat to file header
               // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
               Seek( fhNew, strlen( HEADERSTRING ), OFFSET_BEGINNING );
               data->ulUnusedSpace = 0;
               lDiskStat = Write( fhNew, &(data->ulUnusedSpace), 4 );
               if ( lDiskStat != 4 )
               {
                  params->pErrRpt->lError = -1;
                  Fault( IoErr(),
                         "Could not mark backup file as 'OLD' because",
                         params->pErrRpt->szError,
                         sizeof( params->pErrRpt->szError ) );
               }
               else
               {
                  fReOrgSuccess = TRUE;
               }
            }
         }

         data->ulTotalSpace = Seek( fhNew, 0, OFFSET_END );
         data->ulUnusedSpace = 0;

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

   FreeVec( pDiskBufferOut );
   FreeVec( pDiskBufferIn );

   if ( fReOrgSuccess == TRUE )
   {
      // clear list
      DoMethod( obj, MUIM_List_Clear, TRUE );

      // re-load list?
      if ( params->fReloadList )
      {
         //
         // set window to sleep while loading recipe data...
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         if ( fnReadFileIntoList( obj, data, params->pErrRpt ) == FALSE )
         {
            // file read error... unable to re-load
            params->pErrRpt->lError = -1;
            strcpy( params->pErrRpt->szError, "Reorganization succeeded but unable to re-load data into list." );
            return( FALSE );
         }

         //
         // activate first item in listview
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         set( obj, MUIA_List_Active, MUIV_List_Active_Top );
      }
   }

   // list can wake up now...
   set( obj, MUIA_List_Quiet, FALSE );

   return( fReOrgSuccess );
}
SAVEDS ULONG mReplaceEntryObj(struct IClass *cl,Object *obj,struct MUIP_HHDLReplaceEntryObj *msg)
{
   struct MyData                   *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLReplaceEntryObj *params = msg;

   char  *pszTmpRecord;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   pszTmpRecord = fnMakeRecStringFromObjects( data, params->pPutArray, params->pErrRpt );
   if ( pszTmpRecord )
   {
      DoMethod( obj, MUIM_HHDataList_ReplaceFromPointer, pszTmpRecord,
                params->lLineToReplace, params->pErrRpt );
      FreeVec( pszTmpRecord );
      return( TRUE );
   }
   else
   {
      return( FALSE );
   }
}
SAVEDS ULONG mReplaceEntryPtr(struct IClass *cl,Object *obj,struct MUIP_HHDLReplaceEntryPtr *msg)
{
   struct MyData                   *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLReplaceEntryPtr *params = msg;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   return( (ULONG)fnWriteDiskRecord( obj, data, params->pszRecord, params->lLineToReplace,
                                     params->pErrRpt, CHANGE_MODE ) );
}
SAVEDS ULONG mRetrieveObj(struct IClass *cl,Object *obj,struct MUIP_HHDLRetrieveObj *msg)
{
   struct MyData               *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLRetrieveObj *params = msg;
   struct HHDataList_IOArray   *pIO    = NULL;

   APTR    pValue;
   ULONG   ul2, ulIndex;
   UBYTE   ubType, ubLen;
   UBYTE  *psz, *psz2, *pszTmpRecord = NULL;
   UBYTE  *pszStart, *pszEnd;
   UBYTE   szArrayPtr[11];
   UBYTE **ppszArrayAddr = NULL;

   UBYTE  *pubTmpRecNdx;    // pointers inside tmprecord
   UWORD  *puwTmpRecNdx;
   ULONG  *pulTmpRecNdx;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   pszTmpRecord = fnReadDiskRecord( obj, data, params->pos, params->pErrRpt );
   if ( pszTmpRecord )
   {
      //
      // pull data from disk-string and stuff into objects...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ulIndex = 0, ul2 = 10; ul2 < strlen( data->pszListSpec ); ul2 += 13 )
      {
         pubTmpRecNdx = (UBYTE *)( pszTmpRecord + ulIndex );
         puwTmpRecNdx = (UWORD *)( pszTmpRecord + ulIndex );
         pulTmpRecNdx = (ULONG *)( pszTmpRecord + ulIndex );

         ubType = *( data->pszListSpec + ul2 - 9 );
         ubLen  = *( data->pszListSpec + ul2 ) - '0';

         pIO = ( params->pGetArray + ( ul2/13 ) );

         switch( ubType )
         {
            case 'A':
               switch( ubLen )
               {
                  case 1:  pValue = (APTR)*pubTmpRecNdx;   break;
                  case 2:  pValue = (APTR)*puwTmpRecNdx;   break;
                  case 4:  pValue = (APTR)*pulTmpRecNdx;   break;
               }
               set( pIO->pObj, pIO->ulAttr, pValue );
               ulIndex += ubLen;
               break;
            case 'S':
               switch( ubLen )
               {
                  case 1:  pValue = (APTR)*pubTmpRecNdx;   break;
                  case 2:  pValue = (APTR)*puwTmpRecNdx;   break;
                  case 4:  pValue = (APTR)*pulTmpRecNdx;   break;
               }
               set( pIO->pObj, pIO->ulAttr, pValue );
               ulIndex += ubLen;
               break;
            case 'P':
               pIO = ( params->pGetArray + ( ul2/13 ) );
               if ( pIO )
               {
                  if ( pIO->ulAttr == MUIV_HHDataList_ObjectContents )
                  {
                     // do for each item
                     pszStart = pubTmpRecNdx;
                     pszEnd = strchr( pszStart, LEVEL1BREAK );

                     for ( psz = pszStart; psz < ( pszEnd-1 ); )
                     {
                        psz2 = strchr( psz+1, LEVEL2BREAK );
                        if ( psz2 )
                        {
                           *psz2 = 0;
                           DoMethod( pIO->pObj, MUIM_List_InsertSingle, &psz, MUIV_List_Insert_Sorted );
                           *psz2 = LEVEL2BREAK;
                           psz = psz2+1;
                        }
                        else
                        {
                           params->pErrRpt->lError = -1;
                           strcpy( params->pErrRpt->szError,
                                   "Error reading list data.  Some data may be missing." );
                           break;
                        }
                     }
                     ulIndex += ( pszEnd - pszStart + 1 );
                  }
                  else
                  {
                     psz = strchr( pubTmpRecNdx, LEVEL1BREAK );
                     if ( psz )
                     {
                        *psz = 0;
                        set( pIO->pObj, pIO->ulAttr, psz );
                        *psz = LEVEL1BREAK;
                     }
                     ulIndex += ( strlen( psz ) + 1 );
                  }
               }
               break;
            case 'I':
            case 'N':
               sprintf( szArrayPtr, "0x%-8.8s", *( data->pszListSpec + ul2 - 8 ) );
               ppszArrayAddr = (UBYTE **)atol( szArrayPtr );
               switch( ubLen )
               {
                  case 1:  pValue = (char *)( ppszArrayAddr + *pubTmpRecNdx );   break;
                  case 2:  pValue = (char *)( ppszArrayAddr + *puwTmpRecNdx );   break;
                  case 4:  pValue = (char *)( ppszArrayAddr + *pulTmpRecNdx );   break;
               }
               set( pIO->pObj, pIO->ulAttr, pValue );
               ulIndex += ubLen;
               break;
         }
      }
      FreeVec( pszTmpRecord );
   }
   else
   {
      params->pErrRpt->lError = -1;
      strcpy( params->pErrRpt->szError, "Cannot allocate memory for temporary record." );
      return( NULL );
   }
}
SAVEDS ULONG mRetrievePtr(struct IClass *cl,Object *obj,struct MUIP_HHDLRetrievePtr *msg)
{
   struct MyData               *data   = INST_DATA(cl,obj);
   struct MUIP_HHDLRetrievePtr *params = msg;


   // init error structure...
   params->pErrRpt->lError = 0;
   strcpy( params->pErrRpt->szError, "" );

   //
   // caller MUST free the supplied buffer!!!
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   params->pszRecord = fnReadDiskRecord( obj, data, params->pos, params->pErrRpt );
   return( params->pszRecord ? (ULONG)TRUE : (ULONG)FALSE );
}


/*
** Here comes the dispatcher for our custom class. We only need to
** care about MUIM_AskMinMax and MUIM_Draw in this simple case.
** Unknown/unused methods are passed to the superclass immediately.
*/

SAVEDS ASM ULONG MyDispatcher(REG(a0) struct IClass *cl,REG(a2) Object *obj,REG(a1) Msg msg)
{
   switch (msg->MethodID)
   {
      case OM_NEW        : return(mNew      (cl,obj,(APTR)msg));
      case OM_DISPOSE    : return(mDispose  (cl,obj,(APTR)msg));
      case OM_SET        : return(mSet      (cl,obj,(APTR)msg));
      case OM_GET        : return(mGet      (cl,obj,(APTR)msg));
      case MUIM_AskMinMax: return(mAskMinMax(cl,obj,(APTR)msg));
      case MUIM_Draw     : return(mDraw     (cl,obj,(APTR)msg));

      case MUIM_HHDataList_InsertFromObjects  :  return(mInsertObj      (cl,obj,(APTR)msg));
      case MUIM_HHDataList_InsertFromPointer  :  return(mInsertPtr      (cl,obj,(APTR)msg));
      case MUIM_HHDataList_Remove             :  return(mRemove         (cl,obj,(APTR)msg));
      case MUIM_HHDataList_ReOrganize         :  return(mReOrganize     (cl,obj,(APTR)msg));
      case MUIM_HHDataList_ReplaceFromObjects :  return(mReplaceEntryObj(cl,obj,(APTR)msg));
      case MUIM_HHDataList_ReplaceFromPointer :  return(mReplaceEntryPtr(cl,obj,(APTR)msg));
      case MUIM_HHDataList_RetrieveIntoObjects:  return(mRetrieveObj    (cl,obj,(APTR)msg));
      case MUIM_HHDataList_RetrievePointer    :  return(mRetrievePtr    (cl,obj,(APTR)msg));
   }

   return(DoSuperMethodA(cl,obj,msg));
}

/***************************************/
/* Now lets see how things are used... */
/***************************************/
int main(int argc,char *argv[])
{
	APTR app,window,MyObj,SuperClass;
	Object *o1_str, *o2_int, *o3_cyc, *o4_but;
	struct IClass *MyClass;
	ULONG signals;
	BOOL running = TRUE;

   static char *cycspec[] = {
      "monday",
      "tuesday",
      "wednesday",
      "thursday",
      "friday",
      "saturday",
      "sunday",
      NULL
   };
   static struct HHDataList_IOArray ioPutOne[] = {
      { NULL,   (ULONG)MUIA_String_Contents },
      { NULL,   MUIA_String_Contents },
      { NULL,   MUIA_Cycle_Active    },
      { NULL,   NULL                 }
   };
   static struct ErrorReport ErrRpt;


	init();

   //
   // open libraries
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   DosBase = OpenLibrary( "dos.library", 37L );
   if ( !DosBase ) fail(app,"Cannot open dos.library 37+");

   IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37L);
   if (!IntuitionBase) fail(app,"Cannot open intuition.library 37+");

   GadToolsBase = (struct Library *)OpenLibrary("gadtools.library", 37L);
   if (!GadToolsBase) fail(app,"Cannot open gadtools.library 37+");

   GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",37L);
   if (!GfxBase) fail(app,"Cannot open graphics.library 37+");

   AslBase = OpenLibrary("asl.library",37L);
   if (!AslBase) fail(app,"Cannot open asl.library 37+");

   UtilityBase = (struct Library *)OpenLibrary("utility.library",37L);
   if (!UtilityBase) fail(app,"Cannot open utility.library 37+" );


	/* Get a pointer to the superclass. MUI will lock this */
	/* and prevent it from being flushed during you hold   */
	/* the pointer. When you're done, you have to call     */
	/* MUI_FreeClass() to release this lock.               */

	if (!(SuperClass=MUI_GetClass(MUIC_Listview)))
		fail(NULL,"Superclass for the new class not found.");

	/* create the new class */
	if (!(MyClass = MakeClass(NULL,NULL,SuperClass,sizeof(struct MyData),0)))
	{
		MUI_FreeClass(SuperClass);
		fail(NULL,"Failed to create class.");
	}

	/* set the dispatcher for the new class */
	MyClass->cl_Dispatcher.h_Entry    = (APTR)MyDispatcher;
	MyClass->cl_Dispatcher.h_SubEntry = NULL;
	MyClass->cl_Dispatcher.h_Data     = NULL;

	app = ApplicationObject,
		MUIA_Application_Title      , "HHDL TEST",
		MUIA_Application_Version    , "$VER: HHDLTest 1.0 (01.12.93)",
		MUIA_Application_Copyright  , ")1993, j 2",
		MUIA_Application_Author     , "j 2",
		MUIA_Application_Description, "Demonstrate the use of custom classes.",
		MUIA_Application_Base       , "HHDLTEST",

		SubWindow, window = WindowObject,
			MUIA_Window_Title, "A rather(!) complex custom class",
			MUIA_Window_ID   , MAKE_ID('C','L','S','4'),
			WindowContents, VGroup,

            Child, o1_str = String("Name",20),
            Child, o2_int = StringObject,
               MUIA_String_Accept, "0123456789",
               MUIA_String_MaxLen, 4+1,
               End,
            Child, o3_cyc = Cycle(cycspec),

				Child, MyObj = NewObject(MyClass,NULL,
					TextFrame,
               MUIA_HHDataList_FileSpec, "ram:hhdltest.data",
               MUIA_HHDataList_ListSpec, " P000000004MY0A000000002MN0S000000001DY",
               MUIA_HHDataList_Listview, ListviewObject,
						MUIA_Listview_Input, TRUE,
						MUIA_Listview_List, ListObject,
							InputListFrame,
							End,
						End,
               MUIA_HHDataList_SortSpec, "D00A02",
					TAG_DONE),

            Child, o4_but = SimpleButton("Add"),
				End,

			End,
		End;

	if (!app)
		fail(app,"Failed to create Application.");

   ioPutOne[0].pObj = o1_str;
   ioPutOne[1].pObj = o2_int;
   ioPutOne[2].pObj = o3_cyc;

	set(window,MUIA_Window_DefaultObject, MyObj);

	DoMethod(window,MUIM_Notify,MUIA_Window_CloseRequest,TRUE,
		app,2,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit);

   DoMethod(o4_but,MUIM_Notify,MUIA_Pressed,FALSE,
      app,2,MUIM_Application_ReturnID,111);


/*
** Input loop...
*/

	set(window,MUIA_Window_Open,TRUE);

	while (running)
	{
		switch (DoMethod(app,MUIM_Application_Input,&signals))
		{
			case MUIV_Application_ReturnID_Quit:
				running = FALSE;
				break;

         case 111:
         /*
         ** Insert some data...
         */
            DoMethod( MyObj, MUIM_HHDataList_InsertFromObjects, (APTR)&ioPutOne,
                      MUIV_List_Insert_Bottom, &ErrRpt );
            break;
		}

		if (running && signals) Wait(signals);
	}

	set(window,MUIA_Window_Open,FALSE);


/*
** Shut down...
*/

	MUI_DisposeObject(app);     /* dispose all objects. */
	FreeClass(MyClass);         /* free our custom class. */
	MUI_FreeClass(SuperClass); /* release super class pointer. */
	fail(NULL,NULL);            /* exit, app is already disposed. */
}
