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,537 @@
|
|
|
1
|
+
"""Common UI components"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
from PySide6.QtGui import QFont
|
|
7
|
+
from PySide6.QtWidgets import (
|
|
8
|
+
QComboBox, QWidget, QHBoxLayout, QVBoxLayout, QLineEdit, QLabel,
|
|
9
|
+
QPushButton, QDialog, QTableWidget, QTableWidgetItem, QHeaderView,
|
|
10
|
+
QMessageBox, QAbstractItemView, QFormLayout, QDialogButtonBox,
|
|
11
|
+
QSpinBox, QCheckBox
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from statable.global_defs import GlobalDefinitions, CustomTypeDef, StructMemberDef
|
|
15
|
+
from .logger import StaTableLogger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TitleEditWidget(QWidget):
|
|
19
|
+
"""Title input widget"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, parent=None, title="", placeholder="Label shown in the list (auto-set if empty)"):
|
|
22
|
+
super().__init__(parent)
|
|
23
|
+
layout = QHBoxLayout(self)
|
|
24
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
25
|
+
|
|
26
|
+
self.label = QLabel(self.tr("Title *"))
|
|
27
|
+
self.label.setFont(QFont("sans-serif", 10, QFont.Bold))
|
|
28
|
+
layout.addWidget(self.label)
|
|
29
|
+
|
|
30
|
+
self.edit = QLineEdit()
|
|
31
|
+
self.edit.setText(title)
|
|
32
|
+
self.edit.setPlaceholderText(placeholder)
|
|
33
|
+
self.edit.setToolTip(self.tr("Enter the title of this item. If empty, a provisional title is set automatically."))
|
|
34
|
+
layout.addWidget(self.edit, stretch=1)
|
|
35
|
+
|
|
36
|
+
def get_title(self) -> str:
|
|
37
|
+
return self.edit.text().strip()
|
|
38
|
+
|
|
39
|
+
def set_title(self, title: str):
|
|
40
|
+
self.edit.setText(title)
|
|
41
|
+
|
|
42
|
+
def ensure_title(self, auto_title: str) -> str:
|
|
43
|
+
if not self.edit.text().strip():
|
|
44
|
+
self.edit.setText(auto_title)
|
|
45
|
+
return self.edit.text().strip()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TypeComboBox(QWidget):
|
|
49
|
+
"""Combo box for type selection"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, parent=None, global_defs: Optional[GlobalDefinitions] = None):
|
|
52
|
+
super().__init__(parent)
|
|
53
|
+
self.global_defs = global_defs if global_defs else GlobalDefinitions()
|
|
54
|
+
|
|
55
|
+
layout = QHBoxLayout(self)
|
|
56
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
57
|
+
|
|
58
|
+
self.combo = QComboBox()
|
|
59
|
+
self.combo.setEditable(True)
|
|
60
|
+
self._refresh_types()
|
|
61
|
+
layout.addWidget(self.combo, stretch=1)
|
|
62
|
+
|
|
63
|
+
add_btn = QPushButton(self.tr("Add..."))
|
|
64
|
+
add_btn.setToolTip(self.tr("Add / edit user-defined types"))
|
|
65
|
+
add_btn.clicked.connect(self._open_type_manager)
|
|
66
|
+
layout.addWidget(add_btn)
|
|
67
|
+
|
|
68
|
+
def _refresh_types(self):
|
|
69
|
+
current = self.combo.currentText()
|
|
70
|
+
self.combo.clear()
|
|
71
|
+
basic_types = [
|
|
72
|
+
"uint8_t", "uint16_t", "uint32_t", "uint64_t",
|
|
73
|
+
"int8_t", "int16_t", "int32_t", "int64_t",
|
|
74
|
+
"float", "double", "bool", "char", "void",
|
|
75
|
+
"volatile uint8_t", "volatile uint16_t",
|
|
76
|
+
"volatile uint32_t", "volatile uint64_t",
|
|
77
|
+
]
|
|
78
|
+
self.combo.addItems(basic_types)
|
|
79
|
+
for ct in self.global_defs.custom_types:
|
|
80
|
+
self.combo.addItem(ct.name)
|
|
81
|
+
if current:
|
|
82
|
+
idx = self.combo.findText(current)
|
|
83
|
+
if idx >= 0:
|
|
84
|
+
self.combo.setCurrentIndex(idx)
|
|
85
|
+
|
|
86
|
+
def _open_type_manager(self):
|
|
87
|
+
dlg = TypeManagerDialog(self, self.global_defs)
|
|
88
|
+
dlg.exec()
|
|
89
|
+
self._refresh_types()
|
|
90
|
+
|
|
91
|
+
def current_text(self) -> str:
|
|
92
|
+
return self.combo.currentText().strip()
|
|
93
|
+
|
|
94
|
+
def set_current_text(self, text: str):
|
|
95
|
+
idx = self.combo.findText(text)
|
|
96
|
+
if idx >= 0:
|
|
97
|
+
self.combo.setCurrentIndex(idx)
|
|
98
|
+
else:
|
|
99
|
+
self.combo.setCurrentText(text)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class GroupComboBox(QWidget):
|
|
103
|
+
"""Combo box for group selection"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, parent=None, groups: Optional[List[str]] = None):
|
|
106
|
+
super().__init__(parent)
|
|
107
|
+
self.groups = groups or []
|
|
108
|
+
|
|
109
|
+
layout = QHBoxLayout(self)
|
|
110
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
111
|
+
|
|
112
|
+
self.combo = QComboBox()
|
|
113
|
+
self.combo.setEditable(True)
|
|
114
|
+
self._refresh_groups()
|
|
115
|
+
layout.addWidget(self.combo, stretch=1)
|
|
116
|
+
|
|
117
|
+
add_btn = QPushButton(self.tr("Add..."))
|
|
118
|
+
add_btn.setToolTip(self.tr("Add a new group"))
|
|
119
|
+
add_btn.clicked.connect(self._add_group)
|
|
120
|
+
layout.addWidget(add_btn)
|
|
121
|
+
|
|
122
|
+
def _refresh_groups(self):
|
|
123
|
+
current = self.combo.currentText()
|
|
124
|
+
self.combo.clear()
|
|
125
|
+
self.combo.addItem(self.tr(""))
|
|
126
|
+
self.combo.addItems(self.groups)
|
|
127
|
+
if current:
|
|
128
|
+
idx = self.combo.findText(current)
|
|
129
|
+
if idx >= 0:
|
|
130
|
+
self.combo.setCurrentIndex(idx)
|
|
131
|
+
|
|
132
|
+
def _add_group(self):
|
|
133
|
+
dialog = GroupAddDialog(self)
|
|
134
|
+
if dialog.exec() == QDialog.Accepted:
|
|
135
|
+
new_group = dialog.get_group_name()
|
|
136
|
+
if new_group and new_group not in self.groups:
|
|
137
|
+
self.groups.append(new_group)
|
|
138
|
+
self._refresh_groups()
|
|
139
|
+
self.combo.setCurrentText(new_group)
|
|
140
|
+
|
|
141
|
+
def current_text(self) -> str:
|
|
142
|
+
return self.combo.currentText().strip()
|
|
143
|
+
|
|
144
|
+
def set_current_text(self, text: str):
|
|
145
|
+
idx = self.combo.findText(text)
|
|
146
|
+
if idx >= 0:
|
|
147
|
+
self.combo.setCurrentIndex(idx)
|
|
148
|
+
else:
|
|
149
|
+
self.combo.setCurrentText(text)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class EventComboBox(QComboBox):
|
|
153
|
+
"""Combo box for event selection"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, parent=None, event_names: Optional[List[str]] = None):
|
|
156
|
+
super().__init__(parent)
|
|
157
|
+
self.setEditable(True)
|
|
158
|
+
self.setInsertPolicy(QComboBox.NoInsert)
|
|
159
|
+
self.addItem(self.tr(""))
|
|
160
|
+
if event_names:
|
|
161
|
+
self.addItems(event_names)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class StateComboBox(QComboBox):
|
|
165
|
+
"""Combo box for state selection"""
|
|
166
|
+
|
|
167
|
+
def __init__(self, parent=None, state_names: Optional[List[str]] = None):
|
|
168
|
+
super().__init__(parent)
|
|
169
|
+
self.setEditable(False)
|
|
170
|
+
self.addItem(self.tr(""))
|
|
171
|
+
if state_names:
|
|
172
|
+
self.addItems(state_names)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class GroupAddDialog(QDialog):
|
|
176
|
+
"""Group add dialog"""
|
|
177
|
+
|
|
178
|
+
def __init__(self, parent=None):
|
|
179
|
+
super().__init__(parent)
|
|
180
|
+
self.setWindowTitle(self.tr("GroupAdd"))
|
|
181
|
+
self.setMinimumWidth(350)
|
|
182
|
+
|
|
183
|
+
layout = QVBoxLayout(self)
|
|
184
|
+
form = QFormLayout()
|
|
185
|
+
layout.addLayout(form)
|
|
186
|
+
|
|
187
|
+
self.name_edit = QLineEdit()
|
|
188
|
+
self.name_edit.setPlaceholderText(self.tr("Enter a new group name"))
|
|
189
|
+
form.addRow("Group name", self.name_edit)
|
|
190
|
+
|
|
191
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
192
|
+
buttons.accepted.connect(self._on_accept)
|
|
193
|
+
buttons.rejected.connect(self.reject)
|
|
194
|
+
layout.addWidget(buttons)
|
|
195
|
+
|
|
196
|
+
def _on_accept(self):
|
|
197
|
+
if not self.name_edit.text().strip():
|
|
198
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a group name."))
|
|
199
|
+
return
|
|
200
|
+
self.accept()
|
|
201
|
+
|
|
202
|
+
def get_group_name(self) -> str:
|
|
203
|
+
return self.name_edit.text().strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TypeManagerDialog(QDialog):
|
|
207
|
+
"""User-defined type management dialog"""
|
|
208
|
+
|
|
209
|
+
def __init__(self, parent=None, global_defs: Optional[GlobalDefinitions] = None):
|
|
210
|
+
super().__init__(parent)
|
|
211
|
+
self.global_defs = global_defs if global_defs else GlobalDefinitions()
|
|
212
|
+
self.setWindowTitle(self.tr("User-defined type management"))
|
|
213
|
+
self.setMinimumSize(800, 500)
|
|
214
|
+
|
|
215
|
+
layout = QVBoxLayout(self)
|
|
216
|
+
|
|
217
|
+
self.table = QTableWidget(0, 3)
|
|
218
|
+
self.table.setHorizontalHeaderLabels(["Title", "Type name", "Member count"])
|
|
219
|
+
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
220
|
+
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
221
|
+
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
222
|
+
self.table.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
223
|
+
self.table.cellDoubleClicked.connect(self._on_double_clicked)
|
|
224
|
+
layout.addWidget(self.table, stretch=1)
|
|
225
|
+
|
|
226
|
+
btn_layout = QHBoxLayout()
|
|
227
|
+
add_btn = QPushButton(self.tr("Add"))
|
|
228
|
+
add_btn.clicked.connect(self._add_type)
|
|
229
|
+
edit_btn = QPushButton(self.tr("Edit"))
|
|
230
|
+
edit_btn.clicked.connect(self._edit_type)
|
|
231
|
+
del_btn = QPushButton(self.tr("Delete"))
|
|
232
|
+
del_btn.clicked.connect(self._delete_type)
|
|
233
|
+
btn_layout.addWidget(add_btn)
|
|
234
|
+
btn_layout.addWidget(edit_btn)
|
|
235
|
+
btn_layout.addWidget(del_btn)
|
|
236
|
+
btn_layout.addStretch()
|
|
237
|
+
layout.addLayout(btn_layout)
|
|
238
|
+
|
|
239
|
+
close_btn = QPushButton(self.tr("Close"))
|
|
240
|
+
close_btn.clicked.connect(self.accept)
|
|
241
|
+
layout.addWidget(close_btn, alignment=Qt.AlignRight)
|
|
242
|
+
|
|
243
|
+
self._refresh_table()
|
|
244
|
+
StaTableLogger.debug("TypeManagerDialog initialized")
|
|
245
|
+
|
|
246
|
+
def _refresh_table(self):
|
|
247
|
+
self.table.setRowCount(0)
|
|
248
|
+
for ct in self.global_defs.custom_types:
|
|
249
|
+
row = self.table.rowCount()
|
|
250
|
+
self.table.insertRow(row)
|
|
251
|
+
self.table.setItem(row, 0, QTableWidgetItem(ct.title))
|
|
252
|
+
self.table.setItem(row, 1, QTableWidgetItem(ct.name))
|
|
253
|
+
self.table.setItem(row, 2, QTableWidgetItem(str(len(ct.members))))
|
|
254
|
+
|
|
255
|
+
def _get_selected_type(self) -> Optional[CustomTypeDef]:
|
|
256
|
+
row = self.table.currentRow()
|
|
257
|
+
if 0 <= row < len(self.global_defs.custom_types):
|
|
258
|
+
return self.global_defs.custom_types[row]
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
def _on_double_clicked(self, row, col):
|
|
262
|
+
self._edit_type()
|
|
263
|
+
|
|
264
|
+
def _add_type(self):
|
|
265
|
+
dlg = TypeEditDialog(self)
|
|
266
|
+
if dlg.exec() == QDialog.Accepted:
|
|
267
|
+
new_type = dlg.get_custom_type()
|
|
268
|
+
if not new_type.name:
|
|
269
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a type name."))
|
|
270
|
+
return
|
|
271
|
+
if any(t.name == new_type.name for t in self.global_defs.custom_types):
|
|
272
|
+
QMessageBox.warning(self, self.tr("Warning"), f"Type '{new_type.name}' already exists.")
|
|
273
|
+
return
|
|
274
|
+
self.global_defs.custom_types.append(new_type)
|
|
275
|
+
self._refresh_table()
|
|
276
|
+
StaTableLogger.info(f"Custom type added: {new_type.name}")
|
|
277
|
+
|
|
278
|
+
def _edit_type(self):
|
|
279
|
+
target = self._get_selected_type()
|
|
280
|
+
if not target:
|
|
281
|
+
return
|
|
282
|
+
dlg = TypeEditDialog(self, custom_type=target)
|
|
283
|
+
if dlg.exec() == QDialog.Accepted:
|
|
284
|
+
new_type = dlg.get_custom_type()
|
|
285
|
+
if not new_type.name:
|
|
286
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a type name."))
|
|
287
|
+
return
|
|
288
|
+
target.name = new_type.name
|
|
289
|
+
target.description = new_type.description
|
|
290
|
+
target.title = new_type.title
|
|
291
|
+
target.members = new_type.members
|
|
292
|
+
self._refresh_table()
|
|
293
|
+
StaTableLogger.info(f"Custom type updated: {target.name}")
|
|
294
|
+
|
|
295
|
+
def _delete_type(self):
|
|
296
|
+
target = self._get_selected_type()
|
|
297
|
+
if not target:
|
|
298
|
+
return
|
|
299
|
+
reply = QMessageBox.question(
|
|
300
|
+
self, self.tr("Confirm"), f"Type '{target.title}': confirm delete?",
|
|
301
|
+
QMessageBox.Yes | QMessageBox.No
|
|
302
|
+
)
|
|
303
|
+
if reply == QMessageBox.Yes:
|
|
304
|
+
self.global_defs.custom_types.remove(target)
|
|
305
|
+
self._refresh_table()
|
|
306
|
+
StaTableLogger.info(f"Custom type deleted: {target.name}")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class TypeEditDialog(QDialog):
|
|
310
|
+
"""User-defined type edit dialog"""
|
|
311
|
+
|
|
312
|
+
def __init__(self, parent=None, custom_type: Optional[CustomTypeDef] = None):
|
|
313
|
+
super().__init__(parent)
|
|
314
|
+
self.custom_type = custom_type if custom_type else CustomTypeDef(name="", title="")
|
|
315
|
+
self.setWindowTitle(self.tr("Edit user-defined type"))
|
|
316
|
+
self.setMinimumSize(700, 500)
|
|
317
|
+
|
|
318
|
+
layout = QVBoxLayout(self)
|
|
319
|
+
form = QFormLayout()
|
|
320
|
+
layout.addLayout(form)
|
|
321
|
+
|
|
322
|
+
self.title_edit = QLineEdit(self.custom_type.title)
|
|
323
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
324
|
+
form.addRow("Title *", self.title_edit)
|
|
325
|
+
|
|
326
|
+
self.name_edit = QLineEdit(self.custom_type.name)
|
|
327
|
+
form.addRow("Type name", self.name_edit)
|
|
328
|
+
|
|
329
|
+
self.desc_edit = QLineEdit(self.custom_type.description)
|
|
330
|
+
form.addRow("Description", self.desc_edit)
|
|
331
|
+
|
|
332
|
+
layout.addWidget(QLabel(self.tr("Member list:")))
|
|
333
|
+
self.member_table = QTableWidget(0, 5)
|
|
334
|
+
self.member_table.setHorizontalHeaderLabels(["Member name", "Type", "Bit width", "Array", "Description"])
|
|
335
|
+
self.member_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
336
|
+
self.member_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
337
|
+
self.member_table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
338
|
+
self.member_table.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
339
|
+
self.member_table.cellDoubleClicked.connect(self._edit_member)
|
|
340
|
+
layout.addWidget(self.member_table, stretch=1)
|
|
341
|
+
|
|
342
|
+
btn_layout = QHBoxLayout()
|
|
343
|
+
add_btn = QPushButton(self.tr("Add member"))
|
|
344
|
+
add_btn.clicked.connect(self._add_member)
|
|
345
|
+
edit_btn = QPushButton(self.tr("Edit member"))
|
|
346
|
+
edit_btn.clicked.connect(self._edit_member)
|
|
347
|
+
del_btn = QPushButton(self.tr("Delete member"))
|
|
348
|
+
del_btn.clicked.connect(self._delete_member)
|
|
349
|
+
btn_layout.addWidget(add_btn)
|
|
350
|
+
btn_layout.addWidget(edit_btn)
|
|
351
|
+
btn_layout.addWidget(del_btn)
|
|
352
|
+
btn_layout.addStretch()
|
|
353
|
+
layout.addLayout(btn_layout)
|
|
354
|
+
|
|
355
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
356
|
+
buttons.accepted.connect(self._on_accept)
|
|
357
|
+
buttons.rejected.connect(self.reject)
|
|
358
|
+
layout.addWidget(buttons)
|
|
359
|
+
|
|
360
|
+
self._refresh_member_table()
|
|
361
|
+
StaTableLogger.debug("TypeEditDialog initialized")
|
|
362
|
+
|
|
363
|
+
def _refresh_member_table(self):
|
|
364
|
+
self.member_table.setRowCount(0)
|
|
365
|
+
for m in self.custom_type.members:
|
|
366
|
+
row = self.member_table.rowCount()
|
|
367
|
+
self.member_table.insertRow(row)
|
|
368
|
+
self.member_table.setItem(row, 0, QTableWidgetItem(m.name))
|
|
369
|
+
self.member_table.setItem(row, 1, QTableWidgetItem(m.data_type))
|
|
370
|
+
self.member_table.setItem(row, 2, QTableWidgetItem(str(m.bit_width) if m.bit_width > 0 else "-"))
|
|
371
|
+
self.member_table.setItem(row, 3, QTableWidgetItem(str(m.array_size) if m.array_size > 0 else "-"))
|
|
372
|
+
self.member_table.setItem(row, 4, QTableWidgetItem(m.description))
|
|
373
|
+
|
|
374
|
+
def _get_selected_member(self) -> Optional[StructMemberDef]:
|
|
375
|
+
row = self.member_table.currentRow()
|
|
376
|
+
if 0 <= row < len(self.custom_type.members):
|
|
377
|
+
return self.custom_type.members[row]
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def _add_member(self):
|
|
381
|
+
dlg = StructMemberEditDialog(self)
|
|
382
|
+
if dlg.exec() == QDialog.Accepted:
|
|
383
|
+
member = dlg.get_member()
|
|
384
|
+
if not member.name:
|
|
385
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a member name."))
|
|
386
|
+
return
|
|
387
|
+
if any(m.name == member.name for m in self.custom_type.members):
|
|
388
|
+
QMessageBox.warning(self, self.tr("Warning"), f"Member '{member.name}' already exists.")
|
|
389
|
+
return
|
|
390
|
+
self.custom_type.members.append(member)
|
|
391
|
+
self._refresh_member_table()
|
|
392
|
+
StaTableLogger.info(f"Member added: {member.name}")
|
|
393
|
+
|
|
394
|
+
def _edit_member(self):
|
|
395
|
+
target = self._get_selected_member()
|
|
396
|
+
if not target:
|
|
397
|
+
return
|
|
398
|
+
dlg = StructMemberEditDialog(self, member=target)
|
|
399
|
+
if dlg.exec() == QDialog.Accepted:
|
|
400
|
+
new_member = dlg.get_member()
|
|
401
|
+
if not new_member.name:
|
|
402
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a member name."))
|
|
403
|
+
return
|
|
404
|
+
target.name = new_member.name
|
|
405
|
+
target.data_type = new_member.data_type
|
|
406
|
+
target.bit_width = new_member.bit_width
|
|
407
|
+
target.description = new_member.description
|
|
408
|
+
target.title = new_member.title
|
|
409
|
+
target.array_size = new_member.array_size
|
|
410
|
+
self._refresh_member_table()
|
|
411
|
+
StaTableLogger.info(f"Member updated: {target.name}")
|
|
412
|
+
|
|
413
|
+
def _delete_member(self):
|
|
414
|
+
target = self._get_selected_member()
|
|
415
|
+
if not target:
|
|
416
|
+
return
|
|
417
|
+
self.custom_type.members.remove(target)
|
|
418
|
+
self._refresh_member_table()
|
|
419
|
+
StaTableLogger.info(f"Member deleted: {target.name}")
|
|
420
|
+
|
|
421
|
+
def _on_accept(self):
|
|
422
|
+
if not self.title_edit.text().strip():
|
|
423
|
+
self.title_edit.setText(f"Type: {self.name_edit.text().strip() or '(unnamed)'}")
|
|
424
|
+
if not self.name_edit.text().strip():
|
|
425
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a type name."))
|
|
426
|
+
return
|
|
427
|
+
self.accept()
|
|
428
|
+
|
|
429
|
+
def get_custom_type(self) -> CustomTypeDef:
|
|
430
|
+
return CustomTypeDef(
|
|
431
|
+
name=self.name_edit.text().strip(),
|
|
432
|
+
description=self.desc_edit.text().strip(),
|
|
433
|
+
members=list(self.custom_type.members),
|
|
434
|
+
title=self.title_edit.text().strip(),
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class StructMemberEditDialog(QDialog):
|
|
439
|
+
"""Struct member edit dialog"""
|
|
440
|
+
|
|
441
|
+
def __init__(self, parent=None, member: Optional[StructMemberDef] = None):
|
|
442
|
+
super().__init__(parent)
|
|
443
|
+
self.member = member if member else StructMemberDef(name="", data_type="uint8_t")
|
|
444
|
+
self.setWindowTitle(self.tr("Struct member edit"))
|
|
445
|
+
self.setMinimumWidth(450)
|
|
446
|
+
|
|
447
|
+
layout = QVBoxLayout(self)
|
|
448
|
+
form = QFormLayout()
|
|
449
|
+
layout.addLayout(form)
|
|
450
|
+
|
|
451
|
+
self.title_edit = QLineEdit(self.member.title)
|
|
452
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
453
|
+
form.addRow("Title *", self.title_edit)
|
|
454
|
+
|
|
455
|
+
self.name_edit = QLineEdit(self.member.name)
|
|
456
|
+
form.addRow("Member name", self.name_edit)
|
|
457
|
+
|
|
458
|
+
self.type_combo = QComboBox()
|
|
459
|
+
self.type_combo.setEditable(True)
|
|
460
|
+
self.type_combo.addItems([
|
|
461
|
+
"uint8_t", "uint16_t", "uint32_t", "uint64_t",
|
|
462
|
+
"int8_t", "int16_t", "int32_t", "int64_t",
|
|
463
|
+
"float", "double", "bool", "char",
|
|
464
|
+
])
|
|
465
|
+
idx = self.type_combo.findText(self.member.data_type)
|
|
466
|
+
if idx >= 0:
|
|
467
|
+
self.type_combo.setCurrentIndex(idx)
|
|
468
|
+
else:
|
|
469
|
+
self.type_combo.setCurrentText(self.member.data_type)
|
|
470
|
+
form.addRow("Type", self.type_combo)
|
|
471
|
+
|
|
472
|
+
# Bit field
|
|
473
|
+
self.bitfield_check = QCheckBox(self.tr("Use bit fields"))
|
|
474
|
+
self.bitfield_check.setChecked(self.member.bit_width > 0)
|
|
475
|
+
form.addRow("", self.bitfield_check)
|
|
476
|
+
|
|
477
|
+
self.bit_width_spin = QSpinBox()
|
|
478
|
+
self.bit_width_spin.setRange(1, 64)
|
|
479
|
+
self.bit_width_spin.setValue(self.member.bit_width if self.member.bit_width > 0 else 1)
|
|
480
|
+
form.addRow("Bit width", self.bit_width_spin)
|
|
481
|
+
|
|
482
|
+
# Array
|
|
483
|
+
self.array_check = QCheckBox(self.tr("Use array"))
|
|
484
|
+
self.array_check.setChecked(self.member.array_size > 0)
|
|
485
|
+
form.addRow("", self.array_check)
|
|
486
|
+
|
|
487
|
+
self.array_size_spin = QSpinBox()
|
|
488
|
+
self.array_size_spin.setRange(1, 65536)
|
|
489
|
+
self.array_size_spin.setValue(self.member.array_size if self.member.array_size > 0 else 1)
|
|
490
|
+
form.addRow("Array size", self.array_size_spin)
|
|
491
|
+
|
|
492
|
+
self.bitfield_check.toggled.connect(self._on_bitfield_toggled)
|
|
493
|
+
self.array_check.toggled.connect(self._on_array_toggled)
|
|
494
|
+
self._on_bitfield_toggled(self.bitfield_check.isChecked())
|
|
495
|
+
self._on_array_toggled(self.array_check.isChecked())
|
|
496
|
+
|
|
497
|
+
self.desc_edit = QLineEdit(self.member.description)
|
|
498
|
+
form.addRow("Description", self.desc_edit)
|
|
499
|
+
|
|
500
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
501
|
+
buttons.accepted.connect(self._on_accept)
|
|
502
|
+
buttons.rejected.connect(self.reject)
|
|
503
|
+
layout.addWidget(buttons)
|
|
504
|
+
|
|
505
|
+
StaTableLogger.debug("StructMemberEditDialog initialized")
|
|
506
|
+
|
|
507
|
+
def _on_bitfield_toggled(self, checked: bool):
|
|
508
|
+
self.bit_width_spin.setEnabled(checked)
|
|
509
|
+
if checked:
|
|
510
|
+
self.array_check.setChecked(False)
|
|
511
|
+
|
|
512
|
+
def _on_array_toggled(self, checked: bool):
|
|
513
|
+
self.array_size_spin.setEnabled(checked)
|
|
514
|
+
if checked:
|
|
515
|
+
self.bitfield_check.setChecked(False)
|
|
516
|
+
|
|
517
|
+
def _on_accept(self):
|
|
518
|
+
if not self.title_edit.text().strip():
|
|
519
|
+
if self.bitfield_check.isChecked():
|
|
520
|
+
self.title_edit.setText(f"{self.name_edit.text().strip() or '(unnamed)'}:{self.bit_width_spin.value()}")
|
|
521
|
+
elif self.array_check.isChecked():
|
|
522
|
+
self.title_edit.setText(f"{self.name_edit.text().strip() or '(unnamed)'}[{self.array_size_spin.value()}]")
|
|
523
|
+
else:
|
|
524
|
+
self.title_edit.setText(f"Member: {self.name_edit.text().strip() or '(unnamed)'}")
|
|
525
|
+
self.accept()
|
|
526
|
+
|
|
527
|
+
def get_member(self) -> StructMemberDef:
|
|
528
|
+
bit_width = self.bit_width_spin.value() if self.bitfield_check.isChecked() else 0
|
|
529
|
+
array_size = self.array_size_spin.value() if self.array_check.isChecked() else 0
|
|
530
|
+
return StructMemberDef(
|
|
531
|
+
name=self.name_edit.text().strip(),
|
|
532
|
+
data_type=self.type_combo.currentText().strip(),
|
|
533
|
+
bit_width=bit_width,
|
|
534
|
+
description=self.desc_edit.text().strip(),
|
|
535
|
+
title=self.title_edit.text().strip(),
|
|
536
|
+
array_size=array_size,
|
|
537
|
+
)
|