/* class comment */!!

inherit(EditWindow, #MBrowser, #(addrBegin /* col where address displayed */
addrLength /* length of address display */
addrEnd /* last col of address */
memBegin /* col of first mem element */
memRepeatCount /* # mem elements */
memByteGrouping /*1:byte, 2:word, 4:long */
memDisplaySize /* width of display element */
memDisplaySizePlus1 /* just what it says */
memMaxDisplayUnit /* max display value */
scrollOffset scrollDivisor
  /* scroll range; offset must be positive */
memEnd /* last col of mem elements */
memRadix /* memory display radix */
memEndian /* memory endian type */
asciiBegin /* col where ascii displayed */
asciiLength /* length of ascii display */
asciiEnd /* last col of ascii */
lineLength /* length of display line */
maxRow /* largest memory row addr */
minRow /* smallest memory row addr */
workTextTop /* row addr at top of workText */
workTextEnd /* row addr at end of workText */
workTextSize /* number of rows in workText */
workTextBytes /* byte equivalent of screen */
desiredScreenTop /* new screenTop, subject to cancel */
screenTop /* row address at top of screen */
screenEnd /* row addr at bottom of screen */
initializeCalled /* true if initialize called */
validChar /* radix-specific valid char method */
formatMemoryDisplay
  /* disp. size specific format mem method */
updateMemoryDisplayUnit /* method for ... */
updateByteArraySize /* byte array method */
displayField /* 0:address, 1:memory, 2:ascii */
cursorWasInAddressArea /* true if ... */
linesOnScreen /* visLines */
dirtyBytes /* info on dirty bytes */
dirtyByteRow  /* row containing db's */
dirtyByteColMin /* min column with db's */
dirtyByteColMax /* max col with db's */
cacheInvalid /* true if cache invalid */
ignoreMemoryChangeEvent
/* true if mem chg event should be ignored */
byteArray /* for writing to server */
inFocus /* true if in focus */
maxCache /* max lines in workText cache */
addrDisplayDigits /* #address display digits */
readError /* true if read err on last mem read */
tempWord tempDWord  /* to format mem */), 2, nil)!!

