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,796 @@
1
+ """Global variables / event flag definition management screen"""
2
+
3
+ from typing import Optional, List
4
+
5
+ from PySide6.QtCore import Qt, Signal
6
+ from PySide6.QtGui import QKeyEvent
7
+ from PySide6.QtWidgets import (
8
+ QDialog, QVBoxLayout, QHBoxLayout, QLineEdit, QTabWidget,
9
+ QTableWidget, QTableWidgetItem, QPushButton, QHeaderView,
10
+ QLabel, QMessageBox, QFormLayout, QComboBox, QSpinBox,
11
+ QDialogButtonBox, QAbstractItemView, QWidget, QMenu,
12
+ QStyledItemDelegate, QCheckBox
13
+ )
14
+
15
+ from statable.global_defs import SystemVariable, EventFlag, GlobalDefinitions
16
+ from .common_widgets import TitleEditWidget, TypeComboBox, GroupComboBox
17
+ from .logger import StaTableLogger
18
+
19
+
20
+ class ComboBoxDelegate(QStyledItemDelegate):
21
+ def __init__(self, items=None, editable=True, parent=None):
22
+ super().__init__(parent)
23
+ self.items = items or []
24
+ self.editable = editable
25
+
26
+ def set_items(self, items):
27
+ self.items = items
28
+
29
+ def createEditor(self, parent, option, index):
30
+ combo = QComboBox(parent)
31
+ combo.setEditable(self.editable)
32
+ combo.addItems(self.items)
33
+ return combo
34
+
35
+ def setEditorData(self, editor, index):
36
+ value = index.data(Qt.EditRole)
37
+ if value is not None:
38
+ editor.setCurrentText(str(value))
39
+
40
+ def setModelData(self, editor, model, index):
41
+ model.setData(index, editor.currentText())
42
+
43
+
44
+ class InsertableTable(QTableWidget):
45
+ insert_requested = Signal()
46
+
47
+ def __init__(self, *args, **kwargs):
48
+ super().__init__(*args, **kwargs)
49
+ self.setContextMenuPolicy(Qt.CustomContextMenu)
50
+ self.customContextMenuRequested.connect(self._show_context_menu)
51
+
52
+ def keyPressEvent(self, event: QKeyEvent):
53
+ if event.key() == Qt.Key_Insert:
54
+ self.insert_requested.emit()
55
+ return
56
+ super().keyPressEvent(event)
57
+
58
+ def _show_context_menu(self, pos):
59
+ menu = QMenu(self)
60
+ add_action = menu.addAction(self.tr("Add row"))
61
+ add_action.triggered.connect(self.insert_requested.emit)
62
+ menu.exec(self.viewport().mapToGlobal(pos))
63
+
64
+
65
+ class ReadOnlyDelegate(QStyledItemDelegate):
66
+ def createEditor(self, parent, option, index):
67
+ return None
68
+
69
+
70
+ class VariableEditDialog(QDialog):
71
+ def __init__(self, parent=None, groups=None, variable: Optional[SystemVariable] = None, global_defs=None):
72
+ super().__init__(parent)
73
+ self.global_defs = global_defs if global_defs else GlobalDefinitions()
74
+ self.groups = groups or []
75
+ self.setWindowTitle(self.tr("Global variablesEdit"))
76
+ self.setMinimumWidth(500)
77
+
78
+ layout = QFormLayout(self)
79
+
80
+ self.title_widget = TitleEditWidget(self, title=variable.title if variable else "")
81
+ layout.addRow("", self.title_widget)
82
+
83
+ self.name_edit = QLineEdit(variable.name if variable else "")
84
+ layout.addRow("Name", self.name_edit)
85
+
86
+ self.type_combo = TypeComboBox(self, global_defs=self.global_defs)
87
+ if variable:
88
+ self.type_combo.set_current_text(variable.type)
89
+ layout.addRow("Type", self.type_combo)
90
+
91
+ self.array_check = QCheckBox(self.tr("Use array"))
92
+ self.array_check.setChecked(variable.array_size > 0 if variable else False)
93
+ layout.addRow("", self.array_check)
94
+
95
+ self.array_size_spin = QSpinBox()
96
+ self.array_size_spin.setRange(1, 65536)
97
+ self.array_size_spin.setValue(variable.array_size if variable and variable.array_size > 0 else 1)
98
+ layout.addRow("Array size", self.array_size_spin)
99
+
100
+ self.array_check.toggled.connect(self._on_array_toggled)
101
+ self._on_array_toggled(self.array_check.isChecked())
102
+
103
+ self.unit_edit = QLineEdit(variable.unit if variable else "")
104
+ layout.addRow("Unit", self.unit_edit)
105
+
106
+ self.default_edit = QLineEdit(variable.default_value if variable else "")
107
+ layout.addRow("Initial value", self.default_edit)
108
+
109
+ self.group_combo = GroupComboBox(self, groups=self.groups)
110
+ if variable:
111
+ self.group_combo.set_current_text(variable.group)
112
+ layout.addRow("Group", self.group_combo)
113
+
114
+ self.desc_edit = QLineEdit(variable.description if variable else "")
115
+ layout.addRow("Description", self.desc_edit)
116
+
117
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
118
+ buttons.accepted.connect(self._on_accept)
119
+ buttons.rejected.connect(self.reject)
120
+ layout.addRow(buttons)
121
+
122
+ def _on_array_toggled(self, checked: bool):
123
+ self.array_size_spin.setEnabled(checked)
124
+
125
+ def _on_accept(self):
126
+ auto_title = f"Variable: {self.name_edit.text().strip() or '(unnamed)'}"
127
+ if self.array_check.isChecked():
128
+ auto_title = f"{self.name_edit.text().strip() or '(unnamed)'}[{self.array_size_spin.value()}]"
129
+ self.title_widget.ensure_title(auto_title)
130
+ self.accept()
131
+
132
+ def get_variable(self) -> SystemVariable:
133
+ array_size = self.array_size_spin.value() if self.array_check.isChecked() else 0
134
+ return SystemVariable(
135
+ name=self.name_edit.text().strip(),
136
+ type=self.type_combo.current_text(),
137
+ unit=self.unit_edit.text().strip(),
138
+ default_value=self.default_edit.text().strip(),
139
+ group=self.group_combo.current_text(),
140
+ description=self.desc_edit.text().strip(),
141
+ title=self.title_widget.get_title(),
142
+ array_size=array_size,
143
+ )
144
+
145
+
146
+ class FlagEditDialog(QDialog):
147
+ def __init__(self, parent=None, groups=None, flag: Optional[EventFlag] = None):
148
+ super().__init__(parent)
149
+ self.groups = groups or []
150
+ self.setWindowTitle(self.tr("Event flagsEdit"))
151
+ self.setMinimumWidth(500)
152
+
153
+ layout = QFormLayout(self)
154
+
155
+ self.title_widget = TitleEditWidget(self, title=flag.title if flag else "")
156
+ layout.addRow("", self.title_widget)
157
+
158
+ self.name_edit = QLineEdit(flag.name if flag else "")
159
+ layout.addRow("Flag name", self.name_edit)
160
+
161
+ self.min_spin = QSpinBox()
162
+ self.min_spin.setRange(0, 2**31 - 1)
163
+ self.min_spin.setValue(flag.min_value if flag else 0)
164
+ layout.addRow("Min value", self.min_spin)
165
+
166
+ self.max_spin = QSpinBox()
167
+ self.max_spin.setRange(0, 2**31 - 1)
168
+ self.max_spin.setValue(flag.max_value if flag else 3)
169
+ layout.addRow("Max value", self.max_spin)
170
+
171
+ self.bit_width_label = QLabel()
172
+ self.update_bit_width_label()
173
+ self.min_spin.valueChanged.connect(self.update_bit_width_label)
174
+ self.max_spin.valueChanged.connect(self.update_bit_width_label)
175
+ layout.addRow("Bit width", self.bit_width_label)
176
+
177
+ self.group_combo = GroupComboBox(self, groups=self.groups)
178
+ if flag:
179
+ self.group_combo.set_current_text(flag.group)
180
+ layout.addRow("Group", self.group_combo)
181
+
182
+ self.desc_edit = QLineEdit(flag.description if flag else "")
183
+ layout.addRow("Description", self.desc_edit)
184
+
185
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
186
+ buttons.accepted.connect(self._on_accept)
187
+ buttons.rejected.connect(self.reject)
188
+ layout.addRow(buttons)
189
+
190
+ def update_bit_width_label(self):
191
+ min_val = self.min_spin.value()
192
+ max_val = self.max_spin.value()
193
+ if max_val < min_val:
194
+ self.bit_width_label.setText(self.tr("Error: Max value < Min value"))
195
+ self.bit_width_label.setStyleSheet("color: red;")
196
+ else:
197
+ width = (max_val - min_val).bit_length()
198
+ self.bit_width_label.setText(f"{width} bit")
199
+ self.bit_width_label.setStyleSheet("")
200
+
201
+ def _on_accept(self):
202
+ auto_title = f"Flag: {self.name_edit.text().strip() or '(unnamed)'}"
203
+ self.title_widget.ensure_title(auto_title)
204
+ self.accept()
205
+
206
+ def get_flag(self) -> EventFlag:
207
+ return EventFlag(
208
+ name=self.name_edit.text().strip(),
209
+ min_value=self.min_spin.value(),
210
+ max_value=self.max_spin.value(),
211
+ group=self.group_combo.current_text(),
212
+ description=self.desc_edit.text().strip(),
213
+ title=self.title_widget.get_title(),
214
+ )
215
+
216
+
217
+ class BulkVariableDialog(QDialog):
218
+ """Bulk registration dialog for global variables"""
219
+
220
+ def __init__(self, parent=None, groups=None, global_defs=None):
221
+ super().__init__(parent)
222
+ self.global_defs = global_defs if global_defs else GlobalDefinitions()
223
+ self.groups = groups or []
224
+ self.setWindowTitle(self.tr("Bulk registration of global variables"))
225
+ self.setMinimumSize(900, 400)
226
+
227
+ layout = QVBoxLayout(self)
228
+ self.table = InsertableTable(0, 8)
229
+ self.table.setHorizontalHeaderLabels(["Title", "Name", "Type", "Array", "Unit", "Initial value", "Group", "Description"])
230
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
231
+ self.table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
232
+ layout.addWidget(self.table)
233
+
234
+ self.type_delegate = ComboBoxDelegate(items=self._type_list(), editable=True)
235
+ self.table.setItemDelegateForColumn(2, self.type_delegate)
236
+
237
+ self.group_delegate = ComboBoxDelegate(items=self.groups, editable=True)
238
+ self.table.setItemDelegateForColumn(6, self.group_delegate)
239
+
240
+ btn_layout = QHBoxLayout()
241
+ add_btn = QPushButton(self.tr("Add row"))
242
+ add_btn.clicked.connect(self.add_empty_row)
243
+ del_btn = QPushButton(self.tr("Delete row"))
244
+ del_btn.clicked.connect(self.delete_row)
245
+ btn_layout.addWidget(add_btn)
246
+ btn_layout.addWidget(del_btn)
247
+ btn_layout.addStretch()
248
+ layout.addLayout(btn_layout)
249
+
250
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
251
+ buttons.accepted.connect(self._on_accept)
252
+ buttons.rejected.connect(self.reject)
253
+ layout.addWidget(buttons)
254
+
255
+ self.table.insert_requested.connect(self.add_empty_row)
256
+
257
+ def _type_list(self):
258
+ types = ["uint8_t", "uint16_t", "uint32_t", "uint64_t",
259
+ "int8_t", "int16_t", "int32_t", "int64_t",
260
+ "float", "double", "bool"]
261
+ types.extend([t.name for t in self.global_defs.custom_types])
262
+ return types
263
+
264
+ def set_variables(self, variables: List[SystemVariable]):
265
+ self.table.setRowCount(0)
266
+ for var in variables:
267
+ self.add_row(var)
268
+
269
+ def add_row(self, variable: Optional[SystemVariable] = None):
270
+ row = self.table.rowCount()
271
+ self.table.insertRow(row)
272
+ if variable:
273
+ values = [variable.title, variable.name, variable.type,
274
+ str(variable.array_size) if variable.array_size > 0 else "",
275
+ variable.unit, variable.default_value,
276
+ variable.group, variable.description]
277
+ else:
278
+ values = ["", "", "uint16_t", "", "", "", "", ""]
279
+ for col, text in enumerate(values):
280
+ self.table.setItem(row, col, QTableWidgetItem(text))
281
+
282
+ def add_empty_row(self):
283
+ current_row = self.table.currentRow()
284
+ if current_row < 0:
285
+ current_row = self.table.rowCount() - 1
286
+ self.table.insertRow(current_row + 1)
287
+ for col in range(8):
288
+ if col == 2:
289
+ self.table.setItem(current_row + 1, col, QTableWidgetItem("uint16_t"))
290
+ else:
291
+ self.table.setItem(current_row + 1, col, QTableWidgetItem(""))
292
+
293
+ def delete_row(self):
294
+ row = self.table.currentRow()
295
+ if row >= 0:
296
+ self.table.removeRow(row)
297
+
298
+ def _on_accept(self):
299
+ for row in range(self.table.rowCount()):
300
+ title_item = self.table.item(row, 0)
301
+ name_item = self.table.item(row, 1)
302
+ if title_item and name_item and not title_item.text().strip() and name_item.text().strip():
303
+ arr_item = self.table.item(row, 3)
304
+ arr_str = arr_item.text().strip() if arr_item else ""
305
+ if arr_str:
306
+ title_item.setText(f"{name_item.text().strip()}[{arr_str}]")
307
+ else:
308
+ title_item.setText(f"Variable: {name_item.text().strip()}")
309
+ self.accept()
310
+
311
+ def get_variables(self) -> List[SystemVariable]:
312
+ variables = []
313
+ for row in range(self.table.rowCount()):
314
+ title = self.table.item(row, 0).text().strip() if self.table.item(row, 0) else ""
315
+ name = self.table.item(row, 1).text().strip() if self.table.item(row, 1) else ""
316
+ if not name:
317
+ continue
318
+ typ = self.table.item(row, 2).text().strip() if self.table.item(row, 2) else ""
319
+ arr_str = self.table.item(row, 3).text().strip() if self.table.item(row, 3) else "0"
320
+ try:
321
+ arr = int(arr_str) if arr_str else 0
322
+ except ValueError:
323
+ arr = 0
324
+ unit = self.table.item(row, 4).text().strip() if self.table.item(row, 4) else ""
325
+ default = self.table.item(row, 5).text().strip() if self.table.item(row, 5) else ""
326
+ group = self.table.item(row, 6).text().strip() if self.table.item(row, 6) else ""
327
+ desc = self.table.item(row, 7).text().strip() if self.table.item(row, 7) else ""
328
+ if not title:
329
+ if arr > 0:
330
+ title = f"{name}[{arr}]"
331
+ else:
332
+ title = f"Variable: {name}"
333
+ variables.append(SystemVariable(name, typ, unit, default, group, desc, title, arr))
334
+ return variables
335
+
336
+
337
+ class BulkFlagDialog(QDialog):
338
+ """Bulk registration dialog for event flags"""
339
+
340
+ def __init__(self, parent=None, groups=None):
341
+ super().__init__(parent)
342
+ self.groups = groups or []
343
+ self.setWindowTitle(self.tr("Bulk registration of event flags"))
344
+ self.setMinimumSize(900, 400)
345
+
346
+ layout = QVBoxLayout(self)
347
+ self.table = InsertableTable(0, 7)
348
+ self.table.setHorizontalHeaderLabels(["Title", "Flag name", "Min value", "Max value", "Bit width", "Group", "Description"])
349
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
350
+ self.table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
351
+ layout.addWidget(self.table)
352
+
353
+ self.group_delegate = ComboBoxDelegate(items=self.groups, editable=True)
354
+ self.table.setItemDelegateForColumn(5, self.group_delegate)
355
+ self.table.setItemDelegateForColumn(4, ReadOnlyDelegate(self.table))
356
+
357
+ btn_layout = QHBoxLayout()
358
+ add_btn = QPushButton(self.tr("Add row"))
359
+ add_btn.clicked.connect(self.add_empty_row)
360
+ del_btn = QPushButton(self.tr("Delete row"))
361
+ del_btn.clicked.connect(self.delete_row)
362
+ btn_layout.addWidget(add_btn)
363
+ btn_layout.addWidget(del_btn)
364
+ btn_layout.addStretch()
365
+ layout.addLayout(btn_layout)
366
+
367
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
368
+ buttons.accepted.connect(self._on_accept)
369
+ buttons.rejected.connect(self.reject)
370
+ layout.addWidget(buttons)
371
+
372
+ self.table.itemChanged.connect(self.on_item_changed)
373
+ self.table.insert_requested.connect(self.add_empty_row)
374
+
375
+ def set_flags(self, flags: List[EventFlag]):
376
+ self.table.setRowCount(0)
377
+ for flag in flags:
378
+ self.add_row(flag)
379
+
380
+ def add_row(self, flag: Optional[EventFlag] = None):
381
+ row = self.table.rowCount()
382
+ self.table.insertRow(row)
383
+ if flag:
384
+ values = [flag.title, flag.name, str(flag.min_value), str(flag.max_value),
385
+ str(flag.bit_width), flag.group, flag.description]
386
+ else:
387
+ values = ["", "", "0", "3", "2", "", ""]
388
+ for col, text in enumerate(values):
389
+ item = QTableWidgetItem(text)
390
+ if col == 4:
391
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
392
+ self.table.setItem(row, col, item)
393
+ self.update_bit_width(row)
394
+
395
+ def add_empty_row(self):
396
+ current_row = self.table.currentRow()
397
+ if current_row < 0:
398
+ current_row = self.table.rowCount() - 1
399
+ self.table.insertRow(current_row + 1)
400
+ for col in range(7):
401
+ if col == 2:
402
+ self.table.setItem(current_row + 1, col, QTableWidgetItem("0"))
403
+ elif col == 3:
404
+ self.table.setItem(current_row + 1, col, QTableWidgetItem("3"))
405
+ elif col == 4:
406
+ item = QTableWidgetItem("2")
407
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
408
+ self.table.setItem(current_row + 1, col, item)
409
+ else:
410
+ self.table.setItem(current_row + 1, col, QTableWidgetItem(""))
411
+
412
+ def delete_row(self):
413
+ row = self.table.currentRow()
414
+ if row >= 0:
415
+ self.table.removeRow(row)
416
+
417
+ def on_item_changed(self, item):
418
+ if item.column() in (2, 3):
419
+ self.update_bit_width(item.row())
420
+
421
+ def update_bit_width(self, row):
422
+ min_item = self.table.item(row, 2)
423
+ max_item = self.table.item(row, 3)
424
+ if not min_item or not max_item:
425
+ return
426
+ try:
427
+ min_val = int(min_item.text())
428
+ max_val = int(max_item.text())
429
+ if max_val < min_val:
430
+ width = "Error"
431
+ else:
432
+ width = str((max_val - min_val).bit_length())
433
+ except ValueError:
434
+ width = "?"
435
+ width_item = self.table.item(row, 4)
436
+ if width_item:
437
+ width_item.setText(width)
438
+
439
+ def _on_accept(self):
440
+ for row in range(self.table.rowCount()):
441
+ title_item = self.table.item(row, 0)
442
+ name_item = self.table.item(row, 1)
443
+ if title_item and name_item and not title_item.text().strip() and name_item.text().strip():
444
+ title_item.setText(f"Flag: {name_item.text().strip()}")
445
+ self.accept()
446
+
447
+ def get_flags(self) -> List[EventFlag]:
448
+ flags = []
449
+ for row in range(self.table.rowCount()):
450
+ title = self.table.item(row, 0).text().strip() if self.table.item(row, 0) else ""
451
+ name = self.table.item(row, 1).text().strip() if self.table.item(row, 1) else ""
452
+ if not name:
453
+ continue
454
+ min_str = self.table.item(row, 2).text().strip() if self.table.item(row, 2) else "0"
455
+ max_str = self.table.item(row, 3).text().strip() if self.table.item(row, 3) else "0"
456
+ try:
457
+ min_val = int(min_str)
458
+ max_val = int(max_str)
459
+ except ValueError:
460
+ min_val, max_val = 0, 0
461
+ group = self.table.item(row, 5).text().strip() if self.table.item(row, 5) else ""
462
+ desc = self.table.item(row, 6).text().strip() if self.table.item(row, 6) else ""
463
+ if not title:
464
+ title = f"Flag: {name}"
465
+ flags.append(EventFlag(name, min_val, max_val, group, desc, title))
466
+ return flags
467
+
468
+
469
+ class GlobalDefinitionsDialog(QDialog):
470
+ def __init__(self, defs: GlobalDefinitions, parent=None):
471
+ super().__init__(parent)
472
+ self.defs = defs
473
+ self._updating = False
474
+ self.setWindowTitle(self.tr("Global variables & Event flag definition"))
475
+ self.setMinimumSize(900, 600)
476
+
477
+ layout = QVBoxLayout(self)
478
+
479
+ search_layout = QHBoxLayout()
480
+ search_layout.addWidget(QLabel(self.tr("Search (prefix match):")))
481
+ self.search_edit = QLineEdit()
482
+ self.search_edit.setPlaceholderText(self.tr("Title, member name, group name"))
483
+ self.search_edit.textChanged.connect(self.on_search_changed)
484
+ search_layout.addWidget(self.search_edit)
485
+ layout.addLayout(search_layout)
486
+
487
+ self.tab = QTabWidget()
488
+ layout.addWidget(self.tab)
489
+
490
+ self.tab.addTab(self._create_variable_tab(), self.tr("Global variables"))
491
+ self.tab.addTab(self._create_flag_tab(), self.tr("Event flags"))
492
+
493
+ close_btn = QPushButton(self.tr("Close"))
494
+ close_btn.clicked.connect(self.accept)
495
+ layout.addWidget(close_btn, alignment=Qt.AlignRight)
496
+
497
+ self.refresh_variables()
498
+ self.refresh_flags()
499
+ StaTableLogger.debug("GlobalDefinitionsDialog initialized")
500
+
501
+ def _create_variable_tab(self):
502
+ widget = QWidget()
503
+ layout = QVBoxLayout(widget)
504
+
505
+ self.var_table = InsertableTable(0, 8)
506
+ self.var_table.setHorizontalHeaderLabels(["Title", "Name", "Type", "Array", "Unit", "Initial value", "Group", "Description"])
507
+ self.var_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
508
+ self.var_table.setSelectionBehavior(QAbstractItemView.SelectRows)
509
+ self.var_table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
510
+ layout.addWidget(self.var_table)
511
+
512
+ self.type_delegate = ComboBoxDelegate(items=self._type_list(), editable=True)
513
+ self.var_table.setItemDelegateForColumn(2, self.type_delegate)
514
+
515
+ self.group_delegate = ComboBoxDelegate(items=self.defs.variable_groups(), editable=True)
516
+ self.var_table.setItemDelegateForColumn(6, self.group_delegate)
517
+
518
+ btn_layout = QHBoxLayout()
519
+ del_btn = QPushButton(self.tr("Delete"))
520
+ del_btn.clicked.connect(self.delete_variable)
521
+ bulk_btn = QPushButton(self.tr("Register in bulk..."))
522
+ bulk_btn.clicked.connect(self.bulk_variables)
523
+ btn_layout.addWidget(del_btn)
524
+ btn_layout.addStretch()
525
+ btn_layout.addWidget(bulk_btn)
526
+ layout.addLayout(btn_layout)
527
+
528
+ self.var_table.insert_requested.connect(self.add_empty_variable_row)
529
+ self.var_table.itemChanged.connect(self.on_variable_item_changed)
530
+
531
+ return widget
532
+
533
+ def _create_flag_tab(self):
534
+ widget = QWidget()
535
+ layout = QVBoxLayout(widget)
536
+
537
+ self.flag_table = InsertableTable(0, 7)
538
+ self.flag_table.setHorizontalHeaderLabels(["Title", "Flag name", "Min value", "Max value", "Bit width", "Group", "Description"])
539
+ self.flag_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
540
+ self.flag_table.setSelectionBehavior(QAbstractItemView.SelectRows)
541
+ self.flag_table.setEditTriggers(QAbstractItemView.DoubleClicked | QAbstractItemView.EditKeyPressed)
542
+ self.flag_table.setItemDelegateForColumn(4, ReadOnlyDelegate(self.flag_table))
543
+ layout.addWidget(self.flag_table)
544
+
545
+ self.flag_group_delegate = ComboBoxDelegate(items=self.defs.flag_groups(), editable=True)
546
+ self.flag_table.setItemDelegateForColumn(5, self.flag_group_delegate)
547
+
548
+ btn_layout = QHBoxLayout()
549
+ del_btn = QPushButton(self.tr("Delete"))
550
+ del_btn.clicked.connect(self.delete_flag)
551
+ bulk_btn = QPushButton(self.tr("Register in bulk..."))
552
+ bulk_btn.clicked.connect(self.bulk_flags)
553
+ btn_layout.addWidget(del_btn)
554
+ btn_layout.addStretch()
555
+ btn_layout.addWidget(bulk_btn)
556
+ layout.addLayout(btn_layout)
557
+
558
+ self.flag_table.insert_requested.connect(self.add_empty_flag_row)
559
+ self.flag_table.itemChanged.connect(self.on_flag_item_changed)
560
+
561
+ return widget
562
+
563
+ def _type_list(self):
564
+ types = ["uint8_t", "uint16_t", "uint32_t", "uint64_t",
565
+ "int8_t", "int16_t", "int32_t", "int64_t",
566
+ "float", "double", "bool"]
567
+ types.extend([t.name for t in self.defs.custom_types])
568
+ return types
569
+
570
+ def on_search_changed(self, text):
571
+ self.refresh_variables()
572
+ self.refresh_flags()
573
+
574
+ def _matches(self, title: str, name: str, group: str, query: str) -> bool:
575
+ if not query:
576
+ return True
577
+ q = query.lower()
578
+ return title.lower().startswith(q) or name.lower().startswith(q) or group.lower().startswith(q)
579
+
580
+ def refresh_variables(self):
581
+ self._updating = True
582
+ query = self.search_edit.text().strip().lower()
583
+ self.var_table.setRowCount(0)
584
+ for var in self.defs.variables:
585
+ if self._matches(var.title, var.name, var.group, query):
586
+ row = self.var_table.rowCount()
587
+ self.var_table.insertRow(row)
588
+ values = [var.title, var.name, var.type,
589
+ str(var.array_size) if var.array_size > 0 else "",
590
+ var.unit, var.default_value, var.group, var.description]
591
+ for col, text in enumerate(values):
592
+ item = QTableWidgetItem(text)
593
+ item.setData(Qt.UserRole, var)
594
+ self.var_table.setItem(row, col, item)
595
+ self.var_table.insertRow(self.var_table.rowCount())
596
+ for col in range(8):
597
+ self.var_table.setItem(self.var_table.rowCount() - 1, col, QTableWidgetItem(""))
598
+ self.type_delegate.set_items(self._type_list())
599
+ self.group_delegate.set_items(self.defs.variable_groups())
600
+ self._updating = False
601
+
602
+ def on_variable_item_changed(self, item):
603
+ if self._updating:
604
+ return
605
+ var = item.data(Qt.UserRole)
606
+ if var:
607
+ row = item.row()
608
+ var.title = self.var_table.item(row, 0).text().strip() if self.var_table.item(row, 0) else ""
609
+ var.name = self.var_table.item(row, 1).text().strip() if self.var_table.item(row, 1) else ""
610
+ var.type = self.var_table.item(row, 2).text().strip() if self.var_table.item(row, 2) else ""
611
+ arr_str = self.var_table.item(row, 3).text().strip() if self.var_table.item(row, 3) else "0"
612
+ try:
613
+ var.array_size = int(arr_str) if arr_str else 0
614
+ except ValueError:
615
+ var.array_size = 0
616
+ var.unit = self.var_table.item(row, 4).text().strip() if self.var_table.item(row, 4) else ""
617
+ var.default_value = self.var_table.item(row, 5).text().strip() if self.var_table.item(row, 5) else ""
618
+ var.group = self.var_table.item(row, 6).text().strip() if self.var_table.item(row, 6) else ""
619
+ var.description = self.var_table.item(row, 7).text().strip() if self.var_table.item(row, 7) else ""
620
+ if not var.title and var.name:
621
+ if var.array_size > 0:
622
+ var.title = f"{var.name}[{var.array_size}]"
623
+ else:
624
+ var.title = f"Variable: {var.name}"
625
+ else:
626
+ row = item.row()
627
+ name_item = self.var_table.item(row, 1)
628
+ if name_item and name_item.text().strip():
629
+ new_var = self._read_variable_row(row)
630
+ if new_var:
631
+ self.defs.variables.append(new_var)
632
+ self.refresh_variables()
633
+
634
+ def _read_variable_row(self, row) -> Optional[SystemVariable]:
635
+ title = self.var_table.item(row, 0).text().strip() if self.var_table.item(row, 0) else ""
636
+ name = self.var_table.item(row, 1).text().strip() if self.var_table.item(row, 1) else ""
637
+ if not name:
638
+ return None
639
+ typ = self.var_table.item(row, 2).text().strip() if self.var_table.item(row, 2) else ""
640
+ arr_str = self.var_table.item(row, 3).text().strip() if self.var_table.item(row, 3) else "0"
641
+ try:
642
+ arr = int(arr_str) if arr_str else 0
643
+ except ValueError:
644
+ arr = 0
645
+ unit = self.var_table.item(row, 4).text().strip() if self.var_table.item(row, 4) else ""
646
+ default = self.var_table.item(row, 5).text().strip() if self.var_table.item(row, 5) else ""
647
+ group = self.var_table.item(row, 6).text().strip() if self.var_table.item(row, 6) else ""
648
+ desc = self.var_table.item(row, 7).text().strip() if self.var_table.item(row, 7) else ""
649
+ if not title:
650
+ if arr > 0:
651
+ title = f"{name}[{arr}]"
652
+ else:
653
+ title = f"Variable: {name}"
654
+ return SystemVariable(name, typ, unit, default, group, desc, title, arr)
655
+
656
+ def add_empty_variable_row(self):
657
+ current_row = self.var_table.currentRow()
658
+ if current_row < 0:
659
+ current_row = self.var_table.rowCount() - 2
660
+ self.var_table.insertRow(current_row + 1)
661
+ for col in range(8):
662
+ if col == 2:
663
+ self.var_table.setItem(current_row + 1, col, QTableWidgetItem("uint16_t"))
664
+ else:
665
+ self.var_table.setItem(current_row + 1, col, QTableWidgetItem(""))
666
+
667
+ def delete_variable(self):
668
+ row = self.var_table.currentRow()
669
+ if row < 0:
670
+ return
671
+ item = self.var_table.item(row, 1)
672
+ if item:
673
+ var = item.data(Qt.UserRole)
674
+ if var:
675
+ self.defs.variables.remove(var)
676
+ self.var_table.removeRow(row)
677
+ if self.var_table.rowCount() == 0 or self.var_table.item(self.var_table.rowCount()-1, 1).text() != "":
678
+ self.refresh_variables()
679
+
680
+ def bulk_variables(self):
681
+ dlg = BulkVariableDialog(self, groups=self.defs.variable_groups(), global_defs=self.defs)
682
+ dlg.set_variables(self.defs.variables)
683
+ if dlg.exec() == QDialog.Accepted:
684
+ self.defs.variables = dlg.get_variables()
685
+ self.refresh_variables()
686
+
687
+ def refresh_flags(self):
688
+ self._updating = True
689
+ query = self.search_edit.text().strip().lower()
690
+ self.flag_table.setRowCount(0)
691
+ for flag in self.defs.flags:
692
+ if self._matches(flag.title, flag.name, flag.group, query):
693
+ row = self.flag_table.rowCount()
694
+ self.flag_table.insertRow(row)
695
+ values = [flag.title, flag.name, str(flag.min_value), str(flag.max_value),
696
+ str(flag.bit_width), flag.group, flag.description]
697
+ for col, text in enumerate(values):
698
+ item = QTableWidgetItem(text)
699
+ if col == 4:
700
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
701
+ item.setData(Qt.UserRole, flag)
702
+ self.flag_table.setItem(row, col, item)
703
+ self.flag_table.insertRow(self.flag_table.rowCount())
704
+ for col in range(7):
705
+ item = QTableWidgetItem("")
706
+ if col == 4:
707
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
708
+ self.flag_table.setItem(self.flag_table.rowCount() - 1, col, item)
709
+ self.flag_group_delegate.set_items(self.defs.flag_groups())
710
+ self._updating = False
711
+
712
+ def on_flag_item_changed(self, item):
713
+ if self._updating:
714
+ return
715
+ flag = item.data(Qt.UserRole)
716
+ if flag:
717
+ row = item.row()
718
+ flag.title = self.flag_table.item(row, 0).text().strip() if self.flag_table.item(row, 0) else ""
719
+ flag.name = self.flag_table.item(row, 1).text().strip() if self.flag_table.item(row, 1) else ""
720
+ min_str = self.flag_table.item(row, 2).text().strip() if self.flag_table.item(row, 2) else "0"
721
+ max_str = self.flag_table.item(row, 3).text().strip() if self.flag_table.item(row, 3) else "0"
722
+ try:
723
+ flag.min_value = int(min_str)
724
+ flag.max_value = int(max_str)
725
+ except ValueError:
726
+ flag.min_value, flag.max_value = 0, 0
727
+ flag.group = self.flag_table.item(row, 5).text().strip() if self.flag_table.item(row, 5) else ""
728
+ flag.description = self.flag_table.item(row, 6).text().strip() if self.flag_table.item(row, 6) else ""
729
+ if not flag.title and flag.name:
730
+ flag.title = f"Flag: {flag.name}"
731
+ width_item = self.flag_table.item(row, 4)
732
+ if width_item:
733
+ width_item.setText(str(flag.bit_width))
734
+ else:
735
+ row = item.row()
736
+ name_item = self.flag_table.item(row, 1)
737
+ if name_item and name_item.text().strip():
738
+ new_flag = self._read_flag_row(row)
739
+ if new_flag:
740
+ self.defs.flags.append(new_flag)
741
+ self.refresh_flags()
742
+
743
+ def _read_flag_row(self, row) -> Optional[EventFlag]:
744
+ title = self.flag_table.item(row, 0).text().strip() if self.flag_table.item(row, 0) else ""
745
+ name = self.flag_table.item(row, 1).text().strip() if self.flag_table.item(row, 1) else ""
746
+ if not name:
747
+ return None
748
+ min_str = self.flag_table.item(row, 2).text().strip() if self.flag_table.item(row, 2) else "0"
749
+ max_str = self.flag_table.item(row, 3).text().strip() if self.flag_table.item(row, 3) else "0"
750
+ try:
751
+ min_val = int(min_str)
752
+ max_val = int(max_str)
753
+ except ValueError:
754
+ min_val, max_val = 0, 0
755
+ group = self.flag_table.item(row, 5).text().strip() if self.flag_table.item(row, 5) else ""
756
+ desc = self.flag_table.item(row, 6).text().strip() if self.flag_table.item(row, 6) else ""
757
+ if not title:
758
+ title = f"Flag: {name}"
759
+ return EventFlag(name, min_val, max_val, group, desc, title)
760
+
761
+ def add_empty_flag_row(self):
762
+ current_row = self.flag_table.currentRow()
763
+ if current_row < 0:
764
+ current_row = self.flag_table.rowCount() - 2
765
+ self.flag_table.insertRow(current_row + 1)
766
+ for col in range(7):
767
+ if col == 2:
768
+ self.flag_table.setItem(current_row + 1, col, QTableWidgetItem("0"))
769
+ elif col == 3:
770
+ self.flag_table.setItem(current_row + 1, col, QTableWidgetItem("3"))
771
+ elif col == 4:
772
+ item = QTableWidgetItem("2")
773
+ item.setFlags(item.flags() & ~Qt.ItemIsEditable)
774
+ self.flag_table.setItem(current_row + 1, col, item)
775
+ else:
776
+ self.flag_table.setItem(current_row + 1, col, QTableWidgetItem(""))
777
+
778
+ def delete_flag(self):
779
+ row = self.flag_table.currentRow()
780
+ if row < 0:
781
+ return
782
+ item = self.flag_table.item(row, 1)
783
+ if item:
784
+ flag = item.data(Qt.UserRole)
785
+ if flag:
786
+ self.defs.flags.remove(flag)
787
+ self.flag_table.removeRow(row)
788
+ if self.flag_table.rowCount() == 0 or self.flag_table.item(self.flag_table.rowCount()-1, 1).text() != "":
789
+ self.refresh_flags()
790
+
791
+ def bulk_flags(self):
792
+ dlg = BulkFlagDialog(self, groups=self.defs.flag_groups())
793
+ dlg.set_flags(self.defs.flags)
794
+ if dlg.exec() == QDialog.Accepted:
795
+ self.defs.flags = dlg.get_flags()
796
+ self.refresh_flags()