/*
 * xman - X window system manual page display program.
 *
 * $XConsortium: ScrollByL.c,v 1.27 91/07/26 18:30:47 dave Exp $
 * $Header: ScrollByL.c,v 1.27 91/07/26 18:30:47 dave Exp $
 *
 * Copyright 1987, 1988 Massachusetts Institute of Technology
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  M.I.T. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * Author:    Chris D. Peterson, MIT Project Athena
 * Created:   December 5, 1987
 */

#if ( !defined(lint) && !defined(SABER))
  static char rcs_version[] = "$Athena: ScrollByL.c,v 4.5 88/12/19 13:46:04 kit Exp $";
#endif

#include <stdio.h>
#include <ctype.h>

#include <X11/IntrinsicP.h>
#include <sys/stat.h>		/* depends on IntrinsicP.h */
#include <X11/StringDefs.h>

#ifdef MOTIF
#include <Xm/ScrollBar.h>
#include <Xm/ScrollBarP.h>
#else
#include <X11/Xaw/Scrollbar.h>
#endif
#include <X11/Xmu/Misc.h>

#include "ScrollByLP.h"

/* Default Translation Table */

static char defaultTranslations[] = 
  "<Key>f:      Page(Forward) \n\
   <Key>b:      Page(Back) \n\
   <Key>1:      Page(Line, 1) \n\
   <Key>2:      Page(Line, 2) \n\
   <Key>3:      Page(Line, 3) \n\
   <Key>4:      Page(Line, 4) \n\
   <Key>\\ :    Page(Forward)";

      
/****************************************************************
 *
 * ScrollByLine Resources
 *
 ****************************************************************/

#define Offset(field) XtOffset(ScrollByLineWidget, scroll.field)
#define CoreOffset(field) XtOffset(ScrollByLineWidget, core.field)

static XtResource resources[] = {
    {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
       CoreOffset(width), XtRImmediate, (caddr_t) 500},
    {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
       CoreOffset(height), XtRImmediate, (caddr_t) 700},
    {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
       Offset(foreground), XtRString, "XtDefaultForeground"},
    {XtNforceVert, XtCBoolean, XtRBoolean, sizeof(Boolean),
       Offset(force_vert), XtRImmediate, (caddr_t) FALSE},
    {XtNindent, XtCIndent, XtRDimension, sizeof(Dimension),
       Offset(indent), XtRImmediate, (caddr_t) 15},
    {XtNuseRight, XtCBoolean, XtRBoolean, sizeof(Boolean),
       Offset(use_right), XtRImmediate, (caddr_t) FALSE},
    {XtNmanualFontNormal, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
       Offset(normal_font), XtRString, MANPAGE_NORMAL},
    {XtNmanualFontBold, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
       Offset(bold_font), XtRString, MANPAGE_BOLD},
    {XtNmanualFontItalic, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
       Offset(italic_font), XtRString, MANPAGE_ITALIC},
    {XtNmanualFontSymbol, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
       Offset(symbol_font), XtRString, MANPAGE_SYMBOL},
    {XtNfile, XtCFile, XtRFile, sizeof(FILE *),
       Offset(file), XtRImmediate, (caddr_t) NULL},
};

#undef Offset
#undef CoreOffset

/****************************************************************
 *
 * Full class record constant
 *
 ****************************************************************/

static Boolean ScrollVerticalText();
static void MoveAndClearText(), LoadFile(), PrintText(), VerticalJump();
static void VerticalScroll(), SetThumbHeight(), PaintText(), Layout();

/* semi - public functions. */

static void Realize(), Initialize(), Destroy(), Redisplay(), Page();
static Boolean SetValuesHook();

static XtActionsRec actions[] = {
  { "Page",   Page},
};

#ifdef MOTIF
#define superclass		(&xmPrimitiveClassRec)
#else
#define superclass		(&simpleClassRec)
#endif

