/*****************************************************************************
*           Change Log
*  Date     | Change
*-----------+-----------------------------------------------------------------
* 21-Nov-91 | [1.435] <jmn> Created
*  7-Dec-91 | [1.483] <jmn> virtual_printer_init truncates file
* 21-Dec-91 | [1.492] <jmn> implemented pgup/pgdn
* 21-Dec-91 | [1.493] <jmn> use char_perf for perforations
* 22-Dec-91 | [1.505] <jmn> unsigned char for write_translated
* 23-Dec-91 | [1.544] <jmn> added skip_to_channel
* 23-Dec-91 | [1.544] <jmn> added 132-column support for full printer display
*****************************************************************************/
#include "stdio.h"
#include "graph.h"
#include "boolean.h"
#include "string.h"
#include "errno.h"

#include "btypes.h"
#include "pr.h"
#include "printers.h"
#include "scdspmsg.h"
#include "mouse.h"
#include "color.h"
#include "kb.h"
#include "kbd.h"
#include "scan.h"
#include "button.h"
#include "display.h"
#include "hercules.h"
#include "bcd.h"
#include "diag.h"
#include "chars.h"
#include "prdev.h"
#include "vprinter.h"
#include "1401.h"
#include "print.h"

typedef struct {
	char data[PRINT_LENGTH + 1];
	       } vprinter_line;

#define vlpt "printer.prn"

#define COLUMN0 14
static long top_of_screen_line = 0;
static coord leftmargin = COLUMN0;
extern struct videoconfig video;
static boolean prdone;

#define MIN_LEFTMARGIN 0
#define MAX_LEFTMARGIN (COLUMN0 + 132 - video.numtextcols+4)

attrib bglight = GREEN;
attrib fglight = BLACK;
attrib bgdark = GREEN;
attrib fgdark = GREEN;

/* VGA colors */
#define RGB(r,g,b) ( ((long)((b) & 0x3F) << 16L) + \
	             ((long)((g) & 0x3F) << 8L) + \
		     ((long)((r) & 0x3F)) )

static long LT1 = RGB(0,41,0);
static long DK1 = RGB(0,54,0);

#define full_diagnostics true 

unsigned char screenmap[256];

void activate_vprinter(mouse_coord mX, mouse_coord mY);
void cancel_vprinter(mouse_coord mX, mouse_coord mY);
void key_cancel_vprinter(void);

void vp_up(void);
void vp_down(void);
void vp_left(void);
void vp_right(void);
void vp_pgup(void);
void vp_pgdn(void);
void vp_home(void);
void vp_end(void);
void vp_ctrl_left(void);
void vp_ctrl_right(void);

static keybindings kb =
	   { 
	    activate_vprinter,	/* mouse left */
	    cancel_vprinter,	/* mouse middle */
	    activate_vprinter,	/* mouse right */
	    NULL,		/* INS */
	    NULL,		/* DEL */
	    vp_up,		/* uparrow */
	    vp_down,		/* downarrow */
	    vp_left,		/* leftarrow */
	    vp_right,		/* rightarrow */
	    vp_pgup,		/* pgup */
	    vp_pgdn,		/* pgdn */
	    vp_home,		/* home */
	    vp_end,		/* end */
	    vp_ctrl_left,	/* ctrl_left */
	    vp_ctrl_right,	/* ctrl_right */
	    {
	     NULL,		/* 000 */
	     NULL,		/* ^A */
	     NULL,		/* ^B */
	     ctlc,		/* ^C */
	     NULL,		/* ^D */
	     NULL,		/* ^E */
	     NULL,		/* ^F */
	     NULL,		/* ^G */
	     NULL,		/* ^H */
	     NULL,		/* ^I */
	     NULL,		/* ^J */
	     NULL,		/* ^K */
	     draw_current_screen,/* ^L */
	     ins_key,		/* ^M */
	     NULL,		/* ^N */
	     NULL,		/* ^O */
	     ctlp,		/* ^P */
	     ctlq,		/* ^Q */
	     NULL,		/* ^R */
	     ctls,		/* ^S */
	     NULL,		/* ^T */
	     NULL,		/* ^U */
	     NULL,		/* ^V */
	     NULL,		/* ^W */
	     NULL,		/* ^X */
	     NULL,		/* ^Y */
	     NULL,		/* ^Z */
	     key_cancel_vprinter,/* ^[ */
	     NULL,		/* ^\ */
	     NULL,		/* ^] */
	     NULL,		/* ^^ */
	     NULL		/* ^_ */
	    }
	   };

