#define MANX

/*
 * browser - Rummage around on disks.
 *
 *	copyright (c) 1986, Mike Meyer
 *
 * Permission is hereby granted to distribute this program, so long as this
 * source file is distributed with it, and this copyright notice is not
 * removed from the file.
 *
 * Locks, general -	do I need a stack of locks, one per directory level?
 *			do I have to free the lock in done?
 */

#ifdef MANX
#include <functions.h>
#endif
#include <exec/types.h>
#include <graphics/gfxbase.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <stdio.h>
#define INTUITION_REV		1L
#define GRAPHICS_REV		1L

#define LONGEST_NAME		80	/* Longest file name we can deal with */
#define LONGEST_LINE		256	/* Longest line we will deal with */
#define AVG_LINE_LENGTH		66	/* A guess, tune it if you need to */

#define UP_GADGET		((unsigned short) 0)
#define DOWN_GADGET		((unsigned short) 1)
#define SCROLL_GADGET	((unsigned short) 2)
#define GWIDTH			16	/* Width of my two gadgets */
#define GHEIGHT			9	/* and their heights */

#define FIRST 18L
/*
 * Pictures for the up and down arrows
 */
static USHORT arrows[2][GHEIGHT] = {
	{0xFE7F, 0xFC3F, 0xF81F, 0xF00F,	/* Up */
	 0xFC3F, 0xFC3F, 0xFC3F, 0xFC3F, 0xFC3F},
	{0xFC3F, 0xFC3F, 0xFC3F, 0xFC3F,	/* Down */
	 0xFC3F, 0xF00F, 0xF81F, 0xFC3F, 0xFE7F}
	} ;
/*
 * Now, the Image structures that use the arrows
 */
static struct Image Arrow_Image[2] = {
	{0, 0, GWIDTH, GHEIGHT, 1, arrows[UP_GADGET], 1, 0,  /* Up */
		/*(struct Image *)*/ NULL},
	{0, 0, GWIDTH, GHEIGHT, 1, arrows[DOWN_GADGET], 1, 0,  /* Down */
		/*(struct Image *)*/ NULL}
	};
/*
 * Now, my Gadget structures
 */
static struct PropInfo prop;
static struct Image prop_img;

static struct Gadget Scroll_Gadget = {
	/*(struct Gadget *)*/ NULL,	/* End of Gadgets */
	0,11+GHEIGHT,				/* Left, Top */
	GWIDTH, -((GHEIGHT*2)+11),
	GRELHEIGHT | GADGHCOMP | GADGDISABLED,
	RELVERIFY,					/* Messages when released */
	PROPGADGET,
	(APTR) &prop_img,
	(APTR) NULL,				/* No rendering image, using HCOMP */
	/*(struct IntuiText *)*/ NULL,
	0L,							/* No mutex */
	(APTR) &prop,
	SCROLL_GADGET,				/* Yes, this is the scroll gadget */
	(APTR) NULL					/* And nothing of mine */
};

static struct Gadget Up_Gadget = {
	&Scroll_Gadget,				/* next gadget is scroll */
	0,11,						/* Left, Top */
	GWIDTH, GHEIGHT,
	GADGIMAGE | GADGDISABLED | GADGHCOMP,
	RELVERIFY,					/* Messages when released */
	BOOLGADGET,					/* These be boolean gadgets */
	(APTR) &(Arrow_Image[UP_GADGET]),
	(APTR) NULL,				/* No rendering image, using HCOMP */
	/*(struct IntuiText *)*/ NULL,
	0L,							/* No mutex */
	(APTR) NULL,				/* Nothing special */
	UP_GADGET,					/* Yes, this is the up gadget */
	(APTR) NULL					/* And nothing of mine */
};

static struct Gadget Down_Gadget = {
	&Up_Gadget,					/* Next gadget is Up_Gadget */
	0, 1 - GHEIGHT,				/* Left, Top */
	GWIDTH, GHEIGHT,
	GRELBOTTOM | GADGIMAGE |	/* Standard bottom border gadget */
	GADGDISABLED | GADGHCOMP,
	RELVERIFY | BOTTOMBORDER,	/* Messages when released */
	BOOLGADGET,					/* These be boolean gadgets */
	(APTR) &(Arrow_Image[DOWN_GADGET]),
	(APTR) NULL,				/* No rendering image, using HCOMP */
	/*(struct IntuiText *)*/ NULL,
	0L,							/* No mutex */
	(APTR) NULL,				/* Nothing special */
	DOWN_GADGET,				/* Yes, this is the up gadget */
	(APTR) NULL					/* And nothing of mine */
	}; 

/*
 * Now, the window for it all
 */