ScrollByLineClassRec scrollByLineClassRec = {
  {
/* core_class fields      */
    /* superclass         */    (WidgetClass) superclass,
    /* class_name         */    "ScrollByLine",
    /* widget_size        */    sizeof(ScrollByLineRec),
    /* class_initialize   */    NULL,
    /* class_part_init    */    NULL,
    /* class_inited       */	FALSE,
    /* initialize         */    Initialize,
    /* initialize_hook    */    NULL,
    /* realize            */    Realize,
    /* actions            */    actions,
    /* num_actions	  */	XtNumber(actions),
    /* resources          */    resources,
    /* num_resources      */    XtNumber(resources),
    /* xrm_class          */    NULLQUARK,
    /* compress_motion	  */	TRUE,
    /* compress_exposure  */	FALSE,
    /* compress_enterleave*/    TRUE,
    /* visible_interest   */    FALSE,
    /* destroy            */    Destroy,
    /* resize             */    Layout,
    /* expose             */    Redisplay,
    /* set_values         */    NULL,
    /* set_values_hook    */    SetValuesHook,
    /* set_values_almost  */    XtInheritSetValuesAlmost,
    /* get_values_hook    */    NULL,
    /* accept_focus       */    NULL,
    /* version            */    XtVersion,
    /* callback_private   */    NULL,
    /* tm_table           */    defaultTranslations,
    /* query_geometry	  */	XtInheritQueryGeometry,
    /* display_accelerator*/	XtInheritDisplayAccelerator,
    /* extension	  */	NULL,
  },
#ifdef MOTIF  
  /* (7/9/93:hajadi) I copied this from O'Reilly Vol Four (motif edition)
     page 198.  I'm not sure whether I'm doing the right thing or not 8-). 
  */
  { /* Primitive class part */
    /* border_highlight */      XmInheritBorderHighlight,
    /* border_unhighlight */    XmInheritBorderUnhighlight,
    /* translations */          XmInheritTranslations,
    /* arm_and_activate */      NULL,
    /* syn_resources */         NULL,
    /* num_syn_resources */     0,
    /* extension */             NULL,
  }
#else
  { /* simple fields */
    /* change_sensitive		*/	XtInheritChangeSensitive
  }
#endif
};

WidgetClass scrollByLineWidgetClass = 
            (WidgetClass) &scrollByLineClassRec;


/****************************************************************
 *
 * Private Routines
 *
 ****************************************************************/

/*	Function Name: Layout
 *	Description: This function lays out the scroll widget.
 *	Arguments: w - the scroll widget.
 *                 key - a boolean: if true then resize the widget to the child
 *                                  if false the resize children to fit widget.
 *	Returns: TRUE if successful.
 */

static void
Layout(w)
Widget w;
{    
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  Dimension width, height;
  Widget bar;
  Position bar_bw;
#ifdef MOTIF
  Dimension shadowThickness;
  XtVaGetValues( XtParent( sblw ),
    XmNshadowThickness, &shadowThickness,
    NULL );
#endif

/* 
 * For now always show the bar.
 */

  bar = sblw->scroll.bar;
#ifdef MOTIF
  /* (7/9/93:hajadi)
     Motif calls Layout before the scroll.bar exists.. dunno whose fault.
     So, just return.
  */
  if ( !bar )
    return;
#endif

  height = sblw->core.height;
  width = sblw->core.width;
  bar_bw = bar->core.border_width;

  /* Move child and v_bar to correct location. */

  if (sblw->scroll.use_right) {
#ifdef MOTIF
    XtMoveWidget( bar, width+3*shadowThickness, 0 );
    XtVaSetValues( XtParent( sblw ),
      XmNrightOffset, bar->core.width+( 3*bar_bw )+shadowThickness,
      NULL );
#else
    XtMoveWidget(bar, width - (bar->core.width + bar_bw), - bar_bw);
#endif
    sblw->scroll.offset = 0;
  }
  else {
#ifdef MOTIF
    /* Motif UI guideline recommends the scrlbar on the right, so this
       should never be used. */
    XtMoveWidget(bar, - bar_bw, - bar_bw);
    XtVaSetValues( XtParent( sblw ),
      XmNleftOffset, bar->core.width+( 3*bar_bw ),
      NULL );
#else
    XtMoveWidget(bar, - bar_bw, - bar_bw);
#endif
    sblw->scroll.offset = bar->core.width + bar_bw;
  }

  /* resize the scrollbar to be the correct height or width. */

#ifdef MOTIF
  XtResizeWidget(bar, 
    bar->core.width, 
    height+2*shadowThickness, 
    bar->core.border_width);
#else
  XtResizeWidget(bar, bar->core.width, height, bar->core.border_width);
#endif

  SetThumbHeight(w);
}

/* ARGSUSED */
static void 
GExpose(w,junk,event,cont)
Widget w;
XtPointer junk;
XEvent *event;
Boolean *cont;
{

/*
 * Graphics exposure events are not currently sent to exposure proc.
 */

  if (event->type == GraphicsExpose)
    Redisplay(w, event, NULL);

} /* ChildExpose */

/*
 * Repaint the widget's child Window Widget.
 */

/* ARGSUSED */
static void Redisplay(w, event, region)
Widget w;
XEvent *event;
Region region;
{
  int top, height;		/* the locations of the top and height
				   of the region that needs to be repainted. */
  
/*
 * This routine tells the client which sections of the window to 
 * repaint in his callback function which does the actual repainting.
 */

  if (event->type == Expose) {
    top = event->xexpose.y;
    height = event->xexpose.height;
  }
  else {
    top = event->xgraphicsexpose.y;
    height  = event->xgraphicsexpose.height;
  }
  
  PaintText(w, top, height);
} /* redisplay (expose) */

/*	Function Name: PaintText
 *	Description: paints the text at the give location for a given height.
 *	Arguments: w - the sbl widget.
 *                 y_loc, height - location and size of area to paint.
 *	Returns: none
 */

