#include	<stdio.h>
#include	<string.h>
#include	<time.h>
#include	"picture.h"
#include	"gb.h"
#include	"gfxfunc.h"
#include	"font.h"
#include	"options.h"
#include	"main.h"
#include	"prefs.h"
#include	"keycodes.h"
#include	"system.h"

#define	DEMO	1
#define	DEMO_TIME	(60*10+3)		// 10 minutes 3 seconds
//	Palette layout

//	0-15		Colors used by fonts, cursor, backdrop
//	16-31		Gameboy display palette
//	32-47		Workspace for palette prefs screen
//	48-79		Backdrop palette
//	80-111		Gadgets palette
//	112+		Unused

//***************************************************//
//*													*//
//*	DEFINES											*//
//*													*//
//***************************************************//

//	Where different bits are situated in the palette
#define	GAMEBOY_BASE	16
#define	BACKDROP_BASE	48
#define	GADGETS_BASE	80

#define	ELEMENTS(x)	(sizeof(x)/sizeof(x[0]))
#define	KILLMEM(x)	if(x)GXFreeMem(x);

#define	KEYOPTIONS_TITLE_Y	10
#define	KEYOPTIONS_Y	30
#define	KEYOPTIONS_DY	16

#define	ITEM_STRING		0
#define	ITEM_COLORFILL	1	
#define	GAMEBOY_X	0
#define	GAMEBOY_Y	0

#define	DISPLAY_X	0
#define	DISPLAY_Y	0

#define	PALETTE_PICTURE_X	4
#define	PALETTE_PICTURE_Y	20


#define	SLIDEBOTTOM_X			6
#define	SLIDEBOTTOM_Y			185

#define	DIRECTORYNAME_X			5
#define	DIRECTORYNAME_Y			3
#define	DIRECTORYNAME_WIDTH		212
#define	DIRECTORYNAME_HEIGHT	12
#define	SIDEPANEL_X				230
#define	SIDEPANEL_Y				0

#define	UPARROW_X	208
#define	UPARROW_Y	21

#define	DOWNARROW_X	208
#define	DOWNARROW_Y	181

#define	BUTTONS_X	234
#define	BUTTONS_Y	66
#define	BUTTONS_DY	23

#define	BUTTONID_DOWNARROW		0
#define	BUTTONID_UPARROW		1
#define	BUTTONID_PARENT			2
#define	BUTTONID_VOLUMES		3
#define	BUTTONID_OK				4
#define	BUTTONID_CANCEL			5
#define	BUTTONID_LIST			6
#define	BUTTONID_STRINGGADGET	7

#define	LISTTYPE_FILES		0
#define	LISTTYPE_VOLUMES	1

#define	MAINBUTTON_LOAD		0
#define	MAINBUTTON_PALETTE	1
#define	MAINBUTTON_KEYS		2
#define	MAINBUTTON_SAVE		3
#define	MAINBUTTON_GRAB		4
#define	MAINBUTTON_QUIT		5
#define	MAINBUTTON_CANCEL	6
#define	MAINBUTTON_NUMBER	7

#define	MAINBUTTON_DUMPGFX	7
#define	MENUBUTTON_Y	26

//************************************************************
//	Stuff to do with palette requestor
//************************************************************
#define	COLOR0_X			244
#define	COLOR0_Y			27
#define	COLOR_WIDTH			9
#define	COLOR_HEIGHT		9
#define	COLORSEPERATION_X	16
#define	COLORSEPERATION_Y	19

#define	PALETTE_OKAY_X		4
#define	PALETTE_OKAY_Y		170

#define	PALETTE_CANCEL_X	89
#define	PALETTE_CANCEL_Y	170

#define	RGB_X				239
#define	RGB_Y				111
#define	RGB_DY				29

#define	PALETTE_SECTION_X				205
#define	PALETTE_SECTION_Y				31
#define	PALETTE_SECTION_DY				19

#define	RGB_SLIDER_MINX	168
#define	RGB_SLIDER_MAXX	304
#define	RGB_SLIDER_Y	118
#define	RGB_SLIDER_DY	29
#define	SLIDER_RANGE	(RGB_SLIDER_MAXX-RGB_SLIDER_MINX)

#define	RGB_SLIDER_GFX_X	168
#define	RGB_SLIDER_GFX_Y	104
#define	RGB_SLIDER_GFX_DY	29

#define	RGB_PALETTE_LINE_GFX_X	168
#define	RGB_PALETTE_LINE_GFX_Y	22
#define	RGB_PALETTE_LINE_GFX_DY	19



//***************************************************//
//*													*//
//*	STRUCTURES										*//
//*													*//
//***************************************************//
typedef struct{
	int			Type;
	GXOBLONG	Oblong;
	char		*String;
	int			Color;
	int			BackColor;
	TXFONT		*Font;
	int			File;
	int			ListOrder;
}ITEMDATA;

typedef struct{
	int		X,Y;
	int		MaxX,MaxY;
	int		Width,Height;
	TXFONT	*Font;
	char	*Text;
	GXIMAGE	*Image;
	int		NItem;
	ITEMDATA	**Item;
	int		BackColor;
}SCROLLWINDOW;

typedef struct{
	GXIMAGE	*Image;
	long	MinX,MinY;
	long	MaxX,MaxY;
	long	X,Y;
	long	Width,Height;
	long	Vertical;
	long	Position;
	long	BackColor;
}SLIDER;

typedef struct{
	UBYTE		Grab;
	long		X;		//x coordinate of slider relative to origin
}RGBSLIDER;	

typedef struct{
	GXOBLONG	Area;
	char		*String;
	int			CursorPosition;
}STRINGGADGET;

//***************************************************//
//*													*//
//*	PROTOTYPES										*//
//*													*//
//***************************************************//
void			PrintLoadingMessage(void);
void			UpdateSlider(SLIDER *s, long fraction, GXIMAGE *screen);
int				MakeFileList(SCROLLWINDOW *window, char *dir);
void			MakeVolumeList(SCROLLWINDOW *window);
void			ScrollWindow(SCROLLWINDOW *window, int x, int y);
SCROLLWINDOW	*MakeScrollWindow(int width, int height);
void			KillScrollWindow(SCROLLWINDOW *window);
int				UpdateButtons(GXOBLONG *button, int nbutton);
void			DrawLine(SCROLLWINDOW *window, int ydraw);
void			DrawWindow(SCROLLWINDOW *window, int x, int y, GXIMAGE *image);
int				GetFileSelection(int y, ITEMDATA **list);
void			RedrawItem(SCROLLWINDOW *window, ITEMDATA *item);
ITEMDATA		 **MakeItemData(int NItem, char **name, TXFONT **font, int *ListOrder);
void			KillWindowItemData(SCROLLWINDOW *window);
int				ListHeight(ITEMDATA **ItemList);
void			PrintDirectoryName(GXIMAGE *Screen, TXFONT *TheFont, char *name);
void			ClearWindow(SCROLLWINDOW *window, long color);
void			SaveScreen(void);
void			RestoreScreen(void);
int				DoMenu(char *title, char **options, int NOptions);
void			PalettePrefs(void);
void			DrawPalette(int	j, GXIMAGE *image, int highlight);
void			ResetButtons(void);
void			MoveSlider(int is, int NewX, GXIMAGE *ToImage);
void			KeyOptions(void);
void			DrawButton(GXIMAGE *dest, char *text, long x, long y);
char			*KeyName(long RawCode);
STRINGGADGET	*MakeStringGadget(char *StartString, GXOBLONG *Area, GXIMAGE *ToImage);
void			UpdateStringGadge(STRINGGADGET *sg, int AsciiKey, GXIMAGE *ToImage);
int				RawToAscii(int RawKey);
int				SaveSnapshot(char *name);
int				LoadSnapshot(char *name);
void			ChangeKeySelection(int selection, int NewSelection, GXIMAGE *ScreenImage);
void			PrintStringGadget(STRINGGADGET *sg, GXIMAGE *ToImage, int cursoron);
void			UpdateStringGadget(STRINGGADGET *sg, int AsciiKey, GXIMAGE *ToImage, 
									int active);
void			ChangeStringGadget(char *str, STRINGGADGET *sg, GXIMAGE *ToImage);
char			*GetStringGadget(STRINGGADGET *sg);
void			KillStringGadget(STRINGGADGET *sg);
int				MouseClick(void);
void			MakeWaitMessageList(SCROLLWINDOW *window);
void			TimeOutCheck();
void			TimeOutMessage(void);
void			WaitNoKeys(void);
void			DrawSlider(int is, int x, GXIMAGE *ToImage);
void			DrawBackground(GXIMAGE *image, GXOBLONG *area);


//***************************************************//
//*													*//
//*	STATIC STUFF									*//
//*													*//
//***************************************************//
GXIMAGE		*GameBoyImage=0,*GadgetsImage=0;
TXFONT		*BlackFont,*RedFont,*WhiteFont;
GXPALETTE	White={0xff,0xff,0xff,0};
GXPALETTE	Black={0x00,0x00,0x00,0};
GXPALETTE	Red={0xff,0x00,0x00,0};
GXPALETTE	Orange={255, 156, 0, 0};
GXPALETTE	Grey={112,112,112,0};
int			WhiteIndex,BlackIndex,RedIndex,OrangeIndex,GreyIndex;

//			Location of all the buttons & stuff in the source gfx
GXOBLONG	FileSelector={0,0,320,196};
GXOBLONG	SidePanel={230,19,90,200};
GXOBLONG	ButtonUp={0,197,75,17};
GXOBLONG	SliderDown={76,197,10,11};
GXOBLONG	SliderUp={76,209,10,11};
GXOBLONG	AcrossKnob={76,221,19,6};
GXOBLONG	DownKnob={87,203,6,13};
GXOBLONG	MessageBox={122,197,198,59};
GXOBLONG	PaletteRequestor={0,234,320,196};
GXOBLONG	PaletteNormal={132,203,15,15};
GXOBLONG	PaletteHighlight={178,203,15,15};
GXOBLONG	StringGadget={5,181,201,12};
GXOBLONG	FileTitle={12,3,301,12};
GXOBLONG	TitleBox={0,0,320,18};
GXOBLONG	List={10,20,192,156};
GXOBLONG	SlideSide={209,32,8,149};

GXOBLONG	CornerTL={232,203,7,7};
GXOBLONG	CornerTR={239,203,7,7};
GXOBLONG	CornerBR={239,211,7,7};
GXOBLONG	CornerBL={232,211,7,7};

GXOBLONG	PaletteSlider={177,255-29,142,30};
GXOBLONG	PaletteLine={1,511-276,142,20};
GXOBLONG	BackTile={145,218,31,31};

long		ScreenWidth,ScreenHeight;
GXOBLONG	GameBoyDisplay={64+32,33,160,144};	
GXOBLONG	MainButtonLocation[MAINBUTTON_NUMBER];
long		BackdropX=8+32,BackdropY=0;	//where the backrop image is drawn to
int			CurrentButton=-1;
GXIMAGE		*ScreenSaveImage=0;

