/*===========================================================================
  Copyright (C) 1986-2009 European Southern Observatory (ESO)
 
  This program is free software; you can redistribute it and/or 
  modify it under the terms of the GNU General Public License as 
  published by the Free Software Foundation; either version 2 of 
  the License, or (at your option) any later version.
 
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 
  You should have received a copy of the GNU General Public 
  License along with this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetss Ave, Cambridge, 
  MA 02139, USA.
 
  Corresponding concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.TYPE		Module
.IDENTIFICATION tvcursor.c
.AUTHOR    	Francois Ochsenbein [ESO-IPG]
.LANGUAGE  	C
.KEYWORDS	Terminal Independant i/o Package.
.ENVIRONMENT 	TermWindows
.COMMENTS	This module includes functions to define and perform
		scrolling, and cursor movements.
\begin{TeX}
Used Capabilities:
$$\begin{tabular}{|lp{30em}|}	\hline
{\tt cm}	& Move cursor to specified position	\\
{\tt ff}	& Form-feed				\\
{\tt ho}	& Move cursor to home (\ie [0,0]) position\\
{\tt cs}	& Define a scrolling region	\\
{\tt sf}	& Scroll forwards (up)		\\
{\tt sr}	& Scroll reverse (down)		\\
\hline
\end{tabular}$$
\end{TeX}
.VERSION   1.0	07-Jul-1986:	Creation
.VERSION   1.1	23-Oct-1986	Flag the move to a newline, to ensure
			that correct attributes are set even with HP's
			(attributes end with end-of-line).
			Take care of cursor move if a scrolling region 
			exists: reset first the scrolling region.
			(added tv_sr0 to do this)

.VERSION   1.2	26-Nov-1986	Remove bug in tv_sr0

.VERSION   2.0	03-Dec-1986	New terminal-independant graphic characters
			with output buffering version.
.VERSION   2.1	11-Jun-1987	Version '2'. Improvements for hard terminals.
.VERSION   2.2	23-Jun-1987	Adapted to UNIX and Minimal Implementation.
.VERSION   2.3	04-Dec-1987	Save and Get Cursor also save Attribute
.VERSION   3.0	20-Mar-1988	Version '3' of TermWindows.
.VERSION   3.1	06-Oct-1989	Be sure that tv_sr0 RESETS the scrolling region.
.VERSION   3.2	23-May-1990	Hard terminals up movement does nothing...

 090831		last modif
----------------------------------------------------------------------------*/

#define DEBUG		0	/* For debugging only	*/

#define PM_LEVEL 	LEVEL_TV

#define	TW_MACROS	0		/* Don't use TermWindows Macros */
#define	TW_import	0	
#define TW_STRUCT	0	/* Do not use window Structures		*/
#include <twset.h>

extern int tv_buffer(), tv_where(), tv_goto(), tv_out();
extern int tv_attr(), tv_mvc(), tv_open(), tv_sr0();
extern int tv_nl(), tv_send(), tv_imode(), tv_il(), tv_delay();

extern int tu_format();

extern int  pm_enter(), pm_iexit();


MID_EXTERN TERM	*terms ;		

#define		SEND(p)		tv_send(p,1)

#if 	DEBUG
#	define	TERMTEST	if(!terms->version) tv_gopen();\
				tv_ed(terms)
#else
#	define	TERMTEST	if(!terms->version) tv_gopen()
#endif

#define FINISH		goto FIN
#define	ENTER_TV(x)	static int 	state_buffer;			\
			ENTER(x);	TERMTEST;			\
			state_buffer  =	tv_buffer(1);	
#define	EXIT_TV(f)	FIN:						\
			tv_buffer(state_buffer);			\
			EXIT(f)

#if DEBUG
	MID_STATIC char ch=EOS;
	MID_STATIC int type=0;
#endif

		/* Macros just to improve readibility	*/
#define		newl			new_pos[0]
#define		newc			new_pos[1]
#define		oldl			old_pos[0]
#define		oldc			old_pos[1]
#define		curl			(terms->pos[0])
#define		curc			(terms->pos[1])
#define		diml			(terms->dim[0])
#define		dimc			(terms->dim[1])
#define		BUFS			(terms->bufs)


	MONITOR (ZTCURSOR);

/*==========================================================================
 *			tv_cm(line, col)
 *==========================================================================*/
