/* class comment */!!

inherit(EditWindow, #DasmBrowser, #(addrBegin /* col where address displayed */
addrLength /* length of address display */
addrEnd /* last col of address */
dasmBegin /* col where dasm begins */
topOffset /* offset at top of workText */
bottomOffset /* offset at bottom of workText */
offsetMax /* max acceptable offset */
desiredOffset /* desired topOffset */
desiredAdjustment
   /* line movement in addition to topOffset */
doubleClickField /* 0:address, 1:dasm */
maxOffset /* max address offset */
scrollOffset scrollDivisor
  /* scroll range; offset must be positive */
maxCache /* max lines in cache */
initializeCalled /* true if initialize called */
linesOnScreen /* visLines */
workTextChanged
inFocus /* true if in focus */
cacheInvalid /* true if cache invalid */
readError /* true if read err on last mem read */), 2, nil)!!

setClassVars(DasmBrowser, #())!!

now(class(DasmBrowser))!!

now(DasmBrowser)!!

Def asmLineOnce(self, line | dlg source lineNumber status)
{
  /* reject if valid memory not displayed */
  if readError?(self)
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_DASM_MEMORY, FORCE_POPUP, nil, nil, nil);
    ^0;
  endif;
  
  /* get line number from source */
  lineNumber := asLong(getAddressField(self, workText, topLine + line));
  
  dlg := open(InputDlgWithHelp);

  setUp(dlg, "Single-Line Assembly",
         "&Source Line: " + asUnsignedStringRadix(lineNumber, 16),
         getSourceField(self, workText[topLine + line]),
         HE_DLGI_DASMBROW_1);

  if (runModal(dlg, DLG_INPUT_WITH_HELP, ThePort) = IDOK) then 
    source := getText(dlg);
    status := assembleSource(AsmLibClass$Inst, asmServer(parent),
      lineNumber, source);
    if not(status)
      ^nil;
    endif;
  endif;
  
  ^0; 
}!!

Def cacheInvalidSet(self)
{
  cacheInvalid := 0;
}!!

Def cacheInvalidClear(self)
{
  cacheInvalid := nil;
}!!

/* Return true if wrap-around is detected.  If startOffset is within 1000
   bytes of the end, and endOffset is within 1000 bytes of the beginning,
   then a wrap-around condition has occurred. */
Def wrapAround?(self, startOffset, endOffset | temp)
{
  temp := compareOffsets(AddressLibClass$Inst, startOffset, maxOffset-1000);
  if (temp = ADRLIB_ADDR_GREATER_THAN)
    temp := compareOffsets(AddressLibClass$Inst, endOffset, 1000);
    if (temp = ADRLIB_ADDR_LESS_THAN)
      ^0;
    endif;
  endif;
  ^nil;
}!!

/* Filter out undesireable behaviors. */
Def command(self, wP, lP)
{ select
    case wP == EDIT_CUT cor
         wP == EDIT_COPY cor
         wP == EDIT_PASTE cor
         wP == EDIT_CLEAR cor
         wP == EDIT_SELALL cor
         wP == EDIT_SRCH cor
         wP == EDIT_RPLC cor
         wP == RPLC_ALL cor
         wP == VK_F3 cor
         wP == EDIT_TAB is
             beep();
             ^0;
     endCase;
   endSelect;
   ^command(self:ancestor, wP, lP);
}!!

/* gaining focus */
Def activateWindow(self)
{
  if inFocus
    ^0;
  endif;
  
  inFocus := 0;

  /* if error, must move view to force read */
  if readError?(self)
    invalidate(self);
    ^nil;
  endif;
  
  /* is invalidate cache pending? */
  if cacheInvalid cor readError?(self)
    doRefresh(self);
  endif;
  
  ^0
}!!

/* Respond to arrow keys and home and page-end keys. */
Def arrows(self, wP)
{
  if readError?(self)
    beep();
    ^0;
  endif;
}!!

/* single-line assembly call 
 *
 * Notes (Ron, 6/8/93): Changed to use InputDlgWithHelp instead of
 *                      InputDialog as part of PowerViews Improvement
 *                      Project.
 */
!!

/* call asmLineOnce until it returns 0 for success. */
Def asmLine(self, line | status)
{
  loop
    status := asmLineOnce(self, line);
  while not(status)
  endLoop;
  
  ^0;
}!!

