/*****************************************************************************
*   General routines to	handle the graphic calls.			     *
*									     *
* Written by:  Gershon Elber			       Ver 0.9, Apr. 1989    *
*									     *
* Support: Hercules, CGA, EGA/VGA graphic cards.			     *
*									     *
* 0.3  modif. -	menus now can read alpha and compere to	first chars in menus.*
* 0.32 modif. -	if erase of GGDrawMenu TRUE, simply clear area first.	     *
*		fix DrawPoint title drawing error...			     *
*		fix set up color default (not to be black).		     *
* 0.4  modif. - MS mouse support added.					     *
* 0.5  modif. - CGA support was added.					     *
* 0.6  modif. - Prefix all functions/external vars with GG.		     *
*		Add window frame to view & status windows.		     *
* 0.7  modif. - Improving the shape of the graphical cursor.		     *
*		Disabling stdout during graphic mode to unshow ^c...	     *
* 0.8  modif. - Adding support for the EGA/VGA class of machines.	     *
* 0.9  modif. - Full line editing replaced old simplified version.	     *
* 1.0  modif. - Fix EGA/VGA type of devices aspect ratio.		     *
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <alloc.h>
#include <dir.h>
#include <dos.h>
#include <mem.h>
#include <math.h>
#include <ctype.h>
#include <graphics.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <conio.h>

/* See also the conditional compilation flags in GraphGnG.h! */

#include "Program.h"
#include "GraphGnG.h"
#include "GraphGnL.h"
#include "MouseDrv.h"

extern int MouseExists;			       /* Responsibility of program! */
extern int GraphDriver;						       /* "" */

int    GGGraphMode;				 /* The Graphics mode value. */
double GGAspectRatio;		   /* Aspect ratio of a pixel on the screen. */
int    GGScreenMaxX, GGScreenMaxY;  /* The maximum resolution of the screen. */
int    GGScreenMaxY2;					  /* ScreenMaxY / 2. */
int    GGScreenMaxColors;	       /* The maximum # of colors available. */
int    GGScreenMaxPages;	        /* The maximum # of pages available. */
int    GGScreenErrorCode;	             /* Reports any graphics errors. */
int    GGCurrentCursorX, GGCurrentCursorY;       /* Cursor current position. */
int    GGScreenGraphicMode = FALSE;   /* TRUE if the screen in graphic mode. */

static double WndwAspectRatio, GlblAspectRatio;
static struct palettetype palette; 	       /* Used to read palette info. */
static char *CursorImageBuffer;				    /* Cursor shape. */
static int LocalMoveToX, LocalMoveToY;	      /* Save last position we move. */
static char LastGetChar;	  /* Last char recieved in GGUpdateGetPoint. */
static double MouseXRatio, MouseYRatio;
static int DefaultFontSize, MenuLineWidth;	/* Depend on graphic driver. */
static int ScreenCursorColor;	    /* Graphic and text (in graphic screen). */
static int ViewPortX, ViewPortY;    /* Viewport offset from top left (0, 0). */
static int OldStdOut;	/* So we could recover it when exit from graph mode. */
static int CurrentColor = 1;

#ifndef NOWINDOWS

typedef struct WindowStruct {			    /* The window structure. */
    double MinX, MaxX, MinY, MaxY;
    int FrameColor, LineColor;
} WindowStruct;

#define	FRAME_X_WIDTH 0.03		      /* Width of the window frames. */
#define FRAME_Y_WIDTH 0.03

static WindowStruct MenuWindow =			     /* Menu window. */
{   SW_MIN_X, SW_MAX_X, SW_MIN_Y, SW_MAX_Y,
    SW_FRAME_COLOR, SW_LINE_COLOR
};

static WindowStruct ViewWindow =			     /* View window. */
{   VW_MIN_X, VW_MAX_X, VW_MIN_Y, VW_MAX_Y,
    VW_FRAME_COLOR, VW_LINE_COLOR
};

static WindowStruct *WndwViewWindow, *WndwMenuWindow;

static void WndwDrawAllWndwFrames(void);
static void WndwDrawWindowFrame(WindowStruct *Window);
static void WndwSetUpAllWndws(void);
static void WndwClearWindow(WindowStruct *Window);
static void WndwSetViewPort(double XMin, double YMin, double XMax, double YMax);

#endif	NOWIDNOWS

#ifndef NOMENUS

static struct MenuItem *ActvMenu;	/* Last drawn menu (active menu...). */
static int ActvMenuN, ActvMenuActive;		    /* Info. on active menu. */

static void GGDrawMenuFrame(int n, int color);
static void GGDrawMenuItems(int n, struct MenuItem m[], int *active);

#endif	NOMENUS

#ifndef NOHELP

static FILE *GGFindFile(char *FileName, char *Attribute);
static void HelpUpdateY(double *Yaxis);

#endif	NOHELP

static int GGUpdateGetPointKbd(int *x, int *y);
static int GGGetKey(void);

/****************************************************************************
* Routine to move to a normalized point	between	-1..1 on both axes :	    *
****************************************************************************/
void GGMyMove(double x, double y)
{
#ifndef	NOGRAPHICS
    /* Note I use ScreenMaxY2 as X>ScreenMaxY2 left for	text (menus). */
    LocalMoveToX = (int) ((x * WndwAspectRatio * GGScreenMaxY2 + GGScreenMaxY2)
							* GlblAspectRatio);
    LocalMoveToY = (int) (-y * GGScreenMaxY2 + GGScreenMaxY2);
    moveto(LocalMoveToX - ViewPortX, LocalMoveToY - ViewPortY);
#endif NOGRAPHICS
}

