/* CLI Presenter's transcript window, inherits scrolling and selection from
   Edit window.  Does not allow direct text input.  */!!

inherit(EditWindow, #CliNewTranscript, #(desiredTranscriptLines
   /* # transcript lines desired by user */
actualTranscriptLines
  /* # of lines in transcript for screen size */
eolNeededFlag
   /* T if last op write, F is last op newline */
scrolledToBottom
   /* T if transcript is at bottom (scroll OK) */
initComplete /* true when init complete */), 2, nil)!!

now(class(CliNewTranscript))!!

now(CliNewTranscript)!!

/* discard transcript lines beyond limit */
Def truncateTranscript(self | index)
{
  loop
  while size(workText) > actualTranscriptLines
  begin
    removeFirst(workText);
  endLoop;
}!!

/* 8/31/1994 16:47 */
Def setDesiredTranscriptLines(self, value)
{
  /* reject bad input */
  if not(value) cor (value < 0) cor (value > 1000)
    ^nil;
  endif;
  
  desiredTranscriptLines := value;
  calcActualTranscriptLines(self);
  truncateTranscript(self);
  ^0;
}
!!

/* 8/31/1994 16:47 */
Def getDesiredTranscriptLines(self)
{
  ^desiredTranscriptLines;
}
!!

Def calcActualTranscriptLines(self)
{
  actualTranscriptLines :=
    max(visLines(self)+1, desiredTranscriptLines);
}!!

/* Append the current text to the last line. */
Def addText(self, text | lastLine)
{
  lastLine := size(workText) - 1;
  workText[lastLine] := workText[lastLine] + expandTabs(text,4);
  eolNeededFlag := 0;
}
!!

/* Process MS-Window's character input message. */
Def charIn(self, wP, lP | rect)
{
  select
    /* if char is tab, switch to edit window */
    case (wP == VK_TAB) is
      setFocus(hEdit(parent));
      ^0;
    endCase;
  endSelect;
  ^0;
}!!

/* make sure cursor is on current transcript screen. */
Def cleanUpCursor(self)
{
  hideCaret(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;

  moveCaret(self);
  showCaret(self);
  ^0;
}!!

/* clear transcript window */
Def clearTranscript(self)
{
  cls(self);
  ^0;
}
!!

/* Dispatch menu choices, accelerators. */
Def command(self, wP, lP)
{ select
    case wP == EDIT_COPY
    is xCopy(self)
    endCase
    case high(lP) <> 1
    is ^0;
    endCase
    case wP == EDIT_PRIOR
    is ^WM_VSCROLL(self, SB_PAGEUP, 0);
    endCase
    case wP == EDIT_NEXT
    is ^WM_VSCROLL(self, SB_PAGEDOWN, 0);
    endCase
  default arrows(self, wP)
  endSelect;
  if isEmpty(workText)
  then initEditParms(self)
  endif;
  viewInsertPoint(self);
  ^0;
}!!

/* return true if eol needed */
Def eolNeeded(self)
{
  ^eolNeededFlag;
}
!!

/* Notify parent of focus. */
Def gotFocus(self, hWndPrev)
{
  transcriptGotFocus(parent);
  ^gotFocus(self:ancestor, hWndPrev);
}!!

/* initialize. */
Def init(self)
{
  actualTranscriptLines := 0;
  desiredTranscriptLines := CLI_DEFAULT_TRANSCRIPT_SIZE;
  eolNeededFlag := nil;
  init(self:ancestor);
  initComplete := 0;
}
!!

/* notify parent. */
Def losingFocus(self, hWndNew | temp)
{
  scrolledToBottom := nil;
  transcriptLostFocus(parent);
  temp := losingFocus(self:ancestor, hWndNew);
  ^temp;
}!!

/* Append a newline to the bottom.  Since this is the only place where
   lines are added, only need to remove one line when too many lines. */
Def newLine(self)
{
  add(workText, "");
  if (size(workText) > actualTranscriptLines)
    removeFirst(workText);
  endif;
  eolNeededFlag := nil;
  showLastLine(self);
}
!!

/* catch reSize message. */
Def reSize(self, wP, lP | stat)
{
  if not(initComplete)
    ^nil;
  endif;
  scrolledToBottom := nil;
  stat := reSize(self:ancestor, wP, lP);
  calcActualTranscriptLines(self);
  truncateTranscript(self);
  cleanUpCursor(self);
  ^stat;
}!!

/* Show last line at bottom of screen; scroll if possible. */
Def showLastLine(self | temp fixRect ttop tbot wt vl)
{
  wt := size(workText);
  vl := visLines(self);
  topLine := max(0, wt - vl);
  
  if scrolledToBottom
    if wt > vl
      /* OK to scroll */
      fixRect := clientRect(self);
      Call ScrollWindow(hWnd, 0, negate(tmHeight), 0, 0);
      setTop(fixRect, tmHeight*(vl - 2));
      Call InvalidateRect(hWnd, fixRect, 1);
    else
      /* just refresh the new line */
      fixRect := clientRect(self);
      ttop := tmHeight*(wt-2);
      tbot := ttop + tmHeight + 2;  /* 2 pixels for descenders */
      setTop(fixRect, ttop);
      setBottom(fixRect, tbot);
      Call InvalidateRect(hWnd, fixRect, 1);
    endif;
  else
    /* must refresh whole thing */
    invalidate(self);
    scrolledToBottom := 0;
  endif;
  
  /* execute loop only if lock obtained. */
  if checkMessageLock(cliServer(parent))
  
    loop
    while not(checkMessage())
    begin /* skip */ ;
    endLoop;
    
    checkMessageUnlock(cliServer(parent));
  endif;
  closePendingProcess(parent);
  
  /* if abort hit, throw the remaining stack away.  The only way that the
     CLIWorkspace can be set to not executing for this portion of code is
     if the user aborted the command.  Unfortunately, in this context, the
     abort() does not blow away all of the stack, because the stack contains
     the CLIWorkspace:charIn for the command, and the charIn for the escape
     key.  Blow away the remainder of the stack to complete command abort. */
  if parent cand hEdit(parent) cand not(executingCommand(hEdit(parent)))
    abort();
  endif;
}
!!

Def WM_HSCROLL(self, wP, lP)
{
  setFocus(self);
  ^WM_HSCROLL(self:ancestor, wP, lP);
}!!

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

Def WM_VSCROLL(self, wP, lP | stat)
{
  /* if we're busy, ignore operation */
  if cliBusy?(cliServer(parent))
    beep();
    ^nil
  endif;
  setFocus(self);
  stat := WM_VSCROLL(self:ancestor, wP, lP);
  cleanUpCursor(self);
  ^stat;
}!!