#define BIGMOVE 10

void draw_vprinter_line(printobj * pr, long line);
void draw_vprinter_screen(boolean clear);

static unsigned char ccpunch = 'þ';
static vprinter_line vline;


/****************************************************************************
*                                 vp_colormap
* Result: void
*       
* Effect: 
*       Maps the colors for the virtual printer display
****************************************************************************/

void vp_colormap()
    {
     switch(video.adapter)
        { /* display types */
	 case _CGA:
		 bglight = BLACK;
		 fglight = WHITE;
		 bgdark  = BLACK;
		 fgdark  = GRAY;
		 break;
	 case _EGA:
		 _remappalette(MAGENTA,_LIGHTGREEN);
		 bglight = MAGENTA;
		 fglight = BLACK;
		 bgdark  = GREEN;
		 fgdark  = BLACK;
		 break;
	 case _VGA:
		 _remappalette(MAGENTA,LT1);
		 _remappalette(GREEN, DK1);
		 bglight = MAGENTA;
		 fglight = BLACK;
		 bgdark  = GREEN;
		 fgdark  = BLACK;
		 break;
	 case _HGC: 
		 fglight = H_INTENSIFIED;
		 bglight = BLACK;
		 fgdark  = H_NORMAL;
		 bgdark  = BLACK;
		 break;
	 default:
		 fglight = H_INTENSIFIED;
		 bglight = BLACK;
		 fgdark  = H_NORMAL;
		 bgdark  = BLACK;
		 break;
	} /* display types */
    }

/****************************************************************************
*                             set_132_column_mode
* Result: boolean
*       true if success
*	false if error
* Effect: 
*       Attempts to set 132 column mode; if successful, returns true
*	and updates video.numtextcols and video.numtextrows
****************************************************************************/

boolean set_132_column_mode()
    {
     unsigned char m;
     unsigned char n;
#define video_mode_132 0x23
     _asm {
           mov ah,0;  /* set video mode */
	   mov al, video_mode_132;  /* ATI 132x25 mode */
	   int 0x10;	/* video interrupt */
	   mov ah, 0x0F;  /* get video mode */
	   int 0x10;	  /* video interrupt */
	   mov n,ah;	 /* save columns */
	   mov m,al;	/* save mode */
	  };

       if( n != 132 || m != video_mode_132)
          { /* failed */
	   standard_video_mode();
	   screen_font_reset();
	   vp_colormap();
	   return false;
	  } /* failed */

       set_color_map();
       screen_font_reset();
       vp_colormap();
       video.numtextcols = n;
       return true;
    }

/****************************************************************************
*                                 vp_lastline
* Inputs:
*       printobj * pr:
* Result: long
*       The last line of the file
****************************************************************************/

long vp_lastline(printobj * pr)
    {
     long where;
     int rv;

     if(pr->prn != NULL)
	{ /* position it */
	 rv = fseek(pr->prn, 0, SEEK_END);
	 if(rv != 0)
	    return 0;
         where = ftell(pr->prn);
	 return (where / sizeof(vprinter_line));
	} /* position it */
     else
        { /* null */
	 return 0;
	} /* null */
    }

/****************************************************************************
*                                    vp_up
* Result: void
*       
* Effect: 
*       Moves page up one line
****************************************************************************/

