MODULE ObxTickers;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- YYYYMMDD, nn, ...
	"
	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.
