<<-- SceneJunk.mesa: Junk from scene sources>> <<-- last modified by Stolfi - August 13, 1983 4:52 am>> DIRECTORY Rope USING [ROPE]; SceneJunk: CEDAR DEFINITIONS = BEGIN <<-- The Objects in a Scene are organized in an tree structure. Facilities are provided to add, delete, move, or modify Objects or Groups. The implementation keeps track automatically of the bounding boxes of each Group, so that, when some component of the Scene is modified, only the affected part of the image is repainted.>> <<-- The implementation provides also flexible scaling facilities. For example, a Group can specify that the coordinate system be uniformly scaled and translated so that its bounding box fits snugly into the current viewer window. Forthcoming is a facility for "blowing up" a selected portion of the Scene. Client-defined objects can use all primitives of CedarGraphics. The tree structure also allows more efficient repainting of the viewer in case of localized changes. >> <<-- The implementation makes it rather easy to keep the drawing scaled so as to fit into the current viewer window, with automatic rescaling and repainting when the latter changes. Forthcoming is a facility for "blowing up" a selected portion of the Scene. Client-defined objects can use all primitives of CedarGraphics. >> <<-- Besides the standard menu items (Destroy, Adjust, Top, <--, --> Grow, Close), the client can attach to the viewer other procedures to be called when specific menu items are selected, or when specific mouse buttons are clicked.>> <<-- In addition to the set of objects being displayed, a Scene posesses a list of client-defined properties, which may be used to store data that is global to all objects but local to the Scene. For example, this allows menu and mouse procedures to communicate with each other and with the procedures that paint objects on the screen. Properties consist of pairs (key, value) where the key is an atom and the value a REF ANY.>> <<-- A Scene also implements an interlocking mechanism for synchronizing the painting processes, the menu and mouse routines (that are called as forked processes) and the client program. This mechanism is implemented by the Acquire and Release procedures; it is somewhat awkward, but the standard MESA LOCKs aren't much easier to use, would not give complete protection, and would create narrower bottlenecks. >> <<-- PROCESS SYNCHRONIZATION - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> Access: TYPE = {read, write, none}; -- Possible access rights. Change: TYPE [1]; -- Records a change in access rights for later undoing. noChange: READONLY Change; -- A null change in access rights GetReadRights: PROC [sc: Scene] RETURNS [c: Change]; GetWriteRights: PROC [sc: Scene] RETURNS [c: Change]; GetRights: PROC [sc: Scene, wants: Access] RETURNS [c: Change] = INLINE BEGIN RETURN [SELECT wants FROM read => GetReadRights[sc], write => GetWriteRights[sc] ENDCASE => noChange] END; <<-- At any instant, the Scene is (a) completely available, (b) acquired by one or more processes for reading, or (c) acquired by exactly one process for writing. GetWriteRights [sc] will wait until the Scene becomes completely available; GetReadRights[sc] will wait until no one is modifying it.>> <<-- The result of these two procedures is a record of the change made in the access rights of the calling process relative to the drawing sc. This record is used with Release (see below) to undo the change and revert to previously held access rights. For every invocation c _ GetReadRights[sc] or c _ GetWriteRights[sc], a process P must execute a corresponding call Release[sc, c], and these calls must be nested in the proper last-in-first-out order; otherwise chaos and deadlocks will result. Therefore, client procedures that acquire any access rights should catch UNWIND signals and perform the appropriate Release before exiting through ANY path.>> Release: PROC [sc: Scene, c: Change]; <<-- Release[sc, c] restores the access rights of the calling process to what they weere before the change c (which MUST be the result of the last unReleased call to GetReadRights[sc, c] or GetWriteRights[sc, c].>> <<-- AFTER August 9, 1983 2:50 pm - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- Synchronization among the several processes that manipulate the scene and its Object tree is effected by two locks: the system's viewer lock (that controls access to the scene viewer), and the global scene lock (that controls access to alll scenes). The connection between a scene and its viewer is controlled by both locks, so a process using/modifying that connection must acquire both locks. When this is the case, the scene lock must always be acquired first. >> DoWithSceneLock: PROC [action: SceneAction, obj: Object]; <<-- Performs the given action while holding the global scene lock. >> GraphicSceneAction: TYPE = PROC [obj: Object, ctx: Gr.Context]; DoWithSceneAndViewerLock: [Action: GraphicSceneAction, obj: Object]; <<-- Performs the given action while holding the global scene lock and also the paintlock of the viewer to which the object's tree is attached. The context of that viewer is passed to the action. If the object is in a detached tree, or the viewer is closed or iconic, the action is not executed.>> <>>The basic mechanism for acquiring that lock is by the DoUnderReadLock and DoUnderWriteLock procedure in this interface. To use it, the client shoul write his To call many of the procedures in this interface, as well as to directly access fields in the scene and object records, the client should first acquire that lock, either by using one of the pre-packaged DoUnderLock provides a convenient mechanism for that purpose. If no such requirements are stated, the procedure itself acquires the necessary locks and guards, so its effect is always an indivisible operation. >> <<-- <<>> To modify the parameters of an existing object obj and get the change to be manifested on the screen, the client has basically four options:>> <<-- (0) acquire write rights to the drawing, modify the parameters of the obj, release the rights, and do a RePaintAll on the scene within the object's bounding box (or have some independent process do it periodically). This may be too time-consuming for complex Drawings, and assumes the bounding box of the object is not going to be affected by the change.>> <<-- (1) acquire write rights to the drawing, call Hide [obj], modify its parameters, call Show [obj], and release the rights. This may be reasonable in a complex procedure that has to acquire the write rights anyhow, and/or modifies several objects simultaneously. >> <<-- (2) call Remove [obj], modify its parameters, and Add it back again. The caller must be sure that the obj is still in the Scene's list by the time the Remove is done (otherwise he may get an error); in the extreme cases, the caller may have to hold read access (at least) continuously in the interval between the Add and the next Remove.>> <<-- (3) write an ObjectActionProc P that does the modification, and call ModifyObject[obj, P]. This particularly useful in conjunction with ModifyAll.>> <<-- Note that SetStyle is the simplest way to change only the color, size and/or style of an object. >> <<-- a read-only visibility flag that, when reset, causes the Object (and all its descendants) to be ignored by the Scene painting procedures. It is a quick way of making an object disappear from view temporarily, without all the expensive pointer manipulations needed to remove or add it from the tree structure.>> <> MakeThing: PROC [class: ThingClass, parms: REF, visible: BOOL _ TRUE] RETURNS [thing: Thing]; MakeGroup: PROC [visible: BOOL _ TRUE] RETURNS [group: Group]; Hide: Proc[obj: Object]; <> Show: Proc[obj: Object]; <> MenuActionEntry: TYPE = RECORD [name: ROPE, Proc: MenuProc, menuData: REF, needs: Access, -- to be acquired before calling the Proc needsContext: BOOL -- true if the Proc needs the viewer's context. ]; groupClass: ObjectClass = NIL; ModifyObjects: PUBLIC PROC [obj: Object, Action: ObjectModifyProc, getContext: BOOL _ FALSE] = <<-- Synchronized>> BEGIN DoModifyThing: TreeAction = BEGIN sc: Scene = UnS.GetScene[thing]; changed: BOOL; [thing.parms, changed] _ Action[thing, context]; IF changed AND sc # NIL THEN BEGIN UnS.ScheduleRepaint[sc, thing.box]; -- repaint old box... boxChanged _ UnS.RecomputeBox[thing]; IF boxChanged THEN {IF thing.parent IS Group THEN UnS.RecomputeAncestorBoxes[NARROW[thing.parent]]; UnS.ScheduleRepaint[sc, thing.box]} -- repaint new box too END END; DoWithWriteAccess[thing, DoModifyThing, getContext]; END; <<-- a property list. Among other things specifies how the objects should be scaled to fit the viewer (if any). It also provides a way for mouse and menu procedures to communicate with each other and with the object painting procedures.>> RemoveObject: PROC [obj: Object] RETURNS [oldParent: REF, oldNext: Object]; <<-- If obj is an attached root, detaches it from the scene (so the scene's tree becomes NIL). In that case, returns the scene as the oldParent, and oldNext=NIL.>> InsertObject: PROC [object: Object, newParent: REF, newNext: Object _ NIL]; <<-- If newParent is a scene, sets the given object as the scene's tree. In this case the original tree of newParent must be NIL, and newNext too must be NIL.>> ModifyThing: PROC [thing: Thing, Modifier: ParmsModifier]; <<-- If the thing is attached to a scene, ModifyThing will also obtain the scene's graphics context and pass it (appropriately scaled and clipped to the thing's current bounding box) to the client's Modifier. The Modifier can paint on this contex, for example to show debugging information. If the thing s detached or the viewer is iconic, a null context is passed to the Modifier.>> <<-- The repainting will take place only if repaint=TRUE and the Action returns changed=TRUE. The Action must not modify the parent and next links of the thing; it may only change the parms record, the property list, and the class of the thing.>> ModifyAllThings: PROC [subtree: Subtree, Modifier: ParmsModifier]; <<-- Write rights to the tree (and to its viewer, depending on the getContext flag) are acquired only once, and held throughout the duration of ModifyAllThings.>> <<-- The automatic repainting will be skipped if the Modifier returns changed=FALSE. take place only if repaint=TRUE and the Action returns changed=TRUE. The Action must not modify the parent and next links of the thing; it may only change the parms record, the property list, and the class of the thing.>> ParmsModifier: TYPE = PROC [parms: REF, class: ThingClass, context: Gr.Context] RETURNS [changed: BOOL _ TRUE] <<-- It is called with write or read access to the tree already acquired, and so it may access the thing's fields directly. For this reason, a ThingAction should not call any of the procedures that acquire access rights, otherwise a self-deadlock may result. The procedures in the ``unsynchronized operations'' section are for use inside ThingActions.>> <<-- If the ParmsModifier decides not to modify the thing it receives, it may optionally warn ModifyThing and ModifyAllThings of this fact by returning changed=FALSE. This information may enable them to save some time in the repainting and the restoration of the data structure invriants. This is useful mainly when the ParmsModifier changes only a few of the things enumerated by ModifyAllThings.>> <<-- Each single object tree is usually handled by several parallel processes, so the client is not allowed to examine or modify its objects's fields and links at will. The procedures listed above do that To modify the parameter record of a thing, the client must write a ParmsModifier procedure that does the change, and call >> <<-- MODIFYING OBJECT PARAMETERS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> ThingExaminer: TYPE = PROC [thing: Thing, context: Gr.Context]; <<-- It is called with write or read access to the tree already acquired, and so it may access the thing's fields directly. For this reason, a ThingAction should not call any of the procedures that acquire access rights, otherwise a self-deadlock may result. The procedures in the ``unsynchronized operations'' section are for use inside ThingActions.>> ModifyAllThings: PROC [node: REF, Action: ThingAction, getContext: BOOL _ FALSE, repaint: BOOL _ TRUE]; <<-- Equivalent to ModifyThings[thing, Action, getContext, repaint] applied to all things in the subtree rooted at the given node (which must be either an Object or a SceneViewer). Individual repaints will be scheduled for the bounding boxes of affected objects. >> <<-- Write rights to the tree (and to its viewer, depending on the getContext flag) are acquired only once, and held throughout the duration of ModifyAllThings.>> <<-- UNSYNCHRONIZED PROCEDURES - - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- The following low-level procedures may be used to erase and repaint objects. However, the caller must acquire the necessary access right to the SceneViewer by himself, before calling these procedures. >> SceduleRepaint: PROC [scene: SceneViewer, box: BoundingBox]; <<-- Erases everything (by painting the whole viewer white) and repaints all objects, in the specified order. Will acquire read access for the painting.>> DoErase: PROC [scene: SceneViewer, object: Object]; <<-- "Erases" the indicated object by painting it with white color. The object is neither added to nor deleted from the SceneViewer, and its color field is not changed, so subsequent paintings (which are inhibited while the caller holds write rights) will restore the original drawing appearance. The caller must have write access rights to the SceneViewer.>> DoPaint: PROC [scene: SceneViewer, object: Object]; <<-- Repaints the indicated object on top of the existing image. The object is neither added nor deleted from the SceneViewer's list, so, if the object is not in it, the original image may be restored when the whole SceneViewer is repainted. The caller must have at least read access to the SceneViewer. >> DoPaintAll: PROC [scene: SceneViewer]; <<-- Erases the screen and repaints all objects in the correct paint order. The caller must have at least read access to the SceneViewer. >> <<-- PROPERTY LIST - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> <> <> <<-- Gets the value of the property named key for the given node (either an object or a scene). 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=sceneViewer, only the property listy of the scene containing the object (if any) is searched. If scope=path, the property lists of the node, of the node's class (if a Thing), of all the ancestors of the node, and of the node's scene, in that order, will be searched for the key, until a non-NIL value for it is found.. >> <<-- The caller must have at least read access to the node's scene.>> <> <<-- Sets the value of the property named key for the given node (either an object or a scene). Removes the property if the value is NIL.>> <<-- The caller must have write access to the node's scene. Will recompute the bounding boxes of the node and all its descendants and ancestors. Will also schedule a repaint for the affected region of the viewer. >> <<>> <> <<-- Sets the Object tree of scene; its current Object tree, if any, becomes detached. The new tree may be either NIL or a detached root (with parent=next=NIL). >> <<-- Schedules a whole viewer repaint (i.e., sets the repaintAll flag of the scene).>> <<-- The client must acquire write access to the scene before calling this procedure.>> RemoveChild: PROC [object: Object]; <<-- Removes object (and its descendants) from its current tree, making it into the root of a detached tree. The object object itself should not be a tree root.>> <<-- The client must acquire write access to the scene containing object before calling this procedure.>> <> <<-- Returns the root of the current Object tree of scene.>> <<-- The client must acquire read access to the scene before calling this procedure. >> RemoveAndRepaintObject: PROC [object: Object] RETURNS [oldParent: REF, oldNext: Object]; <<-- Unsynchronized; requires write rights to the scene containing the object.>> <<-- If object is not a root, removes it from the children list of its parent. It then returns the old parent node (oldParent) of object, and its old next sibling (oldNext), the one that would be painted just after (on top of) object. If object was the last (topmost) child, returns oldNext=NIL.>> <<-- If object is an attached root, detaches it from the scene (so the scene's tree becomes NIL). In that case, returns the scene as the oldParent, and oldNext=NIL.>> <<-- If object is a detached root, does nothing and returns oldNext=oldParent=NIL.>> <<-- In any case, if the tree to which object belongs is attached to a scene, it will recompute the bounding boxes of the ancestors of object (if any), and repaint the portion of the scene that was affected by the deletion of object.>> InsertAndRepaintObject: PROC [object: Object, newParent: REF, newNext: Object _ NIL]; <<-- Unsynchronized; requires write rights to the scene of the newParent.>> <<-- If newParent is a scene, sets the given object as the scene's tree. In this case the original tree of newParent must then be NIL, and newNext too must be NIL.>> <<-- If newParent is an object, inserts object as one of its children, immediately before (below) the specified child object newNext. If newNext= NIL, object is inserted as the last (topmost) child of newParent. The newParent must be a group, and newNext (if not NIL) must be a child of newParent.>> <<-- If newParent = NIL, the operation is a no-op.>> <<-- In any case, object must be a detached root. If the newParent is a scene or its tree is attached to one, the entire subtree hanging from object has its bounding boxes and lineWidths recomputed. The bounding boxes of newParent and its ancestors also are recomputed, and a repaint is scheduled for the portion inside the new bounding box of object.>> ModifyAndRepaintObjects: PROC [node: REF, Action: ObjectModifyProc, option: TraversalOption _ root, context: Gr.context _ NIL, repaint: BOOL _ TRUE]; <<-- Unsynchronized. Requires write access to the object's scene.>> <<-- Modifies several object(s) descendant from the given node using the client-given Action. The node may be either an object, a scene, or NIL. If a scene, the effect is the same as if node were the root of the scene's object tree. If node = NIL, the operation is a no-op. The rest of the comments assume node is an object.>> <<-- If option = single, will apply the Action only to the given node. If option = all, will apply it to the node and all its descendants, in preorder (parent before its children). Finally, if option = things, will apply the Action only to the things descendant from the given node.>> <<-- In any case, if the tree to which the node belongs is attached to some scene, after the Action is completed the procedure will recompute the bounding boxes and cached lineWidths of the object and its descendants, and adjust the bounding boxes of its ancestors in the object tree. The procedure assumes the Action affects only node and its descendants; in particular, assumes the lineWidth cache of node's parent is not affected. >> <<-- If the object's tree is attached, the procedure will also schedule the repainting of the affected portions of the image. The repainting will take place only if repaint=TRUE and only within the bounding boxes of objects for which the Action returned changed=TRUE.>> <<-- The given context is just passed along to the client's Action. Normally, if not NIL, context is the scene's context; in that case, the client should have acquired the paint lock.>> <<>> RecomputeLineWidth: PUBLIC PROC [node: REF] = <<-- Unsynchronized. Requires write access to the node's scene.>> <<-- Recomputes the default line width for an object or scene, as specified by the $LineWidth property in its property list or the lineWidth filed of its parent (if any). Assumes the parent's lineWidth, if any, is correct.>> GetScene: PUBLIC PROC [node: REF] RETURNS [sc: Scene] = <<-- Unsynchronized. Requires read access to the node's scene.>> <<-- Gets the Scene to whose tree the node belongs. The note can itself be a scene.>> <<-- GENERAL SYNCHRONIZED AND NICE OPERATIONS - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- All the operations in this section are synchronized: can be safely used by several processes operating on the same scene(s). The procedures acquire the neccessary locks in the proper order. >> <<-- The procedures also will repaint automatically the portions of the viewer's image affected by them, and restore the internal data structure invariants.>> <<-- The effect of these procedures is determined by a client-written procedure (ModifyProc or ExamineProc) that does the actual modification or examination of the affected object(s).>> ModifyObjects: PROC [node: REF, Action: ModifyProc, option: TraversalOption _ single, getContext: BOOL _ FALSE, repaint: BOOL _ TRUE]; <<-- Modifies several object(s) descendant from the given node using the client-given Action. The node may be either an object, a scene, or NIL. If a scene, the effect is the same as if node were the root of the scene's object tree. If node = NIL, the operation is a no-op. The rest of the comments assume node is an object.>> <<-- If option = single, will apply the Action only to the given node. If option = all, will apply it to the node and all its descendants, in preorder (parent before its children). Finally, if option = things, will apply the Action only to the things descendant from the given node.>> <<-- In any case, if the tree to which the node belongs is attached to some scene, after the Action is completed the procedure will recompute the bounding boxes and cached lineWidths of the object and its descendants, and adjust the bounding boxes of its ancestors in the object tree. The procedure assumes the Action affects only node and its descendants; in particular, assumes the lineWidth cache of node's parent is not affected. >> <<-- If the object's tree is attached, the procedure will also schedule the repainting of the affected portions of the image. The repainting will take place only if repaint=TRUE and only within the bounding boxes of objects for which the Action returned changed=TRUE.>> <<-- Will acquire write access to object's scene throughout the whole traversal. If getContext is true and the tree is attached to a scene, ModifyObjects will also get write rights to its viewer, and pass the viewer's context to the Action. Otherwise, the Action is called with context=NIL.>> ModifyProc: TYPE = PROC [object: Object, context: Gr.Context] RETURNS [changed: BOOL _ TRUE]; <<-- A procedure called by ModifyObjects to modify an individual object. >> <<-- It is called with write access to the tree already acquired, and so it may examine and change the object's fields directly. However, the changes must be entirely confined to the given object and its descendants. In particular, the contents of a thing's parms record cannot be modified if the same is shared by other things elsewhere.>> <<-- The ModifyProc should return TRUE if anything changed in the >> <<-- A ModifyProc should not call any synchronized procedures (those that automatically acquire access rights to its arguments), otherwise a self-deadlock may result. A ModifyProc should not touch (or rely upon) the bounding box of any object. Therefore, it should use only the procedures in the ``unsynchronized and not nice'' section.>> <<-- QUEUING PROCEDURES - - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- These procedures insert and remove entries from the painting request and mouse/menu action queues of a scene. They do not acquire scene or viewer locks; they are synchronized by a separate monitor lock.>> ScheduleRepaint: PUBLIC PROC [sc: Scene, box: BoundingBox] = <<-- Schedules a repaint of the specified scene within the given bounding box.>> <<-- For now, multiple repaints are simply coalesced in a single rectangular repaint. In future, perhaps we should do something fancier.>> END. <> <<>> <> <<>> <<>> <<>> <<>> <<>> <<>> <<>>