MODULE ScreenPlot; (* In addition to the client and screen systems, there is the "plot" system, where the scale [-1 _ +1] corresponds to the range [min _ max] specified by the client. *) IMPORT VBT, ScreenPlotVBT, LR3, LR4; <* PRAGMA LL *> TYPE Coords = ARRAY OF LR3.T; REVEAL T = Public BRANDED OBJECT vbt: ScreenPlotVBT.T; kicking: BOOLEAN := FALSE; (* This field is a local cache of "t.vbt.alive()", to reduce the cost of method calls after the window dies. It may lag behind the truth, though... *) stopped: BOOLEAN; (* Set by "pause" and reset by "resume". It is meant to mirror the current (presumed) state of "t.vbt"'s painter thread. It will be correct since the only calls to "t.vbt.setPainting" are made by the "pause" and "resume" methods of this object. *) needsRepainting: BOOLEAN; (* Set by the procedures that change the data structures. Its state is relevant only while "t.stopped = TRUE". It tells "Resume" that a repaint is required. *) c: REF Coords; shift: LR3.T; (* Client-to-plot displacement *) scale: LR3.T; (* Client-to-plot scaling (after shift) *) nItems: CARDINAL; OVERRIDES init := Init; alive := Alive; setCoords := SetCoords; setCoord := SetCoord; setScale := SetScale; point := AddPoint; segment := AddSegment; pause := Pause; resume := Resume; waitKey := WaitKey; waitDone := WaitDone; END; PROCEDURE Init(t: T; nPoints: CARDINAL): T = BEGIN t.vbt := NEW(ScreenPlotVBT.T).init(); t.c := NEW(REF Coords, nPoints); WITH c = t.c^ DO FOR i := 0 TO nPoints-1 DO c[i] := LR3.T{0.0d0,..} END END; t.kicking := TRUE; t.stopped := FALSE; t.needsRepainting := FALSE; t.shift := LR3.T{0.0d0, ..}; t.scale := LR3.T{1.0d0, ..}; RETURN t END Init; PROCEDURE Alive(t: T): BOOLEAN = BEGIN IF t.kicking THEN t.kicking := t.vbt.alive() END; RETURN t.kicking END Alive; PROCEDURE Pause(t: T) = BEGIN IF NOT t.kicking THEN RETURN END; <* ASSERT NOT t.stopped *> t.vbt.setPainting(FALSE); t.stopped := TRUE; t.needsRepainting := FALSE; END Pause; PROCEDURE Resume(t: T) = BEGIN <* ASSERT t.stopped *> t.stopped := FALSE; IF t.needsRepainting THEN t.vbt.forceRepaint(); t.needsRepainting := FALSE END; (* Now is a good time to refresh "t.kicking": *) IF NOT Alive(t) THEN RETURN END; t.vbt.setPainting(TRUE) END Resume; PROCEDURE WaitKey(t: T): KeySym = BEGIN IF NOT t.kicking THEN RETURN VBT.NoKey END; RETURN t.vbt.waitKey() END WaitKey; PROCEDURE WaitDone(t: T) = BEGIN IF NOT t.kicking THEN RETURN END; <* ASSERT NOT t.stopped *> t.vbt.waitDone() END WaitDone; PROCEDURE SetScale(t: T; axis: Axis; min, max: LONG) = VAR wasStopped: BOOLEAN := t.stopped; BEGIN IF NOT t.kicking THEN RETURN END; IF NOT t.stopped THEN Pause(t) END; WITH j = ORD(axis), m = (max+min)/2.0d0, r = (max-min)/2.0d0 DO t.shift[j] := - m; t.scale[j] := 1.0d0/r END; t.needsRepainting := TRUE; IF NOT wasStopped THEN Resume(t) END; END SetScale; PROCEDURE SetCoords(t: T; start: CARDINAL; READONLY c: Coords) = VAR wasStopped: BOOLEAN := t.stopped; BEGIN (* Now is a good time to refresh "t.kicking": *) IF NOT Alive(t) THEN RETURN END; IF NOT t.stopped THEN Pause(t) END; WITH N = NUMBER(c) DO EnsureExists(t.c, start + N - 1); SUBARRAY(t.c^, start, N) := c END; t.needsRepainting := TRUE; IF NOT wasStopped THEN Resume(t) END; END SetCoords; PROCEDURE SetCoord(t: T; p: CARDINAL; READONLY c: LR3.T) = VAR wasStopped: BOOLEAN := t.stopped; BEGIN IF NOT t.kicking THEN RETURN END; IF NOT t.stopped THEN Pause(t) END; EnsureExists(t.c, p); t.c[p] := c; t.needsRepainting := TRUE; IF NOT wasStopped THEN Resume(t) END; END SetCoord; PROCEDURE EnsureExists(VAR tc: REF Coords; p: CARDINAL) = BEGIN IF tc = NIL OR p >= NUMBER(tc^) THEN WITH N = NUMBER(tc^), NNew = MAX(p + 1, N + MIN(N DIV 2, 10000)), tcNew = NEW(REF Coords, NNew) DO SUBARRAY(tcNew^, 0, N) := tc^; tc := tcNew END END; END EnsureExists; PROCEDURE AddSegment(t: T; p, q: CARDINAL; width: REAL) = BEGIN IF NOT t.kicking THEN RETURN END; WITH seg = NEW(SegmentItem, plot := t, p := p, q := q, width := ROUND(width)) DO t.vbt.setItem(t.nItems, seg) END; INC(t.nItems); END AddSegment; PROCEDURE AddPoint(t: T; p: CARDINAL; size: REAL) = BEGIN IF NOT t.kicking THEN RETURN END; WITH seg = NEW(PointItem, plot := t, p := p, size := ROUND(size)) DO t.vbt.setItem(t.nItems, seg) END; INC(t.nItems); END AddPoint; (* DISPLAY LIST ITEMS *) TYPE PlotItem = ScreenPlotVBT.Item OBJECT plot: T; (* The parent plot *) END; PointItem = PlotItem OBJECT p: CARDINAL; (* Position *) size: CARDINAL; (* In pixels *) OVERRIDES paintSelf := PaintPoint; END; SegmentItem = PlotItem OBJECT p, q: CARDINAL; (* Endpoints *) width: CARDINAL; (* In pixels *) OVERRIDES paintSelf := PaintSegment; END; PROCEDURE PaintSegment(s: SegmentItem; v: ScreenPlotVBT.T) = <* LL.sup = VBT.mu *> BEGIN WITH p = PlotPointFromClientPoint(s.p, s.plot), q = PlotPointFromClientPoint(s.q, s.plot) DO v.line(p, q, s.width) END END PaintSegment; PROCEDURE PaintPoint(s: PointItem; v: ScreenPlotVBT.T) = <* LL.sup = VBT.mu *> BEGIN WITH ctr = PlotPointFromClientPoint(s.p, s.plot) DO v.dot(ctr, s.size) END END PaintPoint; PROCEDURE PlotPointFromClientPoint(p: CARDINAL; t: T): LR4.T = <* LL.sup = VBT.mu *> BEGIN WITH c = LR3.Weigh(t.scale, LR3.Add(t.c[p], t.shift)) DO RETURN LR4.T{1.0d0, c[0], c[1], c[2]} END END PlotPointFromClientPoint; BEGIN END ScreenPlot.