
/*
**
**  $VER: dispatch.c 1.4 (25.8.96)
**  directory.datatype 1.0
**
**  Dispatch routine for a DataTypes class
**
**  Written 1996 by Roland 'Gizzy' Mainz
**  Original example source from David N. Junod
**
*/

/* main includes */
#include "classbase.h"

struct NotifyTaskMessage
{
    struct Message        ntm_Message;   /* EXEC Message */
    struct ClassBase     *ntm_CB;        /* Datatypes context */
    Object               *ntm_Object;    /* Object */
    struct NotifyRequest  ntm_NR;
};

struct DirectoryInstData
{
    APTR                      did_Pool;
    BOOL                      did_NewData;     /* Source data changed ?    */
    BOOL                      did_InMarkMode;  /* Currently in mark mode ? */
    STRPTR                    did_DirName;     /* Same as DTA_ObjName      */
    struct Window            *did_Window;      /* Attached to (win, req)   */
    struct Requester         *did_Requester;
    struct NotifyTaskMessage  did_NTM;         /* Notify task */
    ULONG                    *did_Methods;
};


/*****************************************************************************/


struct IClass *initClass( struct ClassBase *cb )
{
    struct IClass *cl;

    /* Create our class... */
    if( cl = MakeClass( DIRECTORYDTCLASS, TEXTDTCLASS, NULL, sizeof( struct DirectoryInstData ), 0UL ) )
    {
      cl -> cl_Dispatcher . h_Entry = (HOOKFUNC)Dispatch;
      cl -> cl_UserData             = (ULONG)cb;

      AddClass( cl );
    }

    return( cl );
}

/*****************************************************************************/


void mysprintf( struct ClassBase *cb, STRPTR buffer, STRPTR fmt, ... )
{
    APTR args;

    args = (APTR)((&fmt) + 1);

    RawDoFmt( fmt, args, (void (*))"\x16\xc0\x4e\x75", buffer );
}


ULONG directory_excludemethods[] =
{
#ifdef COMMENTED_OUT
    DTM_COPY,
    DTM_WRITE,
    DTM_PRINT,
    DTM_CLEARSELECTED,
#endif /* COMMENTED_OUT */
    (~0UL)
};


ULONG directory_includemethods[] =
{
    DTM_SELECT,
    (~0UL)
};


