#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/gadgetclass.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <dos/dosextens.h>
#include <dos/exall.h>
#include <gadgets/listbrowser.h>
#include <pragmas/listbrowser_pragmas.h>

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

AGLOBAL struct List          FilesList;

MODULE void files_work(void);
MODULE ABOOL __inline quickcomp(STRPTR first, STRPTR second);
MODULE void dobuffer(ABOOL item, ULONG whichpen, ABOOL force);
MODULE void files_operation(void);
MODULE void updatefiles(void);

IMPORT ABOOL                pal;
IMPORT TEXT                 IOBuffer[LONGESTFIELD + 1],
                            aslresult[MEDFIELD + 1];
IMPORT UBYTE               *FileNamePtr[16768],
                           *DirNamePtr[2048];
IMPORT SBYTE                page;
IMPORT ULONG                DirNamesAllocated,
                            FileNamesAllocated,
                            wbval;
IMPORT struct SharedStruct  shared;
IMPORT struct Screen*       ScreenPtr;
IMPORT struct Library*      ListBrowserBase;
IMPORT struct ExAllData*    ead;

MODULE ABOOL                list_has_nodes = FALSE;
MODULE struct
{   ABOOL show[8];
    TEXT  showlabel[8][17 + 1];
    ULONG entries, totalbytesval, freeval;
    TEXT  totalbytesstring[11], freestring[11],
          oldpath[VLONGFIELD + 1], path[VLONGFIELD + 1];
    ULONG bytes[2048], topdir[2048], dirsize[2048], filesize[2048];
    LONG  type[2048];
} files =
{   TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE,
    "V_ery important",
    "_Important",
    "_Semi-important",
    "U_nimportant",
    "O_bsolete",
    "_3rd-party",
    "Missin_g",
    "Missing o_ptional",
    0, 0, 0,
    "", "", "SYS:", "SYS:"
};
MODULE  BPTR           TheFileHandle       = NULL;
MODULE  UWORD          osversion;
MODULE  TEXT           stringholder1[VLONGFIELD + 1],
                       stringholder2[VLONGFIELD + 1],
                       stringholder3[VLONGFIELD + 1];
