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,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
+ )