                                 DataTypes

                              David N. Junod

	(c) Copyright 1992-93 Commodore-Amiga, Inc.  All Rights Reserved


Introduction

DataTypes provides an object oriented approach for determing data types and
handling those data types.

There are many advantages to using DataTypes:


  o  An application can detect what data type a file is and handle it
     accordingly.  An example of this would be a BBS that examines incoming
     files and labels them by file type so that the appropriate integrity
     checks can be applied or the appropriate contents viewer invoked for
     that archive type.  Or a directory utility that invokes that
     appropriate editor for the data type of the file that the user selects.

  o  An developer can easily embedded a DataType viewer within their
     application without worrying about writing a lot of code.  For
     example, if an application needs to display a picture, or word-wrapped
     text in a proportional font, it can do it easily using the functions
     of DataTypes.

  o  An developer can easily add clipboard support to their application
     using the functions of DataTypes.  Each root DataType class implements
     clipboard support in the native Amiga format for that type of data.
     All the application needs to do is submit its data to a DataType object
     and invoke the copy method.

  o  Applications can handle multiple formats of a data type transparently.
     For example a Paint package can handle ILBM, GIF, Windows BMP or any
     other type of picture data as long as there is a DataTypes class to
     handle it.

  o  A data viewer can transparently handle any type of data.  An example of
     this is the MultiView utility that comes with 3.0 or the ClipView
     utility on the examples disk.

  o  It is easy to write a sub-class for most of the data types, because all
     the class implementor needs to do is convert the data to the internal
     Amiga format for that data type.  For example, the picture class
     handles the color remapping of a 256 color picture to the screen depth
     of the destination Amiga.

  o  The DataType objects have a consistent interface.  There is no
     difference between displaying a picture, text, or animation.

  o  A DataType object can be queried to see what methods and commands they
     support.


Determining Data Type

One of the main features of the DataTypes system is its ability to determine
the data type of a block of data.  This data block can reside in a file or
the clipboard.

The following functions are used to determine the DataType of a data block:


    ObtainDataTypeA()	Obtain the DataType descriptor for a data block.

    ReleaseDataType()	Release the DataType descriptor for a data block.


The data type detection functions use the DataType structure.


    struct DataType
    {
        struct Node	 	 dtn_Node1;
        struct Node		 dtn_Node2;
        struct DataTypeHeader	*dtn_Header;
        struct List		 dtn_ToolList;
        STRPTR			 dtn_FunctionName;
        struct TagItem		*dtn_AttrList;
        ULONG			 dtn_Length;
    };


The DataType structure is read-only.   The only pertinent field is the
dtn_Header field, which points to a DataTypeHeader structure.


    struct DataTypeHeader
    {
        STRPTR	 dth_Name;
        STRPTR	 dth_BaseName;
        STRPTR	 dth_Pattern;
        WORD	*dth_Mask;
        ULONG	 dth_GroupID;
        ULONG	 dth_ID;
        WORD	 dth_MaskLen;
        WORD	 dth_Pad;
        UWORD	 dth_Flags;
        WORD	 dth_Priority;
    };


The DataTypeHeader structure fields are as follows:

dth_Name
    Descriptive name of the data type.  For example, the description for an
    ILBM data type could possibly be "Amiga BitMap Picture".

dth_BaseName
    This is the base name for the data type and is used to obtain the class
    that handles this data type.

dth_GroupID
    This indicates the main type data that the object contains.  Following
    are the possible values:

    GID_SYSTEM		Fonts, Executables, Libraries, Devices, etc...
    GID_TEXT		Formatted or unformatted text.
    GID_DOCUMENT	Formatted text with embedded DataTypes (such as pictures).
    GID_SOUND		Audio samples.
    GID_INSTRUMENT	Audio samples used for playing music.
    GID_MUSIC		Musical scores.
    GID_PICTURE		Graphic picture or brush.
    GID_ANIMATION	Moving picture or cartoon.
    GID_MOVIE		Moving picture or cartoon with sound.

dth_ID
    This is an individual indentifier for the DataType.  For IFF files it is
    the same as the FORM type, for example ILBM for an Amiga BitMap picture.
    For non-IFF files, it is the first four characters of dth_Name.

dth_Flags
    The flags field contains, among other information, the coarse type of
    data.  The type can be obtained by ANDing DTF_TYPE_MASK with this field.

    DTF_IFF		Interchange File Format
    DTF_BINARY		Non-readable characters
    DTF_ASCII		Readable characters
    DTF_MISC		Disks and drawers

dth_Pattern
dth_Mask
dth_MaskLen
dth_Priority
    These fields are used by the detection code in datatypes.library for
    determining the data type.  See the "Defining a DataType Descriptor"
    section for more information.


Following is a code fragment that shows how to determine the data type of a
file.  This fragment uses functions from datatypes.library, dos.library, and
iffparse.library.


    STRPTR name = "somefilename";
    BPTR lock;

    struct DataTypeHeader *dth;
    struct DataType *dtn;
    UBYTE idesc[5];
    STRPTR tdesc;
    STRPTR gdesc;
    UWORD ttype;

    /* Obtain a lock on the file that we want information on */
    if (lock = Lock (name, ACCESS_READ))
    {
	/* Get a pointer to the appropriate DataType structure */
	if (dtn = ObtainDataTypeA (DTST_FILE, (APTR)lock, NULL))
	{
	    /* Get a pointer to the DataTypeHeader structure */
	    dth = dtn->dtn_Header;

	    /* Get the coarse type */
	    ttype = dth->dth_Flags & DTF_TYPE_MASK;

	    /* Get a pointer to the text strings */
	    tdesc = GetDTString (ttype + DTMSG_TYPE_OFFSET);
	    gdesc = GetDTString (dth->dth_GroupID);

	    /* Convert the ID to a string. */
	    IDtoStr (dth->dth_ID, idesc);

	    /* Display the information */
	    printf ("   Description: %s\n", dth->dth_Name);
	    printf ("     Base Name: %s\n", dth->dth_BaseName);
	    printf ("          Type: %d - %s\n", ttype, tdesc);
	    printf ("         Group: %s\n", gdesc);
	    printf ("            ID: %s\n", idesc);

	    /* Release the DataType structure now that we are done with it */
	    ReleaseDataType (dtn);
	}

	/* Release the DOS lock on the file */
	UnLock (lock);
    }