MODULE  struct Gadget *MX101_OSVersion     = NULL,
                      *TE101_OSVersionText = NULL,
                      *TE101_ShowText      = NULL,
                      *CB101_Show[8] =
                      {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
MODULE  STRPTR OSOptions[5] =
{   (STRPTR) "OS3.1",
    (STRPTR) "OS3.5",
    (STRPTR) "OS3.5 & Boing Bag(s)",
    (STRPTR) "OS3.9",
    NULL
};
MODULE struct
{   ULONG red, green, blue, pennumber;
} penn[7] =
{     {0xFFFFFFFF, 0x22222222, 0x22222222, -1}, // red
      {0xFFFFFFFF, 0x88888888, 0x00000000, -1}, // orange
      {0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, -1}, // yellow
      {0x55555555, 0xFFFFFFFF, 0x55555555, -1}, // green
      {0x55555555, 0x55555555, 0xFFFFFFFF, -1}, // blue (obsolete)
      {0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, -1}, // cyan (3rd-party)
      {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, -1}  // white (missing)
};

// from rp.c
IMPORT SBYTE               page;
IMPORT struct NewGadget*   Gadget;
IMPORT struct Gadget      *BU99_Right,
                          *ST99_Output,
                          *BU99_OutputASL,
                          *BU99_Update,
                          *CB99_Log,
                          *PrevGadPtr;
IMPORT struct Window*      MainWindowPtr;
IMPORT TEXT                weekdaystring[10],
                           datestring[10],
                           timestring[9];

AGLOBAL void files1(void)
{   ULONG i;

    navigate();

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

    // OS version
    setgadget(72, 44, 0, 0, NULL, PLACETEXT_RIGHT);
    MX101_OSVersion = PrevGadPtr = (struct Gadget *) CreateGadget
    (   MX_KIND,
        PrevGadPtr,
        &Gadget,
        GTMX_Labels, &OSOptions,
        GTMX_Active, osversion,
        GT_Underscore, '_',
        TAG_DONE
    );
    setgadget(66, 43, 128, 0, "AmigaOS _Version:", PLACETEXT_ABOVE);
    TE101_OSVersionText = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    // show
    setgadget(66, 99, 40, 0, "_Show:", PLACETEXT_ABOVE);
    TE101_ShowText = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    for (i = 0; i <= 7; i++)
    {   setgadget(72, 100 + (i * 12), 0, 0, files.showlabel[i], PLACETEXT_RIGHT);
        CB101_Show[i] = PrevGadPtr = (struct Gadget *) CreateGadget
        (   CHECKBOX_KIND,
            PrevGadPtr,
            &Gadget,
            GTCB_Checked, files.show[i],
            GT_Underscore, '_',
            TAG_DONE
        );
    }

    if (wbval >= 44)
        shared.log = FALSE;
    else shared.log = TRUE;
    // 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,
        GT_Underscore, '_',
        TAG_DONE
    );

    newwindow
    (   640, FILES1HEIGHT,
        "Report+: System File Report",
        BUTTONIDCMP | STRINGIDCMP | MXIDCMP
    );
    if (wbval < 44)
    {   GT_SetGadgetAttrs
        (   CB99_Log,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }

    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
        );
    }
    SetAPen(MainWindowPtr->RPort, BLACK);
    Move(MainWindowPtr->RPort, 512, 30);
    Draw(MainWindowPtr->RPort, 512 + (8 * 5), 30);
    loop();
    strcpy
    (   shared.output,
        ((struct StringInfo *) ST99_Output->SpecialInfo)->Buffer
    );
    closewindow();
}

