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

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

- update guard text value on flow disconnection

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