/*
 * GHHDL: 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.
 *
 */

/* Includes */
#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/mui.h>
#include <dos/dos.h>
#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>
#include <clib/utility_protos.h>
#include <clib/muimaster_protos.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>

#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/intuition_pragmas.h>
#include <pragmas/utility_pragmas.h>
#include <pragmas/muimaster_pragmas.h>

#include "GHHDataList_mcc.h"

#define REG(x) register __ ## x
#define ASM    __asm
#define SAVEDS __saveds

static const char VersionTag[] = "\0$VER: GHHDataList.mcc 1.0 (24.10.95)";

#define CLASS      MUIC_GHHDataList
#define SUPERCLASS MUIC_Group

struct GHHDataList {
   APTR     pObj;
   ULONG    ulAttr;
   ULONG    ulDefaultValue;
   UBYTE    ubType;           // TYPE_INTEGER, TYPE_STRING, TYPE_INDEXED
   APTR    *ppValuesArray;    // array of strings (or image-
                              // strings) to index into with the
                              // data value for this object.
                              // e.g. point to the labels for an
                              //      RadioObject.
   UBYTE    ubMemOrDisk;      // 'M' = store data in memory and on
                              // disk; 'D' = Disk only
   UBYTE    ubInList;         // 'Y' = put this field in the List
                              // if 'Y', ubMemOrDisk *MUST* be 'M'

   // The G_Hook function will have the disk-address of the
   // corresponding data passed to it (just in case they need it --
   // like GHHDataList would!)  See docs for more info.
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   struct Hook  *hI_Hook;     // hook to call to 'init' this
                              // object's data
   struct Hook  *hS_Hook;     // hook to call for 'setting' this
                              // object's data
   struct Hook  *hG_Hook;     // hook to call for 'getting' this
                              // object's data
};


struct Library *HHDataListBase = NULL;

struct Library *MUIMasterBase = NULL;
struct Library *UtilityBase   = NULL;
struct Library *SysBase       = NULL;
struct Library *DOSBase       = NULL;
struct Library *IntuitionBase = NULL;

int  CXBRK(void) { return(0); }
int _CXBRK(void) { return(0); }
void chkabort(void) {}

#ifndef MAKE_ID
#define MAKE_ID(a,b,c,d) ((ULONG) (a)<<24 | (ULONG) (b)<<16 | (ULONG) (c)<<8 | (ULONG) (d))
#endif


//ULONG CallHook(struct Hook *hook, APTR object, ULONG command, ... )
//{
//   return(CallHookPkt(hook,object,(APTR)&command));
//}



//
//   F  U  N  C  T  I  O  N  S
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// fnBuildRecord(): temp file is returned in szTmpNam
//                  szTmpNam should be dimensioned with [L_tmpnam + 3]
//
//                  If szTmpNam == NULL, dpn't create a temp file!
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
APTR fnBuildRecord( struct Data *data, ULONG *pulObjAttrs, UBYTE szTmpNam[] )
{
   ULONG  ulDataLen = 0, ulStat;
   ULONG  ulData[256];
   ULONG  ulDA1, ulDA2;
   LONG   lDiskStat;
   UBYTE  ub, *pszStat, *pszBufPtr, szBuf[256];
   ULONG *pulData = NULL;
   BPTR   fhTemp = NULL;


   //write to T:
   //get size
   //allocvec for mem items + diskaddr of each object not in mem
   //fill memory struct
   //return

   //
   // Open temp file in T: drive
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( szTmpNam )
   {
      strcpy( szTmpNam, "T:" );
      tmpnam( &szTmpNam[2] );
      if ( szTmpNam[2] )
      {
         fhTemp = Open( szTmpNam, MODE_NEWFILE );
      }
   }

   //
   // Get data from attached objects
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   for ( lDiskStat = 0, ub = 0; ub < data->ubDLLen; ub++ )
   {
      if ( pulObjAttrs == NULL )
      {
         // get data from attached objects
         if ( ((data->pDL)+ub)->hG_Hook )
         {
            //
            // Call hook to get data for this object into 'pszStat'
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            CallHook( ((data->pDL)+ub)->hG_Hook, ((data->pDL)+ub)->pObj, &pszStat );
         }
         else
         {
            get( ((data->pDL)+ub)->pObj, ((data->pDL)+ub)->ulAttr, &pszStat );
         }
      }
      else
      {
         // get data from passed-in array
         pszStat = (UBYTE *)*( pulObjAttrs + ub );
      }

      ulData[ ub ] = (ULONG)pszStat;

      switch( ((data->pDL)+ub)->ubType )
      {
         case TYPE_STRING:
            if ( fhTemp )
            {
               ulDA1 = Seek( fhTemp, 0, OFFSET_CURRENT );
               if ( ( lDiskStat = FPuts( fhTemp, pszStat     ) ) == -1 )   break;      // -1 = error
               if ( ( lDiskStat = FPutC( fhTemp, LEVEL1BREAK ) ) == -1 )   break;      // -1 = error
               ulDA2 = Seek( fhTemp, 0, OFFSET_CURRENT );
            }
            else
            {
               ulDA2 = strlen( pszStat ) + 1;
               ulDA1 = 0;
            }
            break;
         case TYPE_INTEGER:
            if ( fhTemp )
            {
               ulDA1 = Seek( fhTemp, 0, OFFSET_CURRENT );
               sprintf( szBuf, "%ld%c", atol( pszStat ), LEVEL1BREAK );
               if ( ( lDiskStat = FPuts( fhTemp, pszStat ) ) == -1 )       break;      // -1 = error
               ulDA2 = Seek( fhTemp, 0, OFFSET_CURRENT );
            }
            else
            {
               ulDA2 = strlen( szBuf );
               ulDA1 = 0;
            }
            break;
         case TYPE_INDEXED:
            if ( fhTemp )
            {
               ulDA1 = Seek( fhTemp, 0, OFFSET_CURRENT );
               sprintf( szBuf, "%ld%c", (ULONG)pszStat, LEVEL1BREAK );
               if ( ( lDiskStat = FPuts( fhTemp, pszStat ) ) == -1 )       break;      // -1 = error
               ulDA2 = Seek( fhTemp, 0, OFFSET_CURRENT );
            }
            else
            {
               ulDA2 = strlen( szBuf );
               ulDA1 = 0;
            }
            break;
      }

      //
      // Add up required length of memory pool
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( ((data->pDL)+ub)->ubMemOrDisk == 'M' )
         ulDataLen += ( ulDA2 - ulDA1 );
      else
         ulDataLen += 4;      // alloc a ULONG for DiskAddr
   }

   if ( fhTemp )     Close( fhTemp );

   if ( lDiskStat == -1 )
   {
      return( 0 );      // disk error!
   }

   //
   // Allocate storage and fill it in
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( !( pulData = AllocVec( ulDataLen, MEMF_ANY ) ) )
      return( 0 );

   pszBufPtr = (UBYTE *)(pulData + data->ubDLLen);
   for ( ub = 0; ub < data->ubDLLen; ub++ )
   {
      if ( ((data->pDL)+ub)->ubMemOrDisk == 'M' )
      {
         switch( ((data->pDL)+ub)->ubType )
         {
            case TYPE_STRING:
               *(pulData+ub) = (ULONG)pszBufPtr;
               strcpy( pszBufPtr, (UBYTE *)ulStat );
               pszBufPtr += ( strlen( (UBYTE *)ulStat ) + 1 );
               break;
            case TYPE_INTEGER:
               *(pulData+ub) = (ULONG)pszBufPtr;
               sprintf( pszBufPtr, "%ld", (LONG)ulStat );
               pszBufPtr += ( strlen( pszBufPtr ) + 1 );
               break;
            case TYPE_INDEXED:
               *(pulData+ub) = ulData[ ub ];
               break;
         }
      }
   }

   return( (APTR)pulData );
}


