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.
Files changed (79) hide show
  1. chartflow/__init__.py +28 -0
  2. chartflow/_verify_importexport.py +169 -0
  3. chartflow/edit/__init__.py +119 -0
  4. chartflow/edit/code_editor.py +1007 -0
  5. chartflow/edit/code_folder.py +523 -0
  6. chartflow/edit/completer.py +2652 -0
  7. chartflow/edit/context_analyzer.py +521 -0
  8. chartflow/edit/demo.py +469 -0
  9. chartflow/edit/line_number_area.py +548 -0
  10. chartflow/edit/minimap.py +581 -0
  11. chartflow/edit/python_editor.py +509 -0
  12. chartflow/edit/syntax_highlighter.py +291 -0
  13. chartflow/edit/test_editor.py +190 -0
  14. chartflow/flow/__init__.py +31 -0
  15. chartflow/flow/_demo.py +98 -0
  16. chartflow/flow/chart.py +1101 -0
  17. chartflow/flow/editor.py +375 -0
  18. chartflow/flow/flow.py +6 -0
  19. chartflow/flow/qchart.py +1250 -0
  20. chartflow/flow/test/__init__.py +0 -0
  21. chartflow/flow/test/test_chart.py +537 -0
  22. chartflow/flow/test_decorator_sync.py +102 -0
  23. chartflow/fsm/__init__.py +84 -0
  24. chartflow/fsm/decorators.py +395 -0
  25. chartflow/fsm/demo.py +381 -0
  26. chartflow/fsm/demo_pyqt6.py +881 -0
  27. chartflow/fsm/demo_tree.py +46 -0
  28. chartflow/fsm/logic.py +447 -0
  29. chartflow/fsm/meta.py +569 -0
  30. chartflow/fsm/scheduler.py +427 -0
  31. chartflow/fsm/stage.py +2079 -0
  32. chartflow/fsm/stdio.py +66 -0
  33. chartflow/fsm/test/__init__.py +7 -0
  34. chartflow/fsm/test/test_decorators.py +210 -0
  35. chartflow/fsm/test/test_events.py +202 -0
  36. chartflow/fsm/test/test_integration.py +596 -0
  37. chartflow/fsm/test/test_logic.py +371 -0
  38. chartflow/fsm/test/test_meta.py +192 -0
  39. chartflow/fsm/test/test_runtime_param.py +336 -0
  40. chartflow/fsm/test/test_scheduler.py +280 -0
  41. chartflow/fsm/test/test_stage.py +314 -0
  42. chartflow/fsm/test_behavior_events.py +263 -0
  43. chartflow/fsm/test_nested_parallel.py +80 -0
  44. chartflow/fsm/test_stage_spec.py +448 -0
  45. chartflow/fsm/utils.py +34 -0
  46. chartflow/node/__init__.py +66 -0
  47. chartflow/node/_node_base.py +727 -0
  48. chartflow/node/_node_interaction.py +488 -0
  49. chartflow/node/_node_interface.py +426 -0
  50. chartflow/node/_node_markers.py +648 -0
  51. chartflow/node/_node_ports.py +536 -0
  52. chartflow/node/_port_interface.py +111 -0
  53. chartflow/node/arrow_item.py +287 -0
  54. chartflow/node/color_utils.py +113 -0
  55. chartflow/node/connection_item.py +939 -0
  56. chartflow/node/demo.py +258 -0
  57. chartflow/node/demo_arrow_drag.py +46 -0
  58. chartflow/node/demo_decorator.py +63 -0
  59. chartflow/node/demo_ports.py +69 -0
  60. chartflow/node/enums.py +35 -0
  61. chartflow/node/example.py +84 -0
  62. chartflow/node/layout_utils.py +307 -0
  63. chartflow/node/main_window.py +196 -0
  64. chartflow/node/menu_bar.py +240 -0
  65. chartflow/node/node_canvas.py +1730 -0
  66. chartflow/node/node_item.py +754 -0
  67. chartflow/node/popmenu.py +472 -0
  68. chartflow/node/port_item.py +591 -0
  69. chartflow/node/qnode_editor.py +73 -0
  70. chartflow/node/selection_rect.py +53 -0
  71. chartflow/node/style_panel.py +615 -0
  72. chartflow/node/styles.py +63 -0
  73. chartflow/node/test_qnode.py +2336 -0
  74. chartflow/showcase.py +528 -0
  75. chartflow/theme.py +508 -0
  76. chartflow-0.1.dist-info/METADATA +23 -0
  77. chartflow-0.1.dist-info/RECORD +79 -0
  78. chartflow-0.1.dist-info/WHEEL +5 -0
  79. chartflow-0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,291 @@
