/*
 *              SUBWIN.C                                                                                        vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *              This module handles all the windows other than the main window.
 *              That is, the functions window, settings window and format
 *              window.
 */             

#include "system.h"
#include "snoopdos.h"
#include "gui.h"

#if 0
#define DB(s)   KPrintF(s)
#else
#define DB(s)
#endif

/*
 *              These are used by all the requesters to decide what options to do
 *              when leaving a requester (make current settings permanent or
 *              restore the old settings)
 */
#define SEL_NONE                0               /* User didn't select an option yet             */
#define SEL_USE                 1               /* User selected USE in a requester             */
#define SEL_CANCEL              2               /* User selected CANCEL in a requester  */

/*
 *              These control which items are displayed in the System and DOS
 *              columns in the function window.
 */
#define COL_SELECTED    0               /* Column shows selected items                  */
#define COL_ALL                 1               /* Column shows all items                               */
#define COL_NONE                2               /* Column shows no items                                */

#define SETBUTTON_FILE  0               /* Little image of a file folder                */
#define SETBUTTON_FONT  1               /* Little image of a font folder                */
#define SETBUTTON_MAX   2               /* Anything above this is a string ID   */

/*
 *              These two are imported from SNOOPDOS.C
 */
extern char     SnoopDosTitle[];

/*
 *              Variables for the format editor
 */
int FBoxA_Left;                         /* X pos of left box in format window   */
int FBoxA_Top;                          /* Y pos of left box in format window   */
int FBoxA_Width;                        /* Width of left box in format window   */
int FBoxA_Height;                       /* Height of left box in format window  */
int FBoxA_NumChars;                     /* No. of characters across left box    */
int FBoxA_CurLines;                     /* No. of lines currently in left box   */

int FBoxB_Left;                         /* X pos of right box in format window  */
int FBoxB_Top;                          /* Y pos of right box in format window  */
int FBoxB_Width;                        /* Hidth of right box in format window  */
int FBoxB_Height;                       /* Weight of right box in format window */
int FBoxB_NumChars;                     /* No. of characters across right box   */
int FBoxB_CurLines;                     /* No. of lines currently in right box  */

int FBoxSpacing;                        /* Distance between box text baselines  */
int FBoxSelected;                       /* Line number of right box selection   */
                                                        /* (or FBOX_NOSELECT / FBOX_SELECTLEFT) */

int FBoxDeltaX;                         /* Offset from mouse to corner of drag  */
int FBoxDeltaY;                         /* Offset from mouse to corner of drag  */
int FBoxDragSelect;                     /* Currently selected drag drop area    */
int FormatCurLMB;                       /* Current state of left mouse button   */
int FormatMovingField;          /* If true, currently dragging field    */
int FormatDragBox;                      /* Box we're dragging from                              */
int FormatDragLine;                     /* Line we're dragging from                             */

char FBoxA_FormString[20];      /* sprintf format string for left box   */
char FBoxB_FormString[20];      /* sprintf format string for right box  */

char FormatSaveFormat[MAX_FORM_LEN];    /* Save area for format ed      */

/*
 *              These next two arrays are indices into the FieldTypes[] array
 *              of format initialisers.
 */
typedef struct {
        int     type;                                                   /* Type of this entry           */
        int width;                                                      /* Width of this entry          */
} EditEvent;
        
extern FieldInit FieldTypes[];                  /* Pulled in from BUFFER.C      */

EditEvent CurrentFields[EF_COUNT+1];    /* Fields in current format     */
EditEvent AvailableFields[EF_COUNT+1];  /* Fields still available       */

Settings                        SavedFuncSets;          /* Saved function settings      */
Settings                        SavedSetupSets;         /* Saved setup settings         */

struct Gadget           *SetGadList;            /* Settings window gadgets      */
struct Gadget           *FuncGadList;           /* Function window gadgets      */
struct Gadget           *FormGadList;           /* Format window gadgets        */
struct Image            *FileButtonImage;       /* Image of a file button       */
struct Image            *FontButtonImage;       /* Image of a font button       */

struct TextFont         *FormBufFont;           /* Format window listview       */
struct TextFont         *FormGadFont;           /* Format window gadgets        */

/*
 *              We don't usually free the Function window gadgets when we close
 *              the window (since it takes a long time to generate them). However,
 *              if we change window font, then we need a way to be able to change
 *              them. This variable shows you how.
 */
int             FuncSystemCol;          /* Mode of system column (all/none/sel) */
int             FuncDOSCol;                     /* Mode of DOS column (all/none/select) */

UBYTE   SavedCols[GID_NUMFUNCSETS];     /* Currently selected settings  */
UBYTE   TempCols[GID_NUMFUNCSETS];      /* Currently selected settings  */

UBYTE   SetKeyboard[KB_SHORTCUT_SIZE];  /* For keyboard equivs          */
UBYTE   FuncKeyboard[KB_SHORTCUT_SIZE]; /* For keyboard equivs          */
UBYTE   FormKeyboard[KB_SHORTCUT_SIZE]; /* For keyboard equivs          */

/*
 *              Now our font structures.
 *
 *              We do everything in terms of FontAttr structures, and whenever a
 *              window needs a font, it OpenFont's it itself. This has several
 *              advantages: it allows each window to dynamically select the
 *              best-fitting font, and it also ensures no problems if we have a
 *              window open with one font and the user than changes the font
 *              to something else -- the first window will still have a valid
 *              lock on the old font.
 */
extern struct TextAttr TopazFontAttr;
extern struct TextAttr SystemFontAttr;
extern struct TextAttr WindowFontAttr;
extern struct TextAttr BufferFontAttr;

extern struct {
        struct TextAttr *gadgetfa;
        struct TextAttr *bufferfa;
} MainWindowFontList[];                         /* Imported from MAINWIN.C */

struct TextAttr *SubWindowFontList[] = {
        &WindowFontAttr,
        &SystemFontAttr,
        &TopazFontAttr,
        NULL
};

/*
 *              Next, all our gadget structures -- these are used to build the
 *              font-sensitive gadget windows at runtime.
 */
struct FuncGadgets {
        ULONG   gadgid;                 /* Gadget ID                                    */
        UWORD   stringid;               /* Name of checkmark gadget     */
        UBYTE   col;                    /* Column (0, 1 or 2)                   */
        UBYTE   row;                    /* Row (0 -- 13)                                */
};

struct FuncGadgets FuncGadgs[] = {
        /*
         *              General controls
         */
        GID_ONLYFAILS,          MSG_SHOWFAILS_GAD,              0, 0,
        GID_SHOWCLINUM,         MSG_SHOWCLI_GAD,                0, 1,
        GID_SHOWPATHS,          MSG_SHOWFULLPATHS_GAD,  0, 2,
        GID_USEDEVNAMES,        MSG_USEDEVNAMES_GAD,    0, 3,
        GID_MONPACKETS,         MSG_MONPACKETS_GAD,             0, 4,
        GID_MONALLPACKETS,      MSG_MONALLPACKETS_GAD,  0, 5,
        GID_MONROMCALLS,        MSG_MONROMCALLS_GAD,    0, 6,
        GID_IGNOREWB,           MSG_IGNOREWB_GAD,               0, 7,
        /*
         *              System functions
         */
        GID_FINDPORT,           MSG_FINDPORT_GAD,               1, 0,
        GID_FINDRESIDENT,       MSG_FINDRESIDENT_GAD,   1, 1,
        GID_FINDSEMAPHORE,      MSG_FINDSEMAPHORE_GAD,  1, 2,
        GID_FINDTASK,           MSG_FINDTASK_GAD,               1, 3,
        GID_LOCKSCREEN,         MSG_LOCKSCREEN_GAD,             1, 4,
        GID_OPENDEVICE,         MSG_OPENDEVICE_GAD,             1, 5,
        GID_OPENFONT,           MSG_OPENFONT_GAD,               1, 6,
        GID_OPENLIBRARY,        MSG_OPENLIBRARY_GAD,    1, 7,
        GID_OPENRESOURCE,       MSG_OPENRESOURCE_GAD,   1, 8,
        GID_READTOOLTYPES,      MSG_READTOOLTYPES_GAD,  1, 9,
        GID_SENDREXX,           MSG_SENDREXX_GAD,               1, 10,

        /*
         *              AmigaDOS Functions
         */
        GID_CHANGEDIR,          MSG_CHANGEDIR_GAD,              2, 0,
        GID_DELETE,                     MSG_DELETE_GAD,                 2, 1,
        GID_EXECUTE,            MSG_EXECUTE_GAD,                2, 2,
        GID_GETVAR,                     MSG_GETVAR_GAD,                 2, 3,
        GID_LOADSEG,            MSG_LOADSEG_GAD,                2, 4,
        GID_LOCKFILE,           MSG_LOCKFILE_GAD,               2, 5,
        GID_MAKEDIR,            MSG_MAKEDIR_GAD,                2, 6,
        GID_MAKELINK,           MSG_MAKELINK_GAD,               2, 7,
        GID_OPENFILE,           MSG_OPENFILE_GAD,               2, 8,
        GID_RENAME,                     MSG_RENAME_GAD,                 2, 9,
        GID_RUNCOMMAND,         MSG_RUNCOMMAND_GAD,             2, 10,
        GID_SETVAR,                     MSG_SETVAR_GAD,                 2, 11,
        GID_SYSTEM,                     MSG_SYSTEM_GAD,                 2, 12,

        0,                                      0,                                              0, 0
};

/*
 *              This structure defines the gadgets in the settings window. There
 *              are three basic types of gadgets supported: cycle, string and
 *              display. For cycle gadgets, data1 is the index of the current
 *              selection, data2 is a 0-terminated array of message IDs that
 *              will hold the possible ListView choices, and data3 is a pointer
 *              to an array of string pointers of the same size (including
 *              terminating NULL) that will be filled in by the code with actual
 *              pointers to those strings.
 *              
 *              For string and display gadgets, data2 is a pointer to the string
 *              to be displayed inside the gadget and data1/data3 are unused.
 *              However, if promptid is specified, then data1 is the message
 *              number of the prompt to be used in the button instead of defaulting
 *              to a question mark (the width is automatically adjusted).
 */             
struct SetGadgets {
        UWORD   gadgid;                 /* Gadget ID                                                            */
        UWORD   stringid;               /* ID of label name                                     */
        ULONG   gtype;                  /* Gadget type (cycle, string, or display)      */
        UWORD   promptid;               /* If non-zero, ID of optional "?" button       */
        ULONG   data1;                  /* See above                                                            */
        void    *data2;                 /* See above                                                            */
        void    *data3;                 /* See above                                                            */
        UBYTE   col;                    /* Start col (0 or 1)                                           */
        UBYTE   cwidth;                 /* Column width (0, 1 or 2)                                     */
        UBYTE   row;                    /* Row (0 to 6)                                                         */
};

int SetCylHide[] = {
        MSG_CYL_HIDE_GAD, MSG_CYL_ICONIFY_GAD, MSG_CYL_TOOLSMENU_GAD,
        MSG_CYL_NONE_GAD, 0
};

int SetCylOpenOn[] = {
        MSG_CYL_DEFSCREEN_GAD, MSG_CYL_FRONTSCREEN_GAD, MSG_CYL_NAMEDSCREEN_GAD, 0
};

int SetCylLogMode[] = {
        MSG_CYL_PROMPT_USER_GAD, MSG_CYL_APPEND_GAD, MSG_CYL_OVERWRITE_GAD,
        MSG_CYL_SERIALPORT_GAD, 0
};

int SetCylFileIO[] = {
        MSG_CYL_AUTOMATIC_GAD, MSG_CYL_IMMEDIATE_GAD, MSG_CYL_BUFFERED_GAD, 0
};

char *SetCylHideText[HIDE_MAX+1];
char *SetCylOpenOnText[SCREEN_MAX+1];
char *SetCylLogModeText[LOGMODE_MAX+1];
char *SetCylFileIOText[FILE_MAX+1];


/*
 *              If you re-arrange the order of these gadget definitions,
 *              make sure you re-order the enum's that follow below as well.
 */
struct SetGadgets SetGadgs[] = {
        GID_BUFFORMAT,  MSG_BUFFORMAT_GAD,      STRING_KIND,    GID_FORMATEDIT, 
                                                                                                                MSG_FORMATEDIT_GAD,
                                        NULL,                           0,                                      0,      2,      5,

        GID_LOGFORMAT,  MSG_LOGFORMAT_GAD,      STRING_KIND,    GID_FORMATCOPY,
                                                                                                                MSG_FORMATCOPYF_GAD,
                                        NULL,                           0,                                      0,      2,      6,
        
        GID_HIDEMETHOD, MSG_HIDEMETHOD_GAD,     CYCLE_KIND,             0,      0,
                                        SetCylHide,                     SetCylHideText,         0,      0,      0,
        
        GID_OPENON,             MSG_OPENON_GAD,         CYCLE_KIND,             0,      0,
                                        SetCylOpenOn,           SetCylOpenOnText,       0,      0,      1,

        GID_LOGMODE,    MSG_LOGMODE_GAD,        CYCLE_KIND,             0,      0,
                                        SetCylLogMode,          SetCylLogModeText,      0,      0,      2,

        GID_FILEIO,             MSG_FILEIO_GAD,         CYCLE_KIND,             0,      0,
                                        SetCylFileIO,           SetCylFileIOText,       0,      0,      3,
        
        GID_HOTKEY,             MSG_HOTKEY_GAD,         STRING_KIND,    0,      0,
                                        NULL,                           0,                                      1,      1,      0,
        
        GID_SCREENNAME, MSG_SCREENNAME_GAD,     STRING_KIND,    0,      0,
                                        NULL,                           0,                                      1,      1,      1,
        
        GID_LOGFILE,    MSG_LOGFILE_GAD,        STRING_KIND,    GID_FILEPROMPT,
                                                                                                                SETBUTTON_FILE,
                                        NULL,                           0,                                      1,      1,      2,

        GID_WINDOWFONT, MSG_WINDOWFONT_GAD,     TEXT_KIND,              GID_WFONTPROMPT,
                                                                                                                SETBUTTON_FONT,
                                        NULL,                           0,                                      1,      1,      3,
        
        GID_BUFFERFONT, MSG_BUFFERFONT_GAD,     TEXT_KIND,              GID_BFONTPROMPT,
                                                                                                                SETBUTTON_FONT,
                                        NULL,                           0,                                      1,      1,      4,

        0
};

/*
 *              The following gadgets should match the _order_ that the above
 *              gadgets are laid out in, since they are used as indices into the
 *              above array.
 */
typedef enum {
        SG_BufferFormat,
        SG_LogfileFormat,
        SG_HideMethod,
        SG_ScreenType,
        SG_LogMode,
        SG_FileIOType,
        SG_HotKey,
        SG_ScreenName,
        SG_LogFile,
        SG_WindowFont,
        SG_BufferFont,
} SetGadgetEnums;

/*
 *              Arrays of gadget names for calculating minimum width
 */

/*
 *              Miscellaneous gadgets in functions window
 */
int MiscColNames[] = {
        MSG_SHOWFAILS_GAD,
        MSG_SHOWCLI_GAD,
        MSG_SHOWFULLPATHS_GAD,
        MSG_USEDEVNAMES_GAD,
        MSG_MONPACKETS_GAD,
        MSG_MONALLPACKETS_GAD,
        MSG_IGNOREWB_GAD,
        0
};

/*
 *              System gadgets in functions window
 */
int SysColNames[] = {
        MSG_FINDPORT_GAD,     
        MSG_FINDRESIDENT_GAD, 
        MSG_FINDSEMAPHORE_GAD,
        MSG_FINDTASK_GAD,     
        MSG_LOCKSCREEN_GAD,   
        MSG_OPENDEVICE_GAD,   
        MSG_OPENFONT_GAD,     
        MSG_OPENLIBRARY_GAD,  
        MSG_OPENRESOURCE_GAD, 
        MSG_READTOOLTYPES_GAD,
        MSG_SENDREXX_GAD, 
        0
};

/*
 *              AmigaDOS gadgets in functions window
 */
int DOSColNames[] = {
        MSG_CHANGEDIR_GAD, 
        MSG_DELETE_GAD,    
        MSG_EXECUTE_GAD,   
        MSG_GETVAR_GAD,    
        MSG_LOADSEG_GAD,   
        MSG_LOCKFILE_GAD,  
        MSG_MAKEDIR_GAD,   
        MSG_MAKELINK_GAD,  
        MSG_OPENFILE_GAD,  
        MSG_RENAME_GAD,    
        MSG_RUNCOMMAND_GAD,
        MSG_SETVAR_GAD,    
        MSG_SYSTEM_GAD,
        0
};

/*
 *              Aditional gadgets in function window
 */
int FuncCycleText[]             = { MSG_ALL_GAD, MSG_NONE_GAD, MSG_SELECTED_GAD, 0 };
int FuncCycleName[]             = { MSG_SELSYSTEM_GAD, MSG_SELDOS_GAD, 0 };
int UseCancelUndoText[] = { MSG_USE_GAD, MSG_CANCEL_GAD, MSG_UNDO_GAD, 0 };

/*
 *              Left column gadgets names in settings window
 */
int SettingsLeftNames[] = {
        MSG_HIDEMETHOD_GAD,
        MSG_OPENON_GAD,
        MSG_LOGMODE_GAD,
        MSG_FILEIO_GAD,
        MSG_BUFFORMAT_GAD,
        MSG_LOGFORMAT_GAD,
        0
};

/*
 *              Right column gadget names in settings window
 */
