/* CLASS: CachingBrowser
   This browser keeps track of which chunk a line of cacheText belongs,
   does the scrollbar arithmetic, and displays the text by calling a
   display routine.
   See the file "CachingB.nts for design and usage details.

   REQUIRE: VIRTDATA.CLS VIRTDASM.CLS VIRTFILE.CLS & their child objects.

*/!!

inherit(Window, #CachingBrowser, #(cacheStartLine /* 1st cacheText line displayed */
cacheText    /* a TextCollection */
cacheValid  /* flag */
chunkInfo  /* chunk mgmt info */
dataObject /* source for chunks */
drawRoutine  /* a code block:  drawRoutine(vLineNum, #(aStr, yPix, lineWidth), hDC) */
getTxtLineRoutine /* code block:
  getTxtLineRoutine(txtLine) */
startCol /* Start column of text collection */
textMetrics /* A Struct with font information */
tmHeight    /* Height of font in pixels */
tmWidth     /* Width of font in pixels */
virtualLineLimit  /* last virtual line # */
virtualStartLine /* virtual line which is cacheText line 0 */
xMax        /* Maximum number chars printable in line */
lineAdjustment /* info about line number adjustment */
visibleLines), 2, nil)!!

now(class(CachingBrowser))!!

/* 06/22/92 - PUBLIC
   Create a new CachingBrowser: 
   parent -- parent window or nil
   rect   -- location (size) rectangle--where to put window
   style  -- window style (VScroll and Child attributes added here)
   virtualDataObject -- source of text data and info:
      getChunk( virtualDataObject, chunkNumber ) 
          -- gets a textCollection "chunk"
      chunkNumberFromLineNumber( virtualDataObject, vLineNumber ) 
          -- gets chunk # containing virtual line vLineNumber
   totalLines -- number of lines in virtual data object (guess if unknown)
   firstLine  -- virtual line # of 1st line to display (top of screen) 
           
*/
Def new(self, parent, rect, style, virtualDataObject, totalLines, firstLine 
        | newInstance )
{ 
  newInstance := new(self:Behavior);
  setPaintStruct(newInstance, new(Struct, 32));
  create(newInstance, 
         parent, 
         "", 
         rect cor sizeRect(self),
         (style bitOr WS_VSCROLL bitOr WS_HSCROLL bitOr WS_CHILD)
        );
  setLocRect(newInstance);
  ^init(newInstance, virtualDataObject, totalLines, firstLine); 
}
!!

now(CachingBrowser)!!

/* 6/10/1993 6:06 */
Def clearLineAdjustment(self)
{
  lineAdjustment := new(OrderedCollection, 0);
}
!!

/* 6/10/1993 6:06 */
Def getLineAdjustment(self, line)
{
  /* NOTES: lineAdjustment = #(line number, line increment) */
  do(lineAdjustment,
     { using(adjustment)
     if (line >= adjustment[0])
       line := line + adjustment[1];
     endif;
     });
  ^line;
}
!!

/* 6/9/1993 7:37 */
Def setCallbacks(self)
{
  /* Set the resizeCallback routine for virtualdataObject */
  setResizeCallback(dataObject, 
    {using(numLines) 
      setVirtualLines(self, numLines);
    });
  /* Set the lineAdjustCallback routine for virtualdataObject */
  setLineAdjustCallback(dataObject, 
    {using(firstLine, adjustment) 
      if (chunkInfo) recalcChunks(chunkInfo, dataObject); endif;
      add(lineAdjustment, tuple(firstLine, adjustment));
    });
 }
!!

/* 7/26/1992 23:18 - PUBLIC
  Set the get text line routine which gets called to copy text from the 
  cached buffer to be displayed by the drawRoutine.
  (parent client supplied)
*/
Def setGetTextLineRoutine(self, aBlock)
{ 
  ^getTxtLineRoutine := aBlock;
}
 
!!