static void
PaintText(w, y_loc, height)
Widget w;
int y_loc, height;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  int start_line, num_lines, location;

  start_line = y_loc / sblw->scroll.font_height + sblw->scroll.line_pointer;

  if (start_line >= sblw->scroll.lines)
    return;
  
  num_lines = height / sblw->scroll.font_height + 1;

/*
 * Only integer arithmetic makes this possible. 
 */

  location =  y_loc / sblw->scroll.font_height * sblw->scroll.font_height;

  PrintText(w, start_line, num_lines, location);
} 

/*	Function Name: Page
 *	Description: This function pages the widget, by the amount it recieves
 *                   from the translation Manager.
 *	Arguments: w - the ScrollByLineWidget.
 *                 event - the event that caused this return.
 *                 params - the parameters passed to it.
 *                 num_params - the number of parameters.
 *	Returns: none.
 */

/* ARGSUSED */
static void 
Page(w, event, params, num_params)
Widget w;
XEvent * event;
String * params;
Cardinal *num_params;
{
   ScrollByLineWidget sblw = (ScrollByLineWidget) w;
   Widget bar = sblw->scroll.bar;

   if (*num_params < 1)
     return;
/*
 * If no scroll bar is visible then do not page, as the entire window is shown,
 * of scrolling has been turned off. 
 */

   if (bar == (Widget) NULL)
     return;

   switch ( params[0][0] ) {
   case 'f':
   case 'F':
     /* move one page forward */
#ifdef MOTIF
     VerticalScroll(bar, (XtPointer)sblw, (int) bar->core.height);
#else
     VerticalScroll(bar, NULL, (int) bar->core.height);
#endif
     break;
   case 'b':
   case 'B':
     /* move one page backward */
#ifdef MOTIF
     VerticalScroll(bar, (XtPointer)sblw,  - (int) bar->core.height);
#else
     VerticalScroll(bar, NULL,  - (int) bar->core.height);
#endif
     break;
   case 'L':
   case 'l':
     /* move one line forward */
#ifdef MOTIF
     VerticalScroll(bar, (XtPointer)sblw, 
#else
     VerticalScroll(bar, NULL, 
#endif
		    (int) atoi(params[1]) * sblw->scroll.font_height);
     break;
   default:
     return;
   }
}

/*	Function Name: CreateScrollbar
 *	Description: createst the scrollbar for us.
 *	Arguments: w - sblw widget.
 *	Returns: none.
 */

static void
CreateScrollbar(w)
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  Arg args[5];
  Cardinal num_args = 0;

  if (sblw->scroll.bar != NULL) 
    return;

#ifdef MOTIF

  /* parent of parent of 'w' is a form.  */
  /* see buttons.c:CreateManpageWidget() */
  XtSetArg(args[num_args], XmNorientation, XmVERTICAL); num_args++;
  XtSetArg(args[num_args], XmNtopAttachment, XmATTACH_FORM); num_args++;
  XtSetArg(args[num_args], XmNbottomAttachment, XmATTACH_FORM); num_args++;
  
  sblw->scroll.bar = XtCreateWidget("scrollbar", xmScrollBarWidgetClass, 
    				XtParent( XtParent( w ) ), args, num_args);
  XtAddCallback(sblw->scroll.bar, XmNvalueChangedCallback, VerticalJump, 
		  (XtPointer) sblw); 
  XtAddCallback(sblw->scroll.bar, XmNdragCallback, VerticalJump, 
		  (XtPointer) sblw); 
#else
  XtSetArg(args[num_args], XtNorientation, XtorientVertical); num_args++;

  sblw->scroll.bar = XtCreateWidget("scrollbar", scrollbarWidgetClass, w, 
				    args, num_args);
  XtAddCallback(sblw->scroll.bar, XtNscrollProc, VerticalScroll, NULL);
  XtAddCallback(sblw->scroll.bar, XtNjumpProc, VerticalJump, NULL); 
#endif
}

/*	Function Name: ScrollVerticalText
 *	Description: This accomplished the actual movement of the text.
 *	Arguments: w - the ScrollByLine Widget.
 *                 new_line - the new location for the line pointer
 *                 force_redisplay - should we force this window to get 
 *                                   redisplayed?
 *	Returns: True if the thumb needs to be moved.
 */

static Boolean
ScrollVerticalText(w, new_line, force_redisp)
Widget w;
int new_line;
Boolean force_redisp;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  int num_lines = (int)w->core.height / sblw->scroll.font_height + 1;
  int max_lines, old_line;
  Boolean move_thumb = FALSE;

/*
 * Do not let the window extend out of bounds.
 */

  if ( new_line < 0) {
    new_line = 0;
    move_thumb = TRUE;
  }
  else {
    max_lines = sblw->scroll.lines - (int)w->core.height / sblw->scroll.font_height;
    AssignMax(max_lines, 0);

    if ( new_line > max_lines ) {
      new_line = max_lines;
      move_thumb = TRUE;
    }
  }