void vp_up()
    {
     long newtop;
     long where;
     int rv;

     if(top_of_screen_line == 0)
	return;
     newtop =  top_of_screen_line - 1;
     where = newtop * sizeof(vprinter_line);
     rv = fseek(current_printer->prn, where, SEEK_SET);
     if(rv != 0)
        { /* failed */
	} /* failed */
     else
        { /* success */
	 scscroll((unsigned char)1, 
	          (unsigned char)BLACK, 
		  0,0,(unsigned char)video.numtextrows - 1, 
	 		        (unsigned char)video.numtextcols - 1, 
				SCR_DOWN);
	 top_of_screen_line = newtop;
	 draw_vprinter_line(current_printer,
	 			top_of_screen_line);
	} /* success */
    }

/****************************************************************************
*                                   vp_down
* Result: void
*       
* Effect: 
*       Moves page down one line
* Notes:
*	scrolls the current page up by one one, then writes the last line
*	of the page
****************************************************************************/

void vp_down()
    {
     long newtop = top_of_screen_line + 1;
     long where;
     int rv;

     where = newtop * sizeof(vprinter_line);
     rv = fseek(current_printer->prn, where, SEEK_SET);
     if(rv != 0)
        { /* failed */
	} /* failed */
     else
        { /* success */
	 scscroll(1, BLACK, 0,0,(unsigned char)video.numtextrows - 1, 
	 		        (unsigned char)video.numtextcols - 1, 
				SCR_UP);
	 top_of_screen_line = newtop;
	 draw_vprinter_line(current_printer,
	 			top_of_screen_line+video.numtextrows - 1);
	} /* success */
    }

/****************************************************************************
*                                   vp_left
* Result: void
*       
* Effect: 
*       Moves page left one position
****************************************************************************/

void vp_left()
    {
     if(leftmargin == MIN_LEFTMARGIN)
	return;
     leftmargin--;
     draw_vprinter_screen(false);
    }

/****************************************************************************
*                                  vp_right
* Result: void
*       
* Effect: 
*       Moves page right one position
****************************************************************************/

void vp_right()
    {
     if(leftmargin >= MAX_LEFTMARGIN)
	return;
     leftmargin++;
     draw_vprinter_screen(false);
     
    }

/****************************************************************************
*                                   vp_pgup
* Result: void
*       
* Effect: 
*       Moves to previous screen
****************************************************************************/

void vp_pgup()
    {

     if(top_of_screen_line == 0)
	return;

     top_of_screen_line -= BIGMOVE;
     if(top_of_screen_line < 0)
	top_of_screen_line = 0;

     draw_vprinter_screen(false);

    }

/****************************************************************************
*                                   vp_pgdn
* Result: void
*       
* Effect: 
*       Moves to next screen
****************************************************************************/

void vp_pgdn()
    {
     top_of_screen_line += BIGMOVE;
     
     draw_vprinter_screen(false);
    }

/****************************************************************************
*                                   vp_end
* Result: void
*       
* Effect: 
*       Moves to last line on output
****************************************************************************/

void vp_end()
    {
     long last;
     /*
	We don't bother to do any redisplay if the last line of the
	file is on the screen.

	This is true if the current_line (which is the current output
	line) is greater than top_of_screen_line and less than
	top_of_screen_line + numtextrows - 1
     */
     last = vp_lastline(current_printer);
     if(last > top_of_screen_line &&
	last < top_of_screen_line + video.numtextrows - 1)
	return;

     top_of_screen_line = last - video.numtextrows - 1;
     if(top_of_screen_line < 0)
	top_of_screen_line = 0;

     draw_vprinter_screen(false);
     
    }

/****************************************************************************
*                                   vp_home
* Result: void
*       
* Effect: 
*       Moves to first line of output
****************************************************************************/

void vp_home()
    {
     if(top_of_screen_line == 0)
	return;
     top_of_screen_line = 0;
     draw_vprinter_screen(false);
    }

/****************************************************************************
*                                vp_ctrl_left
* Result: void
*       
* Effect: 
*       Moves screen left by BIGMOVE positions
****************************************************************************/

void vp_ctrl_left()
    {
     if(leftmargin <= COLUMN0)
	leftmargin = 0;
     else
     if(leftmargin < COLUMN0 + BIGMOVE)
	leftmargin = COLUMN0;
     else
	leftmargin -= BIGMOVE;

     draw_vprinter_screen(false);
    }