setClassVars(MBrowser, #())!!

now(class(MBrowser))!!

now(MBrowser)!!

/* return next x,y point; 3rd element is 0 to show display item changed. */
Def getPreviousCharPosAscii(self, x, y)
{
  if (x+leftChar) = asciiBegin
    ^tuple(asciiEnd - leftChar, y-1, 0);
  else
    ^tuple(x-1, y, 0);
  endif;
}!!

/* return previous x,y point; 3rd element true if display element changed */
Def getPreviousCharPosMem(self, x, y)
{
  if (x + leftChar) = memBegin
    ^tuple(memEnd - leftChar, y-1, 0);
  else
    if spaceChar(self, x + leftChar - 1)
      ^tuple(x-2, y, 0);
    else
      ^tuple(x-1, y, nil);
    endif;
  endif;
}!!

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

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

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

/* 2/15/1993 13:37 */
Def displayRow(self, offset | temp)
{ 
  if (offset < 0) then
    /* the max output address is 0xffffffffL so strip the sign bit. */
    /* this algorithm will fail when the address is > 0xFFFFFFFFL */
    temp := (offset bitAnd 0x7FFFFFFFL) / 16L;
    /* now add the msb back into the equation */
    temp := temp bitOr 0x8000000L;
  else
    temp := offset / 16L;
  endif;
  ^temp;
}
!!

/* 2/15/1993 13:37 */
Def maxDisplayRows(self | temp rows)
{ 
  if ((temp := maxOutputOffset(AddressLibClass$Inst,0)) < 0) then
    /* the max output address is 0xffffffffL so strip the sign bit. */
    /* this algorithm will fail when the address is > 0xFFFFFFFFL */
    rows := (temp bitAnd 0x7FFFFFFFL) / 16L;
    /* now add the msb back in */
    rows := rows bitOr 0x8000000L;
  else
    rows := temp / 16L;
  endif;
  ^rows;
}
!!

/* gaining focus */
Def activateWindow(self)
{
  /* if already in focus or working on it, shortcut recursion */
  if inFocus
    ^0;
  endif;
  
  inFocus := 0;

  /* is invalidate cache pending? */
  if cacheInvalid cor readError?(self)
    doRefresh(self);
  endif;
  
  cleanUpCursor(self);
  
  ^0
}!!

/* Make sure cursor (xPos and yPos) is resting on a valid location. */
Def adjustCursorPos(self | where)
{
  hideCaret(self);
  
  cursorInAddrAreaClear(self);
  /* now move cursor to valid area if not already there */
  where := xPos + leftChar;
  
  /* if read error, no caret */
  if readError?(self)
    if where <= (addrEnd + 1)
      cursorInAddrAreaSet(self);
    endif;
    ^0;
  endif;
  
  /* don't go to partial line at bottom */
  if yPos = visLines(self)
    yPos := yPos - 1;
  endif;
  select
    case (where < memBegin) is
      xPos := memBegin - leftChar;
      displayField := 1;  /* memory */
      /* record double-click in address field, or one space beyond */
      if where <= (addrEnd + 1)
        cursorInAddrAreaSet(self);
      endif;
    endCase
    case (where > asciiEnd) is
      xPos := asciiEnd - leftChar;
      displayField := 2;  /* ascii */
    endCase
    case (where > memEnd cand where < asciiBegin) is
      xPos := asciiBegin - leftChar;
      displayField := 2;  /* ascii */
    endCase
    case (where >= memBegin cand where <= memEnd) is
      displayField := 1;  /* memory */
    endCase
    case (where >= asciiBegin cand where <= asciiEnd) is
      displayField := 2;  /* ascii */
    endCase
  endSelect;

  /* move off a space if we're on one */
  if displayField = 1 cand spaceChar(self, xPos + leftChar)
    xPos := xPos + 1;
  endif;
  
  moveCaret(self);
  showCaret(self);

}!!

/* Respond to arrow keys and home and page-end keys. */
Def arrows(self, wP | nextPos vis)
{
  /* reject arrows */
  if readError?(self)
    beep();
    ^0;
  endif;
  
  writeDirtyBytes(self, 0);
  vis := visLines(self);
  hideCaret(self);
  invSelTxt(self);
  endChar := startChar := xPos + leftChar;
  endLine := startLine := yPos;
  select
    case wP == 37   /* left */ is
      nextPos := getPreviousCharPos(self, xPos, yPos);
      /* use results only if valid */
      if (screenTop + nextPos[1]) >= 0
        yPos := nextPos[1];
        xPos := nextPos[0];
      endif;
    endCase;
    case wP == 39   /* right */ is
      nextPos := getNextCharPos(self, xPos, yPos);
      if (screenTop + nextPos[1]) <= maxRow
        yPos := nextPos[1];
        xPos := nextPos[0];
      endif;
    endCase;
    case wP == 38   /* up */ is
      if (screenTop + yPos) >= 1
        yPos := yPos - 1;
      endif;
    endCase;
    case wP == 40   /* down */ is
      if (yPos + screenTop) < maxRow
        yPos := yPos + 1;
      endif;
    endCase;
    case wP == EDIT_HOME   /* home */ is
      xPos := yPos := 0;
    endCase;
    case wP == EDIT_END   /* end */ is
      xPos := 0;
      yPos := vis - 1;
    endCase;
  endSelect;

  /* process cursor-outside-screen conditions */
  select
    case yPos < 0 is
      desiredScreenTop := screenTop - 1L;
      yPos := yPos + 1;
      fixScreen(self);
    endCase;
    case yPos > (vis - 1) is
      desiredScreenTop := screenTop + 1L;
      yPos := yPos - 1;
      fixScreen(self);
    endCase;
  endSelect;
  initSelParms(self);
  viewInsertPoint(self);
  adjustCursorPos(self);
  ^0;
}!!

/* calculate remaining display attributes */
Def calcDisplayAttributes(self)
{
  /* calculate remaining values */
  addrEnd := addrBegin + addrLength - 1;
  memBegin := addrEnd + 3;  /* 2 spaces between addr and mem display */
  memDisplaySizePlus1 := memDisplaySize + 1;
  memRepeatCount := 16 / memByteGrouping;
  memEnd := memBegin + (memDisplaySizePlus1 * memRepeatCount) - 2;
  asciiBegin := memEnd + 2;  /* 1 space between mem and ascii display */
  asciiEnd := asciiBegin + asciiLength - 1;
  lineLength := asciiEnd;
  ^0;
}!!

/* Process MS-Window's character input message. */
Def charIn(self, wP, lP | rect)
{
  if buttonDn
    ^0;
  endif;
  
  /* reject input if read error */
  if readError?(self)
    beep();
    ^0;
  endif;
  
  hideCaret(self);
  charInput(self, asChar(wP));
  /* process cursor-outside-screen conditions */
  select
    case yPos < 0 is
      desiredScreenTop := screenTop - 1L;
      yPos := yPos + 1;
      fixScreen(self);
    endCase;
    case yPos > (linesOnScreen - 1) is
      desiredScreenTop := screenTop + 1L;
      yPos := yPos - 1;
      fixScreen(self);
    endCase;
  endSelect;
  initSelParms(self);
  viewInsertPoint(self);
  moveCaret(self);
  showCaret(self);
  ^0;
}!!

/* Handle the inputted character, return true if aChar is
  a CR. Delete selected text first. */
Def charInput(self, aChar | line nextPos fieldIndex offset charVal)
{
  invSelTxt(self);  /* remove inverted text */
  endChar := startChar := xPos + leftChar;
  endLine := startLine := yPos;
  select
    case isPrintable(aChar) is
      /* process memory input */
      if displayField = 1
        if charInputMemory(self, aChar)
          charInputCleanup(self);
        endif;
      else
        /* assume ascii input because address input illegal */
        if charInputAscii(self, aChar)
          charInputCleanup(self);
        endif;
      endif;
    endCase
    case Char == BS is
      nextPos := getPreviousCharPos(self, xPos, yPos);
      xPos := nextPos[0];
      yPos := nextPos[1];
      if nextPos[2]
        writeDirtyBytes(self, 0);
      endif;
    endCase
    case isNewLineChar(self, aChar) is
      writeDirtyBytes(self, 0);
    endCase
    default
      beep();
  endSelect;
  /* always return nil; don't want charIn messing with the screen */
  ^nil;
}!!

/* process character input in the ascii display area.
   Return code 0 */
Def charInputAscii(self, aChar | fieldIndex offset charVal newString newByte)
{
  /* we already know from charInput that the character is printable. */
  /* stuff the byte equivalent of the ascii character into the byte array */
  fieldIndex := getAsciiFieldIndex(self, xPos + leftChar);
  newByte := asInt(aChar);
  workTextBytes[asInt(yPos+topLine)][asInt(fieldIndex)] := newByte;
  /* reflect the ascii input in the memory display area */
  workText[asInt(yPos+topLine)] := formatTextLine(self, yPos+topLine);
  markDirtyBytes(self, yPos + topLine, fieldIndex, 1);
  ^0;
}!!

/* Clean up after the workText has been updated: redraw the modified line,
   and reposition the cursor. */
Def charInputCleanup(self | nextPos)
{
  /* update the line on the screen (syncs both mem and ascii displays) */
  drawLine(self, asInt(startLine+topLine));
  
  /* get next cursor position */
  nextPos := getNextCharPos(self, xPos, yPos);
  xPos := nextPos[0];
  yPos := nextPos[1];
  
  /* write bytes if moved to a new display unit */
  if nextPos[2]
    writeDirtyBytes(self, nil);
  endif;
}!!

/* process character input in the memory display area. */
Def charInputMemory(self, aChar | fieldIndex offset charVal)
{
  /* process valid char, or beep if bad */
  if (aChar := validChar(self, aChar))
    if perform(self, aChar, updateMemoryDisplayUnit)
      /* read memory display unit into byte array */
      updateByteArray(self);
      /* update ascii display from byte array */
      updateAsciiDisplay(self);
      ^0;
    else
      beep();
      ^nil;
    endif;
  else
    beep();
    ^nil;
  endif;
}!!

/* clean up the cursor after screen reformatting. */
Def cleanUpCursor(self)
{
  /* fix up yPos if it is not visible */
  if yPos < 0
    yPos := 0;
  else
    if yPos > (visLines(self) - 1)
      yPos := visLines(self) - 1;
    endif;
  endif;

  /* make sure cursor is in valid location */
  adjustCursorPos(self);
}!!

/* cursor in address area? */
Def cursorInAddrArea?(self)
{
  ^cursorWasInAddressArea;
}!!

/* cursor not in address area. */
Def cursorInAddrAreaClear(self)
{
  cursorWasInAddressArea := nil;
  ^0;
}!!

/* cursor in address area. */
Def cursorInAddrAreaSet(self)
{
  cursorWasInAddressArea := 0;
  ^0;
}!!

/* losing focus */
Def deactivateWindow(self)
{
  /* if already not focused or working on it, shortcut recursion */
  if not(inFocus)
    ^0;
  endif;
  
  writeDirtyBytes(self, 0);
  inFocus := nil;
  
  ^0;
}!!

/* disable delChar. */
Def delChar(self)
{ beep();
}!!

Def doGoto(self, line | gotoLine dlg inputstr lineOffset 
           charOffset lineAndCol temp cursorPos)
{
  writeDirtyBytes(self, 0);

  /* use good value as default */
  if not(line) cor readError?(self)
    gotoLine := screenTop;
  else
    gotoLine := screenTop + line;
  endif;
  
  dlg := open(GotoDialog);
  setValue(dlg, "0x" + asUnsignedStringRadix(asLong(gotoLine * 16), 16));
  
  if runModal(dlg, DLG_GOTO, ThePort) = IDOK
    inputstr := getValue(dlg);
    
    /* remove leading, trailing blanks */
    inputstr := leftJustify(rightJustify(inputstr));
    
    /* 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;
    
    lineOffset := displayRow(self,temp);
    charOffset := temp mod 16;
    desiredScreenTop := lineOffset;
    yPos := 0;
    xPos := charOffset;
    /* correct if bad */
    if desiredScreenTop < 0 then
      yPos := yPos + desiredScreenTop;
      desiredScreenTop := 0;
    endif;
    fixScreen(self);
  
    /* position the cursor on the byte */
    placeCursor(self, lineOffset, charOffset);
    
  endif;
  ^0;
}!!

/* respond to memory change event; ignore if we sent it */
Def doRefresh(self)
{
  readErrorClear(self);
  /* if in focus, invalidate now; otherwise, wait until we get focus */
  if inFocus
    if not(ignoreMemoryChangeEvent) cor reRead(parent) cor cacheInvalid
      invalidateCache(self);
      cacheInvalidClear(self);
    endif;
  else
    cacheInvalidSet(self);
  endif;
  ignoreMemoryChangeEvent := nil;
}!!

Def doViewDecBytes(self)
{
  if memRadix = 10 cand memByteGrouping = 1
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 10;
  memByteGrouping := 1;
  memMaxDisplayUnit := "255";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit10;
  updateByteArraySize := #updateByteArrayByte;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  desiredScreenTop := screenTop;
  invalidateWorkText(self);
  fixScreen(self);
  showOldCurs();
}!!

Def doViewDecDWords(self)
{
  if memRadix = 10 cand memByteGrouping = 4
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 10;
  memByteGrouping := 4;
  memMaxDisplayUnit := "4294967295";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit10;
  updateByteArraySize := #updateByteArrayDWord;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  desiredScreenTop := screenTop;
  invalidateWorkText(self);
  fixScreen(self);
  showOldCurs();
}!!

Def doViewDecWords(self)
{
  if memRadix = 10 cand memByteGrouping = 2
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 10;
  memByteGrouping := 2;
  memMaxDisplayUnit := "65535";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit10;
  updateByteArraySize := #updateByteArrayWord;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  desiredScreenTop := screenTop;
  invalidateWorkText(self);
  fixScreen(self);
  showOldCurs();
}!!

Def doViewDisassembly(self | err lastError str)
{
}!!

Def doViewHexBytes(self)
{
  if memRadix = 16 cand memByteGrouping = 1
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 16;
  memByteGrouping := 1;
  memMaxDisplayUnit := "";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit16;
  updateByteArraySize := #updateByteArrayByte;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  if initializeCalled
    desiredScreenTop := screenTop;
    invalidateWorkText(self);
    fixScreen(self);
  endif;
  showOldCurs();
}!!

Def doViewHexDWords(self)
{
  if memRadix = 16 cand memByteGrouping = 4
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 16;
  memByteGrouping := 4;
  memMaxDisplayUnit := "";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit16;
  updateByteArraySize := #updateByteArrayDWord;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  desiredScreenTop := screenTop;
  invalidateWorkText(self);
  fixScreen(self);
  showOldCurs();
}!!

Def doViewHexWords(self)
{
  if memRadix = 16 cand memByteGrouping = 2
    ^0;
  endif;
  
  showWaitCurs();
  /* set display attributes */
  memRadix := 16;
  memByteGrouping := 2;
  memMaxDisplayUnit := "";
  updateMemoryDisplayUnit := #updateMemoryDisplayUnit16;
  updateByteArraySize := #updateByteArrayWord;
  setDisplayAttributes(self);

  /* re-construct screen from workTextBytes */
  if initializeCalled
    desiredScreenTop := screenTop;
    invalidateWorkText(self);
    fixScreen(self);
  endif;
  showOldCurs();
}!!

/* desiredScreenTop has been set to the top row to be displayed.
   Do the following:
     - set screenEnd.
     - call fixWorkText to update the workText if necessary.
     - adjust the screen to show the screenTop.
     - set vscroll position.
*/
Def fixScreen(self |
    index vis oldScreenTop scrollLines fixRect scrollRect fixStat errStat)
{
  showWaitCurs();
  oldScreenTop := screenTop;  /* save for 1-line scroll test later */
  screenTop := desiredScreenTop;
  vis := asLong(visLines(self));
  screenEnd := screenTop + vis - 1L;
  /* back off if exposing beyond end; only occurs on reSize? */
  if screenEnd > maxRow
    yPos := yPos - (screenEnd - maxRow);
    screenEnd := maxRow;
    screenTop := screenEnd - vis + 1L;
  endif;

  /* fill the screen with data; force repaint unless scrollable. */
  errStat := readError?(self);
  fixStat := fixWorkText(self);
  if not(errStat) cand fixStat
    scrollLines := screenTop - oldScreenTop; /* if ok, prepare for scroll */
  else
    scrollLines := nil;  /* force repaint */
  endif;
  
  select
    case scrollLines = 1 is
      fixRect := clientRect(self);
      setTop(fixRect, tmHeight*(screenEnd-screenTop));
      Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0);
      Call InvalidateRect(hWnd, fixRect, 1);
    endCase
    case scrollLines = -1 is
      fixRect := clientRect(self);
      setBottom(fixRect, tmHeight);
      scrollRect := clientRect(self);
      setTop(scrollRect, tmHeight);
      setBottom(scrollRect, tmHeight*(screenEnd-screenTop));
      Call ScrollWindow(hWnd, 0, tmHeight, scrollRect, 0);
      Call InvalidateRect(hWnd, fixRect, 1);
    endCase
    default
      invalidate(self);
  endSelect;

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

  /* do cursor processing */
  cleanUpCursor(self);
  showOldCurs();
  ^0;
}!!