Embeddding DataTypes

Since DataType objects are a sub-class of the Intuition Gadget class,
DataType objects can be attached to an Intuition window in a similiar way
that gadgets can be added to a window.  DataTypes use a parallel set of
functions because it requires additional information that the Intuition
functions weren't able to provide.

Currently the handling of the data is limited to reading, writing, printing,
viewing (audio or visual), and clipboard access.  The MultiView utility is
an example of an application that embeds DataType objects.

The following functions are used to access DataType objects.


    NewDTObjectA()		Obtain a handle on a DataType object.

    DisposeDTObject()		Release the handle on a DataType object.

    SetDTAttrsA()		Set the attributes of a DataType object.

    GetDTAttrsA()		Get attributes of a DataType object

    AddDTObject()		Add a DataType object to a window.

    RefreshDTObjectA()		Refresh the rendering of a DataType object.

    RemoveDTObject()		Remove a DataType object from a window.

    GetDTMethods()		Get a list of the methods that a DataTypes
				object supports.  Write, Copy, and Select
				are examples of methods that an object may
				support.

    GetDTTriggerMethods()	Get a list of the trigger methods that a
				DataType object supports.  An action like
				Play, Pause, and Resume are examples of
				trigger methods that an object may support.

    DoDTMethod()		Invoke a DataTypes method.

    PrintDTObject()		Asynchronously print a DataType object.

    GetDTString()		Get the localized text string for a DataTypes
				text id.  Useful for obtaining localized error
				messages.

Creating a DataType Object

The DataTypes function NewDTObjectA() must be used to create a new DataType
object.

    dto = (Object *) NewDTObjectA (APTR name, struct TagItem *attrs)

The pointer that NewDTObjectA() returns is a pointer to a BOOPSI object.
Like other BOOPSI objects, DataType objects are "black boxes" and are not to
be peeked and poked without using the provided interface.

To create a DataType object, NewDTObjectA() needs to know the where to
obtain the data used to create the object.  By default the name is treated
as file name.

The attrs tag list is a list of tag/value pairs, each of which contains an
initial value for an attribute.  There are a number of attributes defined in
<datatypes/datatypesclass.h> that can be used at creation time.


    DTA_SourceType	Specify the type of the source data.  The default
			is DTST_FILE.

			DTST_RAM	Source data is in RAM.

			DTST_FILE	Source data is a file.  Name is
					the name of the file.

			DTST_CLIPBOARD	Source data is in the clipboard.
					Name is the unit number.  For
					example, (APTR)0, for clipboard
					unit zero.

			DTST_HOTLINK	Reserved for future use.

    DTA_Handle		Can be used instead of the name field.  If the
			source type is DTST_FILE then handle must be a
			valid BPTR file handle.  If the source type is
			DTST_CLIPBOARD then handle must be a valid
			IFFHandle.

    DTA_DataType	Can be used to specify the class for handling the
			data.  Data must be a pointer to a valid DataType.
			This should only be used when attempting to create
			a new object that doesn't have source data, or could
			be handled by multiple classes (for example, could
			be used to force an object to be handled by the
			AmigaGuide class).

    DTA_GroupID		If this tag is present, then the data must be of
			the specified type, or the object creation will fail
			with ERROR_OBJECT_WRONG_TYPE.  This can be used by a
			Sound editor to ensure that only sounds can be
			loaded, for example.

    DTA_TextAttr	Specify the font to use for any text rendered by
			this object.


There are additional attributes that can specified at creation time, but are
dependant on the data type of the object being created.  See the header
files <datatypes/#?class.h> for more attributes.

The following attributes, which are defined in <intuition/gadgetclass.h>,
are also valid.


    GA_Left		Specify the left edge of the gadget.

    GA_RelRight		Specify the left edge of the gadget being relative
			to the right edge of the containing window.

    GA_Top		Specify the top edge of the gadget.

    GA_RelBottom	Specify the top edge of the gadget being relative
			to the bottom edge of the containing window.

    GA_Width		Specify the width of the gadget.

    GA_RelWidth		Specify the width of the gadget being relative to
			the width of the containing window.

    GA_Height		Specify the heigh of the gadget.

    GA_RelHeight	Specify the heigh of the gadget being relative to
			the height of the containing window.

    GA_ID		Specify a ID associated with the gadget.

    GA_UserData		Attach application data to the gadget.

    GA_Immediate	Indicate that the application should be notified
			of gadget down events for this gadget.

    GA_RelVerify	Indicate that the application should be notified
			of gadget up events for this gadget.

    GA_Previous		For adding the gadget to a list of gadgets.

    GA_DrawInfo		A pointer to a struct DrawInfo.


In order for the application to receive information from the DataType
object, it must set up a target for the notification attributes that the
object sends out.


    ICA_TARGET		Specify a target for the notification attributes
			that the DataType object sends out.

    ICA_MAP		Specify an attribute mapping for the notification
			attributes that the DataType object sends out.

The usual method to obtain notification is to set up an ICA_TARGET of
ICTARGET_IDCMP so that the application will receive the attributes via the
IDCMP_IDCMPUPDATE Intuition message class.  But it is also possible to set
up another BOOPSI object as the receiver.

If NewDTObjectA() is successful, it returns a pointer to a DataType object.
Otherwise it returns NULL and the reason for failure can be obtained using
IoErr().  See the Autodocs for datatypes.library for more information.


Disposing of a DataType Object

When the application is done with the DataType object it has to dispose of
the object.  To dispose of a DataType object, you must use the DataTypes
function DisposeDTObject():

    VOID DisposeDTObject (Object *dto)

where dto is a pointer to the DataType object to be disposed.  This will
abort any PLAY trigger method, such as playing sounds or animations, but will
wait for a print or save to complete.


Obtaining Environment Information for a DataType

