[alt.sources] browse.c: A stupid but user-friendly file browser

gmaranca@cipc1.Dayton.NCR.COM (Gabriel Maranca) (08/19/89)

This program is NOT as powerful as other pagers - programmers will
probably never use it. If you have an application that outputs print 
files to disk, and you want to give your users ability to page through
the print file without worrying about them escaping to the shell and
doing dangerous things, this may be a solution. I particularly like
the way form-feeds and long lines are treated.

So far, I have tested this program on two NCR Towers (600 and 800) 
running Unix System V and on an NCR PC 9/16 running Unix System V/386. 
I don't have a lot of experience using curses, so there may be ways
to optimize the code - any suggestions would be appreciated.

Hope you find this useful. If you have any comments, please send me
direct mail,  as I don't have time to look at the news very often.

#-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----
#! /bin/sh
# This is a shar archive.  "sh" it to unpack (no "csh", please).
#
#  Lines   Words   Bytes Filename
#  =====   =====   ===== ========
#      2       8      52 makefile
#    415    1529    9661 browse.c
#    417    1537    9713  total
# Wrapped on Fri Aug 18 13:52:24 EDT 1989
#
if [ -s makefile ]
then
	echo will not clobber existing makefile 1>&2
else
	echo x - makefile 1>&2
	sed 's/^X//' >makefile <<'~~END-of-makefile~~'
Xbrowse: browse.c
X	cc -O browse.c -o browse -lcurses
~~END-of-makefile~~
	if [ `wc -c < makefile` -eq      52  ]
	then echo "...  verifies OK" 1>&2
	else echo "^^^  CHECKSUM ERROR!!!" 1>&2
	fi
fi
if [ -s browse.c ]
then
	echo will not clobber existing browse.c 1>&2
else
	echo x - browse.c 1>&2
	sed 's/^X//' >browse.c <<'~~END-of-browse.c~~'
