#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>
#include <dos/exall.h>
#include <dos/dostags.h>

#include <stdlib.h>
#include "amigan.h"

#define SIZELISTVIEWCHARS 37

MODULE void pop(void);
MODULE void push(STRPTR name, ULONG index);
MODULE void subdir(ULONG index);
MODULE void parent(void);
MODULE void root(void);
MODULE void pathasl(void);
MODULE void pathstring(void);
MODULE void comma(ULONG value);

IMPORT ABOOL               fillwindows;
IMPORT TEXT                asldir[VLONGFIELD + 1],
                           aslresult[MEDFIELD + 1];
// we only use LIMIT_DIRS worth of FileNamePtrs.
MODULE STRPTR              FileNamePtr[LIMIT_FILES];
MODULE STRPTR              DirNamePtr[LIMIT_DIRS];
MODULE UBYTE               valuestring[11],
                           commastring[14];
IMPORT  SBYTE              page;
MODULE  ULONG              DirNamesAllocated  = 0,
                           FileNamesAllocated = 0;
IMPORT  TEXT               globalname[VLONGFIELD + 1];
IMPORT  TEXT               IOBuffer[LONGESTFIELD + 1];
IMPORT  TEXT               weekdaystring[10],
                           datestring[10],
                           timestring[9];
IMPORT struct SharedStruct shared;
IMPORT struct NewGadget    Gadget;
IMPORT struct Screen*      ScreenPtr;
IMPORT struct Window*      MainWindowPtr;
IMPORT struct Gadget      *BU99_Left,
                          *BU99_Right,
                          *ST99_Output,
                          *BU99_OutputASL,
                          *CB99_Log,
                          *BU99_Update,
                          *BU99_Stop,
                          *GListPtr,
                          *PrevGadPtr;
IMPORT struct ExAllData*   ead;

MODULE struct List*       SizeListPtr    = NULL;
MODULE struct Gadget      *LV81_Listview = NULL,
                          *ST81_Path     = NULL,
                          *BU81_PathASL  = NULL,
                          *BU81_Root     = NULL,
                          *BU81_Parent   = NULL,
                          *TE81_Total    = NULL,
                          *TE81_Free     = NULL;
MODULE struct
{   ULONG entries, totalbytesval, freeval, stackentries;
    TEXT  totalbytesstring[11], freestring[11],
          oldpath[VLONGFIELD + 1], path[VLONGFIELD + 1];
    ULONG bytes[LIMIT_DIRS], topdir[LIMIT_DIRS];
    LONG  type[LIMIT_DIRS];
} size;
MODULE ABOOL stop;

