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,180 @@
1
+ # statable_gui/layer_settings_dialog.py
2
+ """\nLayer settings dialog\n- Batch configure execution priority and layer name for each tab (layer)\n"""
3
+
4
+ import logging
5
+ from typing import List, Tuple
6
+
7
+ from PySide6.QtWidgets import (
8
+ QDialog, QVBoxLayout, QHBoxLayout, QTableWidget,
9
+ QTableWidgetItem, QHeaderView, QLabel, QPushButton,
10
+ QSpinBox, QAbstractItemView, QMessageBox, QGroupBox
11
+ )
12
+ from PySide6.QtCore import Qt
13
+
14
+ from statable.state_machine import StateMachine
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class LayerSettingsDialog(QDialog):
20
+ """Layer settings dialog"""
21
+
22
+ def __init__(self, layers: List[Tuple[str, StateMachine]],
23
+ parent=None):
24
+ """
25
+ Args:
26
+ layers: [(tab_name, state_machine), ...]
27
+ """
28
+ super().__init__(parent)
29
+ self.layers = layers
30
+
31
+ self.setWindowTitle(self.tr("Layer settings"))
32
+ self.setMinimumSize(640, 420)
33
+
34
+ self._setup_ui()
35
+ self._load_layers()
36
+
37
+ def _setup_ui(self):
38
+ layout = QVBoxLayout(self)
39
+
40
+ # description
41
+ info_group = QGroupBox(self.tr("About settings items"))
42
+ info_layout = QVBoxLayout(info_group)
43
+
44
+ info_label = QLabel(
45
+ self.tr("- Layer name: used in generated code identifiers (e.g., Driver -> "
46
+ "STATE_Driver_Idle).\n"
47
+ " Leaving it empty generates a version without a layer name (STATE_Idle).\n"
48
+ "- Priority: range 1-9. 1 (low) runs / initializes first.\n"
49
+ "- Tab name: display name (change via the tab rename menu).")
50
+ )
51
+ info_label.setStyleSheet("color: gray;")
52
+ info_layout.addWidget(info_label)
53
+
54
+ layout.addWidget(info_group)
55
+
56
+ # Layer list table (4 columns)
57
+ self.table = QTableWidget(0, 4)
58
+ self.table.setHorizontalHeaderLabels(
59
+ ["Tab name", "Layer name", "Priority", "Description"]
60
+ )
61
+ self.table.horizontalHeader().setSectionResizeMode(
62
+ 0, QHeaderView.ResizeToContents)
63
+ self.table.horizontalHeader().setSectionResizeMode(
64
+ 1, QHeaderView.ResizeToContents)
65
+ self.table.horizontalHeader().setSectionResizeMode(
66
+ 2, QHeaderView.ResizeToContents)
67
+ self.table.horizontalHeader().setSectionResizeMode(
68
+ 3, QHeaderView.Stretch)
69
+ self.table.setSelectionMode(
70
+ QAbstractItemView.SingleSelection)
71
+ self.table.setEditTriggers(
72
+ QAbstractItemView.DoubleClicked
73
+ | QAbstractItemView.EditKeyPressed
74
+ )
75
+
76
+ layout.addWidget(self.table)
77
+
78
+ # Buttons
79
+ button_layout = QHBoxLayout()
80
+ button_layout.addStretch()
81
+
82
+ cancel_btn = QPushButton(self.tr("Cancel"))
83
+ cancel_btn.clicked.connect(self.reject)
84
+ button_layout.addWidget(cancel_btn)
85
+
86
+ ok_btn = QPushButton(self.tr("OK"))
87
+ ok_btn.clicked.connect(self._on_ok)
88
+ button_layout.addWidget(ok_btn)
89
+
90
+ layout.addLayout(button_layout)
91
+
92
+ def _load_layers(self):
93
+ self.table.setRowCount(len(self.layers))
94
+
95
+ # Sort by priority ascending
96
+ sorted_layers = sorted(
97
+ self.layers,
98
+ key=lambda x: getattr(x[1], 'layer_priority', 5)
99
+ )
100
+
101
+ for row, (tab_name, sm) in enumerate(sorted_layers):
102
+ # Tab name (not editable)
103
+ tab_item = QTableWidgetItem(tab_name)
104
+ tab_item.setFlags(
105
+ tab_item.flags() & ~Qt.ItemIsEditable
106
+ )
107
+ self.table.setItem(row, 0, tab_item)
108
+
109
+ # Layer name (editable)
110
+ layer_name = getattr(sm, 'layer_name', '')
111
+ name_item = QTableWidgetItem(layer_name)
112
+ self.table.setItem(row, 1, name_item)
113
+
114
+ # Priority (spin box)
115
+ priority = getattr(sm, 'layer_priority', 5)
116
+ priority_spin = QSpinBox()
117
+ priority_spin.setRange(1, 9)
118
+ priority_spin.setValue(priority)
119
+ self.table.setCellWidget(row, 2, priority_spin)
120
+
121
+ # description
122
+ desc = getattr(sm, 'layer_description', '')
123
+ desc_item = QTableWidgetItem(desc)
124
+ self.table.setItem(row, 3, desc_item)
125
+
126
+ self.table.resizeRowsToContents()
127
+
128
+ def _on_ok(self):
129
+ # Priority duplicate check
130
+ priorities = []
131
+ for row in range(self.table.rowCount()):
132
+ spin = self.table.cellWidget(row, 2)
133
+ if spin:
134
+ priorities.append(spin.value())
135
+
136
+ if len(priorities) != len(set(priorities)):
137
+ reply = QMessageBox.question(
138
+ self, self.tr("Confirm"),
139
+ self.tr("Priority is duplicated. Continue anyway?"),
140
+ QMessageBox.Yes | QMessageBox.No
141
+ )
142
+ if reply == QMessageBox.No:
143
+ return
144
+
145
+ self.accept()
146
+
147
+ def apply_settings(self):
148
+ """Apply settings to each StateMachine"""
149
+ for row in range(self.table.rowCount()):
150
+ tab_name = self.table.item(row, 0).text()
151
+
152
+ #Search for corresponding StateMachine
153
+ target_sm = None
154
+ for name, sm in self.layers:
155
+ if name == tab_name:
156
+ target_sm = sm
157
+ break
158
+
159
+ if target_sm is None:
160
+ continue
161
+
162
+ # Layer name
163
+ name_item = self.table.item(row, 1)
164
+ if name_item:
165
+ target_sm.layer_name = name_item.text().strip()
166
+
167
+ # Priority
168
+ spin = self.table.cellWidget(row, 2)
169
+ if spin:
170
+ target_sm.layer_priority = spin.value()
171
+
172
+ # description
173
+ desc_item = self.table.item(row, 3)
174
+ if desc_item:
175
+ target_sm.layer_description = desc_item.text()
176
+
177
+ logger.info(
178
+ f"Layer settings applied for "
179
+ f"{len(self.layers)} layers"
180
+ )
@@ -0,0 +1,23 @@
1
+ """[v3.0 / G-1a] Backward-compat shim for statable_gui.libcntrl.
2
+
3
+ All shared library classes now live in ``statable.shared``.
4
+ This package re-exports them for backward compatibility.
5
+ """
6
+
7
+ from statable.shared import ( # noqa: F401
8
+ RoleFunctionLibrary,
9
+ RoleFunction,
10
+ ConditionLibrary,
11
+ ConditionTemplate,
12
+ LiteralLibrary,
13
+ LiteralDefinition,
14
+ )
15
+
16
+ __all__ = [
17
+ "RoleFunctionLibrary",
18
+ "RoleFunction",
19
+ "ConditionLibrary",
20
+ "ConditionTemplate",
21
+ "LiteralLibrary",
22
+ "LiteralDefinition",
23
+ ]
@@ -0,0 +1,13 @@
1
+ """[v3.0 / G-1a] Backward-compat shim.
2
+
3
+ Content moved to: statable.shared.condition_library
4
+
5
+ Old imports under ``statable_gui.libcntrl`` keep working via re-export.
6
+ New code should import from ``statable.shared`` instead.
7
+ """
8
+ from statable.shared.condition_library import ( # noqa: F401
9
+ ConditionLibrary,
10
+ ConditionTemplate,
11
+ )
12
+
13
+ __all__ = ["ConditionLibrary", "ConditionTemplate"]
@@ -0,0 +1,13 @@
1
+ """[v3.0 / G-1a] Backward-compat shim.
2
+
3
+ Content moved to: statable.shared.literal_library
4
+
5
+ Old imports under ``statable_gui.libcntrl`` keep working via re-export.
6
+ New code should import from ``statable.shared`` instead.
7
+ """
8
+ from statable.shared.literal_library import ( # noqa: F401
9
+ LiteralDefinition,
10
+ LiteralLibrary,
11
+ )
12
+
13
+ __all__ = ["LiteralDefinition", "LiteralLibrary"]
@@ -0,0 +1,185 @@
1
+ # statable_gui/libcntrl/literal_management_dialog.py
2
+ """
3
+ Literal management dialog (table format, edit-fixed version)
4
+ """
5
+
6
+ from PySide6.QtWidgets import (
7
+ QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
8
+ QPushButton, QMessageBox, QLineEdit, QFormLayout, QDialogButtonBox,
9
+ QHeaderView
10
+ )
11
+ from PySide6.QtCore import Qt
12
+
13
+ from .literal_library import LiteralLibrary, LiteralDefinition
14
+
15
+
16
+ class LiteralManagementDialog(QDialog):
17
+ """Shared literal management dialog"""
18
+
19
+ def __init__(self, literal_library: LiteralLibrary, parent=None):
20
+ super().__init__(parent)
21
+ self.literal_library = literal_library
22
+
23
+ self.setWindowTitle(self.tr("Literal management"))
24
+ self.setMinimumSize(600, 400)
25
+
26
+ self._setup_ui()
27
+ self._load_table()
28
+
29
+ def _setup_ui(self):
30
+ main_layout = QVBoxLayout(self)
31
+
32
+ self.table = QTableWidget(0, 4)
33
+ self.table.setHorizontalHeaderLabels(["Name", "Value", "Type", "Description"])
34
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
35
+ self.table.setSelectionBehavior(QTableWidget.SelectRows)
36
+ self.table.setSelectionMode(QTableWidget.SingleSelection)
37
+ self.table.setEditTriggers(QTableWidget.NoEditTriggers)
38
+ main_layout.addWidget(self.table)
39
+
40
+ btn_layout = QHBoxLayout()
41
+ add_btn = QPushButton(self.tr("Add"))
42
+ add_btn.clicked.connect(self._add_literal)
43
+ edit_btn = QPushButton(self.tr("Edit"))
44
+ edit_btn.clicked.connect(self._edit_literal)
45
+ delete_btn = QPushButton(self.tr("Delete"))
46
+ delete_btn.clicked.connect(self._delete_literal)
47
+ btn_layout.addWidget(add_btn)
48
+ btn_layout.addWidget(edit_btn)
49
+ btn_layout.addWidget(delete_btn)
50
+ main_layout.addLayout(btn_layout)
51
+
52
+ close_btn = QPushButton(self.tr("Close"))
53
+ close_btn.clicked.connect(self.accept)
54
+ main_layout.addWidget(close_btn)
55
+
56
+ def _load_table(self):
57
+ self.table.setRowCount(0)
58
+ for lit in self.literal_library.list_all():
59
+ row = self.table.rowCount()
60
+ self.table.insertRow(row)
61
+
62
+ name_item = QTableWidgetItem(lit.name)
63
+ name_item.setData(Qt.UserRole, lit.name)
64
+ self.table.setItem(row, 0, name_item)
65
+
66
+ self.table.setItem(row, 1, QTableWidgetItem(lit.value))
67
+ self.table.setItem(row, 2, QTableWidgetItem(lit.literal_type))
68
+ self.table.setItem(row, 3, QTableWidgetItem(lit.description))
69
+
70
+ def _get_selected_literal_name(self):
71
+ row = self.table.currentRow()
72
+ if row < 0:
73
+ return None
74
+ item = self.table.item(row, 0)
75
+ if item:
76
+ return item.data(Qt.UserRole)
77
+ return None
78
+
79
+ def _add_literal(self):
80
+ dialog = LiteralEditDialog(parent=self)
81
+ if dialog.exec() == QDialog.Accepted:
82
+ lit = dialog.get_literal()
83
+ try:
84
+ self.literal_library.add(lit)
85
+ self._load_table()
86
+ except ValueError as e:
87
+ QMessageBox.warning(self, self.tr("Warning"), str(e))
88
+
89
+ def _edit_literal(self):
90
+ name = self._get_selected_literal_name()
91
+ if name is None:
92
+ QMessageBox.information(self, self.tr("Info"), self.tr("Please select a literal to edit."))
93
+ return
94
+
95
+ lit = self.literal_library.get(name)
96
+ if lit is None:
97
+ return
98
+
99
+ dialog = LiteralEditDialog(literal=lit, parent=self)
100
+ if dialog.exec() == QDialog.Accepted:
101
+ new_lit = dialog.get_literal()
102
+
103
+ # On edit, always delete the old entry before adding
104
+ self.literal_library.remove(name)
105
+ try:
106
+ self.literal_library.add(new_lit)
107
+ self._load_table()
108
+ except ValueError as e:
109
+ # If failure occurs, revert
110
+ self.literal_library.add(lit)
111
+ QMessageBox.warning(self, self.tr("Warning"), str(e))
112
+
113
+ def _delete_literal(self):
114
+ name = self._get_selected_literal_name()
115
+ if name is None:
116
+ QMessageBox.information(self, self.tr("Info"), self.tr("Please select a literal to delete."))
117
+ return
118
+
119
+ ret = QMessageBox.warning(
120
+ self,
121
+ self.tr("Confirm"),
122
+ f"Literal '{name}': confirm delete?\n"
123
+ "If any transition condition uses this literal,\n"
124
+ "It must also be removed from the relevant condition expression.",
125
+ QMessageBox.Yes | QMessageBox.No
126
+ )
127
+ if ret == QMessageBox.Yes:
128
+ self.literal_library.remove(name)
129
+ self._load_table()
130
+
131
+
132
+ class LiteralEditDialog(QDialog):
133
+ """Dialog for adding / editing literals"""
134
+
135
+ def __init__(self, literal: LiteralDefinition = None, parent=None):
136
+ super().__init__(parent)
137
+ self.literal = literal
138
+
139
+ self.setWindowTitle("LiteralEdit" if literal else "LiteralAdd")
140
+ self.setMinimumWidth(350)
141
+
142
+ layout = QVBoxLayout(self)
143
+ form = QFormLayout()
144
+
145
+ self.name_edit = QLineEdit()
146
+ if literal:
147
+ self.name_edit.setText(literal.name)
148
+ form.addRow("Name:", self.name_edit)
149
+
150
+ self.value_edit = QLineEdit()
151
+ if literal:
152
+ self.value_edit.setText(literal.value)
153
+ form.addRow("Value:", self.value_edit)
154
+
155
+ self.type_edit = QLineEdit("int")
156
+ if literal:
157
+ self.type_edit.setText(literal.literal_type)
158
+ form.addRow("Type:", self.type_edit)
159
+
160
+ self.desc_edit = QLineEdit()
161
+ if literal:
162
+ self.desc_edit.setText(literal.description)
163
+ form.addRow("Description:", self.desc_edit)
164
+
165
+ layout.addLayout(form)
166
+
167
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
168
+ buttons.accepted.connect(self._on_accept)
169
+ buttons.rejected.connect(self.reject)
170
+ layout.addWidget(buttons)
171
+
172
+ def _on_accept(self):
173
+ name = self.name_edit.text().strip()
174
+ if not name:
175
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a name."))
176
+ return
177
+ self.accept()
178
+
179
+ def get_literal(self) -> LiteralDefinition:
180
+ return LiteralDefinition(
181
+ name=self.name_edit.text().strip(),
182
+ value=self.value_edit.text().strip(),
183
+ literal_type=self.type_edit.text().strip() or "int",
184
+ description=self.desc_edit.text().strip(),
185
+ )
@@ -0,0 +1,145 @@
1
+ # statable_gui/libcntrl/role_function_edit_dialog.py
2
+ """
3
+ Edit role function dialog
4
+ """
5
+
6
+ from typing import List
7
+
8
+ from PySide6.QtWidgets import (
9
+ QDialog, QVBoxLayout, QFormLayout, QLineEdit,
10
+ QListWidget, QListWidgetItem, QDialogButtonBox, QGroupBox
11
+ )
12
+ from PySide6.QtCore import Qt
13
+
14
+ from .role_function_library import RoleFunction
15
+
16
+
17
+ class RoleFunctionEditDialog(QDialog):
18
+ """Shared role function edit dialog"""
19
+
20
+ def __init__(self, role_function: RoleFunction,
21
+ global_vars: List[str] = None,
22
+ events: List[str] = None,
23
+ literals: List[str] = None,
24
+ parent=None):
25
+ super().__init__(parent)
26
+ self.role_function = role_function
27
+ self.global_vars = global_vars or []
28
+ self.events = events or []
29
+ self.literals = literals or []
30
+
31
+ self.setWindowTitle(self.tr("Edit role function"))
32
+ self.setMinimumWidth(500)
33
+
34
+ self._setup_ui()
35
+ self._load_data()
36
+
37
+ def _setup_ui(self):
38
+ main_layout = QVBoxLayout(self)
39
+
40
+ form = QFormLayout()
41
+ self.name_edit = QLineEdit()
42
+ form.addRow("Function name:", self.name_edit)
43
+ self.title_edit = QLineEdit()
44
+ form.addRow("Display name:", self.title_edit)
45
+ self.desc_edit = QLineEdit()
46
+ form.addRow("Description:", self.desc_edit)
47
+ main_layout.addLayout(form)
48
+
49
+ # Used global variables
50
+ global_group = QGroupBox(self.tr("Used global variables"))
51
+ global_layout = QVBoxLayout(global_group)
52
+ self.global_list = QListWidget()
53
+ self.global_list.setSelectionMode(QListWidget.NoSelection)
54
+ for var in self.global_vars:
55
+ item = QListWidgetItem(var)
56
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
57
+ item.setCheckState(Qt.Unchecked)
58
+ self.global_list.addItem(item)
59
+ global_layout.addWidget(self.global_list)
60
+ main_layout.addWidget(global_group)
61
+
62
+ # Used events
63
+ event_group = QGroupBox(self.tr("Used events"))
64
+ event_layout = QVBoxLayout(event_group)
65
+ self.event_list = QListWidget()
66
+ self.event_list.setSelectionMode(QListWidget.NoSelection)
67
+ for event in self.events:
68
+ item = QListWidgetItem(event)
69
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
70
+ item.setCheckState(Qt.Unchecked)
71
+ self.event_list.addItem(item)
72
+ event_layout.addWidget(self.event_list)
73
+ main_layout.addWidget(event_group)
74
+
75
+ # Used literals
76
+ literal_group = QGroupBox(self.tr("Used literals (select from existing)"))
77
+ literal_layout = QVBoxLayout(literal_group)
78
+ self.literal_list = QListWidget()
79
+ self.literal_list.setSelectionMode(QListWidget.NoSelection)
80
+ for lit in self.literals:
81
+ item = QListWidgetItem(lit)
82
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
83
+ item.setCheckState(Qt.Unchecked)
84
+ self.literal_list.addItem(item)
85
+ literal_layout.addWidget(self.literal_list)
86
+ main_layout.addWidget(literal_group)
87
+
88
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
89
+ buttons.accepted.connect(self._on_accept)
90
+ buttons.rejected.connect(self.reject)
91
+ main_layout.addWidget(buttons)
92
+
93
+ def _load_data(self):
94
+ self.name_edit.setText(self.role_function.name)
95
+ self.title_edit.setText(self.role_function.title)
96
+ self.desc_edit.setText(self.role_function.description)
97
+
98
+ for i in range(self.global_list.count()):
99
+ item = self.global_list.item(i)
100
+ if item.text() in self.role_function.used_global_vars:
101
+ item.setCheckState(Qt.Checked)
102
+
103
+ for i in range(self.event_list.count()):
104
+ item = self.event_list.item(i)
105
+ if item.text() in self.role_function.used_events:
106
+ item.setCheckState(Qt.Checked)
107
+
108
+ for i in range(self.literal_list.count()):
109
+ item = self.literal_list.item(i)
110
+ if item.text() in self.role_function.used_literals:
111
+ item.setCheckState(Qt.Checked)
112
+
113
+ def _on_accept(self):
114
+ name = self.name_edit.text().strip()
115
+ if not name:
116
+ name = "NewRoleFunction"
117
+ self.role_function.name = name
118
+ self.role_function.title = self.title_edit.text().strip() or name
119
+ self.role_function.description = self.desc_edit.text().strip()
120
+
121
+ used_global_vars = []
122
+ for i in range(self.global_list.count()):
123
+ item = self.global_list.item(i)
124
+ if item.checkState() == Qt.Checked:
125
+ used_global_vars.append(item.text())
126
+ self.role_function.used_global_vars = used_global_vars
127
+
128
+ used_events = []
129
+ for i in range(self.event_list.count()):
130
+ item = self.event_list.item(i)
131
+ if item.checkState() == Qt.Checked:
132
+ used_events.append(item.text())
133
+ self.role_function.used_events = used_events
134
+
135
+ used_literals = []
136
+ for i in range(self.literal_list.count()):
137
+ item = self.literal_list.item(i)
138
+ if item.checkState() == Qt.Checked:
139
+ used_literals.append(item.text())
140
+ self.role_function.used_literals = used_literals
141
+
142
+ self.accept()
143
+
144
+ def get_role_function(self) -> RoleFunction:
145
+ return self.role_function
@@ -0,0 +1,13 @@
1
+ """[v3.0 / G-1a] Backward-compat shim.
2
+
3
+ Content moved to: statable.shared.role_function_library
4
+
5
+ Old imports under ``statable_gui.libcntrl`` keep working via re-export.
6
+ New code should import from ``statable.shared`` instead.
7
+ """
8
+ from statable.shared.role_function_library import ( # noqa: F401
9
+ RoleFunction,
10
+ RoleFunctionLibrary,
11
+ )
12
+
13
+ __all__ = ["RoleFunction", "RoleFunctionLibrary"]
@@ -0,0 +1,62 @@
1
+ # statable_gui/literal_definition_dialog.py
2
+ """New literal definition dialog (R-7 support).
3
+
4
+ Simple dialog for creating a LiteralDefinition, used by
5
+ RoleFunctionDialog's "+ New Literal" button.
6
+ """
7
+
8
+ from PySide6.QtWidgets import (
9
+ QDialog, QVBoxLayout, QFormLayout, QLineEdit, QComboBox,
10
+ QDialogButtonBox, QMessageBox,
11
+ )
12
+
13
+ from statable_gui.libcntrl.literal_library import LiteralDefinition
14
+
15
+
16
+ class NewLiteralDialog(QDialog):
17
+ """Dialog to create a new LiteralDefinition."""
18
+
19
+ TYPES = ["int", "float", "bool", "string"]
20
+
21
+ def __init__(self, parent=None):
22
+ super().__init__(parent)
23
+ self.setWindowTitle(self.tr("New Literal"))
24
+ self.setMinimumWidth(400)
25
+
26
+ layout = QVBoxLayout(self)
27
+ form = QFormLayout()
28
+
29
+ self.name_edit = QLineEdit()
30
+ self.value_edit = QLineEdit()
31
+ self.type_combo = QComboBox()
32
+ self.type_combo.addItems(self.TYPES)
33
+ self.desc_edit = QLineEdit()
34
+
35
+ form.addRow("Name:", self.name_edit)
36
+ form.addRow("Value:", self.value_edit)
37
+ form.addRow("Type:", self.type_combo)
38
+ form.addRow("Description:", self.desc_edit)
39
+ layout.addLayout(form)
40
+
41
+ buttons = QDialogButtonBox(
42
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
43
+ buttons.accepted.connect(self._on_accept)
44
+ buttons.rejected.connect(self.reject)
45
+ layout.addWidget(buttons)
46
+
47
+ def _on_accept(self):
48
+ if not self.name_edit.text().strip():
49
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("Name is required."))
50
+ return
51
+ if not self.value_edit.text().strip():
52
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("Value is required."))
53
+ return
54
+ self.accept()
55
+
56
+ def get_literal(self) -> LiteralDefinition:
57
+ return LiteralDefinition(
58
+ name=self.name_edit.text().strip(),
59
+ value=self.value_edit.text().strip(),
60
+ literal_type=self.type_combo.currentText(),
61
+ description=self.desc_edit.text().strip(),
62
+ )