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.
- codegen/__init__.py +38 -0
- codegen/c_code_generator.py +1752 -0
- codegen/code_merger.py +371 -0
- codegen/code_templates.py +509 -0
- codegen/config.py +167 -0
- codegen/enum_generator.py +377 -0
- codegen/event_queue_generator.py +303 -0
- codegen/interrupt_generator.py +345 -0
- codegen/naming_convention.py +171 -0
- codegen/osal_generator.py +282 -0
- codegen/role_function_generator.py +1244 -0
- codegen/sample_data.py +269 -0
- codegen/state_actions_generator.py +395 -0
- codegen/struct_generator.py +721 -0
- codegen/timer_generator.py +299 -0
- codegen/transition_generator.py +907 -0
- codegen/type_mapper.py +69 -0
- codegen/validate/__init__.py +38 -0
- codegen/validate/change_actions.py +90 -0
- codegen/validate/change_applier.py +416 -0
- codegen/validate/clipboard_manager.py +47 -0
- codegen/validate/data/__init__.py +30 -0
- codegen/validate/data/action_definitions.py +200 -0
- codegen/validate/data/keywords.py +46 -0
- codegen/validate/data/prompt_templates.py +176 -0
- codegen/validate/data/validation_rules.py +205 -0
- codegen/validate/items/__init__.py +30 -0
- codegen/validate/items/base_validator.py +25 -0
- codegen/validate/items/cell_validator.py +275 -0
- codegen/validate/items/custom_type_validator.py +75 -0
- codegen/validate/items/event_validator.py +78 -0
- codegen/validate/items/flag_validator.py +74 -0
- codegen/validate/items/interrupt_validator.py +75 -0
- codegen/validate/items/queue_validator.py +73 -0
- codegen/validate/items/role_function_validator.py +175 -0
- codegen/validate/items/state_validator.py +88 -0
- codegen/validate/items/timer_validator.py +120 -0
- codegen/validate/items/transition_validator.py +113 -0
- codegen/validate/items/variable_validator.py +132 -0
- codegen/validate/logger.py +52 -0
- codegen/validate/models.py +177 -0
- codegen/validate/prompt_generator.py +724 -0
- codegen/validate/response_parser.py +154 -0
- codegen/validate/response_validator.py +275 -0
- codegen/validate/validation_dialog.py +322 -0
- codegen/validate/validator.py +94 -0
- codegen/variable_generator.py +419 -0
- statable/__init__.py +71 -0
- statable/__main__.py +49 -0
- statable/cli.py +251 -0
- statable/global_defs.py +256 -0
- statable/mermaid_gen.py +144 -0
- statable/model.py +426 -0
- statable/parser.py +64 -0
- statable/sample_data.py +435 -0
- statable/shared/__init__.py +24 -0
- statable/shared/condition_library.py +63 -0
- statable/shared/literal_library.py +66 -0
- statable/shared/role_function_library.py +150 -0
- statable/state_machine.py +121 -0
- statable/xml_io.py +1164 -0
- statable-3.1.0.dist-info/METADATA +151 -0
- statable-3.1.0.dist-info/RECORD +117 -0
- statable-3.1.0.dist-info/WHEEL +5 -0
- statable-3.1.0.dist-info/entry_points.txt +2 -0
- statable-3.1.0.dist-info/top_level.txt +3 -0
- statable_gui/__init__.py +23 -0
- statable_gui/action_edit_dialog.py +214 -0
- statable_gui/code_generation_dialog.py +486 -0
- statable_gui/code_generation_settings_dialog.py +522 -0
- statable_gui/common_widgets.py +537 -0
- statable_gui/condition_builder_dialog.py +500 -0
- statable_gui/condition_edit_dialog.py +123 -0
- statable_gui/condition_template_dialog.py +58 -0
- statable_gui/config.py +21 -0
- statable_gui/dialogs.py +439 -0
- statable_gui/event_definition_dialog.py +605 -0
- statable_gui/event_delivery_settings_dialog.py +191 -0
- statable_gui/event_queue_dialog.py +245 -0
- statable_gui/global_defs.py +167 -0
- statable_gui/global_defs_dialog.py +796 -0
- statable_gui/i18n/__init__.py +118 -0
- statable_gui/interrupt_handler_edit_dialog.py +810 -0
- statable_gui/layer_settings_dialog.py +180 -0
- statable_gui/libcntrl/__init__.py +23 -0
- statable_gui/libcntrl/condition_library.py +13 -0
- statable_gui/libcntrl/literal_library.py +13 -0
- statable_gui/libcntrl/literal_management_dialog.py +185 -0
- statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
- statable_gui/libcntrl/role_function_library.py +13 -0
- statable_gui/literal_definition_dialog.py +62 -0
- statable_gui/logger.py +83 -0
- statable_gui/main_window.py +1380 -0
- statable_gui/matrix_table.py +413 -0
- statable_gui/preference_keys.py +18 -0
- statable_gui/preferences.py +74 -0
- statable_gui/role_function_dialog.py +244 -0
- statable_gui/sample_data.py +11 -0
- statable_gui/state_actions_dialog.py +398 -0
- statable_gui/symbol_picker.py +170 -0
- statable_gui/traceball.py +37 -0
- statable_gui/transition_editor_direct/__init__.py +15 -0
- statable_gui/transition_editor_direct/actions_tab.py +856 -0
- statable_gui/transition_editor_direct/code_widget.py +344 -0
- statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
- statable_gui/transition_editor_direct/dialog.py +335 -0
- statable_gui/transition_editor_direct/draft.py +270 -0
- statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
- statable_gui/transition_editor_direct/overview_tab.py +173 -0
- statable_gui/transition_editor_direct/palette_widget.py +233 -0
- statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
- statable_gui/transition_editor_direct/relations_tab.py +230 -0
- statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
- statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
- statable_gui/transition_editor_direct/transitions_tab.py +620 -0
- statable_gui/validation_dialog.py +285 -0
- 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
|
+
)
|