/* Read the bytes from the server, and display the corresponding text. */
Def fixWorkText(self)
{
  /* return nil if fixWorkTextMemory fails */
  if not(fixWorkTextMemory(self))
    ^nil;
  endif;
  topLine := asInt(screenTop - workTextTop);
  fixWorkTextLines(self);
  ^0;
}!!

/* Format the workText lines that are visible on the screen. */
Def fixWorkTextLines(self | index screenSize temp)
{
  screenSize := asInt(screenEnd - screenTop + 1);
  index := 0;
  loop
  while index < min(screenSize, size(workText))
  begin
    temp := asInt(topLine + index);
    if not(workText[temp])
      workText[temp] := formatTextLine(self, temp);
    endif;
    index := index + 1;
  endLoop;

  /* fixScreen invalidates screen */
  ^0;
}!!

/* Update the workTextBytes cache. */
Def fixWorkTextMemory(self | screenSize)
{
  screenSize := asInt(screenEnd - screenTop + 1);

  if not(workTextTop) cor (screenTop < workTextTop)
    ^fixWorkTextMemory1(self, screenSize);
  else
    ^fixWorkTextMemory2(self, screenSize);
  endif;
  ^nil;
}!!

/* Update the workTextBytes cache. */
Def fixWorkTextMemory2(self, screenSize
      | index startRead stopRead deleteLineCount maxRead)
{
  /* screenTop >= workTextTop */
  if (screenEnd > workTextEnd)
    /* read below current work text; figure start/stop read rows */
    if readAhead(parent)
      stopRead := min(maxRow, (screenEnd + (3 * screenSize)));
      maxRead := maxCache;
    else
      stopRead := min(maxRow, screenEnd);
      maxRead := screenSize;
    endif;
    if ((stopRead + 1 - maxRead) > workTextEnd)
      /* can't save previous workTextArray */
      if readAhead(parent)
        startRead := max(minRow, (stopRead - (5 * screenSize)));
      else
        startRead := max(minRow, stopRead + 1 - screenSize);
      endif;
      workTextBytes := newWorkTextBytes(self);
      workText := newWorkText(self);
      dirtyBytes := newDirtyBytes(self);

      /* read memory, return if error */
      readErrorClear(self);  /* innocent until proven guilty... */
      if not(readWorkTextBytesEnd(self, startRead, stopRead))
          readErrorSet(self);
          ^nil;
        endif;

      workTextTop := startRead;
      workTextEnd := stopRead;
    else
      /* can save some or all of previous workTextArray */
      startRead := workTextEnd + 1; /* set to read below end line */

      /* discard workTextArray lines if too many */
      index := 0;
      deleteLineCount := stopRead - workTextTop - maxRead;
      loop
      while index < deleteLineCount
      begin
        removeFirst(workTextBytes);
        removeFirst(workText);
        index := index + 1;
      endLoop;

      /* read memory, return if error */
      readErrorClear(self);  /* innocent until proven guilty... */
      if not(readWorkTextBytesEnd(self, startRead, stopRead))
        readErrorSet(self);
        ^nil;
      endif;

      workTextTop := workTextTop + index;
      workTextEnd := stopRead;
    endif;
  endif;
  workTextSize := workTextEnd - workTextTop + 1;
  ^0;
}!!

