<<-- COGDrawing.mesa: Viewers for geometrical drawings>> <<-- last modified by Stolfi - September 24, 1982 9:24 pm>> <<-- To do: add bounding boxes to objects?>> <<-- To do: implement partial repainting with given bounding box>> <<-- compile COGDrawing >> DIRECTORY Graphics USING [Color, Context, black, white], Rope USING [ROPE], ViewerClasses USING [Viewer], COGCart USING [Box, ScaleFactors], COGHomo USING [Point]; COGDrawing: CEDAR DEFINITIONS = BEGIN OPEN Cart: COGCart, Homo: COGHomo, Rope; <<>> <<-- VIEWER CREATION - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> Drawing: TYPE = ViewerClasses.Viewer; <<-- A Drawing is a viewer for displaying a dynamically varying collection of dynamically varying Objects in a dynamically varying viewer (so that you can get dynamically varying bugs). The objects may be points, lines, polygons, or any client-defined entities (e.g., Delaunay diagrams). Objects added to the Drawing are remembered, so that the entire Drawing can be repainted when the viewer is moved, and an individual object can be easily erased or modified, without losing the others.>> <<-- The implementation makes it rather easy to keep the drawing scaled so as to fit into the curr viewer window, with automatic rescaling and repainting when the latter changes. Forthcoming is a facility for "blowing up" a selected portion of the Viewer Client-defined objects can use all primitives of CedarGraphics. >> <<-- Besides the standard menu items (Grow, Destroy, <-->, 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 object list, a Drawing posesses a list of client-defined properties, which may be used to store data that is global to all objects but local to the Drawing. 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 Drawing 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. >> MakeDrawing: PROC [name: ROPE, box: Cart.Box] RETURNS [dr: Drawing]; <<-- Creates a new Drawing, initially with no Objects. The box is used to define the scale factors used for painting the objects.>> <<-- The initial menu contains the basic viewer entries (Close, Grow, <-->, Destroy) plus a Repaint entry that causes the whole object list to be repainted.>> Alive: PROC [dr: Drawing] RETURNS [BOOL]; <<-- Returns FALSE if the Drawing's viewer has been destroyed for any reason.>> <<>> <<-- PROCESS SYNCHRONIZATION - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> Access: TYPE = {read, write, none}; Acquire: PROC [dr: Drawing, wants: Access, hasSome: BOOL] RETURNS [had: Access]; Release: PROC [dr: Drawing, hasSome: BOOL] = INLINE {Acquire [dr, none, hasSome]}; <<-- To examine or modify the Drawing's contents (its property list, the object list, the objects themselves, or the mouse action table), a process P must first Acquire rights to do so; in this way, other processes will be prevented from modifying the viever data under the feet of P. Moreover, if P is going to modify the Drawing's contents, the others will be prevented from reading it until P relinquishes its rights.>> <<-- At any instant, the Drawing is (a) available, (b) acquired by one or more processes for reading, or (c) acquired by only one process for writing. Acquire [dr, write] will wait until the Drawing becomes available; Acquire[dr, read] will wait until no one is modifying it.>> <<-- Due to implementation difficulties, the calling process is required to inform Acquire (via the hasSome parameter) whether he already has some kind of access rights (hasSome=FALSE means none, hasSome = TRUE means read or write; Acquire is able to figure out which). Chaos and deadlocks will result if the hasSome parameter is incorrect.>> <<-- Release is used to relinquish such access rights. Every process must relinquish its access rights as soon as possible, and always call Release with the correct hasSome parameter; otherwise chaos and deadlocks will result. Client procedures that acquire any access rights should catch UNWIND signals and perform the appropriate Release before exiting.>> <<-- The switching from the current rights to the ones wanted is indivisible, so other modifying processes cannot sneak in while switching from read to write or vice-versa. However, note that switching from read to write may cause a deadlock if another process with read rights decides to do the same; for this reason, this switching should be avoided as much as possible. Possible suggestions: (1) acquire write rights instead of read, whenever there is a chance that they will be needed; (2) if you want to switch from read to write, try to Release the read first, then Acquire the write, and check whether the info you extracted from the Drawing before the Release hasn't changed in between.>> <<>> <<-- GRAPHIC OBJECTS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> Color: TYPE = Graphics.Color; black: Color = Graphics.black; white: Color = Graphics.white; Size: TYPE = NAT [0..256); -- client-defined object size/width/thickness/etc. Style: TYPE = NAT [0..256); -- client-defined object style. PaintOrder: TYPE = INTEGER [0..6]; -- all 0's are painted first, then all 1's, etc. ObjectParms: TYPE = RECORD [data: REF ANY, -- kind, coordinates &c of graphic object color: Color _ black, size: Size _ 0, style: Style _ 0 ]; ObjectRec: TYPE = RECORD [Painter: PainterProc, -- a procedure that knows how to paint the object parms: ObjectParms, -- kind, coordinates, color, style, &c of graphic object order: PRIVATE PaintOrder, -- used when repainting everything from scratch pred, succ: PRIVATE Object, -- Objects are connected in doubly linked lists for each order dr: -- READONLY -- Drawing -- to which this object currently belongs (or NIL if none) ]; Object: TYPE = REF ObjectRec; PainterProc: TYPE = PROC [dr: Drawing, context: Graphics.Context, sf: Cart.ScaleFactors, parms: ObjectParms]; <<-- The Painter of each object is called to paint it on the given context. The interpretation of color, size, style and data is left to the procedure. However, note that to erase an object from the screen the Drawing manager sets its color to white and then calls the corresponding Painter. (The color, size and style fields should be part of the data, but in many applications it is easier to have them as separate variables. For example, the data may point to an edge of a graph structure that is being debugged; . >> <<-- The context passed to the Painter is the raw viewer's context, maybe clipped but not scaled; the scale factors sf are such that the Drawing's bounding box (as specified to MakeDrawing) would scale to the context's bounding box, minus a modest margin all around. The Painter should use Cart.Scale to convert object coordinates before calling CedarGraphics. (The context could be scaled once before calling all Painters, but sometimes the dimensions of painted objects (e.g., line thickness) are given in device units). >> <<-- The Drawing package guarantees (I hope) that when a Painter is called, the process doing the call holds at least read access to the Drawing.>> <<-- 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 (or have some independent process do it periodically). This may be too time-consuming for complex Drawings.>> <<-- (1) acquire write rights to the drawing, call DoErase [obj], modify its parameters, call DoPaint [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 Drawing's list by the time the Remove is done (otherwise he may get an error); in the extreme cases, the caller may have tohold read access (at least) continuously in the interval between the Add and the 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. >> Make: PROC [Painter: PainterProc, parms: ObjectParms] RETURNS [obj: Object]; <<-- Allocates a new ObjectRec obj with given parameters and painting procedure.>> Add: PROC [dr: Drawing, obj: Object, order: PaintOrder _ 3, hasSome: BOOL]; <<-- Inserts obj in the object list of the Drawing dr, in the specified painting order, and calls obj.Painter to draw it on the screen.>> <<-- The order is used only when repainting the all objects from scratch; the painting invoked by Add, and all individual repaintings, are done on top of the existing image. The hasSome parameter tells the Add routine whether the caller already holds any access rights (read or write) to dr; Add will acquire write rights while adding the new object.>> <<-- If the object is already in the Drawing dr, the action will be a no-op. If it is in some other Drawing, an error will be generated. >> Remove: PROC [obj: Object, hasSome: BOOL]; <<-- Erases the indicated object (by painting it with color=white) and removes it from the list of objects. If the object is not in any Drawing, the operation is a no-op.>> RemoveAll: PROC [dr: Drawing, hasSome: BOOL]; <<-- Erases everything (by painting the whole viewer white) and sets the object list to NIL. The current access rights are switched to write in order to do this.>> RepaintAll: PROC [dr: Drawing, hasSome: BOOL]; <<-- Erases everything (by painting the whole viewer white) and repaints all objects, in the specified order. If the curr acccess is none, read access will be acquired for the painting.>> SetStyle: PROC [obj: Object, color: Color _ black, size: Size _ 1, style: Style _ 0, hasSome: BOOL]; <<-- "Erases" the object (by repainting it in white), then sets its color, style and size as indicated, and repaints it on top of the existing image. The current rights are switched to write while the call is in progress.>> Modify: PROC [obj: Object, Action: ObjectAction, actionData: REF _ NIL, erase, repaint: BOOL _ TRUE, hasSome: BOOL] RETURNS [actionResult: REF]; <<-- Computes actionResult _ Action [dr, obj, actionData]. If erase is true, the object is first "erased" (by painting it white); if repaint is TRUE, the object is repainted after the modification. The current access rights are switched to write to do the Action.>> <<>> ModifyAll: PROC [dr: Drawing, Action: ObjectAction, actionData: REF _ NIL, erase, repaint: BOOL _ TRUE, hasSome: BOOL] RETURNS [actionResult: REF]; <<-- Performs actionData _ Action[dr, obj, actionData] for each object in the drawing dr (in the paint order), and then returns actionResult _ actionData. If erase is true, each obj is erased (by painting it white) before the Action; if repaint is TRUE, each object is repainted after it. ModifyAll will switch the current access rights to write, and will hold them for the whole process.>> Enumerate: PROC [dr: Drawing, Action: ObjectAction, actionData: REF _ NIL, hasSome: BOOL] RETURNS [actionResult: REF]; <<-- Performs actionData _ Action[dr, obj, actionData] for each object in the drawing dr, and then returns actionResult _ actionData. The Action should not write the objects, only look at them; no repainting takes place, and Enumerate will acquire only read access rights to dr (unless the current ones are write).>> ObjectAction: TYPE = PROC [dr: Drawing, obj: Object, actionData: REF] RETURNS [actionResult: REF _ NIL]; <<>> <<-- LOW-LEVEL OBJECT DRAWING - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- The following low-level procedures may be used to erase and repaint objects. However, the caller must acquire the necessary access right to the Drawing by himself, before calling these procedures. >> DoErase: PROC [dr: Drawing, obj: Object]; <<-- "Erases" the indicated object by painting it with white color. The object is neither added to nor deleted from the Drawing, 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 Drawing.>> DoPaint: PROC [dr: Drawing, obj: Object]; <<-- Repaints the indicated object on top of the existing image. The object is neither added nor deleted from the Drawing's list, so, if the object is not in it, the original image may be restored when the whole Drawing is repainted. The caller must have at least read access to the Drawing. >> DoPaintAll: PROC [dr: Drawing]; <<-- Erases the screen and repaints all objects in the correct paint order. The caller must have at least read access to the Drawing. >> <<>> <<-- PREDEFINED OBJECTS - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> <<-- The following procedures are extensions to CedarGraphics that use homogeneous coordinates and perform automatic scaling by the given scale factors. They are useful in the coding of PainterProcs. >> DrawDot: PROC [context: Graphics.Context, sf: Cart.ScaleFactors, pt: Homo.Point, side: Size _ 0, color: Color _ black]; <<-- Draws a small square of given side (in pixels, independent of the scale factors) centered at pt. >> DrawSegment: PROC [context: Graphics.Context, sf: Cart.ScaleFactors, org, dest: Homo.Point, width: Size _ 1, color: Color _ black]; <<-- Draws a line segment of given width (in pixels, independent of the scale factors) from org to dest. If width = 0 or 1, the CedarGraphics "thin line width" is used. >> <<-- org and dest should not be both infinite.>> MoveTo: PROC [context: Graphics.Context, sf: Cart.ScaleFactors, pt: Homo.Point]; <<-- Moves the cursor to the given point, starting a new path. >> LineTo: PROC [context: Graphics.Context, sf: Cart.ScaleFactors, pt: Homo.Point]; <<-- Extends the current path to the given point by a straight line segment. >> <<>> <<-- MENU AND MOUSE PROCEDURES - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> MouseButton: TYPE = {left, center, right}; EventKind: TYPE = {down, move, up}; -- move means mouse moves with button down. MouseEvent: TYPE = RECORD [button: MouseButton, shift, ctrl: BOOL _ FALSE, kind: EventKind _ down]; MenuProc: TYPE = PROC [dr: Drawing, menuData: REF ANY, button: MouseButton]; MouseProc: TYPE = PROC [dr: Drawing, eventData: REF ANY, x, y: REAL, event: MouseEvent]; <<-- MenuProcs and MouseProcs may be attached respectively to client-defined menu entries or to specific mouse events in the Drawing's viewer. To such events, the client may also attach an arbitrary data record, which is passed to the proc together with the Drawing's state record.>> AddMenuAction: PROC [dr: Drawing, name: ROPE, Proc: MenuProc, menuData: REF _ NIL, needs: Access]; <<-- Adds a new menu entry with given name, Proc and menuData. After this call, whenever the menu entry is activated by the user, the Proc will be called by a forked process with the specified access rights already acquired, and these will be automatically released upon exit of the Proc.>> <<-- The Drawing should not have been acquired by the caller when AddMenuAction is invoked.>> AddPropertyButton: PROC [dr: Drawing, key: ATOM]; <<-- Adds a new menu entry with name = Atom.GetPName[key].>> <<-- When the user clicks such entry with the left mouse button, the key property of the drawing is set to $TRUE; when the right button is used, the key property is set to NIL. The entire drawing is repainted in each case.>> <<-- The Drawing should not have been acquired by the caller when AddPropertyButton is invoked.>> AddMouseAction: PROC [dr: Drawing, event: MouseEvent, Proc: MouseProc, eventData: REF _ NIL, needs: Access]; <<-- Enables the specified mouse event, attaching to it the given Proc and eventData. After this call, whenever the specified event occurs in the window of dr, the Proc will be called with the specified access rights already acquired, and they will be automatically released upon exit.>> <<-- The Drawing should not have been acquired by the caller when AddMouseAction is invoked.>> <<-- PROPERTY LIST - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->> GetProp: PROC [dr: Drawing, key: ATOM, hasSome: BOOL] RETURNS [value: REF]; <<-- Returns the value of the specified property from the drawing's property list. If the property is missing, returns NIL. Will acquire read access rights (at least) to do this.>> PutProp: PROC [dr: Drawing, key: ATOM, value: REF, hasSome: BOOL]; <<-- Replaces the value of the specified property by the given value. If the value is NIL, the property is removed from the list. If the property is missing, it is added to the list. Will acquire write access rights (at least) to do this>> END.