/* CLASS: VARBROWSER
   Variable Browser instances are used by the Variable Presenter
   and Stack Presenter windows.

   Requires or parent:
    selected( parent, what )   -- what is AT_*_FIELD
    menu key propagation
*/!!

inherit(TextWindow, #VarBrowser, #(viewColl /* SortedCollection of VPviewInfo */
varCollection /* SortedCollection of VPvarInfo */
varSession /* Variable Server session/connection instance */
focusVar /* 1st var on display:redraw */
selLineInfo /* #(viewInfo, ATParser, field, bufTextIndex) for selected line or nil */
fieldEnabled /* menu field id or nil */
editWin /* a EditField window or nil */
startLine /* for compressed view */
searchStr /* text of search string or nil */
foundSet /* varInfos for successful match */
sortType /* #history or #name */
popWindow /* nil or popup menu */
expandGlobal? /* true => expansions to TheVariablePresenter */
/*parent:  receiver for selected and deselected methods */
eventDescriptor /* from EvNoteLib */
columnPos /* horiz scroll offset */
closingEditField?
displayCompressed?
compressRefreshed?
editStr /* text of add dialog string or nil */
undoLifo /* ordered collection of VPvarInfo */), 2, nil)!!

now(class(VarBrowser))!!
/* PUBLIC -- the typical creation */
Def open(self, cRect, parent, expandGlobal?, style | aBrowser)
{ 
  aBrowser := newStyle( VarBrowser, 
                      parent, nil, 
                      "Variable Browser", 
                      nil, nil, 
                      ( style bitOr WS_CHILD bitOr WS_VSCROLL) );
  if aBrowser then
    initialize( aBrowser, cRect, parent, expandGlobal? ) ;
  endif ;

  ^aBrowser
} 
!!

now(VarBrowser)!!

/* Return the length of the longest string in workText.
   NOTE:  THIS DOES NOT WORK BECAUSE WORKTEXT DOES NOT CONTAIN THE
   DISPLAY TEXT. */
Def maxChars(self | len)
{ len := 0;
  do(workText,
  {using(str) len := max(len, size(str));
  });
  ^len;
}!!


/* From edit box */
Def arrows(self, wP)
{ /* NYI */ }
!!

/* PRIVATE 
   -- Set parent's hilighted menu options based on selLineInfo.
      Return the selectType (see ATParser class init code).
*/
Def setMenuHilights(self | selectType)
{
  resetSelected( self ) ;

  if not(selLineInfo) then ^nil endif ;

  /* 1st char in field has select type */
  selectType := asInt( fullFieldStr(selLineInfo[1], selLineInfo[2])[0] ) ;
  selectType := (selectType bitAnd AT_FIELD_MASK) ;
  select
    case (selectType = AT_VAR_NAME_FIELD)
    is
      /* 1st line ? */
      if (bufNum(selLineInfo[0]) = 0) cand (selLineInfo[3] = 0) 
      then
        selected( parent, VP_MENU_DELETE ) ; /* variable name */
  /*@@  selected( parent, VP_MENU_SHOW_ADDR ) ; @@@VarServer NYI@@@*/
      else
        selected( parent, VP_MENU_SHOW ) ;
      endif ;
    endCase

    case (selectType = AT_EDIT_FIELD)
    is
      selected( parent, VP_MENU_EDIT ) ;
    endCase

    case (selectType = AT_REFERENCE_FIELD)
    is
      /* 1st line ? */
      if (bufNum(selLineInfo[0]) = 0) cand (selLineInfo[3] = 0)
      then
        selected( parent, VP_MENU_DELETE ) ; /* variable name */
/*@@    selected( parent, VP_MENU_SHOW_ADDR ) ; @@*/
      endif ;
      selected( parent, VP_MENU_SHOW ) ;
    endCase

    default
      /* no default action */
  endSelect;

  ^selectType
}
!!

/* PRIVATE 
Add N (> 0) lines to the bottom of the display.  If the ordered
collection newViewC is not nil, then use the view data supplied; else
get the data directly based on varCollection.  If we can't scroll
the full amount, go as far as is reasonable.  Returns the number of
lines actually scrolled.  Called in Uncompressed mode only!
*/ 
Def addNlinesToDisplayBottom(self, linesWanted, newViewC | numViewLinesAdded)
{ 
  if not( newViewC )
  then /* get views to use */
    newViewC := getNewBottomViewColl( self, linesWanted ) ;
  endif ;

  /* how many lines did we get? */
  numViewLinesAdded := 0 ;
  do(newViewC,
     {using(viewInfo) 
       numViewLinesAdded := 
         ( numViewLinesAdded + size(viewInfo) ) ;         
     }
  ) ;
  
  /* use the right amount */
  numViewLinesAdded := min( linesWanted, numViewLinesAdded ) ;

  /* add the new views into viewColl -- views are already positioned */
  do(newViewC,
     {using(viewInfo) 
      add( viewColl, viewInfo ) ;
     }
  ) ;

/*{ adjust view offsets -- done by caller if scrolls
  adjustViewCollOffset( viewColl, negate(numViewLinesAdded) ) ;
}*/
  
  ^numViewLinesAdded
}!!

/* PRIVATE 
Add N (> 0) lines to the top of the display.  If the ordered
collection newViewC is not nil, then use the view data supplied; else
get the data directly based on varCollection.  If we can't scroll
the full amount, go as far as is reasonable.  Returns the number of
lines actually scrolled.  Called in Uncompressed mode only!
*/ 
Def addNlinesToDisplayTop(self, linesWanted, newViewC | numViewLinesAdded)
{ 
  /* DEBUG break("addNLinesToDisplayTop"); */
  if not( newViewC )
  then /* get views to use */
    newViewC := getNewTopViewColl( self, linesWanted ) ;
  endif ;

  /* how many lines did we get? */
  numViewLinesAdded := 0 ;
  do(newViewC,
     {using(viewInfo) 
       numViewLinesAdded := 
         ( numViewLinesAdded + size(viewInfo) ) ;         
     }
  ) ;
  
  /* use the right amount */
  numViewLinesAdded := min( linesWanted, numViewLinesAdded ) ;

  /* add the new views into viewColl -- views are already positioned */
  do(newViewC,
     {using(viewInfo) 
      add( viewColl, viewInfo ) ;
     }
  ) ;
  /* DEBUG break("check viewColl after adding the newView"); */
/*{ adjust view offsets -- done by caller if scrolls
  adjustViewCollOffset( viewColl, numViewLinesAdded ) ;
}*/

  /* NOTA BENE: trimming the unneeded views is done by caller,
     e.g. so this may be called by reSize(self) before the view
     size changes. */

  ^numViewLinesAdded
}!!