/****************************************************************************
* Routine to draw to a normalized point	between	-1..1 on both axes :	    *
****************************************************************************/
void GGMyDraw(double x, double y)
{
#ifndef	NOGRAPHICS
    int	NewX, NewY;

    /* Note I use ScreenMaxY2 as X>ScreenMaxY2 left for	text (menus). */
    NewX = (int) ((x * WndwAspectRatio * GGScreenMaxY2 + GGScreenMaxY2)
							* GlblAspectRatio);
    NewY = (int) (-y * GGScreenMaxY2 + GGScreenMaxY2);
    line(LocalMoveToX - ViewPortX, LocalMoveToY - ViewPortY,
					NewX - ViewPortX, NewY - ViewPortY);
    LocalMoveToX = NewX;
    LocalMoveToY = NewY;
#endif NOGRAPHICS
}

/****************************************************************************
* Routine to draw to a normelized point	between	-1..1 on both axes :	    *
****************************************************************************/
void GGMySetColor(int Color)
{
#ifndef	NOGRAPHICS
    if (Color >= GGScreenMaxColors)
	Color = Color % (GGScreenMaxColors-1) + 1;
    CurrentColor = Color;
    setcolor(Color);
#endif NOGRAPHICS
}

/****************************************************************************
* Routine to reset all the system to starting condition	:		    *
****************************************************************************/
void GGInitGraph(void)
{
#ifndef	NOGRAPHICS
    int	xasp, yasp,			   /* Used to read the aspect ratio. */
	i, j;

    if (registerbgidriver(Herc_driver) < 0) MyExit(1);
    if (registerbgidriver(CGA_driver) < 0) MyExit(1);
    if (registerbgidriver(EGAVGA_driver) < 0) MyExit(1);

    /* For some wierd reason, some machines waits much more than expected on */
    /* the first delay. Lets do it now, so people will consider it part of   */
    /* the initialization (make it a feature...)...                          */
    delay(1);

    if (GraphDriver == 0) {
	/* Use autodetect feature of graphic library to see what we have. */
	detectgraph(&GraphDriver, &GGGraphMode);
	if (GraphDriver < 0) {
	    fprintf(stderr, "Auto detect: No graphics device detected\n");
	    MyExit(1);
	}
    }

    /* Put in the following any graphic driver specific setup: */
    switch (GraphDriver) {
	case CGA:
	    GGGraphMode = CGAHI;
	    break;
	case EGA:
	    GGGraphMode = EGAHI;
	    break;
	case EGA64:
	    GGGraphMode = EGA64HI;
	    break;
	case EGAMONO:
	    GGGraphMode = EGAMONOHI;
	    break;
	case HERCMONO:
	    GGGraphMode = HERCMONOHI;
	    break;
	case VGA:
	    GGGraphMode = VGAHI;
	    break;
	default:
	    fprintf(stderr, "Requested graphic device (%d - see .CFG file) is not supported\n",
		GraphDriver);
	    MyExit(1);
	    break;
    }

    initgraph(&GraphDriver, &GGGraphMode, "");
    GGScreenErrorCode = graphresult();	   /* Read result of initialization. */
    if (GGScreenErrorCode != grOk) {	       /* Error occured during init. */
	fprintf(stderr, " Graphics System Error: %s\n",
				grapherrormsg(GGScreenErrorCode));
	MyExit(1);
    }

    getpalette(&palette);		     /* Read the palette from board. */
    GGScreenMaxColors = getmaxcolor() + 1; /* Read maximum number of colors. */
    ScreenCursorColor = (GGScreenMaxColors > 1 ? GGScreenMaxColors - 1 :
						 GGScreenMaxColors);

    GGScreenMaxX = getmaxx();			     /* Read size of screen. */
    GGScreenMaxY = getmaxy();
    GGScreenMaxY2 = GGScreenMaxY / 2;
    GGCurrentCursorX = GGScreenMaxX / 2;
    GGCurrentCursorY = GGScreenMaxY2;
    getaspectratio(&xasp, &yasp);		/* Read the hardware aspect. */
    GGAspectRatio = (double) xasp / (double) yasp; /* Get correction factor: */
    GlblAspectRatio = 1.0 / GGAspectRatio;
    WndwAspectRatio = 1.0;

    /* Put in the following any graphic driver specific setup: */
    switch (GraphDriver) {
	case CGA:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = NORM_WIDTH;
	    GlblAspectRatio = 1 / (GGAspectRatio * 1.14);    /* Make bigger. */
	    GGScreenMaxPages = 1;
	    GGScreenMaxY -= 2;				 /* Dont ask my why! */
	    GGScreenMaxY2 -= 1;
	    break;

	case EGA:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = THICK_WIDTH;
	    GGScreenMaxPages = 2;
	    GlblAspectRatio = 1.2;/* I dont like it either, but its simple...*/
	    break;

	case EGA64:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = THICK_WIDTH;
	    GGScreenMaxPages = 1;
	    GlblAspectRatio = 1.2;/* I dont like it either, but its simple...*/
	    break;

	case EGAMONO:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = THICK_WIDTH;
	    GGScreenMaxPages = 2;
	    GGScreenMaxColors = 2;	      /* Dont want blinking lines... */
	    GlblAspectRatio = 1.2;/* I dont like it either, but its simple...*/
	    break;

	case HERCMONO:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = THICK_WIDTH;
	    GGScreenMaxPages = 2;
	    break;

	case VGA:
	    DefaultFontSize = 1;			/* Fixed width font. */
	    MenuLineWidth = THICK_WIDTH;
	    GGScreenMaxPages = 2;
	    GlblAspectRatio = 0.88;/*I dont like it either, but its simple...*/
	    break;
    }

#   ifdef SINGLEPAGE
	GGScreenMaxPages = 1;
#   endif SINGLEPAGE
#   ifdef DEBUG
	GGCloseGraph();
	fprintf(stderr, "ScreenMaxX = %d, ScreenMaxY = %d, MaxColors = %d\n",
	    GGScreenMaxX, GGScreenMaxY, GGScreenMaxColors);
	fprintf(stderr, "AspectRatio = %lf\n", GGAspectRatio);
	MyExit(1);
#   endif DEBUG

    /* Prepare the cursor (Arrow) image : */
    cleardevice();
    GGMySetColor(ScreenCursorColor);
    setlinestyle(SOLID_LINE, 0,	NORM_WIDTH);
    line(0, 0, CURSOR_IMAGE_X, CURSOR_IMAGE_Y);
    j = CURSOR_IMAGE_X / 3;
    for	(i=1; i<=7; i++) line(0, 0, j, j + i);	     /* Draw the arrow head. */
    j = CURSOR_IMAGE_Y / 3;
    for	(i=1; i<=7; i++) line(0, 0, j + i, j);

    CursorImageBuffer = malloc(imagesize(0, 0, CURSOR_IMAGE_X,
							CURSOR_IMAGE_Y));
    getimage(0,	0, CURSOR_IMAGE_X, CURSOR_IMAGE_Y, CursorImageBuffer);

    for (i = GGScreenMaxPages - 1; i >= 0; i--) {
	setactivepage(i);
#	ifndef NOWINDOWS
	    WndwSetUpAllWndws();
#	else
	    GGClearAllScreen();
#	endif  NOWINDOWS
    }

    settextstyle(DEFAULT_FONT, HORIZ_DIR, DefaultFontSize);

    if (MouseExists) {
	switch (GraphDriver) {
	    case CGA:
		MSMouseXmax = 1000;			/* Set Column Range. */
		MSMouseYmax = 1000;			   /* Set Row range. */
		break;
	    case EGA:
	    case EGA64:
	    case EGAMONO:
	    case VGA:
		/* Although 1000x1000 is the regular normalized mouse screen */
		/* it is too lazy on 1:1 ratio.				     */
		MSMouseXmax = 1000;			/* Set Column Range. */
		MSMouseYmax = 1000;			   /* Set Row range. */
		break;
	    case HERCMONO:
		/* Compensate on the text mode the mouse knows about as      */
		/* hercules is not supported by the mouse driver!            */
		MSMouseXmax = 8000;			/* Set Column Range. */
		MSMouseYmax = 8000;			   /* Set Row range. */
		break;
	}
	MouseXRatio = ((double) GGScreenMaxX) / MSMouseXmax;
	MouseYRatio = ((double) GGScreenMaxY) / MSMouseYmax;
    }

    GGScreenGraphicMode = TRUE;
    i = open("nul", O_WRONLY);	  /* Redirect the stdout to nul: (no ^C...). */
    fflush(stdout);
    OldStdOut = dup(1);
    dup2(i, 1);
    close(i);
#endif NOGRAPHICS
}