/****************************************************************************
*                                vp_ctrl_right
* Result: void
*       
* Effect: 
*       Moves right by BIGMOVE positions
****************************************************************************/

void vp_ctrl_right()
    {
     if(leftmargin >= MAX_LEFTMARGIN)
	return;

     if(leftmargin < COLUMN0)
	leftmargin = COLUMN0;
     else
	leftmargin += BIGMOVE;

     if(leftmargin > MAX_LEFTMARGIN)
	leftmargin = MAX_LEFTMARGIN;

     draw_vprinter_screen(false);
    }

/****************************************************************************
*                              activate_vprinter
* Inputs:
*       mouse_coord mX:
*	mouse_coord mY:
* Result: void
*       
* Effect: 
*       Handles mouse button down for printer
****************************************************************************/

static void activate_vprinter(mouse_coord mX, mouse_coord mY)
    {
     coord X;
     coord Y;
     coord old_x;
     coord old_y;

     sccurpos(&old_y, &old_x);

     X = mouse_to_screen_x(mX);
     Y = mouse_to_screen_y(mY);

     /*
	Scroll the printer page around
     */

     sccurset(old_y, old_x);
    }

/****************************************************************************
*                               cancel_vprinter
* Inputs:
*       mouse_coord mX: ignored
*	mouse_coord mY: ignored
* Result: void
*       
* Effect: 
*       Cancels the printer screen
****************************************************************************/

void cancel_vprinter(mouse_coord mX, mouse_coord mY)
    {
     prdone = true;
    }

/****************************************************************************
*                             key_cancel_vprinter
* Result: void
*       
* Effect:
*       Cancels the printer screen (called from key stroke)
****************************************************************************/

void key_cancel_vprinter()
    {
     prdone = true;
    }

/****************************************************************************
*                                  maketape
* Inputs:
*       int hi: Hi limit
*	int lo: Low limit
*	int offset: Offset into vector
*	char * cc: carriage control tape vector
* Result: void
*       
* Effect: 
*       Updates the carriage control tape by putting 'ccpunch' characters
*	into the appropriate places
****************************************************************************/

void maketape(int hi, int lo, int offset, char * cc, int line)
    {
     int i;
     int row;

     row=current_printer->form->tape[line % current_printer->form->pagelength];
     for(i=hi; i >= lo; i--)
        { /* build tape */
	 if((row & (1 << i)) != 0)
	    { /* has punch */
	     cc[12 - i + offset] = ccpunch;
	    } /* has punch */
	} /* build tape */
     
    }

/****************************************************************************
*                                 prettytape
* Inputs:
*       int hi: high column
*	int lo: low column
*	int offset: offset into tape
*	unsigned char * cc: Tape
* Result: void
*       
* Effect: 
*       Pretties up a (line mod 3) == 0 carriage tape
****************************************************************************/

void prettytape(int hi, int lo, int offset, unsigned char * cc)
    {
     /*

	        1 1 1
                2 1 0 9 8 7
		Ã Å Å Å Å ´
		þ Å Å Å Å ´
		Ã þ Å Å Å ´
		Ã Å þ Å Å ´
		Ã Å Å þ Å ´
		Ã Å Å Å þ ´
		Ã Å Å Å Å þ

     */
     /* NYI */
    }

/****************************************************************************
*                                cctape_string
* Inputs:
*       short row: Row # of the page
* Result: unsigned char *
*       Representation of the entire carriage control tape
* Effect: 
*       Creates a carriage control tape string
****************************************************************************/

