
#include "psm.h"

struct List ScreenModeList;

/**
 ** Definition of main window
 **/

enum {
    WINTAGS_ID = 1,

    PUBLIST_ID,
    
    OWNER_ID,
    
    CLOSE_ID,
    JUMPTO_ID,
    EXAMINE_ID,

    MAKEDEF_ID,
    DEFAULT_ID,
    DUPSCRN_ID,
    MODSCRN_ID,
    
    SHANGHAI_ID,
    AUTOPOP_ID,
    NEWSCRN_ID,
    QUIT_ID,
    
    ABOUT_ID,
};

#define CURSCRN_GID GO_GRPID_A      // Gadget group that needs a public screen
                                    // to be selected.

/**
 ** Almost a menu strip for the main window
 **/

struct NewMenu mainmenu[] = {
{ NM_TITLE, "Project",    0 , 0, 0, 0 },
    {  NM_ITEM, "About...",   "A", 0, 0, (void *)ABOUT_ID },
    {  NM_ITEM, "Quit",       "Q", 0, 0, (void *)QUIT_ID },

    {   NM_END, NULL,          0 , 0, 0, 0 },
};

static ULONG __saveds __interrupt
main_handler(struct GadOutline *go, ULONG command, struct GOIMsg *msg);

static ULONG main_outline[] = {

GO_OUTLINETAGS(0,0),
    TAG_END,
    GOA_BaseName,           (ULONG)&__BaseName[0],
    GOA_SetUserHandler,     (ULONG)&main_handler,
    GOA_SetTransHookData,   NULL,
    GOA_UserIDCMP,          ~0,
    GOA_ErrorReportLevel,     
          (1L<<GOTYPE_FINE)  | (1L<<GOTYPE_FINE2)
        | (1L<<GOTYPE_NOTE)  | (1L<<GOTYPE_NOTE2)
        | (1L<<GOTYPE_WARN)  | (1L<<GOTYPE_WARN2)
        | (1L<<GOTYPE_ALERT) | (1L<<GOTYPE_ALERT2) | (1L<<GOTYPE_ALERT3)
        | (1L<<GOTYPE_FAIL)  | (1L<<GOTYPE_FAIL2)  | (1L<<GOTYPE_FAIL3),
    GOA_AllocMenus,         (ULONG)&mainmenu[0],
    TAG_END,

GO_WINDOWTAGS(0,WINTAGS_ID),
    TAG_END,
    WA_Title,           (ULONG)&__ShortTitle[0],
    WA_ScreenTitle,     (ULONG)&__LongTitle[0],
    WA_PubScreenFallBack, TRUE,
    WA_IDCMP,           IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW |
                        IDCMP_INTUITICKS | IDCMP_MENUPICK,
    WA_Activate,        TRUE,
    WA_CloseGadget,     TRUE,
    WA_DepthGadget,     TRUE,
    WA_DragBar,         TRUE,
    WA_SizeGadget,      TRUE,
    WA_SizeBBottom,     TRUE,
    WA_SimpleRefresh,   TRUE,
    WA_BackFill,        NULL,
    TAG_END,

GO_COMMANDTAGS(0,0),
    TAG_END,
    GOCT_SetHotKey, 0,
    TAG_END,

GO_VERTGRP(0,0,1),
GOCT_SizeSpaceAbove, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
GOCT_SizeSpaceBelow, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
GOCT_SizeSpaceLeft, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
GOCT_SizeSpaceRight, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
TAG_END,

    GO_VDRAWGRP(0,0,1),
    GOCT_SizeSpaceAbove, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
    GOCT_SizeSpaceBelow, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
    GOCT_SizeSpaceLeft, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
    GOCT_SizeSpaceRight, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
    TAG_END,
    GODT_DrawStdFrame, GO_SCLPNT(SHINEPEN,0,0,63,63),
    TAG_END,

        GO_GTBOX(LISTVIEW_KIND, 0, PUBLIST_ID, 1,
            (ULONG)&"_Public Screens", PLACETEXT_ABOVE|NG_HIGHLABEL),
        GOCT_SizeBodyHeight, GO_TSIZE(GOM_StdMax,500,GOT_PercCharH),
        GOCT_SizeUser1, GO_TSIZE(GOM_VarSet,200,GOT_PercCharW),
        GOCT_CopyUser1ToTag, GTLV_ScrollWidth,
        TAG_END,
        GTLV_Selected, ~0,
        GTLV_Labels, NULL,
        GTLV_ShowSelected, NULL,
        GTLV_ScrollWidth, 16,
        GT_Underscore, '_',
        GA_Disabled, FALSE,
        TAG_END,

        GO_GTBOX(TEXT_KIND, 0, OWNER_ID, 0,
            (ULONG)&"Owner: ", PLACETEXT_LEFT|NG_HIGHLABEL),
        GOCT_SizeBodyWidth, GO_TSIZE(GOM_StdAdd,500,GOT_PercCharW),
        GOCT_SizeSpaceBelow, GO_TSIZE(GOM_StdSet,50,GOT_PercCharH),
        TAG_END,
        GTTX_Text, (ULONG)&"",
        GTTX_Border, TRUE,
        GA_Disabled, FALSE,
        TAG_END,

        GO_VDRAWGRP(0,0,0),
        GOCT_SizeSpaceAbove, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
        GOCT_SizeSpaceBelow, GO_TSIZE(GOM_PadSet,50,GOT_PercCharH),
        GOCT_SizeSpaceLeft, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
        GOCT_SizeSpaceRight, GO_TSIZE(GOM_PadSet,50,GOT_PercCharW),
        TAG_END,
        GODT_DrawStdFrame, GO_SCLPNT(SHINEPEN,0,0,63,63),
        TAG_END,

            GO_HORIZGRP(0,0,0), GOCT_EvenDistGroup, TRUE, TAG_END,
    
                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, CLOSE_ID, 0,
                    (ULONG)&"_Close", PLACETEXT_IN),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
        
                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, JUMPTO_ID, 0,
                    (ULONG)&"_Jump to", PLACETEXT_IN),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
        
                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, EXAMINE_ID, 0,
                    (ULONG)&"_Examine", PLACETEXT_IN),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
    
            GO_ENDGRP(),
    
            GO_HORIZGRP(0,0,0),
            GOCT_EvenDistGroup, TRUE,
            TAG_END,

                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, DUPSCRN_ID, 0,
                    (ULONG)&"_Dup Scrn", PLACETEXT_IN),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
        
                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, MODSCRN_ID, 0,
                    (ULONG)&"_Mod Scrn", PLACETEXT_IN),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
        
            GO_ENDGRP(),
    
            GO_HORIZGRP(0,0,0), TAG_END,

                GO_GTBOX(BUTTON_KIND, CURSCRN_GID, MAKEDEF_ID, 0,
                    (ULONG)&"De_fault", PLACETEXT_IN),
                GOCT_SizeSpaceRight, GO_TSIZE(GOM_AllSet,0,GOT_Pixels),
                TAG_END,
                GA_Disabled, FALSE,
                GT_Underscore, '_',
                TAG_END,
        
                GO_GTBOX(TEXT_KIND, 0, DEFAULT_ID, 1, NULL, 0),
                GOCT_SizeBodyWidth, GO_TSIZE(GOM_StdAdd,500,GOT_PercCharW),
                GOCT_SizeSpaceLeft, GO_TSIZE(GOM_AllSet,0,GOT_Pixels),
                TAG_END,
                GTTX_Text, (ULONG)&"",
                GTTX_Border, TRUE,
                GA_Disabled, FALSE,
                TAG_END,
                
            GO_ENDGRP(),

        GO_ENDGRP(),

    GO_ENDGRP(),
    
    GO_HORIZGRP(0,0,0), 
    GOCT_EvenDistGroup, TRUE, 
    GOCT_SizeSpaceAbove, GO_TSIZE(GOM_StdSet,50,GOT_PercCharH),
    TAG_END,

        GO_GTBOX(CHECKBOX_KIND, 0, SHANGHAI_ID, 0,
            (ULONG)&"_Shanghai", PLACETEXT_RIGHT),
        TAG_END,
        GTCB_Checked, FALSE,
        GA_Disabled, FALSE,
        GT_Underscore, '_',
        TAG_END,
        
        GO_GTBOX(CHECKBOX_KIND, 0, AUTOPOP_ID, 0,
            (ULONG)&"_AutoPop", PLACETEXT_RIGHT),
        TAG_END,
        GTCB_Checked, FALSE,
        GA_Disabled, FALSE,
        GT_Underscore, '_',
        TAG_END,
        
    GO_ENDGRP(),
    
    GO_HORIZGRP(0,0,0), 
    GOCT_EvenDistGroup, TRUE, 
    TAG_END,

        GO_GTBOX(BUTTON_KIND, 0, NEWSCRN_ID, 0,
            (ULONG)&"_New Scrn", PLACETEXT_IN),
        TAG_END,
        GA_Disabled, FALSE,
        GT_Underscore, '_',
        TAG_END,
        
        GO_GTBOX(BUTTON_KIND, 0, QUIT_ID, 0,
            (ULONG)&"_Quit", PLACETEXT_IN),
        TAG_END,
        GA_Disabled, FALSE,
        GT_Underscore, '_',
        TAG_END,
    
    GO_ENDGRP(),
    
