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
chartflow/flow/editor.py
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
qflow.editor - PyEdit-based editor for qflowchart node editing
|
|
6
|
+
|
|
7
|
+
This module provides StageEditor, a specialized Python editor for editing
|
|
8
|
+
qfsm Stage/Logic state methods with proper context awareness.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from chartflow.fsm.stage import Stage
|
|
12
|
+
from chartflow.theme import PALETTE
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Type
|
|
17
|
+
|
|
18
|
+
from PyQt6.QtWidgets import (
|
|
19
|
+
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
|
20
|
+
QLabel, QDialog, QApplication
|
|
21
|
+
)
|
|
22
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
|
23
|
+
from PyQt6.QtGui import QFont
|
|
24
|
+
|
|
25
|
+
from chartflow.edit import PythonEditor, PythonEditorFactory
|
|
26
|
+
|
|
27
|
+
class StageEditor(PythonEditor):
|
|
28
|
+
"""
|
|
29
|
+
Python editor specialized for qfsm Stage/Logic state editing.
|
|
30
|
+
|
|
31
|
+
Provides context-aware editing for state methods with:
|
|
32
|
+
- Class definition context (CA)
|
|
33
|
+
- Function signature context (CB)
|
|
34
|
+
- Proper self and inst parameter completion
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
editor = StageEditor()
|
|
38
|
+
editor.set_stage_context(MyStageClass, "running")
|
|
39
|
+
editor.set_code("return 'completed'")
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
code_saved = pyqtSignal(str) # Emitted when code is saved
|
|
43
|
+
|
|
44
|
+
def __init__(self, parent: QWidget | None = None):
|
|
45
|
+
super().__init__(parent)
|
|
46
|
+
self._stage_class: Type[Stage] | None = None
|
|
47
|
+
self._state_name: str = ""
|
|
48
|
+
self._inst_type: str = "Any"
|
|
49
|
+
|
|
50
|
+
def set_stage_context(
|
|
51
|
+
self,
|
|
52
|
+
stage_class: Type[Stage],
|
|
53
|
+
state_name: str,
|
|
54
|
+
inst_type: str = "Any"
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Set the Stage/Logic class context for editing.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
stage_class: The Stage/Logic class being edited
|
|
61
|
+
state_name: The state method name being edited
|
|
62
|
+
inst_type: Type annotation for inst parameter (default: Any)
|
|
63
|
+
"""
|
|
64
|
+
self._stage_class = stage_class
|
|
65
|
+
self._state_name = state_name
|
|
66
|
+
self._inst_type = inst_type
|
|
67
|
+
|
|
68
|
+
# Build context code
|
|
69
|
+
code_a = self._build_class_context()
|
|
70
|
+
code_b = self._build_function_context()
|
|
71
|
+
|
|
72
|
+
# Set context with 8-space prefix for method body
|
|
73
|
+
self.set_context_code(
|
|
74
|
+
code_a=code_a,
|
|
75
|
+
code_b=code_b,
|
|
76
|
+
prefix=" " # 8 spaces for method indentation
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def _build_class_context(self) -> str:
|
|
80
|
+
"""Build class definition context (CA)."""
|
|
81
|
+
if self._stage_class is None:
|
|
82
|
+
return ""
|
|
83
|
+
|
|
84
|
+
class_name = self._stage_class.__name__
|
|
85
|
+
|
|
86
|
+
# Get base classes
|
|
87
|
+
bases = []
|
|
88
|
+
for base in self._stage_class.__bases__:
|
|
89
|
+
if base.__name__ != "object":
|
|
90
|
+
bases.append(base.__name__)
|
|
91
|
+
|
|
92
|
+
base_str = f"({', '.join(bases)})" if bases else ""
|
|
93
|
+
|
|
94
|
+
# Build class definition with common attributes
|
|
95
|
+
lines = [
|
|
96
|
+
f"class {class_name}{base_str}:",
|
|
97
|
+
f' NAME = "{self._stage_class.NAME}"',
|
|
98
|
+
"",
|
|
99
|
+
" # Class-level attributes",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
# Add common Stage/Logic attributes
|
|
103
|
+
lines.extend([
|
|
104
|
+
" LOG: bool = True",
|
|
105
|
+
" LIMIT: int = 24",
|
|
106
|
+
"",
|
|
107
|
+
" def __init__(self, inst: Any = None) -> None:",
|
|
108
|
+
" self._inst = inst",
|
|
109
|
+
" self._current: str | None = None",
|
|
110
|
+
"",
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
# Add other state methods as stubs for completion
|
|
114
|
+
states = getattr(self._stage_class, "__states__", [])
|
|
115
|
+
for state in states:
|
|
116
|
+
if state != self._state_name:
|
|
117
|
+
lines.append(f" def {state}(self, inst: {self._inst_type}):")
|
|
118
|
+
lines.append(" ...")
|
|
119
|
+
lines.append("")
|
|
120
|
+
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
def _build_function_context(self) -> str:
|
|
124
|
+
"""Build function definition context (CB)."""
|
|
125
|
+
if not self._state_name:
|
|
126
|
+
return ""
|
|
127
|
+
|
|
128
|
+
lines = [
|
|
129
|
+
f" def {self._state_name}(self, inst: {self._inst_type}):",
|
|
130
|
+
' """',
|
|
131
|
+
f" State: {self._state_name}",
|
|
132
|
+
"",
|
|
133
|
+
" Args:",
|
|
134
|
+
f" inst: The bound instance ({self._inst_type})",
|
|
135
|
+
"",
|
|
136
|
+
" Returns:",
|
|
137
|
+
" str: Next state name to transition to",
|
|
138
|
+
" None: Stay in current state",
|
|
139
|
+
" True: Success (stay)",
|
|
140
|
+
" False: Failure (for behavior trees)",
|
|
141
|
+
' """',
|
|
142
|
+
"<__begin__>", # Placeholder for visible code
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
return "\n".join(lines)
|
|
146
|
+
|
|
147
|
+
def get_state_code(self) -> str:
|
|
148
|
+
"""
|
|
149
|
+
Get the edited code (function body only, without indentation).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
The code without the 8-space prefix
|
|
153
|
+
"""
|
|
154
|
+
full_code = self.get_code()
|
|
155
|
+
lines = full_code.split('\n')
|
|
156
|
+
|
|
157
|
+
# Remove 8-space prefix from each line
|
|
158
|
+
result_lines = []
|
|
159
|
+
for line in lines:
|
|
160
|
+
if line.startswith(" "):
|
|
161
|
+
result_lines.append(line[8:])
|
|
162
|
+
elif line.startswith("\t"):
|
|
163
|
+
# Handle tab case
|
|
164
|
+
result_lines.append(line[1:])
|
|
165
|
+
else:
|
|
166
|
+
result_lines.append(line)
|
|
167
|
+
|
|
168
|
+
return '\n'.join(result_lines)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class StageEditorDialog(QDialog):
|
|
172
|
+
"""
|
|
173
|
+
Dialog for editing Stage/Logic state methods.
|
|
174
|
+
|
|
175
|
+
Provides a modal dialog with StageEditor for editing state code.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
def __init__(
|
|
179
|
+
self,
|
|
180
|
+
stage_class: Type[Stage],
|
|
181
|
+
state_name: str,
|
|
182
|
+
initial_code: str = "",
|
|
183
|
+
doc: str = "",
|
|
184
|
+
parent: QWidget | None = None
|
|
185
|
+
):
|
|
186
|
+
"""
|
|
187
|
+
Initialize the editor dialog.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
stage_class: The Stage/Logic class
|
|
191
|
+
state_name: Name of the state being edited
|
|
192
|
+
initial_code: Initial code content
|
|
193
|
+
doc: Documentation string for the state
|
|
194
|
+
parent: Parent widget
|
|
195
|
+
"""
|
|
196
|
+
super().__init__(parent)
|
|
197
|
+
|
|
198
|
+
self._stage_class = stage_class
|
|
199
|
+
self._state_name = state_name
|
|
200
|
+
self._doc = doc
|
|
201
|
+
self._saved_code: str = ""
|
|
202
|
+
|
|
203
|
+
self.setWindowTitle(f"Edit State: {state_name}")
|
|
204
|
+
self.setMinimumSize(900, 700)
|
|
205
|
+
self.resize(1000, 750)
|
|
206
|
+
|
|
207
|
+
self._setup_ui()
|
|
208
|
+
self._setup_editor(initial_code)
|
|
209
|
+
|
|
210
|
+
def _setup_ui(self) -> None:
|
|
211
|
+
"""Setup the dialog UI."""
|
|
212
|
+
self.setStyleSheet(f"""
|
|
213
|
+
QDialog {{
|
|
214
|
+
background-color: {PALETTE.window};
|
|
215
|
+
}}
|
|
216
|
+
""")
|
|
217
|
+
|
|
218
|
+
layout = QVBoxLayout(self)
|
|
219
|
+
layout.setContentsMargins(10, 10, 10, 10)
|
|
220
|
+
layout.setSpacing(10)
|
|
221
|
+
|
|
222
|
+
# Header with doc
|
|
223
|
+
if self._doc:
|
|
224
|
+
doc_label = QLabel(f"Documentation: {self._doc}")
|
|
225
|
+
doc_label.setStyleSheet(f"""
|
|
226
|
+
QLabel {{
|
|
227
|
+
color: {PALETTE.text_dim};
|
|
228
|
+
font-size: 12px;
|
|
229
|
+
padding: 6px 8px;
|
|
230
|
+
background-color: {PALETTE.panel};
|
|
231
|
+
border: 1px solid {PALETTE.border};
|
|
232
|
+
border-radius: 4px;
|
|
233
|
+
}}
|
|
234
|
+
""")
|
|
235
|
+
doc_label.setWordWrap(True)
|
|
236
|
+
layout.addWidget(doc_label)
|
|
237
|
+
|
|
238
|
+
# Editor
|
|
239
|
+
self._editor = StageEditor()
|
|
240
|
+
layout.addWidget(self._editor, stretch=1)
|
|
241
|
+
|
|
242
|
+
# Buttons
|
|
243
|
+
btn_layout = QHBoxLayout()
|
|
244
|
+
btn_layout.addStretch()
|
|
245
|
+
|
|
246
|
+
self._btn_save = QPushButton("Save")
|
|
247
|
+
self._btn_save.setObjectName("primary")
|
|
248
|
+
self._btn_save.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
249
|
+
self._btn_save.clicked.connect(self._on_save)
|
|
250
|
+
|
|
251
|
+
self._btn_cancel = QPushButton("Cancel")
|
|
252
|
+
self._btn_cancel.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
253
|
+
self._btn_cancel.clicked.connect(self.reject)
|
|
254
|
+
|
|
255
|
+
btn_layout.addWidget(self._btn_cancel)
|
|
256
|
+
btn_layout.addWidget(self._btn_save)
|
|
257
|
+
|
|
258
|
+
layout.addLayout(btn_layout)
|
|
259
|
+
|
|
260
|
+
def _setup_editor(self, initial_code: str) -> None:
|
|
261
|
+
"""Setup the editor with context and initial code."""
|
|
262
|
+
self._editor.set_stage_context(
|
|
263
|
+
self._stage_class,
|
|
264
|
+
self._state_name
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if initial_code:
|
|
268
|
+
self._editor.set_code(initial_code)
|
|
269
|
+
|
|
270
|
+
def _on_save(self) -> None:
|
|
271
|
+
"""Handle save button click."""
|
|
272
|
+
self._saved_code = self._editor.get_state_code()
|
|
273
|
+
self.accept()
|
|
274
|
+
|
|
275
|
+
def get_code(self) -> str:
|
|
276
|
+
"""Get the edited code."""
|
|
277
|
+
return self._saved_code
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class StageEditorFactory:
|
|
281
|
+
"""Factory for creating StageEditor instances."""
|
|
282
|
+
|
|
283
|
+
@staticmethod
|
|
284
|
+
def create_editor(
|
|
285
|
+
stage_class: Type[Stage],
|
|
286
|
+
state_name: str,
|
|
287
|
+
initial_code: str = "",
|
|
288
|
+
parent: QWidget | None = None
|
|
289
|
+
) -> StageEditor:
|
|
290
|
+
"""
|
|
291
|
+
Create a configured StageEditor.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
stage_class: The Stage/Logic class
|
|
295
|
+
state_name: Name of the state being edited
|
|
296
|
+
initial_code: Initial code content
|
|
297
|
+
parent: Parent widget
|
|
298
|
+
|
|
299
|
+
Returns:
|
|
300
|
+
Configured StageEditor instance
|
|
301
|
+
"""
|
|
302
|
+
editor = StageEditor(parent)
|
|
303
|
+
editor.set_stage_context(stage_class, state_name)
|
|
304
|
+
|
|
305
|
+
if initial_code:
|
|
306
|
+
editor.set_code(initial_code)
|
|
307
|
+
|
|
308
|
+
return editor
|
|
309
|
+
|
|
310
|
+
@staticmethod
|
|
311
|
+
def edit_state(
|
|
312
|
+
stage_class: Type[Stage],
|
|
313
|
+
state_name: str,
|
|
314
|
+
initial_code: str = "",
|
|
315
|
+
doc: str = "",
|
|
316
|
+
parent: QWidget | None = None
|
|
317
|
+
) -> str | None:
|
|
318
|
+
"""
|
|
319
|
+
Open a modal dialog to edit a state method.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
stage_class: The Stage/Logic class
|
|
323
|
+
state_name: Name of the state being edited
|
|
324
|
+
initial_code: Initial code content
|
|
325
|
+
doc: Documentation string
|
|
326
|
+
parent: Parent widget
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
The edited code if saved, None if cancelled
|
|
330
|
+
"""
|
|
331
|
+
dialog = StageEditorDialog(
|
|
332
|
+
stage_class=stage_class,
|
|
333
|
+
state_name=state_name,
|
|
334
|
+
initial_code=initial_code,
|
|
335
|
+
doc=doc,
|
|
336
|
+
parent=parent
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
340
|
+
return dialog.get_code()
|
|
341
|
+
return None
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
# Convenience function
|
|
345
|
+
def edit_state(
|
|
346
|
+
stage_class: Type[Stage],
|
|
347
|
+
state_name: str,
|
|
348
|
+
initial_code: str = "",
|
|
349
|
+
doc: str = "",
|
|
350
|
+
parent: QWidget | None = None
|
|
351
|
+
) -> str | None:
|
|
352
|
+
"""
|
|
353
|
+
Open a modal dialog to edit a state method.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
stage_class: The Stage/Logic class
|
|
357
|
+
state_name: Name of the state being edited
|
|
358
|
+
initial_code: Initial code content
|
|
359
|
+
doc: Documentation string
|
|
360
|
+
parent: Parent widget
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
The edited code if saved, None if cancelled
|
|
364
|
+
"""
|
|
365
|
+
return StageEditorFactory.edit_state(
|
|
366
|
+
stage_class, state_name, initial_code, doc, parent
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
__all__ = [
|
|
371
|
+
"StageEditor",
|
|
372
|
+
"StageEditorDialog",
|
|
373
|
+
"StageEditorFactory",
|
|
374
|
+
"edit_state",
|
|
375
|
+
]
|