/* 06/20/92 - PRIVATE 
   Get chunk with number chunkNum and add it at the beginning of cacheText.
   Return nil on failure, chunk number on success.
   Don't forget to update chunkInfo and virtualStartLine.
*/
Def addChunkAtBeginning(self, chunkNum | aChunk, chunkSize)
{ 
  /* WHERE: aChunk = #(startLine#, TextCollection) of the dataObject */
  if not(aChunk :=  getChunk(dataObject, chunkNum )) then
    ^nil;
  endif;
  
  chunkSize := size(aChunk[1]);
 
  /* Add text to cache with text collection (aChunk[1]) - start at 0 */
  setCacheText( self, insertAll(cacheText, aChunk[1], 0 ));

  /* tell the chunkManager about it: chunkNum, startLine, numLines */
  addAtTop(chunkInfo, chunkNum, aChunk[0], chunkSize);
  
  /* update the books */
  virtualStartLine := getFirstLineNumber(chunkInfo);
  
  ^chunkNum
}
!!

/* 06/20/92 - PRIVATE 
   Get chunk with number chunkNum and add it at the end of cacheText.
   Return nil on failure, chunk number on success.
   Don't forget to update chunkInfo.
*/
Def addChunkAtEnd(self, chunkNum | aChunk, chunkSize)
{ 
  /* NOTES: aChunk = #(startLine#, TextCollection) of the dataObject */
  if not(aChunk :=  getChunk(dataObject, chunkNum )) then 
    ^nil ; 
  endif ;

  chunkSize := size(aChunk[1]) ;
  
  /* 
  ** NOTES: AppendText to cacheText with text collection (aChunk[1]) at
  ** index total cacheText (size(cacheText)).
  */
  setCacheText( self, insertAll(cacheText, aChunk[1], size(cacheText) ) ) ;

  /* Tell the chunkManager about it: chunkNum, startLine, numLines */
  addAtBottom(chunkInfo, chunkNum, aChunk[0], chunkSize);

  ^chunkNum
}
!!

/* 06/23/92 - PRIVATE 
   Cache has been flushed. Get first/initial chunk and return its chunk number
   Return nil on failure.
   
   NOTES: If vStartLine is > virtualLineLimit it is unlikely that a
    chunk will be found.
*/
Def addFirstChunk(self, vStartLine | aChunk, chunkNum)
{ 
  /* Given a line#, get its chunk number */
  if not(chunkNum := chunkNumberFromLineNumber(dataObject, vStartLine)) then
    ^nil
  endif ;
  
  /* NOTES: aChunk = #(startLine#, TextCollection) of the dataObject */
  if not(aChunk :=  getChunk(dataObject, chunkNum )) then
    ^nil ; 
  endif ;
  setCacheText(self, aChunk[1] ) ; /* grab 1st text collection */
  
  /* Tell the chunkManager about it: chunkNum, startLine, numLines */
  addAtTop(chunkInfo, chunkNum, aChunk[0], size(aChunk[1]) ) ;
  /* Update the books */
  virtualStartLine := getFirstLineNumber(chunkInfo);
  cacheStartLine   := 0 ;  /* start display at 1st cached text line...   */
                           /* caller will reset if he does not like this */
  ^chunkNum ; /* return number of the chunk we found */
}
!!