AGLOBAL void size1(void)
{   verynewwindow
    (   SIZE1WIDTH, SIZE1HEIGHT,
        "Report+: Path Size Report",
        BUTTONIDCMP | STRINGIDCMP | LISTVIEWIDCMP
    );
    if (fillwindows)
    {   SetAPen(MainWindowPtr->RPort, 0);
        RectFill(MainWindowPtr->RPort,  12,  44,  12 + 348,  44 + 126 - 3);
        RectFill(MainWindowPtr->RPort, 228, 168, 228 + 116, 168 + 12);
        RectFill(MainWindowPtr->RPort, 228, 180, 228 + 116, 180 + 12);
    }

    /* path */
    strcpy(size.path, shared.output);
    setgadget(60, 20, 270, 12, "_Path:", NULL);
    ST81_Path = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(size.path),
        GTST_MaxChars, VLONGFIELD,
        GA_TabCycle, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* path... */
    setgadget(332, 20, 28, 12, "_...", NULL);
    BU81_PathASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    // root
    setgadget(60, 32, 150, 12, "_Root", NULL);
    BU81_Root = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    // parent
    setgadget(210, 32, 150, 12, "P_arent", NULL);
    BU81_Parent = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    setgadget(12, 44, 348, 126, NULL, NULL);
    LV81_Listview = PrevGadPtr = (struct Gadget *) CreateGadget
    (   LISTVIEW_KIND,
        PrevGadPtr,
        &Gadget,
        GTLV_Labels, NULL,
        TAG_DONE
    );

    /* total bytes */
    setgadget(228, 168, 116, 12, "Path bytes:", NULL);
    TE81_Total = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, size.totalbytesstring,
        GTTX_Border, TRUE,
        TAG_DONE
    );
    /* free bytes */
    setgadget(228, 180, 116, 12, "Free bytes:", NULL);
    TE81_Free = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, size.freestring,
        GTTX_Border, TRUE,
        TAG_DONE
    );

    // log to file?
    setgadget(370, 61, 0, 0, "_Log to file?", PLACETEXT_RIGHT);
    CB99_Log = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GTCB_Checked, shared.log,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* output */
    if (!(shared.output[0]))
        strcpy(shared.output, "RAM:Report.txt");
    setgadget(370, 88, 230, 12, "_Output pathname:", PLACETEXT_ABOVE);
    ST99_Output = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(shared.output),
        GTST_MaxChars, LONGFIELD,
        GA_TabCycle, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* output... */
    setgadget(602, 88, 28, 12, "...", NULL);
    BU99_OutputASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        TAG_DONE
    );

    /* update */
    setgadget(370, 20, 60, 12, "_Update", NULL);
    BU99_Update = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* stop */
    setgadget(430, 20, 60, 12, "Stop", NULL);
    BU99_Stop = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        TAG_DONE
    );

    /* There are 19 gadgets:
    1 BU99_Right     = 1
    2 ST81_Path      = 3
    1 BU81_PathASL   = 4
    1 BU81_Root      = 5
    1 BU81_Parent    = 6
    4 LV81_Listview  =10
    2 TE81_Total     =12
    2 TE81_Free      =14
    2 ST99_Output    =16
    1 BU99_OutputASL =17
    1 BU99_Update    =18
    1 BU99_Stop      =19 */
    drawgadgets(19);

    if (!shared.log)
    {   GT_SetGadgetAttrs
        (   ST99_Output,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_OutputASL,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }
    updatesize();
    ActivateGadget(ST81_Path, MainWindowPtr, NULL);
    loop();
    strcpy
    (   shared.output,
        ((struct StringInfo *) ST99_Output->SpecialInfo)->Buffer
    );
    closewindow();
    size_exit();
}