GO_ENDGRP(),

GO_ENDOUTLINE()
};

/**
 ** Handler for main window
 **/

#define TEXT_LEN 252

struct main_globals {
    struct List pubscrn_list;           // List of screens (struct pubscrn_entry)
    struct pubscreen_entry *cur_pubscrn;    // Currently selected screen
    ULONG ticks_since_update;           // How long since it's been updated

    struct List empty_list;             // Unused pubscrn_entry structures.
    
    struct List outline_list;           // All opened outlines.
    
    struct Hook bf_hook;

    UBYTE defpub_name[TEXT_LEN+4];      // Name of current default pub screen
};

struct pubscreen_entry {
    struct Node node;       // ln_Name points to &desc[0]
    WORD resv0;
    UWORD flags;            // as per PubScreenNode
    WORD visitor_count;     // ""
    struct Screen *screen;  // the actual screen
    struct Task *owner;     // task which owns the screen
    UBYTE *name;            // start of actual name in buffer
    UBYTE desc[TEXT_LEN+4]; // description - 'x yy zzzzzzz...'
                            // x = P if private, yy is visitor count,
                            // zzz... is screen name.
    UBYTE owner_name[TEXT_LEN+4];   // name of owner task.
};

static void update_checkbox(struct GadOutline *go,CMDID cmdid,ULONG newval)
{
    ULONG oldval;
    
    oldval = GO_GetObjAttr(go,cmdid,0, GTCB_Checked,FALSE);
    if( (!oldval && newval) || (oldval && !newval) ) {
        GO_SetObjAttrs(go,cmdid,0, GTCB_Checked,newval,TAG_END);
    }
}