static int tv_cm(line, col)
/*+++
.PURPOSE Position the cursor using direct cursor addressing
.RETURNS OK / NOK (no direct addressing)
.REMARKS NO DELAY IS SENT HERE (for optimization).
	NO TEST on validy of line and col.
	*** For internal use ***. Not Traced.
------------------*/
	int	line;			/* IN: Line number to move to */
	int	col;			/* IN: Column number to move to */
{
  	short int new_pos[2];

  newl = line;
  newc = col;

  status = tv_out(BUFS, tu_format(BUFS, terms->tt_cm, new_pos));
	/* tv_delay(terms->tt_cm); */

  return(status);
}

/*==========================================================================
 *			tv_range
 *==========================================================================*/
int tv_range(coo, limits)
/*++++++++++++++
.PURPOSE Verify and updates the cursor position versus limits.
	Lines are numbered from 0 at the top of the screen; columns are
	numbered from 0 at the left margin of the screen. 
.RETURNS OK / NOK (cursor position modified)
.REMARKS Not traced.
----------------*/
	short int coo[2]; /* MOD: Cursor position (line, col) to verify	*/
	short int limits[2];	/* IN: Limits of the screen		*/
{
	register int s;

  s = OK;

  if(coo[0] < 0) { coo[0] = 0; s = NOK;}
  if(coo[1] < 0) { coo[1] = 0; s = NOK;}

  if(coo[0] >= limits[0]) { coo[0] = limits[0]-1; s = NOK;}
  if(coo[1] >= limits[1]) { coo[1] = limits[1]-1; s = NOK;}

  return(s);
}

/*==========================================================================
 *			tv_pos
 *==========================================================================*/
int tv_pos()
/*++++++++++++++
.PURPOSE Returns the cursor position, as a number of characters 
	from the top left.
.RETURNS Position, in range [0, lines*cols]
.REMARKS Not traced.
----------------*/
{
  return (terms->pos[0]*terms->dim[1] + terms->pos[1]);
}

/*==========================================================================
 *			tv_cur()
 *==========================================================================*/
int	tv_cur(saved)
/*+++++++++++
.PURPOSE Restore the cursor position.
.RETURNS OK
.REMARKS 
-----------------------------------------------------------------------*/
	TVSAVE	*saved;		/* IN: Old position + attribute */
{
  ENTER_TV("tv_cur");

  status = tv_goto(saved->pos[0], saved->pos[1]);
  tv_attr(saved->attr);
  FINISH;

  EXIT_TV(status);
}

/*==========================================================================
 *			tv_cus()
 *==========================================================================*/
int	tv_cus(saved)
/*++++++++++++++
.PURPOSE Save the current cursor position.
.RETURNS OK
.REMARKS Not traced.
---------------*/
	TVSAVE	*saved;		/* OUT: Current position + attribute */
{
  saved->pos[0] = terms->pos[0];
  saved->pos[1] = terms->pos[1];
  saved->attr   = terms->attr;

  return(OK);
}
 
/*==========================================================================
 *			tv_agoto
 *==========================================================================*/
int tv_agoto(pos)
/*+++++++++++
.PURPOSE Position the cursor to a specified byte position.
	Position are numbered from 0 at the top left corner to N-1 at bottom
	right corner.
.RETURNS Actual position
.REMARKS 
------------*/
	int	pos;			/* IN: Position to move to */
{
	int J, N, n;

  J = terms->dim[1];
  N = J * terms->dim[0];
  n = MAX(pos, 0);
  if (n >= N)	n = N-1;
  tv_goto(n/J, n%J);
  return(n);
}
  
/*==========================================================================
 *			tv_goto
 *==========================================================================*/