/* Update the workTextBytes cache. */
Def fixWorkTextMemory1(self, screenSize
      | index startRead stopRead deleteLineCount maxRead)
{
  /* must read lines above current work text; figure start/stop read rows */
  if (readAhead(parent))
    startRead := max(minRow, (screenTop - screenSize));
    maxRead := maxCache;
  else
    startRead := max(minRow, screenTop);
    maxRead := screenSize;
  endif;
  if not(workTextTop) cor ((startRead + maxRead - 1) < workTextTop)
    /* can't save previous workTextArray */
    if (readAhead(parent))
      stopRead := min(maxRow, screenTop + (4 * screenSize));
    else
      stopRead := min(maxRow, screenTop + screenSize - 1);
    endif;
    workTextBytes := newWorkTextBytes(self);
    workText := newWorkText(self);
    dirtyBytes := newDirtyBytes(self);
    /* read memory, return if error */
    readErrorClear(self);  /* innocent until proven guilty... */
    if not(readWorkTextBytesTop(self, startRead, stopRead))
      readErrorSet(self);
      ^nil;
    endif;
    workTextTop := startRead;
    workTextEnd := stopRead;
  else
    /* can save some/all of previous workTextArray */
    stopRead := workTextTop - 1; /* read up to previous top line */

    /* discard workTextArray lines if too many */
    index := 0;
    deleteLineCount := workTextEnd - startRead - maxRead;
    loop
    while index < deleteLineCount
    begin
      removeLast(workTextBytes);
      removeLast(workText);
      index := index + 1;
    endLoop;

    /* read memory, return if error */
    readErrorClear(self);  /* innocent until proven guilty... */
    if not(readWorkTextBytesTop(self, startRead, stopRead))
      readErrorSet(self);
      ^nil;
    endif;

    workTextEnd := workTextEnd - index;
    workTextTop := startRead;
  endif;
  ^0;
}!!

/* construct and return the indicated byte string. */
/* rowIndex is absolute */
Def formatMemoryDisplayByte(self, rowIndex | string index)
{
  string := "";
  index := 0;
  loop
  while index < memRepeatCount
  begin
    string := string + right(
      asUnsignedStringRadix(asLong(workTextBytes[asInt(rowIndex)][index]),
      memRadix), memDisplaySize, "0") + " ";
    index := index + 1;
  endLoop;
  ^string;
}!!

/* construct and return the indicated dword string. */
/* rowIndex is absolute */
Def formatMemoryDisplayDWord(self, rowIndex | string index temp)
{
  string := "";
  index := 0;
  loop
  while index < memRepeatCount
  begin
    temp := index*memByteGrouping;
    
    /* note: this is intel endian */
    /*
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp)], 0);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+1)], 1);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+2)], 2);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+3)], 3);
    */
    /* note: this is moto endian */
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp)], 3);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+1)], 2);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+2)], 1);
    putByte(tempDWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+3)], 0);
    string := string + right(
      asUnsignedStringRadix(asLong(longAt(tempDWord, 0)), memRadix),
      memDisplaySize, "0") + " ";
    index := index + 1;
  endLoop;
  ^string;
}!!

/* construct and return the indicated word string. */
/* rowIndex is absolute */
Def formatMemoryDisplayWord(self, rowIndex | string lWord index temp)
{
  string := "";
  index := 0;
  loop
  while index < memRepeatCount
  begin
    temp := index*memByteGrouping;
    /* note: this is intel endian */
    /*
    putByte(tempWord,
      workTextBytes[asInt(rowIndex)][asInt(temp)], 0);
    putByte(tempWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+1)], 1);
    */
    /* note: this is moto endian */
    putByte(tempWord,
      workTextBytes[asInt(rowIndex)][asInt(temp)], 1);
    putByte(tempWord,
      workTextBytes[asInt(rowIndex)][asInt(temp+1)], 0);

    /* convert to long, and mask off potential sign extension;
       use Long:asUnsignedStringRadix directly, not MBrowser copy. */
    lWord := asLong(wordAt(tempWord, 0)) bitAnd 0x0000ffff;
    string := string + right(
      asUnsignedStringRadix(asLong(lWord), memRadix),
      memDisplaySize, "0") + " ";
    index := index + 1;
  endLoop;
  ^string;
}!!