static BOOL update_publist(struct GadOutline *go)
{
    struct main_globals *gl;
    struct PubScreenNode *cur_scrn;
    struct pubscreen_entry *cur_entry;
    BOOL list_changed = FALSE;
    UBYTE name_selected[TEXT_LEN+4];

    if( !(gl = go->go_UserData) ) return FALSE;

    cur_entry = head_node(&gl->pubscrn_list);
    name_selected[0] = 0;
    
    for(cur_scrn = head_node(LockPubScreenList());
        cur_scrn != NULL;
        cur_scrn = next_node(cur_scrn)) {
        
        // Check if node in list and pubscreen nodes match...
        if( cur_entry == NULL || cur_entry->flags != cur_scrn->psn_Flags
            || cur_entry->visitor_count != cur_scrn->psn_VisitorCount
            || strcmp(cur_entry->name,cur_scrn->psn_Node.ln_Name) != 0 ) {
            
            // They don't; set changed flag and detach from listview because
            // we are going to make changes.
            if(list_changed == FALSE) {
                GO_SetObjAttrs(go,PUBLIST_ID,0,GTLV_Labels,~0,TAG_END);
                list_changed = TRUE;
            }
            
            // If this is the currently selected screen, stash its name.
            if(cur_entry && gl->cur_pubscrn == cur_entry) {
                strcpy(name_selected,gl->cur_pubscrn->name);
                gl->cur_pubscrn = NULL;
            }

            // If failed because no more entries in list, get a new one.
            if(cur_entry == NULL) {
                cur_entry = (struct pubscreen_entry *)RemHead(&gl->empty_list);
                if(cur_entry == NULL)
                    cur_entry = AllocVec(sizeof(struct pubscreen_entry),MEMF_PUBLIC);
                if(cur_entry != NULL)
                    AddTail(&gl->pubscrn_list,&cur_entry->node);
            }
            
            // Fillin this entry.
            if(cur_entry) {
                cur_entry->flags = cur_scrn->psn_Flags;
                cur_entry->visitor_count = cur_scrn->psn_VisitorCount;
                cur_entry->screen = cur_scrn->psn_Screen;
                cur_entry->owner = cur_scrn->psn_SigTask;

                if(CheckScrnTrack(cur_scrn->psn_Screen)) {
                    cur_entry->desc[0] = '*';
                } else {
                    cur_entry->desc[0] = 
                        ((cur_scrn->psn_Flags&PSNF_PRIVATE) ? 'P' : ' ');
                }
                cur_entry->desc[1] = (cur_scrn->psn_VisitorCount/10)+ '0';
                cur_entry->desc[2] = cur_scrn->psn_VisitorCount
                                    - ((cur_entry->desc[1]-'0')*10) + '0';
                cur_entry->desc[3] = ' ';
                strncpy(&cur_entry->desc[4],cur_scrn->psn_Node.ln_Name,TEXT_LEN);
                    
                cur_entry->node.ln_Name = 
                    &cur_entry->desc[0];
                cur_entry->name = &cur_entry->desc[4];
                
                if(cur_scrn->psn_SigTask) {
                    if(cur_scrn->psn_SigTask->tc_Node.ln_Name) {
                        strncpy(&cur_entry->owner_name[0],
                                cur_scrn->psn_SigTask->tc_Node.ln_Name,TEXT_LEN);
                    } else {
                        strcpy(&cur_entry->owner_name[0],"Unknown.");
                    }
                } else {
                    strcpy(&cur_entry->owner_name[0],"None.");
                }
            }
        }
        
        // Skip to next node in listview display.
        cur_entry = next_node(cur_entry);
    }
    UnlockPubScreenList();

    // Remove any extra entries from list.
    while(cur_entry) {
        struct pubscreen_entry *next_entry;

        // Set changed flag and detach from listview if needed.
        if(list_changed == FALSE) {
            GO_SetObjAttrs(go,PUBLIST_ID,0,GTLV_Labels,~0,TAG_END);
            list_changed = TRUE;
        }
        
        // If this is the currently selected screen, stash its name.
        if(cur_entry && gl->cur_pubscrn == cur_entry) {
            strcpy(name_selected,gl->cur_pubscrn->name);
            gl->cur_pubscrn = NULL;
        }

        next_entry = next_node(cur_entry);
        Remove(&cur_entry->node);
        AddHead(&gl->empty_list,&cur_entry->node);
        cur_entry = next_entry;
    }
    
    if(name_selected[0] == 0 && list_changed) {
        if(go->go_Screen) {
            for(cur_entry = head_node(&gl->pubscrn_list);
                cur_entry != NULL && cur_entry->screen != go->go_Screen;
                cur_entry = next_node(cur_entry))
                ;
            if(cur_entry != NULL) {
                strcpy(name_selected,cur_entry->name);
            }
        } else {
            GetDefaultPubScreen(name_selected);
        }
    }
            
    if(list_changed) {
        gl->cur_pubscrn = NULL;
        for(cur_entry = head_node(&gl->pubscrn_list);
            gl->cur_pubscrn == NULL && cur_entry != NULL;
            cur_entry = next_node(cur_entry)) {
            
            if(strcmp(name_selected,cur_entry->name) == 0)
                gl->cur_pubscrn = cur_entry;
        }
        
        if(!gl->cur_pubscrn) gl->cur_pubscrn = head_node(&gl->pubscrn_list);
        
        GO_SetObjAttrs(go,PUBLIST_ID,0,
            GTLV_Labels, &gl->pubscrn_list,
            GTLV_Selected, node_to_num(&gl->pubscrn_list,gl->cur_pubscrn),
            TAG_END);
        GO_SetObjAttrs(go,OWNER_ID,0,
            GTTX_Text, gl->cur_pubscrn ? gl->cur_pubscrn->owner_name : "",
            TAG_END);
    }
    
    // Check if default pub screen has changed
    GetDefaultPubScreen(&name_selected[0]);
    if(strcmp(name_selected,gl->defpub_name) != 0) {
        strcpy(gl->defpub_name,name_selected);
        list_changed = TRUE;
        GO_SetObjAttrs(go,DEFAULT_ID,0,
            GTTX_Text, gl->defpub_name,
            TAG_END);
    }

    {
        UWORD modes;
        
        Forbid();
        modes = SetPubScreenModes(0);
        SetPubScreenModes(modes);
        Permit();
        
        update_checkbox(go,SHANGHAI_ID,modes&SHANGHAI);
        update_checkbox(go,AUTOPOP_ID,modes&POPPUBSCREEN);
    }

    return list_changed;
}