int SettingsRightNames[] = {
        MSG_HOTKEY_GAD,
        MSG_SCREENNAME_GAD,
        MSG_LOGFILE_GAD,
        MSG_WINDOWFONT_GAD,
        MSG_BUFFERFONT_GAD,
        0
};

/*
 *              Two mini buttons attached to format gadgets in settings window
 *              Note that we have two types of possibility: one for fixed point
 *              fonts and one for proportional fonts. This is so that we can
 *              arrange for the two strings "Edit..." and "Copy" to always
 *              look properly aligned, regardless of font type. If we didn't do
 *              this, then with fixed point fonts, "Copy" looks mis-aligned
 *              with respect to "Edit..." because they are different lengths.
 */
int SetMiniButtonsP[] = {
        MSG_FORMATCOPYP_GAD,
        MSG_FORMATEDIT_GAD,
        0
};

int SetMiniButtonsF[] = {
        MSG_FORMATCOPYF_GAD,
        MSG_FORMATEDIT_GAD,
        0
};

/*
 *              Left column gadget contents in settings
 */
int SettingsLeftContents[] = {
        MSG_CYL_HIDE_GAD,
        MSG_CYL_ICONIFY_GAD,
        MSG_CYL_TOOLSMENU_GAD,
        MSG_CYL_DEFSCREEN_GAD,
        MSG_CYL_FRONTSCREEN_GAD,
        MSG_CYL_NAMEDSCREEN_GAD,
        MSG_CYL_PROMPT_USER_GAD,
        MSG_CYL_APPEND_GAD,
        MSG_CYL_OVERWRITE_GAD,
        MSG_CYL_AUTOMATIC_GAD,
        MSG_CYL_IMMEDIATE_GAD,
        MSG_CYL_BUFFERED_GAD,
        0
};

/*
 *              Mega struct that collects all the data needed to create useful BOBs.
 */
typedef struct MyGel {
        struct GelsInfo  gelinfo;                       /* GelInfo for entire structure */
        WORD                     nextline[8];           /* Nextline data                                */ 
        WORD                     *lastcolor[8];         /* Last colour data                             */
        struct RastPort  *mainrast;                     /* Rastport bob is displayed in */
        struct collTable colltable;                     /* Collision table                              */
        struct VSprite   vshead;                        /* Head sprite anchor                   */
        struct VSprite   vstail;                        /* Tail sprite anchor                   */
        struct VSprite   bobvsprite;            /* Vsprite used for bob                 */
        struct Bob               bob;                           /* Data for a single bob                */
        struct RastPort  rastport;                      /* Rastport for our BOB                 */
        struct BitMap    bitmap;                        /* Bitmap for our BOB                   */
        ULONG                    planesize;                     /* Size of one plane in bob             */
        UBYTE                   *planedata;                     /* Pointer to first plane               */
        UBYTE                   *chipdata;                      /* Pointer to all CHIP RAM data */
        ULONG                    chipsize;                      /* Total size of allocated CHIP */
} MyGel;

MyGel   *TextBob;                                               /* Info about GEL/BOBs                  */

/*
 *              Some prototypes private to this file
 */
MyGel *CreateBob(struct RastPort *rport, int width, int height, int transp);
void FreeBob(MyGel *mygel);
void UpdateBob(int x, int y);
int  PickupBob(int hit, int x, int y);
void DropBob(void);

/*****************************************************************************
 *
 *              Start of functions!
 *
 *****************************************************************************/

/*
 *              CleanupSubWindows()
 *
 *              Frees any resources associated with this module
 */
void CleanupSubWindows(void)
{
        PurgeFuncGadgets = 1;
        CloseFunctionWindow();
        CloseSettingsWindow();
        CloseFormatWindow();
}

/*
 *              GetFuncName(gadgetid)
 *
 *              Returns a pointer to a string describing the function name which
 *              matches the gadget ID passed in. This is used by PATCHES.C when
 *              it is unpatching the patched functions at exit time to identify
 *              which function can't be unpatched in the event of a problem.
 */
char *GetFuncName(int gadgetid)
{
        struct FuncGadgets *fg = FuncGadgs;

        if (gadgetid == GID_SENDREXX)   /* Special-case this multi-function one */
                return MSG(MSG_PACKET_NAME);

        for (fg = &FuncGadgs[0]; fg->gadgid; fg++) {
                if (fg->gadgid == gadgetid)
                        return MSG(fg->stringid);
        }
        return ("<Unknown Function>");
}

/*
 *              CreateFunctionGadgets(fontattr, getsize, &width, &height)
 *
 *              Creates all the gadgets for the function window. If getsize
 *              is true, then the width and height values are filled in with
 *              the dimensions of the window needed to hold the gadgets. In
 *              this case, NULL is returned if the window would be too big to
 *              fit on the current screen, or non-NULL if width and height
 *              were successfully initialised.
 *
 *              If getsize is zero, then the actual gadgets are created and
 *              a pointer to the gadget list is returned, or NULL if an
 *              error occurred (typically, out of memory).
 */
struct Gadget *CreateFunctionGadgets(struct TextAttr *fontattr,
                                                                         int getsize, int *pwidth, int *pheight)
{
        static char *cyltext[4];
        struct TextFont *font;
        struct FuncGadgets *fg;
        struct NewGadget ng;
        struct Gadget *gadlist;
        struct Gadget *gad;
        UWORD spacing[3];
        UWORD yoffset[3]; 
        UWORD colpos[3];
        int heightadjust = 0;
        int width;
        int height;
        int gwidth;
        int gheight;
        int fonty;
        int colspace = 30;
        int topline;
        int w1, w2;

        if (!FuncVI) {
                FuncVI = GetVisualInfoA(SnoopScreen, NULL);
                if (!FuncVI)
                        return (NULL);
        }
        font = MyOpenFont(fontattr);
        if (!font)
                return (NULL);

        fonty           = font->tf_YSize;
        gheight         = fonty   + 3;
        gwidth          = gheight + 15;
        
        spacing[0]  = fonty + 4;
        spacing[1]  = fonty + 4;
        spacing[2]  = fonty + 4;
        yoffset[1]      = TitlebarHeight + fonty/2;
        yoffset[2]      = yoffset[1];
        yoffset[0]      = yoffset[1] + 10 * spacing[1] - 7 * spacing[0];

        colpos[0]       = BorderLeft + FUNC_MARGIN;
        colpos[1]       = colpos[0]  + MaxTextLen(font, MiscColNames)
                                  + gwidth + colspace;
        w1                      = MaxTextLen(font, SysColNames) + gwidth;
        w2                      = GetTextLen(font, MSG(MSG_SYSFUNCS_GAD));
        colpos[2]       = colpos[1]  + MAX(w1, w2) + colspace;
        w1                      = MaxTextLen(font, DOSColNames) + gwidth;
        w2                      = GetTextLen(font, MSG(MSG_DOSFUNCS_GAD));
        width           = colpos[2]  + MAX(w1, w2) + FUNC_MARGIN + BorderRight + 8;
        height      = yoffset[2] + spacing[2] * 15 + BorderBottom;

        topline = yoffset[2];
        if (ScreenHeight < 256 && height > ScreenHeight) {
                /*
                 *              For medium and low resolution screens, shave another
                 *              few pixels from the height. Our aim is to be able to
                 *              open on a 640 x 200 screen with a 15 point screen font.
                 */
                heightadjust = -2;
                height      += heightadjust * 2;
        }
        if ((height + spacing[2] * 2) <= ScreenHeight) {
                int disp = (spacing[2] * 3) / 2;

                yoffset[0] += disp;
                yoffset[1] += disp;
                yoffset[2] += disp;
                height     += disp;
        }

        if (width > ScreenWidth || height > ScreenHeight) {
                CloseFont(font);
                return (NULL);
        }
        if (getsize) {
                CloseFont(font);
                *pwidth  = width;
                *pheight = height;
                return (struct Gadget *)(-1);
        }

        /*
         *              Now create our new gadgets
         */     
        ng.ng_Height            = gheight; 
        ng.ng_Width                     = gwidth;  
        ng.ng_TextAttr          = fontattr;
        ng.ng_VisualInfo        = FuncVI;
        ng.ng_Flags         = PLACETEXT_RIGHT;

        gadlist = NULL;
        gad = CreateContext(&gadlist);
        if (!gad)
                goto fgad_failed;

        for (fg = &FuncGadgs[0]; fg->gadgid; fg++) {
                int col = fg->col;

                ng.ng_LeftEdge          = colpos[col];
                ng.ng_TopEdge           = fg->row * spacing[col] + yoffset[col];
                ng.ng_GadgetText        = MSG(fg->stringid);
                ng.ng_GadgetID          = fg->gadgid;

                gad = CreateGadget(CHECKBOX_KIND, gad, &ng,
                                                   GT_Underscore,       '_',
                                                   GTCB_Scaled,     TRUE,
                                                   TAG_DONE);
                if (!gad)
                        goto fgad_failed;
                Gadget[fg->gadgid] = gad;
                AddKeyShortcut(FuncKeyboard, fg->gadgid, fg->stringid);
        }

        /*
         *              Now create the remaining window gadgets
         */
        ng.ng_LeftEdge          = colpos[0]
                                                  + GetTextLen(font, MSG(MSG_MATCHNAME_GAD)) + 10;
        ng.ng_TopEdge           = yoffset[1] + (23 * spacing[1]) / 2;
        ng.ng_GadgetText        = MSG(MSG_MATCHNAME_GAD);
        ng.ng_GadgetID          = GID_MATCHNAME;
        ng.ng_Width         = colpos[2] - ng.ng_LeftEdge - colspace + 8;
        ng.ng_Height            = gheight + 3;
        ng.ng_Flags         = PLACETEXT_LEFT;

        gad = CreateGadget(STRING_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           GTST_MaxChars,       MAX_STR_LEN,
                                           GTST_String,         CurSettings.Func.Pattern,
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_MATCHNAME] = gad;
        AddKeyShortcut(FuncKeyboard, GID_MATCHNAME, MSG_MATCHNAME_GAD);

        ng.ng_GadgetText        = MSG(MSG_SELSYSTEM_GAD);
        ng.ng_GadgetID          = GID_SELSYSTEM;
        ng.ng_Height            = gheight + 3;
        ng.ng_Width                     = MaxTextLen(font, FuncCycleText) + 40;
        ng.ng_LeftEdge          = colpos[0] + MaxTextLen(font, FuncCycleName) + 10;
        ng.ng_TopEdge           = yoffset[1];
        cyltext[0]                      = MSG(MSG_SELECTED_GAD);
        cyltext[1]                      = MSG(MSG_ALL_GAD);
        cyltext[2]                      = MSG(MSG_NONE_GAD);
        cyltext[3]                      = NULL;

        gad = CreateGadget(CYCLE_KIND,          gad, &ng,
                                           GT_Underscore,       '_',
                                           GTCY_Labels,         cyltext,
                                           GTCY_Active,         COL_SELECTED,
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_SELSYSTEM] = gad;
        AddKeyShortcut(FuncKeyboard, GID_SELSYSTEM, MSG_SELSYSTEM_GAD);

        ng.ng_GadgetText        = MSG(MSG_SELDOS_GAD);
        ng.ng_GadgetID          = GID_SELDOS;
        ng.ng_TopEdge           = (yoffset[0] + yoffset[1]) / 2;

        gad = CreateGadget(CYCLE_KIND,          gad, &ng,
                                           GT_Underscore,       '_',
                                           GTCY_Labels,         cyltext,
                                           GTCY_Active,         COL_SELECTED,
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_SELDOS] = gad;
        AddKeyShortcut(FuncKeyboard, GID_SELDOS, MSG_SELDOS_GAD);

        ng.ng_Width                     = MaxTextLen(font, UseCancelUndoText) + 32;
        ng.ng_Height            = fonty + GadgetHeight;
        ng.ng_GadgetText        = MSG(MSG_USE_GAD);
        ng.ng_GadgetID          = GID_FUNCUSE;
        ng.ng_LeftEdge          = colpos[0];
        ng.ng_TopEdge           = yoffset[2] + heightadjust + (27 * spacing[2]) / 2;
        ng.ng_Flags                     = PLACETEXT_IN;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_FUNCUSE] = gad;
        AddKeyShortcut(FuncKeyboard, GID_FUNCUSE, MSG_USE_GAD);
        
        ng.ng_LeftEdge          = width - BorderRight - FUNC_MARGIN - ng.ng_Width;
        ng.ng_GadgetText        = MSG(MSG_CANCEL_GAD);
        ng.ng_GadgetID          = GID_FUNCCANCEL;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_FUNCCANCEL] = gad;
        AddKeyShortcut(FuncKeyboard, GID_FUNCCANCEL, MSG_CANCEL_GAD);

        ng.ng_LeftEdge          = (ng.ng_LeftEdge + BorderLeft + FUNC_MARGIN) / 2;
        ng.ng_GadgetText        = MSG(MSG_UNDO_GAD);
        ng.ng_GadgetID          = GID_FUNCUNDO;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fgad_failed;
        Gadget[GID_FUNCUNDO] = gad;
        AddKeyShortcut(FuncKeyboard, GID_FUNCUNDO, MSG_UNDO_GAD);
        
        if (topline != yoffset[2]) {
                /*
                 *              If there's room at the top of the window, create two text
                 *              gadgets with the column headings for the System and DOS cols.
                 */
                ng.ng_TopEdge           = topline;
                ng.ng_LeftEdge          = colpos[1];
                ng.ng_GadgetText        = "";
                ng.ng_GadgetID          = 0;

                gad = CreateGadget(TEXT_KIND,   gad, &ng,
                                                   GTTX_Text,   MSG(MSG_SYSFUNCS_GAD),
                                                   TAG_DONE);
                if (!gad)
                        goto fgad_failed;

                ng.ng_LeftEdge          = colpos[2];
                gad = CreateGadget(TEXT_KIND,   gad, &ng,
                                                   GTTX_Text,   MSG(MSG_DOSFUNCS_GAD),
                                                   TAG_DONE);
                if (!gad)
                        goto fgad_failed;
        }
        PurgeFuncGadgets = 0;   /* No need to purge now */
        CloseFont(font);
        return (gadlist);

fgad_failed:
        if (gadlist)
                FreeGadgets(gadlist);
        CloseFont(font);
        return (NULL);
}

/*
 *              OpenFunctionWindow()
 *
 *              Opens the functions window with all the functions gadgets.
 *              Returns TRUE for success, FALSE for failure.
 *              The window will contain all necessary gadgets.
 *
 *              As with the Settings window, we try a variety of fonts until we
 *              find one that fits onto the screen.
 */
int OpenFunctionWindow(void)
{
        static struct TextAttr funcfontattr =
                { "empty-storage-for-func-fonts...", 0, FS_NORMAL, FPB_DISKFONT};

        static int width;               /* Maintained from call to call */
        static int height;

        int left = CurSettings.FuncWinLeft;
        int top  = CurSettings.FuncWinTop;
        struct TextAttr *fontattr = NULL;
        struct Window *win;
        int i;

        CheckSegTracker();
        if (FuncWindow) {
                WindowToFront(FuncWindow);
                ActivateWindow(FuncWindow);
                return (TRUE);
        }
        if (!CheckForScreen())
                return (FALSE);

        SavedFuncSets = CurSettings;            /* Save so we can restore later */

        if (!FuncGadList) {
                /*
                 *              Find out what dimensions our new window should be; to do
                 *              this, we calculate the size with a variety of fonts until
                 *              we find one that works.
                 */
                int i;

                for (i = 0; fontattr = SubWindowFontList[i]; i++) {
                        if (CreateFunctionGadgets(fontattr, TRUE, &width, &height))
                                break;
                }
                if (!fontattr) {
                        ShowError(MSG(MSG_ERROR_OPENFUNC));
                        return (FALSE);
                }
                /*
                 *              We need to make a copy of the fontattr now so that if
                 *              the user changes the font, all our gadgets don't suddenly
                 *              inherit the new font (at least until the window has been
                 *              closed and re-opened!)
                 */
                strcpy(funcfontattr.ta_Name, fontattr->ta_Name);
                funcfontattr.ta_YSize = fontattr->ta_YSize;
                fontattr = &funcfontattr;
        }
        if (left == -1)  left = (ScreenWidth  - width)  / 2;
        if (top  == -1)  top  = (ScreenHeight - height) / 2;

        win = OpenWindowTags(NULL,
                                                 WA_Title,                      MSG(MSG_FUNCTION_TITLE),
                                                 WA_IDCMP,                      IDCMP_CLOSEWINDOW        |
                                                                                        IDCMP_REFRESHWINDOW      |
                                                                                        IDCMP_NEWSIZE            |
                                                                                        IDCMP_INACTIVEWINDOW |
                                                                                        IDCMP_RAWKEY             |
                                                                                        BUTTONIDCMP,
                                                 WA_Left,                       left,
                                                 WA_Top,                        top,
                                                 WA_Width,                      width,
                                                 WA_Height,                     height,
                                                 WA_Flags,                      WFLG_DRAGBAR             |
                                                                                        WFLG_DEPTHGADGET         |
                                                                                        WFLG_ACTIVATE            |
                                                                                        WFLG_RMBTRAP             |
                                                                                        WFLG_NEWLOOKMENUS,
                                                 RefreshTag,            TRUE,
                                                 WA_NoCareRefresh,      NoCareRefreshBool,
                                                 WA_PubScreen,          SnoopScreen,
                                                 TAG_DONE);
        if (!win) {
                ShowError(MSG(MSG_ERROR_OPENFUNC));
                return (FALSE);
        }

        if (!FuncGadList) {
                FuncGadList = CreateFunctionGadgets(fontattr, 0, 0, 0);
                if (!FuncGadList) {
                        CloseWindow(win);
                        ShowError(MSG(MSG_ERROR_OPENFUNC));
                        return (FALSE);
                }
        }

        /*
         *              Now update function gadgets to reflect current settings
         *              for AmigaDOS functions. Under V39+, we can do this before
         *              adding them to the window. Under V37, we have to do it
         *              after adding them to the window.
         */
        if (GadToolsBase->lib_Version < 39) {
                AddGList(win, FuncGadList, -1, -1, NULL);
                RefreshGList(FuncGadList, win, NULL, -1);
                GT_RefreshWindow(win, NULL);
                for (i = FIRST_BOOL_GADGET; i <= LAST_BOOL_GADGET; i++) {
                        if (Gadget[i]) {
                                GT_SetGadgetAttrs(Gadget[i], win, NULL,
                                                                  GTCB_Checked, CurSettings.Func.Opts[i],
                                                                  TAG_DONE);
                        }
                }
        } else { /* GadToolsBase->lib_Version >= 39 */
                for (i = FIRST_BOOL_GADGET; i <= LAST_BOOL_GADGET; i++) {
                        if (Gadget[i]) {
                                GT_SetGadgetAttrs(Gadget[i], NULL, NULL,
                                                                  GTCB_Checked, CurSettings.Func.Opts[i],
                                                                  TAG_DONE);
                        }
                }
                AddGList(win, FuncGadList, -1, -1, NULL);
                RefreshGList(FuncGadList, win, NULL, -1);
                GT_RefreshWindow(win, NULL);
        }

        /*
         *              All done, so initialise some globals and return
         */
        FuncWindow              = win;
        FuncWindowPort  = win->UserPort;
        FuncWindowMask  = 1 << FuncWindowPort->mp_SigBit;
        FuncSystemCol   = COL_SELECTED;
        FuncDOSCol              = COL_SELECTED;
        if (DisableNestCount)
                DisableWindow(FuncWindow, &FuncRequester);
        return (TRUE);
}

