-- Grapevine: Server display "hot" module

-- [Juniper]<Grapevine>MS>LogDisplayHot.mesa

-- Roy Levin
-- Andrew Birrell  11-Jun-81 14:46:51

DIRECTORY
  AltoDisplay USING [MaxBitsPerLine, MaxScanLines],
  BitBltDefs USING [AlignedBBTable, BBptr, BBTable, BBTableSpace, BITBLT],
  FontDefs USING [FontHandle, FontObject],
  InlineDefs USING [DIVMOD, HighHalf, LowHalf],
  KeyDefs USING [KeyBits, Keys],
  LogPrivateDefs USING [
    BitmapState, bmPointsPerLine, BMStatePtr, Cursor, cursorBM, DCBchainHead, DCBHandle,
    DCBnil, Face, Machine, NumberHouse, NumberHouseObject, ScreenPoints, topMargin],
  LogDefs USING [BS, CR, SP, TAB],
  Mopcodes USING [zJRAM, zKFCB, zLI0, zLIW, zPOP],
  ProcessDefs USING [
    Abort, Aborted, Detach, Milliseconds, MsecToTicks, Seconds,
    SecondsToTicks, SetTimeout],
  SDDefs USING [sULongDivMod],
  SegmentDefs USING [FileSegmentAddress, FileSegmentHandle, SwapIn, Unlock],
  StringDefs USING [AppendChar, AppendLongNumber, AppendNumber],
  TimeDefs USING [CurrentDayTime, PackedTime];