unsigned char * cctape_string(short row)
    {
     /*
	The cctape_codes array declares the shape of the columns.
	The columns are of the form:
	        1 1 1
                2 1 0 9 8 7   6 5 4 3 2 1
		Ã Å Å Å Å ´ Z Ã Å Å Å Å ´

		³ ³ ³ ³ ³ ³ Z ³ ³ ³ ³ ³ ³ 

	The cases are:
		L: Left edge of the column
		I: Interior column
		R: Right edge of column
		Z: Null column

	The carriage tape is encoded as a 12-bit vector, where the
	bit number (shift position) is the column:

         15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
	+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
	|//|//|//|  |  |  |  |  |  |  |  |  |  |  |  |//|
	+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+

     */


     static unsigned char * cctape0 = "ÃÅÅÅÅ´øÃÅÅÅÅ´ ";    /* 0 mod 3 tape */
     static unsigned char * cctape1 = "³³³³³³ø³³³³³³ ";  /* != 0 mod 3 tape */
     static unsigned char cc[20];
     
     if(row % 3 == 0)
        { /* zero */
	 strcpy(cc,cctape0);
	} /* zero */
     else
        { /* nonzero */
	 strcpy(cc,cctape1);
	} /* nonzero */

     maketape(12,7,0,cc,row);
     maketape(6, 1,1,cc,row);
     /*
	At this point we have a tape which has not been "prettied up".
	If we are in a zero-row, we need to fix up the symbols on each
	side of a ccpunch with the appropriate special character.
     */
     prettytape(12,7,0,cc);
     prettytape( 6,1,1,cc);
     
     return cc;
    }

/****************************************************************************
*                             draw_vprinter_line
* Inputs:
*       printobj * pr: Printer object
*	long line: the physical line from the output to be displayed
* Result: void
*       
* Effect: 
*       Draws the line indexed by 'line' on the screen.
* Notes:
*	Assumes that line - top_of_screen_line does not exceed
*	video.maxtextrows
****************************************************************************/

void draw_vprinter_line(printobj * pr, long line)
    {
     /* lines mod 0,1,2 are light green
	lines mod 3,4,5 are dark green
     */
     attrib back;
     attrib fore;
     char text_line[150];
     char adjusted_text_line[150];
     vprinter_line vline;
     char * text;
     coord row;
     coord screen_col;
     coord source_col;
     int rv;
     short width;
     long where;
     int cctape_width;
     int cctape_col;
     unsigned char * cc;

     if (line % 6 < 3)
        { /* light */
	 back = bglight;
	 fore = fglight;
	} /* light */
     else
        { /* dark */
	 back = bgdark;
	 fore = fgdark;
	} /* dark */

     /*
         11111          \  cctape values: 0 if not displayed
         43210987654321 /
	 chnl          Ú---- leftmargin
	 111           ³
	 210987 654321 
	+-------------+-----------------------------------...---------------+
      0	|ÃÅÅÅÅ´øÃÅÅÅÅÅ´ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaa |
      1	|³³³³³³ø³³³³³³³ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaa |
      2	|³³³³³³ø³³³³³³³ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaa |
      3	|ÃÅÅÅÅ´øÃÅÅÅ´þ³ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbb |
      4	|³³³³³³ø³³³³³³³ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbb |
      5	|³³³³³³ø³³³³³³³ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbb |
      :	|ÃÅÅÅÅ´øÃÅÅÅÅÅ´ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbb |
      : ::     ø      :                                                     :
      :	|³³³³³³ø³³³³³³³ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaa |
      :	|³³³þ³³ø³³³³³³³ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb...bbbbbbbbbbbbb |
      : |þ³³³³³ø³³³³³³³                                                     :
     66	|³³³³³³ø³³³³³³³ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...aaaaaaaaaaaaa |
	+-------------+-----------------------------------...---------------+
     */

     
     where = line * sizeof(vprinter_line);

#if full_diagnostics
     if(diagnostics_on)
        { /* report */
	 sprintf(diag_buffer,"vprinter< seek %ld", where);
	 tell(diag_buffer);
	} /* report */
#endif

     rv = fseek(pr->prn, where, SEEK_SET);
     if(rv != 0)
        { /* failed */
	 sprintf(diag_buffer,"vprinter< seek failure prior to read: %s",strerror(errno));
	 text = "";
	} /* failed */
     else
        { /* success */
	 rv = fread(&vline, sizeof(vline), 1, pr->prn);
	 if(rv != 1)
	    { /* failed */
	     if(!feof(pr->prn))
	        { /* error */
		 sprintf(diag_buffer,"vprinter< input failure: %s", strerror(errno));
		 tell(diag_buffer);
		} /* error */
	     text = "";
	    } /* failed */
	 else
	    text = vline.data;
	} /* success */

#if full_diagnostics
     sprintf(diag_buffer,"vprinter< %ld: |%s|", where, text);
     tell(diag_buffer);
#endif


     sprintf(text_line," %-*.*s ",PRINT_LENGTH, PRINT_LENGTH, text);

     row = line - top_of_screen_line;

     sccurset(row,0);

     /*
	'source_col' is the printer data column at which we start. 
     */

     if(leftmargin < COLUMN0)
        { /* output cc tape */
	 /* The x-position of the cursor is left where we want to start */
	 /* If we are showing a carriage tape, the source_col is 0 */
	 source_col = 0;                        /* start a printer column 0 */
	 cc = cctape_string( (short) (line % (long)pr->form->pagelength));
	 cctape_width = (COLUMN0 - leftmargin);
	 cctape_col = leftmargin;
	 screen_col = cctape_width;
	} /* output cc tape */
     else
        { /* no cc tape */
	 cc = "";
	 cctape_col = 0;
	 screen_col = 0;
	 cctape_width = 0;
	 source_col = leftmargin - COLUMN0;
	} /* no cc tape */

     /*
	The value of 'width' is the width of the printer that is visible
	on the screen.  Note that if leftmargin is < COLUMN0, width is
	always less than numtextcols
	'cctape' is the number of right-hand columns of the carriage
	control tape that will be displayed.
     */

     width = video.numtextcols - cctape_width;
	 
     sprintf(adjusted_text_line,"%-*.*s",width,width,&text_line[source_col]);

     if(line > 1 && (line + 1) % pr->form->pagelength == 0)
        { /* draw perfs */
	 short i;
	 /*
	    The "perf line" is drawn as a series of alternating underscores
	    (which are not in the 1401 set).  Only spaces are drawn this
	    way, but since the last line is almost always blank this will give
	    the illusion of perfs
	 */
	 for(i=0; adjusted_text_line[i] != '\0'; i++)
	    if(adjusted_text_line[i] == ' ' && (i & 1))  /* underlines on odd positions */
	       adjusted_text_line[i] = char_perf;
	} /* draw perfs */

     if(cctape_width > 0)
	scdspmsg(row,0,BLACK, CYAN, &cc[cctape_col]);
     
     scdspmsg(row, screen_col, BLACK, back, adjusted_text_line);
    }