/* construct and return the formatted text line corresponding to the
   workTextBytes byte array row. */
/* rowIndex is absolute */
Def formatTextLine(self, rowIndex | string index charVal asciiString timer)
{
  string := formatTextLineAddress(self, workTextTop + rowIndex) +
    perform(self, rowIndex, formatMemoryDisplay);

  /* place ascii portion into display */
  asciiString := "";
  index := 0;
  loop
  while index < 16
  begin
    charVal := asChar(workTextBytes[asInt(rowIndex)][asInt(index)]);
    if isPrintable(charVal)
      asciiString := asciiString + asString(charVal);
    else
      asciiString := asciiString + ".";
    endif;
    index := index + 1;
  endLoop;
  ^string + asciiString;
}!!

/* construct and return the address portion of the formatted text line. */
Def formatTextLineAddress(self, rowIndex | string index charVal asciiString timer)
{
  /* put in address field; always display 16 bytes, address always base 16 */
  ^right(asUnsignedStringRadix(asLong((rowIndex)*16), 16),
     addrDisplayDigits, "0") +
     "  ";  /* two spaces */
}!!

/* Given the offset into the workText line, return the index of the
   ascii display element. */
Def getAsciiFieldIndex(self, x)
{
  ^asInt((x - asciiBegin));
}!!

/* Given the index of the ascii display element, return the beginning
   offset into the workText line. */
Def getAsciiFieldOffset(self, x)
{
  ^asInt((asciiBegin + x));
}!!

/* Given the offset into the workText line, return the index of the
   memory display element. */
Def getMemFieldIndex(self, x)
{
  ^asInt(((x - memBegin) / memDisplaySizePlus1));
}!!

/* Given the index of the memory display element, return the beginning
   offset into the workText line. */
Def getMemFieldOffset(self, x)
{
  ^asInt((memBegin + (x * memDisplaySizePlus1)));
}!!

/* return next x,y point */
Def getNextCharPos(self, x, y)
{
  if displayField = 1
    ^getNextCharPosMem(self, x, y);
  else
    ^getNextCharPosAscii(self, x, y);
  endif;
}!!

/* return next x,y point; 3rd element is 0 to show display item changed. */
Def getNextCharPosAscii(self, x, y)
{
  if (x+leftChar) = asciiEnd
    ^tuple(asciiBegin - leftChar, y+1, 0);
  else
    ^tuple(x+1, y, 0);
  endif;
}!!

/* return next x,y point in memory area; 3rd element is 0 if moving
   to another display unit, nil if not. */
Def getNextCharPosMem(self, x, y)
{
  if (x+leftChar) = memEnd
    ^tuple(memBegin - leftChar, y+1, 0);
  else
    if spaceChar(self, x + leftChar + 1)
      ^tuple(x+2, y, 0);
    else
      ^tuple(x+1, y, nil);
    endif;
  endif;
}!!

/* return offset at cursor */
Def getOffset(self)
{
  if (displayField = 1)
    ^(((screenTop + yPos) * 16) + (getMemFieldIndex(self, xPos + leftChar) * memByteGrouping));
  else
    ^(((screenTop + yPos) * 16) + getAsciiFieldIndex(self, xPos + leftChar));
  endif;
}!!

/* return previous x,y point; 3rd element true if display element changed */
Def getPreviousCharPos(self, x, y)
{
  if displayField = 1
    ^getPreviousCharPosMem(self, x, y);
  else
    ^getPreviousCharPosAscii(self, x, y);
  endif;
}!!

/* gaining focus */
Def gotFocus(self, hWndPrev | status)
{
  activateWindow(self);
  status := gotFocus(self:ancestor, hWndPrev);
  
  hideCaret(self);
  
  /* display caret if no read error */
  if not(readError?(self))
    moveCaret(self);
    showCaret(self);
  endif;
  
  ^status;
}!!

/* initialize display */
Def initialize(self | temp)
{
  init(self:ancestor);
  linesOnScreen := 0;
  maxCache := 100;  /* maximum size of workText, workTextBytes cache */

  /* next 2 settings should come from memory server */
  minRow := 0L;
  maxRow := maxDisplayRows(self);

  scrollOffset := maxRow;
  scrollDivisor := 1;
  loop
  while (scrollOffset < 0)
    scrollDivisor := scrollDivisor * 10;
    temp := asUnsignedStringRadix(scrollOffset, 10);
    scrollOffset := asInt(subString(temp, 0, size(temp)-1), 10);
  endLoop;

  addrDisplayDigits := maxOutputAddrDigits(AddressLibClass$Inst,0);

  screenTop := 0L;
  desiredScreenTop := screenTop;
  byteArray := new(Struct, 4);
  
  tempWord := new(Struct, 2);
  tempDWord := new(Struct, 4);
  
  cacheInvalidSet(self);

  /* initially set to display hex bytes */
  doViewHexWords(self);

  initializeCalled := 0;
}!!

/* 6/29/1992 15:31  Discard cached memory bytes */
Def invalidateCache(self | index)
{
  desiredScreenTop := screenTop;
  workTextTop := nil;  /* make fixWorkTextMemory think nothing is there */
  workTextEnd := 0;  /* make fixWorkTextMemory think nothing is there */
  fixScreen(self);
  ^0;
}!!

/* 6/24/1992 15:31  Discard formatted lines if display format changes. */
Def invalidateWorkText(self | index)
{
  if workText
    index := 0;
    loop
    while index < size(workText)
    begin
      workText[asInt(index)] := nil;
      index := index + 1;
    endLoop;
  endif;
  ^0;
}!!

/* return 0 if bad memory display unit index, nil if OK. */
Def invalidMemoryIndex(self, rowIndex | temp)
{
  temp := (16 / memByteGrouping) - 1;
  if (rowIndex < 0 cor rowIndex > temp)
    ^0;
  endif;
  ^nil;
}!!

/* losing focus */
Def losingFocus(self, hWndNew)
{
  /* Do not write memory if the server is gone */
  if memServerValid?(parent) then
    writeDirtyBytes(self, 0);
  endif;
  ^losingFocus(self:ancestor, hWndNew)
}!!

/* save info on dirty bytes. */
Def markDirtyBytes(self, y, x, count)
{
  /* capture row */
  if not(dirtyByteRow)
    dirtyByteRow := y;
  endif;
  
  /* capture min row */
  if not(dirtyByteColMin) cor dirtyByteColMin > x
    dirtyByteColMin := x;
  endif;
  
  /* capture max row */
  if not(dirtyByteColMax) cor dirtyByteColMax < (x + count - 1)
    dirtyByteColMax := x + count - 1;
  endif;
  
  ^0;
}!!

