root/gaphor/tags/gaphor-0.12.0/gaphor/diagram/message.py

Revision 2079, 9.4 kB (checked in by wrobe..@pld-linux.org, 1 year ago)

- documentation fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Sequence and communication diagram messages.
3
4 Messages are implemented according to UML 2.1.1 specification.
5
6 Implementation Details
7 ======================
8 Message sort is supported but occurence specification is not implemented.
9 This means that model drawn on a diagram is not complete on UML datamodel
10 level, still it is valid UML diagram (see Lifelines Diagram in UML
11 specification, page 461).
12
13 Reply Messages
14 --------------
15 Different sources show that reply message has filled arrow, including
16 UML 2.0.
17
18 UML 2.1.1 specification says that reply message should be drawn with an
19 open arrow. This is visible on examples in UML 2.0 and UML 2.1.1
20 specifications.
21
22 Asynchronous Signal
23 --------------------
24 It is not clear how to draw signals. It is usually drawn with a half-open
25 arrow.  This approach is used in Gaphor, too.
26
27 Delete Message
28 --------------
29 Different sources show that delete message has a "X" at the tail.
30 It does not seem to be correct solution. A "X" should be shown
31 at the end of lifeline's lifetime instead (see ``lifeline`` module
32 documentation for more information).
33
34 Events
35 ------
36 Occurence specification is not implemented, therefore
37 - no events implemented (i.e. destroy event)
38 - no message sequence number on communication diagram
39
40 Operations
41 ----------
42 ``Lifeline.represents`` attribute is ``None``, so it is not possible to
43 specify operation (or signal) for a message. Instead, one has to put
44 operation information in message's name.
45
46 See also ``lifeline`` module documentation.
47 """
48
49 from math import pi
50
51 from gaphas.util import path_ellipse
52
53 from gaphor import UML
54 from gaphor.diagram.diagramline import NamedLine
55 from gaphor.misc.odict import odict
56 from gaphor.diagram.style import ALIGN_CENTER, ALIGN_BOTTOM
57
58 PI_2 = pi / 2
59
60 class MessageItem(NamedLine):
61     """
62     Message item is drawn on sequence and communication diagrams.
63
64     On communication diagram, message item is decorated with an arrow in
65     the middle of a line.
66
67     Attributes:
68
69     - _is_communication: check if message is on communication diagram
70     - _arrow_pos: decorating arrow position
71     - _arrow_angle: decorating arrow angle
72     """
73
74     __style__ = {
75         'name-align-str': ':',
76     }
77
78     # name padding on sequence diagram
79     SD_PADDING = NamedLine.style.name_padding
80
81     # name padding on communication diagram
82     CD_PADDING = (10, 10, 10, 10)
83
84     def __init__(self, id=None):
85         super(MessageItem, self).__init__(id)
86         self._is_communication = False
87         self._arrow_pos = 0, 0
88         self._arrow_angle = 0
89         self._messages = odict()
90         self._inverted_messages = odict()
91
92
93     def pre_update(self, context):
94         """
95         Update communication diagram information.
96         """
97         self._is_communication = self.is_communication()
98         if self._is_communication:
99             self._name.style.text_padding = self.CD_PADDING
100         else:
101             self._name.style.text_padding = self.SD_PADDING
102
103         super(MessageItem, self).pre_update(context)
104
105
106     def post_update(self, context):
107         """
108         Update communication diagram information.
109         """
110         super(MessageItem, self).post_update(context)
111
112         if self._is_communication:
113             pos, angle = self._get_center_pos()
114             self._arrow_pos = pos
115             self._arrow_angle = angle
116
117
118     def save(self, save_func):
119         save_func('message', list(self._messages), reference=True)
120         save_func('inverted', list(self._inverted_messages), reference=True)
121
122         super(MessageItem, self).save(save_func)
123
124
125     def load(self, name, value):
126         if name == 'message':
127             #print 'message! value =', value
128             self.add_message(value, False)
129         elif name == 'inverted':
130             #print 'inverted! value =', value
131             self.add_message(value, True)
132         else:
133             super(MessageItem, self).load(name, value)
134
135
136     def postload(self):
137         for message in self._messages:
138             self.set_message_text(message, message.name, False)
139
140         for message in self._inverted_messages:
141             self.set_message_text(message, message.name, True)
142
143         super(MessageItem, self).postload()
144
145
146     def _draw_circle(self, cr):
147         """
148         Draw circle for lost/found messages.
149         """
150         # method is called by draw_head or by draw_tail methods,
151         # so draw in (0, 0))
152         cr.set_line_width(0.01)
153         cr.arc(0.0, 0.0, 4, 0.0, 2 * pi)
154         cr.fill()
155
156
157     def _draw_arrow(self, cr, half=False, filled=True):
158         """
159         Draw an arrow.
160
161         Parameters:
162
163         - half: draw half-open arrow
164         - filled: draw filled arrow
165         """
166         cr.move_to(15, 6)
167         cr.line_to(0, 0)
168         if not half:
169             cr.line_to(15, -6)
170         if filled:
171             cr.close_path()
172             cr.fill_preserve()
173
174
175     def draw_head(self, context):
176         cr = context.cairo
177         # no head drawing in case of communication diagram
178         if self._is_communication:
179             cr.move_to(0, 0)
180             return
181
182         cr.move_to(0, 0)
183
184         subject = self.subject
185         if subject and subject.messageKind == 'found':
186             self._draw_circle(cr)
187             cr.stroke()
188
189         cr.move_to(0, 0)
190
191
192     def draw_tail(self, context):
193         cr = context.cairo
194
195         # no tail drawing in case of communication diagram
196         if self._is_communication:
197             cr.line_to(0, 0)
198             return
199
200         subject = self.subject
201
202         if subject and subject.messageSort in ('createMessage', 'reply'):
203             cr.set_dash((7.0, 5.0), 0)
204
205         cr.line_to(0, 0)
206         cr.stroke()
207
208         cr.set_dash((), 0)
209
210         if subject:
211             w = cr.get_line_width()
212             if subject.messageKind == 'lost':
213                 self._draw_circle(cr)
214                 cr.stroke()
215
216             cr.set_line_width(w)
217             half = subject.messageSort == 'asynchSignal'
218             filled = subject.messageSort in ('synchCall', 'deleteMessage')
219             self._draw_arrow(cr, half, filled)
220         else:
221             self._draw_arrow(cr)
222
223         cr.stroke()
224
225
226     def _draw_decorating_arrow(self, cr, inverted=False):
227         cr.save()
228         try:
229             angle = self._arrow_angle
230
231             hint = -1
232
233             # rotation hint, keep arrow on the same side as message text
234             # elements
235             if abs(angle) >= PI_2 and angle != -PI_2:
236                 hint = 1
237
238             if inverted:
239                 angle += hint * pi
240
241             x, y = self._arrow_pos
242
243             # move to arrow pos and rotate, below we operate in horizontal
244             # mode
245             cr.translate(x, y)
246             cr.rotate(angle)
247             # add some padding
248             cr.translate(0, 6 * hint)
249
250             # draw decorating arrow
251             d = 15
252             dr = d - 4
253             r = 3
254             cr.set_line_width(1.5)
255             cr.move_to(-d, 0)
256             cr.line_to(d, 0)
257             cr.line_to(dr, r)
258             cr.move_to(dr, -r)
259             cr.line_to(d, 0)
260             cr.stroke()
261         finally:
262             cr.restore()
263
264
265     def draw(self, context):
266         super(MessageItem, self).draw(context)
267
268         # on communication diagram draw decorating arrows for messages and
269         # inverted messages
270         if self._is_communication:
271             cr = context.cairo
272             self._draw_decorating_arrow(cr)
273             if len(self._inverted_messages) > 0:
274                 self._draw_decorating_arrow(cr, True)
275
276
277     def is_communication(self):
278         """
279         Check if message is connecting to lifelines on communication
280         diagram.
281         """
282         lf1 = self.head.connected_to
283         lf2 = self.tail.connected_to
284         return lf1 and not lf1.lifetime.is_visible \
285                 or lf2 and not lf2.lifetime.is_visible
286
287
288     def add_message(self, message, inverted):
289         """
290         Add message onto communication diagram.
291         """
292         if inverted:
293             messages = self._inverted_messages
294             style = {
295                 'text-align-group': 'inverted',
296                 'text-align': (ALIGN_CENTER, ALIGN_BOTTOM),
297             }
298         else:
299             messages = self._messages
300             group = 'stereotype'
301             style = {
302                 'text-align-group': 'stereotype',
303             }
304
305         style['text-align-str'] = ':'
306         style['text-padding'] = self.CD_PADDING
307         txt = self.add_text('name', style=style)
308         txt.text = message.name
309         messages[message] = txt
310
311
312     def remove_message(self, message, inverted):
313         """
314         Remove message from communication diagram.
315         """
316         if inverted:
317             messages = self._inverted_messages
318         else:
319             messages = self._messages
320         txt = messages[message]
321         self.remove_text(txt)
322         del messages[message]
323
324
325     def set_message_text(self, message, text, inverted):
326         """
327         Set text of message on communication diagram.
328         """
329         if inverted:
330             messages = self._inverted_messages
331         else:
332             messages = self._messages
333         messages[message].text = text
334
335
336     def swap_messages(self, m1, m2, inverted):
337         """
338         Swap order of two messages on communication diagram.
339         """
340         if inverted:
341             messages = self._inverted_messages
342         else:
343             messages = self._messages
344         t1 = messages[m1]
345         t2 = messages[m2]
346         self.swap_texts(t1, t2)
347         messages.swap(m1, m2)
348         return True
349
350
351 # vim:sw=4:et
Note: See TracBrowser for help on using the browser.