/*-- AutoRev header do NOT edit!
*
*   Program         :   main.c
*   Copyright       :   © Copyright 1992 Jaba Development
*   Author          :   Jan van den Baard
*   Creation Date   :   08-Mar-92
*   Current version :   1.0
*   Translator      :   Dice v2.06.40
*
*   REVISION HISTORY
*
*   Date          Version         Comment
*   ---------     -------         ------------------------------------------
*   08-Mar-92     1.0             Main file for JbSpool.
*
*-- REV_END --*/

/*
 * include a whole bunch of stuff.
 */
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/errors.h>
#include <dos/dos.h>
#include <dos/exall.h>
#include <libraries/gadtools.h>
#include <libraries/commodities.h>
#include <libraries/asl.h>
#include <intuition/intuition.h>
#include <intuition/classes.h>
#include <intuition/gadgetclass.h>
#include <devices/timer.h>
#include <devices/printer.h>
#include <workbench/startup.h>
#include <workbench/icon.h>
#include <workbench/workbench.h>

#ifndef abs
#define abs
#endif

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/commodities_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/asl_protos.h>
#include <clib/utility_protos.h>
#include <clib/intuition_protos.h>
#include <clib/alib_protos.h>
#include <clib/wb_protos.h>

#include "JbSpoolGadgets.h"                         /* GadToolsBox file */
#include "Protos.h"                                 /* routine protos. */

/*
 * Some internally used structures
 * to tie the files together.
 */
struct JbFile {
    struct JbFile          *jb_Next;                /* next file */
    struct JbFile          *jb_Prev;                /* previous file */
    UBYTE                   jb_Bull0;               /* not used */
    BYTE                    jb_Bull1;               /* not used */
    UBYTE                  *jb_Name;                /* ptr to name */
    UBYTE                   jb_NameBytes[ 108 ];    /* the name */
    ULONG                   jb_Size;                /* the size */
    UWORD                   jb_Flags;               /* flags */
};

#define ISCHECKED           0x0001                  /* file checked */

struct JbList {
    struct JbFile           *jb_First;              /* first file */
    struct JbFile           *jb_EndMark;            /* always null */
    struct JbFile           *jb_Last;               /* last file */
};

/*
 * commodity muck
 */

/*
 * An easy way to switch the commodity on or off
 */
#define CxOn( b )           ActivateCxObj( b, TRUE )
#define CxOff( b )          ActivateCxObj( b, FALSE );

/*
 * The following strings represent the
 * possible tooltypes of the JbSpool icon.
 */
UBYTE   *CX_PRIORITY        =       "CX_PRIORITY";
UBYTE   *CX_POPUP           =       "CX_POPUP";
UBYTE   *CX_POPKEY          =       "CX_POPKEY";
UBYTE   *CX_QUIT            =       "QUIT";
UBYTE   *CX_MINUTES         =       "MINUTES";
UBYTE   *CX_SECONDS         =       "SECONDS";
UBYTE   *CX_HALT            =       "DONTSTART";
UBYTE   *CX_APP             =       "NOAPPICON";

/*
 * Following are the default JbSpool
 * settings which can be changed with
 * the command line or the icon tooltypes.
 */
#define  CX_DEFPRI                  0
#define  CX_DEFMINS                 1
#define  CX_DEFSECS                 0
UBYTE   *CX_DEFPOPKEY       =       "control lshift j";
UBYTE   *CX_DEFPOPUP        =       "YES";
UBYTE   *CX_DEFQUIT         =       "control lshift q";

/*
 * Two HotKey events.
 */
#define  CX_SHOW            1L                      /* show window */
#define  CX_SHUTUP          2L                      /* quit JbSpool */

/*
 * Miscellanious program information.
 */
#define  JbVersion          "v1.0"
#define  JbName             "JbSpool"
#define  JbDescr            "Printer spooler."
#define  JbCopy             "© 1992 Jaba Development"
#define  JbTitle            JbName " " JbVersion ", " JbCopy

struct MsgPort              *JbComPort = NULL;      /* commodity port */
ULONG                        JbMask    = NULL;      /* the port bit-mask */
UBYTE                       *JbTTypes  = NULL;      /* the tooltype array */
ULONG                       *JbArgs;                /* shell args array */
CxObj                       *JbBroker  = NULL;      /* the broker */

/*
 * The NewBroker structure defining
 * some important information for
 * the commodities.library and Exchange.
 */
struct NewBroker             JbNBrok  = {
    NB_VERSION, JbName, JbTitle, JbDescr,
    NBU_NOTIFY | NBU_UNIQUE, COF_SHOW_HIDE, NULL, 0
};

/*
 * DCBack required data. DCBack is a tiny link library
 * Iv'e written to make it possible to write auto-detachable
 * programs with Dice. DCBack also parses the argument line
 * for you using ReadArgs().
 */
UBYTE                       *_procname      = JbName "_" JbVersion;
UBYTE                       *_template      = "CX_PRIORITY/K/N,CX_POPUP/K,CX_POPKEY/K,QUIT/K,MINUTES/K/N,SECONDS/K/N,DONTSTART/S,NOAPPICON/S";
UBYTE                       *_exthelp       = NULL;
LONG                         _stack         = 2048L;
LONG                         _priority      = NULL;
LONG                         _BackGroundIO  = NULL;

/*
 * Shell version string.
 */
static UBYTE JbVer[]        =       "$VER: JbSpool 37.54 (18.4.92)";

/*
 * Required libraries that are not in Dice
 * it's auto-init library.
 */
struct Library              *CxBase        = NULL;
struct Library              *IconBase      = NULL;
struct Library              *WorkbenchBase = NULL;

/*
 * The following libraries are all auto-init.
 */
extern struct IntuitionBase        *IntuitionBase;
extern struct UtilityBase          *UtilityBase;
extern struct GadToolsBase         *GadToolsBase;

/*
 * Some other usefull global data
 */
struct WBStartup            *WbMsg      = NULL;     /* workbench message */
struct MsgPort              *JbIdPort   = NULL;     /* IDCMP port */
UBYTE                       *JbPopkey;              /* popkey string ptr */
ULONG                        JbIdMask   = NULL;     /* IDCMP port bit-mask */
UBYTE                        JbIsOpen   = FALSE;    /* window open? */
UBYTE                        JbPop      = TRUE;     /* open on startup? */
UBYTE                        JbFF       = TRUE;     /* insert a FF? */
UBYTE                        JbPrinting = FALSE;    /* are we printing? */
UBYTE                        JbWTitle[80];          /* window title */
UBYTE                        JbStopped  = FALSE;    /* stopped? */
UBYTE                        JbDelQuit  = FALSE;    /* delayed quit? */
UBYTE                        JbJobCan   = FALSE;    /* Job cancelled? */
UBYTE                        JbDoIcon   = TRUE;     /* Add icon? */
UBYTE                        JbNoPrt    = TRUE;     /* print? */
LONG                         JbMins, JbSecs;        /* minutes and seconds */
ULONG                        JbDone     = NULL;     /* bytes printed */
UWORD                        JbNodeNum  = 0;        /* active node number */
struct JbList                JbFiles;               /* list of files */
struct JbFile               *JbCurrentJob = NULL;   /* printing this file */
struct JbFile               *JbActionNode = NULL;   /* put this up or down */