AGLOBAL void updatesize(void)
{   BOOL                 more; // note that it is BOOL, not ABOOL
    TEXT                 tempstring1[16], tempstring2[SIZELISTVIEWCHARS + 1],
                         tempstring3[VLONGFIELD + 1], tempname[LONGFIELD + 1];
    ULONG                i, j, tempbytes;
    LONG                 templength, temptype; // these both MUST be signed
    BPTR                 DirHandle   = NULL;
    struct InfoData*     InfoDataPtr = NULL;
    struct ExAllControl* eac         = NULL;
    struct ExAllData*    trueead     = ead;

    // 1: (Un)ghost relevant gadgets, for duration of the operation.

    stop = FALSE;
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );

    // 2: Now we get a Lock() on the path.

    size.stackentries = size.entries = 0;
    /* From RKRM I&A, p. 65: */
    if (!(DirHandle = (BPTR) Lock(size.path, ACCESS_READ)))
    {   DisplayBeep(ScreenPtr);
        GT_SetGadgetAttrs
        (   BU99_Right,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_Update,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_Stop,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
        strcpy(size.path, size.oldpath);
        GT_SetGadgetAttrs
        (   ST81_Path,
            MainWindowPtr, NULL,
            GTST_String, size.path,
            TAG_DONE
        );
        return;
    }

    GT_SetGadgetAttrs
    (   ST81_Path,
        MainWindowPtr, NULL,
        GTST_String, size.path,
        TAG_DONE
    );
    strcpy(size.oldpath, size.path);

    // 3: Now we call Info() to ascertain the free bytes.

    /* InfoDataPtr must be longword-aligned; that is why we use AllocMem(). */
    if (!(InfoDataPtr = AllocMem(sizeof(struct InfoData), MEMF_CLEAR)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Out of memory!");
    }
    if (Info(DirHandle, InfoDataPtr))
    {   size.freeval =
        (InfoDataPtr->id_NumBlocks - InfoDataPtr->id_NumBlocksUsed) *
         InfoDataPtr->id_BytesPerBlock;
        stcl_d(tempstring1, size.freeval);
        templength = 10 - strlen(tempstring1);
        strcpy(size.freestring, "");
        for (i = 1; i <= templength; i++)
            strcat(size.freestring, " ");
        strcat(size.freestring, tempstring1);
    } else strcpy(size.freestring, "         ?");
    FreeMem(InfoDataPtr, sizeof(struct InfoData));
    InfoDataPtr = NULL;

    /* 4: Now we are ready to begin the main part of the operation.

    (top-level files & dirs)
    (subdirs)

    size.topdir[]     = the 'owning' top-level directory for this subdir.
                        Top-level dirs are owned by themselves.
    size.bytes[]      = the size of this top-level directory/file.
    size.entries      = how many top-level dirs, ie. how many names in the
                        listview gadget.
    size.path         = the path (drive, partition, etc.) the user is doing
                        the report on.
    size.stackentries = the current size of the stack.
    DirNamesAllocated = the number of allocated chunks for the topdir names. */

    // This must be allocated with AllocDosObject()
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Can't allocate DOS object!");
    }

    eac->eac_LastKey = 0;
    do
    {   more = ExAll(DirHandle, (struct ExAllData *) trueead, 2048, ED_SIZE, eac);
        if ((!more) && (IoErr() != ERROR_NO_MORE_ENTRIES))
        {   FreeDosObject(DOS_EXALLCONTROL, eac);
            eac = NULL;
            UnLock(DirHandle);
            DirHandle = NULL;
            rq("Can't examine path!");
        }
        if (eac->eac_Entries == 0)
        {   ; /* ExAll() failed normally with no entries */
            continue; /* more is USUALLY zero */
        }
        // OK, ExAll() has filled &ead with (up to) 2K of file data.
        ead = trueead;

        do
        {   /* use ead here */

            if (ead->ed_Type == -3 || ead->ed_Type == 2) // if a normal file or normal directory (ie. not a link)
            {   size.entries++;
                if (size.entries == LIMIT_DIRS)
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Overflow!");
                }
                size.type[size.entries] = ead->ed_Type;
                if (size.entries > DirNamesAllocated)
                {   if (!(DirNamePtr[DirNamesAllocated + 1] = AllocMem(VLONGFIELD, MEMF_PUBLIC)))
                    {   FreeMem(ead, 2048);
                        ead = NULL;
                        FreeDosObject(DOS_EXALLCONTROL, eac);
                        eac = NULL;
                        UnLock(DirHandle);
                        DirHandle = NULL;
                        rq("Out of memory!");
                    }
                    DirNamesAllocated++;
                }

                strcpy(DirNamePtr[size.entries], ead->ed_Name);
                if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
                {   size.bytes[size.entries] = 0;
                    strcpy(tempstring3, size.path);
                    if (!AddPart(tempstring3, ead->ed_Name, VLONGFIELD))
                    {   FreeMem(ead, 2048);
                        ead = NULL;
                        FreeDosObject(DOS_EXALLCONTROL, eac);
                        eac = NULL;
                        UnLock(DirHandle);
                        DirHandle = NULL;
                        rq("Can't add filename to pathname!");
                    }
                    push(tempstring3, size.entries);
                } else size.bytes[size.entries] = ead->ed_Size;
            }
            /* get next ead */
            ead = ead->ed_Next;
        } while(ead);
    } while(more);

    ead = trueead;
    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
    pop();

    // Now handle the list.

    GT_SetGadgetAttrs
    (   LV81_Listview,
        MainWindowPtr,
        NULL,
        GTLV_Labels, (~0),
        TAG_DONE
    );

    if (SizeListPtr)
    {   FreeNameNodes(SizeListPtr);
        FreeMem(SizeListPtr, sizeof(struct List));
        SizeListPtr = NULL;
    }
    if (!(SizeListPtr = AllocMem(sizeof(struct List), MEMF_CLEAR)))
        rq("Out of memory!");
    NewList(SizeListPtr);

