| | 25 | def glue(self, handle, x, y): |
|---|
| | 26 | """ |
|---|
| | 27 | Return the point the handle could connect to. None if no connection |
|---|
| | 28 | is allowed. |
|---|
| | 29 | """ |
|---|
| | 30 | raise NotImplemented, 'Implement glue() in the subclass' |
|---|
| | 31 | |
|---|
| | 32 | def connect(self, handle, x, y): |
|---|
| | 33 | """ |
|---|
| | 34 | Connect to an element. Note that at this point the line may |
|---|
| | 35 | be connected to some other, or the same element by means of the |
|---|
| | 36 | handle.connected_to property. Also the connection at UML level |
|---|
| | 37 | still exists. |
|---|
| | 38 | |
|---|
| | 39 | Returns True if a connection is established. |
|---|
| | 40 | """ |
|---|
| | 41 | element = self.element |
|---|
| | 42 | canvas = element.canvas |
|---|
| | 43 | solver = canvas.solver |
|---|
| | 44 | |
|---|
| | 45 | pos = self.glue(handle, x, y) |
|---|
| | 46 | |
|---|
| | 47 | # Disconnect old model connection |
|---|
| | 48 | if handle.connected_to and handle.connected_to is not self.element: |
|---|
| | 49 | handle.disconnect() |
|---|
| | 50 | |
|---|
| | 51 | # Stop here if no new connection should be established |
|---|
| | 52 | if not pos: |
|---|
| | 53 | return False |
|---|
| | 54 | |
|---|
| | 55 | self.connect_constraint(handle, pos[0], pos[1]) |
|---|
| | 56 | |
|---|
| | 57 | # Set disconnect handler in the adapter, so it will also wotk if |
|---|
| | 58 | # connections are created programmatically. |
|---|
| | 59 | def _disconnect(): |
|---|
| | 60 | self.disconnect(handle) |
|---|
| | 61 | handle.disconnect = lambda: 0 |
|---|
| | 62 | handle.disconnect = _disconnect |
|---|
| | 63 | |
|---|
| | 64 | return True |
|---|
| | 65 | |
|---|
| | 66 | def disconnect(self, handle): |
|---|
| | 67 | """ |
|---|
| | 68 | Do a full disconnect, also disconnect at UML model level. |
|---|
| | 69 | Subclasses should disconnect model-level connections. |
|---|
| | 70 | """ |
|---|
| | 71 | self.disconnect_constraints(handle) |
|---|
| | 72 | handle.connected_to = None |
|---|
| | 73 | |
|---|
| | 74 | def connect_constraint(self, handle, x, y): |
|---|
| | 75 | """ |
|---|
| | 76 | Create the actual constraint. The handle should be positioned before |
|---|
| | 77 | this method is called. |
|---|
| | 78 | x, y is the position the handle should have on the line. |
|---|
| | 79 | """ |
|---|
| | 80 | raise NotImplemented, 'Implement connect_constraint() in a subclass' |
|---|
| | 81 | |
|---|
| | 82 | def disconnect_constraints(self, handle): |
|---|
| | 83 | """ |
|---|
| | 84 | Disconnect() takes care of disconnecting the handle from the |
|---|
| | 85 | element it's attached to, by removing the constraints. |
|---|
| | 86 | """ |
|---|
| | 87 | solver = self.element.canvas.solver |
|---|
| | 88 | try: |
|---|
| | 89 | solver.remove_constraint(handle._connect_constraint) |
|---|
| | 90 | except AttributeError: |
|---|
| | 91 | pass # No _connect_constraint property yet |
|---|
| | 92 | handle._connect_constraint = None |
|---|
| | 93 | |
|---|
| | 94 | |
|---|
| | 95 | class ElementConnect(AbstractConnect): |
|---|
| | 96 | """ |
|---|
| | 97 | Base class for connecting a line to an ElementItem class. |
|---|
| | 98 | """ |
|---|
| | 99 | |
|---|
| 61 | | pos = self.glue(handle, x, y) |
|---|
| 62 | | |
|---|
| 63 | | # Disconnect old model connection |
|---|
| 64 | | if handle.connected_to and handle.connected_to is not self.element: |
|---|
| 65 | | #adapter = component.queryMultiAdapter((handle.connected_to, self.line), IConnect) |
|---|
| 66 | | #adapter.disconnect(handle) |
|---|
| 67 | | #print 'disconnect is', handle.disconnect |
|---|
| 68 | | handle.disconnect() |
|---|
| 69 | | |
|---|
| 70 | | # Stop here if no new connection should be established |
|---|
| 71 | | if not pos: |
|---|
| 72 | | return False |
|---|
| 73 | | |
|---|
| 74 | | s = self.side(pos, element) |
|---|
| | 135 | |
|---|
| | 136 | s = self.side((x, y), element) |
|---|
| 80 | | |
|---|
| 81 | | # Set disconnect handler in the adapter, so it will also wotk if |
|---|
| 82 | | # connections are created programmatically. |
|---|
| 83 | | def _disconnect(): |
|---|
| 84 | | self.disconnect(handle) |
|---|
| 85 | | handle.disconnect = lambda: 0 |
|---|
| 86 | | handle.disconnect = _disconnect |
|---|
| 87 | | |
|---|
| 88 | | return True |
|---|
| 89 | | |
|---|
| 90 | | def disconnect_constraints(self, handle): |
|---|
| 91 | | """ |
|---|
| 92 | | Disconnect() takes care of disconnecting the handle from the |
|---|
| 93 | | element it's attached to, by removing the constraints. |
|---|
| 94 | | """ |
|---|
| 95 | | solver = self.element.canvas.solver |
|---|
| 96 | | try: |
|---|
| 97 | | solver.remove_constraint(handle._connect_constraint) |
|---|
| 98 | | except AttributeError: |
|---|
| 99 | | pass # No _connect_constraint property yet |
|---|
| 100 | | handle._connect_constraint = None |
|---|
| 101 | | |
|---|
| 102 | | def disconnect(self, handle): |
|---|
| 103 | | """ |
|---|
| 104 | | Do a full disconnect, also disconnect at UML model level. |
|---|
| 105 | | Subclasses should disconnect model-level connections. |
|---|
| 106 | | """ |
|---|
| 107 | | self.disconnect_constraints(handle) |
|---|
| 108 | | handle.connected_to = None |
|---|
| 109 | | |
|---|
| 110 | | |
|---|
| 111 | | class CommentLineConnect(SimpleConnect): |
|---|
| | 142 | |
|---|
| | 143 | |
|---|
| | 144 | class LineConnect(AbstractConnect): |
|---|
| | 145 | """ |
|---|
| | 146 | Base class for connecting two lines to each other. |
|---|
| | 147 | The line that is conencted to is called 'element', as in ElementConnect. |
|---|
| | 148 | """ |
|---|
| | 149 | |
|---|
| | 150 | def _glue(self, handle, x, y): |
|---|
| | 151 | """ |
|---|
| | 152 | Return the point on the element (DiagramLine) closest to (x, y) |
|---|
| | 153 | """ |
|---|
| | 154 | h = self.line.handles() |
|---|
| | 155 | pos = (x, y) |
|---|
| | 156 | h0 = h[0] |
|---|
| | 157 | min_d = None |
|---|
| | 158 | segment = -1 |
|---|
| | 159 | min_p = None |
|---|
| | 160 | dlp = geometry.distance_line_point |
|---|
| | 161 | for s, h1 in enumerate(h[1:]): |
|---|
| | 162 | d, p = dlp(h0.pos, h1.pos, pos) |
|---|
| | 163 | if not s or d < min_d: |
|---|
| | 164 | min_d = d |
|---|
| | 165 | segment = s |
|---|
| | 166 | min_p = p |
|---|
| | 167 | return s, min_p |
|---|
| | 168 | |
|---|
| | 169 | def glue(self, handle, x, y): |
|---|
| | 170 | """ |
|---|
| | 171 | Return the point on the element (DiagramLine) closest to (x, y) |
|---|
| | 172 | """ |
|---|
| | 173 | return self._glue(handle, x, y)[1] |
|---|
| | 174 | |
|---|
| | 175 | def connect_constraint(self, handle, x, y): |
|---|
| | 176 | """ |
|---|
| | 177 | Create the actual constraint. The handle should be positioned before |
|---|
| | 178 | this method is called. |
|---|
| | 179 | """ |
|---|
| | 180 | element = self.element |
|---|
| | 181 | canvas = element.canvas |
|---|
| | 182 | solver = canvas.solver |
|---|
| | 183 | |
|---|
| | 184 | s, pos = self._glue(handle, x, y) |
|---|
| | 185 | handle._connect_constraint = \ |
|---|
| | 186 | constraint.LineConstraint(canvas, element, element.handles()[s], |
|---|
| | 187 | element.handles()[s+1], self.line, handle) |
|---|
| | 188 | solver.add_constraint(handle._connect_constraint) |
|---|
| | 189 | handle.connected_to = element |
|---|
| | 190 | |
|---|
| | 191 | |
|---|
| | 192 | class CommentLineElementConnect(ElementConnect): |
|---|
| 147 | | return super(CommentLineConnect, self).glue(handle, x, y) |
|---|
| 148 | | |
|---|
| 149 | | def connect(self, handle, x, y): |
|---|
| 150 | | if super(CommentLineConnect, self).connect(handle, x, y): |
|---|
| | 228 | return super(CommentLineElementConnect, self).glue(handle, x, y) |
|---|
| | 229 | |
|---|
| | 230 | def connect(self, handle, x, y): |
|---|
| | 231 | if super(CommentLineElementConnect, self).connect(handle, x, y): |
|---|
| 165 | | super(CommentLineConnect, self).disconnect(handle) |
|---|
| 166 | | |
|---|
| 167 | | component.provideAdapter(CommentLineConnect) |
|---|
| 168 | | |
|---|
| 169 | | |
|---|
| 170 | | class RelationshipConnect(SimpleConnect): |
|---|
| | 246 | super(CommentLineElementConnect, self).disconnect(handle) |
|---|
| | 247 | |
|---|
| | 248 | component.provideAdapter(CommentLineElementConnect) |
|---|
| | 249 | |
|---|
| | 250 | class CommentLineLineConnect(LineConnect): |
|---|
| | 251 | """ |
|---|
| | 252 | Connect a comment line to a comment item. |
|---|
| | 253 | Connect Comment.annotatedElement to any element |
|---|
| | 254 | |
|---|
| | 255 | TODO: adapt both ElementItem and DiagramLine |
|---|
| | 256 | use component.provideAdapter? |
|---|
| | 257 | """ |
|---|
| | 258 | component.adapts(items.DiagramLine, items.CommentLineItem) |
|---|
| | 259 | |
|---|
| | 260 | def glue(self, handle, x, y): |
|---|
| | 261 | """ |
|---|
| | 262 | In addition to the normal check, both line ends may not be connected |
|---|
| | 263 | to the same element. Same goes for subjects. |
|---|
| | 264 | One of the ends should be connected to a UML.Comment element. |
|---|
| | 265 | """ |
|---|
| | 266 | opposite = self.line.opposite(handle) |
|---|
| | 267 | element = self.element |
|---|
| | 268 | connected_to = opposite.connected_to |
|---|
| | 269 | if connected_to is element: |
|---|
| | 270 | #print 'item identical', connected_to, element |
|---|
| | 271 | return None |
|---|
| | 272 | |
|---|
| | 273 | # Same goes for subjects: |
|---|
| | 274 | if connected_to and \ |
|---|
| | 275 | (not (connected_to.subject or element.subject)) \ |
|---|
| | 276 | and connected_to.subject is element.subject: |
|---|
| | 277 | #print 'Subjects none or match:', connected_to.subject, element.subject |
|---|
| | 278 | return None |
|---|
| | 279 | |
|---|
| | 280 | # One end should be connected to a CommentItem: |
|---|
| | 281 | if connected_to and \ |
|---|
| | 282 | ((isinstance(connected_to, items.CommentItem) and isinstance(self.element, items.CommentItem)) or \ |
|---|
| | 283 | (not isinstance(connected_to, items.CommentItem) and not isinstance(self.element, items.CommentItem))): |
|---|
| | 284 | return None |
|---|
| | 285 | |
|---|
| | 286 | return super(CommentLineLineConnect, self).glue(handle, x, y) |
|---|
| | 287 | |
|---|
| | 288 | def connect(self, handle, x, y): |
|---|
| | 289 | if super(CommentLineLineConnect, self).connect(handle, x, y): |
|---|
| | 290 | opposite = self.line.opposite(handle) |
|---|
| | 291 | if opposite.connected_to: |
|---|
| | 292 | if isinstance(opposite.connected_to.subject, UML.Comment): |
|---|
| | 293 | opposite.connected_to.subject.annotatedElement = self.element.subject |
|---|
| | 294 | else: |
|---|
| | 295 | self.element.subject.annotatedElement = opposite.connected_to.subject |
|---|
| | 296 | |
|---|
| | 297 | def disconnect(self, handle): |
|---|
| | 298 | opposite = self.line.opposite(handle) |
|---|
| | 299 | if handle.connected_to and opposite.connected_to: |
|---|
| | 300 | if isinstance(opposite.connected_to.subject, UML.Comment): |
|---|
| | 301 | del opposite.connected_to.subject.annotatedElement[handle.connected_to.subject] |
|---|
| | 302 | else: |
|---|
| | 303 | del handle.connected_to.subject.annotatedElement[opposite.connected_to.subject] |
|---|
| | 304 | super(CommentLineLineConnect, self).disconnect(handle) |
|---|
| | 305 | |
|---|
| | 306 | component.provideAdapter(CommentLineLineConnect) |
|---|
| | 307 | |
|---|
| | 308 | |
|---|
| | 309 | class RelationshipConnect(ElementConnect): |
|---|