In order to embed a DataType object in a window, it is neccessary to ask the
object what its minimum environment is.  For example, since the remap code
doesn't handle remapping HAM pictures, they must be shown on a HAM screen,
and therefore can't be added to a window that is on a non-HAM screen.


    ULONG modeid = INVALID_ID;
    LONG nomwidth, nomheight;
    BOOL useScreen = FALSE;
    struct dtFrameBox dtf;
    struct FrameInfo fri;

    /* Get the attributes that we are interested in */
    GetDTAttrs (dto,

		/* Get the mode ID */
		PDTA_ModeID,		&modeid,

		/* Get the desired size */
		DTA_NominalHoriz,	&nomwidth,
		DTA_NominalVert,	&nomheight,

		TAG_DONE);

    /* Clear the structures */
    memset (&dtf, 0, sizeof (struct dtFrameBox));
    memset (&fri, 0, sizeof (struct FrameInfo));

    /* Fill in the message */
    dtf.MethodID = DTM_FRAMEBOX;
    dtf.dtf_FrameInfo = &fri;
    dtf.dtf_ContentsInfo = &fri;
    dtf.dtf_SizeFrameInfo = sizeof (struct FrameInfo);

    /* Perform the frame method */
    if (DoDTMethodA (dto, NULL, NULL, (Msg) &dtf))
    {
	/* Check to see if the object requires a HAM screen */
	if (fri.fri_PropertyFlags & DIPF_IS_HAM)
	{
	    printf ("HAM\n");
	    useScreen = TRUE;
	}
	/* Check to see if the object requires an ExtraHalfBrite screen */
	else if (fri.fri_PropertyFlags & DIPF_IS_EXTRAHALFBRITE)
	{
	    printf ("ExtraHalfBrite\n");
	    useScreen = TRUE;
	}
	/* A safety check to see if a screen is required */
	else if ((fri.fri_PropertyFlags == 0) && (modeid & 0x800) && (modeid != INVALID_ID))
	{
	    printf ("ModeID=0x%08lx\n", modeid);
	    useScreen = TRUE;
	}
    }
    else
    {
	/* No special environment required, can be attached to any screen mode */
    }


Adding a DataType Object to a Window

A DataType object must be added to a window using the AddDTObject() function
of DataTypes.

    LONG AddDTObject (struct Window *w, struct Requester *r, Object *dto, LONG pos)

This function will add a DataTypes object to the existing gadget list for
the specified window.  The recommended value for pos is -1 which will cause
the DataType object to be added to the end of the list.

DataType objects should not be added using the WA_Gadgets attribute to
OpenWindowTagList() or by using the AddGList() function.  There is special
information that DataTypes requires that will not obtained if any method
other than AddDTObject() is used.

When the DataType object is added to the window, the layout method for the
object will be invoked.  It is possible that the layout will take a while to
perform, in that case the object will spawn a process to handle the layout
asynchronously.  In order to refresh the object's visual information, it is
necessary to obtain IDCMP_IDCMPUPDATE messages from the object and refresh
the object when a DTA_Sync attribute is received.

The following code fragment illustrates adding a DataType object to a
window.


    Object *dto;

    struct IntuiMessage *imsg;
    struct Window *win;
    ULONG sigr;

    struct TagItem *tstate, *tags;
    ULONG tidata;
    ULONG errnum;

    BOOL going = TRUE;

    /* Set the pertinent attributes of the DataType object */
    SetDTAttrs (dto, NULL, NULL,

                /* Set the dimensions of the object */
                GA_Left,    win->BorderLeft,
                GA_Top,     win->BorderTop,
                GA_Width,   win->Width - win->BorderLeft - win->BorderRight,
                GA_Height,  win->Height - win->BorderTop - win->BorderBottom,

                /* Make sure we receive IDCMP_IDCMPUPDATE messages from
                 * the object */
                ICA_TARGET, ICTARGET_IDCMP,
                TAG_DONE);

    /* Add the object to the window */
    AddDTObject (win, NULL, dto, -1);

    /* Refresh the DataType object */
    RefreshDTObjects (dto, win, NULL, NULL);

    /* Keep going until we're told to stop */
    while (going)
    {
        /* Wait for an event */
        sigr = Wait ((1L << win->UserPort->mp_SigBit) | SIGBREAKF_CTRL_C);

        /* Did we get a break signal */
        if (sigr & SIGBREAKF_CTRL_C)
            going = FALSE;

        /* Pull Intuition messages */
        while (imsg = (struct IntuiMessage *) GetMsg (win->UserPort))
        {
            /* Handle each message */
            switch (imsg->Class)
            {
                case IDCMP_IDCMPUPDATE:
                    /* Get a pointer to the attribute list */
                    tstate = tags = (struct TagItem *) imsg->IAddress;

                    /* Step through the attribute list */
                    while (tag = NextTagItem (&tstate))
                    {
                        tidata = tag->ti_Data;
                        switch (tag->ti_Tag)
                        {
                            /* Change in busy state */
                            case DTA_Busy:
                                if (tidata)
                                    SetWindowPointer (win, WA_BusyPointer, TRUE, TAG_DONE);
                                else
                                    SetWindowPointer (win, WA_Pointer, NULL, TAG_DONE);
                                break;

                            /* Error message */
                            case DTA_ErrorLevel:
                                if (tidata)
                                {
                                    errnum = GetTagData (DTA_ErrorNumber, NULL, tags);
                                    PrintErrorMsg (errnum, (STRPTR) options[OPT_NAME]);
                                }
                                break;

                            /* Time to refresh */
                            case DTA_Sync:
                                /* Refresh the DataType object */
                                RefreshDTObjects (dto, win, NULL, NULL);
                                break;
                        }
                    }
                    break;
            }

            /* Done with the message, so reply to it */
            ReplyMsg ((struct Message *) imsg);
        }
    }


Removing a DataType Object from a Window

A DataType object must be removed from the window using the RemoveDTObject()
function.

    LONG RemoveDTObject (struct Window *w, Object *dto)

This function removes the DataType object from the window's gadget list.

This is the only way that a DataType object should be removed from the
window list.  Using RemoveGList() is not supported, nor is removing the
object manually.


Setting an Existing DataType Object's Attributes

An objects attributes are not necessarily static.  An application can ask an
object to set certain attributes using the SetDTAttrs() function.

    ULONG SetDTAttrsA (Object *dto, struct Window *w, struct Requester *r, struct TagItem *attrs)

The return value is DataType object specific, but generally a non-zero value
means that the object needs to be visually refreshed.