if (!stop)
{   /* size.entries is the actual number of entries. The array elements are
    from 1 to size.entries.

    bubble sort routine */

    for (i = size.entries; i >= 2; i--)
    {   for (j = 2; j <= i; j++) 
        {   if (size.bytes[j - 1] < size.bytes[j]) 
            {   // swap them
                tempbytes = size.bytes[j - 1];
                size.bytes[j - 1] = size.bytes[j]; 
                size.bytes[j] = tempbytes;
                temptype = size.type[j - 1];
                size.type[j - 1] = size.type[j];
                size.type[j] = temptype;
                strcpy(tempname, DirNamePtr[j - 1]);
                strcpy(DirNamePtr[j - 1], DirNamePtr[j]);
                strcpy(DirNamePtr[j], tempname);
    }   }   }

    getdate();
    if (shared.log)
    {   tempstring1[0] = QUOTE;
        tempstring1[1] = 0;
        strcpy(IOBuffer, "Path: ");
        strcat(IOBuffer, tempstring1);
        strcat(IOBuffer, size.path);
        strcat(IOBuffer, tempstring1);
        strcat(IOBuffer, " at ");
        strcat(IOBuffer, timestring);
        strcat(IOBuffer, " on ");
        strcat(IOBuffer, weekdaystring);
        strcat(IOBuffer, " ");
        strcat(IOBuffer, datestring);
        strcat(IOBuffer, ":\n\n");
    }
    // very long dirnames (truncated ones) are not selectable, unfortunately.
    size.totalbytesval = 0;
    for (i = 1; i <= size.entries; i++)
    {   comma(size.bytes[i]);
        if (strlen(DirNamePtr[i]) > 22)
            *(DirNamePtr[i] + 22) = 0; // truncate
        if (size.type[i] == 2)
            strcpy(tempstring2, "*");
        else strcpy(tempstring2, " ");
        strcat(tempstring2, DirNamePtr[i]); /* tempstring2 = "*<name>" */
        templength = 26 - strlen(DirNamePtr[i]);
        if (templength >= 1)
            for (j = 1; j <= templength; j++)
                strcat(tempstring2, " "); /* tempstring2 = "*<name>   " */
        strcat(tempstring2, commastring);
        AddNameToTail(SizeListPtr, tempstring2);
        if (shared.log)
        {   strcat(IOBuffer, tempstring2);
            strcat(IOBuffer, "\n");
        }
        size.totalbytesval += size.bytes[i];
    }

    comma(size.totalbytesval);
    GT_SetGadgetAttrs
    (   TE81_Total,
        MainWindowPtr, NULL,
        GTTX_Text, commastring,
        TAG_DONE
    );
    comma(size.freeval);
    GT_SetGadgetAttrs
    (   TE81_Free,
        MainWindowPtr,
        NULL,
        GTTX_Text, commastring,
        TAG_DONE
    );
    if (shared.log)
    {   strcat(IOBuffer, "\nPath bytes:             ");
        strcat(IOBuffer, size.totalbytesstring);
        strcat(IOBuffer, "\nFree bytes:             ");
        strcat(IOBuffer, size.freestring);
        strcat(IOBuffer, "\n\n");
        append(shared.output);
}   }
else
{   GT_SetGadgetAttrs
    (   TE81_Total,
        MainWindowPtr, NULL,
        GTTX_Text, "",
        TAG_DONE
    );
    comma(size.freeval);
    GT_SetGadgetAttrs
    (   TE81_Free,
        MainWindowPtr,
        NULL,
        GTTX_Text, "",
        TAG_DONE
    );
}
    GT_SetGadgetAttrs
    (   LV81_Listview,
        MainWindowPtr,
        NULL,
        GTLV_Labels, SizeListPtr,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Stop,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );

    while(FileNamesAllocated)
    {   FreeMem(FileNamePtr[FileNamesAllocated], VLONGFIELD);
        FileNamePtr[FileNamesAllocated--] = NULL;
    }
    // We need the DirNamePtr[] array to remain valid, because the user
    // can click on the listview gadget.
}