#if	DEMO
//**********************************************************************
//	TimeOutCheck()
//**********************************************************************
//	Description: Halt the game after a short period for demo version
//	Input: None
//	Output: None
//**********************************************************************
void	TimeOutCheck()
{
	int		DemoTime;
	static	long	TimeOut=DEMO_TIME,StartTime=0;

	if(!StartTime){
		StartTime=GXTimer();
	}else{
		DemoTime=(GXTimer()-StartTime)/GXTicksPerSecond();
		if(DemoTime>TimeOut){
			TimeOutMessage();
			GXQuitCode("Done\n");
		}
	}
}
//**********************************************************************
//	TimeOutMessage()
//**********************************************************************
//	Description: Print goodbye message for demo version
//	Input: None
//	Output: None
//**********************************************************************
void	TimeOutMessage(void)
{
	GXIMAGE		*ScreenImage;
	long		x,y,StartTime,seconds;
	GXMOUSEINFO	MouseInfo;
	int			LeftReleased=0,MiddleReleased=0,RightReleased=0,j;
	
	SaveScreen();
	ScreenImage=GXStartDrawing(0);

	DrawBackground(ScreenImage, 0);
	y=(ScreenHeight-NTimeOutStrings*TXFONTHeight(RedFont))/2;
	for(j=0; j<NTimeOutStrings; j++){
		x=(ScreenWidth-TXStringWidth(TimeOutStrings[j], RedFont))/2;
		TXPrintString(TimeOutStrings[j], x, y, ScreenImage, RedFont);
		y+=TXFONTHeight(RedFont);
	}
	GXDoBlit();
	StartTime=GXTimer();
	do{
		GXSystemUpdate();

		//	quit after 10 seconds
		seconds=(GXTimer()-StartTime)/GXTicksPerSecond();
		if(seconds>10)break;
		

		//	When any mouse button is pressed after being released,
		//	exit.
		GXReadMouse(&MouseInfo);
		if(!MouseInfo.LeftButton)LeftReleased=1;
		if(!MouseInfo.MiddleButton)MiddleReleased=1;
		if(!MouseInfo.RightButton)RightReleased=1;

		if(MouseInfo.LeftButton&&LeftReleased)break;
		if(MouseInfo.MiddleButton&&MiddleReleased)break;
		if(MouseInfo.RightButton&&RightReleased)break;
	}while(1);
	RestoreScreen();
}
#endif

void	ResetButtons(void)
{
	CurrentButton=-1;
}

//**********************************************************************
//	UpdateButtons()
//**********************************************************************
//	Description: Find out if any buttons have been pressed
//	Input: arrays telling where the buttons are, number of buttons, 
//			output for structure to be filled with button info
//	Output: Button pressed?
//**********************************************************************
int	UpdateButtons(GXOBLONG *button, int nbutton)
{
	long				x,y;
	long				ibutton=-1;
	int					j;
	GXMOUSEINFO			mouse;

	GXReadMouse(&mouse);
	for(j=0; j<nbutton; j++,button++){
		x=mouse.X-button->X;
		if(x<0)continue;
		if(x>button->Width)continue;
		y=mouse.Y-button->Y;
		if(y<0)continue;
		if(y>button->Height)continue;
		ibutton=j;
		break;
	}
	return ibutton;
}

//**********************************************************************
//	DrawWindow()
//**********************************************************************
//	Description: Copy window to image
//	Input: window, location to be drawn to in image, image 
//	Output: none
//**********************************************************************
void	DrawWindow(SCROLLWINDOW *window, int x, int y, GXIMAGE *image)
{
	GXBLITLIST	*b;
	int			xp,yp,xm,ym;

	xp=window->X%window->Width;
	yp=window->Y%window->Height;
	xm=window->Width-xp;
	ym=window->Height-yp;

	if(xm&&ym){
		//	Part A
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->FromImage=window->Image;
		b->ToImage=image;
		b->From.Width=xm;
		b->From.Height=ym;
		b->From.X=xp;
		b->From.Y=yp;
		b->XTo=x;
		b->YTo=y;
	}
	if(xp&&ym){
		//	Part B
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->FromImage=window->Image;
		b->ToImage=image;
		b->From.Width=xp;
		b->From.Height=ym;
		b->From.X=0;
		b->From.Y=yp;
		b->XTo=xm+x;
		b->YTo=y;
	}
	if(xm&&yp){
		//	Part C
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->FromImage=window->Image;
		b->ToImage=image;
		b->From.Width=xm;
		b->From.Height=yp;
		b->From.X=xp;
		b->From.Y=0;
		b->XTo=x;
		b->YTo=y+ym;
	}
	if(xp&&yp){
		//	Part D
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->FromImage=window->Image;
		b->ToImage=image;
		b->From.Width=xp;
		b->From.Height=yp;
		b->From.X=0;
		b->From.Y=0;
		b->XTo=x+xm;
		b->YTo=y+ym;
	}
}

//**********************************************************************
//	ScrollWindow()
//**********************************************************************
//	Description: Move the corner of the window
//	Input: window, coordinates wanted for screen corner 
//	Output: none
//**********************************************************************
void	ScrollWindow(SCROLLWINDOW *window, int x, int y)
{
	int	nloop,ydraw;
	
	//////////////////////////////////
	//	Y Scrolling					//
	//////////////////////////////////
	nloop=y-window->Y;
	if(nloop>0){
		if(nloop>window->Height){	//dont draw more than a whole screen
			ydraw=y;
			nloop=window->Height;
		}else{
			ydraw=window->Y+window->Height;
		}
		do{
			DrawLine(window, ydraw);
			ydraw++;
			nloop--;
		}while(nloop);
	}else if(nloop<0){
		if(nloop>window->Height){	//dont draw more than a whole screen
			nloop=window->Height;		
			ydraw=y+window->Height-1;
		}else{
			nloop*=-1;
			ydraw=window->Y-1;
		}
		do{
			DrawLine(window, ydraw);
			ydraw--;
			nloop--;
		}while(nloop);
	}
	window->Y=y;
}


//**********************************************************************
//	DrawLine()
//**********************************************************************
//	Description: Draw line on window
//	Input: window, y value of line to draw 
//	Output: none
//**********************************************************************
void	DrawLine(SCROLLWINDOW *window, int ydraw)
{
	ITEMDATA		*item,**ItemList;
	TXFONT			*font;
	unsigned char	*str,letter;
	TXCUTDATA		*CutData,*cut;
	GXBLITLIST		*b;
	int				YItem,xs,xe,x0,x1,yto;
	
	yto=ydraw%window->Height;
	ItemList=window->Item;
	if(!ItemList){
		return;
	}
	while(1){
		item=*ItemList++;
		if(!item){
			break;
		}
		YItem=ydraw-item->Oblong.Y;
		if( (YItem>=0) && (YItem<item->Oblong.Height) ){
			if(item->Type==ITEM_STRING){	
				//////////////////////////////
				//	Draw a string			//
				//////////////////////////////

				//	Draw background	
				b=GXNextBlit();
				b->Type=BLIT_COLORFILL;
				b->ToImage=window->Image;
				b->From.Width=window->Width;
				b->From.Height=1;
				b->XTo=0;
				b->YTo=yto;
				b->Color=item->BackColor;

				xs=item->Oblong.X - window->X;
				str=(unsigned char *)item->String;
				font=item->Font;
				CutData=font->Cut;
				while(letter=*str++){
					if(xs>=window->Width)
						break;				//gone past end of window
					cut=CutData+letter;
					if(cut->Control){
						//	Control character
						xs+=font->Width;
					}else{
						//	Normal character
						xe=xs+cut->Oblong.Width-1;	//=x value last pixel drawn
						if(xe>=0){			//inside left edge
							if(xs<0){
								x0=0;
							}else{
								x0=xs;
							}
							if(xe>=window->Width){
								x1=window->Width-1;
							}else{
								x1=xe;
							}
							b=GXNextBlit();
							b->Type=BLIT_IMAGE;
							b->FromImage=font->Image;
							b->ToImage=window->Image;
							b->From.X=x0-xs+cut->Oblong.X;
							b->From.Y=YItem;
							b->From.Width=x1-x0+1;
							b->From.Height=1;
							b->XTo=x0;
							b->YTo=yto;
						}
						xs+=(cut->Oblong.Width+cut->XOffset);
					}
				}
			}else{
				//////////////////////////
				//	Draw a rectangle	//
				//////////////////////////
				GXQuitCode("Called dodgy code\n");
				xs=item->Oblong.X - window->X;
				if(xs<window->Width){
					if(xs<0){
						x0=0;
					}else{
						x0=xs;
					}
					xe=xs+item->Oblong.Width-1;
					if(xe>=0){
						if(xe>=window->Width){
							x1=window->Width-1;
						}else{
							x1=xe;
						}
						b=GXNextBlit();
						b->Type=BLIT_COLORFILL;
						b->ToImage=window->Image;
						b->From.Width=x1-x0+1;
						b->From.Height=1;
						b->XTo=x0;
						b->YTo=yto;
						b->Color=item->Color;
					}
				}
			}			
		}
	}
}

//**********************************************************************
//	MakeScrollWindow()
//**********************************************************************
//	Description: Make scrollwindow
//	Input: size 
//	Output: window
//**********************************************************************
SCROLLWINDOW	*MakeScrollWindow(int width, int height)
{
	SCROLLWINDOW	*window=0;
	
	window=(SCROLLWINDOW *)GXGetMem(sizeof(SCROLLWINDOW), MEMF_ABORT|MEMF_ZERO);
	window->Image=GXMakeImage(width, height);
	if(!window->Image){
		GXPrintf("MakeScrollWindow(): Couldnt make image\n");
		goto fail;
	}	
	window->Width=width;
	window->Height=height;
	window->X=window->Y=0;
	window->BackColor=WhiteIndex;
	return window;
fail:
	GXQuitCode("Failed in scroll window creation\n");
	return	0;
}

//**********************************************************************
//	KillScrollWindow()
//**********************************************************************
//	Description: Kill window made above
//	Input: window
//	Output: none
//**********************************************************************
void	KillScrollWindow(SCROLLWINDOW *window)
{
	GXKillImage(window->Image);
	GXFreeMem(window);
}


//**********************************************************************
//	SetupGFX()
//**********************************************************************
//	Description: Load in all the graphics and fonts needed
//	Input: none
//	Output: none
//**********************************************************************
void	SetupGFX(void)
{
	GXPICTURE	*GadgetsPic,*GameBoyPic,*pic;
	GXIMAGE		*ScreenImage;
	int			j;

	ScreenImage=GXStartDrawing(0);	
	ScreenWidth=GXImageWidth(ScreenImage);
	ScreenHeight=GXImageHeight(ScreenImage);

	for(j=0; j<256; j++){
		GXSetPaletteRange(&Black, j, 1);
	}

	GXSetPaletteRange(GameBoyPalette, GAMEBOY_BASE, 16);
	GXReserveColors(0, 256,	0);
	GXReserveColors(GAMEBOY_BASE, 32, 1);
	GXReserveColors(0, 1, 1);

	GXSetPaletteRange(&Black, 1, 1);
	BlackIndex=1;
	GXSetPaletteRange(&White, 2, 1);
	WhiteIndex=2;
	GXSetPaletteRange(&Red, 3, 1);
	RedIndex=3;
	GXSetPaletteRange(&Orange, 4, 1);
	OrangeIndex=4;
	GXSetPaletteRange(&Grey, 5, 1);
	GreyIndex=5;
	
	//	Load pics
	GadgetsPic=LoadPicture("gfx/gadgets.pic");
	if(!GadgetsPic)goto fail;
	GameBoyPic=LoadPicture("gfx/gameboy.pic");
	if(!GameBoyPic)goto fail;

	//	Set palette range used by gadgets & gameboy
	GXSetPaletteRange(GadgetsPic->Palette, GADGETS_BASE, 32);
	GXSetPaletteRange(GameBoyPic->Palette, BACKDROP_BASE, 32); 

	//	Create gadgets & gameboy images
	GadgetsImage=GXPictureToImage(GadgetsPic, 1, 0);
	if(!GadgetsImage)goto fail;
	GameBoyImage=GXPictureToImage(GameBoyPic, 0, 0);
	if(!GameBoyImage)goto fail;
	KillPicture(GadgetsPic);
	KillPicture(GameBoyPic);
	
	//	Load fonts	
	pic=LoadPicture(FontGfxName);
	BlackFont=TXLoadFont(pic, FontCutName, &Black);
	if(!BlackFont)goto fail;
	RedFont=TXLoadFont(pic, FontCutName, &Red);
	if(!RedFont)goto fail;
	WhiteFont=TXLoadFont(pic, FontCutName, &White);
	if(!WhiteFont)goto fail;
	KillPicture(pic);
	return;
fail:
	GXQuitCode("Failed to find setup graphics\n");	
}