The following fragment illustrates how to set the current top values for a
DataType object, using the VarArgs version of SetDTAttrsA().

    SetDTAttrs (dto, window, NULL,
		DTA_TopVert,	0,
		DTA_TopHoriz,	0,
		TAG_DONE);

This will cause the DataType object to update its vertical and horizontal
top values.  If the object has been added to a window, then the display will
be updated accordingly.

Note that it is not OK to call SetGadgetAttrs() or SetAttrs() on a DataType
object.


Getting a DataType Object's Attributes

The DataTypes function GetDTAttrsA() is used to obtain the values for a list
of attributes from a DataType object.

    ULONG GetDTAttrsA (Object *dto, struct TagItem *attrs)

Where dto is a pointer to a DataType object returned by NewDTObjectA().

And attrs is a TAG_DONE terminated array of attributes.  Where the data
element of each pair contains the address of the storage variable for that
attribute.

This function will return a number that indicates that number of attributes
that it was able to obtain.  For example if four attributes asked for and
GetDTAttrs returns a four, then all the attributes were obtained.

The following code fragment illustrates how to get the current top values
for a DataTypes object using the VarArgs form of GetDTAttrsA().

    LONG topv, toph;

    if (GetDTAttrs (dto, DTA_TopVert, &topv, DTA_TopHoriz, &toph, TAG_DONE) == 2)
    {
	printf ("Top: vertical=%ld, horizontal=%ld\n", topv, toph);
    }
    else
    {
	printf ("couldn't obtain the top values\n");
    }




Writing A Sub-Class

For each of the DataType categories, there is a class that handles the data.
Handlers for explicit data are sub-classes under the appropriate class.  For
example, a class that handles ILBM pictures would be a sub-class of the
Picture class.

In order to fully understand the class concept used by DataTypes it helps to
have a basic understanding of BOOPSI.

A sub-class must provide an OM_NEW method that converts the source data into
the data format required by the super-class.  The sub-class must optionally
have a OM_DISPOSE method that discards any of the data constructed in the
OM_NEW method.   All other methods must be passed to the super-class.