DISPATCHERFLAGS
ULONG Dispatch( REGA0 struct IClass *cl, REGA2 Object *o, REGA1 Msg msg )
{
    struct ClassBase         *cb = (struct ClassBase *)(cl -> cl_UserData);
    struct DirectoryInstData *did;
    ULONG                     retval = 0UL;

    switch( msg -> MethodID )
    {
        case OM_NEW:
        {
            struct TagItem *ti;

            /* We only support a DTA_SourceType of DTST_FILE */
            if( ti = FindTagItem( DTA_SourceType, (((struct opSet *)msg) -> ops_AttrList) ) )
            {
              if( (ti -> ti_Data) != DTST_FILE )
              {
                SetIoErr( ERROR_OBJECT_WRONG_TYPE );

                break;
              }
            }

            if( retval = DoSuperMethodA( cl, o, msg ) )
            {
              /* Get a pointer to the object data */
              did = (struct DirectoryInstData *)INST_DATA( cl, retval );

              /* Create a memory pool for the line list (1K blocks should be enough) */
              if( did -> did_Pool = CreatePool( (MEMF_CLEAR | MEMF_PUBLIC), 1024UL, 1024UL ) )
              {
                /* Set word delim */
                SetAttrs( (Object *)retval, TDTA_WordDelim, "\xa0\n", TAG_DONE );

                did -> did_Methods = CopyDTSupportedMethods( cb, (Object *)retval, (did -> did_Pool), directory_includemethods, directory_excludemethods );
              }
              else
              {
                SetIoErr( ERROR_NO_FREE_STORE );
              }
            }
        }
            break;

        case OM_DISPOSE:
        {
            struct List *linelist;

            /* Get a pointer to our object data */
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            /* Remove notify task */
            DeleteNotifyTask( cb, o );

            /* Don't let the super class free the line list */
            if( GetAttr( TDTA_LineList, o, (ULONG *)(&linelist) ) == 1UL )
            {
              if( linelist )
              {
                NewList( linelist );
              }
            }

            /* Delete the line pool */
            DeletePool( (did -> did_Pool) );

            SetAttrs( o, TDTA_Buffer,    NULL,
                         TDTA_BufferLen, 0UL,
                         TAG_DONE );

            DoSuperMethodA( cl, o, msg );
        }
            break;

        case OM_GET:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            if( ((((struct opGet *)msg) -> opg_AttrID) == DTA_Methods) && (did -> did_Methods) )
            {
              *(((struct opGet *)msg) -> opg_Storage) = (ULONG)(did -> did_Methods);

              retval = 1UL;
            }
            else
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

        case OM_NOTIFY:
        {
            struct TagItem *ti;

            if( ti = FindTagItem( TDTA_WordSelect, (((struct opUpdate *)msg) -> opu_AttrList) ) )
            {
              if( ti -> ti_Data )
              {
                struct DTSpecialInfo *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
                struct MinList       *linelist;

                /* Lock the global object data (including TDTA_LineList) so that nobody else can manipulate it */
                ObtainSemaphore( (&(si -> si_Lock)) );

                if( GetAttr( TDTA_LineList, o, (ULONG *)(&linelist) ) == 1UL )
                {
                  struct Line *worknode,
                              *nextnode;
                  TEXT         buff[ 256 ];

                  worknode = (struct Line *)(linelist -> mlh_Head);

                  while( nextnode = (struct Line *)(worknode -> ln_Link . mln_Succ) )
                  {
                    STRPTR term;

                    if( term = strstr( (worknode -> ln_Text), "\xa0" ) )
                    {
                      stccpy( buff, (worknode -> ln_Text), MIN( (sizeof( buff ) - 1), (((size_t)(term - (worknode -> ln_Text))) + 1UL) ) );

                      if( !Stricmp( buff, (STRPTR)(ti -> ti_Data) ) )
                      {
                        break;
                      }
                    }

                    worknode = nextnode;
                  }

                  /* Something found ? */
                  if( nextnode )
                  {
                    ULONG  olddata;
                    ULONG  pathbuffsize;
                    STRPTR pathbuff;

                    did = (struct DirectoryInstData *)INST_DATA( cl, o );

                    pathbuffsize = strlen( (did -> did_DirName) ) + strlen( buff ) + 9UL;

                    if( pathbuff = (STRPTR)AllocMem( pathbuffsize, MEMF_PUBLIC ) )
                    {
                      strcpy( pathbuff, (did -> did_DirName) );

                      if( AddPart( pathbuff, buff, (pathbuffsize - 6UL) ) )
                      {
                        strcat( pathbuff, "/MAIN" );

                        olddata = ti -> ti_Data;

                        /* patch msg
                         * BUG: Official way would be to make a copy of the msg and it's contents
                         * (e.g. (opu -> opu_AttrList))
                         */
                        ti -> ti_Data = (ULONG)pathbuff;

                          retval = DoSuperMethodA( cl, o, msg );

                        ti -> ti_Data = olddata;
                      }
                      else
                      {
                        retval = DoSuperMethodA( cl, o, msg );
                      }

                      FreeMem( (APTR)pathbuff, pathbuffsize );
                    }
                  }
                  else
                  {
                    /* No matching node found...
                     * notify user using DisplayBeep on object's screen
                     */
                    struct Screen *scr;

                    did = (struct DirectoryInstData *)INST_DATA( cl, o );

                    if( did -> did_Window )
                    {
                      scr = did -> did_Window -> WScreen;
                    }
                    else
                    {
                      scr = NULL;
                    }

                    DisplayBeep( scr );
                  }
                }

                ReleaseSemaphore( (&(si -> si_Lock)) );
              }
            }
            else
            {
              retval = DoSuperMethodA( cl, o, msg );
            }
        }
            break;

        case OM_UPDATE:
        case OM_SET:
        {
            /* Avoid OM_NOTIFY loops */
            if( DoMethod( o, ICM_CHECKLOOP ) )
            {
              break;
            }

            /* Pass the attributes to the text class and force a refresh
             * if we need it
             */
            if( (retval = DoSuperMethodA( cl, o, msg )) && (OCLASS( o ) == cl) )
            {
              struct RastPort *rp;

              /* Get a pointer to the rastport */
              if( rp = ObtainGIRPort( (((struct opSet *)msg) -> ops_GInfo) ) )
              {
                struct gpRender gpr;

                /* Force a redraw */
                gpr . MethodID   = GM_RENDER;
                gpr . gpr_GInfo  = ((struct opSet *)msg) -> ops_GInfo;
                gpr . gpr_RPort  = rp;
                gpr . gpr_Redraw = GREDRAW_UPDATE;

                DoMethodA( o, (Msg)(&gpr) );

                /* Release the temporary rastport */
                ReleaseGIRPort( rp );
              }

              retval = 0UL;
            }
        }
            break;

        case GM_LAYOUT:
        {
            /* Tell everyone that we are busy doing things */
            notifyAttrChanges( o, (((struct gpLayout *)msg) -> gpl_GInfo), 0UL,
                               GA_ID,    (G( o ) -> GadgetID),
                               DTA_Busy, TRUE,
                               TAG_DONE );

            /* Let the super-class partake */
            retval = DoSuperMethodA( cl, o, msg );

            /* We need to do this one asynchronously */
            retval += DoAsyncLayout( o, (struct gpLayout *)msg );
        }
            break;

        case DTM_PROCLAYOUT:
        {
            /* Tell everyone that we are busy doing things */
            notifyAttrChanges( o, (((struct gpLayout *)msg) -> gpl_GInfo), 0UL,
                               GA_ID,    (G( o ) -> GadgetID),
                               DTA_Busy, TRUE,
                               TAG_DONE );

            /* Let the super-class partake and then fall through to our layout method */
            DoSuperMethodA( cl, o, msg );
        }
        case DTM_ASYNCLAYOUT:
        {
            /* Layout the text */
            retval = layoutMethod( cb, cl, o, ((struct gpLayout *)msg) );
        }
            break;

        case DTM_REMOVEDTOBJECT:
        {
            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            DeleteNotifyTask( cb, o );

            did -> did_Window    = NULL;
            did -> did_Requester = NULL;

            retval = DoSuperMethodA( cl, o, msg );
        }
            break;

        case GM_GOACTIVE:
        case GM_HANDLEINPUT:
        {
            struct DTSpecialInfo *si  = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
            struct gpInput       *gpi = (struct gpInput *)msg;

            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            if( (did -> did_InMarkMode) == FALSE )
            {
              if( (si -> si_Flags) & DTSIF_DRAGSELECT )
              {
                did -> did_InMarkMode = TRUE;

                DoMethod( o, DTM_CLEARSELECTED, (gpi -> gpi_GInfo) );
              }
            }

            retval = DoSuperMethodA( cl, o, msg );
        }
            break;

        case GM_GOINACTIVE:
        {
            struct gpGoInactive *gpgi = (struct gpGoInactive *)msg;

            did = (struct DirectoryInstData *)INST_DATA( cl, o );

            retval = DoSuperMethodA( cl, o, msg );

            if( did -> did_InMarkMode )
            {
              struct gpLayout gpl;

              did -> did_InMarkMode = FALSE;

              gpl . MethodID    = GM_LAYOUT;
              gpl . gpl_GInfo   = gpgi -> gpgi_GInfo;
              gpl . gpl_Initial = 0L;

              DoMethodA( o, (Msg)(&gpl) );

              notifyAttrChanges( o, (gpgi -> gpgi_GInfo), 0UL,
                                 GA_ID,    (ULONG)(G( o ) -> GadgetID),
                                 DTA_Sync, 1UL,
                                 TAG_DONE );
            }
        }
            break;

        case DTM_CLEARSELECTED:
        {
            struct IBox     *selectdomain;
            struct RastPort *rp;

            if( GetAttr( DTA_SelectDomain, o, (ULONG *)(&selectdomain) ) == 1UL )
            {
              if( selectdomain )
              {
                static const struct IBox  all_selected = { ~0, ~0, ~0, ~0 };

                ((struct DTSpecialInfo *)(G( o ) -> SpecialInfo)) -> si_Flags &= ~DTSIF_HIGHLIGHT;

                SetAttrs( o, DTA_SelectDomain, (&all_selected), TAG_DONE );
              }
            }

            /* Get a pointer to the rastport */
            if( rp = ObtainGIRPort( (((struct dtGeneral *)msg) -> dtg_GInfo) ) )
            {
              struct gpRender gpr;

              /* Force a redraw */
              gpr . MethodID   = GM_RENDER;
              gpr . gpr_GInfo  = ((struct dtGeneral *)msg) -> dtg_GInfo;
              gpr . gpr_RPort  = rp;
              gpr . gpr_Redraw = GREDRAW_REDRAW;

              DoMethodA( o, (Msg)(&gpr) );

              /* Release the temporary rastport */
              ReleaseGIRPort( rp );
            }
        }
            break;

        /* Let the superclass handle everything else */
        default:
        {
            retval = DoSuperMethodA( cl, o, msg );
        }
            break;
    }

    return( retval );
}