//**********************************************************************
//	ClearWindow()
//**********************************************************************
//	Description: Fill window with given color
//	Input: window, color
//	Output: none
//**********************************************************************
void	ClearWindow(SCROLLWINDOW *window, long color)
{
	GXBLITLIST	*b;

	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From.Width=window->Width;
	b->From.Height=window->Height;
	b->Color=color;
	b->XTo=0;
	b->YTo=0;
	b->ToImage=window->Image;
	GXDoBlit();
}
//**********************************************************************
//	MakeWaitMessageList()
//**********************************************************************
//	Description: Set up list to show "Loading..." message
//	Input: window
//	Output: none
//**********************************************************************
void	MakeWaitMessageList(SCROLLWINDOW *window)
{
	char	*list[1];
	int		ListOrder[]={1};
	TXFONT	*font[1];

	list[0]=FileStrings[4];
	font[0]=BlackFont;
	window->Item=MakeItemData(1, list, font, ListOrder);
}

//**********************************************************************
//	MakeVolumeList()
//**********************************************************************
//	Description: Make item data for list of volumes and attach it to
//				 a window
//	Input: window
//	Output: none
//**********************************************************************
void	MakeVolumeList(SCROLLWINDOW *window)
{
	GXVOLUME	*Volume;
	long		NVolume;
	char		**strings;
	TXFONT		**fonts;
	int			*ListOrder;
	int			NItem,j;


	Volume=GXMakeVolumeInfo(&NVolume);
	strings=(char **)GXGetMem(NVolume*sizeof(char *), MEMF_ABORT);
	fonts=(TXFONT **)GXGetMem(NVolume*sizeof(TXFONT *), MEMF_ABORT);
	ListOrder=(int *)GXGetMem(NVolume*sizeof(int), MEMF_ABORT);
	NItem=NVolume;
	for(j=0; j<NItem; j++){
		strings[j]=Volume[j].Name;
		fonts[j]=BlackFont;
		ListOrder[j]=0;
	}
	window->Item=MakeItemData(NItem, strings, fonts, ListOrder);
	GXKillVolumeInfo(Volume);
	GXFreeMem(strings);
	GXFreeMem(fonts);
	GXFreeMem(ListOrder);

	ClearWindow(window, WhiteIndex);
	for(j=0; j<window->Height; j++){
		DrawLine(window, j);
	}
	window->MaxY=ListHeight(window->Item)-window->Height;
	if(window->MaxY<0)window->MaxY=0;
}

//**********************************************************************
//	MakeFileList()
//**********************************************************************
//	Description: Make item data for directory listing and attach it to
//					a window
//	Input: window, directory name
//	Output: Did i get a list?
//**********************************************************************
int	MakeFileList(SCROLLWINDOW *window, char *dir)
{
	GXFILEINFO	*FileInfo=0,*info;
	int			NItem,j;
	char		**strings;
	TXFONT		**fonts;
	int			*ListOrder;
	ITEMDATA	**ItemList;

	if(!GXMakeDirectoryInfo(dir, &FileInfo)){
		return 0;
	}
	info=FileInfo;
	if(!info){
		window->MaxY=0;
		ClearWindow(window, WhiteIndex);
		GXDoBlit();
		return 1;
	}
	NItem=0;
	while(info){
		NItem++;
		info=(GXFILEINFO *)info->Next;
	}
	strings=(char **)GXGetMem(NItem*sizeof(char *), MEMF_ABORT);
	fonts=(TXFONT **)GXGetMem(NItem*sizeof(TXFONT *), MEMF_ABORT);
	ListOrder=(int *)GXGetMem(NItem*sizeof(int), MEMF_ABORT);
	info=FileInfo;
	for(j=0; j<NItem; j++){
		strings[j]=info->Name;
		if(info->File){
			ListOrder[j]=0;
			fonts[j]=BlackFont;
		}else{
			ListOrder[j]=1;
			fonts[j]=RedFont;
		}
		info=(GXFILEINFO *)info->Next;
	}
	ItemList=MakeItemData(NItem, strings, fonts, ListOrder);
	GXKillDirectoryInfo(FileInfo);
	GXFreeMem(strings);
	GXFreeMem(fonts);
	GXFreeMem(ListOrder);

	window->Item=ItemList;
	
	//	Get scroll limits
	window->MaxY=ListHeight(window->Item)-window->Height;
	if(window->MaxY<0)window->MaxY=0;
	GXPrintf("MaxY=%d\n",window->MaxY);

	ClearWindow(window, WhiteIndex);

	for(j=0; j<window->Height; j++){
		DrawLine(window, j);
	}
	GXDoBlit();
	return 1;
}

//**********************************************************************
//	MakeItemData()
//**********************************************************************
//	Description: Make item data for scroll window
//	Input:  number of items, array of name, array of fonts, array 
//			giving priority in list		
//	Output: item data or zero if it went wrong
//**********************************************************************
ITEMDATA **MakeItemData(int NItem, char **name, TXFONT **font, int *ListOrder)
{
	ITEMDATA 	**ItemList,*item,*item2;
	size_t		size;
	int			j,k;
	long		y;
	char		*str,*str2,letter,letter2;
	int			swap;

	size=(NItem + 1)*sizeof(ITEMDATA *);
	ItemList=(ITEMDATA **)GXGetMem(size, MEMF_ABORT);

	for(j=0; j<NItem; j++){
		size=sizeof(ITEMDATA) + strlen(name[j]) + 1;
		ItemList[j]=item=(ITEMDATA *)GXGetMem(size, MEMF_ABORT|MEMF_ZERO);

		item->String=(char *)(item+1);
		strcpy(item->String, name[j]);

		item->Font=font[j];
		item->Type=ITEM_STRING;
		item->ListOrder=ListOrder[j];
		item->BackColor=WhiteIndex;
	}
	ItemList[j]=0;
	
	//	Sort entries
	for(j=0; j<(NItem-1); j++){
		item=ItemList[j];
		for(k=j+1; k<NItem; k++){
			item2=ItemList[k];
			if(item2->ListOrder>item->ListOrder){
				//	Directories come before files
				ItemList[k]=item;
				ItemList[j]=item2;
				item=item2;
			}else if(item2->ListOrder==item->ListOrder){
				//	Entries are of same type so compare alpabetically
				str=item->String;
				str2=item2->String;
				while(1){
					letter=*str++;
					letter2=*str2++;
					if(!letter2){
						swap=1;
						break;					
					}
					if(!letter){
						swap=0;
						break;
					}
					if( (letter>='a')&&(letter<='z') ){
						letter+=('A'-'a');
					}
					if( (letter2>='a')&&(letter2<='z') ){
						letter2+=('A'-'a');
					}
					if(letter2<letter){
						swap=1;
						break;
					}else if(letter2>letter){
						swap=0;
						break;
					}
				}
				if(swap){
					ItemList[k]=item;
					ItemList[j]=item2;
					item=item2;
				}
			}
		}	
	}

	//	Figure out positions
	y=0;
	for(j=0; j<NItem; j++){
		item=ItemList[j];
		item->Oblong.X=0;
		item->Oblong.Y=y;
		item->Oblong.Width=TXStringWidth(item->String, item->Font);
		item->Oblong.Height=item->Font->Height;
		y+=item->Font->Height;
	}
	return	ItemList;
}


//**********************************************************************
//	KillWindowItemData()
//**********************************************************************
//	Description: Free up item list made with above functions
//	Input: item list
//	Output: none
//**********************************************************************
void	KillWindowItemData(SCROLLWINDOW *window)
{
	ITEMDATA *item,**from;
	
	if(!window->Item){
		return;
	}
	from=window->Item;
	while(item=*from++){
		GXFreeMem(item);
	}
	GXFreeMem(window->Item);
	window->Item=0;
}

//**********************************************************************
//	ListHeight()
//**********************************************************************
//	Description: Find maximum y value for item list
//	Input: item list
//	Output: maximum y
//**********************************************************************
int	ListHeight(ITEMDATA **ItemList)
{
	ITEMDATA *item;
	
	
	while(*ItemList++)
		;
	item=*(ItemList-2);
	return	(item->Oblong.Y+item->Oblong.Height);
}

//**********************************************************************
//	PrintDirectoryName()
//**********************************************************************
//	Description: Print directory name at top of screen
//	Input: screen image, font, directory
//	Output: none
//**********************************************************************
void	PrintDirectoryName(GXIMAGE *Screen, TXFONT *TheFont, char *name)
{
	long		XPlot,YPlot;
	GXBLITLIST	*b;
	char		*DirName=" ";
	
	if(*name){
		DirName=name;
	}
	while(*DirName){
		if( TXStringWidth(DirName, TheFont) <= DIRECTORYNAME_WIDTH ){
			break;
		}
		DirName++;
	}
	if(!(*DirName)){
		GXQuitCode("PrintDirectoryName(): Couldnt get Directory name to fit\n");
	}	
	
	//	Figure out plot position
	XPlot=(DIRECTORYNAME_WIDTH - TXStringWidth(DirName, TheFont))/2;
	if(XPlot<0){
		XPlot=DIRECTORYNAME_X;
	}else{
		XPlot+=DIRECTORYNAME_X;
	}
	YPlot=(DIRECTORYNAME_HEIGHT - TXFONTHeight(TheFont))/2;
	if(YPlot<0){
		YPlot=DIRECTORYNAME_Y;
	}else{
		YPlot+=DIRECTORYNAME_Y;
	}
	
	//	Clear out last string
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From.Width=DIRECTORYNAME_WIDTH;
	b->From.Height=DIRECTORYNAME_HEIGHT;
	b->Color=WhiteIndex;
	b->XTo=DIRECTORYNAME_X;
	b->YTo=DIRECTORYNAME_Y;
	b->ToImage=Screen;
	GXDoBlit();
	
	//	Print new string
	TXPrintString(DirName, XPlot, YPlot, Screen, TheFont);
}

//**********************************************************************
//	GetFileSelection()
//**********************************************************************
//	Description: Find what item correspnds to a given Y value in a
//					window.
//	Input:  y value, items 
//	Output: Number of item or -1 if none found
//**********************************************************************
int	GetFileSelection(int y, ITEMDATA **list)
{
	int			yy,n;
	ITEMDATA	*item;
	
	if(!list){
		return -1;
	}
	n=0;
	while(item=*list++){
		yy=y-item->Oblong.Y;
		if( (yy>0) && (yy<item->Oblong.Height) ){
			return	n;
		}
		n++;
	}
	return -1;
}

