root/gaphor/tags/gaphor-0.12.5/gaphor/diagram/connector.py
| Revision 2055, 18.9 kB (checked in by wrobe..@pld-linux.org, 1 year ago) | |
|---|---|
| |
| Line | |
|---|---|
| 1 | """ |
| 2 | Connectors and connector ends from Composite Structures and Components. |
| 3 | |
| 4 | Actually only Assembly connector (see Components in UML specs) is |
| 5 | implemented. This is done with AssemblyConnectorItem class. |
| 6 | AssemblyConnectorItem uses ConnectorEndItem (see ProvidedConnectorEndItem |
| 7 | and RequiredConnectorEndItem classes) instances to connect to components. |
| 8 | |
| 9 | Component should provide at least one interface so ProvidedConnectorEndItem |
| 10 | can be connected to it. If there are more than one provided interfaces, |
| 11 | then user can choose appropriate one from ProvidedConnectorEndItem object |
| 12 | menu. Above also applies for required stuff. |
| 13 | |
| 14 | UML Specificatiom Issues |
| 15 | ======================== |
| 16 | In some areas UML specification is not clear about connector kind and |
| 17 | interfaces as connectable elements, see also |
| 18 | |
| 19 | http://modeldrivenengineering.org/bin/view/Umlrtf/CurrentProposals |
| 20 | |
| 21 | Connector Kind |
| 22 | ---------------------------------- |
| 23 | Components chapter adds kind attribute to connector class. According to UML |
| 24 | specs it is enumeration with two possible values 'assembly' and |
| 25 | 'delegation'. |
| 26 | |
| 27 | This is an issue for connector between connectable elements like |
| 28 | properties, parameters or more specific ports not characterized by |
| 29 | interfaces. It is not clear what value should be assigned for connector |
| 30 | kind for connectors between such elements. |
| 31 | |
| 32 | |
| 33 | Interfaces as Connectable Elements |
| 34 | ---------------------------------- |
| 35 | Composite Structures chapter in UML Superstructure (formal 05/07/04) |
| 36 | document does not specify interfaces as connectable elements. |
| 37 | |
| 38 | But definition of assembly connector says: |
| 39 | |
| 40 | An assembly connector is a connector between two components that |
| 41 | defines that one component provides the services that another component |
| 42 | requires. An assembly connector is a connector that is defined from a |
| 43 | required _interface_ or port to a provided _interface_ or port |
| 44 | |
| 45 | Therefore, code of connectors is written with assumption, that interfaces |
| 46 | are connectable elements. |
| 47 | |
| 48 | This is subject to change in the future when UML specification clarifies in |
| 49 | this area. |
| 50 | """ |
| 51 | |
| 52 | ###from gaphor import UML |
| 53 | ###from diagramline import DiagramLine, FreeLine |
| 54 | ###from elementitem import ElementItem |
| 55 | ###from component import ComponentItem |
| 56 | ### |
| 57 | ####from gaphor.diagram.interfaceicon import AssembledInterfaceIcon |
| 58 | ###from gaphor.diagram.rotatable import SimpleRotation |
| 59 | ###from gaphor.diagram import TextElement |
| 60 | ###from gaphor.misc import uniqueid |
| 61 | ###from gaphor.misc.meta import GObjectPropsMerge |
| 62 | ###from gaphor.diagram.groupable import GroupBase |
| 63 | ### |
| 64 | ### |
| 65 | ###class ConnectorEndItem(FreeLine, GroupBase): |
| 66 | ### """ |
| 67 | ### Connector end item abstract class. |
| 68 | ### |
| 69 | ### Connector end item without subject is called free connector end item. |
| 70 | ### |
| 71 | ### Free connector has no id, too. |
| 72 | ### |
| 73 | ### Connector end item has main point. Initially free connector end item is |
| 74 | ### put in its main point. If free connector end item is moved behind the |
| 75 | ### main point and it is not connected to any item, then it is moved to |
| 76 | ### main point again. |
| 77 | ### |
| 78 | ### Deriving classes should implement get_interfaces method. |
| 79 | ### |
| 80 | ### For non-abstract implementations see ProvidedConnectorEndItem and |
| 81 | ### RequiredConnectorEndItem classes. |
| 82 | ### """ |
| 83 | ### |
| 84 | ### interface_actions = [] |
| 85 | ### |
| 86 | ### def __init__(self, id = None, mpt = None): |
| 87 | ### """ |
| 88 | ### Create connector end item. |
| 89 | ### |
| 90 | ### If main point is not specified then it is set to (0, 0). |
| 91 | ### """ |
| 92 | ### FreeLine.__init__(self, id, mpt) |
| 93 | ### GroupBase.__init__(self) |
| 94 | ### |
| 95 | ### self.set_flags(diacanvas.COMPOSITE) |
| 96 | ### |
| 97 | ### self._interface_name = TextElement('name') |
| 98 | ### self.add(self._interface_name) |
| 99 | ### |
| 100 | ### |
| 101 | ### def get_component(self): |
| 102 | ### """ |
| 103 | ### Get component to which this connector end item is connected to or |
| 104 | ### None. |
| 105 | ### """ |
| 106 | ### if self._handle.connected_to: |
| 107 | ### return self._handle.connected_to.subject |
| 108 | ### return None |
| 109 | ### |
| 110 | ### |
| 111 | ### def save(self, save_func): |
| 112 | ### """ |
| 113 | ### Save handle position, main point and reference to connected item. |
| 114 | ### """ |
| 115 | ### FreeLine.save(self, save_func) |
| 116 | ### save_func('handle-position', self._handle.get_pos_w()) |
| 117 | ### save_func('main-point', self._main_point) |
| 118 | ### save_func('connected-to', self._handle.connected_to, True) |
| 119 | ### |
| 120 | ### |
| 121 | ### def postload(self): |
| 122 | ### """ |
| 123 | ### Establish real connection between connector end item and connected |
| 124 | ### item after loading diagram. |
| 125 | ### """ |
| 126 | ### if hasattr(self, '_load_connected_to'): |
| 127 | ### self._load_connected_to.connect_handle(self._handle) |
| 128 | ### del self._load_connected_to |
| 129 | ### FreeLine.postload(self) |
| 130 | ### |
| 131 | ### |
| 132 | ### def load(self, name, value): |
| 133 | ### """ |
| 134 | ### Load handle position, main point and reference to connected item. |
| 135 | ### |
| 136 | ### Reference to connected item is stored in ConnectorEndItem._load_connected_to. |
| 137 | ### Real connection between connector end item and connected item is |
| 138 | ### established in ConnectorEndItem.postload method. |
| 139 | ### """ |
| 140 | ### if name == 'handle-position': |
| 141 | ### x, y = eval(value) |
| 142 | ### self._handle.set_pos_w(x, y) |
| 143 | ### elif name == 'main-point': |
| 144 | ### self._main_point = eval(value) |
| 145 | ### elif name == 'connected-to': |
| 146 | ### self._load_connected_to = value |
| 147 | ### else: |
| 148 | ### FreeLine.load(self, name, value) |
| 149 | ### |
| 150 | ### |
| 151 | ### def on_handle_motion(self, handle, x, y, event_mask): |
| 152 | ### """ |
| 153 | ### Control handle position. If handle position is near the main point, |
| 154 | ### then it is moved to main point. |
| 155 | ### |
| 156 | ### This allows user to easily move handle to main point. |
| 157 | ### """ |
| 158 | ### if handle is self._handle: |
| 159 | ### mpt = self._main_point |
| 160 | ### |
| 161 | ### x, y = self.affine_point_w2i(x, y) |
| 162 | ### |
| 163 | ### if abs(mpt[0] - x) < 10 and abs(mpt[1] - y) < 10: |
| 164 | ### x, y = mpt |
| 165 | ### return self.affine_point_i2w(x, y) |
| 166 | ### else: |
| 167 | ### return FreeLine.on_handle_motion(self, handle, x, y, event_mask) |
| 168 | ### |
| 169 | ### |
| 170 | ### def on_update(self, affine): |
| 171 | ### """ |
| 172 | ### Draw line between main point and current position. |
| 173 | ### """ |
| 174 | ### def get_pos_centered(p1, p2, width, height): |
| 175 | ### x = p1[0] > p2[0] and width + 2 or -2 |
| 176 | ### x = (p1[0] + p2[0]) / 2.0 - x |
| 177 | ### y = p1[1] <= p2[1] and height or 0 |
| 178 | ### y = (p1[1] + p2[1]) / 2.0 - y |
| 179 | ### return x, y |
| 180 | ### |
| 181 | ### # move handle to main point if there is no component |
| 182 | ### # associated with this connector end item |
| 183 | ### if not self.is_focused() and not self.subject: |
| 184 | ### self._handle.set_pos_i(self._main_point[0], self._main_point[1]) |
| 185 | ### |
| 186 | ### pos = self._handle.get_pos_i() |
| 187 | ### w, h = self._interface_name.get_size() |
| 188 | ### x, y = get_pos_centered(self._main_point, pos, w, h) |
| 189 | ### self._interface_name.update_label(x, y) |
| 190 | ### |
| 191 | ### FreeLine.on_update(self, affine) |
| 192 | ### GroupBase.on_update(self, affine) |
| 193 | ### |
| 194 | ### |
| 195 | ### def on_subject_notify(self, pspec, notifiers=()): |
| 196 | ### """ |
| 197 | ### Generate an id when subject is set or set id to None if there is no |
| 198 | ### subject. |
| 199 | ### """ |
| 200 | ### FreeLine.on_subject_notify(self, pspec, notifiers) |
| 201 | ### if self.subject and not self._id: |
| 202 | ### self._id = uniqueid.generate_id() |
| 203 | ### |
| 204 | ### if self.subject: |
| 205 | ### self._interface_name.subject = self.subject |
| 206 | ### |
| 207 | ### # kill connector end item if interface is no longer provided |
| 208 | ### # or required by component |
| 209 | ### component = self.get_component() |
| 210 | ### component.connect('clientDependency', self.on_component_change) |
| 211 | ### |
| 212 | ### else: |
| 213 | ### self._interface_name.subject = None |
| 214 | ### self._id = None |
| 215 | ### |
| 216 | ### self.request_update() |
| 217 | ### |
| 218 | ### |
| 219 | ### def allow_connect_handle(self, handle, item): |
| 220 | ### """ |
| 221 | ### Allow to connect to components only and if they require or provide |
| 222 | ### at least one interface. |
| 223 | ### """ |
| 224 | ### return isinstance(item, ComponentItem) \ |
| 225 | ### and len(self.get_interfaces(item.subject)) > 0 |
| 226 | ### |
| 227 | ### |
| 228 | ### def confirm_connect_handle(self, handle): |
| 229 | ### """ |
| 230 | ### Set first interface as subject of item. |
| 231 | ### """ |
| 232 | ### if not self.subject: |
| 233 | ### assert len(self.get_interfaces()) > 0 |
| 234 | ### self.set_subject(self.get_interfaces()[0]) |
| 235 | ### |
| 236 | ### |
| 237 | ### def confirm_disconnect_handle(self, handle, item): |
| 238 | ### """ |
| 239 | ### Remove subject. |
| 240 | ### """ |
| 241 | ### self.set_subject(None) |
| 242 | ### |
| 243 | ### |
| 244 | ### def allow_disconnect_handle(self, handle): |
| 245 | ### """ |
| 246 | ### It is always allowed to disconnect handle. |
| 247 | ### """ |
| 248 | ### return True |
| 249 | ### |
| 250 | ### |
| 251 | ### def on_component_change(self, component, data): |
| 252 | ### """ |
| 253 | ### Kill connector end item if interface is no longer in component |
| 254 | ### environment. |
| 255 | ### """ |
| 256 | ### if self.subject: |
| 257 | ### if self.subject not in self.get_interfaces(): |
| 258 | ### self.unlink() |
| 259 | ### |
| 260 | ### |
| 261 | ### |
| 262 | ###class ProvidedConnectorEndItem(ConnectorEndItem): |
| 263 | ### """ |
| 264 | ### Connector end item which allows to connect to components, which provide |
| 265 | ### interfaces. |
| 266 | ### """ |
| 267 | ### def get_interfaces(self, component = None): |
| 268 | ### """ |
| 269 | ### Get component provided interfaces. |
| 270 | ### """ |
| 271 | ### if not component: |
| 272 | ### component = self.get_component() |
| 273 | ### return component.provided |
| 274 | ### |
| 275 | ### |
| 276 | ### |
| 277 | ###class RequiredConnectorEndItem(ConnectorEndItem): |
| 278 | ### """ |
| 279 | ### Connector end item which allows to connect to components, which require |
| 280 | ### interfaces. |
| 281 | ### """ |
| 282 | ### def get_interfaces(self, component = None): |
| 283 | ### """ |
| 284 | ### Get component required interfaces. |
| 285 | ### """ |
| 286 | ### if not component: |
| 287 | ### component = self.get_component() |
| 288 | ### return component.required |
| 289 | ### |
| 290 | ### |
| 291 | ### |
| 292 | ###class AssemblyConnectorItem(ElementItem, SimpleRotation, GroupBase): |
| 293 | ### """ |
| 294 | ### Assembly connector item. |
| 295 | ### |
| 296 | ### It has exactly two free connector end items. |
| 297 | ### |
| 298 | ### Connector end items are added and removed from assembly connector using |
| 299 | ### canvas groupable interface. |
| 300 | ### |
| 301 | ### Assembly connector item distinguishes between two kind of main points. |
| 302 | ### One is for connector end items, which connect to provided interfaces/ports. |
| 303 | ### Other is for required interfaces/ports. |
| 304 | ### """ |
| 305 | ### |
| 306 | ### __metaclass__ = GObjectPropsMerge # merge properties from SimpleRotation |
| 307 | ### |
| 308 | ### popup_menu = ( |
| 309 | ### 'EditDelete', |
| 310 | ### 'separator', |
| 311 | ### 'Rotate', |
| 312 | ### ) |
| 313 | ### |
| 314 | ### def __init__(self, id): |
| 315 | ### """ |
| 316 | ### Create assembly connector item. |
| 317 | ### """ |
| 318 | ### GroupBase.__init__(self) |
| 319 | ### ElementItem.__init__(self, id) |
| 320 | ### SimpleRotation.__init__(self) |
| 321 | ### |
| 322 | ### #self._assembly = AssembledInterfaceIcon(self) |
| 323 | ### |
| 324 | ### for h in self.handles: |
| 325 | ### h.props.movable = False |
| 326 | ### |
| 327 | ### self.set(width = self._assembly.width, |
| 328 | ### height = self._assembly.height) |
| 329 | ### |
| 330 | ### |
| 331 | ### def do_set_property(self, pspec, value): |
| 332 | ### if pspec.name in SimpleRotation.__gproperties__: |
| 333 | ### SimpleRotation.do_set_property(self, pspec, value) |
| 334 | ### else: |
| 335 | ### ElementItem.do_set_property(self, pspec, value) |
| 336 | ### |
| 337 | ### |
| 338 | ### def do_get_property(self, pspec): |
| 339 | ### if pspec.name in SimpleRotation.__gproperties__: |
| 340 | ### return SimpleRotation.do_get_property(self, pspec) |
| 341 | ### else: |
| 342 | ### return ElementItem.do_get_property(self, pspec) |
| 343 | ### |
| 344 | ### |
| 345 | ### def get_end_pos(self): |
| 346 | ### return self._assembly.get_required_pos_i(), \ |
| 347 | ### self._assembly.get_provided_pos_i() |
| 348 | ### |
| 349 | ### |
| 350 | ### def update_end_main_point(self, old_rmpt, old_pmpt): |
| 351 | ### rmpt, pmpt = self.get_end_pos() |
| 352 | ### for c in self.children: |
| 353 | ### if c._main_point == old_rmpt: |
| 354 | ### c._main_point = rmpt |
| 355 | ### elif c._main_point == old_pmpt: |
| 356 | ### c._main_point = pmpt |
| 357 | ### |
| 358 | ### |
| 359 | ### def save(self, save_func): |
| 360 | ### """ |
| 361 | ### Save non-free connector end items. |
| 362 | ### """ |
| 363 | ### ElementItem.save(self, save_func) |
| 364 | ### for c in self.children: |
| 365 | ### if c.subject: |
| 366 | ### save_func(None, c) |
| 367 | ### |
| 368 | ### |
| 369 | ### def load(self, name, value): |
| 370 | ### """ |
| 371 | ### Change free connector end items position after direction update. |
| 372 | ### """ |
| 373 | ### if name == 'dir': |
| 374 | ### old_rmpt, old_pmpt = self.get_end_pos() |
| 375 | ### ElementItem.load(self, name, value) |
| 376 | ### self.update_end_main_point(old_rmpt, old_pmpt) |
| 377 | ### else: |
| 378 | ### ElementItem.load(self, name, value) |
| 379 | ### |
| 380 | ### |
| 381 | ### |
| 382 | ### def rotate(self, step = 1): |
| 383 | ### """ |
| 384 | ### Rotate assembly connector icon and set appropriate main point |
| 385 | ### of connector end items. |
| 386 | ### """ |
| 387 | ### old_rmpt, old_pmpt = self.get_end_pos() |
| 388 | ### SimpleRotation.rotate(self, step) |
| 389 | ### self.update_end_main_point(old_rmpt, old_pmpt) |
| 390 | ### |
| 391 | ### self.request_update() |
| 392 | ### |
| 393 | ### |
| 394 | ### def create_end(self, cls, mpt): |
| 395 | ### """ |
| 396 | ### Create new connector end item. |
| 397 | ### |
| 398 | ### Connector end item is added using canvas groupable interface. |
| 399 | ### """ |
| 400 | ### c = cls(mpt = mpt) |
| 401 | ### self.add(c) |
| 402 | ### return c |
| 403 | ### |
| 404 | ### |
| 405 | ### def on_update(self, affine): |
| 406 | ### """ |
| 407 | ### Update assembly connector and its connector end items. |
| 408 | ### |
| 409 | ### Maintain also free connector end items, so there is always two of |
| 410 | ### them, one per main point kind (provided/required). |
| 411 | ### """ |
| 412 | ### self._assembly.update_icon() |
| 413 | ### |
| 414 | ### rmpt = self._assembly.get_required_pos_i() |
| 415 | ### pmpt = self._assembly.get_provided_pos_i() |
| 416 | ### |
| 417 | ### # store free connector end items by main point |
| 418 | ### free_ends = { |
| 419 | ### rmpt: set(), |
| 420 | ### pmpt: set(), |
| 421 | ### } |
| 422 | ### |
| 423 | ### for c in self.children: |
| 424 | ### # store free conector end items; checking for id and subject is |
| 425 | ### # important during diagram load |
| 426 | ### if not c.id and not c.subject: |
| 427 | ### free_ends[c._main_point].add(c) |
| 428 | ### |
| 429 | ### self.update_free_ends(free_ends, ProvidedConnectorEndItem, pmpt, affine) |
| 430 | ### self.update_free_ends(free_ends, RequiredConnectorEndItem, rmpt, affine) |
| 431 | ### |
| 432 | ### ElementItem.on_update(self, affine) |
| 433 | ### GroupBase.on_update(self, affine) |
| 434 | ### |
| 435 | ### |
| 436 | ### def update_free_ends(self, free_ends, cls, mpt, affine): |
| 437 | ### """ |
| 438 | ### There should only one free connector end item for given main point. |
| 439 | ### |
| 440 | ### Remove these free connector end items, which are no longer |
| 441 | ### necessary. |
| 442 | ### |
| 443 | ### Create free connector end item in given main point if it is |
| 444 | ### missing. |
| 445 | ### """ |
| 446 | ### count = len(free_ends[mpt]) |
| 447 | ### if count > 1: |
| 448 | ### # leave only one free connector end item |
| 449 | ### |
| 450 | ### # first, remove all, which are not in main point |
| 451 | ### to_be_removed = list(free_ends[mpt]) |
| 452 | ### for c in to_be_removed: |
| 453 | ### if c._handle.get_pos_i() != c._main_point: |
| 454 | ### c.unlink() |
| 455 | ### free_ends[mpt].remove(c) |
| 456 | ### |
| 457 | ### # now, leave only one connector end item in main point |
| 458 | ### to_be_removed = list(free_ends[mpt]) |
| 459 | ### if len(to_be_removed) > 1: |
| 460 | ### for c in to_be_removed[1:]: |
| 461 | ### c.unlink() |
| 462 | ### free_ends[mpt].remove(c) |
| 463 | ### |
| 464 | ### # finally, we should have one free connector end item in given main point |
| 465 | ### assert len(free_ends[mpt]) == 1 |
| 466 | ### |
| 467 | ### elif count == 0: |
| 468 | ### # if there is no free connector end items, then create one |
| 469 | ### c = self.create_end(cls, mpt) |
| 470 | ### self.update_child(c, affine) |
| 471 | ### |
| 472 | ### |
| 473 | ### def on_subject_notify(self, pspec, notifiers = ()): |
| 474 | ### """ |
| 475 | ### Set appropriate value of connector kind attribute. This is |
| 476 | ### 'assembly', of course. |
| 477 | ### """ |
| 478 | ### ElementItem.on_subject_notify(self, pspec, notifiers) |
| 479 | ### if self.subject: |
| 480 | ### self.subject.kind = 'assembly' |
| 481 | ### log.debug('creating assembly connector') |
| 482 | ### |
| 483 | ### |
| 484 | ### def on_shape_iter(self): |
| 485 | ### """ |
| 486 | ### Return assembled interface icon as a shape. |
| 487 | ### """ |
| 488 | ### return self._assembly.on_shape_iter() |
| 489 | ### |
| 490 | ### |
| 491 | ### |
| 492 | ###class ConnectorItem(DiagramLine): |
| 493 | ### pass |
| 494 | #### """ |
| 495 | #### todo: ports |
| 496 | #### """ |
| 497 | #### |
| 498 | #### def __init__(self, id=None): |
| 499 | #### DiagramLine.__init__(self, id) |
| 500 | #### |
| 501 | #### |
| 502 | #### def allow_connect_handle(self, handle, connecting_to): |
| 503 | #### return True |
| 504 | #### return isinstance(connecting_to.subject, UML.Interface) |
| 505 | #### |
| 506 | #### |
| 507 | #### def confirm_connect_handle (self, handle): |
| 508 | #### c1 = self.handles[0].connected_to |
| 509 | #### c2 = self.handles[-1].connected_to |
| 510 | #### if c1 and c2: |
| 511 | #### s1 = c1.subject |
| 512 | #### s2 = c2.subject |
| 513 | #### print s1.end |
| 514 | #### print s2.end |
| 515 | #### connector = self.relationship |
| 516 | #### if not connector: |
| 517 | #### connector = resource(UML.ElementFactory).create(UML.Connector) |
| 518 | #### connector.kind = 'assembly' |
| 519 | #### create_connector_end(connector, s1) |
| 520 | #### create_connector_end(connector, s2) |
| 521 | #### |
| 522 | #### self.subject = connector |
| 523 | #### |
| 524 | #### |
| 525 | #### def confirm_disconnect_handle(self, handle, was_connected_to): |
| 526 | #### if self.subject: |
| 527 | #### if __debug__: |
| 528 | #### end1, end2 = tuple(self.subject.end) |
| 529 | #### assert len(end1.role.end) > 0 |
| 530 | #### assert len(end2.role.end) > 0 |
| 531 | #### self.set_subject(None) |
| 532 | #### if __debug__: |
| 533 | #### assert end1.role is None |
| 534 | #### assert end2.role is None |
| 535 | #### |
| 536 | #### |
| 537 | #### def is_assembly(self): |
| 538 | #### c1 = self.handles[0].connected_to |
| 539 | #### c2 = self.handles[-1].connected_to |
| 540 | #### return c1 and c2 \ |
| 541 | #### and isinstance(c1.subject, UML.Interface) \ |
| 542 | #### and isinstance(c2.subject, UML.Interface) |
| 543 | #### |
| 544 | #### |
| 545 | #### def on_update(self, affine): |
| 546 | #### DiagramLine.on_update(self, affine) |
| 547 | #### x1, y1 = self.handles[0].get_pos_i() |
| 548 | #### x2, y2 = self.handles[-1].get_pos_i() |
| 549 | #### x = (x1 + x2) / 2 |
| 550 | #### y = (y1 + y2) / 2 |
| 551 | #### if self.is_assembly(): |
| 552 | #### self._assembly.line(_required_arcs[3]) |
| 553 | #### #self._assembly.set_pos((x, y)) |
| 554 | #### |
| 555 | #### |
| 556 | #### def on_shape_iter(self): |
| 557 | #### if self.is_assembly(): |
| 558 | #### return iter([self._assembly]) |
| 559 | #### else: |
| 560 | #### return DiagramLine.on_shape_iter(self) |
| 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 | #### vim:sw=4:et |
Note: See TracBrowser for help on using the browser.