ULONG layoutMethod( struct ClassBase *cb, struct IClass *cl, Object *o, struct gpLayout *gpl )
{
    /* Context */
    struct DTSpecialInfo       *si = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);
    struct DirectoryInstData   *did = (struct DirectoryInstData *)INST_DATA( cl, o );

    /* Attributes obtained from super-class */
    struct TextAttr            *tattr;
    struct TextFont            *font;
    struct MinList             *linelist;
    struct IBox                *domain;
    STRPTR                      title;

    /* Line information */
    ULONG                       xoffset = 0UL;
    ULONG                       yoffset = 0UL;
    UBYTE                       fgpen = 1U,     /* Should be one of the drawinfo pens */
                                bgpen = 0U;

    /* Misc */
    ULONG                       nomwidth,
                                nomheight;

    ULONG                       maxx = 0UL,
                                maxy;

    ULONG                       total = 0UL;
    ULONG                       bsig = 0UL;

    LONG                        errorlevel = RETURN_OK,
                                error = 0L;

    struct TagItem              NotifyTags[ 16 ];

    /* Get all the attributes that we are going to need for a successful layout */
    if( GetDTAttrs( o,
                    DTA_TextAttr,   (&tattr),
                    DTA_TextFont,   (&font),
                    DTA_Domain,     (&domain),
                    DTA_ObjName,    (&title),
                    TDTA_LineList,  (&linelist),
                    TAG_DONE ) == 5UL )
    {
      struct RastPort trp;

      /* Lock the global object data so that nobody else can manipulate it */
      ObtainSemaphore( (&(si -> si_Lock)) );

      /* Initialize the temporary RastPort */
      InitRastPort( (&trp) );
      SetFont( (&trp), font );

      /* Calculate the nominal size */
      nomheight = (ULONG)(24UL * (font -> tf_YSize) );
      nomwidth  = (ULONG)(80UL * (font -> tf_XSize) );

      /* Store name */
      did -> did_DirName = title;

      /* Store GadgetInfo context */
      if( (did -> did_Window) == NULL )
      {
        did -> did_Window    = gpl -> gpl_GInfo -> gi_Window;
        did -> did_Requester = gpl -> gpl_GInfo -> gi_Requester;
      }

      /* We only need to perform layout if this is the initial layout call
       * or if the source data (directory) has been changed
       */
      if( (gpl -> gpl_Initial) || (did -> did_NewData) )
      {
        struct Line *line;
        BPTR         dirlock;

        did -> did_NewData = FALSE;

        /* Delete the old line list */
        while( line = (struct Line *)RemHead( (struct List *)linelist ) )
        {
          FreePooled( (did -> did_Pool), (APTR)line, (ULONG)sizeof( struct Line ) );
        }

        /* Lock dir given by DTA_ObjName */
        if( dirlock = Lock( title, SHARED_LOCK ) )
        {
          struct FileInfoBlock *fib;

          if( fib = (struct FileInfoBlock *)AllocDosObject( DOS_FIB, NULL ) )
          {
            if( Examine( dirlock, fib ) )
            {
              /* dirlock should be a directory lock */
              if( (fib -> fib_DirEntryType) >= 0L )
              {
                STRPTR version;
                BOOL   succ;
                STRPTR tbuff;
                ULONG  tbuffsize;

                tbuffsize = 0UL;

                /* Set DTA_ObjAnnotation to (fib -> fib_Comment),
                 * DTA_ObjVersion to ISO file versions ("file;6" for file "file", version 6)
                 */
                if( version = strstr( title, ";" ) )
                {
                  /* Get char following ';' */
                  version++;
                }
                else
                {
                  version = "0";
                }

                SetAttrs( o, DTA_ObjAnnotation, (fib -> fib_Comment),
                             DTA_ObjVersion,    version,
                             TAG_DONE );

                /* Step through the directory,
                 * stop if we reach the end of dir,
                 * or if be got SIGBREAKF_CTRL_C signal (abort layout)
                 * or if any error occurs
                 */
                while( (succ = ExNext( dirlock, fib )),          /* Get next directory entry */
                       (bsig = CheckSignal( SIGBREAKF_CTRL_C )), /* Check to see if layout has been aborted */
                       succ && (bsig == 0UL) && (IoErr() == 0L) && (errorlevel == RETURN_OK) )
                {
                  ULONG  filenamelen,
                         memsize;
                  STRPTR buff,
                         infobuff;

                  filenamelen = (ULONG)strlen( (fib -> fib_FileName) );

                  memsize = filenamelen + 512UL;

                  tbuffsize += memsize;

                  if( buff = (STRPTR)AllocVec( memsize, MEMF_ANY ) )
                  {
                    /* Allocate a new line segment from our memory pool */
                    if( line = (struct Line *)AllocPooled( (did -> did_Pool), (ULONG)sizeof( struct Line ) ) )
                    {
                      ULONG  swidth;
                      STRPTR typename;
                      TEXT   sizebuff[ 12 ],
                             uidbuff[ 12 ],
                             gidbuff[ 12 ];
                      STRPTR datebuff;

                      memset( (void *)buff, ' ', 32 );
                      buff[ 31 ] = '\0';
                      strcpy( buff, (fib -> fib_FileName) );

                      infobuff = buff + filenamelen;
                      *infobuff++ = '\xa0'; /* file name word terminator */

                      if( (infobuff - buff) < 30UL )
                      {
                        infobuff = &buff[ 30 ];
                      }

                      /* Determinate filesystem object type */
                      switch( (fib -> fib_DirEntryType) )
                      {
                        case ST_ROOT:
                        {
                            typename = "root";
                        }
                            break;

                        case ST_USERDIR:
                        {
                            typename = "dir";
                        }
                            break;

                        case ST_SOFTLINK:
                        {
                            typename = "softlink"; /* looks like dir, but may point to a file! */
                        }
                            break;

                        case ST_LINKDIR:
                        {
                            typename = "linkdir";  /* hard link to dir */
                        }
                            break;

                        case ST_FILE:
                        {
                            typename = "file";
                        }
                            break;

                        case ST_LINKFILE:
                        {
                            typename = "linkfile"; /* hard link to file */
                        }
                            break;

                        case ST_PIPEFILE:
                        {
                            typename = "pipefile"; /* for pipes that support ExamineFH */
                        }
                            break;

                        default:
                        {
                            /* (fib -> fib_DirEntryType) does not match any ST_#? type above,
                             * return a simple, generic type
                             */
                            if( (fib -> fib_DirEntryType) < 0L )
                            {
                              typename = "file like";
                            }
                            else
                            {
                              typename = "dir like";
                            }
                        }
                            break;
                      }

                      /* Fill buffers (file size, user id and group id */
                      mysprintf( cb, sizebuff, "%lu", (ULONG)(fib -> fib_Size)     );
                      mysprintf( cb, uidbuff,  "%lu", (ULONG)(fib -> fib_OwnerUID) );
                      mysprintf( cb, gidbuff,  "%lu", (ULONG)(fib -> fib_OwnerGID) );

                      mysprintf( cb, infobuff, "%10s %8s <%8s> <%8s> %s%s%s%s%s%s%s%s %s%s%s%s %s%s%s%s ",
                                 typename,
                                 /* size */
                                 (((fib -> fib_DirEntryType) < 0L)?(sizebuff):("")),
                                 /* UID, GID */
                                 ((fib -> fib_OwnerUID)?(uidbuff):("No Owner")),
                                 ((fib -> fib_OwnerGID)?(gidbuff):("No Group")),
                                 /* protection/flags */
                                 (((fib -> fib_Protection) & 7L)              ?("-"):("-")), /* hold flag */
                                 (((fib -> fib_Protection) & FIBF_SCRIPT)     ?("s"):("-")),
                                 (((fib -> fib_Protection) & FIBF_PURE)       ?("p"):("-")),
                                 (((fib -> fib_Protection) & FIBF_ARCHIVE)    ?("a"):("-")),
                                 (((fib -> fib_Protection) & FIBF_READ)       ?("-"):("r")),
                                 (((fib -> fib_Protection) & FIBF_WRITE)      ?("-"):("w")),
                                 (((fib -> fib_Protection) & FIBF_EXECUTE)    ?("-"):("e")),
                                 (((fib -> fib_Protection) & FIBF_DELETE)     ?("-"):("d")),
                                 /* group access */
                                 (((fib -> fib_Protection) & FIBF_GRP_READ)   ?("r"):("-")),
                                 (((fib -> fib_Protection) & FIBF_GRP_WRITE)  ?("w"):("-")),
                                 (((fib -> fib_Protection) & FIBF_GRP_EXECUTE)?("e"):("-")),
                                 (((fib -> fib_Protection) & FIBF_GRP_DELETE) ?("d"):("-")),
                                 /* other access */
                                 (((fib -> fib_Protection) & FIBF_OTR_READ)   ?("r"):("-")),
                                 (((fib -> fib_Protection) & FIBF_OTR_WRITE)  ?("w"):("-")),
                                 (((fib -> fib_Protection) & FIBF_OTR_EXECUTE)?("e"):("-")),
                                 (((fib -> fib_Protection) & FIBF_OTR_DELETE) ?("d"):("-")) );

                      /* Write date to buffer */
                      datebuff = infobuff + strlen( infobuff );
                      sprintfdate( cb, datebuff );

                      swidth = (ULONG)TextLength( (&trp), buff, (ULONG)((&infobuff[ strlen( infobuff ) ]) - buff) );

                      line -> ln_Text = buff;

                      line -> ln_TextLen = (ULONG)((&infobuff[ strlen( infobuff ) ]) - buff);
                      line -> ln_XOffset = xoffset;
                      line -> ln_YOffset = yoffset + (font -> tf_Baseline);
                      line -> ln_Width   = swidth;
                      line -> ln_Height  = font -> tf_YSize;
                      line -> ln_Flags   = LNF_LF;
                      line -> ln_FgPen   = fgpen;
                      line -> ln_BgPen   = bgpen;
                      line -> ln_Style   = FS_NORMAL;
                      line -> ln_Data    = NULL;

                      /* Add the line to the list */
                      AddTail( (struct List *)linelist, (struct Node *)(&(line -> ln_Link)) );

                      /* Increment the line width */
                      xoffset += swidth;

                      maxx = (xoffset > maxx)?(xoffset):(maxx);

                      /* Linefeed (new line) */
                      yoffset += font -> tf_YSize;
                      xoffset = 0UL;
                      total++;
                    }
                    else
                    {
                      FreeVec( (APTR)buff );

                      errorlevel = RETURN_ERROR;
                      error      = ERROR_NO_FREE_STORE;
                    }
                  }
                  else
                  {
                    errorlevel = RETURN_ERROR;
                    error      = ERROR_NO_FREE_STORE;
                  }
                }

                /* Alloc TDTA_Buffer buffer */
                if( tbuff = (STRPTR)AllocPooled( (did -> did_Pool), tbuffsize ) )
                {
                  struct Line *worknode,
                              *nextnode;
                  STRPTR       s;

                  s        = tbuff;
                  worknode = (struct Line *)(linelist -> mlh_Head);

                  /* Copy line nodes's text into buffer and free the temp text */
                  while( nextnode = (struct Line *)(worknode -> ln_Link . mln_Succ) )
                  {
                    STRPTR text;

                    text = worknode -> ln_Text;

                    strcpy( s, text );

                    worknode -> ln_Text = s;

                    s += (worknode -> ln_TextLen);
                    *s++ = '\n';

                    FreeVec( (APTR)text );

                    worknode = nextnode;
                  }

                  SetAttrs( o, TDTA_Buffer,     tbuff,
                               TDTA_BufferLen,  tbuffsize,
                               TAG_DONE );
                }
                else
                {
                  struct Line *worknode,
                              *nextnode;

                  worknode = (struct Line *)(linelist -> mlh_Head);

                  while( nextnode = (struct Line *)(worknode -> ln_Link . mln_Succ) )
                  {
                    FreeVec( (APTR)(worknode -> ln_Text) );

                    worknode = nextnode;
                  }

                  errorlevel = RETURN_ERROR;
                  error      = ERROR_NO_FREE_STORE;
                }

                /* Check if we reached the end of dir (IoErr() == ERROR_NO_MORE_ENTRIES) */
                if( (succ == FALSE) && (IoErr() != ERROR_NO_MORE_ENTRIES) )
                {
                  errorlevel = RETURN_ERROR;
                  error      = IoErr();
                }
              }
              else
              {
                errorlevel = RETURN_FAIL;
                error      = ERROR_OBJECT_WRONG_TYPE;
              }
            }
            else
            {
              errorlevel = RETURN_FAIL;
              error      = IoErr();
            }

            FreeDosObject( DOS_FIB, (APTR)fib );
          }
          else
          {
            errorlevel = RETURN_FAIL;
            error      = ERROR_NO_FREE_STORE;
          }

          UnLock( dirlock );
        }
        else
        {
          errorlevel = RETURN_FAIL;
          error      = IoErr();
        }
      }
      else
      {
        /* No layout to perform */
        total = si -> si_TotVert;
        maxx  = si -> si_TotHoriz;
      }

      maxy = total * (font -> tf_YSize);

      /* Compute the lines and columns type information */
      si -> si_VertUnit  = (LONG)(font -> tf_YSize);
      si -> si_VisVert   = (LONG)((domain -> Height) / (si -> si_VertUnit));
      si -> si_TotVert   = (LONG)(maxy / (si -> si_VertUnit));

      si -> si_HorizUnit = 1L;
      si -> si_VisHoriz  = (LONG)(domain -> Width);
      si -> si_TotHoriz  = (LONG)maxx;

      /* Build notify attrs */
      NotifyTags[  0 ] . ti_Tag  = DTA_VisibleVert;
      NotifyTags[  0 ] . ti_Data = (si -> si_VisVert);
      NotifyTags[  1 ] . ti_Tag  = DTA_TotalVert;
      NotifyTags[  1 ] . ti_Data = (si -> si_TotVert);
      NotifyTags[  2 ] . ti_Tag  = DTA_NominalVert;
      NotifyTags[  2 ] . ti_Data = nomheight;
      NotifyTags[  3 ] . ti_Tag  = DTA_VertUnit;
      NotifyTags[  3 ] . ti_Data = (si -> si_VertUnit);
      NotifyTags[  4 ] . ti_Tag  = DTA_VisibleHoriz;
      NotifyTags[  4 ] . ti_Data = (si -> si_VisHoriz);
      NotifyTags[  5 ] . ti_Tag  = DTA_TotalHoriz;
      NotifyTags[  5 ] . ti_Data = (si -> si_TotHoriz);
      NotifyTags[  6 ] . ti_Tag  = DTA_NominalHoriz;
      NotifyTags[  6 ] . ti_Data = nomwidth;
      NotifyTags[  7 ] . ti_Tag  = DTA_HorizUnit;
      NotifyTags[  7 ] . ti_Data = (ULONG)(si -> si_HorizUnit);
      NotifyTags[  8 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorLevel );
      NotifyTags[  8 ] . ti_Data = errorlevel;
      NotifyTags[  9 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorNumber );
      NotifyTags[  9 ] . ti_Data = error;
      NotifyTags[ 10 ] . ti_Tag  = XTAG( errorlevel, DTA_ErrorString );
      NotifyTags[ 10 ] . ti_Data = (ULONG)title;
      NotifyTags[ 11 ] . ti_Tag  = GA_ID;
      NotifyTags[ 11 ] . ti_Data = (ULONG)(G( o ) -> GadgetID);
      NotifyTags[ 12 ] . ti_Tag  = XTAG( (errorlevel == RETURN_OK), DTA_Title );
      NotifyTags[ 12 ] . ti_Data = (ULONG)title;
      NotifyTags[ 13 ] . ti_Tag  = DTA_Busy;
      NotifyTags[ 13 ] . ti_Data = (ULONG)FALSE;
      NotifyTags[ 14 ] . ti_Tag  = DTA_Sync;
      NotifyTags[ 14 ] . ti_Data = (ULONG)TRUE;
      NotifyTags[ 15 ] . ti_Tag  = TAG_DONE;
      NotifyTags[ 15 ] . ti_Data = 0UL;

      /* Release the global data lock */
      ReleaseSemaphore( (&(si -> si_Lock)) );

      /* Were we aborted? */
      if( bsig == 0UL )
      {
        /* Not aborted, so tell the world of our newest attributes */
        notifyAttrChanges( o, (gpl -> gpl_GInfo), 0UL, TAG_MORE, NotifyTags );

        /* Create notify task at initial layout time */
        if( gpl -> gpl_Initial )
        {
          CreateNotifyTask( cb, o );
        }
      }
    }

    return( total );
}