MODULE void subdir(ULONG topdir)
{   BOOL                 more;
    TEXT                 tempstring[VLONGFIELD + 1];
    BPTR                 DirHandle = NULL;
    struct ExAllControl* eac       = NULL;
    struct ExAllData*    trueead   = ead;

    /* Instead of passing a pointer to the path, we use a global string.
    There are memory corruption problems if you pass pointers to variables
    which are really local to other functions.

    This is dynamically allocated as it must be at least word-aligned;
    also remember not to allocate large arrays on the stack. */

    if (!(DirHandle = (BPTR) Lock(globalname, ACCESS_READ)))
    {   DisplayBeep(ScreenPtr);
        return;
    }
    if (!(eac = AllocDosObject(DOS_EXALLCONTROL, NULL)))
    {   UnLock(DirHandle);
        DirHandle = NULL;
        rq("Can't allocate DOS object!");
    }

    eac->eac_LastKey = 0;
    do
    {   more = ExAll(DirHandle, (struct ExAllData *) trueead, 2048, ED_SIZE, eac);
        if ((!more) && (IoErr() != ERROR_NO_MORE_ENTRIES))
        {   FreeDosObject(DOS_EXALLCONTROL, eac);
            eac = NULL;
            UnLock(DirHandle);
            DirHandle = NULL;
            rq("Can't examine path!");
        }
        if (eac->eac_Entries == 0)
        {   ; /* ExAll() failed normally with no entries */
            continue; /* more is USUALLY zero */
        }
        ead = trueead;

        do
        {   /* use ead here */
            if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file*/
            {   strcpy(tempstring, globalname);
                if (!AddPart(tempstring, ead->ed_Name, VLONGFIELD))
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Can't add filename to pathname!");
                }
                push(tempstring, topdir);
            } elif (ead->ed_Type == -3)
            {   size.bytes[topdir] += ead->ed_Size;
            }
            /* get next ead */
            ead = ead->ed_Next;
        } while(ead);
    } while(more);

    ead = trueead;
    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
    // don't call pop() yourself!
}

MODULE void pop(void)
{   ULONG breakval;

    while (size.stackentries && !stop)
    {   size.stackentries--;
        strcpy(globalname, FileNamePtr[size.stackentries + 1]);
        subdir(size.topdir[size.stackentries + 1]);
        breakval = checkbreak();
        if (breakval == 2)
            cleanexit(EXIT_SUCCESS);
        elif (breakval == 1)
            stop = TRUE;
}   }
MODULE void push(STRPTR name, ULONG index)
{   size.stackentries++;
    if (size.stackentries == LIMIT_DIRS)
        rq("Overflow!");
    if (size.stackentries > FileNamesAllocated)
    {   if (!(FileNamePtr[FileNamesAllocated + 1] = AllocMem(VLONGFIELD, MEMF_PUBLIC)))
            rq("Out of memory!");
        FileNamesAllocated++;
    }
    strcpy(FileNamePtr[size.stackentries], name);
    size.topdir[size.stackentries] = index;
}