/*
 *              CreateSettingsGadgets(fontattr, getsize, &pwidth, &pheight)
 *
 *              Creates the gadgets list for the settings window using the specified
 *              window font. If the window would be to big for the current screen
 *              using this font, or if there is a problem creating the gadgets,
 *              returns NULL.
 *
 *              The returned gadget list should be passed to FreeGadgets() when
 *              it is no longer required.
 *
 *              If getsize is true, then pwidth and pheight are filled in with the
 *              dimensions of the window size needed to hold the gadgets, but
 *              no actual gadget creation is carried out. In this case, NULL is
 *              returned for a failure (couldn't open font, or window size would
 *              exceed current screen size) and non-NULL for success.
 */
struct Gadget *CreateSettingsGadgets(struct TextAttr *fontattr,
                                                                         int getsize, int *pwidth, int *pheight)
{
        struct SetGadgets *sg;
        struct NewGadget ng;
        struct NewGadget ngbut;
        struct TextFont *font;
        struct Gadget *gadlist;
        struct Gadget *gad;
        UWORD spacing;
        UWORD yoffset;
        UWORD colpos[2];
        UWORD colwidth[3];
        int miniwidth;
        int width;
        int height;
        int gheight;
        int fonty;
        int fontx;

        if (!SetVI) {
                SetVI = GetVisualInfoA(SnoopScreen, NULL);
                if (!SetVI)
                        return (NULL);
        }
        font = MyOpenFont(fontattr);
        if (!font)
                return (NULL);
        
        fonty           = font->tf_YSize;
        fontx           = font->tf_XSize;

        gheight         = fonty + GadgetHeight;
        spacing     = fonty + GadgetSpacing;
        yoffset         = TitlebarHeight + fonty/2;
        colpos[0]       = BorderLeft + SET_MARGIN
                                  + MaxTextLen(font, SettingsLeftNames) + 7;
        colwidth[0] = MaxTextLen(font, SettingsLeftContents) + 40;
        colpos[1]       = colpos[0] + colwidth[0] + SET_MARGIN*2
                                                          + MaxTextLen(font, SettingsRightNames);
        colwidth[1] = fontx * 24; /* Room for about 24 chars in string gadgets */
        colwidth[2] = colwidth[1] + colpos[1] - colpos[0];      /* Dual width */

        /*
         *              See SetMiniButtons[] definitions for explanation of this bit
         */
        if (font->tf_Flags & FPF_PROPORTIONAL) {
                miniwidth = MaxTextLen(font, SetMiniButtonsP) + 20;
                SetGadgs[1].data1 = MSG_FORMATCOPYP_GAD;
        } else {
                miniwidth = MaxTextLen(font, SetMiniButtonsF) + 20;
                SetGadgs[1].data1 = MSG_FORMATCOPYF_GAD;
        }

        width           = colpos[1] + colwidth[1] + SET_MARGIN + BorderRight;
        height      = yoffset + spacing * 8 + BorderBottom;

        if (width > ScreenWidth || height >ScreenHeight) {
                CloseFont(font);
                return (NULL);
        }
        if (getsize) {
                *pwidth  = width;
                *pheight = height;
                CloseFont(font);
                return (struct Gadget *)(-1);
        }

        /*
         *              Okay, looks like our size is okay so now prime our gadget
         *              table with the current settings values to use when the
         *              gadget is created.
         *
         */
#define DEF_CYCLE(x)    SetGadgs[SG_##x].data1
#define DEF_STRING(x)   SetGadgs[SG_##x].data2

        DEF_CYCLE(HideMethod)     = CurSettings.Setup.HideMethod;
        DEF_CYCLE(ScreenType)     = CurSettings.Setup.ScreenType;
        DEF_CYCLE(LogMode)                = CurSettings.Setup.LogMode;
        DEF_CYCLE(FileIOType)     = CurSettings.Setup.FileIOType;
        DEF_STRING(HotKey)                = CurSettings.Setup.HotKey;
        DEF_STRING(ScreenName)    = CurSettings.Setup.ScreenName;
        DEF_STRING(LogFile)               = CurSettings.Setup.LogFile;
        DEF_STRING(WindowFont)    = GetFontDesc(WindowFontDesc,
                                                                                    WindowFontName, WindowFontSize);
        DEF_STRING(BufferFont)    = GetFontDesc(BufferFontDesc,
                                                                                BufferFontName, BufferFontSize);
        DEF_STRING(BufferFormat)  = BufFormat;
        DEF_STRING(LogfileFormat) = LogFormat;

        /*
         *              Next, onto the gadget creation itself
         */
        ng.ng_Height            = gheight; 
        ng.ng_TextAttr          = fontattr;
        ng.ng_VisualInfo        = SetVI;
        ng.ng_Flags         = PLACETEXT_LEFT;

        ngbut.ng_Height         = gheight; 
        ngbut.ng_TextAttr       = fontattr;
        ngbut.ng_VisualInfo     = SetVI;
        ngbut.ng_Flags          = PLACETEXT_IN;

        FileButtonImage = CreateCustomImage(IMAGE_FILE, gheight+2-2*SquareAspect);
        FontButtonImage = CreateCustomImage(IMAGE_FONT, gheight);
        if (!FileButtonImage || !FontButtonImage) {
                CloseFont(font);
                return (NULL);
        }
        gadlist = NULL;
        gad = CreateContext(&gadlist);
        if (!gad) {
                CloseFont(font);
                return (NULL);
        }

        for (sg = &SetGadgs[0]; sg->gadgid; sg++) {
                ng.ng_LeftEdge          = colpos[sg->col];
                ng.ng_Width                     = colwidth[sg->cwidth];
                ng.ng_TopEdge           = yoffset + sg->row * spacing;
                ng.ng_GadgetText        = MSG(sg->stringid);
                ng.ng_GadgetID          = sg->gadgid;
                ng.ng_Height            = gheight;

                if (sg->promptid) {
                        /*
                         *              Create optional mini gadget to right of main gadget.
                         *              If data1 == 0 or data1 == 1 then use a file or font
                         *              BOOPSI gadget image, else use a Gadtools text button
                         *              containing the label corresponding to MSG(data1) else
                         *              use string ID indicated by data1
                         */
                        ngbut.ng_GadgetID  = sg->promptid;
                        ngbut.ng_TopEdge   = ng.ng_TopEdge;
                        ngbut.ng_Height    = gheight;
                        if (sg->gtype == STRING_KIND && !SquareAspect) {
                                ngbut.ng_Height  += 2;
                                ngbut.ng_TopEdge -= 1;
                        }
                        if (sg->data1 > SETBUTTON_MAX) {
                                /*
                                 *              Creating a text gadget
                                 */
                                ngbut.ng_LeftEdge   = ng.ng_LeftEdge + ng.ng_Width - miniwidth;
                                ngbut.ng_Width          = miniwidth;
                                ngbut.ng_GadgetText = MSG(sg->data1);
                                gad = CreateGadget(BUTTON_KIND, gad, &ngbut,
                                                                   GT_Underscore, '_',
                                                                   TAG_DONE);
                                if (!gad)
                                        goto sgad_failed;
                                AddKeyShortcut(SetKeyboard, sg->promptid, sg->data1);
                        } else {
                                /*
                                 *              Creating an image gadget
                                 */
                                struct Image *img = FileButtonImage;

                                if (sg->data1 == SETBUTTON_FONT)
                                        img = FontButtonImage;

                                ngbut.ng_Width          = img->Width;
                                ngbut.ng_GadgetText = NULL;
                                ngbut.ng_LeftEdge   = ng.ng_LeftEdge + ng.ng_Width -img->Width;
                                gad = CreateGadget(GENERIC_KIND, gad, &ngbut, TAG_DONE);
                                if (!gad)
                                        goto sgad_failed;

                                /*
                                 *              Now fill in the details of our gadget
                                 */
                                gad->Flags                      |= GFLG_GADGIMAGE | GFLG_GADGHIMAGE;
                                gad->Activation         |= GACT_IMMEDIATE | GACT_RELVERIFY;
                                gad->GadgetType         |= GTYP_BOOLGADGET;
                                gad->GadgetRender       = img;
                                gad->SelectRender       = img + 1;
                        }
                        Gadget[sg->promptid] = gad;
                        ng.ng_Width     -= ngbut.ng_Width + 2;
                }
                switch (sg->gtype) {
                        case STRING_KIND:
                                if (!SquareAspect) {
                                        ng.ng_Height  += 2;
                                        ng.ng_TopEdge -= 1;
                                }
                                gad = CreateGadget(STRING_KIND, gad, &ng,
                                                                   GT_Underscore,       '_',
                                                                   GTST_String,         sg->data2,
                                                                   GTST_MaxChars,       MAX_STR_LEN,
                                                                   TAG_DONE);
                                break;

                        case TEXT_KIND:
                                ng.ng_Width-= 2;
                                ng.ng_LeftEdge++;
                                gad = CreateGadget(TEXT_KIND, gad, &ng,
                                                                   GT_Underscore,       '_',
                                                                   GTTX_Text,           sg->data2,
                                                                   GTTX_Border,         TRUE,
                                                                   TAG_DONE);
                                break;

                        case CYCLE_KIND:
                        {
                                int *msgid = sg->data2;
                                char **msg = sg->data3;
                                int i;

                                for (i = 0; msgid[i]; i++)
                                        msg[i] = MSG(msgid[i]);
                                msg[i] = NULL;
                                gad = CreateGadget(CYCLE_KIND, gad, &ng,
                                                                   GT_Underscore,       '_',
                                                                   GTCY_Active,         sg->data1,
                                                                   GTCY_Labels,         msg,
                                                                   TAG_DONE);
                                break;
                        }
                }
                if (!gad)
                        goto sgad_failed;
                Gadget[sg->gadgid] = gad;
                AddKeyShortcut(SetKeyboard, sg->gadgid, sg->stringid);
        }

        ng.ng_GadgetText        = MSG(MSG_BUFFERSIZE_GAD);
        ng.ng_GadgetID          = GID_BUFFERSIZE;
        ng.ng_TopEdge           = yoffset + spacing * 4 + (1 - SquareAspect);
        ng.ng_Height            = gheight + (2 - 2 * SquareAspect);
        ng.ng_Width                     = GetTextLen(font, "10000") + 14;
        ng.ng_LeftEdge          = colpos[0] + colwidth[0] - ng.ng_Width;

        gad = CreateGadget(INTEGER_KIND,        gad, &ng,
                                           GT_Underscore,       '_',
                                           GTIN_Number,         CurSettings.Setup.BufferSize,
                                           TAG_DONE);
        if (!gad)
                goto sgad_failed;
        Gadget[GID_BUFFERSIZE] = gad;
        AddKeyShortcut(SetKeyboard, GID_BUFFERSIZE, MSG_BUFFERSIZE_GAD);

        ng.ng_Width                     = MaxTextLen(font, UseCancelUndoText) + 32;
        ng.ng_Height            = fonty + GadgetHeight;
        ng.ng_GadgetText        = MSG(MSG_USE_GAD);
        ng.ng_GadgetID          = GID_SETUSE;
        ng.ng_LeftEdge          = BorderLeft + SET_MARGIN;
        ng.ng_TopEdge           = yoffset + 7 * spacing;
        ng.ng_Flags                     = PLACETEXT_IN;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto sgad_failed;
        Gadget[GID_SETUSE] = gad;
        AddKeyShortcut(SetKeyboard, GID_SETUSE, MSG_USE_GAD);
        
        ng.ng_LeftEdge          = width - BorderRight - SET_MARGIN - ng.ng_Width;
        ng.ng_GadgetText        = MSG(MSG_CANCEL_GAD);
        ng.ng_GadgetID          = GID_SETCANCEL;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto sgad_failed;
        Gadget[GID_SETCANCEL] = gad;
        AddKeyShortcut(SetKeyboard, GID_SETCANCEL, MSG_CANCEL_GAD);
        
        ng.ng_LeftEdge          = (ng.ng_LeftEdge + BorderLeft + SET_MARGIN) / 2;
        ng.ng_GadgetText        = MSG(MSG_UNDO_GAD);
        ng.ng_GadgetID          = GID_SETUNDO;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto sgad_failed;
        Gadget[GID_SETUNDO] = gad;
        AddKeyShortcut(SetKeyboard, GID_SETUNDO, MSG_UNDO_GAD);

        CloseFont(font);
        return (gadlist);

sgad_failed:
        if (gadlist)
                FreeGadgets(gadlist);
        CloseFont(font);
        return (FALSE);
}

/*
 *              OpenSettingsWindow()
 *
 *              Opens the settings window with all the settings gadgets.
 *              The window will contain all necessary gadgets. Note that
 *              since gadgets can take some time to create on 68000 systems,
 *              we cache the gadget list so that on subsequent re-opens of
 *              the window, we don't need to recreate them all.
 *
 *              Returns TRUE for success, false for failure.
 */
int OpenSettingsWindow(void)
{
        static struct TextAttr setfontattr =
                { "empty-storage-for-setup-fonts..", 0, FS_NORMAL, FPB_DISKFONT};

        static int width;               /* Maintained from call to call */
        static int height;

        int left = CurSettings.SetupWinLeft;
        int top  = CurSettings.SetupWinTop;
        struct TextAttr *fontattr = NULL;
        struct Window *win;

        CheckSegTracker();
        if (SetWindow) {
                WindowToFront(SetWindow);
                ActivateWindow(SetWindow);
                return (TRUE);
        }
        if (!CheckForScreen())
                return (FALSE);

        SavedSetupSets = CurSettings;           /* Save so we can restore later */

        if (!SetGadList) {
                /*
                 *              Find out what dimensions our new window should be; to do
                 *              this, we calculate the size with a variety of fonts until
                 *              we find one that works.
                 */
                int i;

                for (i = 0; fontattr = SubWindowFontList[i]; i++) {
                        if (CreateSettingsGadgets(fontattr, TRUE, &width, &height))
                                break;
                }
                if (!fontattr) {
                        ShowError(MSG(MSG_ERROR_OPENSET));
                        return (FALSE);
                }
                /*
                 *              We need to make a copy of the fontattr now so that if
                 *              the user changes the font, all our gadgets don't suddenly
                 *              inherit the new font (at least until the window has been
                 *              closed and re-opened!)
                 */
                strcpy(setfontattr.ta_Name, fontattr->ta_Name);
                setfontattr.ta_YSize = fontattr->ta_YSize;
                fontattr = &setfontattr;
        }
        if (left == -1)  left = (ScreenWidth  - width)  / 2;
        if (top  == -1)  top  = (ScreenHeight - height) / 2;

        win = OpenWindowTags(NULL,
                                                 WA_Title,                      MSG(MSG_SETTINGS_TITLE),
                                                 WA_IDCMP,                      IDCMP_CLOSEWINDOW        |
                                                                                        IDCMP_REFRESHWINDOW      |
                                                                                        IDCMP_NEWSIZE            |
                                                                                        IDCMP_RAWKEY             |
                                                                                        IDCMP_INACTIVEWINDOW |
                                                                                        BUTTONIDCMP,
                                                 WA_Left,                       left,
                                                 WA_Top,                        top,
                                                 WA_Width,                      width,
                                                 WA_Height,                     height,
                                                 WA_Flags,                      WFLG_DRAGBAR             |
                                                                                        WFLG_DEPTHGADGET         |
                                                                                        WFLG_ACTIVATE            |
                                                                                        WFLG_RMBTRAP             |
                                                                                        WFLG_NEWLOOKMENUS,
                                                 RefreshTag,            TRUE,
                                                 WA_NoCareRefresh,      NoCareRefreshBool,
                                                 WA_PubScreen,          SnoopScreen,
                                                 TAG_DONE);
        if (!win) {
                ShowError(MSG(MSG_ERROR_OPENSET));
                return (FALSE);
        }
        if (!SetGadList) {
                SetGadList = CreateSettingsGadgets(fontattr, 0, 0, 0);
                if (!SetGadList) {
                        CloseWindow(win);
                        ShowError(MSG(MSG_ERROR_OPENSET));
                        return (FALSE);
                }
        }
        AddGList(win, SetGadList, -1, -1, NULL);
        RefreshGList(SetGadList, win, NULL, -1);
        GT_RefreshWindow(win, NULL);

        SetWindow               = win;
        SetWindowPort   = win->UserPort;
        SetWindowMask   = 1 << SetWindowPort->mp_SigBit;
        if (DisableNestCount)
                DisableWindow(SetWindow, &SetRequester);
        return (TRUE);
}