static struct NewWindow New_Window = {
#ifdef DEBUG
	0, 0, 540, 150,			/* smaller window to left printf's show up */
#else
	0, 0, 640, 200,			/* Full screen */
#endif
	-1L, -1L,				/* Default pens */
	MENUPICK | CLOSEWINDOW		/* I want to know about menupicks */
	| GADGETUP,			/* Window closes and gadgets */
	ACTIVATE			/* Standard window */
	| SMART_REFRESH | NOCAREREFRESH | SIZEBBOTTOM
	| WINDOWSIZING | WINDOWDEPTH | WINDOWCLOSE | WINDOWDRAG,
	&Down_Gadget,			/* Add my gadgets */
	/*(struct Image *)*/ NULL,
	"   Browser 0.1 with Bob Leivian's additions",	/* Title */
	/*(struct Screen *)*/NULL,
	/*(struct BitMap *)*/NULL,
	100, 40,			/* Minimum sizes */
	640, 200,			/* Maximum sizes */
	WBENCHSCREEN			/* and put it on the workbench */
	} ;

/*
 * My very own variables (mostly for done)
 */
static struct Window	*My_Window = NULL ;
static FILE		*infile = NULL ;	/* Current input file */
#ifdef MANX
static void		Page_File();
#else
static void		Page_File(unsigned short) ;
#endif

/*
 * And someone else's variables
 */
struct IntuitionBase	*IntuitionBase ;
struct GfxBase		*GfxBase ;
extern struct Menu	*AutoMenu ;

/*
 * Finally, declare the string twiddling functions as voids
 */
#ifdef MANX
void	strcat(), strcpy(), strncat();
#else
void	strcat(char *, char *), strcpy(char *, char *) ,
	strncat(char *, char *, int) ;
#endif

main() {
	register struct IntuiMessage	*message;
	register unsigned short		class, code ;

	/* Set up the world this lives in */
	if ((IntuitionBase = (struct IntuitionBase *)
	    OpenLibrary("intuition.library", INTUITION_REV)) == NULL)
		done(20, "can't open intuition library") ;

	if ((GfxBase = (struct GfxBase *)
	    OpenLibrary("graphics.library", GRAPHICS_REV)) == NULL)
		done(20, "can't open graphics library") ;

	/* set up the scroll bar */
	prop.Flags = FREEVERT | AUTOKNOB;
	prop.VertBody = 0x1000;

	if ((My_Window = (struct Window *) OpenWindow(&New_Window)) == NULL)
		done(20, "can't open the window") ;

	SetAPen(My_Window -> RPort, 1L) ;	/* Should be default! */

	show_message("Pick Disk with Menu Button ");

	/* Set up the first menu level */
	Menu_Init() ;
	Menu_Add("disks ", 1L) ;
	Menu_Item_Add("df0:", ITEMENABLED, 0L, 0) ;
	Menu_Item_Add("df1:", ITEMENABLED, 0L, 0) ;
	Menu_Item_Add("ram:", ITEMENABLED, 0L, 0) ;
	SetMenuStrip(My_Window, AutoMenu) ;

	/* Now spin on messages, handling them as they arrive */
	for (;;) {
		Wait(1L << (long) My_Window -> UserPort -> mp_SigBit) ;
		while ((message = GetMsg(My_Window -> UserPort)) != NULL) {
			class = message -> Class ;
			code = message -> Code ;
			ReplyMsg(message) ;
#ifdef DEBUG
printf("message:%d\n", class);
#endif
			switch (class) {
				case CLOSEWINDOW:
					done(0, NULL) ;

				case MENUPICK:
					if (code != MENUNULL)
						Examine_Menu_Pick(code) ;
					break ;

				case GADGETUP:
					Page_File(((struct Gadget *) (message -> IAddress)) -> GadgetID) ;
					break ;

				default:
					printf("browser: intuition event 0x%x\n", class) ;
					done(20, "unexpected intution event") ;
					break ;
				}
			}
		}
	done(20, "broke out of never-breaking loop!") ;
}


/*
 * Examine_Menu_Pick - takes a menu pick, and twiddles the state variables
 *	to match that pick.
 */
