root/gaphor/trunk/gaphor/diagram/flow.py

Revision 2139, 16.8 kB (checked in by arj..@yirdis.nl, 1 year ago)

fixed typo in flow.py.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 """
2 Control flow and object flow implementation.
3
4 Contains also implementation to split flows using activity edge connectors.
5 """
6
7 from math import atan, pi, sin, cos
8
9 from gaphor import UML
10 from gaphor.diagram.diagramline import NamedLine
11 from gaphor.diagram.style import ALIGN_LEFT, ALIGN_RIGHT, ALIGN_TOP
12
13
14 node_classes = {
15     UML.ForkNode:     UML.JoinNode,
16     UML.DecisionNode: UML.MergeNode,
17     UML.JoinNode:     UML.ForkNode,
18     UML.MergeNode:    UML.DecisionNode,
19 }
20
21
22
23 class FlowItem(NamedLine):
24     """
25     Representation of control flow and object flow. Flow item has name and
26     guard. It can be splitted into two flows with activity edge connectors.
27     """
28
29     __uml__ = UML.ControlFlow
30
31     __style__ = {
32             'name-align': (ALIGN_RIGHT, ALIGN_TOP),
33             'name-padding': (5, 15, 5, 5),
34     }
35
36     def __init__(self, id = None):
37         NamedLine.__init__(self, id)
38         self._guard = self.add_text('guard.value', editable=True)
39         self.add_watch(UML.ControlFlow.guard)
40         self.add_watch(UML.LiteralSpecification.value)
41
42
43     def postload(self):
44         if self.subject and self.subject.guard:
45             self._guard.text = self.subject.guard.value
46         super(FlowItem, self).postload()
47
48
49     def on_control_flow_guard(self, event):
50         element = event.element
51         subject = self.subject
52         if subject and (element is subject or element is subject.guard):
53             if not subject.guard or not subject.guard.value:
54                 self._guard.text = ''
55             self.request_update()
56
57
58     def set_guard(self, value):
59         self.subject.guard.value = value
60         self._guard.text = value
61
62
63     def draw_tail(self, context):
64         cr = context.cairo
65         cr.line_to(0, 0)
66         cr.stroke()
67         cr.move_to(15, -6)
68         cr.line_to(0, 0)
69         cr.line_to(15, 6)
70
71        
72 #class ACItem(TextElement):
73 class ACItem(object):
74     """
75     Activity edge connector. It is a circle with name inside.
76     """
77
78     RADIUS = 10
79     def __init__(self, id):
80         pass
81         #TextElement.__init__(self, id)
82         #self._circle = diacanvas.shape.Ellipse()
83         #self._circle.set_line_width(2.0)
84         #self._circle.set_fill_color(diacanvas.color(255, 255, 255))
85         #self._circle.set_fill(diacanvas.shape.FILL_SOLID)
86         #self.show_border = False
87
88         # set new value notification function to change activity edge
89         # connector name globally
90         #vnf = self.on_subject_notify__value
91         #def f(subject, pspec):
92         #    vnf(subject, pspec)
93         #    if self.parent._opposite:
94         #        self.parent._opposite._connector.subject.value = subject.value
95         #self.on_subject_notify__value = f
96
97
98     def move_center(self, x, y):
99         """
100         Move center of item to point (x, y). Other parts of item are
101         aligned to this point.
102         """
103         a = self.props.affine
104         x -= self.RADIUS
105         y -= self.RADIUS
106         self.props.affine = (a[0], a[1], a[2], a[3], x, y)
107
108
109     def on_update(self, affine):
110         """
111         Center name of activity edge connector and put a circle around it.
112         """
113         r = self.RADIUS * 2
114         x = self.RADIUS
115         y = self.RADIUS
116
117         self._circle.ellipse(center = (x, y), width = r, height = r)
118
119         # get label size and move it so it is centered with circle
120         w, h = self.get_size()
121         x, y = x - w / 2, y - h / 2
122         self._name.set_pos((x, y))
123         self._name_bounds = (x, y, x + w, y + h)
124
125         TextElement.on_update(self, affine)
126
127         self.set_bounds((-1, -1, r + 1, r + 1))
128
129
130 #class CFlowItem(FlowItem):
131 #    """
132 #    Abstract class for flows with activity edge connector. Flow with
133 #    activity edge connector references other one, which has activity edge
134 #    connector with same name (it is called opposite one).
135 #
136 #    Such flows have active and inactive ends. Active end is connected to
137 #    any node and inactive end is connected only to activity edge connector.
138 #    """
139 #
140 #    def __init__(self, id = None):
141 #        FlowItem.__init__(self, id)
142 #
143 #        self._connector = ACItem('value')
144 #
145 #        factory = resource(UML.ElementFactory)
146 #        self._connector.subject = factory.create(UML.LiteralSpecification)
147 #        self.add(self._connector)
148 #
149 #        self._opposite = None
150 #
151 #        # when flow item with connector is deleted, then kill opposite, too
152 #        self.unlink_handler_id = self.connect('__unlink__', self.kill_opposite)
153 #
154 #
155 #    def kill_opposite(self, source, name):
156 #        # do not allow to be killed by opposite
157 #        self._opposite.disconnect(self._opposite.unlink_handler_id)
158 #        self._opposite.unlink()
159 #
160 #
161 #    def save(self, save_func):
162 #        """
163 #        Save connector name and opposite flow with activity edge connector.
164 #        """
165 #        FlowItem.save(self, save_func)
166 #        save_func('opposite', self._opposite, True)
167 #        save_func('connector-name', self._connector.subject.value)
168 #
169 #
170 #    def load(self, name, value):
171 #        """
172 #        Load connector name and opposite flow with activity edge connector.
173 #        """
174 #        if name == 'connector-name':
175 #            self._connector.subject.value = value
176 #        elif name == 'opposite':
177 #            self._opposite = value
178 #        else:
179 #            FlowItem.load(self, name, value)
180 #
181 #
182 #    def on_update(self, affine):
183 #        """
184 #        Draw flow line and activity edge connector.
185 #        """
186 #        # get parent line points to determine angle
187 #        # used to rotate position of activity edge connector
188 #        p1, p2 = self.get_line()
189 #
190 #        # calculate position of connector center
191 #        r = self._connector.RADIUS
192 #        #x = p1[0] < p2[0] and r or -r
193 #        x = p1[0] < p2[0] and -r or r
194 #        y = 0
195 #        x, y = rotate(p1, p2, x, y, p1[0], p1[1])
196 #
197 #        self._connector.move_center(x, y)
198 #
199 #        FlowItem.on_update(self, affine)
200 #
201 #
202 #    def confirm_connect_handle(self, handle):
203 #        """See NamedLine.confirm_connect_handle().
204 #        """
205 #        c1 = self.get_active_handle().connected_to            # source
206 #        c2 = self._opposite.get_active_handle().connected_to  # target
207 #
208 #        # set correct relationship between connected items;
209 #        # it should be (source, target) not (target, source);
210 #        # otherwise we are looking for non-existing or wrong relationship
211 #        if isinstance(self, CFlowItemB):
212 #            c1, c2 = c2, c1
213 #
214 #        self.connect_items(c1, c2)
215 #        self._opposite.set_subject(self.subject)
216 #
217 #
218 #    def allow_connect_handle(self, handle, connecting_to):
219 #        if handle == self.get_inactive_handle():
220 #            return False
221 #        return FlowItem.allow_connect_handle(self, handle, connecting_to)
222 #
223 #
224 #    def confirm_disconnect_handle (self, handle, was_connected_to):
225 #        """See NamedLine.confirm_disconnect_handle().
226 #        """
227 #        c1 = self.get_active_handle().connected_to             # source
228 #        c2 = self._opposite.get_active_handle().connected_to   # target
229 #        self.disconnect_items(c1, c2, was_connected_to)
230 #        self._opposite.set_subject(None)
231 #
232 #
233 #
234 #class CFlowItemA(CFlowItem):
235 #    """
236 #    * Is used for split flows, as is CFlowItemB *
237 #
238 #    Flow with activity edge connector, which starts from node and points to
239 #    activity edge connector.
240 #    """
241 #    def __init__(self, id):
242 #        CFlowItem.__init__(self, id)
243 #        self.create_guard()
244 #
245 #
246 #    def on_update(self, affine):
247 #        self.update_guard(affine)
248 #        CFlowItem.on_update(self, affine)
249 #
250 #
251 #    def get_line(self):
252 #        p1 = self.handles[-1].get_pos_i()
253 #        p2 = self.handles[-2].get_pos_i()
254 #        return p1, p2
255 #
256 #
257 #    def get_active_handle(self):
258 #        """
259 #        Return source handle as active one.
260 #        """
261 #        return self.handles[0]
262 #
263 #
264 #    def get_inactive_handle(self):
265 #        """
266 #        Return target handle as inactive one.
267 #        """
268 #        return self.handles[-1]
269 #
270 #
271 #
272 #class CFlowItemB(CFlowItem):
273 #    """
274 #    Flow with activity edge connector, which starts from activity edge
275 #    connector and points to a node.
276 #    """
277 #    def __init__(self, id):
278 #        CFlowItem.__init__(self, id)
279 #        self.create_name()
280 #
281 #
282 #    def on_update(self, affine):
283 #        self.update_name(affine)
284 #        CFlowItem.on_update(self, affine)
285 #
286 #
287 #    def get_line(self):
288 #        p1 = self.handles[0].get_pos_i()
289 #        p2 = self.handles[1].get_pos_i()
290 #        return p1, p2
291 #
292 #
293 #    def get_active_handle(self):
294 #        """
295 #        Return target handle as active one.
296 #        """
297 #        return self.handles[-1]
298 #
299 #
300 #    def get_inactive_handle(self):
301 #        """
302 #        Return source handle as inactive one.
303 #        """
304 #        return self.handles[0]
305 #
306 #
307 #def move_collection(src, target, name):
308 #    """
309 #    Copy collection from one object to another.
310 #
311 #    src    - source object
312 #    target - target object
313 #    name   - name of attribute, which is collection to copy
314 #    """
315 #    # first make of copy of collection, because assigning
316 #    # element to target collection moves this element
317 #    for flow in list(getattr(src, name)):
318 #        getattr(target, name).append(flow)
319 #
320
321 #def is_fd(node):
322 #    """
323 #    Check if node is fork or decision node.
324 #    """
325 #    return isinstance(node, (UML.ForkNode, UML.DecisionNode))
326 #
327 #
328 #def change_node_class(node):
329 #    """
330 #    If UML constraints for fork, join, decision and merge nodes are not
331 #    met, then create new node depending on input node class, i.e. create
332 #    fork node from join node or merge node from decision node.
333 #
334 #    If constraints are met, then return node itself.
335 #    """
336 #    if is_fd(node) and len(node.incoming) > 1 \
337 #            or not is_fd(node) and len(node.incoming) < 2:
338 #
339 #        factory = resource(UML.ElementFactory)
340 #        cls = node_classes[node.__class__]
341 #        log.debug('creating %s' % cls)
342 #        nn = factory.create(cls)
343 #        move_collection(node, nn, 'incoming')
344 #        move_collection(node, nn, 'outgoing')
345 #    else:
346 #        nn = node
347 #
348 #    assert nn is not None
349 #
350 #    # we have to accept zero of outgoing edges in case of fork/descision
351 #    # nodes
352 #    assert is_fd(nn) and len(nn.incoming) <= 1 \
353 #        or not is_fd(nn) and len(nn.incoming) >= 1, '%s' % nn
354 #    assert is_fd(nn) and len(nn.outgoing) >= 0 \
355 #        or not is_fd(nn) and len(nn.outgoing) <= 1, '%s' % nn
356 #    return nn
357 #
358 #
359 #def combine_nodes(node):
360 #    """
361 #    Create fork/join (decision/merge) nodes combination as described in UML
362 #    specification.
363 #    """
364 #    log.debug('combining nodes')
365 #
366 #    cls = node_classes[node.__class__]
367 #    log.debug('creating %s' % cls)
368 #    factory = resource(UML.ElementFactory)
369 #    target = factory.create(cls)
370 #
371 #    source = node
372 #    if is_fd(node):
373 #        source = target
374 #        move_collection(node, target, 'incoming')
375 #
376 #        # create new fork node
377 #        cls = node_classes[target.__class__]
378 #        log.debug('creating %s' % cls)
379 #        target = factory.create(cls)
380 #        move_collection(node, target, 'outgoing')
381 #    else:
382 #        # fork node is created, referenced by target
383 #        move_collection(node, target, 'outgoing')
384 #
385 #    assert not is_fd(source)
386 #    assert is_fd(target)
387 #
388 #    # create flow
389 #    c1 = count_object_flows(source, 'incoming')
390 #    c2 = count_object_flows(target, 'outgoing')
391 #
392 #    if c1 > 0 or c2 > 0:
393 #        flow = factory.create(UML.ControlFlow)
394 #    else:
395 #        flow = factory.create(UML.ObjectFlow)
396 #    flow.source = source
397 #    flow.target = target
398 #
399 #    assert len(source.incoming) > 1
400 #    assert len(source.outgoing) == 1
401 #
402 #    assert len(target.incoming) == 1
403 #    assert len(target.outgoing) > 1
404 #
405 #    return source
406 #
407 #
408 #def decombine_nodes(source):
409 #    """
410 #    Create node depending on source argument which denotes combination of
411 #    fork/join (decision/merge) nodes as described in UML specification.
412 #
413 #    Combination of nodes is destroyed.
414 #    """
415 #    log.debug('decombining nodes')
416 #    flow = source.outgoing[0]
417 #    target = flow.target
418 #
419 #    if len(source.incoming) < 2:
420 #        # create fork or decision
421 #        cls = target.__class__
422 #    else:
423 #        # create join or merge
424 #        cls = source.__class__
425 #
426 #    factory = resource(UML.ElementFactory)
427 #    node = factory.create(cls)
428 #
429 #    move_collection(source, node, 'incoming')
430 #    move_collection(target, node, 'outgoing')
431 #
432 #    assert source != node
433 #
434 #    # delete target and combining flow
435 #    # source should be deleted by caller
436 #    target.unlink()
437 #    flow.unlink()
438 #
439 #    # return new node
440 #    return node
441
442
443 #def determine_node_on_connect(el):
444 #    """
445 #    Determine classes of nodes depending on amount of incoming
446 #    and outgoing edges. This method is called when flow is attached
447 #    to node.
448 #
449 #    If there is more than one incoming edge and more than one
450 #    outgoing edge, then create two nodes and combine them with
451 #    flow as described in UML specification.
452 #    """
453 #    subject = el.subject
454 #    if not isinstance(subject, tuple(node_classes.keys())):
455 #        return
456 #
457 #    new_subject = subject
458 #
459 #    if len(subject.incoming) > 1 and len(subject.outgoing) > 1:
460 #        new_subject = combine_nodes(subject)
461 #        el.props.combined = True
462 #
463 #    else:
464 #        new_subject = change_node_class(subject)
465 #
466 #    change_node_subject(el, new_subject)
467 #
468 #    if el.props.combined:
469 #        check_combining_flow(el)
470
471
472 #def determine_node_on_disconnect(el):
473 #    """
474 #    Determine classes of nodes depending on amount of incoming
475 #    and outgoing edges. This method is called when flow is dettached
476 #    from node.
477 #
478 #    If there are combined nodes and there is no need for them, then replace
479 #    combination with appropriate node (i.e. replace with fork node when
480 #    there are less than two incoming edges). This way data model is kept as
481 #    simple as possible.
482 #    """
483 #    subject = el.subject
484 #    if not isinstance(subject, tuple(node_classes.keys())):
485 #        return
486 #
487 #    new_subject = subject
488 #
489 #    if el.props.combined:
490 #        cs = subject.outgoing[0].target
491 #        # decombine node when there is no more than one incoming
492 #        # and no more than one outgoing flow
493 #        if len(subject.incoming) < 2 or len(cs.outgoing) < 2:
494 #            new_subject = decombine_nodes(subject)
495 #            el.props.combined = False
496 #        else:
497 #            check_combining_flow(el)
498 #
499 #    else:
500 #        new_subject = change_node_class(subject)
501 #
502 #    change_node_subject(el, new_subject)
503
504
505 #def change_node_subject(el, new_subject):
506 #    """
507 #    Change element's subject if new subject is different than element's
508 #    subject. If subject is changed, then old subject is destroyed.
509 #    """
510 #    subject = el.subject
511 #    if new_subject != subject:
512 #        log.debug('changing subject of ui node %s' % el)
513 #        el.set_subject(new_subject)
514 #
515 #        log.debug('deleting node %s' % subject)
516 #        subject.unlink()
517
518
519 #def create_flow(cls, flow):
520 #    """
521 #    Create new flow of class cls. Flow data from flow argument are copied
522 #    to new created flow. Old flow is destroyed.
523 #    """
524 #    factory = resource(UML.ElementFactory)
525 #    f = factory.create(cls)
526 #    f.source = flow.source
527 #    f.target = flow.target
528 #    flow.unlink()
529 #    return f
530
531
532 #def count_object_flows(node, attr):
533 #   """
534 #    Count incoming or outgoing object flows.
535 #    """
536 #    return len(getattr(node, attr)
537 #        .select(lambda flow: isinstance(flow, UML.ObjectFlow)))
538 #
539 #
540 #def check_combining_flow(el):
541 #    """
542 #    Set object flow as combining flow when incoming or outgoing flow count
543 #    is greater than zero. Otherwise change combining flow to control flow.
544 #    """
545 #    subject = el.subject
546 #    flow = subject.outgoing[0] # combining flow
547 #    combined = flow.target     # combined node
548 #
549 #    c1 = count_object_flows(subject, 'incoming')
550 #    c2 = count_object_flows(combined, 'outgoing')
551 #
552 #    log.debug('combined incoming and outgoing object flow count: (%d, %d)' % (c1, c2))
553 #
554 #    if (c1 > 0 or c2 > 0) and isinstance(flow, UML.ControlFlow):
555 #        log.debug('changing combing flow to object flow')
556 #        create_flow(UML.ObjectFlow, flow)
557 #    elif c1 == 0 and c2 == 0 and isinstance(flow, UML.ObjectFlow):
558 #        log.debug('changing combing flow to control flow')
559 #        create_flow(UML.ControlFlow, flow)
560 #
561
562 #def create_connector_end(connector, role):
563 #    """
564 #    Create Connector End, set role and attach created end to
565 #    connector.
566 #    """
567 #    end = resource(UML.ElementFactory).create(UML.ConnectorEnd)
568 #    end.role = role
569 #    connector.end = end
570 #    assert end in role.end
571 #    return end
572 #
573
574 #def rotate(p1, p2, a, b, x, y):
575 #    """
576 #    Rotate point (a, b) by angle, which is determined by line (p1, p2).
577 #
578 #    Rotated point is moved by vector (x, y).
579 #    """
580 #    try:
581 #        angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0]))
582 #    except ZeroDivisionError:
583 #        da = p1[1] < p2[1] and 1.5 or -1.5
584 #        angle = pi * da
585 #
586 #    sin_angle = sin(angle)
587 #    cos_angle = cos(angle)
588 #    return (cos_angle * a - sin_angle * b + x,
589 #            sin_angle * a + cos_angle * b + y)
590
591 # vim:sw=4:et:ai
Note: See TracBrowser for help on using the browser.