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,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
+ ]
chartflow/flow/flow.py ADDED
@@ -0,0 +1,6 @@
1
+ from chartflow.fsm import Logic
2
+ from PyQt6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout
3
+
4
+
5
+ class QFlowChart(QWidget):
6
+ pass