/* Map an unsigned value onto a signed value:
   0 -> -0x80000000
   1 -> -0x7fffffff
    : :
   0x7ffffffe -> -2
   0x7fffffff -> -1
   0x80000000 -> 0
    : :
   0xffffffff -> 0x7fffffff
*/
Def asUnsigned(self, u | diff)
{
  if not(u)
    ^nil;
  endif;

  diff := u - 0x80000000;
  
  if negative(u)
    if negative(diff)
      ^negate(diff);
    endif;
  endif;
  ^diff;
}
!!

/* Calculate workText related values */
Def calcWorkTextStats(self)
{
  topOffset := getStatementOffset(self, workText, 0);
  bottomOffset := getStatementOffset(self, workText, size(workText)-1);
}!!

/* Process MS-Window's character input message. */
Def charIn(self, wP, lP | rect)
{
  ^0;
}!!

/* clean up the cursor after screen reformatting. */
Def cleanUpCursor(self | t1 t2 t3 t4)
{
  hideCaret(self);
}!!

/* losing focus */
Def deactivateWindow(self)
{
  inFocus := nil;
  
  ^0;
}!!

Def doGoto(self, line | dlg inputstr address temp)
{
  /* default to top line */
  if readError?(self)
    address := 0;
  else
    if not(line)
      temp := topLine;
    else
      temp := topLine + line;
    endif;
    address := getAddressField(self, workText, temp);
    if not(address)
      address := 0;
    endif;
  endif;
  
  dlg := open(GotoDialog);
  setValue(dlg, "0x" + asUnsignedStringRadix(asLong(address), 16));
  
  if runModal(dlg, DLG_GOTO, ThePort) = IDOK then
    inputstr := getValue(dlg);
    
    /* convert text->address, abort if error (msg already posted) */
    temp := convertTextToAddress(AddressLibClass$Inst, inputstr);
    if not(temp)
      ^nil;
    endif;
    
    /* get offset, abort if error (msg already posted) */
    temp := getOffset(AddressLibClass$Inst, temp);
    if not(temp)
      ^nil;
    endif;

    /* invalidate cache to force re-alignment of dasm display */
    invalidateCache(self);
    setOffset(self, temp);
  endif;
  ^0;
}!!

Def doRefresh(self | test)
{
  readErrorClear(self);
  if inFocus
    desiredOffset := getScreenOffset(self);
    desiredAdjustment := nil;
    invalidateCache(self);
    fixScreen(self);
    cacheInvalidClear(self);
  else
    cacheInvalidSet(self);
  endif;
  
  ^0;
}!!

/* Return the index of the next statement that has an address. */
Def findAddressIndex(self, col, index, increment | colSize)
{
  colSize := size(col);
  
  loop
  while 1
  begin
    /* make sure it's there */
    if (index < 0) cor (index >= colSize) cor (not(col[index]))
      ^nil;
    endif;
    
    /* if its there, return index if it has an address */
    if getAddressField(self, col, index)
      ^index;
    endif;
    
    /* otherwise, increment and try again. */
    index := index + increment;
    
  endLoop;
}!!

/* return the index into the collection of the statement containing offset
   as its address, or nil if not found.
   A hard search must find exact address; soft will return neighboring
   address.  */
Def findOffsetInCollection(self, col, offset, hard | top bottom index temp)
{
  /* exit if bad col */
  if not(col) cor not(offset)
    ^nil;
  endif;

  /* get top and bottom offsets */
  top := getStatementOffset(self, col, 0);
  bottom := getStatementOffset(self, col, size(col)-1);
  
  /* exit if not in range */
  if not(top) cor not(bottom) cor
      (compareOffsets(AddressLibClass$Inst,top, offset) = ADRLIB_ADDR_GREATER_THAN) cor 
      (compareOffsets(AddressLibClass$Inst,offset,bottom) = ADRLIB_ADDR_GREATER_THAN)
    ^nil;
  endif;

  if size(col)
    /* do sequential search when small */
    temp := findOffsetSequentially(self, col, offset, hard, 0, 1);
    ^temp;
  else  
        /* due to the signed arith. problem this branch is being */
        /* bypassed.  */
    /* when large, pick good starting point, search up or down */
    index := asInt( (size(col) * (offset - top)) / (bottom - top) );
    index := max(0, min(index, size(col) - 1) );
      /* make sure index is valid index */
    temp := getStatementOffset(self, col, index);
    temp := compareOffsets(AddressLibClass$Inst,temp,offset);
    if ((temp = ADRLIB_ADDR_EQUAL) cor 
        (temp = ADRLIB_ADDR_GREATER_THAN)) /* temp >= offset */
      /* search backwards */
      temp := findOffsetSequentially(self, col, offset, hard, index, -1);
      ^temp;
    else
      /* search forward */
      temp := findOffsetSequentially(self, col, offset, hard, index, 1);
      ^temp;
    endif;
  endif;
}!!