/* 
 * If forced to redisplay then do a full redisplay and return.
 */

  old_line = sblw->scroll.line_pointer;	
  sblw->scroll.line_pointer = new_line;	/* Set current top of page. */

  if (force_redisp) 
    MoveAndClearText(w, 0, /* cause a full redisplay */ 0, 0);

  if (new_line == old_line)
    return(move_thumb);

/*
 * Scroll forward.
 */

  else if (new_line < old_line) {
    int lines_to_scroll = old_line - new_line;
    MoveAndClearText(w, 0, num_lines - lines_to_scroll, lines_to_scroll);
  }

/* 
 * Scroll back.
 */

  else {
    int lines_to_scroll = new_line - old_line;
    MoveAndClearText(w, lines_to_scroll, num_lines - lines_to_scroll, 0);
  }

  return(move_thumb);
}

/*	Function Name: MoveAndClearText
 *	Description: Blits as much text as it can and clear the
 *                   remaining area with generate exposures TRUE.
 *	Arguments: w - the sbl widget.
 *                 old_y - the old y position.
 *                 height - height of area to move.
 *                 new_y - new y position.
 *	Returns: none
 */
	   
static void
MoveAndClearText(w, old_y, height, new_y)
Widget w;
int old_y, new_y, height;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  int from_left = sblw->scroll.indent + sblw->scroll.offset;
  int y_clear;

  old_y *= sblw->scroll.font_height;
  new_y *= sblw->scroll.font_height;
  height *= sblw->scroll.font_height;

/*
 * If we are already at the right location then do nothing.
 * (height == 0).
 *
 * If we have scrolled more than a screen height then just clear
 * the window.
 */

  if (height <= sblw->scroll.font_height) { /* avoid rounding errors. */
    XClearArea( XtDisplay(w), XtWindow(w), from_left, 0, 
	       (unsigned int) 0, (unsigned int) 0, FALSE);    
    PaintText(w, 0, (int) sblw->core.height);
    return;
  }

  if ((int)height + (int)old_y > (int)w->core.height)
    height = w->core.height - old_y;

  XCopyArea(XtDisplay(w), XtWindow(w), XtWindow(w), sblw->scroll.move_gc,
	    from_left, old_y, 
	    (unsigned int) w->core.width - from_left, (unsigned int) height,
	    from_left, new_y);

#ifdef MOTIF
  height -= sblw->scroll.font_height;	/* clear 1 font of extra space,
					   needed for those extra 3d borders,
					   to make sure we don't lose or
					   gain decenders. */
#else
  height -= sblw->scroll.font_height/2;	/* clear 1/2 font of extra space,
					   to make sure we don't lose or
					   gain decenders. */
#endif
  if (old_y > new_y)
    y_clear = height;
  else
    y_clear = 0;
  
/*
 * We cannot use generate exposures, since that may allow another move and
 * clear before the area get repainted, this would be bad.
 */

  XClearArea( XtDisplay(w), XtWindow(w), from_left, y_clear,
	     (unsigned int) 0, (unsigned int) (w->core.height - height),
	     FALSE);
  PaintText(w, (int) y_clear, (int) (w->core.height - height));
}

/*	Function Name: SetThumbHeight
 *	Description: Set the height of the thumb.
 *	Arguments: w - the sblw widget.
 *	Returns: none
 */

static void
SetThumbHeight(w)
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  float shown;

  if (sblw->scroll.bar == NULL)
    return;

  if (sblw->scroll.lines == 0) 
    shown = 1.0;
  else
    shown = (float) w->core.height / (float) (sblw->scroll.lines *
					      sblw->scroll.font_height);
  if (shown > 1.0)
    shown = 1.0;

#ifdef MOTIF
  if ( (shown<=1.0) && (shown*100) >= 1.0 ) 
    XtVaSetValues( sblw->scroll.bar,
      XmNsliderSize, (int) (shown*100),
      NULL );
#else
  XawScrollbarSetThumb( sblw->scroll.bar, (float) -1, shown );
#endif
}

/*	Function Name: SetThumb
 *	Description: Set the thumb location.
 *	Arguments: w - the sblw.
 *	Returns: none
 */

static void
SetThumb(w) 
Widget w;
{
  float location;
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  if ( (sblw->scroll.lines == 0) || (sblw->scroll.bar == NULL) )
    return;

  location = (float) sblw->scroll.line_pointer / (float) sblw->scroll.lines; 
#ifdef MOTIF
  XtVaSetValues( sblw->scroll.bar,
    XmNvalue, (int) (location*100),
    NULL );
#else
  XawScrollbarSetThumb( sblw->scroll.bar, location , (float) -1 );
#endif
}

/*	Function Name: VerticalJump.
 *	Description: This function moves the test
 *                   as the vertical scroll bar is moved.
 *	Arguments: w - the scrollbar widget.
 *                 junk - not used.
 *                 percent - the position of the scrollbar.
 *	Returns: none.
 */