static
Examine_Menu_Pick(Menu_Number)
unsigned short Menu_Number; 
{
	register unsigned short	level, i, dirp ;
	register char		*cp ;
	char			*name, *index() ;
#ifdef MANX
	struct MenuItem		*ItemAddress(/* struct Menu *, long*/) ;
#else
	struct MenuItem		*ItemAddress(struct Menu *, unsigned short) ;
#endif
	/* State variables that describe the current directory */
	static char		Dir_Name[LONGEST_NAME] ;
	static unsigned short	Menu_Level = 0 ;

	name = ((struct IntuiText *)
		(ItemAddress(AutoMenu, (long) Menu_Number) -> ItemFill)) -> IText ;
	level = MENUNUM(Menu_Number) ;

	/* Got what we want, so clear the menu to avoid confusing the user */
	ClearMenuStrip(My_Window);
	OffGadget((APTR)&Down_Gadget, My_Window, NULL);
	OffGadget((APTR)&Up_Gadget, My_Window, NULL);
	OffGadget((APTR)&Scroll_Gadget, My_Window, NULL);

	/* set dirp to FALSE if the name is not a directory or disk */
	dirp = (index(name, '/') != NULL || index(name, ':') != NULL) ;

	/* First, set the directory name right */
	if (level > Menu_Level)			/* Not possible, die */
		done(20, "impossible menu value returned") ;
	else if (level == 0)			/* picked a new disk */
		Dir_Name[0] = '\0' ;
	else if (level < Menu_Level) {		/* Throw away some levels */
		for (i = 1, cp = index(Dir_Name, ':'); i < level; i++) {
			if (cp == NULL) done(20, "broken file name") ;
			cp = index(cp, '/') ;
		}
		if (cp == NULL) done(20, "broken file name") ;
		*++cp = '\0' ;
	}
	/* else Menu_Level == level, chose a file at current level */

	/* Now, fix up the menu and it's state variable */
	while (Menu_Level > level) {
		Menu_Level-- ;
		Menu_Pop() ;
	}

	/* If we added a directory, tack it onto the name */
	if (dirp) {
		Menu_Level++ ;
		strncat(Dir_Name, name,	LONGEST_NAME - strlen(Dir_Name) - 1) ;
	}

	/* Now, tell the user all about it */
	if (dirp)
		Add_Dir(Dir_Name, name) ;
	else
		Display_File(Dir_Name, name) ;
	SetMenuStrip(My_Window, AutoMenu) ;
}

/*
 * Add_Dir - given a dir and a name, add the menu name with the files in
 *	dir as entries.
 */
static
Add_Dir(dir, name) char *dir, *name; {
	struct FileInfoBlock		File_Info ;
	register char			*last_char ;
#ifdef MANX
	register struct FileLock	*my_lock;
#else
	register struct FileLock	*my_lock, *Lock(char *, int) ;
#endif
	unsigned short			count ;
	static char			Name_Buf[LONGEST_NAME] ;

	/* Fix up the trailing / if it needs it */
	last_char = &dir[strlen(dir) - 1] ;
	if (*last_char == '/') *last_char = '\0' ;

	/* Now, start on the directory */
	if ((my_lock = Lock(dir, ACCESS_READ)) == NULL) {
#ifdef	DEBUG
		printf("browser: error trying to lock %s, IoErr: %d\n", dir, IoErr()) ;
#endif
		done(20, "can't get lock on file") ;
	}

	if (!Examine(my_lock, &File_Info)) done(20, "can't examine file") ;
	if (File_Info . fib_DirEntryType < 0)
		done(20, "Add_Dir called with a non-directory") ;

	Menu_Add(name, TRUE) ;

	show_message("Loading Directory          ");

	for (ExNext(my_lock, &File_Info), count = 0;
	     IoErr() != ERROR_NO_MORE_ENTRIES;
	     ExNext(my_lock, &File_Info), count++)
		if (File_Info . fib_DirEntryType < 0)
			/* file */
			Menu_Item_Add(File_Info . fib_FileName, ITEMENABLED, 0L, 0) ;
		else {
			/* directory */
			strcpy(Name_Buf, File_Info . fib_FileName) ;
#ifdef DEBUG
printf("dir name: %s\n", Name_Buf);
#endif
			strcat(Name_Buf, "/");
			Menu_Item_Add(Name_Buf, ITEMENABLED, 0L, 0) ;
		}
	if (count == 0) Menu_Item_Add("EMPTY", 0L, 0L, 0) ;

	show_message("Directory Available         ");

	/* Put everything back */
	if (*last_char == '\0') *last_char = '/' ;
	UnLock(my_lock) ;
}

/*
 * Display_File - given a directory path and file name, put the first page of
 *	the file in the window.
 */

long aprox_lines, file_size;
long Page_Length = 22L;