/* PRIVATE - Used by findOffsetInCollection only.
   Find the offset sequentially within the collection given a starting
   position, and a search direction.
   Hard search must be exact. */
Def findOffsetSequentially(self, col, offset, hard, start, increment |
      index temp offsetCompare)
{
  index := start;
  loop
  while 1  /* returns from inside loop */
    temp := getAddressField(self, col, index);
    if (temp) then
      offsetCompare := compareOffsets(AddressLibClass$Inst, temp, offset);
    endif;
    select
      case not(temp)     is  /* nil */ endCase;    /* do nothing */
      case (offsetCompare = ADRLIB_ADDR_EQUAL) is  /* temp = offset */
        ^index;   
      endCase;    /* found */
      /* next two cases indicate no match */
      case increment = 1  cand  (offsetCompare = 0) is /* temp > offset */
          if hard
            ^nil;
          else
            ^index;
          endif;
      endCase; 
      case increment = -1 cand  (offsetCompare = 1) is /* temp < offset */
          if hard
            ^nil;
          else
            ^index;
          endif;
      endCase;
    endSelect;
    index := index + increment;
  endLoop;
}!!

/* The desired address offset and / or line adjustment has been entered.
   Update the screen accordingly. */
Def fixScreen(self | oldTopLine scrollLines fixRect scrollRect)
{
  if not(initializeCalled)
    ^0;
  endif;
  
  showWaitCurs();
  oldTopLine := topLine;  /* save for scrolling test */
  workTextChanged := nil;
  
  /* update workText (text) contents; if fails, don't repaint screen. */
  fixWorkText(self);

  /* calculate scroll distance if relevant */
  if topLine cand oldTopLine
    scrollLines := topLine - oldTopLine;
  endif;
  
  select
    case workTextChanged is  /* workText changed; can't scroll */
      invalidate(self);
    endCase
    case scrollLines = 1 is  /* scroll down 1 line */
      fixRect := clientRect(self);
      setTop(fixRect, tmHeight*(visLines(self) - 1));
      Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0);
      Call InvalidateRect(hWnd, fixRect, 1);
    endCase
    case scrollLines = -1 is  /* scroll up 1 line */
      fixRect := clientRect(self);
      setBottom(fixRect, tmHeight);
      scrollRect := clientRect(self);
      setTop(scrollRect, tmHeight);
      setBottom(scrollRect, tmHeight*(visLines(self) - 1));
      Call ScrollWindow(hWnd, 0, tmHeight, scrollRect, 0);
      Call InvalidateRect(hWnd, fixRect, 1);
    endCase
    default  /* can't scroll */
      invalidate(self);
  endSelect;

  /* set thumb position */
  setScrollPos(self);

  showOldCurs();
  ^0;
}!!

/* Update the workText contents. */
Def fixWorkText(self | status)
{
  /* read lines if offset not cached */
  status := getOffsetInCache(self, desiredOffset, desiredAdjustment);
  if not(status)
    ^nil;
  endif;
  
  /* read lines if adjustment not cached */
  status := getAdjustmentInCache(self, desiredOffset, desiredAdjustment);
  if not(status)
    ^nil;
  endif;
  
  /* trim cache if it is too big */
  status := trimCache(self);
  if not(status)
    ^nil;
  endif;
  
  ^0;
}!!

/* return the address offset given a dasm line. */
Def getAddressField(self, source, index | temp)
{
  if not(source) cor not(index) cor (index < 0) cor (index >= size(source))
    ^nil;
  endif;
  
  ^asInt(subString(source[index], 0, addrLength), 16)  /* is nil if not numeric */
}!!

