/* class comment */!!

inherit(EditWindow, #DasmBrowser, #(addrDisplayDigits /* length of address display */
addrBegin /* col where address displayed */
addrEnd /* last col of address */
dasmBegin /* col where dasm begins */
topOffset /* offset at top of workText */
bottomOffset /* offset at bottom of workText */
desiredOffset /* desired topOffset */
desiredAdjustment
   /* line movement in addition to topOffset */
doubleClickField /* 0:address, 1:dasm */
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 */
baseAddressDescriptor /* A. D. for min offset */
workAddressDescriptor /* A. D. for this offset */
minOffset maxOffset /* for scroll range */
processorFamily /* intel or moto */
eipRegisterID /* id of EIP register */), 2, nil)!!

setClassVars(DasmBrowser, #())!!

now(class(DasmBrowser))!!

now(DasmBrowser)!!

/* Respond to mode change. */
Def updateAddrMode(self)
{
  /* set beginning location in memory */
  if not(setInitialLocation(self, baseAddressDescriptor))
    /* if failed, set type to physical, and try again... */
    if not(setAddrType(AddressLibClass$Inst, baseAddressDescriptor, ADDR_PHYSICAL)) cor
       not(getSegmentInfo(self, baseAddressDescriptor, 0))
         cleanUp(self);
         ^nil;
    endif;
  endif;
  
  /* update segment limits et al */
  if not(getSegmentInfo(self, baseAddressDescriptor, 0))
    cleanUp(self);
    ^nil;
  endif;
  
  
  ^0;
}!!

/* clean up allocated objects. */
Def cleanUp(self | temp)
{
  if baseAddressDescriptor
    destroyAddress(AddressLibClass$Inst, baseAddressDescriptor);
    baseAddressDescriptor := nil;
  endif;
  
  if workAddressDescriptor
    destroyAddress(AddressLibClass$Inst, workAddressDescriptor);
    workAddressDescriptor := nil;
  endif;
}!!

/* close. */
Def close(self | temp)
{
  cleanUp(self);
  close(self:ancestor);
}!!

/* if x86 processor, set to CS:EIP. */
Def setInitialLocation(self, addressDescriptor | eipValue)
{
  if TheProcFamily = PROC_FAMILY_X86
  
    /* set segment to CS */
    if not(setAddressSegmentSelector(AddressLibClass$Inst,
      addressDescriptor, ADDR_USE_CS))
        ^nil;
    endif;

    /* set offset to EIP */
    /* get register ID for EIP (first time only) */
    if not(eipRegisterID)
      if not(eipRegisterID := findRegisterID(CpuLibClass$Inst, "EIP"))
        ^nil;
      endif;
    endif;
    
    /* get register value */
    if not(eipValue := getRegValue(CpuLibClass$Inst, eipRegisterID))
      ^nil;
    endif;
    
    /* set offset */
    if not(setOffset(AddressLibClass$Inst, addressDescriptor, eipValue))
      setOffset(AddressLibClass$Inst, addressDescriptor, 0);
      ^nil;
    endif;
  endif;
}
!!

/* return the handle to the dasm server. */
Def asmServer(self)
{
  ^asmServer(parent);
}!!

/* return the handle to the dasm server. */
Def dasmServer(self)
{
  ^dasmServer(parent);
}!!


/* Override parent's processing; causes bad arrow key behavior. */
Def viewInsertPoint(self | oldLeft oldTop)
{
}!!

/* 7/5/1994 15:24 */
Def debugScroll(self, pos)
{
  WM_VSCROLL(self, SB_THUMBPOSITION, pos);
  ^0;
}!!

/* 7/5/1994 15:24 */
Def debugWorkTextBottom(self)
{
  if not(workText)
    ^nil;
  endif;
  ^workText[size(workText)-1];
}!!

/* 7/5/1994 15:24 */
Def debugWorkTextTop(self)
{
  if not(workText)
    ^nil;
  endif;
  ^workText[0];
}
!!

/* Position to offset within current display range. */
Def gotoOffset(self, offset | theDlg inputstr offset temp resourceId)
{
  /* invalidate cache to force re-alignment of dasm display */
  invalidateCache(self);
  setOffset(self, offset);
  ^0;
}!!

/* 6/23/1994 14:07 return base address descriptor. */
Def baseAddressDescriptor(self)
{
  ^baseAddressDescriptor;
}!!

/* 6/23/1994 14:07 return work address descriptor. */
Def workAddressDescriptor(self)
{
  ^workAddressDescriptor;
}!!

/* Perform common processing on an address string:
   - copy base address into work address
   - copy offset into work address
   > return nil if error, or side-effect work address if successful  */
Def processOffset(self, offset, workDescriptor)
{
  /* set up address descriptor */
  if not(copyAddress(AddressLibClass$Inst, baseAddressDescriptor,
    workDescriptor))
      ^nil;
  endif;

  if not(setOffset(AddressLibClass$Inst, workDescriptor, offset))
    ^nil;
  endif;
  
  ^0;
}
!!

Def getSegmentInfo(self, addressDescriptor, initFlag | temp segChanged rc)
{
  invalidateCache(self);  /* make sure to re-display */
  if not(temp := getAddressLimitsNoError(AddressLibClass$Inst, addressDescriptor))
    ^nil;
  endif;
  minOffset := temp[0];
  rc := getAddressType(AddressLibClass$Inst, addressDescriptor);
  if (rc = 2)
    maxOffset := temp[1];
  else
    maxOffset := 0xFFFF;
  endif;
   
  /* adjust scrolling parms to avoid 32-bit signed problem */
  scrollOffset := maxOffset;
  scrollDivisor := 10;
  temp := asUnsignedStringRadix(asLong(scrollOffset), 10);
  scrollOffset := asInt(subString(temp, 0, size(temp)-1), 10);

  addrDisplayDigits := maxOutputAddrDigits(AddressLibClass$Inst,
    addressDescriptor);
    
  addrEnd := (addrBegin cor 0) + addrDisplayDigits - 1;
  dasmBegin := addrEnd + 2;
  
  /* update base address descriptor */
  if not(initFlag)
    if not(copyAddress(AddressLibClass$Inst, addressDescriptor,
      baseAddressDescriptor))
        ^nil;
    endif;
    if not(setOffset(AddressLibClass$Inst, baseAddressDescriptor,
      minOffset))
        ^nil;
    endif;
  endif;
}
!!

/* return the address field text from the string. */
Def getAddressFieldText(self, string | temp addrDesc)
{
  if readError?(self) cor not(string) cor (size(string) < addrDisplayDigits)
    ^"";
  endif;
  
  ^subString(string, 0, addrDisplayDigits);  /* get address text */
}!!

/* return the address offset given a dasm line. */
/* (formerly getAddressField) */
Def getAddressFieldOffset(self, source | temp1 temp2 temp3 temp4 addrDesc)
{
  if not(source)
    ^nil;
  endif;
  
  temp1 := getAddressFieldText(self, source);
  if not(temp1)
    ^nil;
  endif;
  
  /* reject if blank address field */
  temp2 := leftJustify(rightJustify(temp1));
  if (temp2 = "")
    ^nil;
  endif;
  
  /* convert text to address */
  if not(addrDesc := convertTextToAddress(AddressLibClass$Inst, temp2))
    ^nil;
  endif;

  /* get offset */
  temp3 := getOffset(AddressLibClass$Inst, addrDesc);
  destroyAddress(AddressLibClass$Inst, addrDesc);
  if not(temp3)
    ^nil;
  endif;

  temp4 := asLong(temp3);
  
  ^temp4;
}!!

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)
{
  if (TheProcFamily = PROC_FAMILY_X86)
    ^nil;
  endif;

  /* leave original CPU 32 code unchanged */
  temp := compareOffsetsUsingTemplate(AddressLibClass$Inst, startOffset,
    maxOffset-1000, baseAddressDescriptor);
  if (temp = ADRLIB_ADDR_GREATER_THAN)
    temp := compareOffsetsUsingTemplate(AddressLibClass$Inst, endOffset,
      1000, baseAddressDescriptor);
    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 */
Def arrows(self, wP)
{
  if readError?(self)
    beep();
    ^0;
  endif;

  /* process arrow keys */
  select
    case wP = 38 is
      sendMessage(self, 0x115 /* WM_VSCROLL */, SB_LINEUP, 0L);
    endCase
    case wP = 40 is
      sendMessage(self, 0x115 /* WM_VSCROLL */, SB_LINEDOWN, 0L);
    endCase
  endSelect;
  
  ^0;
}!!

Def asmLine(self, line | dlg source offset status)
{
  /* reject if valid memory not displayed */
  if readError?(self)
    displayFormattedError(ErrorTextLibClass$Inst, 
       ER_DASM_MEMORY, FORCE_POPUP, nil, nil, nil);
    ^0;
  endif;
  
  if not(offset := getAddressFieldOffset(self, workText[topLine + line]))
    ^nil;
  endif;
  
  /* set up address descriptor, stuff in offset */
  if not(copyAddress(AddressLibClass$Inst, baseAddressDescriptor,
    workAddressDescriptor))
      ^nil;
  endif;
  if not(setOffset(AddressLibClass$Inst, workAddressDescriptor,
    offset))
      ^nil;
  endif;

  /* handle processor families differently */
  if (TheProcFamily = PROC_FAMILY_X86)
    dlg := open(AsmDlg2, workAddressDescriptor, self, HE_DLGI_DASMBROW_1);
    if (runModal(dlg, DLG_SLASM, ThePort) <> IDOK)
      ^0;
    endif;
  else
    dlg := open(InputDlgWithHelp);
    setUp(dlg, "Single-Line Assembly",
      "&Source Line: " + getAddressFieldText(self, workText[topLine+line]),
      getSourceField(self, workText[topLine + line]),
      HE_DLGI_DASMBROW_1);
    if (runModal(dlg, DLG_INPUT_WITH_HELP, ThePort) = IDOK)
      ^0;
    endif; 
  endif;
  
  ^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 | theDlg inputstr offset temp resourceId)
{
  /* default to top line */
  if readError?(self)
    offset := 0;
  else
    if not(line)
      temp := topLine;
    else
      temp := topLine + line;
    endif;
    offset := getAddressFieldOffset(self, workText[temp]);
    if not(offset)
      offset := 0;
    endif;
  endif;
  
  /* set up address descriptor, stuff in offset */
  if not(copyAddress(AddressLibClass$Inst, baseAddressDescriptor,
    workAddressDescriptor))
      ^nil;
  endif;
  if not(setOffset(AddressLibClass$Inst, workAddressDescriptor,
    offset))
      ^nil;
  endif;
  
  if (TheProcFamily = PROC_FAMILY_X86)
    theDlg := openForMem(GotoIntelAddrDialog, workAddressDescriptor,
      viewSize(parent));
    resourceId := DLG_GOTO_INTEL_ADDRESS;
  else
    theDlg := open(GotoAddrDialog, workAddressDescriptor);
    resourceId := DLG_GOTO;
  endif;
  
  /* Open the dialog box */
  setHelpEntry(theDlg, HE_DLGR_GOTO_ADDRESS);
  if (runModal(theDlg, resourceId, ThePort) <> IDOK)
    ^nil;
  endif;
    
  /* Get the address result to perform Goto */
  if not(getAddr(theDlg))
    ^displayFormattedError(ErrorTextLibClass$Inst,
       ER_SRC_INVALID_ADDRESS, FORCE_POPUP, nil, nil, nil);
  endif;

  /* Get the address space */
  temp := getAddrSpace(theDlg);
  updateSpaceMenu(parent, temp);

  /* what to do with view address? */
   
  /* Goto the address */
  showWaitCurs();

  /* get offset, abort if error (msg already posted) */
  if not(temp := getOffset(AddressLibClass$Inst, workAddressDescriptor))
    ^nil;
  endif;
  
  /* see if segment limits et al need to be updated */
  if not(getSegmentInfo(self, workAddressDescriptor, nil))
    ^nil;
  endif;
  
  ^gotoOffset(self, temp);
}!!

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 getAddressFieldOffset(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
      (compareOffsetsUsingTemplate(AddressLibClass$Inst, top, offset,
         baseAddressDescriptor) = ADRLIB_ADDR_GREATER_THAN) cor 
      (compareOffsetsUsingTemplate(AddressLibClass$Inst, offset, bottom,
         baseAddressDescriptor) = 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 := compareOffsetsUsingTemplate(AddressLibClass$Inst,
      temp, offset, baseAddressDescriptor);
    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 := getAddressFieldOffset(self, col[index]);
    if (temp) then
      offsetCompare := compareOffsetsUsingTemplate(AddressLibClass$Inst,
        temp, offset, baseAddressDescriptor);
    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;
}!!

/* 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)
{
  /* 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
    /* set up address descriptor, stuff in offset */
    if not(copyAddress(AddressLibClass$Inst, baseAddressDescriptor,
      workAddressDescriptor))
        ^nil;
    endif;
    if not(setOffset(AddressLibClass$Inst, workAddressDescriptor, offset))
      ^nil;
    endif;
    if not(addToAddress(AddressLibClass$Inst, workAddressDescriptor,
      linesOnScreen))
        ^nil;
    endif;
    if not(temp2 := getOffset(AddressLibClass$Inst, workAddressDescriptor))
      ^nil;
    endif;
    if not(temp := compareOffsetsUsingTemplate(AddressLibClass$Inst, offset,
      topOffset, baseAddressDescriptor))
        ^nil;
    endif;
    if not(temp2 := compareOffsetsUsingTemplate(AddressLibClass$Inst, temp2,
      bottomOffset, baseAddressDescriptor))
        ^nil;
    endif;
    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  into new new work text */
  if workText cand offset cand topOffset
    if not(temp := compareOffsetsUsingTemplate(AddressLibClass$Inst, offset,
      topOffset, baseAddressDescriptor))
        ^nil;
    endif;
    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)
    if not(top := getStatementOffset(self, workText, 0))
      ^nil;
    endif;
    if not(temp := compareOffsetsUsingTemplate(AddressLibClass$Inst,
      offset, top, baseAddressDescriptor))
        ^nil;
    endif;
    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) cor (size(workText) = 1 cand workText[0] = "")
    /* if no lines on screen, return initial offset, or reasonable default */
    if (temp := getOffset(AddressLibClass$Inst, baseAddressDescriptor))
      ^temp
    else
      ^0;
    endif;
  endif;
  
  temp := getStatementOffset(self, workText, topLine);
  if not(temp)
    ^0;
  endif;
  ^temp;
}!!