/* ARGSUSED */
static void
VerticalJump(w, junk, percent_ptr)
Widget w;
caddr_t junk;
caddr_t percent_ptr;
{
#ifdef MOTIF
  float percent = ( ((XmScrollBarCallbackStruct *) percent_ptr)->value ) / 100.0;
  ScrollByLineWidget sblw = (ScrollByLineWidget) junk;
#else
  float percent = *((float *) percent_ptr);
  ScrollByLineWidget sblw = (ScrollByLineWidget) XtParent(w);
#endif
  int new_line;			/* The new location for the line pointer. */

  new_line = (int) ((float) sblw->scroll.lines * percent);
  if (ScrollVerticalText( (Widget) sblw, new_line, FALSE))
    SetThumb((Widget) sblw);
#ifdef DEBUG
printf( "VerticalJump: %f\n", percent );
#endif
}

/*	Function Name: VerticalScroll
 *	Description: This function moves the postition of the interior window
 *                   as the vertical scroll bar is moved.
 *	Arguments: w - the scrollbar widget.
 *                 junk - not used.
 *                 pos - the position of the cursor.
 *	Returns: none.
 */

/* ARGSUSED */
static void
VerticalScroll(w,junk,pos)
Widget w;
caddr_t junk;
int pos;
{
  int new_line;			/* The new location for the line pointer. */
#ifdef MOTIF
  ScrollByLineWidget sblw = (ScrollByLineWidget) junk;
#else
  ScrollByLineWidget sblw = (ScrollByLineWidget) XtParent(w);
#endif

  new_line = sblw->scroll.line_pointer + (pos / sblw->scroll.font_height);
  (void) ScrollVerticalText( (Widget) sblw, new_line, FALSE);
  SetThumb( (Widget) sblw);
#ifdef DEBUG
printf( "VerticalScroll: pos=%d\n", pos );
#endif
}

int h_width;			/* main font width */


/* ARGSUSED */
static void 
Initialize(req, new)
Widget req, new;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) new;
  unsigned long figWidth;
  Atom atomNum;

  sblw->scroll.top_line = NULL;
  sblw->scroll.line_pointer = 0;
  LoadFile(new);
  sblw->scroll.bar = (Widget) NULL;

  sblw->scroll.font_height = (sblw->scroll.normal_font->max_bounds.ascent + 
			      sblw->scroll.normal_font->max_bounds.descent); 

  atomNum = XInternAtom(XtDisplay(req), "FIGURE_WIDTH", False);

  if (XGetFontProperty(sblw->scroll.normal_font, atomNum, &figWidth))
      h_width = figWidth;
  else
    h_width = XTextWidth(sblw->scroll.normal_font, "$", 1);

} /* Initialize. */

/*	Function Name: CreateGCs
 *	Description: Creates the graphics contexts that we need. 
 *	Arguments: w - the sblw.
 *	Returns: none
 */

static void
CreateGCs(w) 
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  XtGCMask mask;
  XGCValues values;

  values.graphics_exposures = TRUE;
  sblw->scroll.move_gc = XtGetGC(w, GCGraphicsExposures, &values);

  mask = GCForeground | GCFont;
  values.foreground = sblw->scroll.foreground;
  
  values.font = sblw->scroll.normal_font->fid;
  sblw->scroll.normal_gc = XtGetGC(w, mask, &values);

  values.font = sblw->scroll.italic_font->fid;
  sblw->scroll.italic_gc = XtGetGC(w, mask, &values);

  values.font = sblw->scroll.bold_font->fid;
  sblw->scroll.bold_gc = XtGetGC(w, mask, &values);

  values.font = sblw->scroll.symbol_font->fid;
  sblw->scroll.symbol_gc = XtGetGC(w, mask, &values);
}

/*	Function Name: DestroyGCs
 *	Description: removes all gcs for this widget.
 *	Arguments: w - the widget.
 *	Returns: none
 */

static void
DestroyGCs(w)
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  XtReleaseGC(w, sblw->scroll.normal_gc);
  XtReleaseGC(w, sblw->scroll.bold_gc);
  XtReleaseGC(w, sblw->scroll.italic_gc);
  XtReleaseGC(w, sblw->scroll.move_gc);
}

static void
Realize(w, valueMask, attributes)
register Widget w;
Mask *valueMask;
XSetWindowAttributes *attributes;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  CreateScrollbar(w);
  CreateGCs(w);
  Layout(w);
  (*superclass->core_class.realize) (w, valueMask, attributes);
  XtRealizeWidget(sblw->scroll.bar); /* realize scrollbar. */
  XtMapWidget(sblw->scroll.bar); /* map scrollbar. */

  XtAddEventHandler(w, 0, TRUE, GExpose, NULL); /* Get Graphics Exposures */
} /* Realize */

/*	Function Name: Destroy
 *	Description: Cleans up when we are killed.
 *	Arguments: w - the widget.
 *	Returns: none
 */