/* 3/23/1992 16:13 */
Def memByteGrouping(self)
{
  ^memByteGrouping;
}!!

/* 3/23/1992 16:13 */
Def memRadix(self)
{
  ^memRadix;
}!!

/* respond to menu selection from parent */
/* @@ MPresenter must call */
Def menuSelection(self)
{
  writeDirtyBytes(self, 0);
}!!

/* return new dirtyBytes structure */
Def newDirtyBytes(self)
{
  ^new(OrderedCollection, 1);
}!!

/* return new workText structure */
Def newWorkText(self)
{
  ^new(TextCollection, 1);
}!!

/* return new workTextBytes structure */
Def newWorkTextBytes(self)
{
  ^new(OrderedCollection, 1);
}!!

/* 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)
{ 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[asInt(idx)], leftChar, size(workText[asInt(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;
  
  hideCaret(self);
  
  /* display caret if no read error */
  if not(readError?(self))
    moveCaret(self);
    showCaret(self);
  endif;
}!!

/* place the cursor properly */
Def placeCursor(self, lineOffset, charOffset | lineAndCol temp cursorPos)
{
  /* place cursor on proper line */
  yPos := lineOffset - screenTop;
  
  /* place cursor on proper column */
  temp := charOffset / memByteGrouping;  /* temp is # display units */
  cursorPos := addrDisplayDigits + 2 + /* addr + separating spaces */
    (temp * memDisplaySize) +  /* # of display units x size */
    temp;  /* # of display unit separators */
    
  /* if this is hex, go to actual byte; forget about decimal. */
  if memRadix = 16
    temp := charOffset mod memByteGrouping;
    /* to move over a byte, move two char positions */
    cursorPos := cursorPos + (2 * (charOffset mod memByteGrouping));
  endif;
  xPos := cursorPos - leftChar;
  
  cleanUpCursor(self);
}!!

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

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

/* set read error */
Def readErrorSet(self)
{
  readError := 0;
  workTextTop := nil;  /* indicate cache is bad */
  workTextEnd := 0;  /* indicate cache is bad */
  workTextBytes := newWorkTextBytes(self);
  
  /* put something up on the screen */
  workText := newWorkText(self);
  add(workText,
    formatTextLineAddress(self, screenTop) + "UNABLE TO READ MEMORY");
  ^0;
}
!!

/* Read the bytes from the server; place at end of workTextBytes. */
Def readWorkTextBytesEnd(self, startRead, stopRead |
      lineCount rowIndex byteArrayCol)
{
  lineCount := asInt(stopRead - startRead + 1);

  /* read bytes from the server; 16 bytes for each row requested */
  if not(byteArrayCol := readLines(memServer(parent), lineCount, startRead))
    ^nil;  /* error reporting done by library */
  endif;

  /* stuff the byte arrays into workTextBytes */
  rowIndex := 0;
  loop
  while rowIndex < lineCount
  begin
    add(workTextBytes, byteArrayCol[rowIndex]);
      /* stuff row into workTextBytes */
    add(workText, nil);  /* indicate line is not formatted yet */
    rowIndex := rowIndex + 1;
  endLoop;

  ^0;
}!!

/* Read the bytes from the server; place at top of workTextBytes. */
Def readWorkTextBytesTop(self, startRead, stopRead |
      lineCount rowIndex byteArrayCol)
{
  /* if workTextBytes is empty, operation is the same as (simpler)
     readWorkTextBytesEnd */
  if not(workTextBytes) cor size(workTextBytes) = 0
    ^readWorkTextBytesEnd(self, startRead, stopRead);
  endif;

  lineCount := asInt(stopRead - startRead + 1);

  /* read bytes from the server; 16 bytes for each row requested */
  if not(byteArrayCol := readLines(memServer(parent), lineCount, startRead))
    ^nil;  /* error reporting done by library */
  endif;

  /* stuff the bytes into workTextBytes backwards */
  rowIndex := lineCount - 1;
  loop
  while rowIndex >= 0
  begin
    insert(workTextBytes, byteArrayCol[rowIndex], 0);
      /* stuff row into workTextBytes */
    insert(workText, nil, 0);  /* work text line not yet formatted */
    rowIndex := rowIndex - 1;
  endLoop;

  ^0;
}!!

/* fix screen, call parent. */
Def reSize(self, wP, lP | visLines)
{
  if not(initializeCalled)
    ^0;
  endif;
  
  visLines := visLines(self);
  if visLines <> linesOnScreen
    writeDirtyBytes(self, 0);
    fixScreen(self);
  endif;
  reSize(self:ancestor, wP, lP);
  linesOnScreen := visLines;
  writeDirtyBytes(self, 0);
  adjustCursorPos(self);
}!!

/* Set cursor position (xPos, yPos) according to the
  specified point. */
Def setCurPos(self, aPnt)
{
  setFocus(self);

  /* this sets xPos and yPos */
  setCurPos(self:ancestor, aPnt);

  /* make sure cursor is on a valid location */
  adjustCursorPos(self);

  writeDirtyBytes(self, 0);
}!!

/* set display attributes; assume memRadix and memByteGrouping are set. */
Def setDisplayAttributes(self)
{
  /* stuff the values that we know */
  addrBegin := 0;
  addrLength := addrDisplayDigits;
  asciiLength := 16;

  /* stuff the radix/grouping dependent value */
  select
    case memRadix = 16 is
      select
        case memByteGrouping = 1  is memDisplaySize := 2; endCase
        case memByteGrouping = 2  is memDisplaySize := 4; endCase
        case memByteGrouping = 4  is memDisplaySize := 8; endCase
        default
           displayFormattedError(ErrorTextLibClass$Inst, 
              ER_INTERNAL, FORCE_POPUP, "Illegal hex byte grouping",
              "MBrowser:setDisplayAttributes", nil);
      endSelect;
    endCase
    case memRadix = 10 is
      select
        case memByteGrouping = 1  is memDisplaySize := 3; endCase
        case memByteGrouping = 2  is memDisplaySize := 5; endCase
        case memByteGrouping = 4  is memDisplaySize := 10; endCase
        default
          displayFormattedError(ErrorTextLibClass$Inst, 
             ER_INTERNAL, FORCE_POPUP, "Illegal decimal byte grouping",
             "MBrowser:setDisplayAttributes", nil);
      endSelect;
    endCase
    default
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_INTERNAL, FORCE_POPUP, "Illegal radix",
         "MBrowser:setDisplayAttributes", nil);
  endSelect;

  select
    case memByteGrouping = 1  is
      formatMemoryDisplay := #formatMemoryDisplayByte; endCase
    case memByteGrouping = 2  is
      formatMemoryDisplay := #formatMemoryDisplayWord; endCase
    case memByteGrouping = 4  is
      formatMemoryDisplay := #formatMemoryDisplayDWord endCase
    default
      displayFormattedError(ErrorTextLibClass$Inst, 
         ER_INTERNAL, FORCE_POPUP, "Illegal byte grouping",
         "MBrowser:setDisplayAttributes", nil);
  endSelect;

  /* calculate the rest of the values */
  calcDisplayAttributes(self);
  ^0;
}!!