int tv_goto(line, col)
/*+++++++++++
.PURPOSE Position the cursor to a specified line and column position on the screen.
	Lines are numbered from 0 at the top of the screen; columns are
	numbered from 0 at the left margin of the screen. 
	If full screen is not asked for, no test is performed on the
	validity of line and column.
.RETURNS OK / NOK (not possible, e.g. up on a hard terminal)
.REMARKS An attempt to position the cursor off the screen will place it 
	on an edge.
	NO DELAY IS SENT HERE. (for optimization)
------------*/
	int	line;			/* IN: Line number to move to */
	int	col;			/* IN: Column number to move to */
{
	short int new_pos[2];
	short int old_pos[2];
	register int n;

  ENTER_TV("tv_goto");

  status = OK;
  tv_where(old_pos);			/* Save current position	*/
  newl = line;
  newc = col;
  tv_range(new_pos, terms->dim);	/* Verify cursor within screen	*/

#if 	DEBUG
  TRACE_ED_I("Old       line ",oldl);
  TRACE_ED_I("      ... col  ",oldc);
  TRACE_ED_I("Moving to line ",line);
  TRACE_ED_I("      ... col  ",col);
#endif

	/* Optimize for move on the same line	*/

  if (newl == oldl) 
  {	n = newc - oldc;			/* Displacement	*/
	if ( n == 0 ) 		FINISH;		/* No move	*/
	if ( (n > -3) && (n < 3) )
	{	status = tv_mvc(_RIGHT_, n);
		FINISH;
	}
  }
  else	terms->standout |= ChangedLine;

  if ( (newl < terms->scregion[0]) || (newl > terms->scregion[1]) )
  	tv_sr0();			/* Reset scrolling region */


	/* Optimize for a move to the left margin	*/
  if (newc == 0 )
  {	if (oldl == newl) 	
	{ 	status = tv_out("\r",1);
		curc = 0;
		FINISH;
	}
	if (newl == (oldl+1))	
	{ 	status = tv_nl();
		FINISH;
	}
  }

	/* Cursor addressing capability MUST exist in Minimal Implementation */

#if (TW_LEVEL == 0)
  status = tv_cm(newl, newc);
  curl = newl;
  curc = newc;
#else
  if (*terms->tt_cm)
  {  	status = tv_cm(newl, newc);
	curl = newl;
	curc = newc;
	FINISH;
  }

	/* Unknown terminal: use right-left, up-down mouvements	 ---
	 * but Optimize for move on the same line to the right	*/

  if (newl == oldl) 
  {	n = newc - oldc;			/* Displacement	*/
	if ( n > 0 ) 	
	{	status = tv_mvc(_RIGHT_, n);
		FINISH;
	}
  }

  status = tv_out("\r",1);	/* Go first to left margin	*/
  curc = 0;	oldc = 0;

  n = newl - oldl;		/* ... then move down ...	*/

  while(n>0) 	
  {	tv_nl();
	n--;
  }
				/* ... move up if required...	*/
  if(n) 	status = tv_mvc(_UP_, -n);
				/* ... finally, move right	*/
  status = tv_mvc(_RIGHT_, newc);
#endif

  EXIT_TV(status);
}

/*======================================================================
 *			tv_home
 *======================================================================*/
int tv_home()
/*+++
.PURPOSE Move the cursor to home position
.RETURNS OK
.REMARKS Minimal implementation: use goto(0,0)
---*/
{
#if (TW_LEVEL > 0)
	MID_REGISTER char *p;
#endif

  ENTER_TV("tv_home");

  if (terms->scregion[0])		/* Reset scrolling region */
	tv_sr0();		

#if (TW_LEVEL == 0)			/* Minimal Implementation */
  status = tv_cm(0,0);
  curl = 0;	curc = 0; 	
#else					/* --Full Implementation */
  /* Look if "ho" possibility exists	*/
  p = SearchCap("ho");
  if (p) 	
  {	status = SEND(p);
	curl = 0;
	curc = 0;
	FINISH;
  }

	/* If up movements not available,
	   simply issue a newline
	*/

  if(*terms->tt_ptr[_UP_] == 0)	
  	tv_nl();
  	
	/* Use up movements . Note that zt_mvc takes care of the
	 * existance or not of up movements.			*/

  tv_out("\r",1);	/* Go first to left margin	*/
  curc = 0;
  status = tv_mvc(_UP_, curl);
#endif

  EXIT_TV(status);
}

/*======================================================================
 *			tv_mvc
 *======================================================================*/