/* read lines if adjustment not cached */
Def getAdjustmentInCache(self, offset, adjustment |
   col diff temp oldSize offsetGuess newOffset)
{    
  /* quick exit if no adjustment */
  if not(adjustment) cor adjustment = 0
    ^0;
  endif;

  diff := topLine + adjustment;  /* calculate number of lines to move */

  /* exit if entire screen already cached */
  if (diff >= 0) cand ((diff + linesOnScreen) <= size(workText))
    topLine := okTopLine(self, topLine + adjustment);
    ^0;
  endif;

  /* read source lines containing desired adjustment */
  if diff < 0
    /* read lines above current cache; merge with workText */
    col := readChunk(self, topOffset, readSize(self), 0);
    if not(col)
      ^nil;
    endif;
    
    temp := mergeCollections(self, workText, col, 0);
    oldSize := size(workText);  /* save for setting topLine later */
  else
    /* read lines below current cache; merge with workText */
    col := readChunk(self, bottomOffset, 0, readSize(self));
    if not(col)
      ^nil;
    endif;
    
    temp := mergeCollections(self, workText, col, nil);
  endif;
  
  workTextChanged := 1;

  /* if merge failed, reconstruct cache by reading again */
  if not(temp)
    invalidateCache(self);
    /* wild guess at address of desired top offset */
    offsetGuess := ((DASM_MAX_BYTES_PER_LINE + DASM_MIN_BYTES_PER_LINE) / 2);
    newOffset := topOffset + (diff * offsetGuess);
    /* reset if wraps around 0 */
    if (topOffset >= 0) cand ((newOffset > topOffset) cor (newOffset < 0))
      newOffset := 0;
    endif;
    ^getOffsetInCache(self, newOffset, 0);
  endif;

  /* merge successful */
  workText := temp;
  calcWorkTextStats(self);

  /* reset topLine if added to top of cache; topLine should remain a
     constant distance from the end of the cache. */
  if diff < 0
    topLine := okTopLine(self, size(workText) + topLine - oldSize);
  endif;
  
  /* add adjustment to topLine */
  topLine := okTopLine(self, topLine + adjustment);

  ^0;

}!!

/* return offset for top of screen */
Def getOffset(self | temp)
{
  if (temp := getScreenOffset(self))
    ^temp;
  endif;
  ^0
}!!

/* read lines if offset not cached */
Def getOffsetInCache(self, offset, adjustment | col temp temp2 top tmpDesc)
{
  /* quick exit if already cached; top and bottom offset must be defined,
     and either offset is nil, or both the top and bottom line of the
     screen are in the cache. */
  /* if offset not specified, do nothing */
  if not(offset)
    ^0;
  endif;
  
  if workText cand topOffset cand bottomOffset
    tmpDesc := createAndSetAddress(AddressLibClass$Inst, offset);
    addToAddress(AddressLibClass$Inst, tmpDesc, linesOnScreen);
    temp2 := getOffset(AddressLibClass$Inst, tmpDesc);
    destroyAddress(AddressLibClass$Inst, tmpDesc);
    temp := compareOffsets(AddressLibClass$Inst, offset, topOffset);
    temp2 := compareOffsets(AddressLibClass$Inst, temp2, bottomOffset);
    if ( ((temp = ADRLIB_ADDR_EQUAL) cor 
          (temp = ADRLIB_ADDR_GREATER_THAN)) cand  /* offset >= topOffset */
       ( ((temp2 = ADRLIB_ADDR_EQUAL) cor 
          (temp2 = ADRLIB_ADDR_LESS_THAN) )))  /* bottomOffset >= offset+linenum */
       /* set topline */
       topLine := findOffsetInCollection(self, workText, offset, nil);
       if not(topLine)
         ^nil;
       endif;
       ^0;
    endif
  endif;

  /* read source lines containing desired offset */
  col :=
    readChunk(self, offset, readBehindSize(self), readAheadSize(self));
  if not(col)
    ^nil;
  endif;

  /* merge new lines, old work text into new work text */
  if offset cand topOffset
    temp := compareOffsets(AddressLibClass$Inst, offset, topOffset);
    if ((temp = ADRLIB_ADDR_EQUAL) cor 
        (temp = ADRLIB_ADDR_LESS_THAN))  /* offset !> topOffset */
      temp := nil; 
    endif;
    temp := mergeCollections(self, col, workText, temp);
  endif;

  /* if failed, just use newly read lines; if OK, use merged lines */
  if not(temp)
    workText := col;
  else
    workText := temp;
  endif;

  calcWorkTextStats(self);
  
  if not(adjustment)
    adjustment := 0;
  endif;
  
  /* find offset in collection; if fails, set to either top or bottom */
  temp := findOffsetInCollection(self, workText, offset, nil);
  if not(temp)
    top := getStatementOffset(self, workText, 0);
    temp := compareOffsets(AddressLibClass$Inst, offset, top);
    if (temp = ADRLIB_ADDR_LESS_THAN)  /* offset < top */
      temp := 0;
    else
      temp := size(workText) - 1;
    endif;
  endif;
  
  topLine := okTopLine(self, temp + adjustment);
  ^0;
}!!