X/* 
X
X   browse.c: A very stupid but user-friendly file browser based on curses
X   Author:   Gabriel Maranca - NCR Corporation - F*A*S*T, CDISS - August 1989
X
X   The only advantages of this program over other pagers are the treatment
X   of lines longer than the screen width, and the highlighting of form-feeds.
X
X   Sorry; no standard input, no shell escapes, no multiple parameters,
X   no pattern matching! I said the program was stupid.
X
X		cc -O browse.c -lcurses -o browse
X
X*/
X#include <curses.h>
X#include <signal.h>
X#define LENGTH (LINES - 2)
X#define WIDTH (COLS * 2)
X#define ECOL ((SCOL + COLS - 1 > WIDTH)? WIDTH : (SCOL + COLS - 1))
X#define ELIN ((SLIN + LENGTH - 1 > LENGTH * 2)?(LENGTH * 2):(SLIN + LENGTH - 1))
X#define HLIN 12
X#define HCOL 48
X#ifndef SEEK_SET
X#define SEEK_SET 0
X#define SEEK_CUR 1
X#define SEEK_END 2
X#endif
X#define ABS(x) (((long)(x) < (long)0)? -(x) : (x))
X
Xchar *progname, 
X     *Buffer,
X     *Hlpmsg[HLIN] = {
X	"   q: Quit              (also INTERRUPT)        ",
X	"   t: Go to the top     (also f)                ",
X	"   b: Go to the bottom  (also l)                ",
X	"   ?: This help screen  (also h)                ",
X	"(n)+: Advance n screens (also SPACEBAR, j)      ",
X        "(n)-: Back n screens    (also k)                ",
X	"(n)g: Go to screen n    (also RETURN, LINE-FEED)",
X        "(n)>: Right shift n col (also RIGHT-ARROW)      ",
X        "(n)<: Left shift n col  (also LEFT-ARROW)       ",
X	"(n)c: Shift to col n    (also w)                ",
X	"(n)k: Up n lines        (also UP-ARROW)         ",
X	"(n)j: Down n lines      (also DOWN-ARROW)       "
X     };
XFILE *fp;
Xtypedef struct screen_list {
X	int sno;
X	long saddr;
X	struct screen_list *next, *prev;
X} slst;
Xslst *Head = (slst *)NULL,
X     *Tail = (slst *)NULL,
X     *Curr = (slst *)NULL;
Xint SCOL = 1, SLIN = 1;
Xlong Laddr = 0L;
XWINDOW *Pad, *Top, *Bot, *Rgt, *Hlp;
X
Xvoid done()
X{
X	move(LINES - 1, 0);
X	clrtoeol();
X	refresh();
X	flushinp();
X	endwin();
X	exit(0);
X}
X
Xvoid error(s1, s2) 
Xchar *s1, *s2;
X{
X	extern int errno, sys_nerr;
X	extern char *sys_errlist[], *progname;
X	move(LINES - 1, 0);
X	clrtoeol();
X	refresh();
X	flushinp();
X	endwin();
X	if (progname)
X		fprintf(stderr, "%s: ", progname);
X	fprintf(stderr, s1, s2);
X	if (errno > 0 && errno < sys_nerr)
X		fprintf(stderr, " (%s)", sys_errlist[errno]);
X	fprintf(stderr, "\n");
X	exit(1);
X}
X
Xslst *aslst(sno, saddr)
Xint sno;
Xlong saddr;
X{
X	slst *s = (slst *)malloc(sizeof(slst));
X	if (s == (slst *)NULL)
X		error("error calling malloc", "");
X	s->sno = sno;
X	s->saddr = saddr;
X	s->next = s;
X	if (Head == (slst *)NULL) {
X		Head = s;
X		Tail = s;
X	}
X	Tail->next = s;
X	s->prev = Tail;
X	Tail = s;
X	return(Tail);
X}
X
Xslst *fslst(sno)
X{
X	slst *s = Curr;
X	if (sno <= Head->sno)
X		return(Head);
X	if (sno >= Tail->sno)
X		return(Tail);
X	if ( ABS(s->sno - sno)  > ABS(Tail->sno - sno))
X		s = Tail;
X	if ( ABS(s->sno - sno)  > ABS(Head->sno - sno))
X		s = Head;
X	while(sno > s->sno)
X		s = s->next;
X	while(sno < s->sno)
X		s = s->prev;
X	return(s);
X}
X
Xvoid display(sno)
Xint sno;
X{
X	long offset;
X	int whence = SEEK_CUR;
X	int i = 0, j = 0, c = 0;
X	Curr = fslst(sno);
X	offset = Curr->saddr - ftell(fp);
X	if ( ABS(offset) > Curr->saddr ) {
X		offset = Curr->saddr;
X		whence = SEEK_SET;
X	}
X	else if ( ABS(offset) > ABS(Curr->saddr - Laddr) ) {
X		offset = Curr->saddr - Laddr;
X		whence = SEEK_END;
X	}
X	if (fseek(fp, offset, whence))
X		error("error calling fseek", "");
X	offset = ftell(fp);
X	mvwprintw(Top, 0, 0, "Screen #:%4d of%4d", sno, Tail->sno);
X	wrefresh(Top);
X	wclear(Pad);
X	for (i = 0; offset < Laddr && i < LENGTH * 2; i++) {
X		j = 0;
X		Buffer[WIDTH] = '\0';
X		do {
X			c = fgetc(fp);
X			offset++;
X			if (j < WIDTH) {
X				if (c == '\n' || 
X				    c == '\f' ||
X				    c == EOF)
X					do
X						Buffer[j++] = ' ';
X					while (j < WIDTH);
X				else if (c == '\t')
X					do
X						Buffer[j++] = ' ';
X					while (j < WIDTH && j % 8 != 0);
X				else if (c > '~' || c < ' ')
X					Buffer[j++] = '?';
X				else Buffer[j++] = c;
X			}
X		}
X		while (c != '\n' && c!= '\f' && c != EOF);
X		mvwaddstr(Pad, i, 0, Buffer);
X		if (c == '\f')
X			wstandout(Pad);
X		else	wattrset(Pad, 0);
X	}
X	pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 1, 0, LENGTH, COLS - 1);
X}
X
Xmain(argc, argv)
Xint argc;
Xchar *argv[];
X{
X	int h = 0, i = 0, j = 0, k = 1, kk = 1, c = 0, x = 0, y = 0;
X	signal(SIGINT, done);
X	initscr();
X	noecho();
X	cbreak();
X	nonl();
X	Buffer = (char *)malloc((WIDTH + 1)* sizeof(char));
X	if (Buffer == (char *)NULL)
X		error("error calling malloc", "");
X	progname = argv[0];
X	if (argc < 2)
X		error("Usage: %s file-name", progname);
X	if ((fp = fopen(argv[1], "r")) == NULL)
X		error("can't open %s", argv[1]);
X	Top = newwin(1, COLS, 0, 0);
X	Bot = newwin(1, COLS - 20, LINES - 1, 0);
X	Rgt = newwin(1, 20, LINES - 1, COLS - 19);
X	Hlp = newwin(HLIN, HCOL, (LINES - HLIN)/2, (COLS - HCOL)/2);
X	Pad = newpad(LENGTH * 2, WIDTH);
X	keypad(Bot, TRUE);
X	wattrset(Top, A_BOLD);
X	wattrset(Bot, A_BOLD);
X	wattrset(Rgt, A_BOLD);
X	wattrset(Hlp, A_BOLD | A_REVERSE);
X	for(h = 0; h < HLIN; h++)
X		mvwaddstr(Hlp, h, 0, Hlpmsg[h]);
X	mvwaddstr(Top, 0, (COLS - strlen(argv[1])) / 2, argv[1]);
X	mvwprintw(Top, 0, COLS - 19, "Columns:%4d -%4d", SCOL, ECOL);
X	mvwprintw(Rgt, 0, 0        , "Lines:  %4d -%4d", SLIN, ELIN);
X	wnoutrefresh(Rgt);
X	c = fgetc(fp);
X	for (j = 1; c != EOF; j++) {
X		mvwprintw(Top, 0, 0, "Screen #:%4d of%4d", 0, j);
X		wrefresh(Top);
X		Curr = aslst(j, Laddr);
X		for (i = 0; i < LENGTH && c != EOF; i++) 
X			do {
X				c = fgetc(fp);
X				Laddr++;
X			}
X			while (c != '\n' && c!= '\f' && c != EOF);
X		if (c != EOF) {
X			c = fgetc(fp);
X			Laddr++;
X		}
X	}
X	c = 'T';
X	do {
X		switch(c) {
X		case '?': case 'H': case 'h':
X			touchwin(Hlp);
X			wnoutrefresh(Hlp);
X			wrefresh(Bot);
X			wgetch(Bot);
X			touchwin(Pad);
X			pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X			wrefresh(Bot);
X			break;
X		case 'q': case 'Q':
X			done();
X			break;
X		case 't': case 'T': case 'f': case 'F':
X			SLIN = 1;
X			mvwprintw(Rgt, 0, 0, 
X				"Lines:  %4d -%4d", SLIN, ELIN);
X			wnoutrefresh(Rgt);
X			display(j = 1);
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case 'b':  case 'B': case 'l': case 'L':
X			SLIN = 1;
X			mvwprintw(Rgt, 0, 0, 
X				"Lines:  %4d -%4d", SLIN, ELIN);
X			wnoutrefresh(Rgt);
X			display(j = Tail->sno);
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case 'j': case 'J': case KEY_DOWN:
X			if (ELIN + k < LENGTH * 2) {
X				SLIN+=k;
X				mvwprintw(Rgt, 0, 0, 
X					"Lines:  %4d -%4d", SLIN, ELIN);
X				wnoutrefresh(Rgt);
X				pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X				wclear(Bot);
X				mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X				wrefresh(Bot);
X				kk = k = 1;
X				break;
X			}
X			else if (j < Tail->sno) {
X				SLIN = 1;
X				mvwprintw(Rgt, 0, 0, 
X					"Lines:  %4d -%4d", SLIN, ELIN);
X				wnoutrefresh(Rgt);
X				kk = k = 1;
X				c = '+';
X			}
X		case ' ': case '+':
X			if (j + k > Tail->sno)
X				beep();
X			else display(j+=k);
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case 'k': case 'K': case KEY_UP:
X			if (SLIN - k > 0) {
X				SLIN-=k;
X				mvwprintw(Rgt, 0, 0, 
X					"Lines:  %4d -%4d", SLIN, ELIN);
X				wnoutrefresh(Rgt);
X				pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X				wclear(Bot);
X				mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X				wrefresh(Bot);
X				kk = k = 1;
X				break;
X			}
X			else if (j > Head->sno) {
X				SLIN = LENGTH - 1;
X				mvwprintw(Rgt, 0, 0, 
X					"Lines:  %4d -%4d", SLIN, ELIN);
X				wnoutrefresh(Rgt);
X				kk = k = 1;
X				c = '-';
X			}
X		case '-': 
X			if (j - k < 1)
X				beep();
X			else display(j-=k);
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case 'g': case 'G': case '\n': case '\r':
X			if (k < 1 || k > Tail->sno)
X				beep();
X			else display(j = k);
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case 'w': case 'W': case 'c': case 'C':
X			if (k > WIDTH - COLS + 1 || k < 1 )
X				beep();
X			else {
X				SCOL = k;
X				mvwprintw(Top, 0, COLS - 19, 
X					"Columns:%4d -%4d", SCOL, ECOL);
X				wnoutrefresh(Top);
X				pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X			}
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case '>': case KEY_RIGHT:
X			if (SCOL + k > WIDTH - COLS + 1)
X				beep();
X			else {
X				SCOL+=k;
X				mvwprintw(Top, 0, COLS - 19, 
X					"Columns:%4d -%4d", SCOL, ECOL);
X				wnoutrefresh(Top);
X				pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X			}
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case '<': case KEY_LEFT:
X			if (SCOL - k < 1)
X				beep();
X			else {
X				SCOL-=k;
X				mvwprintw(Top, 0, COLS - 19, 
X					"Columns:%4d -%4d", SCOL, ECOL);
X				wnoutrefresh(Top);
X				pnoutrefresh(Pad, SLIN - 1, SCOL - 1, 
X					1, 0, LENGTH, COLS - 1);
X			}
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		case '0': case '1': case '2': case '3': case '4':
X		case '5': case '6': case '7': case '8': case '9':
X			if (kk)
X				kk = k = 0;
X			k = k * 10 + (c - '0');
X			waddch(Bot, c);
X			wrefresh(Bot);
X			break;
X		case '\b':
X			getyx(Bot, y, x);
X			if (x > 17 && !kk) {
X				mvwdelch(Bot, y, x-1);
X				wrefresh(Bot);
X			}
X			k = k / 10;
X			if (k == 0)
X				kk = k = 1;
X			break;
X		default:
X			beep();
X			wclear(Bot);
X			mvwaddstr(Bot, 0, 0, "qtb? (n)+-g><ckj:");
X			wrefresh(Bot);
X			kk = k = 1;
X			break;
X		}
X	}
X	while(c = wgetch(Bot));
X	done();
X}
~~END-of-browse.c~~
	if [ `wc -c < browse.c` -eq    9661  ]
	then echo "...  verifies OK" 1>&2
	else echo "^^^  CHECKSUM ERROR!!!" 1>&2
	fi
fi
#-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----cut-----
#Gabriel Maranca
#Gabriel.Maranca@cipc1.Dayton.NCR.COM
#...!uunet!ncrlnk!cipc1!gmaranca