MODULE void updatefiles(void)
{   ULONG i;

    /* Gadgets are: BU99_Right ('menu')
                    BU99_Update
                    CB99_Log, ST99_Output, BU99_OutputASL
                    MX101_OSVersion
                    CB101_Show[]
    All are ghosted during operation. Then their ghosting status returns
    to normal (not necessarily unghosted). */

    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB99_Log,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   ST99_Output,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_OutputASL,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   MX101_OSVersion,
        MainWindowPtr, NULL,
        GA_Disabled, TRUE,
        TAG_DONE
    );
    for (i = 0; i <= 7; i++)
    {   GT_SetGadgetAttrs
        (   CB101_Show[i],
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );
    }

    SetAPen(MainWindowPtr->RPort, BLACK);
    SetDrMd(MainWindowPtr->RPort, JAM2);
    Move
    (   MainWindowPtr->RPort,
        512,
        28
    );
    Text(MainWindowPtr->RPort, "     ", 5);
    Move
    (   MainWindowPtr->RPort,
        512,
        28
    );
    Text(MainWindowPtr->RPort, ".", 1); // 1st dot

    // this code (effectively) pushes "SYS:" onto the stack, to start the
    // process.
    if (!(DirNamePtr[1] = AllocMem(5, MEMF_PUBLIC)))
        rq("Out of memory!");
    DirNamesAllocated = 1;
    files.dirsize[1] = 5;
    strcpy(DirNamePtr[1], "");

    // pop all the directories from the work stack, and send them one
    // at a time to files_work() for processing.
    while (DirNamesAllocated)
    {   strcpy(stringholder1, "SYS:"); // I suspect this may only need doing once
        strcat(stringholder1, DirNamePtr[DirNamesAllocated]);
        FreeMem(DirNamePtr[DirNamesAllocated], files.dirsize[DirNamesAllocated]);
        DirNamePtr[DirNamesAllocated] = NULL;
        DirNamesAllocated--;
        files_work();
    }

    Text(MainWindowPtr->RPort, ".", 1); // 2nd dot
    NewList(&FilesList);
    files_operation();
    while(FileNamesAllocated)
    {   FreeMem(FileNamePtr[FileNamesAllocated], files.filesize[FileNamesAllocated]);
        FileNamePtr[FileNamesAllocated--] = NULL;
    }

    Text(MainWindowPtr->RPort, ".", 1); // 4th dot
    if (wbval >= 44)
    {   /* pens go thusly:
        0 red
        1 orange
        2 yellow
        3 green
        4 blue
        5 cyan
        6 white */

        GT_SetGadgetAttrs
        (   BU99_Right,
            MainWindowPtr, NULL,
            GA_Disabled, TRUE,
            TAG_DONE
        );

        listbrowse(); // this routine is synchronous, not asynchronous
        clearlblist();
        NewList(&FilesList); // to avoid accidental reuse
    }

    GT_SetGadgetAttrs
    (   BU99_Right,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   BU99_Update,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    if (wbval >= 44)
    {   GT_SetGadgetAttrs
        (   CB99_Log,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }
    if (shared.log)
    {   GT_SetGadgetAttrs
        (   ST99_Output,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   BU99_OutputASL,
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }
    GT_SetGadgetAttrs
    (   MX101_OSVersion,
        MainWindowPtr, NULL,
        GA_Disabled, FALSE,
        TAG_DONE
    );
    for (i = 0; i <= 7; i++)
    {   GT_SetGadgetAttrs
        (   CB101_Show[i],
            MainWindowPtr, NULL,
            GA_Disabled, FALSE,
            TAG_DONE
        );
    }

    Text(MainWindowPtr->RPort, ".", 1); // 5th dot
    Move
    (   MainWindowPtr->RPort,
        512,
        28
    );
}

MODULE void files_operation(void)
{   ABOOL matched, infomatched;
    ULONG i, j, length, whichpen;
    TEXT  tempname[VLONGFIELD + 1];
    UBYTE theversion;

    /* Everything is already allocated. Don't deallocate in this routine.

    FileNamesAllocated is the actual number of entries. The array
    elements are from 1 to FileNamesAllocated. */

    Text(MainWindowPtr->RPort, ".", 1); // 3rd dot
    if (shared.log)
    {   if (!(TheFileHandle = (BPTR) Open(shared.output, MODE_READWRITE)))
            rq("Can't open file for appending!");
        Seek(TheFileHandle, 0, OFFSET_END);
    }

    // Now we have the list of files

    getdate();
    strcpy(IOBuffer, "AmigaOS 3.");
    if (osversion == 0)
    {   theversion = 31;
        strcat(IOBuffer, "1");
    } elif (osversion == 1)
    {   theversion = 35;
        strcat(IOBuffer, "5");
    } elif (osversion == 2)
    {   theversion = 36;
        strcat(IOBuffer, "5 + BB1/2");
    } else
    {   // assert(osversion == 3);
        theversion = 39;
        strcat(IOBuffer, "9");
    }
    strcat(IOBuffer, " system files at ");
    strcat(IOBuffer, timestring);
    strcat(IOBuffer, " on ");
    strcat(IOBuffer, weekdaystring);
    strcat(IOBuffer, " ");
    strcat(IOBuffer, datestring);
    strcat(IOBuffer, ":\n"); // another \n is inserted automatically by dobuffer()
    dobuffer(FALSE, 0, FALSE);

    for (i = 0; i <= FILES; i++)
        os[i].matched = os[i].infomatched = FALSE;

    for (i = 1; i <= FileNamesAllocated; i++)
    {   matched = infomatched = FALSE;
        length = strlen(FileNamePtr[i]);
        for (j = 0; j <= FILES; j++)
        {   if (os[j].version > theversion)
                ; // don't check
            else
            {   if (!matched && quickcomp((STRPTR) &(FileNamePtr[i][4]), os[j].pathname))
                {   // we have a match
                    if (theversion == 39 && (os[j].flags & NOT39))
                    {   whichpen = 12;
                        strcpy(IOBuffer, os[j].pathname);
                        matched = TRUE;
                        os[j].matched = TRUE;
                        dobuffer(TRUE, whichpen, TRUE);
                    } else
                    {   if (os[j].flags & CODE_RED)
                            whichpen = 8;
                        elif (os[j].flags & CODE_ORANGE)
                            whichpen = 9;
                        elif (os[j].flags & CODE_YELLOW)
                            whichpen = 10;
                        elif (os[j].flags & CODE_GREEN)
                            whichpen = 11;
                        else
                        {   // assert (os[j].flags & CODE_BLUE);
                            whichpen = 12;
                        }
                        strcpy(IOBuffer, os[j].pathname);
                        matched = TRUE;
                        os[j].matched = TRUE;
                        dobuffer(TRUE, whichpen, FALSE);
                    }
                    break; // pointless to keep looping
                } elif (!infomatched && (os[j].flags & INFO) && (*(FileNamePtr[i] + length - 5) == '.')) // if seems likely to end in ".info"
                {   strcpy(tempname, "SYS:");
                    strcat(tempname, os[j].pathname);
                    strcat(tempname, ".info");
                    if (quickcomp((STRPTR) &(FileNamePtr[i][4]), &(tempname[4])))
                    {   // we have a match
                        infomatched = TRUE;
                        os[j].infomatched = TRUE;
                        strcpy(IOBuffer, &tempname[4]);
                        if (theversion == 39 && (os[j].flags & NOT39))
                            dobuffer(TRUE, 12, TRUE);
                        else dobuffer(TRUE, 11, FALSE); // automatic .info files are always green
                        break; // pointless to keep looping
        }   }   }   }
        if (!matched && !infomatched) // third-party (cyan)
        {   strcpy(IOBuffer, &(FileNamePtr[i][4]));
            dobuffer(TRUE, 13, FALSE);
    }   }

    // now do missings
    for (i = 0; i <= FILES; i++)
    {   if (!os[i].matched && !(os[i].flags & CODE_BLUE))
            if ((files.show[6] && !(os[i].flags & OPTIONAL))
             || (files.show[7] && (os[i].flags & OPTIONAL)))
            {   if (os[i].version > theversion || (theversion == 39 && os[i].flags & NOT39))
                    ; // don't print
                else
                {   strcpy(IOBuffer, os[i].pathname); // white (missing)
                    dobuffer(TRUE, 14, FALSE);
            }   }
        if (os[i].flags & INFO)
        {   if (!os[i].infomatched && !(os[i].flags & CODE_BLUE))
            {   if
                (   (files.show[6] && !(os[i].flags & OPTIONAL))
                 || (files.show[7] && (os[i].flags & OPTIONAL))
                )
                {   if (os[i].version > theversion || (theversion == 39 && os[i].flags & NOT39))
                    {   ; // don't print
                    } else
                    {   strcpy(IOBuffer, os[i].pathname); // white (missing)
                        strcat(IOBuffer, ".info");
                        dobuffer(TRUE, 14, FALSE);
    }   }   }   }   }

    if (shared.log && TheFileHandle)
    {   Close(TheFileHandle); // Close() doesn't return an error code
        TheFileHandle = NULL;
}   }

/* FileNamePtr[]: each of these is a pointer to the allocated memory
area holding the full pathname of that file found.
FileNamesAllocated = number of elements of FileNamePtr[]. Or, another way
to think of it, the number of files on the SYS: partition. Element 0 of
the array is not used. */

MODULE void files_work(void)
{   BOOL                 more; // BOOL, not ABOOL
    BPTR                 DirHandle = NULL;
    struct ExAllControl* eac       = NULL;
    struct ExAllData*    trueead   = ead;

    /* Service routine. Each call of this routine
    handles one directory from the work stack. This routine is the one
    that actually makes the DOS calls. It pushes any subdirectories
    found onto the stack.

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

    if (!(DirHandle = (BPTR) Lock(stringholder1, 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!"); /* ExAll() failed abnormally */
        }
        if (eac->eac_Entries == 0)
        {   ; /* ExAll() failed normally with no entries */
            continue; /* more is USUALLY zero */
        }
        ead = trueead; // the two eads are about to split

        do
        {   /* use ead here */

            strcpy(stringholder2, stringholder1);
            if (!AddPart(stringholder2, ead->ed_Name, VLONGFIELD))
            {   FreeDosObject(DOS_EXALLCONTROL, eac);
                eac = NULL;
                UnLock(DirHandle);
                DirHandle = NULL;
                rq("Can't add filename to pathname!");
                // crashes when exiting from here; why?
            }
            strcpy(stringholder3, &stringholder2[4]);
            if (ead->ed_Type == 2) /* +2 is dir, +3 is softlink, -3 is file */
            {   // Push the directory onto the stack.

                if (DirNamesAllocated == 2047)
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Overflow!");
                }
                if (!(DirNamePtr[DirNamesAllocated + 1] = AllocMem(strlen(stringholder2) + 1, MEMF_PUBLIC)))
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Out of memory!");
                }
                DirNamesAllocated++;
                files.dirsize[DirNamesAllocated] = strlen(stringholder2) + 1;
                strcpy(DirNamePtr[DirNamesAllocated], &(stringholder2[4]));
            } elif (ead->ed_Type == -3)
            {   if (FileNamesAllocated == 16767)
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Overflow!");
                }
                if (!(FileNamePtr[FileNamesAllocated + 1] = AllocMem(strlen(stringholder2) + 1, MEMF_PUBLIC)))
                {   FreeDosObject(DOS_EXALLCONTROL, eac);
                    eac = NULL;
                    UnLock(DirHandle);
                    DirHandle = NULL;
                    rq("Out of memory!");
                }
                FileNamesAllocated++;
                files.filesize[FileNamesAllocated] = strlen(stringholder2) + 1;
                strcpy(FileNamePtr[FileNamesAllocated], stringholder2);
            }
            /* get next ead */
            ead = ead->ed_Next;
        } while(ead);
        // the way that works is that ead is fooled with. So by the end
        // of the routine ead is wrong, and can't be reused.
    } while(more);

    ead = trueead; // the two eads are reunited as one :-)
    FreeDosObject(DOS_EXALLCONTROL, eac);
    eac = NULL;
    UnLock(DirHandle);
    DirHandle = NULL;
}