//
// fnCoalesceNS(): Merge adjacent NS
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void fnCoalesceNS( struct Data *data, LONG lDiskAddr )
{
   struct NSML_Node  *pNode = NULL, *pPred, *pSucc;
   BOOL               fFreeNode = FALSE, fFreeSucc = FALSE;


   for ( pNode = data->NSMLDA.mlh_Head; pNode; pNode = pNode->mln_Succ )
   {
      if ( pNode->ulDiskAddr == lDiskAddr )
      {
         pPred = pNode->mln_Pred;
         pSucc = pNode->mln_Succ;

         //
         // Combine previous node with current?
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         if ( ( pPred->ulDiskAddr + pPred->ulByteSize ) == pNode->ulDiskAddr )
         {
            pPred->ulByteSize += pNode->ulByteSize;
            fFreeNode = TRUE;
         }

         //
         // Combine current node with successor?
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         if ( ( pNode->ulDiskAddr + pNode->ulByteSize ) == pSucc->ulDiskAddr )
         {
            pNode->ulByteSize += pSucc->ulByteSize;
            fFreeSucc = TRUE;
         }

         if ( fFreeNode )
         {
            Remove( (struct Node *)pNode );
            FreeVec( pNode );
         }

         if ( fFreeSucc )
         {
            Remove( (struct Node *)pSucc );
            FreeVec( pSucc );
         }
         break;
      }
   }
}


LONG fnCompareData( ULONG *p1, ULONG *p2, UBYTE ubType, struct Data *data, LONG lColumn )
{
   LONG   rc = 0;    // assume comparison is equal


   switch( ubType )
   {
      case TYPE_INTEGER:
         if      ( (LONG)*p1 > (LONG)*p2 )  rc = 1;
         else if ( (LONG)*p1 < (LONG)*p2 )  rc = -1;
         break;
      case TYPE_STRING:
         rc = strcmp( (UBYTE *)*p1, (UBYTE *)*p2 );
         break;
      case TYPE_INDEXED:
         // ISSUE: compare ordinal value or compare against strings????
         // user will see strings -- compare against those (if ppValues != NULL)
         if ( ((data->pDL)+lColumn)->ppValuesArray )
         {
            // compare string values indexed into...
            rc = strcmp( (UBYTE *)*( ( ((data->pDL)+lColumn)->ppValuesArray )+(LONG)*p1 ),
                         (UBYTE *)*( ( ((data->pDL)+lColumn)->ppValuesArray )+(LONG)*p2 ) );
         }
         else
         {
            // compare ordinal values
            if      ( (LONG)*p1 > (LONG)*p2 )  rc = 1;
            else if ( (LONG)*p1 < (LONG)*p2 )  rc = -1;
         }
         break;
   }

   return( rc );
}


//
// fnNewNS(): Add NullSpace to NSML/NSMLDA
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BOOL fnNewNS( struct Data *data, ULONG ulDiskAddr, ULONG ulNSLen )
{
   BOOL               fInserted;
   struct NSML_Node  *pNodeML, *pNodeML2;


   // ADD TO NSML/NSMLDA
   pNodeML = (struct NSML_Node *)AllocVec( sizeof( struct NSML_Node ) * 2, MEMF_CLEAR );
   if ( pNodeML )
   {
      pNodeML->ulDiskAddr = ulDiskAddr;
      pNodeML->ulByteSize = ulNSLen;

      (pNodeML+1)->ulDiskAddr = ulDiskAddr;
      (pNodeML+1)->ulByteSize = ulNSLen;

      //
      // ADD TO NSML (sorted by block size -- descending order)
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( fInserted = FALSE, pNodeML2 = data->NSML.mlh_Head; pNodeML2; pNodeML2 = pNodeML2->mln_Succ )
      {
         if ( pNodeML->ulByteSize > pNodeML2->ulByteSize )
         {
            fInserted = TRUE;
            Insert( &(data->NSML), pNodeML, pNodeML2->mn_Node.mln_Pred );
            break;
         }
      }
      if ( fInserted == FALSE )
      {
         // add to tail of list
         AddTail( &(data->NSML), pNodeML );
      }

      //
      // ADD TO NSMLDA (sorted by diskaddr -- ascending order)
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( fInserted = FALSE, pNodeML2 = data->NSMLDA.mlh_Head; pNodeML2; pNodeML2 = pNodeML2->mln_Succ )
      {
         if ( (pNodeML+1)->ulDiskAddr < pNodeML2->ulDiskAddr )
         {
            fInserted = TRUE;
            Insert( &(data->NSMLDA), (pNodeML+1), pNodeML2->mn_Node.mln_Pred );
            break;
         }
      }
      if ( fInserted == FALSE )
      {
         // add to tail of list
         AddTail( &(data->NSMLDA), (pNodeML+1) );
      }
   }

   return( fInserted );
}


//
// fnReadDiskRecord(): write a record string to disk
//    RETURN pointer or NULL on failure
//    Caller must free returned buffer.
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
char *fnReadDiskRecord( Object *obj,     struct Data *data,
                        LONG lListIndex )
{
   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 )
      {
         data->lError = IoErr();
         Fault( IoErr(), "Disk read error!  ", data->szError, sizeof( data->szError ) );
         return( FALSE );
      }
      if ( ulRecSize != 0 )
      {
         rc = AllocVec( ulRecSize+4 );
         if ( rc )
         {
            *rc = Seek( data->fhListFile, 0, OFFSET_CURRENT ) - 4;
            lDiskStat = Read( data->fhListFile, rc+1, ulRecSize );
            if ( lDiskStat != ulRecSize )
            {
               data->lError = IoErr();
               Fault( IoErr(), "Disk read error!  ", data->szError, sizeof( data->szError ) );
               FreeVec( rc );
               return( FALSE );
            }
         }
      }
   }

   return( (char *)rc );
}


//
// fnSkipNullSpaces(): Read NULLSPACEs until no more or EOF
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SHORT fnSkipNullSpaces( struct Data *data )
{
   LONG     lDiskStat, rc;
   ULONG    ulDA1, ulDA2;
   UBYTE    ub[256];


   ulDA1 = Seek( data->fhListFile, 0, OFFSET_CURRENT );
   for ( rc = -1; rc == -1; )
   {
      lDiskStat = Read( data->fhListFile, ub, 256 );
      if ( ( lDiskStat > 0 ) && ( lDiskStat <= 256 ) )
      {
         for ( i = 0; i < lDiskStat; i++ )
         {
            if ( ubChar[ i ] != NULLSPACE )
            {
               // we've passed the NULLSPACE... reverse direction and return
               Seek( data->fhListFile, -(256-i+1), OFFSET_CURRENT );

               //
               // If we read a full 256 bytes, more data exists so
               // return 1 else we're at EOF, return 2
               // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
               rc = ( lDiskStat == 256 ) ? 1 : 2;
               break;
            }
         }
      }
      else
      {
         rc = 0;  // disk error
         break;
      }
   }

   //
   // If any NS was found, add it to the NSML/NSMLDA
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   ulDA2 = Seek( data->fhListFile, 0, OFFSET_CURRENT );
   if ( ulDA2 > ulDA1 )
   {
      fnNewNS( ulDA1, ulDA2 - ulDA1 + 1 );
   }

   return( rc );
}