static void remove_outline(struct GadOutline *go,struct GadOutline *dest_go)
{
    struct main_globals *gl;
    struct Node *cur_node;

    if(!go) return;
    gl = go->go_UserData;
        
    if(gl) {
        for(cur_node = head_node(&gl->outline_list);
            cur_node != NULL;
            cur_node = next_node(cur_node)) {
            
            if((void *)cur_node->ln_Name == (void *)dest_go) {
                Remove(cur_node);
                FreeVec(cur_node);
                cur_node = NULL;
            }
        }
    }

    if(dest_go) ((HANDLER *)dest_go->go_UserHandler)(dest_go,
                                HNDCMD_SHUTDOWN,(struct GOIMsg *)dest_go);
}

static void dist_hndcmd(struct GadOutline *go,ULONG cmd,void *msg)
{
    struct main_globals *gl;
    struct Node *curnode;

    if(!go) return;
    gl = go->go_UserData;
        
    if(gl) {
    
        // Send command to children.
        for(curnode = head_node(&gl->outline_list);
            curnode != NULL;
            curnode = next_node(curnode)) {

            struct GadOutline *cur_go;
            
            cur_go = (struct GadOutline *)curnode->ln_Name;
            if( ((HANDLER *)cur_go->go_UserHandler)(cur_go,cmd,msg)
                    == HNDRES_CLOSEWIN ) {
                
                curnode = curnode->ln_Succ;
                remove_outline(go,cur_go);
                curnode = curnode->ln_Pred;
            }
        }
    }
}

static void hide_windows(struct GadOutline *go)
{
    dist_hndcmd(go,HNDCMD_HIDEWIN,NULL);
    GO_CloseScreen(go);     // Make sure we totally unlink.
}

static void move_windows(struct GadOutline *go)
{
    dist_hndcmd(go,HNDCMD_MOVEWIN,go->go_Screen);
}

static void lock_program(struct GadOutline *go)
{
    dist_hndcmd(go,HNDCMD_LOCK,NULL);
    LockGadOutline(go);
}

static void unlock_program(struct GadOutline *go)
{
    UnlockGadOutline(go);
    dist_hndcmd(go,HNDCMD_UNLOCK,NULL);
}

static struct Node *add_outline(struct GadOutline *go,struct GadOutline *dest_go)
{
    struct main_globals *gl;
    struct Node *cur_node = NULL;

    if(!go) return NULL;
    if(!(gl = go->go_UserData)) return NULL;
        
    if(gl) {
        if(!(cur_node = AllocVec(sizeof(struct Node),MEMF_PUBLIC|MEMF_CLEAR))) {
            return NULL;
        }
        
        cur_node->ln_Name = (UBYTE *)dest_go;
        AddTail(&gl->outline_list,cur_node);
    }
    return cur_node;
}