//*****************************************************************************
//	WaitNoKeys()
//*****************************************************************************
//	Description: Wait until no keys are pressed
//	input:  None
//	output: None
//*****************************************************************************
void	WaitNoKeys(void)
{
	long	j;

	//	Wait till no keys are pressed
	do{
		GXSystemUpdate();
		for(j=0; j<256; j++){
			if(GXKeyDown(j)){
				break;
			}
		}
	}while(j!=256);

	//	Empty keyboard buffer
	while(GXNextKey())
		;
}

//*****************************************************************************
//	DoMenu()
//*****************************************************************************
//	Description: Let the user choose from options on a side panel
//	input:  menu title, strings for options, number of options
//	output: option chosen or -1 if none
//*****************************************************************************
#define	CLICK_SELECT	30	//how quick double click must be to select
int		DoMenu(char *title, char **options, int NOptions)
{
	GXBLITLIST	*b;
	long		x,y,j;
	GXOBLONG	*Buttons=0;	
	GXMOUSEINFO	MouseInfo,LastMouseInfo;
	GXIMAGE		*ScreenImage;
	long		selection;
	long		frame=0,JustPressed=0,NewSelection,delay,xx,tt,number;
	long		DoubleClick,DoubleClickDelay,moving;

	WaitNoKeys();
	SaveScreen();
	ScreenImage=GXStartDrawing(0);

	//	Draw backdrop
	DrawBackground(ScreenImage, 0);
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=TitleBox;
	b->XTo=b->YTo=0;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;
	
	//	Print title
	x=TitleBox.Width/2 + TitleBox.X - TXStringWidth(title, BlackFont)/2;
	y=TitleBox.Height/2 + TitleBox.Y - TXFONTHeight(BlackFont)/2;
	TXPrintString(title, x, y, ScreenImage, BlackFont);

	//	Print menu options & initialize button data
	Buttons=(GXOBLONG *)GXGetMem(NOptions*sizeof(GXOBLONG), MEMF_ZERO|MEMF_ABORT);
	for(j=0; j<NOptions; j++){
		y=KEYOPTIONS_Y+KEYOPTIONS_DY*j;	
		Buttons[j].Width=TXStringWidth(options[j], BlackFont);
		Buttons[j].Height=TXFONTHeight(BlackFont);
		Buttons[j].X=(ScreenWidth-Buttons[j].Width)/2;
		Buttons[j].Y=y;
		if(j){
			TXPrintString(options[j], Buttons[j].X, Buttons[j].Y, ScreenImage, BlackFont);
		}else{
			TXPrintString(options[j], Buttons[j].X, Buttons[j].Y, ScreenImage, RedFont);
		}
		y+=Buttons[j].Height;
	}	

	selection=0;
	delay=GXTicksPerSecond()/60;
	DoubleClickDelay=CLICK_SELECT+1;
	if(!delay)delay=1;
	GXReadMouse(&LastMouseInfo);
	moving=0;
	do{
		//	Wait till next sixtieth to do next frame
		xx=GXTimer();
		do{
			tt=GXTimer()-xx;
			if(tt<0)break;
		}while(tt< delay );
		frame++;

		//	Update everything
		GXSystemUpdate();
		GXReadMouse(&MouseInfo);
		GXDoBlit();
		while(GXNextKey())
			;

		//	Update menu selection due to mouse
		NewSelection=selection;
		JustPressed=MouseInfo.LeftButton&(!LastMouseInfo.LeftButton);
		if( (number=UpdateButtons(Buttons, NOptions)) != -1 ){
			if(JustPressed){
				NewSelection=number;
			}
		}
		DoubleClick=0;
		if(JustPressed){
			if( (DoubleClickDelay<CLICK_SELECT) && (NewSelection==selection) ){
				DoubleClick=1;
			}
			DoubleClickDelay=0;
		}else{
			DoubleClickDelay++;
		}

		//	Update menu selection due to keyboard/joypad
		if( (frame==20) || (!moving) ){
			if(GXKeyDown(KeyPrefs[3])){
				if(selection==(NOptions-1)){
					NewSelection=0;
				}else{
					NewSelection=selection+1;
				}
				moving=1;
			}else if(GXKeyDown(KeyPrefs[2])){
				if(!selection){
					NewSelection=NOptions-1;
				}else{
					NewSelection=selection-1;
				}
				moving=1;
			}else{
				moving=0;
			}
			frame=0;
		}
		//	Check for menu exit
		if( DoubleClick || GXKeyDown(KeyPrefs[4]) || GXKeyDown(KEY_RETURN) ){
			break;
		}

		//	Update highlighted option
		if(selection!=NewSelection){
			TXPrintString(options[selection], 
				Buttons[selection].X, Buttons[selection].Y, ScreenImage, BlackFont);
			selection=NewSelection;
			TXPrintString(options[selection], 
				Buttons[selection].X, Buttons[selection].Y, ScreenImage, RedFont);
		}
		//	Save mouse input for use next frame
		LastMouseInfo=MouseInfo;
	}while(1);

	//	Free up everything and restore screen
	if(Buttons)GXFreeMem(Buttons);
	RestoreScreen();
	return selection;
}	
//*****************************************************************************
//	MakeGameScreen()
//*****************************************************************************
//	Description: Draw the in-game screen
//	input: none
//	output: none
//*****************************************************************************
void	MakeGameScreen(void)
{
	GXBLITLIST	*b;
	GXIMAGE		*ScreenImage;
	long		GameBoyY;
	long		xx,width,height,YStep;

	ScreenImage=GXStartDrawing(0);
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From.Width=ScreenWidth;
	b->From.Height=ScreenHeight;
	b->Color=BlackIndex;
	b->XTo=0;
	b->YTo=0;
	b->ToImage=ScreenImage;

	//	Scroll in gameboy
	width=GXImageWidth(GameBoyImage);
	xx=ScreenWidth-BackdropX;
	if(xx<width){
		width=xx;
	}
	GameBoyY=ScreenHeight-1;
	YStep=ScreenHeight/25;		//scroll up in 50 frames
	if(!YStep)YStep=1;
	do{
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->From.X=b->From.Y=0;
		b->From.Width=width;

		height=GXImageHeight(GameBoyImage);
		xx=ScreenHeight-GameBoyY;
		if(xx<height)height=xx;
		b->From.Height=height;
	
		b->XTo=BackdropX;
		b->YTo=GameBoyY;
		b->FromImage=GameBoyImage;
		b->ToImage=ScreenImage;
		GXDoBlit();
		if(GameBoyY==BackdropY)break;
		GameBoyY-=YStep;
		if(GameBoyY<BackdropY)GameBoyY=BackdropY;
		GXSystemUpdate();
	}while(1);

	//	Clear out area to be drawn to by gameboy
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From=GameBoyDisplay;
	b->Color=GAMEBOY_BASE;
	b->XTo=GameBoyDisplay.X;
	b->YTo=GameBoyDisplay.Y;
	b->ToImage=ScreenImage;
	GXDoBlit();
}

	GXIMAGE			*ScreenImage;

