/* 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 */
cursorLinked
tabWidth /* number of space per Tab */), 2, nil)!!

now(class(SourceCB))!!

now(SourceCB)!!

/* 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 := visLines(self);
  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 */
  if (lineAndCol[0] > virtualLineLimit) then
    /* 
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_SOURCE_END, FORCE_POPUP, nil, nil, nil);
    */
    /* 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 down.
*/
Def getNewDasmObject(self | oldRange, newRange, newOffset, addrRange, newObj, 
  oldObj, virtLine)
{ 
  /* Get a copy of the old addrRange then set the new range from its end offset */
  if (oldRange := addressRange(dataObject)) then
    /* Make the end of old data object as the begin of the new one */
    if (newOffset := getEndOffset(AddressLibClass$Inst, oldRange)) then
      /* Adjust the newOffset to even address boundary */
      if (newOffset mod 2) <> 0 then 
        newOffset := newOffset - 1; /* NON PORTABLE */
      endif;  
      if (newOffset >= 0xfffffeL) then
        newOffset := 0x0; /* Wrap around */ 
      endif;
      addrRange := min(128, (0xffffffL - newOffset));    
      if not(setOffset(AddressLibClass$Inst, oldRange, newOffset)) cor
            not(newRange := setAddrRangeLength(AddressLibClass$Inst, oldRange, addrRange)) then
        destroyAddress(AddressLibClass$Inst, oldRange);
        ^nil;
      endif;  
    else
      ^nil;
    endif;
    /* Open a new data object with the new addrRange desc */
    if not(newObj := open(VirtSmrtDasmObject, newRange)) then
      ^nil;
    endif;
    /* Reset the browser with a new data object */ 
    resetBrowser(TheSourcePresenter, newObj, #true /* saveHistory */, self);     
    if not(virtLine := getVirtLineNum(self, getOffsetNoError(AddressLibClass$Inst, newRange)) ) then
      displayFormattedError(ErrorTextLibClass$Inst, 
       ER_CANT_SCROLL, FORCE_POPUP, nil, nil, nil);
      ^nil;      
    endif;
    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 := visLines(self);  
  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, 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)
{
  /* Update the PC on the bkptRegion */ 
  clearPC(self);
  yPos := 0; 
  startLine := cacheStartLine;
  endLine   := min(size(cacheText), (cacheStartLine + 1 + visLines(self)));
  do(over(startLine, endLine),
    {using(line) 
      /* Get the text string */
      aStr := eval(getTxtLineRoutine, cacheText[line]);  
      if (currentExecPoint(dataObject) = lineNum(dataObject, 
          (virtualStartLine + line),  /* VirtDataObj line # */
           aStr)) then                /* the text itself */
        /* Set the new PC Line Position */
        setPCLine(bkptRegion, 
          (yPos * tmHeight + 2));     /* Y position in pixels (x is always 0) */
        ^GOOD;  
      endif;    
      yPos := yPos + 1;
  });  
}
!!

/* 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 */
}
!!

/* 8/23/1992 5:25 - PUBLIC (to its parent) */
Def setCursorLinked(self, newLinked)
{ 
  cursorLinked := newLinked;
}
!!

/* 8/23/1992 5:23 - PRIVATE
  Return the current CursorLinked.
*/
Def cursorLinked(self)
{ 
  ^cursorLinked;
}
!!

/* 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.
  */
  selTxt := cacheText[asInt(lineNum+cacheStartLine)];
  if not(selTxt) cor not(dataObjLine := mapLineNum(dataObject, lineNum+cacheStartLine+virtualStartLine, selTxt)) then
    ^nil;
  endif;
  /* There is breakpoint setting, clear it - Let event notify to update */
  if bkptId := findBkptInDict(dataObject, dataObjLine) then
    removeBkptDesc(dataObject, bkptId);
  else
    /* 1 is column of the first statement */
    if not(setBkpt(dataObject, bkptType, dataObjLine, 1, selTxt)) then
      ^nil;
    endif;    
  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 ;
}
!!