static void update_curscrn(struct GadOutline *go)
{
    struct main_globals *gl;

    if(!go) return;
    if( !(gl = go->go_UserData)) return;
        
    GO_SetObjAttrs(go,OWNER_ID,0,
        GTTX_Text, gl->cur_pubscrn
                    ? gl->cur_pubscrn->owner_name : "",
        TAG_END);

    if(!gl->cur_pubscrn || (gl->cur_pubscrn->flags&PSNF_PRIVATE)) {
        GO_SetObjGrpAttrs(go,GO_CMDID(CURSCRN_GID,0),0,
            GA_Disabled,TRUE,TAG_END);
    } else {
        GO_SetObjGrpAttrs(go,GO_CMDID(CURSCRN_GID,0),0,
            GA_Disabled,FALSE,TAG_END);
        if(gl->cur_pubscrn->flags&PSNF_PRIVATE) {
            GO_SetObjAttrs(go,JUMPTO_ID,0,GA_Disabled,TRUE,TAG_END);
        } else {
            GO_SetObjAttrs(go,JUMPTO_ID,0,GA_Disabled,FALSE,TAG_END);
        }
        if( gl->cur_pubscrn->visitor_count > 0
            && !(gl->cur_pubscrn->screen == go->go_Screen
                 && gl->cur_pubscrn->visitor_count <= 2) ) {
            GO_SetObjAttrs(go,CLOSE_ID,0,GA_Disabled,TRUE,TAG_END);
            GO_SetObjAttrs(go,MODSCRN_ID,0,GA_Disabled,TRUE,TAG_END);
        } else {
            GO_SetObjAttrs(go,CLOSE_ID,0,GA_Disabled,FALSE,TAG_END);
            GO_SetObjAttrs(go,MODSCRN_ID,0,GA_Disabled,FALSE,TAG_END);
        }
    }
}