//*****************************************************************************
//	FileRequestor()
//*****************************************************************************
//	Description: Gets file name selection
//	input: address of output string
//	output: file chosen ?
//*****************************************************************************
int	FileRequestor(char *output, char *title)
{
	GXBLITLIST		*b;
	GXMOUSEINFO		MouseInfo,OldMouseInfo;
	SCROLLWINDOW	*window=0;
	ITEMDATA		*item;
	long			Y;
	int				j;
	long			yy,xx,ScrollY,Grab;
	SLIDER			Up;
	GXOBLONG		Buttons[8];
	char			letter;
	int				selection;
	long			ClickInterval;
	long			ListType;
	static	char	DirectoryString[256]="";
	static	char	FileString[256]="";
	static	char	DeviceString[256]="";
	char			ss[256];
	char			*from;
	int				NSlash;
	int				finished,Highlight;
	STRINGGADGET	*sg=0;
	int				ActiveGadget,key;
	int				JustPressed,number;
	int				success=0;
	
	//	Set parameters for sliders
	Up.Width=DownKnob.Width;
	Up.Height=DownKnob.Height;
	Up.MinX=Up.MaxX=SlideSide.X+1;
	Up.MinY=SlideSide.Y+1;
	Up.MaxY=Up.MinY+SlideSide.Height-Up.Height-2;
	Up.Vertical=1;
	Up.Position=0;
	Up.X=Up.MinX;
	Up.Y=Up.MinY;
	Up.BackColor=WhiteIndex;

	//////////////////////////////////
	//	Set up the scroll window	//
	//////////////////////////////////
	window=MakeScrollWindow(List.Width,List.Height);
	sprintf(ss,"%s%s",DeviceString,DirectoryString);
	if(!MakeFileList(window, ss)){
		strcpy(DeviceString,"");
		strcpy(DirectoryString,"");			//fixit
		if(!MakeFileList(window, ss)){
			GXQuitCode(ErrorString[1]);
		}
	}
	ListType=LISTTYPE_FILES;

	ScreenImage=GXStartDrawing(0);
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From.Width=GXImageWidth(ScreenImage);
	b->From.Height=GXImageHeight(ScreenImage);
	b->Color=BlackIndex;
	b->XTo=0;
	b->YTo=0;
	b->ToImage=ScreenImage;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=FileSelector;
	b->XTo=0;
	b->YTo=0;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;
	DrawWindow(window, List.X, List.Y, ScreenImage);

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=SliderUp;
	b->XTo=UPARROW_X;
	b->YTo=UPARROW_Y;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;
	
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=SliderDown;
	b->XTo=DOWNARROW_X;
	b->YTo=DOWNARROW_Y;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;
	
	sprintf(ss,"%s%s",DeviceString,DirectoryString);

	//	DrawButtons
	for(j=0; j<4; j++){
		DrawButton(ScreenImage, FileStrings[j], BUTTONS_X, BUTTONS_Y+j*BUTTONS_DY);
	}	
	
	//	Title
	xx=(FileTitle.Width/2) - TXStringWidth(title, BlackFont)/2 + FileTitle.X;
	yy=(FileTitle.Height/2) - TXFONTHeight(BlackFont)/2 + FileTitle.Y;
	TXPrintString(title, xx, yy, ScreenImage, BlackFont);

	
	//	Make buttons
	Buttons[0]=SliderDown;
	Buttons[0].X=DOWNARROW_X;
	Buttons[0].Y=DOWNARROW_Y;
		
	Buttons[1]=SliderUp;
	Buttons[1].X=UPARROW_X;
	Buttons[1].Y=UPARROW_Y;

	//	Parent button
	Buttons[2]=ButtonUp;
	Buttons[2].X=BUTTONS_X;
	Buttons[2].Y=BUTTONS_Y;

	//	Volumes button
	Buttons[3]=Buttons[2];
	Buttons[3].Y+=BUTTONS_DY;
	
	//	OK button
	Buttons[4]=Buttons[3];
	Buttons[4].Y+=BUTTONS_DY;

	//	CANCEL
	Buttons[5]=Buttons[4];
	Buttons[5].Y+=BUTTONS_DY;
	
	//	LIST BOX
	Buttons[6].X=List.X;
	Buttons[6].Y=List.Y;
	Buttons[6].Width=List.Width;
	Buttons[6].Height=List.Height;

	//	STRING GADGET
	Buttons[7]=StringGadget;

	sg=MakeStringGadget(FileString, &StringGadget, ScreenImage);
	/************************************************/
	/*												*/
	/*	INPUT LOOP									*/
	/*												*/
	/************************************************/
#define	LIST_VOLUMES	{							\
						PrintLoadingMessage();		\
						MakeVolumeList(window);		\
						ListType=LISTTYPE_VOLUMES;	\
						ScrollY=0;					\
						Y=Up.MinY;					\
						Highlight=-1;				\
						break;						\
						}

	ScrollY=0;
	GXReadMouse(&OldMouseInfo);
	Y=Up.MinY;
	Grab=0;
	Highlight=-1;
	selection=-1;
	ClickInterval=0;
	finished=0;
	ActiveGadget=-1;
	while(!finished){
		//********************************************
		//	Update input devices and system
		//********************************************
		ClickInterval++;
		GXSystemUpdate();
		GXReadMouse(&MouseInfo);
		JustPressed=MouseClick();
		key=GXNextKey();
		while(GXNextKey())
			;
		
		//********************************************
		//	Update string gadget
		//********************************************
		UpdateStringGadget(sg, RawToAscii(key), ScreenImage, (ActiveGadget==7));

		//********************************************
		//	Update slider
		//********************************************
		if(JustPressed){
			//	Check for grabbing slider
			xx=MouseInfo.X-Up.X;
			if( (xx>=0) && (xx<Up.Width) ){
				yy=MouseInfo.Y-Up.Y;
				if( (yy>=0) && (yy<Up.Height) ){
					Grab=1;
				}
			}
		}
		if(!MouseInfo.LeftButton){
			Grab=0;
		}
		if(Grab){
			Y+=(MouseInfo.Y-OldMouseInfo.Y);
			if(Y<Up.MinY){
				Y=Up.MinY;
			}else if(Y>Up.MaxY){
				Y=Up.MaxY;
			}
			ScrollY=(Y-Up.MinY)*window->MaxY/(Up.MaxY-Up.MinY);
		}else if( (number=UpdateButtons(Buttons, 8)) != -1 ){
			//********************************************
			//	Update buttons
			//********************************************
			if(JustPressed){
				ActiveGadget=number;
			}
			switch(number){
			case BUTTONID_STRINGGADGET:
				break;
			case BUTTONID_DOWNARROW: 
				if(!MouseInfo.LeftButton)break;
				if(window->MaxY){
					ScrollY++;	
					if(ScrollY>window->MaxY){
						ScrollY=window->MaxY;
					}
					Y=(Up.MaxY-Up.MinY)*ScrollY/window->MaxY;
					Y+=Up.MinY;
				}
				break;
			case BUTTONID_UPARROW: 
				if(!MouseInfo.LeftButton)break;
				if(window->MaxY){
					ScrollY--;	
					if(ScrollY<0){
						ScrollY=0;
					}
					Y=(Up.MaxY-Up.MinY)*ScrollY/window->MaxY;
					Y+=Up.MinY;
				}
				break;
			case BUTTONID_PARENT:
				if(!JustPressed){
					break;
				}
				Highlight=-1;
				KillWindowItemData(window);
				PrintLoadingMessage();
				if( !DirectoryString[0] ){
					MakeVolumeList(window);
					ListType=LISTTYPE_VOLUMES;
					ScrollY=0;
					Y=Up.MinY;
					break;
				}else{
					from=DirectoryString;
					NSlash=0;
					while(letter=*from++){
						if(letter=='/')NSlash++;
					}
					if(NSlash>1){
						from=DirectoryString+strlen(DirectoryString)-2;					
						while(letter=*from--){
							if(letter=='/')break;
						}
						from[2]=0;
					}else{
						DirectoryString[0]=0;
					}
				}
				sprintf(ss,"%s%s",DeviceString,DirectoryString);
				//PrintDirectoryName(ScreenImage, BlackFont, ss);
				PrintLoadingMessage();
				if(!MakeFileList(window, ss)){
					LIST_VOLUMES
					break;
				}
				ListType=LISTTYPE_FILES;
				Y=Up.MinY;
				ScrollY=0;
				break;
			case BUTTONID_VOLUMES:
				if(JustPressed){
					KillWindowItemData(window);
					LIST_VOLUMES
				}
				break;
			case BUTTONID_OK:
				if(JustPressed){
					if( ListType==LISTTYPE_FILES ){
						success=1;
						finished=1;
					}
				}
				break;
			case BUTTONID_CANCEL:
				if(JustPressed){
					*output=0;
					finished=1;					
				}
				break;
			case BUTTONID_LIST:
				if(!JustPressed){
					break;
				}
				xx=GetFileSelection(MouseInfo.Y-List.Y+window->Y, window->Item);
				if(xx==-1)break;
				selection=xx;
				if(selection==Highlight){	//clicked twice on same item
					if(ClickInterval<GXTicksPerSecond()){
						//****************************************
						//	Double clicked
						//****************************************
						if(ListType==LISTTYPE_FILES){
							item=window->Item[selection];
							if(item->ListOrder==1){
								//	directory
								strcat(DirectoryString, item->String);
								strcat(DirectoryString, "/");
								KillWindowItemData(window);
								sprintf(ss,"%s%s",DeviceString,DirectoryString);
								//PrintDirectoryName(ScreenImage, BlackFont, ss);
								PrintLoadingMessage();
								if(!MakeFileList(window, ss)){
									GXPrintf("*** Failed to make list of files!!\n");
									LIST_VOLUMES;
									break;
								}else{
									GXPrintf("*** Made file list\n");
								}
								ListType=LISTTYPE_FILES;
								Y=Up.MinY;
								ScrollY=0;
								Highlight=-1;
							}else{
								//	file
								success=1;
								finished=1;
							}
						}else{
							strcpy(DeviceString, window->Item[selection]->String);
							strcpy(DirectoryString,"");
							KillWindowItemData(window);
							//PrintDirectoryName(ScreenImage, BlackFont, DeviceString);
							PrintLoadingMessage();
							if(!MakeFileList(window, DeviceString)){
								LIST_VOLUMES;
								break;
							}
							ListType=LISTTYPE_FILES;
							Y=Up.MinY;
							ScrollY=0;
							GXPrintf("Volume selected!!\n");
							GXPrintf("%s\n",DeviceString);
							Highlight=-1;
						}
					}
				}else{
					item=window->Item[selection];
					if( (ListType==LISTTYPE_FILES) && (item->ListOrder==0)){
						ChangeStringGadget(item->String, sg, ScreenImage);											
					}
					if(item->ListOrder==0){
						item->BackColor=BlackIndex;
					}else{
						item->BackColor=RedIndex;
					}
					item->Font=WhiteFont;
					RedrawItem(window, item);
					if(Highlight!=-1){
						item=window->Item[Highlight];
						if(item->ListOrder==0){
							item->Font=BlackFont;
						}else{
							item->Font=RedFont;
						}
						item->BackColor=WhiteIndex;
						RedrawItem(window, item);
					}
					Highlight=selection;
				}
				ClickInterval=0;
				break;
			case -1:
				break;
			default:
				GXQuitCode("Invalid button\n");
			}
		}
		//	Check cursor keys
		if(GXKeyDown(KEY_CURSORDOWN)){ 
			if(window->MaxY){
				ScrollY+=2;	
				if(ScrollY>window->MaxY){
					ScrollY=window->MaxY;
				}
				Y=(Up.MaxY-Up.MinY)*ScrollY/window->MaxY;
				Y+=Up.MinY;
			}
		}
		if(GXKeyDown(KEY_CURSORUP)){
			if(window->MaxY){
				ScrollY-=2;	
				if(ScrollY<0){
					ScrollY=0;
				}
				Y=(Up.MaxY-Up.MinY)*ScrollY/window->MaxY;
				Y+=Up.MinY;
			}
		}
		if(GXKeyDown(KEY_RETURN)){
			if(ListType==LISTTYPE_FILES){
				success=1;
				finished=1;
			}
		}
		ScrollWindow(window, 0, ScrollY);
		DrawWindow(window, List.X, List.Y, ScreenImage);
		UpdateSlider(&Up, Y, ScreenImage);
		GXDoBlit();
		OldMouseInfo=MouseInfo;
	}
	if(success){
		sprintf(output, "%s%s%s",
			DeviceString, DirectoryString, GetStringGadget(sg));
	}else{
		*output=0;
	}
	KillWindowItemData(window);
	KillScrollWindow(window);
	if(sg)KillStringGadget(sg);

	return success;
}

int	MouseClick(void)
{
	int			jj;
	GXMOUSEINFO	mouse;
	static		GXMOUSEINFO	LastMouse;	
	
	GXReadMouse(&mouse);
	jj=(mouse.LeftButton)&(!LastMouse.LeftButton);
	LastMouse=mouse;
	return jj;
}

//**********************************************************************
//	PrintLoadingMessage()
//**********************************************************************
//	Description:print "READING..." message in the file list to let
//				the user know that a directory or volume list is being
//				read.
//	Input: none
//	Output: none
//**********************************************************************
void	PrintLoadingMessage(void)
{
	GXBLITLIST	*b;
	
	//	Clear out list window then print message
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->From.Width=List.Width;
	b->From.Height=List.Height;
	b->XTo=List.X;
	b->YTo=List.Y;
	b->ToImage=ScreenImage;
	b->Color=WhiteIndex;

	TXPrintString(FileStrings[4], List.X, List.Y, ScreenImage, BlackFont);
	GXDoBlit();				
	GXSystemUpdate();	//could possibly mess up input.
}

//**********************************************************************
//	RedrawItem()
//**********************************************************************
//	Description: Redraw and item in a window
//	Input: window, item
//	Output: none
//**********************************************************************
void	RedrawItem(SCROLLWINDOW *window, ITEMDATA *item)
{
	int	y0,y1,y,n;

	y=item->Oblong.Y;
	y0=window->Y;
	y1=window->Y+window->Height;
	for(n=item->Oblong.Height; n; n--){
		if( (y>=y0)&&(y<y1) ){
			DrawLine(window, y);
		}
		y++;
	}
}

//**********************************************************************
//	UpdateSlider()
//**********************************************************************
//	Description: Move slider to new position on screen
//	Input:  slider data, new y value wanted, screen image 
//	Output: none
//**********************************************************************
void	UpdateSlider(SLIDER *s, long y, GXIMAGE *screen)
{
	GXBLITLIST	*b;
	long		height;

	if(y<s->MinY){
		y=s->MinY;
	}else if(y>s->MaxY){
		y=s->MaxY;
	}
	height=y-s->Y;
	if(height>0){
		b=GXNextBlit();
		b->Type=BLIT_COLORFILL;
		b->From.Width=DownKnob.Width;
		b->From.Height=height;
		b->XTo=s->X;
		b->YTo=s->Y;
		b->ToImage=screen;
		b->Color=s->BackColor;
	}else if(height<0){
		b=GXNextBlit();
		b->Type=BLIT_COLORFILL;
		b->From.Width=DownKnob.Width;
		b->From.Height=-height;
		b->XTo=s->X;
		b->YTo=y+DownKnob.Height;
		b->ToImage=screen;
		b->Color=s->BackColor;
	}
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=DownKnob;
	b->XTo=s->X;
	b->YTo=y;
	b->FromImage=GadgetsImage;
	b->ToImage=screen;
	s->Y=y;
}

