chartflow 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
菜单栏模块 - 提供文件、编辑、视图等菜单
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from chartflow.node.layout_utils import ViewportFitter
|
|
6
|
+
from chartflow.node.layout_utils import DenseLayoutEngine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import Optional, Callable
|
|
10
|
+
|
|
11
|
+
from PyQt6.QtWidgets import QMenuBar, QMenu, QFileDialog, QMessageBox
|
|
12
|
+
from PyQt6.QtCore import pyqtSignal, QObject
|
|
13
|
+
from PyQt6.QtGui import QKeySequence, QAction
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MenuBar(QMenuBar):
|
|
17
|
+
"""
|
|
18
|
+
菜单栏 - 提供文件、编辑、视图等菜单
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# 信号
|
|
22
|
+
newGraphRequested = pyqtSignal()
|
|
23
|
+
openGraphRequested = pyqtSignal(str)
|
|
24
|
+
saveGraphRequested = pyqtSignal()
|
|
25
|
+
saveAsGraphRequested = pyqtSignal(str)
|
|
26
|
+
exportGraphRequested = pyqtSignal()
|
|
27
|
+
importGraphRequested = pyqtSignal(str)
|
|
28
|
+
exitRequested = pyqtSignal()
|
|
29
|
+
|
|
30
|
+
def __init__(self, parent: Optional['QWidget'] = None) -> None:
|
|
31
|
+
super().__init__(parent)
|
|
32
|
+
|
|
33
|
+
self._createFileMenu()
|
|
34
|
+
self._createEditMenu()
|
|
35
|
+
self._createViewMenu()
|
|
36
|
+
self._createLayoutMenu()
|
|
37
|
+
|
|
38
|
+
def _createFileMenu(self) -> None:
|
|
39
|
+
"""创建文件菜单"""
|
|
40
|
+
file_menu = self.addMenu("File")
|
|
41
|
+
|
|
42
|
+
# New
|
|
43
|
+
new_action = QAction("New", self)
|
|
44
|
+
new_action.setShortcut(QKeySequence.StandardKey.New)
|
|
45
|
+
new_action.triggered.connect(self.newGraphRequested.emit)
|
|
46
|
+
file_menu.addAction(new_action)
|
|
47
|
+
|
|
48
|
+
# Open
|
|
49
|
+
open_action = QAction("Open...", self)
|
|
50
|
+
open_action.setShortcut(QKeySequence.StandardKey.Open)
|
|
51
|
+
open_action.triggered.connect(self._onOpen)
|
|
52
|
+
file_menu.addAction(open_action)
|
|
53
|
+
|
|
54
|
+
file_menu.addSeparator()
|
|
55
|
+
|
|
56
|
+
# Save
|
|
57
|
+
save_action = QAction("Save", self)
|
|
58
|
+
save_action.setShortcut(QKeySequence.StandardKey.Save)
|
|
59
|
+
save_action.triggered.connect(self.saveGraphRequested.emit)
|
|
60
|
+
file_menu.addAction(save_action)
|
|
61
|
+
|
|
62
|
+
# Save As
|
|
63
|
+
save_as_action = QAction("Save As...", self)
|
|
64
|
+
save_as_action.setShortcut(QKeySequence.StandardKey.SaveAs)
|
|
65
|
+
save_as_action.triggered.connect(self._onSaveAs)
|
|
66
|
+
file_menu.addAction(save_as_action)
|
|
67
|
+
|
|
68
|
+
file_menu.addSeparator()
|
|
69
|
+
|
|
70
|
+
# Export
|
|
71
|
+
export_action = QAction("Export...", self)
|
|
72
|
+
export_action.triggered.connect(self._onExport)
|
|
73
|
+
file_menu.addAction(export_action)
|
|
74
|
+
|
|
75
|
+
# Import
|
|
76
|
+
import_action = QAction("Import...", self)
|
|
77
|
+
import_action.triggered.connect(self._onImport)
|
|
78
|
+
file_menu.addAction(import_action)
|
|
79
|
+
|
|
80
|
+
file_menu.addSeparator()
|
|
81
|
+
|
|
82
|
+
# Exit
|
|
83
|
+
exit_action = QAction("Exit", self)
|
|
84
|
+
exit_action.setShortcut(QKeySequence.StandardKey.Quit)
|
|
85
|
+
exit_action.triggered.connect(self.exitRequested.emit)
|
|
86
|
+
file_menu.addAction(exit_action)
|
|
87
|
+
|
|
88
|
+
def _createEditMenu(self) -> None:
|
|
89
|
+
"""创建编辑菜单"""
|
|
90
|
+
edit_menu = self.addMenu("Edit")
|
|
91
|
+
|
|
92
|
+
# Delete
|
|
93
|
+
delete_action = QAction("Delete", self)
|
|
94
|
+
delete_action.setShortcut(QKeySequence.StandardKey.Delete)
|
|
95
|
+
delete_action.triggered.connect(self._onDelete)
|
|
96
|
+
edit_menu.addAction(delete_action)
|
|
97
|
+
|
|
98
|
+
# Select All
|
|
99
|
+
select_all_action = QAction("Select All", self)
|
|
100
|
+
select_all_action.setShortcut(QKeySequence.StandardKey.SelectAll)
|
|
101
|
+
select_all_action.triggered.connect(self._onSelectAll)
|
|
102
|
+
edit_menu.addAction(select_all_action)
|
|
103
|
+
|
|
104
|
+
def _createViewMenu(self) -> None:
|
|
105
|
+
"""创建视图菜单"""
|
|
106
|
+
view_menu = self.addMenu("View")
|
|
107
|
+
|
|
108
|
+
# Zoom In
|
|
109
|
+
zoom_in_action = QAction("Zoom In", self)
|
|
110
|
+
zoom_in_action.setShortcut(QKeySequence.StandardKey.ZoomIn)
|
|
111
|
+
zoom_in_action.triggered.connect(self._onZoomIn)
|
|
112
|
+
view_menu.addAction(zoom_in_action)
|
|
113
|
+
|
|
114
|
+
# Zoom Out
|
|
115
|
+
zoom_out_action = QAction("Zoom Out", self)
|
|
116
|
+
zoom_out_action.setShortcut(QKeySequence.StandardKey.ZoomOut)
|
|
117
|
+
zoom_out_action.triggered.connect(self._onZoomOut)
|
|
118
|
+
view_menu.addAction(zoom_out_action)
|
|
119
|
+
|
|
120
|
+
# Fit to Viewport
|
|
121
|
+
fit_viewport_action = QAction("Fit to Viewport", self)
|
|
122
|
+
fit_viewport_action.triggered.connect(self._onFitViewport)
|
|
123
|
+
view_menu.addAction(fit_viewport_action)
|
|
124
|
+
|
|
125
|
+
# Reset Zoom
|
|
126
|
+
reset_zoom_action = QAction("Reset Zoom", self)
|
|
127
|
+
reset_zoom_action.triggered.connect(self._onResetZoom)
|
|
128
|
+
view_menu.addAction(reset_zoom_action)
|
|
129
|
+
|
|
130
|
+
def _createLayoutMenu(self) -> None:
|
|
131
|
+
"""创建布局菜单"""
|
|
132
|
+
layout_menu = self.addMenu("Layout")
|
|
133
|
+
|
|
134
|
+
# Compact
|
|
135
|
+
compact_action = QAction("Compact", self)
|
|
136
|
+
compact_action.triggered.connect(lambda: self._onLayout("compact"))
|
|
137
|
+
layout_menu.addAction(compact_action)
|
|
138
|
+
|
|
139
|
+
# Circular
|
|
140
|
+
circular_action = QAction("Circular", self)
|
|
141
|
+
circular_action.triggered.connect(lambda: self._onLayout("circular"))
|
|
142
|
+
layout_menu.addAction(circular_action)
|
|
143
|
+
|
|
144
|
+
# Grid
|
|
145
|
+
grid_action = QAction("Grid", self)
|
|
146
|
+
grid_action.triggered.connect(lambda: self._onLayout("grid"))
|
|
147
|
+
layout_menu.addAction(grid_action)
|
|
148
|
+
|
|
149
|
+
# Hierarchical
|
|
150
|
+
hierarchical_action = QAction("Hierarchical", self)
|
|
151
|
+
hierarchical_action.triggered.connect(lambda: self._onLayout("hierarchical"))
|
|
152
|
+
layout_menu.addAction(hierarchical_action)
|
|
153
|
+
|
|
154
|
+
# Force Directed
|
|
155
|
+
force_action = QAction("Force Directed", self)
|
|
156
|
+
force_action.triggered.connect(lambda: self._onLayout("force"))
|
|
157
|
+
layout_menu.addAction(force_action)
|
|
158
|
+
|
|
159
|
+
def _onOpen(self) -> None:
|
|
160
|
+
"""打开文件"""
|
|
161
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
162
|
+
self, "Open Graph", "", "JSON Files (*.json);;All Files (*)"
|
|
163
|
+
)
|
|
164
|
+
if file_path:
|
|
165
|
+
self.openGraphRequested.emit(file_path)
|
|
166
|
+
|
|
167
|
+
def _onSaveAs(self) -> None:
|
|
168
|
+
"""另存为"""
|
|
169
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
170
|
+
self, "Save Graph As", "", "JSON Files (*.json);;All Files (*)"
|
|
171
|
+
)
|
|
172
|
+
if file_path:
|
|
173
|
+
self.saveAsGraphRequested.emit(file_path)
|
|
174
|
+
|
|
175
|
+
def _onExport(self) -> None:
|
|
176
|
+
"""导出"""
|
|
177
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
178
|
+
self, "Export Graph", "", "JSON Files (*.json);;All Files (*)"
|
|
179
|
+
)
|
|
180
|
+
if file_path:
|
|
181
|
+
self.exportGraphRequested.emit()
|
|
182
|
+
|
|
183
|
+
def _onImport(self) -> None:
|
|
184
|
+
"""导入"""
|
|
185
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
186
|
+
self, "Import Graph", "", "JSON Files (*.json);;All Files (*)"
|
|
187
|
+
)
|
|
188
|
+
if file_path:
|
|
189
|
+
self.importGraphRequested.emit(file_path)
|
|
190
|
+
|
|
191
|
+
def _onDelete(self) -> None:
|
|
192
|
+
"""删除选中"""
|
|
193
|
+
# 通过父窗口获取画布并删除选中项
|
|
194
|
+
parent = self.parent()
|
|
195
|
+
if parent and hasattr(parent, 'canvas'):
|
|
196
|
+
canvas = parent.canvas
|
|
197
|
+
# 删除选中的节点
|
|
198
|
+
for node in canvas.nodes[:]:
|
|
199
|
+
if node.isSelected():
|
|
200
|
+
canvas.removeNode(node)
|
|
201
|
+
|
|
202
|
+
def _onSelectAll(self) -> None:
|
|
203
|
+
"""全选"""
|
|
204
|
+
parent = self.parent()
|
|
205
|
+
if parent and hasattr(parent, 'canvas'):
|
|
206
|
+
canvas = parent.canvas
|
|
207
|
+
for node in canvas.nodes:
|
|
208
|
+
node.setSelected(True)
|
|
209
|
+
|
|
210
|
+
def _onZoomIn(self) -> None:
|
|
211
|
+
"""放大"""
|
|
212
|
+
parent = self.parent()
|
|
213
|
+
if parent and hasattr(parent, 'canvas'):
|
|
214
|
+
parent.canvas.scale(1.1, 1.1)
|
|
215
|
+
|
|
216
|
+
def _onZoomOut(self) -> None:
|
|
217
|
+
"""缩小"""
|
|
218
|
+
parent = self.parent()
|
|
219
|
+
if parent and hasattr(parent, 'canvas'):
|
|
220
|
+
parent.canvas.scale(0.9, 0.9)
|
|
221
|
+
|
|
222
|
+
def _onFitViewport(self) -> None:
|
|
223
|
+
"""适应视口"""
|
|
224
|
+
parent = self.parent()
|
|
225
|
+
if parent and hasattr(parent, 'canvas'):
|
|
226
|
+
fitter = ViewportFitter(parent.canvas)
|
|
227
|
+
fitter.fitViewport()
|
|
228
|
+
|
|
229
|
+
def _onResetZoom(self) -> None:
|
|
230
|
+
"""重置缩放"""
|
|
231
|
+
parent = self.parent()
|
|
232
|
+
if parent and hasattr(parent, 'canvas'):
|
|
233
|
+
parent.canvas.resetTransform()
|
|
234
|
+
|
|
235
|
+
def _onLayout(self, strategy: str) -> None:
|
|
236
|
+
"""执行布局"""
|
|
237
|
+
parent = self.parent()
|
|
238
|
+
if parent and hasattr(parent, 'canvas'):
|
|
239
|
+
engine = DenseLayoutEngine(parent.canvas)
|
|
240
|
+
engine.autoLayout(strategy)
|