static ULONG __saveds __interrupt
main_handler(struct GadOutline *go, ULONG command, struct GOIMsg *msg)
{
    struct main_globals *gl;
    struct Gadget *gadget;
    ULONG class;
    UWORD code;
    UWORD qual;

    if(go == NULL) return HNDRES_TERMINATE;

    if( !(gl = (struct main_globals *)go->go_UserData) ) {
        DestroyMain(go);
        return HNDRES_TERMINATE;
    }

    if(command == HNDCMD_IDCMPMSG) {
        if( msg ) {
            class = msg->StdIMsg.Class;
            code = msg->StdIMsg.Code;
            qual = msg->StdIMsg.Qualifier;
            gadget = (struct Gadget *)msg->StdIMsg.IAddress;
        } else {
            return HNDRES_TERMINATE;
        }
    } else if(command == HNDCMD_SHUTDOWN) {
        if(msg && go != (struct GadOutline *)msg) {
            remove_outline(go,(struct GadOutline *)msg);
            update_curscrn(go);
            return HNDRES_NORMAL;
        } else {
            return HNDRES_TERMINATE;
        }
    } else if(command == HNDCMD_TICK) {
        dist_hndcmd(go,command,msg);
        class = IDCMP_INTUITICKS;
    } else {
        return HNDRES_NORMAL;
    }

    switch (class) {

        case IDCMP_CLOSEWINDOW:
        {
            return HNDRES_TERMINATE;
        } break;

        case IDCMP_REFRESHWINDOW:
        {
            GO_BeginRefresh(go);
            GO_EndRefresh(go, TRUE);
        } break;

        case IDCMP_INTUITICKS:
        {
            if( (++gl->ticks_since_update) > 5) {
                if(update_publist(go)) update_curscrn(go);
                gl->ticks_since_update = 0;
            }
        } break;

        case IDCMP_MENUPICK:
        {
            struct MenuItem *item;
            while(code != MENUNULL) {
                item = ItemAddress(go->go_MenuStrip, code);

                switch((ULONG)GTMENUITEM_USERDATA(item)) {
                
                    case QUIT_ID:
                    {
                        return HNDRES_TERMINATE;
                    } break;
                    
                    case ABOUT_ID:
                    {
                        lock_program(go);
                        GO_ShowError(go,GO_MAKEERR(GOTYPE_FINE,0),NULL,
"%ls v%ls\nCopyright © %ls Dianne Hackborn\nCreated on %ls at %ls\n\n"
"User interface created with\n^Zb v^Zv.^Zr\n\n"
"This program is shareware.  If you find\n"
"it useful, please send $5 or whatever you\n"
"feel it is worth to:\n"
"    Dianne Hackborn\n"
"    2895 Los Altos Drive\n"
"    Meridian, ID 83642",
                            &__ProgName[0],&__FullVersion[0],&__BuildYear[0],&__BuildDate[0],&__BuildTime[0]);
                        unlock_program(go);
                    } break;
                }

                code = item->NextSelect;
            }
        } break;

        case IDCMP_GADGETUP:
        case IDCMP_GADGETDOWN:
        {
        
            switch(gadget->GadgetID) {
            
                case PUBLIST_ID:
                {
                    gl->cur_pubscrn = num_to_node(&gl->pubscrn_list,code);
                    update_curscrn(go);
                } break;

                case CLOSE_ID:
                {
                    struct Screen *scrn;

                    if(gl->cur_pubscrn) {
                        BOOL do_reopen = FALSE;
    
                        scrn = LockPubScreen(gl->cur_pubscrn->name);
                        if(scrn == go->go_Screen) {
                            do_reopen = TRUE;
                            hide_windows(go);
                            GO_CloseScreen(go);     // Make sure we totally unlink
                        }

                        Forbid();
                        UnlockPubScreen(NULL,scrn);
                        (void)CloseScrnTrack(scrn);
                        Permit();
                        if(do_reopen) {
                            GO_OpenWindow(go,
                                    WA_PubScreenName, gl->cur_pubscrn->name,
                                    TAG_END);
                            move_windows(go);
                        }
                        gl->cur_pubscrn = NULL;
                    }
                    if(update_publist(go)) update_curscrn(go);
                } break;

                case JUMPTO_ID:
                {
                    hide_windows(go);
                    DimenGadOutline(go,
                        GOA_ScreenName,
                            gl->cur_pubscrn ? gl->cur_pubscrn->name : "",
                        GOA_ScreenFallBack, TRUE,
                        TAG_END);
                    if(go->go_LastReqReturn)
                        DimenGadOutline(go,GOA_ScreenAddr,NULL,TAG_END);
                        if(go->go_LastReqReturn)
                            DimenGadOutline(go,GOA_ScreenName,"Workbench",TAG_END);
                            if(go->go_LastReqReturn)
                                return HNDRES_TERMINATE;

                    move_windows(go);
                    if(!GO_OpenWindow(go,TAG_END)) return HNDRES_TERMINATE;
                    update_publist(go);
                    update_curscrn(go);
                } break;

                case EXAMINE_ID:
                {
                    struct GadOutline *ex_go;
                    struct Screen *scrn;
                    
                    if(!gl->cur_pubscrn)
                        return HNDRES_NORMAL;
                    if(!(scrn=LockPubScreen(gl->cur_pubscrn->name)))
                        return HNDRES_NORMAL;

                    if(ex_go=CreateExamine(go,gl->cur_pubscrn->name,scrn)) {
                        if(!add_outline(go,ex_go)) {
                            ((HANDLER *)ex_go->go_UserHandler)(ex_go,
                                        HNDCMD_SHUTDOWN,(struct GOIMsg *)ex_go);
                        }
                    }
                    UnlockPubScreen(NULL,scrn);
                    if(update_publist(go)) update_curscrn(go);
                        
                } break;

                case MAKEDEF_ID:
                {
                    if(gl->cur_pubscrn) {
                        SetDefaultPubScreen(gl->cur_pubscrn->name);
                        update_publist(go);
                        update_curscrn(go);
                    }
                } break;

                case DUPSCRN_ID:
                {
                    struct GadOutline *ns_go;
                    struct Screen *scrn;
                    
                    if(!gl->cur_pubscrn)
                        return HNDRES_NORMAL;
                    if(!(scrn=LockPubScreen(gl->cur_pubscrn->name)))
                        return HNDRES_NORMAL;

                    if(ns_go=CreateNewScrn(go,gl->cur_pubscrn->name,scrn,FALSE)) {
                        if(!add_outline(go,ns_go)) {
                            ((HANDLER *)ns_go->go_UserHandler)(ns_go,
                                        HNDCMD_SHUTDOWN,(struct GOIMsg *)ns_go);
                        }
                    }
                    UnlockPubScreen(NULL,scrn);
                    if(update_publist(go)) update_curscrn(go);
                        
                } break;

                case MODSCRN_ID:
                {
                    struct GadOutline *ns_go;
                    struct Screen *scrn;
                    
                    if(!gl->cur_pubscrn)
                        return HNDRES_NORMAL;
                    if(!(scrn=LockPubScreen(gl->cur_pubscrn->name)))
                        return HNDRES_NORMAL;

                    if(ns_go=CreateNewScrn(go,gl->cur_pubscrn->name,scrn,TRUE)) {
                        if(!add_outline(go,ns_go)) {
                            ((HANDLER *)ns_go->go_UserHandler)(ns_go,
                                        HNDCMD_SHUTDOWN,(struct GOIMsg *)ns_go);
                        }
                    }
                    UnlockPubScreen(NULL,scrn);
                    if(update_publist(go)) update_curscrn(go);
                        
                } break;

                case SHANGHAI_ID:
                {
                    ULONG newval;
                    UWORD modes;
                    
                    newval = GO_GetObjAttr(go,SHANGHAI_ID,0, GTCB_Checked,FALSE);
                    Forbid();
                    modes = SetPubScreenModes(0);
                    SetPubScreenModes(
                        (modes&~(SHANGHAI)) | (newval ? SHANGHAI : 0) );
                    Permit();
                } break;

                case AUTOPOP_ID:
                {
                    ULONG newval;
                    UWORD modes;
                    
                    newval = GO_GetObjAttr(go,AUTOPOP_ID,0, GTCB_Checked,FALSE);
                    Forbid();
                    modes = SetPubScreenModes(0);
                    SetPubScreenModes(
                        (modes&~(POPPUBSCREEN)) | (newval ? POPPUBSCREEN : 0) );
                    Permit();
                } break;

                case NEWSCRN_ID:
                {
                    struct GadOutline *ns_go;
                    struct Screen *scrn;
                    
                    if(!(scrn=LockPubScreen(NULL)))
                        return HNDRES_NORMAL;

                    if(ns_go=CreateNewScrn(go,NULL,scrn,FALSE)) {
                        if(!add_outline(go,ns_go)) {
                            ((HANDLER *)ns_go->go_UserHandler)(ns_go,
                                        HNDCMD_SHUTDOWN,(struct GOIMsg *)ns_go);
                        }
                    }
                    UnlockPubScreen(NULL,scrn);
                    if(update_publist(go)) update_curscrn(go);
                        
                } break;

                case QUIT_ID:
                {
                    return HNDRES_TERMINATE;
                } break;
            }
        } break;
    }
    
    return HNDRES_NORMAL;
}

