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,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
|
+
]
|
chartflow/flow/_demo.py
ADDED
|
@@ -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())
|