/*
 *              CloseFunctionWindow(void)
 *
 *              Closes the functions window. Note that we do NOT free the gadget
 *              list in this case; since it takes so long to build, we leave it
 *              alone for the benefit of future opens.
 */
void CloseFunctionWindow(void)
{
        if (FuncWindow) {
                RecordWindowSizes();
                CloseWindow(FuncWindow);
                FuncWindow     = NULL;
                FuncWindowMask = 0;
        }
        if (PurgeFuncGadgets && FuncGadList) {
                FreeGadgets(FuncGadList);
                FuncGadList        = NULL;
        }
        if (PurgeFuncGadgets && FuncVI) {
                FreeVisualInfo(FuncVI);
                FuncVI = NULL;
        }
        PurgeFuncGadgets = 0;
}

/*
 *              CloseSettingsWindow(void)
 *
 *              Closes the settings window, and free gadgets associated with window.
 */
void CloseSettingsWindow(void)
{
        if (SetWindow) {
                RecordWindowSizes();
                CloseWindow(SetWindow);
                SetWindow     = NULL;
                SetWindowMask = 0;
        }
        if (SetGadList) {
                FreeGadgets(SetGadList);
                SetGadList = NULL;
        }
        if (FileButtonImage) {
                FreeCustomImage(FileButtonImage);
                FileButtonImage = NULL;
        }
        if (FontButtonImage) {
                FreeCustomImage(FontButtonImage);
                FontButtonImage = NULL;
        }
        if (SetVI) {
                FreeVisualInfo(SetVI);
                SetVI = NULL;
        }
}
 
/*
 *              HandleSettingsMsgs()
 *
 *              Processes all outstanding messages associated with the Settings
 *              gadget window. To be called whenever we get a settings event.
 */

#define STRVAL(gid)     (((struct StringInfo *)(Gadget[gid]->SpecialInfo))->Buffer)

void HandleSettingsMsgs(void)
{
        static int activegad;                   /* Non-zero if gadget is depressed   */
        static int activekey;                   /* Keycode shortcut of activegad     */

        struct IntuiMessage *imsg;
        int doneset = SEL_NONE;
        char *str;
        int val;

        if (!SetWindow)
                return;

        while ((imsg = GT_GetIMsg(SetWindowPort)) != NULL) {
                struct Gadget *gad;
                int gadid;
                int shift  = (imsg->Qualifier & IE_SHIFT);
                int newval;

                switch (imsg->Class) {
                        
                        case IDCMP_CLOSEWINDOW:
                                doneset = SEL_USE;
                                break;

                        case IDCMP_REFRESHWINDOW:
                                GT_BeginRefresh(SetWindow);
                                GT_EndRefresh(SetWindow, TRUE);
                                break;

                        case IDCMP_MOUSEMOVE: 
                        case IDCMP_GADGETDOWN:
                        case IDCMP_GADGETUP:
                                gad    = (struct Gadget *)imsg->IAddress;
                                gadid  = gad->GadgetID;
                                newval = imsg->Code;

handle_set_gads:
                                switch (gadid) {

                                        case GID_FILEPROMPT:
                                        {
                                                char *defname = "";

                                                /*
                                                 *              Choose a default name for the logfile
                                                 */
                                                if (IsFileSystem(DefaultLogName))
                                                        defname = DefaultLogName;

                                                if (SelectFile(DefaultLogName, defname, SetWindow,
                                                                           FILESEL_DEFLOGNAME))
                                                {
                                                        /*
                                                         *              Got a new default filename, so update
                                                         *              the logfile gadget accordingly
                                                         */
                                                        GT_SetGadgetAttrs(Gadget[GID_LOGFILE],
                                                                                          SetWindow, NULL,
                                                                                          GTST_String, DefaultLogName,
                                                                                          TAG_DONE);
                                                }
                                                break;
                                        }

                                        case GID_WFONTPROMPT:
                                                /*
                                                 *              Choose a new gadget (window) font
                                                 */
                                                if (SelectFont(SetWindow, FONTSEL_WINDOW)) {
                                                        strcpy(WindowFontName, WindowFR->fo_Attr.ta_Name);
                                                        WindowFontSize = WindowFR->fo_Attr.ta_YSize;
                                                        WindowFontAttr.ta_YSize = WindowFontSize;

                                                        GT_SetGadgetAttrs(
                                                                Gadget[GID_WINDOWFONT], SetWindow, NULL,
                                                                GTTX_Text, GetFontDesc(WindowFontDesc,
                                                                                                           WindowFontName,
                                                                                                           WindowFontSize),
                                                                TAG_DONE);
                                                        ReOpenMainWindow();
                                                        WindowToFront(SetWindow);
                                                        /*
                                                         *              Since we allow the function gadgets
                                                         *              to hang around even when the function
                                                         *              window closes, we need to free the
                                                         *              gadgets when we change fonts to force
                                                         *              them to be regenerated in the new
                                                         *              font.
                                                         */             
                                                        if (!FuncWindow) {
                                                                FreeGadgets(FuncGadList);
                                                                FuncGadList              = NULL;
                                                        } else {
                                                                PurgeFuncGadgets = TRUE;
                                                        }
                                                }
                                                break;

                                        case GID_BFONTPROMPT:
                                                /*
                                                 *              Choose a new buffer font
                                                 */
                                                if (SelectFont(SetWindow, FONTSEL_BUFFER)) {
                                                        strcpy(BufferFontName, BufferFR->fo_Attr.ta_Name);
                                                        BufferFontSize = BufferFR->fo_Attr.ta_YSize;
                                                        BufferFontAttr.ta_YSize = BufferFontSize;

                                                        GT_SetGadgetAttrs(
                                                                Gadget[GID_BUFFERFONT],
                                                                SetWindow, NULL,
                                                                GTTX_Text, GetFontDesc(BufferFontDesc,
                                                                                                           BufferFontName,
                                                                                                           BufferFontSize),
                                                                TAG_DONE);
                                                        ReOpenMainWindow();
                                                        WindowToFront(SetWindow);
                                                }
                                                break;

                                        case GID_FORMATEDIT:
                                                OpenFormatWindow();
                                                break;

                                        case GID_FORMATCOPY:
                                                if (shift)
                                                        *LogFormat = '\0';
                                                else
                                                        strcpy(LogFormat, BufFormat);
                                                GT_SetGadgetAttrs(Gadget[GID_LOGFORMAT],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, LogFormat,
                                                                                  TAG_DONE);
                                                break;

                                        case GID_FILEIO:
                                                CurSettings.Setup.FileIOType = newval;
                                                break;

                                        case GID_OPENON:
                                                CurSettings.Setup.ScreenType = newval;
                                                break;

                                        case GID_LOGMODE:
                                                CurSettings.Setup.LogMode        = newval;
                                                SetLogGadget(newval, LG_REFRESH);
                                                break;

                                        case GID_HIDEMETHOD:
                                                if (!CxBase)
                                                        newval = 0;

                                                if (newval != CurSettings.Setup.HideMethod) {
                                                        int oldval = CurSettings.Setup.HideMethod;

                                                        CurSettings.Setup.HideMethod = newval;
                                                        if (newval == HIDE_NONE)
                                                                CleanupHotKey();
                                                        else if (oldval == HIDE_NONE) {
                                                                /*
                                                                 *              Switching out of hide state so
                                                                 *              re-activate hotkey
                                                                 */
                                                                InstallHotKey(CurSettings.Setup.HotKey);
                                                        }
                                                        SetMainHideState(newval);
                                                }
                                                break;

                                        case GID_HOTKEY:
                                        {
                                                char *msg = CurSettings.Setup.HotKey;

                                                strcpy(msg, STRVAL(GID_HOTKEY));
                                                InstallHotKey(msg);
                                                SetMainHideState(CurSettings.Setup.HideMethod);
                                                break;
                                        }

                                        case GID_SCREENNAME:
                                                strcpy(CurSettings.Setup.ScreenName,
                                                           STRVAL(GID_SCREENNAME));
                                                break;

                                        case GID_LOGFILE:
                                                strcpy(CurSettings.Setup.LogFile, STRVAL(GID_LOGFILE));
                                                break;

                                        case GID_LOGFORMAT:
                                                strcpy(CurSettings.Setup.LogfileFormat,
                                                           STRVAL(GID_LOGFORMAT));
                                                break;

                                        case GID_BUFFORMAT:
                                                str = STRVAL(GID_BUFFORMAT);
                                                if (strcmp(BufFormat, str) != 0) {
                                                        strcpy(BufFormat, str);
                                                        InstallNewFormat(NEW_STRING);
                                                }
                                                break;

                                        case GID_BUFFERSIZE:
                                                /*
                                                 *              We don't do anything here at all ... instead,
                                                 *              the update is handled when the user clicks
                                                 *              on USE.
                                                 */
                                                break;

                                        case GID_SETUSE:
                                                doneset = SEL_USE;
                                                break;

                                        case GID_SETCANCEL:
                                                doneset = SEL_CANCEL;
                                                break;

                                        case GID_SETUNDO:
                                                /*
                                                 *              Major bummer -- we need to manually update
                                                 *              ALL the string gadgets, since if the user
                                                 *              modified one of them and didn't press RETURN
                                                 *              we won't have picked it up and so we won't
                                                 *              know to undo it :-(
                                                 */
                                                InstallSettings(&SavedSetupSets, SET_SETUP);

#define UndoSet SavedSetupSets.Setup

                                                GT_SetGadgetAttrs(Gadget[GID_HOTKEY],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, UndoSet.HotKey,
                                                                                  TAG_DONE);
                                                GT_SetGadgetAttrs(Gadget[GID_SCREENNAME],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, UndoSet.ScreenName,
                                                                                  TAG_DONE);
                                                GT_SetGadgetAttrs(Gadget[GID_LOGFILE],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, UndoSet.LogFile,
                                                                                  TAG_DONE);
                                                GT_SetGadgetAttrs(Gadget[GID_BUFFORMAT],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, UndoSet.BufferFormat,
                                                                                  TAG_DONE);
                                                GT_SetGadgetAttrs(Gadget[GID_LOGFORMAT],
                                                                                  SetWindow, NULL,
                                                                                  GTST_String, UndoSet.LogfileFormat,
                                                                                  TAG_DONE);
                                                GT_SetGadgetAttrs(Gadget[GID_BUFFERSIZE],
                                                                                  SetWindow, NULL,
                                                                                  GTIN_Number, UndoSet.BufferSize,
                                                                                  TAG_DONE);
                                                break;
                                }
                                break;

                        case IDCMP_INACTIVEWINDOW:
                                if (activegad) {
                                        ShowGadget(SetWindow, Gadget[activegad], GADGET_UP);
                                        activegad = 0;
                                }
                                break;

                        case IDCMP_RAWKEY:
                        {
                                int upstroke = imsg->Code & 0x80;
                                int keypress = ConvertIMsgToChar(imsg);

                                gadid  = SetKeyboard[keypress];
                                if (activegad) {
                                        /*
                                         *              We're releasing a gadget that was pressed
                                         *              earlier, so handle it now
                                         */
                                        int samekey = (imsg->Code & 0x7f) == activekey;

                                        if (samekey && !upstroke)       /* Ignore repeated keys */
                                                break;

                                        ShowGadget(SetWindow, Gadget[activegad], GADGET_UP);
                                        if (samekey) {
                                                /*
                                                 *              Just released the key that was originally
                                                 *              pressed for this gadget, so now go and
                                                 *              handle the gadget action.
                                                 */
                                                gadid     = activegad;
                                                gad       = Gadget[gadid];
                                                activegad = 0;
                                                goto handle_set_gads;
                                        }

                                        /*
                                         *              If the above check didn't kick in, it means
                                         *              we got a downpress of a different key, so
                                         *              disable the gadget for this key and continue
                                         *              to press the other key.
                                         */
                                        activegad = 0;
                                }

                                if (imsg->Code == HELPKEY) {
                                        ShowAGuide(MSG(MSG_LINK_SETUPWIN));
                                        break;
                                }

                                /*
                                 *              Handle keyboard equivalent keypresses for gadgets
                                 */
                                if (gadid) {
                                        int numopts;

                                        gad = Gadget[gadid];

                                        switch (gadid) {
                                                case GID_HIDEMETHOD:
                                                        newval  = CurSettings.Setup.HideMethod;
                                                        numopts = HIDE_MAX;
                                                        goto handle_set_cycle;

                                                case GID_OPENON:
                                                        newval  = CurSettings.Setup.ScreenType;
                                                        numopts = SCREEN_MAX;
                                                        goto handle_set_cycle;

                                                case GID_LOGMODE:
                                                        newval  = CurSettings.Setup.LogMode;
                                                        numopts = LOGMODE_MAX;
                                                        goto handle_set_cycle;

                                                case GID_FILEIO:
                                                        newval  = CurSettings.Setup.FileIOType;
                                                        numopts = FILE_MAX;
handle_set_cycle:
                                                        newval++;
                                                        if (shift)
                                                                newval -= 2;
                                                        if (newval < 0)                 newval += numopts;
                                                        if (newval >= numopts)  newval -= numopts;
                                                        GT_SetGadgetAttrs(gad, SetWindow, NULL,
                                                                                          GTCY_Active, newval,
                                                                                          TAG_DONE);
                                                        goto handle_set_gads;

                                                case GID_BUFFERSIZE:
                                                case GID_BUFFORMAT:
                                                case GID_LOGFORMAT:
                                                case GID_HOTKEY:
                                                case GID_SCREENNAME:
                                                case GID_LOGFILE:
                                                        if (gadid != GID_LOGFILE || !shift) {
                                                                ActivateGadget(gad, SetWindow, NULL);
                                                                break;
                                                        }
                                                        /*
                                                         *              User pressed GID_LOGFILE with SHIFT
                                                         *              pressed, so change gadget type to
                                                         *              GID_FILEPROMPT and fall through to
                                                         *              button code.
                                                         */
                                                        gadid = GID_FILEPROMPT;
                                                        /* Fall through */

                                                case GID_SETUSE:
                                                case GID_SETUNDO:
                                                case GID_SETCANCEL:
                                                case GID_FORMATCOPY:
                                                case GID_FORMATEDIT:
                                                case GID_WINDOWFONT:
                                                case GID_BUFFERFONT:
                                                case GID_WFONTPROMPT:
                                                case GID_BFONTPROMPT:
                                                        if (gadid == GID_WINDOWFONT)
                                                                gadid = GID_WFONTPROMPT;
                                                        if (gadid == GID_BUFFERFONT)
                                                                gadid = GID_BFONTPROMPT;
                                                        gad = Gadget[gadid];
                                                        ShowGadget(SetWindow, gad, GADGET_DOWN);
                                                        activegad = gadid;
                                                        activekey = imsg->Code;
                                                        break;
                                        }
                                }
                                break;
                        }
                }
                GT_ReplyIMsg(imsg);
        }
        /*
         *              Handled all messages. Now check for possible USE/CANCEL
         */
        if (doneset == SEL_USE) {
                /*
                 *              Use the current settings. To be safe, we update all
                 *              our string gadgets in case they were modified but
                 *              we didn't catch the modification (i.e. user didn't press
                 *              Return).
                 */
                str = STRVAL(GID_BUFFORMAT);
                if (strcmp(BufFormat, str) != 0) {
                        strcpy(BufFormat, str);
                        InstallNewFormat(NEW_STRING);
                }
                strcpy(CurSettings.Setup.LogFile,                STRVAL(GID_LOGFILE));
                strcpy(CurSettings.Setup.ScreenName,     STRVAL(GID_SCREENNAME));
                strcpy(CurSettings.Setup.HotKey,                 STRVAL(GID_HOTKEY));
                strcpy(CurSettings.Setup.LogfileFormat,  STRVAL(GID_LOGFORMAT));
                strcpy(CurSettings.Setup.LogfileFormat,  STRVAL(GID_LOGFORMAT));
                val = atoi(STRVAL(GID_BUFFERSIZE));
                CloseSettingsWindow();
                /*
                 *              Now check if we've got a new buffer size --
                 *              if so, install it.
                 */
                if (val < 1)
                        val = 1;

                if (val != SavedSetupSets.Setup.BufferSize) {
                        CurSettings.Setup.BufferSize = val;
                        ClearWindowBuffer();
                        if (!SetTotalBufferSize(val * 1024, SETBUF_FORCENEW)) {
                                ShowError(MSG(MSG_ERROR_NOBUFMEM), val);
                                Cleanup(20);
                        }
                }
        } else if (doneset == SEL_CANCEL) {
                /*
                 *              Cancel the current settings, and restore those that
                 *              were in effect before we started this. We close
                 *              the settings window first in case the main window
                 *              needs to close and re-open (change of font) since
                 *              this looks cleaner.
                 *
                 *              Note that even though we saved the entire set of settings,
                 *              we only restore the SETUP settings, not all of them.
                 */
                CloseSettingsWindow();
                InstallSettings(&SavedSetupSets, SET_SETUP);
        }
}