void DestroyMain(struct GadOutline *go)
{
    struct main_globals *gl;
    struct Node *curnode;

    if(!go) return;
    gl = go->go_UserData;
        
    GO_SetObjAttrs(go,PUBLIST_ID,0,GTLV_Labels,0,TAG_END);

    if(gl) {
    
        // Close all opened outlines.
        while( (curnode = RemTail(&gl->outline_list)) != NULL ) {
            struct GadOutline *cur_go;
            
            cur_go = (struct GadOutline *)curnode->ln_Name;
            ((HANDLER *)cur_go->go_UserHandler)(cur_go,
                    HNDCMD_SHUTDOWN,(struct GOIMsg *)cur_go);
            FreeVec(curnode);
        }
        
        // Free list of public screens.
        while( (curnode = RemTail(&gl->pubscrn_list)) != NULL ) {
            FreeVec(curnode);
        }
        
        // Free extra list of public screen nodes.
        while( (curnode = RemTail(&gl->empty_list)) != NULL ) {
            FreeVec(curnode);
        }
    }
    
    FreeGadOutline(go);
    
    // Free global variables.
    if(gl) FreeVec(gl);
}

struct GadOutline *CreateMain(struct MsgPort *glob_port)
{
    struct GadOutline *go;
    struct main_globals *gl;
    
    go = AllocGadOutline(&main_outline[0],
            GOA_OutlineSize,    sizeof(main_outline),
            //GOA_UserIDCMP,        glob_port,
            TAG_END);
    if(!go) return NULL;
    
    gl = AllocVec(sizeof(struct main_globals),MEMF_PUBLIC|MEMF_CLEAR);
    if(!gl) {
        DestroyMain(go);
        return NULL;
    }
    
    NewList(&gl->pubscrn_list);
    NewList(&gl->empty_list);
    NewList(&gl->outline_list);
    go->go_UserData = gl;
    SetupBackFillHook(&gl->bf_hook,go);
    GO_SetObjAttrs(go,WINTAGS_ID,0,WA_BackFill,&gl->bf_hook,TAG_END);

    update_publist(go);
    update_curscrn(go);

    if( !GO_OpenWindow(go, WA_PubScreen, NULL, TAG_END) ) {
        if( !GO_OpenWindow(go, WA_PubScreenName, "Workbench", TAG_END) ) {
            DestroyMain(go);
            return NULL;
        }
    }
    
    return go;
}

static void pnum32(UBYTE *to,ULONG num,ULONG bitsperdig,ULONG numdig,ULONG space)
{
    if(space == 0) space = 2000;
    
    while(numdig > 0) {
        UBYTE curdig;
        
        curdig = (num>>((numdig-1)*bitsperdig)) & ((1<<bitsperdig)-1);
        if(curdig < 10) *to = curdig + '0';
        else *to = curdig + 'A' - 10;
        to++;
        numdig--;
        if(numdig > 0 && (numdig%space) == 0) {
            *to = '.';
            to++;
        }
    }
    *to = 0;
}

#define phex32(to,num,numdig,space) pnum32(to,num,4,numdig,space)
#define pbin32(to,num,numdig,space) pnum32(to,num,1,numdig,space)

static void read_modes(struct GadOutline *go,struct List *modelist)
{
    ULONG mode;

    NewList(modelist);

    // Get display modes.
    mode = INVALID_ID;

    while( (mode = NextDisplayInfo(mode)) != INVALID_ID ) {
        struct ModeEntry *entry;
        struct NameInfo info;

        if( ModeNotAvailable(mode) == 0
            && GetDisplayInfoData(NULL,(UBYTE *)&info,sizeof(info),
                DTAG_NAME,mode) ) {

            if(entry = GO_AllocMem(go,GOTYPE_WARN,
                            sizeof(struct ModeEntry),MEMF_PUBLIC|MEMF_CLEAR)) {
                entry->mode = mode;
                GetDisplayInfoData(NULL,(UBYTE *)&entry->disp,
                                    sizeof(entry->disp),DTAG_DISP,mode);
                GetDisplayInfoData(NULL,(UBYTE *)&entry->dims,
                                    sizeof(entry->dims),DTAG_DIMS,mode);
                GetDisplayInfoData(NULL,(UBYTE *)&entry->mntr,
                                    sizeof(entry->mntr),DTAG_MNTR,mode);
                entry->name = info;
                if(entry->name.Name[0] == 0) {
                    entry->name.Name[0] = '0';
                    entry->name.Name[1] = 'x';
                    phex32(&entry->name.Name[2],mode,8,0);
                }
                entry->node.ln_Name = &entry->name.Name[0];
                AddTail(modelist,&entry->node);
            }

        }
    }
}
    
