/*
 * Switcher.c - bring up a requester listing screens & windows, and bring
 * the one selected to the front. Shut down on window deactivation.
 * ===build
 * % lc -Lcdn -v -tr -O -mcs -r -rr -b -cfsu -d0 switcher ; output= switcher inputs= switcher.c
 * ===endbuild
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <intuition/gadgetclass.h>
#include <graphics/gfxmacros.h>
#include <graphics/gfxbase.h>
#include <utility/tagitem.h>
#include <clib/macros.h>
#include <string.h>

#include <libraries/gadtools.h>

#include <clib/exec_protos.h>
#include <pragmas/exec.h>
#include <clib/graphics_protos.h>
#include <pragmas/graphics.h>
#include <clib/intuition_protos.h>
#include <pragmas/intuition.h>
#include <clib/gadtools_protos.h>
#include <pragmas/gadtools.h>

extern struct GfxBase *GfxBase = NULL;
extern struct IntuitionBase *IntuitionBase = NULL;
extern struct Library *GadToolsBase = NULL;

/*
 * Test code; generate a main to call the switcher.
 */
void
_main(char *x) {
    void switcher(void);

    /* Open libraries */
    GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 36L);
    IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 36L);
    GadToolsBase = OpenLibrary("gadtools.library", 36L);

    /* Run the switcher */
    if (GfxBase && IntuitionBase && GadToolsBase) switcher();

    /* and shut them down */
    if (GadToolsBase) CloseLibrary(GadToolsBase);
    if (IntuitionBase) CloseLibrary(IntuitionBase);
    if (GfxBase) CloseLibrary(GfxBase);
    }

static struct TagItem windowtags[] = { WA_AutoAdjust, TRUE, TAG_DONE } ;
static struct ExtNewWindow mynewwin =
{
    0, 0,		/* LeftEdge, TopEdge */
    0, 0,		/* Width, Height filled in later */
    -1, -1,             /* DetailPen, BlockPen */
    IDCMP_GADGETUP | IDCMP_GADGETDOWN | IDCMP_REFRESHWINDOW | IDCMP_INACTIVEWINDOW,
    WFLG_ACTIVATE | WFLG_BORDERLESS | WFLG_SIMPLE_REFRESH | WFLG_NW_EXTENDED,
    NULL,		/* FirstGadget */
    NULL,		/* CheckMark */
    NULL,		/* Title */
    NULL,		/* Screen */
    NULL,		/* BitMap */    
    0, 0,		/* MinWidth, MinHeight */
    0, 0,		/* MaxWidth, MaxHeight */
    CUSTOMSCREEN,	/* Type */
    windowtags		/* Tags */
};

struct MBMNode {
	struct Node m_Node;
	struct Window *m_Window;
	struct Screen *m_Screen;
	};

/* My globals */
static struct Remember *RKey = NULL;

#define MAXNAMELEN	40

static USHORT
AddName(struct Screen *s, struct Window *w, struct List *lh) {
    struct MBMNode *node;
    char namebuf[MAXNAMELEN + 1];

    if (w) {
	if (!w->Title || !*w->Title) return 0;
	*namebuf = ' ';
	strncpy(namebuf + 1, w->Title, MAXNAMELEN - 1);
	}
    else {
	*namebuf = '\273';
	if (s->DefaultTitle) strncpy(namebuf + 1, s->DefaultTitle, MAXNAMELEN - 1);
	else namebuf[1] = '\0';
	}
    namebuf[MAXNAMELEN] = '\0';	/* Insurance */

    node = (struct MBMNode *)AllocRemember(&RKey, sizeof(struct MBMNode), MEMF_CLEAR);
    node->m_Node.ln_Name = strdup(namebuf);
    node->m_Window = w;
    node->m_Screen = s;
    AddTail(lh, node);
    return strlen(namebuf);
    }

/* 
 * Gadgets Finds the data to go in the gadget, and set various sizes based on
 * that data.
 */