BPTR                         JbPrinter    = NULL;   /* printer file */
BPTR                         JbInput      = NULL;   /* file to print */
BPTR                         JbDirLock    = NULL;   /* lock to JBS: */

/*
 * ASL requester tags & data.
 */
UBYTE                        JbFileN[ 32 ];         /* file name */
UBYTE                        JbDir[ 256 ];          /* path name */

struct TagItem               JbTags[] = {
    ASL_Hail,                "Pick file to add:",    /* req title */
    ASL_Window,              NULL,                   /* the window */
    ASL_File,                JbFileN,                /* file name buffer */
    ASL_Dir,                 JbDir,                  /* path name buffer */
    ASL_OKText,              "Add",                  /* make OK Add */
    ASL_FuncFlags,           FILF_MULTISELECT,       /* multi selections! */
    TAG_DONE };

/*
 * This is the size of the
 * buffer I am using for ExAll();
 */
#define EXALLSIZE           (( ULONG ) 10 * sizeof( struct ExAllData ))

/*
 * The JbSpool directory from which
 * the files to be printed are read.
 */
UBYTE                       *JbDirName    = "JBS:";

/*
 * timer.device stuff
 */
struct MsgPort         *timerPort = NULL;           /* timer reply port */
ULONG                   timerMask = NULL;           /* port bit-mask */
struct timerequest     *timerReq  = NULL;           /* timerequest */
long                    timerErr  = NULL;           /* device error */
UBYTE                   timerOn   = FALSE;          /* are we timing? */

/*
 * The data for the application icon.
 */
__chip UWORD AppData1[] =
{   0x0000,0x0000,0x0000,0x0400,0x0000,0x0000,0x0000,0x0C00,
    0x0000,0x0000,0x0000,0x0C00,0x0000,0x0000,0x0000,0x0C00,
    0x01FF,0xFFFF,0xFFFC,0x0C00,0x0100,0x0000,0x0004,0x0C00,
    0x0103,0xEFF1,0xF404,0x0C00,0x0101,0xF7FB,0xFE04,0x0C00,
    0x0101,0xE7BF,0x86C4,0x0C00,0x0101,0xE7BF,0xE2E4,0x0C00,
    0x0101,0xE7FD,0xF864,0x0C00,0x0119,0xE7F8,0xFC04,0x0C00,
    0x011D,0xE7BE,0x3E04,0x0C00,0x011D,0xE7BF,0x0EC4,0x0C00,
    0x010F,0xEFFF,0xFEE4,0x0C00,0x0107,0xC7F9,0x7C64,0x0C00,
    0x0100,0x0000,0x0004,0x0C00,0x01FF,0xFFFF,0xFFFC,0x0C00,
    0x0000,0x0000,0x0000,0x0C00,0x0000,0x0000,0x0000,0x0C00,
    0x0000,0x0000,0x0000,0x0C00,0x7FFF,0xFFFF,0xFFFF,0xFC00,
    0xFFFF,0xFFFF,0xFFFF,0xF800,0xD555,0x5555,0x5555,0x5000,
    0xD555,0x5555,0x5555,0x5000,0xD555,0x5555,0x5555,0x5000,
    0xD400,0x0000,0x0001,0x5000,0xD4FF,0xFFFF,0xFFF9,0x5000,
    0xD4FF,0xFFFF,0xFFF9,0x5000,0xD4FF,0xCF3F,0x0DF9,0x5000,
    0xD4FF,0xDF7B,0xFDF9,0x5000,0xD4FF,0xDF7B,0xFDD9,0x5000,
    0xD4FF,0xDFF3,0xFF99,0x5000,0xD4FF,0xDF3F,0x7FF9,0x5000,
    0xD4FB,0xDF7B,0xDDF9,0x5000,0xD4F3,0xDF7B,0xFDF9,0x5000,
    0xD4FF,0x9FF2,0xF9D9,0x5000,0xD4F8,0x3806,0x8399,0x5000,
    0xD4FF,0xFFFF,0xFFF9,0x5000,0xD400,0x0000,0x0001,0x5000,
    0xD555,0x5555,0x5555,0x5000,0xD555,0x5555,0x5555,0x5000,
    0xD555,0x5555,0x5555,0x5000,0x8000,0x0000,0x0000,0x0000,
};

__chip UWORD AppData2[] =
{   0xFFFF,0xFFFF,0xFFFF,0xF800,0xC000,0x0000,0x0000,0x0000,
    0xC000,0x0000,0x0000,0x0000,0xC000,0x0000,0x0000,0x0000,
    0xC1FF,0xFFFF,0xFFFC,0x0000,0xC100,0x0000,0x0004,0x0000,
    0xC103,0xEFF1,0xF404,0x0000,0xC101,0xF7FB,0xFE04,0x0000,
    0xC101,0xE7BF,0x86C4,0x0000,0xC101,0xE7BF,0xE2E4,0x0000,
    0xC101,0xE7FD,0xF864,0x0000,0xC119,0xE7F8,0xFC04,0x0000,
    0xC11D,0xE7BE,0x3E04,0x0000,0xC11D,0xE7BF,0x0EC4,0x0000,
    0xC10F,0xEFFF,0xFEE4,0x0000,0xC107,0xC7F9,0x7C64,0x0000,
    0xC100,0x0000,0x0004,0x0000,0xC1FF,0xFFFF,0xFFFC,0x0000,
    0xC000,0x0000,0x0000,0x0000,0xC000,0x0000,0x0000,0x0000,
    0xC000,0x0000,0x0000,0x0000,0x8000,0x0000,0x0000,0x0000,
    0x0000,0x0000,0x0000,0x0400,0x1555,0x5555,0x5555,0x5C00,
    0x1555,0x5555,0x5555,0x5C00,0x1555,0x5555,0x5555,0x5C00,
    0x1400,0x0000,0x0001,0x5C00,0x14FF,0xFFFF,0xFFF9,0x5C00,
    0x14FF,0xFFFF,0xFFF9,0x5C00,0x14FF,0xCF3F,0x0DF9,0x5C00,
    0x14FF,0xDF7B,0xFDF9,0x5C00,0x14FF,0xDF7B,0xFDD9,0x5C00,
    0x14FF,0xDFF3,0xFF99,0x5C00,0x14FF,0xDF3F,0x7FF9,0x5C00,
    0x14FB,0xDF7B,0xDDF9,0x5C00,0x14F3,0xDF7B,0xFDF9,0x5C00,
    0x14FF,0x9FF2,0xF9D9,0x5C00,0x14F8,0x3806,0x8399,0x5C00,
    0x14FF,0xFFFF,0xFFF9,0x5C00,0x1400,0x0000,0x0001,0x5C00,
    0x1555,0x5555,0x5555,0x5C00,0x1555,0x5555,0x5555,0x5C00,
    0x1555,0x5555,0x5555,0x5C00,0x7FFF,0xFFFF,0xFFFF,0xFC00,
};

/*
 * Images for the application icon.
 */
