statable 3.1.0__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 (117) hide show
  1. codegen/__init__.py +38 -0
  2. codegen/c_code_generator.py +1752 -0
  3. codegen/code_merger.py +371 -0
  4. codegen/code_templates.py +509 -0
  5. codegen/config.py +167 -0
  6. codegen/enum_generator.py +377 -0
  7. codegen/event_queue_generator.py +303 -0
  8. codegen/interrupt_generator.py +345 -0
  9. codegen/naming_convention.py +171 -0
  10. codegen/osal_generator.py +282 -0
  11. codegen/role_function_generator.py +1244 -0
  12. codegen/sample_data.py +269 -0
  13. codegen/state_actions_generator.py +395 -0
  14. codegen/struct_generator.py +721 -0
  15. codegen/timer_generator.py +299 -0
  16. codegen/transition_generator.py +907 -0
  17. codegen/type_mapper.py +69 -0
  18. codegen/validate/__init__.py +38 -0
  19. codegen/validate/change_actions.py +90 -0
  20. codegen/validate/change_applier.py +416 -0
  21. codegen/validate/clipboard_manager.py +47 -0
  22. codegen/validate/data/__init__.py +30 -0
  23. codegen/validate/data/action_definitions.py +200 -0
  24. codegen/validate/data/keywords.py +46 -0
  25. codegen/validate/data/prompt_templates.py +176 -0
  26. codegen/validate/data/validation_rules.py +205 -0
  27. codegen/validate/items/__init__.py +30 -0
  28. codegen/validate/items/base_validator.py +25 -0
  29. codegen/validate/items/cell_validator.py +275 -0
  30. codegen/validate/items/custom_type_validator.py +75 -0
  31. codegen/validate/items/event_validator.py +78 -0
  32. codegen/validate/items/flag_validator.py +74 -0
  33. codegen/validate/items/interrupt_validator.py +75 -0
  34. codegen/validate/items/queue_validator.py +73 -0
  35. codegen/validate/items/role_function_validator.py +175 -0
  36. codegen/validate/items/state_validator.py +88 -0
  37. codegen/validate/items/timer_validator.py +120 -0
  38. codegen/validate/items/transition_validator.py +113 -0
  39. codegen/validate/items/variable_validator.py +132 -0
  40. codegen/validate/logger.py +52 -0
  41. codegen/validate/models.py +177 -0
  42. codegen/validate/prompt_generator.py +724 -0
  43. codegen/validate/response_parser.py +154 -0
  44. codegen/validate/response_validator.py +275 -0
  45. codegen/validate/validation_dialog.py +322 -0
  46. codegen/validate/validator.py +94 -0
  47. codegen/variable_generator.py +419 -0
  48. statable/__init__.py +71 -0
  49. statable/__main__.py +49 -0
  50. statable/cli.py +251 -0
  51. statable/global_defs.py +256 -0
  52. statable/mermaid_gen.py +144 -0
  53. statable/model.py +426 -0
  54. statable/parser.py +64 -0
  55. statable/sample_data.py +435 -0
  56. statable/shared/__init__.py +24 -0
  57. statable/shared/condition_library.py +63 -0
  58. statable/shared/literal_library.py +66 -0
  59. statable/shared/role_function_library.py +150 -0
  60. statable/state_machine.py +121 -0
  61. statable/xml_io.py +1164 -0
  62. statable-3.1.0.dist-info/METADATA +151 -0
  63. statable-3.1.0.dist-info/RECORD +117 -0
  64. statable-3.1.0.dist-info/WHEEL +5 -0
  65. statable-3.1.0.dist-info/entry_points.txt +2 -0
  66. statable-3.1.0.dist-info/top_level.txt +3 -0
  67. statable_gui/__init__.py +23 -0
  68. statable_gui/action_edit_dialog.py +214 -0
  69. statable_gui/code_generation_dialog.py +486 -0
  70. statable_gui/code_generation_settings_dialog.py +522 -0
  71. statable_gui/common_widgets.py +537 -0
  72. statable_gui/condition_builder_dialog.py +500 -0
  73. statable_gui/condition_edit_dialog.py +123 -0
  74. statable_gui/condition_template_dialog.py +58 -0
  75. statable_gui/config.py +21 -0
  76. statable_gui/dialogs.py +439 -0
  77. statable_gui/event_definition_dialog.py +605 -0
  78. statable_gui/event_delivery_settings_dialog.py +191 -0
  79. statable_gui/event_queue_dialog.py +245 -0
  80. statable_gui/global_defs.py +167 -0
  81. statable_gui/global_defs_dialog.py +796 -0
  82. statable_gui/i18n/__init__.py +118 -0
  83. statable_gui/interrupt_handler_edit_dialog.py +810 -0
  84. statable_gui/layer_settings_dialog.py +180 -0
  85. statable_gui/libcntrl/__init__.py +23 -0
  86. statable_gui/libcntrl/condition_library.py +13 -0
  87. statable_gui/libcntrl/literal_library.py +13 -0
  88. statable_gui/libcntrl/literal_management_dialog.py +185 -0
  89. statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
  90. statable_gui/libcntrl/role_function_library.py +13 -0
  91. statable_gui/literal_definition_dialog.py +62 -0
  92. statable_gui/logger.py +83 -0
  93. statable_gui/main_window.py +1380 -0
  94. statable_gui/matrix_table.py +413 -0
  95. statable_gui/preference_keys.py +18 -0
  96. statable_gui/preferences.py +74 -0
  97. statable_gui/role_function_dialog.py +244 -0
  98. statable_gui/sample_data.py +11 -0
  99. statable_gui/state_actions_dialog.py +398 -0
  100. statable_gui/symbol_picker.py +170 -0
  101. statable_gui/traceball.py +37 -0
  102. statable_gui/transition_editor_direct/__init__.py +15 -0
  103. statable_gui/transition_editor_direct/actions_tab.py +856 -0
  104. statable_gui/transition_editor_direct/code_widget.py +344 -0
  105. statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
  106. statable_gui/transition_editor_direct/dialog.py +335 -0
  107. statable_gui/transition_editor_direct/draft.py +270 -0
  108. statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
  109. statable_gui/transition_editor_direct/overview_tab.py +173 -0
  110. statable_gui/transition_editor_direct/palette_widget.py +233 -0
  111. statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
  112. statable_gui/transition_editor_direct/relations_tab.py +230 -0
  113. statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
  114. statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
  115. statable_gui/transition_editor_direct/transitions_tab.py +620 -0
  116. statable_gui/validation_dialog.py +285 -0
  117. statable_gui/widgets.py +1315 -0