/* get offset for top of screen */
Def getScreenOffset(self | temp)
{
  if not(workText)
    ^0;  /* return 0 if no screen */
  endif;
  
  temp := getStatementOffset(self, workText, topLine);
  if not(temp)
    ^0;
  endif;
  ^temp;
}!!

/* return the source given a dasm line. */
Def getSourceField(self, line)
{
  ^subString(line, /* @@ */ 18 + addrLength, size(line));
}!!

/* return the offset of a statement; must back up if multi-line statement. */
Def getStatementOffset(self, source, index | temp sourceSize)
{
  sourceSize := size(source);
  
  loop
  while index >= 0
  begin
    if index < 0 cor index > sourceSize
      ^nil;
    endif;
    
    if temp := getAddressField(self, source, index)
      ^temp
    endif;
    index := index - 1;
  endLoop;
  /* if all else fails, return 0 as the address */
  ^0;
}!!

/* gaining focus */
Def gotFocus(self, hWndPrev | status)
{
  activateWindow(self);
  status := gotFocus(self:ancestor, hWndPrev);
  
  /* don't show caret */
  hideCaret(self);
  
  ^status
}!!

/* initialize display */
Def initialize(self | temp)
{
  init(self:ancestor);
  linesOnScreen := visLines(self);

  maxOffset := maxOutputOffset(AddressLibClass$Inst,0);
  
  scrollOffset := maxOffset;
  scrollDivisor := 10;
  temp := asUnsignedStringRadix(asLong(scrollOffset), 10);
  scrollOffset := asInt(subString(temp, 0, size(temp)-1), 10);
    
  maxCache := 500;
  doubleClickField := nil;
  addrBegin := 0;
  addrLength := maxOutputAddrDigits(AddressLibClass$Inst,0);
  addrEnd := addrBegin + addrLength - 1;
  dasmBegin := addrEnd + 2;
  topOffset := 0;  /* leave bottomOffset nil to force read */
  desiredOffset := topOffset;
  desiredAdjustment := 0;
  cacheInvalidSet(self);
  initializeCalled := 0;
}!!

/* invalidate the cache, forcing a re-read of dasm lines. */
Def invalidateCache(self)
{
  workText := nil;
  bottomOffset := nil;  /* set to bogus values to force re-read */
  ^0;
}!!

/* losing focus */
Def losingFocus(self, hWndNew)
{
  /* deactivateWindow(self); */
  ^losingFocus(self:ancestor, hWndNew)
}!!

/* only MBrowser wants this. */
Def menuSelection(self)
{
  ^0
}
!!

/* merge the two collections; return nil if failed, the merged collection if
   successful.  This algorithm depends on there being at least one statement
   overlap within the two collections; otherwise, there are either alignment
   problems, or a gap between the two collections which can't be handled
   here.  If append is true, col1 should be appended to col2; otherwise,
   col1 should be prepended to col2.  Return nil if merge unsuccessful,
   and 0 if successful. */
Def mergeCollections(self, col1, col2, append |
      bottomSize bottomLastStmt topLastStmt bottomCol topCol index)
{
  /* reject if two collections not provided */
  if not(col1) cor not(col2)
    ^nil;
  endif;
  
  if append
    /* col2 comes before col1 */
    topCol := col2;
    bottomCol := col1;
  else
    /* col1 comes before col2 */
    topCol := col1;
    bottomCol := col2;
  endif;
  
  /* find the last statement of the top collection within the bottom;
     if we can't find it, the collections either have conflicting
     addresses, do not overlap, or bottom is contained within top. */
  topLastStmt := getStatementOffset(self, topCol, size(topCol) - 1);
  index := findOffsetInCollection(self, bottomCol, topLastStmt, 0);
  if not(index)
    bottomLastStmt :=
      getStatementOffset(self, bottomCol, size(bottomCol) - 1);
    index := findOffsetInCollection(self, topCol, bottomLastStmt, 0);
    if index
      /* bottom completely within top; return top (although wasteful, other
         parts of the code assume the bottom index remains unchanged; discard
         below bottomCol's last offset. */
      ^copyFrom(topCol, 0, index+1);
    endif;
    /* either no overlap or conflict; return error */
    ^nil;
  endif;
  
  /* merge collections */
  /* advance past (possibly multi-line) source stmt common to both col's */
  bottomSize := size(bottomCol);
  index := index + 1;
  loop
  while (index < bottomSize) cand not(getAddressField(self, bottomCol, index))
  begin
    index := index + 1;
  endLoop;
  
  /* add the rest of bottomCol to topCol */
  loop
  while index < bottomSize
  begin
    add(topCol, bottomCol[index]);
    index := index + 1;
  endLoop;

  ^topCol
}!!

