/*
 * $Id: HelpView.cxx,v 1.1 2003/09/16 10:35:14 hut66au Exp $
 *
 * Imview, the portable image analysis application
 * http://www.cmis.csiro.au/Hugues.Talbot/imview
 * ----------------------------------------------------------
 *
 *  Imview is an attempt to provide an image display application
 *  suitable for professional image analysis. It was started in
 *  1997 and is mostly the result of the efforts of Hugues Talbot,
 *  Image Analysis Project, CSIRO Mathematical and Information
 *  Sciences, with help from others (see the CREDITS files for
 *  more information)
 *
 *  Imview is Copyrighted (C) 1997-2001 by Hugues Talbot and was
 *  supported in parts by the Australian Commonwealth Science and 
 *  Industry Research Organisation. Please see the COPYRIGHT file 
 *  for full details. Imview also includes the contributions of 
 *  many others. Please see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 * */

//
//
//   Help Viewer widget routines.
//
//   Copyright 1997-2000 by Easy Software Products.
//
//   These coded instructions, statements, and computer programs are the
//   property of Easy Software Products and are protected by Federal
//   copyright law.  Distribution and use rights are outblockd in the file
//   "COPYING" which should have been included with this file.  If this
//   file is missing or damaged please contact Easy Software Products
//   at:
//
//       Attn: ESP Licensing Information
//       Easy Software Products
//       44141 Airport View Drive, Suite 204
//       Hollywood, Maryland 20636-3111 USA
//
//       Voice: (301) 373-9600
//       EMail: info@easysw.com
//         WWW: http://www.easysw.com
//
// Contents:
//
//   HelpView::add_block()       - Add a text block to the list.
//   HelpView::add_link()        - Add a new link to the list.
//   HelpView::add_target()      - Add a new target to the list.
//   HelpView::compare_targets() - Compare two targets.
//   HelpView::do_align()        - Compute the alignment for a line in a block.
//   HelpView::draw()            - Draw the HelpView widget.
//   HelpView::format()          - Format the help text.
//   HelpView::get_align()       - Get an alignment attribute.
//   HelpView::get_attr()        - Get an attribute value from the string.
//   HelpView::get_color()       - Get an alignment attribute.
//   HelpView::handle()          - Handle events in the widget.
//   HelpView::HelpView()        - Build a HelpView widget.
//   HelpView::~HelpView()       - Destroy a HelpView widget.
//   HelpView::load()            - Load the specified file.
//   HelpView::resize()          - Resize the help widget.
//   HelpView::topline()         - Set the top line to the named target.
//   HelpView::topline()         - Set the top line by number.
//   HelpView::value()           - Set the help text directly.
//   scrollbar_callback()        - A callback for the scrollbar.
//

//
// Include necessary header files...
//

#include "HelpView.hxx"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include "imunistd.h"


//
// Local functions...
//

static void	scrollbar_callback(Fl_Widget *s, void *);


//
// 'HelpView::add_block()' - Add a text block to the list.
//

HelpBlock *				// O - Pointer to new block
HelpView::add_block(const char *s,	// I - Pointer to start of block text
                    int        xx,	// I - X position of block
		    int        yy,	// I - Y position of block
		    int        ww,	// I - Right margin of block
		    int        hh,	// I - Height of block
		    uchar      border)	// I - Draw border?
{
  HelpBlock	*temp;			// New block


  if (nblocks_ >= ablocks_)
  {
    ablocks_ += 16;

    if (ablocks_ == 16)
      blocks_ = (HelpBlock *)malloc(sizeof(HelpBlock) * ablocks_);
    else
      blocks_ = (HelpBlock *)realloc(blocks_, sizeof(HelpBlock) * ablocks_);
  }

  temp = blocks_ + nblocks_;
  temp->start  = s;
  temp->x      = xx;
  temp->y      = yy;
  temp->w      = ww;
  temp->h      = hh;
  temp->border = border;
  nblocks_ ++;

  return (temp);
}


//
// 'HelpView::add_link()' - Add a new link to the list.
//

void
HelpView::add_link(const char *n,	// I - Name of link
                   int        xx,	// I - X position of link
		   int        yy,	// I - Y position of link
		   int        ww,	// I - Width of link text
		   int        hh)	// I - Height of link text
{
  HelpLink	*temp;			// New link
  char		*target;		// Pointer to target name


  if (nlinks_ >= alinks_)
  {
    alinks_ += 16;

    if (alinks_ == 16)
      links_ = (HelpLink *)malloc(sizeof(HelpLink) * alinks_);
    else
      links_ = (HelpLink *)realloc(links_, sizeof(HelpLink) * alinks_);
  }

  temp = links_ + nlinks_;

  temp->x       = xx;
  temp->y       = yy;
  temp->w       = xx + ww;
  temp->h       = yy + hh;

  strncpy(temp->filename, n, sizeof(temp->filename));
  temp->filename[sizeof(temp->filename) - 1] = '\0';

  if ((target = strrchr(temp->filename, '#')) != NULL)
  {
    *target++ = '\0';
    strncpy(temp->name, target, sizeof(temp->name));
    temp->name[sizeof(temp->name) - 1] = '\0';
  }
  else
    temp->name[0] = '\0';

  nlinks_ ++;
}


//
// 'HelpView::add_target()' - Add a new target to the list.
//

void
HelpView::add_target(const char *n,	// I - Name of target
                     int        yy)	// I - Y position of target
{
  HelpTarget	*temp;			// New target


  if (ntargets_ >= atargets_)
  {
    atargets_ += 16;

    if (atargets_ == 16)
      targets_ = (HelpTarget *)malloc(sizeof(HelpTarget) * atargets_);
    else
      targets_ = (HelpTarget *)realloc(targets_, sizeof(HelpTarget) * atargets_);
  }

  temp = targets_ + ntargets_;

  temp->y = yy;
  strncpy(temp->name, n, sizeof(temp->name));
  temp->name[sizeof(temp->name) - 1] = '\0';

  ntargets_ ++;
}


//
// 'HelpView::compare_targets()' - Compare two targets.
//

int						// O - Result of comparison
HelpView::compare_targets(const HelpTarget *t0,	// I - First target
                          const HelpTarget *t1)	// I - Second target
{
  return (strcasecmp(t0->name, t1->name));
}


//
// 'HelpView::do_align()' - Compute the alignment for a line in a block.
//