/* PRIVATE
   Reuse a dead VarInfo.
*/
Def addVarByDeadVarInfo(self, deletedVar | numLines)
{
  maybeCloseEditWin( self ) ;
  showWaitCurs() ; 
  /* refresh numLines--may have changed */
  numLines := getLinesForVar( varSession, varID(deletedVar) ) ;
  if numLines
  then
    setLineNumInfo( deletedVar, numLines, #Exact   );
  else
    setLineNumInfo( deletedVar, 0,        #Unknown );
  endif ;

  addVarToDisplay( self, deletedVar ) ;
  showOldCurs() ;
}  
!!

/* PUBLIC
   Add a symbol descriptor (from the Symbol Manager) to self as a symbol.
   Nota Bene: varNameStr may be nil
*/
Def addVarComposite(self, symbolDesc, varStr, startOffset | aVar names)
{
  maybeCloseEditWin( self ) ;
  resetSelected( self ) ;

  showWaitCurs() ;
  if (aVar := newFromCompositeVar( VPvarInfo, symbolDesc, varStr, startOffset, varSession))
  then
    if (names := getVarNames( self, aVar )) then
      setName( aVar, names[0]);
      setExpandedName( aVar, names[1]);
    endif ;
    setVarSymbol( aVar, symbolIDfrom(varSession, varID(aVar)) );
    if (visLines(self) > linesShownIn(viewColl)) cand (sortType == #history) then 
      addVarToDisplayNoScroll( self, aVar ) ;
    else 
      addVarToDisplay( self, aVar ) ;
    endif ;
  endif ;
  showOldCurs() ;  
  ^aVar  /* Caller handles failure */
}
!!

/* PUBLIC
   Add a symbol descriptor (from the Symbol Manager) to self as a symbol.
   Nota Bene: varNameStr may be nil
              Called only by Stack Presenter
              Never called in compressed mode
*/
Def addVarSymAndFrameNoScroll(self, varSymbol, varNameStr, frameNum 
                             | aVar names)
{
  maybeCloseEditWin(self);
  resetSelected(self);

  showWaitCurs() ; 
  if (aVar := newFromSymbolAndFrame( VPvarInfo, varSymbol, 
      varNameStr, varSession, frameNum)) then
    if (names := getVarNames( self, aVar )) then
      setName( aVar, names[0] ) ;
      setExpandedName( aVar, names[1] ) ;
    else
      setName( aVar, varNameStr ) ;
    endif ;
    setVarSymbol( aVar, symbolIDfrom(varSession, varID(aVar)) ) ;
    addVarToDisplayNoScroll( self, aVar ) ;
  endif ;
  showOldCurs() ;
  ^aVar  /* Caller handles failure */
}
!!

/* PUBLIC
   Add a symbol descriptor (from the Symbol Manager) to self as a symbol.
   Nota Bene: varNameStr may be nil
*/
Def addVarSymbol(self, varSymbol, varNameStr | aVar names)
{
  maybeCloseEditWin( self ) ;
  resetSelected( self ) ;

  showWaitCurs() ; 
  if (aVar := newFromSymbol( VPvarInfo, varSymbol, varNameStr, 
    varSession )) then

    if (names := getVarNames( self, aVar )) then
      setName( aVar, names[0] ) ;
      setExpandedName( aVar, names[1] ) ;
    endif ;
    setVarSymbol(aVar, symbolIDfrom(varSession, varID(aVar)) );
    if (visLines( self ) > linesShownIn(viewColl)) then
      addVarToDisplayNoScroll( self, aVar );
    else 
      addVarToDisplay( self, aVar );
    endif ;
  endif ;
  showOldCurs() ;

  ^aVar  /* Caller handles failure */
}
!!

/* PUBLIC
   Add a symbol descriptor (from the Symbol Manager) to self as a symbol.
   Nota Bene: varNameStr may be nil
*/
Def addVarSymNoScroll(self, varSymbol, varNameStr | aVar names)
{
  maybeCloseEditWin( self ) ;
  resetSelected( self ) ;

  showWaitCurs() ; 
  if (aVar := newFromSymbol( VPvarInfo, varSymbol, varNameStr, 
    varSession )) then
    if (names := getVarNames( self, aVar )) then
      setName( aVar, names[0] ) ;
      setExpandedName( aVar, names[1] );
    endif ;
    setVarSymbol( aVar, symbolIDfrom(varSession, varID(aVar)) ) ;
    addVarToDisplayNoScroll(self, aVar);
  endif ;
  showOldCurs() ;

  ^aVar  /* caller handles failure */
}
!!

/* PRIVATE
   A var has been added to this viewers data.  We want to make sure that
   the user also sees it.  This basically means that the var becomes
   the focusVar and we need to rethink the display.
*/
Def addVarToDisplay(self, aVar)
{
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  resetSelected( self ) ;

  add( varCollection, aVar ) ;
  focusVar := aVar ;
  scrollToFocus( self ) ;
}
!!

/* PRIVATE
   A var has been added to this viewers data.  We can add it to the 
   user's view if we have room.
*/
Def addVarToDisplayNoScroll(self, aVar | startLine bufNum)
{
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  resetSelected( self ) ;

  /* Handle smartly about these cases later */
  if (sortType <> #history) cor displayCompressed? then
    ^addVarToDisplay(self, aVar)  
  endif;
  add( varCollection, aVar ) ;
  /* screen already full */
  if (linesShownIn(viewColl) >= visLines(self)) then
    ^aVar 
  endif ; 

  /* add var's view to end */
  bufNum := 0 ;
  if (size(viewColl) > 0) /* view exist? */
  then
    startLine := ( viewEndLine(last(viewColl)) + 1 ) ;
  else
    startLine := 0 ;
  endif ;
  if not(addVarViewNAt( self, aVar, bufNum, startLine )) then 
    ^nil;
  endif;
  /* add more to view if we have more view lines and more var to view */
  if size(viewColl) > 0 then
    loop
    while (visLines( self ) >= numLinesIn(viewColl)) 
        cand not(lastBuf?(last(viewColl)))
    begin 
      bufNum := ( bufNum + 1 ) ;
      addVarViewNAt( self, aVar, bufNum, (viewEndLine(last(viewColl)) + 1) ) ;
    endLoop;  
    invalidate( self ) ;  /* repaint to display it */
  endif;  
}
!!

/* PUBLIC
   Add a variable to self as a user selected reference.
*/
Def addVarViaRef(self | aVar, varSelStruct, aVarID, viewInfo, names)
{
  maybeCloseEditWin( self ) ;
  /* selLineInfo has shape #(viewInfo, ATParser, field, bufTextIndex) */
  if not(viewInfo := selLineInfo[0]) then
    resetSelected(self);
    ^nil;
  endif;  
  /* openComponentVar(aVarLibInst, rootVarID, bufNum, lineNum, varStr) */
  if not(aVarID   := openComponentVar( varSession, 
                       varID(var(viewInfo)), 
                       bufNum(viewInfo),  /* buf  # */
                       selLineInfo[3],    /* line # */
                       fullFieldStr(selLineInfo[1], selLineInfo[2]) )) then
    ^nil; /* Failed to add Var */
  endif ;

  if (aVar := newFromVarId( VPvarInfo, varSession, aVarID, nil )) then
    if (names := getVarNames( self, aVar )) then
      setName(aVar, names[0]);
      setExpandedName(aVar, names[1]);
    else
      ^nil;  
    endif ;
    setVarSymbol( aVar, symbolIDfrom(varSession, varID(aVar)) ) ;
    /* Show new variable */
    if (visLines( self ) > linesShownIn(viewColl)) then
      addVarToDisplayNoScroll( self, aVar );
    else 
      addVarToDisplay( self, aVar ) ;
    endif; 
  endif ;
  ^aVar  /* Caller handles failure */
}
!!

/* PRIVATE
   Create a new ViewInfo instance for VarInfo buffer N and add it
   to viewColl.  Return the ViewInfo instance.
*/
Def addVarViewNAt(self, varInfo, bufNum, startLine | viewInfo)
{
  maybeCloseEditWin( self ) ;
  if (viewInfo := new(VPviewInfo, varInfo))
     cand initBufText( viewInfo, varSession, startLine, bufNum )
  then
    add( viewColl, viewInfo);
    ^viewInfo
  else
    ^nil /* ERROR */
  endif ;
}
!!

/* DOCUMENTATION */
Def aReadMe(self)
{
/*
There are 2 fundamental modes.  If displayCompressed? is true, then
the display is in compressed mode and only 1st lines of variables are
shown.  Display and bookkeeping routines must keep the proper invariants.

- The varCollection contains currently viewable variables.
- The viewCollection is only used in expanded (uncompressed) viewing.
- The workText is only used in compressed viewing.
- Display routines are interleaved if simple, but those involving non-
trivial work are prefixed in "c" for compressed (eg: cPaint vs Paint).

*/
}
!!

/* PRIVATE -- show last page
*/
Def bottomPage(self | aView linesWanted lastVar bufNum startLine)
{
  if displayCompressed? then ^cBottomPage( self ); endif ;
  if (size(varCollection) = 0) then ^nil endif ;

  /* all text displayed */
  if (visLines(self) >= varTextSize(self)) then 
    ^nil
  endif ;

  viewColl := newViewCollection( self ) ; /* redo from scratch */
  selLineInfo := nil ;
  
  /* start with last buffer of last var at bottom */

  lastVar := last( varCollection ) ;
  bufNum  := asLong( numLines(lastVar) / maxBufLines(self) ) ;
  startLine := 0 ;
  if not(aView := addVarViewNAt( self, lastVar, bufNum, startLine )) then
    invalidate(self); 
    ^nil 
  endif ;

  if ((linesWanted := (visLines(self) - size(aView))) > 0) then
    addNlinesToDisplayTop( self, linesWanted, nil ) ;
  endif ;

  focusVar := var( aView ) ;

  /* If we have more lines above and don't show much, 
  ** scroll text down to display more 
  */
  adjustViewCollOffset( viewColl, linesWanted ) ;

  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE */
Def bs(self)
{
  /* Override parent's method to ignore backspace */
}
!!

/* PRIVATE -- show last page (compressed)
*/
Def cBottomPage(self | numLines)
{
  numLines := size(workText) ;

  if (numLines = 0) cor (visLines(self) >= numLines)
  then /* all text displayed */
    ^nil
  endif ;

  startLine := (numLines - visLines(self)) ; 
  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE */
Def charIn(self, wP, lP)
{
  /* No character input currently handled */
}
!!

/* PRIVATE -- (Public to FieldWindow)
   Field Editing is aborted.  
*/
Def childIsLosingFocus(self, child | bufRef, retCode, fieldStr, charPos)
{
  maybeCloseEditWin( self ) ;
  invalidate( self ) ;
}!!

/* PRIVATE -- scroll up 1 line (down arrow key) (Compressed) */
Def cLineDown(self | fixRect)
{
  if ( (startLine + visLines(self)) < size(workText) ) 
  then
    startLine := (startLine + 1) ;
    /* NOT SUPPORT - @@{
    fixRect := clientRect( self ) ;
    setTop( fixRect, (bottom(fixRect) - (2*tmHeight)) ) ;
    Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0) ;
    Call InvalidateRect( hWnd, fixRect, 1 ) ;
    }@@ */
    invalidate( self ) ;
    setVScrollPos( self ) ;
  endif ;
}
!!

/* PRIVATE -- scroll down 1 line (up arrow key) (Compressed) */
Def cLineUp(self | fixRect)
{
  if (startLine > 0) 
  then
    startLine := (startLine - 1) ;
    /* NOT SUPPORT - @@{
    hWnd := hWnd( self ) ;
    fixRect := clientRect( self ) ;
    setBottom( fixRect, tmHeight + 6 ) ;
    Call ScrollWindow( hWnd, 0, tmHeight, 0, 0 ) ;
    Call InvalidateRect( hWnd, fixRect, 1 ) ;
    }@@ */
    invalidate( self ) ;
    setVScrollPos( self ) ;
  endif ;
}
!!

/* PRIVATE
   Destroy session connection with Variable Server
*/
Def close(self)
{
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  closeSession( self ) ;

  if eventDescriptor then
    closeEvents(EvNoteLibClass$Inst, eventDescriptor);
  endif ;

  close( self:ancestor ) ; 
}
!!

/* PUBLIC */
Def closeAllVars(self)
{
  deleteAllVars(self)
}
!!

/* PRIVATE
   Destroy session connection with Variable Server {called by close(self)}
*/
Def closeSession(self)
{
  deleteAllVars( self ) ;
  ^closeSession( varSession )
}
!!

/* PRIVATE -- scroll left ~1 charwidth */
Def columnLeft(self)
{
  maybeCloseEditWin(self);
  
  /* avoid useless scroll if possible */
  if columnPos <= 0
    columnPos := 0;
    ^0;
  endif;
  
  columnPos := columnPos-1; /* range 0..maxWidth */
  invalidate(self); /* causes repaint, which does setHScrollPos */
}
!!

/* PRIVATE -- scroll right ~1 charwidth */
Def columnRight(self)
{
  maybeCloseEditWin(self);

  /* avoid useless scroll if possible */
  if columnPos >= maxWidth(self)
    columnPos := maxWidth(self);
    ^0;
  endif;

  columnPos := columnPos+1;
  invalidate(self); /* causes repaint, which does setHScrollPos */
}
!!

/* Dispatch menu choices, accelerators. 
  NOTES: Nghia - 10/12/93
  Clear ESC key before processing command.
*/
Def command(self, item, lP | msg)
{
  /* Clear ESC key */
  if (TaskLibClass$Inst)
     checkAbort(TaskLibClass$Inst);
  endif;

  maybeCloseEditWin( self ) ;
  select
  /* NOT SUPPORT - @@{
    case ( msg := action(menu, item) )
    is
      ^perform( self, item, msg )
    endCase
    case item == EDIT_COPY
    is xCopy(self)
    endCase
    case item == EDIT_SELALL
    is selectAll(self);
      repaint(self); ^0
    endCase
  }@@ */
    case item == EDIT_SRCH
    is ^findVar(self, nil)
    endCase
    case high(lP) <> 1
    is ^0
    endCase
    case item == EDIT_PRIOR
    is ^pageUp(self);
    endCase
    case item == EDIT_NEXT
    is ^pageDown(self);
    endCase
    /* NOT SUPPORT  -- no arrow key handling yet @@
    case item == VK_LEFT
    is ^leftArrow(self);
    endCase
    case item == VK_UP
    is ^upArrow(self);
    endCase
    case item == VK_RIGHT
    is ^rightArrow(self);
    endCase
    case item == VK_DOWN
    is ^downArrow(self);
    endCase
    case item == EDIT_TAB
    is ^rightArrow(self); /* alias for rightArrow @?@ *
    endCase
    @@ */
    case (item == VK_DELETE) cor (item == 301)  /* "EDIT_DELETE" */
    is ^deleteKey(self); 
    endCase
    default
      ancestorCommand( parent, item, lP ) ;
  endSelect;
  ^0
}!!

/* PRIVATE -- scroll up ~1 screen (Compressed) */
Def cPageDown(self | displayLines delta)
{
  displayLines := visLines( self ) ;
  delta := (size(workText) - startLine) ;

  if (delta <= displayLines) then 
    ^0 ;  /* done; last text is showing */
  else
    startLine := ( startLine + displayLines ) ;
  endif ;

  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE -- scroll down ~1 screen (Compressed) */
Def cPageUp(self)
{
  startLine := max( 0, (startLine - visLines(self)) ) ;
  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* WINDOWS (Compressed)
   Draw the text on the screen -- just iterate over viewColl
*/
Def cPaint(self, hDC | viewLines hScrollPix)
{
  viewLines := visLines( self ) ; /* # lines on display */

  setVScrollPos( self ) ;  setHScrollPos( self ) ;

  hScrollPix  := negate(columnPos * tmWidth) ;

  do(over(startLine, min(size(workText), (viewLines + startLine)) ),
    {using(index | anoText)
      anoText := new( ATParser, workText[index] ) ;
      doDisplay( anoText, hDC, 
                 hScrollPix, ((index-startLine) * tmHeight + 2), 
                 tmWidth, tmHeight ) ;
    });

  selLineInfo cand displaySelectedLine(self, hDC) ;
}!!

/* PRIVATE  -- scroll to focus var (Compressed) */
Def cScrollToFocus(self | index)
{
  if not(focusVar) then
    ^nil ;
  endif ;
  makeCompressedDisplay( self ) ;  /* here because scrollToFocus() rebuilds */
  if not(index := find( varCollection, focusVar )) then
    ^nil  
  endif ;
  startLine := index; 
  
  /* 12/16/92 - Nghia removed check for # lines display */
  /* in searching hilight in commpress mode */ 
  invalidate(self);
}
!!

/* PRIVATE
   Set scroll bar position based on startLine. (Compressed)
*/
Def cSetHScrollPos(self | cPos)
{
  cPos := asInt( (columnPos * 100) / maxCWidth(self) ) ;

  Call SetScrollPos(hWnd(self),
                    SB_HORZ,  /* what bar to redraw (only 1) */
                    cPos,     /* where to put it -- % */
                    1) ;      /* do the redraw (why else would we call?) */
}!!

/* PRIVATE
   Set scroll bar position based on startLine.
*/
Def cSetVScrollPos(self 
                 | currLineNum totalLines varIndex viewLines perCent adjust)
{
  totalLines  := size( workText ) ;
  currLineNum := startLine ;
  viewLines := visLines( self ) ;

  select
    case (totalLines = 0) cor (currLineNum = 0)
    is /* empty */
      varIndex := 0 ;
    endCase

    case (currLineNum + viewLines) >= totalLines
    is /* ~max */
      varIndex := 100 ;
    endCase

    default
      perCent  := ( (100 * currLineNum) / max((totalLines - viewLines),1) ) ;
      adjust   := asInt((perCent * viewLines) / 100) ;
      varIndex := asInt( (100 * (currLineNum + adjust)) 
                         / totalLines ) ;
  endSelect;
  /* DEBUG - @@
  printNewLine( "setVScrollPos: (%,adj,vis) = (" 
                   + asString(perCent)+","+asString(adjust)
                   +","+asString(viewLines)+")" ) ;
  printNewLine( "setVScrollPos: scrollIndex = " + asString(varIndex) ) ;
  @@ */
  Call SetScrollPos(hWnd(self),
                    SB_VERT,  /* what bar to redraw (only 1) */
                    varIndex, /* where to put it -- % */
                    1) ;      /* do the redraw (why else would we call?) */
}!!

/* PRIVATE -- scroll to proximal var */
Def cThumbScroll(self, vPos 
               | totalLines lineWanted varAndLine viewLines adjust)
{
  select
    case (size(varCollection) = 0)
    is ^nil;
    endCase
    case (vPos < 1)
    is ^topPage(self);
    endCase
    case (vPos > 98)
    is ^bottomPage(self);
    endCase
  endSelect;
  
  totalLines := size( workText ) ;
  viewLines  := visLines( self ) ;
  /* DEBUG
  printLine("thumbScroll: (viewCollSize,totalLines) = (" 
     + asString(numLinesIn(varColl)) + "," + asString(totalLines) + ")" );
  */
  if (viewLines >= totalLines) then /* all text displayed */
    ^nil
  else  /* adjust for % displayed */
    adjust := asInt((vPos * viewLines) / 100) ; 
    lineWanted := asInt( (totalLines * vPos) / 100 ) - adjust ;

  /* DEBUG
  printLine("thumbScroll: lineWanted = "
            + asStringRadix(lineWanted, 10) );
  */
    startLine := lineWanted ;
    invalidate( self ) ;
  endif ;
}
!!

/* PRIVATE
   Delete all variables (e.g. loaded new file)
*/
Def deleteAllVars(self)
{
  resetSelected( self ) ;
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  startLine := 0 ; /* columnPos := 0 ; -- keep col-pos for Stack P */
  selLineInfo := compressRefreshed? := nil ;
  do(varCollection,
    {using(aVarInfo) 
      closeVar( varSession, varID(aVarInfo) ) ;
    });
  do(undoLifo,
    {using(aVarInfo) 
      closeVar( varSession, varID(aVarInfo) ) ;
    });
  
  viewColl      := newViewCollection( self ) ;
  varCollection := newVarCollection( self ) ;
  undoLifo      := newUndoLifo( self ) ;
 
  invalidate( self ) ;
}
!!

/* PRIVATE -- the DELETE key has been pressed */
Def deleteKey(self | selectType)
{
  /* This currently works only for deleting variable views */
  if (selLineInfo)
  then
    selectType := asInt( fullFieldStr(selLineInfo[1], selLineInfo[2])[0] ) ;
    selectType := (selectType bitAnd AT_FIELD_MASK) ;
    if ((selectType = AT_VAR_NAME_FIELD) cor (selectType = AT_REFERENCE_FIELD))
       cand (bufNum(selLineInfo[0]) = 0) cand (selLineInfo[3] = 0)
    then    /* delete selected var's view */
      ^menuDelete(self, #ignored) ;
    endif ;
  endif ;

  beep();  /* found nothing to delete */
  ^0
}
!!

/* PRIVATE
   Delete selected varInfo.
   selLineInfo has shape: #( varInfo, ATParser, field, bufTextIndex )
*/
Def deleteSelVar(self | varIndex dyingVar)
{
  maybeCloseEditWin( self ) ;
  if not(selLineInfo)
  then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_NO_DEL_SEL, FORCE_POPUP, nil, nil, nil);
    ^nil
  endif ;

  push(undoLifo, var(selLineInfo[0]));
  removeVarByVarInfo( self, var(selLineInfo[0]));
  resetSelected( self ) ;
}
!!

/* PRIVATE
   selLineInfo is #(viewInfo, ATParser, field, bufTextIndex) 
*/
Def displaySelectedLine(self, hDC | lineNum ownDC?)
{
  if not(hDC) then 
    ownDC? := #true ;
    hDC    := getContext( self ) ;
    initTextColors( self, hDC ) ;
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FIXED_FONT));
  endif ;

  if displayCompressed? then
    lineNum := find( varCollection, var(selLineInfo[0]) ) - startLine ;
  else
    lineNum := viewStartLine( selLineInfo[0] ) + selLineInfo[3] ;
  endif ;

  doDisplay( selLineInfo[1], hDC, 
             negate(columnPos * tmWidth), (lineNum * tmHeight)+2, 
             tmWidth, tmHeight) ;

  if ownDC? then
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FONT));
    releaseContext( self, hDC ) ;    
  endif ;
}
!!

/* PRIVATE -- put up a fieldEdit Window for var */
Def doFieldEdit(self, info, editText | aRect, xCharPos, yCharPos) 
{ 
  /* NOTES:
  **   info        = #(fieldWidth, charsetStr)
  **   selLineInfo = #(viewInfo, ATParser, field, bufTextIndex)
  **   field       = #(field, outPos, inStartPos, inEndPos) -- pos => charPos
  */
  maybeCloseEditWin( self ) ; /* close old, as needed */

  /* find where to put editField -- overlay field text */
  xCharPos := selLineInfo[2][1] ;
  if displayCompressed? then
    yCharPos := find( varCollection, var(selLineInfo[0]) ) - startLine ;
  else
    yCharPos := ( viewStartLine( selLineInfo[0] ) + selLineInfo[3] ) ;
  endif ;
  aRect := rect( ((xCharPos-columnPos) * tmWidth)-1,
                 (yCharPos * tmHeight)+1,
                 ((xCharPos + info[0]) * tmWidth) + 4,
                 ((yCharPos + 1) * tmHeight) + 4
               ) ;

  /* create editField */
  editWin := newChild( EditField, 666, self, aRect ) ;
  setLegalChars( editWin, asSet(info[1]) ) ;
  setMaxLength( editWin, info[0] ) ;
  setText( editWin, editText ) ;
  setOutConvertFun( editWin, {using(aString) ^aString} ) ; /* no conversion */
  show( editWin, SW_SHOW ) ;
  setFocus( editWin ) ;
}
!!

/* PUBLIC */
Def downArrow(self)
{
  maybeCloseEditWin( self ) ;

  /* selLineInfo is #(viewInfo, ATParser, field, bufTextIndex) */
  if ((selLineInfo[3] + 1) < size(selLineInfo[0])) then
    selLineInfo[3] := (selLineInfo[3] + 1) ;
    selLineInfo[1] := parse(new(ATParser, 
                                bufText(selLineInfo[0])[(selLineInfo[3])]
                           )   ) ;
    selLineInfo[2] := firstField( selLineInfo[2] ) ;
    if selLineInfo[2] then
      hiLight( selLineInfo[1], selLineInfo[2] ) ;
    else
      downArrow( self ) ; /* try the next line down */
    endif ;
  else
    /* Goto next field */
    selLineInfo := nil ;
  endif ; 
}
!!

/* PRIVATE -- edit the selected editable field */
Def editFieldValue(self | editText, fieldWidthRef, charSetRef, retCode, startChar)
{
  /* selLineInfo is #(viewInfo, ATParser, field, bufTextIndex) */
  /* Check and see what the Variable Server wants to do */
  if not(selLineInfo) then
    ^nil;
  endif;  
  editText := fullFieldStr( selLineInfo[1], selLineInfo[2] ) ;
  startChar := selLineInfo[2][2] - 1;
  if (retCode := editFieldValue( varSession, 
                             varID( var( selLineInfo[0] ) ), 
                             bufNum( selLineInfo[0] ), 
                             selLineInfo[3], 
                             size( editText ), 
                             asciiz( editText ),
                             startChar)) then
    /* 
    ** retcode is non-nil, Create an EditField window 
    ** and do editing locally - retcode  #( fieldWidth, charsetString ) 
    */
    doFieldEdit( self, retCode, fieldStr(selLineInfo[1], selLineInfo[2]) ) ;
  endif ;

  ^0
}
!!

/* PUBLIC */
Def expandGlobal?(self)
{ ^expandGlobal? }
!!

/* PRIVATE -- (Public to FieldWindow)
   Field Editing is complete.  Check text with Variable Server and recover
   from any errors.
*/
Def fieldEditComplete(self, newText | bufRef, retCode, fieldStr, charPos, temp1, temp2)
{ /* selLineInfo is #(viewInfo, ATParser, field, bufTextIndex) */
  if (closingEditField?) cor (getStatus(editWin) = #false) then 
     nilifySelLineInfoNoDisplay( self ) ;
     ^nil ; 
  endif ; /* semaphore for no update */

  /* NOTES: must augment text string with field markers (at both ends) */
  fieldStr := fullFieldStr( selLineInfo[1], selLineInfo[2] ) ;
  charPos  := selLineInfo[2][2] ;

  bufRef := newFieldValue( varSession,
                           varID(var(selLineInfo[0])),
                           bufNum(selLineInfo[0]),
                           selLineInfo[3],
                           charPos,
                           ( asString(fieldStr[0]) + newText + 
                             asString(fieldStr[size(fieldStr)-1]) )
                         ) ;
  /* Free bufRef manually */
  if (bufRef cand (bufRef <> 0L)) then
    temp1 := copyFromLong( new(Struct,12), bufRef ) ;
    if ((temp2 := wordAt(temp1, 0)) <> 0) then
      Call GlobalFree(temp2) ;
    else
      cFree( MallocLibClass$Inst, bufRef ) ;
    endif ;
  endif ;

  nilifySelLineInfoNoDisplay( self ) ;
  editWin := nil ; 
  invalidate( self ) ; /* cause repaint */
}
!!

/* MENU
 * User wants us to look for a variable by name
 *
 * Notes (Ron, 6/8/93): Changed to use InputDlgWithHelp instead of
 *                      InputDialog as part of PowerViews Improvement
 *                      Project.
 */
!!

Def findVar(self, again? | inputDlg varInfo continue)
{
  maybeCloseEditWin( self ) ;
  continue := #true ;

  if not(again? cand searchStr) then
    foundSet := new( Set, 1 );

    if not(inputDlg := open(InputDlgWithHelp)) then
      ^nil;
    endif;
    setUp(inputDlg, "Search", 
                  "&Search for:", searchStr cor "", HE_DLGI_VARBROWS_1);
    if (runModal(inputDlg, DLG_INPUT_WITH_HELP, self) = IDCANCEL) then 
      ^nil;
    endif;   
    searchStr := getText(inputDlg);
  endif ;
  if continue cand searchStr cand (size(searchStr) > 0)
     cand (varInfo := findVarName(self, again?)) then
    focusVar := varInfo ;
    add( foundSet, varInfo ) ;
    scrollToFocus( self ) ;
    setSelectLineFromFocus( self ) ;
  endif ;
}
!!

/* PRIVATE -- find variable with text: searchStr
   If found, return the varInfo
*/
Def findVarName(self, again? | len, txtMsg)
{
  if not( searchStr ) then ^nil endif ;

  do(varCollection,
    {using(varInfo)
      if (caseOffCompare(asString(name(varInfo)), searchStr) = #=)
        cand not(varInfo in foundSet)
      then
        ^varInfo
      endif ;
    });
  txtMsg := searchStr;
  len := size(searchStr);
  if (len > 40) then
     txtMsg := ".."+subString(searchStr, len-40, len); /* truncate search text */
  endif;  
  displayFormattedError(ErrorTextLibClass$Inst, 
     ER_VAR_CANT_FIND_VAR, FORCE_POPUP, txtMsg, nil, nil);

  ^nil /* failed */
}
!!

/* PRIVATE
  This function is to create viewInfo's to suffix (add to bottom of)
  the current view collection, viewColl.  It uses info in varCollection
  to do this. 
  NOTES:
    - Returns an ordered collection of viewInfo -- which may be empty.
    - View trimming is done by caller.
*/
Def getNewBottomViewColl(self, linesWanted 
    | newColl viewVar lastVar lastView varIndex bufNum startLine)
{
  newColl := new( OrderedCollection, 1 ) ;
  if (size(varCollection) = 0) then 
    ^newColl  /* no vars to add */
  endif ;
  
  /* rebuild views from scratch -- we have at lease 1 var */
  if (size(viewColl) = 0) then     
    addVarViewNAt( self, first(varCollection), 0, 0 ) ;
  endif ;
  lastView := last( viewColl ) ;       /* last view */
  viewVar  := var( lastView ) ;        /* last var we have in view  */
  /* varIndex is index of last view's var in varCollection */
  varIndex := findItemIndex( varCollection, viewVar ) ;
  if varIndex[0] then /* found it */
    varIndex := varIndex[1] ;
  else /* not found */
    ^newColl
  endif ;
  lastVar   := last( varCollection ) ;  /* last var we actually have */ 
  startLine := ( viewEndLine( lastView ) + 1 ) ;
  loop while (linesWanted > 0)
  begin   
    if (lastVar = viewVar) cand lastBuf?(lastView) /* at end? */
    then
      ^newColl  /* already showing last info */
    else
      if lastBuf?(lastView)
      then  /* get next var's data -- we have at least 1 more var */
        varIndex := ( varIndex + 1 ) ;
        viewVar := varCollection[ varIndex ] ;
        bufNum := 0 ;
      else /* get next buf data for var */
        bufNum := ( bufNum(lastView) + 1 ) ;
      endif ;
      lastView := new( VPviewInfo, viewVar ) ;
      initBufText( lastView, varSession, startLine, bufNum ) ;
      startLine := ( viewEndLine( lastView ) + 1 ) ;
      linesWanted := ( linesWanted - size(lastView) ) ;
      add( newColl, lastView ) ;
    endif ;
  endLoop ;

  ^newColl
}!!

/* PRIVATE
  This function is to create viewInfo's to prefix (add to the top of)
  the current view collection, viewColl.  It uses info in varCollection
  to do this. 
  NOTES: - Returns an ordered collection of viewInfo -- which may be empty.
         - View trimming is done by caller.
  Nghia: Fixed bug 12/15/92 - bufNum range from 0-(n-1)       
*/
Def getNewTopViewColl(self, linesWanted 
  | newColl newPrev? viewVar firstVar firstView varIndex bufNum startLine)
{
  /* DEBUG break("getNewTopViewColl"); */
  newColl := new( OrderedCollection, 1 ) ;

  if (size(varCollection) = 0) then
    ^newColl /* empty */
  endif;
  
  if (size(viewColl) = 0) then
    addVarViewNAt( self, first(varCollection), 0, 0 ) ;  /* 1st one */
  endif ;
  
  firstView := first( viewColl ) ;    /* first view */
  viewVar   := var( firstView ) ;     /* first var we have in view  */

  /* varIndex is index of 1st view's var in varCollection */
  varIndex := findItemIndex( varCollection, viewVar ) ;
  if varIndex[0] then 
    varIndex := varIndex[1] ;
  else
    ^newColl /* empty */
  endif ;

  firstVar  := first( varCollection ) ;
  startLine := viewStartLine( firstView ) ;
  loop while (linesWanted > 0)
  begin   
    if (firstVar = viewVar) cand (bufNum(firstView) = 0) /* at beginning? */
    then
      ^reverse(newColl)  /* already showing first info */
    else
      if (bufNum(firstView) > 0) then
        /* get previous buf */
        bufNum := max(0, (bufNum(firstView) - 1));
      else
        /* DEBUG break("get previous var"); */
        /* get previous var */
        varIndex := ( varIndex - 1 ) ;
        viewVar  := varCollection[ varIndex ] ;
        /* Get last buffer */
        if (linesVeracity(viewVar) == #Exact) then 
          if ((numLines(viewVar) mod maxBufLines(self)) <> 0) then
            bufNum := max(0,asLong( numLines(viewVar) / maxBufLines(self)));
          else
            bufNum := max(0,asLong( numLines(viewVar) / maxBufLines(self))-1);
          endif;  
        else    
          bufNum := min(1, numLines(viewVar));
        endif ;                
      endif ;
      /* DEBUG break("check bufnum"); */
      firstView := new( VPviewInfo, viewVar ) ;
      /* Get bufNum from 0 to n-1 */
      initBufText( firstView, varSession, 0, bufNum ) ;
      startLine := ( startLine - size(firstView) ) ;
      setViewStartLine( firstView, startLine ) ;
      /* DEBUG linesWanted := ( linesWanted - size(firstView) ) ; */
    endif ;
    add( newColl, firstView ) ;
     /* DEBUG break("check newColl"); */
  endLoop ;

  ^reverse(newColl)
}!!

/* PRIVATE
   Return a comparison function (anonomous block) based on sort type.
*/
Def getSortBlock(self, sortType)
{
  select
    case (sortType = #history)
    is ^{using(a,b) (timeStamp(a) < timeStamp(b))};
    endCase
    case (sortType = #name)
    is ^{using(a,b) /* sort block */
         if (name(a) = name(b))
         then
           (varID(a) < varID(b)) /* break all ties, else binary search breaks */
         else
           (name(a) < name(b))
         endif ;
        }
    endCase
    default
       displayFormattedError(ErrorTextLibClass$Inst, 
          ER_VAR_UNKNOWN_SORT_TYPE, FORCE_POPUP, nil, nil, nil);
       ^nil;
  endSelect;
}
!!

/* PRIVATE -- get the address of a var on the display or fail */
Def getVarAddress(self, aVarInfo | symb, addr)
{
   if (symb := varSymbol(aVarInfo)) then
     addr := getSymbolAddress( SymbolLibClass$Inst, symb);
   else
     ^nil ;
   endif ;

   ^addr 
   /* NOTES: caller must deallocate addr:
      destroyAddress( AddressLibClass$Inst, addr ) ;
   */
}
!!

/* PRIVATE -- get the address test of a var on the display or fail */
Def getVarAddressText(self, aVarInfo | addr aText)
{
   if (addr := getVarAddress(self, aVarInfo)) then
     aText := getAddressText( AddressLibClass$Inst, addr); 
     destroyAddress( AddressLibClass$Inst, addr ) ;
   else
     ^nil ; /* error! */
   endif ;

   ^aText
}
!!

/* PRIVATE -- get the names (short and expanded) of a var on the 
   display or fail.  Expanded name is just 1st line of text.
*/
Def getVarNames(self, aVarInfo | aView firstLine parsedLine nameField)
{
   /* find the viewInfo buf 0 corresponding to varInfo */
   if not(aView := varInViewN?( viewColl, aVarInfo, 0 )) then   
     aView := new( VPviewInfo, aVarInfo ) ;
     if aView then 
       initBufText( aView, varSession, 0, 0);
     else
       ^nil;    
     endif;
   endif ;

   if aView then 
     if (firstLine := bufText(aView) cand bufText(aView)[0]) then
       parsedLine := parse( new( ATParser, firstLine ) ) ;
       nameField := firstField( parsedLine ) ;
       loop while nameField
                  cand (nameField[0] <> AT_VAR_NAME_FIELD) 
                  cand (nameField[0] <> AT_REFERENCE_FIELD)
       begin nameField := nextField( parsedLine, nameField ) ;
       endLoop;
       if nameField then
         ^tuple( fieldStr(parsedLine, nameField), firstLine ) ;
       endif ;
     endif ;
   endif ;

   /* DEBUG */
   if (parsedLine cand (class(parsedLine) = String)) then
     displayFormattedError(ErrorTextLibClass$Inst, 
        ER_VAR_CANT_FIND_VAR, FORCE_POPUP, asciiz(parsedLine), nil, nil);
   endif;
   ^nil /* failed */
}
!!

/* WINDOWS */
Def gotFocus(self, ignored)
{ /* do nothing; overrides TextWindow cursor creation */ }!!

/* PRIVATE -- scroll to proximal col */
Def hThumbScroll(self, hPos)
{
   maybeCloseEditWin( self ) ;

   if displayCompressed? then 
    columnPos := asInt( (hPos * maxCWidth(self)) / 100 ) ;
   else 
    columnPos := asInt( hPos ) ;
   endif ;
   
   invalidate( self ) ;
}
!!

/* PRIVATE 
  Initialize a Variable Browser.
*/
Def init(self)
{
  init( self:ancestor ) ;
  sortType := #history ;  /* default */
  startLine := columnPos := 0 ;
  foundSet  := new( Set, 1 ) ;
  varCollection := newVarCollection( self ) ;
  viewColl      := newViewCollection( self ) ;
  undoLifo      := newUndoLifo( self ) ;  /* LIFO Stack */
  editStr := copy("#");
  if openSession(self) then /* sets varSession */
    if not(initEvents( self )) then 
      ^nil;
    endif;  
  else
    ^nil /* failure */
  endif ;
}
!!

/* PRIVATE */
Def initEvents(self | eventDesc, events)
{
  /* REGISTER with Event Notification for 
   *       Symbols Deleted, 
   *       Variable value update,
   *       Whatever...
   */
  /* dynamic to pick up current values */
  events := tuple(EVENT_VAR_EDIT,
                  EVENT_VAR_HALTED,
                  EVENT_SYMBOL_DELETED,
                  EVENT_SYMBOL_INIT_LOAD);

  /* register with Event Notification library */
  requireWithPath( EvNoteLib, "enlib.dll" ) ;                
  ^(eventDescriptor := registerEvents( EvNoteLibClass$Inst, hWnd(self), events ));
}
!!

/* PRIVATE -- N.B. init(self) has already done most of the work */
Def initialize(self, cRect, parent, expandToGlobalView?)
{
  setParent( self, parent ) ;
  setExpandGlobal?( self, expandToGlobalView? ) ;
  setCRect( self, cRect ) ; 
  moveWindow( self ) ;
}
!!

/* PRIVATE
  Initialize text metrics data for this browser.  Load the font data 
  into textMetrics, set the text width and height instance variables.
  
  NOTES: This sets up for Windows' defaults.  The containing window
  should use setTmWidth and setTmHeight methods to size for its chosen
  font characteristics.
*/
Def initTextMetrics(self | hdc, tm)
{ tm := new(Struct, 32);
  Call SelectObject(hdc := getContext(self),
                    Call GetStockObject(SYSTEM_FIXED_FONT));
  Call GetTextMetrics(hdc, tm);
  tmWidth  := asInt(wordAt(tm, 10));
  tmHeight := asInt(wordAt(tm, 8)) + asInt(wordAt(tm, 0));
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FONT));
  releaseContext(self, hdc);
}!!

/* PRIVATE METHOD.  Initialize the workText. */
Def initWorkText(self)
{ add(workText := new(TextCollection, size(varCollection)), "");
}!!

/* PUBLIC */
Def leftArrow(self)
{
  maybeCloseEditWin( self ) ;

  ^leftField( self )
}
!!

/* PRIVATE -- go left 1 field */
Def leftField(self | prevField, hDC, viewLineNum)
{
  maybeCloseEditWin( self ) ;

  if selLineInfo  /* #(viewInfo, ATParser, field, bufTextIndex) */
  then /* use line info */
    if (prevField := prevField( selLineInfo[1], selLineInfo[2] )) then
      viewLineNum := viewStartLine( selLineInfo[0] ) + selLineInfo[3] ;
      hDC := getContext( self ) ;
      /* unselect old */
      hiLightOff( selLineInfo[1] ) ;
      doDisplay( selLineInfo[1], hDC, 0, 
                 (viewLineNum * tmHeight), tmWidth, tmHeight) ;
      selLineInfo[2] := prevField ;
      /* select new */
      hiLight( selLineInfo[1], selLineInfo[2] ) ;
      doDisplay( selLineInfo[1], hDC, 0,
                (viewLineNum * tmHeight), tmWidth, tmHeight) ;
      releaseContext( self, hDC ) ;
      /* NOTES: else goto prev line */
    endif ;
  else /* no line to start from (no default) */
    beep() ; /* should use 1st display line */
  endif ;
  ^0
}
!!

/* PRIVATE - scroll up 1 line (down arrow key) */
Def lineDown(self | hWnd fixRect)
{
  maybeCloseEditWin( self ) ;
  
  if displayCompressed?
  then ^cLineDown( self ) ;
  endif ;
  hWnd := hWnd( self ) ;
  if (size(viewColl) > 0)
     cand (visLines(self) <= (viewEndLine(last(viewColl))))
     cor (addNlinesToDisplayBottom(self, 1, nil) > 0)
  then /* added a line */
    adjustViewCollOffset( viewColl, -1 ) ;
    trimDisplayViews( self ) ;
    setFocusByView( self ) ;
    fixRect := clientRect( self ) ;
    setTop( fixRect, (bottom(fixRect) - (2*tmHeight)) ) ;
    Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0) ;
    Call InvalidateRect( hWnd, fixRect, 1 ) ;
    setVScrollPos( self ) ;
  endif ;
}
!!

/* PRIVATE - scroll down 1 line (up arrow key) */
Def lineUp(self | hWind fixRect)
{
  maybeCloseEditWin( self ) ;

  if displayCompressed? then 
    ^cLineUp( self );
  endif ;
  /*DEBUG  break("doing lineUp"); */
  if (size(viewColl) > 0) 
     cand ((viewStartLine(first(viewColl)) < 0)
           cor (addNlinesToDisplayTop(self, 1, nil) > 0))
  then /* added a line */
    adjustViewCollOffset( viewColl, 1 ) ;
    trimDisplayViews( self ) ;
    setFocusByView( self ) ;
    /* do the Windows hacks */
    hWnd := hWnd( self ) ;
    fixRect := clientRect( self ) ;
    setBottom( fixRect, tmHeight + 6 ) ;
    Call ScrollWindow( hWnd, 0, tmHeight, 0, 0 ) ;
    Call InvalidateRect( hWnd, fixRect, 1 ) ;
    setVScrollPos( self ) ;
  endif ;

}
!!

/* PRIVATE
   close popup window if open 
*/
Def mabeClosePopup(self)
{
  if popWindow then
    destroy( popWindow ) ;
    nilifyPopWindow( self ) ;
  endif;
}!!

/* PRIVATE */
Def makeCompressedDisplay(self)
{
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  viewColl := newViewCollection( self ) ; /* flush views */
  startLine := columnPos := 0 ;
  selLineInfo := nil;
  refreshHeaderLines( self ) ;
  workText := new( TextCollection, size(varCollection) ) ;

  do(varCollection,
    {using(varInfo)
      add( workText, expandedName(varInfo) ) ;
    });
  displayCompressed? := #true ;
  invalidate( self )
}
!!

/* PRIVATE */
Def makeUncompressedDisplay(self)
{
  mabeClosePopup(self);
  maybeCloseEditWin(self);
  startLine := columnPos := 0 ;
  workText := displayCompressed? := nil;
  selLineInfo := nil;
  scrollToFocus(self);   /* rebuilds viewColl */
}
!!

/* PRIVATE -- return # of lines max per buffer */
Def maxBufLines(self)
{
  ^20  /*@@ should go to the var server for this @@*/
}
!!

/* PRIVATE 
 -- return the number of chars in the longest line or 1 (Compressed view)
   NOTE:  THIS DOES NOT WORK BECAUSE WORKTEXT DOES NOT CONTAIN THE
   DISPLAY TEXT.
*/
Def maxCWidth(self | maxCharWidth)
{
  maxCharWidth := 1 ; /* default max */
  
  do(over(0,size(workText)),
    {using(index)
      maxCharWidth := max( maxCharWidth, size(workText[index]) ) ;
    });

  ^maxCharWidth
}
!!

/* PRIVATE -- scroll left to max */
Def maxLeft(self)
{
  columnPos := 0 ; /* range 0..100 */

  invalidate( self ) ; /* causes repaint, which does setHScrollPos */
}
!!

/* PRIVATE -- scroll right to max */
Def maxRight(self)
{
  columnPos := maxWidth( self ) ; 

  invalidate( self ) ; /* causes repaint, which does setHScrollPos */
}
!!

/* PRIVATE 
 -- return the number of chars in the longest line or 1 (Compressed view)
*/
Def maxWidth(self)
{
  /* 12/16/92 - Nghia - Added computing for the maxWidth of text */
    if displayCompressed?
    then ^maxCWidth(self)
    else ^100
    endif;
}
!!

/* PRIVATE */
Def maybeCloseEditWin(self)
{
  closingEditField? := #true ; /* close without update semaphore */
  if editWin cand hWnd(editWin)
  then
    close( editWin ) ;
  endif ;

  editWin := nil ;
  closingEditField? := nil ;
}
!!

/* PUBLIC
   INVARIANT: This is only enabled when an expandable reference field
   has been selected.
*/
Def menuAddr(self, ignored | aText aVar)
{
  maybeCloseEditWin( self ) ;
  aVar := var( selLineInfo[0] ) ;
  if aVar cand (aText := getVarAddressText(self, aVar)) then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_IS_AT_ADDR, FORCE_POPUP, asciiz(name(aVar)),
       asciiz(aText), nil);
  else
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_UNKNOWN_ADDR, FORCE_POPUP, asciiz(name(aVar)),
       nil, nil);
  endif ;
  ^0
}
!!

/* MENU -- Add a new variable                             [Ron 5/6/93] 
 *
 * The text string entered by the user may be either a simple variable
 * name, in which case we look it up the same way that the source window
 * does (using the current context), or a '#'-type symbol name.  In
 * either case, it may have pointer or member "dereferencing" hanging off
 * a base name, which will confuse everything but the Variable Server,
 * so we trim that off temporarily here.
 */
Def menuAddVar(self | varStr tmpStr continue ind1 ind2 inputDlg symbolDesc)
{
   resetSelected(self);  /* Reset the menu */
   maybeCloseEditWin(self);

   inputDlg := open(InputDlgWithHelp);

   setUp(inputDlg, "Add Variable", 
                  "&Variable:", editStr, HE_DLGI_ADDVAR);

   if (runModal(inputDlg, DLG_INPUT_WITH_HELP, ThePort) <> IDOK) then 
      ^nil;
   endif;
 
   tmpStr := getText(inputDlg);
   if (size(tmpStr) <= 0) then
      ^GOOD;
   endif;
   editStr := copy(tmpStr); /* save it for next time */

   /*
    * Cut the trailing "->" or "." + chain off the varName before passing
    * it off to the symbol lookup methods.
    *
    * Note: 2 out of 3 code reviewers say: only put this in a separate 
    *       function when it's needed somewhere else.
    */
   ind1 := indexOf(tmpStr,'.',0);
   ind2 := find(tmpStr,"->",0);

   /* find the FIRST "->" or "." */
   if (ind1 cand (ind1 > 0)) then
      if (ind2 cand (ind2 > 0)) then
         if (ind2 < ind1) then
            ind1 := ind2;
         endif;  
      endif;  
   else
      if (ind2 cand (ind2 > 0)) then
         ind1 := ind2;
      endif;
   endif; 

   /* ind1 is index of first '.' or '->', if any */
   if (ind1 cand (ind1 > 0)) then
      varStr := subString(tmpStr,0,ind1);
   else
      varStr := copy(tmpStr);
      tmpStr := nil;
   endif;
   
   /* clear out abort key */
   queryAbort(TaskLibClass$Inst);

   symbolDesc := varNameToSymDesc(SymbolLibClass$Inst, varStr);
   if (not(symbolDesc)) then
      ^nil;
   endif;

   if (not(tmpStr)) then
      continue := addVarSymbol(self, symbolDesc, varStr);
   else 
      ind2 := find(tmpStr,varStr,0); /* location of var name */
      varStr := subString(tmpStr,ind2,size(tmpStr));
      continue := addVarComposite(self, symbolDesc, varStr, 0);
   endif;
   if (continue) then
      setSelectLineFromFocus( self ) ;
   else
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_VAR_CANT_FIND_VAR, FORCE_POPUP, tmpStr, nil, nil);
      ^nil;
   endif;

   ^GOOD
}
!!

/* MENU -- Major mode switch */
Def menuCompress(self, ignored | aText aVar)
{
  resetSelected(self);  /* Reset all menu */
  maybeCloseEditWin(self);
  if displayCompressed? then /* Uncompress */
    unCheckMenuItem(menu(parent), VP_MENU_COMPRESS ) ;
    makeUncompressedDisplay( self ) ;
  else /* Compress */
    checkMenuItem(menu(parent), VP_MENU_COMPRESS ) ;
    makeCompressedDisplay( self ) ;
  endif ;
  ^0
}
!!

/* PUBLIC
   INVARIANT: This is only enabled when an deleteable value field has been
   selected.
*/
Def menuDelete(self, ignored)
{
  maybeCloseEditWin( self ) ;
  deleteSelVar( self ) ; 

  ^1
}
!!


/* PUBLIC
   INVARIANT: This is only enabled when an editable value field has been
   selected.
*/
Def menuEdit(self, ignored)
{
  editFieldValue( self ) ;

  ^0
}
!!

/* PUBLIC
   INVARIANT: This is only enabled when an expandable reference field
   has been selected.
*/
Def menuShow(self, ignored)
{
  addVarViaRef( self ) ;

  ^0
}
!!

/* PUBLIC
   INVARIANT: This is only enabled when an deleteable value field has been
   selected.
*/
Def menuUndelete(self, ignored)
{
  maybeCloseEditWin( self ) ;
  restoreLastVar( self ) ;
  resetSelected( self ) ;

  ^1
}
!!

/* PRIVATE */
Def newUndoLifo(self)
{
  ^new( OrderedCollection, 4 ) ;
}
!!

/* PRIVATE */
Def newVarCollection(self)
{
  ^newWithCompareBlock(SortedCollection,
                       1,  
                       getSortBlock(self, sortType)
                      )
}
!!

/* PRIVATE -- flush old viewCollection; 
   new view collection is sorted by display line number.
*/
Def newViewCollection(self)
{
  nilifySelLineInfo( self ) ;
  viewColl := newWithCompareBlock(VPviewCollection, 1,
            {using(a,b)
              if not(a) then
                 b 
              else
                 if (viewStartLine(a) < viewStartLine(b)) then
                   #true
                 else 
                   nil
                 endif
              endif ;
             } );
  ^viewColl;
}
!!

/* PRIVATE (PUBLIC only to popWindow) */
Def nilifyPopWindow(self)
{
  popWindow := nil ; 
}

!!

/* PRIVATE  */
Def nilifySelLineInfo(self | hDC lineNum varInfo)
{
  if selLineInfo
  then
    hDC := getContext( self ) ;
    initTextColors( self, hDC ) ;
    if displayCompressed?
    then
      varInfo := var( selLineInfo[0] ) ;
      lineNum := find( varCollection, varInfo ) - startLine ;
    else
      lineNum := ( viewStartLine( selLineInfo[0] ) + selLineInfo[3] ) ;
    endif ;
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FIXED_FONT));
    unHiLight( selLineInfo[1], selLineInfo[2], hDC, 
               negate(columnPos * tmWidth), (lineNum * tmHeight)+2, 
               tmWidth, tmHeight) ;
    displaySelectedLine( self, hDC ) ;
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FONT));
    releaseContext( self, hDC ) ;
    selLineInfo := nil ;
  endif ;
}

