diff --git a/qt_dotgraph/src/qt_dotgraph/dot_shapes.py b/qt_dotgraph/src/qt_dotgraph/dot_shapes.py new file mode 100644 index 00000000..51017b62 --- /dev/null +++ b/qt_dotgraph/src/qt_dotgraph/dot_shapes.py @@ -0,0 +1,43 @@ +from python_qt_binding.QtCore import QRectF +from python_qt_binding.QtWidgets import QAbstractGraphicsShapeItem + +class QGraphicsBox3dItem(QAbstractGraphicsShapeItem): + def __init__(self, bounding_box): + super(QGraphicsBox3dItem, self).__init__() + self._bounding_box = bounding_box + + def boundingRect(self): + return self._bounding_box + + def paint(self, painter, option, widget): + # Main rectangle + rectangle = QRectF(self._bounding_box.topLeft().x(), + self._bounding_box.topLeft().y() + self._bounding_box.height() * 0.1, + self._bounding_box.width() - self._bounding_box.height() * 0.1, + self._bounding_box.height() - self._bounding_box.height() * 0.1) + painter.drawRect(rectangle) + # Top line + painter.drawLine(rectangle.topLeft().x() + self._bounding_box.height() * 0.1, + self._bounding_box.topLeft().y(), + self._bounding_box.topRight().x(), + self._bounding_box.topRight().y()) + # Top left corner + painter.drawLine(rectangle.topLeft().x() + self._bounding_box.height() * 0.1, + self._bounding_box.topLeft().y(), + self._bounding_box.topLeft().x() + 1, + rectangle.topLeft().y()) + # Top right corner + painter.drawLine(self._bounding_box.topRight().x(), + self._bounding_box.topRight().y(), + rectangle.topRight().x(), + rectangle.topRight().y()) + # Bottom right corner + painter.drawLine(rectangle.bottomRight().x() + 1, + rectangle.bottomRight().y() - 1, + self._bounding_box.bottomRight().x(), + rectangle.bottomRight().y() - self._bounding_box.height() * 0.1) + # Right line + painter.drawLine(self._bounding_box.topRight().x(), + self._bounding_box.topRight().y(), + self._bounding_box.topRight().x(), + self._bounding_box.bottomRight().y() - self._bounding_box.height() * 0.1) diff --git a/qt_dotgraph/src/qt_dotgraph/dot_to_qt.py b/qt_dotgraph/src/qt_dotgraph/dot_to_qt.py index 41ff364a..77ee7b69 100644 --- a/qt_dotgraph/src/qt_dotgraph/dot_to_qt.py +++ b/qt_dotgraph/src/qt_dotgraph/dot_to_qt.py @@ -67,7 +67,7 @@ class DotToQtGenerator(): def __init__(self): pass - def getNodeItemForSubgraph(self, subgraph, highlight_level): + def getNodeItemForSubgraph(self, subgraph, highlight_level, scene=None): # let pydot imitate pygraphviz api attr = {} for name in subgraph.get_attributes().keys(): @@ -103,6 +103,7 @@ def getNodeItemForSubgraph(self, subgraph, highlight_level): label=name, shape='box', color=color, + parent=scene.activePanel() if scene is not None else None, label_pos=QPointF(float(label_pos[0]), -float(label_pos[1]))) bounding_box = QRectF(bounding_box) # With clusters we have the problem that mouse hovers cannot @@ -111,9 +112,12 @@ def getNodeItemForSubgraph(self, subgraph, highlight_level): # border region would be even better (multiple RectF) bounding_box.setHeight(LABEL_HEIGHT) subgraph_nodeitem.set_hovershape(bounding_box) + + if scene is not None: + scene.addItem(subgraph_nodeitem) return subgraph_nodeitem - def getNodeItemForNode(self, node, highlight_level): + def getNodeItemForNode(self, node, highlight_level, scene=None): """ returns a pyqt NodeItem object, or None in case of error or invisible style """ @@ -152,13 +156,15 @@ def getNodeItemForNode(self, node, highlight_level): label=name, shape=node.attr.get('shape', 'ellipse'), color=color, - tooltip=node.attr.get('tooltip') - #parent=None, + tooltip=node.attr.get('tooltip'), + parent=scene.activePanel() if scene is not None else None #label_pos=None ) + if scene is not None: + scene.addItem(node_item) return node_item - def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False): + def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False, scene=None): """ adds EdgeItem by data in edge to edges :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting) @@ -208,6 +214,7 @@ def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=F from_node=nodes[source_node], to_node=nodes[destination_node], penwidth=penwidth, + parent=scene.activePanel() if scene is not None else None, edge_color=color, style=style) @@ -228,8 +235,10 @@ def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=F if edge_name not in edges: edges[edge_name] = [] edges[edge_name].append(edge_item) + if scene is not None: + edge_item.add_to_scene(scene) - def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False): + def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False, scene=None): """ takes dotcode, runs layout, and creates qt items based on the dot layout. returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item @@ -245,11 +254,11 @@ def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=Fals #graph = pygraphviz.AGraph(string=self._current_dotcode, strict=False, directed=True) #graph.layout(prog='dot') - nodes = self.parse_nodes(graph, highlight_level) - edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings) + nodes = self.parse_nodes(graph, highlight_level, scene=scene) + edges = self.parse_edges(graph, nodes, highlight_level, same_label_siblings, scene=scene) return nodes, edges - def parse_nodes(self, graph, highlight_level): + def parse_nodes(self, graph, highlight_level, scene=None): """Recursively searches all nodes inside the graph and all subgraphs.""" # let pydot imitate pygraphviz api graph.nodes_iter = graph.get_node_list @@ -257,8 +266,8 @@ def parse_nodes(self, graph, highlight_level): nodes = {} for subgraph in graph.subgraphs_iter(): - subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level) - nodes.update(self.parse_nodes(subgraph, highlight_level)) + subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level, scene=scene) + nodes.update(self.parse_nodes(subgraph, highlight_level, scene=scene)) # skip subgraphs with empty bounding boxes if subgraph_nodeitem is None: continue @@ -269,15 +278,15 @@ def parse_nodes(self, graph, highlight_level): # hack required by pydot if node.get_name() in ('graph', 'node', 'empty'): continue - nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level) + nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene) for node in graph.nodes_iter(): # hack required by pydot if node.get_name() in ('graph', 'node', 'empty'): continue - nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level) + nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level, scene=scene) return nodes - def parse_edges(self, graph, nodes, highlight_level, same_label_siblings): + def parse_edges(self, graph, nodes, highlight_level, same_label_siblings, scene=None): """Recursively searches all edges inside the graph and all subgraphs.""" # let pydot imitate pygraphviz api graph.subgraphs_iter = graph.get_subgraph_list @@ -286,15 +295,17 @@ def parse_edges(self, graph, nodes, highlight_level, same_label_siblings): edges = {} for subgraph in graph.subgraphs_iter(): subgraph.edges_iter = subgraph.get_edge_list - edges.update(self.parse_edges(subgraph, nodes, highlight_level, same_label_siblings)) + edges.update(self.parse_edges(subgraph, nodes, highlight_level, same_label_siblings, scene=scene)) for edge in subgraph.edges_iter(): self.addEdgeItem(edge, nodes, edges, highlight_level=highlight_level, - same_label_siblings=same_label_siblings) + same_label_siblings=same_label_siblings, + scene=scene) for edge in graph.edges_iter(): self.addEdgeItem(edge, nodes, edges, highlight_level=highlight_level, - same_label_siblings=same_label_siblings) + same_label_siblings=same_label_siblings, + scene=scene) return edges diff --git a/qt_dotgraph/src/qt_dotgraph/edge_item.py b/qt_dotgraph/src/qt_dotgraph/edge_item.py index 11f08602..daa6cf75 100644 --- a/qt_dotgraph/src/qt_dotgraph/edge_item.py +++ b/qt_dotgraph/src/qt_dotgraph/edge_item.py @@ -126,7 +126,7 @@ def __init__(self, highlight_level, spline, label_center, label, from_node, to_n self._arrow.hoverLeaveEvent = self._handle_hoverLeaveEvent self._arrow.setAcceptHoverEvents(True) - self._path = QGraphicsPathItem() + self._path = QGraphicsPathItem(parent) self._path.setPath(path) self.addToGroup(self._path) diff --git a/qt_dotgraph/src/qt_dotgraph/node_item.py b/qt_dotgraph/src/qt_dotgraph/node_item.py index f517d875..e25a465f 100644 --- a/qt_dotgraph/src/qt_dotgraph/node_item.py +++ b/qt_dotgraph/src/qt_dotgraph/node_item.py @@ -28,10 +28,15 @@ # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from __future__ import print_function + +import sys + from python_qt_binding.QtCore import Qt from python_qt_binding.QtGui import QBrush, QPainterPath, QPen from python_qt_binding.QtWidgets import QGraphicsEllipseItem, QGraphicsRectItem, QGraphicsSimpleTextItem +from .dot_shapes import QGraphicsBox3dItem from .graph_item import GraphItem @@ -51,10 +56,7 @@ def __init__(self, highlight_level, bounding_box, label, shape, color=None, pare self._incoming_edges = set() self._outgoing_edges = set() - if shape == 'box': - self._graphics_item = QGraphicsRectItem(bounding_box) - else: - self._graphics_item = QGraphicsEllipseItem(bounding_box) + self.parse_shape(shape, bounding_box) self.addToGroup(self._graphics_item) self._label = QGraphicsSimpleTextItem(label) @@ -75,6 +77,17 @@ def __init__(self, highlight_level, bounding_box, label, shape, color=None, pare self.hovershape = None + def parse_shape(self, shape, bounding_box): + if shape in ('box', 'rect', 'rectangle'): + self._graphics_item = QGraphicsRectItem(bounding_box) + elif shape in ('ellipse', 'oval', 'circle'): + self._graphics_item = QGraphicsEllipseItem(bounding_box) + elif shape in ('box3d', ): + self._graphics_item = QGraphicsBox3dItem(bounding_box) + else: + print("Invalid shape '%s', defaulting to ellipse" % shape, file=sys.stderr) + self._graphics_item = QGraphicsEllipseItem(bounding_box) + def set_hovershape(self, newhovershape): self.hovershape = newhovershape