/****************************************************************************
* Routine to close and shutdown	graphic	mode :				    *
****************************************************************************/
void GGCloseGraph(void)
{
#ifndef	NOGRAPHICS
    if (!GGScreenGraphicMode) return;
    closegraph();			  /* Return the system to text mode. */
    GGScreenGraphicMode = FALSE;
    dup2(OldStdOut, 1);		    /* Recover stdout to its regular status. */
    close(OldStdOut);
#endif NOGRAPHICS
}

/****************************************************************************
* Routine to update x, y coordinates according to current x, y and key	    *
* pressed. Returns TRUE only if <Enter> or alpha char is pressed.	    *
****************************************************************************/
static int GGUpdateGetPointKbd(int *x, int *y)
{
    int	BrkPressed = FALSE;

    switch (LastGetChar	= getch()) {
	case 0:
	    switch (getch()) {			      /* Extended character: */
		case 16: MyExit(0);		  /* Alt - Q, async. exit... */
		case 71:
		    *x -= 1;		      /* Arrowes - move 1 at a time. */
		    *y -= 1;
		    break;
		case 72:
		    *y -= 1;
		    break;
		case 73:
		    *x += 1;
		    *y -= 1;
		    break;
		case 75:
		    *x -= 1;
		    break;
		case 77:
		    *x += 1;
		    break;
		case 79:
		    *x -= 1;
		    *y += 1;
		    break;
		case 80:
		    *y += 1;
		    break;
		case 81:
		    *x += 1;
		    *y += 1;
		    break;
	    }
	    break;
	case 10:
	case 13:
	    BrkPressed = TRUE;
	    break;
	case '1':
	    *x -= 10;		     /* Shifted arrowes - move 10 at a time. */
	    *y += 10;
	    break;
	case '2':
	    *y += 10;
	    break;
	case '3':
	    *x += 10;
	    *y += 10;
	    break;
	case '4':
	    *x -= 10;
	    break;
	case '6':
	    *x += 10;
	    break;
	case '7':
	    *x -= 10;
	    *y -= 10;
	    break;
	case '8':
	    *y -= 10;
	    break;
	case '9':
	    *x += 10;
	    *y -= 10;
	    break;
	/* If the key pressed is an alpha - exit */
	default:
	    if isalpha(LastGetChar) BrkPressed = TRUE;
	    break;
    }

    return BrkPressed;
}