!!

/* PRIVATE  */
Def nilifySelLineInfoNoDisplay(self | hDC lineNum varInfo)
{
  if selLineInfo
  then
    hDC := getContext( self ) ;
    initTextColors( self, hDC ) ;
    if displayCompressed?
    then
      varInfo := var( selLineInfo[0] ) ;
      lineNum := find( varCollection, varInfo ) - startLine ;
    else
      lineNum := ( viewStartLine( selLineInfo[0] ) + selLineInfo[3] ) ;
    endif ;
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FIXED_FONT));
    unHiLight( selLineInfo[1], selLineInfo[2], hDC, 
               negate(columnPos * tmWidth), (lineNum * tmHeight)+2, 
               tmWidth, tmHeight) ;
    Call SelectObject(hDC, Call GetStockObject(SYSTEM_FONT));
    releaseContext( self, hDC ) ;
    selLineInfo := nil ;
  endif ;
}

!!

/* PRIVATE
   Open session connection with Variable Server
*/
Def openSession(self)
{
  /* make sure Variable Server dll is loaded */
  if not(requireWithPath(VarServLib, "varservr.dll") and
    requireWithPath(MallocLib, "malloc.dll")) then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_NO_LIBS, FORCE_POPUP, "Variable", nil, nil);
    ^nil 
  endif ;
  
  varSession := openSession( VarServLibClass$Inst, hWnd(self) ) ;
  if not( varSession )then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_CANT_OPEN_SESSION, FORCE_POPUP, nil, nil, nil);
    ^nil 
  endif ;
}
!!