AGLOBAL void size_loop(ULONG class, struct Gadget* addr, UWORD code, UWORD qual)
{   UWORD moveto;

    if (class == IDCMP_RAWKEY)
    {   if (qual & IEQUALIFIER_CONTROL)
        {   if (code == SCAN_UP)
            {   GT_SetGadgetAttrs
                (   LV81_Listview,
                    MainWindowPtr,
                    NULL,
                    GTLV_Top, 0,
                    TAG_DONE
                );
            } elif (code == SCAN_DOWN)
            {   if (size.entries > 15)          // this number must change
                {   moveto = size.entries - 15; // if the height of the
                } else moveto = 0;              // listview changes
                GT_SetGadgetAttrs
                (   LV81_Listview,
                    MainWindowPtr,
                    NULL,
                    GTLV_Top, moveto,
                    TAG_DONE
                );
    }   }   }
    elif (class == IDCMP_VANILLAKEY)
    {   code = toupper(code);
        if (code == ESCAPE)
        {   page = 0;
        } elif (code == 'A')
            parent();
        elif (code == 'R')
            root();
        elif (code == '.')
            pathasl();
        elif (code == 'P')
            pathstring();
        elif (code == 'L')
            fliplog(TRUE);
        elif (code == 'O')
            outputstring();
        elif (code == 'U')
            updatesize();
    } elif (class == IDCMP_GADGETUP)
    {   /* IDCMP_GADGETUP is sent by the string gadget
        when the user presses RETURN, ENTER, Help, Tab
        or Shift-Tab inside the string gadget. */

        if (addr == BU99_Right)
        {   page = 0;
        } elif (addr == BU99_Update)
        {   updatesize();
        } elif (addr == BU81_PathASL)
        {   pathasl();
        } elif (addr == ST81_Path)
        {   pathstring();
        } elif (addr == CB99_Log)
        {   fliplog(FALSE);
        } elif (addr == ST99_Output)
        {   outputstring();
        } elif (addr == BU99_OutputASL)
        {   outputasl();
        } elif (addr == BU81_Root)
        {   root();
        } elif (addr == BU81_Parent)
        {   parent();
        } elif (addr == LV81_Listview)
        {   /* code is the position within the list, starting from 0. */

            if (code + 1 <= size.entries && size.type[code + 1] == 2)
            {   if (!(AddPart(size.path, DirNamePtr[code + 1], VLONGFIELD + 1)))
                    rq("Can't add filename to pathname!");
                else updatesize();
}   }   }   }

AGLOBAL void size_exit(void)
{   // Only call this if the list gadget is detached or not present.

    if (SizeListPtr)
    {   FreeNameNodes(SizeListPtr);
        FreeMem(SizeListPtr, sizeof(struct List));
        SizeListPtr = NULL;
    }

    // safer to also clean up ead, etc.
    while(FileNamesAllocated)
    {   FreeMem(FileNamePtr[FileNamesAllocated], VLONGFIELD);
        FileNamePtr[FileNamesAllocated--] = NULL;
    }

    while( DirNamesAllocated)
    {   FreeMem( DirNamePtr[ DirNamesAllocated], VLONGFIELD);
         DirNamePtr[ DirNamesAllocated--] = NULL;
}   }

MODULE void parent(void)
{   BPTR DirHandle    = NULL,
         ParentHandle = NULL;

    if (!(DirHandle = (BPTR) Lock(size.path, ACCESS_READ)))
        rq("Can't lock directory!");
    if (!(ParentHandle = ParentDir(DirHandle)))
    {   /* It returned a NULL lock; ie. a lock on SYS:
        That is not what we want. */
        ParentHandle = DirHandle;
    }
    if (!NameFromLock(ParentHandle, size.path, VLONGFIELD))
        rq("Can't get name from lock!");
    UnLock(DirHandle);
    DirHandle = NULL;

    updatesize();
}