int tv_mvc(direction,times)
/*+++
.PURPOSE Move the cursor n times in the specified direction: up, down,
	right, left or newline.
.RETURNS OK / NOK (not possible, e.g. up on a hard terminal)
.REMARKS A negative number of times asks for the opposite direction.
	If full screen is not asked for, no test on the number of
	of cursor mouvements is performed.
	--- Minimal Implementation: cursor movements ALWAYS exist.
---*/
	int direction;	/* IN: The direction of the displacement	*/
	int times;	/* IN: Number of times to move	*/
{
	short int old_pos[2];
	MID_RSTATIC short int step[2] 	= {-1, 1};
	int 	k, n, init_attr;

  ENTER_TV("tv_mvc");

#if DEBUG
  TRACE_ED_I("Moving to direction: ",direction);
  TRACE_ED_I("....          times: ",times);
#endif

  status = OK;

  if (times == 0) 	FINISH;

  tv_where(old_pos);				/* Save current position    */
  init_attr = terms->attr_init;

  tv_range(terms->pos, terms->dim);		/* Test the cursor position */

  k = direction >> 1;				/* Lead to Horiz., Vert. or */

  switch(k)
  { case _VERTICAL_: 
	terms->standout |= ChangedLine;
    case _HORIZONTAL_: 
	n = times * step[direction & 1];
	terms->pos[k] += n;
	tv_range(terms->pos, terms->dim);
	if (k == _HORIZONTAL_)	n = curc - oldc;
	break;
    default: 					/* _NEWLINE_ is assumed	*/
	if ( (n = times) < 0)	FINISH;
	while (--n>=0) 		tv_nl(); 
	FINISH;
  }
  k <<= 1;				/* Final Direction		*/
  if (n < 0)		n = -n;
  else			k++;


#if (TW_LEVEL > 0)			/* ==> Full Implementation	*/
  switch(k)	
  { case _UP_	: 		/* New line for hard terminals: Nothing	*/
	if(*terms->tt_ptr[_UP_] == 0)
	{	/* tv_nl(); */
		status = NOK;
		FINISH;
	}
	break;

    case _DOWN_	: 		/* Use newlines if cap' doesn't exist	*/
 	if (*terms->tt_ptr[_DOWN_] == 0)
	{	curl = oldl;
		curc = oldc;
		while (--n >= 0)	tv_nl();
		n = oldc;
		curc = n;	
		k = _RIGHT_;
	}		
	break;

    case _LEFT_	: 		/* When capability doesn't exist, use \r
				 * then move to the right		*/
 	if (*terms->tt_ptr[_LEFT_] == 0)
	{	tv_out("\r", 1);
		n = curc;		/* Blanks to move	*/
		k = _RIGHT_;		/* Next op. is RIGHT	*/
	}		
	else	break;		

    case _RIGHT_: 		/* Test if blank is used --- if so, don't 
				 * forget to use standard attribute !	*/
	if (*(terms->tt_ptr[_RIGHT_] + 4) == ' ')
		init_attr = terms->attr, tv_attr((int)terms->attr_init);
	break;
  }

#endif

	/* Send n times the sequence	*/
  status = tv_send(terms->tt_ptr[k], n);

  if (terms->attr_init != init_attr)	/* If blanks used for _RIGHT_ */
  	tv_attr(init_attr);

  EXIT_TV(status);
}

/*==========================================================================
 *			tv_nl
 *==========================================================================*/
int tv_nl()
/*+++++++++++++++
.PURPOSE New line, i.e. position the cursor to the beginning of the next line.
.RETURNS OK
.REMARKS The cursor position is at the beginning of the next line.
	The insert mode is switched off.
-----------*/
{
	register char *p;
	unsigned char old_attr;

  ENTER_TV("tv_nl");

  status = OK;

#if (TW_LEVEL > 0)
	/* First, switch off the insert mode if set	*/
  tv_imode(0);
#endif


  curc = 0;			/* The beginning of a line	*/
  p = terms->tt_ptr[_NEWLINE_];
  old_attr = terms->attr;
  tv_attr(terms->attr_init);

  curl++;
  if (curl >= diml)		curl = diml -1;
  status = SEND(p);
  tv_attr(old_attr);
  FINISH;

  EXIT_TV(status); 
}
 
/*======================================================================
 *			tv_scroll
 *======================================================================*/