@@ -0,0 +1,156 @@
1
+ # statable_gui/transition_editor_direct/relations_edit_dialog.py
2
+ """Dialog to edit one TransitionRelation (v2.2).
3
+
4
+ [v2.2 enhancement]
5
+ - Members list shows each transition as "T1: cond_A -> Active"
6
+ so the user can identify which label belongs to which condition.
7
+ """
8
+
9
+ import logging
10
+ from typing import List, Optional, Dict
11
+
12
+ from PySide6.QtCore import Qt
13
+ from PySide6.QtWidgets import (
14
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
15
+ QLineEdit, QListWidget, QListWidgetItem, QDialogButtonBox,
16
+ QGroupBox, QMessageBox,
17
+ )
18
+
19
+ from statable.model import TransitionRelation
20
+
21
+ logger = logging.getLogger("transition_editor_direct.relations_edit_dialog")
22
+
23
+ KIND_CHOICES = ["sequential", "exclusive", "group"]
24
+
25
+
26
+ def _fmt_member(label: str, detail: str) -> str:
27
+ """Format a member list entry: 'T1: cond_A -> Active'."""
28
+ if detail:
29
+ return f"{label}: {detail}"
30
+ return label
31
+
32
+
33
+ class RelationsEditDialog(QDialog):
34
+ """Edit a single relation (kind / members / shared_condition)."""
35
+
36
+ def __init__(self, parent=None,
37
+ relation: Optional[TransitionRelation] = None,
38
+ available_labels: Optional[List[str]] = None,
39
+ member_details: Optional[Dict[str, str]] = None):
40
+ """
41
+ Args:
42
+ relation: existing relation (None for new)
43
+ available_labels: list of Transition labels in this cell
44
+ member_details: {label: "cond_A -> Active"} for display
45
+ """
46
+ super().__init__(parent)
47
+ self.setWindowTitle(self.tr("Edit relation"))
48
+ self.setMinimumSize(600, 500)
49
+
50
+ self.available_labels = list(available_labels or [])
51
+ self.member_details = dict(member_details or {})
52
+
53
+ layout = QVBoxLayout(self)
54
+
55
+ # ---- Kind ----
56
+ kind_layout = QHBoxLayout()
57
+ kind_layout.addWidget(QLabel(self.tr("Kind:")))
58
+ self.kind_combo = QComboBox()
59
+ self.kind_combo.addItems(KIND_CHOICES)
60
+ kind_layout.addWidget(self.kind_combo)
61
+ kind_layout.addStretch()
62
+ layout.addLayout(kind_layout)
63
+
64
+ # ---- Members (checkbox list with condition/target) ----
65
+ members_group = QGroupBox(self.tr("Members (Transition labels)"))
66
+ members_layout = QVBoxLayout(members_group)
67
+ self.members_list = QListWidget()
68
+ for lbl in self.available_labels:
69
+ detail = self.member_details.get(lbl, "")
70
+ text = _fmt_member(lbl, detail)
71
+ item = QListWidgetItem(text)
72
+ item.setData(Qt.UserRole, lbl) # store the raw label
73
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
74
+ item.setCheckState(Qt.Unchecked)
75
+ if detail:
76
+ item.setToolTip(f"{lbl}\n{detail}")
77
+ self.members_list.addItem(item)
78
+ members_layout.addWidget(self.members_list)
79
+
80
+ if not self.available_labels:
81
+ hint = QLabel(
82
+ self.tr("No transitions defined in this cell. "
83
+ "Add transitions first."))
84
+ hint.setStyleSheet("color: #a00;")
85
+ members_layout.addWidget(hint)
86
+ layout.addWidget(members_group)
87
+
88
+ # ---- Shared condition ----
89
+ cond_group = QGroupBox(self.tr("Shared condition (used when kind = group)"))
90
+ cond_layout = QVBoxLayout(cond_group)
91
+ self.shared_edit = QLineEdit()
92
+ self.shared_edit.setPlaceholderText(self.tr("e.g. cond_A"))
93
+ cond_layout.addWidget(self.shared_edit)
94
+ layout.addWidget(cond_group)
95
+
96
+ # ---- Note ----
97
+ note_layout = QHBoxLayout()
98
+ note_layout.addWidget(QLabel(self.tr("Note:")))
99
+ self.note_edit = QLineEdit()
100
+ note_layout.addWidget(self.note_edit)
101
+ layout.addLayout(note_layout)
102
+
103
+ # ---- OK / Cancel ----
104
+ buttons = QDialogButtonBox(
105
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
106
+ buttons.accepted.connect(self._on_accept)
107
+ buttons.rejected.connect(self.reject)
108
+ layout.addWidget(buttons)
109
+
110
+ # Load
111
+ if relation is not None:
112
+ self._load(relation)
113
+
114
+ def _load(self, relation: TransitionRelation):
115
+ kind = relation.kind if relation.kind in KIND_CHOICES else "sequential"
116
+ self.kind_combo.setCurrentText(kind)
117
+ self.shared_edit.setText(relation.shared_condition or "")
118
+ self.note_edit.setText(relation.note or "")
119
+
120
+ selected = set(relation.members or [])
121
+ for i in range(self.members_list.count()):
122
+ item = self.members_list.item(i)
123
+ raw_label = item.data(Qt.UserRole) or item.text()
124
+ if raw_label in selected:
125
+ item.setCheckState(Qt.Checked)
126
+
127
+ def _on_accept(self):
128
+ members = self.get_members()
129
+ kind = self.kind_combo.currentText()
130
+ if kind == "group" and not members:
131
+ QMessageBox.warning(
132
+ self, self.tr("Warning"),
133
+ self.tr("A group relation requires at least one member."))
134
+ return
135
+ self.accept()
136
+
137
+ # ------------------------------------------------------------------
138
+ # Public API
139
+ # ------------------------------------------------------------------
140
+ def get_members(self) -> List[str]:
141
+ """Return only the raw labels (T1, T2, ...)."""
142
+ result = []
143
+ for i in range(self.members_list.count()):
144
+ item = self.members_list.item(i)
145
+ if item.checkState() == Qt.Checked:
146
+ label = item.data(Qt.UserRole) or item.text()
147
+ result.append(label)
148
+ return result
149
+
150
+ def get_relation(self) -> TransitionRelation:
151
+ return TransitionRelation(
152
+ kind=self.kind_combo.currentText(),
153
+ members=self.get_members(),
154
+ shared_condition=self.shared_edit.text().strip(),
155
+ note=self.note_edit.text().strip(),
156
+ )
@@ -0,0 +1,230 @@
1
+ # statable_gui/transition_editor_direct/relations_tab.py
2
+ """Relations tab widget (v2.2).
3
+
4
+ Sequential / Exclusive / Group relations between transitions.
5
+ Editing is done via RelationsEditDialog.
6
+
7
+ [v2.2 enhancement]
8
+ - Members list shows "T1: cond_A -> Active" so labels are identifiable.
9
+ - set_transitions() lets the dialog pass transition details.
10
+
11
+ [v2.3.1 fix]
12
+ - Add setEditTriggers(NoEditTriggers) to prevent inline edit from
13
+ swallowing subsequent double-clicks. Same fix as transitions_tab.py.
14
+ """
15
+
16
+ import logging
17
+ from typing import List, Optional, Dict
18
+
19
+ from PySide6.QtCore import Qt, Signal
20
+ from PySide6.QtWidgets import (
21
+ QWidget, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
22
+ QPushButton, QHeaderView, QAbstractItemView, QLabel, QMessageBox,
23
+ )
24
+
25
+ from statable.model import TransitionRelation
26
+ from .draft import ActionDraft
27
+ from .relations_edit_dialog import RelationsEditDialog
28
+
29
+ logger = logging.getLogger("transition_editor_direct.relations_tab")
30
+
31
+ COL_KIND = 0
32
+ COL_MEMBERS = 1
33
+ COL_SHARED = 2
34
+ COL_NOTE = 3
35
+ COLUMN_COUNT = 4
36
+
37
+
38
+ class RelationsTab(QWidget):
39
+ """Relations tab."""
40
+
41
+ relations_changed = Signal()
42
+
43
+ def __init__(self, draft: ActionDraft, parent=None):
44
+ super().__init__(parent)
45
+ self.draft = draft
46
+ # v2.2: {label: "cond_A -> Active"} populated by set_transitions()
47
+ self.member_details: Dict[str, str] = {}
48
+ self._build_ui()
49
+
50
+ def _build_ui(self):
51
+ layout = QVBoxLayout(self)
52
+
53
+ info = QLabel(
54
+ self.tr("Relations between transitions in this cell.\n"
55
+ " sequential: evaluate members in order\n"
56
+ " exclusive : at most one fires\n"
57
+ " group : hoists shared_condition as outer if"))
58
+ layout.addWidget(info)
59
+
60
+ self.table = QTableWidget(0, COLUMN_COUNT)
61
+ self.table.setHorizontalHeaderLabels(
62
+ ["Kind", "Members", "Shared condition", "Note"])
63
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
64
+ self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
65
+ self.table.setSelectionMode(QAbstractItemView.SingleSelection)
66
+ # [v2.3.1 fix] Disable inline editing so that double-click always
67
+ # triggers the edit dialog.
68
+ self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
69
+ self.table.cellDoubleClicked.connect(self._on_double_click)
70
+ layout.addWidget(self.table)
71
+
72
+ btn_layout = QHBoxLayout()
73
+ add_btn = QPushButton(self.tr("+ Add"))
74
+ add_btn.clicked.connect(lambda: self._add())
75
+ edit_btn = QPushButton(self.tr("Edit..."))
76
+ edit_btn.clicked.connect(lambda: self._edit(self.table.currentRow()))
77
+ del_btn = QPushButton(self.tr("Delete"))
78
+ del_btn.clicked.connect(lambda: self.delete_relation(
79
+ self.table.currentRow()))
80
+
81
+ btn_layout.addWidget(add_btn)
82
+ btn_layout.addWidget(edit_btn)
83
+ btn_layout.addWidget(del_btn)
84
+ btn_layout.addStretch()
85
+ layout.addLayout(btn_layout)
86
+
87
+ # ------------------------------------------------------------------
88
+ # Member details (called by dialog.py)
89
+ # ------------------------------------------------------------------
90
+ def set_member_details(self, details: Dict[str, str]):
91
+ """Update the {label: 'cond -> target'} map.
92
+
93
+ Called by ActionEditorDialog whenever transitions change.
94
+ """
95
+ self.member_details = dict(details or {})
96
+
97
+ # ------------------------------------------------------------------
98
+ # Available labels (from current cell's transitions)
99
+ # ------------------------------------------------------------------
100
+ def _get_available_labels(self) -> List[str]:
101
+ labels = []
102
+ for item in self.draft.flow_items:
103
+ if item.item_type == "transition":
104
+ lbl = item.params.get("label", "")
105
+ if lbl:
106
+ labels.append(lbl)
107
+ return labels
108
+
109
+ # ------------------------------------------------------------------
110
+ # Public API
111
+ # ------------------------------------------------------------------
112
+ def row_count(self) -> int:
113
+ return self.table.rowCount()
114
+
115
+ def _make_item(self, text: str) -> QTableWidgetItem:
116
+ """Create a non-editable table item."""
117
+ item = QTableWidgetItem(text)
118
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
119
+ return item
120
+
121
+ def add_relation(self, relation: Optional[TransitionRelation] = None) -> int:
122
+ """Add a relation. If relation is None, opens the edit dialog first."""
123
+ if relation is None:
124
+ available = self._get_available_labels()
125
+ if not available:
126
+ QMessageBox.warning(
127
+ self, self.tr("Warning"),
128
+ self.tr("No transitions defined in this cell.\n"
129
+ "Add transitions first."))
130
+ return -1
131
+ dlg = RelationsEditDialog(
132
+ parent=self,
133
+ relation=None,
134
+ available_labels=available,
135
+ member_details=self.member_details,
136
+ )
137
+ if dlg.exec() != RelationsEditDialog.Accepted:
138
+ return -1
139
+ relation = dlg.get_relation()
140
+
141
+ row = self.table.rowCount()
142
+ self.table.insertRow(row)
143
+
144
+ self.table.setItem(row, COL_KIND, self._make_item(relation.kind))
145
+ self.table.setItem(row, COL_MEMBERS,
146
+ self._make_item(", ".join(relation.members)))
147
+ self.table.setItem(row, COL_SHARED,
148
+ self._make_item(relation.shared_condition))
149
+ self.table.setItem(row, COL_NOTE, self._make_item(relation.note))
150
+
151
+ self.relations_changed.emit()
152
+ return row
153
+
154
+ def delete_relation(self, row: int) -> None:
155
+ if 0 <= row < self.table.rowCount():
156
+ self.table.removeRow(row)
157
+ self.relations_changed.emit()
158
+
159
+ def get_relations(self) -> List[TransitionRelation]:
160
+ result = []
161
+ for row in range(self.table.rowCount()):
162
+ def _txt(col: int) -> str:
163
+ it = self.table.item(row, col)
164
+ return it.text().strip() if it else ""
165
+
166
+ members = [m.strip() for m in _txt(COL_MEMBERS).split(",")
167
+ if m.strip()]
168
+ result.append(TransitionRelation(
169
+ kind=_txt(COL_KIND) or "sequential",
170
+ members=members,
171
+ shared_condition=_txt(COL_SHARED),
172
+ note=_txt(COL_NOTE),
173
+ ))
174
+ return result
175
+
176
+ def set_relations(self, relations: List[TransitionRelation]) -> None:
177
+ self.table.setRowCount(0)
178
+ for r in relations:
179
+ row = self.table.rowCount()
180
+ self.table.insertRow(row)
181
+ self.table.setItem(row, COL_KIND, self._make_item(r.kind))
182
+ self.table.setItem(row, COL_MEMBERS,
183
+ self._make_item(", ".join(r.members)))
184
+ self.table.setItem(row, COL_SHARED,
185
+ self._make_item(r.shared_condition))
186
+ self.table.setItem(row, COL_NOTE, self._make_item(r.note))
187
+
188
+ # ------------------------------------------------------------------
189
+ # Slots
190
+ # ------------------------------------------------------------------
191
+ def _on_double_click(self, row: int, col: int):
192
+ self._edit(row)
193
+
194
+ def _add(self):
195
+ self.add_relation(None)
196
+
197
+ def _edit(self, row: int):
198
+ if row < 0 or row >= self.table.rowCount():
199
+ return
200
+
201
+ def _txt(col: int) -> str:
202
+ it = self.table.item(row, col)
203
+ return it.text().strip() if it else ""
204
+
205
+ current = TransitionRelation(
206
+ kind=_txt(COL_KIND) or "sequential",
207
+ members=[m.strip() for m in _txt(COL_MEMBERS).split(",")
208
+ if m.strip()],
209
+ shared_condition=_txt(COL_SHARED),
210
+ note=_txt(COL_NOTE),
211
+ )
212
+
213
+ available = self._get_available_labels()
214
+ dlg = RelationsEditDialog(
215
+ parent=self,
216
+ relation=current,
217
+ available_labels=available,
218
+ member_details=self.member_details,
219
+ )
220
+ if dlg.exec() != RelationsEditDialog.Accepted:
221
+ return
222
+
223
+ new_r = dlg.get_relation()
224
+ self.table.setItem(row, COL_KIND, self._make_item(new_r.kind))
225
+ self.table.setItem(row, COL_MEMBERS,
226
+ self._make_item(", ".join(new_r.members)))
227
+ self.table.setItem(row, COL_SHARED,
228
+ self._make_item(new_r.shared_condition))
229
+ self.table.setItem(row, COL_NOTE, self._make_item(new_r.note))
230
+ self.relations_changed.emit()
@@ -0,0 +1,52 @@
1
+ # statable_gui/transition_editor_direct/system_global_dialog.py
2
+ """
3
+ System global variables (separate screen)
4
+ """
5
+
6
+ from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QListWidget, QInputDialog
7
+
8
+ from .draft import SystemGlobal
9
+
10
+
11
+ class SystemGlobalDialog(QDialog):
12
+ def __init__(self, draft, parent=None):
13
+ super().__init__(parent)
14
+ self.draft = draft
15
+ self.setWindowTitle(self.tr("System global variables"))
16
+ self.setMinimumSize(400, 300)
17
+
18
+ layout = QVBoxLayout(self)
19
+ self.list_widget = QListWidget()
20
+ layout.addWidget(self.list_widget)
21
+
22
+ btn_layout = QHBoxLayout()
23
+ add_btn = QPushButton(self.tr("Add"))
24
+ add_btn.clicked.connect(self._add)
25
+ btn_layout.addWidget(add_btn)
26
+ del_btn = QPushButton(self.tr("Delete"))
27
+ del_btn.clicked.connect(self._delete)
28
+ btn_layout.addWidget(del_btn)
29
+ layout.addLayout(btn_layout)
30
+
31
+ close_btn = QPushButton(self.tr("Close"))
32
+ close_btn.clicked.connect(self.accept)
33
+ layout.addWidget(close_btn)
34
+
35
+ self._load()
36
+
37
+ def _load(self):
38
+ self.list_widget.clear()
39
+ for g in self.draft.system_globals:
40
+ self.list_widget.addItem(f"{g.name} : {g.type} = {g.initial_value}")
41
+
42
+ def _add(self):
43
+ name, ok = QInputDialog.getText(self, "Add", "Variable name:")
44
+ if ok and name:
45
+ self.draft.system_globals.append(SystemGlobal(name=name))
46
+ self._load()
47
+
48
+ def _delete(self):
49
+ row = self.list_widget.currentRow()
50
+ if row >= 0:
51
+ del self.draft.system_globals[row]
52
+ self._load()
@@ -0,0 +1,109 @@
1
+ # statable_gui/transition_editor_direct/transition_actions_dialog.py
2
+ """Dialog to edit pre_actions / else_actions for one transition (v2.2)."""
3
+
4
+ import logging
5
+ from typing import List
6
+
7
+ from PySide6.QtCore import Qt
8
+ from PySide6.QtWidgets import (
9
+ QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox, QPushButton,
10
+ QListWidget, QListWidgetItem, QDialogButtonBox, QGroupBox,
11
+ )
12
+
13
+ logger = logging.getLogger("transition_editor_direct.transition_actions_dialog")
14
+
15
+
16
+ class TransitionActionsDialog(QDialog):
17
+ """Edit pre_actions and else_actions for a single transition.
18
+
19
+ Provides two list widgets with [+ Add] / [Delete] buttons.
20
+ The add button uses a combo box populated from the role function library.
21
+ """
22
+
23
+ def __init__(self, parent=None,
24
+ label: str = "",
25
+ pre_actions: List[str] = None,
26
+ else_actions: List[str] = None,
27
+ role_functions: List[str] = None):
28
+ super().__init__(parent)
29
+ self.label = label or "(unnamed)"
30
+ self.role_functions = role_functions or []
31
+
32
+ self.setWindowTitle(f"Edit actions: {self.label}")
33
+ self.setMinimumSize(500, 450)
34
+
35
+ layout = QVBoxLayout(self)
36
+ layout.addWidget(QLabel(
37
+ f"Transition: {self.label}\n"
38
+ "Set role functions executed before / after the transition."))
39
+
40
+ # ---- Pre-actions group ----
41
+ pre_group = QGroupBox(self.tr("Pre-actions (executed before next_state assignment)"))
42
+ pre_layout = QVBoxLayout(pre_group)
43
+ self.pre_list = QListWidget()
44
+ for a in (pre_actions or []):
45
+ self.pre_list.addItem(a)
46
+ pre_layout.addWidget(self.pre_list)
47
+
48
+ pre_btn = QHBoxLayout()
49
+ self.pre_combo = QComboBox()
50
+ self.pre_combo.setEditable(True)
51
+ self.pre_combo.addItems(self.role_functions)
52
+ pre_btn.addWidget(self.pre_combo, stretch=1)
53
+ pre_add = QPushButton(self.tr("+ Add"))
54
+ pre_add.clicked.connect(lambda: self._add(self.pre_list, self.pre_combo))
55
+ pre_btn.addWidget(pre_add)
56
+ pre_del = QPushButton(self.tr("Delete"))
57
+ pre_del.clicked.connect(lambda: self._del(self.pre_list))
58
+ pre_btn.addWidget(pre_del)
59
+ pre_layout.addLayout(pre_btn)
60
+ layout.addWidget(pre_group)
61
+
62
+ # ---- Else-actions group ----
63
+ else_group = QGroupBox(self.tr("Else-actions (executed in the else branch)"))
64
+ else_layout = QVBoxLayout(else_group)
65
+ self.else_list = QListWidget()
66
+ for a in (else_actions or []):
67
+ self.else_list.addItem(a)
68
+ else_layout.addWidget(self.else_list)
69
+
70
+ else_btn = QHBoxLayout()
71
+ self.else_combo = QComboBox()
72
+ self.else_combo.setEditable(True)
73
+ self.else_combo.addItems(self.role_functions)
74
+ else_btn.addWidget(self.else_combo, stretch=1)
75
+ else_add = QPushButton(self.tr("+ Add"))
76
+ else_add.clicked.connect(lambda: self._add(self.else_list, self.else_combo))
77
+ else_btn.addWidget(else_add)
78
+ else_del = QPushButton(self.tr("Delete"))
79
+ else_del.clicked.connect(lambda: self._del(self.else_list))
80
+ else_btn.addWidget(else_del)
81
+ else_layout.addLayout(else_btn)
82
+ layout.addWidget(else_group)
83
+
84
+ # ---- OK / Cancel ----
85
+ buttons = QDialogButtonBox(
86
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
87
+ buttons.accepted.connect(self.accept)
88
+ buttons.rejected.connect(self.reject)
89
+ layout.addWidget(buttons)
90
+
91
+ def _add(self, list_widget: QListWidget, combo: QComboBox):
92
+ name = combo.currentText().strip()
93
+ if not name:
94
+ return
95
+ list_widget.addItem(name)
96
+ logger.debug(f"Added: {name}")
97
+
98
+ def _del(self, list_widget: QListWidget):
99
+ row = list_widget.currentRow()
100
+ if row >= 0:
101
+ list_widget.takeItem(row)
102
+
103
+ def get_pre_actions(self) -> List[str]:
104
+ return [self.pre_list.item(i).text()
105
+ for i in range(self.pre_list.count())]
106
+
107
+ def get_else_actions(self) -> List[str]:
108
+ return [self.else_list.item(i).text()
109
+ for i in range(self.else_list.count())]