/* make sure topline is within valid range */
Def okTopLine(self, topline)
{
  if not(topline)
    ^nil;
  endif;
  ^max(0, min(topline, size(workText) - linesOnScreen))
}!!

/* STOLEN DIRECTLY FROM EditWindow, EXCEPT FIXED BUG AS MARKED.
  Redraw the workText from topLine down, and from
  leftChar rightwards, preserve xPos and yPos.  If
  window has focus, show selected text.*/
Def paint(self, hdc | aStr, xH, yH)
{
  /* avoid premature repaint */
  if not(workText) cor not(topLine)
    ^0;
  endif;
  
  xH := xPos;
  yH := yPos;
  home(self);
  initTextColors(self, hdc);
  /* ORIGINAL CODE: <printed too much> 1 + topline + visLines(self))), */
  do(over(topLine, min(size(workText), topLine + visLines(self))),
  {using(idx) aStr := copyFrom(workText[idx], leftChar, size(workText[idx]));
    Call TextOut(hdc, x(self), y(self), aStr, size(aStr));
    xPos := 0;
    yPos := yPos + 1;
  });
  xPos := xH;
  yPos := yH;
  setScrollPos(self);
  setHScrollPos(self);
  if hasFocus(self) and isSelText(self)
  then dragDC := hdc;
      invSelTxt(self);
      hideCaret(self);
  endif;
}!!

/* return number of lines to read after the focus line */
Def readAheadSize(self)
{
  if readAhead(parent)
    if not(linesOnScreen)
      ^max(visLines(self) * 4, 60);
    else
      ^max(linesOnScreen * 4, 60);
    endif;
  endif;
  ^linesOnScreen;
}!!

/* return number of lines to read prior to focus line */
Def readBehindSize(self)
{
  /* even if caching, respect start line...
  if readAhead(parent)
    ^max(linesOnScreen * 2, 30);
  endif;
  */
  ^0;
}!!

/* read a chunk of dasm lines */
Def readChunk(self, offset, before, after | collection)
{
  showWaitCurs();
  readErrorClear(self);  /* innocent until proven guilty... */
  collection := getDasmInstAroundOffset(DisAsmLibClass$Inst,
    dasmServer(parent), offset, before, after);
  if not(collection)
    readErrorSet(self);
  endif;
  showOldCurs();
  ^truncateDasmWrapAround(self, offset, collection);
}!!

/* return read error */
Def readError?(self)
{
  ^readError;
}
!!

/* clear read error */
Def readErrorClear(self)
{
  readError := nil;
  ^0;
}
!!

/* set read error */
Def readErrorSet(self)
{
  readError := 0;
  invalidateCache(self);
  
  /* put something up on the screen */
  workText := new(TextCollection, 1);
  add(workText, "UNABLE TO READ DISASSEMBLED MEMORY");
  invalidate(self);
  ^0;
}
!!

/* return number of lines to read when extending the cache */
Def readSize(self)
{
  ^readAheadSize(self)
}!!

/* fix screen, call parent. */
Def reSize(self, wP, lP | visLines)
{  
  if not(initializeCalled)
    ^0;
  endif;
  
  visLines := visLines(self);
  /* don't get new lines until we're visible; bottomOffset valid then */
  if not(readError?(self)) cand (visLines <> linesOnScreen) cand bottomOffset
    desiredOffset := getScreenOffset(self);
    if not(desiredOffset)
      desiredOffset := 0;
    endif;
    desiredAdjustment := nil;
    if (visLines > linesOnScreen) cand (visLines > size(workText))
      invalidateCache(self);
    endif;
    linesOnScreen := visLines;
    fixScreen(self);
  endif;
  
  offsetMax :=
    maxOffset - (DASM_MAX_BYTES_PER_INSTRUCTION * linesOnScreen);

  reSize(self:ancestor, wP, lP);
  
  hideCaret(self);
  ^0;
}!!