/****************************************************************************
*                            draw_vprinter_screen
* Inputs:
*       boolean clear: true to clear screen, false to non-clear screen
* Result: void
*       
* Effect: 
*       Redraws the screen
****************************************************************************/

void draw_vprinter_screen(boolean clear)
    {
     coord x;
     coord y;
     long i;

     sccurpos(&y, &x);
     hide_mouse_cursor();

     sccurset(0,0);
     if(clear)
	_clearscreen(_GCLEARSCREEN);
     
     for(i = top_of_screen_line; i < top_of_screen_line + video.numtextrows; i++)
        { /* draw all */
	 draw_vprinter_line(current_printer,i);
	} /* draw all */

     sccurset(y,x);
     show_mouse_cursor();
    }

/****************************************************************************
*                                draw_vprinter
* Result: void
*       
* Effect: 
*       Draws the virtual printer screen.  This draws the screen based
*	on the current top of screen line and is used for both refresh
*	and initial display
****************************************************************************/

void draw_vprinter()
    {
     draw_vprinter_screen(true);
    }

/****************************************************************************
*                                  vprinter
* Result: void
*       
* Effect: 
*       Handles the virtual printer display.  This is the top-level
*	procedure called to display the virtual printer
****************************************************************************/

void vprinter()
    {
     short old_display;
     coord x;
     coord y;
     boolean need_reset = false;


     if(current_printer->prn == NULL)
	return;

     sccurpos(&y, &x);

     clear_off();

     old_display = set_display(VPRINTER);

     tell("--------Starting vprinter display--------");

     /* 
	Compute the top of screen line to be the last line of the
	file minus the screen height, or 0 if the computed value
	is negative (meaning less than one screen has been printed)
     */

     {
      long curpos;
      long endpos;
      long tos;

      curpos = ftell(current_printer->prn);
      fseek(current_printer->prn,0,SEEK_END);
      endpos = ftell(current_printer->prn);
      fseek(current_printer->prn,curpos,SEEK_SET);
      tos = endpos / sizeof(vprinter_line) - video.numtextrows;

      if(tos < 0)
	 tos = 0;
      top_of_screen_line = tos;
     }

     if(mode132)
	need_reset = set_132_column_mode();

     draw_current_screen();

     show_mouse_cursor();

     prdone = false;

     scan(&prdone, &kb, false);

     clear_off();

     if(need_reset)
        { /* reset it */
	 standard_video_mode();
	 screen_font_reset();
	} /* reset it */

     tell("--------Vprinter display complete--------");
     set_display(old_display);
     draw_current_screen();
     sccurset(y,x);
    }