struct Image AppIm1 =
{   0,0,54,22,2,AppData1,0x0003,0x0000,NULL };
struct Image AppIm2 =
{   0,0,54,22,2,AppData2,0x0003,0x0000,NULL };

/*
 * The DiskObject structure for the application icon.
 */
struct DiskObject AppIc =
{   WB_DISKMAGIC,WB_DISKVERSION,
    {   NULL,0,0,54,23,GADGIMAGE|GADGHIMAGE,
        RELVERIFY,BOOLGADGET,
        (APTR)&AppIm1,(APTR)&AppIm2,
        NULL,NULL,NULL,100,(APTR) 0x0001,
    },
    WBTOOL,NULL,NULL,NO_ICON_POSITION,
    NO_ICON_POSITION,NULL,NULL,0
};

struct AppIcon          *JbApIcon = NULL;            /* The AppIcon */
struct MsgPort          *JbWbPort = NULL;            /* The AppIcon port */
ULONG                    JbWbMask = NULL;            /* And the bit mask */

/*
 * Whe the AppIcon isn't setup yet this
 * routine will try to set it up again.
 */
void SetupAppIcon( void )
{
    if ( ! JbApIcon && JbDoIcon ) {
        if ( JbWbPort = CreateMsgPort()) {
            JbWbMask = ( 1L << JbWbPort->mp_SigBit );
            if ( JbApIcon = AddAppIcon( NULL, NULL, "JbSpool", JbWbPort,
                                        NULL, &AppIc, TAG_DONE ))
                return;
        }
        CloseDownAppIcon();
    }
}

/*
 * This deletes the AppIcon again.
 */
void CloseDownAppIcon( void )
{
    struct Message      *msg;

    if ( JbApIcon ) {
        RemoveAppIcon( JbApIcon );
        JbApIcon = NULL;
    }

    if ( JbWbPort ) {
        DeleteMsgPort( JbWbPort );
        JbWbPort = NULL;
        JbWbMask = NULL;
    }
}

/*
 * This structure is used to determine wether
 * or not the (parallel) printer is online and
 * has paper in it.
 * (Amiga ROM Kernel Reference : Devices 3rd. edition page 186 )
 */
struct Data {
    UBYTE   LSB, MSB;
};

/*
 * Query the printer status. This
 * will check if the printer is online
 * and if it has paper in it. Note that
 * this will only work with parallel printers.
 */
BOOL QueryPrinter( __A0 UBYTE *name )
{
    struct MsgPort  *port;
    struct IOStdReq *pio;
    struct Data      data;
    UBYTE           *ptr;
    ULONG            size = 1L;
    BOOL             ret = TRUE;

    if ( port = CreateMsgPort()) {
        if ( pio = CreateStdIO( port )) {
            if ( ! OpenDevice( "printer.device", NULL, ( struct IORequest * )pio, NULL )) {

                retry:

                pio->io_Data = (APTR)&data;
                pio->io_Command = PRD_QUERY;

                DoIO(( struct IORequest * )pio );

               /*
                * If bit 0 of the LSB (Least Significant Byte) is
                * set it means that the printer is offline.
                * If bit 1 of the LSB is set it means that there
                * is no paper in the printer.
                */
                if ( data.LSB & 1 ) {
                    if ( Req( "Retry|Cancel", "I want to print \"%s\" but\nyour printer isn't online.", name ))
                        goto retry;
                    else
                        ret = FALSE;
                } else if ( data.LSB & 2 ) {
                    if ( Req( "Retry|Cancel", "I want to print\"%s\" but\nyour printer doesn't have any paper.", name ))
                        goto retry;
                    else
                        ret = FALSE;
                }

                CloseDevice(( struct IORequest * )pio );
            }
            DeleteStdIO( pio );
        }
        DeleteMsgPort( port );
    }

   /*
    * Flush the memory forcing the system
    * to un-load the printer.device when it's
    * use count is 0. This will ensure the
    * correct preferences settings of the
    * printer when JbSpool prints it's files.
    */
    while( ptr = AllocMem( size, MEMF_PUBLIC )) {
        FreeMem( ptr, size );
        size <<= 1L;
    }

    return( ret );
}

/*
 * Set the window gadgets and menus
 * accoording to the different program flags.
 */
void SetupDisplay( __D0 BOOL prc )
{
    if ( JbIsOpen ) {

        if ( prc )
            GT_SetGadgetAttrs( SpoolGadgets[ GDX_DONE ], SpoolWnd, NULL, GTNM_Number, ( JbDone * 100 ) / JbCurrentJob->jb_Size, TAG_DONE );
        else {
            if ( JbActionNode )
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, &JbFiles, GTLV_Selected, JbNodeNum, GTLV_Top, JbNodeNum );
            else
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, &JbFiles, TAG_DONE );
            GT_SetGadgetAttrs( SpoolGadgets[ GDX_MINS  ], SpoolWnd, NULL, GTIN_Number, JbMins, TAG_DONE );
            GT_SetGadgetAttrs( SpoolGadgets[ GDX_SECS  ], SpoolWnd, NULL, GTIN_Number, JbSecs, TAG_DONE );
            GT_SetGadgetAttrs( SpoolGadgets[ GDX_FF    ], SpoolWnd, NULL, GTCB_Checked, JbFF, TAG_DONE );
            GT_SetGadgetAttrs( SpoolGadgets[ GDX_DONE ], SpoolWnd, NULL, GTNM_Number, NULL, TAG_DONE );

            if ( ! JbActionNode ) {
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_REMOVE ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_UP     ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_DOWN   ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_TOP    ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_BOTTOM ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                OffMenu( SpoolWnd, SHIFTMENU(0)|SHIFTITEM(3)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(0)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(1)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(2)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(3)|SHIFTSUB(NOSUB));
            } else {
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_REMOVE ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_UP     ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_DOWN   ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_TOP    ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_BOTTOM ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                OnMenu( SpoolWnd, SHIFTMENU(0)|SHIFTITEM(3)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(0)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(1)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(2)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(3)|SHIFTSUB(NOSUB));
            }

            if ( JbPrinting && JbCurrentJob ) {
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_CURRENT ], SpoolWnd, NULL, GTTX_Text, JbCurrentJob->jb_Name, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_STOP    ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_CONT    ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_ABORT   ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(5)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(6)|SHIFTSUB(NOSUB));
                OnMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(8)|SHIFTSUB(NOSUB));
            } else {
                if ( JbJobCan )
                    GT_SetGadgetAttrs( SpoolGadgets[ GDX_CURRENT ], SpoolWnd, NULL, GTTX_Text, "*** DELAYED ***", TAG_DONE );
                else if ( ! JbFiles.jb_First->jb_Next )
                    GT_SetGadgetAttrs( SpoolGadgets[ GDX_CURRENT ], SpoolWnd, NULL, GTTX_Text, "*** IDLE ***", TAG_DONE );
                else if ( JbStopped )
                    GT_SetGadgetAttrs( SpoolGadgets[ GDX_CURRENT ], SpoolWnd, NULL, GTTX_Text, "*** STOPPED ***", TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_STOP    ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_CONT    ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_ABORT   ], SpoolWnd, NULL, GA_Disabled, TRUE, TAG_DONE );
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(5)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(6)|SHIFTSUB(NOSUB));
                OffMenu( SpoolWnd, SHIFTMENU(1)|SHIFTITEM(8)|SHIFTSUB(NOSUB));
            }

            if ( JbStopped )
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_CONT    ], SpoolWnd, NULL, GA_Disabled, FALSE, TAG_DONE );
        }
    }
}

