-- ScenePrivate.mesa: Private definitions and unsynchronized operations on scenes
-- last modified by Stolfi - September 12, 1983 6:08 pm
-- To do: everything
DIRECTORY
Graphics USING [Context, Box];
ScenePrivate: CEDAR DEFINITIONS
= PRIVATE BEGIN OPEN Gr: Graphics;
-- PRIVATE DECLARATIONS - - - - - - - - - - - - - - - - - - - - - - - - -
Object: TYPE = REF ObjectRec;
Group: TYPE = REF group ObjectRec;
Thing: TYPE = REF thing ObjectRec;
ObjectRec: TYPE =
RECORD
[parent:
REF ←
NIL,
-- the parent
Object, or a
Scene, or
NIL.
next: Object ←
NIL,
-- next sibling, or first (bottommost) if last one.
props: List.AList ←
NIL,
-- property list for this object.
-- The two fields below are valid only in attached trees
box: BoundingBox ← emptyBox,
-- bounding box of the object (in client units).
var: SELECT type: * FROM
thing =>
[class: ObjectClass,
-- painter and bounder procedures, and default properties
parms:
PUBLIC REF ←
NIL
-- client-defined parameters of object.
],
group =>
[child: Object -- the last (topmost) child in painting order (NIL If none).
ENDCASE
];
-- An Object (of both types) carries the following private fields:
-- a link to its parent in the object tree. The root of a detached tree has NIL as parent. To save a pointer field, the parent link of an attached tree root points to the corresponding Scene record. In all other cases, the parent is a group.
-- a link to its next sibling in the painting order (first-to-last = back-to-front = bottom-to-top). The next link of the last sibling (the one to be painted on top of all others) points back to the first (bottom-most) one. The next field of a root points always to itself.
-- a list of properties which is used to store several oddball parameters for the object. Some of them have their meaning fixed by this interface (like $Color, $LineWidth, $Background); the Client can define others if it so desires. The client can consult and modify this list through the GetProp and SetProp procedures.
-- a bounding box which encloses its image (including those of its descendants, if the object is a group).
-- A group object also has a link to its last (topmost) child in painting order.
-- THING CLASSES - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ThingClass: TYPE = REF ThingClassRec;
ThingClassRec:
PRIVATE TYPE =
RECORD
[Painter: PainterProc,
-- a procedure that knows how to paint the object
Bounder: BounderProc,
-- kind, coordinates, color, style, &c of graphic object
props: List.AList
-- default properties for things of this class
];
-- A ThingClass record contains:
-- a Painter procedure that that knows how to paint the thing,
-- a Bounder procedure that that knows how to compute a bounding box for the object,
-- A list of default properties for all things of this class.
-- OBJECT PAINTING PROCEDURES - - - - - - - - - - - - - - - - - - - - - - -
PainterProc:
TYPE =
PROC [thing: Thing, context: Graphics.Context];
-- The Painter of each Thing is called to paint it on the given context.
-- The context passed to the Painter is the viewer's context, scaled as specified by the options in the Scene record. The coordinate system of the context depends only on the current viewer window size, on the bounding box of the root of the scene's object tree, and on the $Origin and $Scaling options specified in the root's property list. This coordinate system will be called the ``client'' system, as opposed to the ``device'' system (that of the raw viewer's context).
-- The Painter is called with (at least) read access to the thing's tree, and write access to the viewer's paint lock. Therefore, it should call only unsynchronized procedures from this interface..
-- The Painter procedure is also given access to the property list of the thing, the property list of the thing's class, and the property lists of all ancestors of the thing in the object tree. The GetProp procedure from this interface can search all these property lists in a single call as if they were concatenated in the order given above. Consistent use of this option by the Painter procedures gives ``scoping'' behavior to the property lists.
-- The context passed to the Painter will already have default color and line width equal to the ``most recent'' value of the properties $Color and $LineWidth (that is, as given by the lowest node, in the path from the root to the thing, in whose property list they occur). If an object (thing or group) has the $Background property, whose value is a Graphics.Color, the bounding box of the object is painted with that color before the object's Painter is called. If an object has the $Border property (also color-valued), a frame of that color and the current line width will be drawn all around the outside of its bounding box.
-- BOUNDING BOXES - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BoundingBox:
TYPE =
RECORD
[basic: Gr.Box,
-- bounding box of the object (in client units).
extraX, extraY:
REAL ← 0.0,
-- overscan: extra margin around box (in device units).
unbounded:
BOOL ←
FALSE
-- true if image is unbounded
]
-- The bounding boxes in the object tree are used to speed up repainting in face of localized changes. A bounding box consists of the following items:
-- the basic bounding box in the client coordinate system.
-- the extra x and y margins, in device units, by which the image of the thing may overflow the basic bounding box.
-- an unbouded flag, which is TRUE when the image of the object is actually infinite.
-- The ``true'' bounding box of a bounded thing is thus the basic one plus left and right margins extraX wide, and top and bottom margins extraY wide.
-- If the unbounded flag is true, the object's image is actually infinitely large. In this case the bounding box refers only to its ``interesting'' part, and is used only to select the scale of the client's coordinate system. For example, a Voronoi diagram object would better have unbounded=TRUE, with a bounding box barely enclosing its sites (plus perhaps its vertices and/or some extra margin all around). Any change in an unbounded thing will cause the whole viewer to be repainted.
-- The bounding boxes and overscan corrections of group objects are computed by the Scene package, and are automatically updated when the objects or the object tree are are modified by the client, as long as the latter uses only ``synchronized'' (and/or read-only) operations from this interface. (Wizard apprentices who would like to do more should first read the comments in the implementation module, and worry about maintaining the invariants described there.) A group that contains unbounded components is itself unbounded.
BounderProc:
TYPE =
PROC [thing: Thing]
RETURNS [box: BoundingBox];
-- The Bounder of each Thing is called by the implementation module to compute the thing's bounding box.
-- The Bounder of each thing must be able to compute its bounding box from the thing's parameter record and its current properties (proper or inherited; see GetProp), without knowing the relation between client and device units. This is why the basic box and the extra margins cannot be combined in a single bounding box. For example, the Bounder for a thing whose image is a line segment from (a,b) to (c,d) with labels on its endpoints should return the bounding box [a,c,b,d] (opportunely sorted), and an overscan correction equal to the maximum width and height of the labels.
-- The Bounder need not account in the overscan correction for the default line width of the context pased to the painter. The scene implementation module keeps track of that width and automatically adds it to the extraX and extraY returned by the Bounder. However, if the thing's Painter sets the line width of the context to some other value, the Bounder must account for that in the extraX and extraY it returns.
-- The Bounder is called with (at least) read access to the thing's tree. Therefore, it should call only unsynchronized procedures from this interface..
MakeThingClass:
PROC [Painter: PainterProc, Bounder: BounderProc, props: List.AList ←
NIL]
RETURNS [class: ThingClass];
-- Creates a new ThingClass record with given attributes and returns a pointer to it.
-- The contents of a ThingClass record should not be changed once created. This is because ThingClasses are usually shared among several trees, and the implementation module has no easy way to find out what should be updated if a class record were to be modified. To enforce this immutability, the ThingClass record is private, and it points to a copy of the property list props, rather than to the list itself.
-- UNSYNCHRONIZED, NOT SO NICE OPERATIONS - - - - - - - - - - - - - - - - - - - -
-- To use the operations below the client must acquire first the appropriate access rights to the scene(s) involved. Furthermore, the client must worry about restoring the bounding box invariant after any modifications. Also, it is the client's responsibility to schedule the appropriate viewer repaint requests.
-- The bounding box invariant says that the bounding box of a group is the smalles bounding ox enclosing its children, with the extra marging at least as large as the group's $LineWidth property. The bounding box for a thing is the one returned by its Bounder, plus the current $LineWidth. In either case, add another $LineWidth if the object has the $Border property. Bounding boxes of detached trees need not be consistent, however.
GetTree: PROC [scene: SceneViewer] RETURNS [tree: Tree];
-- Unsynchronized. Requires read access to the scene.
-- Obtains the object tree currently attached to the given scene. Returns NIL if the scene viewer has been destroyed.
RemoveTree:
PROC [
scene: SceneViewer]
RETURNS [tree:
Tree];
-- Unsynchronized. Requires write access to the scene.
-- Detaches object tree of the scene and returns it.
-- Does not recompute the bounding boxes and lineWidths of the detached tree. Does not schedule repaints.
SetTree:
PUBLIC
PROC [
scene: SceneViewer, tree: tree:
Tree] =
-- Unsynchronized. Requires write access to the scene sc.
-- Attaches the given object tree to the scene.
-- Does not recompute bounding boxes or lineWidths for the attached tree. Does not schedule repaints.
-- The tree should be a detached one, and the scene should have null object tree.
GetSceneViewer: PROC [object: OBject] RETURNS [scene: SceneViewer];
-- Unsynchronized. Requires read access to the object's scene.
-- Obtains the scene viewer to which the object is currently attached. Returns NIL if none.
Scope: TYPE = {local, root, path};
GetProp: PROC [object: Object, key: Atom, scope: Scope ← path] RETURNS [value: REF];
-- Unsynchronized. Requires read access to the object's tree.
-- Gets the value of the property named key for the given object. Returns NIL if the key is not found.
-- If scope=local, only the node's own property list is searched for the key. If scope=root, only the property listy of the object's root is searched. If scope=path, the property lists of all nodes in the path from the object up to the root (including these two), will be searched in sequence for the key, until a non-NIL value for it is found..
SetProp: PROC [object: Object, key: Atom, value: REF];
-- Unsynchronized. Requires write access to the object's tree.
-- Sets the value of the property named key for the given object. Removes the property if the value is NIL.
-- Does not recompute the bounding boxes of either tree. Does not schedule repaint of affected areas..
Remove:
PUBLIC
PROC [object: Object]
RETURNS [oldParent: Group, oldNext: Object] =
-- Unsynchronized. Requires write access to the object's tree.
-- The object is disconnected (with all its descendants) from its current parent. The subtree rooted at the object becomes a detached tree by itself. The object must not be a root.
-- The procedure returns the old parent node (oldParent) of the object, and the old sibling (oldNext) that immediately followed it in the painting order. If the object was the last (topmost) child of oldParent, returns oldNext=NIL.
-- Does not recompute the bounding boxes of either tree. Does not schedule repaint of affected areas.
Add:
PUBLIC
PROC [object: Object, newParent: Group, newNext: Object ←
NIL] =
-- Unsynchronized. Requires write access to the new parent's scene.
-- Adds object (with its descendants) one of the children of newParent, immediately before (below) the specified child object newNext. If newNext= NIL, object is inserted as the last (topmost) child of newParent. The given newNext (if not NIL) must be a child of newParent.
-- The object to be added must be a detached root, and the newParent cannot be NIL.
-- Does not recompute the bounding boxes. Does not repaint the affected areas.
RecomputeAncestorBoxes:
PUBLIC
PROC [dad: Group] =
-- Unsynchronized. Requires write access to the object's scene.
-- Recomputes the bounding boxes of the group dad (may be NIL) and all its ancestors, to account for changes in the bounding boxes of one or more of its children. It is assumed that the properties of dad and its ancestors, and the boxes of all its children, are now correct.
RecomputeBoxes:
PUBLIC
PROC [object: Object, lineWidth: REAL]
RETURNS [boxChanged:
BOOL] =
-- Unsynchronized. Requires write access to the object's scene.
-- Recomputes the bounding boxes of the object and all its descendants. Returns TRUE if new bounding box is different from old one.
-- To speed up the computation, requires that the $LineWidth property of the object be passed as a parameter.
RecomputeBox:
PUBLIC
PROC [object: Object]
RETURNS [changed:
BOOL] =
-- Unsynchronized. Requires write access to the object's scene.
-- Recomputes the bounding box of the object object. Assumes the children of object (if a Group) to have correct bounding boxes. Returns TRUE if new bounding box is different from old one.
GetLineWidth:
PUBLIC
PROC [object: Object]
RETURNS [lineWidth:
REAL] =
-- Unsynchronized. Requires read access to the node's scene.
-- Returns the default line width for an object, as specified by the nearest $LineWidth property in its path.
-- MISCELLANEOUS - - - - - - - - - - - - - - - - - - - - - - - - -
JoinBoxes:
PROC [box1, box2: BoundingBoxes]
RETURNS [box: BoundingBox];
-- Combines two bounding boxes into one.
-- PROCESS SYNCHRONIZATION - - - - - - - - - - - - - - - - - - - - - - - - - -
-- The nature of the viewer and menu mechanism is such that multiple processes sharing the same scene are inevitable. Therefore, no process should examine or modify the contents of a scene or object record without acquiring access rights to it. These are granted basically by the DoWithReadAccess and DoWithWriteAccess procedures below.
TreeAction: TYPE = PROC [ctx: Gr.Context];
Access: TYPE = {none, read, write};
DoWithAccess:
PROC
[Action: TreeAction, node:
REF, access: Access, getContext:
BOOL ←
FALSE];
-- Performs the given Action while holding the stated access rights to the entire tree containing the specified node (either a scene or an object).
-- If access=read, other (properly synchronized) process will be prevented from modifying the scene or its objects while the Action is being executed. If access=write, other processes will also be prevented from examining the scene attributes and its objects for the duration of the call. The option access=none provides no such protection, and is here only to provide access to the viewer's paintlock as described below.
-- If getContext is TRUE, the scene viewer's painter lock is also acquired for write for the duration of the action. The viewer's context (unclipped but scaled) is passed to the action, so the latter can paint on the screen using Cedar graphics. If getContext is FALSE, the viewer's painter lock is not acquired, and the action is called with ctx=NIL.
-- This procedure does not recompute automatically the bounding boxes in the tree nodes, and does not repaint the affected portions of the viewer, even if access=write. Therefore, it is here mainly for wizards' use. Normal clients should use the procedures like MoveObject, SetProp, GetProp, ModifyThing, and so forth, that automatically update the bounding boxes and repaint the affected areas.
-- Currently, DoWithReadAccess and DoWithWriteAccess are identical: both acquire a single global lock that controls access to all scenes. In the future maybe we will have separate multiple-read-access reentrant locks for each scene...
DoWithReadAccess:
PROC [Action: TreeAction, node:
REF, getContext:
BOOL ←
FALSE];
Equivalent to (but slightly faster than) DoWithAccess[..., access: read, ...].
DoWithWriteAccess:
PROC [Action: TreeAction, node:
REF, getContext:
BOOL ←
FALSE];
Equivalent to (but slightly faster than) DoWithAccess[..., access: write, ...].
DoWithContext:
PROC [Action: TreeAction, node:
REF];
Equivalent to DoWithAccess[..., access: none, getContext:TRUE ].
END.