/* CLASS: SourceCB
    Caching Browser specialized for browsing source files and disassembly
    data object with selection.
 
    REQUIRE: BKPTREGION.CLS CACHINGB.CLS
*/!!

inherit(CachingBrowser, #SourceCB, #(languageName /* for current file */
languageDict /* for lang (private) */
dragDC    /* The handle to a display context used for selecting text */
selectLine  /* The current line # for selection*/
startChar /* Starting character # of highlighted range of text */
endChar   /* Ending character # of highlighted range of text */
tokenInfo /* #( tokStr, type, charPos, textStartPos, textEndPos, symbolDesc ) */
invertOn? /* if non-nil, region is inverted */
bkptRegion /* SingleClick set/clear bkpt */
xStart /* start offset from BkptRegion */
showLineNum /* Boolean flag */
bkColor /* normal or Gray if not active */
tabWidth /* number of space per Tab */
drawAddrDesc /* an address descriptor */), 2, nil)!!

now(class(SourceCB))!!

now(SourceCB)!!

/* 06/20/92 - PUBLIC
  Set language parser of the browser.
  NOTES: 1/11/94 - Support inspecting symbols in Mixed mode.
  Used the token parser of #C language for all virtXXXObjects.
*/
Def setLanguageParser(self)
{
  languageName := #C;
  /* Set Browser language Dictionary */
  if not(languageDict := at(AppDictionary[#LanguageDict], languageName)) then
    displayFormattedError(ErrorTextLibClass$Inst,
       ER_INVALID_LANGUAGE, FORCE_POPUP, nil, nil, nil);
    languageName := #Unknown ;
    languageDict := at( AppDictionary[#LanguageDict], #Unknown );
    setLanguageName( dataObject, languageName );
  endif ;
  ^languageName     /* caller can check this for error */
}
!!

/* 5/15/1992 10:27 - PRIVATE 
  Return #true if offset is on screen, nil otherwise.
  NOTES: Only used with dataObject = VirtSMixedObject.
    - Caller has ownership of the input <addrDesc>. 
    - View of Browser's data buffer.  
    
      +---------+   <- VirtualStartLine
      |  chunk  |
     >-----------<  +    CacheStartLine 
      +---------+   |
      |  chunk  |   |
      +---------+   | <- Screen Viewport 
      +---------+   | 
     >-----------<  +   
      |  chunk  |
      +---------+  
*/
Def mixedOffsetOnScreen1?(self, addrDesc, topLine, bottomLine | 
  topDesc, bottomDesc, temp, temp2)
{
  /* map both topLine and bottomLine to address descriptor */
  if not(cacheText) cor (size(cacheText) = 0) cor
     not(topDesc := mapLine(dataObject, #true, cacheText[topLine])) then
    ^nil;
  endif;
  
  if not(bottomDesc := mapLine(dataObject, #true, cacheText[bottomLine])) then
    destroyAddress(AddressLibClass$Inst, topDesc);
    ^nil;
  endif;  
  /* DEBUG 
  printLine("on Screen: "+asAddr(AddressLibClass$Inst, addrDesc));
  */ 

  /* Compare the input <addrDesc> between the TopDesc and BottomDesc */
  temp := compareAddressesNoError(AddressLibClass$Inst, addrDesc, topDesc);
  temp2 := compareAddressesNoError(AddressLibClass$Inst, addrDesc, bottomDesc);
  /* Get rid of the unused descriptors */
  destroyAddress(AddressLibClass$Inst, topDesc);
  destroyAddress(AddressLibClass$Inst, bottomDesc);
  if not(temp) cor not(temp2) then
    ^nil;
  endif;
  
  /* addrDesc >= topDesc && addrDesc < bottomDesc */
  ^(((temp = ADRLIB_ADDR_GREATER_THAN) cor (temp = ADRLIB_ADDR_EQUAL)) cand 
     (temp2 = ADRLIB_ADDR_LESS_THAN)); 
}
!!

/* 5/15/1992 10:27 - PRIVATE 
  Return #true if offset is on screen, nil otherwise.
  NOTES: Only used with dataObject = VirtSMixedObject.
    - Caller has ownership of the input <addrDesc>. 
    - View of Browser's data buffer.  
    
      +---------+   <- VirtualStartLine
      |  chunk  |
     >-----------<  +    CacheStartLine 
      +---------+   |
      |  chunk  |   |
      +---------+   | <- Screen Viewport 
      +---------+   | 
     >-----------<  +   
      |  chunk  |
      +---------+  
*/
Def mixedOffsetOnScreen?(self, addrDesc, topLine, bottomLine | 
  line1, line2, lineInfo)
{
  /* map both topLine and bottomLine to address descriptor */
  if not(cacheText) cor (size(cacheText) = 0) cor
     not(line1 := getLineNum(dataObject, cacheText[topLine])) then
    ^nil;
  endif;  
  if not(line2 := getLineNum(dataObject, cacheText[bottomLine])) then
    ^nil;
  endif;
  if not(lineInfo := addr2lineAndModule(SymbolLibClass$Inst, addrDesc)) then
    ^nil;
  endif;
  /* NOTES: addrInfo = #(moduleDesc, moduleAddrDesc, lineNum, colNum, lineAddrDesc, lineNumDesc) */
  /* destroy the unused address descriptors */
  destroyAddress(AddressLibClass$Inst, lineInfo[1]); 
  destroyAddress(AddressLibClass$Inst, lineInfo[4]);
  /* RETURN: #(moduleDesc, lineNum, colNum) */ 
  /* DEBUG 
  ** printLine(asString(line1)+"-"+asString(line2)+" #"+asString(lineInfo[2]));  
  */
  ^((lineInfo[2] >= line1) cand (lineInfo[2] < line2));   
}
!!

/* 3/11/1994 17:25 - PRIVATE
  Set the new drawAddrDesc for self.
*/
Def setNewDrawAddrDesc(self, newDrawAddr)
{ 
  if (drawAddrDesc) then
    destroyAddress(AddressLibClass$Inst, drawAddrDesc);
  endif;
  drawAddrDesc := newDrawAddr;
}
!!

/* 1/12/1993 10:49 - PUBLIC (Only to virtual data objects)
  Return the getTxtLineRoutine.
*/
Def getTxtLineRoutine(self)
{ 
  ^getTxtLineRoutine;
}
!!

/* 06/20/92 - PUBLIC
   Goto line and show token.  lineAndCol is #( line, col )
   Check that we don't go off the end of the data object.
*/
Def showLineAt(self, lineAndCol | linesShown, lineToShow, rangePt)
{
  clearPC(self);
  invertOff(self);
  linesShown := visibleLines;
  lineToShow := max(1, (lineAndCol[0] - (linesShown/2)));
  /* Get text from data object */
  cacheHit(self, lineToShow, linesShown);
  /* cacheHit can cause line renumbering.  Update lines we need. */
  lineAndCol[0] := getLineAdjustment(self, lineAndCol[0]);
  
  /* Set the select Line to be shown */
  selectLine := (lineAndCol[0] - (virtualStartLine+cacheStartLine));
  if (selectLine < 0) then selectLine := 0 endif;
  startChar := endChar := lineAndCol[1];
  doInvert(self); /* Invert then paint will hilight again */
  /* Check for the end of virtLineLimit */
  if (lineAndCol[0] > virtualLineLimit) then
    /* Reset to max virttualLineLimit */
    virtualStartLine := (virtualLineLimit - linesShown +2);
    /* Let get the text */  
    cacheHit(self, virtualStartLine, linesShown);
  endif;
  invalidate(self); 
}
!!

/* 12/4/1992 8:34 - PRIVATE (Modified from TextCollection class)
  Return a point giving the location in cacheText of self
  of the supplied string. Return nil if not found. 
*/
Def findStringInCacheReverse(self, startLine, searchStr | findCol)
{ 
  /* Search from startLine to top of cacheText buffer */
  do(overBy(startLine, 0, -1),
    {using(i)
      if findCol := find(eval(getTxtLineRoutine, cacheText[i]), searchStr, 0) then 
        ^point(findCol, i);
      endif;
    }); 
  ^nil;
}

!!

/* 11/4/1992 16:14 - PRIVATE (Modified from TextCollection class)
  Return a point giving the location in cacheText of self
  of the supplied string. Return nil if not found. 
*/
Def findStringInCache(self, str, startLine, strtChar | sL, fC)
{ 
  sL := startLine;
  if strtChar > 0 then 
    fC := find(eval(getTxtLineRoutine, cacheText[sL]), str, strtChar);
    sL := sL + 1;
  endif;
  if fC then ^point(fC, startLine);  endif;
  
  do(over(sL, size(cacheText)),
  {using(i)
    if fC := find(eval(getTxtLineRoutine, cacheText[i]), str, 0) then 
      ^point(fC, i);
    endif;
  });
  ^nil;
}

!!

/* 10/23/1992 16:47 - PUBLIC (to its parent) 
  same as pointFromStartChar()
*/
Def getLogicalCharPos(self, charPos | selStr, col, textMap )
{ 
  /* Map output to input position */
  if not(selStr := selectedLineText(self)) then
    ^1;
  endif;

  if not(showLineNum) then 
    selStr := subString(selStr, 9, size(selStr));
  endif;

  /* Tab expansion for the string after trim off the linenumber */
  textMap := expandTabsMap(selStr, tabWidth);

  /* Map out the logical of charPos */
  if (size(textMap) > 0) then
    /* For a regular text token */ 
    if showLineNum then 
      col := max(textMap[min(charPos, size(textMap)-1)] - 9, 1); 
    else
      col := max(textMap[ min(charPos, size(textMap)-1) ], 1); 
    endif;
  else
    if showLineNum then
      col := 9;
    else
      col := 1;  /* null text string */
    endif;  
  endif ;
  ^col;
}
!!

/* 10/23/1992 11:31 - DEBUG
   Print out the tab expansion map.
*/
Def printMap(self, mapColl)
{ 
  do (size(mapColl),
    {using(i)
        print(asString(mapColl[i])+" ");
     });   
  printLine(" ");   
}
!!

/* 10/22/1992 15:06 - PRIVATE
  Get the adjacent dasm location to scroll up|down. Depend on the direction
  to show the address position.
  NOTES: 
    direction = MOVE_RANGE_TO_LOW_ADDR, MOVE_RANGE_TO_HI_ADDR
*/
Def getNewDasmObject(self, direction | addrRange, addrDesc, showAddr, newObj, virtLine, offset)
{ 
  /* Get a copy of the old addrRange then set the new range from its end offset */
  if (addrDesc := addressRange(dataObject)) then
    /* check for 0 offset - should work with all address type */
    if (direction = MOVE_RANGE_TO_LOW_ADDR) cand
        (not(offset := getOffset(AddressLibClass$Inst, addrDesc)) cor
        (asLong(offset) = 0L)) 
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil;
    endif;           
    /* DEBUG 
    printLine("Old dasm range: "+asAddrRange(AddressLibClass$Inst, addrDesc));
    */
    addrRange := max(visLines(self)*2, 128); /* bytes */
    /* Move the address range follow the specified direction */
    if not(adjustRange(AddressLibClass$Inst, addrDesc, addrRange, 0, direction)) then
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil;
    endif;
    /* DEBUG 
    printLine("New dasm range: "+asAddrRange(AddressLibClass$Inst, addrDesc));
    */ 
    /* Prepare to show at the begin/end of the old data object */     
    if not(showAddr := addressRange(dataObject)) then
      destroyAddress(AddressLibClass$Inst, addrDesc);
      ^nil;
    endif;

    /* Open a new data object with the new addrRange desc - open() consumes the <addrDesc> */
    if not(newObj := open(VirtSmrtDasmObject, addrDesc, viewAddressSize(parent))) then     
      displayFormattedError(ErrorTextLibClass$Inst, 
              ER_CANT_SCROLL, FORCE_POPUP, nil, nil, nil);
      destroyAddress(AddressLibClass$Inst, showAddr);
      ^nil;
    endif;
     
    /* Reset the browser with a new data object - reset scroll range */ 
    resetBrowser(TheSourcePresenter, newObj, #true /* saveHistory */, self);
 
    /* Adjust the show address if move foreward */
    if (direction = MOVE_RANGE_TO_HI_ADDR) then
      /* Use the end offset for the <showAddr> start offset */
      if not(offset := getEndOffset(AddressLibClass$Inst, showAddr)) cor
         not(setOffset(AddressLibClass$Inst, showAddr, offset)) then
        destroyAddress(AddressLibClass$Inst, showAddr);   
        ^nil; 
      endif;
    endif;
        
    /* map the <showAddr> to its virtual line number */
    if not(virtLine := getVirtLineNum(self, showAddr)) then
      /* Cannot get the <virtLine>, set to start line or end line */
      if (direction = MOVE_RANGE_TO_HI_ADDR)
        virtLine := 1;
      else     
        virtLine := virtualLineLimit;
      endif;      
    endif;    
    /* Always destroy the <showAddr> */
    destroyAddress(AddressLibClass$Inst, showAddr);       
    /* show the virtual line of the showAddress */
    showTokenAt(self, tuple(virtLine, 0));
    ^GOOD;
  endif;
  ^nil;    
}
!!

/* 10/15/1992 10:44 - PUBLIC (to its parent)
  Allow its parent to set a new tab width.
*/
Def setTabWidth(self, newSize)
{ 
  tabWidth := newSize;
}
!!

/* 10/13/1992 11:11 - PUBLIC (to its parent) 
  Clear the PC mark ">>" on its breakpoint region.
*/
Def clearPC(self)
{ 
  setPCLine(bkptRegion, nil); /* Get rid of the old mark */
  invalidate(bkptRegion);     /* Clear old mark */
}
!!

/* 06/20/92 - PUBLIC
   Find a string in cacheText, starting from selectLine, startChar.
   If found, scroll to it and highlight it, else return the virtual
   end line # just after where the search failed.  Return nil on success.
*/
Def findAndShowStringReverse(self, searchStr | pointPos, numLinesShown, startLine)
{
  numLinesShown := visibleLines;
  if not(cacheText) then 
    cacheHit(self, max(1, asInt(virtualStartLine+cacheStartLine)), numLinesShown);
  endif;

  /* Search the token and return its position */
  if selectLine cand (selectLine < numLinesShown) then /* select line shown */  
    startLine := asInt(max(cacheStartLine, (selectLine+cacheStartLine)));
  else
    startLine :=  asInt(cacheStartLine);
  endif;  
  /* Search reverse from startLine - Hilight the found token or return the next location */
  if not(pointPos := findStringInCacheReverse(self, startLine+1, searchStr)) then
    ^(virtualStartLine + size(cacheText)); /* search failed */
  else
    invertOff(self);
    selectLine := y(pointPos) - cacheStartLine;  /* remember where we found it */
    startChar  := x(pointPos);
    endChar    := (startChar + size(searchStr));
  endif;
  /* Highlight the token */
  doInvert(self);
  ^nil  /* found it */
}
!!

/* 8/27/1992 17:07 - PRIVATE
  Allow parent to invalidate the PC mark on the bkptRegion without invalidate self.
*/
Def updatePC(self | yPos, aStr, startLine, endLine, cloneDesc, result)
{
  /* Update the PC on the bkptRegion */ 
  clearPC(self);
  yPos := 0; 
  startLine := cacheStartLine;
  endLine   := min(size(cacheText), (cacheStartLine + 1 + visLines(self)));
  /* Make sure there is a currentExecPoint in dataObject */
  if not(currentExecPoint(dataObject)) cand
     not(getCurrentExecPoint(dataObject)) then
    ^nil; 
  endif;
  if (class(dataObject) <> VirtSmartFileObject)   
     cloneDesc := duplicateAddress(AddressLibClass$Inst, currentExecPoint(dataObject));
  endif;
      
  do(over(startLine, endLine),
    {using(line) 
      /* For VirtSmrtFileObject */ 
      if (class(dataObject) = VirtSmartFileObject) then
         if (currentExecPoint(dataObject) = (virtualStartLine + line)) then
            /* Set the new PC Line Position */
            setPCLine(bkptRegion, 
                       /* Y position in pixels (x is always 0) */
                       (yPos * tmHeight + 2));            
            ^GOOD;           
         endif;
      else 
         /* For VirtSmrtDasmObject and VirtSMixedObject */ 
         /* Get the text string */
         aStr := eval(getTxtLineRoutine, cacheText[line]);
         if ((mapLineUsingAddrDesc(dataObject, nil, aStr, cloneDesc) = GOOD) cand 
            (result := compareAddressesNoError(AddressLibClass$Inst,
                              currentExecPoint(dataObject),
                              cloneDesc)) cand (result = ADRLIB_ADDR_EQUAL)) then  
            /* Set the new PC Line Position */
            setPCLine(bkptRegion, 
                       /* Y position in pixels (x is always 0) */
                       (yPos * tmHeight + 2));
            /* destroy the unused address descriptor */                       
            destroyAddress(AddressLibClass$Inst, cloneDesc);           
            ^GOOD;  
         endif;   
      endif;    
      yPos := yPos + 1;
  });  
  /* need to clean up */
  destroyAddress(AddressLibClass$Inst, cloneDesc);
}
!!

/* 8/25/1992 16:38 - PUBLIC (to its parents) */
Def bkptRegion(self)
{ 
  ^bkptRegion;
}
!!

/* 8/25/1992 10:59 - PRIVATE
  Return a tuple of disable command according to its data object type for the
  popup menu.
*/
Def getDisableCmdSet(self, category)
{   
  if class(dataObject) <> VirtSmartFileObject then
    if category = #functionName then
      ^tuple(SRC_POPUP_BKPT_LINE_PERM, SRC_POPUP_BKPT_LINE_TEMP, SRC_POPUP_BKPT_CLEAR);
    else  
      ^tuple(SRC_POPUP_BKPT_LINE_PERM, SRC_POPUP_BKPT_LINE_TEMP);
    endif;
  endif;
  ^nil; /* nil for none */
}
!!

/* 7/30/1992 16:48 - PUBLIC (to its parent)
  Set show line number flag for self.
*/
Def showLineNum(self, newFlag, shiftHilight?)
{ 
  showLineNum := newFlag;
  /* Do not need to check for VirtSmrtDasmObject - parent object already did */
  if shiftHilight? then /* Only do this if call from cmdViewShowLinenum() */
    if (showLineNum) then
      startChar := startChar + 9;  /* Move the hilight to new postion */
      endChar := endChar + 9;
    else
      startChar  := max(0, startChar - 9);
      endChar := max(0, endChar - 9);
    endif;  
  endif;
}
!!

/* 7/28/1992 11:12 */
Def clickOnBkptRegion(self, yPos, bkptType | lineNum, dataObjLine, bkptId, selTxt )
{ 
  /* Close any opend popup menu */
  maybeClosePopup(parent);
  
  /* Compute the lineNumber */
  lineNum := asInt(yPos / tmHeight); 
  /* Map lineNum to dataObject lineNum and check in the dataObject for setting breakpoint */   
  if ((lineNum+cacheStartLine) > size(cacheText)-1) then
    /* Blank screen, there is no cacheText */
    ^nil;
  endif;
  /* 
  ** Map virtual line to data object type line - NOTES: each data object has its own
  ** lineNum interpretation.
  */
  if not(selTxt := cacheText[asInt(lineNum+cacheStartLine)]) cor
     not(dataObjLine := mapLine(dataObject, lineNum+cacheStartLine+virtualStartLine,
        selTxt)) then  
    ^nil;
  endif;
  /* If there is a breakpoint setting, clear it, else set a breakpoint */
  if not(bkptSetOrClear(dataObject, bkptType, dataObjLine, selTxt)) then
    ^nil;
  endif;  

  /* Set select line that corresponding to the clicked region */
  selectLine := lineNum;
  startChar := endChar := 0;
  /* Set the browser to be the active one */
  if (selectedWin(parent) <> self) then
    /* make self the selected browser window */
    invertOff(selectedWin(parent));
    setSelectedWin(parent, self);
    invalidate(parent);
  endif ;
}
!!

/* 7/23/1992 13:51 - PUBLIC (To Parent)
  Get the virtLine of a dataObject Line.  Assume caller has check lineNum with
  the lineOnScreen?() routine.  
  Return a virtual line of the questioned <lineOrAddr> or 1 if error.
  NOTES: 
    For VirtSmrtDasmObject/VirtSMixedObject
       lineOrAddr = address descriptor
       This method does not destroy the <lineOrAddr>.     
    For VirtSmartFileObject
       lineOrAddr = line number
*/
Def getVirtLineNum(self, lineOrAddr | startLine, newVirtLine, lineInfo) 
{ 
  /* return first line for all dataObject type */
  if not(lineOrAddr) 
    ^1;
  endif;      

  select
    case (class(dataObject) = VirtSmartFileObject)  
      ^lineOrAddr; /* return line # */   
    endCase;
    
    case (class(dataObject) = VirtSMixedObject)
       lineInfo := addr2lineAndModule(SymbolLibClass$Inst, lineOrAddr);
       if not(lineInfo) then
          /* NOTES: lineInfo = #(moduleDesc, moduleAddrDesc, lineNum, colNum, 
          **                     lineAddrDesc, lineDesc) 
          */
         ^1;          
       else
         /* destroy the unused address descriptor */
         destroyAddress(AddressLibClass$Inst, lineInfo[1]);
         destroyAddress(AddressLibClass$Inst, lineInfo[4]);
         startLine := lineInfo[2];   
       endif;  
    endCase;

    case (class(dataObject) = VirtSmrtDasmObject)       
       startLine := max(cacheStartLine, 1);         
    endCase;
  endSelect;
  
  /* If the dataObject is closed then need to reopen the dataObject */
  if closed?(dataObject) then
    reOpen(dataObject, viewAddressSize(parent));
  else
    /* 06/09/94 - Flush the cache to get the new text */
    flushCache(self);      
  endif ;

  /* Look up for the address from startLine - lineOrAddr is <addrDesc> */ 
  if not(startLine) cor
     not(newVirtLine := searchAddress(dataObject, lineOrAddr, startLine)) then
    /* Can not find it? - return the last line */
    ^min((cacheStartLine + visibleLines), virtualLineLimit);         
  endif; 
  ^newVirtLine;      
}
!!

/* 5/15/1992 15:17 - PUBLIC (to Parent Object)
  Return #true if the line is on Source browser screen, else nil. 
*/
Def lineOnScreen?(self, lineOrAddr | viewLines, topLine)
{ 
  /* Get the total viewable line and top line of source */  
  viewLines := visibleLines;
  if not(cacheText) cor (size(cacheText) = 0) cor (viewLines = 0) then
    ^nil;
  endif;
  select 
    case class(dataObject) = VirtSMixedObject is
      /* lineOrAddr = AddressDescriptor */
      ^mixedOffsetOnScreen?(self, lineOrAddr, asInt(cacheStartLine),
                            asInt(min(size(cacheText)-1, 
                            cacheStartLine+viewLines-1)));      
    endCase  
    case class(dataObject) = VirtSmrtDasmObject is
      /* lineOrAddr = AddressDescriptor */
      ^dasmOffsetOnScreen?(self, lineOrAddr, asInt(cacheStartLine),
                           asInt(min(size(cacheText)-1, 
                           cacheStartLine+viewLines-1)));
    endCase
  endSelect;
  
  /* Default to VirtSmartFileObject - virtSelectLine */
  if not(cacheText) cor (size(cacheText) = 0) then
    ^nil;
  endif;
  topLine := asLong(virtualStartLine + cacheStartLine);
  ^((lineOrAddr >= topLine) cand (lineOrAddr < topLine+viewLines));
}
!!

/* 5/15/1992 10:27 - PRIVATE 
  Return #true if <addrDesc> is on screen, nil otherwise.
  NOTES: Only used with dataObject = VirtSmrtDasmObject.
    - Caller has ownership of the input <addrDesc>.
    - View of Browser's data buffer.  
    
      +---------+   <- VirtualStartLine
      |  chunk  |
     >-----------<  +    CacheStartLine (topLine)
      +---------+   |
      |  chunk  |   |
      +---------+   | <- Screen Viewport 
      +---------+   | 
     >-----------<  +                   (bottomLine)     
      |  chunk  |
      +---------+  
*/
Def dasmOffsetOnScreen?(self, addrDesc, topLine, bottomLine | 
    topDesc, bottomDesc, temp, temp2)
{
  /* map both topLine and bottomLine to address descriptor */
  if not(cacheText) cor (size(cacheText) = 0) cor
     not(topDesc := mapLine(dataObject, nil, cacheText[topLine])) then
    ^nil;
  endif;
  
  if not(bottomDesc := mapLine(dataObject, nil, cacheText[bottomLine])) then
    destroyAddress(AddressLibClass$Inst, topDesc);
    ^nil;
  endif;  
  /* DEBUG 
  printLine("on Screen: "+asAddr(AddressLibClass$Inst, addrDesc));
  */ 
  /* Compare the input <addrDesc> between the TopDesc and BottomDesc */
  temp := compareAddressesNoError(AddressLibClass$Inst, addrDesc, topDesc);
  temp2 := compareAddressesNoError(AddressLibClass$Inst, addrDesc, bottomDesc);
  
  /* Get rid of the unused descriptors */
  destroyAddress(AddressLibClass$Inst, topDesc);
  destroyAddress(AddressLibClass$Inst, bottomDesc);
  if not(temp) cor not(temp2) then
    ^nil;
  endif;
  
  /* addrDesc >= topDesc && addrDesc <= bottomDesc */
  ^(((temp = ADRLIB_ADDR_GREATER_THAN) cor (temp = ADRLIB_ADDR_EQUAL)) 
    cand ((temp2 = ADRLIB_ADDR_LESS_THAN) cor (temp2 = ADRLIB_ADDR_EQUAL)));    
}
!!

/* 06/20/92 - PRIVATE
  Set scroll bar position, avoiding divide by 0. 
*/
Def setVScrollPos(self | linesShowing, virtFirstLine, perCent, adjustment, scrollPos)
{
  scrollPos     := 0 ;
  linesShowing  := visibleLines;
  virtFirstLine := (virtualStartLine + cacheStartLine - 1);
  /* Calculate the new scrolling position */
  if (virtualLineLimit cand virtualStartLine cand (virtualLineLimit > 0)) then
    perCent := ((100 * virtFirstLine) / max(1,(virtualLineLimit - linesShowing)) );
    adjustment := asLong((perCent * linesShowing) / 100);
    scrollPos  := asLong( (100 * (virtFirstLine + adjustment)) 
                         / virtualLineLimit );
  endif;
  Call SetScrollPos(hWnd,       /* Our cachingBrowser window */
                    SB_VERT,    /* What bar to redraw (only 1) */
                    scrollPos,  /* New Elevator position */ 
                    1) ;        /* do the redraw (why else would we call?) */
  ^scrollPos;                  
}!!

/* 6/24/1992 17:08 - PRIVATE
  Set Horizontal postion of the HScroll Bar. Return the current position
*/
Def setHScrollPos(self | scrollPos)
{ 
  scrollPos := 0;
  if cacheText then
    scrollPos := (100 * startCol) / max(1, 256); /* Maximum col @ 255 */
  endif;  
  Call SetScrollPos(hWnd,       /* Our cachingBrowser window */
                    SB_HORZ,    /* What bar to redraw */
                    scrollPos,  /* New Elevator position */ 
                    1) ;        /* do the redraw (why else would we call?) */  
  ^scrollPos;                    
}
!!

/* 7/14/1992 16:01 - Actor
  Processing the show message
*/
Def show(self, displayMode)
{ 
  show(self:ancestor, displayMode);
  show(bkptRegion, SW_SHOWNA);
}
!!

/* 7/14/1992 15:56 - Actor 
  Process resize message
*/
Def reSize(self, wp, lp | cr)
{
  reSize(self:ancestor, wp, lp);
  if bkptRegion then 
    cr := clientRect(self);
    setCRect(bkptRegion, rect(left(cr), 0, xStart, bottom(cr))); 
    moveWindow(bkptRegion);  
  endif;  
}
!!

/* 7/1/1992 9:55 - PRIVATE
   Draw special line: specialTag is one of
      AT_BREAK_ACT_FIELD, AT_BREAK_INACT_FIELD, AT_BREAK_HIT_FIELD,
      AT_CURSOR_ACT_FIELD, AT_CURSOR_INACT_FIELD,
      AT_PC_HERE_FIELD , AT_SELECTED_FIELD-- see ATParser class code
   WHERE: 
    NOTES: lineInfo = #( textStr, YPos-in-pixels, lineWidth-in-chars )
           text String is pre-trim off line number portion.
*/
Def drawSpecial(self, specialTag, lineNum, lineInfo, hDC
                 | origBkColor, expandedText, wRect)
{
  /* Draw highlight as colored background with text over */
  wRect := clientRect(self);
  origBkColor := Call GetBkColor(hDC);

  /* Expanding the text string - TAB char - Adjust for the lineNumber display */
  expandedText := expandTabs(lineInfo[0], tabWidth);
  expandedText := subString(expandedText, startCol, size(expandedText)); 

  /* Set special background color */
  Call SetBkColor(hDC, $fieldColorDict(ATParser)[specialTag]);
  /* Set the rectangle area */  
  setLeft(wRect, xStart);  
  setTop(wRect, lineInfo[1] + tmHeight);
  setBottom(wRect, lineInfo[1]);
  
  Call ExtTextOut(hDC, xStart, lineInfo[1], ETO_OPAQUE /* 2 */, wRect, 
        expandedText, size(expandedText),  0);
  /* Restore background color */
  Call SetBkColor(hDC, origBkColor);
}
 
!!

/* 7/1/1992 9:54 - PRIVATE
   Draw a regular text line - pre-trim off line number portion.
*/
Def drawRegular(self, lineNum, lineInfo, hDC | expandedText)
{
  /* NOTES: lineInfo = #(textStr, yPos, xMax) */
  expandedText := expandTabs(lineInfo[0], tabWidth);
  expandedText := subString(expandedText, startCol, size(expandedText));   
  Call TextOut(hDC, xStart, lineInfo[1], expandedText, size(expandedText));
}
!!

/* 7/1/1992 9:48 - PRIVATE
   Check to see if this line is a special line. Return nil or one of the
   ATParser field codes:
      AT_BREAK_ACT_FIELD, AT_BREAK_INACT_FIELD, AT_BREAK_HIT_FIELD,
      AT_CURSOR_ACT_FIELD, AT_CURSOR_INACT_FIELD,
   Except:   
     if AT_PC_HERE_FIELD then draw the PC mark in the Breakpoint region 
     and continue to check for breakpoint on the line.
   Example: speciaLine?(browser, 1, #("c = a+b;", 23, 70), <virtualLine#>);
   NOTES:
      drawAddrDesc is an instance address descriptor of the SourceCB object.
      The object itself will allocate and destroy the <drawAddrDesc>. 
*/
Def specialLine?(self, lineNum, lineInfo | result)
{ 
  /* Current selected window - Remap lineNum to data object line type */
  if dataObject then
    if (class(dataObject) == VirtSmartFileObject) then
       if currentExecPoint(dataObject) cand 
          (lineNum = currentExecPoint(dataObject)) then
         /* Draw PC mark on the left region  then draw the text regularly */
         setPCLine(bkptRegion, lineInfo[1]);
       endif;
         
       if cursorLinked(dataObject) cand 
          (lineNum = cursorLinked(dataObject)) then
         ^AT_PC_HERE_FIELD;
       endif;
       
       /* Anything else is checked for Breakpoints setting */ 
       ^findSpecialLine(dataObject, lineNum);
    else
      /* VirtSmrtDasmObject and VirtSMixedObject */
      if ((drawAddrDesc) cand 
         /* For VirtSMixedObject, <nil> to prevent mapping lineNumber to address */ 
         (mapLineUsingAddrDesc(dataObject, nil, lineInfo[0], drawAddrDesc))) then 
        /* Check for current Execution point - PC - draw it */
        if currentExecPoint(dataObject) then
          result := compareAddressesNoError(AddressLibClass$Inst, drawAddrDesc, 
                                            currentExecPoint(dataObject));
          if result cand (result = ADRLIB_ADDR_EQUAL) then
            /* Draw PC mark on the left region  then draw the text regularly */
            setPCLine(bkptRegion, lineInfo[1]);
          endif;  
        endif;
       
        /* Check for current cursor linked */ 
        if cursorLinked(dataObject) then
          result := comparePhysicalAddressesNoError(AddressLibClass$Inst, 
            drawAddrDesc, cursorLinked(dataObject));
          if result cand (result = ADRLIB_ADDR_EQUAL) then
            ^AT_PC_HERE_FIELD;
          endif;  
        endif; 
       
        /* Anything else is checked for Breakpoints setting */ 
        ^findSpecialLine(dataObject, drawAddrDesc);
      endif;    
    endif;
  endif;  
  ^nil  /* the default is non-special */
}
 
!!

/* 7/1/1992 9:44 - PRIVATE 
  Handle text drawing for the browser.
*/
Def drawRoutine(self, lineNum, lineInfo, hDC | specialTag)
{
  /* Draw a line of text according to its type */
  if (specialTag := specialLine?(self, lineNum, lineInfo)) then
    drawSpecial(self, specialTag, lineNum, lineInfo, hDC);
  else
    drawRegular(self, lineNum, lineInfo, hDC);
  endif ;
}
!!

/* 6/30/1992 15:20 - PRIVATE (WM_VSCROLL only) */
Def doSelLineInvert(self, vSelLine, inverted?, setFlag)
{ 
  setSelectFromVirt(self, vSelLine);
  if (inverted? cand (endChar <> 0)) then
    if setFlag then
      doInvert(self);  
    else
      invertOn? := #true; 
    endif;   
  endif; 
}
!!

/* 7/2/1992 12:08 - WINDOWS (PUBLIC) 
   Handle Horizontal Scroll Bar Mesages...
    wp = LINEUP/DOWN   - scroll startCol 1  position.
    wp = PAGEUP/DOWN   - scroll startCol 10 position.
    wp = THUMBPOSITION - calculate startCol from thumb position.
*/
Def WM_HSCROLL(self, wp, lp)
{
  /* Close any popup if opened */
  maybeClosePopup(parent);
  
  /* NOTES: wp = Scroll code, lp = hBarPos */
  select
    case (wp == SB_THUMBTRACK) cor (wp == SB_ENDSCROLL)
      /* reject useless scroll request */
      ^0;
    endCase
    case (wp == SB_LINEDOWN)
      if startCol >= 255
        startCol := 255;
        ^0;  /* reject useless scroll */
      else
        startCol := startCol + 1;
      endif;
    endCase
    case (wp == SB_PAGEDOWN)
      startCol := min(startCol + 10, 255);
    endCase
    case (wp == SB_LINEUP)
      if startCol <= 0
        startCol := 0;
        ^0;  /* reject useless scroll */
      else
        startCol := startCol - 1;
      endif;
    endCase
    case (wp == SB_PAGEUP)
      startCol := max(startCol - 10, 0); 
    endCase
    case (wp == SB_TOP)
      startCol :=  0;
    endCase
    case (wp == SB_BOTTOM)
      startCol := 255;
    endCase  
    case (wp == SB_THUMBPOSITION) 
      startCol := asInt((255 * low(lp)) / 100); /* high(lp) is handle */
      ^invalidate(self);
      /* don't need to do setHScrollPos() */
    endCase
  endSelect; 
  setHScrollPos(self);
  invalidate(self);
}
!!

/* SOURCECB DOCUMENTATION */
Def aREAD_ME(self)
{
/*
  There are 3 frames of reference which are related via line number:
   [1] file line # (virtual object; from 1): e.g.: virtualStartLine
   [2] cacheText relative (from 0): e.g.: cacheStartLine
   [3] viewport relative (from 0: e.g.: selectLine
   
  These are related as:
    file line # of selectLine is 
        (selectLine + cacheStartLine + virtualStartLine)
        
  Example: say 
    selectLine is 1, virtualStartLine is 9, cacheStartLine is 4
    
    Then the 1st viewport line is cacheStartLine=4 is file line 12
    and selectLine is cacheText line 5 and file line 14.

  ===
  
  There are 2 frames of reference for character position within a line:
  tabs expanded and unexpanded.  See String:expandTabs, expandTabsMap,
  and expandTabsInverseMap.  Text in the cacheText text collection are
  kept in unexpanded form and expanded opon output.
  
  In general, all bookkeeping is done on unexpanded text (e.g. startChar)
  and conversion is performed `at the user interface'.
  
*/
}
!!

/* 06/20/92 - PUBLIC 
  Close self and its data object.
*/
Def close(self)
{ 
  maybeClosePopup(parent); /*  Close Popup, if it opened */
  close(dataObject);
  if (drawAddrDesc)
     destroyAddress(AddressLibClass$Inst, drawAddrDesc);
     drawAddrDesc := nil;
  endif;    
  ^close(self:ancestor);
}
!!

/* 06/20/92 - PUBLIC 
  Return the current dataObject of self.
*/
Def dataObject(self)
{ 
  ^dataObject;
}
!!

/* 06/20/92 - PRIVATE
   Toggle inverted region and invertOn? flag
*/
Def doInvert(self | drawingContext)
{ 
  drawingContext := getContext(self);
  invertTok(self, drawingContext);
  releaseContext(self, drawingContext);
  ^(invertOn? := not(invertOn?)) 
}
!!

/* 06/20/92 - PUBLIC
   Find a string in cacheText, starting from selectLine, startChar.
   If found, scroll to it and highlight it, else return the virtual
   end line # just after where the search failed.  Return nil on success.
   NOTES: Caller is cmdPopFuncSource().
*/
Def findAndShowString(self, searchStr | pointPos, numLinesShown, virtLine)
{
  if not(cacheText) then 
    ^virtualStartLine   /* failed, so start from line 1 */
  endif ;

  numLinesShown := visibleLines;
  if selectLine cand (selectLine < numLinesShown) then /* select line shown */
    pointPos := findStringInCache(self, searchStr, 
                 asInt(max(cacheStartLine, (selectLine+cacheStartLine))), endChar);
  else
    pointPos := findStringInCache(self, searchStr, cacheStartLine, 0 ) ;
  endif ;
  /* Hilight the found token or return the next location */
  if not(pointPos) then
    ^(virtualStartLine + size(cacheText)); /* search failed */
  else
    invertOff( self ) ;
    selectLine := y(pointPos) - cacheStartLine ;  /* remember where we found it */
    virtLine   := y(pointPos) + virtualStartLine ; 
    startChar  := x(pointPos) ;
    endChar    := (startChar + size(searchStr)) ;
    /* line past center of window? */
    if ((numLinesShown / 2) < selectLine) then /* scroll to show it, centered */
      cacheHit(self, 
               max(1, asInt(virtLine - (numLinesShown/2))), 
               numLinesShown ) ;
      /* cacheHit can cause line renumbering.  Update lines we need. */
      virtLine := getLineAdjustment(self, virtLine);
      selectLine := (virtLine - virtualStartLine) - cacheStartLine ;
      invalidate(self);
    endif ;
  endif ;

  /* highlight the token */
  doInvert(self);
  ^nil  /* found it */
}
!!

/* 06/20/90 - PRIVATE
  Return the start and end chars in selectLine that comprise the 
  given token. Return as a point, in which x is start and y is finish.
  Assumes called after setCurPos(self, mousePoint).  Deals only with 
  input (storage) coordinates. 
    NOTES: 
    Senders are WM_LBUTTONDOWN and WM_RBUTTONDOWN.
*/
Def findRangeToken(self| textStr, tokInfo)
{ 
  /* Get the text string of cached text */
  if not(cacheText) cor (size(cacheText) = 0) cor 
    not(textStr  := cacheText[asInt(selectLine + cacheStartLine)]) cor
    not(startChar) cor (size(textStr) = 0) then
    startChar := endChar := 0; 
    ^point(2, 0)
  endif;
  /* Trim off line Number portion of string before parse -
  ** Do not apply to VirtSmrtDasmObject type
  */
  if (class(dataObject) <> VirtSmrtDasmObject) cand not(showLineNum) then
    textStr := subString(textStr, 9, size(textStr));
  endif;

  /* IMPORTANT: Begin parsing the token here - Call CString class methods */
  if (size(textStr) > 0) then
    setTokenInfo( self, 
                  eval(languageDict[ #tokenParseFunction],
                        textStr,
                        startChar));
  endif ;
  ^point(startChar, endChar); /* internal coords */
}!!

/* 06/20/92 - PRIVATE
  Return the start and end chars in selectLine that comprise the 
  given token. Return x@y as a point, in which x is start and y is finish.
  Assumes called after setCurPos(self, mousePoint). 
*/
Def findToken( self | left, right, line, tokenCharSet, maxChar, txtStr)
{
  line := asInt(min((selectLine + cacheStartLine), size(cacheText)-1));
  if not(cacheText) cor not(txtStr := cacheText[line]) then
    ^point(0,0);
  endif;
  
  txtStr := expandTabs(txtStr, tabWidth);  
  if not(showLineNum) then 
    txtStr := subString(txtStr, 9, size(txtStr));
  endif;
    
  maxChar := max(0, size(txtStr)-1);
  /* Make sure that left and right positions are within the textCollection */
  if (startChar > maxChar) then 
    startChar := endChar := maxChar; 
  endif;
  left := right := startChar;
  tokenCharSet := languageDict[#tokenCharSet] ;
  /* Find the token */
  if (size(tokenCharSet) > 0) then 
    loop
    while (left > 0) cand (txtStr[left-1] in tokenCharSet)
    begin 
      left := left - 1;
    endLoop;
  
    loop
    while (right < maxChar) cand (txtStr[right] in tokenCharSet)
    begin 
      right := right + 1;
    endLoop;
  endif;
  ^point(left, right)   /* internal coords */
}!!

/* 06/20/92 - PRIVATE 
   Return leading (or entire) token text.
   [E.g. if tokenInfo[0] = "a.b.c->x", return "a"]
*/
Def getLeadingToken(self | tokStr, endPos, maxPos, legalChars)
{
  if not(tokenInfo) then
    ^"";
  endif ;

  tokStr := tokenInfo[0] ;
  if ( (maxPos := size(tokStr)) = 0 ) then
    ^"";
  endif ;

  /* This test for valid legal chars set */
  if (size(legalChars := languageDict[#tokenCharSet]) = 0) then
    ^"";
  endif;

  endPos := 0 ;
  loop while (endPos < maxPos) cand (tokStr[endPos] in legalChars)
  begin
    endPos := (endPos + 1)
  endLoop ;

  ^subString( tokStr, 0, endPos )
}
!!

/* 06/20/92 - PRIVATE 
   Return token text as a string: startChar through endChar on selectLine.
*/
Def getToken(self) /* textCollLine) */
{
  if tokenInfo then
    ^tokenInfo[0];
  endif;
  ^"";
}
!!

/* 06/20/92 - PRIVATE 
   Given a non-null token, return its category:
     #variableName
     #functionName
     #publicLabel
     #typeName
     #other
     #unknown
*/
Def getTokenCategory(self, token | blockDesc symInfo, virtLineNum, tokenNum,
   symDesc)
{
  select
    case class(dataObject) == VirtSmartFileObject 
      virtLineNum := asLong(selectLine + cacheStartLine + virtualStartLine);
    endCase
    case class(dataObject) == VirtSMixedObject
      /* 11/1/94 - Nghia - Support inspecting symbol in mixed mode */
      if not(cacheText[asInt(selectLine+cacheStartLine)]) cor 
      not(tokenNum := findStrings(cacheText[asInt(selectLine+cacheStartLine)],
                        "[]")[0]) cor
      not(virtLineNum := asInt(tokenNum, 10)) then
        ^#unknown;
      endif;
    endCase
    default
      /* VirtSmrtDasmObject - Should not have any category */      
      ^#unknown;
  endSelect;    

  if not(moduleDescriptor(dataObject)) then
     ^#unknown;
  endif;
  blockDesc := primeLine2blockDescriptor(SymbolLibClass$Inst, 
      moduleDescriptor(dataObject), virtLineNum);
  /*Hera 5/26/97*/
  if(blockDesc = nil) then
     blockDesc := moduleDescriptor(dataObject);
  endif;
  /* normal case, local symbol in module */
  if (blockDesc) then
     /* NOTES: symInfo = #(symbolDesciptor, type, isGlobal? ) */
     if symInfo := primeSymbolFromContext(SymbolLibClass$Inst, 
                              blockDesc, token) then
        if symInfo[1] <> #other then
           if tokenInfo then 
              tokenInfo[4] := symInfo[0]  /* Save the symbol descriptor */
           endif;
           ^symInfo[1]; /* return the symbol type to open popup */
        endif;
     else
       ^#unknown
     endif ;
  endif;
  /* support data modules, which don't have line numbers */
  /* Note that data modules consist solely of data declarations, so we
     know the type to return must be #variableName type.
   */
  if not(token) cor 
     not(symDesc := varNameToSymDesc(SymbolLibClass$Inst, token)) then
     ^#unknown;
  endif;
  tokenInfo[4] := symDesc;  /* Save the symbol descriptor */
  ^#variableName;
}
!!

/* 06/20/92 - PRIVATE 
   Return token text as a string: startChar through endChar on selectLine
*/
Def getTokenInfo(self)
{
  ^tokenInfo
}
!!

/* 06/20/92 - PRIVATE 
   Return token offset in selectLine.
*/
Def getTokenOffset(self) /* textCollLine) */
{
  if tokenInfo then
    ^tokenInfo[1]
  else
    ^0
  endif ;
}
!!

/* 06/20/92 - PRIVATE 
   Return token symbol descriptor or nil
*/
Def getTokenSymbol(self)
{
  if not(tokenInfo) then
    ^nil
  endif;
  ^tokenInfo[4];
}
!!

/* 06/20/92 - PRIVATE
  Initialize a sourceCB instance.
*/
Def init(self, virtualDataObject, totalLines, firstLine | cr)
{ 
  /* Use its ancestor to initialize */
  init(self:ancestor, virtualDataObject, totalLines, firstLine);
  /* Set draw routine for ancestor: CACHINGBROWSER */
  setDrawRoutine(self:ancestor,
    {using(lineNum, lineInfo, hDC | specialTag)
       drawRoutine(self, lineNum, lineInfo, hDC);  /* draw text engine */
    });

  setLanguageParser(self);
  selectLine := startChar := endChar := 0;
  showLineNum := nil;
  xStart := tmWidth * 2;        /* Offset of the client Rect and BkptRegion */
  tabWidth := tabWidth(parent); /* Get tab width from Source Presenter */
  drawAddrDesc := nil; 
  /* set the new drawAddrDesc - enable drawing */ 
  setNewDrawAddrDesc(self, addressRange(virtualDataObject)); 
  /* Create the breakpoint region */
  cr := clientRect(self);
  bkptRegion := new(BkptRegion, self, nil, "Bkpt Area", 
    rect(left(cr), 0, xStart, bottom(cr)));
}!!

/* 06/20/92 - PRIVATE
   If invertOn? then turn it off.
   Tell the caller whether invert was originally on.
*/
Def invertOff(self | wasInverted?)
{
  if (wasInverted? := invertOn?) then
     doInvert( self ) ;   /* toggles invertOn? to nil */
  endif ;
  ^wasInverted?
}
!!

/* 06/20/92 - PRIVATE 
   Use selectLine, startChar, and endChar as a region definition to
   invert.  If startChar equals endChar, draw a 2-pixel cursor.
   NOTES: Does NOT touch invertOn? flag.
*/
Def invertTok( self, dragDC | pt, width)
{

  /* Get the x,y position of selected token - x@y */
  if not(pt := pointFromStartChar(self)) then
    ^nil;  /* returns output coords */
  endif;
  width := (endChar - startChar);
  if (width = 0) then
    width := 2 ;
  else 
    width := (width * tmWidth) ;
  endif ;
  /* Do Bit-blit the region */
  Call PatBlt(dragDC, 
              x(pt), 
              y(pt)+2,
              width,         /* width */
              tmHeight+1,    /* height */
              DSTINVERT) ;
}!!

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

/* 06/20/92 - Actor 
  Repaint the browsing text.
*/
Def paint(self, hdc | cr)
{
  /* Move the bkptRegion to the new location */
  if bkptRegion then
    cr := clientRect(self);
    setCRect(bkptRegion, rect(left(cr), 0, xStart, bottom(cr))); 
    moveWindow(bkptRegion); 
  endif;   
  /* Ancestor takes care all the work */
  paint(self:ancestor, hdc);
  if invertOn? then /* put it back */
    invertTok(self, hdc) ; /* NOTES: don't change invertOn? flag */
  endif;
  /* Force updating the PCLine in the bkptRegion - A must do */
  invalidate(bkptRegion);
}
!!

/* 06/20/92 - PRIVATE 
   Calculate point from startChar on selectLine.
   Convert from input to output char position; point is in output coords.
   NOTES: 
    - StartLine is based on lines shown on screen, not cacheText, position.
    - This routine set the hilight position - setCursor() set the text corrdinates.
      findRangeToken() parses for text token.  These three routine must use text expansion
      for text-screen coordinates translation.
*/
Def pointFromStartChar(self | textMap, col, yPos, offset, p, selStr)
{
  /* Check for selected line */
  if ((yPos := (selectLine * tmHeight))  < 0) cor
    not(selStr := selectedLineText(self)) then
    ^nil;
  endif;

  /* 11/1/94 - Remove setting hilighting the whole line for Dasm objects */

  /* Adjust offset for showing lineNumber */
  if (class(dataObject) == VirtSmrtDasmObject) cor showLineNum then
    offset := startCol ;
  else
    offset := (startCol + 9) ;
  endif ;

  /* IMPORTANT:
  ** Create a tab map of the selStr string to be used in token parsing
  */

  /* Map output to input position */
  if (class(dataObject) <> VirtSmrtDasmObject) cand not(showLineNum) then
    selStr := subString(selStr, 9, size(selStr));
  endif;

  /* Tab expansion for the string after trim off the linenumber */
  textMap := expandTabsMap(selStr, tabWidth);

  /* Map out the x@y of startChar */
  if (size(textMap) > 0) then
    /* For a regular text token */
    /* DEBUG - printLine("offset:"+asString(offset)); */
    if (class(dataObject) = VirtSmrtDasmObject) cor showLineNum then
      col := textMap[min(startChar, size(textMap)-1)] - offset + 2;
      /* DEBUG
      printLine("Out char1: "+asString(startChar)+"@"+asString(endChar)+
        " - "+asString(col-2));
      */
    else
      col := textMap[ min(startChar, size(textMap)-1) ] - startCol + 2;
      /* DEBUG
      printLine("Out char2: "+asString(startChar)+"@"+asString(endChar)+
        " - "+asString(col-2));
      */
    endif;
    /* DEBUG - printMap(self, textMap); */
  else
    if showLineNum then
      col := 11;
    else
      col := 2;  /* null text string */
    endif;
  endif ;
  ^p := point( (col * tmWidth), yPos);
}
!!

/* 06/20/92 - PRIVATE 
  Return the text of selected line or null string. 
*/
Def selectedLineText(self | selLineNum)
{
  selLineNum := asInt(min((selectLine + cacheStartLine), size(cacheText)-1));
  if (selLineNum) cand ((selLineNum <= size(cacheText)-1) cand (selLineNum >= 0)) then
    ^cacheText[selLineNum];
  else
    ^""
  endif;
}
!!

/* 3/25/1992 12:56 - PUBLIC 
  return the current select line of the browser.
*/
Def selectLine(self)
{ 
  ^selectLine; 
}
!!

/* 06/20/92 - PRIVATE
  Set cursor position (startChar, selectLine) according to the
  specified point.  SelectLine is Window relative (i.e. from StartLine).
  Set as per unexpanded text.  Assumed set from mouse coordinates.
  NOTES: This routine isthe input setting for the pointFromStartChar().
*/
Def setCurPos(self, point 
    | selStr, relativeLine, outCharPos, textMap, textSize, offset)
{
  if not(cacheText) cor (size(cacheText) = 0)
    ^nil;
  endif;
  if invertOn? then
    doInvert( self ) ; /* turn invert off before reset */
  endif ;

  /* offset the horizontal scroll */
  if showLineNum then 
    offset := startCol ;
  else 
    offset := (startCol + 9) ;
  endif ;

  relativeLine := (max( 0, y(point)-2 ) / tmHeight);
  selectLine := asInt(
                min(((size(cacheText)-1) - cacheStartLine), /* last line */
                    relativeLine
                ));
  /* Map output to input position */
  selStr := selectedLineText(self);
  /* 11/1/94 - Nghia - Support inspecting symbol in Mixed mode */
  /* Chop off the lineNumber portion of the original text string */
  if (class(dataObject) <> VirtSmrtDasmObject) cand not(showLineNum) then
    selStr := subString(selStr, 9, size(selStr));
  endif;
  /* DEBUG - printLine(selStr); */
  textMap := expandTabsInverseMap(selStr, tabWidth);

  /* Check for blank text - Set cursor at 0 for blank line */
  if size(textMap) = 0 then 
     ^startChar := endChar := 0;
  endif;

  /* Map the client screeen coordinates to char coordinates */
  outCharPos := (x(point) / tmWidth) - 2;  /* offset the bkpt region */
  textSize := size(textMap);
  startChar := endChar := 0; 
  /* Map the startChar to it internal char position */
  if showLineNum then
    startChar := textMap[min(outCharPos+offset, textSize - 1)];  
    /* DEBUG 
    printLine("In char1 :"+asString(startChar)+"@"+asString(endChar)+
    " - "+asString(outCharPos+offset));
    */
  else
    startChar := textMap[min(outCharPos+startCol, textSize - 1)];  
    /* DEBUG 
    printLine("In char2 :"+asString(startChar)+"@"+asString(endChar)+
    " - "+asString(outCharPos+startCol));
    */
  endif; 
  /* DEBUG 
  printMap(self, textMap);
  */
}!!

/* 06/20/92 - PUBLIC
   Reset self from pop'ed (historical) VirtSmartDataObject.
   Caller takes care of close, etc. of previous dataObject.
*/
Def setDataObject(self, newDataObj, tokenLoc)
{
  dataObject := newDataObj;
  /* set the new drawAddrDesc - enable drawing */ 
  setNewDrawAddrDesc(self, addressRange(dataObject)); 
  flushCache(self);
  selectLine := startChar := endChar := 0;
  startCol := 0;   /* Column 0 to 255 */  
  virtualLineLimit := numLinesIn(newDataObj);
  /* 11/1/94 - Used the same language parser for all data object types */
  setPCLine(bkptRegion, nil); /* Clear out the old PC mark */
  if tokenLoc then
    showTokenAt(self, tuple(tokenLoc[0], tokenLoc[1]));
  else
    invalidate(self);
  endif;
}
!!

/* 06/20/92 - PUBLIC 
  Set language name of the browser.
  11/1/94 - Obsoleted - Only one language parser is used for all object types.
*/
Def setLanguageName(self, aName)
{ 
  /* accept string or symbol if needed ..cast to symbol */
  if aName then
    languageName := asSymbol(aName); 
  else
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_INVALID_LANGUAGE, FORCE_POPUP, nil, nil, nil);
    languageName := #Unknown;
    setLanguageName( dataObject, languageName );
  endif;    
  
  /* Set Browser language Dictionary */
  if not(languageDict := at(AppDictionary[#LanguageDict], languageName )) then 
    languageName := #Unknown ;    
    languageDict := at( AppDictionary[#LanguageDict], #Unknown );
  endif ;
 
  ^languageName     /* caller can check this for error */                  
}
!!

/* 06/20/92 - PRIVATE
   vSelLine is (selectLine + cacheStartLine + virtualStartLine).
   ViewPort has probably changed, so recover selectLine.
   NOTES: Do NOT touch inverted region.
*/
Def setSelectFromVirt(self, vSelLine)
{
  selectLine := vSelLine - (cacheStartLine + virtualStartLine) ;
  if (selectLine < 0) or (selectLine > visibleLines-1) then
    selectLine := startChar := endChar := 0 ;
  endif;
}
!!

/* 06/20/92 - PRIVATE
  TokenInfo may be nil 
*/
Def setTokenInfo(self, tokInfo)
{
  /* tokenInfo = #(tokStr, chrPos, textStartPos, textEndPos, nil) */
  tokenInfo := tokInfo ;
  /* set highlight info */
  if tokenInfo then
    startChar := tokenInfo[2];
    endChar   := tokenInfo[3];
  endif ;
}
!!

/* 06/20/92 - PUBLIC
   Goto line and show token.  lineAndCol is #( line, col )
   Check that we don't go off the end of the data object.
*/
Def showTokenAt(self, lineAndCol 
 | linesShown, lineToShow, rangePt, notDone, oldLine)
{
  clearPC(self);
  invertOff(self);
  linesShown := visibleLines;
  if (lineAndCol[0] >= virtualLineLimit) then
    lineToShow := max(1, (lineAndCol[0] - linesShown+1));
  else
    lineToShow := max(1, (lineAndCol[0] - (linesShown/2)));
  endif;
  notDone := nil;
  oldLine := lineToShow;
  /* Get text from data object */
  loop
    if (notDone := (cacheHit(self, lineToShow, linesShown) <> BOOL_TRUE)) cor
       (size(cacheText) = 0)
      if not(lineToShow := totalLineExact(dataObject))
        /* Cannot get cache text and did not reach the end of data? */
        ^nil;
      endif;
      /* adjust lineToShow */
      oldLine := lineToShow;
      lineAndCol[0] := max(lineToShow, virtualLineLimit);
      lineToShow := max(1, (lineAndCol[0] - linesShown+1));
    endif;
  while ((notDone) cor not(cacheValid)) cand (lineToShow <> oldLine)
  begin
  endLoop;

  /* cacheHit can cause line renumbering.  Update lines we need. */
  lineAndCol[0] := getLineAdjustment(self, lineAndCol[0]);
  
  /* Set the select Line to be shown */
  selectLine := (lineAndCol[0] - (virtualStartLine+cacheStartLine));
  if (selectLine < 0) then selectLine := 0 endif;
  startChar := endChar := lineAndCol[1];
  rangePt   := findToken(self);
  startChar := x(rangePt);
  endChar   := y(rangePt);
  doInvert(self); /* Invert then paint will hilight again */
  /* Check for the end of virtualLineLimit */
  if (lineAndCol[0] >= virtualLineLimit) then
    /* Reset to max virttualLineLimit */
    virtualStartLine :=max(1, (virtualLineLimit - linesShown +1));
    /* Let get the text */  
    if (cacheHit(self, virtualStartLine, linesShown) <> BOOL_TRUE) cor
       not(cacheText) cor (size(cacheText) = 0) then
      ^nil;
    endif;
    beep(); /* Warn user about reaching the end of data */
  endif;
  invalidate(self); 
}
!!

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

/* 06/20/92 - PUBLIC 
  Data object {file/dasm} (internal) coordinates used 
*/
Def tokenLocation(self)
{ 
  ^tuple((selectLine + cacheStartLine + virtualStartLine), /* vSelLine */
          startChar, 
          endChar )
}
!!

/* 06/20/92 - Windows
  Allow "arrow" keys and the tab key to work without using accelerators. 
*/
Def WM_KEYDOWN(self, wp, lp)
{ 
  if between(wp, 37, 40) then 
    command(self, wp, 0x10000);
  else
    if wp == 9 then 
      command(self, EDIT_TAB, 0x10000);
    endif;
  endif;
}!!

/* 06/20/92 - WINDOWS (PUBLIC)
   Bring up PopUp Menu with choices based on object selected
*/
Def WM_LBUTTONDBLCLK(self, wp, lp | token, category, startPoint, inPoint, disableCmdSet)
{
  /* Step 1: Invert Selection -- just select it */
  WM_LBUTTONDOWN(self, wp, lp);
  
  /* Step 2: find out what it is */
  token := getLeadingToken(self);
  
  /* Step 3: dispatch */
  if not(token) cor (size(token) = 0) then
    ^nil;
  endif ;
  
  if ( token in languageDict[#keywords] ) then
    ^nil;
  endif ;
  
  /* Categorize the token */
  category := getTokenCategory(self, token);
  
  /* startPoint in Screen coordinates */
  if not(inPoint := pointFromStartChar(self)) then
    ^nil;
  endif;
    
  startPoint := clientToScreen(self, inPoint);
  token := getToken(self); /* use used selected subtoken */
  select
    case (category = #variableName)
      popVariableMenu(parent, token, startPoint, nil /* getDisableCmdSet(self, category) */ );
    endCase
    case (category = #functionName) cor (category = #publicLabel) cor (category = #Label) /* <Winky 9/1/97> */
      popFunctionMenu(parent, token, startPoint, nil /* getDisableCmdSet(self, category) */);
    endCase
    case (category = #rangeError) 
       displayFormattedError(ErrorTextLibClass$Inst, 
          ER_NO_SYMBOLIC_INFO, FORCE_POPUP, nil, nil, nil);
       ^nil;
    endCase
    default
      beep();
  endSelect;
}!!

/* 06/20/92 - WINDOWS (PUBLIC)
   Select object nearest to mouse point or set cursor position.
*/
Def WM_LBUTTONDOWN(self, wp, lp | mousePt, rangePt)
{
  invertOff(self);         /* clear any previous selection */
  mousePt := asPoint(lp) ; /* NOTES: window relative */
  if not(setCurPos(self, mousePt)) then
    ^nil;
  endif;
  findRangeToken(self);    /* Scan for token range */
  dragDC := getContext(self);
  invertTok(self, dragDC); /* invert new selection */
  releaseContext(self, dragDC);
  invertOn? := #byMouse ;
  maybeClosePopup(parent);
  if (selectedWin(parent) <> self) then
    /* make self the selected browser window */
    invertOff(selectedWin(parent));
    setSelectedWin(parent, self);
    invalidate(parent);
  endif;
}!!

/* 06/20/92 - WINDOWS (PUBLIC)
   Bring up PopUp Menu with choices based on object selected
*/
Def WM_RBUTTONDBLCLK(self, wp, lp | token, category, startPoint, inPoint, disableCmdSet)
{
  /* Step 1: Invert Selection -- just select it */
  WM_LBUTTONDOWN(self, wp, lp );
  
  /* Step 2: find out what it is */
  token := getLeadingToken(self);
  
  /* Step 3: dispatch */
  if not(token) cor (size(token) = 0) then
    ^nil;
  endif ;
  
  if (token in languageDict[#keywords] ) then
    ^nil;
  endif ;
  
  /* Categorize the token */
  category := getTokenCategory(self, token);
  
  /* startPoint in Screen coordinates */
  if not(inPoint := pointFromStartChar(self)) then
    ^nil;
  endif;
    
  startPoint := clientToScreen(self, inPoint);
  token := getToken(self); /* use used selected subtoken */
  select
    case (category = #variableName)
      popVariableMenu(parent, token, startPoint, nil /* getDisableCmdSet(self, category) */ );
    endCase
    case (category = #functionName) cor (category = #publicLabel)  cor (category = #Label)
      popFunctionMenu(parent, token, startPoint, nil /* getDisableCmdSet(self, category) */ );
    endCase
    case (category = #rangeError) 
       displayFormattedError(ErrorTextLibClass$Inst, 
          ER_NO_SYMBOLIC_INFO, FORCE_POPUP, nil, nil, nil);
      ^nil;
    endCase
    default
      beep(); 
  endSelect;
}!!

/* 06/20/92 - WINDOWS 
   Select object nearest to mouse point or set cursor position.
   Same as left button + control.
*/
Def WM_RBUTTONDOWN(self, wp, lp | mousePt, rangePt)
{
  invertOff(self);        /* clear any previous selection */
  mousePt := asPoint(lp); /* NOTES: window relative */
  if not(setCurPos(self, mousePt)) then
    ^nil;
  endif;  
  findRangeToken(self);   /* Scan for token range */
  dragDC := getContext(self);
  invertTok(self, dragDC); /* invert new selection */
  releaseContext(self, dragDC);
  invertOn? := #byMouse ;
  maybeClosePopup(parent);
  if (selectedWin(parent) <> self) then
    /* make self the selected browser window */
    invertOff(selectedWin(parent));
    setSelectedWin(parent, self);
    invalidate(parent);
  endif;
}!!

/* 09/01/92 - PRIVATE
   Reduce text buffer to compile the WM_VSCROLL method.
*/
Def initStart(self)
{
  setPCLine(bkptRegion, nil);
  ^invertOff(self);
}
!!

/* 11/04/92 - PRIVATE
  Perform thumb operations for WM_VSCROLL 
     NOTES: retrieve text to display as the thumb position advanced.
*/
Def doThumbOperation(self, viewLines, vSelLine, lP | vStart, inverted?) {
  
  inverted? := invertOff(self);
  /* DEBUG - printLine("thumb: "+asString(low(lP))); */
  /* Convert the thumb position to Virtual start line */
  vStart := asLong(((virtualLineLimit - viewLines + 1) * low(lP)) / 100);
  /* Make sure that it in the current range */
  if vStart < 1 then
    vStart := 1;
  else
    if (vStart > (virtualLineLimit - (viewLines - 1))) then
      vStart := max(1, (virtualLineLimit - (viewLines - 1)));
    endif;
  endif;
  /* DEBUG 
  printLine("VStart: "+asString(vStart)+" - VLimit: "+asString(virtualLineLimit)); 
  */
  setPCLine(bkptRegion, nil);
  cacheHit(self, vStart, viewLines);
  if (low(lP) > 98) cand not(totalLineExact(dataObject))
    /* Force to get the total lines */
    flushCache(self);
    rebuildCache(self, virtualLineLimit, viewLines); 
  endif;   
     
  /* cacheHit() can cause line renumbering.  Update lines we need. */
  vStart     := getLineAdjustment(self, vStart);
  vSelLine   := getLineAdjustment(self, vSelLine);
  selectLine := getLineAdjustment(self, selectLine);
  
  /* DEBUG 
  printLine("VStart: "+asString(vStart)+" - VLimit: "+asString(virtualLineLimit)); 
  */ 
  
  /* NOTES:
  ** If after cacheHit() and cacheText buffer is empty then retry with the new
  ** Information from the data object.  Also, force checking for EOF at end.
  */
  if (not(cacheText) cand (low(lP) > 98)) cor
        (vStart > virtualLineLimit) then 
    /* cacheText = nil, dataObject was reset browser virtLineLimit */
    virtualLineLimit := numLinesIn(dataObject);
    /* NOTES:
    **  Since vStart might be greater than virtualLineLimit, set vStart to
    **  virtualLineLimit - visLines(self) + 1 
    */
    vStart := max(0, asLong(virtualLineLimit - viewLines + 1));
    /* DEBUG 
    printLine("VStart: "+asString(vStart)+" - VLimit: "+asString(virtualLineLimit)); 
    */
    cacheHit(self, vStart, viewLines);
    /* cacheHit() can cause line renumbering.  Update lines we need. */
    vStart     := getLineAdjustment(self, vStart);
    vSelLine   := getLineAdjustment(self, vSelLine);
    selectLine := getLineAdjustment(self, selectLine);
    
    /* Make sure that there is cacheText to display after abort */
    if not(cacheText) cor not(cacheValid) then
      flushCache(self); 
      rebuildCache(self, vStart, viewLines);
    endif; 
    /* re-adjust self scroll position */ 
    if (Call PostMessage(hWnd, 0x115L /* WM_VSCROLL */, SB_PAGEUP, 0L) = 0)
       /* Failed to adjust, ask the user to readjust */ 
       displayFormattedError(ErrorTextLibClass$Inst, 
         ER_CANT_DISPLAY_BUF, FORCE_POPUP, nil, nil, nil);
    endif; 
  endif;
  /* DEBUG
  printLine("start: "+asString(virtualStartLine));
  */
  doSelLineInvert(self, vSelLine, inverted?, nil);
  invalidate(self);
  ^0;
 }
!!

/* 06/20/92 - PRIVATE
  Handle vertical scrolling. wP is the scroll request.
*/
Def doScrollDown(self, wP, viewLines, vStart, vSelLine | inverted?)
{ 
  select
    case (wP == SB_LINEDOWN)
      /* At the end of text */
      if (vStart > (virtualLineLimit-(viewLines-1))) then
        if (class(dataObject) = VirtSmrtDasmObject) cand
          getNewDasmObject(self, MOVE_RANGE_TO_HI_ADDR) then
          ^1;
        endif; 
      else 
        inverted? := initStart(self);
        cacheHit(self, vStart+1, viewLines);
        /* cacheHit can cause line renumbering...update lines we care about */
        vSelLine   := getLineAdjustment(self, vSelLine);
        selectLine := getLineAdjustment(self, selectLine);
        Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0);
        setVScrollPos(self);
        doSelLineInvert(self, vSelLine, inverted?, 1);
        invalidate(bkptRegion);
      endif;
     endCase

    case (wP == SB_PAGEDOWN)
      /* At the end of text */
      if (vStart + viewLines + 1) > virtualLineLimit then
        if (class(dataObject) = VirtSmrtDasmObject) cand
          getNewDasmObject(self, MOVE_RANGE_TO_HI_ADDR) then
          ^1;
        endif;
      else
        inverted? := initStart(self);
        if ((virtualLineLimit - (vStart+viewLines-1)) < viewLines) then 
          cacheHit(self, 
            max(1, virtualLineLimit - (viewLines - 2)), viewLines) ;
        else 
          cacheHit(self, vStart+viewLines, viewLines);
        endif;
        vSelLine   := getLineAdjustment(self, vSelLine);
        selectLine := getLineAdjustment(self, selectLine);
        doSelLineInvert(self, vSelLine, inverted?, nil);
        setVScrollPos(self);
        invalidate(self);
      endif;
    endCase
  endSelect;  
  ^0;
}
!!

/* 06/20/92 - PRIVATE 
  Handle vertical scrolling. wP is the scroll request.
*/
Def doScrollUp(self, wP, viewLines, vStart, vSelLine | inverted?, fixRect)
{ 
  select
    case (wP == SB_LINEUP) 
      /* At the beginning of text */
      if (vStart = 1) then
        ^0;
        /* DISABLED SCROLL UP FOR NOW */
        if (class(dataObject) = VirtSmrtDasmObject) cand
          getNewDasmObject(self, MOVE_RANGE_TO_LOW_ADDR) then
          ^1;
        endif;
      else 
        inverted? := initStart(self);
        cacheHit(self, vStart-1, viewLines);
        vSelLine   := getLineAdjustment(self, vSelLine);
        selectLine := getLineAdjustment(self, selectLine);
        fixRect := clientRect(self);
        setBottom(fixRect, tmHeight+2);
        Call ScrollWindow(hWnd, 0, tmHeight, 0, 0);
        Call InvalidateRect(hWnd, fixRect, 1);
        setVScrollPos(self);
        doSelLineInvert(self, vSelLine, inverted?, 1);
        invalidate(bkptRegion);
      endif;  
    endCase

    case (wP == SB_PAGEUP)
      /* At the beginning of text */
      if (vStart = 1) then
        invalidate(bkptRegion);
        ^0; 
        /* DISABLE SCROLL UP FOR NOW */
        if (class(dataObject) = VirtSmrtDasmObject) cand
          getNewDasmObject(self, MOVE_RANGE_TO_LOW_ADDR) then
          ^1;
        else
          invalidate(bkptRegion);
        endif;  
      else 
        inverted? := initStart(self);
        /* modified by cjchen 1996/12/09 */
        /*cacheHit(self, max(1, vStart - (viewLines-1)), viewLines);*/
        cacheHit(self, max(1, vStart - viewLines), viewLines);
        vSelLine   := getLineAdjustment(self, vSelLine);
        selectLine := getLineAdjustment(self, selectLine);
        doSelLineInvert(self, vSelLine, inverted?, nil);
        invalidate(self);
      endif;
    endCase;
  endSelect;
  ^0; /* Processed message, so return 0 */  
}
!!

/* 06/20/92 - WINDOWS 
  Handle vertical scrolling. wP is the scroll request.
*/
Def WM_VSCROLL(self, wP, lP | viewLines, vStart, vSelLine, errMsg)
{ 
  /* Clear ESC key */
  if (TaskLibClass$Inst)
    checkAbort(TaskLibClass$Inst);
  endif;

  /* Check to avoid hang up in accessing memory with emulator is running */ 
  if (class(dataObject(self)) <> VirtSmartFileObject) cand
     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;

  maybeClosePopup(parent);
  viewLines := visLines(self);
  vStart    := (virtualStartLine + cacheStartLine);
  vSelLine  := (selectLine + vStart);
  
  /* Only Stop scrolling if the current dataObject is not a VirtSmrtDasmObject */
  if (viewLines > virtualLineLimit) cand (vStart = 1) cand
    (class(dataObject(self)) <> VirtSmrtDasmObject) then
    ^0  /* shown all text */
  endif;

  select
    case (wP == SB_LINEDOWN) cor (wP == SB_PAGEDOWN)
       ^doScrollDown(self, wP, viewLines, vStart, vSelLine);
    endCase

    case (wP == SB_LINEUP) cor (wP == SB_PAGEUP)
       ^doScrollUp(self, wP, viewLines, vStart, vSelLine);
    endCase

    case (wP == SB_THUMBPOSITION)
       ^doThumbOperation(self, viewLines, vSelLine, lP);
    endCase
  endSelect;
  ^0;
}!!