MODULE void root(void)
{   BPTR  DirHandle    = NULL,
          ParentHandle = NULL;
    ABOOL rootdone     = FALSE;

    if (!(DirHandle = (BPTR) Lock(size.path, ACCESS_READ)))
        rq("Can't lock directory!");
    do
    {   if (!(ParentHandle = ParentDir(DirHandle)))
        {   /* It returned a NULL lock; ie. a lock on SYS:
               That is not what we want. */
            ParentHandle = DirHandle;
            rootdone = TRUE;
        } else DirHandle = ParentHandle;
    } while (!rootdone);
    if (!NameFromLock(ParentHandle, size.path, VLONGFIELD))
        rq("Can't get name from lock!");
    UnLock(DirHandle);
    DirHandle = NULL;

    updatesize();
}

MODULE void pathasl(void)
{   strcpy(size.oldpath, size.path);
    strcpy(asldir, size.path);
    dirasl(); // path for ASL must be valid?
    strcpy(size.path, aslresult);
    updatesize();
}

MODULE void pathstring(void)
{   strcpy(size.oldpath, size.path);
    strcpy
    (   size.path,
        ((struct StringInfo *) ST81_Path->SpecialInfo)->Buffer
    );
    updatesize();
}

MODULE void comma(ULONG value)
{   ABOOL yeah = FALSE;

    strcpy(commastring, " ,   ,   ,   ");
    //                    1   5   9

    valuestring[ 0] = '0' + (value / 1000000000);
    value %= 1000000000;
    if (valuestring[ 0] != '0')
    {   commastring[0] = valuestring[0];
        yeah = TRUE;
    } else
    {   commastring[ 0] = ' ';
        commastring[ 1] = ' ';
    }

    valuestring[ 1] = '0' + (value /  100000000);
    value %=  100000000;
    if (yeah || valuestring[1] != '0')
    {   commastring[2] = valuestring[1];
        yeah = TRUE;
    } else commastring[2] = ' ';

    valuestring[ 2] = '0' + (value /   10000000);
    value %=   10000000;
    if (yeah || valuestring[2] != '0')
    {   commastring[3] = valuestring[2];
        yeah = TRUE;
    } else commastring[3] = ' ';

    valuestring[ 3] = '0' + (value /    1000000);
    value %=    1000000;
    if (yeah || valuestring[3] != '0')
    {   commastring[4] = valuestring[3];
        yeah = TRUE;
    } else
    {   commastring[4] = ' ';
        commastring[5] = ' ';
    }

    valuestring[ 4] = '0' + (value /     100000);
    value %=     100000;
    if (yeah || valuestring[4] != '0')
    {   commastring[6] = valuestring[4];
        yeah = TRUE;
    } else commastring[6] = ' ';

    valuestring[ 5] = '0' + (value /      10000);
    value %=      10000;
    if (yeah || valuestring[5] != '0')
    {   commastring[7] = valuestring[5];
        yeah = TRUE;
    } else commastring[7] = ' ';

    valuestring[ 6] = '0' + (value /       1000);
    value %=       1000;
    if (yeah || valuestring[6] != '0')
    {   commastring[8] = valuestring[6];
        yeah = TRUE;
    } else
    {   commastring[8] = ' ';
        commastring[9] = ' ';
    }

    valuestring[ 7] = '0' + (value /        100);
    value %=        100;
    if (yeah || valuestring[7] != '0')
    {   commastring[10] = valuestring[7];
        yeah = TRUE;
    } else commastring[10] = ' ';

    valuestring[ 8] = '0' + (value /         10);
    value %=         10;
    if (yeah || valuestring[8] != '0')
    {   commastring[11] = valuestring[8];
        yeah = TRUE;
    } else commastring[11] = ' ';

    valuestring[ 9] = '0' +  value              ;
    commastring[12] = valuestring[9];
}
