/*-------------------------------------*
 | File: TS.c - Text scroller routines |
 +-------------------------------------------------------------*
 | These routines were inspired from an example due to Martin  |
 | Taillefer and published on AmigaMail; although the original |
 | routines have been heavily modified, I am in debt with M.T. |
 +---------------------------------------------------------+---*
 | Author:  Maurizio Loreti, aka MLO or I3NOO.             |
 | Address: University of Padova - Department of Physics   |
 |          Via F. Marzolo, 8 - 35131 PADOVA - Italy       |
 | Phone:   (39)(49) 844-313         FAX: (39)(49) 844-245 |
 | E-Mail:  loreti@padova.infn.it (TCP/IP)                 |
 | Home: Via G. Donizetti 6 - 35010 CADONEGHE (PD) - Italy |
 *---------------------------------------------------------*/

/**
 | #includes
**/

#include <stddef.h>                       /* Standard library */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>                   /* AmigaDOS specific */
#include <clib/graphics_protos.h>
#include <clib/layers_protos.h>
#include <clib/gadtools_protos.h>
#include <clib/intuition_protos.h>
#include "main.h"                         /* Local stuff */
#include "ts.h"
#include "ext.h"

void AddLine(
  char *s,
  size_t n,
  BOOL update
){
/**
 | Adds the string "s" ("n" bytes long) to our document:
 | a new "Line" structure is added to the linked list, and
 | filled; if "update" is TRUE, the text is scrolled to
 | display the last line and updated.
**/

  Line *pL;

  if ((pL = malloc(sizeof(Line) + n)) == NULL) {
    Error("No memory for Line");
  }

  pL->sl_Length = n;
  strcpy(pL->sl_Text, s);
  pL->sl_Forw = NULL;
  if ((pL->sl_Back = lastLine) == NULL) {
    firstLine = pL;
  } else {
    lastLine->sl_Forw = pL;
  }
  lastLine = pL;
  numLines++;

  if (update) {
    if (numLines > linesVisible) {
      topLine = numLines - linesVisible;
    } else {
      topLine = 0;
    }
    UpdateScroller();
  }
}

void BusyState(
  BOOL state
){
/**
 | Whenever the application is busy, this function should be called
 | as BusyState(TRUE); this will change the pointer to a default
 | OS 2.0 busy-pointer too, to tell the user that we are not
 | listening to the IDCMP's for awhile. When finished, call
 | BusyState(FALSE).
**/

  static UWORD __chip waitPointer[] = {
    0x0000, 0x0000, 0x0400, 0x07C0, 0x0000, 0x07C0, 0x0100, 0x0380,
    0x0000, 0x07E0, 0x07C0, 0x1FF8, 0x1FF0, 0x3FEC, 0x3FF8, 0x7FDE,
    0x3FF8, 0x7FBE, 0x7FFC, 0xFF7F, 0x7EFC, 0xFFFF, 0x7FFC, 0xFFFF,
    0x3FF8, 0x7FFE, 0x3FF8, 0x7FFE, 0x1FF0, 0x3FFC, 0x07C0, 0x1FF8,
    0x0000, 0x07E0, 0x0000, 0x0000
  };

  if (state == TRUE) {
    SetPointer(pWind, waitPointer, 16, 16, -6, 0);
  } else {
    ClearPointer(pWind);
  }

  if (pWind->WLayer->Flags & LAYERREFRESH) {
    RefreshView(TRUE);
  }
}

void ClearText(
  BOOL update
){
/**
 | This procedure free()s all the linked list holding the
 | scrolled text. If "update" is TRUE, the window scrolling
 | region is really cleared.
**/

  Line *pL;

  for (pL=firstLine; pL!=NULL; ) {
    Line *next = pL->sl_Forw;

    free(pL);
    pL = next;
  }

  firstLine = lastLine = NULL;
  numLines = topLine = 0;

  if (update) {
    SetScroller(pWind, pScroller, linesVisible, numLines, topLine);
    RefreshView(TRUE);
  }
}