static void
Destroy(w)
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  if (sblw->scroll.bar != NULL)
    XtDestroyWidget(sblw->scroll.bar); /* Destroy scrollbar. */
  if (sblw->scroll.file != NULL)
    fclose(sblw->scroll.file);
  DestroyGCs(w);
}

/*
 *
 * Set Values
 *
 */

/* ARGSUSED */
static Boolean 
SetValuesHook( w, args, num_args)
Widget w;
ArgList args;
Cardinal *num_args;
{
  Boolean ret = TRUE;
  int i;

  for (i = 0; i < *num_args; i++) {
    if (strcmp(XtNfile, args[i].name) == 0) {
      LoadFile(w);
      ret = TRUE;
    }
  }

/*
 * Changing anthing else will have strange effects, I don't need it so
 * I didn't code it.
 */

  return(ret);

} /* Set Values */

/* 
 * A little design philosophy is probabally wise to include at this point.
 *
 * One of the things that I has hoped to achieve with xman is to make the
 * viewing of manpage not only easy for the nieve user, but also fast for
 * the experienced user, I wanted to be able to use it too.  To achieve this
 * I end up sacrificing a bit of start up time for the manual data structure.
 * As well as, the overhead of reading the entire file before putting it up 
 * on the display.  This is actually hardly even noticeable since most manual
 * pages are shots, one to two pages - the notable exception is of course csh,
 * but then that should be broken up anyway. 
 *
 * METHOD:
 *
 * I allocate a chunk of space that is the size of the file, plus a null for
 * debugging.  Then copiesthe file into this chunk of memory.  I then allocate
 * an array of char*'s that are assigned to the beginning of each line.  Yes,
 * this means that I have to read the file twice, and could probabally be more
 * clever about it, but once it is in memory the second read is damn fast.
 * There are a few obsucrities here about guessing the number of lines and
 * reallocing if I guess wrong, but other than that it is pretty straight 
 * forward.
 *
 *                                         Chris Peterson
 *                                         1/27/88
 */

#define ADD_MORE_MEM 100	/* first guesses for allocations. */
#define CHAR_PER_LINE 40

/*	Function Name: LoadFile
 *	Description: Loads the current file into the internal memory.
 *	Arguments: w - the sblw.
 *	Returns: none
 */

static void
LoadFile(w)
Widget w;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  FILE * file = sblw->scroll.file;

  char *page;
  char **line_pointer,**top_line; /* pointers to beginnings of the 
				     lines of the file. */
  int nlines;			/* the current number of allocated lines. */
  struct stat fileinfo;		/* file information from fstat. */

  if ( sblw->scroll.top_line != NULL) {
    XtFree(*(sblw->scroll.top_line)); /* free characters. */
    XtFree((char *)(sblw->scroll.top_line)); /* free lines. */
  }
  sblw->scroll.top_line = NULL;

  if (file == NULL)
    return;

/*
 * Get file size and allocate a chunk of memory for the file to be 
 * copied into.
 */

  if (fstat(fileno(file), &fileinfo))
    XtAppError(XtWidgetToApplicationContext(w), 
	       "SBLW LoadFile: Failure in fstat.");

/* 
 * Allocate a space for a list of pointer to the beginning of each line.
 */

  if ( (nlines = fileinfo.st_size/CHAR_PER_LINE) == 0)
    return;

  page = XtMalloc(fileinfo.st_size + 1); /* leave space for the NULL */
  top_line = line_pointer = (char**) XtMalloc( nlines * sizeof(char *) );

/*
 * Copy the file into memory. 
 */

  if (fread(page, sizeof(char), fileinfo.st_size, file) == 0)
    XtAppError(XtWidgetToApplicationContext(w), 
	       "SBLW LoadFile: Failure in fread.");


/* put NULL at end of buffer. */

  *(page + fileinfo.st_size) = '\0';

/*
 * Go through the file setting a line pointer to the character after each 
 * new line.  If we run out of line pointer space then realloc that space
 * with space for more lines.
 */

  *line_pointer++ = page;	/* first line points to first char in buffer.*/
  while (*page != '\0') {

    if ( *page == '\n' ) {
      *line_pointer++ = page + 1;

      if (line_pointer >= top_line + nlines) {
	top_line = (char **) XtRealloc( (char *)top_line, (nlines +
					  ADD_MORE_MEM) * sizeof(char *) );
	line_pointer = top_line + nlines;
	nlines += ADD_MORE_MEM;
      }
    }
    page++;
  }
   
/*
 *  Realloc the line pointer space to take only the minimum amount of memory
 */

  sblw->scroll.lines = nlines = line_pointer - top_line - 1;
  top_line = (char **) XtRealloc((char *)top_line, nlines * sizeof(char *));

/*
 * Store the memory pointers
 */

  sblw->scroll.top_line = top_line;
  sblw->scroll.line_pointer = 0;
  SetThumbHeight(w);
  SetThumb(w);
}

#define NLINES  66		/* This is the number of lines to wait until
				   we boldify the line again, this allows 
				   me to bold the first line of each page.*/
