/****************************************************************
  draw.c V1.2
  Created 30-Aug-90 by Peter Ørbæk
  Last modified 2-Sep-90 by Peter Ørbæk
  This is in the Public Domain.
  Demo of some features of odin.library
  Draws lines in multiple windows
  Compiles with Aztec C3.40A, certainly not very ANSI-like
  NB. Use tabsize = 3 for best looks
*****************************************************************/

#include	"exec/types.h"
#include	"exec/ports.h"
#include	"exec/memory.h"
#include	"exec/interrupts.h"
#include	"exec/semaphores.h"
#include	"exec/tasks.h"
#include	"graphics/graphint.h"
#include	"graphics/rastport.h"
#include	"graphics/text.h"
#include	"intuition/intuition.h"
#include	"stdio.h"
#include	"odin.h"

/* function return-types */
void *OpenLibrary(),*AllocRemember();
int strlen();
char *strcpy(),*strcat();
struct Window *OpenWindow();
struct MsgPort *CreatePort();
void *GetMsg();

/* global data */
void *IntuitionBase,*GfxBase;
void *OdinBase;
struct SignalSemaphore ss,sem;

/* for debugging, surround evt. printf's with this */
#define CR(s) ObtainSemaphore(&ss); { s } ReleaseSemaphore(&ss);

/*****************************************************************/

/* create menu from simple data */
struct Menu *create_menu(menutitle,text,num)
char *text[], *menutitle;
int num;
{
	struct Remember *rk;
	struct Menu *menu;
	struct MenuItem *item,*prev_item;
	struct IntuiText *tt;
	int i;

	rk = NULL;
	menu = (struct Menu *)AllocRemember(&rk,
			(long)sizeof(struct Menu),MEMF_CLEAR|MEMF_PUBLIC);
	if(menu == NULL) goto cleanup;

	menu->Width = 10*strlen(menutitle);
	menu->Height = 10*num+5;
	menu->Flags = MENUENABLED;
	menu->MenuName = (BYTE *)menutitle;
	
	for(i = 0; i < num; i++) {
		item = (struct MenuItem *)AllocRemember(&rk,(long)sizeof(struct MenuItem),MEMF_CLEAR|MEMF_PUBLIC);
		if(item == NULL) goto cleanup;

		if(i == 0) menu->FirstItem = item;
		if(i > 0) prev_item->NextItem = item;
		item->TopEdge = 10*i;
		item->Width = 10*strlen(text[i]);
		item->Height = 10;
		item->Flags = ITEMTEXT|ITEMENABLED|HIGHCOMP;

		tt = (struct IntuiText *)AllocRemember(&rk,(long)sizeof(struct IntuiText),MEMF_CLEAR|MEMF_PUBLIC);
		if(tt == NULL) goto cleanup;

		tt->FrontPen = 0; tt->BackPen = 1;
		tt->DrawMode = JAM2;
		tt->IText = (UBYTE *)text[i];

		item->ItemFill = (APTR)tt;
		prev_item = item;
	}

	FreeRemember(&rk,FALSE);
	return menu;

	cleanup:
		{
			FreeRemember(&rk,TRUE);
			return NULL;
		}

}

/* free memory used by Menu */
void destroy_menu(menu)
struct Menu *menu;
{
	struct MenuItem *item,*next_item;
	struct IntuiText *tt;

	item = menu->FirstItem;
	FreeMem(menu,(long)sizeof(struct Menu));

	while(item != NULL) {
		FreeMem(item->ItemFill,(long)sizeof(struct IntuiText));
		next_item = item->NextItem;
		FreeMem(item,(long)sizeof(struct MenuItem));
		item = next_item;
	}
}

/*********************************************************************/

/* envelope used to communicate refresher<->windowmanager */
struct DrawEnv {
	Envelope e;
	int type;			/* what type of action to take */
	struct Window *window;
	char *cmdname;		/* name of envelope that holds command */
	char *replyname;	/* name of envelope that is used to reply */
};

/* draw a line in a non-GZZ window */
void line(rp,x,y,x1,y1)
struct RastPort *rp;
int x,y,x1,y1;
{
	x += 4; x1 += 4;			/* don't write over the windowframe */
	y += 10; y1 += 10;
	Move(rp,(long)x,(long)y);
	Draw(rp,(long)x1,(long)y1);
}

/* sub-routine for drawing the graphics, polls for commands from manager */
struct DrawEnv *draw_pattern(rp,w,h,envname)
struct RastPort *rp;
UWORD w,h;
char *envname;
{
	UWORD	x,y;
	struct DrawEnv *env;

	w -= 8+14; h -= 14;		/* adjust for borders */

	SetDrMd(rp,JAM2+COMPLEMENT);
	SetAPen(rp,1L); SetBPen(rp,0L);
	while(1) {
		for(x = 0; x < w; x += 2) {
			line(rp,x,0,w-x,h);
			if(env = (struct DrawEnv *)PollNamedEnvelope(envname)) return env;
		}
		for(y = 0; y < h; y += 2) {
			line(rp,w,y,0,h-y);
			if(env = (struct DrawEnv *)PollNamedEnvelope(envname)) return env;
		}
		for(x = 0; x < w; x += 2) {
			line(rp,x,0,x,h);
			if(env = (struct DrawEnv *)PollNamedEnvelope(envname)) return env;
		}
		for(y = 0; y < h; y += 2) {
			line(rp,0,y,w,y);
			if(env = (struct DrawEnv *)PollNamedEnvelope(envname)) return env;
		}
	}
}