/* 5/15/1992 10:27 - PRIVATE 
  Return #true if offset is on screen, nil otherwise.
  NOTES: Only used with dataObject = VirtSMixedObject.
    - View of Browser's data buffer.  
    
      +---------+   <- VirtualStartLine
      |  chunk  |
     >-----------<  +    CacheStartLine 
      +---------+   |
      |  chunk  |   |
      +---------+   | <- Screen Viewport 
      +---------+   | 
     >-----------<  +   
      |  chunk  |
      +---------+  
*/
Def mixedOffsetOnScreen?(self, offset, topLine, bottomLine | 
  i, searchStr, addrSize)
{
  if not(cacheText) cor (size(cacheText) = 0) then
    ^nil;
  endif;
  i := topLine;
  searchStr := format(AddressLibClass$Inst, offset); 
  addrSize := addressSize(dataObject);
  loop
  while (i <= bottomLine)
  begin
    /* CacheText[x] = "[xxxxxx] addrSize text.." - 9 = linenum space */
    if find(subString(cacheText[i], 9, addrSize+10), searchStr, 0) then
      ^#true; /* found it */
    endif;
    i := i + 1;
  endLoop;
  ^nil;    
}
!!

/* 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 line.
    NOTES: For a VirtSmartFileObject - return lineNum.  Otherwise, remap it from
      its data object.
*/
Def getVirtLineNum(self, lineNum | newVirtLine )
{ 
  /* Handle the VirtSmrtDasmObject and VirtSMixedObject */ 
  if class(dataObject) = VirtSmartFileObject 
    ^lineNum;  /* Return Line # */
  endif;
     
  /* Look up for the address from the to of cache buffer */ 
  if not(newVirtLine := searchAddress(dataObject, lineNum, max(cacheStartLine, 1)) ) then
    /* Can not find it? - return the last line */
    ^min((cacheStartLine + visLines(self)), 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, lineCheck | viewLines, topLine)
{ 
  /* Get the total viewable line and top line of source */  
  viewLines := visLines(self);
  if not(cacheText) cor (size(cacheText) = 0) then
    ^nil;
  endif;
  
  select 
    case class(dataObject) = VirtSMixedObject is
      /* lineCheck = offsetValue */
      ^mixedOffsetOnScreen?(self, lineCheck, asInt(cacheStartLine),
                               asInt(min(size(cacheText)-1, cacheStartLine+viewLines-1)));

    endCase  
    case class(dataObject) = VirtSmrtDasmObject is
      /* lineCheck = offsetValue */
      ^dasmOffsetOnScreen?(self, lineCheck, asInt(cacheStartLine),
                                asInt(min(size(cacheText)-1, cacheStartLine+viewLines-1)));
    endCase
  endSelect;
  
  /* Default to VirtSmartFileObject - virtSelectLine */  
  topLine := asLong(virtualStartLine + cacheStartLine);
  ^((lineCheck >= topLine) cand (lineCheck < topLine+viewLines));
}
!!

/* 5/15/1992 10:27 - PRIVATE 
  Return #true if offset is on screen, nil otherwise.
  NOTES: Only used with dataObject = VirtSmrtDasmObject.
    - View of Browser's data buffer.  
    
      +---------+   <- VirtualStartLine
      |  chunk  |
     >-----------<  +    CacheStartLine 
      +---------+   |
      |  chunk  |   |
      +---------+   | <- Screen Viewport 
      +---------+   | 
     >-----------<  +   
      |  chunk  |
      +---------+  
*/
Def dasmOffsetOnScreen?(self, offset, topLine, bottomLine | topOffset,
                        bottomOffset, temp, temp2)
{
  if not(cacheText) cor (size(cacheText) = 0) then
    ^nil;
  endif;
  if (topLine < 0) cor (bottomLine < 0) then
     ^#false;
  endif;   
  
  /* extract the top and bottom offset of the current view port */
  if not(topOffset := getAddressField(dataObject, cacheText[topLine])) cor
     not(bottomOffset := getAddressField(dataObject, cacheText[bottomLine])) then
    ^nil;
  endif;
  
  /* Compare the TopOffset and BottomOffset */
  if not(temp := compareOffsets(AddressLibClass$Inst, offset, topOffset))
    ^nil;
  endif;

  if not(temp2 := compareOffsets(AddressLibClass$Inst, offset, bottomOffset))
    ^nil;
  endif;
  /* offset >= topOffset   and   offset < bottomOffset */
  ^(((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  := visLines(self);
  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.
   EX: speciaLine?(browser, 1, #("c = a+b;", 23, 70), 3456);   
*/
Def specialLine?(self, lineNum, lineInfo | pcAddr, addrInfo, line, hDC)
{ 
  /* Current selected window - Remap lineNum to data object line type */
  if dataObject cand (line := lineNum(dataObject, lineNum, lineInfo[0])) then 
    /* Check for current Execution point - PC - draw it */
    if currentExecPoint(dataObject) cand (line = currentExecPoint(dataObject)) then
      /* Draw PC mark on the left region  then draw the text regularly */
      setPCLine(bkptRegion, lineInfo[1]);
    endif;
    
    /* Check for current cursor linked */ 
    if (line = cursorLinked(self)) then 
      ^AT_PC_HERE_FIELD;
    endif; 
       
    /* Anything else is checked for Breakpoints setting */ 
    ^findSpecialLine(dataObject, line);
  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);
  ^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 := visLines(self);
  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 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 */
  if not(showLineNum) then 
    textStr := subString(textStr, 9, size(textStr));
  endif;

  /* For Dasm and Mixed Object */
  if class(dataObject) <> VirtSmartFileObject cand 
    (getAddressField(dataObject, textStr) <> nil) then
    startChar := startCol + 2; endChar := min(2, size(textStr)-startCol); 
    ^point(startChar, endChar);
  endif;  

  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 ;

  if (size(legalChars := languageDict[#tokenCharSet]) = 0) then
    ^"";  /* This test for Assembly language */
  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
      if (cacheText[asInt(selectLine+cacheStartLine)]) cand
      getAddressField(dataObject, cacheText[asInt(selectLine+cacheStartLine)]) then
        ^#unknown;
      endif;
      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);
  /* To be able to double click on assembly code -- 08/29/96 Hera */
  if(blockDesc = nil) then
     blockDesc := moduleDescriptor(dataObject);
  endif;
  /*Hera*/

  /* normal case, local symbol in module */
  if (blockDesc) then
     /* NOTES: symInfo = #(symbolDesciptor, type, isGlobal? ) */
     if symInfo := primeSymbolFromContext(SymbolLibClass$Inst, blockDesc, token) then
       if tokenInfo then 
          if symInfo[1] <> #other 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 */
    });

  languageName := #Unknown;
  selectLine := startChar := endChar := 0;
  cursorLinked := showLineNum := nil;
  xStart := tmWidth * 2;        /* Offset of the client Rect and BkptRegion */
  tabWidth := tabWidth(parent); /* Get tab width from Source Presenter */
  
  /* 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.
    NOTES: 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;
 
  /* Filter out the dasm text line and set hilighting the whole line */
  if (class(dataObject) <> VirtSmartFileObject) cand 
    (getAddressField(dataObject, selStr) <> nil) then
    /* Adjust offset for showing lineNumber */
    if showLineNum then /* 9 = [xxxxxx]b..... */
      offset := max(2, 9+2-startCol);
    else 
      offset := 2;
    endif ;
    /* Set the startChar and endChar */
    startChar := offset*tmWidth; 
    if showLineNum then
      endChar := max(startChar, (size(selStr)-startCol-offset+2)+startChar); 
    else
      if class(dataObject) = VirtSmrtDasmObject then
        endChar := max(startChar, (size(selStr)-startCol)+startChar); 
      else
        /* Offset the LineNumber portion of the string */
        endChar := max(startChar, (size(selStr)-startCol-9)+startChar); 
      endif;  
    endif;  
    ^point(startChar, yPos);
  endif;  
  
  /* Adjust offset for showing lineNumber */
  if showLineNum then
    offset := startCol ;
  else 
    offset := (startCol + 9) ;
  endif ;
  /* Create a tab map of the selStr string */
  /* Map output to input position */
  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 x@y of startChar */
  if (size(textMap) > 0) then
    /* For a regular text token */ 
    if 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 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);
  if not(showLineNum) then 
    selStr := subString(selStr, 9, size(selStr));
  endif;
  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;
  flushCache(self);
  selectLine := startChar := endChar := 0;
  startCol := 0;   /* Column 0 to 255 */  
  virtualLineLimit := numLinesIn(newDataObj);
  setLanguageName( self, languageName(newDataObj));
  setPCLine(bkptRegion, nil); /* Clear out the old PC mark */
  if tokenLoc then
    showTokenAt(self, tuple(tokenLoc[0], tokenLoc[1]));
  else
    invalidate(self);
  endif;
  
  /* Enable/Disable commands specific to data object type */
  if (class(dataObject) = VirtSmrtDasmObject) then
    setCmdsForAsm(parent);
  else
    setCmdsForSource(parent);
  endif;  
}
!!

/* 06/20/92 - PUBLIC 
  Set language name of the browser.
*/
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 > visLines(self)-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)
{
  clearPC(self);
  invertOff(self);
  linesShown := visLines(self);
  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];
  rangePt := findToken(self);
  startChar := x(rangePt);
  endChar   := y(rangePt);
  doInvert(self); /* Invert then paint will hilight again */
  if (lineAndCol[0] > virtualLineLimit) then
    /*
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_SOURCE_END, FORCE_POPUP, nil, nil, nil);
    */  
    /* Reset to max virttualLineLimit */
    virtualStartLine := (virtualLineLimit - linesShown +2);
    /* Let get the text */  
    cacheHit(self, virtualStartLine, linesShown);
    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)
      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)
      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, inverted?, vStart, viewLines, vSelLine, lP) {
  
  inverted? := invertOff(self);
  vStart := asLong(((virtualLineLimit - viewLines + 1) * low(lP)) / 100);
  if vStart < 1 then
    vStart := 1;
  else
    if vStart > (virtualLineLimit - (viewLines - 1)) then
      vStart := max(1, (virtualLineLimit - (viewLines - 1)));
    endif;
  endif;
  setPCLine(bkptRegion, nil);
  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);
  
  /* 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) > 95)) 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);
    /* Make sure that there is cacheText to display after abort */
    if not(cacheText) cor not(cacheValid) then
      flushCache(self); 
      rebuildCache(self, vStart, viewLines);
    endif;  
    /* Error Report - Should not happen at all */
    if not(cacheText) cand not(cacheValid) then
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_CANT_DISPLAY_BUF, FORCE_POPUP, nil, nil, nil);
    endif; 
  endif;
  doSelLineInvert(self, vSelLine, inverted?, nil);
  invalidate(self);
 }
