优化重叠情况

重构代码,添加流程图自动捕捉,并且像素可以调节,方便调整精度。
This commit is contained in:
侯展意 2020-09-24 21:06:10 +08:00
parent b52c6d3d19
commit c914727edf
4 changed files with 240 additions and 46 deletions

View File

@ -3,7 +3,7 @@ from PyQt5.QtGui import QPolygonF, QPen, QPainterPath, QColor, QPainter, QBrush
from PyQt5.QtWidgets import QGraphicsLineItem, QGraphicsItem, QGraphicsSceneMouseEvent, QGraphicsObject, \
QStyleOptionGraphicsItem, QWidget, QGraphicsSceneHoverEvent
from typing import TYPE_CHECKING, Tuple, List
from typing import TYPE_CHECKING, Tuple, List, Callable
if TYPE_CHECKING:
from .flowchart_widget import Node, PMGraphicsScene
@ -11,9 +11,41 @@ if TYPE_CHECKING:
COLOR_NORMAL = QColor(212, 227, 242)
COLOR_HOVER = QColor(255, 200, 00)
COLOR_HOVER_PORT = QColor(0, 0, 50)
COLOR_HOVER_MID_POINT = QColor(0, 0, 200)
COLOR_SELECTED = QColor(255, 255, 0)
def round_position(point: QPointF, pixels=5):
x, y = point.x(), point.y()
x_cor, y_cor = round(x * 1.0 / pixels) * pixels, round(y * 1.0 / pixels) * pixels
return QPointF(x_cor, y_cor)
class PMGGraphicsLineItem(QGraphicsLineItem):
def __init__(self, line: QLineF):
super(PMGGraphicsLineItem, self).__init__(line)
self.callback = None
self.parent_line: 'CustomLine' = None
def bind_callback(self, parent_line: 'CustomLine' = None):
self.parent_line = parent_line
def mousePressEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
'''
这里不能进行继承一旦进行了继承似乎会让下面所有的东西都收到信号从而无法选定
:param event:
:return:
'''
super(PMGGraphicsLineItem, self).mousePressEvent(event)
self.parent_line.on_line_item_pressed(event, self)
def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
self.parent_line.add_mid_point(self, pos=event.scenePos())
def bind_mouse_clicked(self, callback: Callable):
self.callback = callback
class CustomLine(QGraphicsLineItem):
'''
CustomLine不是QObject没有办法绑定信号只能用回调函数的方式
@ -35,24 +67,61 @@ class CustomLine(QGraphicsLineItem):
repaint_callback = None
def __init__(self, start_port: 'CustomPort', end_port: 'CustomPort', canvas: 'PMGraphicsScene' = None,
mid_points: 'List[CustomCenterPoint]' = None):
mid_points: 'List[CustomMidPoint]' = None):
super(CustomLine, self).__init__()
self.color = COLOR_NORMAL
self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) # 拖动
self.start_port = start_port
self.end_port = end_port
if self not in start_port.connected_lines:
start_port.connected_lines.append(self)
if self not in end_port.connected_lines:
end_port.connected_lines.append(self)
self.line_Items: List['QGraphicsLineItem'] = []
self.center_points = mid_points if (mid_points is not None) else [CustomCenterPoint()]
self.center_points = mid_points if (mid_points is not None) else []
for p in self.center_points:
canvas.addItem(p)
center_point = self.center_points[0]
center_point.point_dragged.connect(self.refresh)
p.point_dragged.connect(self.refresh)
p.line = self
canvas.signal_clear_selection.connect(self.on_clear_selection)
self.refresh()
self.canvas = canvas
self.init_line_items()
def remove_mid_point(self, mid_point: 'CustomMidPoint'):
index = self.center_points.index(mid_point)
point_to_remove = self.center_points.pop(index)
line_to_remove = self.line_Items.pop(index)
self.canvas.removeItem(point_to_remove)
self.canvas.removeItem(line_to_remove)
self.canvas.removeItem(mid_point)
self.refresh_line_items()
self.update()
self.canvas.graphics_view.viewport().update()
def add_mid_point(self, line_item: 'PMGGraphicsLineItem', pos: QPointF):
index = self.line_Items.index(line_item)
new_center_point = CustomMidPoint(pos, line=self)
self.canvas.addItem(new_center_point)
self.center_points.insert(index, new_center_point)
new_center_point.point_dragged.connect(self.refresh)
if index == 0:
last_pos = self.start_port.center_pos
else:
last_pos = self.center_points[index].center_pos
next_pos = new_center_point.center_pos
new_line_item = PMGGraphicsLineItem(QLineF(last_pos, next_pos))
new_line_item.bind_callback(self)
pen = QPen()
pen.setColor(self.color)
pen.setWidth(5)
new_line_item.setPen(pen)
self.canvas.addItem(new_line_item)
self.line_Items.insert(index, new_line_item)
self.refresh_line_items()
self.update()
self.canvas.graphics_view.viewport().update()
def init_line_items(self):
'''
初始化线段包括其中间节点
@ -62,8 +131,9 @@ class CustomLine(QGraphicsLineItem):
last_pos = self.start_port.center_pos
for p in self.center_points:
pos = p.center_pos
line_item = QGraphicsLineItem(QLineF(last_pos, pos))
line_item.mousePressEvent = self.on_line_item_pressed
line_item = PMGGraphicsLineItem(QLineF(last_pos, pos))
# line_item.mousePressEvent = self.on_line_item_pressed
line_item.bind_callback(self)
pen = QPen()
pen.setColor(self.color)
pen.setWidth(5)
@ -71,8 +141,9 @@ class CustomLine(QGraphicsLineItem):
self.canvas.addItem(line_item)
last_pos = pos
self.line_Items.append(line_item)
line_item = QGraphicsLineItem(QLineF(last_pos, self.end_port.center_pos))
line_item.mousePressEvent = self.on_line_item_pressed
line_item = PMGGraphicsLineItem(QLineF(last_pos, self.end_port.center_pos))
# line_item.mousePressEvent = self.on_line_item_pressed
line_item.bind_callback(self)
self.canvas.addItem(line_item)
self.line_Items.append(line_item)
@ -93,19 +164,18 @@ class CustomLine(QGraphicsLineItem):
line = QLineF(last_pos, self.end_port.center_pos)
self.line_Items[-1].setLine(line)
def on_line_item_pressed(self, e: 'QGraphicsSceneMouseEvent'):
def on_line_item_pressed(self, e: 'QGraphicsSceneMouseEvent', line_item: 'CustomLine'):
'''
当线段被点击时触发的事件
:param e:
:return:
'''
print(e)
print(line_item)
if e.button() == Qt.LeftButton:
if not e.modifiers() == Qt.ControlModifier:
self.canvas.signal_clear_selection.emit()
self.setSelected(True)
print(self.canvas.selectedItems())
self.canvas.select_item(self)
print(self.canvas.selected_items)
self.color = COLOR_SELECTED
def on_clear_selection(self):
@ -114,7 +184,7 @@ class CustomLine(QGraphicsLineItem):
:return:
'''
self.color = COLOR_NORMAL
self.setSelected(False)
self.canvas.unselect_item(self)
# self.update()
def refresh(self):
@ -191,12 +261,36 @@ class CustomLine(QGraphicsLineItem):
self.update()
def on_delete(self):
'''
删除线对象
首先从起始和结束的端口的连线中删除这个线
然后从画布上移除自身所有的中间线段对象和中继点对象
从画布上删除自身
从所有连线的列表中移除自身
:return:
'''
try:
self.start_port.connected_lines.remove(self)
except ValueError:
pass
try:
self.end_port.connected_lines.remove(self)
except ValueError:
pass
for line_item in self.line_Items:
self.canvas.removeItem(line_item)
for mid_item in self.center_points:
self.canvas.removeItem(mid_item)
self.canvas.removeItem(self)
self.canvas.lines.remove(self)
class CustomCenterPoint(QGraphicsObject):
class CustomMidPoint(QGraphicsObject):
point_dragged = pyqtSignal(QGraphicsObject)
def __init__(self, pos=None):
super(CustomCenterPoint, self).__init__()
def __init__(self, pos: QPointF = None, line: 'CustomLine' = None):
super(CustomMidPoint, self).__init__()
# self.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) # 拖动
self.setAcceptHoverEvents(True) # 接受鼠标悬停事件
self.relative_pos = (0, 0)
@ -204,7 +298,12 @@ class CustomCenterPoint(QGraphicsObject):
self.size = (10, 10)
# self.line = line
if pos is not None:
self.setPos(QPointF(*pos))
# self.setPos()
# if isinstance(pos,list) or isinstance(pos,tuple):
# self.setPos(pos[0],pos[1])
# else:
self.setPos(pos)
self.line = line
def boundingRect(self):
return QRectF(0, 0, self.size[0], self.size[1])
@ -214,13 +313,18 @@ class CustomCenterPoint(QGraphicsObject):
return QPointF(self.x() + self.size[0] / 2, self.y() + self.size[1] / 2)
def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
'''
鼠标拖动时触发的事件
:param event:
:return:
'''
mouse_x, mouse_y = event.scenePos().x(), event.scenePos().y()
self.setPos(QPointF(mouse_x, mouse_y))
self.setPos(round_position(QPointF(mouse_x, mouse_y)))
self.point_dragged.emit(self)
def paint(self, painter, styles, widget=None):
pen1 = QPen(Qt.SolidLine)
pen1.setColor(QColor(128, 128, 128))
pen1.setColor(self.color)
painter.setPen(pen1)
brush1 = QBrush(Qt.SolidPattern)
@ -231,8 +335,7 @@ class CustomCenterPoint(QGraphicsObject):
painter.drawRoundedRect(self.boundingRect(), 10, 10)
def hoverEnterEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
self.color = COLOR_HOVER_PORT
self.color = COLOR_HOVER_MID_POINT
self.update()
def hoverLeaveEvent(self, event: 'QGraphicsSceneHoverEvent') -> None:
@ -261,6 +364,9 @@ class CustomCenterPoint(QGraphicsObject):
painter.drawRoundedRect(self.boundingRect(), 10, 10) # 绘制函数
painter.end()
def mouseDoubleClickEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
self.line.remove_mid_point(self)
class CustomPort(QGraphicsObject):
port_clicked = pyqtSignal(QGraphicsObject)
@ -273,6 +379,8 @@ class CustomPort(QGraphicsObject):
self.id = port_id
self.size = (10, 10)
self.content = content
self.connected_lines = []
self.canvas = None
def boundingRect(self):
return QRectF(0, 0, self.size[0], self.size[1])
@ -328,6 +436,11 @@ class CustomPort(QGraphicsObject):
pos = self.pos()
return pos.x(), pos.y()
def on_delete(self):
for line in self.connected_lines:
line.on_delete()
self.canvas.removeItem(self)
def __repr__(self):
return super(CustomPort, self).__repr__() + 'id = ' + str(self.id)
@ -342,8 +455,6 @@ class CustomRect(QGraphicsItem):
self.color = COLOR_NORMAL
self.node = node
def boundingRect(self):
return QRectF(0, 0, 200, 50)
@ -372,7 +483,9 @@ class CustomRect(QGraphicsItem):
self.update()
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
self.setPos(event.scenePos().x() - self.relative_pos[0], event.scenePos().y() - self.relative_pos[1])
self.setPos(round_position(
QPointF(event.scenePos().x() - self.relative_pos[0],
event.scenePos().y() - self.relative_pos[1])))
self.node.refresh_pos()
def mousePressEvent(self, evt: QGraphicsSceneMouseEvent):
@ -385,7 +498,7 @@ class CustomRect(QGraphicsItem):
print(self.relative_pos)
if not evt.modifiers() == Qt.ControlModifier:
self.scene().signal_clear_selection.emit()
self.setSelected(True)
self.scene().select_item(self)
print(self.scene().selectedItems())
self.color = COLOR_SELECTED
elif evt.button() == Qt.RightButton:
@ -408,4 +521,8 @@ class CustomRect(QGraphicsItem):
:return:
'''
self.color = COLOR_NORMAL
self.setSelected(False)
self.scene().unselect_item(self)
def on_delete(self):
self.node.on_delete()
pass