static struct Gadget *
Gadgets(struct Gadget **glistptr, void *vi, struct TextFont *f, struct List *lh)
{
    struct TagItem gadtags[2] ;
    void NewList(struct List *);
    struct NewGadget ng;
    struct Gadget *gad;
    LONG lock;
    USHORT tmplen, longname = 0, length;
    struct Screen *s;
    struct Window *w;

    gad = CreateContext(glistptr);

    NewList(lh);
    lock = LockIBase(0L);
    for (s = IntuitionBase -> FirstScreen, length = 0; s;
      s = s -> NextScreen, length += 1) {
	if ((tmplen = AddName(s, NULL, lh)) > longname) longname = tmplen;
	for (w = s->FirstWindow; w; w = w -> NextWindow) {
	    if ((tmplen = AddName(s, w, lh)) > longname) longname = tmplen;
	    if (tmplen) length += 1;
	    }
	}
    UnlockIBase(lock);

    if (length > 20) length = 20;
    ng.ng_Width = mynewwin.Width = f->tf_XSize * (MAXNAMELEN + 2);
    ng.ng_Height = mynewwin.Height = f->tf_YSize * length + 6;
    ng.ng_LeftEdge = 0;
    ng.ng_TopEdge = 0;
    ng.ng_GadgetText = NULL;
    ng.ng_TextAttr = NULL;
    ng.ng_Flags = NG_HIGHLABEL|PLACETEXT_LEFT;
    ng.ng_VisualInfo = vi;
    gadtags[0].ti_Tag = GTLV_Labels;
    gadtags[0].ti_Data = (ULONG) lh;
    gadtags[1].ti_Tag = TAG_DONE;
    gad = CreateGadgetA(LISTVIEW_KIND, gad, &ng, gadtags);

    /* Finsh tweaking the window */
    mynewwin.LeftEdge = IntuitionBase->ActiveScreen->MouseX - (mynewwin.Width / 2);
    if (mynewwin.LeftEdge < 0) mynewwin.LeftEdge = 0;
    mynewwin.TopEdge = IntuitionBase->ActiveScreen->MouseY - (mynewwin.Height / 2);
    if (mynewwin.TopEdge < 0) mynewwin.TopEdge = 0;
    return(gad);
    }

/*
 * Start of the switcher proper.
 */
static void
switcher(void) {
    struct List lh;
    BOOL terminated = FALSE;
    struct Screen *s = NULL;
    struct Window *w;
    struct Gadget *glist = NULL;
    struct Window *mywin = NULL;
    void *vi = NULL;
    struct IntuiMessage *imsg;
    struct Gadget *gad;
    ULONG imsgClass;
    UWORD imsgCode, CountCode;
    LONG  lock;
    struct MBMNode *NodeCode;

    /*
     * This probably all ought to happen inside a LockIBase, but...
     */

    /* Information needed to open the window */
    s = IntuitionBase->ActiveScreen;
    if (!(vi = GetVisualInfoA(s, NULL))) goto out;
    if (!Gadgets(&glist, vi, s->RastPort.Font, &lh)) goto out;
    mynewwin.FirstGadget = glist;
    mynewwin.Screen = s;

    /* Open the window, and render it */
    if (!(mywin = OpenWindow(&mynewwin))) goto out;
    GT_RefreshWindow(mywin, NULL);

    while (!terminated) {
	Wait (1 << mywin->UserPort->mp_SigBit);
	/* GT_GetIMsg() returns a cooked-up IntuiMessage with
	 * more friendly information for complex gadget classes.  Use
	 * it wherever you get IntuiMessages:
	 */
	while ((!terminated) && (imsg = GT_GetIMsg(mywin->UserPort)))
	{
	    imsgClass = imsg->Class;
	    imsgCode = imsg->Code;
	    /* Presuming a gadget, of course, but no harm... */
	    gad = (struct Gadget *)imsg->IAddress;
	    /* Use the toolkit message-replying function here... */
	    GT_ReplyIMsg(imsg);
	    switch (imsgClass)
	    {
		case IDCMP_GADGETUP:
		    for (CountCode = imsgCode, NodeCode = (struct MBMNode *) lh.lh_Head; CountCode--;
			NodeCode = (struct MBMNode *) NodeCode->m_Node.ln_Succ)
			    ;
		    lock = LockIBase(0);
		    for (s = IntuitionBase->FirstScreen; s; s = s->NextScreen)
		    	if (s == NodeCode->m_Screen)
				break;
		    if (!s) {
		    	UnlockIBase(lock);
			break;
			}
		    ScreenToFront(s);
		    for (w = s->FirstWindow; w; w = w->NextWindow)
		    	if (w == NodeCode->m_Window) break;
		    if (w) WindowToFront(w);
		    UnlockIBase(lock);
		    terminated = 1;
		    break;

		case IDCMP_INACTIVEWINDOW:
		    terminated = 1;
		    break;

		case IDCMP_REFRESHWINDOW:
		    GT_BeginRefresh(mywin);
		    GT_EndRefresh(mywin, TRUE);
		    break;
	    }
	}
    }
out:
    if (mywin) CloseWindow(mywin);
    FreeVisualInfo(vi);
    FreeGadgets(glist);
    if (RKey) FreeRemember(&RKey, TRUE);
}
