/* CLASS: StackListBox
   Provide the specific capabilities of a list box that holds stack frame items.
   It's a child control of the StackPresenter class.

   REQUIRE: STACKPRE.CLS
*/!!

inherit(ListBox, #StackListBox, #(viewOptions /* display flag: 
0x1 = stack addres, 0x2 = code address,
0x4 = function's params */
framesDict /* dictionary of stack frame */
varLoaded /* collection of flag to load var */
totalFrames /* total number of displayable frames */
framesCached /* total frame already cached in the StackListBox */
doneCached /* boolean flag of cached buffer */
stackLabel /* label of stack frame elements */
lineNum /* vertical scroll position in list */
stackAddrSize /* maximum number of digits in stack address */
codeAddrSize /* maximum number of digits in code address */
), 2, nil)!!

setClassVars(StackListBox, #())!!

now(class(StackListBox))!!

/* 3/6/1992 10:35 - PUBLIC -- Create a new Stack list box */
Def new(self, id, par | theLB)
{ 
  theLB := new(self:Behavior);
  /* create object */
  setVars(theLB, id, par);
  create(theLB, nil, "ListBox", LBS_STANDARD bitOr WS_CHILD
    bitOr LBS_STANDARD_NOSORT bitOr WS_HSCROLL);
  /* Error if can not create */  
  if handle(theLB) = 0
  then alert(System, self, #wCreateError);
  endif;
  init(theLB);
  ^theLB;
}
!!

now(StackListBox)!!

/* 8/18/1994 14:25 
  Return an address descriptor of the stack address of the 
  specified stack frame.  Return nil if error.
  NOTES:
    - Caller is responsible to destroy the returned descriptor.
    - frameTxt = [stackAddr codeAddr Symbol]
      column position: [(0 to stackAddrSize-1)] 1 blank [(stackAddrSize+1 to (stackAddrSize+codeAddrSize+1)]...
*/
Def getStackAddress(self, frameId | frameTxt, addrTxt, addrDesc)
{ 
  if size(framesDict) > 0 cand
     (frameTxt := framesDict[frameId]) cand
     (addrTxt := subString(frameTxt,0, stackAddrSize)) then
     if (addrDesc := convertTextToAddress(AddressLibClass$Inst, addrTxt)) then  
       ^addrDesc;    
     endif;
  endif;       
  ^nil;
}
!!

/* 3/17/1994 14:10 - PUBLIC (to its parent)
  Return an address descriptor of the caller of the 
  specified stack frame.  Return nil if error.
  NOTES:
    - Caller is responsible to destroy the returned descriptor.
    - frameTxt = [stackAddr codeAddr Symbol]
      column position: [(0 to stackAddrSize-1)] 1 blank [(stackAddrSize+1 to (stackAddrSize+codeAddrSize+1)]...
*/
Def getCallerAddress(self, frameId | frameTxt, addrTxt, addrDesc)
{ 
  if size(framesDict) > 0 cand
     (frameTxt := framesDict[frameId]) cand
     (addrTxt := subString(frameTxt, stackAddrSize+1, stackAddrSize+codeAddrSize+1)) then
     if (addrDesc := convertTextToAddress(AddressLibClass$Inst, addrTxt)) then  
       ^addrDesc;    
     endif;
  endif;       
  ^nil;
}
!!

/* 3/6/1992 10:45 - PUBLIC --- Clear StackListBox */
Def clear(self)
{ 
  framesCached := 0; 
  doneCached := nil;
  totalFrames := 0;
  stackLabel := " ";
  clear(framesDict);
  clear(varLoaded);
  clearList(self);
}
!!

/* 3/6/1992 14:13 - PRIVATE 
  Given a Collection of Stack Frame - extract each frame and put into
  the display list according to its id
*/
Def extractFrames(self, frameColl, locID | i)
{ 
  /* Go through the collection and extract out its element to insert */
  do(frameColl,
    {using(idx) 
      add(framesDict, locID, idx);
      add(varLoaded, locID, nil);
      locID := locID + 1;
    });
  /* Free the collection */
  frameColl := nil;    
}
!!

/* 3/6/1992 10:54 - PUBLIC */
Def getSelStkFrame(self | selIdx)
{ 
  if (selIdx := getSelIdx(self)) <> nil
  then
    /* Check to cache some more stack frames into the list */
    if not(doneCached) cand (selIdx >= (framesCached - 5))
    then
      loadStackFrames(self);
      invalidateStackFrames(self);
    endif;
    ^selIdx; /* current stack frame ID */ 
  endif;
  ^nil;
}
!!

/* 3/11/1992 22:50 - PUBLIC 
  Return View options of stack frames
*/
Def getViewOptions(self)
{ 
  ^viewOptions;
}
!!

/* 3/6/1992 10:40 - PRIVATE */
Def init(self | addrDesc)
{
  framesDict  := new(Dictionary, 5);
  varLoaded   := new(Dictionary, 5); 
  viewOptions := 0;
  lineNum     := 0;
  clear(self);
  if not(stackAddrSize := addrWidth(parent)) then 
    stackAddrSize := 15; /* Default max number of digits in address */
  endif;
  if not(codeAddrSize := addrWidth(parent)) then 
    codeAddrSize := 15; /* Default max number of digits in address */
  endif; 
  sendMessage(self, WM_SETFONT, 
        Call GetStockObject(SYSTEM_FIXED_FONT), 1L);
}
!!

/* 3/11/1992 21:52 - PRIVATE 
  Parsing the str into code, stack address and symbol tokens to display.
  the addrSize depend upon the max output address value in PROC.DLL
  NOTES: str format = [stackAddr codeAddr Symbol]
     6/30/93 - all addresses will not have 0x prefix. - PIP for stack.  
     column position: [(0 to stackAddrSize-1)] 1 blank ...
                      [(stackAddrSize+1 to (stackAddrSize+codeAddrSize+1)] ...
                      1 blank [stackAddrSize+codeAddrSize+2]
*/
Def insertFrame(self, str, idx | tmpStr, hasSymbol, space1, space2  )
{ 
  /* str has symbol name when its length > (2 addresses + 1 space) */
  if (hasSymbol := (size(str) > (stackAddrSize+codeAddrSize+1) )) then 
     str := str + "(...)";
  endif;
  /*Hera 5/21/97*/
  space1 := indexOf(str,' ',0);
  space2 := indexOf(str,' ',space1+1);
  if space2 = nil then
     space2 := size(str);
  endif;

  select
    case viewOptions = 2 /* Include Code Address */
      str := subString(str, space1+1, size(str));
      stackLabel := "Return";
    endCase
    case viewOptions = 1 /* Include Stack Address */
      if hasSymbol then /* frame has symbol info */
        tmpStr := subString(str, 0, space1); 
        str := tmpStr + subString(str, space2, size(str));
      endif; 
      stackLabel := "Stack";
    endCase
    case viewOptions = 0 /* only function name */
      if hasSymbol then /* frame has symbol info */  
        /* Display symbol */
        str := subString(str, space2+1, size(str));
      else
        /* Display frame address if no symbol available */
        str := subString(str, 0, stackAddrSize);  /* no symbol */
      endif; 
      stackLabel := "Function"; 
    endCase
    default /* display all */
      stackLabel := left("Stack", stackAddrSize, " ") + " Return";
  endSelect;
  /* Other options 3 */
  insertString(self, str, idx);
}
!!

/* 3/11/1992 23:08 - PUBLIC 
  Invalidate the stack frames list to display according to its options
  Set up the address number of characters prior to inserting the frames.
*/
Def invalidateStackFrames(self | addrDesc )
{ 
  if size(framesDict) > 0
  then
    clearList(self);
    redrawOff(self);
    if not(addrDesc := getStackAddress(self, 0)) cor
      not(stackAddrSize := maxOutputAddrDigits(AddressLibClass$Inst,addrDesc)) then 
      stackAddrSize := 15; /* Default max number of digits in stack address */
    endif;
    if not(addrDesc := getCallerAddress(self, 0)) cor
      not(codeAddrSize := maxOutputAddrDigits(AddressLibClass$Inst,addrDesc)) then 
      codeAddrSize := 15; /* Default max number of digits in stack address */
    endif;
    do (framesDict,
      {using(id) 
        insertFrame(self, id, keyAt(framesDict,id));
      });
    redrawOn(self);
    invalidate(self);
  endif;  
}
!!

/* 3/15/1992 12:47 - PUBLIC ]
  Sender must ensure that frameID is valid.
*/
Def isVarLoaded?(self, frameID)
{ 
  ^varLoaded[frameID];
}
!!

/* 3/6/1992 10:50 - PUBLIC 
  Load stack frames into list box - Setup cache so that only the first
  20 frames are read in.
*/
Def loadStackFrames(self| startF, endF, fColl, openNum, anyFrame)
{ 
  showWaitCurs();
  if not(anyFrame := anyFrames(stackSession(parent))) cor 
      (anyFrame = BOOL_FALSE) then
    ^nil;
  endif;  
  /* For now use the openStack frame */
  totalFrames := openStack(stackSession(parent), 20);

  if not(totalFrames) cor (totalFrames = 0) cor doneCached
  then
     ^nil;
  endif;

  /* There are frames to cache, so do it */
  if (framesCached < totalFrames) then
     openNum := min(totalFrames - framesCached, 20);
     startF := framesCached; /* old frame value */
     framesCached := framesCached + openNum;
     endF := framesCached - 1; /* new frame value */ 
         
     loop
     while startF < framesCached 
     begin 
        /* Return nil if cannot get stack frames */
        if not(fColl := getFrame(stackSession(parent), startF, endF)) then
          ^nil;
        endif;  
        /* Extract frames from collection */
        extractFrames(self, fColl, startF);
        startF := startF + size(fColl);
     endLoop;
     /* Done caching - No more frame to read */
     if framesCached = totalFrames
     then
       doneCached := true;
     endif;
  endif;
  showOldCurs();
}
!!

/* 3/11/1992 16:58 - PUBLIC 
  Set the current selected stack frame to frame ID, and display its 
  variables. 
  Notes: Parent of self must have a valid VarBrowser and valid. Also,
  currentFrame of parent must be valid.
*/
Def setSelStkFrame(self, frameID )
{ 
  if frameID then
    setCurSel(self, frameID);
    showStackFrameVar(parent); /* Show the variables of frame via its parent */
  endif;
}
!!

/* 3/11/1992 21:29 - PUBLIC 
  Set view options of the stack frame
*/
Def setViewOptions(self, option)
{ 
  viewOptions := option; 
}
!!

/* 3/13/1992 9:10 - PUBLIC 
  Return the stack label of its view options setting.
*/
Def stackLabel(self)
{ 
  ^stackLabel;
}
!!

/* 3/13/1992 13:54 - PUBLIC */
Def totalFrames(self)
{ 
  if totalFrames then
    ^totalFrames
  endif;
  ^0; /* No frame */
}
!!

/* 3/15/1992 12:49 - PUBLIC */
Def varLoaded(self, frameID)
{ 
  varLoaded[frameID] := #true;
}
!!

/* 3/15/1992 20:41 - PRIVATE
 Return the number of visible text lines in window. - TextWindow Class
*/
Def visLines(self)
{ 
  ^(height(clientRect(self)) / tmHeight(parent));
}
!!

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

/* 3/12/1992 22:17 - WINDOWS
  Respond to MS-Window's vertical scrolling message.
  wP tells what kind of scrolling request has been made. 
*/
Def WM_VSCROLL(self, wp, lp | topIdx, endFrame, idx)
{ 
  /* Peek into message to handle caching */
  if (wp = SB_ENDSCROLL) cand not(doneCached) 
  then
    endFrame := framesCached - visLines(self) - 2;  
    if (topIdx := sendMessage(self, LB_GETTOPINDEX, 0, 0L)) > endFrame
    then
      loadStackFrames(self);
      invalidateStackFrames(self);
      if not(idx := currentFrame(parent)) then
        idx := 0;
        /* Set the currentFrame of parent to frame 0 */
        setCurrentFrame(parent, idx);
      endif;  
      /* Select the current stack frame */
      setSelStkFrame(self, idx);
      ^0
    endif;   
  endif;
  ^execWindowProc(self, #WM_VSCROLL, wp, lp);
}
 
!!