/* 06/23/92 - PRIVATE
   Check if the browser may need more text.  Check for it and get it as necessary.
*/
Def cacheHit(self, vStartLine, numLinesNeeded | 
    cacheOK, vSLine, cSLine, oldCache, oldChunk, savedOldCache)
{ 
  /* Save the old cache info to restore if user abort */
  if (savedOldCache := cacheValid) then
    vSLine   := virtualStartLine;
    cSLine   := cacheStartLine;
    oldCache := cacheText;
    oldChunk := deepCopy(chunkInfo);  
    clearAbort(dataObject); /* Reset the dataObject abort flag */
  endif;
     
  /* If not cacheValid or there is not enough lines, get more */
  if rangeDisjoint?(self, vStartLine, numLinesNeeded) then
    flushCache(self);  /* invalidates cache */
  endif ;
  
  clearLineAdjustment(self);   /* Callback will modify if lines are renumbered */
  showWaitCurs();
  if not(cacheValid) then
    cacheOK := rebuildCache(self, vStartLine, numLinesNeeded);
  else 
    cacheOK := validateCache(self, vStartLine, numLinesNeeded);
  endif ; 
  showOldCurs();

  /* Make sure that there is cacheText to display */
  if not(cacheText) cor (size(cacheText) = 0) then
    flushCache(self); 
    cacheOK := rebuildCache(self, vStartLine, numLinesNeeded);
  endif;  

  /* User abort requested - Restore the old display (cacheStartLine, cacheText) */
  if ((cacheOK <> BOOL_TRUE) cand (savedOldCache)) then
    virtualStartLine := vSLine;
    cacheStartLine   := cSLine;
    setCacheText(self, oldCache);
    chunkInfo        := oldChunk;
    oldChunk := nil;
    /* Make sure that there is cacheText to display after abort */
    if not(cacheText) then
      flushCache(self); 
      rebuildCache(self, vSLine, numLinesNeeded);
    endif;  
    /* clearPC() to wipe out the old PC mark */
    clearPC(self); 
    ^BOOL_TRUE;
  endif;  

  /* If filling cache caused line adjustment, update vStartLine to point
     to the same text as before */
  vStartLine := getLineAdjustment(self, vStartLine);  
  /* Show text starting from vStartLine - This may be different than virtualStartLine. */
  cacheStartLine := max(0, (vStartLine - virtualStartLine));  
  ^cacheOK;   
}
!!

/* 06/20/92 - PUBLIC
   Translate from viewport line # to cacheText line # 
*/
Def cacheLineFromViewLine(self, viewLineNumber)
{ 
  ^(viewLineNumber + cacheStartLine(self))
}
!!

/* 06/20/92 - PRIVATE */
Def cacheStartLine(self)
{ 
  ^cacheStartLine; 
}
!!

/* 06/20/92 - PRIVATE
  Return the cacheText, usually a text collection. 
*/
Def cacheText(self)
{ 
  ^cacheText;
}!!

/* 06/20/92 - PRIVATE */
Def chunkInfo(self)
{
  /* chunkInfo = #(startLine#, TextCollection) */
  ^chunkInfo 
}
!!

/* 06/20/92 - PRIVATE */
Def dataObject(self)
{
  /* dataObject = VirtSmartFileObject | VirtSmrtDasmObject | VirtMixedObject */
  ^dataObject 
}
!!

/* 06/24/92 - PRIVATE 
   Flush cache data and make cache text as invalid.
   **Don't redraw**.
*/
Def flushCache(self)
{ 
  /* Reset all cache variable */
  cacheText        := nil ;
  cacheStartLine   := 0 ;
  chunkInfo        := new(ChunkManager, 0); 
  cacheValid       := nil ;
  startCol         := 0;
}
!!

/* 06/20/92 - PRIVATE
  Return a display context for self.  Set the display
  context's font to be the System Fixed Font. 
*/
Def getContext(self | hdc)
{ 
  hdc := getContext(self:ancestor);
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FIXED_FONT));
  ^hdc;
}!!

/* 06/22/92 - PRIVATE
  Initialize a CachingBrowser instance.
*/
Def init(self, virtualDataObject, totalLines, firstLine )
{
  /* Set defaults */
  init(self:ancestor);
  initTextMetrics(self);
  flushCache(self);
  
  /* save parameter values */
  virtualStartLine := firstLine ; /* NOTES: virtual line # starts at 1 */
  virtualLineLimit := totalLines ;
  drawRoutine      := nil; /* Set by child object after init */
  dataObject       := virtualDataObject ; 
  startCol         := 0;   /* Column 0 to 255 */
  setCallbacks(self);
  clearLineAdjustment(self);

  /* set derived values */
  xMax := right(clientRect(self) ) / (tmWidth - 1); 
  visibleLines := (height(clientRect(self)) / tmHeight);
  ^self;
}!!