/****************************************************************************
* Routine to get one x, y selected point in normelized form (-1..1) :	    *
****************************************************************************/
void GGGetPoint(double *x, double *y)
{
#ifndef	NOGRAPHICS
    int	Xtemp, Ytemp, Buttons, Xscreen, Yscreen, Quit, GetInput;
    struct viewporttype view;

    WndwAspectRatio = 1.0;

    getviewsettings(&view);  /* During Mouse input - must enable all screen. */
    setviewport(ViewPortX = 0, ViewPortY = 0, getmaxx(), getmaxy(), FALSE);

    LastGetChar = 0x001;	      /* Make no kbd input if mouse input... */

    Xtemp = GGCurrentCursorX;
    Ytemp = GGCurrentCursorY;
    putimage(GGCurrentCursorX,	     /* Draw the cursor - starting position. */
	     GGCurrentCursorY, CursorImageBuffer, XOR_PUT);

    if (MouseExists)			/* Update mouse on current position. */
	MouseSetPosition((int) (GGCurrentCursorX / MouseXRatio),
			 (int) (GGCurrentCursorY / MouseYRatio));
    Quit = FALSE;
    do {
	GetInput = FALSE;
	do {
	    /* Wait for input from one of the devices: Mouse/Keyboard. */
	    if (MouseExists && MouseQueryBuffer()) {
		MouseGetBuffer(&Xscreen, &Yscreen, &Buttons);
		GetInput = TRUE;
		GGCurrentCursorX = (int) (Xscreen * MouseXRatio);
		GGCurrentCursorY = (int) (Yscreen * MouseYRatio);
		Quit = Buttons & 0x01;
	    }
	    if (kbhit()) {
		Quit = GGUpdateGetPointKbd(&GGCurrentCursorX,
					   &GGCurrentCursorY);
		if (MouseExists)	/* Update mouse on the new position. */
		    MouseSetPosition((int) (GGCurrentCursorX / MouseXRatio),
				     (int) (GGCurrentCursorY / MouseYRatio));
		GetInput = TRUE;
	    }
	}
	while (!GetInput);
	if (GGCurrentCursorX < 0) GGCurrentCursorX = 0;
	if (GGCurrentCursorY < 0) GGCurrentCursorY = 0;
	if (GGCurrentCursorX > GGScreenMaxX - CURSOR_IMAGE_X)
	    GGCurrentCursorX = GGScreenMaxX - CURSOR_IMAGE_X;
	if (GGCurrentCursorY > GGScreenMaxY - CURSOR_IMAGE_Y / 2)
	    GGCurrentCursorY = GGScreenMaxY - CURSOR_IMAGE_Y / 2;

	putimage(Xtemp, Ytemp, CursorImageBuffer, XOR_PUT);/* Erase old crsr!*/
	putimage(GGCurrentCursorX, GGCurrentCursorY, CursorImageBuffer,
						 XOR_PUT); /* Draw new crsr! */
	Xtemp = GGCurrentCursorX;   /* Save them so we could erase it later. */
	Ytemp = GGCurrentCursorY;
    }
    while (!Quit);

    putimage(Xtemp,		    /* Erase last old cursor before quiting. */
	     Ytemp, CursorImageBuffer, XOR_PUT);

    /* See GGMyMove for ScreenMaxY2 */
    *x = (((double) GGCurrentCursorX) / GlblAspectRatio - GGScreenMaxY2) /
								GGScreenMaxY2;
    *y = (((double) GGCurrentCursorY) - GGScreenMaxY2) / (-GGScreenMaxY2);

    setviewport(ViewPortX = view.left, ViewPortY = view.top,
		view.right, view.bottom, view.clip);

    if (MouseExists) {
	delay(250);   /* Flush out any spikes mouse buttons had generated... */
	MouseFlushBuffer();
    }

#endif NOGRAPHICS
}

/****************************************************************************
* Routine to draw a point on screen as marker +	with a title :		    *
****************************************************************************/
void GGDrawPoint(double p[], char title[], int PointColor)
{
#ifndef	NOGRAPHICS
    GGMySetColor(PointColor);
    GGMyMove(p[1] + POINT_SIZE, p[2]);
    GGMyDraw(p[1] - POINT_SIZE, p[2]);
    GGMyMove(p[1], p[2] + POINT_SIZE);
    GGMyDraw(p[1], p[2] - POINT_SIZE);

    GGPutMsgXY(title, p[1] + POINT_TITLE, p[2] + POINT_TITLE);
#endif NOGRAPHICS
}

#ifndef NOMENUS

static double MenuBaseY;