/*
 * This routine checks if the character
 * is a valid ascii character.
 */
long IsAsciiChar( __D0 UBYTE chr )
{
    if( chr > 0x00 && chr < 0x09 ) return FALSE;
    if( chr > 0x0D && chr < 0x1B ) return FALSE;
    if( chr > 0x1B && chr < 0x20 ) return FALSE;
    if( chr > 0x7F && chr < 0x9b ) return FALSE;
    return TRUE;
}

/*
 * This routine will check if the file is
 * really an ascii text file. This way it
 * is made sure that JbSpool only prints
 * the text files.
 */
long CheckAscii( __A0 struct JbFile *jf )
{
    BPTR     file;
    UBYTE    memory[ 100 ], chr;
    ULONG    cnt, ret = TRUE, len;

    if( file = Open( jf->jb_Name, MODE_OLDFILE )) {
        if ( len = Read( file, memory, 100L )) {
            for( cnt = 0; cnt < len; cnt++ ) {
                chr = memory[ cnt ];
                if( ! IsAsciiChar( chr )) {
                    ret = FALSE;
                    break;
                }
            }
            Close(file);
        } else
            ret = FALSE;
    }

    if ( ! ret )
        DeleteFile( jf->jb_Name );

    return( ret );
}

/*
 * Get the JbFile node "num" from
 * the queue.
 */
struct JbFile *FindNode( UWORD node )
{
    struct JbFile  *file;
    UWORD           num = 0;

    if ( JbIsOpen )
        GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, ~0, TAG_DONE );

    for ( file = JbFiles.jb_First; file->jb_Next; file = file->jb_Next ) {
        if ( num++ == node ) break;
    }

    if ( JbIsOpen )
        GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, &JbFiles, GTLV_Selected, num - 1, TAG_DONE );

    return( file );
}

/*
 * Check if the file in the directory is
 * already in the queue list. The routine simply
 * goes through the list of files and checks
 * if the name already is in the file list.
 */
long CheckIfExists( __A0 UBYTE *name )
{
    struct JbFile       *file;

    if ( JbCurrentJob ) {
        if ( ! stricmp( name, JbCurrentJob->jb_Name ))
            return( TRUE );
    }

    for ( file = JbFiles.jb_First; file->jb_Next; file = file->jb_Next ) {
        if ( ! stricmp( name, file->jb_Name ))
            return( TRUE );
    }
    return( FALSE );
}

/*
 * Remove the files from the queue that doesn't
 * exist anymore. This to prevent the program from
 * trying to read and print a file that doesn't
 * exist in the JBS: directory anymore. Also all
 * non-ascii files are removed by this routine.
 */
void RemoveObsoleteFiles( void )
{
    struct JbFile   *file;
    BPTR                      lock, cd;

    cd = CurrentDir( JbDirLock );

    goAgain:

    for ( file = JbFiles.jb_First; file->jb_Next; file = file->jb_Next ) {
        if (( file->jb_Flags & ISCHECKED ) != ISCHECKED ) {

           /*
            * Remove the non-ascii files.
            */
            if ( ! CheckAscii( file ))
                goto skipIt;

            if ( ! ( lock = Lock ( file->jb_Name, ACCESS_READ ))) {
               /*
                * The file in the list doesn't actually
                * exists on the disk anymore so we
                * remove it from the list.
                */
                skipIt:
                Remove(( struct Node * )file );
                FreeVec( file );
                goto goAgain;
            } else {
                UnLock( lock );
               /*
                * Set the ISCHECKED flag which saves time on
                * the next pass through the list.
                */
                file->jb_Flags |= ISCHECKED;
            }
        }
    }

   /*
    * Clear all ISCHECKED flags again.
    */
    for ( file = JbFiles.jb_First; file->jb_Next; file = file->jb_Next )
        file->jb_Flags &= ~ISCHECKED;

    CurrentDir( cd );
}

/*
 * Scan through the JBS: directory to find out
 * if there are new files to be added to the queue
 */
void ScanDirectory( void )
{
    struct ExAllControl     *eac;
    struct ExAllData        *ead, *tmp;
    struct FileInfoBlock    *fib;
    struct JbFile           *file;
    BPTR                     lock;
    BOOL                     ismore;

    if ( JbIsOpen )
        GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, ~0, TAG_DONE );

    if ( lock = Lock( JbDirName, ACCESS_READ )) {
        if ( fib = ( struct FileInfoBlock * )AllocDosObject( DOS_FIB, NULL )) {
            if ( Examine( lock, fib )) {
                if ( fib->fib_DirEntryType > 0 ) {
                    if ( eac = ( struct ExAllControl * ) AllocDosObject( DOS_EXALLCONTROL, NULL )) {

                       /*
                        * THIS IS VERY IMPORTANT !!!!
                        */
                        eac->eac_LastKey = NULL;

                        if ( ead = ( struct ExAllData * )AllocVec( EXALLSIZE, MEMF_PUBLIC | MEMF_CLEAR )) {

                            do {
                                ismore = ExAll( lock, ead, EXALLSIZE, ED_SIZE, eac );

                                if (( ! ismore ) && ( IoErr() != ERROR_NO_MORE_ENTRIES ))
                                    break;

                                if ( ! eac->eac_Entries )
                                    continue;

                                tmp = ead;

                                do {
                                    if ( tmp->ed_Type < 0 ) {
                                        if ( ! CheckIfExists( tmp->ed_Name )) {
                                            if ( file = ( struct JbFile * )AllocVec(( ULONG )sizeof( struct JbFile ), MEMF_PUBLIC | MEMF_CLEAR )) {
                                                file->jb_Name = &file->jb_NameBytes[ 0 ];
                                                strcpy( file->jb_Name, tmp->ed_Name );
                                                file->jb_Size = tmp->ed_Size;
                                                AddTail(( struct List * )&JbFiles, ( struct Node * )file );
                                            }
                                        }
                                    }
                                    tmp = tmp->ed_Next;
                                } while ( tmp );
                            } while( ismore );

                            FreeVec( ead );
                        }
                        FreeDosObject( DOS_EXALLCONTROL, eac );
                    }
                }
            }
            FreeDosObject( DOS_FIB, fib );
        }
        UnLock( lock );
    }

   /*
    * Remove all unwanted files from the list.
    */
    RemoveObsoleteFiles();

   /*
    * Try to setup an AppIcon.
    * This is done after each directory
    * scan so that when the user open the
    * workbench we have an AppIcon on it.
    */
    SetupAppIcon();

    SetupDisplay( FALSE );

    if ( JbIsOpen )
        GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, &JbFiles, TAG_DONE );
}