void InitScroller(void)
{
/**
 | Scroller initialisation.
 | - memory is obtained to hold a copy of the window raster port to
 |   be used for clearing (the originel raster port is used for
 |   rendering); this speeds up these operations avoiding SetAPen() etc.
 | - some local global variables are initialised;
 | - the scrolling region is drawn (with the help text loaded in the
 |   initialisation routines).
**/

  memcpy(pClearRP, (pRP = pWind->RPort), sizeof(struct RastPort));
  SetDrMd(pClearRP, JAM2);
  SetAPen(pClearRP, 0);
  SetAPen(pRP, 1);

  fontWidth  = pWind->RPort->Font->tf_XSize;
  fontHeight = pWind->RPort->Font->tf_YSize;

  bevelLeft   = pWind->BorderLeft + BEVEL_LEFT;
  bevelTop    = pWind->BorderTop  + BEVEL_TOP;
  bevelWidth  = BEVEL_WIDTH;
  bevelHeight = BEVEL_HEIGHT;
  textLeft    = bevelLeft + TEXT_XOFFSET;
  textTop     = bevelTop  + TEXT_YOFFSET;

  viewWidth      = (USHORT) (bevelWidth  - 2 * (TEXT_XOFFSET) - 1);
  viewHeight     = (USHORT) (bevelHeight - 2 * (TEXT_YOFFSET) - 1);
  columnsVisible = viewWidth  / fontWidth;
  linesVisible   = viewHeight / fontHeight;
  usefulWidth    = (USHORT) (columnsVisible * fontWidth);

  RefreshView(TRUE);
  SetScroller(pWind, pScroller, linesVisible, numLines, topLine);
}

void LastLine(
  char *s,
  size_t n
){
/**
 | A line is drawn at the end of the text, but NOT inserted
 | in the linked list; it will then be overdrawn from the next
 | AddLine()d or LastLine()d text; the text is scrolled if needed,
 | to display the bottom line.
 | LastLine() here is only used to display the
 | "reading track X head Y" text.
 | "s" is the string to be displayed, "n" bytes long.
**/

  USHORT y;

  if (numLines >= linesVisible) {
    topLine = numLines - linesVisible + 1;
    UpdateScroller();
    y = linesVisible;
  } else {
    y = numLines - topLine + 1;
  }
  y = (USHORT) (textTop + pRP->TxBaseline + fontHeight * (y - 1));
  RenderLine(textLeft, y, usefulWidth, s, n);
}