void delete_timer(struct timerequest *tr)
{
    struct MsgPort *tp;

    if (tr != 0 ) {
        tp = tr->tr_node.io_Message.mn_ReplyPort;

        if (tp != 0) {
            DeletePort(tp);
        }

        CloseDevice( (struct IORequest *) tr );
        DeleteExtIO( (struct IORequest *) tr );
    }
}

struct timerequest *create_timer( ULONG unit )
{
    LONG error;
    struct MsgPort *timerport;
    struct timerequest *TimerIO;

    timerport = CreatePort( 0, 0 );
    if (timerport == NULL )
        return( NULL );

    TimerIO = (struct timerequest *)
        CreateExtIO( timerport, sizeof( struct timerequest ) );
    if (TimerIO == NULL ) {
        DeletePort(timerport);   /* Delete message port */
        return( NULL );
    }

    error = OpenDevice( TIMERNAME, unit,(struct IORequest *) TimerIO, 0L );
    if (error != 0 ) {
        delete_timer( TimerIO );
        return( NULL );
    }
    return( TimerIO );
}

void abort_timer(struct timerequest *tr)
{
    AbortIO((struct IORequest *)tr);
    WaitIO((struct IORequest *)tr);
}

void send_timer(struct timerequest *tr)
{
    tr->tr_time.tv_secs = 1;
    tr->tr_time.tv_micro = 0;
    tr->tr_node.io_Command = TR_ADDREQUEST;
    SendIO((struct IORequest *)tr);
}

#ifdef BETALIB
struct Library *GadOutlineBase = NULL;
#endif

void __regargs main(int argc,char **argv)
{
    struct Process *me = NULL;
    struct Window *oldwin = NULL;   // what me->pr_WindowPtr previously was
    struct timerequest *timer;
    struct GadOutline *main_go;

    if( !(me = (struct Process *)FindTask(NULL)) ) {
        exit(20);
    }
    oldwin = (struct Window *)me->pr_WindowPtr;

    timer = create_timer(UNIT_VBLANK);
    if(!timer) exit(20);

    #ifdef BETALIB
    if( !(GadOutlineBase = OpenLibrary("beta_gadoutline.library",1)) ) {
        delete_timer(timer);
        exit(20);
    }
    #endif
    
    main_go = CreateMain(NULL);
    if(!main_go) {
        #ifdef BETALIB
        CloseLibrary(GadOutlineBase);
        #endif
        delete_timer(timer);
        exit(20);
    }
    
    read_modes(main_go,&ScreenModeList);

    send_timer(timer);  // Prime the pump... ;)

    while(1) {
        struct GadOutline *msg_go;
        struct GOIMsg *msg;
        ULONG ret = HNDRES_NORMAL;

        me->pr_WindowPtr = (APTR)main_go->go_Window;
        
        Wait( (1L<<main_go->go_MsgPort->mp_SigBit) 
              | (1L<<timer->tr_node.io_Message.mn_ReplyPort->mp_SigBit) );

        while( (msg = GO_GetGOIMsg(main_go)) ) {
            struct GOIMsg *dup_msg;
            
            msg_go = GO_GetGOFromGOIMsg(msg);
            
            if(msg_go)
                dup_msg = GO_DupGOIMsg(msg_go,msg);
            else
                dup_msg = NULL;

            GO_ReplyGOIMsg(msg);
            
            if(dup_msg) {
                ret = ((HANDLER *)msg_go->go_UserHandler)
                    (msg_go,HNDCMD_IDCMPMSG,dup_msg);
                (void)GO_UndupGOIMsg(dup_msg);
            }
            
            if(ret == HNDRES_CLOSEWIN) {
                ret = ((HANDLER *)main_go->go_UserHandler)
                    (main_go,HNDCMD_SHUTDOWN,(struct GOIMsg *)msg_go);
            }
            if(ret == HNDRES_TERMINATE) {
                D(bug("Quiting..."));
                DestroyMain(main_go);
                #ifdef BETALIB
                CloseLibrary(GadOutlineBase);
                #endif
                D(bug(" GadOutline..."));
                abort_timer(timer);
                D(bug(" Timer Request..."));
                delete_timer(timer);
                D(bug(" Timer Device.  Done.\n"));
                me->pr_WindowPtr = (APTR)oldwin;
                exit(0);
            }
        }

        if( GetMsg(timer->tr_node.io_Message.mn_ReplyPort) ) {
            send_timer(timer);
            ((HANDLER *)main_go->go_UserHandler)(main_go,HNDCMD_TICK,NULL);
        }
    }
}