/*
 *              ShowFuncOpts(options, firstid, lastid)
 *
 *              Updates the function window gadgets to reflect the new values
 *              in the specified options array.
 *
 *              The new options are compared with the existing options to see
 *              if they've changed, so you should make sure to call this function
 *              _before_ copying the new options onto the current settings.
 *
 *              Only gadgets from the first to the last ID given are affected.
 */
void ShowFuncOpts(UBYTE newopts[], int firstid, int lastid)
{
        int i;

        for (i = firstid; i <= lastid; i++) {
                if (Gadget[i] != NULL && newopts[i] != CurSettings.Func.Opts[i]) {
                        GT_SetGadgetAttrs(Gadget[i], FuncWindow, NULL,
                                                          GTCB_Checked, newopts[i],
                                                          TAG_DONE);
                }
        }
}

/*
 *              ResetFuncToSelected()
 *
 *              Changes the two cycle gadgets in the function window back to the
 *              SELECTED state, and updates the variables accordingly. Note
 *              that doesn't change the actual boolean settings themselves; in
 *              effect, it makes the current settings permanent by stopping you
 *              switching back to the SELECTED state to restore an older set
 *              by clicking on the cycle gadget yourself.
 */
void ResetFuncToSelected(void)
{
        if (FuncSystemCol != COL_SELECTED) {
                GT_SetGadgetAttrs(Gadget[GID_SELSYSTEM],
                                                  FuncWindow, NULL,
                                                  GTCY_Active, COL_SELECTED,
                                                  TAG_DONE);
        }
        if (FuncDOSCol != COL_SELECTED) {
                GT_SetGadgetAttrs(Gadget[GID_SELDOS],
                                                  FuncWindow, NULL,
                                                  GTCY_Active, COL_SELECTED,
                                                  TAG_DONE);
        }
        FuncSystemCol = COL_SELECTED;
        FuncDOSCol    = COL_SELECTED;
}

/*
 *              HandleFuncMsgs()
 *
 *              Processes all outstanding messages associated with the Functions
 *              gadget window. To be called whenever we get a functions event.
 *
 *              The only slightly complicated bit here concerns the two cycle
 *              gadgets which can be used to select all or none of either of
 *              the DOS and System columns, or to restore a previous setting
 *              installed before.
 */
void HandleFuncMsgs(void)
{
        static int activegad;                   /* Non-zero if gadget is depressed   */
        static int activekey;                   /* Keycode shortcut of activegad     */

        struct IntuiMessage *imsg;
        int donefunc = SEL_NONE;

        if (!FuncWindow)
                return;

        while ((imsg = GT_GetIMsg(FuncWindowPort)) != NULL) {
                struct Gadget *gad;
                int shift = (imsg->Qualifier & IE_SHIFT);
                LONG gadid;
                int newval;

                switch (imsg->Class) {
                        
                        case IDCMP_REFRESHWINDOW:
                                GT_BeginRefresh(FuncWindow);
                                GT_EndRefresh(FuncWindow, TRUE);
                                break;

                        case IDCMP_CLOSEWINDOW:
                                donefunc = SEL_USE;
                                break;

                        case IDCMP_MOUSEMOVE: 
                        case IDCMP_GADGETDOWN:
                        case IDCMP_GADGETUP:
                                gad    = (struct Gadget *)imsg->IAddress;
                                gadid  = gad->GadgetID;
                                newval = imsg->Code;

handle_func_gads:

                                switch (gadid) {

                                        case GID_MATCHNAME:
                                                strcpy(MatchName, STRVAL(GID_MATCHNAME));
                                                SetPattern(MatchName, IgnoreWBShell);
                                                break;

                                        case GID_SELSYSTEM:
                                        case GID_SELDOS:
                                                /*
                                                 *              Changing system column cycle gadget.
                                                 *              Update to show ALL, NONE or SELECTED
                                                 *              system gadgets.
                                                 */
                                        {
                                                int first    = FIRST_SYS_GADGET;
                                                int last     = LAST_SYS_GADGET;
                                                int numgads  = NUM_SYS_GADGETS;
                                                int *pcurval = &FuncSystemCol;

                                                if (gadid == GID_SELDOS) {
                                                        /*
                                                         *              Override above defaults with new
                                                         *              defaults for DOS cycle gadget
                                                         */
                                                        first    = FIRST_DOS_GADGET;
                                                        last     = LAST_DOS_GADGET;
                                                        numgads  = NUM_DOS_GADGETS;
                                                        pcurval  = &FuncDOSCol;
                                                }
                                                if (newval == *pcurval)
                                                        break;          /* Ignore no-op events */

                                                if (newval == COL_SELECTED) {
                                                        /*
                                                         *              Restore previously saved system col values
                                                         */
                                                        ShowFuncOpts(SavedCols, first, last);
                                                        memcpy(&CurSettings.Func.Opts[first],
                                                                   &SavedCols[first], numgads);
                                                } else {
                                                        /*
                                                         *              Select either ALL or NONE of the settings
                                                         *              in the system column.
                                                         */
                                                        int fillval = (newval == COL_ALL);

                                                        memset(&TempCols[first], fillval, numgads);
                                                        ShowFuncOpts(TempCols, first, last);
                                                                        
                                                        if (*pcurval == COL_SELECTED) {
                                                           /*
                                                            *           If we are moving out of the
                                                                *               selected state, then save the
                                                                *               existing settings for later.
                                                                */
                                                           memcpy(&SavedCols[first],
                                                                          &CurSettings.Func.Opts[first], numgads);
                                                        }
                                                        memset(&CurSettings.Func.Opts[first],
                                                                   fillval, numgads);
                                                }
                                                LoadFuncSettings(&CurSettings.Func);
                                                *pcurval = newval;
                                                break;
                                        }

                                        case GID_FUNCUSE:
                                                /*
                                                 *              Retain existing settings, and exit
                                                 */
                                                donefunc = SEL_USE;
                                                break;

                                        case GID_FUNCCANCEL:
                                                /*
                                                 *              Restore original settings and exit
                                                 */
                                                donefunc = SEL_CANCEL;
                                                break;

                                        case GID_FUNCUNDO:
                                                /*
                                                 *              Restore original settings but don't exit
                                                 */
                                                InstallSettings(&SavedFuncSets, SET_FUNC);
                                                break;

                                        default:
                                                if (gadid >= FIRST_BOOL_GADGET &&
                                                                                                gadid <= LAST_BOOL_GADGET) {
                                                        /*
                                                         *              Getting a new boolean gadget so update
                                                         *              CurSettings to reflect this.
                                                         */
                                                        CurSettings.Func.Opts[gadid] =
                                                                                (gad->Flags & GFLG_SELECTED) != 0;
                                                        LoadFuncSettings(&CurSettings.Func);
                                                        /*
                                                         *              Now check to see if we need to put
                                                         *              either column back into the selected
                                                         *              state again.
                                                         */
                                                        if (FuncSystemCol != COL_SELECTED &&
                                                                gadid >= FIRST_SYS_GADGET         &&
                                                            gadid <= LAST_SYS_GADGET)
                                                        {
                                                                FuncSystemCol = COL_SELECTED;
                                                                GT_SetGadgetAttrs(Gadget[GID_SELSYSTEM],
                                                                                                  FuncWindow, NULL,
                                                                                                  GTCY_Active, COL_SELECTED,
                                                                                                  TAG_DONE);
                                                        }
                                                        if (FuncDOSCol != COL_SELECTED  &&
                                                                gadid >= FIRST_DOS_GADGET       &&
                                                            gadid <= LAST_DOS_GADGET)
                                                        {
                                                                FuncDOSCol = COL_SELECTED;
                                                                GT_SetGadgetAttrs(Gadget[GID_SELDOS],
                                                                                                  FuncWindow, NULL,
                                                                                                  GTCY_Active, COL_SELECTED,
                                                                                                  TAG_DONE);
                                                        }
                                                }
                                                break;
                                }
                                break;

                        case IDCMP_INACTIVEWINDOW:
                                if (activegad) {
                                        ShowGadget(FuncWindow, Gadget[activegad], GADGET_UP);
                                        activegad = 0;
                                }
                                break;

                        case IDCMP_RAWKEY:
                        {
                                int upstroke = imsg->Code & 0x80;
                                int keypress = ConvertIMsgToChar(imsg);

                                gadid  = FuncKeyboard[keypress];
                                if (activegad) {
                                        /*
                                         *              We're releasing a gadget that was pressed
                                         *              earlier, so handle it now
                                         */
                                        int samekey = (imsg->Code & 0x7f) == activekey;

                                        if (samekey && !upstroke)       /* Ignore repeated keys */
                                                break;

                                        ShowGadget(FuncWindow, Gadget[activegad], GADGET_UP);
                                        if (samekey) {
                                                /*
                                                 *              Just released the key that was originally
                                                 *              pressed for this gadget, so now go and
                                                 *              handle the gadget action.
                                                 */
                                                gadid     = activegad;
                                                gad       = Gadget[gadid];
                                                activegad = 0;
                                                goto handle_func_gads;
                                        }
                                        /*
                                         *              If the above check didn't kick in, it means
                                         *              we got a downpress of a different key, so
                                         *              disable the gadget for this key and continue
                                         *              to press the other key.
                                         */
                                        activegad = 0;
                                }

                                if (imsg->Code == HELPKEY) {
                                        ShowAGuide(MSG(MSG_LINK_FUNCWIN));
                                        break;
                                }

                                /*
                                 *              Handle keyboard equivalents for gadgets
                                 */
                                gadid = FuncKeyboard[keypress];
                                if (gadid) {
                                        gad = Gadget[gadid];

                                        if (gadid >= FIRST_BOOL_GADGET &&
                                                                                gadid <= LAST_BOOL_GADGET) {
                                                /*
                                                 *              It's a boolean gadget so just invert its
                                                 *              current setting
                                                 */
                                                newval = !CurSettings.Func.Opts[gadid];
                                                GT_SetGadgetAttrs(Gadget[gadid], FuncWindow, NULL,
                                                                                  GTCB_Checked, newval, TAG_DONE);
                                                goto handle_func_gads;
                                        } else if (gadid == GID_SELSYSTEM || gadid == GID_SELDOS) {
                                                /*
                                                 *              One of the two cycle gadgets. Cycle forward
                                                 *              if the unshifted key was pressed or back
                                                 *              if the shifted version was pressed
                                                 */
                                                newval = FuncSystemCol;
                                                if (gadid == GID_SELDOS)
                                                        newval = FuncDOSCol;
                                                newval++;
                                                if (shift)
                                                        newval -= 2;
                                                if (newval < 0) newval = 2;
                                                if (newval > 2) newval = 0;
                                                GT_SetGadgetAttrs(gad, FuncWindow, NULL,
                                                                                  GTCY_Active, newval, TAG_DONE);
                                                goto handle_func_gads;
                                        } else if (gadid == GID_MATCHNAME) {
                                                /* 
                                                 *              The string gadget, so simply activate it
                                                 */
                                                ActivateGadget(gad, FuncWindow, NULL);
                                        } else {
                                                /*
                                                 *              It must be a button gadget so show it
                                                 *              selected first before processing it
                                                 */
                                                ShowGadget(FuncWindow, gad, GADGET_DOWN);
                                                activegad = gadid;
                                                activekey = imsg->Code;
                                        }
                                }
                                break;
                        }
                }
                GT_ReplyIMsg(imsg);
        }
        if (donefunc != SEL_NONE) {
                /*
                 *              Since we cache the function gadgets between window opens,
                 *              we need to ensure the two cycle gadgets are reset to
                 *              SELECTED mode ready for the next time we open the window.
                 *              (Ideally, we would remember the cycle state and use the
                 *              same state next time, but that's too much work!)
                 */
                ResetFuncToSelected();
        }
        if (donefunc == SEL_USE) {
                strcpy(MatchName, STRVAL(GID_MATCHNAME));
                SetPattern(MatchName, IgnoreWBShell);
                CloseFunctionWindow();
        } else if (donefunc == SEL_CANCEL) {
                CloseFunctionWindow();
                /*
                 *              Note that though we saved all the settings, we only
                 *              re-install the original function settings, not all
                 *              of them.
                 */
                InstallSettings(&SavedFuncSets, SET_FUNC);
        }
}

/*
 *              CreateFormatGadgets(gadgetfa, bufferfa, getsize, &width, &height)
 *
 *              Creates all the gadgets for the format window. If getsize
 *              is true, then the width and height values are filled in with
 *              the dimensions of the window needed to hold the gadgets. In
 *              this case, NULL is returned if the window would be too big to
 *              fit on the current screen, or non-NULL if width and height
 *              were successfully initialised.
 *
 *              gadgetfa gives the gadget font to use, bufferfa gives the buffer
 *              font (for the format listings).
 *
 *              If getsize is zero, then the actual gadgets are created and
 *              a pointer to the gadget list is returned, or NULL if an
 *              error occurred (typically, out of memory).
 */
