1#/usr/bin/pic2plot -Tps
2#
3# Pic macros for drawing UML sequence diagrams
4#
5# (C) Copyright 2004-2005 Diomidis Spinellis.
6#
7# Permission to use, copy, and distribute this software and its
8# documentation for any purpose and without fee is hereby granted,
9# provided that the above copyright notice appear in all copies and that
10# both that copyright notice and this permission notice appear in
11# supporting documentation.
12#
13# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
14# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
15# MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16#
17#
18
19
20# Default parameters (can be redefined)
21
22# Spacing between messages
23spacing = 0.25;
24# Active box width
25awid = .1;
26# Box height
27boxht = 0.3;
28# Commend folding
29corner_fold=awid
30# Comment distance
31define comment_default_move {up 0.25 right 0.25};
32# Comment height
33comment_default_ht=0.5;
34# Comment width
35comment_default_wid=1;
36# Underline object name
37underline=1;
38
39# Create a new object(name,label)
40define object {
41	$1: box $2; move;
42	# Could also underline text with \mk\ul\ul\ul...\rt
43	if (underline) then {
44		line from $1.w + (.1, -.07) to $1.e + (-.1, -.07);
45	}
46	move to $1.e;
47	move right;
48	# Active is the level of activations of the object
49	# 0 : inactive : draw thin line swimlane
50	# 1 : active : draw thick swimlane
51	# > 1: nested : draw nested swimlane
52	active_$1 = 0;
53	lifestart_$1 = $1.s.y;
54}
55
56# Create a new external actor(name,label)
57define actor {
58	$1: [
59		XSEQC: circle rad 0.06;
60		XSEQL: line from XSEQC.s down .12;
61		line from XSEQL.start - (.15,.02) to XSEQL.start + (.15,-.02);
62		XSEQL1: line from XSEQL.end left .08 down .15;
63		XSEQL2: line from XSEQL.end right .08 down .15;
64		line at XSEQC.n invis "" "" "" $2;
65	]
66	move to $1.e;
67	move right;
68	active_$1 = 0;
69	lifestart_$1 = $1.s.y - .05;
70}
71
72# Create a new placeholder object(name)
73define placeholder_object {
74	$1: box invisible;
75	move;
76	move to $1.e;
77	move right;
78	active_$1 = 0;
79	lifestart_$1 = $1.s.y;
80}
81
82define pobject {
83	placeholder_object($1);
84}
85
86define extend_lifeline {
87	if (active_$1 > 0) then {
88                # draw the left edges of the boxes
89		move to ($1.x - awid/2, Here.y);
90		for level = 1 to active_$1 do {
91			line from (Here.x, lifestart_$1) to Here;
92			move right awid/2
93		}
94
95                # draw the right edge of the innermost box
96		move right awid/2;
97		line from (Here.x, lifestart_$1) to Here;
98	} else {
99		line from ($1.x, lifestart_$1) to ($1.x, Here.y) dashed;
100	}
101	lifestart_$1 = Here.y;
102}
103
104# complete(name)
105# Complete the lifeline of the object with the given name
106define complete {
107	extend_lifeline($1)
108	if (active_$1) then {
109		# draw bottom of all active boxes
110		line right ((active_$1 + 1) * awid/2) from ($1.x - awid/2, Here.y);
111	}
112}
113
114# Draw a message(from_object,to_object,label)
115define message {
116	down;
117	move spacing;
118	# Adjust so that lines and arrows do not fall into the
119	# active box.  Should be .5, but the arrow heads tend to
120	# overshoot.
121	if ($1.x <= $2.x) then {
122		off_from = awid * .6;
123		off_to = -awid * .6;
124	} else {
125		off_from = -awid * .6;
126		off_to = awid * .6;
127	}
128
129        # add half a box width for each level of nesting
130        if (active_$1 > 1) then {
131                off_from = off_from + (active_$1 - 1) * awid/2;
132        }
133
134        # add half a box width for each level of nesting
135        if (active_$2 > 1) then {
136                off_to = off_to + (active_$2 - 1) * awid/2;
137        }
138
139	if ($1.x == $2.x) then {
140		arrow from ($1.x + off_from, Here.y) right then down .25 then left $3 ljust " " " " " " ;
141	} else {
142		arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) $3 " ";
143	}
144}
145
146# Display a lifeline constraint(object,label)
147define lifeline_constraint {
148        off_from = awid;
149        # add half a box width for each level of nesting
150        if (active_$1 > 1) then {
151                off_from = off_from + (active_$1 - 1) * awid/2;
152        }
153
154	box at ($1.x + off_from, Here.y) invis $2 ljust " " ;
155}
156
157define lconstraint {
158	lifeline_constraint($1,$2);
159}
160
161# Display an object constraint(label)
162# for the last object drawn
163define object_constraint {
164	{ box invis with .s at last box .nw $1 ljust; }
165}
166
167define oconstraint {
168	object_constraint($1);
169}
170
171# Draw a creation message(from_object,to_object,object_label)
172define create_message {
173	down;
174	move spacing;
175	if ($1.x <= $2.x) then {
176		off_from = awid * .6;
177		off_to = -boxwid * .51;
178	} else {
179		off_from = -awid * .6;
180		off_to = boxwid * .51;
181	}
182
183        # add half a box width for each level of nesting
184        if (active_$1 > 1) then {
185                off_from = off_from + (active_$1 - 1) * awid/2;
186        }
187
188	# See comment in destroy_message
189	XSEQA: arrow from ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) "�create�" " ";
190	if ($1.x <= $2.x) then {
191		{ XSEQB: box $3 with .w at XSEQA.end; }
192	} else {
193		{ XSEQB: box $3 with .e at XSEQA.end; }
194	}
195	{
196		line from XSEQB.w + (.1, -.07) to XSEQB.e + (-.1, -.07);
197	}
198	lifestart_$2 = XSEQB.s.y;
199	move (spacing + boxht) / 2;
200}
201
202define cmessage {
203	create_message($1,$2,$3);
204}
205
206# Draw an X for a given object
207define drawx {
208	{
209	line from($1.x - awid, lifestart_$1 - awid) to ($1.x + awid, lifestart_$1 + awid);
210	line from($1.x - awid, lifestart_$1 + awid) to ($1.x + awid, lifestart_$1 - awid);
211	}
212}
213
214# Draw a destroy message(from_object,to_object)
215define destroy_message {
216	down;
217	move spacing;
218	# The troff code is \(Fo \(Fc
219	# The groff code is also \[Fo] \[Fc]
220	# The pic2plot code is \Fo \Fc
221	# See http://www.delorie.com/gnu/docs/plotutils/plotutils_71.html
222	# To stay compatible with all we have to hardcode the characters
223	message($1,$2,"�destroy�");
224	complete($2);
225	drawx($2);
226}
227
228define dmessage {
229	destroy_message($1,$2);
230}
231
232# An object deletes itself: delete(object)
233define delete {
234	complete($1);
235	lifestart_$1 = lifestart_$1 - awid;
236	drawx($1);
237}
238
239# Draw a message return(from_object,to_object,label)
240define return_message {
241	down;
242	move spacing;
243	# See comment in message
244	if ($1.x <= $2.x) then {
245		off_from = awid * .6;
246		off_to = -awid * .6;
247	} else {
248		off_from = -awid * .6;
249		off_to = awid * .6;
250	}
251
252        # add half a box width for each level of nesting
253        if (active_$1 > 1) then {
254                off_from = off_from + (active_$1 - 1) * awid/2;
255        }
256
257        # add half a box width for each level of nesting
258        if (active_$2 > 1) then {
259                off_to = off_to + (active_$2 - 1) * awid/2;
260        }
261
262	arrow from  ($1.x + off_from, Here.y) to ($2.x + off_to, Here.y) dashed $3 " ";
263}
264
265define rmessage {
266	return_message($1,$2,$3);
267}
268
269# Object becomes active
270# Can be nested to show recursion
271define active {
272	extend_lifeline($1);
273	# draw top of new active box
274	line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y);
275	active_$1 = active_$1 + 1;
276}
277
278# Object becomes inactive
279# Can be nested to show recursion
280define inactive {
281	extend_lifeline($1);
282	active_$1 = active_$1 - 1;
283	# draw bottom of innermost active box
284	line right awid from ($1.x + (active_$1 - 1) * awid/2, Here.y);
285}
286
287# Time step
288# Useful at the beginning and the end
289# to show object states
290define step {
291	down;
292	move spacing;
293}
294
295# Switch to asynchronous messages
296define async {
297	arrowhead = 0;
298	arrowwid = arrowwid * 2;
299}
300
301# Switch to synchronous messages
302define sync {
303	arrowhead = 1;
304	arrowwid = arrowwid / 2;
305}
306
307# same as lifeline_constraint, but Text and empty string are exchanged.
308define lconstraint_below{
309        off_from = awid;
310        # add half a box width for each level of nesting
311        if (active_$1 > 1) then {
312                off_from = off_from + (active_$1 - 1) * awid/2;
313        }
314
315	box at ($1.x + off_from, Here.y) invis "" $2 ljust;
316}
317
318# begin_frame(left_object,name,label_text);
319define begin_frame {
320	# The lifeline will be cut here
321	extend_lifeline($1);
322	# draw the frame-label
323	$2: box $3 invis with .n at ($1.x, Here.y);
324	d = $2.e.y - $2.se.y;
325	line from $2.ne to $2.e then down d left d then to $2.sw;
326	# continue the lifeline below the frame-label
327	move to $2.s;
328	lifestart_$1 = Here.y;
329}
330
331# end_frame(right_object,name);
332define end_frame {
333	# dummy-box for the lower right corner:
334	box invis "" with .s at ($1.x, Here.y);
335	# draw the frame
336	frame_wid = last box.se.x - $2.nw.x
337	frame_ht = - last box.se.y + $2.nw.y
338	box with .nw at $2.nw wid frame_wid ht frame_ht;
339	# restore Here.y
340	move to last box.s;
341}
342
343# comment(object,[name],[line_movement], [box_size] text);
344define comment {
345	old_y = Here.y
346	# draw the first connecting line, at which's end the box wil be positioned
347	move to ($1.x, Here.y)
348	if "$3" == "" then {
349		line comment_default_move() dashed;
350	} else {
351		line $3 dashed;
352	}
353
354	# draw the box, use comment_default_xx if no explicit
355	# size is given together with the text in parameter 4
356	old_boxht=boxht;
357	old_boxwid=boxwid;
358	boxht=comment_default_ht;
359	boxwid=comment_default_wid;
360	if "$2" == "" then {
361		box invis $4;
362	} else {
363		$2: box invis $4;
364	}
365	boxht=old_boxht;
366	boxwid=old_boxwid;
367
368	# draw the frame of the comment
369	line from       last box.nw \
370		to          last box.ne - (corner_fold, 0) \
371		then to last box.ne - (0, corner_fold) \
372		then to last box.se \
373		then to last box.sw \
374		then to last box.nw ;
375	line from       last box.ne - (corner_fold, 0) \
376		to          last box.ne - (corner_fold, corner_fold) \
377		then to last box.ne - (0, corner_fold) ;
378
379	# restore Here.y
380	move to ($1.x, old_y)
381}
382
383# connect_to_comment(object,name);
384define connect_to_comment {
385	old_y = Here.y
386	# start at the object
387	move to ($1.x, Here.y)
388	# find the best connection-point of the comment to use as line-end
389	if $1.x < $2.w.x then {
390		line to $2.w dashed;
391	} else {
392		if $1.x > $2.e.x then {
393			line to $2.e dashed;
394		} else {
395			if Here.y < $2.s.y then {
396				line to $2.s dashed;
397			} else {
398				if Here.y > $2.n.y then {
399					line to $2.n dashed;
400				}
401			}
402		}
403	}
404	# restore Here.y
405	move to ($1.x, old_y)
406}
407