void RefreshView(
  BOOL damage
){
/**
 | This function performs most of the rendering work needed.
 | It looks at the current window size and adjusts its rendering
 | variables in consequence; then, if the "damage" parameter is
 | TRUE, this routine explicitly erases any area of the display
 | in which we will not be rendering in the rendering loop: this
 | erases any left over characters that could be there if the
 | user sizes or rearrange the window. Finally, the routine
 | determines which lines of the display need to be updated,
 | and goes on to do it.
**/

  USHORT i;
  Line *pL;
  USHORT y;

/**
 | What is the top line to be rendered?
**/

  if (linesVisible > numLines) {
    usefulHeight = (USHORT) (numLines * fontHeight);
    topLine = 0;
  } else {
    usefulHeight = (USHORT) (linesVisible * fontHeight);
    if (topLine + linesVisible > numLines) {
      topLine = numLines - linesVisible;
    }
  }

/**
 | If the layer has been damaged (and the window is to be refreshed),
 | this is handled now (the gadgetry is already OK at this moment).
**/

  if (damage) {
    RectFill(pClearRP,
             bevelLeft - 1,
             bevelTop  - 1,
             bevelLeft + bevelWidth,
             bevelTop  + bevelHeight);
    DrawBevelBox(pRP,
                 bevelLeft,     bevelTop,
                 bevelWidth,    bevelHeight,
                 GT_VisualInfo, pVI,
                 TAG_END);
  }

/**
 | Do we have something to render? If yes, we find the pointer to the
 | top line text, starting from the top or the bottom of the list.
**/

  if (usefulHeight   &&   usefulWidth) {
    int n;

    if ((n = numLines - (topLine + 1)) < topLine) {
      pL = ScanList(firstLine, topLine);
    } else {
      pL = ScanList(lastLine, -n);
    }

    y = (USHORT) (textTop + pRP->TxBaseline - fontHeight);

    if (damage   ||   (topLine >= oldTopLine + linesVisible - 1)   ||
        ((oldTopLine > linesVisible) &&
         (topLine <= oldTopLine - linesVisible + 1))) {
      i = linesVisible;
    } else if (topLine < oldTopLine) {
      ScrollRaster(pRP,
                   0, -((long) (oldTopLine - topLine) * fontHeight),
                   textLeft,
                   textTop,
                   textLeft + usefulWidth - 1,
                   textTop + usefulHeight - 1);

      i = oldTopLine - topLine;
    } else if (topLine > oldTopLine) {
      ScrollRaster(pRP,
                   0, ((topLine - oldTopLine) * fontHeight),
                   textLeft,
                   textTop,
                   textLeft + usefulWidth - 1,
                   textTop + usefulHeight - 1);

      for (i = linesVisible - (topLine - oldTopLine);
           pL->sl_Forw != NULL  &&  i;
           i--) {
        pL = pL->sl_Forw;
      }

      y += (USHORT) (fontHeight * (linesVisible - (topLine - oldTopLine)));
      i  = topLine - oldTopLine;
    } else if (oldNumLines < numLines  &&  numLines <= linesVisible) {
      for (i = oldNumLines;   pL->sl_Forw != NULL  &&  i;   i--) {
        pL = pL->sl_Forw;
      }

      y += (USHORT) (fontHeight * oldNumLines);
      i  = numLines - oldNumLines;
    } else {
      i = 0;
    }

    while (i--   &&   pL != NULL) {
      y += fontHeight;
      RenderLine(textLeft, y, usefulWidth, pL->sl_Text, pL->sl_Length);
      pL = pL->sl_Forw;
    }
  }

  oldTopLine  = topLine;
  oldNumLines = numLines;
}

void SetTopLine(
  UWORD n
){
/**
 | Sets the top line of the scroller text to "n".
**/

  if (topLine != n) {
    topLine = n;
    RefreshView(FALSE);
  }
}

/*-------------------------- Local Procedures --------------------------*/

static void RenderLine(
  USHORT  x,
  USHORT  y,
  USHORT  w,
  char   *text,
  size_t  len
){
/**
 | Render a single line of text ("text", "len" characters
 | long) at the given position "x","y" (pixels). "w" is the
 | length (pixels) of the rendering region.
**/

  Move(pRP, x, y);
  if (len > columnsVisible) {
    len = columnsVisible;
  }

  (void) Text(pRP, text, len);
  if (len < columnsVisible) {
    RectFill(pClearRP,
             pRP->cp_x,
             y - pRP->TxBaseline,
             x + w - 1,
             y - pRP->TxBaseline + fontHeight - 1);
  }
}

static void SetScroller(
  struct Window *pW,
  struct Gadget *pG,
  USHORT lV,
  USHORT nL,
  USHORT tL
){
/**
 | Adjust the scroller gadget to reflect the current setting:
 | "lV" lines visible, "nL" total lines and "tL" number of the
 | top line.
**/

  GT_SetGadgetAttrs(pG, pW, NULL,
      GTSC_Visible,   lV,
      GTSC_Total,     nL,
      GTSC_Top,       tL,
      TAG_DONE);
}

static Line *ScanList(
  register Line *pL,
  register int n
){
  register Line *pNew;

  if (n > 0) {
    while (n--   &&   (pNew = pL->sl_Forw) != NULL)   pL = pNew;
  } else if (n < 0) {
    while (n++   &&   (pNew = pL->sl_Back) != NULL)   pL = pNew;
  }
  return pL;
}

static void UpdateScroller(void)
{
/**
 | Updates the text scrolling region, reflecting
 | the current settings.
**/

  SetScroller(pWind, pScroller, linesVisible, numLines, topLine);
  RefreshView(FALSE);
}