MODULE ABOOL __inline quickcomp(STRPTR first, STRPTR second)
{   ULONG i = 0;

    while (*(first + i))
    {   if (*(first + i)      != *(second + i)
        &&  *(first + i)      != *(second + i) + 32
        &&  *(first + i) + 32 != *(second + i)
        &&  *(first + i) + 32 != *(second + i) + 32
        )
            return FALSE;
        i++;
    }
    return TRUE;
}

MODULE void dobuffer(ABOOL item, ULONG whichpen, ABOOL force)
{   struct Node* ListBrowserNodePtr;
    TEXT         codestring[VLONGFIELD + 1];

    if (item && wbval >= 44 && (files.show[whichpen - 8] == TRUE || force))
    {   if (pal)
        {   if (!(ListBrowserNodePtr = AllocListBrowserNode
            (   1,              // columns,
                LBNA_Column,    0,
                LBNA_Flags,     LBFLG_CUSTOMPENS,
                LBNCA_FGPen,    BLACK,
                LBNCA_BGPen,    whichpen,
                LBNCA_CopyText, TRUE,
                LBNCA_Text,     IOBuffer,
                TAG_END))
            )
            {   rq("Can't create ReAction listbrowser.gadget node(s)!");
        }   }
        else
        {   if (!(ListBrowserNodePtr = AllocListBrowserNode
            (   1,              // columns,
                LBNA_Column,    0,
                LBNA_Flags,     LBFLG_CUSTOMPENS,
                LBNCA_FGPen,    BLACK,
                LBNCA_BGPen,    penn[whichpen - 8].pennumber,
                LBNCA_CopyText, TRUE,
                LBNCA_Text,     IOBuffer,
                TAG_END))
            )
            {   rq("Can't create ReAction listbrowser.gadget node(s)!");
        }   }
        AddTail(&FilesList, ListBrowserNodePtr); // AddTail() has no return code
        list_has_nodes = TRUE;
    }
    if (shared.log)
    {   if (item)
        {   switch(whichpen - 8)
            {
            case 0:
                strcpy(codestring, "! "); // red
            break;
            case 1:
                strcpy(codestring, "@ "); // orange
            break;
            case 2:
                strcpy(codestring, "# "); // yellow
            break;
            case 3:
                strcpy(codestring, "$ "); // green
            break;
            case 4:
                strcpy(codestring, "% "); // blue (obsolete)
            break;
            case 5:
                strcpy(codestring, "& "); // cyan (3rd-party)
            break;
            case 6:
                strcpy(codestring, "- "); // white (missing)
            break;
            default:
                // assert(0);
            break;
            }
            strcat(codestring, IOBuffer);
        } else strcpy(codestring, IOBuffer);
        strcat(codestring, "\n");
        if (Write(TheFileHandle, codestring, strlen(codestring)) == -1)
        {   rq("Can't append to file!");
}   }   }

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

    // page is ALWAYS 101 in this routine, so it is not checked for.

    if (class == IDCMP_VANILLAKEY)
    {   code = toupper(code);
        if (code == 'V')
        {   if (!(qual & IEQUALIFIER_LSHIFT) && !(qual & IEQUALIFIER_RSHIFT))
            {   if (osversion < 3)
                    osversion++;
                else osversion = 0;
            } else
            {   if (osversion > 0)
                    osversion--;
                else osversion = 3;
            }
            GT_SetGadgetAttrs
            (   MX101_OSVersion,
                MainWindowPtr,
                NULL,
                GTMX_Active, osversion,
                TAG_DONE
            );
        } elif (code == 'L')
        {   fliplog(TRUE);
        } elif (code == 'U')
        {   updatefiles();
        } elif (code == 'E')
        {   if (CB101_Show[0]->Flags & GFLG_SELECTED)
                files.show[0] = FALSE;
            else files.show[0] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[0],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[0],
                TAG_DONE
            );
        } elif (code == 'I')
        {   if (CB101_Show[1]->Flags & GFLG_SELECTED)
                files.show[1] = FALSE;
            else files.show[1] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[1],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[1],
                TAG_DONE
            );
        } elif (code == 'S')
        {   if (CB101_Show[2]->Flags & GFLG_SELECTED)
                files.show[2] = FALSE;
            else files.show[2] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[2],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[2],
                TAG_DONE
            );
        } elif (code == 'N')
        {   if (CB101_Show[3]->Flags & GFLG_SELECTED)
                files.show[3] = FALSE;
            else files.show[3] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[3],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[3],
                TAG_DONE
            );
        } elif (code == 'B')
        {   if (CB101_Show[4]->Flags & GFLG_SELECTED)
                files.show[4] = FALSE;
            else files.show[4] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[4],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[4],
                TAG_DONE
            );
        } elif (code == '3')
        {   if (CB101_Show[5]->Flags & GFLG_SELECTED)
                files.show[5] = FALSE;
            else files.show[5] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[5],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[5],
                TAG_DONE
            );
        } elif (code == 'G')
        {   if (CB101_Show[6]->Flags & GFLG_SELECTED)
                files.show[6] = FALSE;
            else files.show[6] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[6],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[6],
                TAG_DONE
            );
        } elif (code == 'P')
        {   if (CB101_Show[7]->Flags & GFLG_SELECTED)
                files.show[7] = FALSE;
            else files.show[7] = TRUE;
            GT_SetGadgetAttrs
            (   CB101_Show[7],
                MainWindowPtr, NULL,
                GTCB_Checked, files.show[7],
                TAG_DONE
            );
        } elif (code == 'O')
        {   ActivateGadget(ST99_Output, MainWindowPtr, NULL);
        } elif (code == '.')
        {   asl();
            GT_SetGadgetAttrs
            (   ST99_Output,
                MainWindowPtr,
                NULL,
                GTST_String, aslresult,
                TAG_DONE
            );
        } elif (code == ESCAPE)
        {   page = 0;
    }   }
    elif (class == IDCMP_GADGETDOWN)
    {   // radio gadgets
        if (addr == MX101_OSVersion)
            osversion = code;
    } 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)
        {   updatefiles();
        } elif (addr == CB99_Log)
        {   fliplog(FALSE);
        } elif (addr == ST99_Output)
        {   outputstring();
        } elif (addr == BU99_OutputASL)
        {   outputasl();
        } else
        {   for (i = 0; i <= 6; i++)
            {   if (addr == CB101_Show[i])
                {   if (CB101_Show[i]->Flags & GFLG_SELECTED)
                    {   files.show[i] = TRUE;
                    } else files.show[i] = FALSE;
                    GT_SetGadgetAttrs
                    (   CB101_Show[i],
                        MainWindowPtr, NULL,
                        GTCB_Checked, files.show[i],
                        TAG_DONE
                    );
                    break;
}   }   }   }   }

