#/usr/bin/pic2plot -Tps # # Pic macros for drawing UML sequence diagrams # # (C) Copyright 2004-2005 Diomidis Spinellis. # # Permission to use, copy, and distribute this software and its # documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appear in all copies and that # both that copyright notice and this permission notice appear in # supporting documentation. # # THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED # WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF # MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. # # $Id$ # # Default parameters (can be redefined) # Spacing between messages spacing = 0.25; # Active box width awid = .1; # Box height boxht = 0.3; # Commend folding corner_fold=awid # Comment distance define comment_default_move {up 0.25 right 0.25}; # Comment height comment_default_ht=0.5; # Comment width comment_default_wid=1; # Underline object name underline=1; # Create a new object(name,label) define object { $1: box $2; move; # Could also underline text with \mk\ul\ul\ul...\rt if (underline) then { line from $1.w + (.1, -.07) to $1.e + (-.1, -.07); } move to $1.e; move right; # Active is the level of activations of the object # 0 : inactive : draw thin line swimlane # 1 : active : draw thick swimlane # > 1: nested : draw nested swimlane active_$1 = 0; lifestart_$1 = $1.s.y; } # Create a new external actor(name,label) define actor { $1: [ XSEQC: circle rad 0.06; XSEQL: line from XSEQC.s down .12; line from XSEQL.start - (.15,.02) to XSEQL.start + (.15,-.02); XSEQL1: line from XSEQL.end left .08 down .15; XSEQL2: line from XSEQL.end right .08 down .15; line at XSEQC.n invis "" "" "" $2; ] move to $1.e; move right; active_$1 = 0; lifestart_$1 = $1.s.y - .05; } # Create a new placeholder object(name) define placeholder_object { $1: box invisible; move; move to $1.e; move right; active_$1 = 0; lifestart_$1 = $1.s.y; } define pobject { placeholder_object($1); } define extend_lifeline { if (active_$1 > 0) then { # draw the left edges of the boxes move to ($1.x - awid/2, Here.y); for level = 1 to active_$1 do { line from (Here.x, lifestart_$1) to Here; move right awid/2 } # draw the right edge of the innermost box move right awid/2; line from (Here.x, lifestart_$1) to Here; } else { line from ($1.x, lifestart_$1) to ($1.x, Here.y) dashed; } lifestart_$1 = Here.y; } # complete(name) # Complete the lifeline of the object with the given name define complete { extend_lifeline($1) if (active_$1) then { # draw bottom of all active boxes line right ((active_$1 + 1) * awid/2) from ($1.x - awid/2, Here.y); } } # Draw a message(from_object,to_object,label) define message { down; move spacing; # Adjust so that lines and arrows do not fall into the # active box. Should be .5, but the arrow heads tend to # overshoot. if ($1.x <= $2.x) then { off_from = awid * .6; off_to = -awid * .6; } else { off_from = -awid * .6; off_to = awid * .6; } # add half a box width for each level of nesting if (active_$1 > 1) then { off_from = off_from + (active_$1 - 1) * awid/2; } # add half a box width for each level of nesting if (active_$2 > 1) then { off_to = off_to + (active_$2 - 1) * awid/2; } if ($1.x == $2.x) then { arrow from ($1.x + off_from, Here.y) right then down .25 then left $3 ljust " " " "; } else { arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) $3 " "; } } # Display a lifeline constraint(object,label) define lifeline_constraint { off_from = awid; # add half a box width for each level of nesting if (active_$1 > 1) then { off_from = off_from + (active_$1 - 1) * awid/2; } box at ($1.x + off_from, Here.y) invis $2 ljust " " ; } define lconstraint { lifeline_constraint($1,$2); } # Display an object constraint(label) # for the last object drawn define object_constraint { { box invis with .s at last box .nw $1 ljust; } } define oconstraint { object_constraint($1); } # Draw a creation message(from_object,to_object,object_label) define create_message { down; move spacing; if ($1.x <= $2.x) then { off_from = awid * .6; off_to = -boxwid * .51; } else { off_from = -awid * .6; off_to = boxwid * .51; } # add half a box width for each level of nesting if (active_$1 > 1) then { off_from = off_from + (active_$1 - 1) * awid/2; } # See comment in destroy_message XSEQA: arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) "«create»" " "; if ($1.x <= $2.x) then { { XSEQB: box $3 with .w at XSEQA.end; } } else { { XSEQB: box $3 with .e at XSEQA.end; } } { line from XSEQB.w + (.1, -.07) to XSEQB.e + (-.1, -.07); } lifestart_$2 = XSEQB.s.y; move (spacing + boxht) / 2; } define cmessage { create_message($1,$2,$3); } # Draw an X for a given object define drawx { { line from($1.x - awid, lifestart_$1 - awid) to ($1.x + awid, lifestart_$1 + awid); line from($1.x - awid, lifestart_$1 + awid) to ($1.x + awid, lifestart_$1 - awid); } } # Draw a destroy message(from_object,to_object) define destroy_message { down; move spacing; # The troff code is \(Fo \(Fc # The groff code is also \[Fo] \[Fc] # The pic2plot code is \Fo \Fc # See http://www.delorie.com/gnu/docs/plotutils/plotutils_71.html # To stay compatible with all we have to hardcode the characters message($1,$2,"«destroy»"); complete($2); drawx($2); } define dmessage { destroy_message($1,$2); } # An object deletes itself: delete(object) define delete { complete($1); lifestart_$1 = lifestart_$1 - awid; drawx($1); } # Draw a message return(from_object,to_object,label) define return_message { down; move spacing; # See comment in message if ($1.x <= $2.x) then { off_from = awid * .6; off_to = -awid * .6; } else { off_from = -awid * .6; off_to = awid * .6; } # add half a box width for each level of nesting if (active_$1 > 1) then { off_from = off_from + (active_$1 - 1) * awid/2; } # add half a box width for each level of nesting if (active_$2 > 1) then { off_to = off_to + (active_$2 - 1) * awid/2; } arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) dashed $3 " "; } define rmessage { return_message($1,$2,$3); } # Object becomes active # Can be nested to show recursion define active { extend_lifeline($1); # draw top of new active box line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y); active_$1 = active_$1 + 1; } # Object becomes inactive # Can be nested to show recursion define inactive { extend_lifeline($1); active_$1 = active_$1 - 1; # draw bottom of innermost active box line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y); } # Time step # Useful at the beginning and the end # to show object states define step { down; move spacing; } # Switch to asynchronous messages define async { arrowhead = 0; arrowwid = arrowwid * 2; } # Switch to synchronous messages define sync { arrowhead = 1; arrowwid = arrowwid / 2; } # same as lifeline_constraint, but Text and empty string are exchanged. define lconstraint_below{ off_from = awid; # add half a box width for each level of nesting if (active_$1 > 1) then { off_from = off_from + (active_$1 - 1) * awid/2; } box at ($1.x + off_from, Here.y) invis "" $2 ljust; } # begin_frame(left_object,name,label_text); define begin_frame { # The lifeline will be cut here extend_lifeline($1); # draw the frame-label $2: box $3 invis with .n at ($1.x, Here.y); d = $2.e.y - $2.se.y; line from $2.ne to $2.e then down d left d then to $2.sw; # continue the lifeline below the frame-label move to $2.s; lifestart_$1 = Here.y; } # end_frame(right_object,name); define end_frame { # dummy-box for the lower right corner: box invis "" with .s at ($1.x, Here.y); # draw the frame frame_wid = last box.se.x - $2.nw.x frame_ht = - last box.se.y + $2.nw.y box with .nw at $2.nw wid frame_wid ht frame_ht; # restore Here.y move to last box.s; } # comment(object,[name],[line_movement], [box_size] text); define comment { old_y = Here.y # draw the first connecting line, at which's end the box wil be positioned move to ($1.x, Here.y) if "$3" == "" then { line comment_default_move() dashed; } else { line $3 dashed; } # draw the box, use comment_default_xx if no explicit # size is given together with the text in parameter 4 old_boxht=boxht; old_boxwid=boxwid; boxht=comment_default_ht; boxwid=comment_default_wid; if "$2" == "" then { box invis $4; } else { $2: box invis $4; } boxht=old_boxht; boxwid=old_boxwid; # draw the frame of the comment line from last box.nw \ to last box.ne - (corner_fold, 0) \ then to last box.ne - (0, corner_fold) \ then to last box.se \ then to last box.sw \ then to last box.nw ; line from last box.ne - (corner_fold, 0) \ to last box.ne - (corner_fold, corner_fold) \ then to last box.ne - (0, corner_fold) ; # restore Here.y move to ($1.x, old_y) } # connect_to_comment(object,name); define connect_to_comment { old_y = Here.y # start at the object move to ($1.x, Here.y) # find the best connection-point of the comment to use as line-end if $1.x < $2.w.x then { line to $2.w dashed; } else { if $1.x > $2.e.x then { line to $2.e dashed; } else { if Here.y < $2.s.y then { line to $2.s dashed; } else { if Here.y > $2.n.y then { line to $2.n dashed; } } } } # restore Here.y move to ($1.x, old_y) }