BEAV (10/10) ver 1.2 Binary File Editor, new release
Peter Reilley
pvr at wang.com
Thu Jun 6 23:31:55 AEST 1991
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 10 (of 10)."
# Contents: display.c
# Wrapped by pvr at elf on Thu Mar 14 08:16:48 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'display.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'display.c'\"
else
echo shar: Extracting \"'display.c'\" \(49122 characters\)
sed "s/^X//" >'display.c' <<'END_OF_FILE'
X/*
X* The functions in this file handle redisplay. The
X* redisplay system knows almost nothing about the editing
X* process; the editing functions do, however, set some
X* hints to eliminate a lot of the grinding. There is more
X* that can be done; the "vtputc" interface is a real
X* pig. Two conditional compilation flags; the GOSLING
X* flag enables dynamic programming redisplay, using the
X* algorithm published by Jim Gosling in SIGOA. The MEMMAP
X* changes things around for memory mapped video. With
X* both off, the terminal is a VT52.
X*/
X
X#define LINT_ARGS 1 /* enable lint type checking */
X#include "def.h"
X
XD32 get_long ();
XD16 get_int ();
Xvoid writ_echo ();
Xvoid uline ();
Xvoid ucopy ();
Xvoid modeline ();
Xvoid bin_to_text ();
Xuint fill_buf ();
Xuint get_currow ();
Xuint get_curcol ();
X#if MSDOS
Xvoid mem_line ();
X#endif
X
X extern char MSG_prn_to[];
X extern char MSG_disp_r_n[];
X extern char MSG_11lX[];
X extern char MSG_11lo[];
X extern char MSG_11ld[];
X extern char MSG_03o[];
X extern char MSG_06o[];
X extern char MSG_011lo[];
X extern char MSG_03u[];
X extern char MSG_05u[];
X extern char MSG_010lu[];
X extern char MSG_02X[];
X extern char MSG_04X[];
X extern char MSG_08lX[];
X extern char MSG_prog_name[];
X extern char MSG_disp_b_lst[];
X extern char MSG_file[];
X extern char MSG_RO[];
X extern char MSG_WL[];
X extern char MSG_RW[];
X extern char MSG_AU[];
X extern char MSG_NOT_AU[];
X extern char MSG_curs_asc[];
X extern char MSG_curs_ebc[];
X extern char MSG_curs_hex[];
X extern char MSG_curs_bin[];
X extern char MSG_curs_dec[];
X extern char MSG_curs_oct[];
X extern char MSG_siz_8[];
X extern char MSG_siz_16[];
X extern char MSG_siz_32[];
X extern char MSG_siz_null[];
X extern char MSG_int_shift[];
X extern char MSG_mot_shift[];
X extern char MSG_print1[];
X extern char MSG_print2[];
X#if RUNCHK
X extern char ERR_disp_1[];
X extern char ERR_disp_2[];
X extern char ERR_disp_3[];
X extern char ERR_disp_4[];
X extern char ERR_disp_5[];
X extern char ERR_disp_6[];
X#endif
X
X#include "lintfunc.dec"
Xextern char ebcdic_table[];
X
Xextern bool mem_map;
X
X/*
X* You can change these back to the types
X* implied by the name if you get tight for space. If you
X* make both of them "int" you get better code on the VAX.
X* They do nothing if this is not Gosling redisplay, except
X* for change the size of a structure that isn't used.
X* A bit of a cheat.
X*/
X#define XCHAR int
X#define XSHORT int
X
X/*
X* A video structure always holds
X* an array of characters whose length is equal to
X* the longest line possible. Only some of this is
X* used if "ncol" isn't the same as "NCOL".
X*/
Xtypedef struct vid
X{
X short v_hash; /* Hash code, for compares. */
X short v_flag; /* Flag word. */
X short v_color; /* Color of the line. */
X XSHORT v_cost; /* Cost of display. */
X char v_text[NCOL]; /* The actual characters. */
X} VIDEO;
X
X#define VFCHG 0x0001 /* Changed. */
X#define VFHBAD 0x0002 /* Hash and cost are bad. */
X
X/*
X* SCORE structures hold the optimal
X* trace trajectory, and the cost of redisplay, when
X* the dynamic programming redisplay code is used.
X* If no fancy redisplay, this isn't used. The trace index
X* fields can be "char", and the score a "short", but
X* this makes the code worse on the VAX.
X*/
Xtypedef struct
X{
X XCHAR s_itrace; /* "i" index for track back. */
X XCHAR s_jtrace; /* "j" index for trace back. */
X XSHORT s_cost; /* Display cost. */
X} SCORE;
X
Xint sgarbf = TRUE; /* TRUE if screen is garbage. */
Xint vtrow = 0; /* Virtual cursor row. */
Xint vtcol = 0; /* Virtual cursor column. */
Xint tthue = CNONE; /* Current color. */
Xint ttrow = HUGE; /* Physical cursor row. */
Xint ttcol = HUGE; /* Physical cursor column. */
Xint tttop = HUGE; /* Top of scroll region. */
Xint ttbot = HUGE; /* Bottom of scroll region. */
Xchar file_off_bad = FALSE; /* Have file offsets been changed */
X
XVIDEO * vscreen[NROW]; /* Edge vector, virtual. */
XVIDEO * pscreen[NROW]; /* Edge vector, physical. */
XVIDEO video[2 * (NROW)]; /* Actual screen data. */
XVIDEO blanks; /* Blank line image. */
X
X#if GOSLING
X/*
X* This matrix is written as an array because
X* we do funny things in the "setscores" routine, which
X* is very compute intensive, to make the subscripts go away.
X* It would be "SCORE score[NROW][NROW]" in old speak.
X* Look at "setscores" to understand what is up.
X*/
XSCORE score[NROW * NROW];
X#endif
X
X/*
X* Initialize the data structures used
X* by the display code. The edge vectors used
X* to access the screens are set up. The operating
X* system's terminal I/O channel is set up. Fill the
X* "blanks" array with ASCII blanks. The rest is done
X* at compile time. The original window is marked
X* as needing full update, and the physical screen
X* is marked as garbage, so all the right stuff happens
X* on the first call to redisplay.
X*/
Xvoid vtinit ()
X{
X register VIDEO * vp;
X register int i;
X
X ttopen ();
X ttinit ();
X vp = &video[0];
X for (i = 0; i < NROW; ++i)
X {
X vscreen[i] = vp;
X ++vp;
X pscreen[i] = vp;
X ++vp;
X }
X blanks.v_color = CTEXT;
X for (i = 0; i < NCOL; ++i)
X blanks.v_text[i] = ' ';
X}
X
X/*
X* Tidy up the virtual display system
X* in anticipation of a return back to the host
X* operating system. Right now all we do is position
X* the cursor to the last line, erase the line, and
X* close the terminal channel.
X*/
Xvoid vttidy ()
X{
X ttcolor (CTEXT);
X ttnowindow (); /* No scroll window. */
X ttmove (nrow - 1, 0); /* Echo line. */
X tteeol ();
X tttidy ();
X ttflush ();
X ttclose ();
X}
X
X/*
X* Move the virtual cursor to an origin
X* 0 spot on the virtual display screen. I could
X* store the column as a character pointer to the spot
X* on the line, which would make "vtputc" a little bit
X* more efficient. No checking for errors.
X*/
Xvoid vtmove (row, col)
X{
X vtrow = row;
X vtcol = col;
X}
X
X/*
X* Write a character to the virtual display,
X* dealing with long lines and the display of unprintable
X* things like control characters. Also expand tabs every 8
X* columns. This code only puts printing characters into
X* the virtual display image. Special care must be taken when
X* expanding tabs. On a screen whose width is not a multiple
X* of 8, it is possible for the virtual cursor to hit the
X* right margin before the next tab stop is reached. This
X* makes the tab code loop if you are not careful.
X* Three guesses how we found this.
X*/
Xvoid vtputc (c)
Xregister int c;
X{
X register VIDEO * vp;
X
X vp = vscreen[vtrow];
X if (vtcol >= ncol)
X vp -> v_text[ncol - 1] = '$';
X else
X if (ISCTRL (c) != FALSE)
X {
X vtputc ('^');
X vtputc (c ^ 0x40);
X }
X else
X {
X vp -> v_text[vtcol] = c;
X vtcol++;
X }
X
X}
X/*
X* Write an entire screen line in the correct format. pvr
X*
X* This code only puts printing characters into
X* the virtual display image.
X* Return TRUE if something was printed to the line.
X*/
X#define REGI register
Xbool vtputd (wp, row)
XWINDOW * wp;
Xint row; /* line # to print to v screen */
X
X{
X REGI VIDEO * vp;
X REGI char *lin_ptr,
X ch,
X mode;
X REGI A32 row_offst;
X REGI uint chrs_per_row,
X lin_offset,
X crs,
X i,
X j,
X k,
X chrs_in_lin;
X LINE * cur_line;
X REGI uchar tempc;
X REGI D32 temp_long;
X REGI D16 temp_int;
X static char w_buf[128]; /* temp buffer for data */
X REGI char *fmnt_ptr; /* pointer to format structure */
X
X vp = vscreen[vtrow]; /* point to VIDEO structure to print into */
X mode = R_TYPE(wp); /* get type of format structure */
X
X /* get number of bytes per row */
X chrs_per_row = R_BYTES(wp);
X
X /* determine the offset from begining of the buffer for this line */
X row_offst = WIND_POS(wp) + (row * chrs_per_row);
X
X /* search for and point to first character in buffer to be printed */
X cur_line = wp -> w_linep; /* start with current first window line */
X while (TRUE)
X { /* find line with desired character */
X if (cur_line == wp -> w_bufp -> b_linep)
X { /* at end of buffer? */
X return (FALSE);
X }
X if (cur_line -> l_file_offset > row_offst)
X {
X /* if less than current line */
X cur_line = cur_line -> l_bp;/* step back */
X }
X else
X if ((cur_line -> l_file_offset + cur_line -> l_used) <= row_offst)
X {
X cur_line = cur_line -> l_fp;/* step ahead */
X }
X else
X break;
X }
X
X lin_offset = row_offst - cur_line -> l_file_offset;/* offset into line */
X
X /* get index into the current line to start reading the current row's data */
X /* copy line text into buffer */
X chrs_in_lin = fill_buf (wp, cur_line, lin_offset, w_buf, chrs_per_row);
X
X /* limit line length to screen width, used in TEXT mode only */
X if (chrs_in_lin > NCOL)
X chrs_in_lin = NCOL;
X
X /* Clear the line to spaces */
X for (i = 0; i < NCOL; i++)
X {
X vp -> v_text[i] = ' ';
X }
X switch (mode)
X {
X case TEXT:
X break;
X case ASCII:
X case EBCDIC:
X case BINARY:
X case HEX:
X /* print the row offset from the start of the file in HEX */
X sprintf (vp -> v_text, MSG_11lX, row_offst);/* to vid buf */
X break;
X case OCTAL:
X /* print the row offset from the start of the file */
X sprintf (vp -> v_text, MSG_11lo, row_offst);/* to vid buf */
X break;
X case DECIMAL:
X /* print the row offset from the start of the file */
X sprintf (vp -> v_text, MSG_11ld, row_offst);/* to vid buf */
X break;
X#if RUNCHK
X default:
X writ_echo (ERR_disp_1);
X break;
X#endif
X }
X
X /* print the binary data to the text line */
X bin_to_text (w_buf, vp -> v_text, chrs_in_lin,
X wp -> w_fmt_ptr);
X
X vtcol = NCOL;
X return (TRUE);
X}
X
X/*
X* Print the contents of then binary data buffer bin_buf
X* into the proper mode of text into txt_buf.
X* Process 'len' bytes.
X*
X* input:
X* bin_buf pointer to buffer of binary data to process.
X* txt_buf pointer to output buffer to print text result into.
X* len length in bytes of data in bin_buf to process.
X* fmt_ptr pointer to a ROW_FMT to use to format the data
X* conversion and printing process.
X* output:
X* none.
X*/
X
Xvoid bin_to_text (bin_buf, txt_buf, len, fmt_ptr)
X
Xchar *bin_buf,
X *txt_buf;
Xuint len;
XROW_FMT *fmt_ptr;
X
X{
X uchar i,
X ch,
X k,
X j,
X mode,
X size,
X *posn;
X uint temp_int;
X ulong temp_long;
X
X mode = fmt_ptr -> r_type; /* get type of format structure */
X size = fmt_ptr -> r_size; /* get size of format structure */
X posn = fmt_ptr -> r_positions;/* pointer to array of display positions */
X
X switch (mode)
X {
X case TEXT:
X case ASCII:
X for (i = 0; i < len; i++)
X {
X ch = bin_buf[i];
X if ((ch >= ' ') && (ch < 0x7f))
X txt_buf[posn[0] + i] = ch;
X else
X txt_buf[posn[0] + i] = '.';
X }
X break;
X
X case EBCDIC:
X for (i = 0; i < len; i++)
X {
X txt_buf[posn[0] + i] =
X 0xff & ebcdic_table[0xff & bin_buf[i]];
X }
X break;
X
X case OCTAL:
X switch (size)
X {
X case BYTES: /* print octal bytes */
X for (i = 0; i < len; i++)
X {
X sprintf (&txt_buf[
X posn[i]], MSG_03o, 0xff & bin_buf[i]);
X }
X break;
X
X case WORDS: /* print octal words */
X k = 0;
X for (i = 0; i < len;
X i += 2)
X {
X temp_int = get_int (&bin_buf[i]);
X sprintf (&txt_buf[posn[k++]], MSG_06o, temp_int);
X }
X break;
X
X case DWORDS: /* print octal double words */
X k = 0;
X for (i = 0; i < len;
X i += 4)
X {
X temp_long = get_long (&bin_buf[i]);
X sprintf (&txt_buf[posn[k++]], MSG_011lo, temp_long);
X }
X break;
X }
X break;
X
X case DECIMAL:
X switch (size)
X {
X case BYTES: /* print decimal bytes */
X for (i = 0; i < len; i++)
X {
X sprintf (&txt_buf[
X posn[i]], MSG_03u, 0xff & bin_buf[i]);
X }
X break;
X
X case WORDS: /* print decimal words */
X k = 0;
X for (i = 0; i < len;
X i += 2)
X {
X temp_int = get_int (&bin_buf[i]);
X sprintf (&txt_buf[
X posn[k++]], MSG_05u, temp_int);
X }
X break;
X
X case DWORDS: /* print decimal double words */
X k = 0;
X for (i = 0; i < len; i += 4)
X {
X temp_long = get_long (&bin_buf[i]);
X sprintf (&txt_buf[
X posn[k++]], MSG_010lu, temp_long);
X }
X break;
X }
X break;
X
X case HEX:
X switch (size)
X {
X case BYTES: /* print hex bytes and ascii chars */
X for (i = 0; i < len; i++)
X {
X if ((bin_buf[i] >= ' ') && (bin_buf[i] < 0x7f))
X txt_buf[posn[i + 16]] = 0xff & bin_buf[i];
X else
X txt_buf[posn[i + 16]] = '.';
X sprintf (&txt_buf[posn[i]], MSG_02X, 0xff & bin_buf[i]);
X }
X break;
X
X case WORDS: /* print hex words */
X k = 0;
X for (i = 0; i < len; i += 2)
X {
X temp_int = get_int (&bin_buf[i]);
X sprintf (&txt_buf[
X posn[k++]], MSG_04X, temp_int);
X }
X break;
X
X case DWORDS: /* print hex double words */
X k = 0;
X for (i = 0; i < len; i += 4)
X {
X temp_long = get_long (&bin_buf[i]);
X sprintf (&txt_buf[
X posn[k++]], MSG_08lX, temp_long);
X }
X break;
X }
X break;
X
X case BINARY:
X switch (size)
X {
X case BYTES: /* print binary bits */
X for (i = 0; i < len; i++)
X {
X ch = bin_buf[i];/* get char to convert */
X for (k = 0; k < 8; k++)
X {
X if (ch & 0x80)
X txt_buf[posn[i] + k]
X = '1';
X else
X txt_buf[posn[i] + k]
X = '0';
X ch = ch << 1;/* slide next bit into place */
X }
X }
X break;
X
X case WORDS:
X j = 0;
X for (i = 0; i < len; i += 2)
X {
X temp_int = get_int (&bin_buf[i]);
X
X for (k = 0; k < 16; k++)
X {
X if (temp_int & 0x8000)
X txt_buf[posn[j] + k]
X = '1';
X else
X txt_buf[posn[j] + k]
X = '0';
X temp_int = temp_int << 1;
X /* slide next bit into place */
X }
X j++;
X }
X break;
X
X case DWORDS:
X j = 0;
X for (i = 0; i < len; i += 4)
X {
X temp_long = get_long (&bin_buf[i]);
X for (k = 0; k < 32; k++)
X {
X if (temp_long & 0x80000000)
X txt_buf[posn[j] + k]
X = '1';
X else
X txt_buf[posn[j] + k]
X = '0';
X temp_long = temp_long << 1;
X /* slide next bit into place */
X }
X j++;
X }
X break;
X }
X break;
X#if RUNCHK
X default:
X writ_echo (ERR_disp_2);
X break;
X#endif
X }
X len *= (fmt_ptr -> r_chr_per_u + 1);
X /* Clean up any garbage characters left by the sprintf's */
X for (i = 0; i < NCOL; i++)
X {
X if (txt_buf[i] == 0)
X txt_buf[i] = ' ';
X }
X}
X
X/*
X* Get an int from the buffer.
X* Perform the Intel byte shuffle if necessary
X*/
X
XD16 get_int (w_buf)
Xuchar *w_buf;
X
X{
X int temp_int;
X
X if (curwp -> w_intel_mode)
X {
X temp_int = 0xff & w_buf[1];
X temp_int <<= 8;
X temp_int |= 0xff & w_buf[0];
X }
X else
X {
X temp_int = 0xff & w_buf[0];
X temp_int <<= 8;
X temp_int |= 0xff & w_buf[1];
X }
X return (temp_int);
X}
X
X/*
X* Get an long from the buffer.
X* Perform the Intel byte shuffle if necessary
X*/
X
XD32 get_long (w_buf)
Xuchar *w_buf;
X
X{
X long temp_long;
X
X if (curwp -> w_intel_mode)
X {
X temp_long = 0xff & w_buf[3];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[2];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[1];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[0];
X }
X else
X {
X temp_long = 0xff & w_buf[0];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[1];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[2];
X temp_long <<= 8;
X temp_long |= 0xff & w_buf[3];
X }
X return (temp_long);
X}
X
X
X/*
X* Copy a length of bytes from the buffer LINEs into the designated
X* buffer. If the current LINE does not have enough bytes then
X* advance to the next. Return the actual number of bytes copied.
X* The number copied would be less than the number requested if
X* end of file is reached.
X*/
X
Xuint fill_buf (wp, lin, lin_off, w_buff, cnt)
XWINDOW *wp;
XLINE *lin;
Xuint lin_off,
X cnt;
Xchar *w_buff;
X{
X REGI uint src,
X dest,
X i;
X
X src = lin_off; /* initialize source line index */
X dest = 0; /* initialize destination buffer index */
X
X while (TRUE)
X {
X while (src < lin -> l_used)
X {
X w_buff[dest++] = lin -> l_text[src++];/* copy byte */
X
X if (dest == cnt)
X { /* if done */
X return (cnt); /* then leave */
X }
X }
X if (R_TYPE(curwp) == TEXT)
X return (dest); /* in text mode don't advance to next line */
X
X lin = lin -> l_fp; /* move to the next line */
X if (lin == wp -> w_bufp -> b_linep)
X { /* if past last line */
X {
X for (i = dest; i < cnt; ++i)
X w_buff[i] = 0;/* fill rest of buffer with zeros */
X return (dest); /* return number of chars copied */
X }
X }
X src = 0; /* start next LINE at first byte */
X }
X}
X
X/*
X* Erase from the end of the
X* software cursor to the end of the
X* line on which the software cursor is
X* located. The display routines will decide
X* if a hardware erase to end of line command
X* should be used to display this.
X*/
Xvoid vteeol ()
X{
X register VIDEO * vp;
X
X vp = vscreen[vtrow];
X while (vtcol < ncol)
X vp -> v_text[vtcol++] = ' ';
X}
X
X/*
X* Make sure that the display is
X* right. This is a three part process. First,
X* scan through all of the windows looking for dirty
X* ones. Check the framing, and refresh the screen.
X* Second, make the
X* virtual and physical screens the same.
X*/
Xvoid update ()
X{
X register LINE * lp;
X register WINDOW * wp;
X register VIDEO * vp1;
X register VIDEO * vp2;
X register uint i;
X register int j,
X k;
X register int c;
X register int hflag;
X register int offs;
X register int size;
X
X hflag = FALSE; /* Not hard. */
X wp = wheadp;
X while (wp != NULL)
X {
X /* is this window to be displayed in linked mode */
X if ((curbp -> b_flag & BFLINK) &&
X (wp -> w_bufp == curbp))
X { /* move dot to current window's dot position */
X wp -> w_dotp = curwp -> w_dotp;
X wp -> w_doto = curwp -> w_doto;
X move_ptr (wp, 0L, TRUE, TRUE, TRUE); /* insure dot is aligned */
X wind_on_dot (wp); /* move window to new dot position */
X }
X
X if (wp -> w_flag != 0)
X
X { /* Need update. */
X move_ptr (wp, 0L, FALSE, TRUE, TRUE); /* window on row boundary */
X move_ptr (wp, 0L, TRUE, TRUE, TRUE); /* dot on unit boundary */
X if ((wp -> w_flag & WFFORCE) == 0)
X {
X wind_on_dot (wp);/* position window on dot */
X }
X i = get_currow (wp); /* Redo this one line, mabey. */
X if ((wp -> w_flag & ~WFMODE) == WFEDIT)
X {
X vscreen[i] -> v_color = CTEXT;
X vscreen[i] -> v_flag |= (VFCHG | VFHBAD);
X vtmove (i, 0);
X vtputd (wp, i - wp -> w_toprow);/* print line to the screen */
X }
X else
X if ((wp -> w_flag & ~WFMODE) == WFMOVE)
X {
X while (i < wp -> w_toprow + wp -> w_ntrows)
X {
X /* paint entire window */
X vscreen[i] -> v_color = CTEXT;
X vscreen[i] -> v_flag |= (VFCHG | VFHBAD);
X vtmove (i, 0);
X /* print line to the screen */
X if (!vtputd (wp, i - wp -> w_toprow))
X vteeol ();
X ++i;
X }
X }
X else
X if ((wp -> w_flag & (WFEDIT | WFHARD)) != 0)
X {
X hflag = TRUE;
X i = wp -> w_toprow;
X while (i < wp -> w_toprow + wp -> w_ntrows)
X {
X /* paint entire window */
X vscreen[i] -> v_color = CTEXT;
X vscreen[i] -> v_flag |= (VFCHG | VFHBAD);
X vtmove (i, 0);
X /* print line to the screen */
X if (!vtputd (wp, i - wp -> w_toprow))
X vteeol ();
X ++i;
X }
X }
X if ((wp -> w_flag & WFMODE) ||
X (wp -> w_flag & WFMOVE) ||
X (wp -> w_flag & WFHARD))
X modeline (wp);
X wp -> w_flag = 0;
X }
X wp = wp -> w_wndp;
X }
X if (sgarbf != FALSE)
X { /* Screen is garbage. */
X sgarbf = FALSE; /* Erase-page clears */
X epresf = FALSE; /* the message area. */
X tttop = HUGE; /* Forget where you set */
X ttbot = HUGE; /* scroll region. */
X tthue = CNONE; /* Color unknown. */
X ttmove (0, 0);
X tteeop ();
X#if MSDOS
X if (mem_map)
X {
X for (i = 0; i < nrow; ++i)
X {
X mem_line (i, vscreen[i]);
X }
X }
X else
X {
X#endif
X for (i = 0; i < nrow; ++i)
X {
X uline (i, vscreen[i], &blanks);
X ucopy (vscreen[i], pscreen[i]);
X }
X#if MSDOS
X }
X#endif
X ttmove (get_currow (curwp), get_curcol (curwp));
X ttflush ();
X return;
X }
X#if GOSLING
X if (hflag != FALSE)
X { /* Hard update? */
X for (i = 0; i < nrow; ++i)
X { /* Compute hash data. */
X hash (vscreen[i]);
X hash (pscreen[i]);
X }
X offs = 0; /* Get top match. */
X while (offs != nrow)
X {
X vp1 = vscreen[offs];
X vp2 = pscreen[offs];
X if (vp1 -> v_color != vp2 -> v_color
X || vp1 -> v_hash != vp2 -> v_hash)
X break;
X#if MSDOS
X if (mem_map)
X mem_line (offs, vp1);
X else
X#endif
X {
X uline (offs, vp1, vp2);
X ucopy (vp1, vp2);
X }
X ++offs;
X }
X if (offs == nrow - 1)
X { /* Might get it all. */
X ttmove (get_currow (curwp), get_curcol (curwp));
X ttflush ();
X return;
X }
X size = nrow; /* Get bottom match. */
X while (size != offs)
X {
X vp1 = vscreen[size - 1];
X vp2 = pscreen[size - 1];
X if (vp1 -> v_color != vp2 -> v_color
X || vp1 -> v_hash != vp2 -> v_hash)
X break;
X#if MSDOS
X if (mem_map)
X mem_line (size - 1, vp1);
X else
X#endif
X {
X uline (size - 1, vp1, vp2);
X ucopy (vp1, vp2);
X }
X --size;
X }
X if ((size -= offs) == 0)/* Get screen size. */
X abort ();
X setscores (offs, size); /* Do hard update. */
X traceback (offs, size, size, size);
X for (i = 0; i < size; ++i)
X ucopy (vscreen[offs + i], pscreen[offs + i]);
X ttmove (get_currow (curwp), get_curcol (curwp));
X ttflush ();
X return;
X }
X#endif
X for (i = 0; i < nrow; ++i)
X { /* Easy update. */
X vp1 = vscreen[i];
X vp2 = pscreen[i];
X if ((vp1 -> v_flag & VFCHG) != 0)
X {
X#if MSDOS
X if (mem_map)
X mem_line (i, vp1);
X else
X#endif
X {
X uline (i, vp1, vp2);
X ucopy (vp1, vp2);
X }
X }
X }
X ttmove (get_currow (curwp), get_curcol (curwp));
X ttflush ();
X}
X/*
X* Get the window relative row in which the cursor will
X* appear. pvr
X*/
Xuint get_currow (wp)
X WINDOW * wp;
X{
X A32 row;
X /* number of bytes from start of window */
X row = DOT_POS(wp) - WIND_POS(wp);
X /* number of rows down in window */
X row /= R_BYTES(wp);
X row += wp -> w_toprow;
X#if RUNCHK
X if (row < wp -> w_toprow)
X printf (ERR_disp_3);
X if (row > (wp -> w_ntrows + wp -> w_toprow))
X printf (ERR_disp_4);
X#endif
X return (row & 0xffff);
X}
X
X/*
X* Get the window relative column in which the cursor will
X* appear. pvr
X*/
Xuint get_curcol (wp)
X WINDOW * wp;
X{
X long offset,
X index;
X uint b_per_u, pos;
X
X b_per_u = R_B_PER_U(wp);
X /* dot offset from start of buffer */
X offset = DOT_POS(wp);
X offset -= wp -> w_disp_shift;
X offset &= ~(b_per_u - 1);
X /* calculate mod of the current file position */
X index = offset & (R_BYTES(wp) - 1);
X index /= b_per_u;
X /* limit to window width */
X if (index >= NCOL)
X index = NCOL;
X pos = wp -> w_fmt_ptr -> r_positions[index] + wp -> w_unit_offset;
X return (pos);
X}
X#if MSDOS
Xvoid mem_line (row, vvp)
Xint row;
XVIDEO * vvp;
X {
X vvp -> v_flag &= ~VFCHG; /* Changes done. */
X ttcolor (vvp -> v_color);
X putline (row + 1, 1, ncol, &vvp -> v_text[0]);
X }
X#endif
X/*
X* Update a saved copy of a line,
X* kept in a VIDEO structure. The "vvp" is
X* the one in the "vscreen". The "pvp" is the one
X* in the "pscreen". This is called to make the
X* virtual and physical screens the same when
X* display has done an update.
X*/
Xvoid ucopy (vvp, pvp)
Xregister VIDEO * vvp;
Xregister VIDEO * pvp;
X{
X register int i;
X
X vvp -> v_flag &= ~VFCHG; /* Changes done. */
X pvp -> v_flag = vvp -> v_flag;/* Update model. */
X pvp -> v_hash = vvp -> v_hash;
X pvp -> v_cost = vvp -> v_cost;
X pvp -> v_color = vvp -> v_color;
X for (i = 0; i < ncol; ++i)
X pvp -> v_text[i] = vvp -> v_text[i];
X}
X
X/*
X* Update a single line. This routine only
X* uses basic functionality (no insert and delete character,
X* but erase to end of line). The "vvp" points at the VIDEO
X* structure for the line on the virtual screen, and the "pvp"
X* is the same for the physical screen. Avoid erase to end of
X* line when updating CMODE color lines, because of the way that
X* reverse video works on most terminals.
X*/
Xvoid uline (row, vvp, pvp)
XVIDEO * vvp;
XVIDEO * pvp;
X{
X register char *cp1;
X register char *cp2;
X register char *cp3;
X register char *cp4;
X register char *cp5;
X register int nbflag;
X
X if (vvp -> v_color != pvp -> v_color)
X { /* Wrong color, do a */
X ttmove (row, 0); /* full redraw. */
X ttcolor (vvp -> v_color);
X cp1 = &vvp -> v_text[0];
X cp2 = &vvp -> v_text[ncol];
X while (cp1 != cp2)
X {
X ttputc (*cp1++);
X ++ttcol;
X }
X return;
X }
X cp1 = &vvp -> v_text[0]; /* Compute left match. */
X cp2 = &pvp -> v_text[0];
X while (cp1 != &vvp -> v_text[ncol] && cp1[0] == cp2[0])
X {
X ++cp1;
X ++cp2;
X }
X if (cp1 == &vvp -> v_text[ncol])/* All equal. */
X return;
X nbflag = FALSE;
X cp3 = &vvp -> v_text[ncol]; /* Compute right match. */
X cp4 = &pvp -> v_text[ncol];
X while (cp3[-1] == cp4[-1])
X {
X --cp3;
X --cp4;
X if (cp3[0] != ' ') /* Note non-blanks in */
X nbflag = TRUE; /* the right match. */
X }
X cp5 = cp3; /* Is erase good? */
X if (nbflag == FALSE && vvp -> v_color == CTEXT)
X {
X while (cp5 != cp1 && cp5[-1] == ' ')
X --cp5;
X /* Alcyon hack */
X if ((int) (cp3 - cp5) <= tceeol)
X cp5 = cp3;
X }
X /* Alcyon hack */
X ttmove (row, (int) (cp1 - &vvp -> v_text[0]));
X ttcolor (vvp -> v_color);
X while (cp1 != cp5)
X {
X ttputc (*cp1++);
X ++ttcol;
X }
X if (cp5 != cp3) /* Do erase. */
X tteeol ();
X}
X
X/*
X* Redisplay the mode line for
X* the window pointed to by the "wp".
X* This is the only routine that has any idea
X* of how the modeline is formatted. You can
X* change the modeline format by hacking at
X* this routine. Called by "update" any time
X* there is a dirty window.
X*/
X
Xvoid modeline (wp)
Xregister WINDOW * wp;
X{
X register char *cp,
X mode,
X size,
X u_posn,
X *s;
X register int c;
X register int n;
X register BUFFER * bp;
X register A32 posn;
X
X static char posn_buf[30] =
X {0
X }; /* krw */
X
X mode = wp -> w_fmt_ptr -> r_type;/* get type of format structure */
X size = wp -> w_fmt_ptr -> r_size;/* get size of format structure */
X
X n = wp -> w_toprow + wp -> w_ntrows;/* Location. */
X vscreen[n] -> v_color = CMODE;/* Mode line color. */
X vscreen[n] -> v_flag |= (VFCHG | VFHBAD);/* Recompute, display. */
X vtmove (n, 0); /* Seek to right line. */
X bp = wp -> w_bufp;
X
X cp = MSG_prog_name; /* Program name. pvr */
X n = 5;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X
X if ((bp -> b_flag & BFBAD) != 0)/* "?" if trashed. */
X vtputc ('?');
X else
X vtputc (' ');
X
X if ((bp -> b_flag & BFCHG) != 0)/* "*" if changed. */
X vtputc ('*');
X else
X vtputc (' ');
X
X if (insert_mode) /* "I" if insert mode */
X vtputc ('I');
X else
X vtputc ('O');
X
X if (bp == blistp)
X { /* special list */
X cp = MSG_disp_b_lst;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X goto pad;
X }
X
X/* Buffer name */
X vtputc (' ');
X ++n;
X cp = &bp -> b_bname[0];
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X while ((int) (cp - &bp -> b_bname[0]) < NBUFN)
X {
X vtputc (' ');
X n++;
X cp++;
X }
X
X /* File name. */
X vtputc (' ');
X ++n;
X cp = MSG_file;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X cp = &bp -> b_fname[0];
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X cp--;
X while ((int) (cp - &bp -> b_fname[0]) < NFILE)
X {
X vtputc (' ');
X n++;
X cp++;
X }
X
X if (bp -> b_flag & BFVIEW)
X s = MSG_RO;
X else if (bp -> b_flag & BFSLOCK)
X s = MSG_WL;
X else
X s = MSG_RW;
X
X while (*s)
X { /* krw */
X vtputc (*s++);
X ++n;
X }
X
X if (auto_update && !(bp -> b_flag & BFVIEW) && bp -> b_bname[0])/* jam */
X s = MSG_AU;
X else
X s = MSG_NOT_AU;
X for (; *s && n < NCOL;)
X {
X vtputc (*s++);
X ++n;
X }
X
X /* Insert current dot position into mode line. */
X posn = DOT_POS(wp);
X u_posn = R_CHR_PER_U(wp) - wp -> w_unit_offset - 1;
X if (u_posn < 0)
X u_posn = 0;
X switch (mode)
X {
X case TEXT:
X case ASCII:
X sprintf (posn_buf, MSG_curs_asc, posn);
X break;
X case EBCDIC:
X sprintf (posn_buf, MSG_curs_ebc, posn);
X break;
X case HEX:
X sprintf (posn_buf, MSG_curs_hex, posn, u_posn);
X break;
X case BINARY:
X sprintf (posn_buf, MSG_curs_bin, posn, u_posn);
X break;
X case DECIMAL:
X sprintf (posn_buf, MSG_curs_dec, posn, u_posn);
X break;
X case OCTAL:
X sprintf (posn_buf, MSG_curs_oct, posn, u_posn);
X break;
X#if RUNCHK
X default:
X writ_echo (ERR_disp_5);
X break;
X#endif
X }
X
X cp = posn_buf;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X
X
X if ((mode == HEX) ||
X (mode == DECIMAL) ||
X (mode == OCTAL))
X {
X switch (size)
X {
X case BYTES:
X sprintf (posn_buf, MSG_siz_8);
X break;
X case WORDS:
X sprintf (posn_buf, MSG_siz_16);
X break;
X case DWORDS:
X sprintf (posn_buf, MSG_siz_32);
X break;
X#if RUNCHK
X default:
X writ_echo (ERR_disp_6);
X break;
X#endif
X }
X }
X else
X sprintf (posn_buf, MSG_siz_null);
X
X cp = posn_buf;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X
X if (wp -> w_intel_mode)
X sprintf (posn_buf, MSG_int_shift, wp -> w_disp_shift);
X else
X sprintf (posn_buf, MSG_mot_shift, wp -> w_disp_shift);
X cp = posn_buf;
X while ((c = *cp++) != 0)
X {
X vtputc (c);
X ++n;
X }
X
X
X /* pad out */
Xpad:
X while (n < ncol)
X {
X vtputc (' ');
X ++n;
X }
X}
X
X/*
X* write text to the echo line
X*/
Xvoid writ_echo (buf)
Xchar *buf;
X {
X int i, len;
X char *vpp;
X bool fill_spac;
X
X fill_spac = FALSE;
X vpp = vscreen[nrow - 1] -> v_text;
X vscreen[nrow - 1] -> v_color = CTEXT;
X vscreen[nrow - 1] -> v_flag |= VFCHG;
X epresf = TRUE;
X
X for (i = 0; i < NCOL; i++)
X {
X if (buf[i] == 0)
X fill_spac = TRUE;
X if (fill_spac)
X vpp[i] = ' ';
X else
X vpp[i] = buf[i];
X }
X#if MSDOS
X if (mem_map)
X {
X mem_line (nrow - 1, vscreen[nrow - 1]);
X }
X else
X#endif
X {
X uline (nrow - 1, vscreen[nrow - 1], pscreen[nrow - 1]);
X uline (nrow - 1, vscreen[nrow - 1], &blanks);
X ucopy (vscreen[nrow - 1], pscreen[nrow - 1]);
X ttflush ();
X }
X }
X
X#if GOSLING
X/*
X* Compute the hash code for
X* the line pointed to by the "vp". Recompute
X* it if necessary. Also set the approximate redisplay
X* cost. The validity of the hash code is marked by
X* a flag bit. The cost understand the advantages
X* of erase to end of line. Tuned for the VAX
X* by Bob McNamara; better than it used to be on
X* just about any machine.
X*/
Xvoid hash (vp)
Xregister VIDEO * vp;
X{
X register int i;
X register int n;
X register char *s;
X
X if ((vp -> v_flag & VFHBAD) != 0)
X { /* Hash bad. */
X s = &vp -> v_text[ncol - 1];
X for (i = ncol; i != 0; --i, --s)
X if (*s != ' ')
X break;
X n = ncol - i; /* Erase cheaper? */
X if (n > tceeol)
X n = tceeol;
X vp -> v_cost = i + n; /* Bytes + blanks. */
X for (n = 0; i != 0; --i, --s)
X n = (n << 5) + n + *s;
X vp -> v_hash = n; /* Hash code. */
X vp -> v_flag &= ~VFHBAD;/* Flag as all done. */
X }
X}
X
X/*
X* Compute the Insert-Delete
X* cost matrix. The dynamic programming algorithm
X* described by James Gosling is used. This code assumes
X* that the line above the echo line is the last line involved
X* in the scroll region. This is easy to arrange on the VT100
X* because of the scrolling region. The "offs" is the origin 0
X* offset of the first row in the virtual/physical screen that
X* is being updated; the "size" is the length of the chunk of
X* screen being updated. For a full screen update, use offs=0
X* and size=nrow-1.
X*
X* Older versions of this code implemented the score matrix by
X* a two dimensional array of SCORE nodes. This put all kinds of
X* multiply instructions in the code! This version is written to
X* use a linear array and pointers, and contains no multiplication
X* at all. The code has been carefully looked at on the VAX, with
X* only marginal checking on other machines for efficiency. In
X* fact, this has been tuned twice! Bob McNamara tuned it even
X* more for the VAX, which is a big issue for him because of
X* the 66 line X displays.
X*
X* On some machines, replacing the "for (i=1; i<=size; ++i)" with
X* i = 1; do
X { }while (++i <=size)" will make the code quite a
X* bit better; but it looks ugly.
X*/
Xvoid setscores (offs, size)
X{
X register SCORE * sp;
X register int tempcost;
X register int bestcost;
X register int j;
X register int i;
X register VIDEO ** vp;
X register VIDEO ** pp;
X register SCORE * sp1;
X register VIDEO ** vbase;
X register VIDEO ** pbase;
X
X vbase = &vscreen[offs - 1]; /* By hand CSE's. */
X pbase = &pscreen[offs - 1];
X score[0].s_itrace = 0; /* [0, 0] */
X score[0].s_jtrace = 0;
X score[0].s_cost = 0;
X sp = &score[1]; /* Row 0, inserts. */
X tempcost = 0;
X vp = &vbase[1];
X for (j = 1; j <= size; ++j)
X {
X sp -> s_itrace = 0;
X sp -> s_jtrace = j - 1;
X tempcost += tcinsl;
X tempcost += (*vp) -> v_cost;
X sp -> s_cost = tempcost;
X ++vp;
X ++sp;
X }
X sp = &score[NROW]; /* Column 0, deletes. */
X tempcost = 0;
X for (i = 1; i <= size; ++i)
X {
X sp -> s_itrace = i - 1;
X sp -> s_jtrace = 0;
X tempcost += tcdell;
X sp -> s_cost = tempcost;
X sp += NROW;
X }
X sp1 = &score[NROW + 1]; /* [1, 1]. */
X pp = &pbase[1];
X for (i = 1; i <= size; ++i)
X {
X sp = sp1;
X vp = &vbase[1];
X for (j = 1; j <= size; ++j)
X {
X sp -> s_itrace = i - 1;
X sp -> s_jtrace = j;
X bestcost = (sp - NROW) -> s_cost;
X if (j != size) /* Cd(A[i])=0 @ Dis. */
X bestcost += tcdell;
X tempcost = (sp - 1) -> s_cost;
X tempcost += (*vp) -> v_cost;
X if (i != size) /* Ci(B[j])=0 @ Dsj. */
X tempcost += tcinsl;
X if (tempcost < bestcost)
X {
X sp -> s_itrace = i;
X sp -> s_jtrace = j - 1;
X bestcost = tempcost;
X }
X tempcost = (sp - NROW - 1) -> s_cost;
X if ((*pp) -> v_color != (*vp) -> v_color
X || (*pp) -> v_hash != (*vp) -> v_hash)
X tempcost += (*vp) -> v_cost;
X if (tempcost < bestcost)
X {
X sp -> s_itrace = i - 1;
X sp -> s_jtrace = j - 1;
X bestcost = tempcost;
X }
X sp -> s_cost = bestcost;
X ++sp; /* Next column. */
X ++vp;
X }
X ++pp;
X sp1 += NROW; /* Next row. */
X }
X}
X
X/*
X* Trace back through the dynamic programming cost
X* matrix, and update the screen using an optimal sequence
X* of redraws, insert lines, and delete lines. The "offs" is
X* the origin 0 offset of the chunk of the screen we are about to
X* update. The "i" and "j" are always started in the lower right
X* corner of the matrix, and imply the size of the screen.
X* A full screen traceback is called with offs=0 and i=j=nrow-1.
X* There is some do-it-yourself double subscripting here,
X* which is acceptable because this routine is much less compute
X* intensive then the code that builds the score matrix!
X*/
Xvoid traceback (offs, size, i, j)
X{
X register int itrace;
X register int jtrace;
X register int k;
X register int ninsl;
X register int ndraw;
X register int ndell;
X
X if (i == 0 && j == 0) /* End of update. */
X return;
X itrace = score[(NROW * i) + j].s_itrace;
X jtrace = score[(NROW * i) + j].s_jtrace;
X if (itrace == i)
X { /* [i, j-1] */
X ninsl = 0; /* Collect inserts. */
X if (i != size)
X ninsl = 1;
X ndraw = 1;
X while (itrace != 0 || jtrace != 0)
X {
X if (score[(NROW * itrace) + jtrace].s_itrace != itrace)
X break;
X jtrace = score[(NROW * itrace) + jtrace].s_jtrace;
X if (i != size)
X ++ninsl;
X ++ndraw;
X }
X traceback (offs, size, itrace, jtrace);
X if (ninsl != 0)
X {
X ttcolor (CTEXT);
X ttinsl (offs + j - ninsl, offs + size - 1, ninsl);
X }
X do
X { /* B[j], A[j] blank. */
X k = offs + j - ndraw;
X uline (k, vscreen[k], &blanks);
X } while (--ndraw);
X return;
X }
X if (jtrace == j)
X { /* [i-1, j] */
X ndell = 0; /* Collect deletes. */
X if (j != size)
X ndell = 1;
X while (itrace != 0 || jtrace != 0)
X {
X if (score[(NROW * itrace) + jtrace].s_jtrace != jtrace)
X break;
X itrace = score[(NROW * itrace) + jtrace].s_itrace;
X if (j != size)
X ++ndell;
X }
X if (ndell != 0)
X {
X ttcolor (CTEXT);
X ttdell (offs + i - ndell, offs + size - 1, ndell);
X }
X traceback (offs, size, itrace, jtrace);
X return;
X }
X traceback (offs, size, itrace, jtrace);
X k = offs + j - 1;
X uline (k, vscreen[k], pscreen[offs + i - 1]);
X}
X#endif
X
X/*
X* Print the current buffer from mark to dot using the
X* current window's display format.
X* Prompt for file name or io device to print to.
X*/
X
Xbool print ()
X {
X LINE *dot_l_sav, *mark_l_sav, *wind_l_sav;
X int dot_off_sav, mark_off_sav, wind_off_sav, i;
X char s;
X char fname[NFILEN];
X register int nline;
X char buf[80], buf1[60];
X
X /* save the original window state */
X dot_l_sav = curwp -> w_dotp;
X dot_off_sav = curwp -> w_doto;
X mark_l_sav = curwp -> w_markp;
X mark_off_sav = curwp -> w_marko;
X wind_l_sav = curwp -> w_linep;
X wind_off_sav = curwp -> w_loff;
X
X /* if mark is not set then set it to location zero */
X if (curwp -> w_markp == NULL)
X {
X curwp -> w_markp = curwp -> w_bufp -> b_linep -> l_fp;
X curwp -> w_marko = 0;
X }
X
X nline = 0;
X if ((s = ereply (MSG_prn_to, fname, NFILEN, NULL)) == ABORT)
X return (s);
X adjustcase (fname);
X if ((s = ffwopen (fname)) != FIOSUC)/* Open writes message. */
X return (FALSE);
X
X sprintf (buf, MSG_print1, fname);
X writ_echo (buf);
X /* make dot before mark */
X if (DOT_POS(curwp) > MARK_POS(curwp))
X swapmark (); /* make mark first */
X
X while (DOT_POS(curwp) <= MARK_POS(curwp))
X {
X /* check if we should quit */
X if (ttkeyready ())
X {
X ttgetc (); /* through away char that was struck */
X break;
X }
X nline++;
X /* move window so that first line is on dot */
X move_ptr (curwp, DOT_POS(curwp), FALSE, TRUE, FALSE);
X
X if (vtputd (curwp, 0)) /* print line into video buffer */
X {
X for (i = NCOL; (vscreen[vtrow] -> v_text[i] < '!') ||
X (vscreen[vtrow] -> v_text[i] > '~'); i--)
X ;
X i++;
X if ((s = ffputline (vscreen[vtrow] -> v_text, i)) != FIOSUC)
X break;
X if ((s = ffputline (MSG_disp_r_n, 2)) != FIOSUC)
X break;
X }
X else
X break;
X forwline (0, 1, KRANDOM); /* advance to next line */
X }
X ffclose ();
X sprintf (buf1, MSG_print2, R_POS_FMT(curwp));
X sprintf (buf, buf1, (long) nline);
X writ_echo (buf);
X
X /* restore the original window state */
X curwp -> w_dotp = dot_l_sav;
X curwp -> w_doto = dot_off_sav;
X curwp -> w_markp = mark_l_sav;
X curwp -> w_marko = mark_off_sav;
X curwp -> w_linep = wind_l_sav;
X curwp -> w_loff = wind_off_sav;
X curwp -> w_flag |= WFHARD; /* insure that window is still presentable */
X return (TRUE);
X }
END_OF_FILE
if test 49122 -ne `wc -c <'display.c'`; then
echo shar: \"'display.c'\" unpacked with wrong size!
fi
chmod +x 'display.c'
# end of 'display.c'
fi
echo shar: End of archive 10 \(of 10\).
cp /dev/null ark10isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 10 archives.
rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0
--
>>>>>>>>>>>>>>>> Peter Reilley ..... pvr at wang.com <<<<<<<<<<<<<<<<<<<<<<<
Well, that about says it.
More information about the Alt.sources
mailing list