#define BACKSPACE 010		/* I doubt you would want to change this. */

#define NORMAL	0
#define BOLD	1
#define ITALIC	2
#define SYMBOL	3
#define WHICH(italic, bold)	((bold) ? BOLD : ((italic) ? ITALIC : NORMAL))
				/* Choose BOLD over ITALICS.  If neither */
				/* is chosen, use NORMAL. */

static int DumpText();
static Boolean Boldify();	

/*	Function Name: PrintText
 *	Description: This function actually prints the text.
 *	Arguments: w - the ScrollByLine widget.
 *                 start_line - line to start printing,
 *                 num_lines - the number of lines to print.
 *                 location - the location to print the text.
 *	Returns: none.
 */

/* ARGSUSED */

static void
PrintText(w, start_line, num_lines, location)
Widget w;
int  start_line, num_lines, location;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;

  register char *bufp, *c;	/* Generic char pointers */
  int current_line;		/* the number of the currrent line */
  char buf[BUFSIZ];		/* Misc. characters */
  int width;			/* Width of a tab stop. */
  Boolean italicflag = FALSE;	/* Print text in italics?? */
  Boolean first = TRUE;	        /* First line of a manual page??? */
  int x_loc, y_loc;		/* x and y location of text. */

/*
 * For table hack
 * To get columns to line up reasonably in most cases, make the
 * assumption that they were lined up using lots of spaces, where
 * lots is greater than two. Use a space width of 70% of the
 * widest character in the font.
 */
  XFontStruct * h_font;
  int h_col, h_fix;
  char * h_c;

  h_font = sblw->scroll.normal_font;

/*
 * Nothing loaded, take no action.
 */

  if (sblw->scroll.top_line == NULL || num_lines == 0)
    return;

  current_line = start_line;

/* Set the first character to print at the first line. */

  c = *(sblw->scroll.top_line + start_line);

 /* Width of a tab stop. */
  width = 8 * h_width;

/*
 * Because XDrawString uses the bottom of the text as a position
 * reference, add the height from the top of the font to the baseline
 * to the ScollByLine position reference.
 */

  y_loc = location + sblw->scroll.normal_font->max_bounds.ascent;

/*
 * Ok, here's the more than mildly heuristic man page formatter.
 * We put chars into buf until either a font change or newline
 * occurs (at which time we flush it to the screen.)
 */


  bufp = buf;
  x_loc = sblw->scroll.offset + sblw->scroll.indent;
  h_col = 0;
  
/*
 * A fix:
 * Assume that we are always starting to print on or before the
 * first line of a page, and then prove it if we aren't.
 */
  for (h_fix = 1; h_fix <= (start_line % NLINES); h_fix++)
    if (**(sblw->scroll.top_line + start_line - h_fix) != '\n')
      {
	first = FALSE;
	break;
      }

  while(TRUE) {
    if (current_line % NLINES == 0)
      first = TRUE;

/* 
 * Lets make sure that we do not run out of space in our buffer, making full
 * use of it is not critical since no window will be wide enough to display
 * nearly BUFSIZ characters.
 */

    if ( (bufp - buf) > (BUFSIZ - 10) )
      /* Toss everything until we find a <CR> or the end of the buffer. */
      while ( (*c != '\n') && (*c != '\0') ) c++;

    switch(*c) {

    case '\0':		      /* If we reach the end of the file then return */
      DumpText(w, x_loc, y_loc, buf, bufp - buf, WHICH(italicflag, first));
      return;

    case '\n':
      if (bufp != buf) {
	Boolean bold;
	*bufp = '\0';		/* for Boldify. */
	bold = ( (first) || ((x_loc == (sblw->scroll.offset +
			      sblw->scroll.indent)) && Boldify(buf)) );

	(void) DumpText(w, x_loc, y_loc, buf, bufp - buf,
			WHICH(italicflag, bold));

	if (bold)
	  first = FALSE;
      }

/* 
 * If we have painted the required number of lines then we should now return.
 */
      if (++current_line == start_line + num_lines ) 
	return;

      bufp = buf;
      italicflag = FALSE;
      x_loc = sblw->scroll.offset + sblw->scroll.indent;
      h_col = 0;
      y_loc += sblw->scroll.font_height;
      break;

/*
 * This tab handling code is not very clever it moves the cursor over
 * to the next boundry of eight (8) spaces, as calculated in width just
 * before the printing loop started.
 */

    case '\t':			/* TAB */
      x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf,
		       WHICH(italicflag, first));
      h_col += bufp - buf;
      bufp = buf; 
      italicflag = FALSE;
      x_loc = sblw->scroll.offset + sblw->scroll.indent;
      h_col = h_col + 8 - (h_col%8);
      x_loc += h_width * h_col;
      break;

    case ' ':
      h_c = c + 1;
      while (*h_c == ' ') h_c++;

      if (h_c - c < 4)
	{
	  *bufp++ = *c;
	  break;
	}

      x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf,
		       WHICH(italicflag, first));
      h_col += bufp - buf;
      bufp = buf; 
      italicflag = FALSE;

      x_loc = sblw->scroll.offset + sblw->scroll.indent; 
      h_col += (h_c - c);
      x_loc += h_width * h_col; 
      c = h_c - 1;
      break;

    case '\033':		/* ignore esc sequences for now */
      c++;			/* should always be esc-x */
      break;

