diff --git a/typetapper/hierarchy_graph_view.py b/typetapper/hierarchy_graph_view.py index 43587dc..6446d60 100644 --- a/typetapper/hierarchy_graph_view.py +++ b/typetapper/hierarchy_graph_view.py @@ -8,9 +8,9 @@ from threading import Lock import logging from PySide6.QtCore import QRectF, QPointF, QTimeLine, QEasingCurve, QMarginsF, QTimer, Qt, QEvent -from PySide6.QtGui import QColor, QFont, QBrush, QPainterPath, QPen +from PySide6.QtGui import QColor, QFont, QBrush, QPainterPath, QPen, QAction, QShortcut from PySide6.QtWidgets import QGraphicsItem, QGraphicsRectItem, QGraphicsSimpleTextItem, QHBoxLayout, QGraphicsScene, \ - QGraphicsEllipseItem, QGraphicsSceneMouseEvent + QGraphicsEllipseItem, QGraphicsSceneMouseEvent, QMenu, QDialog, QVBoxLayout, QLineEdit, QInputDialog import networkx import pygraphviz @@ -111,6 +111,11 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): super().__init__(parent) + self._action_rename = QAction("Rename...") + self._action_rename.setShortcut("N") + self._action_rename.triggered.connect(self.rename_selected_node) + self.viewport().addAction(self._action_rename) + self.setScene(QGraphicsScene()) self.scene().selectionChanged.connect(self._on_selection_changed_internal) self.current_group.am_subscribe(self._on_change_group) @@ -122,6 +127,7 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): self._layout_animation_controller.timeout.connect(self._layout_animation.stop) self.setDragMode(QZoomableDraggableGraphicsView.ScrollHandDrag) self.viewport().setCursor(Qt.CursorShape.ArrowCursor) + self.viewport().setContextMenuPolicy(Qt.ActionsContextMenu) self._on_change_group(src='__init__') @@ -157,11 +163,14 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): self.current_group.am_event(focus=target) - def label(self, node: RelativeAtomOrGroup) -> str: + def label(self, node: RelativeAtomOrGroupOrExtern) -> str: if node not in self._labels: if isinstance(node, RelativeAtom): self._labels[node] = str(node.atom) + elif isinstance(node, ExternNode): + self._labels[node] = "Out" else: + assert isinstance(node, RelativeAtomGroup) self._labels[node] = 'Unknown group' funcs = {self.hg.kp.atom_to_function(atom) for atom in self.hg.atoms(node) if atom not in self.hg.frontier} if len(funcs) == 1: @@ -333,7 +342,6 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): if edge_replacement is not None: self.drop_target.edges[edge_replacement[1]] = self.drop_target.edges.pop(edge_replacement[0]) - assert isinstance(model, RelativeAtomGroup) for item in self.scene().selectedItems(): if isinstance(item, PropChartHG): @@ -341,10 +349,18 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): self._rekey_externs(item, self.drop_target) self.drop_target.deemphasize() - self.drop_target._layout_marks() + self.drop_target.layout() self.drop_target = None self._layout(0) + def rename_selected_node(self): + if self.selected_node is None: + return + current_name = self._labels[self.selected_node] + text, confirmed = QInputDialog.getText(self, "Rename", "Rename node to:", QLineEdit.EchoMode.Normal, current_name) + if confirmed: + self._labels[self.selected_node] = text + self.qnodes[self.ug_reverse[self.selected_node]].layout() # private interfaces @@ -552,11 +568,17 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): # qt overrides + def viewportEvent(self, event): + if event.type() == QEvent.Type.ContextMenu: + return False + return super().viewportEvent(event) + def keyPressEvent(self, event): if event.key() == Qt.Key_Z and not self.is_expanding: self._layout_animation.start() event.accept() - super().keyPressEvent(event) + else: + super().keyPressEvent(event) def keyReleaseEvent(self, event): if event.key() == Qt.Key_Z: @@ -615,14 +637,13 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): self.centerOn(qnode.center) def _on_selection_changed(self, src=None, **kwargs): - if src == 'qt': - return - self._selection_event_occurring = True - self.scene().clearSelection() - for node in self.selected_nodes: - qnode = self.qnodes[self.ug_reverse[node]] - qnode.setSelected(True) - self._selection_event_occurring = False + if src != 'qt': + self._selection_event_occurring = True + self.scene().clearSelection() + for node in self.selected_nodes: + qnode = self.qnodes[self.ug_reverse[node]] + qnode.setSelected(True) + self._selection_event_occurring = False def _on_selection_changed_internal(self): if self.is_expanding: @@ -642,6 +663,9 @@ class HierarchicalGraphWidget(QZoomableDraggableGraphicsView): for qedge in self.qedges: qedge.update() + single = len(self.selected_nodes) == 1 + self._action_rename.setVisible(single) + class AnimatableItem(QGraphicsItem): def __init__(self, *args, **kwargs): @@ -708,6 +732,9 @@ class HGNode(AnimatableItem): def set_agraph_node_properties(self, node: pygraphviz.Node): raise NotImplementedError + def layout(self): + raise NotImplementedError + # private interfaces def _animate_enter(self, value): @@ -756,7 +783,7 @@ class PropChart(QGraphicsItem): super().__init__(parent) - self._layout_marks() + self.layout() @property def prop(self) -> Prop: @@ -864,9 +891,9 @@ class PropChartHG(HGNode, PropChart): self.setX(v.x() - self.width / 2) self.setY(v.y() - self.height / 2) - def _layout_marks(self): + def layout(self): lbl_height = 20 - super()._layout_marks() + self._layout_marks() self.height += lbl_height for obj in self.objects: if obj is self.object_bg: @@ -967,15 +994,19 @@ class HGExtern(HGNode): self.object_text = None super().__init__(*args, **kwargs) - self._layout() + self.layout() + + def layout(self): + self.objects.clear() + for item in list(self.childItems()): + self.scene().removeItem(item) - def _layout(self): self.object_bg = QGraphicsEllipseItem(QRectF(-self.radius, -self.radius, self.radius*2, self.radius*2), self) self.object_bg.setBrush(BOX_COLOR) self.object_bg.setPen(QPen(BOX_BORDER_COLOR, BOX_BORDER_WIDTH)) self.objects.append(self.object_bg) - self.object_text = QGraphicsSimpleTextItem("Out", self) + self.object_text = QGraphicsSimpleTextItem(self.hgw.label(self.model), self) self.object_text.setPos(-self.object_text.boundingRect().center()) self.object_text.setFont(TEXT_FONT) self.objects.append(self.object_text)