/*
 * Open up all required resources. Check the
 * arguments passed to JbSpool from the argument
 * line if run from the shell or the tool types
 * when run from the workbench.
 */
void SetupSpool( void )
{
    CxObj           *tmp;
    UBYTE           *quitkey, *popup;
    WORD             priority;
    LONG             error;

    NewList(( struct List * )&JbFiles );

    JbFileN[ 0 ] = JbDir[ 0 ] = 0;

    if ( ! ( CxBase = OpenLibrary( "commodities.library", 37L ))) {
        error = 20L;
        goto ohMyGodAnErrorOccurred;
    }

   /*
    * This one is required by the Argxxxx()
    * routines from the amigas20.lib.
    */
    if ( ! ( IconBase = OpenLibrary( "icon.library", 34L ))) {
        error = 21L;
        goto ohMyGodAnErrorOccurred;
    }

    if ( ! ( WorkbenchBase = OpenLibrary( "workbench.library", 37L ))) {
        error = 22L;
        goto ohMyGodAnErrorOccurred;
    }

    if ( WbMsg ) {
       /*
        * When WbMsg is non-null it means that
        * JbSpool was started from the workbench.
        */
        JbTTypes = ( UBYTE * )ArgArrayInit( NULL, ( UBYTE ** )WbMsg );

        priority = ArgInt(( char ** )JbTTypes, CX_PRIORITY, CX_DEFPRI );
        popup    = ArgString(( char ** )JbTTypes, CX_POPUP,    CX_DEFPOPUP );
        JbPopkey = ArgString(( char ** )JbTTypes, CX_POPKEY,   CX_DEFPOPKEY );
        quitkey  = ArgString(( char ** )JbTTypes, CX_QUIT,     CX_DEFQUIT );
        JbMins   = ArgInt(( char ** )JbTTypes, CX_MINUTES,  CX_DEFMINS );
        JbSecs   = ArgInt(( char ** )JbTTypes, CX_SECONDS,  CX_DEFSECS );
        if ( ArgString(( char ** )JbTTypes, CX_HALT, NULL ))
            JbStopped = TRUE;
        if ( ArgString(( char ** )JbTTypes, CX_APP, NULL ))
            JbDoIcon  = FALSE;
    } else {
       /*
        * Otherwise JbSpool was started from the shell.
        */
        if ( JbArgs[ 0 ] )  priority = ( WORD )*(( ULONG * )JbArgs[ 0 ] );
        else                priority = CX_DEFPRI;
        if ( JbArgs[ 1 ] )  popup    = ( UBYTE * )JbArgs[ 1 ];
        else                popup    = CX_DEFPOPUP;
        if ( JbArgs[ 2 ] )  JbPopkey = ( UBYTE * )JbArgs[ 2 ];
        else                JbPopkey = CX_DEFPOPKEY;
        if ( JbArgs[ 3 ] )  quitkey  = ( UBYTE * )JbArgs[ 3 ];
        else                quitkey  = CX_DEFQUIT;
        if ( JbArgs[ 4 ] )  JbMins   = ( WORD )*(( ULONG * )JbArgs[ 4 ] );
        else                JbMins   = CX_DEFMINS;
        if ( JbArgs[ 5 ] )  JbSecs   = ( WORD )*(( ULONG * )JbArgs[ 5 ] );
        else                JbSecs   = CX_DEFSECS;
        if ( JbArgs[ 6 ] )  JbStopped= TRUE;
        if ( JbArgs[ 7 ] )  JbDoIcon = FALSE;
    }

    if ( JbMins < 0 ) JbMins = 0;

    if ( ! JbMins && ( JbSecs < 5 ))
        JbSecs = 5;

    JbNBrok.nb_Pri = priority;

    if ( Stricmp( popup, CX_DEFPOPUP )) JbPop = FALSE;

    if ( JbComPort = CreateMsgPort()) {

        JbMask          = ( 1L << JbComPort->mp_SigBit );
        JbNBrok.nb_Port = JbComPort;

        if ( JbBroker = CxBroker( &JbNBrok, NULL )) {
            if ( tmp = HotKey( JbPopkey, JbComPort, CX_SHOW )) {

                AttachCxObj( JbBroker, tmp );

                if ( tmp = HotKey( quitkey, JbComPort, CX_SHUTUP )) {

                    AttachCxObj( JbBroker, tmp );

                    if ( ! CxObjError( JbBroker )) {
                        CxOn( JbBroker );
                        if ( SetupTimer()) {
                            if ( JbDirLock = Lock ( JbDirName, ACCESS_READ )) {
                                SetupAppIcon();
                                return;
                            } else
                                error = 22L;
                        } else
                            error = 23L;
                    } else
                        error = 24L;
                } else
                    error = 25L;
            } else
                error = 26L;
        } else
            error = 27L;
    } else
        error = 28L;

ohMyGodAnErrorOccurred:

    CloseDownSpool( error );
}

/*
 * Close down and deallocate all resources
 * taken from the system.
 */
void CloseDownSpool( __D0 LONG code )
{
    struct JbFile       *file;
    struct Message      *tmp;
    BPTR                 od;

    CloseDownTimer();
    CloseDownAppIcon();

    od = CurrentDir( JbDirLock );
    while( file = ( struct JbFile * )RemHead(( struct List * )&JbFiles )) {
        DeleteFile( file->jb_Name );
        FreeVec( file );
    }
    CurrentDir( od );

    if ( JbDirLock )        UnLock( JbDirLock );
    if ( JbBroker )         DeleteCxObjAll( JbBroker );
    if ( JbComPort ) {
        while ( tmp = GetMsg( JbComPort )) ReplyMsg( tmp );
        DeleteMsgPort( JbComPort );
    }
    if ( JbTTypes)          ArgArrayDone();
    if ( WorkbenchBase )    CloseLibrary( WorkbenchBase );
    if ( IconBase )         CloseLibrary( IconBase );
    if ( CxBase )           CloseLibrary( CxBase );

    exit( code );
}

/*
 * Setup the timer.device to create a delay
 * between directory scans. The timer.device
 * is also used to create a 1 second delay
 * between printer writes.
 */
long SetupTimer( void )
{
    if ( timerPort = CreateMsgPort()) {
        timerMask = ( 1L << timerPort->mp_SigBit );
        if ( timerReq = ( struct timerequest * )CreateExtIO( timerPort, ( long )sizeof( struct timerequest ))) {
            if ( ! ( timerErr = OpenDevice( TIMERNAME, UNIT_VBLANK, ( struct IORequest * )timerReq, NULL )))
                return( TRUE );
        }
    }
    CloseDownTimer();
    return( FALSE );
}

/*
 * Close down the timer.device
 */
void CloseDownTimer( void )
{
    if ( timerReq ) {

        AbortTimer();

        if ( ! timerErr ){
            CloseDevice(( struct IORequest * )timerReq );
            timerErr = NULL;
        }

        DeleteExtIO(( struct IORequest * )timerReq );
        timerReq = NULL;
    }

    if ( timerPort ) {
        DeleteMsgPort( timerPort );
        timerMask = NULL;
        timerPort = NULL;
    }

    timerOn = FALSE;
}

/*
 * Abort the currently running
 * timer request.
 */
