root/gaphor/trunk/gaphor/diagram/activitynodes.py

Revision 2331, 9.9 kB (checked in by arj..@yirdis.nl, 4 months ago)

Removed ForkNodeItem._constraints, as it is already defined on a super class.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Activity control nodes.
3 """
4
5 import math
6
7 from gaphas.util import path_ellipse
8 from gaphas.state import observed, reversible_property
9 from gaphas.item import Handle, Item
10 from gaphas.constraint import EqualsConstraint, LessThanConstraint
11 from gaphas.geometry import distance_line_point
12
13 from gaphor import UML
14 from gaphor.core import inject
15 from gaphor.diagram.diagramitem import DiagramItem
16 from gaphor.diagram.nameditem import NamedItem
17 from gaphor.diagram.style import ALIGN_LEFT, ALIGN_CENTER, ALIGN_TOP, \
18         ALIGN_RIGHT, ALIGN_BOTTOM
19 from gaphor.diagram.style import get_text_point
20
21
22 DEFAULT_JOIN_SPEC = 'and'
23
24
25 class ActivityNodeItem(NamedItem):
26     """Basic class for simple activity nodes.
27     Simple activity node is not resizable.
28     """
29     __style__   = {
30         'name-outside': True,
31         'name-padding': (2, 2, 2, 2),
32     }
33
34     def __init__(self, id=None):
35         NamedItem.__init__(self, id)
36         # Do not allow resizing of the node
37         for h in self._handles:
38             h.movable = False
39
40        
41 class InitialNodeItem(ActivityNodeItem):
42     """
43     Representation of initial node. Initial node has name which is put near
44     top-left side of node.
45     """
46     __uml__     = UML.InitialNode
47     __style__   = {
48         'min-size':   (20, 20),
49         'name-align': (ALIGN_LEFT, ALIGN_TOP),
50     }
51    
52     RADIUS = 10
53
54     def draw(self, context):
55         cr = context.cairo
56         r = self.RADIUS
57         d = r * 2
58         path_ellipse(cr, r, r, d, d)
59         cr.set_line_width(0.01)
60         cr.fill()
61        
62         super(InitialNodeItem, self).draw(context)
63
64
65 class ActivityFinalNodeItem(ActivityNodeItem):
66     """Representation of activity final node. Activity final node has name
67     which is put near right-bottom side of node.
68     """
69
70     __uml__ = UML.ActivityFinalNode
71     __style__   = {
72         'min-size':   (30, 30),
73         'name-align': (ALIGN_RIGHT, ALIGN_BOTTOM),
74     }
75
76     RADIUS_1 = 10
77     RADIUS_2 = 15
78
79     def draw(self, context):
80         cr = context.cairo
81         r = self.RADIUS_2 + 1
82         d = self.RADIUS_1 * 2
83         path_ellipse(cr, r, r, d, d)
84         cr.set_line_width(0.01)
85         cr.fill()
86
87         d = r * 2
88         path_ellipse(cr, r, r, d, d)
89         cr.set_line_width(0.01)
90         cr.set_line_width(2)
91         cr.stroke()
92
93         super(ActivityFinalNodeItem, self).draw(context)
94
95
96 class FlowFinalNodeItem(ActivityNodeItem):
97     """
98     Representation of flow final node. Flow final node has name which is
99     put near right-bottom side of node.
100     """
101
102     __uml__ = UML.FlowFinalNode
103     __style__   = {
104         'min-size':   (20, 20),
105         'name-align': (ALIGN_RIGHT, ALIGN_BOTTOM),
106     }
107
108     RADIUS = 10
109
110     def draw(self, context):
111         cr = context.cairo
112         r = self.RADIUS
113         d = r * 2
114         path_ellipse(cr, r, r, d, d)
115         cr.stroke()
116
117         dr = (1 - math.sin(math.pi / 4)) * r
118         cr.move_to(dr, dr)
119         cr.line_to(d - dr, d - dr)
120         cr.move_to(dr, d - dr)
121         cr.line_to(d - dr, dr)
122         cr.stroke()
123        
124         super(FlowFinalNodeItem, self).draw(context)
125        
126
127
128 class DecisionNodeItem(ActivityNodeItem):
129     """
130     Representation of decision or merge node.
131     """
132     __uml__   = UML.DecisionNode
133     __style__   = {
134         'min-size':   (20, 30),
135         'name-align': (ALIGN_LEFT, ALIGN_TOP),
136     }
137
138     RADIUS = 15
139
140     def __init__(self, id=None):
141         ActivityNodeItem.__init__(self, id)
142         self._combined = None
143         #self.set_prop_persistent('combined')
144
145     def save(self, save_func):
146         if self._combined:
147             save_func('combined', self._combined, reference=True)
148         super(DecisionNodeItem, self).save(save_func)
149
150     def load(self, name, value):
151         if name == 'combined':
152             self._combined = value
153         else:
154             super(DecisionNodeItem, self).load(name, value)
155
156     @observed
157     def _set_combined(self, value):
158         #self.preserve_property('combined')
159         self._combined = value
160
161     combined = reversible_property(lambda s: s._combined, _set_combined)
162        
163     def draw(self, context):
164         """
165         Draw diamond shape, which represents decision and merge nodes.
166         """
167         cr = context.cairo
168         r = self.RADIUS
169         r2 = r * 2/3
170
171         cr.move_to(r2, 0)
172         cr.line_to(r2 * 2, r)
173         cr.line_to(r2, r * 2)
174         cr.line_to(0, r)
175         cr.close_path()
176         cr.stroke()
177
178         super(DecisionNodeItem, self).draw(context)
179
180
181
182 class ForkNodeItem(Item, DiagramItem):
183     """
184     Representation of fork and join node.
185     """
186
187     element_factory = inject('element_factory')
188
189     __uml__   = UML.ForkNode
190
191     __style__ = {
192         'min-size':   (6, 45),
193         'name-align': (ALIGN_CENTER, ALIGN_BOTTOM),
194         'name-padding': (2, 2, 2, 2),
195         'name-outside': True,
196         'name-align-str': None,
197     }
198
199     STYLE_TOP = {
200         'text-align': (ALIGN_CENTER, ALIGN_TOP),
201         'text-outside': True,
202     }
203
204     def __init__(self, id=None):
205         Item.__init__(self)
206         DiagramItem.__init__(self, id)
207        
208         self._handles.extend((Handle(), Handle()))
209
210         self._combined = None
211
212         self._join_spec = self.add_text('joinSpec.value',
213             pattern='{ joinSpec = %s }',
214             style=self.STYLE_TOP,
215             visible=self.is_join_spec_visible)
216
217         self._name = self.add_text('name', style={
218                     'text-align': self.style.name_align,
219                     'text-padding': self.style.name_padding,
220                     'text-outside': self.style.name_outside,
221                     'text-align-str': self.style.name_align_str,
222                     'text-align-group': 'stereotype',
223                 }, editable=True)
224
225         self.add_watch(UML.NamedElement.name, self.on_named_element_name)
226         self.add_watch(UML.JoinNode.joinSpec, self.on_join_node_join_spec)
227         self.add_watch(UML.LiteralSpecification.value, self.on_join_node_join_spec)
228
229
230     def save(self, save_func):
231         save_func('matrix', tuple(self.matrix))
232         save_func('height', float(self._handles[1].y))
233         if self._combined:
234             save_func('combined', self._combined, reference=True)
235         DiagramItem.save(self, save_func)
236
237     def load(self, name, value):
238         if name == 'matrix':
239             self.matrix = eval(value)
240         elif name == 'height':
241             self._handles[1].y = eval(value)
242         elif name == 'combined':
243             self._combined = value
244         else:
245             #DiagramItem.load(self, name, value)
246             super(ForkNodeItem, self).load(name, value)
247
248     def postload(self):
249         subject = self.subject
250         if subject and isinstance(subject, UML.JoinNode) and subject.joinSpec:
251             self._join_spec.text = self.subject.joinSpec.value
252         super(ForkNodeItem, self).postload()
253
254     @observed
255     def _set_combined(self, value):
256         #self.preserve_property('combined')
257         self._combined = value
258
259     combined = reversible_property(lambda s: s._combined, _set_combined)
260
261     def setup_canvas(self):
262         Item.setup_canvas(self)
263
264         h1, h2 = self._handles
265         cadd = self.canvas.solver.add_constraint
266         c1 = EqualsConstraint(a=h1.x, b=h2.x)
267         c2 = LessThanConstraint(smaller=h1.y, bigger=h2.y, delta=30)
268         self._constraints.extend((cadd(c1), cadd(c2)))
269
270
271     def teardown_canvas(self):
272         super(ForkNodeItem, self).teardown_canvas()
273
274
275     def is_join_spec_visible(self):
276         """
277         Check if join specification should be displayed.
278         """
279         return isinstance(self.subject, UML.JoinNode) \
280             and self.subject.joinSpec is not None \
281             and self.subject.joinSpec.value != DEFAULT_JOIN_SPEC
282
283
284     def text_align(self, extents, align, padding, outside):
285         h1, h2 = self._handles
286         w, _ = self.style.min_size
287         h = h2.y - h1.y
288         x, y = get_text_point(extents, w, h, align, padding, outside)
289
290         return x, y
291
292
293     def pre_update(self, context):
294         self.update_stereotype()
295         Item.pre_update(self, context)
296         DiagramItem.pre_update(self, context)
297
298
299     def post_update(self, context):
300         Item.post_update(self, context)
301         DiagramItem.post_update(self, context)
302
303
304     def draw(self, context):
305         """
306         Draw vertical line - symbol of fork and join nodes. Join
307         specification is also drawn above the item.
308         """
309         Item.draw(self, context)
310         DiagramItem.draw(self, context)
311
312         cr = context.cairo
313
314         cr.set_line_width(6)
315         h1, h2 = self._handles
316         cr.move_to(h1.x, h1.y)
317         cr.line_to(h2.x, h2.y)
318
319         cr.stroke()
320
321
322     def point(self, x, y):
323         h1, h2 = self._handles
324         d, p = distance_line_point(h1.pos, h2.pos, (x, y))
325         # Substract line_width / 2
326         return d - 3
327
328
329     def on_named_element_name(self, event):
330         subject = self.subject
331         if subject and (event is None or event.element is subject):
332             self._name.text = subject.name
333             self.request_update()
334
335     def on_join_node_join_spec(self, event):
336         subject = self.subject
337         if subject and is_join_node(subject) and \
338                 (event is None or \
339                  event.element is subject.joinSpec or \
340                  event.element is subject.joinSpec.value):
341             if is_join_node(subject) and not (subject.joinSpec and subject.joinSpec.value):
342                 self.set_join_spec(DEFAULT_JOIN_SPEC)
343             self.request_update()
344
345
346     def set_join_spec(self, value):
347         """
348         Set join specification.
349         """
350         subject = self.subject
351         if not is_join_node(subject):
352             return
353
354         if not subject.joinSpec:
355             subject.joinSpec = self.element_factory.create(UML.LiteralSpecification)
356
357         if not value:
358             value = DEFAULT_JOIN_SPEC
359
360         subject.joinSpec.value = value
361         self._join_spec.text = value
362
363
364 def is_join_node(subject):
365     """
366     Check if ``subject`` is join node.
367     """
368     return subject and isinstance(subject, UML.JoinNode)
369
370 # vim:sw=4:et
Note: See TracBrowser for help on using the browser.