/* 06/20/92 - PRIVATE
  Initialize text metrics data for this window.  Load the font data 
  into textMetrics, set the text width and height instance variables.
*/
Def initTextMetrics(self | hdc, tm)
{ 
  tm := new(Struct, 32);
  Call GetTextMetrics(hdc := getContext(self), tm);
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FIXED_FONT));
  tmWidth  := asInt(wordAt(tm, 10));
  tmHeight := asInt(wordAt(tm, 8)) + asInt(wordAt(tm, 0));
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FONT));
  releaseContext(self, hdc);
}!!

/* 06/20/92 - PUBLIC 
   Caller notifies us that we have to update snapshot & redraw...
*/
Def invalidateCache(self)
{
  /* 
  ** CacheStartLine gets reset to 0, so anchor virtualStartLine
  ** at position the user sees.  Then cacheHit will do the righ math.
  */
  virtualStartLine := (virtualStartLine + cacheStartLine);
  flushCache(self) ;
  cacheHit(self, virtualStartLine, visibleLines);
  invalidate(self) ; /* cause repaint */
}
!!

/* 06/20/92 - Actor (PUBLIC) 
  Redraw the cacheText. Call the (child client supplied) drawRoutine to do this. 
  Change local coordinates to virtual coordinates, as applicable. 
*/
Def paint(self, hdc | yPos, aStr, startLine, endLine)
{ 
  if not(cacheText) then
    ^nil;
  endif;
  clearPC(self);
  initTextColors(self, hdc);
  setVScrollPos(self);
  setHScrollPos(self);
  yPos := 0; 
  startLine := asInt(cacheStartLine);
  endLine :=  asInt(min(size(cacheText),(cacheStartLine + 1 + visibleLines)) );   
  /* DEBUG 
  printLine("Paint @:  "+asString(startLine)+" - "+asString(endLine));
  */
  do(over(startLine, endLine),
    {using(line) 
      /* getTxtLineRoutine = copyFrom(cacheText[line], startChar, size(cacheText[line]));  */
      aStr := eval(getTxtLineRoutine, cacheText[line]);  
      /*
      ** NOTES: printFun -- a block which takes arguments: {eval takes 4 args max}
      ** printFun( virtualLineNumer, 
      **          tuple(textString, yPixelPos, lineWidth), drawingContext)
      */ 
      eval(drawRoutine,                
         (virtualStartLine + line),  /* VirtDataObj line # */
         tuple(
            aStr,                    /* the text itself */
            (yPos * tmHeight + 2),   /* Y position in pixels (x is always 0) */
            xMax(self)               /* line width in characters */
         ),
         hdc                         /* Drawing Context */
    );
    yPos := yPos + 1 ;
  });
}!!

/* 06/20/92 - PRIVATE 
   Is new cache range disjoint from current?
*/
Def rangeDisjoint?(self, vStartLine, numLinesNeeded 
                      | vEndLine, currentStart, currentEnd)
{ 
  if not(cacheValid) then
    ^#true ;
  endif ;
  
  /* calculate range endpoints */
  vEndLine     := (vStartLine + numLinesNeeded - 1) ;
  currentStart := getFirstLineNumber( chunkInfo ) ;
  currentEnd   := getLastLineNumber(  chunkInfo ) ;
  /* Range is not disjoint if current range contains either
     vStartLine or vEndLine */ 
  if ((vStartLine >= currentStart) and (vStartLine <= currentEnd))
     or
     ((vEndLine >= currentStart) and (vEndLine <= currentEnd))
  then
    ^nil ;  /* ranges overlap */
  endif ;
  
  ^#true ;  /* ranges disjoint */
}
!!