int					// O - New line
HelpView::do_align(HelpBlock *block,	// I - Block to add to
                   int       line,	// I - Current line
		   int       xx,	// I - Current X position
		   int       a,		// I - Current alignment
		   int       &l)	// IO - Starting link
{
  int	offset = 0;				// Alignment offset


  switch (a)
  {
    case RIGHT :	// Right align
	offset = block->w - xx;
	break;
    case CENTER :	// Center
	offset = (block->w - xx) / 2;
	break;
    case LEFT :		// Left align
	offset = 0;
	break;
  }

  block->line[line] = block->x + offset;

  if (line < 31)
    line ++;

  while (l < nlinks_)
  {
    links_[l].x += offset;
    links_[l].w += offset;
    l ++;
  }

  return (line);
}


//
// 'HelpView::draw()' - Draw the HelpView widget.
//

void
HelpView::draw()
{
  int		i;		// Looping var
  const HelpBlock *block;	// Pointer to current block
  const char	*ptr,		// Pointer to text in block
		*attrs  = 0;	// Pointer to start of element attributes
  char		*s,		// Pointer into buffer
		buf[1024],	// Text buffer
		attr[1024];	// Attribute buffer
  int		xx, yy, ww, hh;	// Current positions and sizes
  int		line;		// Current line
  uchar		font, size;	// Current font and size
  int		head, pre,	// Flags for text
		needspace;	// Do we need whitespace?
  Fl_Boxtype	b = box() ? box() : FL_DOWN_BOX;
				// Box to draw...
  Fl_Color	tc, c;		// Table/cell background color


  // Draw the scrollbar and box first...
  if (scrollbar_.visible())
  {
    draw_child(scrollbar_);
    draw_box(b, x(), y(), w() - 17, h(), bgcolor_);
  }
  else
    draw_box(b, x(), y(), w(), h(), bgcolor_);

  if (!value_)
    return;

  // Clip the drawing to the inside of the box...
  fl_push_clip(x() + 4, y() + 4, w() - 28, h() - 8);
  fl_color(textcolor_);

  tc = c = bgcolor_;

  // Draw all visible blocks...
  for (i = 0, block = blocks_; i < nblocks_ && (block->y - topline_) < h(); i ++, block ++)
    if ((block->y + block->h) >= topline_)
    {
      line      = 0;
      xx        = block->line[line];
      yy        = block->y - topline_;
      hh        = 0;
      pre       = 0;
      head      = 0;
      needspace = 0;

      initfont(font, size);

      for (ptr = block->start, s = buf; ptr < block->end;)
      {
	if ((*ptr == '<' || isspace(*ptr)) && s > buf)
	{
	  if (!head && !pre)
	  {
            // Check width...
            *s = '\0';
            s  = buf;
            ww = (int)fl_width(buf);

            if (needspace && xx > block->x)
	      xx += (int)fl_width(' ');

            if ((xx + ww) > block->w)
	    {
	      if (line < 31)
	        line ++;
	      xx = block->line[line];
	      yy += hh;
	      hh = 0;
	    }

            fl_draw(buf, xx + x(), yy + y());

            xx += ww;
	    if ((size + 2) > hh)
	      hh = size + 2;

	    needspace = 0;
	  }
	  else if (pre)
	  {
	    while (isspace(*ptr))
	    {
	      if (*ptr == '\n')
	      {
	        *s = '\0';
                s = buf;

                fl_draw(buf, xx + x(), yy + y());

		if (line < 31)
	          line ++;
		xx = block->line[line];
		yy += hh;
		hh = size + 2;
	      }
	      else if (*ptr == '\t')
	      {
		// Do tabs every 8 columns...
		while (((s - buf) & 7))
	          *s++ = ' ';
	      }
	      else
	        *s++ = ' ';

              if ((size + 2) > hh)
	        hh = size + 2;

              ptr ++;
	    }

            if (s > buf)
	    {
	      *s = '\0';
	      s = buf;

              fl_draw(buf, xx + x(), yy + y());
              xx += (int)fl_width(buf);
	    }

	    needspace = 0;
	  }
	  else
	  {
            s = buf;

	    while (isspace(*ptr))
              ptr ++;
	  }
	}

	if (*ptr == '<')
	{
	  ptr ++;
	  while (*ptr && *ptr != '>' && !isspace(*ptr))
            if (s < (buf + sizeof(buf) - 1))
	      *s++ = *ptr++;
	    else
	      ptr ++;

	  *s = '\0';
	  s = buf;

	  attrs = ptr;
	  while (*ptr && *ptr != '>')
            ptr ++;

	  if (*ptr == '>')
            ptr ++;

	  if (strcasecmp(buf, "HEAD") == 0)
            head = 1;
	  else if (strcasecmp(buf, "BR") == 0)
	  {
	    if (line < 31)
	      line ++;
	    xx = block->line[line];
            yy += hh;
	    hh = 0;
	  }
	  else if (strcasecmp(buf, "HR") == 0)
	  {
	     fl_line(block->x + x(), yy + y(), block->w + x(), yy + y());

	    if (line < 31)
	      line ++;
	    xx = block->line[line];
	    hh += 20;
	    yy += hh;
	    // was: hh = 0...
	  }
	  else if (strcasecmp(buf, "CENTER") == 0 ||
        	   strcasecmp(buf, "P") == 0 ||
        	   strcasecmp(buf, "H1") == 0 ||
		   strcasecmp(buf, "H2") == 0 ||
		   strcasecmp(buf, "H3") == 0 ||
		   strcasecmp(buf, "H4") == 0 ||
		   strcasecmp(buf, "H5") == 0 ||
		   strcasecmp(buf, "H6") == 0 ||
		   strcasecmp(buf, "UL") == 0 ||
		   strcasecmp(buf, "OL") == 0 ||
		   strcasecmp(buf, "DL") == 0 ||
		   strcasecmp(buf, "LI") == 0 ||
		   strcasecmp(buf, "DD") == 0 ||
		   strcasecmp(buf, "DT") == 0 ||
		   strcasecmp(buf, "PRE") == 0)
	  {
            if (tolower(buf[0]) == 'h')
	    {
	      font = FL_HELVETICA_BOLD;
	      size = textsize_ + '7' - buf[1];
	    }
	    else if (strcasecmp(buf, "DT") == 0)
	    {
	      font = textfont_ | FL_ITALIC;
	      size = textsize_;
	    }
	    else if (strcasecmp(buf, "PRE") == 0)
	    {
	      font = FL_COURIER;
	      size = textsize_;
	      pre  = 1;
	    }

            if (strcasecmp(buf, "LI") == 0)
	    {
	      fl_font(FL_SYMBOL, size);
	      fl_draw("\267", xx - size + x(), yy + y());
	    }

	    pushfont(font, size);

            if (c != bgcolor_)
	    {
	      fl_color(c);
              fl_rectf(block->x + x() - 4,
	               block->y - topline_ + y() - size - 3,
		       block->w - block->x + 7, block->h + size - 5);
              fl_color(textcolor_);
	    }
	  }
	  else if (strcasecmp(buf, "A") == 0 &&
	           get_attr(attrs, "HREF", attr, sizeof(attr)) != NULL)
	    fl_color(linkcolor_);
	  else if (strcasecmp(buf, "/A") == 0)
	    fl_color(textcolor_);
	  else if (strcasecmp(buf, "B") == 0)
	    pushfont(font |= FL_BOLD, size);
	  else if (strcasecmp(buf, "TABLE") == 0)
            tc = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), bgcolor_);
	  else if (strcasecmp(buf, "TD") == 0 ||
	           strcasecmp(buf, "TH") == 0)
          {
	    if (tolower(buf[1]) == 'h')
	      pushfont(font |= FL_BOLD, size);
	    else
	      pushfont(font = textfont_, size);

            c = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)), tc);

            if (c != bgcolor_)
	    {
	      fl_color(c);
              fl_rectf(block->x + x() - 4,
	               block->y - topline_ + y() - size - 3,
		       block->w - block->x + 7, block->h + size - 5);
	    }

            if (block->border) {
		fl_rect(block->x + x() - 4,
			block->y - topline_ + y() - size - 3,
			block->w - block->x + 7, block->h + size - 5);
	    }
	  }
	  else if (strcasecmp(buf, "I") == 0)
	    pushfont(font |= FL_ITALIC, size);
	  else if (strcasecmp(buf, "CODE") == 0)
	    pushfont(font = FL_COURIER, size);
	  else if (strcasecmp(buf, "KBD") == 0)
	    pushfont(font = FL_COURIER_BOLD, size);
	  else if (strcasecmp(buf, "VAR") == 0)
	    pushfont(font = FL_COURIER_ITALIC, size);
	  else if (strcasecmp(buf, "/HEAD") == 0)
            head = 0;
	  else if (strcasecmp(buf, "/H1") == 0 ||
		   strcasecmp(buf, "/H2") == 0 ||
		   strcasecmp(buf, "/H3") == 0 ||
		   strcasecmp(buf, "/H4") == 0 ||
		   strcasecmp(buf, "/H5") == 0 ||
		   strcasecmp(buf, "/H6") == 0 ||
		   strcasecmp(buf, "/B") == 0 ||
		   strcasecmp(buf, "/I") == 0 ||
		   strcasecmp(buf, "/CODE") == 0 ||
		   strcasecmp(buf, "/KBD") == 0 ||
		   strcasecmp(buf, "/VAR") == 0)
	    popfont(font, size);
	  else if (strcasecmp(buf, "/TABLE") == 0)
	    tc = c = bgcolor_;
	  else if (strcasecmp(buf, "/TD") == 0 ||
	           strcasecmp(buf, "/TH") == 0)
	    c = tc;
	  else if (strcasecmp(buf, "/PRE") == 0)
	  {
	    popfont(font, size);
	    pre = 0;
	  }
	}
	else if (strcasecmp(buf, "IMG") == 0 &&
        	 get_attr(attrs, "ALT", attr, sizeof(attr)) != NULL)
	{
	  // Show alt text...
	  sprintf(buf, "[%s]", attr);
	  ww = (int)fl_width(buf);

          if (needspace && xx > block->x)
	    xx += (int)fl_width(' ');

          if ((xx + ww) > block->w)
	  {
	    if (line < 31)
	      line ++;
	    xx = block->line[line];
	    yy += hh;
	    hh = 0;
	  }

          fl_draw(buf, xx + x(), yy + y());

          xx += ww;
	  if ((size + 2) > hh)
	    hh = size + 2;

	  needspace = 0;
	}
	else if (*ptr == '\n' && pre)
	{
	  *s = '\0';
	  s = buf;

          fl_draw(buf, xx + x(), yy + y());

	  if (line < 31)
	    line ++;
	  xx = block->line[line];
	  yy += hh;
	  hh = size + 2;
	  needspace = 0;

	  ptr ++;
	}
	else if (isspace(*ptr))
	{
	  if (pre)
	  {
	    if (*ptr == ' ')
	      *s++ = ' ';
	    else
	    {
	      // Do tabs every 8 columns...
	      while (((s - buf) & 7))
	        *s++ = ' ';
            }
	  }

          ptr ++;
	  needspace = 1;
	}
	else if (*ptr == '&')
	{
	  ptr ++;

	  if (strncasecmp(ptr, "amp;", 4) == 0)
	  {
            *s++ = '&';
	    ptr += 4;
	  }
	  else if (strncasecmp(ptr, "lt;", 3) == 0)
	  {
            *s++ = '<';
	    ptr += 3;
	  }
	  else if (strncasecmp(ptr, "gt;", 3) == 0)
	  {
            *s++ = '>';
	    ptr += 3;
	  }
	  else if (strncasecmp(ptr, "nbsp;", 5) == 0)
	  {
            *s++ = ' ';
	    ptr += 5;
	  }
	  else if (strncasecmp(ptr, "copy;", 5) == 0)
	  {
            *s++ = '\251';
	    ptr += 5;
	  }
	  else if (strncasecmp(ptr, "reg;", 4) == 0)
	  {
            *s++ = '\256';
	    ptr += 4;
	  }
	  else if (strncasecmp(ptr, "quot;", 5) == 0)
	  {
            *s++ = '\"';
	    ptr += 5;
	  }
	  else if (*ptr == '#') {
	      /* risky? */
	      const char *subp = ptr + 1;
	      char  numb[4]; /* 3 digits at the most */
	      int   ii, val;
	      for (ii = 0 ; ii < 4 ; ii++) {
		  if (subp[ii] == ';') {
		      numb[ii] = '\0';
		      break;
		  } else
		      numb[ii] = subp[ii];
	      }
	      if (ii != 4) {
		  val = atoi(numb);
		  *s++ = (char)val; /* ascii value put out */
		  ptr += (ii + 2); /* nb of digits + # + ; */
		  //printf("converted %s to %c\n", numb, val);
	      }
	  }
          if ((size + 2) > hh)
	    hh = size + 2;
	}
	else
	{
	  *s++ = *ptr++;

          if ((size + 2) > hh)
	    hh = size + 2;
        }
      }

      *s = '\0';

      if (s > buf && !pre && !head)
      {
	ww = (int)fl_width(buf);

        if (needspace && xx > block->x)
	  xx += (int)fl_width(' ');

	if ((xx + ww) > block->w)
	{
	  if (line < 31)
	    line ++;
	  xx = block->line[line];
	  yy += hh;
	  hh = 0;
	}
      }

      if (s > buf && !head)
        fl_draw(buf, xx + x(), yy + y());
    }

  fl_pop_clip();
}