void AbortTimer( void )
{
    if ( timerOn ) {
        AbortIO(( struct IORequest * )timerReq );
        WaitIO(( struct IORequest * )timerReq );
        timerOn = FALSE;
    }
}

/*
 * Move the "JbActiveNode" up, down, to the top or to the bottom.
 */
UWORD MoveTheNode( __D0 UWORD num, __D1 UWORD dir )
{
    struct JbFile *tmp   = NULL;

    GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, ~0, TAG_DONE );

    if ( JbActionNode ) {

        if ( dir == 1 ) {
            if ( JbActionNode != JbFiles.jb_First )
                tmp = JbActionNode->jb_Prev->jb_Prev;
        } else if ( ! dir ) {
            if ( JbActionNode != JbFiles.jb_Last )
                tmp = JbActionNode->jb_Next;
        } else
            tmp = JbActionNode;

        if ( tmp ) {
            Remove(( struct Node * )JbActionNode );
            if ( dir <= 1 )
                Insert(( struct List * )&JbFiles, ( struct Node * )JbActionNode, ( struct Node * )tmp );
            else if ( dir == 2 )
                AddHead(( struct List * )&JbFiles, ( struct Node * )JbActionNode );
            else
                AddTail(( struct List * )&JbFiles, ( struct Node * )JbActionNode );
        }
    }

    if ( tmp ) {
        if ( dir == 1 )        num--;
        else if ( ! dir )      num++;
        else if ( dir == 2 )   num = 0;
        else {
            num = 0;
            for ( tmp = JbFiles.jb_First; tmp->jb_Next; tmp = tmp->jb_Next )
                num++;
            num--;
        }
    }

    GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, &JbFiles, GTLV_Selected, num, GTLV_Top, num, TAG_DONE );

    return( num );
}

/*
 * Put up a simple requester.
 */
long Req( UBYTE *gadgets, UBYTE *body, ... )
{
    static struct EasyStruct req = {
        sizeof( struct EasyRequest ), NULL, NULL, NULL, NULL };
    va_list                  args;
    LONG                     rc;

    va_start( args, body );

    req.es_Title        = "JbSpool";
    req.es_TextFormat   = body;
    req.es_GadgetFormat = gadgets;

    rc = EasyRequestArgs( NULL, &req, NULL, args );

    va_end( args );

    return( rc );
}

/*
 * Remove the selected node from the
 * list and, if requested, delete the
 * corresponding file from the
 * JBS: directory.
 */
void RemoveTheNode( void )
{
    BPTR    od;

    if ( JbActionNode ) {
        od = CurrentDir( JbDirLock );

        GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, ~0, TAG_DONE );

        Remove(( struct Node * )JbActionNode );
        if ( Req( "Yes|No", "Shall I delete the file\nfrom the JBS: directory too?" ))
            DeleteFile( JbActionNode->jb_Name );
        FreeVec( JbActionNode );
        JbActionNode = NULL;
        JbNodeNum    = 0;

        CurrentDir( od );
        AbortTimer();
        if ( JbPrinting )
            ScanDirectory();
    }
}

/*
 * This copies the file from it's
 * original directory in the JBS:
 * directory so that it can be printed.
 */
void CopyTheFile( __A0 BPTR slock, __A1 UBYTE *fname )
{
    BPTR        filein, fileout;
    BPTR        od;
    UBYTE      *ptr;
    ULONG       size;

    od = CurrentDir( slock );

    if ( filein = Open( fname, MODE_OLDFILE )) {
               Seek( filein, NULL, OFFSET_END );
        size = Seek( filein, NULL, OFFSET_BEGINNING );
        if ( ptr = ( UBYTE * )AllocMem( size, MEMF_PUBLIC )) {
            if ( Read( filein, ptr, size ) == size ) {
                CurrentDir( JbDirLock );
                if ( fileout = Open( fname, MODE_NEWFILE )) {
                    if ( Write( fileout, ptr, size ) != size )
                        Req( "Continue", "Error writing file !" );
                    Close( fileout );
                } else
                    Req( "Continue", "Error opening write file !" );
            } else
                Req( "Continue", "Error reading file !" );
            FreeMem( ptr, size );
        } else
            Req( "Continue", "Out of memory !" );
        Close( filein );
    } else
        Req( "Continue", "Error opening read file !" );
    CurrentDir( od );
}

/*
 * Show the ASL FileRequester to let the
 * user add a file to the queue.
 */
void AddANode( void ) {
    struct WBArg            *wba;
    struct FileRequester    *freq;
    UBYTE                   *ptr;
    ULONG                    size, i;
    BPTR                     lock;

    JbTags[ 1 ].ti_Data = (ULONG)SpoolWnd;

    if ( freq = AllocAslRequest( ASL_FileRequest, JbTags )) {
        if ( RequestFile( freq )) {
            strcpy( JbDir, freq->rf_Dir );
            strcpy( JbFileN, freq->rf_File );

            if ( freq->rf_NumArgs ) {
                wba = freq->rf_ArgList;
                for ( i = 0; i < freq->rf_NumArgs; i++, wba++ )
                    CopyTheFile( wba->wa_Lock, wba->wa_Name );
            } else if ( lock = Lock( JbDir, ACCESS_READ )) {
               /*
                * There is a bug in the asl.library. When you have
                * the multi-select option in the filerequester the
                * requester doesn't give the correct result when
                * you select _one_ file by _double-clicking_ it.
                */
                CopyTheFile( lock, JbFileN );
                UnLock( lock );
            }
        }
        FreeAslRequest( freq );
    }

    AbortTimer();
    if ( JbPrinting )
        ScanDirectory();
    JbJobCan = FALSE;
}

/*
 * Interpred and act on the menu events.
 * Note that this routine supports drag-selection
 * but when Hide is selected the drag-selection
 * loop will break.
 */
UWORD DoTheMenus( __D0 UWORD menucode )
{
    struct MenuItem     *mi;
    UWORD                menu, item, breakloop = FALSE, ret = 0;

    while ( menucode != MENUNULL && ! breakloop ) {
        mi = ( struct MenuItem * )ItemAddress( SpoolMenus, menucode );

        menu = MENUNUM( menucode );
        item = ITEMNUM( menucode );

        switch ( menu ) {
            case    0:
                switch ( item ) {
                    case    0: /* About */
                        Req( "Continue", "%s\n"
                                         "© Copyright 1992 Jaba Development\n\n"
                                         "Release 1 - FreeWare\n\n"
                                         "Written using Dice v2.06.40 by\n"
                                         "Jan van den Baard", &JbVer[ 6 ] );
                        break;
                    case    2: /* Add */
                        AddANode();
                        break;
                    case    3: /* Remove */
                        RemoveTheNode();
                        break;
                    case    5: /* Hide */
                        CloseTheWindow();
                        breakloop = TRUE;
                        break;
                    case    7: /* Quit */
                        ret = 1;
                        break;
                }
                break;

            case    1:
                switch ( item ) {
                    case    0: /* Up */
                        JbNodeNum = MoveTheNode( JbNodeNum, 1 );
                        break;
                    case    1: /* Down */
                        JbNodeNum = MoveTheNode( JbNodeNum, 0 );
                        break;
                    case    2: /* Top */
                        JbNodeNum = MoveTheNode( JbNodeNum, 2 );
                        break;
                    case    3: /* Bottom */
                        JbNodeNum = MoveTheNode( JbNodeNum, 3 );
                        break;
                    case    5: /* Stop */
                        JbStopped = TRUE;
                        break;
                    case    6: /* Continue */
                        JbStopped = FALSE;
                        break;
                    case    8: /* Abort */
                        ret = 2;
                        break;
                }
                break;
        }
        menucode = mi->NextSelect;
    }
    return( ret );
}

