Total Complexity | 149 |
Total Lines | 849 |
Duplicated Lines | 0 % |
Complex classes like Orange.canvas.canvas.CanvasScene often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | """ |
||
36 | class CanvasScene(QGraphicsScene): |
||
37 | """ |
||
38 | A Graphics Scene for displaying an :class:`~.scheme.Scheme` instance. |
||
39 | """ |
||
40 | |||
41 | #: Signal emitted when a :class:`NodeItem` has been added to the scene. |
||
42 | node_item_added = Signal(items.NodeItem) |
||
43 | |||
44 | #: Signal emitted when a :class:`NodeItem` has been removed from the |
||
45 | #: scene. |
||
46 | node_item_removed = Signal(items.LinkItem) |
||
47 | |||
48 | #: Signal emitted when a new :class:`LinkItem` has been added to the |
||
49 | #: scene. |
||
50 | link_item_added = Signal(items.LinkItem) |
||
51 | |||
52 | #: Signal emitted when a :class:`LinkItem` has been removed. |
||
53 | link_item_removed = Signal(items.LinkItem) |
||
54 | |||
55 | #: Signal emitted when a :class:`Annotation` item has been added. |
||
56 | annotation_added = Signal(items.annotationitem.Annotation) |
||
57 | |||
58 | #: Signal emitted when a :class:`Annotation` item has been removed. |
||
59 | annotation_removed = Signal(items.annotationitem.Annotation) |
||
60 | |||
61 | #: Signal emitted when the position of a :class:`NodeItem` has changed. |
||
62 | node_item_position_changed = Signal(items.NodeItem, QPointF) |
||
63 | |||
64 | #: Signal emitted when an :class:`NodeItem` has been double clicked. |
||
65 | node_item_double_clicked = Signal(items.NodeItem) |
||
66 | |||
67 | #: An node item has been activated (clicked) |
||
68 | node_item_activated = Signal(items.NodeItem) |
||
69 | |||
70 | #: An node item has been hovered |
||
71 | node_item_hovered = Signal(items.NodeItem) |
||
72 | |||
73 | #: Link item has been hovered |
||
74 | link_item_hovered = Signal(items.LinkItem) |
||
75 | |||
76 | def __init__(self, *args, **kwargs): |
||
77 | QGraphicsScene.__init__(self, *args, **kwargs) |
||
78 | |||
79 | self.scheme = None |
||
80 | self.registry = None |
||
81 | |||
82 | # All node items |
||
83 | self.__node_items = [] |
||
84 | # Mapping from SchemeNodes to canvas items |
||
85 | self.__item_for_node = {} |
||
86 | # All link items |
||
87 | self.__link_items = [] |
||
88 | # Mapping from SchemeLinks to canvas items. |
||
89 | self.__item_for_link = {} |
||
90 | |||
91 | # All annotation items |
||
92 | self.__annotation_items = [] |
||
93 | # Mapping from SchemeAnnotations to canvas items. |
||
94 | self.__item_for_annotation = {} |
||
95 | |||
96 | # Is the scene editable |
||
97 | self.editable = True |
||
98 | |||
99 | # Anchor Layout |
||
100 | self.__anchor_layout = AnchorLayout() |
||
101 | self.addItem(self.__anchor_layout) |
||
102 | |||
103 | self.__channel_names_visible = True |
||
104 | self.__node_animation_enabled = True |
||
105 | |||
106 | self.user_interaction_handler = None |
||
107 | |||
108 | self.activated_mapper = NodeItemSignalMapper(self) |
||
109 | self.activated_mapper.pyMapped.connect( |
||
110 | self.node_item_activated |
||
111 | ) |
||
112 | |||
113 | self.hovered_mapper = NodeItemSignalMapper(self) |
||
114 | self.hovered_mapper.pyMapped.connect( |
||
115 | self.node_item_hovered |
||
116 | ) |
||
117 | |||
118 | self.position_change_mapper = NodeItemSignalMapper(self) |
||
119 | self.position_change_mapper.pyMapped.connect( |
||
120 | self._on_position_change |
||
121 | ) |
||
122 | |||
123 | log.info("'%s' intitialized." % self) |
||
124 | |||
125 | def clear_scene(self): |
||
126 | """ |
||
127 | Clear (reset) the scene. |
||
128 | """ |
||
129 | if self.scheme is not None: |
||
130 | self.scheme.node_added.disconnect(self.add_node) |
||
131 | self.scheme.node_removed.disconnect(self.remove_node) |
||
132 | |||
133 | self.scheme.link_added.disconnect(self.add_link) |
||
134 | self.scheme.link_removed.disconnect(self.remove_link) |
||
135 | |||
136 | self.scheme.annotation_added.disconnect(self.add_annotation) |
||
137 | self.scheme.annotation_removed.disconnect(self.remove_annotation) |
||
138 | |||
139 | self.scheme.node_state_changed.disconnect( |
||
140 | self.on_widget_state_change |
||
141 | ) |
||
142 | self.scheme.channel_state_changed.disconnect( |
||
143 | self.on_link_state_change |
||
144 | ) |
||
145 | |||
146 | # Remove all items to make sure all signals from scheme items |
||
147 | # to canvas items are disconnected. |
||
148 | |||
149 | for annot in self.scheme.annotations: |
||
150 | if annot in self.__item_for_annotation: |
||
151 | self.remove_annotation(annot) |
||
152 | |||
153 | for link in self.scheme.links: |
||
154 | if link in self.__item_for_link: |
||
155 | self.remove_link(link) |
||
156 | |||
157 | for node in self.scheme.nodes: |
||
158 | if node in self.__item_for_node: |
||
159 | self.remove_node(node) |
||
160 | |||
161 | self.scheme = None |
||
162 | self.__node_items = [] |
||
163 | self.__item_for_node = {} |
||
164 | self.__link_items = [] |
||
165 | self.__item_for_link = {} |
||
166 | self.__annotation_items = [] |
||
167 | self.__item_for_annotation = {} |
||
168 | |||
169 | self.__anchor_layout.deleteLater() |
||
170 | |||
171 | self.user_interaction_handler = None |
||
172 | |||
173 | self.clear() |
||
174 | log.info("'%s' cleared." % self) |
||
175 | |||
176 | def set_scheme(self, scheme): |
||
177 | """ |
||
178 | Set the scheme to display. Populates the scene with nodes and links |
||
179 | already in the scheme. Any further change to the scheme will be |
||
180 | reflected in the scene. |
||
181 | |||
182 | Parameters |
||
183 | ---------- |
||
184 | scheme : :class:`~.scheme.Scheme` |
||
185 | |||
186 | """ |
||
187 | if self.scheme is not None: |
||
188 | # Clear the old scheme |
||
189 | self.clear_scene() |
||
190 | |||
191 | log.info("Setting scheme '%s' on '%s'" % (scheme, self)) |
||
192 | |||
193 | self.scheme = scheme |
||
194 | if self.scheme is not None: |
||
195 | self.scheme.node_added.connect(self.add_node) |
||
196 | self.scheme.node_removed.connect(self.remove_node) |
||
197 | |||
198 | self.scheme.link_added.connect(self.add_link) |
||
199 | self.scheme.link_removed.connect(self.remove_link) |
||
200 | |||
201 | self.scheme.annotation_added.connect(self.add_annotation) |
||
202 | self.scheme.annotation_removed.connect(self.remove_annotation) |
||
203 | |||
204 | self.scheme.node_state_changed.connect( |
||
205 | self.on_widget_state_change |
||
206 | ) |
||
207 | self.scheme.channel_state_changed.connect( |
||
208 | self.on_link_state_change |
||
209 | ) |
||
210 | |||
211 | self.scheme.topology_changed.connect(self.on_scheme_change) |
||
212 | |||
213 | for node in scheme.nodes: |
||
214 | self.add_node(node) |
||
215 | |||
216 | for link in scheme.links: |
||
217 | self.add_link(link) |
||
218 | |||
219 | for annot in scheme.annotations: |
||
220 | self.add_annotation(annot) |
||
221 | |||
222 | def set_registry(self, registry): |
||
223 | """ |
||
224 | Set the widget registry. |
||
225 | """ |
||
226 | # TODO: Remove/Deprecate. Is used only to get the category/background |
||
227 | # color. That should be part of the SchemeNode/WidgetDescription. |
||
228 | log.info("Setting registry '%s on '%s'." % (registry, self)) |
||
229 | self.registry = registry |
||
230 | |||
231 | def set_anchor_layout(self, layout): |
||
232 | """ |
||
233 | Set an :class:`~.layout.AnchorLayout` |
||
234 | """ |
||
235 | if self.__anchor_layout != layout: |
||
236 | if self.__anchor_layout: |
||
237 | self.__anchor_layout.deleteLater() |
||
238 | self.__anchor_layout = None |
||
239 | |||
240 | self.__anchor_layout = layout |
||
241 | |||
242 | def anchor_layout(self): |
||
243 | """ |
||
244 | Return the anchor layout instance. |
||
245 | """ |
||
246 | return self.__anchor_layout |
||
247 | |||
248 | def set_channel_names_visible(self, visible): |
||
249 | """ |
||
250 | Set the channel names visibility. |
||
251 | """ |
||
252 | self.__channel_names_visible = visible |
||
253 | for link in self.__link_items: |
||
254 | link.setChannelNamesVisible(visible) |
||
255 | |||
256 | def channel_names_visible(self): |
||
257 | """ |
||
258 | Return the channel names visibility state. |
||
259 | """ |
||
260 | return self.__channel_names_visible |
||
261 | |||
262 | def set_node_animation_enabled(self, enabled): |
||
263 | """ |
||
264 | Set node animation enabled state. |
||
265 | """ |
||
266 | if self.__node_animation_enabled != enabled: |
||
267 | self.__node_animation_enabled = enabled |
||
268 | |||
269 | for node in self.__node_items: |
||
270 | node.setAnimationEnabled(enabled) |
||
271 | |||
272 | def add_node_item(self, item): |
||
273 | """ |
||
274 | Add a :class:`.NodeItem` instance to the scene. |
||
275 | """ |
||
276 | if item in self.__node_items: |
||
277 | raise ValueError("%r is already in the scene." % item) |
||
278 | |||
279 | if item.pos().isNull(): |
||
280 | if self.__node_items: |
||
281 | pos = self.__node_items[-1].pos() + QPointF(150, 0) |
||
282 | else: |
||
283 | pos = QPointF(150, 150) |
||
284 | |||
285 | item.setPos(pos) |
||
286 | |||
287 | item.setFont(self.font()) |
||
288 | |||
289 | # Set signal mappings |
||
290 | self.activated_mapper.setPyMapping(item, item) |
||
291 | item.activated.connect(self.activated_mapper.pyMap) |
||
292 | |||
293 | self.hovered_mapper.setPyMapping(item, item) |
||
294 | item.hovered.connect(self.hovered_mapper.pyMap) |
||
295 | |||
296 | self.position_change_mapper.setPyMapping(item, item) |
||
297 | item.positionChanged.connect(self.position_change_mapper.pyMap) |
||
298 | |||
299 | self.addItem(item) |
||
300 | |||
301 | self.__node_items.append(item) |
||
302 | |||
303 | self.node_item_added.emit(item) |
||
304 | |||
305 | log.info("Added item '%s' to '%s'" % (item, self)) |
||
306 | return item |
||
307 | |||
308 | def add_node(self, node): |
||
309 | """ |
||
310 | Add and return a default constructed :class:`.NodeItem` for a |
||
311 | :class:`SchemeNode` instance `node`. If the `node` is already in |
||
312 | the scene do nothing and just return its item. |
||
313 | |||
314 | """ |
||
315 | if node in self.__item_for_node: |
||
316 | # Already added |
||
317 | return self.__item_for_node[node] |
||
318 | |||
319 | item = self.new_node_item(node.description) |
||
320 | |||
321 | if node.position: |
||
322 | pos = QPointF(*node.position) |
||
323 | item.setPos(pos) |
||
324 | |||
325 | item.setTitle(node.title) |
||
326 | item.setProcessingState(node.processing_state) |
||
327 | item.setProgress(node.progress) |
||
328 | |||
329 | for message in node.state_messages(): |
||
330 | item.setStateMessage(message) |
||
331 | |||
332 | item.setStatusMessage(node.status_message()) |
||
333 | |||
334 | self.__item_for_node[node] = item |
||
335 | |||
336 | node.position_changed.connect(self.__on_node_pos_changed) |
||
337 | node.title_changed.connect(item.setTitle) |
||
338 | node.progress_changed.connect(item.setProgress) |
||
339 | node.processing_state_changed.connect(item.setProcessingState) |
||
340 | node.state_message_changed.connect(item.setStateMessage) |
||
341 | node.status_message_changed.connect(item.setStatusMessage) |
||
342 | |||
343 | return self.add_node_item(item) |
||
344 | |||
345 | def new_node_item(self, widget_desc, category_desc=None): |
||
346 | """ |
||
347 | Construct an new :class:`.NodeItem` from a `WidgetDescription`. |
||
348 | Optionally also set `CategoryDescription`. |
||
349 | |||
350 | """ |
||
351 | item = items.NodeItem() |
||
352 | item.setWidgetDescription(widget_desc) |
||
353 | |||
354 | if category_desc is None and self.registry and widget_desc.category: |
||
355 | category_desc = self.registry.category(widget_desc.category) |
||
356 | |||
357 | if category_desc is None and self.registry is not None: |
||
358 | try: |
||
359 | category_desc = self.registry.category(widget_desc.category) |
||
360 | except KeyError: |
||
361 | pass |
||
362 | |||
363 | if category_desc is not None: |
||
364 | item.setWidgetCategory(category_desc) |
||
365 | |||
366 | item.setAnimationEnabled(self.__node_animation_enabled) |
||
367 | return item |
||
368 | |||
369 | def remove_node_item(self, item): |
||
370 | """ |
||
371 | Remove `item` (:class:`.NodeItem`) from the scene. |
||
372 | """ |
||
373 | self.activated_mapper.removePyMappings(item) |
||
374 | self.hovered_mapper.removePyMappings(item) |
||
375 | |||
376 | item.hide() |
||
377 | self.removeItem(item) |
||
378 | self.__node_items.remove(item) |
||
379 | |||
380 | self.node_item_removed.emit(item) |
||
381 | |||
382 | log.info("Removed item '%s' from '%s'" % (item, self)) |
||
383 | |||
384 | def remove_node(self, node): |
||
385 | """ |
||
386 | Remove the :class:`.NodeItem` instance that was previously |
||
387 | constructed for a :class:`SchemeNode` `node` using the `add_node` |
||
388 | method. |
||
389 | |||
390 | """ |
||
391 | item = self.__item_for_node.pop(node) |
||
392 | |||
393 | node.position_changed.disconnect(self.__on_node_pos_changed) |
||
394 | node.title_changed.disconnect(item.setTitle) |
||
395 | node.progress_changed.disconnect(item.setProgress) |
||
396 | node.processing_state_changed.disconnect(item.setProcessingState) |
||
397 | node.state_message_changed.disconnect(item.setStateMessage) |
||
398 | |||
399 | self.remove_node_item(item) |
||
400 | |||
401 | def node_items(self): |
||
402 | """ |
||
403 | Return all :class:`.NodeItem` instances in the scene. |
||
404 | """ |
||
405 | return list(self.__node_items) |
||
406 | |||
407 | def add_link_item(self, item): |
||
408 | """ |
||
409 | Add a link (:class:`.LinkItem`) to the scene. |
||
410 | """ |
||
411 | if item.scene() is not self: |
||
412 | self.addItem(item) |
||
413 | |||
414 | item.setFont(self.font()) |
||
415 | self.__link_items.append(item) |
||
416 | |||
417 | self.link_item_added.emit(item) |
||
418 | |||
419 | log.info("Added link %r -> %r to '%s'" % \ |
||
420 | (item.sourceItem.title(), item.sinkItem.title(), self)) |
||
421 | |||
422 | self.__anchor_layout.invalidateLink(item) |
||
423 | |||
424 | return item |
||
425 | |||
426 | def add_link(self, scheme_link): |
||
427 | """ |
||
428 | Create and add a :class:`.LinkItem` instance for a |
||
429 | :class:`SchemeLink` instance. If the link is already in the scene |
||
430 | do nothing and just return its :class:`.LinkItem`. |
||
431 | |||
432 | """ |
||
433 | if scheme_link in self.__item_for_link: |
||
434 | return self.__item_for_link[scheme_link] |
||
435 | |||
436 | source = self.__item_for_node[scheme_link.source_node] |
||
437 | sink = self.__item_for_node[scheme_link.sink_node] |
||
438 | |||
439 | item = self.new_link_item(source, scheme_link.source_channel, |
||
440 | sink, scheme_link.sink_channel) |
||
441 | |||
442 | item.setEnabled(scheme_link.enabled) |
||
443 | scheme_link.enabled_changed.connect(item.setEnabled) |
||
444 | |||
445 | if scheme_link.is_dynamic(): |
||
446 | item.setDynamic(True) |
||
447 | item.setDynamicEnabled(scheme_link.dynamic_enabled) |
||
448 | scheme_link.dynamic_enabled_changed.connect(item.setDynamicEnabled) |
||
449 | |||
450 | self.add_link_item(item) |
||
451 | self.__item_for_link[scheme_link] = item |
||
452 | return item |
||
453 | |||
454 | def new_link_item(self, source_item, source_channel, |
||
455 | sink_item, sink_channel): |
||
456 | """ |
||
457 | Construct and return a new :class:`.LinkItem` |
||
458 | """ |
||
459 | item = items.LinkItem() |
||
460 | item.setSourceItem(source_item) |
||
461 | item.setSinkItem(sink_item) |
||
462 | |||
463 | def channel_name(channel): |
||
464 | if isinstance(channel, str): |
||
465 | return channel |
||
466 | else: |
||
467 | return channel.name |
||
468 | |||
469 | source_name = channel_name(source_channel) |
||
470 | sink_name = channel_name(sink_channel) |
||
471 | |||
472 | fmt = "<b>{0}</b> \u2192 <b>{1}</b>" |
||
473 | item.setToolTip( |
||
474 | fmt.format(escape(source_name), |
||
475 | escape(sink_name)) |
||
476 | ) |
||
477 | |||
478 | item.setSourceName(source_name) |
||
479 | item.setSinkName(sink_name) |
||
480 | item.setChannelNamesVisible(self.__channel_names_visible) |
||
481 | |||
482 | return item |
||
483 | |||
484 | def remove_link_item(self, item): |
||
485 | """ |
||
486 | Remove a link (:class:`.LinkItem`) from the scene. |
||
487 | """ |
||
488 | # Invalidate the anchor layout. |
||
489 | self.__anchor_layout.invalidateAnchorItem( |
||
490 | item.sourceItem.outputAnchorItem |
||
491 | ) |
||
492 | self.__anchor_layout.invalidateAnchorItem( |
||
493 | item.sinkItem.inputAnchorItem |
||
494 | ) |
||
495 | |||
496 | self.__link_items.remove(item) |
||
497 | |||
498 | # Remove the anchor points. |
||
499 | item.removeLink() |
||
500 | self.removeItem(item) |
||
501 | |||
502 | self.link_item_removed.emit(item) |
||
503 | |||
504 | log.info("Removed link '%s' from '%s'" % (item, self)) |
||
505 | |||
506 | return item |
||
507 | |||
508 | def remove_link(self, scheme_link): |
||
509 | """ |
||
510 | Remove a :class:`.LinkItem` instance that was previously constructed |
||
511 | for a :class:`SchemeLink` instance `link` using the `add_link` method. |
||
512 | |||
513 | """ |
||
514 | item = self.__item_for_link.pop(scheme_link) |
||
515 | scheme_link.enabled_changed.disconnect(item.setEnabled) |
||
516 | |||
517 | if scheme_link.is_dynamic(): |
||
518 | scheme_link.dynamic_enabled_changed.disconnect( |
||
519 | item.setDynamicEnabled |
||
520 | ) |
||
521 | |||
522 | self.remove_link_item(item) |
||
523 | |||
524 | def link_items(self): |
||
525 | """ |
||
526 | Return all :class:`.LinkItem`\s in the scene. |
||
527 | """ |
||
528 | return list(self.__link_items) |
||
529 | |||
530 | def add_annotation_item(self, annotation): |
||
531 | """ |
||
532 | Add an :class:`.Annotation` item to the scene. |
||
533 | """ |
||
534 | self.__annotation_items.append(annotation) |
||
535 | self.addItem(annotation) |
||
536 | self.annotation_added.emit(annotation) |
||
537 | return annotation |
||
538 | |||
539 | def add_annotation(self, scheme_annot): |
||
540 | """ |
||
541 | Create a new item for :class:`SchemeAnnotation` and add it |
||
542 | to the scene. If the `scheme_annot` is already in the scene do |
||
543 | nothing and just return its item. |
||
544 | |||
545 | """ |
||
546 | if scheme_annot in self.__item_for_annotation: |
||
547 | # Already added |
||
548 | return self.__item_for_annotation[scheme_annot] |
||
549 | |||
550 | if isinstance(scheme_annot, scheme.SchemeTextAnnotation): |
||
551 | item = items.TextAnnotation() |
||
552 | item.setPlainText(scheme_annot.text) |
||
553 | x, y, w, h = scheme_annot.rect |
||
554 | item.setPos(x, y) |
||
555 | item.resize(w, h) |
||
556 | item.setTextInteractionFlags(Qt.TextEditorInteraction) |
||
557 | |||
558 | font = font_from_dict(scheme_annot.font, item.font()) |
||
559 | item.setFont(font) |
||
560 | scheme_annot.text_changed.connect(item.setPlainText) |
||
561 | |||
562 | elif isinstance(scheme_annot, scheme.SchemeArrowAnnotation): |
||
563 | item = items.ArrowAnnotation() |
||
564 | start, end = scheme_annot.start_pos, scheme_annot.end_pos |
||
565 | item.setLine(QLineF(QPointF(*start), QPointF(*end))) |
||
566 | item.setColor(QColor(scheme_annot.color)) |
||
567 | |||
568 | scheme_annot.geometry_changed.connect( |
||
569 | self.__on_scheme_annot_geometry_change |
||
570 | ) |
||
571 | |||
572 | self.add_annotation_item(item) |
||
573 | self.__item_for_annotation[scheme_annot] = item |
||
574 | |||
575 | return item |
||
576 | |||
577 | def remove_annotation_item(self, annotation): |
||
578 | """ |
||
579 | Remove an :class:`.Annotation` instance from the scene. |
||
580 | |||
581 | """ |
||
582 | self.__annotation_items.remove(annotation) |
||
583 | self.removeItem(annotation) |
||
584 | self.annotation_removed.emit(annotation) |
||
585 | |||
586 | def remove_annotation(self, scheme_annotation): |
||
587 | """ |
||
588 | Remove an :class:`.Annotation` instance that was previously added |
||
589 | using :func:`add_anotation`. |
||
590 | |||
591 | """ |
||
592 | item = self.__item_for_annotation.pop(scheme_annotation) |
||
593 | |||
594 | scheme_annotation.geometry_changed.disconnect( |
||
595 | self.__on_scheme_annot_geometry_change |
||
596 | ) |
||
597 | |||
598 | if isinstance(scheme_annotation, scheme.SchemeTextAnnotation): |
||
599 | scheme_annotation.text_changed.disconnect( |
||
600 | item.setPlainText |
||
601 | ) |
||
602 | |||
603 | self.remove_annotation_item(item) |
||
604 | |||
605 | def annotation_items(self): |
||
606 | """ |
||
607 | Return all :class:`.Annotation` items in the scene. |
||
608 | """ |
||
609 | return self.__annotation_items |
||
610 | |||
611 | def item_for_annotation(self, scheme_annotation): |
||
612 | return self.__item_for_annotation[scheme_annotation] |
||
613 | |||
614 | def annotation_for_item(self, item): |
||
615 | rev = dict(reversed(item) \ |
||
616 | for item in self.__item_for_annotation.items()) |
||
617 | return rev[item] |
||
618 | |||
619 | def commit_scheme_node(self, node): |
||
620 | """ |
||
621 | Commit the `node` into the scheme. |
||
622 | """ |
||
623 | if not self.editable: |
||
624 | raise Exception("Scheme not editable.") |
||
625 | |||
626 | if node not in self.__item_for_node: |
||
627 | raise ValueError("No 'NodeItem' for node.") |
||
628 | |||
629 | item = self.__item_for_node[node] |
||
630 | |||
631 | try: |
||
632 | self.scheme.add_node(node) |
||
633 | except Exception: |
||
634 | log.error("An error occurred while committing node '%s'", |
||
635 | node, exc_info=True) |
||
636 | # Cleanup (remove the node item) |
||
637 | self.remove_node_item(item) |
||
638 | raise |
||
639 | |||
640 | log.info("Commited node '%s' from '%s' to '%s'" % \ |
||
641 | (node, self, self.scheme)) |
||
642 | |||
643 | def commit_scheme_link(self, link): |
||
644 | """ |
||
645 | Commit a scheme link. |
||
646 | """ |
||
647 | if not self.editable: |
||
648 | raise Exception("Scheme not editable") |
||
649 | |||
650 | if link not in self.__item_for_link: |
||
651 | raise ValueError("No 'LinkItem' for link.") |
||
652 | |||
653 | self.scheme.add_link(link) |
||
654 | log.info("Commited link '%s' from '%s' to '%s'" % \ |
||
655 | (link, self, self.scheme)) |
||
656 | |||
657 | def node_for_item(self, item): |
||
658 | """ |
||
659 | Return the `SchemeNode` for the `item`. |
||
660 | """ |
||
661 | rev = dict([(v, k) for k, v in self.__item_for_node.items()]) |
||
662 | return rev[item] |
||
663 | |||
664 | def item_for_node(self, node): |
||
665 | """ |
||
666 | Return the :class:`NodeItem` instance for a :class:`SchemeNode`. |
||
667 | """ |
||
668 | return self.__item_for_node[node] |
||
669 | |||
670 | def link_for_item(self, item): |
||
671 | """ |
||
672 | Return the `SchemeLink for `item` (:class:`LinkItem`). |
||
673 | """ |
||
674 | rev = dict([(v, k) for k, v in self.__item_for_link.items()]) |
||
675 | return rev[item] |
||
676 | |||
677 | def item_for_link(self, link): |
||
678 | """ |
||
679 | Return the :class:`LinkItem` for a :class:`SchemeLink` |
||
680 | """ |
||
681 | return self.__item_for_link[link] |
||
682 | |||
683 | def selected_node_items(self): |
||
684 | """ |
||
685 | Return the selected :class:`NodeItem`'s. |
||
686 | """ |
||
687 | return [item for item in self.__node_items if item.isSelected()] |
||
688 | |||
689 | def selected_annotation_items(self): |
||
690 | """ |
||
691 | Return the selected :class:`Annotation`'s |
||
692 | """ |
||
693 | return [item for item in self.__annotation_items if item.isSelected()] |
||
694 | |||
695 | def node_links(self, node_item): |
||
696 | """ |
||
697 | Return all links from the `node_item` (:class:`NodeItem`). |
||
698 | """ |
||
699 | return self.node_output_links(node_item) + \ |
||
700 | self.node_input_links(node_item) |
||
701 | |||
702 | def node_output_links(self, node_item): |
||
703 | """ |
||
704 | Return a list of all output links from `node_item`. |
||
705 | """ |
||
706 | return [link for link in self.__link_items |
||
707 | if link.sourceItem == node_item] |
||
708 | |||
709 | def node_input_links(self, node_item): |
||
710 | """ |
||
711 | Return a list of all input links for `node_item`. |
||
712 | """ |
||
713 | return [link for link in self.__link_items |
||
714 | if link.sinkItem == node_item] |
||
715 | |||
716 | def neighbor_nodes(self, node_item): |
||
717 | """ |
||
718 | Return a list of `node_item`'s (class:`NodeItem`) neighbor nodes. |
||
719 | """ |
||
720 | neighbors = list(map(attrgetter("sourceItem"), |
||
721 | self.node_input_links(node_item))) |
||
722 | |||
723 | neighbors.extend(map(attrgetter("sinkItem"), |
||
724 | self.node_output_links(node_item))) |
||
725 | return neighbors |
||
726 | |||
727 | def on_widget_state_change(self, widget, state): |
||
728 | pass |
||
729 | |||
730 | def on_link_state_change(self, link, state): |
||
731 | pass |
||
732 | |||
733 | def on_scheme_change(self, ): |
||
734 | pass |
||
735 | |||
736 | def _on_position_change(self, item): |
||
737 | # Invalidate the anchor point layout and schedule a layout. |
||
738 | self.__anchor_layout.invalidateNode(item) |
||
739 | |||
740 | self.node_item_position_changed.emit(item, item.pos()) |
||
741 | |||
742 | def __on_node_pos_changed(self, pos): |
||
743 | node = self.sender() |
||
744 | item = self.__item_for_node[node] |
||
745 | item.setPos(*pos) |
||
746 | |||
747 | def __on_scheme_annot_geometry_change(self): |
||
748 | annot = self.sender() |
||
749 | item = self.__item_for_annotation[annot] |
||
750 | if isinstance(annot, scheme.SchemeTextAnnotation): |
||
751 | item.setGeometry(QRectF(*annot.rect)) |
||
752 | elif isinstance(annot, scheme.SchemeArrowAnnotation): |
||
753 | p1 = item.mapFromScene(QPointF(*annot.start_pos)) |
||
754 | p2 = item.mapFromScene(QPointF(*annot.end_pos)) |
||
755 | item.setLine(QLineF(p1, p2)) |
||
756 | else: |
||
757 | pass |
||
758 | |||
759 | def item_at(self, pos, type_or_tuple=None, buttons=0): |
||
760 | """Return the item at `pos` that is an instance of the specified |
||
761 | type (`type_or_tuple`). If `buttons` (`Qt.MouseButtons`) is given |
||
762 | only return the item if it is the top level item that would |
||
763 | accept any of the buttons (`QGraphicsItem.acceptedMouseButtons`). |
||
764 | |||
765 | """ |
||
766 | rect = QRectF(pos, QSizeF(1, 1)) |
||
767 | items = self.items(rect) |
||
768 | |||
769 | if buttons: |
||
770 | items = itertools.dropwhile( |
||
771 | lambda item: not item.acceptedMouseButtons() & buttons, |
||
772 | items |
||
773 | ) |
||
774 | items = list(items)[:1] |
||
775 | |||
776 | if type_or_tuple: |
||
777 | items = [i for i in items if isinstance(i, type_or_tuple)] |
||
778 | |||
779 | return items[0] if items else None |
||
780 | |||
781 | if list(map(int, PYQT_VERSION_STR.split('.'))) < [4, 9]: |
||
782 | # For QGraphicsObject subclasses items, itemAt ... return a |
||
783 | # QGraphicsItem wrapper instance and not the actual class instance. |
||
784 | def itemAt(self, *args, **kwargs): |
||
785 | item = QGraphicsScene.itemAt(self, *args, **kwargs) |
||
786 | return toGraphicsObjectIfPossible(item) |
||
787 | |||
788 | def items(self, *args, **kwargs): |
||
789 | items = QGraphicsScene.items(self, *args, **kwargs) |
||
790 | return list(map(toGraphicsObjectIfPossible, items)) |
||
791 | |||
792 | def selectedItems(self, *args, **kwargs): |
||
793 | return list(map(toGraphicsObjectIfPossible, |
||
794 | QGraphicsScene.selectedItems(self, *args, **kwargs))) |
||
795 | |||
796 | def collidingItems(self, *args, **kwargs): |
||
797 | return list(map(toGraphicsObjectIfPossible, |
||
798 | QGraphicsScene.collidingItems(self, *args, **kwargs))) |
||
799 | |||
800 | def focusItem(self, *args, **kwargs): |
||
801 | item = QGraphicsScene.focusItem(self, *args, **kwargs) |
||
802 | return toGraphicsObjectIfPossible(item) |
||
803 | |||
804 | def mouseGrabberItem(self, *args, **kwargs): |
||
805 | item = QGraphicsScene.mouseGrabberItem(self, *args, **kwargs) |
||
806 | return toGraphicsObjectIfPossible(item) |
||
807 | |||
808 | def mousePressEvent(self, event): |
||
809 | if self.user_interaction_handler and \ |
||
810 | self.user_interaction_handler.mousePressEvent(event): |
||
811 | return |
||
812 | |||
813 | # Right (context) click on the node item. If the widget is not |
||
814 | # in the current selection then select the widget (only the widget). |
||
815 | # Else simply return and let customContextMenuReqested signal |
||
816 | # handle it |
||
817 | shape_item = self.item_at(event.scenePos(), items.NodeItem) |
||
818 | if shape_item and event.button() == Qt.RightButton and \ |
||
819 | shape_item.flags() & QGraphicsItem.ItemIsSelectable: |
||
820 | if not shape_item.isSelected(): |
||
821 | self.clearSelection() |
||
822 | shape_item.setSelected(True) |
||
823 | |||
824 | return QGraphicsScene.mousePressEvent(self, event) |
||
825 | |||
826 | def mouseMoveEvent(self, event): |
||
827 | if self.user_interaction_handler and \ |
||
828 | self.user_interaction_handler.mouseMoveEvent(event): |
||
829 | return |
||
830 | |||
831 | return QGraphicsScene.mouseMoveEvent(self, event) |
||
832 | |||
833 | def mouseReleaseEvent(self, event): |
||
834 | if self.user_interaction_handler and \ |
||
835 | self.user_interaction_handler.mouseReleaseEvent(event): |
||
836 | return |
||
837 | return QGraphicsScene.mouseReleaseEvent(self, event) |
||
838 | |||
839 | def mouseDoubleClickEvent(self, event): |
||
840 | if self.user_interaction_handler and \ |
||
841 | self.user_interaction_handler.mouseDoubleClickEvent(event): |
||
842 | return |
||
843 | |||
844 | return QGraphicsScene.mouseDoubleClickEvent(self, event) |
||
845 | |||
846 | def keyPressEvent(self, event): |
||
847 | if self.user_interaction_handler and \ |
||
848 | self.user_interaction_handler.keyPressEvent(event): |
||
849 | return |
||
850 | return QGraphicsScene.keyPressEvent(self, event) |
||
851 | |||
852 | def keyReleaseEvent(self, event): |
||
853 | if self.user_interaction_handler and \ |
||
854 | self.user_interaction_handler.keyReleaseEvent(event): |
||
855 | return |
||
856 | return QGraphicsScene.keyReleaseEvent(self, event) |
||
857 | |||
858 | def set_user_interaction_handler(self, handler): |
||
859 | if self.user_interaction_handler and \ |
||
860 | not self.user_interaction_handler.isFinished(): |
||
861 | self.user_interaction_handler.cancel() |
||
862 | |||
863 | log.info("Setting interaction '%s' to '%s'" % (handler, self)) |
||
864 | |||
865 | self.user_interaction_handler = handler |
||
866 | if handler: |
||
867 | handler.start() |
||
868 | |||
869 | def event(self, event): |
||
870 | # TODO: change the base class of Node/LinkItem to QGraphicsWidget. |
||
871 | # It already handles font changes. |
||
872 | if event.type() == QEvent.FontChange: |
||
873 | self.__update_font() |
||
874 | |||
875 | return QGraphicsScene.event(self, event) |
||
876 | |||
877 | def __update_font(self): |
||
878 | font = self.font() |
||
879 | for item in self.__node_items + self.__link_items: |
||
880 | item.setFont(font) |
||
881 | |||
882 | def __str__(self): |
||
883 | return "%s(objectName=%r, ...)" % \ |
||
884 | (type(self).__name__, str(self.objectName())) |
||
885 | |||
950 |
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.py
files in your module folders. Make sure that you place one file in each sub-folder.