struct Gadget *CreateFormatGadgets(struct TextAttr *gadgetfa,
                                                                   struct TextAttr *bufferfa,
                                                                   int getsize, int *pwidth, int *pheight)
{
        struct TextFont *gfont;
        struct TextFont *bfont;
        struct NewGadget ng;
        struct Gadget *gadlist;
        struct Gadget *gad;
        FieldInit *fi;
        int width;
        int height;
        int gwidth;
        int gheight;
        int gfonty;
        int bfontx;
        int bfonty;
        int topline;
        int maxtitlelen;
        int buttonwidth;
        int buttonspace;
        int headingspace;
        int outmargin;

        if (!FormVI) {
                FormVI = GetVisualInfoA(SnoopScreen, NULL);
                if (!FormVI)
                        return (NULL);
        }
        gfont = MyOpenFont(gadgetfa);
        if (!gfont)
                return (NULL);

        bfont = MyOpenFont(bufferfa);
        if (!bfont) {
                CloseFont(gfont);
                return (NULL);
        }
        if (bfont->tf_Flags & FPF_PROPORTIONAL) {
                CloseFont(bfont);
                CloseFont(gfont);
                return (NULL);
        }
        bfontx          = bfont->tf_XSize;
        bfonty          = bfont->tf_YSize;
        gfonty          = gfont->tf_YSize;
        gheight         = gfonty  + 3;
        gwidth          = gheight + 15;
        topline     = TitlebarHeight + gfonty/2;

        /*
         *              Now calculate the width of the window boxes which will contain
         *              the format sequences being displayed. The defaults box must be
         *              wide enough to hold "titlemsg..  %x" whereas the current format
         *              box must be wide enough to hold "titlemsg..  %20x".
         *
         *              Since the message titles may be localised, we calculate the
         *              longest string and work from that.
         */
        maxtitlelen = 0;
        for (fi = FieldTypes; fi->type != EF_END; fi++) {
                int len = strlen(MSG(fi->titlemsgid));

                if (len > maxtitlelen)
                        maxtitlelen = len;
        }
        /*
         *              The next two vars give the number of characters that will fit
         *              across each box. These is used when producing output.
         *
         *              For the left box, this is the length of the longest heading title,
         *              plus 2 for spacing after the heading name, another 2 for the
         *              format ID (%x) and a final 2 for the spacing on either side.
         *
         *              The right box is similar, but uses an additional 2 characters to
         *              display the field width of the selected item.
         */
        FBoxA_NumChars  = maxtitlelen   + 6;
        FBoxB_NumChars  = FBoxA_NumChars + 2;
        FBoxA_Width     = FBoxA_NumChars * bfontx + 4;
        FBoxB_Width     = FBoxB_NumChars * bfontx + 4;

        /*
         *              Create format strings for sprintf formatting later on. These
         *              look like this:
         *
         *                      FBoxA_FormString:       " %-??s  %%%lc "                (2 parameters)
         *                      FBoxB_FormString:       " %-??s  %%%02ld%lc "   (3 parameters)
         *
         *              and when strings are output using these templates, they look like:
         *
         *                      FBoxA_FormString:       " PaddedTitleName....  %x "
         *                      FBoxB_FormString:   " PaddedTitleName....  %12x "
         */
        mysprintf(FBoxA_FormString, " %%-%lds  %%%%%%lc ",               maxtitlelen);
        mysprintf(FBoxB_FormString, " %%-%lds  %%%%%%02ld%%lc ", maxtitlelen);

        /*
         *              Make sure we never have overlapping button gadgets (this might
         *              happen if we had a big gadget font and small buffer font).
         */
        buttonwidth = MaxTextLen(gfont, UseCancelUndoText) + 32;
        buttonspace = buttonwidth * 3 + FORM_MARGIN * 4 + BorderLeft + BorderRight;

        width = FBoxA_Width + FBoxB_Width + FORM_MARGIN * 4 +
                                                  BorderLeft  + BorderRight;

        outmargin = FORM_MARGIN;
        if (buttonspace > width)
                width = buttonspace;

        headingspace =  GetTextLen(gfont, MSG(MSG_AVAILABLE_GAD)) +
                                        GetTextLen(gfont, MSG(MSG_CURFORMAT_GAD)) +
                                        BorderLeft + BorderRight + FORM_MARGIN*3;

        if (headingspace > width)
                width = headingspace;

        /*
         *              Now calculate the dimensions of our two windows boxes. The left
         *              box displays the available format headings, and the right box
         *              displays the currently selected format headings.
         */
        FBoxSpacing             = bfonty + 2;
        FBoxA_Top               = topline + gfonty + 4;
        FBoxA_Height    = EF_COUNT * FBoxSpacing + 4;
        FBoxA_Left      = BorderLeft + outmargin;

        FBoxB_Top               = FBoxA_Top;
        FBoxB_Height    = FBoxA_Height;
        FBoxB_Left      = width - BorderRight - outmargin - FBoxB_Width;
         
        height = FBoxA_Top + FBoxA_Height + BorderBottom +
                         (gfonty + GadgetHeight) * 3;

        if (width > ScreenWidth || height > ScreenHeight) {
                CloseFont(gfont);
                CloseFont(bfont);
                return (NULL);
        }
        if (getsize) {
                CloseFont(gfont);
                CloseFont(bfont);
                *pwidth  = width;
                *pheight = height;
                return (struct Gadget *)(-1);
        }

        /*
         *              Now create our window gadgets. We have a total of 4 -- the
         *              Use, Undo, Cancel buttons, and the field width slider.
         */     
        gadlist = NULL;
        gad = CreateContext(&gadlist);
        if (!gad)
                goto fogad_failed;

        ng.ng_TextAttr          = gadgetfa;
        ng.ng_VisualInfo        = FormVI;
        ng.ng_Flags                     = PLACETEXT_IN;
        ng.ng_TopEdge           = height - BorderBottom - (gfonty+GadgetHeight) * 3/2;
        ng.ng_Height            = gfonty + GadgetHeight;
        ng.ng_Width                     = buttonwidth;

        ng.ng_LeftEdge          = FORM_MARGIN + BorderLeft;
        ng.ng_GadgetText        = MSG(MSG_USE_GAD);
        ng.ng_GadgetID          = GID_FORMUSE;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fogad_failed;
        Gadget[GID_FORMUSE] = gad;
        AddKeyShortcut(FormKeyboard, GID_FORMUSE, MSG_USE_GAD);
        
        ng.ng_LeftEdge          = width - BorderRight - FORM_MARGIN - ng.ng_Width;
        ng.ng_GadgetText        = MSG(MSG_CANCEL_GAD);
        ng.ng_GadgetID          = GID_FORMCANCEL;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fogad_failed;
        Gadget[GID_FORMCANCEL] = gad;
        AddKeyShortcut(FormKeyboard, GID_FORMCANCEL, MSG_CANCEL_GAD);
        
        ng.ng_LeftEdge          = (ng.ng_LeftEdge + BorderLeft + FORM_MARGIN) / 2;
        ng.ng_GadgetText        = MSG(MSG_UNDO_GAD);
        ng.ng_GadgetID          = GID_FORMUNDO;

        gad = CreateGadget(BUTTON_KIND,         gad, &ng,
                                           GT_Underscore,       '_',
                                           TAG_DONE);
        if (!gad)
                goto fogad_failed;
        Gadget[GID_FORMUNDO] = gad;
        AddKeyShortcut(FormKeyboard, GID_FORMUNDO, MSG_UNDO_GAD);

        /*
         *              Create the slider gadget. We need to leave room on the left for
         *              the title, and on the right for the current value.
         */
        ng.ng_LeftEdge          = FORM_MARGIN + BorderLeft + 8 +
                                                  GetTextLen(gfont, MSG(MSG_FIELDWIDTH_GAD));
        ng.ng_Width                     = width - FORM_MARGIN - ng.ng_LeftEdge -
                                                  BorderLeft - BorderRight - GetTextLen(gfont, "99 ");
        ng.ng_Height            = gfonty + 3;
        ng.ng_TopEdge           = (ng.ng_TopEdge + FBoxA_Top + FBoxA_Height -
                                                   ng.ng_Height) / 2;
        ng.ng_GadgetText        = MSG(MSG_FIELDWIDTH_GAD);
        ng.ng_GadgetID          = GID_FORMWIDTH;
        ng.ng_Flags                     = PLACETEXT_LEFT;

        gad = CreateGadget(SLIDER_KIND,                 gad, &ng,
                                           GTSL_LevelFormat,    "%2ld ",
                                           GTSL_LevelPlace,             PLACETEXT_RIGHT,
                                           GTSL_Level,                  1,
                                           GTSL_Min,                    1,
                                           GTSL_Max,                    MAX_FIELD_LEN,
                                           GTSL_MaxLevelLen,    2,
                                           GT_Underscore,               '_',
                                           GA_Disabled,                 TRUE,
                                           GA_RelVerify,                TRUE,
                                           TAG_DONE);
        if (!gad)
                goto fogad_failed;
        Gadget[GID_FORMWIDTH] = gad;
        AddKeyShortcut(FormKeyboard, GID_FORMWIDTH, MSG_FIELDWIDTH_GAD);

        FormGadFont = gfont;
        FormBufFont = bfont;
        return (gadlist);

fogad_failed:
        if (gadlist)
                FreeGadgets(gadlist);
        CloseFont(gfont);
        CloseFont(bfont);
        return (NULL);
}

/*
 *              ShowFormatLine(line, select)
 *
 *              Outputs the numbered line of text in the right-hand box. If the
 *              number given is too high, then a blank line is output instead.
 *              The text will be of the form "HeadingName.... %02x" according
 *              to the current contents of the line.
 *
 *              If select is FORMAT_SELECTED, the line will be highlighted, else
 *              it will be output normally.
 */
void ShowFormatLine(int line, int select)
{
        int bfontbaseline          = FormBufFont->tf_Baseline;
        int boxwidth               = FormBufFont->tf_XSize * FBoxB_NumChars;
        struct RastPort *rport = FormWindow->RPort;
        char viewbuf[60];
        int fillpen = 0;
        int textpen = 1;
        int textrow;
        
        if (select == FORMAT_SELECTED) {
                fillpen = ScreenDI->dri_Pens[FILLPEN];
                textpen = ScreenDI->dri_Pens[FILLTEXTPEN];
        }
        SetFont(rport, FormBufFont);
        SetAPen(rport, textpen);
        SetBPen(rport, fillpen);
        SetDrMd(rport, JAM2);

        if (line < 0 || line >= EF_COUNT)
                return;

        if (line < FBoxB_CurLines) {
                FieldInit *fi = &FieldTypes[CurrentFields[line].type];

                mysprintf(viewbuf, FBoxB_FormString, MSG(fi->titlemsgid),
                                                   CurrentFields[line].width, fi->idchar);
        } else {
                memset(viewbuf, ' ', FBoxB_NumChars);
        }
        textrow = FBoxB_Top + line*FBoxSpacing + 3;
        Move(rport, FBoxB_Left + 2, textrow + bfontbaseline);
        Text(rport, viewbuf, FBoxB_NumChars);

        /*
         *              Now draw highlight on top and bottom of selected line
         *              (or erase, as the case may be). This fills out the
         *              selection area and makes it look much better.
         */
        SetAPen(rport, fillpen);
        Move(rport, FBoxB_Left + 2, textrow - 1);
        Draw(rport, FBoxB_Left + 1 + boxwidth, textrow - 1);
        Move(rport, FBoxB_Left + 2, textrow + FormBufFont->tf_YSize);
        Draw(rport, FBoxB_Left + 1 + boxwidth, textrow + FormBufFont->tf_YSize);
}

/*
 *              ShowDragSelect(pos, select)
 *
 *              Highlights the specified region to indicate where the currently
 *              dragged field will end up if it is released here.
 *
 *              If pos is FBOX_NOSELECT, no drop area is active and no output is
 *              produced. If pos is FBOX_SELECTLEFT, then the left box is active
 *              and it is given an inverse highlight around the interior of the
 *              box giving a bezel effect.
 *
 *              If pos is in the range 0 ... EF_COUNT, then it corresponds to an
 *              insertion point between two lines in the right-most box. This is
 *              show by a two-pixel high highlight line drawn between those two
 *              lines.
 *
 *              If select is FORMAT_SELECTED, the feature is drawn; if select is
 *              FORMAT_UNSELECTED, the feature is instead erased. Note that after
 *              calling this function, it may be necessary to redisplay any
 *              current highlight bar in the right box (see ShowFormatLine) since
 *              there is a one-pixel overlap between the highlight bar and the
 *              rendering done here.
 */
void ShowDragSelect(int pos, int select)
{
        struct RastPort *rport = FormWindow->RPort;
        int black = 1;
        int white = 2; 

        if (pos == FBOX_NOSELECT)
                return;
        
        if (select == FORMAT_UNSELECTED) {
                black = 0;
                white = 0;
        }
        SetDrMd(rport, JAM2);
        if (pos == FBOX_SELECTLEFT) {
                /*
                 *              Draw bezel inside left box
                 */
                int boxleft  = FBoxA_Left + 2;
                int boxtop       = FBoxA_Top  + 1;
                int boxright = FBoxA_Left + FBoxA_Width  - 3;
                int boxbot   = FBoxA_Top  + FBoxA_Height - 2;

                SetAPen(rport, black);
                Move(rport, boxleft,    boxbot);
                Draw(rport, boxleft,    boxtop);
                Move(rport, boxleft+1,  boxbot-1);
                Draw(rport, boxleft+1,  boxtop);
                Draw(rport, boxright-1, boxtop);

                SetAPen(rport, white);
                Move(rport, boxright,   boxtop);
                Draw(rport, boxright,   boxbot);
                Move(rport, boxright-1, boxtop);
                Draw(rport, boxright-1, boxbot);
                Draw(rport, boxleft-1,  boxbot);
        } else if (pos >= 0 && pos <= EF_COUNT) {
                /*
                 *              Draw thin selection line in right box
                 */
                int line = FBoxB_Top + 1 + pos * FBoxSpacing;

                SetAPen(rport, white);
                Move(rport, FBoxB_Left+2,                               line);
                Draw(rport, FBoxB_Left+FBoxB_Width-3,   line);
                SetAPen(rport, black);
                Move(rport, FBoxB_Left+2,                               line+1);
                Draw(rport, FBoxB_Left+FBoxB_Width-3,   line+1);
        }
}

/*
 *              RedrawFormatWindow()
 *
 *              Redraws all the parts of the format window that are generated by
 *              hand: this includes the two bevelled boxes containing the available
 *              format fields, the headings for those boxes, and the current contens
 *              of those boxes, including highlighting where appropriate.
 */
void RedrawFormatWindow(void)
{
        char viewbuf[60];
        struct RastPort *rport = FormWindow->RPort;
        int gfonty                = FormGadFont->tf_YSize;
        int gfontbaseline = FormGadFont->tf_Baseline;
        int bfontbaseline = FormBufFont->tf_Baseline;
        int line;
        int headpos;
        EditEvent *ee;

        DrawBevelBox(rport, FBoxA_Left, FBoxA_Top, FBoxA_Width, FBoxA_Height,
                                                GT_VisualInfo, FormVI,
                                                TAG_DONE);

        DrawBevelBox(rport, FBoxB_Left, FBoxB_Top, FBoxB_Width, FBoxB_Height,
                                                GT_VisualInfo, FormVI,
                                                TAG_DONE);

        SetAPen(rport, 1);
        SetBPen(rport, 0);
        SetDrMd(rport, JAM2);
        SetFont(rport, FormGadFont);

        /*
         *              When outputting the box headings, we adjust the centering
         *              to ensure that headings wider than the boxes don't get
         *              pushed into the window borders.
         */
#define CENTRE(left,width,msgid)        \
                ((width - GetTextLen(FormGadFont, MSG(msgid)))/2 + left)

        headpos = CENTRE(FBoxA_Left, FBoxA_Width, MSG_AVAILABLE_GAD);
        if (headpos < (BorderLeft + 4))
                headpos = FBoxA_Left; /* was: BorderLeft + 4; */

        Move(rport, headpos, FBoxA_Top - gfonty + gfontbaseline - 4);
        Text(rport, MSG(MSG_AVAILABLE_GAD), strlen(MSG(MSG_AVAILABLE_GAD)));

        headpos = CENTRE(FBoxB_Left, FBoxB_Width, MSG_CURFORMAT_GAD);
        if (headpos < (FBoxB_Left - 4))
                headpos = FBoxB_Left + FBoxB_Width -
                                                           GetTextLen(FormGadFont, MSG(MSG_CURFORMAT_GAD));

        Move(rport, headpos, FBoxB_Top - gfonty + gfontbaseline - 4);
        Text(rport, MSG(MSG_CURFORMAT_GAD), strlen(MSG(MSG_CURFORMAT_GAD)));

        /*
         *              Now redraw the text inside the left box (available headings)
         */
        SetFont(rport, FormBufFont);
        ee = &AvailableFields[0];
        for (line = 0; line < (FBoxB_Height - 4); line += FBoxSpacing) {
                if (ee->type != END_EDITLIST) {
                        FieldInit *fi = &FieldTypes[ee->type];
                        mysprintf(viewbuf, FBoxA_FormString, MSG(fi->titlemsgid),
                                                                                                 fi->idchar);
                        ee++;
                } else {
                        memset(viewbuf, ' ', FBoxA_NumChars);
                }
                Move(rport, FBoxA_Left + 2, FBoxA_Top + line + bfontbaseline + 3);
                Text(rport, viewbuf, FBoxA_NumChars);
        }

        /*
         *              Redraw the text inside the right box (currently selected headings)
         */
        ee = &CurrentFields[0];
        for (line = 0; line < EF_COUNT; line++) {
                if (line == FBoxSelected)
                        ShowFormatLine(line, FORMAT_SELECTED);
                else
                        ShowFormatLine(line, FORMAT_UNSELECTED);
        }
}

/*
 *              ConvertListToFormat(list, format)
 *
 *              Converts the passed in EditList to a format list which can be
 *              used for updating the main SnoopDos display. Returns the
 *              total width (in characters) occupied by the format.
 */
int ConvertListToFormat(EditEvent *ee, EventFormat *evformat)
{
        int width = 0;

        while (ee->type != END_EDITLIST) {
                width               += ee->width + 1; /* Include space between cols */
                evformat->type           = FieldTypes[ee->type].type;
                evformat->width          = ee->width;
                evformat->titlemsgid = FieldTypes[ee->type].titlemsgid;
                evformat++;
                ee++;
        }
        evformat->type = EF_END;
        if (width)
                width--;                /* Don't count extra space at end */
        return (width);
}

/*
 *              CreateFormatLists(format)
 *
 *              Parses the passed-in format string and creates the two lists
 *              used by the format editor window to allow the user to re-arrange
 *              the format.
 *
 *              Should be called before RedrawFormatWindow() is called for the
 *              first time.
 *
 *              Also initialises some format globals related to the list boxes.
 */
void CreateFormatLists(EventFormat *evformat)
{
        EditEvent *ee;
        FieldInit *fi;
        EventFormat *ef;

        /*
         *              Now build the lists for the two boxes. The right box contains
         *              all items in the format string (and in the same order that they
         *              appear in the format string). The left box contains all the
         *              items that are NOT in the format string, in the presorted
         *              order that they appear in the FieldTypes[] initialiser array.
         *
         *              Note that the two edit lists contain indexes directly into
         *              the FieldTypes array, rather than specific event types.
         *              Thus, we store the offset into the array when we find a match
         *              rather than fi->type.
         */
        ee = &CurrentFields[0];
        for (ef = evformat; ef->type != EF_END; ef++) {
                for (fi = FieldTypes; fi->type != ef->type && fi->type != EF_END; fi++)
                        ;
                ee->type  = fi - FieldTypes;
                ee->width = ef->width;
                ee++;
        }
        ee->type = END_EDITLIST;
        FBoxB_CurLines = ee - &CurrentFields[0];

        ee = &AvailableFields[0];
        for (fi = FieldTypes; fi->type != EF_END; fi++) {
                for (ef = BufferEFormat; ef->type != fi->type && ef->type != EF_END;
                                                                ef++)
                        ;
                if (ef->type != fi->type) {     /* Not in current format so add it here */
                        ee->type = fi - &FieldTypes[0];
                        ee++;
                }
        }
        ee->type = END_EDITLIST;

        FBoxA_CurLines          = ee - &AvailableFields[0];
        FBoxSelected            = FBOX_NOSELECT;
        FBoxDragSelect          = FBOX_NOSELECT;
        FormatCurLMB            = 0;    /* Left mouse button not selected at start */
        FormatMovingField       = 0;
        /*
         *              Ensure slider gadget is disabled (it might have been left
         *              enabled from an earlier time, which can be confusing).
         */
        if (FormWindow) {
                GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
                                                  GA_Disabled, TRUE,
                                                  TAG_DONE);
        }
}

/*
 *              OpenFormatWindow()
 *
 *              Opens the format editor window with all the format gadgets.
 *              Returns TRUE for success, FALSE for failure.
 *              The window will contain all necessary gadgets.
 *
 *              As with the Settings window, we try a variety of fonts until we
 *              find one that fits onto the screen.
 */
