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,191 @@
1
+ """Event delivery settings dialog"""
2
+
3
+ from PySide6.QtCore import Qt
4
+ from PySide6.QtWidgets import (
5
+ QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem,
6
+ QLabel, QHeaderView, QComboBox, QDialogButtonBox,
7
+ QCheckBox, QAbstractItemView
8
+ )
9
+ from PySide6.QtGui import QFont, QMouseEvent
10
+
11
+ from statable.state_machine import StateMachine
12
+ from statable.model import EventDeliveryType
13
+ from statable.global_defs import GlobalDefinitions
14
+ from .logger import StaTableLogger
15
+
16
+
17
+ class DoubleClickTable(QTableWidget):
18
+ """Table that reliably captures double-click events"""
19
+ def __init__(self, *args, **kwargs):
20
+ super().__init__(*args, **kwargs)
21
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
22
+ self.setSelectionBehavior(QAbstractItemView.SelectRows)
23
+ self.setSelectionMode(QAbstractItemView.SingleSelection)
24
+
25
+ def mouseDoubleClickEvent(self, event: QMouseEvent):
26
+ pos = event.position().toPoint()
27
+ item = self.itemAt(pos)
28
+ if item:
29
+ self.cellDoubleClicked.emit(item.row(), item.column())
30
+
31
+
32
+ class EventDeliverySettingsDialog(QDialog):
33
+ """Dialog to configure event delivery type (DIRECT/QUEUE) and display automatic DOUBLE conversion based on ISR usage"""
34
+ def __init__(self, sm: StateMachine, global_defs: GlobalDefinitions, auto_convert: bool = True, parent=None):
35
+ super().__init__(parent)
36
+ self.sm = sm
37
+ self.global_defs = global_defs
38
+ self.auto_convert = auto_convert
39
+
40
+ self.setWindowTitle(self.tr("Event delivery settings"))
41
+ self.setMinimumSize(900, 500)
42
+
43
+ layout = QVBoxLayout(self)
44
+
45
+ #Global settings checkbox
46
+ self.auto_convert_check = QCheckBox(self.tr("Automatically convert DIRECT events used in ISR to DOUBLE"))
47
+ self.auto_convert_check.setChecked(self.auto_convert)
48
+ layout.addWidget(self.auto_convert_check)
49
+
50
+ # Event list table
51
+ self.table = DoubleClickTable(0, 6)
52
+ self.table.setHorizontalHeaderLabels(["Title", "Event name", "Source", "Delivery type", "ISR usage", "Converted"])
53
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
54
+ self.table.setFont(QFont("Consolas", 10))
55
+ layout.addWidget(self.table, stretch=1)
56
+
57
+ # Help
58
+ help_label = QLabel(
59
+ self.tr("If a DIRECT event is notified from an ISR, it is automatically converted to DOUBLE.\n"
60
+ "Select QUEUE if you need event counts or data.")
61
+ )
62
+ help_label.setStyleSheet("color: gray;")
63
+ layout.addWidget(help_label)
64
+
65
+ # OK/Cancel
66
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
67
+ buttons.accepted.connect(self._on_accept)
68
+ buttons.rejected.connect(self.reject)
69
+ layout.addWidget(buttons)
70
+
71
+ self._build_table()
72
+ self.auto_convert_check.stateChanged.connect(self._update_converted_column)
73
+
74
+ StaTableLogger.debug("EventDeliverySettingsDialog initialized")
75
+
76
+ def _build_table(self):
77
+ """Build the table"""
78
+ self.table.setRowCount(0)
79
+ for event in self.sm.events.values():
80
+ row = self.table.rowCount()
81
+ self.table.insertRow(row)
82
+
83
+ # Title (read-only)
84
+ title_item = QTableWidgetItem(event.title)
85
+ title_item.setFlags(title_item.flags() & ~Qt.ItemIsEditable)
86
+ self.table.setItem(row, 0, title_item)
87
+
88
+ # Event name (read-only)
89
+ name_item = QTableWidgetItem(event.name if event.name else "(completion)")
90
+ name_item.setFlags(name_item.flags() & ~Qt.ItemIsEditable)
91
+ self.table.setItem(row, 1, name_item)
92
+
93
+ # Source (read-only)
94
+ source_item = QTableWidgetItem(event.source_layer.value)
95
+ source_item.setFlags(source_item.flags() & ~Qt.ItemIsEditable)
96
+ self.table.setItem(row, 2, source_item)
97
+
98
+ # Delivery type combo
99
+ combo = QComboBox()
100
+ combo.addItem(self.tr("DIRECT"), EventDeliveryType.DIRECT)
101
+ combo.addItem(self.tr("QUEUE"), EventDeliveryType.QUEUE)
102
+ combo.addItem(self.tr("DOUBLE"), EventDeliveryType.DOUBLE)
103
+ idx = combo.findData(event.delivery_type)
104
+ if idx >= 0:
105
+ combo.setCurrentIndex(idx)
106
+ combo.currentIndexChanged.connect(lambda _index, r=row: self._on_delivery_changed(r))
107
+ self.table.setCellWidget(row, 3, combo)
108
+
109
+ #ISR usage column (read-only)
110
+ isr_used = self._check_isr_usage(event.name)
111
+ isr_item = QTableWidgetItem("Yes" if isr_used else "")
112
+ isr_item.setFlags(isr_item.flags() & ~Qt.ItemIsEditable)
113
+ self.table.setItem(row, 4, isr_item)
114
+
115
+ # Conversion column (read-only)
116
+ converted_item = QTableWidgetItem()
117
+ converted_item.setFlags(converted_item.flags() & ~Qt.ItemIsEditable)
118
+ self.table.setItem(row, 5, converted_item)
119
+
120
+ StaTableLogger.debug(f" Event: '{event.name}' -> ISR used: {isr_used}")
121
+
122
+ self._update_converted_column()
123
+
124
+ def _check_isr_usage(self, event_name: str) -> bool:
125
+ """Determine if event used from interrupt handler"""
126
+ if not event_name:
127
+ return False
128
+
129
+ for intr in self.global_defs.interrupts:
130
+ #Check event_names list
131
+ if event_name in intr.event_names:
132
+ StaTableLogger.debug(f" '{event_name}' found in interrupt '{intr.name}' event_names")
133
+ return True
134
+
135
+ #Check the code in actions
136
+ for act in intr.actions:
137
+ combined = act.condition + "\n" + act.action
138
+ if event_name in combined:
139
+ StaTableLogger.debug(f" '{event_name}' found in interrupt '{intr.name}' action code")
140
+ return True
141
+
142
+ return False
143
+
144
+ def _find_row_by_event_name(self, event_name: str) -> int:
145
+ """Find the row number from the event name"""
146
+ for row in range(self.table.rowCount()):
147
+ item = self.table.item(row, 1)
148
+ if item and item.text() == event_name:
149
+ return row
150
+ return -1
151
+
152
+ def _on_delivery_changed(self, row: int):
153
+ """Update the converted column when the delivery type combo changes"""
154
+ self._update_converted_column()
155
+
156
+ def _update_converted_column(self):
157
+ """Recompute conversion column from settings"""
158
+ auto = self.auto_convert_check.isChecked()
159
+ for row in range(self.table.rowCount()):
160
+ name_item = self.table.item(row, 1)
161
+ event_name = name_item.text() if name_item else ""
162
+ if event_name == "(completion)":
163
+ event_name = ""
164
+
165
+ combo = self.table.cellWidget(row, 3)
166
+ if not combo:
167
+ continue
168
+ current_type = combo.currentData()
169
+ isr_used = self.table.item(row, 4).text() == "Yes"
170
+ converted = current_type
171
+ if auto and isr_used and current_type == EventDeliveryType.DIRECT:
172
+ converted = EventDeliveryType.DOUBLE
173
+ converted_item = self.table.item(row, 5)
174
+ if converted_item:
175
+ converted_item.setText(converted.value if converted else "")
176
+
177
+ def _on_accept(self):
178
+ """OK button: update each event's delivery type and close"""
179
+ for row in range(self.table.rowCount()):
180
+ name_item = self.table.item(row, 1)
181
+ event_name = name_item.text() if name_item else ""
182
+ if event_name == "(completion)":
183
+ event_name = ""
184
+ combo = self.table.cellWidget(row, 3)
185
+ if combo and event_name in self.sm.events:
186
+ self.sm.events[event_name].delivery_type = combo.currentData()
187
+ self.accept()
188
+
189
+ def get_auto_convert(self) -> bool:
190
+ """Return the checkbox state"""
191
+ return self.auto_convert_check.isChecked()
@@ -0,0 +1,245 @@
1
+ """Event queue definition dialog"""
2
+
3
+ from typing import Optional, List
4
+
5
+ from PySide6.QtCore import Qt
6
+ from PySide6.QtGui import QFont, QMouseEvent
7
+ from PySide6.QtWidgets import (
8
+ QDialog, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem,
9
+ QPushButton, QLabel, QHeaderView, QLineEdit, QSpinBox,
10
+ QCheckBox, QFormLayout, QDialogButtonBox, QMessageBox, QAbstractItemView,
11
+ QWidget, QListWidget, QListWidgetItem
12
+ )
13
+
14
+ from statable.global_defs import GlobalDefinitions, EventQueueDef
15
+ from .common_widgets import TitleEditWidget, TypeComboBox
16
+ from .logger import StaTableLogger
17
+
18
+
19
+ class DoubleClickTable(QTableWidget):
20
+ def __init__(self, *args, **kwargs):
21
+ super().__init__(*args, **kwargs)
22
+ self.setEditTriggers(QAbstractItemView.NoEditTriggers)
23
+ self.setSelectionBehavior(QAbstractItemView.SelectRows)
24
+ self.setSelectionMode(QAbstractItemView.SingleSelection)
25
+
26
+ def mouseDoubleClickEvent(self, event: QMouseEvent):
27
+ pos = event.position().toPoint()
28
+ item = self.itemAt(pos)
29
+ if item:
30
+ self.cellDoubleClicked.emit(item.row(), item.column())
31
+
32
+
33
+ class EventQueueEditDialog(QDialog):
34
+ def __init__(self, parent=None, queue_def: Optional[EventQueueDef] = None,
35
+ event_names: Optional[List[str]] = None, global_defs=None):
36
+ super().__init__(parent)
37
+ self.global_defs = global_defs if global_defs else GlobalDefinitions()
38
+ self.event_names = event_names or []
39
+ self.setWindowTitle(self.tr("EventQueueEdit"))
40
+ self.setMinimumWidth(500)
41
+
42
+ layout = QVBoxLayout(self)
43
+ form = QFormLayout()
44
+ layout.addLayout(form)
45
+
46
+ # Title input field (required / provisional title auto-set)
47
+ self.title_widget = TitleEditWidget(self, title=queue_def.title if queue_def else "")
48
+ form.addRow("", self.title_widget)
49
+
50
+ # Queue name
51
+ self.name_edit = QLineEdit(queue_def.name if queue_def else "")
52
+ form.addRow("Queue name", self.name_edit)
53
+
54
+ # Size
55
+ self.size_spin = QSpinBox()
56
+ self.size_spin.setRange(2, 256)
57
+ self.size_spin.setValue(queue_def.size if queue_def else 8)
58
+ form.addRow("Size", self.size_spin)
59
+
60
+ # Element type
61
+ self.type_combo = TypeComboBox(self, global_defs=self.global_defs)
62
+ if queue_def:
63
+ self.type_combo.set_current_text(queue_def.element_type)
64
+ form.addRow("Element type", self.type_combo)
65
+
66
+ # Related event selection list
67
+ self.event_list = QListWidget()
68
+ for event_name in self.event_names:
69
+ item = QListWidgetItem(event_name)
70
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
71
+ item.setCheckState(Qt.Checked if queue_def and event_name in queue_def.event_ids else Qt.Unchecked)
72
+ self.event_list.addItem(item)
73
+ form.addRow("Related events", self.event_list)
74
+
75
+ # With priority
76
+ self.priority_check = QCheckBox()
77
+ self.priority_check.setChecked(queue_def.priority_enabled if queue_def else False)
78
+ form.addRow("With priority", self.priority_check)
79
+
80
+ # Interrupt protection
81
+ self.safe_check = QCheckBox()
82
+ self.safe_check.setChecked(queue_def.interrupt_safe if queue_def else True)
83
+ form.addRow("Interrupt protection", self.safe_check)
84
+
85
+ # RTOS usage
86
+ self.rtos_check = QCheckBox()
87
+ self.rtos_check.setChecked(queue_def.rtos_enabled if queue_def else False)
88
+ form.addRow("RTOS usage", self.rtos_check)
89
+
90
+ # description
91
+ self.desc_edit = QLineEdit(queue_def.description if queue_def else "")
92
+ form.addRow("Description", self.desc_edit)
93
+
94
+ buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
95
+ buttons.accepted.connect(self._on_accept)
96
+ buttons.rejected.connect(self.reject)
97
+ layout.addWidget(buttons)
98
+
99
+ StaTableLogger.debug("EventQueueEditDialog initialized")
100
+
101
+ def _on_accept(self):
102
+ """OK button: auto-set provisional title if title is empty"""
103
+ auto_title = f"Queue: {self.name_edit.text().strip() or '(unnamed)'}"
104
+ self.title_widget.ensure_title(auto_title)
105
+ self.accept()
106
+
107
+ def get_queue_def(self) -> EventQueueDef:
108
+ event_ids = []
109
+ for i in range(self.event_list.count()):
110
+ item = self.event_list.item(i)
111
+ if item.checkState() == Qt.Checked:
112
+ event_ids.append(item.text())
113
+ return EventQueueDef(
114
+ name=self.name_edit.text().strip(),
115
+ size=self.size_spin.value(),
116
+ element_type=self.type_combo.current_text(),
117
+ event_ids=event_ids,
118
+ priority_enabled=self.priority_check.isChecked(),
119
+ interrupt_safe=self.safe_check.isChecked(),
120
+ rtos_enabled=self.rtos_check.isChecked(),
121
+ description=self.desc_edit.text().strip(),
122
+ title=self.title_widget.get_title(),
123
+ )
124
+
125
+
126
+ class EventQueueDefsDialog(QDialog):
127
+ """Event queue definition list dialog"""
128
+ def __init__(self, global_defs: GlobalDefinitions, event_names: Optional[List[str]] = None, parent=None):
129
+ super().__init__(parent)
130
+ self.global_defs = global_defs
131
+ self.event_names = event_names or []
132
+ self.setWindowTitle(self.tr("Event queue definition"))
133
+ self.setMinimumSize(1100, 600)
134
+
135
+ layout = QVBoxLayout(self)
136
+
137
+ # Search
138
+ search_layout = QHBoxLayout()
139
+ search_layout.addWidget(QLabel(self.tr("Search (prefix match):")))
140
+ self.search_edit = QLineEdit()
141
+ self.search_edit.setPlaceholderText(self.tr("Title, queue name, description"))
142
+ self.search_edit.textChanged.connect(self.refresh_table)
143
+ search_layout.addWidget(self.search_edit)
144
+ layout.addLayout(search_layout)
145
+
146
+ # List table (title column added / direct edit)
147
+ self.table = DoubleClickTable(0, 9)
148
+ self.table.setHorizontalHeaderLabels(["Title", "Queue name", "Size", "Element type", "Related events", "Priority", "Interrupt protection", "RTOS", "Description"])
149
+ self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
150
+ self.table.cellDoubleClicked.connect(self.on_double_clicked)
151
+ self.table.itemChanged.connect(self.on_item_changed)
152
+ layout.addWidget(self.table)
153
+
154
+ # Buttons
155
+ btn_layout = QHBoxLayout()
156
+ add_btn = QPushButton(self.tr("Add"))
157
+ add_btn.clicked.connect(self.add_queue)
158
+ del_btn = QPushButton(self.tr("Delete"))
159
+ del_btn.clicked.connect(self.delete_queue)
160
+ btn_layout.addWidget(add_btn)
161
+ btn_layout.addWidget(del_btn)
162
+ btn_layout.addStretch()
163
+ layout.addLayout(btn_layout)
164
+
165
+ # Close
166
+ close_btn = QPushButton(self.tr("Close"))
167
+ close_btn.clicked.connect(self.accept)
168
+ layout.addWidget(close_btn, alignment=Qt.AlignRight)
169
+
170
+ self.refresh_table()
171
+ StaTableLogger.debug("EventQueueDefsDialog initialized")
172
+
173
+ def refresh_table(self):
174
+ query = self.search_edit.text().strip().lower() if hasattr(self, 'search_edit') else ""
175
+ self.table.setRowCount(0)
176
+ for q in self.global_defs.event_queues:
177
+ if not self._matches(q, query):
178
+ continue
179
+ row = self.table.rowCount()
180
+ self.table.insertRow(row)
181
+
182
+ title_item = QTableWidgetItem(q.title)
183
+ title_item.setToolTip(self.tr("Title of this queue. Can be edited directly."))
184
+ self.table.setItem(row, 0, title_item)
185
+
186
+ self.table.setItem(row, 1, QTableWidgetItem(q.name))
187
+ self.table.setItem(row, 2, QTableWidgetItem(str(q.size)))
188
+ self.table.setItem(row, 3, QTableWidgetItem(q.element_type))
189
+ self.table.setItem(row, 4, QTableWidgetItem(", ".join(q.event_ids)))
190
+ self.table.setItem(row, 5, QTableWidgetItem("Yes" if q.priority_enabled else "None"))
191
+ self.table.setItem(row, 6, QTableWidgetItem("Yes" if q.interrupt_safe else "None"))
192
+ self.table.setItem(row, 7, QTableWidgetItem("Yes" if q.rtos_enabled else "None"))
193
+ self.table.setItem(row, 8, QTableWidgetItem(q.description))
194
+
195
+ def _matches(self, q: EventQueueDef, query: str) -> bool:
196
+ """Search filter (prefix match)"""
197
+ if not query:
198
+ return True
199
+ return (
200
+ q.title.lower().startswith(query) or
201
+ q.name.lower().startswith(query) or
202
+ q.description.lower().startswith(query)
203
+ )
204
+
205
+ def on_item_changed(self, item):
206
+ """Handler when the title column is edited directly"""
207
+ if item.column() == 0:
208
+ row = item.row()
209
+ name_item = self.table.item(row, 1)
210
+ if name_item and row < len(self.global_defs.event_queues):
211
+ for q in self.global_defs.event_queues:
212
+ if q.name == name_item.text():
213
+ q.title = item.text().strip() or f"Queue: {q.name}"
214
+ break
215
+
216
+ def on_double_clicked(self, row, col):
217
+ if col == 0:
218
+ return
219
+ if row < 0 or row >= len(self.global_defs.event_queues):
220
+ return
221
+ target = self.global_defs.event_queues[row]
222
+ dlg = EventQueueEditDialog(self, queue_def=target, event_names=self.event_names, global_defs=self.global_defs)
223
+ if dlg.exec() == QDialog.Accepted:
224
+ new_q = dlg.get_queue_def()
225
+ if not new_q.name:
226
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a queue name."))
227
+ return
228
+ self.global_defs.event_queues[row] = new_q
229
+ self.refresh_table()
230
+
231
+ def add_queue(self):
232
+ dlg = EventQueueEditDialog(self, event_names=self.event_names, global_defs=self.global_defs)
233
+ if dlg.exec() == QDialog.Accepted:
234
+ q = dlg.get_queue_def()
235
+ if not q.name:
236
+ QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a queue name."))
237
+ return
238
+ self.global_defs.event_queues.append(q)
239
+ self.refresh_table()
240
+
241
+ def delete_queue(self):
242
+ row = self.table.currentRow()
243
+ if 0 <= row < len(self.global_defs.event_queues):
244
+ self.global_defs.event_queues.pop(row)
245
+ self.refresh_table()
@@ -0,0 +1,167 @@
1
+ """Data model for global variables, event flags, interrupt handlers, device resources, and timer settings"""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List, Optional
5
+
6
+
7
+ @dataclass
8
+ class SystemVariable:
9
+ """Global variables shared across the system"""
10
+ name: str
11
+ type: str
12
+ unit: str = ""
13
+ default_value: str = ""
14
+ group: str = ""
15
+ description: str = ""
16
+ title: str = ""
17
+
18
+ def __post_init__(self):
19
+ if not self.title:
20
+ self.title = f"Variable: {self.name}"
21
+
22
+
23
+ @dataclass
24
+ class EventFlag:
25
+ """Event flags managed as bit fields"""
26
+ name: str
27
+ min_value: int
28
+ max_value: int
29
+ group: str = ""
30
+ description: str = ""
31
+ title: str = ""
32
+
33
+ def __post_init__(self):
34
+ if not self.title:
35
+ self.title = f"Flag: {self.name}"
36
+
37
+ @property
38
+ def bit_width(self) -> int:
39
+ if self.max_value < self.min_value:
40
+ return 0
41
+ return (self.max_value - self.min_value).bit_length()
42
+
43
+
44
+ @dataclass
45
+ class InterruptAction:
46
+ """Conditional actions inside an interrupt handler"""
47
+ condition: str = ""
48
+ action: str = ""
49
+
50
+
51
+ @dataclass
52
+ class InterruptHandlerDef:
53
+ """Interrupt handler definition"""
54
+ name: str
55
+ description: str = ""
56
+ event_names: List[str] = field(default_factory=list)
57
+ is_timer: bool = False
58
+ actions: List[InterruptAction] = field(default_factory=list)
59
+ title: str = ""
60
+
61
+ def __post_init__(self):
62
+ if not self.title:
63
+ self.title = f"Interrupt: {self.name}"
64
+
65
+
66
+ @dataclass
67
+ class DevicePlaceholderDef:
68
+ """Device resource placeholder definition"""
69
+ name: str
70
+ description: str = ""
71
+ title: str = ""
72
+
73
+ def __post_init__(self):
74
+ if not self.title:
75
+ self.title = f"Device: {self.name}"
76
+
77
+
78
+ @dataclass
79
+ class TimerDerivedDef:
80
+ """Derived timer variable definition"""
81
+ period_name: str
82
+ multiplier: int
83
+ variable_name: str
84
+ data_type: str = "uint8_t"
85
+ title: str = ""
86
+
87
+ def __post_init__(self):
88
+ if not self.title:
89
+ self.title = f"Timer: {self.variable_name}"
90
+
91
+
92
+ @dataclass
93
+ class TimerBaseDef:
94
+ """Timer tick variable definition"""
95
+ variable_name: str = "g_system_tick"
96
+ unit: str = "1ms"
97
+ data_type: str = "volatile uint32_t"
98
+ derived: List[TimerDerivedDef] = field(default_factory=list)
99
+ title: str = ""
100
+ interrupt_name: str = "" # Interrupt name driving this timer
101
+
102
+ def __post_init__(self):
103
+ if not self.title:
104
+ self.title = f"Timer base: {self.variable_name}"
105
+
106
+
107
+ @dataclass
108
+ class EventQueueDef:
109
+ """Event queue definition"""
110
+ name: str
111
+ size: int
112
+ element_type: str
113
+ event_ids: List[str] = field(default_factory=list)
114
+ priority_enabled: bool = False
115
+ interrupt_safe: bool = True
116
+ rtos_enabled: bool = False
117
+ description: str = ""
118
+ title: str = ""
119
+
120
+ def __post_init__(self):
121
+ if not self.title:
122
+ self.title = f"Queue: {self.name}"
123
+
124
+
125
+ class GlobalDefinitions:
126
+ """Management class for global variables, event flags, interrupt handlers, device resources, timer settings, and event queues"""
127
+
128
+ def __init__(self):
129
+ self.variables: List[SystemVariable] = []
130
+ self.flags: List[EventFlag] = []
131
+ self.interrupts: List[InterruptHandlerDef] = []
132
+ self.placeholders: List[DevicePlaceholderDef] = []
133
+ self.timer_base: TimerBaseDef = TimerBaseDef()
134
+ self.extra_timers: List[TimerBaseDef] = [] # Add timer base
135
+ self.event_queues: List[EventQueueDef] = []
136
+
137
+ def add_timer_variables(self):
138
+ """Register all timer base variables and derived timer variables as global variables"""
139
+ self.variables = [v for v in self.variables if v.group != "Timer"]
140
+
141
+ all_timers = [self.timer_base] + self.extra_timers
142
+ for timer in all_timers:
143
+ self.variables.append(SystemVariable(
144
+ name=timer.variable_name,
145
+ type=timer.data_type,
146
+ unit=timer.unit,
147
+ default_value="0",
148
+ group="Timer",
149
+ description="Timer base variable",
150
+ title=timer.title,
151
+ ))
152
+ for d in timer.derived:
153
+ self.variables.append(SystemVariable(
154
+ name=d.variable_name,
155
+ type=d.data_type,
156
+ unit=d.period_name,
157
+ default_value="0",
158
+ group="Timer",
159
+ description=f"Derived timer variable ({d.period_name})",
160
+ title=d.title,
161
+ ))
162
+
163
+ def variable_groups(self) -> List[str]:
164
+ return sorted({v.group for v in self.variables if v.group})
165
+
166
+ def flag_groups(self) -> List[str]:
167
+ return sorted({f.group for f in self.flags if f.group})