/*
 * The main program loop which wait for events from
 * the different ports that are setup by the program.
 */
void EventHandler( void )
{
    struct Message          *msg;
    struct IntuiMessage     *imsg;
    struct Gadget           *gad;
    struct JbFile           *tmp;
    ULONG                    sig, type, id, class, min, sec, rd;
    UWORD                    code;
    UBYTE                    running = TRUE, doit, buffer[ 128 ];
    BPTR                     od;

   /*
    * Open JbSpool it's window when
    * CX_POPUP is YES.
    */
    if ( JbPop ) OpenTheWindow();

    ScanDirectory();  /* Perform a directory scan */

    do {
       /*
        * If the delayed quit flag is set and
        * JbSpool isn't busy printing a file we
        * quit.
        */
        if ( ! JbPrinting && JbDelQuit )
            goto QuitIt;

        setupTimer:

       /*
        * If the timer delay isn't running we
        * send a new request to get it
        * started again.
        */
        if ( ! timerOn ) {
            sec = JbSecs;
            min = JbMins;

            if ( JbPrinting ) {
               /*
                * When the printing is in progress
                * JbSpool sends 128 bytes every
                * second to the printer. I have done this
                * because I dont want to hold up the
                * system with a busy loop.
                */
                JbSecs = 1;
                JbMins = 0;
            }

            timerReq->tr_node.io_Command = TR_ADDREQUEST;
            timerReq->tr_time.tv_micro   = 0;
            timerReq->tr_time.tv_secs    = JbMins * 60 + JbSecs;

            SendIO(( struct IORequest * )timerReq );

            timerOn = TRUE;

            if ( JbPrinting ) {
                JbSecs = sec;
                JbMins = min;
            }
        }

       /*
        * When we ar not printing and there is still a file
        * in the queue and JbSpool hasn't been stopped and
        * the offline/paper requester isn't cancelled we
        * remove the first file from the queue and open it
        * plus that we open the printer to write to.
        */
        if ( ! JbPrinting && JbFiles.jb_First->jb_Next && ! JbStopped && ! JbJobCan ) {
            if ( JbIsOpen )
                GT_SetGadgetAttrs( SpoolGadgets[ GDX_QUEUE ], SpoolWnd, NULL, GTLV_Labels, ~0, TAG_DONE );

            JbCurrentJob = ( struct JbFile * )RemHead(( struct List * )&JbFiles );

            if ( QueryPrinter( JbCurrentJob->jb_Name )) {
                od = CurrentDir( JbDirLock );
                SetProtection( JbCurrentJob->jb_Name, FIBF_WRITE | FIBF_DELETE );
                if ( JbInput = Open( JbCurrentJob->jb_Name, MODE_OLDFILE )) {
                    if ( JbPrinter = Open( "PRT:", MODE_NEWFILE )) {
                       /*
                        * Stop the timer so that it
                        * can be setup for the printer
                        * delay time.
                        */
                        AbortTimer();
                        JbPrinting = TRUE;
                        JbDone     = NULL;
                    }
                }
                CurrentDir( od );
            } else {
               /*
                * The offline/paper requester has been cancelled.
                * Now we re-queue the file on top of the list
                * and we set the job canceled flag.
                */
                AddTail(( struct List * )&JbFiles, ( struct Node * )JbCurrentJob );
                JbCurrentJob = NULL;
                JbJobCan     = TRUE;
            }

            SetupDisplay( FALSE );
        }

       /*
        * Wait for the timer, commodity and (if open) the window ports.
        */
        sig = Wait( JbMask | JbIdMask | timerMask | JbWbMask );

        if (( sig & timerMask ) == timerMask ) {
            if ( JbPrinter && JbInput && JbPrinting ) {
               /*
                * Read the next 128 bytes from the file and,
                * if no eof or error occured, send them to
                * the printer.
                */
                if (( rd = Read( JbInput, buffer, 128L )) > 0 ) {
                    JbDone += rd;
                    SetupDisplay( TRUE );
                    Write( JbPrinter, buffer, rd );
                } else {
                   /*
                    * Here we have either reached the end-of-file
                    * or a read error occured. In both cases the
                    * the file will be removed from the list and
                    * the JBS: directory.
                    */
                    AbortPrint:
                   /*
                    * Send a form-feed to the printer
                    * when this is requested.
                    */
                    if ( ! rd && JbFF )
                        FPutC( JbPrinter, '\f' );
                    Close( JbInput );
                    Close( JbPrinter );
                    JbInput = JbPrinter = NULL;
                    od = CurrentDir( JbDirLock );
                    SetProtection( JbCurrentJob->jb_Name, NULL );
                    DeleteFile( JbCurrentJob->jb_Name );
                    FreeVec( JbCurrentJob );
                    JbCurrentJob = NULL;
                    CurrentDir( od );
                    JbPrinting = FALSE;
                }
            }

           /*
            * The timer is done which means that we must
            * re-scan the JBS: directory when we are not
            * printing.
            */
            timerOn  = FALSE;

            while( msg = GetMsg( timerPort )) {
                if (( timerReq->tr_node.io_Error & IOERR_ABORTED ) != IOERR_ABORTED )
                    JbJobCan = FALSE;
            }

            if ( ! JbPrinting )
                ScanDirectory();

            Forbid();
        }

        if (( sig & JbMask ) == JbMask ) {
           /*
            * A commodity message came through.
            */
            while ( msg = GetMsg( JbComPort )) {

                id      = CxMsgID(( CxMsg * )msg );
                type    = CxMsgType(( CxMsg * )msg );
                ReplyMsg( msg );

                switch ( type ) {

                    case    CXM_IEVENT:
                        switch ( id ) {

                            case    CX_SHOW:
                                OpenTheWindow();
                                break;

                            case    CX_SHUTUP:
                                if ( ! JbPrinting )
                                    running = FALSE;
                                else
                                    JbDelQuit = TRUE;
                                break;
                        }
                        break;

                    case    CXM_COMMAND:
                        switch ( id )  {

                            case    CXCMD_KILL:
                                if ( ! JbPrinting )
                                    running = FALSE;
                                else
                                    JbDelQuit = TRUE;
                                break;

                            case    CXCMD_DISABLE:
                                CxOff( JbBroker );
                                break;

                            case    CXCMD_ENABLE:
                                CxOn( JbBroker );
                                break;

                            case    CXCMD_UNIQUE:
                            case    CXCMD_APPEAR:
                                OpenTheWindow();
                                break;

                            case    CXCMD_DISAPPEAR:
                                CloseTheWindow();
                                break;
                        }
                        break;
                }
            }
        }

        if ( JbIsOpen )  {
            if (( sig & JbIdMask ) == JbIdMask ) {
               /*
                * A window message came through.
                */
                while ( JbIdPort && ( imsg = GT_GetIMsg( JbIdPort ))) {

                    class   =   imsg->Class;
                    code    =   imsg->Code;
                    gad     =   ( struct Gadget * )imsg->IAddress;
                    GT_ReplyIMsg( imsg );

                    switch ( class ) {

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

                        case    IDCMP_CLOSEWINDOW:
                            CloseTheWindow();
                            break;

                        case    IDCMP_CHANGEWINDOW:
                            SpoolLeft = SpoolWnd->LeftEdge;
                            SpoolTop  = SpoolWnd->TopEdge;
                            break;

                        case    IDCMP_GADGETUP:
                        case    IDCMP_GADGETDOWN:
                            switch ( gad->GadgetID ) {

                                case    GD_QUEUE:
                                    JbActionNode = FindNode( code );
                                    JbNodeNum = code;
                                    SetupDisplay( FALSE );
                                    break;

                                case    GD_ADD:
                                    AddANode();
                                    break;

                                case    GD_ABORT:
                                    Abort:
                                    AbortTimer();
                                    FPuts( JbPrinter, "\n\n*** JbSpool: Dump aborted! ***\n" );
                                    rd = 1;
                                    goto AbortPrint;

                                case    GD_STOP:
                                    JbStopped = TRUE;
                                    break;

                                case    GD_CONT:
                                    JbStopped = FALSE;
                                    break;

                                case    GD_REMOVE:
                                    RemoveTheNode();
                                    break;

                                case    GD_UP:
                                    JbNodeNum = MoveTheNode( JbNodeNum, 1 );
                                    break;

                                case    GD_DOWN:
                                    JbNodeNum = MoveTheNode( JbNodeNum, 0 );
                                    break;

                                case    GD_TOP:
                                    JbNodeNum = MoveTheNode( JbNodeNum, 2 );
                                    break;

                                case    GD_BOTTOM:
                                    JbNodeNum = MoveTheNode( JbNodeNum, 3 );
                                    break;

                                case    GD_SECS:
                                    JbSecs = (( struct StringInfo * )gad->SpecialInfo )->LongInt;
                                    goto setTimer;

                                case    GD_MINS:
                                    JbMins = (( struct StringInfo * )gad->SpecialInfo )->LongInt;

                                    setTimer:

                                    if ( JbSecs >= 0 && JbMins >= 0 ) {

                                        if ( ! JbMins && ( JbSecs < 5 )) {
                                            GT_SetGadgetAttrs( SpoolGadgets[ GDX_SECS ], SpoolWnd, NULL, GTIN_Number, 5L, TAG_END );
                                            JbSecs = 5;
                                        }

                                        AbortTimer();
                                    } else {
                                        GT_SetGadgetAttrs( SpoolGadgets[ GDX_MINS ], SpoolWnd, NULL, GTIN_Number, min, TAG_END );
                                        GT_SetGadgetAttrs( SpoolGadgets[ GDX_SECS ], SpoolWnd, NULL, GTIN_Number, sec, TAG_END );
                                        JbMins = min;
                                        JbSecs = sec;
                                    }
                                    break;

                                case    GD_FF:
                                    if ( JbFF ) JbFF = FALSE;
                                    else        JbFF = TRUE;

                                    SetupDisplay( FALSE );
                                    break;

                                case    GD_HIDE:
                                    CloseTheWindow();
                                    break;

                                case    GD_QUIT:
                                    Quit:
                                    if ( ! JbPrinting )
                                        running = FALSE;
                                    else
                                        JbDelQuit = TRUE;
                                    break;
                            }
                            break;

                        case    IDCMP_MENUPICK:
                            switch( DoTheMenus( code )) {
                                case    1:
                                    goto Quit;
                                case    2:
                                    goto Abort;
                            }
                            break;
                    }
                }
            }
        }

        if ( JbApIcon ) {
            if (( sig & JbWbMask ) == JbWbMask ) {
               /*
                * We have gotten a message from
                * our AppIcon.
                */
                struct AppMessage   *apm;
                struct WBArg        *wba;
                UWORD                i;

                while ( apm = ( struct AppMessage * )GetMsg( JbWbPort )) {

                    if ( apm->am_NumArgs ) {
                       /*
                        * When am_NumArgs is non-zero it means
                        * that someone has dropped one or more
                        * icon on our AppIcon.
                        */
                        wba = apm->am_ArgList;
                        for ( i = 0; i < apm->am_NumArgs; i++, wba++ )
                           /*
                            * Copy all the files dropped over our
                            * AppIcon into the JBS: directory.
                            */
                            CopyTheFile( wba->wa_Lock, wba->wa_Name );
                    } else if ( ! JbIsOpen )
                       /*
                        * No am_NumArgs? Then we popup
                        * the window if it isn't opened.
                        */
                        OpenTheWindow();

                   /*
                    * All that's left is to reply
                    * the message.
                    */
                    ReplyMsg(( struct Message * )apm );
                }

                AbortTimer();
                if ( JbPrinting )
                    ScanDirectory();
                JbJobCan = FALSE;
            }
        }
    } while ( running );

    QuitIt:

    CloseTheWindow();
    CxOff( JbBroker );
}