/* 06/20/92 - PRIVATE 
   Cache has been flushed.  Rebuild it.
      NOTES: return BOOL_FALSE if error occurs or user abort request.
*/
Def rebuildCache(self, vStartLine, numLinesNeeded 
                     | vEndLine, chunkNum, endData)
{
  if not(chunkNum := addFirstChunk( self, vStartLine )) then
    if userAbort?(dataObject) then
      ^BOOL_FALSE;
    else
      ^BOOL_TRUE ;
    endif;  
  endif ;
  if (vStartLine < 1) then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VS_RANGE, FORCE_POPUP, nil, nil, nil);
    ^BOOL_TRUE;
  endif ;
  
  vEndLine := (vStartLine + numLinesNeeded) ;
  endData  := nil ;  /* Note: may run out of data before display area */
  
  loop 
  while ((vEndLine > getLastLineNumber(chunkInfo)) cand not(endData))
  begin 
    chunkNum := (chunkNum + 1) ;
    endData  := not(addChunkAtEnd(self, chunkNum));
  endLoop;
  
  if userAbort?(dataObject) then
    ^BOOL_FALSE;
  endif;
  
  cacheStartLine := max(0, (vStartLine - getFirstLineNumber(chunkInfo)));
  cacheValid := #true;
  ^BOOL_TRUE;
}
!!

/* 06/20/92 - PRIVATE
  Given a display context for self, change the
  context's font back to the System Font and release
  the context. 
*/
Def releaseContext(self, hdc)
{ 
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FONT));
  ^releaseContext(self:ancestor, hdc);
}!!

/* 06/22/92 - WINDOWS
   Recalculate display width (in characters): xMax.
   Also, if browser grew, it may need to get some more text.
*/
Def reSize(self, wP, lP)
{ 
  /* init routine hit yet? */
  if not(chunkInfo) then ^0; endif ;
  visibleLines := max(0,(height(clientRect(self)) / tmHeight));
  xMax := asInt(right(clientRect(self)) / (tmWidth-1)); 
  cacheHit(self, (virtualStartLine + cacheStartLine), visibleLines);
  setVScrollPos(self);
  setHScrollPos(self);
}!!

/* 06/20/92 - PRIVATE 
   Set the cache text. 
*/
Def setCacheText(self, ct)
{ 
  ^cacheText := ct;
}!!

/* 06/20/92 - PRIVATE
  Set the sizing rectangle to the specified Rect. 
*/
Def setCRect(self, rect | result)
{
  result := setCRect( self:ancestor, rect ) ;
  xMax   := right(clientRect(self)) / (tmWidth-1) ;

  ^result
}!!

/* 06/20/92 - PUBLIC */
Def setDataObject(self, obj)
{
  ^dataObject := obj;
}
!!

/* 06/20/92 - PUBLIC
   Set the drawing routine which gets called to display text 
   (child client-supplied) 
*/
Def setDrawRoutine(self, aBlock)
{ 
  ^drawRoutine := aBlock;
}
!!

/* 06/20/92 - PUBLIC
   Set the font height in pixels. 
   CONSTRAINT: Assumes fixed pitch font.
*/
Def setTmHeight(self, tmh)
{ 
  ^tmHeight := tmh;
}!!

/* 06/20/92 - PUBLIC
   Set the font width in pixels. 
   CONSTRAINT: Assumes fixed pitch font.
*/
Def setTmWidth(self, tmw)
{ 
  ^tmWidth := tmw;
}!!

/* 06/20/92 - PUBLIC
   (Re)sets the number of lines in the virtual data object.
   Used when creator guessed wrong on # virtual data object lines. 
*/
Def setVirtualLines(self, numLines)
{ 
  virtualLineLimit := numLines;
  setVScrollPos(self); 
  setHScrollPos(self);
}
!!

/* 06/20/92 - Actor (PUBLIC)
  Display the TextWindow and calculate a new value
  for the maximum number of characters per line.  The
  val argument determines how the window will appear.
  See the Actor manual, Guide to the Actor Classes,
  Window class, to see the various possible values and
  effects for val. 
*/
Def show(self, val)
{ 
  show(self:ancestor, val);
  xMax := asInt(right(clientRect(self)) / (tmWidth - 1));
}!!

/* 06/20/92 - PUBLIC */
Def startLine(self)
{ 
  ^(virtualStartLine + cacheStartLine) 
}
!!

/* 06/20/92 - PUBLIC
   Return the tmHeight variable. 
*/
Def tmHeight(self)
{ 
  ^tmHeight;
}!!