/* DrawerEnv.type's */
#define DRAWER_RESIZED 1	/* sent when new size is settled upon */
#define DRAWER_QUIT 2		/* ask refresher to die */
#define DRAWER_SUSPEND 3	/* ask refresher to wait */

/* body for each of the refreshers tasks (drawer) */
Envelope *drawing(drawenv)
struct DrawEnv *drawenv;
{
	struct RastPort *myrp;
	int w,h,active;
	struct DrawEnv *env;

	myrp = drawenv->window->RPort;
	w = drawenv->window->Width;
	h = drawenv->window->Height;
	active = TRUE;

	do {
		env = draw_pattern(myrp,w,h,drawenv->cmdname);
		switch(env->type) {
			case DRAWER_QUIT:
				active = FALSE;	/* don't dispose ! */
				break;
			case DRAWER_SUSPEND:
				/* tell windowmanager we are waiting */
				OutEmptyEnvelope(drawenv->replyname);
				DisposeEnvelope(env);
				/* wait for user to resize window */
				env = (struct DrawEnv *)AwaitNamedEnvelope(drawenv->cmdname);
				myrp = env->window->RPort;
				w = env->window->Width;
				h = env->window->Height;
				DisposeEnvelope(env);
				SetRast(myrp,0L);
				RefreshWindowFrame(drawenv->window);
				break;				
		}
	} while(active);

	return (Envelope *)drawenv;
}

/**************************************************************************/

/* WindowPack.cmd defs */
#define MASTER_SPAWN 1	/* ask master-process (first one) to spawn new window */
#define MASTER_DIE 2		/* tell master that this process is dead */
#define MASTERNAME "MasterProcess"	/* envelope-name to comm. with master */

/* this kind of envelope is used to communicate between managers and master */
struct WindowPack {
	Envelope e;
	/* first the params from master to handler transfered at startup */
	struct NewWindow nw;
	char *taskname;		/* name of refreshertask to start */
	struct Menu *menu;	/* menu to add */
	/* second the params. from handler to master eg. for spawning */
	int cmd;
	struct Window *window;
};

/* body of the windowmanagers */
Envelope *windowhandler(wp)
struct WindowPack *wp;
{
	struct Window *win;		/* the window handled here */
	BOOL active = TRUE;		/* are we running still? */
	struct IntuiMessage *imsg;	/* the intuimsg from UserPort */
	struct DrawEnv drawenv,*te,*de;	/* for communication with drawer task */
	char cmdname[30];			/* name of envelope to pass commands to drawer */
	char drawerreply[30];	/* drawer can reply with this name */
	struct WindowPack *ae;	/* comm. windowhandler<->mastertask */

	/* generate unique names for comms. with drawer */
	UniqueName(strcpy(&cmdname[0],"dName"));
	UniqueName(strcpy(&drawerreply[0],"dReply"));

	CR(printf("cmd: %s reply: %s\n",cmdname,drawerreply);)

	/* init parts of NewWindow-struct common to all windows */
	wp->nw.Width = 300; wp->nw.Height = 100;
	wp->nw.DetailPen = 0; wp->nw.BlockPen = 1;
	wp->nw.IDCMPFlags = REFRESHWINDOW|
			MENUPICK|
			CLOSEWINDOW|
			SIZEVERIFY|
			NEWSIZE;
	wp->nw.Flags = WINDOWSIZING|
			WINDOWDRAG|
			WINDOWDEPTH|
			WINDOWCLOSE|
			SIMPLE_REFRESH|
			ACTIVATE;
	wp->nw.Type = WBENCHSCREEN;
	wp->nw.MinWidth = 40; wp->nw.MaxWidth = 640;
	wp->nw.MinHeight = 30; wp->nw.MaxHeight = -1;

	/* open my window */
	wp->cmd = MASTER_DIE;		/* prepare for evt. failure */
	win = OpenWindow(&wp->nw);
	if(win == NULL) return (Envelope *)wp;

	/* set up the menu */
	SetMenuStrip(win,wp->menu);

	/* init parameters for drawer-task */
	te = (struct DrawEnv *)CreateEnvelope(wp->taskname,(long)sizeof(struct DrawEnv));
	te->e.e_proc = drawing;
	te->replyname = &drawerreply[0];
	te->cmdname = &cmdname[0];
	te->window = win;

	/* create new task for drawer */
	Eval(te,0L,4000L,EVAL_TASK);