/****************************************************************************
*                            virtual_printer_init
* Inputs:
*       printobj * pr:
*	boolean test:
* Result: boolean
*       true if successful
*	false if error
* Effect: 
*       Initializes the virtual printer module
* Preconditions:
*	virtual_printer_open has already been called and pr->prn is valid
****************************************************************************/

boolean virtual_printer_init(printobj * pr, boolean test)
    {
     int i;
     boolean rv;

     /* initialize to identity map */

     for(i = 0; i < 256; i++)
	screenmap[i] = i;

     vp_colormap();

     rv = generic_printer_init(pr,test);
     if(!rv)
	return false;

     screenmap[CHAR_GM] = char_gm;
     screenmap[CHAR_WM] = char_wm;
     screenmap[CHAR_RM] = char_rm;
     screenmap[CHAR_SM] = char_sm;
     screenmap[CHAR_DL] = char_dl;
     screenmap[CHAR_SQ] = char_sq;

     return true;
    }

/****************************************************************************
*                                   vp_open
* Inputs:
*       printobj * pr: Print object to open
*	boolean truncate: true to truncate, false to append
* Result: boolean
*       true if success
*	false if error
* Effect: 
*       Opens the virtual printer file
****************************************************************************/

boolean vp_open(printobj * pr, boolean truncate)
    {
     if(truncate)
        { /* truncate */
	 pr->prn = fopen(vlpt,"w+");
	 pr->current_line = 0;
	} /* truncate */
     else
        { /* append */
	 long where;
	 pr->prn = fopen(vlpt,"a+b");
	 if(pr->prn != NULL)
	    pr->current_line = vp_lastline(pr) % pr->form->pagelength;
	} /* append */

     if(pr->prn == NULL)
        { /* open failed */
	 tell("Virtual printer open failed");
	 return false;
	} /* open failed */

     /* do whatever it takes to complete initialization (if anything) */

     skip_to_channel(pr,1);

     return true;
    }

/****************************************************************************
*                            virtual_printer_open
* Inputs:
*       printobj * pr: Printer object
* Result: boolean
*       true if successful
*	false if error
* Effect: 
*       Opens the printer device if it is not opened; otherwise just
*	returns true
****************************************************************************/

boolean virtual_printer_open(printobj * pr)
    {
     if(pr->prn != NULL)
	return true;

     return vp_open(pr,false);
    }

/****************************************************************************
*                           virtual_printer_newline
* Inputs:
*       printobj * pr: Printer object
* Result: void
*       
* Effect: 
*       Performs a newline operation by taking the printer buffer and
*	writing to the output file
****************************************************************************/