/* PRIVATE -- scroll up ~1 screen */
Def pageDown(self | displayLines linesAdded)
{
  maybeCloseEditWin( self ) ;
  
  if displayCompressed? then 
    ^cPageDown(self);
  endif ;

  displayLines := visLines( self ) ;
  if (size(viewColl) > 0) 
     cand (((linesAdded := addNlinesToDisplayBottom(self, displayLines, nil))
           > 0)
           cor ((linesAdded := min(displayLines,
                              (viewEndLine(last(viewColl))-displayLines+1)) )
           > 0)) 
  then
    adjustViewCollOffset( viewColl, negate(linesAdded) ) ;
    trimDisplayViews( self ) ;
    setFocusByView( self ) ;
    invalidate( self ) ; /* causes repaint, which does setVScrollPos */
  endif ;
}
!!

/* PRIVATE -- scroll left ~1 screenwidth */
Def pageLeft(self | screenWidth)
{
  maybeCloseEditWin( self ) ; 
  screenWidth := max((width(clientRect(self)) / tmWidth)-1, 1) ;
  columnPos   := max( (columnPos-screenWidth), 0 ) ;

  invalidate( self ) ; /* causes repaint, which does setHScrollPos */
}
!!

/* PRIVATE -- scroll right ~1 screenwidth */
Def pageRight(self | screenWidth)
{
  maybeCloseEditWin( self ) ;
  screenWidth := max((width(clientRect(self)) / tmWidth)-1, 1) ;
  columnPos   := min( (columnPos+screenWidth), maxWidth(self) ) ; 

  invalidate( self ) ; /* causes repaint, which does setHScrollPos */
}
!!