ULONG notifyAttrChanges( Object *o, struct GadgetInfo *ginfo, ULONG flags, Tag tag1, ... )
{
    struct opUpdate opu;

    opu . MethodID     = OM_NOTIFY;
    opu . opu_AttrList = (struct TagItem *)(&tag1);
    opu . opu_GInfo    = ginfo;
    opu . opu_Flags    = flags;

    return( DoMethodA( o, (Msg)(&opu) ) );
}


void CreateNotifyTask( struct ClassBase *cb, Object *o )
{
    if( cb && o )
    {
      struct DirectoryInstData *did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Class), o );

      Forbid();

        if( (did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task) == NULL )
        {
          memset( (void *)(&(did -> did_NTM . ntm_NR)), 0, sizeof( struct NotifyRequest ) );

          did -> did_NTM . ntm_Message . mn_Node . ln_Type              = NT_UNKNOWN;
          did -> did_NTM . ntm_Message . mn_ReplyPort                   = NULL;
          did -> did_NTM . ntm_Message . mn_Length                      = sizeof( struct NotifyTaskMessage );
          did -> did_NTM . ntm_CB                                       = cb;
          did -> did_NTM . ntm_Object                                   = o;
          did -> did_NTM . ntm_NR . nr_Name                             = did -> did_DirName;
          did -> did_NTM . ntm_NR . nr_Flags                            = NRF_SEND_SIGNAL;
          did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_SignalNum = SIGBREAKB_CTRL_E;

          if( did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task = (struct Task *)CreateNewProcTags( NP_Entry,     DoNotifyTask,
                                                                                                           NP_Name,      (cb -> cb_Class -> cl_ID),
                                                                                                           NP_Priority,  2UL,
                                                                                                           NP_WindowPtr, (did -> did_Window),
                                                                                                           TAG_DONE ) )
          {
            PutMsg( (&(((struct Process *)(did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task)) -> pr_MsgPort)), (&(did -> did_NTM . ntm_Message)) );
          }
        }

      Permit();
    }
}