/*****************************************************************************
*   Routine to draw a menu and return active flag.			     *
* Gets n - number of items, m -	list of	string & colors, menu header first.  *
* erase	- if TRUE force	erase of menu area first			     *
* returns bit pattern integer -	one bit	per entry.			     *
*****************************************************************************/
int GGMenuDraw(int n, struct MenuItem m[], int erase)
{
#ifndef	NOGRAPHICS
    int	active; /* Used to hold how is active (not null) - one bit per item. */
    struct viewporttype	view;

    getviewsettings(&view);
    if (n <= MENU_MAX_ITEMS) {
	MenuBaseY = MENU_TOP_Y - n*MENU_ITEM_Y - MENU_HEAD_Y;/*Calc menu bot.*/

	/* Draw	the frame & header of the Menu */
	GGViewPortMenuArea();
	if (erase) GGClearMenuArea();

	GGDrawMenuFrame(n, MENU_FRAME_COLOR);
	GGDrawMenuItems(n, m, &active);			     /* Draw string. */

	ActvMenu = m; /* Save some information on active menu. */
	ActvMenuN = n;
	ActvMenuActive = active;
    }

    if (!erase)
	setviewport(ViewPortX = view.left, ViewPortY = view.top,
		    view.right, view.bottom, view.clip);

    return active;
#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to draw the	menu frame.					     *
* Gets n - number of items, color - color of frame.			     *
*****************************************************************************/
static void GGDrawMenuFrame(int n, int color)
{
#ifndef	NOGRAPHICS
    int	i;
    struct linesettingstype saveline;

    getlinesettings(&saveline);
    setlinestyle(SOLID_LINE, 0,	MenuLineWidth);

    GGMySetColor(color);
    GGMyMove((double) MENU_BASE_X,
	     (double) MenuBaseY);		    /* Draw the outer frame. */
    GGMyDraw((double) MENU_BASE_X,
	     (double) MenuBaseY+MENU_ITEM_Y*n+MENU_HEAD_Y);
    GGMyDraw((double) MENU_BASE_X+MENU_ITEM_X,
	     (double) MenuBaseY+MENU_ITEM_Y*n+MENU_HEAD_Y);
    GGMyDraw((double) MENU_BASE_X+MENU_ITEM_X,
	     (double) MenuBaseY);
    GGMyDraw((double) MENU_BASE_X,
	     (double) MenuBaseY);
    for	(i=1; i<=n; i++) {
	GGMyMove((double) MENU_BASE_X,
		 (double) MenuBaseY+MENU_ITEM_Y*i);
	GGMyDraw((double) MENU_BASE_X+MENU_ITEM_X,
		 (double) MenuBaseY+MENU_ITEM_Y*i);
    }
    setlinestyle(saveline.linestyle, saveline.upattern, saveline.thickness);
#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to draw the	menu internal strings.				     *
* Gets n - number of items, m string structure,	active - active	string.	     *
* Returns in active the	none null strings (bit per item) - active menu items.*
*****************************************************************************/
static void GGDrawMenuItems(int n, struct MenuItem m[], int *active)
{
#ifndef	NOGRAPHICS
    int i, power2;
    struct textsettingstype oldtext;

    *active = 0;
    power2 = 1;

    gettextsettings(&oldtext);
    settextstyle(DEFAULT_FONT, HORIZ_DIR, DefaultFontSize);
    settextjustify(CENTER_TEXT, CENTER_TEXT);

    for (i=0; i<=n; i++) {
	GGMySetColor(m[i].color);
	if (i == 0)
	    GGPutMsgXY(m[i].string,
		MENU_BASE_X+MENU_ITEM_X/2, MenuBaseY+MENU_ITEM_Y*(n + 0.7));
	else {
	    GGPutMsgXY(m[i].string,
		MENU_BASE_X+MENU_ITEM_X/2,
		MenuBaseY+MENU_ITEM_Y*(n - i + 0.4));
	    if (strlen(m[i].string)) *active |= power2;
	    power2 <<= 1;
	}
    }

    settextstyle(oldtext.font, oldtext.direction, oldtext.charsize);
    settextjustify(oldtext.horiz, oldtext.vert);
#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to pick an item from menu from active ones:			     *
*****************************************************************************/
int GGMenuPick(void)
{
    double x, y;
    int	i, select = 0, Duplicate;

    do {
	if (MouseExists) MouseFlushBuffer();      /* No need of old garbage! */
	GGGetPoint(&x, &y);

	/* Try to match	the input with the menu	first chars. */
	Duplicate = FALSE;
	if (islower(LastGetChar)) LastGetChar =	toupper(LastGetChar);
	for (i=1; i<=ActvMenuN;	i++)
	    if (LastGetChar == ActvMenu[i].string[0])
		if (select) Duplicate =	TRUE;	     /* More than one match! */
		else select = i;
	if (Duplicate) {
	    GGTone(2000, 100);				   /* Do some noise. */
	    GGTone(500, 100);
	    select = 0;
	    continue;
	}
	/* If fails try	to see if the arrow is on a valid menu item: */
	if ((!select) && (x >= MENU_BASE_X && x <= MENU_BASE_X+MENU_ITEM_X)) {
	    /* Convert into integer item number	(still in double...). */
	    y =	1 - (y - MENU_TOP_Y + MENU_HEAD_Y) / MENU_ITEM_Y;
	    select = (int) y;
	    if (!((ActvMenuActive >> (select-1)) & 1))
		select = 0;				   /* inactive item. */
	}
    }
    while (!select);

    return select;
}

/*****************************************************************************
*   Routine to draw the	confrim	special	menu request			     *
* Returns TRUE value if	Yes selected, FALSE if No selected.		     *
*****************************************************************************/
int GGConfirm(char *s)
{
    static struct MenuItem ConfMenu[] =	{	  /* Confirm Menu selection. */
	YELLOW, "                   ",		/* Make space for everybody. */
	MAGENTA, "Yes",
	MAGENTA, "No"
    };
    int	select;

    GGClearMenuArea();
    strcpy(ConfMenu[0].string, s);		   /* Set up for new header. */
    GGMenuDraw(2, ConfMenu, TRUE);
    select = GGMenuPick();
    GGClearMenuArea();

    return (select == 1);
}

#endif	NOMENUS

#ifndef NOHELP

/*****************************************************************************
*   Routine to print help on menu in the menu area. Input is from file	     *
* FileName , Which is scanned until MenuName is	found and until	$ sign	     *
* It is	the responsibility of this routine to clear the	Menu area before     *
* and after the	print help ...						     *
*****************************************************************************/
void GGPrintHelpMenu(char *FileName, char *MenuName)
{
#ifndef	NOGRAPHICS
    FILE *f;
    char s[255];  /* Irregular high length but they are some lengthly lines. */
    int	i, j, flag;
    double Yaxis;
    struct textsettingstype oldtext;

    gettextsettings(&oldtext);
    settextstyle(DEFAULT_FONT, HORIZ_DIR, DefaultFontSize);

    GGClearMenuArea();

    if ((f = GGFindFile(FileName, "rt")) == NULL)
	return;				       /* Search all the path names. */
    do {
	flag = fgets(s, 255, f) != NULL;
	s[strlen(s)-1] = 0;			      /* Clear the new line. */
    }
    while (strcmp(MenuName, s) && (flag));	 /* Skip until correct menu. */

    if (!flag) {
	GGPutErrorMsg("No Such Menu in file");
	fclose(f);
	return;
    }

    Yaxis = 0.93;				/* First line to start with. */
    do {		/* If we are here then we found the menu - Print it. */
	flag = (fgets(s, 255, f) != NULL);
	s[strlen(s)-1] = NULL;			      /* Clear the new line. */
	if (s[0] == '*') {
	    GGMySetColor(HELP_COLOR_HEADER);
	    HelpUpdateY(&Yaxis);
	    s[0] = ' ';
	}
	else GGMySetColor(HELP_COLOR);
	if (s[0] != '$')				    /* Terminator... */
	if (strlen(s) <	MAX_HELP_MENU_CHAR) {
	    GGPutMsgXY(s, MENU_BASE_X, Yaxis);
	    HelpUpdateY(&Yaxis);
	}
	else {
	    i =	0;
	    while ((strlen(&s[i]) > MAX_HELP_MENU_CHAR)) {
		j = i +	MAX_HELP_MENU_CHAR;		 /* Break the line.! */
		while (s[j--] != ' ');		    /* In the nearest space. */
		s[++j] = 0;			/* Put EOL instead of Space. */
		GGPutMsgXY(&s[i], MENU_BASE_X, Yaxis);
		HelpUpdateY(&Yaxis);
		i = j + 1;
	    }
	    if ((strlen(&s[i]) > 0) && (Yaxis >	-1.0)) {
		GGPutMsgXY(&s[i], MENU_BASE_X, Yaxis);
		HelpUpdateY(&Yaxis);
	    }
	}
    }
    while ((flag) && (s[0] != '$'));

    fclose(f);

    GGPutErrorMsg("");				      /* Wait for keystroke. */

    GGClearMenuArea();
    settextstyle(oldtext.font, oldtext.direction, oldtext.charsize);
#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to decrease Y for next line in the help printing. Test is made   *
* for last line, and wait and clear area is performed in that case.	     *
*****************************************************************************/
static void HelpUpdateY(double *Yaxis)
{
    *Yaxis = (*Yaxis) - 0.08;
    if (*Yaxis < -0.75) {
	GGPutErrorMsg("");
	GGClearMenuArea();
	*Yaxis = 0.93;
	GGMySetColor(HELP_COLOR);
    }
}

/*****************************************************************************
*   Routine to search for a file and open it according to attribute at all   *
* directories defined by PATH environment variable and current dir.	     *
*****************************************************************************/
static FILE *GGFindFile(char *FileName, char *Attribute)
{
    FILE *f;
    char *Path;

    Path = searchpath(FileName);
    if ((f = fopen(Path, Attribute)) !=	0) return f;

    GGPutErrorMsg("No Help File Available");
    return (FILE *) NULL;
}

#endif NOHELP

/*****************************************************************************
*   Routine to clear the Menu area, and set view port to it.		     *
*****************************************************************************/
void GGClearMenuArea(void)
{
#ifndef	NOGRAPHICS

#ifdef  NOWINDOWS
    setviewport(ViewPortX = (int) (GGScreenMaxY * GlblAspectRatio),
		ViewPortY = 0,
		GGScreenMaxX, GGScreenMaxY, FALSE);
    clearviewport();
#else
    WndwClearWindow(WndwMenuWindow);
    WndwAspectRatio = 1.0;
#endif NOWINDOWS

#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to clear the Axes area, and set view port to it.		     *
*****************************************************************************/
void GGClearViewArea(void)
{
#ifndef	NOGRAPHICS

#ifdef  NOWINDOWS
    setviewport(ViewPortX = 0, ViewPortY = 0,
		(int) (GGScreenMaxY * GlblAspectRatio), GGScreenMaxY, FALSE);
    clearviewport();
#else
    WndwClearWindow(WndwViewWindow);
    switch (GraphDriver) {
	case HERCMONO:
	case CGA:
	    WndwAspectRatio = 1.0;
	    break;
	case EGA:
	case EGA64:
	case EGAMONO:
	case VGA:
	    WndwAspectRatio = 1.2;
	    break;
    }
#endif NOWINDOWS

#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to clear all the screen.					     *
*****************************************************************************/
void GGClearAllScreen(void)
{
#ifndef	NOGRAPHICS
    setviewport(ViewPortX = 0, ViewPortY = 0, getmaxx(), getmaxy(), FALSE);
    clearviewport();
    WndwAspectRatio = 1.0;
#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to set the view port to the Menu area:			     *
*****************************************************************************/
void GGViewPortMenuArea(void)
{
#ifndef	NOGRAPHICS

#ifdef  NOWINDOWS
    setviewport(ViewPortX = (int) (GGScreenMaxY * GlblAspectRatio),
		ViewPortY = 0,
		GGScreenMaxX, GGScreenMaxY, FALSE);
#else
    WndwSetViewPort(WndwMenuWindow -> MinX,
		    WndwMenuWindow -> MinY,
		    WndwMenuWindow -> MaxX,
		    WndwMenuWindow -> MaxY);
    WndwAspectRatio = 1.0;
#endif NOWINDOWS

#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to clear the Axes area, and set view port to it.		     *
*****************************************************************************/
void GGViewPortViewArea(void)
{
#ifndef	NOGRAPHICS

#ifdef  NOWINDOWS
    setviewport(ViewPortX = 0, ViewPortY = 0,
		(int) (GGScreenMaxY * GlblAspectRatio), GGScreenMaxY, FALSE);
#else
    WndwSetViewPort(WndwViewWindow -> MinX,
		    WndwViewWindow -> MinY,
		    WndwViewWindow -> MaxX,
		    WndwViewWindow -> MaxY);
    WndwAspectRatio = GlblAspectRatio * GlblAspectRatio;
#endif NOWINDOWS

#endif NOGRAPHICS
}

/*****************************************************************************
*   Routine to clear all the screen.					     *
*****************************************************************************/
void GGViewPortAllScreen(void)
{
#ifndef	NOGRAPHICS
    setviewport(ViewPortX = 0, ViewPortY = 0, getmaxx(), getmaxy(), FALSE);
#endif NOGRAPHICS
}

/*****************************************************************************
* Routine to print an error message on the message area	:		     *
*****************************************************************************/
void GGPutErrorMsg(char *s)
{
#ifndef	NOGRAPHICS
    int Quit = FALSE, X, Y, Buttons;

    GGMySetColor(RED);
    GGPutMsgXY(s, MSG_AREA_X, MSG_AREA_Y+0.1);
    GGPutMsgXY("Press Return:", MSG_AREA_X, MSG_AREA_Y);

    if (MouseExists) MouseFlushBuffer();	  /* No need of old garbage! */
    do {
	if (kbhit()) {
	    getch();
	    Quit = TRUE;
	}
	if (MouseExists && MouseQueryBuffer()) {
	    MouseGetBuffer(&X, &Y, &Buttons);
	    Quit = (Buttons != 0);
	}
    }
    while (!Quit);

    GGMySetColor(BLACK);				    /* Erase that... */
    GGPutMsgXY(s, MSG_AREA_X, MSG_AREA_Y + 0.1);
    GGPutMsgXY("Press Return:", MSG_AREA_X, MSG_AREA_Y);
#endif NOGRAPHICS
}

/*****************************************************************************
* Routine to print an message on the given location:			     *
*****************************************************************************/
void GGPutMsgXY(char *s, double x, double y)
{
#ifndef	NOGRAPHICS
    GGMyMove(x, y);
    outtext(s);
#endif	NOGRAPHICS
}

/*****************************************************************************
* Routine to read one line terminated by <Enter> and visualized	on graphic   *
* screen.								     *
* Full line editing is supported which includes:			     *
* 1. Right and left arrow to move along the line.			     *
* 2. Delete to delete current character.				     *
* 3. Insert to toggle Insert/Overwrite mode.				     *
* 4. Backspace to delete character before current one.			     *
* 5. Home/End to move to beginning/End of string respectively.		     *
* 6. Return to accept current line.					     *
* 7. Esc to clear current line.						     *
* If s is not empty it is used as starting string.			     *
* Line is drawn	starting from position x, y on screen (normalized -1..1).    *
*****************************************************************************/
void GGGetGraphicLine(double x, double y, char s[], int color)
{
#ifndef	NOGRAPHICS
    static int Insert = TRUE;
    int	NextKey, Xstep, CursorPos = 0, FirstVisible = 0, Len = strlen(s),
	EndOfString = FALSE;
    char *Cursor;
    struct textsettingstype oldtext;
    struct viewporttype	view;

    getviewsettings(&view);
    GGViewPortMenuArea();

    gettextsettings(&oldtext);
    settextstyle(DEFAULT_FONT, HORIZ_DIR, DefaultFontSize);

    Xstep = textwidth(" ");	 /* Get the width is pixels of current font. */

    while (!EndOfString) {
	/* Set the first character in string to be displayed: */
	FirstVisible = CursorPos > 15 ? CursorPos - 10 : 0;
	GGMySetColor(color);		  /* Draw the string and the cursor: */
	GGPutMsgXY(&s[FirstVisible], x, y);
	Cursor = (Insert ? "_" : "-");
	GGMySetColor(ScreenCursorColor);
	GGMyMove(x, y);
	moverel((CursorPos - FirstVisible) * Xstep, 0);
	outtext(Cursor);

	NextKey = GGGetKey();

	GGMySetColor(BLACK);		 /* Erase the string and the cursor: */
	GGMyMove(x, y);
	moverel((CursorPos - FirstVisible) * Xstep, 0);
	outtext(Cursor);
	GGPutMsgXY(&s[FirstVisible], x, y);

	switch (NextKey) {
	    case KEY_BSPACE:
		if (CursorPos == 0) {
		    GGTone(1000, 100);			 /* Do some noise... */
		    break;
		}
		movmem(&s[CursorPos], &s[CursorPos - 1], Len - CursorPos + 1);
		CursorPos--;
		Len--;
		break;
	    case KEY_DELETE:
		if (CursorPos >= Len) {
		    GGTone(1000, 100);			 /* Do some noise... */
		    break;
		}
		movmem(&s[CursorPos + 1], &s[CursorPos], Len - CursorPos);
		Len--;
		break;
	    case KEY_RIGHT:
		if (CursorPos >= Len) {
		    GGTone(1000, 100);			 /* Do some noise... */
		    break;
		}
		CursorPos++;
		break;
	    case KEY_LEFT:
		if (CursorPos <= 0) {
		    GGTone(1000, 100);			 /* Do some noise... */
		    break;
		}
		CursorPos--;
		break;
	    case KEY_RETURN:
		EndOfString = TRUE;
		break;
	    case KEY_ESC:
		Len = 0;				/* Clear everything. */
		CursorPos = 0;
		s[0] = 0;
		break;
	    case KEY_HOME:
		CursorPos = 0;
		break;
	    case KEY_END:
		CursorPos = Len;
		break;
	    case KEY_INSERT:
		Insert = !Insert;
		break;
	    default:				      /* Regular ascii char. */
		if (Insert) {
		    movmem(&s[CursorPos], &s[CursorPos + 1],
							Len - CursorPos + 1);
		    Len++;
		}
		else if (CursorPos == Len) Len++;/* We are on last character.*/
		s[CursorPos++] = NextKey;
		if (CursorPos == Len) s[CursorPos] = 0;
		break;
	}
    }

    settextstyle(oldtext.font, oldtext.direction, oldtext.charsize);
    setviewport(ViewPortX = view.left, ViewPortY = view.top,
		view.right, view.bottom, view.clip);
#endif NOGRAPHICS
}

/*****************************************************************************
* Get a key from keyboard, and translating operational keys into special     *
* codes (>255).								     *
*****************************************************************************/
static int GGGetKey(void)
{
    char c;

    while (TRUE) switch (c = getch()) {
	case 0:		      /* Extended code - get the next extended char. */
	    switch (getch()) {
		case 75: return KEY_LEFT;
		case 77: return KEY_RIGHT;
		case 71: return KEY_HOME;
		case 79: return KEY_END;
		case 83: return KEY_DELETE;
		case 82: return KEY_INSERT;
	    }
	    break;
	case 8:
	    return KEY_BSPACE;
	case 10:
	case 13:
	    return KEY_RETURN;
	case 27:
	    return KEY_ESC;
	default:
	    if isprint(c) return c;
    }

    return KEY_RETURN;				    /* Make warnings silent. */
}

/*****************************************************************************
* Routine to make some sound with given Frequency, Time milliseconds:	     *
*****************************************************************************/
void GGTone(int Frequency, int Duration)
{
    sound(Frequency);
    delay(Duration);
    nosound();
}

#ifndef  NOWINDOWS

/*****************************************************************************
*  Routine to setup all the window to zero state, assuming graphic mode.     *
*****************************************************************************/
static void WndwSetUpAllWndws(void)
{
#ifndef	NOGRAPHICS
    GGClearAllScreen();

    WndwDrawAllWndwFrames();			 /* Draw the windows frames. */

    WndwViewWindow = &ViewWindow;
    WndwMenuWindow = &MenuWindow;
#endif	NOGRAPHICS
}

/*****************************************************************************
*  Routine to draw all the frames of all the windows defined in the system:  *
*****************************************************************************/
static void WndwDrawAllWndwFrames(void)
{
#ifndef	NOGRAPHICS
    WndwDrawWindowFrame(&MenuWindow);
    WndwDrawWindowFrame(&ViewWindow);
#endif	NOGRAPHICS
}

/*****************************************************************************
*  Routine to draw a frame for the window given:			     *
*****************************************************************************/
static void WndwDrawWindowFrame(WindowStruct *Window)
{
    int i;
    double MinX = Window -> MinX, MaxX = Window -> MaxX,
	   MinY = Window -> MinY, MaxY = Window -> MaxY,
	   Dx = FRAME_X_WIDTH / 10.0, Dy = FRAME_Y_WIDTH / 10.0;

#ifndef	NOGRAPHICS
    GGMySetColor(Window -> FrameColor);

    for (i=0; i<10; i++) {
	GGMyMove(MinX, MinY);
	GGMyDraw(MinX, MaxY);
	GGMyDraw(MaxX, MaxY);
	GGMyDraw(MaxX, MinY);
	GGMyDraw(MinX, MinY);

	MinX -= Dx;
	MinY -= Dy;
	MaxX += Dx;
	MaxY += Dy;
    }
#endif	NOGRAPHICS
}

/*****************************************************************************
*  Routine to clear the given window interior:				     *
*****************************************************************************/
static void WndwClearWindow(WindowStruct *W)
{
#ifndef	NOGRAPHICS
    WndwSetViewPort(W -> MinX, W -> MinY, W -> MaxX, W -> MaxY);
    clearviewport();
#endif	NOGRAPHICS
}

/*****************************************************************************
*    Routine to set up the view port.					     *
*****************************************************************************/
static void WndwSetViewPort(double XMin, double YMin, double XMax, double YMax)
{
#ifndef	NOGRAPHICS
    setviewport(
	(ViewPortX = (int) ((XMin * GGScreenMaxY2 + GGScreenMaxY2) *
							GlblAspectRatio)),
	(ViewPortY = (int) (-YMax * GGScreenMaxY2 + GGScreenMaxY2)),
	(int) ((XMax * GGScreenMaxY2 + GGScreenMaxY2) * GlblAspectRatio),
	(int) (-YMin * GGScreenMaxY2 + GGScreenMaxY2),
	TRUE);
#endif	NOGRAPHICS
}

#endif  NOWINDOWS