int OpenFormatWindow(void)
{
        static struct TextAttr formgfontattr =
                { "empty-storage-for-format1-fonts", 0, FS_NORMAL, FPB_DISKFONT};
        static struct TextAttr formbfontattr =
                { "empty-storage-for-format2-fonts", 0, FS_NORMAL, FPB_DISKFONT};

        static int width;               /* Maintained from call to call */
        static int height;

        int left = CurSettings.FormWinLeft;
        int top  = CurSettings.FormWinTop;
        struct TextAttr *gadgetfa;
        struct TextAttr *bufferfa;
        struct Window *win;
        int i;

        CheckSegTracker();
        if (FormWindow) {
                WindowToFront(FormWindow);
                ActivateWindow(FormWindow);
                return (TRUE);
        }
        if (!CheckForScreen())
                return (FALSE);

        strcpy(FormatSaveFormat, BufFormat);    /* Save for possible Undo */

        /*
         *              Now try all combinations of gadget and buffer font until we
         *              find a font combination that fits on our screen
         */
        for (i = 0; gadgetfa = MainWindowFontList[i].gadgetfa; i++) {
                bufferfa = MainWindowFontList[i].bufferfa;
                if (CreateFormatGadgets(gadgetfa, bufferfa, TRUE, &width, &height))
                        break;
        }
        if (!gadgetfa) {
                ShowError(MSG(MSG_ERROR_OPENFORMAT));
                return (FALSE);
        }
        /*
         *              Now copy our font attributes so that our gadgets won't strangely
         *              get changed behind the scenes if we update our fonts
         */
        strcpy(formgfontattr.ta_Name, gadgetfa->ta_Name);
        formgfontattr.ta_YSize = gadgetfa->ta_YSize;
        gadgetfa = &formgfontattr;

        strcpy(formbfontattr.ta_Name, bufferfa->ta_Name);
        formbfontattr.ta_YSize = bufferfa->ta_YSize;
        bufferfa = &formbfontattr;

        if (left == -1)  left = (ScreenWidth  - width)  / 2;
        if (top  == -1)  top  = (ScreenHeight - height) / 2;

        win = OpenWindowTags(NULL,
                                                 WA_Title,                      MSG(MSG_FORMAT_TITLE),
                                                 WA_IDCMP,                      IDCMP_CLOSEWINDOW        |
                                                                                        IDCMP_REFRESHWINDOW      |
                                                                                        IDCMP_NEWSIZE            |
                                                                                        IDCMP_RAWKEY             |
                                                                                        IDCMP_MOUSEBUTTONS       |
                                                                                        IDCMP_INACTIVEWINDOW |
                                                                                        BUTTONIDCMP                      |
                                                                                        SLIDERIDCMP,
                                                 WA_Left,                       left,
                                                 WA_Top,                        top,
                                                 WA_Width,                      width,
                                                 WA_Height,                     height,
                                                 WA_Flags,                      WFLG_DRAGBAR             |
                                                                                        WFLG_DEPTHGADGET         |
                                                                                        WFLG_ACTIVATE            |
                                                                                        WFLG_RMBTRAP             |
                                                                                        WFLG_NEWLOOKMENUS,
                                                 RefreshTag,            TRUE,
                                                 WA_NoCareRefresh,      NoCareRefreshBool,
                                                 WA_PubScreen,          SnoopScreen,
                                                 TAG_DONE);
        if (!win) {
                ShowError(MSG(MSG_ERROR_OPENFORMAT));
                return (FALSE);
        }

        if (!FormGadList) {
                FormGadList = CreateFormatGadgets(gadgetfa, bufferfa, 0, 0, 0);
                if (!FormGadList) {
                        CloseWindow(win);
                        ShowError(MSG(MSG_ERROR_OPENFORMAT));
                        return (FALSE);
                }
        }

        /*
         *              Now try and create our BOBs for the window
         */
        TextBob = CreateBob(win->RPort, FBoxB_Width + 8,
                                                                    FormBufFont->tf_YSize + 4, 1);
        if (!TextBob) {
                ShowError(MSG(MSG_ERROR_OPENFORMAT));
                FreeGadgets(FormGadList);
                FormGadList     = NULL;
                CloseWindow(win);
                return (FALSE);
        }
        AddBob(&TextBob->bob, win->RPort);

        /*
         *              Now add gadgets to screen window, and then setup the current
         *              format string for editing.
         */
        AddGList(win, FormGadList, -1, -1, NULL);
        RefreshGList(FormGadList, win, NULL, -1);
        GT_RefreshWindow(win, NULL);

        /*
         *              All done, so initialise some globals and return
         */
        FormWindow              = win;
        FormWindowPort  = win->UserPort;
        FormWindowMask  = 1 << FormWindowPort->mp_SigBit;

        CreateFormatLists(BufferEFormat);
        /*
         *              Finally, draw our window to reflect this
         */
        RedrawFormatWindow();
        if (DisableNestCount)
                DisableWindow(FormWindow, &FormRequester);
        return (TRUE);
}

/*
 *              CloseFormatWindow(void)
 *
 *              Close the format window and free gadgets associated with the window.
 */
void CloseFormatWindow(void)
{
        if (FormWindow) {
                if (TextBob) {
                        FreeBob(TextBob);
                        TextBob = NULL;
                }
                RecordWindowSizes();
                CloseWindow(FormWindow);
                CloseFont(FormGadFont);
                CloseFont(FormBufFont);
                FormWindow     = NULL;
                FormWindowMask = 0;
                FormGadFont        = NULL;
                FormBufFont        = NULL;
        }
        if (FormGadList) {
                FreeGadgets(FormGadList);
                FormGadList = NULL;
        }
        if (FormVI) {
                FreeVisualInfo(FormVI);
                FormVI = NULL;
        }
}
 
/*
 *              FormatHitDetect(x, y)
 *
 *              Does a bounds check for the specified X/Y co-ordinates against the
 *              format editor window. A return value in the range 0..EF_COUNT-1
 *              indicates that line number in the right hand window was hit.
 *
 *              A return of FBOX_SELECTLEFT indicates that the left box was selected
 *              (not sure which line; doesn't really matter).
 *
 *              A return of FBOX_NOSELECT indicates neither box was selected.
 */
int FormatHitDetect(int x, int y)
{
        if (x >= FBoxA_Left && x <= (FBoxA_Left + FBoxA_Width  - 1) &&
                y >= FBoxA_Top  && y <= (FBoxA_Top  + FBoxA_Height - 1))
        {
                return (FBOX_SELECTLEFT);
        }
        
        if (x >= FBoxB_Left && x <= (FBoxB_Left + FBoxB_Width  - 1) &&
                y >= FBoxB_Top  && y <= (FBoxB_Top  + FBoxB_Height - 1))
        {
                if (y <= FBoxB_Top + FBoxSpacing + 2)
                        return (0);
                else
                        return (y - FBoxB_Top - 2) / FBoxSpacing;
        }
        return (FBOX_NOSELECT);
}

/*
 *              InstallNewFormat(type)
 *
 *              Installs the currently defined format into the main window for
 *              the user to see immediately.
 *
 *              If type is NEW_LISTVIEW, then the new format is assumed to be
 *              from the format editor window's CurrentFields list. The
 *              setup window, if open, will have its own format string updated
 *              accordingly.
 *
 *              If type is NEW_STRING, then the new format is assumed to be
 *              stored in the BufFormat string. The format window, if open,
 *              will have its own format listview updated accordingly.
 *
 *              If the buffer string is completely invalid (i.e. evaluates to
 *              an empty format) then it will be replaced with an empty string.
 */
void InstallNewFormat(int type)
{
        if (type == NEW_LISTVIEW) {
                BufferWidth = ConvertListToFormat(CurrentFields, BufferEFormat);
        } else /* type == NEW_STRING */ {
                BufferWidth = ParseFormatString(BufFormat, BufferEFormat,
                                                                                                   MAX_FORM_LEN);
                if (BufferWidth == 0) {
                        BufferWidth = ParseFormatString(DefaultSettings.Setup.BufferFormat,
                                                                                        BufferEFormat, MAX_FORM_LEN);
                }
                if (FormWindow) {
                        CreateFormatLists(BufferEFormat);
                        RedrawFormatWindow();
                }
        }
        BuildFormatString(BufferEFormat, BufFormat, MAX_FORM_LEN);
        if (SetWindow) {
                GT_SetGadgetAttrs(Gadget[GID_BUFFORMAT], SetWindow, NULL,
                                                  GTST_String,  BufFormat,
                                                  TAG_DONE);
        }
        ClearMainRHS = 1;
        InitMainMargins();
        UpdateMainHScroll();
        RedrawMainWindow();
}

/*
 *              HandleFormatMsgs()
 *
 *              Processes all outstanding messages associated with the Format
 *              editor gadget window. To be called whenever we get a format event.
 */
void HandleFormatMsgs(void)
{
        static int activegad;                   /* Non-zero if gadget is depressed   */
        static int activekey;                   /* Keycode shortcut of activegad     */

        struct IntuiMessage *imsg;
        int doneform = SEL_NONE;
        int hit;
        int lmb;
        int rmb;

        if (!FormWindow)
                return;

        while ((imsg = GT_GetIMsg(FormWindowPort)) != NULL) {
                struct Gadget *gad;
                int dxwidth = 0;                                /* Amount to adjust width by */
                int newrow  = FBoxSelected;             /* Currently selected row        */
                int gadid;

                switch (imsg->Class) {
                        
                        case IDCMP_CLOSEWINDOW:
                                doneform = SEL_USE;
                                break;

                        case IDCMP_REFRESHWINDOW:
                                GT_BeginRefresh(FormWindow);
                                RedrawFormatWindow();
                                GT_EndRefresh(FormWindow, TRUE);
                                break;

                        case IDCMP_MOUSEBUTTONS:
                                lmb = imsg->Qualifier & IEQUALIFIER_LEFTBUTTON;
                                rmb = imsg->Qualifier & IEQUALIFIER_RBUTTON;
                                /*
                                 *              If we're already dragging something, then the
                                 *              right mouse button allows it to be cancelled.
                                 */
                                if (FormatMovingField && rmb)
                                        goto cancel_moving_field;

                                if (lmb == FormatCurLMB)        /* Ignore spurious events */
                                        break;

                                hit = FormatHitDetect(imsg->MouseX, imsg->MouseY);
                                if (lmb) {
                                        /*
                                         *              User is trying to select or pick-up a new
                                         *              event.
                                         */
                                        if (hit >= 0 && hit < FBoxB_CurLines &&
                                                                                                hit != FBoxSelected) {
                                                if (FBoxSelected >= 0 && FBoxSelected < FBoxB_CurLines)
                                                        ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
                                                FBoxSelected = hit;
                                                ShowFormatLine(hit, FORMAT_SELECTED);
                                                GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
                                                                                  FormWindow,  NULL,
                                                                                  GA_Disabled, FALSE,
                                                                                  GTSL_Level, CurrentFields[hit].width,
                                                                                  TAG_DONE);
                                        }
                                        if (hit != FBOX_NOSELECT) {
                                                /*
                                                 *              Pick-up the icon under mouse
                                                 */
                                                if (PickupBob(hit, imsg->MouseX, imsg->MouseY)) {
                                                        FormatMovingField = 1;
                                                        ReportMouse(1, FormWindow);     /* Enable mouse moves */
                                                }
                                        }
                                } else {
                                        /*
                                         *              We might be finished doing a drag of a field
                                         *              from one position to another -- if so, drop the
                                         *              field in it's new place.
                                         */
                                        if (FormatMovingField) {
                                                int curdragselect = FBoxDragSelect;

                                                FormatMovingField = 0;
                                                ReportMouse(0, FormWindow);     /* Disable mouse moves  */
                                                DropBob();
                                                if (curdragselect != FBOX_NOSELECT)
                                                        InstallNewFormat(NEW_LISTVIEW);
                                        }
                                }
                                FormatCurLMB = lmb;
                                break;
                                                
                        case IDCMP_MOUSEMOVE: 
                                if (FormatMovingField) {
                                        UpdateBob(imsg->MouseX, imsg->MouseY);
                                        break;
                                }

                                /*
                                 *              Fall through to handle MOUSEMOVE for slider gadget
                                 */

                        case IDCMP_GADGETDOWN:
                        case IDCMP_GADGETUP:
                                gad   = (struct Gadget *)imsg->IAddress;
                                gadid = gad->GadgetID;

handle_form_gads:
                                switch (gadid) {

                                        case GID_FORMUSE:
                                                doneform = SEL_USE;
                                                break;

                                        case GID_FORMCANCEL:
                                                doneform = SEL_CANCEL;
                                                break;

                                        case GID_FORMUNDO:
                                                strcpy(BufFormat, FormatSaveFormat);
                                                InstallNewFormat(NEW_STRING);
                                                break;

                                        case GID_FORMWIDTH:
                                                /*
                                                 *              The slider has been moved, so update the
                                                 *              currently selected item accordingly.
                                                 */
                                                if (FBoxSelected >= 0 &&
                                                                                        FBoxSelected < FBoxB_CurLines) {
                                                        CurrentFields[FBoxSelected].width = imsg->Code;
                                                        ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
                                                        if (imsg->Class == IDCMP_GADGETUP)
                                                                InstallNewFormat(NEW_LISTVIEW);
                                                }
                                                break;
                                }
                                break;

                        case IDCMP_INACTIVEWINDOW:
                                /*
                                 *              Cancel any currently pressed button
                                 */
                                if (activegad) {
                                        ShowGadget(FormWindow, Gadget[activegad], GADGET_UP);
                                        activegad = 0;
                                }
                                /*
                                 *              If we're dragging something and the window
                                 *              goes inactive, drop it. Otherwise, when the
                                 *              window is reactivated, it will still be attached
                                 *              to the mouse pointer!
                                 */
                                if (FormatMovingField) {
                                        FormatCurLMB      = 0;
cancel_moving_field:
                                        ReportMouse(0, FormWindow);
                                        UpdateBob(0, -9999);            /* Make position invalid */
                                        DropBob();
                                        FormatMovingField = 0;
                                        if (imsg->Class == IDCMP_INACTIVEWINDOW) {
                                                /*
                                                 *              If we went inactive, then the act of
                                                 *              erasing the BOB has probably resulted
                                                 *              in a portion of the window frame being
                                                 *              restored in its active colours, even
                                                 *              though the window is now inactive -- so
                                                 *              we refresh it to get around this.
                                                 */
                                                RefreshWindowFrame(FormWindow);
                                        }
                                }
                                break;
                                        
                        case IDCMP_RAWKEY:
                        {
                                int shift    = imsg->Qualifier & IE_SHIFT;
                                int altctrl  = imsg->Qualifier & (IE_ALT | IE_CTRL);
                                int upstroke = imsg->Code & 0x80;
                                int keypress = ConvertIMsgToChar(imsg);

                                /*
                                 *              Handle keyboard shortcuts
                                 */
                                if (FormatMovingField && keypress == '\033') {
                                        /*
                                         *              ESC cancels any drag currently in operation
                                         */
                                        goto cancel_moving_field;
                                }

                                gadid  = FormKeyboard[keypress];
                                if (activegad) {
                                        /*
                                         *              We're releasing a gadget that was pressed
                                         *              earlier, so handle it now
                                         */
                                        int samekey = (imsg->Code & 0x7f) == activekey;

                                        if (samekey && !upstroke)       /* Ignore repeated keys */
                                                break;

                                        ShowGadget(FormWindow, Gadget[activegad], GADGET_UP);
                                        if (samekey) {
                                                /*
                                                 *              Just released the key that was originally
                                                 *              pressed for this gadget, so now go and
                                                 *              handle the gadget action.
                                                 */
                                                gadid     = activegad;
                                                gad       = Gadget[gadid];
                                                activegad = 0;
                                                goto handle_form_gads;
                                        }

                                        /*
                                         *              If the above check didn't kick in, it means
                                         *              we got a downpress of a different key, so
                                         *              disable the gadget for this key and continue
                                         *              to press the other key.
                                         */
                                        activegad = 0;
                                }

                                /*
                                 *              Check for normal gadget keyboard shortcuts
                                 */
                                if (gadid) {
                                        gad = Gadget[gadid];
                                        if (gadid == GID_FORMWIDTH) {
                                                /*
                                                 *              Got a slider adjustment request
                                                 */
                                                if (imsg->Qualifier & IE_SHIFT)
                                                        dxwidth = -1;
                                                else
                                                        dxwidth = 1;

                                                if (newrow == FBOX_NOSELECT)
                                                        newrow = 0;     /* If no row active, pick first row */

                                                if (FBoxB_CurLines == 0)  /* Ignore if box is empty */
                                                        break;

                                                goto format_move_slider;
                                        }
                                        /*
                                         *              Must be a simple button press so highlight
                                         *              the button and handle accordingly
                                         */
                                        ShowGadget(FormWindow, gad, GADGET_DOWN);
                                        activegad = gadid;
                                        activekey = imsg->Code;
                                        break;
                                }

                                /*
                                 *              Handle cursor movement. This lets us choose a
                                 *              new item in the format box, or adjust the width
                                 *              of an existing item.
                                 */
                                if (!FBoxB_CurLines)    /* A format must exist */
                                        break;
                                
                                if (newrow == FBOX_NOSELECT)
                                        newrow = -1;    /* Ensure cursor down selects item 0 */

                                switch (imsg->Code) {
                                        case CURSORUP:
                                                if (newrow > 0 && !(shift | altctrl))
                                                        newrow--;
                                                else
                                                        newrow = 0;
                                                break;

                                        case CURSORDOWN:
                                                if (shift | altctrl)
                                                        newrow = FBoxB_CurLines - 1;
                                                else if (newrow < (FBoxB_CurLines-1))
                                                        newrow++;
                                                break;

                                        case CURSORLEFT:
                                                dxwidth = -1;
                                                if (shift)   dxwidth  = -5;
                                                if (altctrl) dxwidth  = -MAX_FIELD_LEN;
                                                break;

                                        case CURSORRIGHT:
                                                dxwidth = 1;
                                                if (shift)   dxwidth = 5;
                                                if (altctrl) dxwidth = MAX_FIELD_LEN;
                                                break;

                                        case HELPKEY:
                                                ShowAGuide(MSG(MSG_LINK_FORMATWIN));
                                                goto done_keys;

                                        default:
                                                goto done_keys;         /* Ignore all other keys */
                                }

format_move_slider:
                                /*
                                 *              First check if the row has changed. (As well as
                                 *              changing via cursor up/down, this can also happen
                                 *              with left/right if there was no item selected to
                                 *              begin with
                                 */
                                if (newrow < 0)
                                        newrow = 0;

                                if (newrow != FBoxSelected && FBoxB_CurLines > 0) {
                                        /*
                                         *              Unactivate old row, activate a new row
                                         */
                                        if (FBoxSelected >= 0 && FBoxSelected < FBoxB_CurLines)
                                                ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
                                        FBoxSelected = newrow;
                                        ShowFormatLine(newrow, FORMAT_SELECTED);
                                        GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
                                                                          FormWindow,  NULL,
                                                                          GA_Disabled, FALSE,
                                                                          GTSL_Level, CurrentFields[newrow].width,
                                                                          TAG_DONE);
                                }

                                /*
                                 *              Now check if the horizontal position of the
                                 *              slider has changed.
                                 */
                                if (dxwidth) {
                                        int newwidth = CurrentFields[FBoxSelected].width + dxwidth;

                                        if (newwidth < 1)                               newwidth = 1;
                                        if (newwidth > MAX_FIELD_LEN)   newwidth = MAX_FIELD_LEN;
                                        if (FBoxSelected != FBOX_NOSELECT) {
                                                if (newwidth != CurrentFields[FBoxSelected].width) {
                                                        GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH],
                                                                                          FormWindow,  NULL,
                                                                                          GTSL_Level,  newwidth,
                                                                                          TAG_DONE);
                                                        CurrentFields[FBoxSelected].width = newwidth;
                                                        ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
                                                        InstallNewFormat(NEW_LISTVIEW);
                                                }
                                        }
                                }