//
// fnWriteDiskRecord(): write a record string to disk
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BOOL  fnWriteDiskRecord( struct Data *data, APTR pRec, UBYTE *pszTmpNam, LONG lReplaceLine )
{
   BOOL   rc = FALSE;      // assume failure
   UBYTE *pNode = NULL;
   LONG   lOldRecAddr = 0;
   LONG   lDiskStat;
   ULONG  ulRecSize, ulBlockSize, ulDiskBlockSize, ulRemainder;
   UBYTE  ubNullSpace = NULLSPACE;
   BOOL   fError;
   ULONG  ul;
   BPTR   fhTmp;


   if ( fhTmp = Open( pszTmpNam, MODE_OLDFILE ) )
   {
      ulDiskBlockSize = Seek( fhTmp, 0, OFFSET_END );
      ulBlockSize += 4;
      Close( fhTmp );
   }

   if ( lReplaceLine != MUIV_GHHDataList_AddRecord_NoneReplace )
   {
      //
      // Look at disk address; get rec size; if big enough,
      // replace ... else write NULLSPACEs over record and append
      // to end of file.
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      DoMethod( data->list, MUIM_List_GetEntry, lReplaceLine, &pNode );
      if ( !pNode )
      {
         data->lError = -1;
         strcpy( data->szError, "Cannot get entry from MUI List.\nThe data was not added to the file." );
         return( FALSE );
      }

      Seek( data->fhListFile, *((ULONG *)pNode), OFFSET_BEGINNING );
      lDiskStat = Read( data->fhListFile, &ulRecSize, 4 );
      if ( lDiskStat != 4 )
      {
         data->lError = IoErr();
         Fault( IoErr(), "Disk read error!  ", data->szError, sizeof( data->szError )-40 );
         strcat( data->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 ( JMH_Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
         {
            if ( fnWriteTmpToFile( data, pszTmpNam ) )
            {
               lDiskStat = FWrite( data->fhListFile, &ubNullSpace, 1, ulRemainder );
               if ( lDiskStat == ulRemainder )
               {
                  fnNewNS( data, ( lOldRecAddr + ulRecSize ), ulRemainder );
                  fnCoalesceNS( data, lOldRecAddr + ulRecSize );
                  data->ulUnusedSpace += ulRemainder;
                  rc = TRUE;  // SUCCESS!
               }
               else
               {
                  fError = TRUE;
                  rc = FALSE;
               }
            }
            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 ( JMH_Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
         {
            MG.ulTotalFileSize += 4;
            if ( fnWriteTmpToFile( data, pszTmpNam ) )
            {
               MG.ulTotalFileSize += ulDiskBlockSize;

               // blitz old record with NULLSPACEs...
               Seek( data->fhListFile, lOldRecAddr-4, OFFSET_BEGINNING );
               lDiskStat = FWrite( data->fhListFile, &ubNullSpace, 1, ulRecSize+4 );
               if ( lDiskStat == ulRecSize+4 )
               {
                  fnNewNS( data, lOldRecAddr, ulRecSize+4 );
                  fnCoalesceNS( data, lOldRecAddr );
                  data->ulUnusedSpace += ( ulRecSize+4 );
                  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 ( JMH_Write( data->fhListFile, &ulDiskBlockSize, 4 ) )
      {
         MG.ulTotalFileSize += 4;
         if ( fnWriteTmpToFile( data, pszTmpNam ) )
         {
            MG.ulTotalFileSize += ulDiskBlockSize;
            rc = TRUE;  // SUCCESS!
         }
      }
   }

   if ( rc == TRUE )
   {
      //
      // Finally, (if no error) add to listview...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( lReplaceLine != MUIV_GHHDataList_AddRecord_NoneReplace )
      {
         DoMethod( data->list, MUIM_List_Remove, lReplaceLine );
      }
      DoMethod( data->list, MUIM_List_InsertSingle, pRec, MUIV_List_Insert_Sorted );
      set( data->list, MUIA_List_Active, lReplaceLine );

      //
      // Delete the temporary file holding this record
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      DeleteFile( pszTmpNam );
   }
   else
   {
      data->lError = -1;
      strcpy( data->szError, "ERROR WRITING DATA -- POSSIBLE CORRUPTION" );
   }

   return( rc );
}


//
// fnWriteTmpToFile(): write temp-record-file to file
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
BOOL  fnWriteTmpToFile( struct Data *data, UBYTE *pszTmpNam )
{
   BOOL   rc = FALSE;      // assume failure
   LONG   lDiskStat, lLen;
   BPTR   fhTmp;
   APTR   pBuf;


   if ( fhTmp = Open( pszTmpNam, MODE_OLDFILE ) )
   {
      lLen = Seek( fhTmp, 0, OFFSET_END );
      pBuf = AllocVec( lLen+1, MEMF_ANY );
      if ( pBuf )
      {
         Seek( fhTmp, 0, OFFSET_BEGINNING );
         lDiskStat = Read( fhTmp, pBuf, lLen );
         if ( lDiskStat == lLen )
         {
            lDiskStat = Write( fhTmp, pBuf, lLen );
            if ( lDiskStat == lLen )
            {
               rc = TRUE;
            }
         }
      }
      Close( fhTmp );
   }

   return( rc );
}



//
// APPLICATION METHOD FUNCTIONS ------------------------------------------
//
static ULONG mAddRecord(struct IClass *cl,Object *obj, struct MUIP_GHHDataList_AddRecord *msg)
{
   LONG   lReplace = -1;
   LONG   lActive = 0, lNewActive;
   APTR   pRec = NULL;
   UBYTE  szTmpNam[L_tmpnam+3];

   struct Data *data = INST_DATA(cl,obj);


   get( data->list, MUIA_List_Active, &lActive );
   lReplace = msg->lReplaceLine;
   if ( lReplace == MUIV_GHHDataList_AddRecord_ActiveReplace )
   {
      lReplace = lActive;
   }
else
{
// there's a case here I can't remember............... ????????????
}

   // Add new record...
   if ( pRec = fnBuildRecord( data, NULL, szTmpNam ) )
   {
      set( data->list, MUIA_List_Quiet, TRUE );

//      // first delete old if needed...
//      if ( lReplace != MUIV_GHHDataList_AddRecord_NoneReplace )
//      {
//         // delete this item
//         DoMethod( data->list, MUIM_List_Remove, lActive );
//      }
//
//      // ...then insert new record
//      DoMethod( data->list, MUIM_List_InsertSingle, pRec, MUIV_List_Insert_Sorted );
//      get( data->list, MUIA_List_InsertPosition, &lNewActive );
//
      fnWriteDiskRecord( data, pRec, szTmpNam, lReplace );

//      // set new active item
//      set( data->list, MUIA_List_Active, lNewActive );
//
      set( data->list, MUIA_List_Quiet, FALSE );
   }
   return(0);
}


static ULONG mAddRecordArray(struct IClass *cl,Object *obj, struct MUIP_GHHDataList_AddRecordFromArray *msg)
{
   LONG   lReplace = -1;
   LONG   lActive = 0, lNewActive;
   APTR   pRec = NULL;
   UBYTE  szTmpNam[L_tmpnam+3];

   struct Data *data = INST_DATA(cl,obj);


   get( data->list, MUIA_List_Active, &lActive );
   lReplace = msg->lReplaceLine;
   if ( lReplace == MUIV_GHHDataList_AddRecord_ActiveReplace )
   {
      lReplace = lActive;
   }
else
{
// there's a case here I can't remember...............????????????
}

   // Add new record...
   if ( pRec = fnBuildRecord( data, (ULONG *)(msg->ppulArray), szTmpNam ) )
   {
      set( data->list, MUIA_List_Quiet, TRUE );

//      // first delete old if needed...
//      if ( lReplace != MUIV_GHHDataList_AddRecord_NoneReplace )
//      {
//         // delete this item
//         DoMethod( data->list, MUIM_List_Remove, lActive+1 );
//      }
//
//      // ...then insert the new record
//      DoMethod( data->list, MUIM_List_InsertSingle, pRec, MUIV_List_Insert_Sorted );
//      get( data->list, MUIA_List_InsertPosition, &lNewActive );
//
      fnWriteDiskRecord( data, pRec, szTmpNam, lReplace );

//      // set new active item
//      set( data->list, MUIA_List_Active, lNewActive );
//
      set( data->list, MUIA_List_Quiet, FALSE );
   }
   return(0);
}


static ULONG mAttach(struct IClass *cl,Object *obj, struct MUIP_GHHDataList_Attach *msg)
{
   struct Data        *data = INST_DATA(cl,obj);
   struct GHHDataList *pDL  = (struct GHHDataList *)data->pDL;


   if ( data->ubDLIndex >= data->ubDLLen )
   {
      return( 1 );      // error
   }

   //
   // Verify data is legit...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( ( msg->ubInList == 'Y' ) && ( msg->ubMemOrDisk != 'M' ) )
   {
      // if ubInList is 'Y', ubMemOrDisk *MUST* be 'M'
      return( 1 );      // error
   }

   pDL += (data->ubDLIndex);
   pDL->pObj           = msg->pObj;
   pDL->ulAttr         = msg->ulAttr;
   pDL->ulDefaultValue = msg->ulDefaultValue;
   pDL->ubType         = msg->ubType;
   pDL->ppValuesArray  = msg->ppValuesArray;
   pDL->ubMemOrDisk    = msg->ubMemOrDisk;
   pDL->ubInList       = msg->ubInList;
   pDL->hI_Hook        = msg->hI_Hook;
   pDL->hS_Hook        = msg->hS_Hook;
   pDL->hG_Hook        = msg->hG_Hook;

   (data->ubDLIndex)++;
   return(0);
}


static ULONG mCloseFile(struct IClass *cl,Object *obj,struct MUIP_GHHDataList_CloseFile *msg)
{
   struct Data       *data = INST_DATA(cl,obj);
   struct NSML_Node  *pNode;


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

   //
   // Free NullSpace memory lists...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   for ( pNode = data->NSML.mlh_Head; pNode; pNode = (struct NSML_Node *)pNode->node.mln_Succ )
   {
      Remove( (struct Node *)pNode );
      FreeVec( pNode );
   }
   memset( &(data->NSML)  , 0, sizeof( struct MinList ) );
   memset( &(data->NSMLDA), 0, sizeof( struct MinList ) );

   DoMethod( data->list, MUIM_List_Clear, TRUE );

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


//
// mOpenFile(): verify legitimacy of file; then read into GHHDL
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static ULONG mOpenFile(struct IClass *cl,Object *obj,struct MUIP_GHHDataList_OpenFile *msg)
{
   LONG   lDiskStat;
   UBYTE  szVerifyBuf[ HEADERSIZE+4+1 ];
   ULONG  ulBufSize = 2048;
   ULONG *pulBuf, ulRecordSize;
   ULONG  ulData[256];
   LONG   lDiskStat;
   ULONG  ulStat;
   SHORT  sOK;
   UBYTE  szDelim[2];


   data->lError = 0;
   data->szError[0] = 0;

   //
   // Open the file in OLD mode; it might not exist
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   data->fhListFile = Open( data->pszFile, MODE_OLDFILE );
   if ( !data->fhListFile )
   {
      //
      // This is a new file
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      data->fhListFile = Open( data->pszFile, MODE_READWRITE );
      if ( !data->fhListFile )
      {
         Fault( IoErr(), "Couldn't open file", data->szError, sizeof( data->szError ) );
         return( 1 );
      }
   }

   DoMethod( data->list, MUIM_List_Clear, TRUE );
   set( data->list, MUIA_List_Quiet, TRUE );

   lDiskStat = Read( data->fhListFile, &szVerifyBuf, HEADERSIZE );
   if ( ( lDiskStat != HEADERSIZE ) || ( strnicmp( szVerifyBuf, HEADERSTRING, HEADERSIZE ) != 0 )
   {
      //
      // Header does not match
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      data->lError = -1;
      strcpy( data->szError, "This is not a valid HHDL data file." );
      return( 1 );
   }

   NewList( &data->NSML );
   NewList( &data->NSMLDA );

   //
   // We'll use this infinite loop but actually loop only once;
   // if an error is encountered, we simply set a return code and break;
   // after the loop, we check for the error code and de-allocate
   // anything that's allocated... much cleaner and easier than having
   // deallocations and return statements spread all over!
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   BREAKLOOP
   {
      // 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 )
      {
         data->lError = -1;
         strcpy( data->szError, "NO MEMORY FOR DISK BUFFER" );
         break;
      }

      ulStat = 0;
      sOK = fnSkipNullSpaces( data );
      if       ( sOK == 0 )      // disk error
      {
         data->lError = IoErr();
         Fault( IoErr(), "Disk read error!\n", data->szError, sizeof( data->szError ) );
         break;
      }
      else if  ( sOK == 2 )      // skipped NULLSPACE; we're at EOF.
      {
         break;
      }

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

      ulRecordSize = *( (ULONG *)pulBuf );
      while ( ulRecordSize != 0 )
      {
         if ( ulRecordSize > ulBufSize )
         {
            FreeVec( pulBuf );
            ulBufSize = ulRecordSize + 4 + 1;
            pulBuf = AllocVec( ulBufSize, MEMF_ANY );
            if ( pulBuf == NULL )
            {
               data->lError = 0;
               strcpy( data->szError, "OUT OF MEMORY" );
               break;
            }
         }

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

         //
         // Build the record and stuff it up your... listbox!
         // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
         szDelim[0] = LEVEL1BREAK;

         pszStart = (UBYTE *)pulBuf;
         for ( ub = 0; ub < data->ubDLLen; ub++ )
         {
            psz = pszStart + strcspn( pszStart, szDelim );
            if ( psz )
            {
               *psz = 0;

               if ( ((data->pDL)+ub)->ubType == TYPE_STRING )
               {
                  ulData[ub] = (ULONG)pszStart;
               }
               else if ( ( ((data->pDL)+ub)->hG_Hook ) ||
                         ( ((data->pDL)+ub)->hS_Hook ) )
               {
                  // if this is a hook'ed field, store a pointer to the data...
                  CallHook( ((data->pDL)+ub)->hI_Hook, ((data->pDL)+ub)->pObj, &ulData[ub] );
               }
               else
               {
                  // TYPE_INTEGER or TYPE_INDEXED
                  ulData[ub] = atol( pszStart );
               }

               psz++;   // skip over delimeter to next field
               pszStart = psz;
            }
         }
         ulData[ data->ubDLLen ] = 0;

         if ( pRec = fnBuildRecord( data, ulData, NULL ) )
         {
            DoMethod( data->list, MUIM_List_InsertSingle, pRec, MUIV_List_Insert_Sorted );
         }

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

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

      if ( ( data->lError ) || ( data->szError[0] ) )
      {
         break;
      }
   }

   //
   // free any allocated resources...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   set( data->list, MUIA_List_Quiet, FALSE );
   if ( pulBuf )  FreeVec( pulBuf );

   //
   // check for errors... then return
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( ( data->lError ) || ( data->szError[0] ) )
   {
      return( 1 );
   }
   else
   {
      set( data->list, MUIA_List_Active, 0L );
      return( 0 );
   }
}


SAVEDS ULONG mRemove(struct IClass *cl,Object *obj,struct MUIP_GHHDLRemove *msg)
{
   struct Data             *data    = INST_DATA(cl,obj);
   struct MUIP_GHHDLRemove *params  = ((struct opSet *)msg)->ops_AttrList;
   struct NSML_Node        *pNodeML = NULL, *pNodeML2;

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


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

   if ( params->pos == MUIV_DataList_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 );

      // del from disk: from robocook/storage.c
      DoMethod( obj, MUIM_List_GetEntry, lActive, &pNode );
      if ( 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 )
         {
            data->lError = IoErr();
            Fault( IoErr(), "Disk read error!  ", data->szError, sizeof( data->szError )-50 );
            strcat( data->szError, "  The recipe was not deleted from the file." );
            return( FALSE );
         }

         lDA = Seek( data->fhListFile, -4, OFFSET_CURRENT );
         SetIoErr( NULL );
         lDiskStat = FWrite( data->fhListFile, &ubNullSpace, 1, ulRecordLen+4 );
         if ( lDiskStat == ulRecordLen+4 )
         {
            fnNewNS( data, *((ULONG *)pNodeML), ulRecordLen+4 );
            fnCoalesceNS( data, lDA );
            data->ulUnusedSpace += ( ulRecordLen+4 );
         }
         else
         {
            fError = TRUE;
            data->lError = IoErr();
            strcpy( data->szError, "ERROR WRITING DATA -- POSSIBLE CORRUPTION" );
            return( FALSE );
         }
      }
      else
      {
         fError = TRUE;
         data->lError = -1;
         strcpy( data->szError, "Cannot get entry from MUI List.\nERROR WRITING DATA -- POSSIBLE CORRUPTION" );
         return( FALSE );
      }
   }
   else
   {
      data->lError = -1;
      strcpy( data->szError, "ERROR WRITING DATA -- POSSIBLE CORRUPTION" );
      return( FALSE );
   }

   return( TRUE );
}


SAVEDS ULONG mReOrganize(struct IClass *cl,Object *obj,struct MUIP_GHHDLReOrganize *msg)
{
   struct Data              *data   = INST_DATA(cl,obj);
   struct MUIP_GHHDLReOrganize *params = ((struct opSet *)msg)->ops_AttrList;

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


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

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

   // reorg
   pDiskBufferIn = AllocVec( 16384, MEMF_ANY );
   if ( !pDiskBufferIn )
   {
      data->lError = -1;
      strcpy( data->szError, "NO MEMORY FOR BACKUP BUFFER" );
      return( FALSE );
   }

   pDiskBufferOut = AllocVec( 16384, MEMF_ANY );
   if ( !pDiskBufferOut )
   {
      data->lError = -1;
      strcpy( data->szError, "NO MEMORY FOR OUTPUT BUFFER" );
      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 data file because",
                data->szError,
                sizeof( data->szError ) );
      else
         strcpy( data->szError, "CANNOT MAKE BACKUP COPY OF DATA FILE" );

      data->lError = IoErr();
   }
   else
   {
      if ( !DeleteFile( data->pszListFile ) )
      {
         data->lError = -1;
         strcpy( data->szError, "COULD NOT DELETE OLD DATA FILE" );
      }
      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;
            }

            data->lError = -1;
            strcpy( data->szError, "COULD NOT OPEN NEW DATA FILE" );

            //
            // 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 )
            {
               data->lError = -1;
               strcpy( data->szError, "CANNOT RESTORE DATA FILE TO ORIGINAL LOCATION" );
               // the file is located in the location the user specified!
            }
         }
         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 )
                     {
                        data->lError = -1;
                        strcpy( data->szError, "CANNOT WRITE DATA -- OLD FILE IS IN BACKUP LOCATION" );
                        break;
                     }
                     else if ( params->pGauge )
                     {
                        lDoneSoFar += lReadSize;
                        ulStat = 100 * (float)( (float)lDoneSoFar / (float)(MG.ulTotalFileSize+HEADERSIZE) );
                        set( params->pGauge, MUIA_Gauge_Current, ulStat );
                     }
                  }
               }
            }
            if ( lDiskStat < 0 )
            {
               data->lError = -1;
               strcpy( data->szError, "CANNOT WRITE DATA -- OLD FILE IS IN BACKUP LOCATION" );
               Fault( IoErr(),
                      "Could not write file status because",
                      data->lError,
                      sizeof( data->lError ) );
            }
            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 )
               {
                  data->lError = -1;
                  Fault( IoErr(),
                         "Could not mark backup file as 'OLD' because",
                         data->szError,
                         sizeof( data->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( data, params ) == FALSE )
         {
            // file read error... unable to re-load
            data->lError = -1;
            strcpy( data->szError, "UNABLE TO RELOAD NEW DATA" );
            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 );
}


static ULONG mRetrieveArray(struct IClass *cl,Object *obj,struct MUIP_GHHDLRetrieveArray *msg)
{
   struct Data *data   = INST_DATA(cl,obj);
   UBYTE        ub, *pszStat;


   for( ub = 0; ub < data->ubDLLen; ub++ )
   {
      if ( ((data->pDL)+ub)->hG_Hook )
      {
         *(ppulArray)+ub = CallHook( ((data->pDL)+ub)->hG_Hook, ((data->pDL)+ub)->pObj, &pszStat );
      }
      else
      {
         get( ((data->pDL)+ub)->pObj, ((data->pDL)+ub)->ulAttr, &pszStat );
         switch ( ((data->pDL)+ub)->ubType )
         {
            case TYPE_INTEGER:
               *(ppulArray)+ub = atol( pszStat );
               break;
            case TYPE_INDEXED:
            case TYPE_STRING:
               *(ppulArray)+ub = pszStat;
               break;
         }
      }
   }

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


//
// MUI-STANDARD METHOD FUNCTIONS -----------------------------------------
//
SAVEDS ULONG mNew(struct IClass *cl,Object *obj,Msg msg)
{
   struct Data  *data;
   APTR          pObjNewDel  = NULL, pObjNumSel  = NULL;
   APTR          pLinkNewDel = NULL, pLinkNumSel = NULL;
   ULONG        ulFrameType;
   BOOL         fListTitles;
   UBYTE        ub;


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

   data = INST_DATA(cl,obj);
   memset( (char *)data, 0, sizeof( struct Data ) );

   data->hDestructHook        = &hDestructHook;
   data->hDisplayHook         = &hDisplayHook;
   data->hDisplayHook->h_Data = data;
   data->hSortHook            = &hSortHook;
   data->hSortHook->h_Data    = data;

   data->ubDLLen = (UBYTE)GetTagData( MUIA_GHHDataList_AttachCount, 1, ((struct opSet *)msg)->ops_AttrList );
   for ( ub = 0; ub < data->ubDLLen; ub++ )
   {
      data->szColumns[ub] = ',';
   }
   ub = ( ub ? ub-1 : 0 );
   data->szColumns[ub] = 0;   // 3 columns means 2 commas!!!

   data->pDL = AllocVec( data->ubDLLen * ( sizeof( struct GHHDataList ) + sizeof( ULONG ) ), MEMF_ANY | MEMF_CLEAR );
   if ( !( data->pDL ) )
   {
      return( 0 );      // couldn't allocate memory for object attachment buffers
   }

   data->pszDelButton   = (UBYTE *)GetTagData(MUIA_GHHDataList_DelButton  , (ULONG)"_Del", ((struct opSet *)msg)->ops_AttrList);
   data->pszDelConfirm  = (UBYTE *)GetTagData(MUIA_GHHDataList_DelConfirm , (ULONG)"Are you sure you want to delete the item(s)?", ((struct opSet *)msg)->ops_AttrList);
   data->pszDelResponse = (UBYTE *)GetTagData(MUIA_GHHDataList_DelResponse, (ULONG)"Yes|*No", ((struct opSet *)msg)->ops_AttrList);
   data->sDelVerify     = (SHORT)  GetTagData(MUIA_GHHDataList_DelVerify  , FALSE , ((struct opSet *)msg)->ops_AttrList);
   data->ubDelimeter    = (UBYTE)  GetTagData(MUIA_GHHDataList_Delimeter  , ','   , ((struct opSet *)msg)->ops_AttrList);
   data->pFocusObject   = (APTR)   GetTagData(MUIA_GHHDataList_FocusObject, NULL  , ((struct opSet *)msg)->ops_AttrList);

   data->pFocusWindowHook = (APTR) GetTagData(MUIA_GHHDataList_FocusWindowHook, NULL, ((struct opSet *)msg)->ops_AttrList);
   data->sFrameType       = (SHORT)GetTagData(MUIA_GHHDataList_Input, TRUE, ((struct opSet *)msg)->ops_AttrList);

   data->pszNewButton    = (UBYTE *) GetTagData(MUIA_GHHDataList_NewButton    , (ULONG)"_New", ((struct opSet *)msg)->ops_AttrList);
   data->pszNumSelected  = (UBYTE *) GetTagData(MUIA_GHHDataList_NumSelected  , NULL, ((struct opSet *)msg)->ops_AttrList);
   data->lSortDirection  = (LONG)    GetTagData(MUIA_GHHDataList_Sort         , MUIV_GHHDataList_Sort_Ascending, ((struct opSet *)msg)->ops_AttrList);
   data->lSortPrimary    = (LONG)    GetTagData(MUIA_GHHDataList_SortPrimary  , MUIV_GHHDataList_Sort_None, ((struct opSet *)msg)->ops_AttrList);
   data->lSortSecondary  = (LONG)    GetTagData(MUIA_GHHDataList_SortSecondary, MUIV_GHHDataList_Sort_None, ((struct opSet *)msg)->ops_AttrList);

   data->ppszTitles      = (UBYTE **)GetTagData(MUIA_GHHDataList_Titles       , NULL, ((struct opSet *)msg)->ops_AttrList);

   ulFrameType = ( data->sFrameType ? MUIV_Frame_InputList : MUIV_Frame_ReadList );
   fListTitles = ( data->ppszTitles ? TRUE : FALSE );

   data->group = HGroup,
      Child, VGroup, GroupSpacing(0),
         Child, data->list = ListviewObject,
            MUIA_Listview_Input, data->sFrameType,
            MUIA_Listview_MultiSelect, MUIV_Listview_MultiSelect_Default,
            MUIA_Listview_List, ListObject,
               MUIA_Frame, ulFrameType,
               MUIA_List_CompareHook  , &hSortHook,
               MUIA_List_DestructHook , &hDestructHook,
               MUIA_List_DisplayHook  , &hDisplayHook,
               MUIA_List_Format       , data->szColumns,
               MUIA_List_Title        , fListTitles,
               End,
            End,
         Child, pLinkNewDel = HGroup,
            End,
         End,
      End;

   //
   // Add New and Delete buttons?
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( data->sFrameType )
   {
      pObjNewDel =
         HGroup,
            Child, data->newbutton = MUI_MakeObject( MUIO_Button, data->pszNewButton ),
            Child, data->delbutton = MUI_MakeObject( MUIO_Button, data->pszDelButton ),
            Child, HSpace(0),
            Child, pLinkNumSel = HGroup,
               End,
            End;
   }
   else
   {
      /* no New/Del for ReadList */
      pObjNewDel = HSpace(0);
   }

   //
   // Add Items-selected text object?
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( data->pszNumSelected )
   {
      pObjNumSel = data->numseltext = TextObject, End;
   }
   else
   {
      /* no seltext object */
      pObjNumSel = HSpace(0);
   }

   //
   // Did everything get constructed?  If not, dispose and return 0
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( !pObjNewDel || !pObjNumSel )
   {
      if ( pObjNewDel )    MUI_DisposeObject( pObjNewDel );
      if ( pObjNumSel )    MUI_DisposeObject( pObjNumSel );
      CoerceMethod(cl,obj,OM_DISPOSE);
      return(0);
   }
   else
   {
      //
      // Attach child groups to parent
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      DoMethod( pLinkNumSel, OM_ADDMEMBER, pObjNumSel );
      DoMethod( pLinkNewDel, OM_ADDMEMBER, pObjNewDel );
      if (!data->group)
      {
         CoerceMethod(cl,obj,OM_DISPOSE);
         return(0);
      }
   }

   if ( data->pszNumSelected )
   {
      sprintf( data->szNumSelected, data->pszNumSelected, 0 );
      set( data->numseltext, MUIA_Text_Contents, data->szNumSelected );
   }

   // set up interconnections and hooks
   DoMethod( data->newbutton   , MUIM_Notify, MUIA_Pressed, FALSE,
             data->newbutton   , 3, MUIM_CallHook, &hNewButtonHook, data );
   DoMethod( data->delbutton   , MUIM_Notify, MUIA_Pressed, FALSE,
             data->delbutton   , 3, MUIM_CallHook, &hDelButtonHook, data );

   DoMethod( data->list        , MUIM_Notify, MUIA_Listview_SelectChange, TRUE,
             data->list        , 3, MUIM_CallHook, &hNumSelHook, data );
   DoMethod( data->list        , MUIM_Notify, MUIA_List_Active, MUIV_EveryTime,
             data->list        , 3, MUIM_CallHook, &hNumSelHook, data );

   // add object
   DoMethod( obj, OM_ADDMEMBER, data->group );

   msg->MethodID = OM_SET;    /* a little trick to parse taglist */
   DoMethodA( obj, (Msg)msg );     /* and set attributes with OM_SET. */
   msg->MethodID = OM_NEW;    /* restore method id               */

   return((ULONG)obj);
}


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


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

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


static ULONG mGet(struct IClass *cl,Object *obj,Msg msg)
{
   struct Data *data = INST_DATA(cl,obj);
   ULONG *store = ((struct opGet *)msg)->opg_Storage;

   switch (((struct opGet *)msg)->opg_AttrID)
   {
      case MUIA_GHHDataList_FileSpec:
         *store = data->pszListFile;
         return( TRUE );
      case MUIA_GHHDataList_FileSize:
         *store = data->ulTotalSpace;
         return( TRUE );
      case MUIA_GHHDataList_FocusObject:
         *store = data->pFocusObject;
         return( TRUE );
      case MUIA_GHHDataList_ReOrgGainBytes:
         *store = data->ulUnusedSpace;
         return( TRUE );
      case MUIA_GHHDataList_SortSpec:
         *store = data->pszSortSpec;
         return( TRUE );
   }

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


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


   for (tags=((struct opSet *)msg)->ops_AttrList;tag=NextTagItem(&tags);)
   {
      switch (tag->ti_Tag)
      {
         case MUIA_GHHDataList_DelConfirm:
            data->pszDelConfirm = (UBYTE *)GetTagData(MUIA_GHHDataList_DelConfirm, (ULONG)"Are you sure you want to delete the item(s)?",
                                                     ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_DelResponse:
            data->pszDelResponse = (UBYTE *)GetTagData(MUIA_GHHDataList_DelResponse, (ULONG)"Yes|*No", ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_DelVerify:
            data->sDelVerify = (SHORT)GetTagData(MUIA_GHHDataList_DelVerify, FALSE, ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_FocusObject:
            data->pFocusObject = (APTR)GetTagData(MUIA_GHHDataList_FocusObject, NULL, ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_Sort:
            data->lSortDirection = (LONG)GetTagData(MUIA_GHHDataList_Sort, MUIV_GHHDataList_Sort_Ascending, ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_SortPrimary:
            data->lSortPrimary = (LONG)GetTagData(MUIA_GHHDataList_SortPrimary, MUIV_GHHDataList_Sort_None, ((struct opSet *)msg)->ops_AttrList);
            break;
         case MUIA_GHHDataList_SortSecondary:
            data->lSortSecondary = (LONG)GetTagData(MUIA_GHHDataList_SortSecondary, MUIV_GHHDataList_Sort_None, ((struct opSet *)msg)->ops_AttrList);
            break;
      }
   }

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


SAVEDS ULONG mSetup(struct IClass *cl,Object *obj,Msg msg)
{
   struct Data  *data;


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

   data = INST_DATA(cl,obj);
// ??????????????????????????????????????????????????????????????
// hopefully it will call the hook first, yes????????????????????
// ??????????????????????????????????????????????????????????????
   DoMethod( data->list        , MUIM_Notify, MUIA_Listview_DoubleClick, TRUE,
             _win( data->list ), 3, MUIM_CallHook, data->hFocusWindowHook, data );
   DoMethod( data->list        , MUIM_Notify, MUIA_Listview_DoubleClick, TRUE,
             _win( data->list ), 3, MUIM_Set, MUIA_Window_ActiveObject, data->pFocusObject );

   return((ULONG)obj);
}


/*
** Here comes the dispatcher for our custom class.
** 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_Setup    : return(mSetup    (cl,obj,(APTR)msg));

      case MUIM_GHHDataList_AddRecord         : return(mAddRecord     (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_AddRecordFromArray: return(mAddRecordArray(cl,obj,(APTR)msg));
      case MUIM_GHHDataList_Attach            : return(mAttach        (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_CloseFile         : return(mCloseFile     (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_FillObjects       : return(mFillObjects   (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_OpenFile          : return(mOpenFile      (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_Remove            : return(mRemove        (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_ReOrganize        : return(mReOrganize    (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_Retrieve          : return(mRetrieve      (cl,obj,(APTR)msg));
      case MUIM_GHHDataList_RetrieveArray     : return(mRetrieveArray (cl,obj,(APTR)msg));
   }

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


//
//   L  I  S  T     H  O  O  K     F  U  N  C  T  I  O  N  S
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SAVEDS ASM LONG fnDisplayFunc(REG(a0) struct Hook *hook,REG(a2) char **array,REG(a1) ULONG *pulArray )
{
   UBYTE        ub;
   ULONG        ulData;
   struct Data *data;


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

   if ( pulArray )
   {
      for ( ub = 0; ub < data->ubDLLen; ub++, array++ )
      {
         ulData = *(pulArray+ub);
         switch( ((data->pDL)+ub)->ubIsString )
         {
            case TYPE_STRING:
               *array = (UBYTE *)ulData;
               break;
            case TYPE_INTEGER:
               *array = (UBYTE *)ulData;
               break;
            case TYPE_INDEXED:
               *array = (UBYTE *)*( ( ((data->pDL)+ub)->ppValuesArray )+ulData );
               break;
         }
      }
      if ( data->ubDLLen ) *array--;
   }
   else if ( data->ppszTitles )
   {
      //
      // display list titles
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      for ( ub = 0; ub < data->ubDLLen; ub++, array++ )
      {
         *array = *((data->ppszTitles)+ub);
      }
      if ( data->ubDLLen ) *array--;
   }

   return(0);
}
static struct Hook hDisplayHook = {
   {NULL, NULL},
   (void *)fnDisplayFunc,
   NULL, NULL
};


SAVEDS ASM LONG fnSortFunc( REG(a0) struct Hook *hook, REG(a1) ULONG *pRec1, REG(a2) ULONG *pRec2 )
{
   LONG   rc = 0;    // equal comparison is default
   APTR   p1, p2;
   LONG   l1, l2;
   UBYTE  ubStr, ubNdx;
   struct Data *data;


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

   if ( data->lSortDirection == MUIV_GHHDataList_Sort_None )
   {
      return( 0 );
   }
   else
   {
      l1 = data->lSortPrimary;
      l2 = data->lSortSecondary;

      //
      // Compare Primary Key...
      // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      if ( l1 != MUIV_GHHDataList_Sort_None )
      {
         p1 = (APTR)(pRec1+l1);
         p2 = (APTR)(pRec2+l1);
         ubType = ((data->pDL)+l1)->ubType;
         rc = fnCompareData( (ULONG *)p1, (ULONG *)p2, ubType, data, l1 );

         if ( rc == 0 )
         {
            //
            // Compare Secondary Key...
            // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            if ( l2 != MUIV_GHHDataList_Sort_None )
            {
               p1 = (APTR)(pRec1+l2);
               p2 = (APTR)(pRec2+l2);
               ubStr = ((data->pDL)+l2)->ubType;
               rc = fnCompareData( (ULONG *)p1, (ULONG *)p2, ubType, data, l2 );
            }
         }
      }
   }

   if ( data->lSortDirection == MUIV_GHHDataList_Sort_Descending )
   {
      rc = -rc;
   }

   return( rc );
}
static struct Hook hSortHook = {
   {NULL, NULL},
   (void *)fnSortFunc,
   NULL, NULL
};


SAVEDS ASM VOID fnDestructFunc(REG(a0) struct Hook *hook, REG(a2) APTR mempool, REG(a1) APTR pMem )
{
   FreeVec( pMem );
}
static struct Hook hDestructHook = {
   {NULL, NULL},
   (void *)fnDestructFunc,
   NULL, NULL
};


SAVEDS ASM APTR fnNumSelFunc( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   ULONG   ulNumSel = 0;
   LONG    lStat = MUIV_List_NextSelected_Start;
   ULONG  *ulArray = NULL;
   UBYTE   ub, *pszStat;

   struct Data *data = (struct Data *)(*args);

   
   //
   // Get total selected
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   DoMethod( obj, MUIM_List_Select, MUIV_List_Select_All, MUIV_List_Select_Ask, &ulNumSel );

   //
   // If more than one item is selected, disable all pDL's
   // else enable and populate with values...
   // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   if ( ulNumSel > 1 )
   {
      for ( ub = 0; ub < data->ubDLLen; ub++ )
      {
         DoMethod( ((data->pDL)+ub)->pObj, MUIM_NoNotifySet, MUIA_Disabled, TRUE );
      }
   }
   else
   {
      DoMethod( data->list, MUIM_List_GetEntry, MUIV_List_GetEntry_Active, &ulArray );
      if ( ulArray )
      {
         for ( ub = 0; ub < data->ubDLLen; ub++ )
         {
            if ( ((data->pDL)+ub)->hG_Hook )
            {
               //
               // Call hook to get data for this object into 'pszStat'
               // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
               CallHook( ((data->pDL)+ub)->hG_Hook, ((data->pDL)+ub)->pObj, &pszStat );
               DoMethod( ((data->pDL)+ub)->pObj, MUIM_NoNotifySet, ((data->pDL)+ub)->ulAttr, pszStat );
            }
            else
            {
               DoMethod( ((data->pDL)+ub)->pObj, MUIM_NoNotifySet, ((data->pDL)+ub)->ulAttr, *(ulArray+ub) );
            }
            DoMethod( ((data->pDL)+ub)->pObj, MUIM_NoNotifySet, MUIA_Disabled, FALSE );
         }
      }
   }
   return(0);
}
static struct Hook hNumSelHook = {
   {NULL, NULL},
   (void *)fnNumSelFunc,
   NULL, NULL
};


//
//   H  O  O  K  S
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SAVEDS ASM APTR hNewButton   ( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   LONG   lStat;
   APTR   pRec = NULL;
   UBYTE  ub;
   ULONG  ulNewData[256];

   struct Data *data = (struct Data *)(*args);


   for ( ub = 0; ub < data->ubDLLen; ub++ )
   {
      if ( ((data->pDL)+ub)->hI_Hook )
      {
         ulNewData[ ub ] = CallHook( ((data->pDL)+ub)->hI_Hook, ((data->pDL)+ub)->pObj, &pszStat );
      }
      else
      {
         ulNewData[ ub ] = ((data->pDL)+ub)->ulDefaultValue;
      }
   }
   if ( pRec = fnBuildRecord( data, ulNewData, NULL ) )
   {
      DoMethod( data->list, MUIM_List_InsertSingle, pRec, data->lInsertPosition );
      get( data->list, MUIA_List_InsertPosition, &lStat );
      set( data->list, MUIA_List_Active, lStat );
      set( _win(obj), MUIA_Window_ActiveObject, data->pFocusObject );
   }

   return( NULL );
}
static struct Hook hNewButtonHook = {
   {NULL, NULL},
   (void *)hNewButton,
   NULL, NULL
};

SAVEDS ASM APTR hDelButton   ( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   BOOL  fDeleteIt = FALSE;
   LONG  lActive;

   struct Data *data = (struct Data *)(*args);


   switch( data->sDelVerify )
   {
      case FALSE:
         fDeleteIt = TRUE;
         break;
      case TRUE:
         if ( MUI_Request( _app(obj), _win(obj), 0, NULL, data->pszDelResponse,
                           data->pszDelConfirm, NULL ) == 1 )
         {
            fDeleteIt = TRUE;
         }
         break;
   }

   if ( fDeleteIt == TRUE )
   {
      get( data->list, MUIA_List_Active, &lActive );

      // delete active item
      if ( lActive != MUIV_List_Active_Off )
      {
         DoMethod( data->list, MUIM_GHHDataList_Remove, lActive );
      }

      // set new active item
      set( data->list, MUIA_List_Active, lActive );
   }

   return( NULL );
}
static struct Hook hDelButtonHook = {
   {NULL, NULL},
   (void *)hDelButton,
   NULL, NULL
};

SAVEDS ASM APTR hUpButton    ( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   struct Data *data = (struct Data *)(*args);
   LONG         lActive;


   get( data->list, MUIA_List_Active, &lActive );
   DoMethod( data->list, MUIM_List_Move, MUIV_List_Move_Active, MUIV_List_Move_Previous );
   set( data->list, MUIA_List_Active, ( lActive > 0 ) ? lActive-1 : 0 );
   return(0);
}
static struct Hook hUpButtonHook = {
   {NULL, NULL},
   (void *)hUpButton,
   NULL, NULL
};

SAVEDS ASM APTR hDownButton  ( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   struct Data *data = (struct Data *)(*args);
   LONG         lActive;


   get( data->list, MUIA_List_Active, &lActive );
   DoMethod( data->list, MUIM_List_Move, MUIV_List_Move_Active, MUIV_List_Move_Next );
   set( data->list, MUIA_List_Active, lActive+1 );
   return(0);
}
static struct Hook hDownButtonHook = {
   {NULL, NULL},
   (void *)hDownButton,
   NULL, NULL
};

SAVEDS ASM APTR hTopButton   ( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   struct Data *data = (struct Data *)(*args);


   DoMethod( data->list, MUIM_List_Move, MUIV_List_Move_Active, MUIV_List_Move_Top );
   set( data->list, MUIA_List_Active, 0 );
   return(0);
}
static struct Hook hTopButtonHook = {
   {NULL, NULL},
   (void *)hTopButton,
   NULL, NULL
};

SAVEDS ASM APTR hBottomButton( REG(a0) struct Hook *hook, REG(a1) APTR **args, REG(a2) APTR obj )
{
   struct Data *data = (struct Data *)(*args);
   LONG         lEntries;


   get( data->list, MUIA_List_Entries, &lEntries );
   DoMethod( data->list, MUIM_List_Move, MUIV_List_Move_Active, MUIV_List_Move_Bottom );
   set( data->list, MUIA_List_Active, lEntries-1 );
   return(0);
}
static struct Hook hBottomButtonHook = {
   {NULL, NULL},
   (void *)hBottomButton,
   NULL, NULL
};