static
Display_File(dir, name)
char *dir, *name;
{
	static char	File_Name[LONGEST_NAME];
	FILE *fopen();
	long ftell();
	long i;

	/* Get the file name */
	strcpy(File_Name, dir);
	strcat(File_Name, name);

	if (infile != NULL)
		fclose(infile);

	if ((infile = fopen(File_Name, "r")) == NULL) {
#ifdef	DEBUG
		printf("Can't open File: %s\n", File_Name) ;
#endif
		done(20, "can't open file") ;
	}

	/* set up the prop gadget for scrolling */
	fseek(infile, 0L, 2);
	file_size = ftell(infile);
	aprox_lines = file_size / AVG_LINE_LENGTH;
	prop.Flags = FREEVERT | AUTOKNOB;
	if (Page_Length >= aprox_lines)
		i = 0xAAAA; /* guess 66% for small files */
	else
		i = (Page_Length * 0x10000) / aprox_lines; /* FFFF=100% - 0000=0% */

	prop.VertBody = i;
	prop.VertPot = 0;	/* always start at begin of file */
	fseek(infile, 0L, 0);

	/* enable the scroll bar */
	OnGadget((APTR)&Down_Gadget, My_Window, NULL);
	OnGadget((APTR)&Up_Gadget, My_Window, NULL);
	OnGadget((APTR)&Scroll_Gadget, My_Window, NULL);

	Page_File(DOWN_GADGET);		/* Down from page 0 */
}

/*
 * Page_File - move the file up or down one "page"
 */
static void
Page_File(direction) 
int direction; 
{
	register long where;
	static char	buffer[LONGEST_LINE];
	static char edited[84]; /* allow room for a tab at end */
	int end_flag = 0;
	long tabs, size, Line_Length;
	int i,j;
	long new_pos;
	char *p;

	if (infile == NULL) return ;

	Page_Length = (My_Window -> Height - 20) / 8 ;
	Line_Length = (My_Window -> Width - (3+FIRST)) / 8;

	switch (direction) {
	case UP_GADGET:		/* Seek back one page */
		if (ftell(infile) < AVG_LINE_LENGTH * (Page_Length + 2))
			fseek(infile, 0L, 0);
		else {
			fseek(infile, (long) -Page_Length * AVG_LINE_LENGTH, 1) ;
			fgets(buffer, LONGEST_LINE, infile) ;
		}
		break;

	case DOWN_GADGET:
		/* we are already down one screen, just print from where we are */
		break;

	case SCROLL_GADGET:
		/* compute new position based on the users scroll bar pot */
		new_pos = (file_size * prop.VertPot) / 0x10000;

#ifdef DEBUG
printf("file size: %ld, new pos:%ld\n", file_size, new_pos);
#endif

		/* if at end of file, back up 1/2 page */
		if (new_pos >= file_size)
			new_pos = file_size - ((Page_Length / 2) * AVG_LINE_LENGTH);
		fseek(infile, new_pos, 0);

		/* discard a partial line */
		if (new_pos)
			fgets(buffer, LONGEST_LINE, infile);
		break;

	default:
			done(20, "Illegal argument to Page_File");
	}


	/* now put out one page's worth of the file's data */
	for (where = 17, j = Page_Length; j--; where += 8) {
		Move(My_Window -> RPort, FIRST, where) ;

		/* blank the buffer first */
		for (i = 0; i < 80; i++)
			edited[i] = ' ';

		if (!end_flag) {
			if (fgets(buffer, LONGEST_LINE, infile) == NULL) 
				end_flag = TRUE;
			else {
				size = strlen(buffer);

				/* remove the newline */
				buffer[size-1] = '\0';
				size--;

				p = buffer;
				size = 0;

				/* edit the buffer for tabs and non-printables */
				while(*p) {
					if (*p == '\t') {
						do {
							edited[size++] = ' ';
						} while(size&3);
					} else if(*p < ' ') {
						edited[size++] = '^';
						edited[size++] = *p + '@';
					} else 
						edited[size++] = *p;

					p++;

					/* mark the line as longer than the window */
					if (size >= Line_Length) {
						edited[size-1] = '>';
						break;
					}
				}
			}
		}
		Text(My_Window -> RPort, edited, Line_Length);
	} /* for 1 to size of window */
}

/*
 * done - just close everything that's open, and exit.
 */
static
done(how, why)
int how;
char *why; 
{

	if (My_Window) {
		ClearMenuStrip(My_Window) ;
		Menu_Clear() ;
		CloseWindow(My_Window) ;
	}
	if (GfxBase) CloseLibrary(GfxBase) ;
	if (IntuitionBase) CloseLibrary(IntuitionBase) ;
	if (infile) fclose(infile) ;

	if (why)
		printf("browser: %s\n", why) ;
	(void) OpenWorkBench() ;
	exit(how) ;
}

show_message(s)
char *s;
{
	Move(My_Window -> RPort, FIRST, 17L);
	Text(My_Window -> RPort, s, (long) strlen(s));
	Move(My_Window -> RPort, FIRST, 25L);
	Text(My_Window -> RPort, "                    ", 20L);
}