//**********************************************************************
//	IngameUpdate()
//**********************************************************************
//	Description: routine called to update menus etc.. while game is
//				running. called from end of ScreenRefresh()
//	Input:  none
//	Output: none
//**********************************************************************
void	IngameUpdate(void)
{
	GXMOUSEINFO	MouseInfo;
	int			option;
	char		LoadGame[256];
	GXPICTURE	*pic;

	GXSystemUpdate();
	#if	DEMO
		TimeOutCheck();
	#endif
	GXReadMouse(&MouseInfo);
	if( MouseInfo.RightButton || GXKeyDown(KEY_F2) ){
		switch( option=DoMenu(MenuStrings[0], &MenuStrings[1], MAINBUTTON_NUMBER) ){
		case	MAINBUTTON_GRAB:
			SaveScreen();
			if( FileRequestor(LoadGame, TitleStrings[2]) ){
				pic=GXGetPicture(ScreenSaveImage, &GameBoyDisplay);
				SavePicture(LoadGame, pic, PICTURE_LBM);
				KillPicture(pic);							//assume this is alright for killing
			}
			RestoreScreen();
			break;
		case	MAINBUTTON_LOAD:
			SaveScreen();
			if( FileRequestor(LoadGame, TitleStrings[0]) ){
				CPURunning=0;
				strcpy(GameToLoad, LoadGame);
				GameOption=GAMEOPTION_LOAD;
			}
			RestoreScreen();
			break;
		case	MAINBUTTON_QUIT:
			CPURunning=0;
			GameOption=GAMEOPTION_QUIT;
			break;
		case 	MAINBUTTON_PALETTE:
			PalettePrefs();	
			break;
		case	MAINBUTTON_KEYS:
			KeyOptions();
			break;
		case	MAINBUTTON_SAVE:
			SaveScreen();
			if(FileRequestor(LoadGame,TitleStrings[1])){
				SaveSnapshot(LoadGame);
			}
			RestoreScreen();
			break;
		case	MAINBUTTON_CANCEL:
		case	-1:
			break;
		default:
			GXQuitCode("Unknown menu option\n");
		}
	}
}

void	SaveScreen(void)
{
	GXIMAGE		*ScreenImage;
	GXBLITLIST	*b;
	
	ScreenImage=GXStartDrawing(0);
	ScreenSaveImage=GXMakeImage(ScreenWidth, ScreenHeight);
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=ScreenImage;
	b->ToImage=ScreenSaveImage;
	b->From.Width=ScreenWidth;
	b->From.Height=ScreenHeight;
	b->From.X=0;
	b->From.Y=0;
	b->XTo=0;
	b->YTo=0;
	GXDoBlit();
}
void	RestoreScreen(void)
{
	GXIMAGE	*ScreenImage;
	GXBLITLIST	*b;
	
	ScreenImage=GXStartDrawing(0);
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=ScreenSaveImage;
	b->ToImage=ScreenImage;
	b->From.Width=ScreenWidth;
	b->From.Height=ScreenHeight;
	b->From.X=0;
	b->From.Y=0;
	b->XTo=0;
	b->YTo=0;
	GXDoBlit();
	GXKillImage(ScreenSaveImage);
}
//******************************************************
//	DrawBackground()
//******************************************************
//	Tile background on given are on given image
//	input: image to tile on to, area to be tiled
//	output: none
//******************************************************
void	DrawBackground(GXIMAGE *image, GXOBLONG *area)
{
	GXBLITLIST	*b;
	long		width,height,nx,ny,xplot,yplot,ix,iy,xx;
	long		ImageWidth,ImageHeight,x0,y0,x1,y1, xend, yend;
	long		xfrom, yfrom, xto, yto;
	
	ImageWidth=GXImageWidth(image);
	ImageHeight=GXImageHeight(image);
	if(!area){
		x0=0;
		y0=0;
		x1=ImageWidth;
		y1=ImageHeight;
	}else{
		x0=area->X;
		y0=area->Y;
		x1=x0+area->Width;
		y1=y0+area->Height;
	}

	//Draw tiles
	nx=(ImageWidth/BackTile.Width);
	if( (ImageWidth%BackTile.Width) ){
		nx++;
	}
	ny=(ImageHeight/BackTile.Height);
	if( (ImageHeight%BackTile.Height) ){
		ny++;
	}
	yplot=0;
	for(iy=ny; iy; iy--,yplot+=BackTile.Height){
		if(yplot>=y1)break;
		yend=yplot+BackTile.Height;
		if(yend<=y0)continue;
		if(yplot<y0){
			if(yend>y1){
				height=area->Height;
			}else{
				height=yend-y0;
			}
			yto=y0;
			yfrom=y0-yplot+BackTile.Y;			
		}else if(yend>y1){
			yto=yplot;
			height=y1-yplot;
			yfrom=BackTile.Y;			
		}else{
			yto=yplot;
			height=BackTile.Height;
			yfrom=BackTile.Y;
		}
		xplot=0;
		for(ix=nx; ix; ix--,xplot+=BackTile.Width){
			if(xplot>=x1)break;
			xend=xplot+BackTile.Width;
			if(xend<=x0)continue;
			if(xplot<x0){
				if(xend>x1){
					width=area->Width;
				}else{
					width=xend-x0;
				}
				xto=x0;
				xfrom=x0-xplot+BackTile.X;			
			}else if(xend>x1){
				xto=xplot;
				width=x1-xplot;
				xfrom=BackTile.X;			
			}else{
				xto=xplot;
				width=BackTile.Width;
				xfrom=BackTile.X;
			}
			b=GXNextBlit();	
			b->Type=BLIT_IMAGE;
			b->FromImage=GadgetsImage;
			b->ToImage=image;	
			b->XTo=xto;
			b->YTo=yto;
			b->From.X=xfrom;	
			b->From.Y=yfrom;
			b->From.Width=width;
			b->From.Height=height;		
		}
	}

	// Draw corners
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=GadgetsImage;
	b->ToImage=image;
	b->XTo=b->YTo=0;
	b->From=CornerTL;
	
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=GadgetsImage;
	b->ToImage=image;
	b->XTo=ImageWidth-CornerTR.Width;
	b->YTo=0;
	b->From=CornerTR;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=GadgetsImage;
	b->ToImage=image;
	b->XTo=ImageWidth-CornerBR.Width;
	b->YTo=ImageHeight-CornerBR.Height;
	b->From=CornerBR;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->FromImage=GadgetsImage;
	b->ToImage=image;
	b->XTo=0;
	b->YTo=ImageHeight-CornerBL.Height;
	b->From=CornerBL;
}

void	ErrorMessage(char *message)
{
	GXIMAGE		*ScreenImage;
	long		x,y;
	GXMOUSEINFO	MouseInfo;
	int			LeftReleased=0,MiddleReleased=0,RightReleased=0;
	
	SaveScreen();
	ScreenImage=GXStartDrawing(0);

	DrawBackground(ScreenImage, 0);
	x=(ScreenWidth-TXStringWidth(message, RedFont))/2;
	y=(ScreenHeight-TXFONTHeight(RedFont))/2;
	TXPrintString(message, x, y, ScreenImage, RedFont);
	GXDoBlit();
	GXPrintf(message);
	do{
		GXSystemUpdate();
		GXReadMouse(&MouseInfo);

		//	When any mouse button is pressed after being released,
		//	exit.
		if(!MouseInfo.LeftButton)LeftReleased=1;
		if(!MouseInfo.MiddleButton)MiddleReleased=1;
		if(!MouseInfo.RightButton)RightReleased=1;

		if(MouseInfo.LeftButton&&LeftReleased)break;
		if(MouseInfo.MiddleButton&&MiddleReleased)break;
		if(MouseInfo.RightButton&&RightReleased)break;
	}while(1);
	RestoreScreen();
}



	RGBSLIDER		slider[3];