/* receive offset for top of screen */
Def setOffset(self, value | lineOffset charOffset)
{
  lineOffset := displayRow(self,value);
  charOffset := value mod 16;
  
  desiredScreenTop := lineOffset;
  fixScreen(self);
  
  /* position the cursor on the byte */
  placeCursor(self, lineOffset, charOffset);
}!!

/* Set scroll bar position, avoiding divide by 0. */
Def setScrollPos(self | percent qbottom vis)
{
  if not(initializeCalled)
    ^0;
  endif;

  /* set thumb position */
  if not (vis := visLines(self))
    vis := 0;
  endif;
  qbottom := max(1, (scrollOffset - vis) / (100 / scrollDivisor));
  percent := screenTop / qbottom;
  Call SetScrollPos(hWnd, SB_VERT, percent, 1);
}!!

/* Return true if the specified character in the worktext is a space. */
Def spaceChar(self, x | lineIndex)
{
  lineIndex := asInt(topLine);
  if not(workText) cor
    (size(workText) <= lineIndex) cor
    (size(workText[lineIndex]) <= x)
      ^nil;
  endif;
  
  /* move off a space if we're on one */
  /* we know that line screenTop is visible */
  if workText[lineIndex][asInt(x)] = ' '
    ^0
  endif;
  ^nil;
}!!

/* Compare a string to a maximum string; return nil if string < max;
   return character offset where string > max otherwise. */
Def stringCompareToMax(self, max, string | maxSize index)
{
  maxSize := size(max);
  index := 0;
  loop
  while index < maxSize
  begin
    if max[index] > string[index]
      ^nil;
    else
      if max[index] < string[index]
        ^index;
      endif;
    endif;
    index := index + 1;
  endLoop;
  ^nil;
}!!

/* Reflect each of the potentially changed bytes in the ascii display. */
Def updateAsciiDisplay(self | fieldIndex startIndex index position char)
{
  fieldIndex := getMemFieldIndex(self, xPos + leftChar);
  startIndex := fieldIndex * memByteGrouping;
  index := 0;
  loop
  while index < memByteGrouping
  begin
    /* byte array position maps 1-1 to ascii position, so calculate once */
    position := startIndex + index;
    char := asChar(workTextBytes[asInt(yPos+topLine)][asInt(position)]);
    if not(isPrintable(char))
      char := '.';
    endif;
    put(workText[asInt(yPos+topLine)], char, position+asciiBegin);
    index := index + 1;
  endLoop;
}!!

/* read memory display unit into byte array */
Def updateByteArray(self | fieldIndex screenOffset value)
{
  /* get display unit # */
  fieldIndex := getMemFieldIndex(self, xPos + leftChar);
  /* where display unit starts on the screen */
  screenOffset := getMemFieldOffset(self, fieldIndex);
  /* grab value from display */
  value := asLong(
             asInt(
               subString(workText[asInt(yPos+topLine)],
                 screenOffset, screenOffset + memDisplaySize),
               memRadix)
           );
   /* stuff bytes into byteArray */
   perform(self, value, fieldIndex, updateByteArraySize);
   ^0
}!!

/* stuff the byte directly into the byte array using fieldIndex as offset. */
Def updateByteArrayByte(self, value, fieldIndex | string)
{
  workTextBytes[asInt(yPos+topLine)][asInt(fieldIndex)] := asInt(value);
  markDirtyBytes(self, yPos + topLine, fieldIndex, 1);
  ^0;
}!!

/* stuff the bytes into the byte array. */
Def updateByteArrayDWord(self, value, fieldIndex | startIndex tempStruct)
{
  tempStruct := new(Struct, 4);
  putLong(tempStruct, value, 0);
  startIndex := fieldIndex * memByteGrouping;

  /* note: this is intel endian */
  /*
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex)] :=
    byteAt(tempStruct, 0);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+1)] :=
    byteAt(tempStruct, 1);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+2)] :=
    byteAt(tempStruct, 2);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+3)] :=
    byteAt(tempStruct, 3);
  */

  /* note: this is moto endian */
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex)] :=
    byteAt(tempStruct, 3);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+1)] :=
    byteAt(tempStruct, 2);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+2)] :=
    byteAt(tempStruct, 1);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+3)] :=
    byteAt(tempStruct, 0);
  markDirtyBytes(self, yPos + topLine, startIndex, 4);
}!!

/* stuff the bytes into the byte array. */
Def updateByteArrayWord(self, value, fieldIndex | startIndex tempStruct t1 t2)
{
  tempStruct := new(Struct, 2);
  putWord(tempStruct, value, 0);
  startIndex := fieldIndex * memByteGrouping;

  /* note: this is intel endian */
  /*
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex)]   := byteAt(tempStruct, 0);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+1)] := byteAt(tempStruct, 1);
  */

  /* note: this is moto endian */
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex)]   := byteAt(tempStruct, 1);
  workTextBytes[asInt(yPos+topLine)][asInt(startIndex+1)] := byteAt(tempStruct, 0);
  markDirtyBytes(self, yPos + topLine, startIndex, 2);
}!!

/* update the memory display unit with the char.
   If the result of inserting the character causes the value to be
     too large, attempt to fix by filling the remainder of the values
     with 0; if the revised value is acceptable, stuff that value into
     the display area.  If the revised value is still to big, return
     nil, which indicates that the character is rejected.
   If the result of inserting the character is OK, update the memory
     display area with the character. */