/* PRIVATE -- scroll down ~1 screen */
Def pageUp(self | displayLines linesAdded)
{
  maybeCloseEditWin( self ) ;
  
  if displayCompressed? then ^cPageUp(self); endif;
  displayLines := visLines( self ) ;
  if (size(viewColl) > 0) 
  then
    linesAdded := negate(viewStartLine(first(viewColl))) ;
    if (linesAdded > 0) then 
      linesAdded := linesAdded + 
                     addNlinesToDisplayTop(self, 
                                           (displayLines - linesAdded),
                                           nil) ;
    else
      linesAdded := addNlinesToDisplayTop(self, displayLines, nil) ;
    endif ;
    adjustViewCollOffset( viewColl, linesAdded ) ;
    trimDisplayViews( self ) ;
    setFocusByView( self ) ;
    invalidate( self ) ; /* causes repaint, which does setVScrollPos */
  endif ;
}
!!

/* WINDOWS (Public)
   Draw the text on the screen -- just iterate over viewColl
*/
Def paint(self, hDC | viewLines hScrollPix)
{
  if displayCompressed?
  then ^cPaint( self, hDC ) ;
  endif ;

  viewLines := visLines( self ) ; /* # lines on display */
  setVScrollPos( self ) ;  setHScrollPos( self ) ;
  hScrollPix := negate(columnPos * tmWidth) ;

  do(viewColl,
  {using(viewInfo | viewStart, textColl)
    viewStart := viewStartLine( viewInfo ) ;
    textColl  := bufText(viewInfo) ;
    do(over(0, size(textColl)),
    {using(index | vLine, anoText)
      vLine := (index + viewStart) ; /* view line # on screen */
      if (vLine >= 0) cand (vLine < viewLines) /* line displayed? */
      then
        anoText := new( ATParser, textColl[index] ) ;
        doDisplay(anoText, hDC, 
                  hScrollPix, (vLine * tmHeight + 2), 
                  tmWidth, tmHeight ) ;
      endif ;
    });
  });
  if selLineInfo  /* display hilighted field */
  then
    displaySelectedLine( self, hDC ) ;
  endif ;
}
!!