/* 06/20/92 - PUBLIC
   Return the tmWidth variable. 
*/
Def tmWidth(self)
{ 
  ^tmWidth;
}!!

/* 06/20/92 - PRIVATE
   TextCollection may have more text than we need.  
   Trim off excess lines in units of Chunk.
*/
Def trimCache(self, vStartLine, vEndLine, direction)
{ 
  /* Call trimChunks() of ChunkManager to trim off cached text */
  setCacheText(self, trimChunks(chunkInfo, 
                         vStartLine, vEndLine, 
                         direction, 
                         cacheText)) ;
  /* Reset Line# information according to the new cached Text */
  virtualStartLine := getFirstLineNumber(chunkInfo);
  cacheStartLine   := (vStartLine - virtualStartLine);
  cacheStartLine   := asInt(cacheStartLine);
}
!!

/* 06/20/92 - PRIVATE
   There is an overlap in range between the desired lines and the
   actual lines.  Release lines that we don't need (by chunk).
   Get more lines if we need them (by chunk).  Be careful of boundary
   conditions (e.g. beyond 1st & last chunks).
      NOTES: return BOOL_FALSE if error occurs or user abort request.
*/
Def validateCache(self, vStartLine, numLinesNeeded 
                      | vEndLine, 
                      currentStart, currentEnd, 
                      direction, endData, chunkNum)
{ 
  vEndLine     := (vStartLine + numLinesNeeded) ;
  currentStart := getFirstLineNumber(chunkInfo);
  currentEnd   := getLastLineNumber(chunkInfo);
  
  if (vStartLine >= currentStart)  then
    cacheStartLine := (vStartLine - currentStart) ;
    if (vEndLine <= currentEnd) then
      ^BOOL_TRUE     /* Done! (we have all the text we need) */
    endif ;
  endif ;
 
  /* 
  ** Need more text if we get here 
  */
  /* Which way are we scrolling up/down? */
  if (vStartLine < virtualStartLine) then
    direction := #up ;     /* scrolling toward beginning */
  else 
    direction := #down ;   /* scrolling toward end */
  endif ;
    
  /* Get new text chunk for display */
  if (direction = #down) then
    chunkNum := getLastChunkNumber(chunkInfo);
    /* Note: may run out of data before display area or User abort */
    endData  := nil ;    
    loop 
    while ((vEndLine > getLastLineNumber(chunkInfo)) cand not(endData))
    begin 
      chunkNum := (chunkNum + 1) ;
      endData  := not(addChunkAtEnd( self, chunkNum )) ;
    endLoop;
  else /* direction = #up */
    chunkNum := getFirstChunkNumber(chunkInfo);
    /* Note: may run out of data before display area or User abort */
    endData  := nil ;  
    loop 
    while ((vStartLine < getFirstLineNumber(chunkInfo)) cand not(endData))
    begin 
      chunkNum := (chunkNum - 1) ;
      endData := not(addChunkAtBeginning(self, chunkNum));
    endLoop;
  endif ;
  
  if userAbort?(dataObject) then
    ^BOOL_FALSE; /* User abort */
  endif;

  /* Trim off the excess text in the cacheText buffer */
  trimCache( self, vStartLine, vEndLine, direction);
  ^BOOL_TRUE;
}
!!

/* 06/20/92 - PUBLIC */
Def virtualLineLimit(self)
{ 
 ^virtualLineLimit; 
}
!!

/* 06/20/92 - PRIVATE */
Def virtualStartLine(self)
{ 
  ^virtualStartLine; 
}
!!

/* 06/20/92 - PRIVATE
   Return the number of visible text lines in window. 
*/
Def visLines(self)
{ 
  ^(height(clientRect(self)) / tmHeight);
}!!

/* PRIVATE - OLD STUFF
   Xlate from cacheText line # to virtual data object line # 
*/
Def vObjLineFromCacheLine(self, cacheLineNumber)
{ 
  ^(cacheLineNumber + virtualStartLine(self))
}
!!

/* PRIVATE - OLD STUFF
   Xlate from viewline line # to virtual data object line # 
*/
Def vObjLineFromViewLine(self, viewLineNumber)
{ 
  ^vObjLineFromCacheLine(self, cacheLineFromViewLine(self, viewLineNumber) );
}
!!

/* 06/20/92 - WINDOWS
  MS-Window's message to paint self -- sends a
  paint(self) message.  This overrides Window:WM_PAINT
  so that TextWindow and its descendants use the
  System Fixed Font instead of the System Font. */
Def WM_PAINT(self, wP, lP | hdc)
{ 
  hdc := Call BeginPaint(getHWnd(self), paintStruct);
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FIXED_FONT));
  paint(self, hdc);
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FONT));
  Call EndPaint(getHWnd(self), paintStruct);
  ^0;
}!!