LogDisplayHot: MONITOR
  IMPORTS BitBltDefs, InlineDefs, ProcessDefs, SegmentDefs,
          StringDefs, TimeDefs
  EXPORTS LogDefs, LogPrivateDefs =

  BEGIN OPEN FontDefs, LogDefs, LogPrivateDefs;


  -- Global Variables --

  machineFlavor: PUBLIC Machine;
  houses: PUBLIC DESCRIPTOR FOR ARRAY OF NumberHouseObject;
  statsFont: PUBLIC FontHandle;
  scriptFont: PUBLIC FontHandle;
  startUpTime: PUBLIC TimeDefs.PackedTime;
  uptimeHouse: PUBLIC NumberHouseObject;
  typescriptOn: PUBLIC BOOLEAN;
  headingDCB, firstScriptDCB,
  lastScriptDCB, firstScriptLineDCB: PUBLIC DCBHandle;
  scriptCurrentDCB: PUBLIC DCBHandle;
  scriptBMStatePtr: PUBLIC BMStatePtr ← @scriptBMState;
  scriptBMState: BitmapState;

  maxCursor: Cursor =
    [104621B, 155112B, 125704B, 105112B,
     105121B, 000000B, 017770B, 010010B,
     012550B, 012550B, 012550B, 010150B,
     010150B, 010010B, 010010B, 017770B];
  minCursor: Cursor =
    [104442B, 154462B, 124452B, 104446B,
     104442B, 000000B, 017770B, 010010B,
     013250B, 013250B, 013250B, 013010B,
     013010B, 010010B, 010010B, 017770B];
  currentCursor: Cursor =
    [160000B, 103403B, 127616B, 167770B,
     007670B, 073550B, 174334B, 174676B,
     177676B, 073276B, 006234B, 074700B,
     175752B, 175752B, 175744B, 070704B];


  -- Miscellaneous Declarations --

  IllegalUseOfLog: PUBLIC ERROR = CODE;
  napInterval: ProcessDefs.Milliseconds = 50;


  -- Statistics Display Procedures (exported to LogPrivateDefs) --

  Displayer: PUBLIC ENTRY PROCEDURE [waitInterval: ProcessDefs.Seconds] =
    -- expects to be FORKed.  Loops until aborted, waiting for the argument
    -- interval, then updates the statistics display.
    BEGIN OPEN ProcessDefs, StringDefs;
    DisplayState: TYPE = {max, current, min};
    displayState: DisplayState ← current;
    savedDCB:    DCBHandle = DCBchainHead↑;
    onInterval:  CARDINAL = 60 -- seconds of display after key action --;
    offTime:     TimeDefs.PackedTime ←
                      TimeDefs.CurrentDayTime[]+onInterval;
    displayIsOn: BOOLEAN ← TRUE;
    Coords:      TYPE = RECORD[x,y: INTEGER];
    mouse:       POINTER TO Coords = LOOPHOLE[424B];
    cursor:      POINTER TO Coords = LOOPHOLE[426B];
    basicMouse:  Coords = [ x: MAX[0,headingDCB.indenting*16-32],
                            y: topMargin ];
    sleep:       CONDITION;
    cat:         PROCESS;

    DisplayOn: INTERNAL PROC = INLINE
      BEGIN
      offTime ← TimeDefs.CurrentDayTime[] + onInterval;
      IF NOT displayIsOn THEN BEGIN
        DCBchainHead↑ ← savedDCB;
        mouse↑ ← basicMouse;
        displayIsOn ← TRUE;
        NOTIFY sleep;
        END;
      END;

    PossibleDisplayOff: INTERNAL PROC = INLINE
      BEGIN
      IF TimeDefs.CurrentDayTime[] > offTime AND KeyDefs.Keys.Lock = up
      THEN BEGIN
        DCBchainHead↑ ← DCBnil;
        cursor↑ ← basicMouse;
        displayIsOn ← FALSE;
        displayState ← current; cursorBM↑ ← currentCursor;
        END;
      END;

    UpdateStatus: PROC = INLINE
      BEGIN OPEN TimeDefs;
      s: STRING = [13];
      bmState: BitmapState;
      AppendElapsedTime[s, CurrentDayTime[] - startUpTime];
      bmState ← BitmapState[
        origin: uptimeHouse.dcb.longBitmap,
        wordsPerLine: uptimeHouse.dcb.width,
        x: uptimeHouse.leftX, y: 0];
      ClearHouse[@uptimeHouse];
      PutStringInBitmap[s, statsFont, plain, @bmState]
      END;

    Cat: ENTRY PROCEDURE =
      BEGIN
      catNap:   CONDITION;
      oldKeys:  KeyDefs.KeyBits ← KeyDefs.Keys↑;
      oldMouse: Coords ← mouse↑;
      SetTimeout[@catNap, MsecToTicks[napInterval]];
      DO
        WAIT catNap[ ! Aborted => EXIT];
        IF KeyDefs.Keys↑ # oldKeys THEN
          BEGIN
          DisplayOn[];
          SELECT TRUE FROM
            KeyDefs.Keys.Red=down AND oldKeys.Red#down =>
              { displayState ← min; NOTIFY sleep };
            KeyDefs.Keys.Blue=down AND oldKeys.Blue#down =>
              { displayState ← max; NOTIFY sleep };
            KeyDefs.Keys.Yellow=down AND oldKeys.Yellow#down =>
              { displayState ← current; NOTIFY sleep };
          ENDCASE => NULL;
          oldKeys ← KeyDefs.Keys↑;
          END;
        IF mouse↑ # oldMouse THEN BEGIN
          oldMouse ← [x: MAX[0,MIN[mouse↑.x, AltoDisplay.MaxBitsPerLine]],
                      y: MAX[0,MIN[mouse↑.y, AltoDisplay.MaxScanLines]] ];
          mouse↑ ← oldMouse;
          DisplayOn[];
          END;
        IF displayIsOn
        THEN cursor↑ ← oldMouse
        ELSE BEGIN -- show we're alive! --
          IF cursor.x < AltoDisplay.MaxBitsPerLine-32
          THEN cursor.x ← cursor.x + 1
          ELSE cursor.x ← 16;
          END;
        ENDLOOP;
      END;
    SetTimeout[@sleep, SecondsToTicks[waitInterval]];
    Detach[cat ← FORK Cat];
    mouse↑ ← basicMouse;
    DO
      -- calculate new min/max, even if display is off --
      FOR i: CARDINAL IN [0..LENGTH[houses]) DO
        h: NumberHouse = @houses[i];
        WITH item: h SELECT FROM
          short =>
            BEGIN
            IF item.max < item.p↑ THEN item.max ← item.p↑;
            IF item.p↑ < item.min THEN item.min ← item.p↑;
            END;
          long =>
            BEGIN
            IF item.max < item.p↑ THEN item.max ← item.p↑;
            IF item.p↑ < item.min THEN item.min ← item.p↑;
            END;
          percent =>
            BEGIN
            pct: CARDINAL ← MIN[item.p↑, 100];
            IF item.max < pct THEN item.max ← pct;
            IF pct < item.min THEN item.min ← pct;
            END;
          ENDCASE;
        ENDLOOP;
      IF displayIsOn THEN PossibleDisplayOff[];
      IF displayIsOn THEN
        BEGIN
        SELECT displayState FROM
          min => cursorBM↑ ← minCursor;
          max => cursorBM↑ ← maxCursor;
          current => cursorBM↑ ← currentCursor;
        ENDCASE => NULL;
        UpdateStatus[];
        FOR i: CARDINAL IN [0..LENGTH[houses]) DO
          h: NumberHouse = @houses[i];
          current: LONG CARDINAL;
          s: STRING = [13];
          bmState: BitmapState;
          ClearHouse[h];
          WITH item: h SELECT FROM
            short => current ← SELECT displayState FROM
                max => item.max,
                min => item.min,
                ENDCASE => LOOPHOLE[item.p↑, CARDINAL];
            long => current ← SELECT displayState FROM
                max => item.max,
                min => item.min,
                ENDCASE => item.p↑;
            percent => current ← SELECT displayState FROM
                max => item.max,
                min => item.min,
                ENDCASE => MIN[item.p↑, 100];
            ENDCASE;
          AppendLongNumber[s, current, 10];
          IF h.format = percent THEN AppendChar[s, '%];
          bmState ← BitmapState[
            origin: h.dcb.longBitmap, wordsPerLine: h.dcb.width,
            x: h.leftX, y: 0];
          PutStringInBitmap[s, statsFont, plain, @bmState];
          ENDLOOP;
        END;
      WAIT sleep[ ! Aborted => BEGIN Abort[cat]; EXIT END];
      ENDLOOP;
    END; --Displayer

  PutStringInBitmap: PUBLIC PROCEDURE [
    s: STRING, font: FontHandle, face: Face, bmState: BMStatePtr] =
    -- computes the width in ScreenPoints of the string 's' presented in 'font' and 'face'.
    BEGIN
    i: CARDINAL;
    x: ScreenPoints;
    SELECT face FROM
      plain, italic =>
        FOR i IN [0..s.length) DO PaintChar[font, s[i], bmState]; ENDLOOP;
      bold =>
        FOR i IN [0..s.length) DO
          x ← bmState.x;
          PaintChar[font, s[i], bmState];
          bmState.x ← x + 1;
          PaintChar[font, s[i], bmState];
          ENDLOOP;
      ENDCASE;
    IF face = italic THEN -- italicize --NULL;
    END; --PutStringInBitmap

  AppendElapsedTime: PUBLIC PROCEDURE [s: STRING, pt: TimeDefs.PackedTime] =
    -- appends to 's' a time derived from 'pt' of the form:  1314:35:28.
    BEGIN OPEN StringDefs;
    Append2: PROCEDURE [s: STRING, v: CARDINAL] =
      BEGIN
      n, r: CARDINAL;
      [n, r] ← InlineDefs.DIVMOD[v, 10];
      AppendChar[s, n + '0];
      AppendChar[s, r + '0];
      END;
    n: LONG CARDINAL;
    mm, ss: CARDINAL;
    [n, ss] ← LongDivMod[pt, 60];
    [n, mm] ← LongDivMod[n, 60];
    AppendLongNumber[s, n, 10];
    AppendChar[s, ':];
    Append2[s, mm];
    AppendChar[s, ':];
    Append2[s, ss];
    END; --AppendElapsedTime

  Even: PUBLIC PROCEDURE [v: UNSPECIFIED] RETURNS [UNSPECIFIED] =
    -- rounds up 'v' to an even number.
    BEGIN RETURN[v + (v MOD 2)] END; --Even
  -- Typescript Procedures (exported to LogDefs) --

  WriteChar: PUBLIC ENTRY PROCEDURE [char: CHARACTER] =
    -- writes the argument character into the typescript.  Characters > 177C will be ignored and ASCII control characters (other than CR, TAB, SP, and BS) will be printed as though the sequence WriteChar['↑]; WriteChar[char+100B] had been executed.  CR, TAB, and SP cause appropriate white space to be introduced. WriteChar must be called after TypescriptOn.
    BEGIN Write[char]; END; --WriteChar

  WriteString: PUBLIC ENTRY PROCEDURE [s: STRING] =
    -- writes all characters of the string into the typescript using WriteChar.
    BEGIN i: CARDINAL; FOR i IN [0..s.length) DO Write[s[i]]; ENDLOOP; END;
  --WriteString

  WriteLine: PUBLIC PROCEDURE [s: STRING] =
    -- equivalent to WriteString[s]; WriteChar[CR].
    BEGIN WriteString[s]; Write[CR]; END; --WriteLine

  WriteDecimal: PUBLIC PROCEDURE [n: CARDINAL] =
    -- writes 'n' on the typescript as an unsigned, decimal quantity.
    BEGIN s: STRING = [5]; StringDefs.AppendNumber[s, n, 10]; WriteString[s]; END;
  --WriteDecimal


  -- Internal Procedures --

  ClearHouse: PROCEDURE [h: NumberHouse] =
    -- clears the bitmap rectangle corresponding to the numeric part of 'h'.
    BEGIN
    bbtSpace: ARRAY [0..SIZE[BitBltDefs.BBTable]] OF UNSPECIFIED;
    bbt: BitBltDefs.BBptr = Even[@bbtSpace];
    bbt↑ ←
      [ptrs: IF machineFlavor=dMachine THEN long ELSE short,
       pad: 0, sourcealt: FALSE, destalt: TRUE, sourcetype: gray,
       function: replace, unused: InlineDefs.HighHalf[h.dcb.longBitmap],
       dbca: InlineDefs.LowHalf[h.dcb.longBitmap], dbmr: h.dcb.width,
       dlx: h.leftX, dty: 0, dw: h.width, dh: h.dcb.height*2,
       sbca:, sbmr:, slx:, sty:,
       gray0: 0, gray1: 0, gray2: 0, gray3: 0,
       slbca:, dlbca: h.dcb.longBitmap];
    myBITBLT[bbt];
    END; --ClearHouse

  Write: PROCEDURE [char: CHARACTER] =
    -- see description under 'WriteChar'.
    BEGIN
    IF ~typescriptOn THEN ERROR IllegalUseOfLog;
    IF char < 040C THEN
      SELECT char FROM
        CR => BEGIN Scroll[]; RETURN END;
        TAB => BEGIN char ← SP; THROUGH [0..2) DO Write[char] ENDLOOP; END;
        -- temporary

        SP => NULL;
        BS => char ← '?; -- temporary

        ENDCASE => BEGIN Write['↑]; char ← char + 100B; END;
    IF CharWidth[scriptFont, char] + scriptBMStatePtr.x > bmPointsPerLine THEN
      Scroll[];
    PaintChar[scriptFont, char, scriptBMStatePtr];
    END; --Write

  Scroll: PROCEDURE =
    -- scrolls the typescript by one line.
    BEGIN
    IF scriptCurrentDCB.next ~= DCBnil THEN
      scriptCurrentDCB ← scriptCurrentDCB.next
    ELSE
      BEGIN
      dcb: DCBHandle ← firstScriptLineDCB;
      firstScriptDCB.next ← firstScriptLineDCB ← dcb.next;
      Zero[dcb.longBitmap, dcb.width*dcb.height*2];
      dcb.next ← DCBnil;
      lastScriptDCB.next ← dcb;
      scriptCurrentDCB ← lastScriptDCB ← dcb;
      END;
    scriptBMStatePtr↑ ← BitmapState[
      origin: scriptCurrentDCB.longBitmap, wordsPerLine: scriptCurrentDCB.width, x: 0,
      y: 0];
    END; --Scroll

  LongDivMod: PROCEDURE [num, den: LONG CARDINAL]
    RETURNS [q: LONG CARDINAL, r: CARDINAL] = MACHINE CODE
    BEGIN Mopcodes.zKFCB, SDDefs.sULongDivMod; Mopcodes.zPOP; END; --LongDivMod

  Zero: PUBLIC PROCEDURE [p: LONG POINTER, count: CARDINAL] =
    BEGIN
    -- zeros given number of words in XM --
    bbtSpace: ARRAY [0..SIZE[BitBltDefs.BBTable]] OF UNSPECIFIED;
    bbt: BitBltDefs.BBptr = Even[@bbtSpace];
    bbt↑ ←
      [ptrs: IF machineFlavor=dMachine THEN long ELSE short,
       pad: 0, sourcealt: FALSE, destalt: TRUE, sourcetype: gray,
       function: replace, unused: InlineDefs.HighHalf[p],
       dbca: InlineDefs.LowHalf[p], dbmr: 1--words--,
       dlx: 0, dty: 0, dw: 16--bits--, dh: count,
       sbca:, sbmr:, slx:, sty:,
       gray0: 0, gray1: 0, gray2: 0, gray3: 0,
       slbca:, dlbca: p];
    myBITBLT[bbt];
    END; --Zero

  myBITBLT: PROC[ptr: BitBltDefs.BBptr] = INLINE
    { IF machineFlavor=xmesa39
      THEN M5BITBLT[ptr] ELSE BitBltDefs.BITBLT[ptr] };

  M5BITBLT: PROC[ptr: BitBltDefs.BBptr] = MACHINE CODE
     -- works with XMesa 5 ROM's --
     { Mopcodes.zLI0;
       Mopcodes.zLIW, 411B/256, 411B MOD 256; Mopcodes.zJRAM};


-- The following is stolen from AlFont.mesa
-- Changed to handle XM bitmaps
-- Assumes the font is locked

  FileSegmentHandle: TYPE = SegmentDefs.FileSegmentHandle;

  AlFontObject: TYPE = RECORD [
    procs: FontObject,
    seg: FileSegmentHandle,
    lockCount: CARDINAL,
    height: CARDINAL];

  AlFontHandle: TYPE = POINTER TO AlFontObject;

  FHptr: TYPE = POINTER TO FontHeader;
  Fptr: TYPE = POINTER TO Font;
  FCDptr: TYPE = POINTER TO FCD;
  FAptr: TYPE = POINTER TO FontArray;
  FontArray: TYPE = ARRAY [0..255] OF FCDptr;

  Font: TYPE = MACHINE DEPENDENT RECORD [
    header: FontHeader,
    FCDptrs: FontArray, -- array of self-relative pointers to
    -- FCD's. Indexed by char value.
    -- font pointer points hear!
    extFCDptrs: FontArray -- array of self-relative pointers to
    -- FCD's for extentions. As large an
    -- array as needed.
    ];

  FontHeader: TYPE = MACHINE DEPENDENT RECORD [
    maxHeight: CARDINAL, -- height of tallest char in font (scan lines)
    variableWidth: BOOLEAN, -- IF TRUE, proportionally spaced font
    blank: [0..177B], -- not used
    maxWidth: [0..377B] -- width of widest char in font (raster units).
    ];

  FCD: TYPE = MACHINE DEPENDENT RECORD [
    widthORext: [0..77777B], -- width or extention index
    hasNoExtension: BOOLEAN, -- TRUE=> no ext.;prevfield=width
    height: [0..377B], -- # scan lines to skip for char
    displacement: [0..377B] -- displacement back to char bitmap
    ];

  CharWidth: PUBLIC PROCEDURE [font: FontHandle, char: CHARACTER]
    RETURNS [w: CARDINAL] =
    BEGIN
    code: CARDINAL;
    cw: FCDptr;
    fontdesc: FAptr; -- checkfor control characters
    IF char = CR THEN char ← SP;
    IF char < SP THEN RETURN[CharWidth[font, '↑] + CharWidth[font, char + 100B]];
    w ← 0;
    fontdesc ← @LockFont[font].FCDptrs;
    code ← LOOPHOLE[char];
    DO
      cw ← LOOPHOLE[fontdesc[code] + LOOPHOLE[fontdesc, CARDINAL] + code];
      IF cw.hasNoExtension THEN EXIT;
      w ← w + 16;
      code ← cw.widthORext;
      ENDLOOP;
    w ← w + cw.widthORext;
    UnlockFont[font];
    RETURN
    END;

  CharHeight: PUBLIC PROCEDURE [font: FontHandle, char: CHARACTER]
    RETURNS [CARDINAL] = BEGIN RETURN[LOOPHOLE[font, AlFontHandle].height] END;

  PaintChar: PROCEDURE [
    font: FontHandle, char: CHARACTER, bmState: POINTER TO BitmapState] =
    BEGIN OPEN BitBltDefs, bmState;
    bba: BitBltDefs.BBTableSpace;
    bbt: BitBltDefs.BBptr = BitBltDefs.AlignedBBTable[@bba];
    cw: FCDptr;
    fontdesc: FAptr = @LockFont[font].FCDptrs;
    code: CARDINAL ← LOOPHOLE[char];
    bbt↑ ←
      [ptrs: IF machineFlavor=dMachine THEN long ELSE short,
       sourcetype: block, function: paint,
       dbca: InlineDefs.LowHalf[origin], dbmr: wordsPerLine,
       destalt: TRUE, unused: InlineDefs.HighHalf[origin],
       dlx: x, dw: 16, sbmr: 1, slx: 0, sty: 0,
       dlbca: origin];
    DO
      cw ← LOOPHOLE[fontdesc[code] + LOOPHOLE[fontdesc, CARDINAL] + code];
      bbt.dty ← y + cw.height;
      bbt.dh ← cw.displacement;
      bbt.sbca ← cw - (bbt.dh ← cw.displacement);
      bbt.slbca ← LONG[bbt.sbca];
      IF cw.hasNoExtension THEN
	BEGIN x ← x + (bbt.dw ← cw.widthORext); myBITBLT[bbt]; EXIT END
      ELSE BEGIN myBITBLT[bbt]; bbt.dlx ← x ← x + 16; END;
      code ← cw.widthORext;
      ENDLOOP;
    UnlockFont[font];
    RETURN
    END;

  LockFont: PROCEDURE [font: FontHandle] RETURNS [Fptr] =
    BEGIN OPEN SegmentDefs, af: LOOPHOLE[font, AlFontHandle];
    IF (af.lockCount ← af.lockCount + 1) = 1 THEN SwapIn[af.seg];
    RETURN[FileSegmentAddress[af.seg]]
    END;

  UnlockFont: PROCEDURE [font: FontHandle] =
    BEGIN OPEN SegmentDefs, af: LOOPHOLE[font, AlFontHandle];
    IF (af.lockCount ← af.lockCount - 1) = 0 THEN
      BEGIN Unlock[af.seg]; af.seg.inuse ← TRUE END;
    RETURN
    END;

-- End of stolen section



  -- Main Body --

  NULL;

  END.
Created by Levin on February 6, 1980  5:48 PM.
Changed by Levin on February 11, 1980  1:56 PM, max/min logic.
Changed by Levin on February 17, 1980  5:59 PM, improve mouse transition logic.
Changed by Levin on February 18, 1980  3:45 PM, move cursor declarations to LogPrivateDefs.
Changed by Birrell on May 12, 1980  6:05 PM, WriteLine not an ENTRY procedure.
Changed by Levin on May 20, 1980  6:19 PM, fix 1000-hour bug in AppendElapsedTime.
Changed by Birrell on  2-Jun-81 14:51:03