/* set offset at top of screen */
Def setOffset(self, value | result)
{
  result := compareOffsets(AddressLibClass$Inst, value, offsetMax);
  if (result = ADRLIB_ADDR_EQUAL) cor (result = ADRLIB_ADDR_GREATER_THAN)
    /* input offset too large */
    desiredOffset := offsetMax;
  else
    /* input offset OK */
    desiredOffset := value;
  endif;
  
  desiredAdjustment := 0;
  
  readErrorClear(self);
  fixScreen(self);
}!!

Def setScrollPos(self | percent qbottom vis temp newTopOffset)
{
  if not(initializeCalled)
    ^0;
  endif;

  /* set thumb position */
  if not(vis := visLines(self))
    vis := 0;
  endif;

  /* avoid overflow for 32-bit addresses due to signed arithmetic */
  if (topOffset >= 0) cand (topOffset <= 9)
    /* handle 0 case */
    newTopOffset := 0;
  else
    temp := asUnsignedStringRadix(asLong(topOffset), 10);
    newTopOffset := asInt(subString(temp, 0, size(temp)-1), 10);
  endif;
  
  qbottom := max(1, (scrollOffset - vis) / 100L);
  percent := newTopOffset / qbottom;
  
  /* comment out code to leave space above/below thumb...
  percent := max(2, min(98, percent));
  */
  
  Call SetScrollPos(hWnd, SB_VERT, percent, 1);
}
!!

/* discard cache lines if it is too big */
Def trimCache(self | startIndex increment index deleter deleteSize deleteIndex)
{
  if size(workText) <= maxCache
    ^0;
  endif;
  
  /* if cache was added to, and we need to trim, topLine should be at
     one end or the other; determine which end needs to be trimmed. */
  select
    case topLine > (maxCache * 0.8) is
      /* topLine at end; trim at beginning */
      startIndex := 1;  /* we know first line has address */
      increment := 1;
      deleter := #removeFirst;
    endCase
    case topLine < (maxCache * 0.2) is
      /* topLine at beginning; trim at end */
      startIndex := size(workText) - 1;
      increment := -1;
      deleter := #removeLast;
    endCase
    default
      /* topLine is not where expected; too dangerous to trim */
      ^0;
  endSelect;
  
  loop
  while startIndex >= 0 cand startIndex < size(workText) cand size(workText) > maxCache
  begin
    index := findAddressIndex(self, workText, startIndex, increment);
    if not(index)
      ^0;
    endif;
    
    if increment = 1
      /* deleting from top */
      deleteSize := index;
      topLine := okTopLine(self, topLine - deleteSize);
        /* reset for next loop */
    else
      /* deleting from bottom */
      deleteSize := size(workText) - index;
      /* no change to topLine */
      startIndex := size(workText) - deleteSize - 1;  /* reset for next loop */
    endif;
    
    /* delete the entire statement */
    deleteIndex := 0;
    loop
    while deleteIndex < deleteSize
    begin
      perform(workText, deleter);
      deleteIndex := deleteIndex + 1;
    endLoop;
    
  endLoop;
  
  calcWorkTextStats(self);
  
  ^0;
}!!

/* If the collection wraps around, chop it off. */
Def truncateDasmWrapAround(self, offset, collection |
      start end index retcol maxsize next temp)
{
  maxsize := size(collection) - 1;
  
  /* reject if bad collection */
  if not(collection) cor (maxsize < 1)
    ^nil;
  endif;
  
  /* get start and end offsets */
  start := getStatementOffset(self, collection, 0);
  end := getStatementOffset(self, collection, maxsize);
  
  if not(start) cor not(end)
    ^nil;
  endif;
  

  /* check for wrap around */
  if wrapAround?(self, offset, start)
    ^nil;
  endif;
  
  /* return collection if start and end in order */
  if not(wrapAround?(self, start, end)) /* start < end */
    ^collection;
  endif;
  
  /* collection wraps around; eliminate lines that wrap around */
  retcol := new(TextCollection, 10);  /* returned text collection */
  index := 0;
  loop
  while index <= maxsize
  begin
    next := getStatementOffset(self, collection, index);
    if not(next)
      ^nil;
    endif;
    
    /* if the line is in order, add to collection, prepare for next line */
    if not(wrapAround?(self, offset, next)) /* start < end */
      add(retcol, collection[index]);
    else
      /* if line is out of order, return collection thus far */
      ^retcol;
    endif;
    index := index + 1;
  endLoop;
  ^retcol;
}!!