/* 
 * Overstrike code supplied by: cs.utexas.edu!ut-emx!clyde@rutgers.edu 
 * Since my manual pages do not have overstrike I couldn't test this.
 */

    case BACKSPACE:		/* Backspacing for nroff bolding */
      if (c[-1] == c[1] && c[1] != BACKSPACE) {	/* overstriking one char */
	bufp--;		/* Zap 1st instance of char to bolden */
	if (bufp > buf) {
	  x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf,
			   WHICH(italicflag, FALSE));
	  h_col += bufp - buf;
	  bufp = buf;
	}
	*bufp++ = c[1];
	x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf, BOLD);
	h_col += bufp - buf;
	bufp = buf;
	first = FALSE;

	/*
	 *     Nroff bolding looks like:
	 *	 	     C\bC\bC\bCN...
	 * c points to ----^      ^
	 * it needs to point to --^
	 */
	while (*c == BACKSPACE && c[-1] == c[1])
	  c += 2;
	c--;		/* Back up to previous char */
      }
      else {
	if ((c[-1] == 'o' && c[1] == '+')          /* Nroff bullet */
	    || (c[-1] == '+' && c[1] == 'o')) {	   /* Nroff bullet */
				/* If we run into a bullet, print out */
				/* everything that's accumulated to this */
				/* point, then the bullet, then resume. */
	  bufp--;
	  x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf,
			   WHICH(italicflag, FALSE));
	  h_col += bufp - buf;
	  bufp = buf;
	  *bufp = (char)183;
	  x_loc = DumpText(w, x_loc, y_loc, buf, 1, SYMBOL);
	  h_col++;
	  c++;
	}
	else {		/* 'real' backspace - back up output ptr */
	  bufp--;
	}
      }
      break;

/* End of contributed overstrike code. */
  
   case '_':			/* look for underlining [italicize] */
      if(*(c + 1) == BACKSPACE) {
	if(!italicflag) {	/* font change? */
	  if (bufp != buf) {
	    x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf, NORMAL);
	    h_col += bufp - buf;
	    bufp = buf;
	  }
	  italicflag = TRUE;
	}
	c += 2;
	*bufp++ = *c;
	break;
      }
      /* else fall through to default, because this was a real underscore. */

    default:
      if(italicflag) { 			/* font change? */
	if (bufp != buf) {
	  x_loc = DumpText(w, x_loc, y_loc, buf, bufp - buf,
			   WHICH(italicflag, FALSE));
	  h_col += bufp - buf;
	  bufp = buf;
	}
	italicflag = FALSE;
      }
      *bufp++ = *c;
      break;
    }
    c++;
  }
}

/*	Function Name: DumpText
 *	Description: Dumps text to the screen.
 *	Arguments: w - the widget.
 *                 x_loc - to dump text at.
 *                 y_loc - the y_location to draw_text.
 *                 buf - buffer to dump.
 *                 italic, bold, boolean that tells us which gc to use.
 *	Returns: x_location of the end of the text.
 */

static int
DumpText(w, x_loc, y_loc, buf, len, format)
Widget w;
int x_loc, y_loc;
char * buf;
int len;
int format;
{
  ScrollByLineWidget sblw = (ScrollByLineWidget) w;
  GC gc;
  XFontStruct * font;

  switch(format) {

  case ITALIC:
    gc = sblw->scroll.italic_gc;
    font = sblw->scroll.italic_font;
    break;

  case BOLD:
      gc = sblw->scroll.bold_gc;
      font = sblw->scroll.bold_font;
    break;

  case SYMBOL:
    gc = sblw->scroll.symbol_gc;
    font = sblw->scroll.symbol_font;
    break;

  default:
      gc = sblw->scroll.normal_gc;
      font = sblw->scroll.normal_font;
    break;
    }

  XDrawString(XtDisplay(w), XtWindow(w), gc, x_loc, y_loc, buf, len);
  return(x_loc + XTextWidth(font, buf, len));
}

/*	Function Name: Boldify
 *	Description: look for keyword.
 *	Arguments: sp - string pointer.
 *	Returns: 1 if keyword else 0.
 */

static Boolean
Boldify(sp)
register char *sp;
{
  register char *sp_pointer;
  int length,count;

/* 
 * If there are not lower case letters in the line the assume it is a
 * keyword and boldify it in PrintManpage.
 */

  length = strlen(sp);
  for (sp_pointer = sp, count = 0; count < length; sp_pointer++,count++) 
    if ( !isupper(*sp_pointer) && !isspace(*sp_pointer) )
      return(0);
  return(1);
}

#undef superclass