Def updateMemoryDisplayUnit10(self, aChar |
      fieldIndex screenOffset displayUnitOffset diffIndex tempString patchSize)
{
  fieldIndex := getMemFieldIndex(self, xPos + leftChar);
    /* get display unit # */
  screenOffset := getMemFieldOffset(self, fieldIndex);
    /* where display unit starts on the screen */
  displayUnitOffset := (xPos + leftChar) - screenOffset;
    /* offset of character within display unit */

  /* create a temporary string with the character inserted */
  tempString := subString(workText[asInt(yPos+topLine)],
    screenOffset, screenOffset + memDisplaySize);
  put(tempString, aChar, displayUnitOffset);

  /* compare the strings; nil: immediately insert character;
     val > diffIndex: legal value can be created by zero fill to right;
     val <= diffIndex: illegal value */
  diffIndex := stringCompareToMax(self, memMaxDisplayUnit, tempString);
  select
    case not(diffIndex) is
      /* the input is valid; stuff the value and return 0 */
      put(workText[asInt(yPos+topLine)], aChar, leftChar + xPos);
      ^0;
    endCase;
    case diffIndex <= displayUnitOffset is
      /* input is illegal; return nil */
      ^nil;
    endCase;
  endSelect;
  /* otherwise, we have a repairable value; zero fill, and return zero */
  patchSize := memDisplaySize - diffIndex;  /* size of zero fill */
  tempString := replace(tempString, "000000000000", 0, patchSize,
    diffIndex, memDisplaySize);

  /* now stuff revised memory display unit into work text */
  workText[asInt(yPos+topLine)] := replace(workText[asInt(yPos+topLine)],
      tempString, 0, memDisplaySize,
      screenOffset, screenOffset + memDisplaySize);
  ^0;
}!!

/* update the memory display unit with the char. */
Def updateMemoryDisplayUnit16(self, aChar)
{
  /* hex values are always acceptable */
  put(workText[asInt(yPos+topLine)], aChar, asInt(xPos + leftChar));
  ^0;
}!!

/* check for valid radix, return approved char or nil if bad.
   Note: even though code implies hex character handling for
         decimal, the radix-specific asDigit call
         short-circuits illegal values. */
Def validChar(self, char | temp)
{
  if not(temp := asDigit(char, memRadix))
    ^nil;
  endif;
  select
    case temp >=0 cand temp <= 9
    is ^char;
    endCase
    case temp >= 10 cand temp <= 15
    is ^asUpperCase(char);
    endCase
  endSelect;
  ^nil;
}!!

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

/* Disable normal double-clicking */
Def WM_LBUTTONDBLCLK(self, wp, lp | yt gotoAddr)
{
  setCurPos(self, asPoint(lp));
  
  if readError?(self)
    hideCaret(self);
  endif;
  
  if cursorInAddrArea?(self)
    if readError?(self)
      gotoAddr := nil;
    else
      yt := max(0, y(asPoint(lp))-2)/tmHeight;
      gotoAddr := min((size(workText)-1) - topLine, yt);
    endif;
    doGoto(self, gotoAddr);
    cursorInAddrAreaClear(self);
  else
    /* beep if dclick not in address area */
    beep();
    ^nil;
  endif;
  ^0;
}!!

/* Disable normal button-down processing; only want to position cursor. */
Def WM_LBUTTONDOWN(self, wp, lp)
{
  setCurPos(self, asPoint(lp));
  
  /* pretend we ignored it... */
  if readError?(self)
    hideCaret(self);
    ^nil;
  endif;
  
  ^0;
}!!

/* Disable normal button-up processing; only want to position the cursor. */
Def WM_LBUTTONUP(self, wp, lp)
{
  setCurPos(self, asPoint(lp));
  
  /* pretend we ignored it... */
  if readError?(self)
    hideCaret(self);
    ^nil;
  endif;
  
  ^0;
}!!

/* Respond to MS-Window's vertical scrolling message. wP tells what kind of
  scrolling request has been made.
  Adjust screenTop to reflect the new top of screen.
  Adjust xPos and yPos to maintain the current virtual cursor location. */
Def WM_VSCROLL(self, wP, lP | vis tempMod rowCount displayPercent thumbPos)
{
  writeDirtyBytes(self, 0);
  vis := visLines(self);
  select
    /* move down one line if not at end */
    case wP == SB_LINEDOWN cand screenEnd < maxRow is
      desiredScreenTop := screenTop + 1L;
      yPos := yPos - 1;
      fixScreen(self);
    endCase
    /* move down a page (less 1 line) subject to remaining lines */
    case wP == SB_PAGEDOWN cand screenEnd < maxRow is
      desiredScreenTop := min(screenTop + vis - 1, maxRow - vis + 1);
      desiredScreenTop := asLong(desiredScreenTop);
      if yPos = (vis - 1) then
        yPos := 0;
      else
        xPos := yPos := 0;
      endif;
      fixScreen(self);
    endCase
    /* move up one line if not at top */
    case wP == SB_LINEUP cand screenTop > 0 is
      desiredScreenTop := screenTop - 1L;
      yPos := yPos + 1;
      fixScreen(self);
    endCase
    /* move down a page (less 1 line) if there is room */
    case wP == SB_PAGEUP cand screenTop > 0 is
      desiredScreenTop := max(0, screenTop - vis + 1);
      desiredScreenTop := asLong(desiredScreenTop);
      if yPos = 0 then
        yPos := vis - 1;
      else
        xPos := yPos := 0;
      endif;
      fixScreen(self);
    endCase
    /* move to relative position; do it funny to avoid over/under flow. */
    case wP == SB_THUMBPOSITION is
      rowCount := maxRow - minRow;
      tempMod := rowCount mod 100L;
      displayPercent := rowCount / 100L;
      thumbPos := asLong(low(lP));
      desiredScreenTop := (displayPercent*thumbPos) +
                   ((tempMod * thumbPos) / 100L);
      /* don't exceed last page */
      desiredScreenTop := min(maxRow - asLong(vis) + 1, desiredScreenTop);
      xPos := yPos := 0;
      fixScreen(self);
    endCase
  endSelect;
}!!

/* Write the workTextBytes array to the server; flush if indicated. */
Def writeDirtyBytes(self, flush | index byteCount writeStatus)
{
  /* exit if there are no dirty bytes */
  if not(dirtyByteRow) cor readError?(self)
    ^0;
  endif;
  
  showWaitCurs();
  
  /* stuff bytes into structure */
  byteCount := dirtyByteColMax - dirtyByteColMin + 1;
  index := 0;
  loop
  while index < byteCount
  begin
    putByte(byteArray,
      workTextBytes[asInt(dirtyByteRow)][asInt(dirtyByteColMin + index)],
      index);
    index := index + 1;
  endLoop;
  
  /* since we are invalidating the cache, ignore it one time only */
  ignoreMemoryChangeEvent := 0;
  
  /* write bytes out; last 3 args are start address, # bytes, bytes. */
  writeStatus := writeMem(
    memServer(parent),
    asLong(((workTextTop + dirtyByteRow) * 16) + dirtyByteColMin),
    byteCount,
    byteArray);
    
  /* clear out old dirty byte info */
  dirtyByteRow := dirtyByteColMin := dirtyByteColMax := nil;
  
  /* invalidate if there is a write error */
  if not(writeStatus)
    invalidateCache(self);
  endif;

  showOldCurs();
  
  ^0;  /* error reporting done by library */
}!!

/* MBrowser Class Initialization Code */