/* return the source given a dasm line. */
Def getSourceField(self, line)
{
  /* get the processor family if not already done */
  if not(processorFamily)
    processorFamily := getProcessorFamily(ProcLibClass$Inst);
  endif;
  
  /* return dasm source for intel */
  if (processorFamily) cand (processorFamily = PROC_FAMILY_X86)
    ^subString(line, /* @@ */ 24, size(line));
  endif;
  
  /* return dasm source for moto */
  ^subString(line, /* @@ */ 18 + addrDisplayDigits, 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 := getAddressFieldOffset(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);

  /* create descriptor for base address */
  if not(baseAddressDescriptor := createAddress(AddressLibClass$Inst))
    ^nil;
  endif;
  if not(setAddrMode(AddressLibClass$Inst, baseAddressDescriptor, ADDR_MODE_CURRENT))
    cleanUp(self);
    ^nil;
  endif;

  /* create descriptor for work address */
  if not(workAddressDescriptor := createAddress(AddressLibClass$Inst))
    cleanUp(self);
    ^nil;
  endif;

  maxCache := 500;
  doubleClickField := nil;
  addrBegin := 0;

  if not(updateAddrMode(self))
    cleanUp(self);
    ^nil;
  endif;
  
  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(getAddressFieldOffset(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 i)
{
  showWaitCurs();
  readErrorClear(self);  /* innocent until proven guilty... */
  if not(processOffset(self, offset, workAddressDescriptor))
    ^nil;
  endif;
  collection := getDasmInstAroundOffset(DisAsmLibClass$Inst,
    dasmServer(parent), workAddressDescriptor, before, after);
  if not(collection)
    readErrorSet(self);
  else
    /* @@ fixup dasm addresses; l and p addrs too long */
    if (collection[0][8] = 'P') cor (collection[0][9] = 'L')
      printLine("**** fixup in progress");
      i := 0;
      loop
      while i < size(collection)
      begin
        collection[i] := subString(collection[i], 1, size(collection[i]));
        i := i + 1;
      endLoop;
    endif;
  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;
  
  reSize(self:ancestor, wP, lP);

  linesOnScreen := visLines;
  
  hideCaret(self);
  ^0;
}!!

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

Def setScrollPos(self | percent qbottom vis temp newTopOffset scrollOffset scrollDivisor)
{
  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 not(TheProcFamily = PROC_FAMILY_X86) cand (topOffset >= 0) cand (topOffset <= 9)
    /* handle 0 case */
    newTopOffset := 0;
  else
    newTopOffset := getStatementOffset(self, workText, topLine);
    if not(newTopOffset)
      newTopOffset := 0;
    endif;
  endif;
  
  scrollOffset := maxOffset;
  scrollDivisor := 10;
  temp := asUnsignedStringRadix(asLong(scrollOffset), 10);
  
  /* early exit if small scroll range */
  if (size(temp) <= 1) cor ((maxOffset > 0) cand ((vis*2) > maxOffset))
     Call SetScrollPos(hWnd, SB_VERT, 0, 1);
    ^0;
  endif;
    
  scrollOffset := asInt(subString(temp, 0, size(temp)-1), 10);
  qbottom := max(1, (scrollOffset) / (100 / scrollDivisor));
  if (newTopOffset < 0)
    temp := asUnsignedStringRadix(asLong(newTopOffset), 10);
    newTopOffset := asInt(subString(temp, 0, size(temp)-1), 10);
    percent := (newTopOffset / qbottom) * 10;
  else
    percent := newTopOffset / qbottom;
  endif;
  
  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 |  temp setting sOffset)
{
  /* 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);
    
    temp := asUnsignedStringRadix(asLong(maxOffset), 10);
    sOffset := asInt(subString(temp, 0, size(temp)-2), 10);
    setting := low(lP) * sOffset;
    
    /* if at end, back off a little so there's lines to read */
    if (setting = maxOffset)
      setting := setting - (2 * visLines(self));
    endif;
        
    setOffset(self, setting);
  endif;
  
  /* don't allow other scroll operations if read error */
  if readError?(self)
    beep();
    ^nil;
  endif;
  
  temp := compareOffsetsUsingTemplate(AddressLibClass$Inst, bottomOffset,
    maxOffset, baseAddressDescriptor);
  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 */