AGLOBAL void files_init(void)
{   ULONG i;

    if (wbval <= 43)
        osversion = 0; // OS3.1
    elif (wbval == 44)
        osversion = 2; // OS3.5 + BB1/2
    else osversion = 3; // OS3.9

    if (!pal)
    {   if (!(ScreenPtr = LockPubScreen(NULL)))
            rq("Can't lock default public screen!");
        for (i = 0; i <= 6; i++)
        {   penn[i].pennumber = FindColor
            (   ScreenPtr->ViewPort.ColorMap,
                penn[i].red,
                penn[i].green,
                penn[i].blue,
                256
            );
        }
        UnlockPubScreen(NULL, ScreenPtr);
        for (i = 0; i <= 6; i++)
        {   penn[i].pennumber = FindColor
            (   ScreenPtr->ViewPort.ColorMap,
                penn[i].red,
                penn[i].green,
                penn[i].blue,
                256
            );
}   }   }

AGLOBAL void files_exit(void)
{   if (shared.log && TheFileHandle)
    {   Close(TheFileHandle); // Close() doesn't return an error code
        TheFileHandle = NULL;
    }
    while(FileNamesAllocated)
    {   FreeMem(FileNamePtr[FileNamesAllocated], files.filesize[FileNamesAllocated]);
        FileNamePtr[FileNamesAllocated] = NULL;
        FileNamesAllocated--;
    }
    while(DirNamesAllocated)
    {   FreeMem(DirNamePtr[DirNamesAllocated], files.dirsize[FileNamesAllocated]);
        DirNamePtr[DirNamesAllocated] = NULL;
        DirNamesAllocated--;
}   }

// Function to free an Exec List of ListBrowser nodes.
AGLOBAL void clearlblist(void)
{   /* Requirements:
        listbrowser class must be already open
        list must not be currently attached to a listbrowser
        non-empty list. */

    struct Node *NodePtr, *NextNodePtr;

    if (list_has_nodes)
    {   NodePtr = FilesList.lh_Head;
        while (NextNodePtr = NodePtr->ln_Succ)
        {   FreeListBrowserNode(NodePtr);
            NodePtr = NextNodePtr;
    }   }
    list_has_nodes = FALSE;
}