void	PalettePrefs(void)
{
	GXIMAGE			*ScreenImage;
	GXBLITLIST		*b;
	GXMOUSEINFO		MouseInfo;
	int				j, ix, iy, x, y, is;
	int				SelectedColor=0;
	GXOBLONG		*buttons=0;
	RGBSLIDER		*pslider;
	int				StartMouseX, StartSliderX;
	GXPALETTE		color,*OldPalette=0,*GameBoyPalette2=0;
	int				JustPressed;
	int				number,changed=0;

	//	Save screen & palette
	SaveScreen();
	OldPalette=(GXPALETTE *)GXGetMem(16*sizeof(GXPALETTE), MEMF_ZERO|MEMF_ABORT);
	GameBoyPalette2=(GXPALETTE *)GXGetMem(16*sizeof(GXPALETTE), MEMF_ZERO|MEMF_ABORT);
	memcpy(GameBoyPalette2, GameBoyPalette, 16*sizeof(GXPALETTE));
	GXGetPaletteRange(OldPalette, GAMEBOY_BASE, 16);
	GXSetPaletteRange(GameBoyPalette2, GAMEBOY_BASE+16, 16);
	ScreenImage=GXStartDrawing(0);
	
	DrawBackground(ScreenImage, 0);

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=TitleBox;
	b->XTo=b->YTo=0;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=GameBoyDisplay;
	b->XTo=PALETTE_PICTURE_X;
	b->YTo=PALETTE_PICTURE_Y;
	b->FromImage=ScreenSaveImage;
	b->ToImage=ScreenImage;

	//	Draw slider background
	for(j=0; j<3; j++){
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->From=PaletteSlider;
		b->XTo=RGB_SLIDER_GFX_X;
		b->YTo=RGB_SLIDER_GFX_Y+RGB_SLIDER_GFX_DY*j;
		b->FromImage=GadgetsImage;
		b->ToImage=ScreenImage;
	}

	//	Draw palette buttons background
	for(j=0; j<4; j++){
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->From=PaletteLine;
		b->XTo=RGB_PALETTE_LINE_GFX_X;
		b->YTo=RGB_PALETTE_LINE_GFX_Y+j*RGB_PALETTE_LINE_GFX_DY;
		b->FromImage=GadgetsImage;
		b->ToImage=ScreenImage;
	}

	for(j=0; j<16; j++){
		DrawPalette(j, ScreenImage, j==SelectedColor);
	}
	
	//	Make up buttons
	buttons=(GXOBLONG *)GXGetMem(21*sizeof(GXOBLONG), MEMF_ZERO|MEMF_ABORT);
	for(j=0; j<16; j++){
		ix=j%4;
		iy=j/4;
		buttons[j].X=COLOR0_X + ix*COLORSEPERATION_X;
		buttons[j].Y=COLOR0_Y + iy*COLORSEPERATION_Y;
		buttons[j].Width=COLOR_WIDTH;
		buttons[j].Height=COLOR_HEIGHT;
	}
	buttons[16]=ButtonUp;
	buttons[16].X=PALETTE_OKAY_X;
	buttons[16].Y=PALETTE_OKAY_Y;

	buttons[17]=ButtonUp;
	buttons[17].X=PALETTE_CANCEL_X;
	buttons[17].Y=PALETTE_CANCEL_Y;
	
	for(j=0; j<3; j++){
		buttons[j+18]=DownKnob;
		buttons[j+18].X=RGB_SLIDER_MINX;
		buttons[j+18].Y=RGB_SLIDER_Y+RGB_SLIDER_DY*j;
	}

	//	Print palette section names
	x=PALETTE_SECTION_X;
	y=PALETTE_SECTION_Y;
	for(j=0; j<4; j++){
		TXPrintString(	PaletteStrings[j+5], 
						(x-TXStringWidth(PaletteStrings[j+5], BlackFont)/2),
						(y-TXFONTHeight(BlackFont)/2),
						ScreenImage, BlackFont);
		y+=PALETTE_SECTION_DY;
	}
	
	//	Print title
	x=TitleBox.Width/2 + TitleBox.X - TXStringWidth(TitleStrings[3], BlackFont)/2;
	y=TitleBox.Height/2 + TitleBox.Y - TXFONTHeight(BlackFont)/2;
	TXPrintString(TitleStrings[3], x, y, ScreenImage, BlackFont);
		
	//	Draw okay & cancel buttons
	DrawButton(ScreenImage, PaletteStrings[3], PALETTE_OKAY_X, PALETTE_OKAY_Y);
	DrawButton(ScreenImage, PaletteStrings[4], PALETTE_CANCEL_X, PALETTE_CANCEL_Y);
	
	//	initialize sliders
	memset(slider, 0, sizeof(slider));

	color=GameBoyPalette2[SelectedColor];
	x=((int)color.Red)*SLIDER_RANGE/255;
	DrawSlider(0, x, ScreenImage); 
	buttons[18].X=slider[0].X+RGB_SLIDER_MINX;
	buttons[18].Y=RGB_SLIDER_Y;

	x=((int)color.Green)*SLIDER_RANGE/255;
	DrawSlider(1, x, ScreenImage); 
	buttons[19].X=slider[1].X+RGB_SLIDER_MINX;
	buttons[19].Y=RGB_SLIDER_Y+RGB_SLIDER_DY;

	x=((int)color.Red)*SLIDER_RANGE/255;
	DrawSlider(2, x, ScreenImage); 
	buttons[20].X=slider[2].X+RGB_SLIDER_MINX;
	buttons[20].Y=RGB_SLIDER_Y+2*RGB_SLIDER_DY;

	GXDoBlit();
	ResetButtons();
	is=-1;
	do{
		GXSystemUpdate();
		GXReadMouse(&MouseInfo);
		JustPressed=MouseClick();
		if( (number=UpdateButtons(buttons, 21)) != -1){
			if(JustPressed){
				if( (number>=0) &&
					(number<16) ){
					DrawPalette(SelectedColor, ScreenImage, 0);
					SelectedColor=number;
					DrawPalette(SelectedColor, ScreenImage, 1);
					color=GameBoyPalette2[SelectedColor];

					x=((long)color.Red)*SLIDER_RANGE/255;
					MoveSlider(0, x, ScreenImage); 
					buttons[18].X=x+RGB_SLIDER_MINX;
					buttons[18].Y=RGB_SLIDER_Y;

					x=((long)color.Green)*SLIDER_RANGE/255;
					MoveSlider(1, x, ScreenImage); 
					buttons[19].X=x+RGB_SLIDER_MINX;
					buttons[19].Y=RGB_SLIDER_Y+RGB_SLIDER_DY;

					x=((long)color.Blue)*SLIDER_RANGE/255;
					MoveSlider(2, x, ScreenImage); 
					buttons[20].X=x+RGB_SLIDER_MINX;
					buttons[20].Y=RGB_SLIDER_Y+2*RGB_SLIDER_DY;
				}else if(number==16){
					//	OKAY PRESSED.
					//	Save palette & quit
					changed=1;
					break;
				}else if(number==17){
					//	CANCEL PRESSED
					//	Restore old palette & quit
					break;
				}else if( (number>17) && (number<21) ){
					is=number-18;
					StartMouseX=MouseInfo.X;
					StartSliderX=slider[is].X;
				}
			}
		}
		//	Update sliders
		if( is!=-1){
			if(!MouseInfo.LeftButton){
				is=-1;
			}else{
				x=MouseInfo.X-StartMouseX+StartSliderX;
				if(x>SLIDER_RANGE)x=SLIDER_RANGE;
				if(x<0)x=0;
				if(x!=slider[is].X){
					MoveSlider(is, x, ScreenImage);
					buttons[18+is].X=RGB_SLIDER_MINX+x;
					buttons[18+is].Y=RGB_SLIDER_Y+RGB_SLIDER_DY*is;

					color.Red=slider[0].X*255/SLIDER_RANGE;
					color.Green=slider[1].X*255/SLIDER_RANGE;
					color.Blue=slider[2].X*255/SLIDER_RANGE;
					GXSetPaletteRange(&color, SelectedColor+16+GAMEBOY_BASE, 1);
					for(j=0; j<16; j++){
						if(SourceColor(j)==SelectedColor){
							GXSetPaletteRange(&color, j+GAMEBOY_BASE, 1);
						}
					}
					GameBoyPalette2[SelectedColor]=color;
				}
			}
		}
		GXDoBlit();
	}while(1);

	if(changed){
		//	Save new palette to prefs
		memcpy(GameBoyPalette, GameBoyPalette2, 16*sizeof(GXPALETTE));
		SavePrefs();
	}else{
		//	Restore old palette
		GXSetPaletteRange(OldPalette, GAMEBOY_BASE, 16);
	}
	//	Free stuff used
	KILLMEM(buttons);
	KILLMEM(OldPalette);
	KILLMEM(GameBoyPalette2);
	RestoreScreen();
}

void	MoveSlider(int is, int x, GXIMAGE *ToImage)
{
	GXBLITLIST	*b;
	RGBSLIDER	*s;
	
	s=&slider[is];

	if(x>SLIDER_RANGE)x=SLIDER_RANGE;
	if(x<0)x=0;
	if(x!=s->X){
		//	Draw over last place that slider was at
		//	Could go wrong if slider range is too big
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->From.X=PaletteSlider.X + s->X + RGB_SLIDER_MINX - RGB_SLIDER_GFX_X;
		b->From.Y=PaletteSlider.Y + RGB_SLIDER_Y - RGB_SLIDER_GFX_Y;
		b->From.Width=DownKnob.Width;
		b->From.Height=DownKnob.Height;
		b->XTo=s->X+RGB_SLIDER_MINX;
		b->YTo=RGB_SLIDER_Y+is*RGB_SLIDER_DY;
		b->FromImage=GadgetsImage;
		b->ToImage=ToImage;

		//	Draw new slider
		b=GXNextBlit();
		b->Type=BLIT_IMAGE;
		b->From=DownKnob;
		b->XTo=x+RGB_SLIDER_MINX;
		b->YTo=RGB_SLIDER_Y+is*RGB_SLIDER_DY;
		b->FromImage=GadgetsImage;
		b->ToImage=ToImage;
		s->X=x;
	}
}

void	DrawSlider(int is, int x, GXIMAGE *ToImage)
{
	GXBLITLIST	*b;
	RGBSLIDER	*s;
	long		xplot,yplot;
	
	s=&slider[is];

	if(x>SLIDER_RANGE)x=SLIDER_RANGE;
	if(x<0)x=0;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=PaletteSlider;
	b->XTo=RGB_SLIDER_GFX_X;
	b->YTo=RGB_SLIDER_GFX_Y+RGB_SLIDER_GFX_DY*is;
	b->FromImage=GadgetsImage;
	b->ToImage=ToImage;

	xplot=RGB_X;
	yplot=RGB_Y+RGB_DY*is;
	TXPrintString(	PaletteStrings[is], 
					(xplot-TXStringWidth(PaletteStrings[is], BlackFont)/2),
					(yplot-TXFONTHeight(BlackFont)/2),
					ToImage, BlackFont);

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=DownKnob;
	b->XTo=x+RGB_SLIDER_MINX;
	b->YTo=RGB_SLIDER_Y+is*RGB_SLIDER_DY;
	b->FromImage=GadgetsImage;
	b->ToImage=ToImage;
	s->X=x;
}

void	DrawPalette(int	j, GXIMAGE *image, int highlight)
{
	int			ix,iy,x,y;
	GXBLITLIST	*b;

	ix=j%4;
	iy=j/4;
	x=COLOR0_X + ix*COLORSEPERATION_X;
	y=COLOR0_Y + iy*COLORSEPERATION_Y;
	
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->XTo=x-3;
	b->YTo=y-3;
	if(highlight){
		b->From=PaletteHighlight;
	}else{
		b->From=PaletteNormal;
	}
	b->FromImage=GadgetsImage;
	b->ToImage=image;
	
	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->Color=j+16+GAMEBOY_BASE;
	b->XTo=x;
	b->YTo=y;
	b->From.Width=COLOR_WIDTH;
	b->From.Height=COLOR_HEIGHT;
	b->ToImage=image;
}

	long		NewKeys[8];


void	KeyOptions(void)
{
	GXBLITLIST	*b;
	GXIMAGE		*ScreenImage;
	GXMOUSEINFO	MouseInfo;
	int			x,y,j,moving;
	GXOBLONG	Buttons[10];
	int			selection,editing,jj;
	long		xx,tt,delay;
	long		key,frame=0;
	int			number;
	int			JustPressed;
	GXOBLONG	area;

	WaitNoKeys();
	SaveScreen();
	ScreenImage=GXStartDrawing(0);
	for(j=0; j<8; j++)
		NewKeys[j]=KeyPrefs[j];

	//	Draw backdrop
	DrawBackground(ScreenImage, 0);
	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=TitleBox;
	b->XTo=b->YTo=0;
	b->FromImage=GadgetsImage;
	b->ToImage=ScreenImage;
	
	//	Print title
	x=TitleBox.Width/2 + TitleBox.X - TXStringWidth(KeyStrings[8], BlackFont)/2;
	y=TitleBox.Height/2 + TitleBox.Y - TXFONTHeight(BlackFont)/2;
	TXPrintString(KeyStrings[8], x, y, ScreenImage, BlackFont);

	//	Set up okay & cancel buttons
	Buttons[8]=Buttons[9]=ButtonUp;
	Buttons[8].X=(ScreenWidth-ButtonUp.Width*2)/4;
	Buttons[9].X=ScreenWidth-Buttons[8].X-ButtonUp.Width;
	Buttons[8].Y=Buttons[9].Y=ScreenHeight-20;
	DrawButton(ScreenImage, "Okay", Buttons[8].X, ScreenHeight-20);
	DrawButton(ScreenImage, "Cancel", Buttons[9].X, ScreenHeight-20);

	for(j=0; j<NKeyPrefs; j++){
		y=KEYOPTIONS_Y+KEYOPTIONS_DY*j;	
		TXPrintString(KeyStrings[j], 
			(ScreenWidth/2)-TXStringWidth(KeyStrings[j], BlackFont)-10,
			y, ScreenImage, BlackFont);
		if(j){
			TXPrintString(KeyName(NewKeys[j]), ScreenWidth/2+10, y, ScreenImage, BlackFont);
		}else{
			TXPrintString(KeyName(NewKeys[j]), ScreenWidth/2+10, y, ScreenImage, RedFont);
		}
		Buttons[j].X=10;
		Buttons[j].Width=ScreenWidth-20;
		Buttons[j].Y=y;
		Buttons[j].Height=TXFONTHeight(BlackFont);
	}	

	selection=0;
	editing=0;
	delay=GXTicksPerSecond()/60;
	if(!delay)delay=1;
	do{
		xx=GXTimer();
		do{
			tt=GXTimer()-xx;
			if(tt<0)break;
		}while(tt< delay );
		frame++;

		GXSystemUpdate();
		GXReadMouse(&MouseInfo);
		GXDoBlit();
		JustPressed=MouseClick();
		key=GXNextKey();
		while(GXNextKey())
			;
		if( (number=UpdateButtons(Buttons, 10)) != -1 ){
			if(JustPressed){
				if(number==8){
					for(j=0; j<8; j++)
						KeyPrefs[j]=NewKeys[j];
					SavePrefs();
					break;
				}else if(number==9){
					break;
				}else{
					ChangeKeySelection(selection, number, ScreenImage);
					selection=number;
					editing=0;
				}
			}
		}
		if(!editing){
			if( GXKeyDown(KeyPrefs[3]) ){
				if( (!moving) || (frame==20) ){
					moving=1;
					jj=(selection+1)&0x07;
					ChangeKeySelection(selection, jj, ScreenImage);
					selection=jj;
					frame=0;
				}
			}else if( GXKeyDown(KeyPrefs[2]) ){
				if( (!moving) || (frame==20) ){
					moving=1;
					jj=(selection-1)&0x07;
					ChangeKeySelection(selection, jj, ScreenImage);
					selection=jj;
					frame=0;
				}
			}else{
				moving=0;
			}
		}else{
			y=KEYOPTIONS_Y+KEYOPTIONS_DY*selection;	
			xx=GXTimer();
			xx=xx%GXTicksPerSecond();
			if( xx < (GXTicksPerSecond()/2) ){
				TXPrintString(KeyName(NewKeys[selection]), 
					ScreenWidth/2+10, y, ScreenImage, RedFont);
			}else{
				area.X=ScreenWidth/2+10;
				area.Y=y;
				area.Width=TXStringWidth(KeyName(NewKeys[selection]), RedFont);
				area.Height=TXFONTHeight(RedFont);
				DrawBackground(ScreenImage, &area);
			}
		}
		if( editing && key && (key!=-1) ){
			area.X=ScreenWidth/2+10;
			area.Y=y;
			area.Width=TXStringWidth(KeyName(NewKeys[selection]), RedFont);
			area.Height=TXFONTHeight(RedFont);
			DrawBackground(ScreenImage, &area);

			NewKeys[selection]=key;
			editing=0;

			TXPrintString(KeyName(NewKeys[selection]), 
				ScreenWidth/2+10, y, ScreenImage, RedFont);
		}else if( (key==KEY_RETURN) && (selection!=-1) ){
			editing=1;
		}
	}while(1);

	RestoreScreen();
}