void virtual_printer_newline(printobj * pr)
    {
     int rv;
     int i;

     /*
	We append the output to the virtual printer's output file,
	so we can retrieve it later for display
     */

     if(pr->prn == NULL)
	return;

     rv = fseek(pr->prn, 0, SEEK_END);
     if(rv != 0)
        { /* failed */
	 sprintf(diag_buffer,"vprinter> seek failure prior to output: %s", strerror(errno));
	 tell(diag_buffer);
	 return;
	} /* failed */

#if full_diagnostics
     sprintf(diag_buffer,"vprinter> %ld: |%s|", ftell(pr->prn), vline.data);
     tell(diag_buffer);
#endif

     rv = fwrite(&vline, sizeof(vline), 1, pr->prn);

     if(rv != 1)
        { /* rv */
	 sprintf(diag_buffer,"vprinter> write failure: %s", strerror(errno));
	 tell(diag_buffer);
	} /* rv */

     /*
	We have written the line; clear the buffer.
     */

     for(i = 0; i < sizeof(vline.data); i++)
	vline.data[i] = '\0';
     
     printer_upline(pr);
    }

/****************************************************************************
*                            virtual_printer_flush
* Inputs:
*       printobj * pr: Printer object to flush
* Result: int
*       0, always
* Effect: 
*       Does nothing on virtual printers.
****************************************************************************/

int virtual_printer_flush(printobj * pr)
    {
     return 0; /* nothing to be done for vprinter */
    }

/****************************************************************************
*                            virtual_printer_ready
* Inputs:
*       printobj * pr: Printer object
* Result: boolean
*       true, always
* Notes: 
*       Virtual printer is always ready
****************************************************************************/

boolean virtual_printer_ready(printobj * pr)
    {
     return true; /* vprinter always ready */
    }

/****************************************************************************
*                          virtual_write_translated
* Inputs:
*       unsigned char * str: text to be written 
*	printobj * prn: print object
* Result: void
*       
* Effect: 
*       Translates to screen characters and outputs to file
* Notes:
*	Always writes out a full-size buffer so we can readily page back&forth
*	in the listing just by using multiples of the record size to seek.
****************************************************************************/

void virtual_write_translated(unsigned char * str, printobj * pr)
    {
     int i;
     int rv;

     if(pr->prn == NULL)
	return;
     for(i=0; i < sizeof(vline.data) - 1 && str[i] != '\0'; i++)
        { /* translate */
	 vline.data[i] = screenmap[str[i]];
	} /* translate */

     /*
	Pad to full width with BCD spaces...
     */
     for(; i < sizeof(vline.data); i++)
	vline.data[i] = '\0';
     
    }

/****************************************************************************
*                           virtual_printer_printf
* Inputs:
*       printobj * pr: Printer object
*	char * fmt: Format string
*	... parameters to printf
* Result: int
*       Number of characters written
* Effect: 
*       Writes the string to the printer device
****************************************************************************/

int virtual_printer_printf(printobj * pr, char * fmt, ...)
    {
     return 0; /* NYI */
    }

/****************************************************************************
*                            virtual_printer_final
* Inputs:
*       printobj * pr: Printer object
* Result: void
*       
* Effect: 
*       Finalizes printer
****************************************************************************/

void virtual_printer_final(printobj * pr)
    {
     /* NYI */
    }

/****************************************************************************
*                            virtual_printer_close
* Inputs:
*       printobj * pr: Printer object
* Result: void
*       
* Effect: 
*       Closes the printer object
****************************************************************************/

void virtual_printer_close(printobj * pr)
    {
     if(pr->prn == NULL)
	return;
     fclose(pr->prn);
     /* NYI: unlink the file */
     pr->prn = NULL;
    }

/****************************************************************************
*                            virtual_printer_eject
* Inputs:
*       printobj * pr: Printer object
* Result: void
*       
* Effect: 
*       Ejects the printer page (virtual printers handle this differently)
****************************************************************************/

void virtual_printer_eject(printobj * pr)
    {
     /* Does nothing */
    }

/****************************************************************************
*                                virtual_test
* Result: boolean
*       true, always
****************************************************************************/

boolean virtual_test()
    {
     return true;
    }

/****************************************************************************
*			     virtual_printer_clear
* Inputs:
*       printobj * pr: Printer object
* Result: void
*       
* Effect: 
*       Truncates the file back to 0
****************************************************************************/

void virtual_printer_clear(printobj * pr)
    {
     if(pr->prn != NULL)
        { /* close printer */
	 virtual_printer_close(pr);
	} /* close printer */

     vp_open(pr,true);
    }