done_keys:
                                break;
                        }
                }
                GT_ReplyIMsg(imsg);
        }
        if (doneform != SEL_NONE) {
                CloseFormatWindow();
                if (doneform == SEL_CANCEL) {
                        strcpy(BufFormat, FormatSaveFormat);
                        InstallNewFormat(NEW_STRING);
                }
        }
}

/*
 *              -----------------------------------------------------------------------
 *              Now our BOB/GEL routines for the drag-and-drop format editor.
 *              These should really be in their own module, but they're being
 *              put here for now for convenience.
 *              -----------------------------------------------------------------------
 */

/*
 *              InitGel(rastport, width, height, transp)
 *
 *              This function does all the initialisation necessary to allow us
 *              to use a single BOB in the given rastport. A new bitmap is allocated
 *              of dimensions width x height into which data can be rendered.
 *
 *              If successful, this function returns a pointer to a MyGel structure
 *              which contains, among other things, the rastport and bob structure
 *              you will use when calling system routines to manipulate the bob.
 *
 *              Note that the bob must be removed from the Gel list before rendering
 *              into it.
 *
 *              If transp is true, then the BOB will be transparent on colour zero.
 *              In this case, you must call InitMasks(mygel->vsprite) whenever
 *              you make a change to the image, to recalculate the mask.
 */
MyGel *CreateBob(struct RastPort *rport, int width, int height, int transp)
{
        struct GelsInfo *gInfo;
        struct Bob              *bob;
        struct VSprite  *vsprite;
        MyGel                   *mygel;
        ULONG                   linesize;
        ULONG                   planesize;
        ULONG                   imagesize;
        ULONG                   chipsize;
        UBYTE                   *chipdata;
        int                             wordwidth;
        int                             depth;
        int                             i;

        wordwidth       = (width + 15) >> 4;
        depth           = rport->BitMap->Depth;

        mygel = (MyGel *)AllocMem(sizeof(MyGel), MEMF_CLEAR);
        if (!mygel)
                return (NULL);
                
        mygel->mainrast         = rport;
        gInfo                           = &mygel->gelinfo;
        gInfo->nextLine         = mygel->nextline;
        gInfo->lastColor        = mygel->lastcolor;
        gInfo->collHandler      = &mygel->colltable;
        gInfo->sprRsrvd         = 0x03;

        /*
         *              Set left-most and top-most to 1 to better keep items
         *              inside the display boundaries.
         */
        gInfo->leftmost   = 1;
        gInfo->topmost    = 1;
        gInfo->rightmost  = (rport->BitMap->BytesPerRow << 3) - 1;
        gInfo->bottommost = rport->BitMap->Rows - 1;
        rport->GelsInfo   = gInfo;
        InitGels(&mygel->vshead, &mygel->vstail, gInfo);

        /*
         *              Now allocate a new BOB to be linked into this structure.
         *              We also allocate a bitmap associated with the bob, and
         *              give it its own RastPort so that we can render into it.
         */
        linesize        = sizeof(WORD)  * wordwidth;
        planesize       = linesize      * height;
        imagesize       = planesize             * depth;
        chipsize        = imagesize*2 + linesize + planesize;
        bob                     = &mygel->bob;
        vsprite         = &mygel->bobvsprite;

        if (!(chipdata = AllocMem(chipsize, MEMF_CHIP | MEMF_CLEAR))) {
                rport->GelsInfo = NULL;
                FreeMem(mygel, sizeof(MyGel));
                return (NULL);
        }
        mygel->chipdata         = (void *)(chipdata);
        mygel->chipsize         = chipsize;

        mygel->planedata        = (void *)(chipdata);
        bob->SaveBuffer         = (void *)(chipdata + imagesize);
        vsprite->BorderLine = (void *)(chipdata + imagesize*2);
        vsprite->CollMask       = (void *)(chipdata + imagesize*2 + linesize);

        vsprite->Y                      = -9999;
        vsprite->X                      = -9999;
        vsprite->Flags      = SAVEBACK | (transp ? OVERLAY : 0);
        vsprite->Width      = wordwidth;
        vsprite->Depth      = depth;
        vsprite->Height     = height;
        vsprite->MeMask     = 0;
        vsprite->HitMask    = 0;
        vsprite->ImageData  = (void *)mygel->planedata;
        vsprite->SprColors  = NULL;
        vsprite->PlanePick  = -1;
        vsprite->PlaneOnOff = 0x00;
        InitMasks(vsprite);

        vsprite->VSBob      = bob;
        bob->BobVSprite     = vsprite;
        bob->ImageShadow    = vsprite->CollMask;
        bob->Flags          = 0;
        bob->Before         = NULL;
        bob->After          = NULL;
        bob->BobComp        = NULL;
        bob->DBuffer            = NULL;

        /*
         *              Now setup the Rastport and Bitmap so we can render into the BOB
         */
        InitRastPort(&mygel->rastport);
        InitBitMap(&mygel->bitmap, depth, width, height);
        mygel->rastport.BitMap = &mygel->bitmap;

        for (i = 0; i < depth; i++)
                mygel->bitmap.Planes[i] = mygel->planedata + i * planesize;

        return (mygel);
}

/*
 *              FreeBob(mygel)
 *
 *              Frees the bob allocated earlier, along with all other associated info 
 *              Also removes all GELs from the system rastport.
 */
void FreeBob(MyGel *mygel)
{
        mygel->mainrast->GelsInfo = NULL;
        FreeMem(mygel->chipdata, mygel->chipsize);
        FreeMem(mygel, sizeof(MyGel));
}

/*
 *              PickupBob(hit, x, y)
 *
 *              Picks up the line and copies it into our bob's bitmap, then adds
 *              the bob to the screen at the current mouse location so the
 *              user can drag it around.
 *
 *              If the hit is correct, we can ignore Y since the field being
 *              dragged is in the right box and we already know which line.
 *              Otherwise, Y is a pixel co-ordinate that we use to calculate
 *              the line of the box that's being chosen.
 *
 *              If hit is FBOX_SELECTLEFT, then we need to calculate which line
 *              we are dealing with.
 *
 *              We also record the start line we use.
 *
 *              Returns true for success, false if we didn't have a hit after all.
 */
int PickupBob(int hit, int x, int y)
{
        struct RastPort *rport    = FormWindow->RPort;
        struct RastPort *bobrport = &TextBob->rastport;
        char viewbuf[50];
        int line;
        int box;
        int xpos;
        int ypos;
        int len;

        if (hit == FBOX_NOSELECT) {
                return (FALSE);
        } else if (hit == FBOX_SELECTLEFT) {
                box  = FORMAT_LEFTBOX;
                line = (y - FBoxA_Top - 2);
                if (line < FBoxSpacing)
                        line = 0;
                else
                        line /= FBoxSpacing;
                if (line >= FBoxA_CurLines)
                        return (FALSE);
        } else {
                /* Right box */
                box  = FORMAT_RIGHTBOX;
                line = hit;
                if (line >= FBoxB_CurLines)
                        return (FALSE);
        }

        /*
         *              Okay, got a definite hit so go ahead and start the drag
         */
        FormatDragBox   = box;
        FormatDragLine  = line;
        
        SetRast(bobrport, 0);                           /* Erase rast bitmap    */
        SetAPen(bobrport, 2);                           /* White foreground             */
        SetBPen(bobrport, 1);                           /* Black background             */
        SetDrMd(bobrport, JAM2);                        /* Solid text                   */
        SetFont(bobrport, FormBufFont);

        if (box == FORMAT_LEFTBOX) {
                FieldInit *fi = &FieldTypes[AvailableFields[line].type];

                mysprintf(viewbuf, FBoxA_FormString, MSG(fi->titlemsgid), fi->idchar);
                len  = FBoxA_NumChars;
                xpos = FBoxA_Left + 2;
                ypos = FBoxA_Top  + 2 + line * FBoxSpacing;
        } else {
                FieldInit *fi = &FieldTypes[CurrentFields[line].type];

                mysprintf(viewbuf, FBoxB_FormString, MSG(fi->titlemsgid),
                                                   CurrentFields[line].width, fi->idchar);
                len  = FBoxB_NumChars;
                xpos = FBoxB_Left + 2;
                ypos = FBoxB_Top  + 2 + line * FBoxSpacing;
        }
        TextBob->bob.BobVSprite->X = xpos;
        TextBob->bob.BobVSprite->Y = ypos;
        FBoxDeltaX = xpos - x;
        FBoxDeltaY = ypos - y;

        Move(bobrport, 0, FormBufFont->tf_Baseline+1);
        Text(bobrport, viewbuf, len);

        /*
         *              Now draw highlight above and below the text
         */
        SetAPen(bobrport, 1);
        Move(bobrport, 0, 0);
        Draw(bobrport, len * FormBufFont->tf_XSize - 1, 0);
        Move(bobrport, 0, FormBufFont->tf_YSize + 1);
        Draw(bobrport, len * FormBufFont->tf_XSize - 1, FormBufFont->tf_YSize + 1);

        InitMasks(TextBob->bob.BobVSprite);
        SortGList(rport);
        DrawGList(rport, ViewPortAddress(FormWindow));
        return (1);
}

/*
 *              DropBob()
 *
 *              Releases the current bob at the target, and updates the two
 *              lists/displays accordingly.
 */
void DropBob(void)
{
        EditEvent savedev;
        EditEvent *ee;
        int destpos;
        int seltype;

        destpos = FBoxDragSelect;                       /* Save destination drop selection      */
        UpdateBob(0, -9999);                            /* Kill any highlighted region          */

        /*
         *              Now re-arrange the edit lists to reflect the new position of
         *              the dragged field. There are three cases to consider:
         *
         *                      Left box        -> Right box
         *                      Right box       -> Right box
         *                      Right box       -> Left box
         */
        if (destpos == FBOX_NOSELECT)
                return;
        
        ShowFormatLine(FBoxSelected, FORMAT_UNSELECTED);
        if (FBoxSelected >= 0 && FBoxSelected < EF_COUNT)
                seltype = CurrentFields[FBoxSelected].type;
        else
                seltype = -1;

        /*
         *              First, delete item from its current list
         */
        if (FormatDragBox == FORMAT_LEFTBOX) {
                /*
                 *              Deleting from left box
                 */
                ee              = &AvailableFields[FormatDragLine];
                savedev.type  = ee->type;
                savedev.width = FieldTypes[ee->type].defwidth;

                while (ee->type != END_EDITLIST) {
                        ee[0] = ee[1];
                        ee++;
                }
                FBoxA_CurLines--;
                seltype = savedev.type; /* New selection is item we just dragged */
        } else {
                /*
                 *              Deleting from right box. If the drop position is after
                 *              the point where we're deleting, we adjust it to reflect
                 *              the deleted line.
                 */

                if (destpos > FormatDragLine && destpos <= EF_COUNT)
                        destpos--;

                ee              = &CurrentFields[FormatDragLine];
                savedev = *ee;
                while (ee->type != END_EDITLIST) {
                        ee[0] = ee[1];
                        ee++;
                }
                FBoxB_CurLines--;
        }
        /*
         *              Now add the item (savedev) to the new list in the appropriate
         *              place
         */
        if (destpos == FBOX_SELECTLEFT) {
                /*
                 *              Search the existing left box list looking for the best place
                 *              to insert the item -- we do this in strict alphabetical
                 *              order (based on the % identifer)
                 */
                int newid = FieldTypes[savedev.type].idchar;
                EditEvent tempev;

                for (ee = &AvailableFields[0]; ee->type != END_EDITLIST; ee++)
                        if (FieldTypes[ee->type].idchar > newid)
                                break;

                do {
                        tempev  = *ee;
                        *ee     = savedev;
                        savedev = tempev;
                        ee++;
                } while (ee[-1].type != END_EDITLIST);
                FBoxA_CurLines++;
        } else {
                /* 
                 *              Adding line to the right box. Simply insert it at the
                 *              specified location.
                 */
                EditEvent tempev;

                ee = &CurrentFields[destpos];
                do {
                        tempev  = *ee;
                        *ee     = savedev;
                        savedev = tempev;
                        ee++;
                } while (ee[-1].type != END_EDITLIST);
                FBoxB_CurLines++;
        }
        /*
         *              Now search our right box list to see can we match the item
         *              that was currently highlighted when we entered this routine.
         */
        FBoxSelected = 0;
        for (ee = &CurrentFields[0]; ee->type != END_EDITLIST; ee++) {
                if (ee->type == seltype)
                        break;
                FBoxSelected++;
        }
        if (ee->type == END_EDITLIST) {
                FBoxSelected = FBOX_NOSELECT;
                GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
                                                  GA_Disabled, TRUE,
                                                  TAG_DONE);
        } else {
                GT_SetGadgetAttrs(Gadget[GID_FORMWIDTH], FormWindow,  NULL,
                                                  GA_Disabled, FALSE,
                                                  GTSL_Level,  ee->width,
                                                  TAG_DONE);
        }

        /*
         *              Now, finally, update our window to show the new arrangement.
         */
        RedrawFormatWindow();
}

/*
 *              UpdateBob(x, y)
 *
 *              Updates the current position of the bob on the screen, and
 *              draws any highlights etc. that are deemed necessary.
 */
void UpdateBob(int x, int y)
{
        int bfonty = FormBufFont->tf_YSize;
        int pos;

        /*
         *              Now see if we are overlapping any of the drop regions, and if
         *              so, highlight the region appropriately.
         *
         *              When dragging into the left box, we highlight the entire box
         *              since the order of items is pre-determined and it doesn't
         *              make any sense to drop a field into one particular place.
         *
         *              When dragging into the right box, we highlight a line in
         *              between two other lines, which shows where the item will
         *              be inserted.
         *
         *              For neatness, we don't show highlighting which would have
         *              no effect on the current settings (i.e. moving a field to
         *              the same position it's already in).
         */
        pos = FBOX_NOSELECT;
        if (x >= FBoxA_Left && x <= (FBoxA_Left + FBoxA_Width  - 1) &&
            y >= FBoxA_Top  && y <= (FBoxA_Top  + FBoxA_Height - 1) &&
                FormatDragBox != FORMAT_LEFTBOX) {
                /*
                 *              Got a hit in left-hand box
                 */
                pos = FBOX_SELECTLEFT;
        } else if (x >= FBoxB_Left && x <= (FBoxB_Left + FBoxB_Width - 1) &&
                           y >= (FBoxB_Top - 10) && y <= (FBoxB_Top + FBoxB_Height + 10)) {
                /*
                 *              Got a hit in the right-hand box, so now calculate which line
                 */
                pos = (y - FBoxB_Top - 2 + bfonty/2) / FBoxSpacing;

                if (pos < 0)                            pos = 0;
                if (pos > FBoxB_CurLines)       pos = FBoxB_CurLines;
                if (FormatDragBox == FORMAT_RIGHTBOX &&
                                                (pos == FormatDragLine || pos == (FormatDragLine+1))) {
                        pos = FBOX_NOSELECT;    /* Don't highlight line we dragged from */
                }
        }

        if (pos != FBoxDragSelect) {
                /*
                 *              Currently selected item has changed, so erase current one,
                 *              then draw new one. We also refresh the currently selected
                 *              line, in case it got trashed by the highlighting.
                 *
                 *              Note that we must first move the bob off-screen while we
                 *              render the changes, otherwise it gets confused and tends
                 *              to obscure the output.
                 */
                TextBob->bob.BobVSprite->X = 0;
                TextBob->bob.BobVSprite->Y = -9999;
                SortGList(FormWindow->RPort);
                DrawGList(FormWindow->RPort, ViewPortAddress(FormWindow));
                ShowDragSelect(FBoxDragSelect, FORMAT_UNSELECTED);
                ShowFormatLine(FBoxSelected, FORMAT_SELECTED);
                ShowDragSelect(pos, FORMAT_SELECTED);
                FBoxDragSelect = pos;
        }

        TextBob->bob.BobVSprite->X = x + FBoxDeltaX;
        TextBob->bob.BobVSprite->Y = y + FBoxDeltaY;
        SortGList(FormWindow->RPort);
        DrawGList(FormWindow->RPort, ViewPortAddress(FormWindow));
}