View File

@ -2,11 +2,12 @@ import sys
from typing import Tuple, List, Dict
import json
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QToolButton, QSpacerItem, QSizePolicy, QGraphicsView, \
QFrame, QApplication, QGraphicsScene, QGraphicsSceneMouseEvent
from PyQt5.QtCore import QSize, QCoreApplication, pyqtSignal, QLineF, QObject
from PyQt5.QtGui import QIcon, QPixmap, QPen, QColor
QFrame, QApplication, QGraphicsScene, QGraphicsSceneMouseEvent, QMenu, QGraphicsSceneContextMenuEvent, \
QGraphicsTextItem
from PyQt5.QtCore import QSize, QCoreApplication, pyqtSignal, QLineF, QObject, QPoint, Qt, QPointF
from PyQt5.QtGui import QIcon, QPixmap, QPen, QColor, QKeyEvent
from pmgwidgets.flowchart.flow_items import CustomRect, CustomPort, CustomLine, CustomCenterPoint
from pmgwidgets.flowchart.flow_items import CustomRect, CustomPort, CustomLine, CustomMidPoint
COLOR_NORMAL = QColor(212, 227, 242)
COLOR_HOVER = QColor(255, 200, 00)
@ -18,16 +19,30 @@ class PMGraphicsScene(QGraphicsScene):
signal_port_clicked = pyqtSignal(str) # 点击端口的事件
signal_clear_selection = pyqtSignal() # 清除选择的事件
def __init__(self, parent=None, graphics_view: 'QGraphicsView' = None):
def __init__(self, parent=None, graphics_view: 'QGraphicsView' = None, flow_widget: 'PMFlowWidget' = None):
super().__init__(parent)
from pmgwidgets.flowchart.flow_items import CustomLine
self.lines: List[CustomLine] = []
self.nodes: List['Node'] = []
self.drawing_lines = False
self.line_start_port = None
self.line_start_point = None
self.line_end_port = None
self.line = self.addLine(0, 0, 100, 100, QPen())
self.line = self.addLine(0, 0, 1, 1, QPen())
self.graphics_view = graphics_view
self.flow_widget: 'PMFlowWidget' = flow_widget
self.menu = QMenu()
self.menu.addAction('New').triggered.connect(lambda x: self.add_node())
self.menu.addAction('Delete Selected').triggered.connect(lambda x: self.delete_selected_item())
self.selected_items = []
def contextMenuEvent(self, event: 'QGraphicsSceneContextMenuEvent') -> None:
super(PMGraphicsScene, self).contextMenuEvent(event)
self.menu.exec_(event.screenPos())
def keyPressEvent(self, event: 'QKeyEvent') -> None:
if event.key() == Qt.Key_Delete:
self.delete_selected_item()
def mouseMoveEvent(self, event: 'QGraphicsSceneMouseEvent') -> None:
super(PMGraphicsScene, self).mouseMoveEvent(event)
@ -46,18 +61,51 @@ class PMGraphicsScene(QGraphicsScene):
self.signal_item_dragged.connect(line.refresh)
self.drawing_lines = False
# def selectionChanged(self) -> None:
# super(PMGraphicsScene, self).selectionChanged()
# print('selection changed !')
def add_node(self):
view = self.graphics_view
n = Node(self, 'node3', input_ports=[CustomPort(10), CustomPort(11)],
output_ports=[CustomPort(12), CustomPort(13), CustomPort(14)])
n.set_pos(200, 50)
print(n.get_pos())
self.nodes.append(n)
def delete_selected_item(self):
'''
删除被选中的物品
:return:
'''
print(self.selectedItems())
for selected_item in self.selected_items:
if hasattr(selected_item, 'on_delete'):
selected_item.on_delete()
self.selected_items = []
print('delete!')
def unselect_item(self, item):
if item in self.selected_items:
self.selected_items.remove(item)
def select_item(self, item):
if item not in self.selected_items:
self.selected_items.append(item)
class Node(QObject):
'''
Node是一个节点的抽象
'''
def __init__(self, canvas: 'PMGraphicsScene', name, input_ports: List[CustomPort] = None,
output_ports: List[CustomPort] = None):
output_ports: List[CustomPort] = None, content: object = None):
super(Node, self).__init__()
self.name = name
self.base_rect = CustomRect(self)
self.text = QGraphicsTextItem(parent=self.base_rect)
self.text.setPos(100, 10)
self.text.setPlainText(self.name)
if input_ports == None:
self.input_ports = [CustomPort(0), CustomPort(1)]
else:
@ -67,13 +115,25 @@ class Node(QObject):
else:
self.output_ports = output_ports
self.canvas = canvas
self.content = None
self.setup()
def set_pos(self, x: int, y: int):
'''
设置位置左上角角点
:param x:
:param y:
:return:
'''
self.base_rect.setPos(x, y)
self.refresh_pos()
def get_pos(self) -> Tuple[int, int]:
'''
获取位置左上角角点
:return:
'''
pos = self.base_rect.pos()
return pos.x(), pos.y()
@ -84,15 +144,16 @@ class Node(QObject):
x_input = self.base_rect.x() + 5
x_output = self.base_rect.x() + self.base_rect.boundingRect().width() - 15
for i, p in enumerate(self.input_ports):
p.setPos(x_input, y + int(dy_input * (1 + i)))
p.setPos(QPointF(x_input, y + int(dy_input * (1 + i))))
for i, p in enumerate(self.output_ports):
p.setPos(x_output, y + int(dy_output * (1 + i)))
p.setPos(QPointF(x_output, y + int(dy_output * (1 + i))))
self.canvas.signal_item_dragged.emit('')
def setup(self):
self.base_rect.setPos(80, 80)
self.canvas.signal_clear_selection.connect(self.base_rect.on_clear_selection)
self.canvas.addItem(self.base_rect)
for p in self.input_ports + self.output_ports:
self.canvas.addItem(p)
p.port_clicked.connect(self.on_port_clicked)
@ -109,6 +170,14 @@ class Node(QObject):
self.canvas.line_start_point = port.center_pos
self.canvas.line_start_port = port
def on_delete(self):
for port in self.input_ports + self.output_ports:
port.canvas = self.canvas
port.on_delete()
self.canvas.removeItem(self.base_rect)
self.canvas.nodes.remove(self)
self.deleteLater()
def __repr__(self):
s = super(Node, self).__repr__()
return s + repr(self.input_ports) + repr(self.output_ports)
@ -274,7 +343,7 @@ class PMFlowWidget(QWidget):
# self.rect2 = CustomRect()
# self.rect2.setPos(100, 100)
#
self.scene = PMGraphicsScene(graphics_view=self.graphicsView)
self.scene = PMGraphicsScene(graphics_view=self.graphicsView, flow_widget=self)
self.scene.setSceneRect(0, 0, 300, 300)
# self.scene.addItem(self.rect)
@ -294,7 +363,7 @@ class PMFlowWidget(QWidget):
self.nodes = [self.n, self.n2]
self.nodes: List[Node] = []
self.nodes: List[Node] = self.scene.nodes
self.lines = self.scene.lines
self.load_flowchart()
@ -342,8 +411,6 @@ class PMFlowWidget(QWidget):
node_properties['input_ports'] = input_ports_dic
node_properties['output_ports'] = output_ports_dic
nodes_dic[node.name] = node_properties
# print(nodes_dic)
with open(r'c:\users\12957\desktop\123.txt', 'w') as f:
json.dump(fc_info, f, indent=4)
pass
@ -380,7 +447,7 @@ class PMFlowWidget(QWidget):
mid_positions = line_property['mid_positions']
mid_points = []
for pos in mid_positions:
mid_points.append(CustomCenterPoint(pos=pos))
mid_points.append(CustomMidPoint(pos=QPointF(*pos)))
print(start_port, end_port, start_id, end_id)
line = CustomLine(canvas=self.scene, start_port=start_port, end_port=end_port, mid_points=mid_points)

View File

@ -0,0 +1,7 @@
from PyQt5.QtCore import QPointF
def round_position(point: QPointF, pixels=5):
x, y = point.x(), point.y()
x_cor, y_cor = round(x * 1.0 / pixels) * pixels, round(y * 1.0 / pixels) * pixels
return QPointF(x_cor, y_cor)

View File

@ -12,6 +12,9 @@ class Extension(BaseExtension):
app_toolbar_interface = self.extension_lib.get_interface('applications_toolbar')
import os
path = os.path.dirname(__file__)
app_toolbar_interface.add_app( group='应用测试', text='测试插件',
icon_path=':/pyqt/source/images/lc_searchdialog.png', callback=lambda :print('this is app'), hint='')
app_toolbar_interface.add_process_action('应用测试', '测试对话框',
':/pyqt/source/images/lc_searchdialog.png',
['python','-u',os.path.join(path,'run_dialog.py')])