SIT/ui/widgets/graph_view_widget.py

172 lines
5.1 KiB
Python
Raw Normal View History

2026-01-29 09:22:54 +00:00
"""
图形化编辑器控件
用于可视化编辑映射图
"""
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QGraphicsView, QGraphicsScene,
QGraphicsEllipseItem, QGraphicsLineItem, QGraphicsTextItem,
QPushButton, QToolBar, QAction
)
from PyQt5.QtCore import Qt, QPointF, QRectF, pyqtSignal
from PyQt5.QtGui import QPen, QBrush, QColor, QPainter
from models.mapping_graph import MappingGraph, GraphNode, GraphEdge
from config import Config
from utils.logger import get_logger
logger = get_logger(__name__)
class GraphNodeItem(QGraphicsEllipseItem):
"""图节点图形项"""
def __init__(self, node: GraphNode, x: float, y: float):
super().__init__(0, 0, Config.NODE_WIDTH, Config.NODE_HEIGHT)
self.node = node
self.setPos(x, y)
# 设置样式
self.setBrush(QBrush(QColor(33, 150, 243)))
self.setPen(QPen(QColor(25, 118, 210), 2))
# 可移动
self.setFlag(QGraphicsEllipseItem.ItemIsMovable)
self.setFlag(QGraphicsEllipseItem.ItemIsSelectable)
self.setFlag(QGraphicsEllipseItem.ItemSendsGeometryChanges)
# 添加文本标签
self.text_item = QGraphicsTextItem(self)
self.text_item.setPlainText(node.field.name)
self.text_item.setDefaultTextColor(Qt.white)
# 居中文本
text_rect = self.text_item.boundingRect()
text_x = (Config.NODE_WIDTH - text_rect.width()) / 2
text_y = (Config.NODE_HEIGHT - text_rect.height()) / 2
self.text_item.setPos(text_x, text_y)
def itemChange(self, change, value):
"""节点位置改变时的处理"""
if change == QGraphicsEllipseItem.ItemPositionChange:
# 更新连接的边
pass
return super().itemChange(change, value)
class GraphEdgeItem(QGraphicsLineItem):
"""图边图形项"""
def __init__(self, edge: GraphEdge, source_item: GraphNodeItem, target_item: GraphNodeItem):
super().__init__()
self.edge = edge
self.source_item = source_item
self.target_item = target_item
# 设置样式
self.setPen(QPen(QColor(100, 100, 100), Config.EDGE_WIDTH))
# 更新位置
self.update_position()
def update_position(self):
"""更新边的位置"""
source_center = self.source_item.sceneBoundingRect().center()
target_center = self.target_item.sceneBoundingRect().center()
self.setLine(
source_center.x(), source_center.y(),
target_center.x(), target_center.y()
)
class GraphViewWidget(QGraphicsView):
"""图形视图控件"""
node_selected = pyqtSignal(GraphNode)
edge_selected = pyqtSignal(GraphEdge)
def __init__(self):
super().__init__()
# 创建场景
self.scene = QGraphicsScene()
self.setScene(self.scene)
# 设置视图属性
self.setRenderHint(QPainter.Antialiasing)
self.setDragMode(QGraphicsView.ScrollHandDrag)
self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
# 存储图形项
self.node_items = {} # field_id -> GraphNodeItem
self.edge_items = {} # edge_id -> GraphEdgeItem
logger.info("Graph view widget initialized")
def load_graph(self, graph: MappingGraph):
"""加载映射图"""
self.scene.clear()
self.node_items.clear()
self.edge_items.clear()
# 添加节点
for field_id, node in graph.nodes.items():
self.add_node(node)
# 添加边
for edge in graph.edges:
self.add_edge(edge)
logger.info(f"Loaded graph: {len(graph.nodes)} nodes, {len(graph.edges)} edges")
def add_node(self, node: GraphNode):
"""添加节点"""
node_item = GraphNodeItem(node, node.x, node.y)
self.scene.addItem(node_item)
self.node_items[node.field.id] = node_item
def add_edge(self, edge: GraphEdge):
"""添加边"""
source_item = self.node_items.get(edge.source_field_id)
target_item = self.node_items.get(edge.target_field_id)
if source_item and target_item:
edge_item = GraphEdgeItem(edge, source_item, target_item)
self.scene.addItem(edge_item)
if edge.id:
self.edge_items[edge.id] = edge_item
def auto_layout(self):
"""自动布局(简单的圆形布局)"""
import math
nodes = list(self.node_items.values())
if not nodes:
return
center_x = 400
center_y = 300
radius = 200
angle_step = 2 * math.pi / len(nodes)
for i, node_item in enumerate(nodes):
angle = i * angle_step
x = center_x + radius * math.cos(angle)
y = center_y + radius * math.sin(angle)
node_item.setPos(x, y)
# 更新所有边
for edge_item in self.edge_items.values():
edge_item.update_position()
logger.info("Auto layout applied")
def wheelEvent(self, event):
"""鼠标滚轮缩放"""
factor = 1.2 if event.angleDelta().y() > 0 else 0.8
self.scale(factor, factor)