/* Allow function keys to work without using accelerators. */
Def WM_KEYDOWN(self, wp, lp)
{ if between(wp, 0x70, 0x79)
    checkFuncKey(self, wp, 0x10000);
  else
    ^WM_KEYDOWN(self:ancestor, wp, lp);
  endif;
}!!

/* goto address if in address, assemble statement if in dasm */
Def WM_LBUTTONDBLCLK(self, wP, lP | yt line)
{
  /* don't allow mouse operations if read error */
  if readError?(self)
    /* already beeped on buttondown */
    ^nil;
  endif;
  
  yt := max(0, y(asPoint(lP))-2)/tmHeight;
  line := min((size(workText)-1) - topLine, yt);
  if doubleClickField = 0  /* address */
    doGoto(self, line);
  else
    asmLine(self, line);  /* dasm */
  endif;
  doubleClickField := nil;
  ^0;
}!!

/* Disable normal button-down processing; only want
   to position the cursor. */
Def WM_LBUTTONDOWN(self, wP, lP)
{
  /* don't allow mouse operations if read error */
  if readError?(self)
    beep();
    ^nil;
  endif;
}!!

/* Disable normal button-up processing; only want
   to position the cursor. */
Def WM_LBUTTONUP(self, wP, lP | p)
{
  /* don't allow mouse operations if read error */
  if readError?(self)
    /* already beeped on buttondown */
    ^nil;
  endif;
  
  setCurPos(self, asPoint(lP));
  if (xPos + leftChar) <= addrEnd
    doubleClickField := 0;
  else
    doubleClickField := 1;
  endif;
  ^0;
}!!

/* Respond to MS-Window's vertical scrolling message. wP tells what kind of
  scrolling request has been made.
  Set address in dasm server to first line of display.
  Adjust xPos and yPos to maintain the current virtual cursor location. */
Def WM_VSCROLL(self, wP, lP | offset temp setting Umax)
{
  /* Clear ESC key */
  if (TaskLibClass$Inst)
    checkAbort(TaskLibClass$Inst);
  endif;
  /* thumb position is only acceptable action when readError? true */
  /* move to relative position; do it funny to avoid over/under flow. */
  if wP == SB_THUMBPOSITION
    readErrorClear(self);
    setting := ((scrollOffset / 100L) * low(lP)) * scrollDivisor;
    setOffset(self, setting);
  endif;
  
  /* don't allow other scroll operations if read error */
  if readError?(self)
    beep();
    ^nil;
  endif;
  
  temp := compareOffsets(AddressLibClass$Inst, bottomOffset, maxOffset);
  if not(temp)
    ^nil;
  endif;
  
  select
    /* move down one line if not at end */
    case wP == SB_LINEDOWN is
      if (temp = ADRLIB_ADDR_LESS_THAN) cor /* bottomOffset < maxOffset */
         ((topLine + linesOnScreen) < size(workText))
        desiredOffset := nil;
        desiredAdjustment := 1;
        fixScreen(self);
      endif;
    endCase
    /* move down a page (less 1 line) subject to remaining lines */
    case wP == SB_PAGEDOWN
      if (temp = ADRLIB_ADDR_LESS_THAN) cor /* bottomOffset < maxOffset */
         ((topLine + linesOnScreen) < size(workText))
        desiredOffset := nil;
        desiredAdjustment := linesOnScreen - 1;
        fixScreen(self);
      endif;
    endCase
    /* move up one line if not at top */
    case wP == SB_LINEUP is
      if (topOffset <> 0) cor (topLine > 0)
        desiredOffset := nil;
        desiredAdjustment := -1;
        fixScreen(self);
      endif;
    endCase
    /* move down a page (less 1 line) if there is room */
    case wP == SB_PAGEUP is
      if (topOffset <> 0) cor (topLine > 0)
        desiredOffset := nil;
        desiredAdjustment := negate(linesOnScreen - 1);
        fixScreen(self);
      endif;
    endCase
  endSelect;
}!!

/* DasmBrowser Class Initialization Code */