//
// 'HelpView::format()' - Format the help text.
//

void
HelpView::format()
{
  HelpBlock	*block,		// Current block
		*cell;		// Current table cell
  int		row;		// Current table row (block number)
  const char	*ptr,		// Pointer into block
		*start,		// Pointer to start of element
		*attrs;		// Pointer to start of element attributes
  char		*s,		// Pointer into buffer
		buf[1024],	// Text buffer
		attr[1024],	// Attribute buffer
		link[1024];	// Link destination
  int		xx, yy,
                ww = 0, hh;	// Size of current text fragment
  int		line;		// Current line in block
  int		links;		// Links for current line
  uchar		font, size;	// Current font and size
  uchar		border = 0;	// Draw border?
  int		align,		// Current alignment
		head,		// In the <HEAD> section?
		pre,		// <PRE> text?
		needspace;	// Do we need whitespace?
  int		table_width;	// Width of table
  int		column = 0,		// Current table column number
		columns[200];	// Column widths


  nblocks_   = 0;
  nlinks_    = 0;
  ntargets_  = 0;
  size_      = 0;
  bgcolor_   = color();
  textcolor_ = textcolor();
  linkcolor_ = selection_color();

  strcpy(title_, "Untitled");

  if (!value_)
    return;

  initfont(font, size);

  line      = 0;
  links     = 0;
  xx        = 4;
  yy        = size + 2;
  hh        = 0;
  block     = add_block(value_, xx, yy, w() - 24, 0);
  row       = 0;
  head      = 0;
  pre       = 0;
  align     = LEFT;
  needspace = 0;
  link[0]   = '\0';

  for (ptr = value_, s = buf; *ptr;)
  {
    if ((*ptr == '<' || isspace(*ptr)) && s > buf)
    {
      if (!head && !pre)
      {
        // Check width...
        *s = '\0';
        ww = (int)fl_width(buf);

        if (needspace && xx > block->x)
	  ww += (int)fl_width(' ');

        if ((xx + ww) > block->w)
	{
          line     = do_align(block, line, xx, align, links);
	  xx       = block->x;
	  yy       += hh;
	  block->h += hh;
	  hh       = 0;
	}

        if (link[0])
	  add_link(link, xx, yy - size, ww, size);

	xx += ww;
	if ((size + 2) > hh)
	  hh = size + 2;

	needspace = 0;
      }
      else if (pre)
      {
        // Handle preformatted text...
	while (isspace(*ptr))
	{
	  if (*ptr == '\n')
	  {
            if (link[0])
	      add_link(link, xx, yy - hh, ww, hh);

            line     = do_align(block, line, xx, align, links);
            xx       = block->x;
	    yy       += hh;
	    block->h += hh;
	    hh       = size + 2;
	  }

          if ((size + 2) > hh)
	    hh = size + 2;

          ptr ++;
	}

	needspace = 0;
      }
      else
      {
        // Handle normal text or stuff in the <HEAD> section...
	while (isspace(*ptr))
          ptr ++;
      }

      s = buf;
    }

    if (*ptr == '<')
    {
      start = ptr;
      ptr ++;
      while (*ptr && *ptr != '>' && !isspace(*ptr))
        if (s < (buf + sizeof(buf) - 1))
          *s++ = *ptr++;
	else
	  ptr ++;

      *s = '\0';
      s = buf;

      attrs = ptr;
      while (*ptr && *ptr != '>')
        ptr ++;

      if (*ptr == '>')
        ptr ++;

      if (strcasecmp(buf, "HEAD") == 0)
        head = 1;
      else if (strcasecmp(buf, "/HEAD") == 0)
        head = 0;
      else if (strcasecmp(buf, "TITLE") == 0)
      {
        // Copy the title in the document...
        for (s = title_;
	     *ptr != '<' && *ptr && s < (title_ + sizeof(title_) - 1);
	     *s++ = *ptr++);

	*s = '\0';
	s = buf;
      }
      else if (strcasecmp(buf, "A") == 0)
      {
        if (get_attr(attrs, "NAME", attr, sizeof(attr)) != NULL)
	  add_target(attr, yy - size - 2);
	else if (get_attr(attrs, "HREF", attr, sizeof(attr)) != NULL)
	{
	  strncpy(link, attr, sizeof(link) - 1);
	  link[sizeof(link) - 1] = '\0';
	}
      }
      else if (strcasecmp(buf, "/A") == 0)
        link[0] = '\0';
      else if (strcasecmp(buf, "BODY") == 0)
      {
        bgcolor_   = get_color(get_attr(attrs, "BGCOLOR", attr, sizeof(attr)),
	                       color());
        textcolor_ = get_color(get_attr(attrs, "TEXT", attr, sizeof(attr)),
	                       textcolor());
        linkcolor_ = get_color(get_attr(attrs, "LINK", attr, sizeof(attr)),
	                       selection_color());
      }
      else if (strcasecmp(buf, "BR") == 0)
      {
        line     = do_align(block, line, xx, align, links);
        xx       = block->x;
	block->h += hh;
        yy       += hh;
	hh       = 0;
      }
      else if (strcasecmp(buf, "CENTER") == 0 ||
               strcasecmp(buf, "P") == 0 ||
               strcasecmp(buf, "H1") == 0 ||
	       strcasecmp(buf, "H2") == 0 ||
	       strcasecmp(buf, "H3") == 0 ||
	       strcasecmp(buf, "H4") == 0 ||
	       strcasecmp(buf, "H5") == 0 ||
	       strcasecmp(buf, "H6") == 0 ||
	       strcasecmp(buf, "UL") == 0 ||
	       strcasecmp(buf, "OL") == 0 ||
	       strcasecmp(buf, "DL") == 0 ||
	       strcasecmp(buf, "LI") == 0 ||
	       strcasecmp(buf, "DD") == 0 ||
	       strcasecmp(buf, "DT") == 0 ||
	       strcasecmp(buf, "HR") == 0 ||
	       strcasecmp(buf, "PRE") == 0 ||
	       strcasecmp(buf, "TABLE") == 0)
      {
        block->end = start;
        line       = do_align(block, line, xx, align, links);
        xx         = block->x;
        block->h   += hh;

        if (!block->h && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

        if (strcasecmp(buf, "UL") == 0 ||
	    strcasecmp(buf, "OL") == 0 ||
	    strcasecmp(buf, "DL") == 0)
	{
	  xx += 4 * size;
	  block->h += size + 2;
	}
        else if (strcasecmp(buf, "TABLE") == 0)
	{
	    if (get_attr(attrs, "BORDER", attr, sizeof(attr))) {
		border = atoi(attr);
		if (border == 0)
		    border = 1;  // originally 0, but I like to see my borders...
	    }  else
	      border = 0; 

	  block->h += size + 2;

          if (get_attr(attrs, "WIDTH", attr, sizeof(attr)))
	  {
	    if (attr[strlen(attr) - 1] == '%')
	      table_width = (int)(atoi(attr) * 0.8 * w() / 100);
	    else
	      table_width = atoi(attr);
	  }
	  else
	      table_width = (int)(0.8*w()); // used to be 100%, that's a bit ambitious

          for (column = 0; column < 200; column ++)
	    columns[column] = table_width / 3;

	  column = 0;
	}

        if (tolower(buf[0]) == 'h' && isdigit(buf[1]))
	{
	  font = FL_HELVETICA_BOLD;
	  size = textsize_ + '7' - buf[1];
	}
	else if (strcasecmp(buf, "DT") == 0)
	{
	  font = textfont_ | FL_ITALIC;
	  size = textsize_;
	}
	else if (strcasecmp(buf, "PRE") == 0)
	{
	  font = FL_COURIER;
	  size = textsize_;
	  pre  = 1;
	}
	else
	{
	  font = textfont_;
	  size = textsize_;
	}

	pushfont(font, size);

        yy = block->y + block->h;
        hh = 0;

        if ((tolower(buf[0]) == 'h' && isdigit(buf[1])) ||
	    strcasecmp(buf, "DD") == 0 ||
	    strcasecmp(buf, "DT") == 0 ||
	    strcasecmp(buf, "UL") == 0 ||
	    strcasecmp(buf, "OL") == 0 ||
	    strcasecmp(buf, "P") == 0)
          yy += size + 2;
	else if (strcasecmp(buf, "HR") == 0)
	{
	  hh += 2 * size;
	  yy += size;
	}

        if (row)
	  block = add_block(start, block->x, yy, block->w, 0);
	else
	  block = add_block(start, xx, yy, w() - 24, 0);

	needspace = 0;
	line      = 0;

	if (strcasecmp(buf, "CENTER") == 0)
	  align = CENTER;
	else
	  align = get_align(attrs, LEFT);
      }
      else if (strcasecmp(buf, "/P") == 0 ||
	       strcasecmp(buf, "/H1") == 0 ||
	       strcasecmp(buf, "/H2") == 0 ||
	       strcasecmp(buf, "/H3") == 0 ||
	       strcasecmp(buf, "/H4") == 0 ||
	       strcasecmp(buf, "/H5") == 0 ||
	       strcasecmp(buf, "/H6") == 0 ||
	       strcasecmp(buf, "/PRE") == 0 ||
	       strcasecmp(buf, "/UL") == 0 ||
	       strcasecmp(buf, "/OL") == 0 ||
	       strcasecmp(buf, "/DL") == 0 ||
	       strcasecmp(buf, "/TABLE") == 0)
      {
        line       = do_align(block, line, xx, align, links);
        xx         = block->x;
        block->end = ptr;

        if (strcasecmp(buf, "/UL") == 0 ||
	    strcasecmp(buf, "/OL") == 0 ||
	    strcasecmp(buf, "/DL") == 0)
	{
	  xx       -= 4 * size;
	  block->h += size + 2;
	}
	else if (strcasecmp(buf, "/TABLE") == 0)
	  block->h += size + 2;
	else if (strcasecmp(buf, "/PRE") == 0)
	{
	  pre = 0;
	  hh  = 0;
	}

        initfont(font, size);

        while (isspace(*ptr))
	  ptr ++;

        block->h += hh;
        yy       += hh;

        if (tolower(buf[2]) == 'l')
          yy += size + 2;

        block     = add_block(ptr, xx, yy, w() - 24, 0);
	needspace = 0;
	hh        = 0;
	line      = 0;
	align     = LEFT;
      }
      else if (strcasecmp(buf, "TR") == 0)
      {
	  if (row) { // do a /TR.
	      line       = do_align(block, line, xx, align, links);
	      block->end = start;
	      block->h   += hh;

	      xx = blocks_[row].x;

	      if (block->end == block->start && nblocks_ > 1)
	      {
		  nblocks_ --;
		  block --;
	      }

	      yy = blocks_[row].y + blocks_[row].h;

	      for (cell = blocks_ + row + 1; cell <= block; cell ++)
		  if ((cell->y + cell->h) > yy)
		      yy = cell->y + cell->h;

	      block->h = yy - block->y + 2;

	      for (cell = blocks_ + row + 1; cell < block; cell ++)
		  cell->h = block->h;

	      yy        = block->y + block->h - 4;
	      block     = add_block(start, xx, yy, w() - 24, 0);
	      needspace = 0;
	      row       = 0;
	      line      = 0;	      
	  }
	  
        block->end = start;
        line       = do_align(block, line, xx, align, links);
        xx         = block->x;
        block->h   += hh;

        if (row)
	{
          yy = blocks_[row].y + blocks_[row].h;

	  for (cell = blocks_ + row + 1; cell <= block; cell ++)
	    if ((cell->y + cell->h) > yy)
	      yy = cell->y + cell->h;

          block->h = yy - block->y + 2;

	  for (cell = blocks_ + row + 1; cell < block; cell ++)
	    cell->h = block->h;
	}

	yy        = block->y + block->h - 4;
	hh        = 0;
        block     = add_block(start, xx, yy, w() - 24, 0);
	row       = block - blocks_;
	needspace = 0;
	column    = 0;
	line      = 0;
      }
      else if (strcasecmp(buf, "/TR") == 0 && row)
      {
        line       = do_align(block, line, xx, align, links);
        block->end = start;
	block->h   += hh;

        xx = blocks_[row].x;

        if (block->end == block->start && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

        yy = blocks_[row].y + blocks_[row].h;

	for (cell = blocks_ + row + 1; cell <= block; cell ++)
	  if ((cell->y + cell->h) > yy)
	    yy = cell->y + cell->h;

        block->h = yy - block->y + 2;

	for (cell = blocks_ + row + 1; cell < block; cell ++)
	  cell->h = block->h;

	yy        = block->y + block->h - 4;
        block     = add_block(start, xx, yy, w() - 24, 0);
	needspace = 0;
	row       = 0;
	line      = 0;
      }
      else if ((strcasecmp(buf, "TD") == 0 ||
                strcasecmp(buf, "TH") == 0) && row)
      {
        line       = do_align(block, line, xx, align, links);
        block->end = start;
	block->h   += hh;

        if (strcasecmp(buf, "TH") == 0)
	  font = textfont_ | FL_BOLD;
	else
	  font = textfont_;

        size = textsize_;

        if (column == 0)
          xx = block->x + size + 3;
	else
          xx = block->w + 6;

        if (block->end == block->start && nblocks_ > 1)
	{
	  nblocks_ --;
	  block --;
	}

	pushfont(font, size);

        if (get_attr(attrs, "WIDTH", attr, sizeof(attr)) != NULL)
	{
	  ww = atoi(attr);

	  if (attr[strlen(attr) - 1] == '%')
	    ww = ww * w() / 100;

          columns[column] = ww;
	}
	else
	  ww = columns[column];

	yy        = blocks_[row].y;
	hh        = 0;
        block     = add_block(start, xx, yy, xx + ww, 0, border);
	needspace = 0;
	line      = 0;

	align = get_align(attrs, tolower(buf[1]) == 'h' ? CENTER : LEFT);

	column ++;
      }
      else if ((strcasecmp(buf, "/TD") == 0 ||
                strcasecmp(buf, "/TH") == 0) && row)
        popfont(font, size);
      else if (strcasecmp(buf, "B") == 0)
	pushfont(font |= FL_BOLD, size);
      else if (strcasecmp(buf, "I") == 0)
	pushfont(font |= FL_ITALIC, size);
      else if (strcasecmp(buf, "CODE") == 0)
	pushfont(font = FL_COURIER, size);
      else if (strcasecmp(buf, "KBD") == 0)
	pushfont(font = FL_COURIER_BOLD, size);
      else if (strcasecmp(buf, "VAR") == 0)
	pushfont(font = FL_COURIER_ITALIC, size);
      else if (strcasecmp(buf, "/B") == 0 ||
	       strcasecmp(buf, "/I") == 0 ||
	       strcasecmp(buf, "/CODE") == 0 ||
	       strcasecmp(buf, "/KBD") == 0 ||
	       strcasecmp(buf, "/VAR") == 0)
	popfont(font, size);
    }
//     else if (strcasecmp(buf, "IMG") == 0 &&
//              get_attr(attrs, "ALT", attr, sizeof(attr)) != NULL)
//     {
//       // Show alt text...
//       ww = (int)(fl_width(attr) + fl_width('[') + fl_width(']'));

//       if (needspace && xx > block->x)
// 	ww += (int)fl_width(' ');

//       if ((xx + ww) > block->w)
//       {
//         line     = do_align(block, line, xx, align, links);
// 	xx       = block->x;
// 	yy       += hh;
// 	block->h += hh;
// 	hh       = 0;
//       }

//       if (link[0])
// 	add_link(link, xx, yy - size, ww, size);

//       xx += ww;
//       if ((size + 2) > hh)
// 	hh = size + 2;

//       needspace = 0;
//     }
    else if (*ptr == '\n' && pre)
    {
      if (link[0])
	add_link(link, xx, yy - hh, ww, hh);

      line      = do_align(block, line, xx, align, links);
      xx        = block->x;
      yy        += hh;
      block->h  += hh;
      needspace = 0;
      ptr ++;
    }
    else if (isspace(*ptr))
    {
      needspace = 1;

      ptr ++;
    }
    else if (*ptr == '&' && s < (buf + sizeof(buf) - 1))
    {
      ptr ++;

      if (strncasecmp(ptr, "amp;", 4) == 0)
      {
        *s++ = '&';
	ptr += 4;
      }
      else if (strncasecmp(ptr, "lt;", 3) == 0)
      {
        *s++ = '<';
	ptr += 3;
      }
      else if (strncasecmp(ptr, "gt;", 3) == 0)
      {
        *s++ = '>';
	ptr += 3;
      }
      else if (strncasecmp(ptr, "nbsp;", 5) == 0)
      {
        *s++ = '\240';
	ptr += 5;
      }
      else if (strncasecmp(ptr, "copy;", 5) == 0)
      {
        *s++ = '\251';
	ptr += 5;
      }
      else if (strncasecmp(ptr, "reg;", 4) == 0)
      {
        *s++ = '\256';
	ptr += 4;
      }
      else if (strncasecmp(ptr, "quot;", 5) == 0)
      {
        *s++ = '\"';
	ptr += 5;
      }

      if ((size + 2) > hh)
        hh = size + 2;
    }
    else
    {
      if (s < (buf + sizeof(buf) - 1))
        *s++ = *ptr++;
      else
        ptr ++;

      if ((size + 2) > hh)
        hh = size + 2;
    }
  }

  if (s > buf && !pre && !head)
  {
    *s = '\0';
    ww = (int)fl_width(buf);

    if (needspace && xx > block->x)
      ww += (int)fl_width(' ');

    if ((xx + ww) > block->w)
    {
      line     = do_align(block, line, xx, align, links);
      xx       = block->x;
      yy       += hh;
      block->h += hh;
      hh       = 0;
    }

    if (link[0])
      add_link(link, xx, yy - size, ww, size);

    xx += ww;
    if ((size + 2) > hh)
      hh = size + 2;

    needspace = 0;
  }

  block->end = ptr;
  size_      = yy + hh;

  if (ntargets_ > 1)
    qsort(targets_, ntargets_, sizeof(HelpTarget),
          (int (*)(const void *, const void *))compare_targets);

  if (size_ < (h() - 8))
    scrollbar_.hide();
  else
    scrollbar_.show();

  topline(topline_);
}


//
// 'HelpView::get_align()' - Get an alignment attribute.
//

int					// O - Alignment
HelpView::get_align(const char *p,	// I - Pointer to start of attrs
                    int        a)	// I - Default alignment
{
  char	buf[255];			// Alignment value


  if (get_attr(p, "ALIGN", buf, sizeof(buf)) == NULL)
    return (a);

  if (strcasecmp(buf, "CENTER") == 0)
    return (CENTER);
  else if (strcasecmp(buf, "RIGHT") == 0)
    return (RIGHT);
  else
    return (LEFT);
}


//
// 'HelpView::get_attr()' - Get an attribute value from the string.
//

const char *				// O - Pointer to buf or NULL
HelpView::get_attr(const char *p,	// I - Pointer to start of attributes
                   const char *n,	// I - Name of attribute
		   char       *buf,	// O - Buffer for attribute value
		   int        bufsize)	// I - Size of buffer
{
  char	name[255],			// Name from string
	*ptr,				// Pointer into name or value
	quote;				// Quote


  while (*p && *p != '>')
  {
    while (isspace(*p))
      p ++;

    if (*p == '>' || !*p)
      return (NULL);

    for (ptr = name; *p && !isspace(*p) && *p != '=' && *p != '>';)
      if (ptr < (name + sizeof(name) - 1))
        *ptr++ = *p++;
      else
        p ++;

    *ptr = '\0';

    if (isspace(*p) || !*p || *p == '>')
      buf[0] = '\0';
    else
    {
      if (*p == '=')
        p ++;

      for (ptr = buf; *p && !isspace(*p) && *p != '>';)
        if (*p == '\'' || *p == '\"')
	{
	  quote = *p++;

	  while (*p && *p != quote)
	    if ((ptr - buf + 1) < bufsize)
	      *ptr++ = *p++;
	    else
	      p ++;

          if (*p == quote)
	    p ++;
	}
	else if ((ptr - buf + 1) < bufsize)
	  *ptr++ = *p++;
	else
	  p ++;

      *ptr = '\0';
    }

    if (strcasecmp(n, name) == 0)
      return (buf);

    if (*p == '>')
      return (NULL);
  }

  return (NULL);
}


//
// 'HelpView::get_color()' - Get an alignment attribute.
//

Fl_Color				// O - Color value
HelpView::get_color(const char *n,	// I - Color name
                    Fl_Color   c)	// I - Default color value
{
  int	rgb, r, g, b;			// RGB values


  if (!n)
    return (c);

  if (n[0] == '#')
  {
    // Do hex color lookup
    rgb = strtol(n + 1, NULL, 16);

    r = rgb >> 16;
    g = (rgb >> 8) & 255;
    b = rgb & 255;

    if (r == g && g == b)
      return (fl_gray_ramp(FL_NUM_GRAY * r / 256));
    else
      return (fl_color_cube((FL_NUM_RED - 1) * r / 255,
                            (FL_NUM_GREEN - 1) * g / 255,
			    (FL_NUM_BLUE - 1) * b / 255));
  }
  else if (strcasecmp(n, "black") == 0)
    return (FL_BLACK);
  else if (strcasecmp(n, "red") == 0)
    return (FL_RED);
  else if (strcasecmp(n, "green") == 0)
    return (fl_color_cube(0, 4, 0));
  else if (strcasecmp(n, "yellow") == 0)
    return (FL_YELLOW);
  else if (strcasecmp(n, "blue") == 0)
    return (FL_BLUE);
  else if (strcasecmp(n, "magenta") == 0 || strcasecmp(n, "fuchsia") == 0)
    return (FL_MAGENTA);
  else if (strcasecmp(n, "cyan") == 0 || strcasecmp(n, "aqua") == 0)
    return (FL_CYAN);
  else if (strcasecmp(n, "white") == 0)
    return (FL_WHITE);
  else if (strcasecmp(n, "gray") == 0 || strcasecmp(n, "grey") == 0)
    return (FL_GRAY);
  else if (strcasecmp(n, "lime") == 0)
    return (FL_GREEN);
  else if (strcasecmp(n, "maroon") == 0)
    return (fl_color_cube(2, 0, 0));
  else if (strcasecmp(n, "navy") == 0)
    return (fl_color_cube(0, 0, 2));
  else if (strcasecmp(n, "olive") == 0)
    return (fl_color_cube(2, 4, 0));
  else if (strcasecmp(n, "purple") == 0)
    return (fl_color_cube(2, 0, 2));
  else if (strcasecmp(n, "silver") == 0)
    return (FL_LIGHT2);
  else if (strcasecmp(n, "teal") == 0)
    return (fl_color_cube(0, 4, 2));
  else
    return (c);
}


//
// 'HelpView::handle()' - Handle events in the widget.
//

int				// O - 1 if we handled it, 0 otherwise
HelpView::handle(int event)	// I - Event to handle
{
  int		i;		// Looping var
  int		xx, yy;		// Adjusted mouse position
  HelpLink	*link;		// Current link
  char		target[32];	// Current target


  switch (event)
  {
    case FL_MOVE :
    case FL_PUSH :
        xx = Fl::event_x() - x();
	yy = Fl::event_y() - y() + topline_;
        if (!scrollbar_.visible() || xx < (w() - 20))
	  break;

    default :
	// Use the Fl_Group handler...
	return (Fl_Group::handle(event));
  }

  // Handle mouse clicks on links...
  for (i = nlinks_, link = links_; i > 0; i --, link ++)
    if (xx >= link->x && xx < link->w &&
        yy >= link->y && yy < link->h)
      break;

  if (!i)
  {
    fl_cursor(FL_CURSOR_DEFAULT);
    return (1);
  }

  // Change the cursor for FL_MOTION events, and go to the link for
  // clicks...
  if (event == FL_MOVE)
    fl_cursor(FL_CURSOR_HAND);
  else
  {
    fl_cursor(FL_CURSOR_DEFAULT);

    strncpy(target, link->name, sizeof(target) - 1);
    target[sizeof(target) - 1] = '\0';

    set_changed();

    if (strcmp(link->filename, filename_) != 0 && link->filename[0])
    {
      char	dir[1024];	// Current directory
      char	temp[1024];	// Temporary filename


      if (link->filename[0] != '/' && strchr(link->filename, ':') == NULL)
      {
	if (directory_[0])
	  sprintf(temp, "%s/%s", directory_, link->filename);
	else
	{
	  getcwd(dir, sizeof(dir));
	  sprintf(temp, "file:%s/%s", dir, link->filename);
	}

        load(temp);
      }
      else
        load(link->filename);
    }
    else if (target[0])
      topline(target);
    else
      topline(0);
  }

  return (1);
}


//
// 'HelpView::HelpView()' - Build a HelpView widget.
//

HelpView::HelpView(int        xx,	// I - Left position
                   int        yy,	// I - Top position
		   int        ww,	// I - Width in pixels
		   int        hh,	// I - Height in pixels
		   const char *l)
    : Fl_Group(xx, yy, ww, hh, l),
      scrollbar_(xx + ww - 17, yy, 17, hh)
{
  filename_[0] = '\0';
  value_       = NULL;

  ablocks_     = 0;
  nblocks_     = 0;
  blocks_      = (HelpBlock *)0;

  alinks_      = 0;
  nlinks_      = 0;
  links_       = (HelpLink *)0;
  link_        = (HelpFunc *)0;

  atargets_    = 0;
  ntargets_    = 0;
  targets_     = (HelpTarget *)0;

  nfonts_      = 0;
  textfont_    = FL_TIMES;
  textsize_    = 12;

  topline_     = 0;
  size_        = 0;

  color(FL_WHITE);
  textcolor(FL_BLACK);
  selection_color(FL_BLUE);

  scrollbar_.value(0, hh, 0, 1);
  scrollbar_.step(8.0);
  scrollbar_.show();
  scrollbar_.callback(scrollbar_callback);

  end();
}


//
// 'HelpView::~HelpView()' - Destroy a HelpView widget.
//

HelpView::~HelpView()
{
  if (nblocks_)
    free(blocks_);
  if (nlinks_)
    free(links_);
  if (ntargets_)
    free(targets_);
  if (value_)
    free((void *)value_);
}


//
// 'HelpView::load()' - Load the specified file.
//

int				// O - 0 on success, -1 on error
HelpView::load(const char *f)	// I - Filename to load (may also have target)
{
  FILE		*fp;		// File to read from
  long		len;		// Length of file
  char		*target;	// Target in file
  char		*slash;		// Directory separator
  const char	*localname;	// Local filename
  char		error[1024];	// Error buffer


  strcpy(filename_, f);
  strcpy(directory_, filename_);

  if ((slash = strrchr(directory_, '/')) == NULL)
    directory_[0] = '\0';
  else if (slash > directory_ && slash[-1] != '/')
    *slash = '\0';

  if ((target = strrchr(filename_, '#')) != NULL)
    *target++ = '\0';

  if (link_)
    localname = (*link_)(filename_);
  else
    localname = filename_;

  if (localname != NULL &&
      (strncmp(localname, "ftp:", 4) == 0 ||
       strncmp(localname, "http:", 5) == 0 ||
       strncmp(localname, "https:", 6) == 0 ||
       strncmp(localname, "ipp:", 4) == 0 ||
       strncmp(localname, "mailto:", 7) == 0 ||
       strncmp(localname, "news:", 5) == 0))
    localname = NULL;	// Remote link wasn't resolved...
  else if (localname != NULL &&
           strncmp(localname, "file:", 5) == 0)
    localname += 5;	// Adjust for local filename...
      
  if (value_ != NULL)
  {
    free((void *)value_);
    value_ = NULL;
  }

  if (localname)
  {
    if ((fp = fopen(localname, "rb")) != NULL)
    {
      fseek(fp, 0, SEEK_END);
      len = ftell(fp);
      rewind(fp);

      value_ = (const char *)calloc(len + 1, 1);
      fread((void *)value_, 1, len, fp);
      fclose(fp);
    }
    else
    {
      sprintf(error, "%s: %s\n", localname, strerror(errno));
      value_ = strdup(error);
    }
  }
  else
    value_ = strdup("File or link could not be opened.\n");

  format();

  if (target)
    topline(target);
  else
    topline(0);

  return (0);
}


//
// 'HelpView::resize()' - Resize the help widget.
//

void
HelpView::resize(int xx,	// I - New left position
                 int yy,	// I - New top position
		 int ww,	// I - New width
		 int hh)	// I - New height
{
  Fl_Widget::resize(xx, yy, ww, hh);
  scrollbar_.resize(xx + ww - 17, yy, 17, hh);

  format();
}


//
// 'HelpView::topline()' - Set the top line to the named target.
//

void
HelpView::topline(const char *n)	// I - Target name
{
  HelpTarget	key,			// Target name key
		*target;		// Pointer to matching target


  if (ntargets_ == 0)
    return;

  strncpy(key.name, n, sizeof(key.name) - 1);
  key.name[sizeof(key.name) - 1] = '\0';

  target = (HelpTarget *)bsearch(&key, targets_, ntargets_, sizeof(HelpTarget),
                                 (int (*)(const void *, const void *))compare_targets);

  if (target != NULL)
    topline(target->y);
}


//
// 'HelpView::topline()' - Set the top line by number.
//

void
HelpView::topline(int t)	// I - Top line number
{
  if (!value_)
    return;

  if (size_ < (h() - 8) || t < 0)
    t = 0;
  else if (t > size_)
    t = size_;

  topline_ = t;

  scrollbar_.value(topline_, h(), 0, size_);

  do_callback();
  clear_changed();

  redraw();
}


//
// 'HelpView::value()' - Set the help text directly.
//

void
HelpView::value(const char *v)	// I - Text to view
{
  if (!v)
    return;

  if (value_ != NULL)
    free((void *)value_);

  value_ = strdup(v);

  format();

  set_changed();
  topline(0);
}


//
// 'scrollbar_callback()' - A callback for the scrollbar.
//

static void
scrollbar_callback(Fl_Widget *s, void *)
{
  ((HelpView *)(s->parent()))->topline(int(((Fl_Scrollbar*)s)->value()));
}


//
// End of "$Id: HelpView.cxx,v 1.1 2003/09/16 10:35:14 hut66au Exp $".
//