| 224 | | class CFlowItem(FlowItem): |
|---|
| 225 | | """ |
|---|
| 226 | | Abstract class for flows with activity edge connector. Flow with |
|---|
| 227 | | activity edge connector references other one, which has activity edge |
|---|
| 228 | | connector with same name (it is called opposite one). |
|---|
| 229 | | |
|---|
| 230 | | Such flows have active and inactive ends. Active end is connected to |
|---|
| 231 | | any node and inactive end is connected only to activity edge connector. |
|---|
| 232 | | """ |
|---|
| 233 | | |
|---|
| 234 | | popup_menu = DiagramLine.popup_menu + ( |
|---|
| 235 | | 'separator', |
|---|
| 236 | | 'MergeFlow', |
|---|
| 237 | | ) |
|---|
| 238 | | |
|---|
| 239 | | def __init__(self, id = None): |
|---|
| 240 | | FlowItem.__init__(self, id) |
|---|
| 241 | | |
|---|
| 242 | | self._connector = ACItem('value') |
|---|
| 243 | | |
|---|
| 244 | | factory = resource(UML.ElementFactory) |
|---|
| 245 | | self._connector.subject = factory.create(UML.LiteralSpecification) |
|---|
| 246 | | self.add(self._connector) |
|---|
| 247 | | |
|---|
| 248 | | self._opposite = None |
|---|
| 249 | | |
|---|
| 250 | | # when flow item with connector is deleted, then kill opposite, too |
|---|
| 251 | | self.unlink_handler_id = self.connect('__unlink__', self.kill_opposite) |
|---|
| 252 | | |
|---|
| 253 | | |
|---|
| 254 | | def kill_opposite(self, source, name): |
|---|
| 255 | | # do not allow to be killed by opposite |
|---|
| 256 | | self._opposite.disconnect(self._opposite.unlink_handler_id) |
|---|
| 257 | | self._opposite.unlink() |
|---|
| 258 | | |
|---|
| 259 | | |
|---|
| 260 | | def save(self, save_func): |
|---|
| 261 | | """ |
|---|
| 262 | | Save connector name and opposite flow with activity edge connector. |
|---|
| 263 | | """ |
|---|
| 264 | | FlowItem.save(self, save_func) |
|---|
| 265 | | save_func('opposite', self._opposite, True) |
|---|
| 266 | | save_func('connector-name', self._connector.subject.value) |
|---|
| 267 | | |
|---|
| 268 | | |
|---|
| 269 | | def load(self, name, value): |
|---|
| 270 | | """ |
|---|
| 271 | | Load connector name and opposite flow with activity edge connector. |
|---|
| 272 | | """ |
|---|
| 273 | | if name == 'connector-name': |
|---|
| 274 | | self._connector.subject.value = value |
|---|
| 275 | | elif name == 'opposite': |
|---|
| 276 | | self._opposite = value |
|---|
| 277 | | else: |
|---|
| 278 | | FlowItem.load(self, name, value) |
|---|
| 279 | | |
|---|
| 280 | | |
|---|
| 281 | | def on_update(self, affine): |
|---|
| 282 | | """ |
|---|
| 283 | | Draw flow line and activity edge connector. |
|---|
| 284 | | """ |
|---|
| 285 | | # get parent line points to determine angle |
|---|
| 286 | | # used to rotate position of activity edge connector |
|---|
| 287 | | p1, p2 = self.get_line() |
|---|
| 288 | | |
|---|
| 289 | | # calculate position of connector center |
|---|
| 290 | | r = self._connector.RADIUS |
|---|
| 291 | | #x = p1[0] < p2[0] and r or -r |
|---|
| 292 | | x = p1[0] < p2[0] and -r or r |
|---|
| 293 | | y = 0 |
|---|
| 294 | | x, y = rotate(p1, p2, x, y, p1[0], p1[1]) |
|---|
| 295 | | |
|---|
| 296 | | self._connector.move_center(x, y) |
|---|
| 297 | | |
|---|
| 298 | | FlowItem.on_update(self, affine) |
|---|
| 299 | | |
|---|
| 300 | | |
|---|
| 301 | | def confirm_connect_handle(self, handle): |
|---|
| 302 | | """See DiagramLine.confirm_connect_handle(). |
|---|
| 303 | | """ |
|---|
| 304 | | c1 = self.get_active_handle().connected_to # source |
|---|
| 305 | | c2 = self._opposite.get_active_handle().connected_to # target |
|---|
| 306 | | |
|---|
| 307 | | # set correct relationship between connected items; |
|---|
| 308 | | # it should be (source, target) not (target, source); |
|---|
| 309 | | # otherwise we are looking for non-existing or wrong relationship |
|---|
| 310 | | if isinstance(self, CFlowItemB): |
|---|
| 311 | | c1, c2 = c2, c1 |
|---|
| 312 | | |
|---|
| 313 | | self.connect_items(c1, c2) |
|---|
| 314 | | self._opposite.set_subject(self.subject) |
|---|
| 315 | | |
|---|
| 316 | | |
|---|
| 317 | | def allow_connect_handle(self, handle, connecting_to): |
|---|
| 318 | | if handle == self.get_inactive_handle(): |
|---|
| 319 | | return False |
|---|
| 320 | | return FlowItem.allow_connect_handle(self, handle, connecting_to) |
|---|
| 321 | | |
|---|
| 322 | | |
|---|
| 323 | | def confirm_disconnect_handle (self, handle, was_connected_to): |
|---|
| 324 | | """See DiagramLine.confirm_disconnect_handle(). |
|---|
| 325 | | """ |
|---|
| 326 | | c1 = self.get_active_handle().connected_to # source |
|---|
| 327 | | c2 = self._opposite.get_active_handle().connected_to # target |
|---|
| 328 | | self.disconnect_items(c1, c2, was_connected_to) |
|---|
| 329 | | self._opposite.set_subject(None) |
|---|
| 330 | | |
|---|
| 331 | | |
|---|
| 332 | | |
|---|
| 333 | | class CFlowItemA(CFlowItem): |
|---|
| 334 | | """ |
|---|
| 335 | | * Is used for split flows, as is CFlowItemB * |
|---|
| 336 | | |
|---|
| 337 | | Flow with activity edge connector, which starts from node and points to |
|---|
| 338 | | activity edge connector. |
|---|
| 339 | | """ |
|---|
| 340 | | def __init__(self, id): |
|---|
| 341 | | CFlowItem.__init__(self, id) |
|---|
| 342 | | self.create_guard() |
|---|
| 343 | | |
|---|
| 344 | | |
|---|
| 345 | | def on_update(self, affine): |
|---|
| 346 | | self.update_guard(affine) |
|---|
| 347 | | CFlowItem.on_update(self, affine) |
|---|
| 348 | | |
|---|
| 349 | | |
|---|
| 350 | | def get_line(self): |
|---|
| 351 | | p1 = self.handles[-1].get_pos_i() |
|---|
| 352 | | p2 = self.handles[-2].get_pos_i() |
|---|
| 353 | | return p1, p2 |
|---|
| 354 | | |
|---|
| 355 | | |
|---|
| 356 | | def get_active_handle(self): |
|---|
| 357 | | """ |
|---|
| 358 | | Return source handle as active one. |
|---|
| 359 | | """ |
|---|
| 360 | | return self.handles[0] |
|---|
| 361 | | |
|---|
| 362 | | |
|---|
| 363 | | def get_inactive_handle(self): |
|---|
| 364 | | """ |
|---|
| 365 | | Return target handle as inactive one. |
|---|
| 366 | | """ |
|---|
| 367 | | return self.handles[-1] |
|---|
| 368 | | |
|---|
| 369 | | |
|---|
| 370 | | |
|---|
| 371 | | class CFlowItemB(CFlowItem): |
|---|
| 372 | | """ |
|---|
| 373 | | Flow with activity edge connector, which starts from activity edge |
|---|
| 374 | | connector and points to a node. |
|---|
| 375 | | """ |
|---|
| 376 | | def __init__(self, id): |
|---|
| 377 | | CFlowItem.__init__(self, id) |
|---|
| 378 | | self.create_name() |
|---|
| 379 | | |
|---|
| 380 | | |
|---|
| 381 | | def on_update(self, affine): |
|---|
| 382 | | self.update_name(affine) |
|---|
| 383 | | CFlowItem.on_update(self, affine) |
|---|
| 384 | | |
|---|
| 385 | | |
|---|
| 386 | | def get_line(self): |
|---|
| 387 | | p1 = self.handles[0].get_pos_i() |
|---|
| 388 | | p2 = self.handles[1].get_pos_i() |
|---|
| 389 | | return p1, p2 |
|---|
| 390 | | |
|---|
| 391 | | |
|---|
| 392 | | def get_active_handle(self): |
|---|
| 393 | | """ |
|---|
| 394 | | Return target handle as active one. |
|---|
| 395 | | """ |
|---|
| 396 | | return self.handles[-1] |
|---|
| 397 | | |
|---|
| 398 | | |
|---|
| 399 | | def get_inactive_handle(self): |
|---|
| 400 | | """ |
|---|
| 401 | | Return source handle as inactive one. |
|---|
| 402 | | """ |
|---|
| 403 | | return self.handles[0] |
|---|
| 404 | | |
|---|
| 405 | | |
|---|
| 406 | | |
|---|
| 407 | | |
|---|
| 408 | | def move_collection(src, target, name): |
|---|
| 409 | | """ |
|---|
| 410 | | Copy collection from one object to another. |
|---|
| 411 | | |
|---|
| 412 | | src - source object |
|---|
| 413 | | target - target object |
|---|
| 414 | | name - name of attribute, which is collection to copy |
|---|
| 415 | | """ |
|---|
| 416 | | # first make of copy of collection, because assigning |
|---|
| 417 | | # element to target collection moves this element |
|---|
| 418 | | for flow in list(getattr(src, name)): |
|---|
| 419 | | getattr(target, name).append(flow) |
|---|
| 420 | | |
|---|
| 421 | | |
|---|
| 422 | | def is_fd(node): |
|---|
| 423 | | """ |
|---|
| 424 | | Check if node is fork or decision node. |
|---|
| 425 | | """ |
|---|
| 426 | | return isinstance(node, (UML.ForkNode, UML.DecisionNode)) |
|---|
| 427 | | |
|---|
| 428 | | |
|---|
| 429 | | def change_node_class(node): |
|---|
| 430 | | """ |
|---|
| 431 | | If UML constraints for fork, join, decision and merge nodes are not |
|---|
| 432 | | met, then create new node depending on input node class, i.e. create |
|---|
| 433 | | fork node from join node or merge node from decision node. |
|---|
| 434 | | |
|---|
| 435 | | If constraints are met, then return node itself. |
|---|
| 436 | | """ |
|---|
| 437 | | if is_fd(node) and len(node.incoming) > 1 \ |
|---|
| 438 | | or not is_fd(node) and len(node.incoming) < 2: |
|---|
| 439 | | |
|---|
| 440 | | factory = resource(UML.ElementFactory) |
|---|
| 441 | | cls = node_classes[node.__class__] |
|---|
| 442 | | log.debug('creating %s' % cls) |
|---|
| 443 | | nn = factory.create(cls) |
|---|
| 444 | | move_collection(node, nn, 'incoming') |
|---|
| 445 | | move_collection(node, nn, 'outgoing') |
|---|
| 446 | | else: |
|---|
| 447 | | nn = node |
|---|
| 448 | | |
|---|
| 449 | | assert nn is not None |
|---|
| 450 | | |
|---|
| 451 | | # we have to accept zero of outgoing edges in case of fork/descision |
|---|
| 452 | | # nodes |
|---|
| 453 | | assert is_fd(nn) and len(nn.incoming) <= 1 \ |
|---|
| 454 | | or not is_fd(nn) and len(nn.incoming) >= 1, '%s' % nn |
|---|
| 455 | | assert is_fd(nn) and len(nn.outgoing) >= 0 \ |
|---|
| 456 | | or not is_fd(nn) and len(nn.outgoing) <= 1, '%s' % nn |
|---|
| 457 | | return nn |
|---|
| 458 | | |
|---|
| 459 | | |
|---|
| 460 | | def combine_nodes(node): |
|---|
| 461 | | """ |
|---|
| 462 | | Create fork/join (decision/merge) nodes combination as described in UML |
|---|
| 463 | | specification. |
|---|
| 464 | | """ |
|---|
| 465 | | log.debug('combining nodes') |
|---|
| 466 | | |
|---|
| 467 | | cls = node_classes[node.__class__] |
|---|
| 468 | | log.debug('creating %s' % cls) |
|---|
| 469 | | factory = resource(UML.ElementFactory) |
|---|
| 470 | | target = factory.create(cls) |
|---|
| 471 | | |
|---|
| 472 | | source = node |
|---|
| 473 | | if is_fd(node): |
|---|
| 474 | | source = target |
|---|
| 475 | | move_collection(node, target, 'incoming') |
|---|
| 476 | | |
|---|
| 477 | | # create new fork node |
|---|
| 478 | | cls = node_classes[target.__class__] |
|---|
| 479 | | log.debug('creating %s' % cls) |
|---|
| 480 | | target = factory.create(cls) |
|---|
| 481 | | move_collection(node, target, 'outgoing') |
|---|
| 482 | | else: |
|---|
| 483 | | # fork node is created, referenced by target |
|---|
| 484 | | move_collection(node, target, 'outgoing') |
|---|
| 485 | | |
|---|
| 486 | | assert not is_fd(source) |
|---|
| 487 | | assert is_fd(target) |
|---|
| 488 | | |
|---|
| 489 | | # create flow |
|---|
| 490 | | c1 = count_object_flows(source, 'incoming') |
|---|
| 491 | | c2 = count_object_flows(target, 'outgoing') |
|---|
| 492 | | |
|---|
| 493 | | if c1 > 0 or c2 > 0: |
|---|
| 494 | | flow = factory.create(UML.ControlFlow) |
|---|
| 495 | | else: |
|---|
| 496 | | flow = factory.create(UML.ObjectFlow) |
|---|
| 497 | | flow.source = source |
|---|
| 498 | | flow.target = target |
|---|
| 499 | | |
|---|
| 500 | | assert len(source.incoming) > 1 |
|---|
| 501 | | assert len(source.outgoing) == 1 |
|---|
| 502 | | |
|---|
| 503 | | assert len(target.incoming) == 1 |
|---|
| 504 | | assert len(target.outgoing) > 1 |
|---|
| 505 | | |
|---|
| 506 | | return source |
|---|
| 507 | | |
|---|
| 508 | | |
|---|
| 509 | | def decombine_nodes(source): |
|---|
| 510 | | """ |
|---|
| 511 | | Create node depending on source argument which denotes combination of |
|---|
| 512 | | fork/join (decision/merge) nodes as described in UML specification. |
|---|
| 513 | | |
|---|
| 514 | | Combination of nodes is destroyed. |
|---|
| 515 | | """ |
|---|
| 516 | | log.debug('decombining nodes') |
|---|
| 517 | | flow = source.outgoing[0] |
|---|
| 518 | | target = flow.target |
|---|
| 519 | | |
|---|
| 520 | | if len(source.incoming) < 2: |
|---|
| 521 | | # create fork or decision |
|---|
| 522 | | cls = target.__class__ |
|---|
| 523 | | else: |
|---|
| 524 | | # create join or merge |
|---|
| 525 | | cls = source.__class__ |
|---|
| 526 | | |
|---|
| 527 | | factory = resource(UML.ElementFactory) |
|---|
| 528 | | node = factory.create(cls) |
|---|
| 529 | | |
|---|
| 530 | | move_collection(source, node, 'incoming') |
|---|
| 531 | | move_collection(target, node, 'outgoing') |
|---|
| 532 | | |
|---|
| 533 | | assert source != node |
|---|
| 534 | | |
|---|
| 535 | | # delete target and combining flow |
|---|
| 536 | | # source should be deleted by caller |
|---|
| 537 | | target.unlink() |
|---|
| 538 | | flow.unlink() |
|---|
| 539 | | |
|---|
| 540 | | # return new node |
|---|
| 541 | | return node |
|---|
| 542 | | |
|---|
| 543 | | |
|---|
| 544 | | def determine_node_on_connect(el): |
|---|
| 545 | | """ |
|---|
| 546 | | Determine classes of nodes depending on amount of incoming |
|---|
| 547 | | and outgoing edges. This method is called when flow is attached |
|---|
| 548 | | to node. |
|---|
| 549 | | |
|---|
| 550 | | If there is more than one incoming edge and more than one |
|---|
| 551 | | outgoing edge, then create two nodes and combine them with |
|---|
| 552 | | flow as described in UML specification. |
|---|
| 553 | | """ |
|---|
| 554 | | subject = el.subject |
|---|
| 555 | | if not isinstance(subject, tuple(node_classes.keys())): |
|---|
| 556 | | return |
|---|
| 557 | | |
|---|
| 558 | | new_subject = subject |
|---|
| 559 | | |
|---|
| 560 | | if len(subject.incoming) > 1 and len(subject.outgoing) > 1: |
|---|
| 561 | | new_subject = combine_nodes(subject) |
|---|
| 562 | | el.props.combined = True |
|---|
| 563 | | |
|---|
| 564 | | else: |
|---|
| 565 | | new_subject = change_node_class(subject) |
|---|
| 566 | | |
|---|
| 567 | | change_node_subject(el, new_subject) |
|---|
| 568 | | |
|---|
| 569 | | if el.props.combined: |
|---|
| 570 | | check_combining_flow(el) |
|---|
| 571 | | |
|---|
| 572 | | |
|---|
| 573 | | def determine_node_on_disconnect(el): |
|---|
| 574 | | """ |
|---|
| 575 | | Determine classes of nodes depending on amount of incoming |
|---|
| 576 | | and outgoing edges. This method is called when flow is dettached |
|---|
| 577 | | from node. |
|---|
| 578 | | |
|---|
| 579 | | If there are combined nodes and there is no need for them, then replace |
|---|
| 580 | | combination with appropriate node (i.e. replace with fork node when |
|---|
| 581 | | there are less than two incoming edges). This way data model is kept as |
|---|
| 582 | | simple as possible. |
|---|
| 583 | | """ |
|---|
| 584 | | subject = el.subject |
|---|
| 585 | | if not isinstance(subject, tuple(node_classes.keys())): |
|---|
| 586 | | return |
|---|
| 587 | | |
|---|
| 588 | | new_subject = subject |
|---|
| 589 | | |
|---|
| 590 | | if el.props.combined: |
|---|
| 591 | | cs = subject.outgoing[0].target |
|---|
| 592 | | # decombine node when there is no more than one incoming |
|---|
| 593 | | # and no more than one outgoing flow |
|---|
| 594 | | if len(subject.incoming) < 2 or len(cs.outgoing) < 2: |
|---|
| 595 | | new_subject = decombine_nodes(subject) |
|---|
| 596 | | el.props.combined = False |
|---|
| 597 | | else: |
|---|
| 598 | | check_combining_flow(el) |
|---|
| 599 | | |
|---|
| 600 | | else: |
|---|
| 601 | | new_subject = change_node_class(subject) |
|---|
| 602 | | |
|---|
| 603 | | change_node_subject(el, new_subject) |
|---|
| 604 | | |
|---|
| 605 | | |
|---|
| 606 | | def change_node_subject(el, new_subject): |
|---|
| 607 | | """ |
|---|
| 608 | | Change element's subject if new subject is different than element's |
|---|
| 609 | | subject. If subject is changed, then old subject is destroyed. |
|---|
| 610 | | """ |
|---|
| 611 | | subject = el.subject |
|---|
| 612 | | if new_subject != subject: |
|---|
| 613 | | log.debug('changing subject of ui node %s' % el) |
|---|
| 614 | | el.set_subject(new_subject) |
|---|
| 615 | | |
|---|
| 616 | | log.debug('deleting node %s' % subject) |
|---|
| 617 | | subject.unlink() |
|---|
| 618 | | |
|---|
| 619 | | |
|---|
| 620 | | def create_flow(cls, flow): |
|---|
| 621 | | """ |
|---|
| 622 | | Create new flow of class cls. Flow data from flow argument are copied |
|---|
| 623 | | to new created flow. Old flow is destroyed. |
|---|
| 624 | | """ |
|---|
| 625 | | factory = resource(UML.ElementFactory) |
|---|
| 626 | | f = factory.create(cls) |
|---|
| 627 | | f.source = flow.source |
|---|
| 628 | | f.target = flow.target |
|---|
| 629 | | flow.unlink() |
|---|
| 630 | | return f |
|---|
| 631 | | |
|---|
| 632 | | |
|---|
| 633 | | def count_object_flows(node, attr): |
|---|
| 634 | | """ |
|---|
| 635 | | Count incoming or outgoing object flows. |
|---|
| 636 | | """ |
|---|
| 637 | | return len(getattr(node, attr) |
|---|
| 638 | | .select(lambda flow: isinstance(flow, UML.ObjectFlow))) |
|---|
| 639 | | |
|---|
| 640 | | |
|---|
| 641 | | def check_combining_flow(el): |
|---|
| 642 | | """ |
|---|
| 643 | | Set object flow as combining flow when incoming or outgoing flow count |
|---|
| 644 | | is greater than zero. Otherwise change combining flow to control flow. |
|---|
| 645 | | """ |
|---|
| 646 | | subject = el.subject |
|---|
| 647 | | flow = subject.outgoing[0] # combining flow |
|---|
| 648 | | combined = flow.target # combined node |
|---|
| 649 | | |
|---|
| 650 | | c1 = count_object_flows(subject, 'incoming') |
|---|
| 651 | | c2 = count_object_flows(combined, 'outgoing') |
|---|
| 652 | | |
|---|
| 653 | | log.debug('combined incoming and outgoing object flow count: (%d, %d)' % (c1, c2)) |
|---|
| 654 | | |
|---|
| 655 | | if (c1 > 0 or c2 > 0) and isinstance(flow, UML.ControlFlow): |
|---|
| 656 | | log.debug('changing combing flow to object flow') |
|---|
| 657 | | create_flow(UML.ObjectFlow, flow) |
|---|
| 658 | | elif c1 == 0 and c2 == 0 and isinstance(flow, UML.ObjectFlow): |
|---|
| 659 | | log.debug('changing combing flow to control flow') |
|---|
| 660 | | create_flow(UML.ControlFlow, flow) |
|---|
| 661 | | |
|---|
| 662 | | |
|---|
| 663 | | def create_connector_end(connector, role): |
|---|
| 664 | | """ |
|---|
| 665 | | Create Connector End, set role and attach created end to |
|---|
| 666 | | connector. |
|---|
| 667 | | """ |
|---|
| 668 | | end = resource(UML.ElementFactory).create(UML.ConnectorEnd) |
|---|
| 669 | | end.role = role |
|---|
| 670 | | connector.end = end |
|---|
| 671 | | assert end in role.end |
|---|
| 672 | | return end |
|---|
| 673 | | |
|---|
| 674 | | |
|---|
| 675 | | def rotate(p1, p2, a, b, x, y): |
|---|
| 676 | | """ |
|---|
| 677 | | Rotate point (a, b) by angle, which is determined by line (p1, p2). |
|---|
| 678 | | |
|---|
| 679 | | Rotated point is moved by vector (x, y). |
|---|
| 680 | | """ |
|---|
| 681 | | try: |
|---|
| 682 | | angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0])) |
|---|
| 683 | | except ZeroDivisionError: |
|---|
| 684 | | da = p1[1] < p2[1] and 1.5 or -1.5 |
|---|
| 685 | | angle = pi * da |
|---|
| 686 | | |
|---|
| 687 | | sin_angle = sin(angle) |
|---|
| 688 | | cos_angle = cos(angle) |
|---|
| 689 | | return (cos_angle * a - sin_angle * b + x, |
|---|
| 690 | | sin_angle * a + cos_angle * b + y) |
|---|
| | 224 | #class CFlowItem(FlowItem): |
|---|
| | 225 | # """ |
|---|
| | 226 | # Abstract class for flows with activity edge connector. Flow with |
|---|
| | 227 | # activity edge connector references other one, which has activity edge |
|---|
| | 228 | # connector with same name (it is called opposite one). |
|---|
| | 229 | # |
|---|
| | 230 | # Such flows have active and inactive ends. Active end is connected to |
|---|
| | 231 | # any node and inactive end is connected only to activity edge connector. |
|---|
| | 232 | # """ |
|---|
| | 233 | # |
|---|
| | 234 | # popup_menu = DiagramLine.popup_menu + ( |
|---|
| | 235 | # 'separator', |
|---|
| | 236 | # 'MergeFlow', |
|---|
| | 237 | # ) |
|---|
| | 238 | # |
|---|
| | 239 | # def __init__(self, id = None): |
|---|
| | 240 | # FlowItem.__init__(self, id) |
|---|
| | 241 | # |
|---|
| | 242 | # self._connector = ACItem('value') |
|---|
| | 243 | # |
|---|
| | 244 | # factory = resource(UML.ElementFactory) |
|---|
| | 245 | # self._connector.subject = factory.create(UML.LiteralSpecification) |
|---|
| | 246 | # self.add(self._connector) |
|---|
| | 247 | # |
|---|
| | 248 | # self._opposite = None |
|---|
| | 249 | # |
|---|
| | 250 | # # when flow item with connector is deleted, then kill opposite, too |
|---|
| | 251 | # self.unlink_handler_id = self.connect('__unlink__', self.kill_opposite) |
|---|
| | 252 | # |
|---|
| | 253 | # |
|---|
| | 254 | # def kill_opposite(self, source, name): |
|---|
| | 255 | # # do not allow to be killed by opposite |
|---|
| | 256 | # self._opposite.disconnect(self._opposite.unlink_handler_id) |
|---|
| | 257 | # self._opposite.unlink() |
|---|
| | 258 | # |
|---|
| | 259 | # |
|---|
| | 260 | # def save(self, save_func): |
|---|
| | 261 | # """ |
|---|
| | 262 | # Save connector name and opposite flow with activity edge connector. |
|---|
| | 263 | # """ |
|---|
| | 264 | # FlowItem.save(self, save_func) |
|---|
| | 265 | # save_func('opposite', self._opposite, True) |
|---|
| | 266 | # save_func('connector-name', self._connector.subject.value) |
|---|
| | 267 | # |
|---|
| | 268 | # |
|---|
| | 269 | # def load(self, name, value): |
|---|
| | 270 | # """ |
|---|
| | 271 | # Load connector name and opposite flow with activity edge connector. |
|---|
| | 272 | # """ |
|---|
| | 273 | # if name == 'connector-name': |
|---|
| | 274 | # self._connector.subject.value = value |
|---|
| | 275 | # elif name == 'opposite': |
|---|
| | 276 | # self._opposite = value |
|---|
| | 277 | # else: |
|---|
| | 278 | # FlowItem.load(self, name, value) |
|---|
| | 279 | # |
|---|
| | 280 | # |
|---|
| | 281 | # def on_update(self, affine): |
|---|
| | 282 | # """ |
|---|
| | 283 | # Draw flow line and activity edge connector. |
|---|
| | 284 | # """ |
|---|
| | 285 | # # get parent line points to determine angle |
|---|
| | 286 | # # used to rotate position of activity edge connector |
|---|
| | 287 | # p1, p2 = self.get_line() |
|---|
| | 288 | # |
|---|
| | 289 | # # calculate position of connector center |
|---|
| | 290 | # r = self._connector.RADIUS |
|---|
| | 291 | # #x = p1[0] < p2[0] and r or -r |
|---|
| | 292 | # x = p1[0] < p2[0] and -r or r |
|---|
| | 293 | # y = 0 |
|---|
| | 294 | # x, y = rotate(p1, p2, x, y, p1[0], p1[1]) |
|---|
| | 295 | # |
|---|
| | 296 | # self._connector.move_center(x, y) |
|---|
| | 297 | # |
|---|
| | 298 | # FlowItem.on_update(self, affine) |
|---|
| | 299 | # |
|---|
| | 300 | # |
|---|
| | 301 | # def confirm_connect_handle(self, handle): |
|---|
| | 302 | # """See DiagramLine.confirm_connect_handle(). |
|---|
| | 303 | # """ |
|---|
| | 304 | # c1 = self.get_active_handle().connected_to # source |
|---|
| | 305 | # c2 = self._opposite.get_active_handle().connected_to # target |
|---|
| | 306 | # |
|---|
| | 307 | # # set correct relationship between connected items; |
|---|
| | 308 | # # it should be (source, target) not (target, source); |
|---|
| | 309 | # # otherwise we are looking for non-existing or wrong relationship |
|---|
| | 310 | # if isinstance(self, CFlowItemB): |
|---|
| | 311 | # c1, c2 = c2, c1 |
|---|
| | 312 | # |
|---|
| | 313 | # self.connect_items(c1, c2) |
|---|
| | 314 | # self._opposite.set_subject(self.subject) |
|---|
| | 315 | # |
|---|
| | 316 | # |
|---|
| | 317 | # def allow_connect_handle(self, handle, connecting_to): |
|---|
| | 318 | # if handle == self.get_inactive_handle(): |
|---|
| | 319 | # return False |
|---|
| | 320 | # return FlowItem.allow_connect_handle(self, handle, connecting_to) |
|---|
| | 321 | # |
|---|
| | 322 | # |
|---|
| | 323 | # def confirm_disconnect_handle (self, handle, was_connected_to): |
|---|
| | 324 | # """See DiagramLine.confirm_disconnect_handle(). |
|---|
| | 325 | # """ |
|---|
| | 326 | # c1 = self.get_active_handle().connected_to # source |
|---|
| | 327 | # c2 = self._opposite.get_active_handle().connected_to # target |
|---|
| | 328 | # self.disconnect_items(c1, c2, was_connected_to) |
|---|
| | 329 | # self._opposite.set_subject(None) |
|---|
| | 330 | # |
|---|
| | 331 | # |
|---|
| | 332 | # |
|---|
| | 333 | #class CFlowItemA(CFlowItem): |
|---|
| | 334 | # """ |
|---|
| | 335 | # * Is used for split flows, as is CFlowItemB * |
|---|
| | 336 | # |
|---|
| | 337 | # Flow with activity edge connector, which starts from node and points to |
|---|
| | 338 | # activity edge connector. |
|---|
| | 339 | # """ |
|---|
| | 340 | # def __init__(self, id): |
|---|
| | 341 | # CFlowItem.__init__(self, id) |
|---|
| | 342 | # self.create_guard() |
|---|
| | 343 | # |
|---|
| | 344 | # |
|---|
| | 345 | # def on_update(self, affine): |
|---|
| | 346 | # self.update_guard(affine) |
|---|
| | 347 | # CFlowItem.on_update(self, affine) |
|---|
| | 348 | # |
|---|
| | 349 | # |
|---|
| | 350 | # def get_line(self): |
|---|
| | 351 | # p1 = self.handles[-1].get_pos_i() |
|---|
| | 352 | # p2 = self.handles[-2].get_pos_i() |
|---|
| | 353 | # return p1, p2 |
|---|
| | 354 | # |
|---|
| | 355 | # |
|---|
| | 356 | # def get_active_handle(self): |
|---|
| | 357 | # """ |
|---|
| | 358 | # Return source handle as active one. |
|---|
| | 359 | # """ |
|---|
| | 360 | # return self.handles[0] |
|---|
| | 361 | # |
|---|
| | 362 | # |
|---|
| | 363 | # def get_inactive_handle(self): |
|---|
| | 364 | # """ |
|---|
| | 365 | # Return target handle as inactive one. |
|---|
| | 366 | # """ |
|---|
| | 367 | # return self.handles[-1] |
|---|
| | 368 | # |
|---|
| | 369 | # |
|---|
| | 370 | # |
|---|
| | 371 | #class CFlowItemB(CFlowItem): |
|---|
| | 372 | # """ |
|---|
| | 373 | # Flow with activity edge connector, which starts from activity edge |
|---|
| | 374 | # connector and points to a node. |
|---|
| | 375 | # """ |
|---|
| | 376 | # def __init__(self, id): |
|---|
| | 377 | # CFlowItem.__init__(self, id) |
|---|
| | 378 | # self.create_name() |
|---|
| | 379 | # |
|---|
| | 380 | # |
|---|
| | 381 | # def on_update(self, affine): |
|---|
| | 382 | # self.update_name(affine) |
|---|
| | 383 | # CFlowItem.on_update(self, affine) |
|---|
| | 384 | # |
|---|
| | 385 | # |
|---|
| | 386 | # def get_line(self): |
|---|
| | 387 | # p1 = self.handles[0].get_pos_i() |
|---|
| | 388 | # p2 = self.handles[1].get_pos_i() |
|---|
| | 389 | # return p1, p2 |
|---|
| | 390 | # |
|---|
| | 391 | # |
|---|
| | 392 | # def get_active_handle(self): |
|---|
| | 393 | # """ |
|---|
| | 394 | # Return target handle as active one. |
|---|
| | 395 | # """ |
|---|
| | 396 | # return self.handles[-1] |
|---|
| | 397 | # |
|---|
| | 398 | # |
|---|
| | 399 | # def get_inactive_handle(self): |
|---|
| | 400 | # """ |
|---|
| | 401 | # Return source handle as inactive one. |
|---|
| | 402 | # """ |
|---|
| | 403 | # return self.handles[0] |
|---|
| | 404 | # |
|---|
| | 405 | # |
|---|
| | 406 | #def move_collection(src, target, name): |
|---|
| | 407 | # """ |
|---|
| | 408 | # Copy collection from one object to another. |
|---|
| | 409 | # |
|---|
| | 410 | # src - source object |
|---|
| | 411 | # target - target object |
|---|
| | 412 | # name - name of attribute, which is collection to copy |
|---|
| | 413 | # """ |
|---|
| | 414 | # # first make of copy of collection, because assigning |
|---|
| | 415 | # # element to target collection moves this element |
|---|
| | 416 | # for flow in list(getattr(src, name)): |
|---|
| | 417 | # getattr(target, name).append(flow) |
|---|
| | 418 | # |
|---|
| | 419 | |
|---|
| | 420 | #def is_fd(node): |
|---|
| | 421 | # """ |
|---|
| | 422 | # Check if node is fork or decision node. |
|---|
| | 423 | # """ |
|---|
| | 424 | # return isinstance(node, (UML.ForkNode, UML.DecisionNode)) |
|---|
| | 425 | # |
|---|
| | 426 | # |
|---|
| | 427 | #def change_node_class(node): |
|---|
| | 428 | # """ |
|---|
| | 429 | # If UML constraints for fork, join, decision and merge nodes are not |
|---|
| | 430 | # met, then create new node depending on input node class, i.e. create |
|---|
| | 431 | # fork node from join node or merge node from decision node. |
|---|
| | 432 | # |
|---|
| | 433 | # If constraints are met, then return node itself. |
|---|
| | 434 | # """ |
|---|
| | 435 | # if is_fd(node) and len(node.incoming) > 1 \ |
|---|
| | 436 | # or not is_fd(node) and len(node.incoming) < 2: |
|---|
| | 437 | # |
|---|
| | 438 | # factory = resource(UML.ElementFactory) |
|---|
| | 439 | # cls = node_classes[node.__class__] |
|---|
| | 440 | # log.debug('creating %s' % cls) |
|---|
| | 441 | # nn = factory.create(cls) |
|---|
| | 442 | # move_collection(node, nn, 'incoming') |
|---|
| | 443 | # move_collection(node, nn, 'outgoing') |
|---|
| | 444 | # else: |
|---|
| | 445 | # nn = node |
|---|
| | 446 | # |
|---|
| | 447 | # assert nn is not None |
|---|
| | 448 | # |
|---|
| | 449 | # # we have to accept zero of outgoing edges in case of fork/descision |
|---|
| | 450 | # # nodes |
|---|
| | 451 | # assert is_fd(nn) and len(nn.incoming) <= 1 \ |
|---|
| | 452 | # or not is_fd(nn) and len(nn.incoming) >= 1, '%s' % nn |
|---|
| | 453 | # assert is_fd(nn) and len(nn.outgoing) >= 0 \ |
|---|
| | 454 | # or not is_fd(nn) and len(nn.outgoing) <= 1, '%s' % nn |
|---|
| | 455 | # return nn |
|---|
| | 456 | # |
|---|
| | 457 | # |
|---|
| | 458 | #def combine_nodes(node): |
|---|
| | 459 | # """ |
|---|
| | 460 | # Create fork/join (decision/merge) nodes combination as described in UML |
|---|
| | 461 | # specification. |
|---|
| | 462 | # """ |
|---|
| | 463 | # log.debug('combining nodes') |
|---|
| | 464 | # |
|---|
| | 465 | # cls = node_classes[node.__class__] |
|---|
| | 466 | # log.debug('creating %s' % cls) |
|---|
| | 467 | # factory = resource(UML.ElementFactory) |
|---|
| | 468 | # target = factory.create(cls) |
|---|
| | 469 | # |
|---|
| | 470 | # source = node |
|---|
| | 471 | # if is_fd(node): |
|---|
| | 472 | # source = target |
|---|
| | 473 | # move_collection(node, target, 'incoming') |
|---|
| | 474 | # |
|---|
| | 475 | # # create new fork node |
|---|
| | 476 | # cls = node_classes[target.__class__] |
|---|
| | 477 | # log.debug('creating %s' % cls) |
|---|
| | 478 | # target = factory.create(cls) |
|---|
| | 479 | # move_collection(node, target, 'outgoing') |
|---|
| | 480 | # else: |
|---|
| | 481 | # # fork node is created, referenced by target |
|---|
| | 482 | # move_collection(node, target, 'outgoing') |
|---|
| | 483 | # |
|---|
| | 484 | # assert not is_fd(source) |
|---|
| | 485 | # assert is_fd(target) |
|---|
| | 486 | # |
|---|
| | 487 | # # create flow |
|---|
| | 488 | # c1 = count_object_flows(source, 'incoming') |
|---|
| | 489 | # c2 = count_object_flows(target, 'outgoing') |
|---|
| | 490 | # |
|---|
| | 491 | # if c1 > 0 or c2 > 0: |
|---|
| | 492 | # flow = factory.create(UML.ControlFlow) |
|---|
| | 493 | # else: |
|---|
| | 494 | # flow = factory.create(UML.ObjectFlow) |
|---|
| | 495 | # flow.source = source |
|---|
| | 496 | # flow.target = target |
|---|
| | 497 | # |
|---|
| | 498 | # assert len(source.incoming) > 1 |
|---|
| | 499 | # assert len(source.outgoing) == 1 |
|---|
| | 500 | # |
|---|
| | 501 | # assert len(target.incoming) == 1 |
|---|
| | 502 | # assert len(target.outgoing) > 1 |
|---|
| | 503 | # |
|---|
| | 504 | # return source |
|---|
| | 505 | # |
|---|
| | 506 | # |
|---|
| | 507 | #def decombine_nodes(source): |
|---|
| | 508 | # """ |
|---|
| | 509 | # Create node depending on source argument which denotes combination of |
|---|
| | 510 | # fork/join (decision/merge) nodes as described in UML specification. |
|---|
| | 511 | # |
|---|
| | 512 | # Combination of nodes is destroyed. |
|---|
| | 513 | # """ |
|---|
| | 514 | # log.debug('decombining nodes') |
|---|
| | 515 | # flow = source.outgoing[0] |
|---|
| | 516 | # target = flow.target |
|---|
| | 517 | # |
|---|
| | 518 | # if len(source.incoming) < 2: |
|---|
| | 519 | # # create fork or decision |
|---|
| | 520 | # cls = target.__class__ |
|---|
| | 521 | # else: |
|---|
| | 522 | # # create join or merge |
|---|
| | 523 | # cls = source.__class__ |
|---|
| | 524 | # |
|---|
| | 525 | # factory = resource(UML.ElementFactory) |
|---|
| | 526 | # node = factory.create(cls) |
|---|
| | 527 | # |
|---|
| | 528 | # move_collection(source, node, 'incoming') |
|---|
| | 529 | # move_collection(target, node, 'outgoing') |
|---|
| | 530 | # |
|---|
| | 531 | # assert source != node |
|---|
| | 532 | # |
|---|
| | 533 | # # delete target and combining flow |
|---|
| | 534 | # # source should be deleted by caller |
|---|
| | 535 | # target.unlink() |
|---|
| | 536 | # flow.unlink() |
|---|
| | 537 | # |
|---|
| | 538 | # # return new node |
|---|
| | 539 | # return node |
|---|
| | 540 | |
|---|
| | 541 | |
|---|
| | 542 | #def determine_node_on_connect(el): |
|---|
| | 543 | # """ |
|---|
| | 544 | # Determine classes of nodes depending on amount of incoming |
|---|
| | 545 | # and outgoing edges. This method is called when flow is attached |
|---|
| | 546 | # to node. |
|---|
| | 547 | # |
|---|
| | 548 | # If there is more than one incoming edge and more than one |
|---|
| | 549 | # outgoing edge, then create two nodes and combine them with |
|---|
| | 550 | # flow as described in UML specification. |
|---|
| | 551 | # """ |
|---|
| | 552 | # subject = el.subject |
|---|
| | 553 | # if not isinstance(subject, tuple(node_classes.keys())): |
|---|
| | 554 | # return |
|---|
| | 555 | # |
|---|
| | 556 | # new_subject = subject |
|---|
| | 557 | # |
|---|
| | 558 | # if len(subject.incoming) > 1 and len(subject.outgoing) > 1: |
|---|
| | 559 | # new_subject = combine_nodes(subject) |
|---|
| | 560 | # el.props.combined = True |
|---|
| | 561 | # |
|---|
| | 562 | # else: |
|---|
| | 563 | # new_subject = change_node_class(subject) |
|---|
| | 564 | # |
|---|
| | 565 | # change_node_subject(el, new_subject) |
|---|
| | 566 | # |
|---|
| | 567 | # if el.props.combined: |
|---|
| | 568 | # check_combining_flow(el) |
|---|
| | 569 | |
|---|
| | 570 | |
|---|
| | 571 | #def determine_node_on_disconnect(el): |
|---|
| | 572 | # """ |
|---|
| | 573 | # Determine classes of nodes depending on amount of incoming |
|---|
| | 574 | # and outgoing edges. This method is called when flow is dettached |
|---|
| | 575 | # from node. |
|---|
| | 576 | # |
|---|
| | 577 | # If there are combined nodes and there is no need for them, then replace |
|---|
| | 578 | # combination with appropriate node (i.e. replace with fork node when |
|---|
| | 579 | # there are less than two incoming edges). This way data model is kept as |
|---|
| | 580 | # simple as possible. |
|---|
| | 581 | # """ |
|---|
| | 582 | # subject = el.subject |
|---|
| | 583 | # if not isinstance(subject, tuple(node_classes.keys())): |
|---|
| | 584 | # return |
|---|
| | 585 | # |
|---|
| | 586 | # new_subject = subject |
|---|
| | 587 | # |
|---|
| | 588 | # if el.props.combined: |
|---|
| | 589 | # cs = subject.outgoing[0].target |
|---|
| | 590 | # # decombine node when there is no more than one incoming |
|---|
| | 591 | # # and no more than one outgoing flow |
|---|
| | 592 | # if len(subject.incoming) < 2 or len(cs.outgoing) < 2: |
|---|
| | 593 | # new_subject = decombine_nodes(subject) |
|---|
| | 594 | # el.props.combined = False |
|---|
| | 595 | # else: |
|---|
| | 596 | # check_combining_flow(el) |
|---|
| | 597 | # |
|---|
| | 598 | # else: |
|---|
| | 599 | # new_subject = change_node_class(subject) |
|---|
| | 600 | # |
|---|
| | 601 | # change_node_subject(el, new_subject) |
|---|
| | 602 | |
|---|
| | 603 | |
|---|
| | 604 | #def change_node_subject(el, new_subject): |
|---|
| | 605 | # """ |
|---|
| | 606 | # Change element's subject if new subject is different than element's |
|---|
| | 607 | # subject. If subject is changed, then old subject is destroyed. |
|---|
| | 608 | # """ |
|---|
| | 609 | # subject = el.subject |
|---|
| | 610 | # if new_subject != subject: |
|---|
| | 611 | # log.debug('changing subject of ui node %s' % el) |
|---|
| | 612 | # el.set_subject(new_subject) |
|---|
| | 613 | # |
|---|
| | 614 | # log.debug('deleting node %s' % subject) |
|---|
| | 615 | # subject.unlink() |
|---|
| | 616 | |
|---|
| | 617 | |
|---|
| | 618 | #def create_flow(cls, flow): |
|---|
| | 619 | # """ |
|---|
| | 620 | # Create new flow of class cls. Flow data from flow argument are copied |
|---|
| | 621 | # to new created flow. Old flow is destroyed. |
|---|
| | 622 | # """ |
|---|
| | 623 | # factory = resource(UML.ElementFactory) |
|---|
| | 624 | # f = factory.create(cls) |
|---|
| | 625 | # f.source = flow.source |
|---|
| | 626 | # f.target = flow.target |
|---|
| | 627 | # flow.unlink() |
|---|
| | 628 | # return f |
|---|
| | 629 | |
|---|
| | 630 | |
|---|
| | 631 | #def count_object_flows(node, attr): |
|---|
| | 632 | # """ |
|---|
| | 633 | # Count incoming or outgoing object flows. |
|---|
| | 634 | # """ |
|---|
| | 635 | # return len(getattr(node, attr) |
|---|
| | 636 | # .select(lambda flow: isinstance(flow, UML.ObjectFlow))) |
|---|
| | 637 | # |
|---|
| | 638 | # |
|---|
| | 639 | #def check_combining_flow(el): |
|---|
| | 640 | # """ |
|---|
| | 641 | # Set object flow as combining flow when incoming or outgoing flow count |
|---|
| | 642 | # is greater than zero. Otherwise change combining flow to control flow. |
|---|
| | 643 | # """ |
|---|
| | 644 | # subject = el.subject |
|---|
| | 645 | # flow = subject.outgoing[0] # combining flow |
|---|
| | 646 | # combined = flow.target # combined node |
|---|
| | 647 | # |
|---|
| | 648 | # c1 = count_object_flows(subject, 'incoming') |
|---|
| | 649 | # c2 = count_object_flows(combined, 'outgoing') |
|---|
| | 650 | # |
|---|
| | 651 | # log.debug('combined incoming and outgoing object flow count: (%d, %d)' % (c1, c2)) |
|---|
| | 652 | # |
|---|
| | 653 | # if (c1 > 0 or c2 > 0) and isinstance(flow, UML.ControlFlow): |
|---|
| | 654 | # log.debug('changing combing flow to object flow') |
|---|
| | 655 | # create_flow(UML.ObjectFlow, flow) |
|---|
| | 656 | # elif c1 == 0 and c2 == 0 and isinstance(flow, UML.ObjectFlow): |
|---|
| | 657 | # log.debug('changing combing flow to control flow') |
|---|
| | 658 | # create_flow(UML.ControlFlow, flow) |
|---|
| | 659 | # |
|---|
| | 660 | |
|---|
| | 661 | #def create_connector_end(connector, role): |
|---|
| | 662 | # """ |
|---|
| | 663 | # Create Connector End, set role and attach created end to |
|---|
| | 664 | # connector. |
|---|
| | 665 | # """ |
|---|
| | 666 | # end = resource(UML.ElementFactory).create(UML.ConnectorEnd) |
|---|
| | 667 | # end.role = role |
|---|
| | 668 | # connector.end = end |
|---|
| | 669 | # assert end in role.end |
|---|
| | 670 | # return end |
|---|
| | 671 | # |
|---|
| | 672 | |
|---|
| | 673 | #def rotate(p1, p2, a, b, x, y): |
|---|
| | 674 | # """ |
|---|
| | 675 | # Rotate point (a, b) by angle, which is determined by line (p1, p2). |
|---|
| | 676 | # |
|---|
| | 677 | # Rotated point is moved by vector (x, y). |
|---|
| | 678 | # """ |
|---|
| | 679 | # try: |
|---|
| | 680 | # angle = atan((p1[1] - p2[1]) / (p1[0] - p2[0])) |
|---|
| | 681 | # except ZeroDivisionError: |
|---|
| | 682 | # da = p1[1] < p2[1] and 1.5 or -1.5 |
|---|
| | 683 | # angle = pi * da |
|---|
| | 684 | # |
|---|
| | 685 | # sin_angle = sin(angle) |
|---|
| | 686 | # cos_angle = cos(angle) |
|---|
| | 687 | # return (cos_angle * a - sin_angle * b + x, |
|---|
| | 688 | # sin_angle * a + cos_angle * b + y) |
|---|