MODULE ObxTickers;
(**

   project   = "BlackBox"
   organization   = "www.oberon.ch"
   contributors   = "Oberon microsystems"
   version   = "System/Rsrc/About"
   copyright   = "System/Rsrc/About"
   license   = "Docu/BB-License"
   changes   = ""
   issues   = ""

**)

   IMPORT Views, Ports, Properties, Services, Stores, Models;

   CONST minVersion = 0; maxVersion = 0;

   TYPE

      Ticks = POINTER TO ARRAY OF INTEGER;
      View = POINTER TO RECORD (Views.View)

         ticks: Ticks;
         last: INTEGER;
         offset: INTEGER
      END;
      Action = POINTER TO RECORD (Services.Action) END;

      Msg = RECORD (Models.Message)

         consumed: BOOLEAN
      END;
   VAR

      action: Action;
      actionIsActive: BOOLEAN;
      seed: INTEGER;
   PROCEDURE UniRand (): REAL;

      CONST a = 16807; m = 2147483647; q = m DIV a; r = m MOD a;
   BEGIN
      seed := a * (seed MOD q) - r * (seed DIV q);
      IF seed <= 0 THEN seed := seed + m END;
      RETURN seed * (1.0 / m)
   END UniRand;
   PROCEDURE (a: Action) Do;

      VAR msg: Msg;
   BEGIN
      msg.consumed := FALSE;
      Views.Omnicast(msg);
      IF msg.consumed THEN
         Services.DoLater(action, Services.Ticks() + Services.resolution DIV 5)
      ELSE
         actionIsActive := FALSE
      END
   END Do;
   (* view *)

   PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);

      VAR i, len: INTEGER;
   BEGIN
      wr.WriteVersion(maxVersion);
      wr.WriteInt(v.offset);
      wr.WriteInt(v.last);
      len := LEN(v.ticks); wr.WriteInt(len);
      FOR i := 0 TO len - 1 DO wr.WriteInt(v.ticks[i]) END
   END Externalize;
   PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);

      VAR version, i, len: INTEGER;
   BEGIN
      rd.ReadVersion(minVersion, maxVersion, version);
      IF ~rd.cancelled THEN
         rd.ReadInt(v.offset);
         rd.ReadInt(v.last);
         rd.ReadInt(len); NEW(v.ticks, len);
         FOR i := 0 TO len - 1 DO rd.ReadInt(v.ticks[i]) END
      END
   END Internalize;
   PROCEDURE (v: View) CopyFromSimpleView (source: Views.View);

      VAR i, len: INTEGER;
   BEGIN
      WITH source: View DO
         v.offset := source.offset;
         v.last := source.last;
         len := LEN(source.ticks);
         NEW(v.ticks, len);
         FOR i := 0 TO len - 1 DO v.ticks[i] := source.ticks[i] END
      END
   END CopyFromSimpleView;
   PROCEDURE (v: View) HandlePropMsg (VAR msg: Properties.Message);

   BEGIN
      WITH msg: Properties.SizePref DO
         IF (msg.w = Views.undefined) OR (msg.h = Views.undefined) THEN
            msg.w := 50 * Ports.mm; msg.h := 10 * Ports.mm
         END
      ELSE
      END
   END HandlePropMsg;
   PROCEDURE (v: View) HandleModelMsg (VAR msg: Models.Message);

      VAR len, m: INTEGER;
   BEGIN
      WITH msg: Msg DO
         len := LEN(v.ticks);
         m := v.ticks[v.last];
         IF UniRand() > 0.5 THEN INC(m) ELSE DEC(m) END;
         v.last := (v.last + 1) MOD len;
         v.ticks[v.last] := m;
         msg.consumed := TRUE;
         Views.Update(v, Views.keepFrames)
      ELSE
      END
   END HandleModelMsg;
   PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: INTEGER);

      VAR w, h, x, y0, ylast, p, p1, len, len2, i, j: INTEGER; ticks: Ticks;
   BEGIN
      IF ~actionIsActive THEN
         Services.DoLater(action, Services.now);
         actionIsActive := TRUE
      END;
      v.context.GetSize(w, h);
      len := LEN(v.ticks); len2 := 1 + w DIV f.dot;
      IF len2 > len THEN   (* allocate new array and copy data *)
         NEW(ticks, len2);
         i := 0; WHILE i <= v.last DO ticks[i] := v.ticks[i]; INC(i) END;
         j := i + len2 - len; WHILE i < len DO ticks[j] := v.ticks[i]; INC(i); INC(j) END;
         v.ticks := ticks; len := len2
      END;
      (* adjust offset so that the last point is visible *)

      ylast := (v.ticks[v.last] + v.offset) * f.dot;
      IF ylast > h DIV 2 THEN DEC(v.offset)
      ELSIF ylast < -h DIV 2 THEN INC(v.offset)
      END;
      p := (v.last - (len2 - 1)) MOD len;   (* index of first entry *)

      x := 0; y0 := h DIV 2 + v.offset * f.dot;
      WHILE p # v.last DO
         p1 := (p + 1) MOD len;
         f.DrawLine(x, y0 + v.ticks[p] * f.dot, x + f.dot, y0 + v.ticks[p1] * f.dot, 0, Ports.red);
         x := x + f.dot; p := p1
      END;
      f.DrawLine(l, y0, r, y0, 0, Ports.black)
   END Restore;
   (* commands *)

   PROCEDURE Deposit*;

      VAR v: View;
   BEGIN
      NEW(v); NEW(v.ticks, 142);
      v.last := 0; v.offset := 0;
      Views.Deposit(v)
   END Deposit;
BEGIN

   NEW(action); actionIsActive := FALSE; seed := SHORT(Services.Ticks())
END ObxTickers.