1
+ """
2
+ Python语法高亮器
3
+ 基于QSyntaxHighlighter实现
4
+ """
5
+
6
+ from PyQt6.QtCore import QRegularExpression
7
+ from PyQt6.QtGui import (
8
+ QSyntaxHighlighter, QTextCharFormat, QColor,
9
+ QFont, QTextDocument
10
+ )
11
+ from typing import List, Tuple, Pattern
12
+ import re
13
+
14
+
15
+ class PythonSyntaxHighlighter(QSyntaxHighlighter):
16
+ """Python语法高亮器 - 浅色主题"""
17
+
18
+ # 浅色语法配色
19
+ COLORS = {
20
+ 'keyword': '#0000FF', # 蓝色 - 关键字
21
+ 'string': '#008000', # 绿色 - 字符串
22
+ 'comment': '#808080', # 灰色 - 注释
23
+ 'function': '#795E26', # 棕色 - 函数名
24
+ 'class': '#267F99', # 青色 - 类名
25
+ 'number': '#098658', # 深绿色 - 数字
26
+ 'operator': '#000000', # 黑色 - 运算符
27
+ 'builtin': '#0000FF', # 蓝色 - 内置函数
28
+ 'decorator': '#795E26', # 棕色 - 装饰器
29
+ 'self': '#0000FF', # 蓝色 - self
30
+ 'f_string_expr': '#001080', # 深蓝色 - f-string表达式
31
+ }
32
+
33
+ # Python关键字
34
+ KEYWORDS = [
35
+ 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue',
36
+ 'def', 'del', 'elif', 'else', 'except', 'False', 'finally', 'for',
37
+ 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'None',
38
+ 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'True', 'try',
39
+ 'while', 'with', 'yield'
40
+ ]
41
+
42
+ # Python内置函数
43
+ BUILTINS = [
44
+ 'abs', 'all', 'any', 'bin', 'bool', 'bytearray', 'bytes', 'callable',
45
+ 'chr', 'classmethod', 'compile', 'complex', 'delattr', 'dict', 'dir',
46
+ 'divmod', 'enumerate', 'eval', 'exec', 'filter', 'float', 'format',
47
+ 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex',
48
+ 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len',
49
+ 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object',
50
+ 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr',
51
+ 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod',
52
+ 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip', '__import__'
53
+ ]
54
+
55
+ def __init__(self, parent: QTextDocument = None):
56
+ super().__init__(parent)
57
+ self._setup_formats()
58
+ self._setup_rules()
59
+
60
+ def _setup_formats(self):
61
+ """设置文本格式"""
62
+ # 关键字格式
63
+ self.keyword_format = QTextCharFormat()
64
+ self.keyword_format.setForeground(QColor(self.COLORS['keyword']))
65
+ self.keyword_format.setFontWeight(QFont.Weight.Bold)
66
+
67
+ # 字符串格式
68
+ self.string_format = QTextCharFormat()
69
+ self.string_format.setForeground(QColor(self.COLORS['string']))
70
+
71
+ # 多行字符串格式
72
+ self.multiline_string_format = QTextCharFormat()
73
+ self.multiline_string_format.setForeground(QColor(self.COLORS['string']))
74
+
75
+ # 注释格式
76
+ self.comment_format = QTextCharFormat()
77
+ self.comment_format.setForeground(QColor(self.COLORS['comment']))
78
+ self.comment_format.setFontItalic(True)
79
+
80
+ # 函数定义格式
81
+ self.function_format = QTextCharFormat()
82
+ self.function_format.setForeground(QColor(self.COLORS['function']))
83
+
84
+ # 类定义格式
85
+ self.class_format = QTextCharFormat()
86
+ self.class_format.setForeground(QColor(self.COLORS['class']))
87
+
88
+ # 数字格式
89
+ self.number_format = QTextCharFormat()
90
+ self.number_format.setForeground(QColor(self.COLORS['number']))
91
+
92
+ # 运算符格式
93
+ self.operator_format = QTextCharFormat()
94
+ self.operator_format.setForeground(QColor(self.COLORS['operator']))
95
+
96
+ # 内置函数格式
97
+ self.builtin_format = QTextCharFormat()
98
+ self.builtin_format.setForeground(QColor(self.COLORS['builtin']))
99
+
100
+ # 装饰器格式
101
+ self.decorator_format = QTextCharFormat()
102
+ self.decorator_format.setForeground(QColor(self.COLORS['decorator']))
103
+
104
+ # self格式
105
+ self.self_format = QTextCharFormat()
106
+ self.self_format.setForeground(QColor(self.COLORS['self']))
107
+ self.self_format.setFontWeight(QFont.Weight.Bold)
108
+
109
+ # f-string表达式格式
110
+ self.f_string_expr_format = QTextCharFormat()
111
+ self.f_string_expr_format.setForeground(QColor(self.COLORS['f_string_expr']))
112
+ self.f_string_expr_format.setFontWeight(QFont.Weight.Bold)
113
+
114
+ def _setup_rules(self):
115
+ """设置高亮规则"""
116
+ self.rules: List[Tuple[QRegularExpression, QTextCharFormat]] = []
117
+
118
+ # 关键字规则
119
+ keyword_pattern = r'\b(' + '|'.join(self.KEYWORDS) + r')\b'
120
+ self.rules.append((QRegularExpression(keyword_pattern), self.keyword_format))
121
+
122
+ # self规则
123
+ self.rules.append((QRegularExpression(r'\bself\b'), self.self_format))
124
+
125
+ # 内置函数规则
126
+ builtin_pattern = r'\b(' + '|'.join(self.BUILTINS) + r')(?=\s*\()'
127
+ self.rules.append((QRegularExpression(builtin_pattern), self.builtin_format))
128
+
129
+ # 函数定义规则
130
+ self.rules.append((
131
+ QRegularExpression(r'\bdef\s+(\w+)'),
132
+ self.function_format
133
+ ))
134
+
135
+ # 类定义规则
136
+ self.rules.append((
137
+ QRegularExpression(r'\bclass\s+(\w+)'),
138
+ self.class_format
139
+ ))
140
+
141
+ # 装饰器规则
142
+ self.rules.append((
143
+ QRegularExpression(r'@[\w.]+'),
144
+ self.decorator_format
145
+ ))
146
+
147
+ # 数字规则
148
+ number_pattern = r'\b\d+\.?\d*([eE][+-]?\d+)?\b|\b0[xX][0-9a-fA-F]+\b|\b0[oO]?[0-7]+\b|\b0[bB][01]+\b'
149
+ self.rules.append((QRegularExpression(number_pattern), self.number_format))
150
+
151
+ # 双引号字符串规则
152
+ self.rules.append((
153
+ QRegularExpression(r'"[^"\\]*(\\.[^"\\]*)*"'),
154
+ self.string_format
155
+ ))
156
+
157
+ # 单引号字符串规则
158
+ self.rules.append((
159
+ QRegularExpression(r"'[^'\\]*(\\.[^'\\]*)*'"),
160
+ self.string_format
161
+ ))
162
+
163
+ # f-string规则 (简化处理)
164
+ self.rules.append((
165
+ QRegularExpression(r'f"[^"\\]*(\\.[^"\\]*)*"'),
166
+ self.string_format
167
+ ))
168
+ self.rules.append((
169
+ QRegularExpression(r"f'[^'\\]*(\\.[^'\\]*)*'"),
170
+ self.string_format
171
+ ))
172
+
173
+ # 单行注释规则
174
+ self.rules.append((
175
+ QRegularExpression(r'#[^\n]*'),
176
+ self.comment_format
177
+ ))
178
+
179
+ def highlightBlock(self, text: str):
180
+ """高亮文本块"""
181
+ # 应用基本规则
182
+ for pattern, fmt in self.rules:
183
+ match_iterator = pattern.globalMatch(text)
184
+ while match_iterator.hasNext():
185
+ match = match_iterator.next()
186
+ # 对于函数和类定义,只高亮名称部分
187
+ if pattern.pattern().startswith(r'\bdef\s+') or \
188
+ pattern.pattern().startswith(r'\bclass\s+'):
189
+ # 获取捕获组(函数/类名)
190
+ if match.lastCapturedIndex() >= 1:
191
+ start = match.capturedStart(1)
192
+ length = match.capturedLength(1)
193
+ else:
194
+ start = match.capturedStart()
195
+ length = match.capturedLength()
196
+ else:
197
+ start = match.capturedStart()
198
+ length = match.capturedLength()
199
+
200
+ self.setFormat(start, length, fmt)
201
+
202
+ # 处理多行字符串
203
+ self._highlight_multiline_strings(text)
204
+
205
+ # 设置当前块状态用于多行字符串跟踪
206
+ self.setCurrentBlockState(0)
207
+
208
+ def _highlight_multiline_strings(self, text: str):
209
+ """高亮多行字符串"""
210
+ # 三引号字符串检测
211
+ triple_double = '"""'
212
+ triple_single = "'''"
213
+
214
+ # 检查是否在多行字符串中
215
+ in_multiline = self.previousBlockState() == 1
216
+
217
+ i = 0
218
+ while i < len(text):
219
+ if not in_multiline:
220
+ # 查找三引号开始
221
+ dd_pos = text.find(triple_double, i)
222
+ ds_pos = text.find(triple_single, i)
223
+
224
+ start_pos = -1
225
+ is_double = True
226
+
227
+ if dd_pos != -1 and (ds_pos == -1 or dd_pos < ds_pos):
228
+ start_pos = dd_pos
229
+ is_double = True
230
+ elif ds_pos != -1:
231
+ start_pos = ds_pos
232
+ is_double = False
233
+
234
+ if start_pos != -1:
235
+ # 查找结束
236
+ end_marker = triple_double if is_double else triple_single
237
+ end_pos = text.find(end_marker, start_pos + 3)
238
+
239
+ if end_pos != -1:
240
+ # 单行多行字符串
241
+ length = end_pos - start_pos + 3
242
+ self.setFormat(start_pos, length, self.multiline_string_format)
243
+ i = end_pos + 3
244
+ else:
245
+ # 多行开始
246
+ length = len(text) - start_pos
247
+ self.setFormat(start_pos, length, self.multiline_string_format)
248
+ in_multiline = True
249
+ self.setCurrentBlockState(1)
250
+ break
251
+ else:
252
+ break
253
+ else:
254
+ # 在多行字符串中,查找结束
255
+ end_pos_dd = text.find(triple_double, i)
256
+ end_pos_ds = text.find(triple_single, i)
257
+
258
+ # 需要知道是哪种三引号
259
+ # 简化处理:检查哪个先出现
260
+ end_pos = -1
261
+ if end_pos_dd != -1 and end_pos_ds != -1:
262
+ end_pos = min(end_pos_dd, end_pos_ds)
263
+ elif end_pos_dd != -1:
264
+ end_pos = end_pos_dd
265
+ elif end_pos_ds != -1:
266
+ end_pos = end_pos_ds
267
+
268
+ if end_pos != -1:
269
+ # 多行字符串结束
270
+ length = end_pos + 3
271
+ self.setFormat(0, length, self.multiline_string_format)
272
+ in_multiline = False
273
+ self.setCurrentBlockState(0)
274
+ i = end_pos + 3
275
+ else:
276
+ # 仍在多行字符串中
277
+ self.setFormat(0, len(text), self.multiline_string_format)
278
+ self.setCurrentBlockState(1)
279
+ break
280
+
281
+
282
+ class HighlightRule:
283
+ """高亮规则辅助类"""
284
+ def __init__(self, pattern: str, color: str, bold: bool = False, italic: bool = False):
285
+ self.pattern = QRegularExpression(pattern)
286
+ self.format = QTextCharFormat()
287
+ self.format.setForeground(QColor(color))
288
+ if bold:
289
+ self.format.setFontWeight(QFont.Weight.Bold)
290
+ if italic:
291
+ self.format.setFontItalic(True)
@@ -0,0 +1,190 @@
1
+ """
2
+ PyEdit测试脚本
3
+ """
4
+
5
+ from chartflow.edit import PythonEditor, PythonEditorFactory
6
+ from chartflow.edit import CodeEditor
7
+ from chartflow.edit import PythonSyntaxHighlighter
8
+ from chartflow.edit import CodeCompleter, SmartCompleter, CompletionItem
9
+ from chartflow.edit import ContextAnalyzer, CodeContext
10
+ from chartflow.edit import CodeFolder, FoldRegion
11
+ from chartflow.edit import LineNumberArea
12
+ from chartflow.edit import MinimapPanel, MinimapWidget
13
+
14
+
15
+ import sys
16
+ from PyQt6.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
17
+ from PyQt6.QtCore import Qt
18
+
19
+ # 测试导入
20
+ try:
21
+ print("✓ PythonEditor 导入成功")
22
+ except ImportError as e:
23
+ print(f"✗ PythonEditor 导入失败: {e}")
24
+ sys.exit(1)
25
+
26
+ try:
27
+ print("✓ CodeEditor 导入成功")
28
+ except ImportError as e:
29
+ print(f"✗ CodeEditor 导入失败: {e}")
30
+
31
+ try:
32
+ print("✓ PythonSyntaxHighlighter 导入成功")
33
+ except ImportError as e:
34
+ print(f"✗ PythonSyntaxHighlighter 导入失败: {e}")
35
+
36
+ try:
37
+ print("✓ Completer 导入成功")
38
+ except ImportError as e:
39
+ print(f"✗ Completer 导入失败: {e}")
40
+
41
+ try:
42
+ print("✓ ContextAnalyzer 导入成功")
43
+ except ImportError as e:
44
+ print(f"✗ ContextAnalyzer 导入失败: {e}")
45
+
46
+ try:
47
+ print("✓ CodeFolder 导入成功")
48
+ except ImportError as e:
49
+ print(f"✗ CodeFolder 导入失败: {e}")
50
+
51
+ try:
52
+ print("✓ LineNumberArea 导入成功")
53
+ except ImportError as e:
54
+ print(f"✗ LineNumberArea 导入失败: {e}")
55
+
56
+ try:
57
+ print("✓ Minimap 导入成功")
58
+ except ImportError as e:
59
+ print(f"✗ Minimap 导入失败: {e}")
60
+
61
+
62
+ class TestWindow(QWidget):
63
+ """测试窗口"""
64
+
65
+ def __init__(self):
66
+ super().__init__()
67
+
68
+ self.setWindowTitle("PyEdit 功能测试")
69
+ self.setMinimumSize(1000, 700)
70
+
71
+ layout = QVBoxLayout(self)
72
+
73
+ # 说明标签
74
+ info = QLabel("PyEdit 功能测试 - 测试上下文代码功能")
75
+ info.setStyleSheet("font-size: 14px; font-weight: bold; padding: 10px;")
76
+ layout.addWidget(info)
77
+
78
+ # 创建编辑器
79
+ self.editor = PythonEditor()
80
+
81
+ # 设置上下文代码(模拟节点编辑器的场景)
82
+ code_a = '''class DialogueNode(BaseNode):
83
+ """对话节点"""
84
+
85
+ NODE_TYPE = "dialogue"
86
+ NAME = "Dialogue"
87
+
88
+ speaker = ""
89
+ text = ""
90
+ choices = []
91
+
92
+ def __init__(self):
93
+ super().__init__()
94
+ self.connections = []
95
+ self.variables = {}
96
+
97
+ def on_enter(self):
98
+ """进入节点"""
99
+ print(f"Entering: {self.text}")
100
+
101
+ def on_exit(self):
102
+ """离开节点"""
103
+ print(f"Exiting: {self.text}")
104
+ '''
105
+
106
+ code_b = '''def handle_connection(self, context: GameContext, player: Player, *args):
107
+ """处理连接"""
108
+ result = None
109
+ selected_choice = None
110
+ '''
111
+
112
+ prefix = " " # 8空格缩进
113
+
114
+ self.editor.set_context_code(code_a, code_b, prefix)
115
+
116
+ # 设置初始代码
117
+ initial_code = '''# 在这里编辑代码
118
+ # 可用的变量: context, player, result, selected_choice
119
+ # 可用的self属性: speaker, text, choices, connections, variables
120
+
121
+ # 示例:显示对话选项
122
+ for i, choice in enumerate(self.choices):
123
+ print(f"{i+1}. {choice}")
124
+
125
+ # 获取玩家选择
126
+ selected_choice = player.get_input()
127
+
128
+ # 根据选择设置结果
129
+ if selected_choice:
130
+ result = f"choice_{selected_choice}"
131
+ else:
132
+ result = "default"
133
+
134
+ return result
135
+ '''
136
+
137
+ self.editor.set_code(initial_code)
138
+ layout.addWidget(self.editor)
139
+
140
+ # 测试按钮
141
+ btn_layout = QVBoxLayout()
142
+
143
+ btn_get_code = QPushButton("获取代码")
144
+ btn_get_code.clicked.connect(self._get_code)
145
+ btn_layout.addWidget(btn_get_code)
146
+
147
+ btn_fold = QPushButton("折叠全部 (Ctrl+Shift+-)")
148
+ btn_fold.clicked.connect(self.editor.fold_all)
149
+ btn_layout.addWidget(btn_fold)
150
+
151
+ btn_unfold = QPushButton("展开全部 (Ctrl+Shift++)")
152
+ btn_unfold.clicked.connect(self.editor.unfold_all)
153
+ btn_layout.addWidget(btn_unfold)
154
+
155
+ layout.addLayout(btn_layout)
156
+
157
+ # 结果显示
158
+ self.result_label = QLabel("结果将显示在这里")
159
+ self.result_label.setStyleSheet("""
160
+ QLabel {
161
+ background-color: #f0f0f0;
162
+ border: 1px solid #cccccc;
163
+ padding: 10px;
164
+ font-family: Consolas, monospace;
165
+ }
166
+ """)
167
+ layout.addWidget(self.result_label)
168
+
169
+ def _get_code(self):
170
+ """获取代码"""
171
+ code = self.editor.get_code()
172
+ # 只显示前200个字符
173
+ display = code[:200] + "..." if len(code) > 200 else code
174
+ self.result_label.setText(f"代码:\n{display}")
175
+
176
+
177
+ def main():
178
+ """主函数"""
179
+ app = QApplication(sys.argv)
180
+
181
+ print("\n启动测试窗口...")
182
+
183
+ window = TestWindow()
184
+ window.show()
185
+
186
+ sys.exit(app.exec())
187
+
188
+
189
+ if __name__ == '__main__':
190
+ main()
@@ -0,0 +1,31 @@
1
+ """
2
+ flow - Flow chart module for chartflow
3
+
4
+ Provides IChart interface and FlowChart implementation for converting
5
+ between node JSON format and fsm Stage/Logic instances.
6
+ """
7
+
8
+ from chartflow.flow.chart import IChart, Chart, ChartNode, ChartEdge, ChartLogic
9
+ from chartflow.flow.qchart import QFlowChart
10
+
11
+ # Editor components
12
+ from chartflow.flow.editor import (
13
+ StageEditor,
14
+ StageEditorDialog,
15
+ StageEditorFactory,
16
+ edit_state,
17
+ )
18
+
19
+ __all__ = [
20
+ "IChart",
21
+ "Chart",
22
+ "ChartNode",
23
+ "ChartEdge",
24
+ "ChartLogic",
25
+ "QFlowChart",
26
+ # Editor exports
27
+ "StageEditor",
28
+ "StageEditorDialog",
29
+ "StageEditorFactory",
30
+ "edit_state",
31
+ ]
@@ -0,0 +1,98 @@
1
+ from __future__ import annotations
2
+ """qflow._demo - Minimal demo for QFlowChart"""
3
+
4
+
5
+ import random
6
+ import time
7
+ import sys
8
+
9
+ from PyQt6.QtWidgets import QApplication, QMainWindow
10
+ from PyQt6.QtCore import Qt
11
+
12
+ from chartflow.flow.qchart import QFlowChart
13
+ from chartflow.flow.chart import ChartNode, ChartEdge
14
+ from chartflow.fsm import *
15
+
16
+ # Module-level QApplication reference to prevent garbage collection
17
+ _qapp: QApplication | None = None
18
+
19
+
20
+ class DemoLogic(Logic):
21
+ """entry -> running -> exit"""
22
+ NAME = "DemoLogic"
23
+
24
+ def entry(self, inst):
25
+ return "root"
26
+
27
+ # =============== Tree ================
28
+ @parallel("a", "b")
29
+ def root(self, it):
30
+ return True
31
+
32
+ def a(self, it):
33
+ return random.random() < 0.5
34
+
35
+ @sequence("c", "d")
36
+ def b(self, it):
37
+ time.sleep(2)
38
+ # print("b被执行")
39
+ return True
40
+
41
+ def c(self, it):
42
+ return random.random() < 0.6
43
+
44
+ def d(self, it):
45
+ return 'record'
46
+ # =====================================
47
+
48
+ @recursive
49
+ def record(self, inst):
50
+ return "exit"
51
+
52
+ def exit(self, inst):
53
+ self._alive = False
54
+
55
+
56
+ class DemoChart(QFlowChart):
57
+ """A demo chart with sample nodes and edges for testing."""
58
+
59
+ def __init__(self):
60
+ # Ensure QApplication exists for headless/test environments
61
+ global _qapp
62
+ if QApplication.instance() is None:
63
+ _qapp = QApplication(sys.argv)
64
+ super().__init__()
65
+ # Create demo nodes
66
+ self.addNode(ChartNode(nid="entry", name="Entry", ntype="entry", shape="ellipse"))
67
+ self.addNode(ChartNode(nid="state1", name="State1", ntype="state", shape="rectangle"))
68
+ self.addNode(ChartNode(nid="state2", name="State2", ntype="state", shape="rectangle"))
69
+ self.addNode(ChartNode(nid="exit", name="Exit", ntype="exit", shape="ellipse"))
70
+
71
+ # Create demo edges
72
+ self.addEdge(ChartEdge(eid="e1", src="entry", dst="state1", etype="flow"))
73
+ self.addEdge(ChartEdge(eid="e2", src="state1", dst="state2", etype="flow"))
74
+ self.addEdge(ChartEdge(eid="e3", src="state2", dst="exit", etype="flow"))
75
+ self.addEdge(ChartEdge(eid="e4", src="state2", dst="state1", etype="flow", bidirectional=True))
76
+
77
+ # Set start node
78
+ self.startnode = "entry"
79
+
80
+
81
+ class DemoWindow(QMainWindow):
82
+ """Demo window for running QFlowChart GUI."""
83
+
84
+ def __init__(self):
85
+ super().__init__()
86
+ self.setWindowTitle("QFlowChart Demo")
87
+ self.setGeometry(100, 100, 1000, 700)
88
+
89
+ self.setCentralWidget(QFlowChart(DemoLogic(), parent=self))
90
+
91
+
92
+ if __name__ == "__main__":
93
+ import sys
94
+
95
+ app = QApplication(sys.argv)
96
+ window = DemoWindow()
97
+ window.show()
98
+ sys.exit(app.exec())