void DeleteNotifyTask( struct ClassBase *cb, Object *o )
{
    if( cb && o )
    {
      struct DirectoryInstData *did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Class), o );

      Forbid();

      if( did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task )
      {
        Signal( (did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task), SIGBREAKF_CTRL_C );

        Permit();

        while( (did -> did_NTM . ntm_Message . mn_Node . ln_Type) != NT_FREEMSG )
        {
          Delay( (TICKS_PER_SECOND / 4UL) );
        }
      }
      else
      {
        Permit();
      }
    }
}


void sprintfdate( struct ClassBase *cb, STRPTR buffer )
{
    TEXT StrDay[ LEN_DATSTRING ],
         StrDate[ LEN_DATSTRING ],
         StrTime[ LEN_DATSTRING ];

    struct DateTime dt;

    dt . dat_Stamp . ds_Days   = 0L;
    dt . dat_Stamp . ds_Minute = 0L;
    dt . dat_Stamp . ds_Tick   = 0L;
    dt . dat_Format            = FORMAT_DOS;
    dt . dat_Flags             = 0U;
    dt . dat_StrDay            = StrDay;
    dt . dat_StrDate           = StrDate;
    dt . dat_StrTime           = StrTime;

    DateStamp( (&(dt . dat_Stamp)) );

    if( DateToStr( (&dt) ) )
    {
      mysprintf( cb, buffer, "%10s %8s %8s", StrDay, StrDate, StrTime );
    }
}