void	DrawButton(GXIMAGE *dest, char *text, long x, long y)
{
	GXBLITLIST		*b;

	b=GXNextBlit();
	b->Type=BLIT_IMAGE;
	b->From=ButtonUp;
	b->XTo=x;
	b->YTo=y;
	b->FromImage=GadgetsImage;
	b->ToImage=dest;
	TXPrintString(text, 
		b->XTo+(b->From.Width-TXStringWidth(text,BlackFont))/2,
		b->YTo+(b->From.Height-TXFONTHeight(BlackFont))/2,
		dest,
		BlackFont);
	
}

char	*KeyName(long RawCode)
{
	KEYNAMES	*names;
	int			j;


	names=KeyNames;
	for(j=0; j<NKeyNames; j++){
		if(names->RawCode==RawCode){
			return names->Name;
		}
		names++;
	}
	return KeyStrings[9];	//string for unknown key
}

void	ChangeKeySelection(int selection, int NewSelection, GXIMAGE *ScreenImage)
{
	int	y;

	if(selection!=-1){
		y=KEYOPTIONS_Y+KEYOPTIONS_DY*selection;	
		TXPrintString(KeyName(NewKeys[selection]), 
			ScreenWidth/2+10, y, ScreenImage, BlackFont);
	}
	y=KEYOPTIONS_Y+KEYOPTIONS_DY*NewSelection;	
	TXPrintString(KeyName(NewKeys[NewSelection]), 
		ScreenWidth/2+10, y, ScreenImage, RedFont);

}

void	KillStringGadget(STRINGGADGET *sg)
{
	if(sg){
		if(sg->String)GXFreeMem(sg->String);
		GXFreeMem(sg);
	}
}

	int	CursorIsOn=0;

STRINGGADGET	*MakeStringGadget(char *StartString, GXOBLONG *Area, GXIMAGE *ToImage)
{
	STRINGGADGET	*sg;

	CursorIsOn=0;	
	sg=(STRINGGADGET *)GXGetMem(sizeof(STRINGGADGET), MEMF_ZERO|MEMF_ABORT);
	sg->String=(char *)GXGetMem(256*sizeof(char), MEMF_ZERO|MEMF_ABORT);	
	sg->Area=*Area;
	ChangeStringGadget(StartString, sg, ToImage);
	return sg;
}

void	ChangeStringGadget(char *str, STRINGGADGET *sg, GXIMAGE *ToImage)
{
	sg->CursorPosition=strlen(str);
	strcpy(sg->String, str);
	PrintStringGadget(sg, ToImage, 0);
}

char	*GetStringGadget(STRINGGADGET *sg)
{
	return sg->String;
}

void	PrintStringGadget(STRINGGADGET *sg, GXIMAGE *ToImage, int cursoron)
{
	char		*from,*to;
	int			j,x,y,width,width1,width2;
	GXBLITLIST	*b;
	static	char	string1[64],string2[64];

	CursorIsOn=cursoron;
	from=sg->String;
	to=string1;
	for(j=sg->CursorPosition; j; j--){
		*to++=*from++;
	}
	*to++=0;
	strcpy(string2, from);

	b=GXNextBlit();
	b->Type=BLIT_COLORFILL;
	b->Color=WhiteIndex;
	b->From=sg->Area;
	b->XTo=sg->Area.X;
	b->YTo=sg->Area.Y;
	b->ToImage=ToImage;
	width1=TXStringWidth(string1, BlackFont);
	width2=TXStringWidth(string2, BlackFont);
	width=width1+width2+BlackFont->Width;
	
	x=(sg->Area.Width/2) - (width/2) + sg->Area.X;
	y=(sg->Area.Height/2) - TXFONTHeight(BlackFont)/2 + sg->Area.Y;
	TXPrintString(string1, x, y, ToImage, BlackFont);
	x+=width1;
	if(cursoron){
		b=GXNextBlit();
		b->Type=BLIT_COLORFILL;
		b->Color=OrangeIndex;
		b->From.Width=BlackFont->Width;
		b->From.Height=BlackFont->Height;
		b->XTo=x;
		b->YTo=y;
		b->ToImage=ToImage;
	}
	x+=BlackFont->Width;
	TXPrintString(string2, x, y, ToImage, BlackFont);
}

void	UpdateStringGadget(STRINGGADGET *sg, int AsciiKey, GXIMAGE *ToImage, 
							int active)
{
	int		StringLength,nloop;
	char	*from,*to;
	int		DrawCursor;
	
	
	DrawCursor=(GXTimer()%GXTicksPerSecond());
	DrawCursor=DrawCursor<(GXTicksPerSecond()/2);	
	if(!active){
		if(!CursorIsOn){
			PrintStringGadget(sg, ToImage, 1);	
		}
		return;
	}
	StringLength=strlen(sg->String);
	if(AsciiKey==0x1c){
		//cursor right
		sg->CursorPosition++;
		if( sg->CursorPosition > StringLength ){
			sg->CursorPosition=StringLength;
		}
	}else if(AsciiKey==0x1d){
		//cursor left
		if(sg->CursorPosition){
			sg->CursorPosition--;
		}
	}else if(AsciiKey==0x7f){
		//delete
		if(sg->CursorPosition<StringLength){
			to=sg->String + sg->CursorPosition;
			from=to+1;
			while(*from){
				*to++=*from++;
			}
			*to=0;
		}
	}else if(AsciiKey==0x01){
		//BackSpace
		if(sg->CursorPosition){
			from=sg->String + sg->CursorPosition;
			to=from-1;
			while(*from){
				*to++=*from++;
			}
			*to=0;
			sg->CursorPosition--;
		}
	}else if( 	((AsciiKey>='a')&&(AsciiKey<='z'))||
			 	((AsciiKey>='0')&&(AsciiKey<='9'))||
				(AsciiKey=='.')||
				(AsciiKey==' ')	 ){
		if(StringLength>=16){
			return;
		}
		from=sg->String+StringLength;
		to=from+1;
		nloop=StringLength-sg->CursorPosition+1;
		while(nloop){
			*to--=*from--;
			nloop--;
		}
		*to=AsciiKey;
		sg->CursorPosition++;
	}else{
		if(DrawCursor!=CursorIsOn){
			PrintStringGadget(sg, ToImage, DrawCursor);
			return;		
		}	
	}
	PrintStringGadget(sg, ToImage, DrawCursor);	
}

int	RawToAscii(int RawKey)
{
	struct{
		UBYTE	AsciiKey;
		UBYTE	RawKey;
	}KeyInfo[]={
					{'a',KEY_A},
					{'b',KEY_B},
					{'c',KEY_C},
					{'d',KEY_D},
					{'e',KEY_E},
					{'f',KEY_F},
					{'g',KEY_G},
					{'h',KEY_H},
					{'i',KEY_I},
					{'j',KEY_J},
					{'k',KEY_K},
					{'l',KEY_L},
					{'m',KEY_M},
					{'n',KEY_N},
					{'o',KEY_O},
					{'p',KEY_P},
					{'q',KEY_Q},
					{'r',KEY_R},
					{'s',KEY_S},
					{'t',KEY_T},
					{'u',KEY_U},
					{'v',KEY_V},
					{'w',KEY_W},
					{'x',KEY_X},
					{'y',KEY_Y},
					{'z',KEY_Z},
					{'0',KEY_0},
					{'1',KEY_1},
					{'2',KEY_2},
					{'3',KEY_3},
					{'4',KEY_4},
					{'5',KEY_5},
					{'6',KEY_6},
					{'7',KEY_7},
					{'8',KEY_8},
					{'9',KEY_9},
					{'0',KEY_NUMPAD0},
					{'1',KEY_NUMPAD1},
					{'2',KEY_NUMPAD2},
					{'3',KEY_NUMPAD3},
					{'4',KEY_NUMPAD4},
					{'5',KEY_NUMPAD5},
					{'6',KEY_NUMPAD6},
					{'7',KEY_NUMPAD7},
					{'8',KEY_NUMPAD8},
					{'9',KEY_NUMPAD9},

					{'.',KEY_FULLSTOP},
					{' ',KEY_SPACE},
					{'	',KEY_TAB},
					{',',KEY_COMMA},
					{0x1c,KEY_CURSORRIGHT},
					{0x1d,KEY_CURSORLEFT},
					{0x1e,KEY_CURSORUP},
					{0x1f,KEY_CURSORDOWN},
					{0x7f,KEY_DELETE},
					{0x01,KEY_BACKSPACE},
					};
	static UBYTE	*ConversionTable=0;
	int				j;

	if(!ConversionTable){
		ConversionTable=(UBYTE *)GXGetMem(256*sizeof(UBYTE), MEMF_ZERO|MEMF_ABORT);
		for(j=0; j<ELEMENTS(KeyInfo); j++){
			ConversionTable[ KeyInfo[j].RawKey]=KeyInfo[j].AsciiKey;
		}
	}
	return ConversionTable[RawKey];
}
