statable 3.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codegen/__init__.py +38 -0
- codegen/c_code_generator.py +1752 -0
- codegen/code_merger.py +371 -0
- codegen/code_templates.py +509 -0
- codegen/config.py +167 -0
- codegen/enum_generator.py +377 -0
- codegen/event_queue_generator.py +303 -0
- codegen/interrupt_generator.py +345 -0
- codegen/naming_convention.py +171 -0
- codegen/osal_generator.py +282 -0
- codegen/role_function_generator.py +1244 -0
- codegen/sample_data.py +269 -0
- codegen/state_actions_generator.py +395 -0
- codegen/struct_generator.py +721 -0
- codegen/timer_generator.py +299 -0
- codegen/transition_generator.py +907 -0
- codegen/type_mapper.py +69 -0
- codegen/validate/__init__.py +38 -0
- codegen/validate/change_actions.py +90 -0
- codegen/validate/change_applier.py +416 -0
- codegen/validate/clipboard_manager.py +47 -0
- codegen/validate/data/__init__.py +30 -0
- codegen/validate/data/action_definitions.py +200 -0
- codegen/validate/data/keywords.py +46 -0
- codegen/validate/data/prompt_templates.py +176 -0
- codegen/validate/data/validation_rules.py +205 -0
- codegen/validate/items/__init__.py +30 -0
- codegen/validate/items/base_validator.py +25 -0
- codegen/validate/items/cell_validator.py +275 -0
- codegen/validate/items/custom_type_validator.py +75 -0
- codegen/validate/items/event_validator.py +78 -0
- codegen/validate/items/flag_validator.py +74 -0
- codegen/validate/items/interrupt_validator.py +75 -0
- codegen/validate/items/queue_validator.py +73 -0
- codegen/validate/items/role_function_validator.py +175 -0
- codegen/validate/items/state_validator.py +88 -0
- codegen/validate/items/timer_validator.py +120 -0
- codegen/validate/items/transition_validator.py +113 -0
- codegen/validate/items/variable_validator.py +132 -0
- codegen/validate/logger.py +52 -0
- codegen/validate/models.py +177 -0
- codegen/validate/prompt_generator.py +724 -0
- codegen/validate/response_parser.py +154 -0
- codegen/validate/response_validator.py +275 -0
- codegen/validate/validation_dialog.py +322 -0
- codegen/validate/validator.py +94 -0
- codegen/variable_generator.py +419 -0
- statable/__init__.py +71 -0
- statable/__main__.py +49 -0
- statable/cli.py +251 -0
- statable/global_defs.py +256 -0
- statable/mermaid_gen.py +144 -0
- statable/model.py +426 -0
- statable/parser.py +64 -0
- statable/sample_data.py +435 -0
- statable/shared/__init__.py +24 -0
- statable/shared/condition_library.py +63 -0
- statable/shared/literal_library.py +66 -0
- statable/shared/role_function_library.py +150 -0
- statable/state_machine.py +121 -0
- statable/xml_io.py +1164 -0
- statable-3.1.0.dist-info/METADATA +151 -0
- statable-3.1.0.dist-info/RECORD +117 -0
- statable-3.1.0.dist-info/WHEEL +5 -0
- statable-3.1.0.dist-info/entry_points.txt +2 -0
- statable-3.1.0.dist-info/top_level.txt +3 -0
- statable_gui/__init__.py +23 -0
- statable_gui/action_edit_dialog.py +214 -0
- statable_gui/code_generation_dialog.py +486 -0
- statable_gui/code_generation_settings_dialog.py +522 -0
- statable_gui/common_widgets.py +537 -0
- statable_gui/condition_builder_dialog.py +500 -0
- statable_gui/condition_edit_dialog.py +123 -0
- statable_gui/condition_template_dialog.py +58 -0
- statable_gui/config.py +21 -0
- statable_gui/dialogs.py +439 -0
- statable_gui/event_definition_dialog.py +605 -0
- statable_gui/event_delivery_settings_dialog.py +191 -0
- statable_gui/event_queue_dialog.py +245 -0
- statable_gui/global_defs.py +167 -0
- statable_gui/global_defs_dialog.py +796 -0
- statable_gui/i18n/__init__.py +118 -0
- statable_gui/interrupt_handler_edit_dialog.py +810 -0
- statable_gui/layer_settings_dialog.py +180 -0
- statable_gui/libcntrl/__init__.py +23 -0
- statable_gui/libcntrl/condition_library.py +13 -0
- statable_gui/libcntrl/literal_library.py +13 -0
- statable_gui/libcntrl/literal_management_dialog.py +185 -0
- statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
- statable_gui/libcntrl/role_function_library.py +13 -0
- statable_gui/literal_definition_dialog.py +62 -0
- statable_gui/logger.py +83 -0
- statable_gui/main_window.py +1380 -0
- statable_gui/matrix_table.py +413 -0
- statable_gui/preference_keys.py +18 -0
- statable_gui/preferences.py +74 -0
- statable_gui/role_function_dialog.py +244 -0
- statable_gui/sample_data.py +11 -0
- statable_gui/state_actions_dialog.py +398 -0
- statable_gui/symbol_picker.py +170 -0
- statable_gui/traceball.py +37 -0
- statable_gui/transition_editor_direct/__init__.py +15 -0
- statable_gui/transition_editor_direct/actions_tab.py +856 -0
- statable_gui/transition_editor_direct/code_widget.py +344 -0
- statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
- statable_gui/transition_editor_direct/dialog.py +335 -0
- statable_gui/transition_editor_direct/draft.py +270 -0
- statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
- statable_gui/transition_editor_direct/overview_tab.py +173 -0
- statable_gui/transition_editor_direct/palette_widget.py +233 -0
- statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
- statable_gui/transition_editor_direct/relations_tab.py +230 -0
- statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
- statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
- statable_gui/transition_editor_direct/transitions_tab.py +620 -0
- statable_gui/validation_dialog.py +285 -0
- statable_gui/widgets.py +1315 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
"""Management dialog for interrupt handlers, device resources, timer settings (multi-timer support / ISR support)"""
|
|
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, QTabWidget, QWidget,
|
|
9
|
+
QTableWidget, QTableWidgetItem, QPushButton, QLabel, QHeaderView,
|
|
10
|
+
QComboBox, QLineEdit, QDialogButtonBox, QMessageBox,
|
|
11
|
+
QAbstractItemView, QSpinBox, QCheckBox, QFormLayout,
|
|
12
|
+
QInputDialog
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from statable.global_defs import (
|
|
16
|
+
GlobalDefinitions,
|
|
17
|
+
InterruptHandlerDef,
|
|
18
|
+
InterruptAction,
|
|
19
|
+
DevicePlaceholderDef,
|
|
20
|
+
TimerBaseDef,
|
|
21
|
+
TimerDerivedDef,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .condition_edit_dialog import ConditionEditDialog
|
|
25
|
+
from .action_edit_dialog import ActionEditDialog
|
|
26
|
+
from .logger import StaTableLogger
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DoubleClickTable(QTableWidget):
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
super().__init__(*args, **kwargs)
|
|
32
|
+
self.setEditTriggers(QAbstractItemView.NoEditTriggers)
|
|
33
|
+
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
|
34
|
+
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
|
35
|
+
|
|
36
|
+
def mouseDoubleClickEvent(self, event: QMouseEvent):
|
|
37
|
+
pos = event.position().toPoint()
|
|
38
|
+
item = self.itemAt(pos)
|
|
39
|
+
if item:
|
|
40
|
+
row = item.row()
|
|
41
|
+
StaTableLogger.debug(f"DoubleClickTable.mouseDoubleClickEvent: row={row}")
|
|
42
|
+
self.cellDoubleClicked.emit(row, item.column())
|
|
43
|
+
else:
|
|
44
|
+
StaTableLogger.debug("DoubleClickTable.mouseDoubleClickEvent: no item")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InterruptEditDialog(QDialog):
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
parent=None,
|
|
51
|
+
event_names=None,
|
|
52
|
+
interrupt: Optional[InterruptHandlerDef] = None,
|
|
53
|
+
global_defs: Optional[GlobalDefinitions] = None,
|
|
54
|
+
role_functions: Optional[dict] = None,
|
|
55
|
+
):
|
|
56
|
+
super().__init__(parent)
|
|
57
|
+
self.setWindowTitle(self.tr("Edit interrupt handler"))
|
|
58
|
+
self.setMinimumSize(1100, 800)
|
|
59
|
+
self.event_names = event_names or []
|
|
60
|
+
self.global_defs = global_defs if global_defs else GlobalDefinitions()
|
|
61
|
+
self.role_functions = role_functions if role_functions is not None else {}
|
|
62
|
+
|
|
63
|
+
layout = QVBoxLayout(self)
|
|
64
|
+
|
|
65
|
+
title = QLabel(self.tr("Edit interrupt handler"))
|
|
66
|
+
title.setFont(QFont("sans-serif", 14, QFont.Bold))
|
|
67
|
+
title.setAlignment(Qt.AlignCenter)
|
|
68
|
+
layout.addWidget(title)
|
|
69
|
+
|
|
70
|
+
form = QFormLayout()
|
|
71
|
+
layout.addLayout(form)
|
|
72
|
+
|
|
73
|
+
self.title_edit = QLineEdit()
|
|
74
|
+
if interrupt:
|
|
75
|
+
self.title_edit.setText(interrupt.title)
|
|
76
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
77
|
+
self.title_edit.setToolTip(self.tr("Enter the title of this interrupt handler. If empty, a provisional title is set automatically."))
|
|
78
|
+
form.addRow("Title *", self.title_edit)
|
|
79
|
+
|
|
80
|
+
self.name_edit = QLineEdit()
|
|
81
|
+
if interrupt:
|
|
82
|
+
self.name_edit.setText(interrupt.name)
|
|
83
|
+
self.name_edit.setToolTip(self.tr("Please enter an interrupt name (e.g., TIMER0_IRQHandler)"))
|
|
84
|
+
form.addRow("Interrupt name", self.name_edit)
|
|
85
|
+
|
|
86
|
+
self.desc_edit = QLineEdit()
|
|
87
|
+
if interrupt:
|
|
88
|
+
self.desc_edit.setText(interrupt.description)
|
|
89
|
+
self.desc_edit.setToolTip(self.tr("Enter the description of this interrupt handler"))
|
|
90
|
+
form.addRow("Description", self.desc_edit)
|
|
91
|
+
|
|
92
|
+
self.event_combo = QComboBox()
|
|
93
|
+
self.event_combo.setEditable(True)
|
|
94
|
+
self.event_combo.setInsertPolicy(QComboBox.NoInsert)
|
|
95
|
+
self.event_combo.addItem(self.tr(""))
|
|
96
|
+
self.event_combo.addItems(self.event_names)
|
|
97
|
+
if interrupt and interrupt.event_names:
|
|
98
|
+
self.event_combo.setCurrentText(interrupt.event_names[0])
|
|
99
|
+
self.event_combo.setToolTip(
|
|
100
|
+
self.tr("Select or enter the state transition event name to notify from the ISR.\n"
|
|
101
|
+
"Entering a new event name registers it as-is.")
|
|
102
|
+
)
|
|
103
|
+
form.addRow("Event name", self.event_combo)
|
|
104
|
+
|
|
105
|
+
self.timer_check = QCheckBox()
|
|
106
|
+
self.timer_check.setChecked(interrupt.is_timer if interrupt else False)
|
|
107
|
+
self.timer_check.setToolTip(self.tr("Check this if it is a timer interrupt"))
|
|
108
|
+
form.addRow("Timer interrupt", self.timer_check)
|
|
109
|
+
|
|
110
|
+
layout.addWidget(QLabel(self.tr("Conditional action list:")))
|
|
111
|
+
help_label = QLabel(
|
|
112
|
+
self.tr("Describe \"state transition condition\" and \"action code\" in each row.\n"
|
|
113
|
+
"If the state transition condition is empty, the action is executed unconditionally.\n"
|
|
114
|
+
"Double-click to edit each cell.")
|
|
115
|
+
)
|
|
116
|
+
help_label.setStyleSheet("color: gray; font-size: 11px;")
|
|
117
|
+
layout.addWidget(help_label)
|
|
118
|
+
|
|
119
|
+
self.action_table = DoubleClickTable(0, 2)
|
|
120
|
+
self.action_table.setHorizontalHeaderLabels(["State transition condition", "Action code"])
|
|
121
|
+
self.action_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
122
|
+
self.action_table.cellDoubleClicked.connect(self.on_action_double_clicked)
|
|
123
|
+
layout.addWidget(self.action_table, stretch=1)
|
|
124
|
+
|
|
125
|
+
btn_layout = QHBoxLayout()
|
|
126
|
+
add_btn = QPushButton(self.tr("Add row"))
|
|
127
|
+
add_btn.clicked.connect(lambda: self.add_action_row())
|
|
128
|
+
del_btn = QPushButton(self.tr("Delete row"))
|
|
129
|
+
del_btn.clicked.connect(lambda: self.delete_action_row())
|
|
130
|
+
btn_layout.addWidget(add_btn)
|
|
131
|
+
btn_layout.addWidget(del_btn)
|
|
132
|
+
btn_layout.addStretch()
|
|
133
|
+
layout.addLayout(btn_layout)
|
|
134
|
+
|
|
135
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
136
|
+
buttons.accepted.connect(self._on_accept)
|
|
137
|
+
buttons.rejected.connect(self.reject)
|
|
138
|
+
layout.addWidget(buttons)
|
|
139
|
+
|
|
140
|
+
if interrupt and interrupt.actions:
|
|
141
|
+
for act in interrupt.actions:
|
|
142
|
+
self.add_action_row(act.condition, act.action)
|
|
143
|
+
else:
|
|
144
|
+
self.add_action_row()
|
|
145
|
+
|
|
146
|
+
StaTableLogger.debug("InterruptEditDialog initialized")
|
|
147
|
+
|
|
148
|
+
def add_action_row(self, condition: str = "", action: str = ""):
|
|
149
|
+
row = self.action_table.rowCount()
|
|
150
|
+
self.action_table.insertRow(row)
|
|
151
|
+
|
|
152
|
+
condition_item = QTableWidgetItem(condition.replace('\n', ' ; ') if condition else "")
|
|
153
|
+
condition_item.setToolTip(self.tr("Double-click to edit the state transition condition"))
|
|
154
|
+
condition_item.setData(Qt.UserRole, condition)
|
|
155
|
+
self.action_table.setItem(row, 0, condition_item)
|
|
156
|
+
|
|
157
|
+
action_item = QTableWidgetItem(action.replace('\n', ' ; ') if action else "")
|
|
158
|
+
action_item.setToolTip(self.tr("Double-click to edit the action code"))
|
|
159
|
+
action_item.setData(Qt.UserRole, action)
|
|
160
|
+
self.action_table.setItem(row, 1, action_item)
|
|
161
|
+
|
|
162
|
+
def delete_action_row(self):
|
|
163
|
+
row = self.action_table.currentRow()
|
|
164
|
+
if row >= 0:
|
|
165
|
+
self.action_table.removeRow(row)
|
|
166
|
+
|
|
167
|
+
def on_action_double_clicked(self, row, col):
|
|
168
|
+
StaTableLogger.debug(f"on_action_double_clicked: row={row}, col={col}")
|
|
169
|
+
|
|
170
|
+
if col == 0:
|
|
171
|
+
item = self.action_table.item(row, 0)
|
|
172
|
+
if not item:
|
|
173
|
+
return
|
|
174
|
+
current_condition = item.data(Qt.UserRole) if item.data(Qt.UserRole) else ""
|
|
175
|
+
dlg = ConditionEditDialog(
|
|
176
|
+
self,
|
|
177
|
+
condition_text=current_condition,
|
|
178
|
+
global_defs=self.global_defs,
|
|
179
|
+
role_functions=self.role_functions
|
|
180
|
+
)
|
|
181
|
+
if dlg.exec() == QDialog.Accepted:
|
|
182
|
+
new_condition = dlg.get_condition_text()
|
|
183
|
+
item.setText(new_condition.replace('\n', ' ; '))
|
|
184
|
+
item.setData(Qt.UserRole, new_condition)
|
|
185
|
+
|
|
186
|
+
elif col == 1:
|
|
187
|
+
item = self.action_table.item(row, 1)
|
|
188
|
+
if not item:
|
|
189
|
+
return
|
|
190
|
+
current_action = item.data(Qt.UserRole) if item.data(Qt.UserRole) else ""
|
|
191
|
+
dlg = ActionEditDialog(
|
|
192
|
+
self,
|
|
193
|
+
action_text=current_action,
|
|
194
|
+
role_functions=self.role_functions,
|
|
195
|
+
global_defs=self.global_defs
|
|
196
|
+
)
|
|
197
|
+
if dlg.exec() == QDialog.Accepted:
|
|
198
|
+
new_action = dlg.get_action_text()
|
|
199
|
+
item.setText(new_action.replace('\n', ' ; '))
|
|
200
|
+
item.setData(Qt.UserRole, new_action)
|
|
201
|
+
|
|
202
|
+
def _on_accept(self):
|
|
203
|
+
if not self.title_edit.text().strip():
|
|
204
|
+
auto_title = f"Interrupt: {self.name_edit.text().strip() or '(unnamed)'}"
|
|
205
|
+
self.title_edit.setText(auto_title)
|
|
206
|
+
StaTableLogger.debug(f"Auto title generated: '{auto_title}'")
|
|
207
|
+
self.accept()
|
|
208
|
+
|
|
209
|
+
# Stage 3: auto-extract used_role_functions / used_variables
|
|
210
|
+
def get_interrupt(self) -> InterruptHandlerDef:
|
|
211
|
+
actions = []
|
|
212
|
+
for row in range(self.action_table.rowCount()):
|
|
213
|
+
condition_item = self.action_table.item(row, 0)
|
|
214
|
+
action_item = self.action_table.item(row, 1)
|
|
215
|
+
if not condition_item or not action_item:
|
|
216
|
+
continue
|
|
217
|
+
condition = condition_item.data(Qt.UserRole) if condition_item.data(Qt.UserRole) else ""
|
|
218
|
+
action = action_item.data(Qt.UserRole) if action_item.data(Qt.UserRole) else ""
|
|
219
|
+
if not condition and not action:
|
|
220
|
+
continue
|
|
221
|
+
actions.append(InterruptAction(condition=condition, action=action))
|
|
222
|
+
|
|
223
|
+
event_names = []
|
|
224
|
+
if self.event_combo.currentText().strip():
|
|
225
|
+
event_names.append(self.event_combo.currentText().strip())
|
|
226
|
+
|
|
227
|
+
handler = InterruptHandlerDef(
|
|
228
|
+
name=self.name_edit.text().strip(),
|
|
229
|
+
description=self.desc_edit.text().strip(),
|
|
230
|
+
event_names=event_names,
|
|
231
|
+
is_timer=self.timer_check.isChecked(),
|
|
232
|
+
actions=actions,
|
|
233
|
+
title=self.title_edit.text().strip(),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Auto-extract used role functions and variables
|
|
237
|
+
try:
|
|
238
|
+
from codegen.interrupt_generator import InterruptGenerator
|
|
239
|
+
gen = InterruptGenerator()
|
|
240
|
+
gen.update_handler_symbols(handler)
|
|
241
|
+
StaTableLogger.debug(
|
|
242
|
+
f"InterruptEditDialog.get_interrupt: "
|
|
243
|
+
f"used_rfs={handler.used_role_functions}, "
|
|
244
|
+
f"used_vars={handler.used_variables}"
|
|
245
|
+
)
|
|
246
|
+
except Exception as e:
|
|
247
|
+
StaTableLogger.debug(
|
|
248
|
+
f"InterruptEditDialog.get_interrupt: "
|
|
249
|
+
f"symbol extraction skipped: {e}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return handler
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class DevicePlaceholderEditDialog(QDialog):
|
|
256
|
+
def __init__(self, parent=None, placeholder: Optional[DevicePlaceholderDef] = None):
|
|
257
|
+
super().__init__(parent)
|
|
258
|
+
self.setWindowTitle(self.tr("Device resource placeholder definitionEdit"))
|
|
259
|
+
self.setMinimumWidth(450)
|
|
260
|
+
|
|
261
|
+
form = QFormLayout(self)
|
|
262
|
+
|
|
263
|
+
self.title_edit = QLineEdit()
|
|
264
|
+
if placeholder:
|
|
265
|
+
self.title_edit.setText(placeholder.title)
|
|
266
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
267
|
+
form.addRow("Title *", self.title_edit)
|
|
268
|
+
|
|
269
|
+
self.name_edit = QLineEdit()
|
|
270
|
+
if placeholder:
|
|
271
|
+
self.name_edit.setText(placeholder.name)
|
|
272
|
+
form.addRow("Placeholder name", self.name_edit)
|
|
273
|
+
|
|
274
|
+
self.desc_edit = QLineEdit()
|
|
275
|
+
if placeholder:
|
|
276
|
+
self.desc_edit.setText(placeholder.description)
|
|
277
|
+
form.addRow("Description", self.desc_edit)
|
|
278
|
+
|
|
279
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
280
|
+
buttons.accepted.connect(self._on_accept)
|
|
281
|
+
buttons.rejected.connect(self.reject)
|
|
282
|
+
form.addRow(buttons)
|
|
283
|
+
|
|
284
|
+
def _on_accept(self):
|
|
285
|
+
if not self.title_edit.text().strip():
|
|
286
|
+
auto_title = f"Device: {self.name_edit.text().strip() or '(unnamed)'}"
|
|
287
|
+
self.title_edit.setText(auto_title)
|
|
288
|
+
self.accept()
|
|
289
|
+
|
|
290
|
+
def get_placeholder(self) -> DevicePlaceholderDef:
|
|
291
|
+
return DevicePlaceholderDef(
|
|
292
|
+
name=self.name_edit.text().strip(),
|
|
293
|
+
description=self.desc_edit.text().strip(),
|
|
294
|
+
title=self.title_edit.text().strip(),
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TimerBaseEditDialog(QDialog):
|
|
299
|
+
def __init__(self, parent=None, timer_base: TimerBaseDef = None):
|
|
300
|
+
super().__init__(parent)
|
|
301
|
+
self.setWindowTitle(self.tr("Timer base variableEdit"))
|
|
302
|
+
self.setMinimumWidth(450)
|
|
303
|
+
|
|
304
|
+
form = QFormLayout(self)
|
|
305
|
+
|
|
306
|
+
self.title_edit = QLineEdit()
|
|
307
|
+
if timer_base:
|
|
308
|
+
self.title_edit.setText(timer_base.title)
|
|
309
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
310
|
+
form.addRow("Title *", self.title_edit)
|
|
311
|
+
|
|
312
|
+
self.var_edit = QLineEdit()
|
|
313
|
+
if timer_base:
|
|
314
|
+
self.var_edit.setText(timer_base.variable_name)
|
|
315
|
+
form.addRow("Base variable name", self.var_edit)
|
|
316
|
+
|
|
317
|
+
self.unit_edit = QLineEdit()
|
|
318
|
+
if timer_base:
|
|
319
|
+
self.unit_edit.setText(timer_base.unit)
|
|
320
|
+
form.addRow("Unit", self.unit_edit)
|
|
321
|
+
|
|
322
|
+
self.type_edit = QLineEdit()
|
|
323
|
+
if timer_base:
|
|
324
|
+
self.type_edit.setText(timer_base.data_type)
|
|
325
|
+
form.addRow("Type", self.type_edit)
|
|
326
|
+
|
|
327
|
+
self.interrupt_edit = QLineEdit()
|
|
328
|
+
if timer_base:
|
|
329
|
+
self.interrupt_edit.setText(timer_base.interrupt_name)
|
|
330
|
+
self.interrupt_edit.setPlaceholderText(self.tr("Interrupt name driving this timer (optional)"))
|
|
331
|
+
form.addRow("Interrupt name", self.interrupt_edit)
|
|
332
|
+
|
|
333
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
334
|
+
buttons.accepted.connect(self._on_accept)
|
|
335
|
+
buttons.rejected.connect(self.reject)
|
|
336
|
+
form.addRow(buttons)
|
|
337
|
+
|
|
338
|
+
def _on_accept(self):
|
|
339
|
+
if not self.title_edit.text().strip():
|
|
340
|
+
auto_title = f"Timer base: {self.var_edit.text().strip() or '(unnamed)'}"
|
|
341
|
+
self.title_edit.setText(auto_title)
|
|
342
|
+
self.accept()
|
|
343
|
+
|
|
344
|
+
def get_timer_base(self) -> TimerBaseDef:
|
|
345
|
+
return TimerBaseDef(
|
|
346
|
+
variable_name=self.var_edit.text().strip(),
|
|
347
|
+
unit=self.unit_edit.text().strip(),
|
|
348
|
+
data_type=self.type_edit.text().strip(),
|
|
349
|
+
title=self.title_edit.text().strip(),
|
|
350
|
+
interrupt_name=self.interrupt_edit.text().strip(),
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class TimerDerivedEditDialog(QDialog):
|
|
355
|
+
def __init__(self, parent=None, derived: Optional[TimerDerivedDef] = None):
|
|
356
|
+
super().__init__(parent)
|
|
357
|
+
self.setWindowTitle(self.tr("Edit derived timer variable"))
|
|
358
|
+
self.setMinimumWidth(450)
|
|
359
|
+
|
|
360
|
+
form = QFormLayout(self)
|
|
361
|
+
|
|
362
|
+
self.title_edit = QLineEdit()
|
|
363
|
+
if derived:
|
|
364
|
+
self.title_edit.setText(derived.title)
|
|
365
|
+
self.title_edit.setPlaceholderText(self.tr("Label shown in the list (auto-set if empty)"))
|
|
366
|
+
form.addRow("Title *", self.title_edit)
|
|
367
|
+
|
|
368
|
+
self.period_edit = QLineEdit()
|
|
369
|
+
if derived:
|
|
370
|
+
self.period_edit.setText(derived.period_name)
|
|
371
|
+
form.addRow("Period name", self.period_edit)
|
|
372
|
+
|
|
373
|
+
self.mult_spin = QSpinBox()
|
|
374
|
+
self.mult_spin.setRange(1, 1000000)
|
|
375
|
+
if derived:
|
|
376
|
+
self.mult_spin.setValue(derived.multiplier)
|
|
377
|
+
form.addRow("Multiplier", self.mult_spin)
|
|
378
|
+
|
|
379
|
+
self.var_edit = QLineEdit()
|
|
380
|
+
if derived:
|
|
381
|
+
self.var_edit.setText(derived.variable_name)
|
|
382
|
+
form.addRow("Variable name", self.var_edit)
|
|
383
|
+
|
|
384
|
+
self.type_edit = QLineEdit()
|
|
385
|
+
if derived:
|
|
386
|
+
self.type_edit.setText(derived.data_type)
|
|
387
|
+
form.addRow("Type", self.type_edit)
|
|
388
|
+
|
|
389
|
+
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
390
|
+
buttons.accepted.connect(self._on_accept)
|
|
391
|
+
buttons.rejected.connect(self.reject)
|
|
392
|
+
form.addRow(buttons)
|
|
393
|
+
|
|
394
|
+
def _on_accept(self):
|
|
395
|
+
if not self.title_edit.text().strip():
|
|
396
|
+
auto_title = f"Timer: {self.var_edit.text().strip() or '(unnamed)'}"
|
|
397
|
+
self.title_edit.setText(auto_title)
|
|
398
|
+
self.accept()
|
|
399
|
+
|
|
400
|
+
def get_derived(self) -> TimerDerivedDef:
|
|
401
|
+
return TimerDerivedDef(
|
|
402
|
+
period_name=self.period_edit.text().strip(),
|
|
403
|
+
multiplier=self.mult_spin.value(),
|
|
404
|
+
variable_name=self.var_edit.text().strip(),
|
|
405
|
+
data_type=self.type_edit.text().strip(),
|
|
406
|
+
title=self.title_edit.text().strip(),
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class InterruptHandlerEditDialog(QDialog):
|
|
411
|
+
"""Dialog to manage interrupt handlers, device resources, and timer settings together"""
|
|
412
|
+
|
|
413
|
+
def __init__(
|
|
414
|
+
self,
|
|
415
|
+
global_defs: GlobalDefinitions,
|
|
416
|
+
event_names: Optional[List[str]] = None,
|
|
417
|
+
role_functions: Optional[dict] = None,
|
|
418
|
+
parent=None
|
|
419
|
+
):
|
|
420
|
+
super().__init__(parent)
|
|
421
|
+
self.global_defs = global_defs
|
|
422
|
+
self.event_names = event_names or []
|
|
423
|
+
self.role_functions = role_functions if role_functions is not None else {}
|
|
424
|
+
|
|
425
|
+
self.setWindowTitle(self.tr("Interrupt handlers, device resources, timer settings"))
|
|
426
|
+
self.setMinimumSize(1200, 800)
|
|
427
|
+
|
|
428
|
+
layout = QVBoxLayout(self)
|
|
429
|
+
|
|
430
|
+
self.tabs = QTabWidget()
|
|
431
|
+
layout.addWidget(self.tabs)
|
|
432
|
+
|
|
433
|
+
self.tabs.addTab(self._create_interrupt_tab(), self.tr("Interrupt handler"))
|
|
434
|
+
self.tabs.addTab(self._create_placeholder_tab(), self.tr("Device resource"))
|
|
435
|
+
self.tabs.addTab(self._create_timer_tab(), self.tr("Timer settings"))
|
|
436
|
+
|
|
437
|
+
close_btn = QPushButton(self.tr("Close"))
|
|
438
|
+
close_btn.clicked.connect(self.accept)
|
|
439
|
+
layout.addWidget(close_btn, alignment=Qt.AlignRight)
|
|
440
|
+
|
|
441
|
+
self.refresh_interrupt_table()
|
|
442
|
+
self.refresh_placeholder_table()
|
|
443
|
+
self.refresh_timer_tabs()
|
|
444
|
+
|
|
445
|
+
StaTableLogger.debug("InterruptHandlerEditDialog initialized")
|
|
446
|
+
|
|
447
|
+
# ------------------------------------------------------------------
|
|
448
|
+
# Interrupt handler tab
|
|
449
|
+
# ------------------------------------------------------------------
|
|
450
|
+
def _create_interrupt_tab(self) -> QWidget:
|
|
451
|
+
widget = QWidget()
|
|
452
|
+
layout = QVBoxLayout(widget)
|
|
453
|
+
|
|
454
|
+
self.interrupt_table = DoubleClickTable(0, 6)
|
|
455
|
+
self.interrupt_table.setHorizontalHeaderLabels([
|
|
456
|
+
"Title", "Interrupt name", "Description", "Event name", "Timer", "Number of conditional actions"
|
|
457
|
+
])
|
|
458
|
+
self.interrupt_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
459
|
+
self.interrupt_table.cellDoubleClicked.connect(self.on_interrupt_double_clicked)
|
|
460
|
+
layout.addWidget(self.interrupt_table)
|
|
461
|
+
|
|
462
|
+
btn_layout = QHBoxLayout()
|
|
463
|
+
add_btn = QPushButton(self.tr("Add"))
|
|
464
|
+
add_btn.clicked.connect(self.add_interrupt)
|
|
465
|
+
del_btn = QPushButton(self.tr("Delete"))
|
|
466
|
+
del_btn.clicked.connect(self.delete_interrupt)
|
|
467
|
+
btn_layout.addWidget(add_btn)
|
|
468
|
+
btn_layout.addWidget(del_btn)
|
|
469
|
+
btn_layout.addStretch()
|
|
470
|
+
layout.addLayout(btn_layout)
|
|
471
|
+
|
|
472
|
+
return widget
|
|
473
|
+
|
|
474
|
+
def refresh_interrupt_table(self):
|
|
475
|
+
self.interrupt_table.setRowCount(0)
|
|
476
|
+
for intr in self.global_defs.interrupts:
|
|
477
|
+
row = self.interrupt_table.rowCount()
|
|
478
|
+
self.interrupt_table.insertRow(row)
|
|
479
|
+
self.interrupt_table.setItem(row, 0, QTableWidgetItem(intr.title))
|
|
480
|
+
self.interrupt_table.setItem(row, 1, QTableWidgetItem(intr.name))
|
|
481
|
+
self.interrupt_table.setItem(row, 2, QTableWidgetItem(intr.description))
|
|
482
|
+
self.interrupt_table.setItem(row, 3, QTableWidgetItem(", ".join(intr.event_names)))
|
|
483
|
+
self.interrupt_table.setItem(row, 4, QTableWidgetItem("✔" if intr.is_timer else ""))
|
|
484
|
+
self.interrupt_table.setItem(row, 5, QTableWidgetItem(str(len(intr.actions))))
|
|
485
|
+
|
|
486
|
+
def on_interrupt_double_clicked(self, row, col):
|
|
487
|
+
StaTableLogger.debug(f"on_interrupt_double_clicked: row={row}, col={col}")
|
|
488
|
+
if row < 0 or row >= len(self.global_defs.interrupts):
|
|
489
|
+
return
|
|
490
|
+
target = self.global_defs.interrupts[row]
|
|
491
|
+
dlg = InterruptEditDialog(
|
|
492
|
+
self,
|
|
493
|
+
event_names=self.event_names,
|
|
494
|
+
interrupt=target,
|
|
495
|
+
global_defs=self.global_defs,
|
|
496
|
+
role_functions=self.role_functions
|
|
497
|
+
)
|
|
498
|
+
if dlg.exec() == QDialog.Accepted:
|
|
499
|
+
new_intr = dlg.get_interrupt()
|
|
500
|
+
if not new_intr.name:
|
|
501
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter an interrupt name."))
|
|
502
|
+
return
|
|
503
|
+
self.global_defs.interrupts[row] = new_intr
|
|
504
|
+
self.refresh_interrupt_table()
|
|
505
|
+
|
|
506
|
+
def add_interrupt(self):
|
|
507
|
+
dlg = InterruptEditDialog(
|
|
508
|
+
self,
|
|
509
|
+
event_names=self.event_names,
|
|
510
|
+
global_defs=self.global_defs,
|
|
511
|
+
role_functions=self.role_functions
|
|
512
|
+
)
|
|
513
|
+
if dlg.exec() == QDialog.Accepted:
|
|
514
|
+
intr = dlg.get_interrupt()
|
|
515
|
+
if not intr.name:
|
|
516
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter an interrupt name."))
|
|
517
|
+
return
|
|
518
|
+
self.global_defs.interrupts.append(intr)
|
|
519
|
+
self.refresh_interrupt_table()
|
|
520
|
+
|
|
521
|
+
def delete_interrupt(self):
|
|
522
|
+
row = self.interrupt_table.currentRow()
|
|
523
|
+
if 0 <= row < len(self.global_defs.interrupts):
|
|
524
|
+
self.global_defs.interrupts.pop(row)
|
|
525
|
+
self.refresh_interrupt_table()
|
|
526
|
+
|
|
527
|
+
# ------------------------------------------------------------------
|
|
528
|
+
# Device resource tab
|
|
529
|
+
# ------------------------------------------------------------------
|
|
530
|
+
def _create_placeholder_tab(self) -> QWidget:
|
|
531
|
+
widget = QWidget()
|
|
532
|
+
layout = QVBoxLayout(widget)
|
|
533
|
+
|
|
534
|
+
self.placeholder_table = DoubleClickTable(0, 3)
|
|
535
|
+
self.placeholder_table.setHorizontalHeaderLabels(["Title", "Placeholder name", "Description"])
|
|
536
|
+
self.placeholder_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
537
|
+
self.placeholder_table.cellDoubleClicked.connect(self.on_placeholder_double_clicked)
|
|
538
|
+
layout.addWidget(self.placeholder_table)
|
|
539
|
+
|
|
540
|
+
btn_layout = QHBoxLayout()
|
|
541
|
+
add_btn = QPushButton(self.tr("Add"))
|
|
542
|
+
add_btn.clicked.connect(self.add_placeholder)
|
|
543
|
+
del_btn = QPushButton(self.tr("Delete"))
|
|
544
|
+
del_btn.clicked.connect(self.delete_placeholder)
|
|
545
|
+
btn_layout.addWidget(add_btn)
|
|
546
|
+
btn_layout.addWidget(del_btn)
|
|
547
|
+
btn_layout.addStretch()
|
|
548
|
+
layout.addLayout(btn_layout)
|
|
549
|
+
|
|
550
|
+
return widget
|
|
551
|
+
|
|
552
|
+
def refresh_placeholder_table(self):
|
|
553
|
+
self.placeholder_table.setRowCount(0)
|
|
554
|
+
for ph in self.global_defs.placeholders:
|
|
555
|
+
row = self.placeholder_table.rowCount()
|
|
556
|
+
self.placeholder_table.insertRow(row)
|
|
557
|
+
self.placeholder_table.setItem(row, 0, QTableWidgetItem(ph.title))
|
|
558
|
+
self.placeholder_table.setItem(row, 1, QTableWidgetItem(ph.name))
|
|
559
|
+
self.placeholder_table.setItem(row, 2, QTableWidgetItem(ph.description))
|
|
560
|
+
|
|
561
|
+
def on_placeholder_double_clicked(self, row, col):
|
|
562
|
+
StaTableLogger.debug(f"on_placeholder_double_clicked: row={row}, col={col}")
|
|
563
|
+
if row < 0 or row >= len(self.global_defs.placeholders):
|
|
564
|
+
return
|
|
565
|
+
target = self.global_defs.placeholders[row]
|
|
566
|
+
dlg = DevicePlaceholderEditDialog(self, placeholder=target)
|
|
567
|
+
if dlg.exec() == QDialog.Accepted:
|
|
568
|
+
new_ph = dlg.get_placeholder()
|
|
569
|
+
if not new_ph.name:
|
|
570
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a placeholder name."))
|
|
571
|
+
return
|
|
572
|
+
self.global_defs.placeholders[row] = new_ph
|
|
573
|
+
self.refresh_placeholder_table()
|
|
574
|
+
|
|
575
|
+
def add_placeholder(self):
|
|
576
|
+
dlg = DevicePlaceholderEditDialog(self)
|
|
577
|
+
if dlg.exec() == QDialog.Accepted:
|
|
578
|
+
ph = dlg.get_placeholder()
|
|
579
|
+
if not ph.name:
|
|
580
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a placeholder name."))
|
|
581
|
+
return
|
|
582
|
+
self.global_defs.placeholders.append(ph)
|
|
583
|
+
self.refresh_placeholder_table()
|
|
584
|
+
|
|
585
|
+
def delete_placeholder(self):
|
|
586
|
+
row = self.placeholder_table.currentRow()
|
|
587
|
+
if 0 <= row < len(self.global_defs.placeholders):
|
|
588
|
+
self.global_defs.placeholders.pop(row)
|
|
589
|
+
self.refresh_placeholder_table()
|
|
590
|
+
|
|
591
|
+
# ------------------------------------------------------------------
|
|
592
|
+
# Timer settings tab (multi-base-timer support)
|
|
593
|
+
# ------------------------------------------------------------------
|
|
594
|
+
def _create_timer_tab(self) -> QWidget:
|
|
595
|
+
widget = QWidget()
|
|
596
|
+
layout = QVBoxLayout(widget)
|
|
597
|
+
|
|
598
|
+
# Base timer tab
|
|
599
|
+
self.timer_tabs = QTabWidget()
|
|
600
|
+
self.timer_tabs.setTabsClosable(True)
|
|
601
|
+
self.timer_tabs.tabCloseRequested.connect(self.close_timer_tab)
|
|
602
|
+
self.timer_tabs.tabBarDoubleClicked.connect(self.rename_timer_tab)
|
|
603
|
+
layout.addWidget(self.timer_tabs)
|
|
604
|
+
|
|
605
|
+
# \"+\" button
|
|
606
|
+
add_timer_btn = QPushButton(self.tr("+"))
|
|
607
|
+
add_timer_btn.setFixedWidth(30)
|
|
608
|
+
add_timer_btn.setToolTip(self.tr("Add base timer"))
|
|
609
|
+
add_timer_btn.clicked.connect(self.add_timer_base)
|
|
610
|
+
layout.addWidget(add_timer_btn, alignment=Qt.AlignRight)
|
|
611
|
+
|
|
612
|
+
return widget
|
|
613
|
+
|
|
614
|
+
def refresh_timer_tabs(self):
|
|
615
|
+
"""Display all base timers as tabs"""
|
|
616
|
+
self.timer_tabs.clear()
|
|
617
|
+
|
|
618
|
+
all_timers = [self.global_defs.timer_base] + self.global_defs.extra_timers
|
|
619
|
+
for timer in all_timers:
|
|
620
|
+
tab = self._create_timer_base_tab(timer)
|
|
621
|
+
self.timer_tabs.addTab(tab, timer.title)
|
|
622
|
+
|
|
623
|
+
def _create_timer_base_tab(self, timer: TimerBaseDef) -> QWidget:
|
|
624
|
+
"""Create an edit tab for one base timer"""
|
|
625
|
+
widget = QWidget()
|
|
626
|
+
layout = QVBoxLayout(widget)
|
|
627
|
+
|
|
628
|
+
form = QFormLayout()
|
|
629
|
+
layout.addLayout(form)
|
|
630
|
+
|
|
631
|
+
# Title (editable, linked to tab name)
|
|
632
|
+
title_edit = QLineEdit(timer.title)
|
|
633
|
+
title_edit.textChanged.connect(lambda text, t=timer: self._on_timer_title_changed(t, text))
|
|
634
|
+
form.addRow("Title *", title_edit)
|
|
635
|
+
|
|
636
|
+
var_edit = QLineEdit(timer.variable_name)
|
|
637
|
+
var_edit.textChanged.connect(lambda text, t=timer: self._on_timer_var_changed(t, text))
|
|
638
|
+
form.addRow("Base variable name", var_edit)
|
|
639
|
+
|
|
640
|
+
unit_edit = QLineEdit(timer.unit)
|
|
641
|
+
unit_edit.textChanged.connect(lambda text, t=timer: self._on_timer_unit_changed(t, text))
|
|
642
|
+
form.addRow("Unit", unit_edit)
|
|
643
|
+
|
|
644
|
+
type_edit = QLineEdit(timer.data_type)
|
|
645
|
+
type_edit.textChanged.connect(lambda text, t=timer: self._on_timer_type_changed(t, text))
|
|
646
|
+
form.addRow("Type", type_edit)
|
|
647
|
+
|
|
648
|
+
interrupt_edit = QLineEdit(timer.interrupt_name)
|
|
649
|
+
interrupt_edit.textChanged.connect(lambda text, t=timer: self._on_timer_interrupt_changed(t, text))
|
|
650
|
+
interrupt_edit.setPlaceholderText(self.tr("Interrupt name (optional)"))
|
|
651
|
+
form.addRow("Interrupt name", interrupt_edit)
|
|
652
|
+
|
|
653
|
+
# Edit button
|
|
654
|
+
edit_btn = QPushButton(self.tr("Edit base timer..."))
|
|
655
|
+
edit_btn.clicked.connect(lambda _checked, t=timer: self.edit_timer_base(t))
|
|
656
|
+
form.addRow("", edit_btn)
|
|
657
|
+
|
|
658
|
+
layout.addWidget(QLabel(self.tr("Derived timer variable:")))
|
|
659
|
+
derived_table = DoubleClickTable(0, 5)
|
|
660
|
+
derived_table.setHorizontalHeaderLabels(["Title", "Period name", "Multiplier", "Variable name", "Type"])
|
|
661
|
+
derived_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
662
|
+
derived_table.cellDoubleClicked.connect(lambda row, col, t=timer: self.on_derived_double_clicked(t, row, col))
|
|
663
|
+
layout.addWidget(derived_table)
|
|
664
|
+
|
|
665
|
+
# Derived timer button
|
|
666
|
+
btn_layout = QHBoxLayout()
|
|
667
|
+
add_btn = QPushButton(self.tr("Add"))
|
|
668
|
+
add_btn.clicked.connect(lambda _checked, t=timer: self.add_derived(t))
|
|
669
|
+
del_btn = QPushButton(self.tr("Delete"))
|
|
670
|
+
del_btn.clicked.connect(lambda _checked, t=timer: self.delete_derived(t))
|
|
671
|
+
btn_layout.addWidget(add_btn)
|
|
672
|
+
btn_layout.addWidget(del_btn)
|
|
673
|
+
btn_layout.addStretch()
|
|
674
|
+
layout.addLayout(btn_layout)
|
|
675
|
+
|
|
676
|
+
self._refresh_derived_table(derived_table, timer)
|
|
677
|
+
|
|
678
|
+
return widget
|
|
679
|
+
|
|
680
|
+
def _refresh_derived_table(self, table: DoubleClickTable, timer: TimerBaseDef):
|
|
681
|
+
"""Display derived timers of the specified timer in the table"""
|
|
682
|
+
table.setRowCount(0)
|
|
683
|
+
for d in timer.derived:
|
|
684
|
+
row = table.rowCount()
|
|
685
|
+
table.insertRow(row)
|
|
686
|
+
table.setItem(row, 0, QTableWidgetItem(d.title))
|
|
687
|
+
table.setItem(row, 1, QTableWidgetItem(d.period_name))
|
|
688
|
+
table.setItem(row, 2, QTableWidgetItem(str(d.multiplier)))
|
|
689
|
+
table.setItem(row, 3, QTableWidgetItem(d.variable_name))
|
|
690
|
+
table.setItem(row, 4, QTableWidgetItem(d.data_type))
|
|
691
|
+
|
|
692
|
+
def _on_timer_title_changed(self, timer: TimerBaseDef, text: str):
|
|
693
|
+
timer.title = text.strip() or f"Timer base: {timer.variable_name}"
|
|
694
|
+
all_timers = [self.global_defs.timer_base] + self.global_defs.extra_timers
|
|
695
|
+
for i, t in enumerate(all_timers):
|
|
696
|
+
if t is timer:
|
|
697
|
+
self.timer_tabs.setTabText(i, timer.title)
|
|
698
|
+
break
|
|
699
|
+
|
|
700
|
+
def _on_timer_var_changed(self, timer: TimerBaseDef, text: str):
|
|
701
|
+
timer.variable_name = text.strip()
|
|
702
|
+
self.global_defs.add_timer_variables()
|
|
703
|
+
|
|
704
|
+
def _on_timer_unit_changed(self, timer: TimerBaseDef, text: str):
|
|
705
|
+
timer.unit = text.strip()
|
|
706
|
+
self.global_defs.add_timer_variables()
|
|
707
|
+
|
|
708
|
+
def _on_timer_type_changed(self, timer: TimerBaseDef, text: str):
|
|
709
|
+
timer.data_type = text.strip()
|
|
710
|
+
self.global_defs.add_timer_variables()
|
|
711
|
+
|
|
712
|
+
def _on_timer_interrupt_changed(self, timer: TimerBaseDef, text: str):
|
|
713
|
+
timer.interrupt_name = text.strip()
|
|
714
|
+
|
|
715
|
+
def add_timer_base(self):
|
|
716
|
+
dlg = TimerBaseEditDialog(self)
|
|
717
|
+
if dlg.exec() == QDialog.Accepted:
|
|
718
|
+
new_timer = dlg.get_timer_base()
|
|
719
|
+
if not new_timer.variable_name:
|
|
720
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a base variable name."))
|
|
721
|
+
return
|
|
722
|
+
self.global_defs.extra_timers.append(new_timer)
|
|
723
|
+
self.global_defs.add_timer_variables()
|
|
724
|
+
self.refresh_timer_tabs()
|
|
725
|
+
StaTableLogger.info(f"Timer base added: {new_timer.title}")
|
|
726
|
+
|
|
727
|
+
def edit_timer_base(self, timer: TimerBaseDef):
|
|
728
|
+
dlg = TimerBaseEditDialog(self, timer_base=timer)
|
|
729
|
+
if dlg.exec() == QDialog.Accepted:
|
|
730
|
+
new_timer = dlg.get_timer_base()
|
|
731
|
+
if not new_timer.variable_name:
|
|
732
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a base variable name."))
|
|
733
|
+
return
|
|
734
|
+
timer.variable_name = new_timer.variable_name
|
|
735
|
+
timer.unit = new_timer.unit
|
|
736
|
+
timer.data_type = new_timer.data_type
|
|
737
|
+
timer.title = new_timer.title
|
|
738
|
+
timer.interrupt_name = new_timer.interrupt_name
|
|
739
|
+
self.global_defs.add_timer_variables()
|
|
740
|
+
self.refresh_timer_tabs()
|
|
741
|
+
StaTableLogger.info(f"Timer base updated: {timer.title}")
|
|
742
|
+
|
|
743
|
+
def close_timer_tab(self, index: int):
|
|
744
|
+
if index == 0:
|
|
745
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("The main timer cannot be deleted."))
|
|
746
|
+
return
|
|
747
|
+
all_timers = [self.global_defs.timer_base] + self.global_defs.extra_timers
|
|
748
|
+
if 0 <= index < len(all_timers):
|
|
749
|
+
timer = all_timers[index]
|
|
750
|
+
reply = QMessageBox.question(
|
|
751
|
+
self, self.tr("Confirm"),
|
|
752
|
+
f"Base timer '{timer.title}': confirm delete?",
|
|
753
|
+
QMessageBox.Yes | QMessageBox.No
|
|
754
|
+
)
|
|
755
|
+
if reply == QMessageBox.Yes:
|
|
756
|
+
self.global_defs.extra_timers.pop(index - 1)
|
|
757
|
+
self.global_defs.add_timer_variables()
|
|
758
|
+
self.refresh_timer_tabs()
|
|
759
|
+
StaTableLogger.info(f"Timer base deleted: {timer.title}")
|
|
760
|
+
|
|
761
|
+
def rename_timer_tab(self, index: int):
|
|
762
|
+
all_timers = [self.global_defs.timer_base] + self.global_defs.extra_timers
|
|
763
|
+
if 0 <= index < len(all_timers):
|
|
764
|
+
current = all_timers[index]
|
|
765
|
+
new_name, ok = QInputDialog.getText(
|
|
766
|
+
self, "Rename timer", "Enter a new timer name:",
|
|
767
|
+
text=current.title
|
|
768
|
+
)
|
|
769
|
+
if ok and new_name.strip():
|
|
770
|
+
current.title = new_name.strip()
|
|
771
|
+
self.timer_tabs.setTabText(index, current.title)
|
|
772
|
+
self.global_defs.add_timer_variables()
|
|
773
|
+
StaTableLogger.info(f"Timer tab renamed: {current.title}")
|
|
774
|
+
|
|
775
|
+
def on_derived_double_clicked(self, timer: TimerBaseDef, row: int, col: int):
|
|
776
|
+
StaTableLogger.debug(f"on_derived_double_clicked: timer='{timer.title}', row={row}, col={col}")
|
|
777
|
+
if row < 0 or row >= len(timer.derived):
|
|
778
|
+
return
|
|
779
|
+
target = timer.derived[row]
|
|
780
|
+
dlg = TimerDerivedEditDialog(self, derived=target)
|
|
781
|
+
if dlg.exec() == QDialog.Accepted:
|
|
782
|
+
new_d = dlg.get_derived()
|
|
783
|
+
if not new_d.period_name:
|
|
784
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a period name."))
|
|
785
|
+
return
|
|
786
|
+
timer.derived[row] = new_d
|
|
787
|
+
self.global_defs.add_timer_variables()
|
|
788
|
+
self.refresh_timer_tabs()
|
|
789
|
+
|
|
790
|
+
def add_derived(self, timer: TimerBaseDef):
|
|
791
|
+
dlg = TimerDerivedEditDialog(self)
|
|
792
|
+
if dlg.exec() == QDialog.Accepted:
|
|
793
|
+
d = dlg.get_derived()
|
|
794
|
+
if not d.period_name:
|
|
795
|
+
QMessageBox.warning(self, self.tr("Warning"), self.tr("Please enter a period name."))
|
|
796
|
+
return
|
|
797
|
+
timer.derived.append(d)
|
|
798
|
+
self.global_defs.add_timer_variables()
|
|
799
|
+
self.refresh_timer_tabs()
|
|
800
|
+
|
|
801
|
+
def delete_derived(self, timer: TimerBaseDef):
|
|
802
|
+
current_widget = self.timer_tabs.currentWidget()
|
|
803
|
+
if current_widget:
|
|
804
|
+
table = current_widget.findChild(DoubleClickTable)
|
|
805
|
+
if table:
|
|
806
|
+
row = table.currentRow()
|
|
807
|
+
if 0 <= row < len(timer.derived):
|
|
808
|
+
timer.derived.pop(row)
|
|
809
|
+
self.global_defs.add_timer_variables()
|
|
810
|
+
self.refresh_timer_tabs()
|