ULONG *CopyDTSupportedMethods( struct ClassBase *cb, Object *o, APTR pool, ULONG *includemethods, ULONG *excludemethods )
{
    if( o && pool )
    {
      ULONG *methods;

      if( methods = GetDTMethods( o ) )
      {
        ULONG  numincludemethods,
               nummethods;

        ULONG *copymethods;

        nummethods        = NumMethods( methods );
        numincludemethods = NumMethods( includemethods );

        if( copymethods = (ULONG *)AllocPooled( pool, ((nummethods + numincludemethods + 2UL) * sizeof( ULONG )) ) )
        {
          ULONG *x;

          /* Copy methods, including their terminator */
          memcpy( (void *)copymethods, (void *)methods, (size_t)((nummethods + 1UL) * sizeof( ULONG )) );

          if( includemethods )
          {
            /* Find terminator (== ~0UL) of copymethods */
            if( x = FindMethod( copymethods, (~0UL) ) )
            {
              memcpy( (void *)x, (void *)includemethods, (size_t)((numincludemethods + 1UL) * sizeof( ULONG )) );
            }
          }

          if( excludemethods )
          {
            while( (*excludemethods) != (~0UL) )
            {
              if( x = FindMethod( copymethods, (*excludemethods) ) )
              {
                *x = OM_NEW;
              }

              excludemethods++;
            }
          }

          return( copymethods );
        }
      }
    }

    return( NULL );
}