!!

/* 06/20/92 - WINDOWS 
  Handle vertical scrolling. wP is the scroll request.
*/
Def WM_VSCROLL(self, wP, lP | fixRect, viewLines, vStart, inverted?, 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);
  if (viewLines > virtualLineLimit) cand (vStart = 1) then
    ^0  /* shown all text */
  endif;
  select
    case (wP == SB_LINEDOWN)
      if (vStart > (virtualLineLimit-(viewLines-1))) then
        if (class(dataObject) = VirtSmrtDasmObject) cand
          getNewDasmObject(self) 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);
        /* ^1; */
      endif;
      ^0; 
     endCase
    case (wP == SB_PAGEDOWN)
      if (vStart + viewLines + 1) > virtualLineLimit then
        ^0; /* at end */
      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
    case (wP == SB_LINEUP) 
      if (vStart > 1) then
        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;  
      ^0;
    endCase
    case (wP == SB_PAGEUP)
      if vStart = 1 then
        invalidate(bkptRegion);
        ^0 /* at beginning */
      else 
        inverted? := initStart(self);
        cacheHit(self, max(1, vStart - (viewLines-1)), viewLines);
        vSelLine := getLineAdjustment(self, vSelLine);
        selectLine := getLineAdjustment(self, selectLine);
        doSelLineInvert(self, vSelLine, inverted?, nil);
        invalidate(self);
      endif;
    endCase
    case (wP == SB_THUMBPOSITION)
       doThumbOperation(self, inverted?, vStart, viewLines, vSelLine, lP);
    endCase
  endSelect;
  ^0;
}
!!