Following is a listing of a example class dispatcher for a sub-class of the
picture class:


    ULONG ASM Dispatch (REG (a0) Class *cl, REG (a2) Object *o, REG (a1) Msg msg)
    {
        struct ClassBase *cb = (struct ClassBase *) cl->cl_UserData;
        ULONG retval;

        switch (msg->MethodID)
        {
            case OM_NEW:
                if (retval = DoSuperMethodA (cl, o, msg))
                {
                    /* Convert the source data to required data format */
                    if (!GetObjectData (cb, cl, (Object *)retval,
                                 ((struct opSet *) msg)->ops_AttrList))
                    {
                        /* Force disposal of the object */
                        CoerceMethod (cl, (Object *) retval, OM_DISPOSE);
                        retval = NULL;
		    }
                }
                break;

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

        return (retval);
    }


Obtaining a Handle to the Source Data

The first step that a class has to perform in order to convert the source
data, is to get a handle on the data.  This is obtained by passing the
DTA_Handle tag to the super-class using the OM_GET method.  For example:


    Object *o;
    BPTR fh;

    GetDTAttrs (o, DTA_Handle, &fh, TAG_DONE);


If the source Type is DTF_IFF, then DTA_Handle points to a struct IFFHandle
that is already initialized and opened for reading, otherwise the handle
points to a BPTR file handle.


Handling Errors

Whenever an error occurs and the class is unable to continue converting the
data, then it must set an appropriate error using the DOS SetIoErr()
function and the <dos/dos.h> error codes or the error codes defined in
<datatypes/datatypes.h>.

For example, if the class is unable to allocate memory:


    if (buf = AllocVec (size, MEMF_CLEAR))
    {
	... continue with conversion ...
    }
    else
    {
	SetIoErr (ERROR_NO_FREE_STORE);
    }


Data Format

The following sections outline the required data format for each of the main
object types.


Picture Class

The picture class is the super-class for any static graphic classes.  The
structures and tags used by this class are defined in
<datatypes/pictureclass.h>.

A picture sub-class must fill out a BitMapHeader structure as well as
provide a Mode ID, a BitMap and a ColorMap during the OM_NEW method of the
class.  There is no need to provide any other methods, as the remainder is
handled by the picture class itself.

The picture sub-class needs to fill in any fields of the BitMapHeader
structure that the class has data for.  This will ensure that the picture
data can then be saved to a file or copied to the clipboard.


    struct BitMapHeader *bmh;

    /* Obtain a pointer to the BitMapHeader structure from the picture class */
    if (GetDTAttrs (dto, PDTA_BitMapHeader, &bmh, TAG_DONE) && bmh)
    {
	/* Fill in some fields */
	bmh->bmh_Width  = 640;
	bmh->bmh_Height = 200;
	bmh->bmh_Depth  = 2;
    }


Before manipulating any color information, the picture sub-class must first
tell the picture class how many colors it has, so that the information
required for color remapping can be established.


    SetDTAttrs (dto, PDTA_NumColors, ncolors, TAG_DONE);


After the number of colors have been established, the palette information
can be filled in for the picture.  The following fragment illustrates
filling in the palette information.


    struct ColorRegister *cmap;
    WORD n, ncolors;
    LONG *cregs;

    /* Get a pointer to the color registers that need to be
     * filled in */
    GetDTAttrs (dto,
		PDTA_ColorRegisters,	&cmap,
		PDTA_CRegs,		&cregs,
		TAG_DONE);

    /* Set the color information */
    for (n = 0; n < ncolors; n++)
    {
	if (Read (fh, &rgb, QSIZE) == QSIZE)
	{
	    /* Set the master color table */
	    cmap->red   = rgb.rgbRed;
	    cmap->green = rgb.rgbGreen;
	    cmap->blue  = rgb.rgbBlue;
	    cmap++;

	    /* Set the color table used for remapping */
	    cregs[n * 3 + 0] = rgb.rgbRed   << 24;
	    cregs[n * 3 + 1] = rgb.rgbGreen << 24;
	    cregs[n * 3 + 2] = rgb.rgbBlue  << 24;
	}
	else
	{
	    /* Indicate that we encountered an error.  DOS Read
	     * will have already filled in the IoErr() value */
	    return (FALSE);
	}
    }


The picture class must get the actual picture information in standard Amiga
bitmap format.  If the bitmap is allocated using the graphics AllocBitMap()
function, then the picture class can dispose of the bitmap at OM_DISPOSE
time.

    struct BitMap *bm;

    if (bm = AllocBitMap (bmh->bmh_Width, bmh->bmh_Height, bmh->bmh_Depth, BMF_CLEAR, NULL))
    {
	/* Tell the picture class about the picture data */
	SetDTAttrsA (dto, PDTA_BitMap, bm, TAG_DONE);
    }
    else
    {
	/* Indicate the error and that we encountered an error */
	SetIoErr (ERROR_NO_FREE_STORE);
	return (FALSE);
    }

A picture sub-class needs to provide the following fields to the
super-class, during the OM_NEW method:

    DTA_NominalHoriz	(LONG) Set to the width of the picture.

    DTA_NominalVert	(LONG) Set to the height of the picture.

    PDTA_ModeID		(ULONG) A valid mode ID for the BitMap.

    DTA_ObjName		(STRPTR) The name, or title, of the picture

    DTA_ObjAuthor	(STRPTR) The author of the picture.

    DTA_ObjAnnotation	(STRPTR) Notes on the picture.

    DTA_ObjCopyright	(STRPTR) Copyright notice for the picture.

    DTA_ObjVersion	(STRPTR) Version of the picture.


If a picture sub-class uses something other than AllocBitMap() to allocate the
bitmap, then it must free the bitmap itself.  This is done by implementing
an OM_DISPOSE method for the sub-class.


    ULONG ASM Dispatch (REG (a0) Class *cl, REG (a2) Object *o, REG (a1) Msg msg)
    {
        struct ClassBase *cb = (struct ClassBase *) cl->cl_UserData;
	struct localData *lod;
        ULONG retval;

        switch (msg->MethodID)
        {
	    case OM_NEW:
		/* ... */
		break;

	    case OM_DISPOSE:
		/* Get a pointer to our object data */
		lod = INST_DATA (cl, o);

		/* Tell the picture class that it doesn't have a
		 * bitmap to free any more */
		SetDTAttrs (o, PDTA_BitMap, NULL, TAG_DONE);

		/* Free the bitmap ourself */
		myfreebitmap (lod->lod_BitMap);

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

        return (retval);
    }


Sound Class

The sound class is the super-class for any sampled audio classes.  The
structures and tags used by this class are defined in
<datatypes/soundclass.h>.

A sound sub-class needs to provide the following fields to the super-class,
during the OM_NEW method:


    SDTA_Sample		(UBYTE *) 8-bit sound data.  The sound class will
			FreeVec() the sample data.

    SDTA_SampleLength	(ULONG) Number of 8-bit bytes in the sound data.

    SDTA_Volume		(UWORD) Number ranging from 0 being the quietest to
			64 being the loudest.

    SDTA_Period		(UWORD) Amount of time to play the sound.

    SDTA_Cycles		(UWORD) Number of times to play the sound.  0 being
			an infinite loop.

    DTA_ObjName		(STRPTR) The name, or title, of the sound

    DTA_ObjAuthor	(STRPTR) The author of the sound.

    DTA_ObjAnnotation	(STRPTR) Notes on the sound.

    DTA_ObjCopyright	(STRPTR) Copyright notice for the sound.

    DTA_ObjVersion	(STRPTR) Version of the sound.


No methods other OM_NEW are need, as the remainder is handled by the sound
class itself.

The sound sub-class also needs to fill in any fields of the VoiceHeader
structure that the class has data for.  This will ensure that the sound data
can then be saved to a file or copied to the clipboard.


    struct VoiceHeader *vh;

    /* Obtain a pointer to the VoiceHeader structure from the sound class */
    if (GetDTAttrs (dto, SDTA_VoiceHeader, &vh, TAG_DONE) && vh)
    {
	/* Fill in some fields */
	vh->vh_Octaves     = 1;
	vh->vh_Compression = 0;
	vh->vh_Volume      = 63;
    }


If a sound sub-class uses something other than AllocVec() to allocate the
sound data, then it must free the sample itself.  This is done by
implementing an OM_DISPOSE method for the sub-class.


    ULONG ASM Dispatch (REG (a0) Class *cl, REG (a2) Object *o, REG (a1) Msg msg)
    {
        struct ClassBase *cb = (struct ClassBase *) cl->cl_UserData;
	struct localData *lod;
        ULONG retval;

        switch (msg->MethodID)
        {
	    case OM_NEW:
		/* ... */
		break;

	    case OM_DISPOSE:
		/* Get a pointer to our object data */
		lod = INST_DATA (cl, o);

		/* Tell the sound class that it doesn't have a
		 * sample to free any more */
		SetDTAttrs (o, SDTA_Sample, NULL, TAG_DONE);

		/* Free the sample ourself */
		FreeMem (lod->lod_Sample, lod->lod_SampleLength);

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

        return (retval);
    }


Text Class

The text class is the super-class for any formatted or non-formatted text
classes.  The structures and tags used by this class are defined in
<datatypes/textclass.h>.

The text class provides the sub-class with a buffer that contains the text
data.  The sub-class must then provide the text class with a list of Line
segments during the layout method.  This Line list should only be created at
gpl_Initial time, unless the TDTA_WordWrap attribute is TRUE.


    struct Line
    {
        struct MinNode	 ln_Link;
        STRPTR		 ln_Text;
        ULONG		 ln_TextLen;
        UWORD		 ln_XOffset;
        UWORD		 ln_YOffset;
        UWORD		 ln_Width;
        UWORD		 ln_Height;
        UWORD		 ln_Flags;
        BYTE		 ln_FgPen;
        BYTE		 ln_BgPen;
        ULONG		 ln_Style;
        APTR		 ln_Data;
    };


The Line structure fields are as follows:

ln_Link
    MinNode used to link to the line list.

ln_Text
    Pointer to the text for this line segment.

ln_TextLen
    Number of bytes of text in this line segment.

ln_XOffset
    Left pixel offset from the left edge of the object for this line segment.

ln_YOffset
    Top pixel offset from the top edge of the object for this line segment.

ln_Width
    Width of the line segment in pixels.

ln_Height
    Height of the line segment in pixels.

ln_Flags
    Control flags for this line segment.

    LNF_LF	Used to indicate that this segment is the end of a line.

ln_FgPen
    Pen to use for the foreground (the text color) for this line segment.

ln_BgPen
    Pen to use for the background color for this line segment.

ln_Style
    Text attribute soft style to use for this line segment.


As each Line segment is allocated it must be added to the Line list.


    struct List *linelist;
    struct Line *line;

    /* Get a pointer to the line list */
    if (GetDTAttrs (o, TDTA_LineList, (ULONG) &linelist, TAG_DONE) && linelist)
    {
	/* Create a Line segment */
	if (line = AllocVec (sizeof (struct Line), MEMF_CLEAR))
	{
	    /* Add it to the list */
	    AddTail (linelist, (struct Node *)&line->ln_Link);
	}
    }


Currently, this is the hardest class to sub-class, due to the number of
methods that must be implemented.  Following is the shell for a dispatcher
and the layout method for a text sub-class.


    struct localData
    {
        VOID	*lod_Pool;
        ULONG	 lod_Flags;
    };

    ULONG ASM Dispatch (REG (a0) Class * cl, REG (a2) Object * o, REG (a1) Msg msg)
    {
        struct ClassBase *cb = (struct ClassBase *) cl->cl_UserData;
        struct localData *lod;
        struct List *linelist;
        ULONG retval = 0L;

        switch (msg->MethodID)
        {
            case OM_NEW:
                if (retval = DoSuperMethodA (cl, o, msg))
                {
                    ULONG len, estlines, poolsize;
                    BOOL success = FALSE;
                    STRPTR buffer;

                    /* Get a pointer to the object data */
                    lod = INST_DATA (cl, (Object *) retval);

                    /* Get the attributes that we need to determine
		     * memory pool size */
                    GetDTAttrs ((Object *) retval,
                                TDTA_Buffer,        (ULONG)&buffer,
                                TDTA_BufferLen,     (ULONG)&len,
                                TAG_DONE);

                    /* Make sure we have a text buffer */
                    if (buffer && len)
                    {
                        /* Estimate the pool size that we will need */
                        estlines = (len / 80) + 1;
                        estlines = (estlines > 200) ? 200 : estlines;
                        poolsize = sizeof (struct Line) * estlines;

                        /* Create a memory pool for the line list */
                        if (lod->lod_Pool = CreatePool (MEMF_CLEAR | MEMF_PUBLIC, poolsize, poolsize))
                            success = TRUE;
			else
			    SetIoErr (ERROR_NO_FREE_STORE);
                    }
                    else
                    {
			/* Indicate that something was missing that we
			 * needed */
                        SetIoErr (ERROR_REQUIRED_ARG_MISSING);
                    }

                    if (!success)
                    {
                        CoerceMethod (cl, (Object *) retval, OM_DISPOSE);
                        retval = NULL;
                    }
                }
                break;

            case OM_UPDATE:
            case OM_SET:
		/* 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, &gpr);

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

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

                /* Let the super-class partake */
                retval = (ULONG) 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, NULL,
                                   GA_ID,	G(o)->GadgetID,
                                   DTA_Busy,	TRUE,
                                   TAG_DONE);

                /* Let the super-class partake and then fall through to our layout method */
                retval = (ULONG) DoSuperMethodA (cl, o, msg);

            case DTM_ASYNCLAYOUT:
                /* Layout the text */
                retval = layoutMethod (cb, cl, o, (struct gpLayout *) msg);
                break;

            case OM_DISPOSE:
                /* Get a pointer to our object data */
                lod = INST_DATA (cl, o);

                /* Don't let the super class free the line list */
                if (GetDTAttrs (o, TDTA_LineList, (ULONG) &linelist, TAG_DONE) && linelist)
                    NewList (linelist);

                /* Delete the line pool */
                DeletePool (lod->lod_Pool);

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

        return (retval);
    }

    ULONG layoutMethod (struct ClassBase *cb, Class * cl, Object * o, struct gpLayout * gpl)
    {
        struct DTSpecialInfo *si = (struct DTSpecialInfo *) G (o)->SpecialInfo;
        struct localData *lod = INST_DATA (cl, o);
        ULONG visible = 0, total = 0;
        struct RastPort trp;
        ULONG hunit = 1;
        ULONG bsig = 0;

        /* Switches */
        BOOL linefeed = FALSE;
        BOOL newseg = FALSE;
        BOOL abort = FALSE;

        /* Attributes obtained from super-class */
        struct TextAttr *tattr;
        struct TextFont *font;
        struct List *linelist;
        struct IBox *domain;
        ULONG wrap = FALSE;
        ULONG bufferlen;
        STRPTR buffer;
        STRPTR title;

        /* Line information */
        ULONG num, offset, swidth;
        ULONG anchor, newanchor;
        ULONG style = FS_NORMAL;
        struct Line *line;
        ULONG yoffset = 0;
        UBYTE fgpen = 1;
        UBYTE bgpen = 0;
        ULONG tabspace;
        ULONG numtabs;
        ULONG i, j;

        ULONG nomwidth, nomheight;

        /* Get all the attributes that we are going to need for a successful layout */
        if (GetDTAttrs (o,
                             DTA_TextAttr,  (ULONG) &tattr,
                             DTA_TextFont,  (ULONG) &font,
                             DTA_Domain,    (ULONG) &domain,
                             DTA_ObjName,   (ULONG) &title,
                             TDTA_Buffer,   (ULONG) &buffer,
                             TDTA_BufferLen,(ULONG) &bufferlen,
                             TDTA_LineList, (ULONG) &linelist,
                             TDTA_WordWrap, (ULONG) &wrap,
                             TAG_DONE) == 8)
        {
            /* Lock the global object data so that nobody else can manipulate it */
            ObtainSemaphore (&(si->si_Lock));

            /* Make sure we have a buffer */
            if (buffer)
            {
                /* Initialize the temporary RastPort */
                InitRastPort (&trp);
                SetFont (&trp, font);

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

		/* Calculate the tab space */
		tabspace = font->tf_XSize * 8;

                /* We only need to perform layout if we are doing word wrap, or this
                 * is the initial layout call */
                if (wrap || gpl->gpl_Initial)
                {
                    /* Delete the old line list */
                    while (line = (struct Line *) RemHead (linelist))
                        FreePooled (lod->lod_Pool, line, sizeof (struct Line));

                    /* Step through the text buffer */
                    for (i = offset = num = numtabs = 0;
                         (i <= bufferlen) && (bsig == 0) && !abort;
                         i++)
                    {
                        /* Check for end of line */
                        if (buffer[i]==13 && buffer[i+1]==10)
                        {
                            newseg = linefeed = TRUE;
                            newanchor = i + 2;
                            i++;
                        }
                        /* Check for end of page */
                        else if (buffer[i] == 12)
                        {
                            newseg = linefeed = TRUE;
                            newanchor = i + 1;
                        }
                        /* Check for tab */
                        else if (buffer[i] == 9)
                        {
                            /* See if we need to terminate a line segment */
                            if ((numtabs == 0) && num)
                                newseg = TRUE;
                            numtabs++;
                        }
                        else
                        {
                            /* See if we have any TABs that we need to finish out */
                            if (numtabs)
                            {
                                offset += (((offset / tabspace) + 1) * tabspace) - offset;
                                num = numtabs = 0;
                                anchor = i;
                            }

			    /* Compute the width of the line. */
                            swidth = TextLength (&trp, &buffer[anchor], num+1);
                            if (offset + swidth > domain->Width)
                            {
                                /* Search for a whitespace character */
                                for (j = i; (j >= anchor) && !newseg; j--)
                                {
                                    if (buffer[j] == ' ')
                                    {
                                        num -= (i - j);
                                        newseg = TRUE;
                                        i = j + 1;
                                    }
                                }

                                newseg = linefeed = TRUE;
                                newanchor = i;
                                i--;
                            }
                            else
                            {
                                num++;
                            }
                        }

                        /* Time for a new text segment yet? */
                        if (newseg)
                        {
                            /* Allocate a new line segment from our memory pool */
                            if (line = AllocPooled (lod->lod_Pool, sizeof (struct Line)))
                            {
                                swidth = TextLength (&trp, &buffer[anchor], num);
                                line->ln_Text    = &buffer[anchor];
                                line->ln_TextLen = num;
                                line->ln_XOffset = offset;
                                line->ln_YOffset = yoffset + font->tf_Baseline;
                                line->ln_Width   = swidth;
                                line->ln_Height  = font->tf_YSize;
                                line->ln_Flags   = (linefeed) ? LNF_LF : NULL;
                                line->ln_FgPen   = fgpen;
                                line->ln_BgPen   = bgpen;
                                line->ln_Style   = style;
                                line->ln_Data    = NULL;

                                /* Add the line to the list */
                                AddTail (linelist, (struct Node *) line);

                                /* Increment the line count */
                                if (linefeed)
                                {
                                    yoffset += font->tf_YSize;
                                    offset = 0;
                                    total++;
                                }
                                else
                                {
                                    /* Increment the offset */
                                    offset += swidth;
                                }
                            }
                            else
                            {
                                abort = TRUE;
                            }

                            /* Clear the variables */
                            newseg = linefeed = FALSE;
                            anchor = newanchor;
                            num = 0;

                            /* Check to see if layout has been aborted */
                            bsig = CheckSignal (SIGBREAKF_CTRL_C);
                        }
                    }
                }
                else
                {
                    /* No layout to perform */
                    total = si->si_TotVert;
                }
            }

            /* Compute the lines and columns type information */
            si->si_VertUnit  = font->tf_YSize;
            si->si_VisVert   = visible = domain->Height / si->si_VertUnit;
            si->si_TotVert   = total;

            si->si_HorizUnit = hunit = 1;
            si->si_VisHoriz  = (LONG) domain->Width / hunit;
            si->si_TotHoriz  = domain->Width;

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

            /* Were we aborted? */
            if (bsig == 0)
            {
                /* Not aborted, so tell the world of our newest attributes */
                notifyAttrChanges (o, gpl->gpl_GInfo, NULL,
                                   GA_ID,                   G(o)->GadgetID,

                                   DTA_VisibleVert,         visible,
                                   DTA_TotalVert,           total,
                                   DTA_NominalVert,         nomheight,
                                   DTA_VertUnit,            font->tf_YSize,

                                   DTA_VisibleHoriz,        (ULONG) (domain->Width / hunit),
                                   DTA_TotalHoriz,          domain->Width,
                                   DTA_NominalHoriz,        nomwidth,
                                   DTA_HorizUnit,           hunit,

                                   DTA_Title,               title,
                                   DTA_Busy,                FALSE,
                                   DTA_Sync,                TRUE,
                                   TAG_DONE);
            }
        }
        return (total);
    }




Defining a DataType Descriptor

DataTypes uses a simple descriptor to determine what type of data a file
contains and what class, if any, is used to handle that data.

The DTDesc utility is used to define a DataType descriptor. The following
steps describe how to use this utility to define a descriptor.

  1.  Load several sample files of the type being defined.  This can be done
by dropping their icons into the DTDesc window or by using the
"Extras/Load Samples..." menu item.

  2.  The view area in the bottom right side of the DTDesc window will show
the first 64 characters of the files.  This area is used to define the Mask.
The characters that don't match will be blotted out and only the similar
characters will be shown.  If more characters are shown as similar than
really are, then they can easily be blotted out by rubbing over them.

  3.  DataTypes can be broken out into several different categories.  Use
the Group menu to select the category that the DataType descriptor belongs
in.

  4.  The remaining fields must be filled out.  Following is a table
describing the fields and the information they require.


Name			Description
======================	====================================================
File Type		User description of the DataType.

Base Name		The base name of the DataType.  The class library
			name is derived from this.

Name Pattern		The name of the file can be used to indicate the
			DataType.  AmigaDOS wildcards can be used to
			specify the file name.  For example

Function		A function can be used to further define a data
			type.  This function must be a stand-alone
			executable that uses the DataType Descriptor Function
			Interface.

Case Sensitive?		The Mask can be either case sensitive or not.

Priority		Descriptors are sorted by Type, Function,
			Name Pattern, and Mask.  The priority field allows a
			DataType descriptor to be assigned a different
			priority than a similar DataType descriptor.

Type			This is a read-only field and is used to indicate
			what basic type a DataType is.  Types include IFF,
			Binary and ASCII.
======================	====================================================


  5.  Once a DataType descriptor has been defined, it must be saved.
Select the "Project/Save As..." menu item for the file requester used to
save a DataType descriptor.  The default name for a DataType descriptor is
the text entered into File Type field.

  6.  In order for a new DataType descriptor to be loaded, the
datatypes.library must be flushed from the system.   This can either be done
with the FlushLibs command or by using Avail Flush several times.  Another
way that the new DataType descriptor can be loaded is by using the
AddDataTypes command with the REFRESH option.


DataType Descriptor Function Interface

Sometimes the fields within the DTDesc utility are not enough to define a
DataType descriptor.  In this case it is necessary to use a Function to
further narrow down a DataType.

A Function is a stand-alone executable that expects the following arguments.


    retval = Function (dthc);
    d0                 a0

    BOOL Function (struct DTHookContext *);


The function must return TRUE if the data matches, FALSE if it doesn't.

The DTHookContext structure contains the fields that are necessary for
narrowing down the DataType.  Following is a listing of the DTHookContext
structure.


    struct DTHookContext
    {
        struct Library		*dthc_SysBase;
        struct Library		*dthc_DOSBase;
        struct Library		*dthc_IFFParseBase;
        struct Library		*dthc_UtilityBase;

        /* File context */
        BPTR			 dthc_Lock;		/* Lock on the file */
        struct FileInfoBlock	*dthc_FIB;		/* Pointer to a FileInfoBlock */
        BPTR			 dthc_FileHandle;	/* Pointer to the file handle (may be NULL) */
        struct IFFHandle	*dthc_IFF;		/* Pointer to an IFFHandle (may be NULL) */
        STRPTR			 dthc_Buffer;		/* Buffer */
        ULONG			 dthc_BufferLength;	/* Length of the buffer */
    };


The DTHookContext structure fields are as follows:

dthc_SysBase
dthc_DOSBase
dthc_IFFParseBase
dthc_UtilityBase
    These are library bases that your function can utilitize.  It will need
    to open any other libraries that it needs.

dthc_Lock
    If the source data is a DOS file, then this is a lock on the file.

dthc_FIB
    If the source data is a DOS file, then this is a filled in FileInfoBlock
    for the file.

dthc_FileHandle
    If the source data is a DOS file, then this is the file handle for the
    file otherwise the handle will be NULL.  The file is guaranteed to be at
    the beginning.

dthc_IFF
    If the source data is IFF, then this is the IFFHandle for accessing the
    data, otherwise the handle will be NULL.  The position is guaranteed to
    be at the beginning of the data.  The DOS file fields must not be
    accessed if the data is IFF.

dthc_Buffer
    This buffer contains the first dthc_BufferLength bytes of data.

dthc_BufferLength
    Indicates the number of bytes in dthc_Buffer, up to 64.


The following example shows how to write a simple DataTypes Descriptor
Function.


    /* This example is to be compiled with no stack checking.  It
     * must not be linked with any startup code so that DTHook is
     * the entry point for the executable.
     *
     */
    #include <exec/types.h>
    #include <dos/dos.h>
    #include <dos/dosextens.h>
    #include <datatypes/datatypes.h>

    #include <clib/exec_protos.h>
    #include <clib/dos_protos.h>
    #include <clib/utility_protos.h>

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

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

    #define SysBase          dthc->dthc_SysBase
    #define DOSBase          dthc->dthc_DOSBase
    #define UtilityBase      dthc->dthc_UtilityBase

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

    BOOL __asm DTHook (register __a0 struct DTHookContext * dthc)
    {
	BOOL retval = FALSE;
        register ULONG i;
	UBYTE ch;

        /* Make sure we have a buffer */
        if (dthc->dthc_Buffer)
        {
	    for (i = 0; (i < dthc->dthc_BufferLength) && !retval; i++)
	    {
		ch = dthc->dthc_Buffer[i];

		/* Look at the data... */
	    }
        }
        return retval;
    }


The next example shows how to write a DataTypes Descriptor that looks into
an IFF file for needed chunk information.


    /* This example is to be compiled with no stack checking.  It
     * must not be linked with any startup code so that DTHook is
     * the entry point for the executable.
     *
     */
    #include <exec/types.h>
    #include <dos/dos.h>
    #include <dos/dosextens.h>
    #include <datatypes/datatypes.h>
    #include <datatypes/pictureclass.h>
    #include <libraries/iffparse.h>

    #include <clib/exec_protos.h>
    #include <clib/dos_protos.h>
    #include <clib/iffparse_protos.h>
    #include <clib/utility_protos.h>

    #include <pragmas/exec_pragmas.h>
    #include <pragmas/dos_pragmas.h>
    #include <pragmas/iffparse_pragmas.h>
    #include <pragmas/utility_pragmas.h>

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

    #define SysBase          dthc->dthc_SysBase
    #define DOSBase          dthc->dthc_DOSBase
    #define IFFParseBase     dthc->dthc_IFFParseBase
    #define UtilityBase      dthc->dthc_UtilityBase

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

    #define BMH_SIZE (sizeof (struct BitMapHeader))

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

    BOOL __asm DTHook (register __a0 struct DTHookContext * dthc)
    {
        struct BitMapHeader bmh;
        struct ContextNode *cn;
        struct IFFHandle *iff;
        BOOL retval = FALSE;

        /* Make sure that this is an IFF data type */
        if (iff = dthc->dthc_IFF)

            /* Stop on the BitMapHeader (type, id) */
            if (StopChunk (iff, ID_ILBM, ID_BMHD) == 0)

		/* Scan through the IFF handle */
                if (ParseIFF (iff, IFFPARSE_SCAN) == 0L)

		    /* Make sure we have a current chunk */
                    if (cn = CurrentChunk (iff))

			/* Make sure the current chunk is ILBM BMHD */
                        if ((cn->cn_Type == ID_ILBM) && (cn->cn_ID == ID_BMHD))

			    /* Read the chunk data */
                            if (ReadChunkBytes (iff, &bmh, BMH_SIZE) == BMH_SIZE)

				/* See if the depth is set to 24 */
                                if (bmh.bmh_Depth == 24)
                                    retval = TRUE;

        return (retval);
    }