/* PUBLIC */
Def parent(self)
{ ^parent }
!!

/* PRIVATE
   Delete selected varInfo.
*/
Def performDeleteSelVar(self, ignored)
{
  maybeCloseEditWin( self ) ;
  deleteSelVar( self ) ;
}
!!

/* PRIVATE -- pop menu for selection of varInfo variable */
Def popupVarMenu(self | xPixelPos, linePos, yPixelPos)
{
  selected( parent, VP_MENU_DELETE ) ; /* variable name */
  ^nil; 

  /*@@ disabled following selection -- user now used DELETE key --kad @@*/

  xPixelPos := (selLineInfo[2][1] * tmWidth) ;
  linePos   := (viewStartLine(selLineInfo[0]) + selLineInfo[3]) ;
  if (linePos < ((2 * visLines(self)) / 3))
  then
    yPixelPos := (linePos * tmHeight) ; /* above 2/3 of visLines */
  else
    yPixelPos := ((linePos - 20) * tmHeight) ; /* low -- move it up */
  endif;

  popWindow := 
        new( PopupMenu,
             "Variable: " + fieldStr(selLineInfo[1], selLineInfo[2]), /* title */
             tuple(                  /* data */ 
                   tuple( "Delete This Variable's View ", 
                          601, #performDeleteSelVar ) 
               /*  tuple( "Show Address       ",    603, #menuAddr     ) */
               /*  tuple( "Set Data Break     ",    603, #varBreak     ) */
                  ), 
             self,                  /* parent */
             point(xPixelPos, yPixelPos)  /* where to put upper-left corner */
           ) ;       
           
  show(popWindow,1);
}
!!

/* PRIVATE -- update "expanded names" for vars (Compressed) */
Def refreshHeaderLines(self)
{
  if not(compressRefreshed?) /* do lazy updates */
  then
    do(varCollection,
    {using(varInfo | names)
      names := getVarNames( self, varInfo ) ;
      if names
        setExpandedName( varInfo, names[1] ) ;  /* 1st line == expanded name */
      endif ;
    });
    compressRefreshed? := #true ;
  endif ;
}
!!

/* PRIVATE */
Def refreshVars(self | linesWanted)
{
  nilifySelLineInfo( self ) ;
  resetSelected( self ) ;
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;

  /* Enumerate over the VarColelction to get new size information */
  do(varCollection,
    {using(var | numLines)
      numLines := getLinesForVar( varSession, varID(var) ) ;
      if numLines then
        setLineNumInfo(var, numLines, #Exact);
      else
        /* DEBUG - printNewLine( "getLinesForVar returned unknown: " + name(var) ); */
        setLineNumInfo(var, 0, #Unknown);
      endif ;
    });
  
  /* Adjust the var display according to display mode */
  if displayCompressed? then
    refreshHeaderLines( self ) ;
    makeCompressedDisplay( self ) ;
  else
    if not(refreshText( viewColl, varSession )) then
      ^nil;
    endif;  
    linesWanted := ( visLines(self) - numLinesIn(viewColl) ) ;
    if (linesWanted > 0) then 
      addNlinesToDisplayBottom( self, linesWanted, nil ) ;
    endif ;
  endif ;

  invalidate( self ) ; /* may reset scroll-pos */
}!!

/* PRIVATE */
Def removeVarByVarInfo(self, dyingVar | varIndex)
{ 
  maybeCloseEditWin( self ) ;

  if (size(varCollection) = 0)
  then
    nilifySelLineInfo( self ) ;
    focusVar := nil ;
    viewColl := newViewCollection( self ) ;
    ^invalidate( self ) ;
  endif ;
  
  /* select a new focusVar as required */
  if (focusVar = dyingVar)
  then 
    varIndex := find( varCollection, dyingVar ) ;
    if not( varIndex ) then ^nil; endif ; /* not found! */
    if ( varIndex > find(varCollection, first(varCollection)) )
    then /* use previous */
      focusVar := at( varCollection, (varIndex - 1) ) ;
    else /* use next */
      if ( varIndex < find(varCollection, last(varCollection)) )
      then focusVar := at( varCollection, (varIndex + 1) ) ;
      else focusVar := nil ;  /* no where else to look */
      endif;
    endif ;
  endif ;

  /* Remove varInfo from varCollection, and nilify selLineInfo */
  nilifySelLineInfo( self ) ;
  remove( varCollection, dyingVar ) ;  /* N.B.: Don't close var! */

  /* rebuild the display (=> scratch viewCollection) */
  if displayCompressed?
  then
    initWorkText( self ) ;
  else
    viewColl := newViewCollection( self ) ;
  endif ;
  if focusVar
  then 
    scrollToFocus( self ) ; /* rebuilds + does invalidate(self) */
  else 
    invalidate( self ) ; /* nothing to show */
  endif ;
} !!

/* PRIVATE */
Def resetSelected(self)
{
  selected( parent, nil ) ;  /* gray all options */
  if (size(undoLifo) > 0)
  then
    selected( parent, VP_MENU_UNDELETE ) ;
  endif ;
}
!!

/* WINDOWS */
Def reSize(self, wP, lP | linesWanted)
{
  if (size(varCollection) = 0) cor displayCompressed?
  then ^invalidate( self ) ;
  endif ;
  
  linesWanted := visLines(self) - 
                   (numLinesIn(viewColl) - prefixLinesHiddenIn(viewColl)) ;
  if (linesWanted > 0)
  then
    addNlinesToDisplayBottom( self, linesWanted, nil ) ;
  endif ;
  /* invalidate-region done by caller */
}
!!

/* PRIVATE  -- restore a variable to self from undoLifo */
Def restoreLastVar(self | aVarInfo)
{
  selected( parent, nil ) ;
  if (size(undoLifo) = 0)
  then
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_NO_VIEWS, FORCE_POPUP, nil, nil, nil);
  else
    aVarInfo := pop( undoLifo ) ;
    addVarByDeadVarInfo( self, aVarInfo ) ; /* does invalidate(self) */
    resetSelected( self ) ;
  endif ;
}
!!

/* PUBLIC */
Def rightArrow(self)
{
  maybeCloseEditWin( self ) ;

  ^rightField( self )
}
!!

/* PRIVATE -- got right 1 field */
Def rightField(self | nextField, hDC, viewLineNum)
{
  if selLineInfo  /* #(viewInfo, ATParser, field, bufTextIndex) */
  then /* use line info */
    nextField := nextField( selLineInfo[1], selLineInfo[2] ) ;
    if nextField
    then
      viewLineNum := viewStartLine( selLineInfo[0] ) + selLineInfo[3] ;
      hDC := getContext( self ) ;
      /* unselect old */
      hiLightOff( selLineInfo[1] ) ;
      doDisplay( selLineInfo[1], hDC, 0, 
                 (viewLineNum * tmHeight), tmWidth, tmHeight) ;
      selLineInfo[2] := nextField ;
      /* select new */
      hiLight( selLineInfo[1], selLineInfo[2] ) ;
      doDisplay( selLineInfo[1], hDC, 0,
                (viewLineNum * tmHeight), tmWidth, tmHeight) ;
      releaseContext( self, hDC ) ;
/*@@ else goto next line @@*/
    endif ;
  else /* no line to start from (no default) */
    beep() ; /* @@ should use 1st display line @@ */
  endif ;
  ^0
}
!!

/* PRIVATE -- scroll to focus var; rebuild viewColl */
Def scrollToFocus(self | aView, oldView, linesWanted)
{
  maybeCloseEditWin( self ) ;
  
  if displayCompressed? then 
    ^cScrollToFocus( self ) ;
  endif ;
  /* Save the old view colelction in case user hit ESC to abort */  
  oldView := viewColl;
  
  viewColl := newViewCollection( self ) ; /* redo from scratch */
  selLineInfo := nil ;

  if not(focusVar) 
  then
    if (size(varCollection) > 0)
      focusVar := first( varCollection ) ;
    else
      invalidate( self ) ;
      ^nil /* empty var & view collections */
    endif ;
  endif ;

  /* bufNum = startLine = 0 */
  if not(aView := addVarViewNAt(self, focusVar, 0, 0 )) then
    /* 07/23/93 - Nghia - Fixed PPR 8552
    ** User might hit ESC to abort, restore the old view 
    */
    if (lastError(varSession) = ER_ABORT_FROM_ESC) then
      viewColl := oldView;
    endif;    
    invalidate(self); 
    ^nil; 
  endif ; 

  linesWanted := ( visLines(self) - size(aView) ) ;
  if ( linesWanted > 0) then
    addNlinesToDisplayBottom( self, linesWanted, nil ) ;
  endif ;
  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE -- scroll to focus var; rebuild viewColl */
Def scrollToFocusWithLine(self, lineNum | aView startLine bufNum linesWanted)
{
  maybeCloseEditWin( self ) ;

  if displayCompressed?
  then ^cScrollToFocus( self ) ;
  endif ;

  viewColl := newViewCollection( self ) ; /* redo from scratch */
  selLineInfo := nil ;

  if not(focusVar) 
  then
    if (size(varCollection) > 0)
      focusVar := first( varCollection ) ;
    else
      invalidate( self ) ;
      ^nil /* empty var & view collections */
    endif ;
  endif ;

  bufNum := asLong( lineNum / maxBufLines(self) ) ;
  startLine := negate( asInt( lineNum mod maxBufLines(self) ) ) ;

/*@@{ printLine( "scrollToFocusWithLine: (buf, startLine) = (" 
          + asStringRadix(bufNum, 10) + ", "
          + asStringRadix(startLine, 10) + ")" ) ;
}@@*/
  aView := addVarViewNAt( self, focusVar, bufNum, startLine ) ; 

  if not( aView ) then invalidate(self); ^nil endif ; /*@@ ugly error @@*/

  linesWanted := ( visLines(self) - size(aView) - startLine ) ;
  if ( linesWanted > 0) 
  then
    addNlinesToDisplayBottom( self, linesWanted, nil ) ;
  endif ;

  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE
  Set cursor position (focusVar, varSelected) according to the
  specified point.  SelectLine is Window relative (i.e. from 0).
  After this call, either selLineInfo is valid or this call has
  returned nil.
*/
Def setCurSelect(self, point
        | lineNum view field index varInfo outCharPos parsedText selectSet)
{
  maybeCloseEditWin( self ) ;
  nilifySelLineInfo( self ) ;  /* turn off any old selection */

  lineNum := ( max( 0, y(point)-2 ) / tmHeight ) ;
  
  if displayCompressed?
  then 
    index := (lineNum + startLine) ;
    if (index >= size(varCollection))
    then /* selection out of range */
      ^nil ;
    endif ;
    focusVar := varInfo := varCollection[ index ] ;
    parsedText := parse( new(ATParser, expandedName(varInfo)) ) ;
  else /* uncompressed */
    if not( view := viewFromLine(viewColl, lineNum) ) /* #( viewInfo, textStr ) */
    then /* selection out of range */
      ^nil ;
    endif ;
    focusVar   := var( view[0] ) ;
    parsedText := parse( new(ATParser, view[1]) ); /* viewLine */
  endif ;

  outCharPos := (x(point) / tmWidth) + columnPos ; /* adjust for Hscroll */
  /*@@@ make selectSet a static set or move to class init--later @@@*/
  selectSet := new( Set, 3 );
  add( selectSet, AT_VAR_NAME_FIELD ) ;
  add( selectSet, AT_EDIT_FIELD ) ;
  add( selectSet, AT_REFERENCE_FIELD ) ;

  /* selected something? */
  if (field := fieldIn(parsedText, outCharPos)) 
     cand (field[0] in selectSet)
  then
     /* set up new selection: #( viewInfo, ATParser, field, bufTextIndex ) */
     if displayCompressed?
     then
       selLineInfo := tuple( new(VPviewInfo, varCollection[index]), 
                             parsedText, 
                             field, 
                             0
                           ) ;
     else
       selLineInfo := /* #( viewInfo, ATParser, field, bufTextIndex ) */
         tuple( view[0], parsedText, field, 
                                     (lineNum - viewStartLine(view[0])) ) ;
     endif ;
     hiLightField( parsedText, field ) ;
     displaySelectedLine( self, nil ) ;
     ^parsedText
  endif ;
  
  ^(selLineInfo := nil)  /* failed */
}!!

/* PUBLIC */
Def setExpandGlobal?(self, bool)
{ ^(expandGlobal? := bool) }
!!

/* PRIVATE
  The viewColl offset has been adjusted.  Set the focusVar to be the
  var of the 1st view displayed.
*/
Def setFocusByView(self | focus)
{
  maybeCloseEditWin( self ) ;

  focus := focusView(viewColl) ;
  if focus
  then focusVar := var( focus ) ;
  else focusVar := nil ; /* empty viewColl */
  endif ;
}
!!

/* PRIVATE
   Set scroll bar position based on startLine.
*/
Def setHScrollPos(self)
{
  /* Compressed Mode */
  if displayCompressed? then 
    ^cSetHScrollPos( self ) ;
  endif ;

  if not( (size(varCollection) > 0) cand (size(viewColl) > 0) )
  then
    columnPos := 0 ;
  endif ;


  Call SetScrollPos(hWnd(self),
                    SB_HORZ,  /* what bar to redraw (only 1) */
                    columnPos, /* where to put it -- % */
                    1) ;       /* do the redraw (why else would we call?) */
}!!

/* PUBLIC */
Def setParent(self, aWindow)
{ ^(parent := aWindow) }
!!

/* PRIVATE -- set selected line from focus var's name.
   N.B.: always called just after scrollToFocus() => 1st line displayed
   is hilight/selected line.
*/
Def setSelectLineFromFocus(self | firstLine parsedLine nameField)
{
  if not( focusVar ) then ^nil endif ;
  
  if displayCompressed?
  then
    firstLine := workText[ startLine ] ;
  else
    /* PPR 8552 - Search then hit ESC will cause viewColl to be empty 
    ** Added check to prevent UAE
    */
    if (size(viewColl) = 0) then
      ^nil;  /* No view variable found - failed - blank window */
    endif;        
    firstLine := bufText( viewColl[0] )[0] ;
  endif ;

  if not(firstLine) then   
    ^nil /* failed */
  else 
    parsedLine := parse( new( ATParser, firstLine ) ) ;
    nameField := firstField( parsedLine ) ;
    loop while nameField
               cand (nameField[0] <> AT_VAR_NAME_FIELD) 
               cand (nameField[0] <> AT_REFERENCE_FIELD)
    begin nameField := nextField( parsedLine, nameField ) ;
    endLoop;
    if nameField
    then
     if displayCompressed?
     then
       selLineInfo :=
         tuple( new(VPviewInfo, focusVar), parsedLine, nameField, 0 ) ;
     else
       selLineInfo := /* #( viewInfo, ATParser, field, bufTextIndex ) */
         tuple( viewColl[0], parsedLine, nameField, 0 ) ;
     endif ;
     hiLightField( parsedLine, nameField ) ;
     displaySelectedLine( self, nil ) ;
    endif ;
  endif ;
  setMenuHilights( self ) ;
}
!!

/* PRIVATE -- called by sortByVarName or sortByHistory */
Def setSortType(self, newSortType)
{
  if not( newSortType in #( #history, #name ) ) then
    ^nil /* error */
  endif ;
  
  sortType := newSortType ;
  setCompareBlock( varCollection, getSortBlock(self, newSortType) ) ;

  if (size(varCollection) > 0)
  then
    focusVar := first( varCollection ) ;
  endif ;

  if focusVar
  then
    scrollToFocus( self ) ;  /* rebuilds viewColl/workText */
  else
    invalidate( self ) ; /* refresh screen */
  endif ;

  ^sortType
}
!!

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

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

/* PRIVATE
   Set scroll bar position based on startLine.
*/
Def setVScrollPos(self 
                 | currLineNum totalLines varIndex viewLines perCent adjust)
{
  if displayCompressed?
  then ^cSetVScrollPos( self ) ;
  endif ;

  totalLines  := varTextSize( self ) ;
  currLineNum := virtualStartLine( self ) ;
  viewLines := visLines( self ) ;

  select
    case (totalLines = 0) cor (currLineNum < 1)
    is /* empty */
      varIndex := 0 ;
    endCase
    case (currLineNum + viewLines) > totalLines
    is /* ~max */
      varIndex := 100 ;
    endCase
    default
      perCent  := ( (100 * currLineNum) / (totalLines - viewLines) ) ;
      adjust   := asInt((perCent * viewLines) / 100) ;
      varIndex := asInt( (100 * (currLineNum + adjust)) 
                         / totalLines ) ;
  endSelect;
  Call SetScrollPos(hWnd(self),
                    SB_VERT,  /* what bar to redraw (only 1) */
                    varIndex, /* where to put it -- % */
                    1) ;      /* do the redraw (why else would we call?) */
}!!

/* PRIVATE */
Def sortByHistory(self, ignored)
{
  maybeCloseEditWin( self ) ;

  setSortType( self, #history ) ;
  unCheckMenuItem( menu(parent), VP_MENU_VNAME_SORT   ) ;
  checkMenuItem(   menu(parent), VP_MENU_HISTORY_SORT ) ;
  scrollToFocus( self ) ;

  ^0
}
!!

/* PRIVATE */
Def sortByVarName(self, ignored)
{
  maybeCloseEditWin( self ) ;

  setSortType( self, #name ) ;
  if menu
  then
    unCheckMenuItem( menu, VP_MENU_HISTORY_SORT ) ;
    checkMenuItem(   menu, VP_MENU_VNAME_SORT   ) ;
    scrollToFocus( self ) ;
  endif ;

  ^0
}
!!

/* PUBLIC (but who cares?) */
Def sortType(self)
{ ^sortType }
!!

/* PRIVATE -- scroll to proximal var */
Def thumbScroll(self, vPos 
               | totalLines lineWanted varAndLine viewLines adjust)
{
  maybeCloseEditWin( self ) ;
  if displayCompressed? then
    ^cThumbScroll( self, vPos )
  endif ;
  select
    case (size(varCollection) = 0)
      ^nil;
    endCase
/*  12/16/92 - Nghia removed fixed text overlap on first line when scroll.  
    case (vPos < 2)
      ^topPage(self);
    endCase
*/    
    case (vPos > 98)
      ^bottomPage(self);
    endCase
  endSelect;
  
  totalLines := varTextSize( self ) ;
  viewLines  := visLines( self ) ;
  if (viewLines >= totalLines) then /* all text displayed */
    ^nil
  else  /* adjust for % displayed */
    adjust := asInt((vPos * viewLines) / 100) ; 
    lineWanted := asLong( (totalLines * vPos) / 100 ) - adjust ;  
    varAndLine := varAndLineContainingLine( self, lineWanted ) ;
    focusVar := varAndLine[0] ;
    scrollToFocusWithLine( self, varAndLine[1] ) ;
  endif ;
}
!!

/* PRIVATE -- scroll up ~1 screen */
Def topPage(self | linesNeeded, aView )
{
  maybeCloseEditWin( self ) ;
  if displayCompressed? then
    startLine := 0 ;
    ^invalidate(self); /* causes repaint, which does setVScrollPos */
  endif ;

  if (size(varCollection) = 0)
  then ^nil
  endif ;

  focusVar := first(varCollection);
  /* NOTES: for topPage : bufNum, startLine are both 0 */
  if not(aView := addVarViewNAt( self, focusVar, 0, 0 )) then   
    invalidate(self);  /* 12/16/92 - Nghia added check */
    ^nil;
  endif; 
   
  if viewColl cand (size(viewColl) > 0) then
    linesNeeded := ( visLines(self) - size(first(viewColl)) );
  endif;  
  
  if (linesNeeded > 0) then
    addNlinesToDisplayBottom( self, linesNeeded, nil ) ;
  endif;
  invalidate( self ) ; /* causes repaint, which does setVScrollPos */
}
!!

/* PRIVATE
  View has been resized.  ViewStartLine numbering is correct.
  Trim unneeded viewInfo's from viewColl
*/
Def trimDisplayViews(self)
{
  ^trimDisplayViews( viewColl, visLines(self) )
}
!!

/* PUBLIC */
Def upArrow(self)
{  maybeCloseEditWin( self ) ;

  /* selLineInfo is #(viewInfo, ATParser, field, bufTextIndex) */
  if (selLineInfo[3] > 0) then
    selLineInfo[3] := (selLineInfo[3] - 1) ;
    selLineInfo[1] := parse(new(ATParser, 
                                bufText(selLineInfo[0])[(selLineInfo[3])]
                           )   ) ;
    selLineInfo[2] := firstField( selLineInfo[2] ) ;
    if selLineInfo[2]
    then
      hiLight( selLineInfo[1], selLineInfo[2] ) ;
    else
      upArrow( self ) ; /* try the next line above */
    endif ;
  else
    /* Goto prev field */
    selLineInfo := nil ;
  endif ; 
}
!!

/* PRIVATE -- returns #( var, lineOffset ) for var containing lineWanted */
Def varAndLineContainingLine(self, lineWanted | totalLines var)
{
  totalLines := 0 ;
  do(varCollection,
    {using(var | newTotal)
     newTotal := totalLines + numLines( var ) ;
      if (totalLines <= lineWanted) cand (lineWanted <= newTotal) then
        ^tuple( var, (lineWanted - totalLines) )
      else
        totalLines := newTotal ;
      endif ;
    });

  /* not found */
  if (lineWanted > totalLines) then
    var := last( varCollection ) ;
    ^tuple( var, numLines(var) ) 
  else
    ^tuple( first(varCollection), 0 )  /*@@ somewhat bogus @@*/
  endif ;
}
!!

/* PRIVATE */
Def varContainingLine(self, lineWanted | totalLines)
{
  totalLines := 0 ;
  do(varCollection,
  {using(var | newTotal)
    newTotal := totalLines + numLines( var ) ;
    if (totalLines <= lineWanted) cand (lineWanted <= newTotal)
    then
      ^var
    else
      totalLines := newTotal ;
    endif ;
  });

  /* not found */
  if (lineWanted > totalLines)
  then
    ^last( varCollection )
  else
    ^first( varCollection ) ; /*@@ somewhat bogus @@*/
  endif ;
}
!!

/* PRIVATE -- return total number of display lines for all vars */
Def varTextSize(self | totalLines)
{
  totalLines := 0 ;
  do(varCollection,
  {using(var)
    totalLines := totalLines + numLines( var ) ;
  });

  ^totalLines
}
!!

/* PRIVATE -- How many lines from the start is the 1st line displayed? */
Def virtualStartLine(self 
| startVar startView startLine varInd lastVarInd howMany)
{
  if (size(viewColl) = 0) cor (size(varCollection) = 0)
  then ^0
  endif ;

  /* get num lines before 1st view var */
  startView := first( viewColl ) ;
  startVar := var( startView ) ;
  howMany := 0 ;
  varInd := find( varCollection, first(varCollection) ) ;
  lastVarInd := findItemIndex(varCollection, startVar)[1] ;
  do(over(varInd,lastVarInd),
  {using(index)
    howMany := howMany + numLines( at(varCollection, index) ) ;
  });
    
  /* add in # lines to startLine */
  startLine := viewStartLine( startView ) ;
  howMany := (howMany +                               /* previous to var */
             (bufNum(startView) * maxBufLines(self))  /* previous buf lines */
              - startLine ) ;                         /* lines to start */
  /* DEBUG - printNewLine("virtualStartLine(varP) is "+asString(howMany)); */
  ^howMany
}
!!

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

/* Translate MS-Windows character input message to an
  Actor-style message. */
Def WM_CHAR(self, wP, lP)
{ charIn( self, wP, lP ) ; }
!!

/* WINDOWS -- sent by event manager (evNoteLib) */
Def WM_EVENT(self, wP, event)
{
  select
    case (event = EVENT_SYMBOL_DELETED) cor (event = EVENT_SYMBOL_INIT_LOAD)       
    is
      deleteAllVars( self ) ;
    endCase

    case (event = EVENT_VAR_EDIT) or (event = EVENT_VAR_HALTED)
    is
      compressRefreshed? := nil ;
      refreshVars( self ) ;
    endCase

    default
       displayFormattedError(ErrorTextLibClass$Inst, 
          ER_UNKNOWN_EVENT, FORCE_POPUP, asciiz(asStringRadix(event, 16)), 
          nil, nil);
      ^1;
  endSelect;

  ^0
}
!!

/* WINDOWS
  Respond to MS-Window's vertical scrolling message. wP tells what kind of
  scrolling request has been made.
*/
Def WM_HSCROLL(self, wP, lP)
{
  mabeClosePopup(self);
  maybeCloseEditWin(self);
  
  /* reject thumb tracking */
  if wP = SB_THUMBTRACK cor wP = SB_ENDSCROLL
    ^0;
  endif;
  
  showWaitCurs();
  select
    case wP == SB_LINEDOWN
      is columnRight(self);
    endCase
    case wP == SB_PAGEDOWN
      is pageRight(self);
    endCase
    case wP == SB_BOTTOM
      is maxRight(self);
    endCase
    case (wP == SB_LINEUP) cand (columnPos > 0)
      is columnLeft(self);
    endCase
    case (wP == SB_PAGEUP) cand (columnPos > 0)
      is pageLeft(self);
    endCase
    case wP == SB_TOP
      is maxLeft(self);
    endCase
    case  wP == SB_THUMBPOSITION
      is hThumbScroll(self, low(lP));
    endCase
  endSelect;
  showOldCurs();
  ^0
}
!!

/* Allow "arrow" keys and the tab key to work 
 without using accelerators. */
Def WM_KEYDOWN(self, wp, lp)
{ if /* between(wp, 37, 40) cor */
    (wp == VK_DELETE) cor (wp == 301) /* allow DELete key */
  then 
    command(self, wp, 0x10000);
  else
    WM_KEYDOWN( self:ancestor, wp, lp ) ;
  endif;
}!!

/* WINDOWS (PUBLIC)
   Select object nearest to mouse point or set cursor position;
   Popup menu selection.
   NOTES: Nghia - 10/12/93
   Clear out any ESC key pending.
*/
Def WM_LBUTTONDBLCLK(self, wp, lp | mousePt, selectType)
{
  /* Clear ESC key */
  if (TaskLibClass$Inst)
    checkAbort(TaskLibClass$Inst);
  endif;
  resetSelected( self ) ;
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  mousePt := asPoint( lp ) ; /* N.B.: window relative */
  if setCurSelect(self, mousePt) then  
    /* 1st CharVal is marker */
    selectType := setMenuHilights( self ) ;
  else
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_VAR_HIGHLIGHT, FORCE_POPUP, nil, nil, nil);
    ^0
  endif ;

  select
    case selectType = AT_VAR_NAME_FIELD
    is
      if (selLineInfo[3] = 0) /* name of varInfo variable */
      then
        popupVarMenu( self ) ;
      else
        addVarViaRef( self ) ;
      endif ;
    endCase
    case selectType = AT_EDIT_FIELD
    is
      editFieldValue( self ) ;
    endCase
    case selectType = AT_REFERENCE_FIELD
    is
      addVarViaRef( self ) ;
    endCase
    default
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_VAR_HIGHLIGHT, FORCE_POPUP, nil, nil, nil);
  endSelect;

  ^0
}
!!

/* WINDOWS (PUBLIC)
   Select object nearest to mouse point or set cursor position.
*/
Def WM_LBUTTONDOWN(self, wp, lp | mousePt)
{
  resetSelected( self ) ;    /* Reset all menu commands applied to selVar */
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  mousePt := asPoint( lp ) ; /* NOTES: window relative */
  if setCurSelect(self, mousePt)
  then setMenuHilights( self ) ;
  endif ;

  ^0
}
!!

/* 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));
  initTextColors( self, hdc ) ;
  paint(self, hdc);
  Call SelectObject(hdc, Call GetStockObject(SYSTEM_FONT));
  Call EndPaint(getHWnd(self), paintStruct);
  ^0;
}!!

/* WINDOWS
  Respond to MS-Window's vertical scrolling message. wP tells what kind of
  scrolling request has been made.
*/
Def WM_VSCROLL(self, wP, lP | errMsg)
{
  mabeClosePopup( self ) ;
  maybeCloseEditWin( self ) ;
  /* Check to avoid hang up in accessing memory with emulator is running */ 
  if not(prim_processorHalted?(HLBrkRootLibClass$Inst)) then
    errMsg := getErrorText(ErrorTextLibClass$Inst, clearError(HLBrkRootLibClass$Inst));
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_SCROLL_NEEDS_MEM, FORCE_POPUP, errMsg, nil, nil);
    ^0;
  endif;
  showWaitCurs(); 
  select
    case wP == SB_LINEDOWN
      lineDown( self );
    endCase
    case wP == SB_PAGEDOWN
      pageDown( self ) ;
    endCase
    case wP == SB_BOTTOM
      bottomPage( self ) ;
    endCase
    case (wP == SB_LINEUP)
      lineUp( self ) ;
    endCase
    case wP == SB_PAGEUP
      pageUp( self ) ;
    endCase
    case wP == SB_TOP
      is topPage( self ) ;
    endCase
    case  wP == SB_THUMBPOSITION
      thumbScroll( self, low(lP) ) ;
    endCase
  endSelect;
  showOldCurs();
  
  ^0 
}
!!