int tv_scroll(direction, times)
/*+++
.PURPOSE Scroll n lines in the specified (_UP_ or _DOWN_) direction.
.RETURNS OK / NOK (always NOK in minimal implementation)
.REMARKS A negative number of times asks for the opposite direction. _UP_ 
	is the default. 
---*/
	int direction;	/* IN: The direction of the displacement	*/
	int times;	/* IN: Number of lines to scroll		*/
{
#if (TW_LEVEL == 0)	/* ===> Minimal Implementation			*/
  return(NOK);
#else			/* ===> FULL   	Implementation			*/
	int 	dir, n;
	char 	*p;
	TVSAVE	saved;

  ENTER_TV("tv_scroll");

#if DEBUG
	TRACE_ED_I("Scrolling lines: ",times);
	tv_getc(&ch,&type);
#endif

  status = OK;
  if (times == 0) 	FINISH;

  dir = direction & 1;
  n = times;
  if (times  < 0) 			/* Change direction 	*/
  {	dir = 1^direction;	
	n   = -times;	
  }
  tv_cus(&saved);			/* Save cursor position */

  /* If no scrolling capability exist: move 
	to top or bottom of screen, and use 
	- down mouvements to scroll up
	- insert lines to scroll down				*/

  if (dir == _DOWN_)	
  {	tv_home();
	p = "sr";
	p = SearchCap(p);
	if (p) 	status = tv_send(p,n);
	else	status = tv_il(n);
  }
  else
  {	tv_goto(terms->dim[0]-1, 0);
	p = "sf";
	p = SearchCap(p);
	if (p) 	status = tv_send(p,n);
	else	status = tv_send(terms->tt_ptr[_DOWN_], n);
  }

  tv_cur(&saved);			/* Restore Cursor	*/

  EXIT_TV(status);
#endif
}

/*==========================================================================
 *			tv_sr
 *==========================================================================*/
int tv_sr(l1, l2)
/*+++++++++++++
.PURPOSE Defines a scrolling region on the terminal between
	lines l1 and l2.
	Lines are numbered from 0 at the top of the screen.
.RETURNS OK, or NOK (capability does not exist)
.REMARKS On return, the cursor is at the left margin within the
	scrolling region.
	It does not move if NOK is returned, or if the scrolling
	region did not change.
----------------*/
	int l1;		/* IN: Upper line of the scrolling region	*/
	int l2;		/* IN: Lower line of the scrolling region	*/
{
	register char *p;
	short int  scroll_lim[2];

  ENTER_TV("tv_sr");

  status = OK;
  p = SearchCap("cs");		/* Null pointer if terminal doesn't allow to
  				   define scrolling regions */

  if_not(p) 
  { 	status = NOK;
  	terms->scregion[0] = 0;	terms->scregion[1] = diml-1;
   	FINISH;
  }

  scroll_lim[0] = MIN(l1,l2);
  scroll_lim[1] = MAX(l1,l2);
  
  if(scroll_lim[0] < 0) 	scroll_lim[0] = 0;
  if(scroll_lim[1] >= diml) 	scroll_lim[1] = diml-1;

  if (	(terms->scregion[0] != scroll_lim[0]) ||
  	(terms->scregion[1] != scroll_lim[1]) )			
  {	
	if_not(status = tv_out(BUFS,tu_format(BUFS,p,scroll_lim)))
			FINISH;
	if_not(status = tv_delay(p))	FINISH;
	terms->scregion[0] = scroll_lim[0];
	terms->scregion[1] = scroll_lim[1];

	if (curl < scroll_lim[0])  	curl = scroll_lim[0];
	if (curl > scroll_lim[1]) 	curl = scroll_lim[1];
	curc = 0;
	if (*terms->tt_cm)  /* If cursor addressing exists, use it */
			tv_cm(curl, 0);
  }


  EXIT_TV(status);
}

/*==========================================================================
 *			tv_sr0
 *==========================================================================*/
int tv_sr0()
/*+++++++++++++++
.PURPOSE Redefines the complete screen as the scrolling region.
.RETURNS OK / NOK (no change in scrolling region)
.REMARKS On return, cursor is at home position if OK returned,
	and remains if NOK.
----------------*/
{
  ENTER_TV("tv_sr0");

	/* Set the current scrolling region to (0,0) to force
	   a change in the tv_sr routine	*/
	  
  terms->scregion[0] = 0, terms->scregion[1] = 0;

  status = tv_sr(0, diml-1);

  FINISH;
  EXIT_TV(status);
}

/*==========================================================================
 *			tv_where
 *==========================================================================*/
int tv_where(coo)
/*+++++++++++
.PURPOSE Returns the current cursor position.
.RETURNS OK
.REMARKS The position is adjusted within the screen limits.
	Not traced.
------------*/
	short int coo[2]; /* OUT: current position: line (0=top), col (0=left) */
{
  coo[0] = curl;
  coo[1] = curc;
  /*	if (terms->flags & TERM_am);
	else	tv_range(coo, terms->dim);
  */
  tv_range(coo, terms->dim);
  return(OK);
}
 