/* 06/20/92 - WINDOWS
  Respond to MS-Window's vertical scrolling message. wP tells what kind of
  scrolling request has been made. This code is "inlined" because it should
  typically be fast.
  NOTES: This method is used as a template for the child classes.
*/
Def WM_VSCROLL(self, wP, lP | fixRect, viewLines, vStart)
{
  viewLines := visibleLines;        /* number of lines visible on screen */
  vStart := ( virtualStartLine + cacheStartLine ) ;  /* virtual line 1st displayed */
  
  select
    case (viewLines > virtualLineLimit)
    is
      ^0  /* entire text is already shown */
    endCase

    case (wP == SB_LINEDOWN) 
    is 
      if ( vStart > (virtualLineLimit - (viewLines-1)) ) then
        ^0 /* at end */
      else
        cacheHit(self, (vStart + 1), viewLines) ;
        Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0) ;
        setVScrollPos(self);
        ^0;
      endif ;
    endCase

    case wP == SB_PAGEDOWN
    is 
      if ((vStart + viewLines + 1) > virtualLineLimit) then
        ^0 /* at end */
      else
        if ((virtualLineLimit - (vStart + viewLines - 1)) < viewLines ) then
          cacheHit( self, max(1, (virtualLineLimit - (viewLines-2))), viewLines ) ;
        else
          cacheHit( self, (vStart + viewLines), viewLines ) ;
        endif ;
        invalidate( self ) ;
      endif ;
    endCase

    case wP == SB_BOTTOM
    is 
      cacheHit( self, max(1, (virtualLineLimit - (viewLines-1))), viewLines ) ;
      invalidate( self ) ;
    endCase

    case (wP == SB_LINEUP) and (vStart > 1)
    is 
      cacheHit(self, (vStart - 1), viewLines) ;
      fixRect := clientRect(self);
      setBottom(fixRect, tmHeight + 2);
      Call ScrollWindow(hWnd, 0, tmHeight, 0, 0);
      Call InvalidateRect(hWnd, fixRect, 1);
      setVScrollPos(self);
    endCase

    case wP == SB_PAGEUP
    is
      if (vStart = 1)
      then
        ^0 /* at beginning */
      else
        cacheHit( self, max(1, (vStart - (viewLines-1))), viewLines ) ;
        invalidate( self ) ;
      endif ;
    endCase

    case wP == SB_TOP
    is 
      cacheHit( self, 1, viewLines ) ;
      invalidate( self ) ;
    endCase

    case  (wP == SB_THUMBPOSITION) /* cor (wP == SB_THUMBTRACK) */
    is 
      vStart := asInt(((virtualLineLimit-visibleLines+2)*low(lP))/100);
      if vStart < 1
      then
        vStart := 1 ;
      else
        if vStart > (virtualLineLimit - (viewLines-2))
        then
          vStart := max(1, (virtualLineLimit - (viewLines-2)) );
        endif ;
      endif ;
      cacheHit( self, vStart, viewLines ) ;
      invalidate( self ) ;
    endCase
  endSelect;

  /* NOTES: invalidate(self) causes a repaint; set VScrollPos done in paint() */
  ^0 
}!!

/* 06/20/92 - PRIVATE
   Return the xMax instance variable (denotes view width in characters). 
*/
Def xMax(self)
{ 
  ^xMax;
}!!