/*
 * Open up the main window and get
 * the pointer to the userport and
 * the port it's bit mask.
 */
void OpenTheWindow( void )
{
    if ( ! JbIsOpen ) {

        JbIsOpen = TRUE;

        if ( SetupScreen()) {
            CloseTheWindow();
            return;
        }

        strcpy( JbWTitle, JbName " " JbVersion " : " );
        strcat( JbWTitle, JbPopkey );

       /*
        * The GadToolsBox generated source gives me
        * the possibility to set the window title
        * at run-time which is done here.
        */
        SpoolWdt = &JbWTitle[ 0 ];

        if ( OpenSpoolWindow()) {
            CloseTheWindow();
            return;
        }

        SetupDisplay( FALSE );

        if ( JbPrinting )
            SetupDisplay( TRUE );

        JbIdPort = SpoolWnd->UserPort;
        JbIdMask = ( 1L << JbIdPort->mp_SigBit );

        return;
    }
}

/*
 * Close the main window and
 * clear the userport and bit mask.
 */
void CloseTheWindow( void )
{
    if ( JbIsOpen ) {

        CloseSpoolWindow();
        CloseDownScreen();

        JbIdPort = NULL;
        JbIdMask = NULL;

        JbIsOpen = FALSE;
    }
}

/*
 * Here we get started when called from the Shell.
 */
void main( long argc, long *array )
{
    JbArgs = array;

    SetupSpool();
    EventHandler();
    CloseDownSpool( NULL );
}

/*
 * Here we get started when called from the workbench.
 */
void wbmain( struct WBStartup *wbs )
{
    WbMsg = wbs;

    SetupSpool();
    EventHandler();
    CloseDownSpool( NULL );
}