	/* the messagehandler loop */
	do {
		WaitPort(win->UserPort);
		imsg = (struct IntuiMessage *)GetMsg(win->UserPort);

		switch(imsg->Class) {
			case SIZEVERIFY:
				de = (struct DrawEnv *)CreateEnvelope(&cmdname[0],(long)sizeof(struct DrawEnv));
				if(de) {
					de->type = DRAWER_SUSPEND;
					Out(de);
					DisposeEnvelope(AwaitNamedEnvelope(&drawerreply[0]));
				}
				break;
			case NEWSIZE:
				de = (struct DrawEnv *)CreateEnvelope(&cmdname[0],(long)sizeof(struct DrawEnv));
				de->type = DRAWER_RESIZED;
				de->window = win;
				Out(de);
				break;
			case REFRESHWINDOW:
				SetRast(win->RPort,0L);
				RefreshWindowFrame(win);
				break;
			case MENUPICK:
				switch(ITEMNUM(imsg->Code)) {
					case NOITEM:
						break;
					case 0: /* try to spawn new window */
						ae = (struct WindowPack *)CreateEnvelope(MASTERNAME,(long)sizeof(struct WindowPack));
						if(ae) {
							ae->cmd = MASTER_SPAWN;
							ae->window = win;
							Out(ae);
						}
						break;
					case 1: /* quit, same as closebox */
						active = FALSE;
						break;
					default:
						break;
				}
				break;
			case CLOSEWINDOW:
				active = FALSE;
				break;
			default:
				break;
		}
		ReplyMsg(imsg);
	} while(active);

	/* ask drawer-task to die. */
	InitEnvelope(&drawenv,&cmdname[0],(long)sizeof(struct DrawEnv),0L);
	drawenv.type = DRAWER_QUIT;
	Out(&drawenv);

	/* erase menu */
	ClearMenuStrip(win);

	/* wait for drawer-task to die */
	DisposeEnvelope(AwaitNamedEnvelope(wp->taskname));

	/* close window and die */
	CloseWindow(win);
	return (Envelope *)wp;	/* cmd field already set up to MASTER_DIE */
}

/************************************************************************/

char *mtitle = "Main menu";	/* title of the menu */

char *items[] = {				/* args to create_menu() */
	"Spawn new",
	"Quit     "
};

main()
{
	struct WindowPack *wp,*args;	/* for comms. with windowmanagers */
	int running = 0;		/* number of windowmanagers running */
	struct Menu *menu;	/* menu for windows */
	char name[30][30];	/* holds names of refresher tasks */

	OdinBase = IntuitionBase = GfxBase = NULL;
	menu = NULL;

	/* open various libs */
	if(!(OdinBase = OpenLibrary("RAM:odin.library",0L))) goto do_exit;
	if(!(GfxBase = OpenLibrary("graphics.library",0L))) goto do_exit;
	if(!(IntuitionBase = OpenLibrary("intuition.library",0L))) goto do_exit;

	InitSemaphore(&ss);	/* used in CR() macro */
	UniqueName(NULL);		/* init first suffix */

	/* create shared menu */
	if(!(menu = create_menu(mtitle,items,2))) {
		printf("didn't create menu\n");
		goto do_exit;
	}

	/* create parameters to windowmanager */
	wp = (struct WindowPack *)CreateEnvelope(MASTERNAME,(long)sizeof(struct WindowPack));
	wp->nw.LeftEdge = 0; wp->nw.TopEdge = 0;
	wp->nw.Title = (UBYTE *)"Initial Window";
	wp->e.e_proc = windowhandler;
	wp->taskname = "initrefresh";	/* name of refreshertask */
	wp->menu = menu;

	/* start initial windowhandler as separate task */	
	if(!Eval(wp,1L,8000L,EVAL_PROCESS)) {
		printf("couldn't create initial windowmanager-task\n");
		goto do_exit;
	}
	running++;

	/* messagehandling-loop */
	do {
		args = (struct WindowPack *)AwaitNamedEnvelope(MASTERNAME);
		switch(args->cmd) {
			case MASTER_SPAWN:
				/* create parameters to windowmanager */
				wp = (struct WindowPack *)CreateEnvelope(MASTERNAME,(long)sizeof(struct WindowPack));
				if(wp) {
					wp->nw.LeftEdge = args->window->LeftEdge+10;
					wp->nw.TopEdge = args->window->TopEdge+20;
					wp->nw.Title = (UBYTE *)"Secondary Window";
					wp->e.e_proc = windowhandler;
					wp->taskname = UniqueName(strcpy(&name[running][0],"refresh"));
					CR(printf("taskname: %s\n",wp->taskname);)
					wp->menu = menu;
					/* spawn new task if possible */
					if(Eval(wp,1L,8000L,EVAL_PROCESS)) running++;
				}
				break;
			case MASTER_DIE:
				running--;
				break;
			default:
				break;
		}
		DisposeEnvelope(args);
	} while(running);

	do_exit:
		{
			if(menu) destroy_menu(menu);	/* free memory used by the menu */
			if(IntuitionBase) CloseLibrary(IntuitionBase);
			if(GfxBase) CloseLibrary(GfxBase);
			if(OdinBase) CloseLibrary(OdinBase);
		}
}