ULONG NumMethods( ULONG *methods )
{
    ULONG num = 0UL;

    if( methods )
    {
      while( (*methods) != (~0UL) )
      {
        methods++;
        num++;
      }
    }

    return( num );
}


ULONG *FindMethod( ULONG *methods, ULONG MethodID )
{
    if( methods )
    {
      while( ((*methods) != (~0UL)) && ((*methods) != MethodID) )
      {
        methods++;
      }

      if( (*methods) == MethodID )
      {
        return( methods );
      }
    }

    return( NULL );
}


void DISPATCHERFLAGS DoNotifyTask( void )
{
#undef SysBase
    struct ExecBase             *SysBase;
    struct Process              *thisprocess;
    struct NotifyTaskMessage    *ntm;
    struct ClassBase            *cb;
    Object                      *o;
    struct DTSpecialInfo        *si;
    struct DirectoryInstData    *did;

    SysBase     = *(struct ExecBase **)4UL;
    thisprocess = (struct Process *)(SysBase -> ThisTask);

    /* sync with parent */
    WaitPort( (&(thisprocess -> pr_MsgPort)) );

    if( ntm = (struct NotifyTaskMessage *)GetMsg( (&(thisprocess -> pr_MsgPort)) ) )
    {
      cb  = ntm -> ntm_CB;
      o   = ntm -> ntm_Object;
      did = (struct DirectoryInstData *)INST_DATA( (cb -> cb_Class), o );
      si  = (struct DTSpecialInfo *)(G( o ) -> SpecialInfo);

      if( StartNotify( (&(ntm -> ntm_NR)) ) )
      {
        ULONG signals;

        for( ;; )
        {
          /* Wait for notification or abort... */
          signals = Wait( (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_E) );

          /* Notification ? */
          if( signals & SIGBREAKF_CTRL_E )
          {
            if( did -> did_Window )
            {
              struct gpLayout gpl;

              ObtainSemaphore( (&(si -> si_Lock)) );

                did -> did_NewData = TRUE;

              ReleaseSemaphore( (&(si -> si_Lock)) );

              /* Let other tasks (filesystem etc.) partake */
              Delay( TICKS_PER_SECOND / 2UL );

              gpl . MethodID    = GM_LAYOUT;
              gpl . gpl_GInfo   = NULL;
              gpl . gpl_Initial = 0L;

              DoDTMethodA( o, (did -> did_Window), (did -> did_Requester), (Msg)(&gpl) );
            }
          }

          /* End notififation ? */
          if( signals & SIGBREAKF_CTRL_C )
          {
            break;
          }
        }

        EndNotify( (&(ntm -> ntm_NR)) );
      }
    }

    /* Reply message and terminate */
    Forbid();

      did -> did_NTM . ntm_NR . nr_stuff . nr_Signal . nr_Task = NULL;

      ReplyMsg( (&(ntm -> ntm_Message)) );
}



