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,486 @@
|
|
|
1
|
+
# statable_gui/code_generation_dialog.py
|
|
2
|
+
"""\nC code generation dialog (PySide6 compatible / multi-layer support)\n"""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, Optional, List
|
|
9
|
+
|
|
10
|
+
from PySide6.QtWidgets import (
|
|
11
|
+
QDialog, QVBoxLayout, QHBoxLayout, QPushButton,
|
|
12
|
+
QLabel, QTextEdit, QFileDialog, QMessageBox,
|
|
13
|
+
QComboBox, QGroupBox, QFormLayout,
|
|
14
|
+
QProgressBar, QLineEdit
|
|
15
|
+
)
|
|
16
|
+
from PySide6.QtCore import Qt
|
|
17
|
+
from PySide6.QtGui import QFont
|
|
18
|
+
|
|
19
|
+
# Path settings
|
|
20
|
+
sys.path.append(
|
|
21
|
+
os.path.dirname(
|
|
22
|
+
os.path.dirname(os.path.abspath(__file__))
|
|
23
|
+
)
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from codegen.c_code_generator import CCodeGenerator
|
|
28
|
+
from codegen.sample_data import SampleDataGenerator
|
|
29
|
+
from codegen.config import (
|
|
30
|
+
CodeGenerationConfig, ConfigManager,
|
|
31
|
+
)
|
|
32
|
+
except ImportError:
|
|
33
|
+
sys.path.append(os.path.join(
|
|
34
|
+
os.path.dirname(
|
|
35
|
+
os.path.dirname(os.path.abspath(__file__))
|
|
36
|
+
),
|
|
37
|
+
'codegen',
|
|
38
|
+
))
|
|
39
|
+
from c_code_generator import CCodeGenerator
|
|
40
|
+
from sample_data import SampleDataGenerator
|
|
41
|
+
from config import (
|
|
42
|
+
CodeGenerationConfig, ConfigManager,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
#Import the settings dialog
|
|
46
|
+
try:
|
|
47
|
+
from .code_generation_settings_dialog import (
|
|
48
|
+
CodeGenerationSettingsDialog,
|
|
49
|
+
)
|
|
50
|
+
except ImportError:
|
|
51
|
+
from code_generation_settings_dialog import (
|
|
52
|
+
CodeGenerationSettingsDialog,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
logger = logging.getLogger(__name__)
|
|
56
|
+
|
|
57
|
+
#Default settings file path
|
|
58
|
+
DEFAULT_SETTINGS_FILE = os.path.join(
|
|
59
|
+
os.path.dirname(os.path.abspath(__file__)),
|
|
60
|
+
"codegen_settings.json",
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ============================================================
|
|
65
|
+
# Warning collector class
|
|
66
|
+
# ============================================================
|
|
67
|
+
class WarningCollector(logging.Handler):
|
|
68
|
+
"""\n Handler that collects logs at WARNING level or higher\n\n usage:\n collector = WarningCollector()\n root = logging.getLogger()\n root.addHandler(collector)\n try:\n ... processing ...\n finally:\n root.removeHandler(collector)\n\n if collector.records:\n show_message_box(collector.records)\n """
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
super().__init__(level=logging.WARNING)
|
|
72
|
+
self.records: List[str] = []
|
|
73
|
+
|
|
74
|
+
def emit(self, record):
|
|
75
|
+
if record.levelno >= logging.WARNING:
|
|
76
|
+
try:
|
|
77
|
+
msg = record.getMessage()
|
|
78
|
+
except Exception:
|
|
79
|
+
msg = str(record.msg)
|
|
80
|
+
self.records.append(msg)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# ============================================================
|
|
84
|
+
# CodeGenerationDialog
|
|
85
|
+
# ============================================================
|
|
86
|
+
class CodeGenerationDialog(QDialog):
|
|
87
|
+
"""C code generation dialog (multi-layer support)"""
|
|
88
|
+
|
|
89
|
+
def __init__(self, state_machine=None,
|
|
90
|
+
global_defs=None,
|
|
91
|
+
parent=None,
|
|
92
|
+
settings_file=None,
|
|
93
|
+
role_function_library=None):
|
|
94
|
+
super().__init__(parent)
|
|
95
|
+
self.state_machine = state_machine
|
|
96
|
+
self.global_defs = global_defs
|
|
97
|
+
self.generated_files: Dict[str, str] = {}
|
|
98
|
+
self.config_manager = ConfigManager()
|
|
99
|
+
self.settings_file = (
|
|
100
|
+
settings_file or DEFAULT_SETTINGS_FILE
|
|
101
|
+
)
|
|
102
|
+
self.role_function_library = role_function_library
|
|
103
|
+
# Multi-layer list (set from outside)
|
|
104
|
+
self.all_layers = None
|
|
105
|
+
|
|
106
|
+
self._load_saved_settings()
|
|
107
|
+
|
|
108
|
+
self.setWindowTitle(self.tr("CCode generation"))
|
|
109
|
+
self.setMinimumSize(800, 600)
|
|
110
|
+
|
|
111
|
+
self._setup_ui()
|
|
112
|
+
self._load_sample_data_if_needed()
|
|
113
|
+
self._load_config_to_ui()
|
|
114
|
+
|
|
115
|
+
#---- Setting read/write ----
|
|
116
|
+
def _load_saved_settings(self):
|
|
117
|
+
try:
|
|
118
|
+
if os.path.exists(self.settings_file):
|
|
119
|
+
with open(self.settings_file, 'r',
|
|
120
|
+
encoding='utf-8') as f:
|
|
121
|
+
data = json.load(f)
|
|
122
|
+
if 'output_directory' in data:
|
|
123
|
+
self.config_manager.update(
|
|
124
|
+
output_directory=data['output_directory'])
|
|
125
|
+
if 'generation_style' in data:
|
|
126
|
+
self.config_manager.update(
|
|
127
|
+
generation_style=data['generation_style'])
|
|
128
|
+
if 'os_type' in data:
|
|
129
|
+
self.config_manager.update(
|
|
130
|
+
os_type=data['os_type'])
|
|
131
|
+
if 'save_with_merge' in data:
|
|
132
|
+
self.config_manager.update(
|
|
133
|
+
save_with_merge=data['save_with_merge'])
|
|
134
|
+
logger.info(
|
|
135
|
+
f"Loaded previous settings: {data}")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.warning(f"Failed to load settings: {e}")
|
|
138
|
+
|
|
139
|
+
def _save_settings(self):
|
|
140
|
+
"""Save current settings"""
|
|
141
|
+
try:
|
|
142
|
+
config = self.config_manager.get_config()
|
|
143
|
+
data = {
|
|
144
|
+
'output_directory': config.output_directory,
|
|
145
|
+
'generation_style': config.generation_style,
|
|
146
|
+
'os_type': config.os_type,
|
|
147
|
+
'save_with_merge': config.save_with_merge,
|
|
148
|
+
}
|
|
149
|
+
settings_dir = os.path.dirname(
|
|
150
|
+
self.settings_file)
|
|
151
|
+
if settings_dir and not os.path.exists(
|
|
152
|
+
settings_dir):
|
|
153
|
+
os.makedirs(settings_dir, exist_ok=True)
|
|
154
|
+
with open(self.settings_file, 'w',
|
|
155
|
+
encoding='utf-8') as f:
|
|
156
|
+
json.dump(data, f, ensure_ascii=False,
|
|
157
|
+
indent=2)
|
|
158
|
+
logger.info(f"Settings saved: {data}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.warning(f"Failed to save settings: {e}")
|
|
161
|
+
|
|
162
|
+
# ---- UI ----
|
|
163
|
+
def _setup_ui(self):
|
|
164
|
+
"""Build UI"""
|
|
165
|
+
main_layout = QVBoxLayout(self)
|
|
166
|
+
|
|
167
|
+
# Settings info group
|
|
168
|
+
info_group = QGroupBox(self.tr("Generation settingsInfo"))
|
|
169
|
+
info_layout = QFormLayout(info_group)
|
|
170
|
+
|
|
171
|
+
# Output destination
|
|
172
|
+
self.output_dir_edit = QLineEdit()
|
|
173
|
+
self.output_dir_edit.setPlaceholderText(
|
|
174
|
+
self.tr("Select output directory"))
|
|
175
|
+
self.output_dir_edit.textChanged.connect(
|
|
176
|
+
self._on_output_dir_changed)
|
|
177
|
+
self.output_dir_btn = QPushButton(self.tr("Browse..."))
|
|
178
|
+
self.output_dir_btn.clicked.connect(
|
|
179
|
+
self._select_output_dir)
|
|
180
|
+
|
|
181
|
+
output_dir_layout = QHBoxLayout()
|
|
182
|
+
output_dir_layout.addWidget(self.output_dir_edit)
|
|
183
|
+
output_dir_layout.addWidget(self.output_dir_btn)
|
|
184
|
+
info_layout.addRow("Output directory:", output_dir_layout)
|
|
185
|
+
|
|
186
|
+
# Generation style
|
|
187
|
+
self.style_combo = QComboBox()
|
|
188
|
+
self.style_combo.addItem(
|
|
189
|
+
self.tr("Table-driven style"), "table_driven")
|
|
190
|
+
self.style_combo.addItem(
|
|
191
|
+
self.tr("switch-case style"), "switch_case")
|
|
192
|
+
self.style_combo.currentIndexChanged.connect(
|
|
193
|
+
self._on_style_changed)
|
|
194
|
+
info_layout.addRow("Generation style:", self.style_combo)
|
|
195
|
+
|
|
196
|
+
# OS type label
|
|
197
|
+
self.os_label = QLabel(self.tr("NonRTOS"))
|
|
198
|
+
info_layout.addRow("OS type:", self.os_label)
|
|
199
|
+
|
|
200
|
+
# Merge settings label
|
|
201
|
+
self.merge_label = QLabel(self.tr("Enabled"))
|
|
202
|
+
info_layout.addRow("Merge:", self.merge_label)
|
|
203
|
+
|
|
204
|
+
# Advanced settings button
|
|
205
|
+
self.settings_btn = QPushButton(self.tr("Advanced settings..."))
|
|
206
|
+
self.settings_btn.clicked.connect(
|
|
207
|
+
self._open_settings_dialog)
|
|
208
|
+
info_layout.addRow("", self.settings_btn)
|
|
209
|
+
|
|
210
|
+
main_layout.addWidget(info_group)
|
|
211
|
+
|
|
212
|
+
# Action buttons
|
|
213
|
+
button_layout = QHBoxLayout()
|
|
214
|
+
|
|
215
|
+
self.generate_btn = QPushButton(self.tr("Code generation"))
|
|
216
|
+
self.generate_btn.clicked.connect(
|
|
217
|
+
self._generate_code)
|
|
218
|
+
button_layout.addWidget(self.generate_btn)
|
|
219
|
+
|
|
220
|
+
self.save_btn = QPushButton(self.tr("Save"))
|
|
221
|
+
self.save_btn.setEnabled(False)
|
|
222
|
+
self.save_btn.clicked.connect(self._save_code)
|
|
223
|
+
button_layout.addWidget(self.save_btn)
|
|
224
|
+
|
|
225
|
+
self.close_btn = QPushButton(self.tr("Close"))
|
|
226
|
+
self.close_btn.clicked.connect(self._on_close)
|
|
227
|
+
button_layout.addWidget(self.close_btn)
|
|
228
|
+
|
|
229
|
+
main_layout.addLayout(button_layout)
|
|
230
|
+
|
|
231
|
+
# Preview area
|
|
232
|
+
preview_label = QLabel(self.tr("Generated code preview:"))
|
|
233
|
+
main_layout.addWidget(preview_label)
|
|
234
|
+
|
|
235
|
+
self.preview_tabs = QComboBox()
|
|
236
|
+
self.preview_tabs.currentIndexChanged.connect(
|
|
237
|
+
self._update_preview)
|
|
238
|
+
main_layout.addWidget(self.preview_tabs)
|
|
239
|
+
|
|
240
|
+
self.preview_text = QTextEdit()
|
|
241
|
+
self.preview_text.setReadOnly(True)
|
|
242
|
+
self.preview_text.setFont(QFont("Consolas", 10))
|
|
243
|
+
main_layout.addWidget(self.preview_text)
|
|
244
|
+
|
|
245
|
+
# Progress bar
|
|
246
|
+
self.progress_bar = QProgressBar()
|
|
247
|
+
self.progress_bar.setVisible(False)
|
|
248
|
+
main_layout.addWidget(self.progress_bar)
|
|
249
|
+
|
|
250
|
+
#---- Data load ----
|
|
251
|
+
def _load_sample_data_if_needed(self):
|
|
252
|
+
"""Load when sample data not set"""
|
|
253
|
+
if self.state_machine is None or \
|
|
254
|
+
self.global_defs is None:
|
|
255
|
+
sample_gen = SampleDataGenerator()
|
|
256
|
+
self.state_machine, self.global_defs = \
|
|
257
|
+
sample_gen.get_sample_data()
|
|
258
|
+
logger.info("Sample data loaded")
|
|
259
|
+
|
|
260
|
+
def _load_config_to_ui(self):
|
|
261
|
+
"""Reflect settings to UI"""
|
|
262
|
+
config = self.config_manager.get_config()
|
|
263
|
+
|
|
264
|
+
if config.output_directory:
|
|
265
|
+
self.output_dir_edit.setText(
|
|
266
|
+
config.output_directory)
|
|
267
|
+
idx = self.style_combo.findData(
|
|
268
|
+
config.generation_style)
|
|
269
|
+
if idx >= 0:
|
|
270
|
+
self.style_combo.setCurrentIndex(idx)
|
|
271
|
+
self._update_info_labels(config)
|
|
272
|
+
|
|
273
|
+
def _update_info_labels(self, config):
|
|
274
|
+
"""Update the settings info label"""
|
|
275
|
+
os_names = {
|
|
276
|
+
'non_rtos': 'NonRTOS (bare metal)',
|
|
277
|
+
'freertos': 'FreeRTOS',
|
|
278
|
+
'threadx': 'ThreadX',
|
|
279
|
+
}
|
|
280
|
+
self.os_label.setText(
|
|
281
|
+
os_names.get(config.os_type, config.os_type))
|
|
282
|
+
self.merge_label.setText(
|
|
283
|
+
"Enabled" if config.save_with_merge else "Disabled")
|
|
284
|
+
|
|
285
|
+
#---- Event handler ----
|
|
286
|
+
def _on_output_dir_changed(self, text):
|
|
287
|
+
"""Handler when output dir changes"""
|
|
288
|
+
self.config_manager.update(
|
|
289
|
+
output_directory=text if text else "")
|
|
290
|
+
|
|
291
|
+
def _select_output_dir(self):
|
|
292
|
+
"""Select output directory"""
|
|
293
|
+
current = self.output_dir_edit.text() or os.getcwd()
|
|
294
|
+
dir_path = QFileDialog.getExistingDirectory(
|
|
295
|
+
self, "Select output directory", current)
|
|
296
|
+
if dir_path:
|
|
297
|
+
self.output_dir_edit.setText(dir_path)
|
|
298
|
+
self.config_manager.update(
|
|
299
|
+
output_directory=dir_path)
|
|
300
|
+
self._save_settings()
|
|
301
|
+
|
|
302
|
+
def _on_style_changed(self, index):
|
|
303
|
+
"""Handler when style changes"""
|
|
304
|
+
style = self.style_combo.currentData()
|
|
305
|
+
self.config_manager.update(
|
|
306
|
+
generation_style=style)
|
|
307
|
+
self._save_settings()
|
|
308
|
+
|
|
309
|
+
def _open_settings_dialog(self):
|
|
310
|
+
"""Open advanced settings dialog"""
|
|
311
|
+
dialog = CodeGenerationSettingsDialog(
|
|
312
|
+
config_manager=self.config_manager,
|
|
313
|
+
parent=self)
|
|
314
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
315
|
+
config = dialog.get_config()
|
|
316
|
+
self.config_manager.set_config(config)
|
|
317
|
+
self._load_config_to_ui()
|
|
318
|
+
self._save_settings()
|
|
319
|
+
|
|
320
|
+
# ---- Warning display ----
|
|
321
|
+
def _show_warnings(self, records):
|
|
322
|
+
if not records:
|
|
323
|
+
return
|
|
324
|
+
# Deduplication (order-preserving)
|
|
325
|
+
seen = set()
|
|
326
|
+
unique = []
|
|
327
|
+
for r in records:
|
|
328
|
+
if r not in seen:
|
|
329
|
+
seen.add(r)
|
|
330
|
+
unique.append(r)
|
|
331
|
+
QMessageBox.warning(
|
|
332
|
+
self,
|
|
333
|
+
self.tr("Warnings during generation"),
|
|
334
|
+
"The following warnings occurred:\n\n"
|
|
335
|
+
+ "\n".join(f"- {m}" for m in unique),
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# ---- ★ Code generation ----
|
|
339
|
+
def _generate_code(self):
|
|
340
|
+
"""Execute code generation"""
|
|
341
|
+
if self.state_machine is None or \
|
|
342
|
+
self.global_defs is None:
|
|
343
|
+
QMessageBox.warning(
|
|
344
|
+
self, self.tr("Warning"),
|
|
345
|
+
self.tr("State machine and global definitions"
|
|
346
|
+
"is not set."))
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
output_dir = self.output_dir_edit.text().strip()
|
|
350
|
+
if output_dir:
|
|
351
|
+
self.config_manager.update(
|
|
352
|
+
output_directory=output_dir)
|
|
353
|
+
|
|
354
|
+
if not output_dir:
|
|
355
|
+
QMessageBox.warning(
|
|
356
|
+
self, self.tr("Warning"),
|
|
357
|
+
self.tr("Please set the output directory."))
|
|
358
|
+
self._select_output_dir()
|
|
359
|
+
output_dir = self.output_dir_edit.text().strip()
|
|
360
|
+
if not output_dir:
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
config = self.config_manager.get_config()
|
|
364
|
+
|
|
365
|
+
self.generate_btn.setEnabled(False)
|
|
366
|
+
self.progress_bar.setVisible(True)
|
|
367
|
+
self.progress_bar.setRange(0, 0)
|
|
368
|
+
|
|
369
|
+
# Attach warning collection handler
|
|
370
|
+
collector = WarningCollector()
|
|
371
|
+
root_logger = logging.getLogger()
|
|
372
|
+
root_logger.addHandler(collector)
|
|
373
|
+
try:
|
|
374
|
+
generator = CCodeGenerator(config=config)
|
|
375
|
+
# Automatic multi-layer / single detection
|
|
376
|
+
if self.all_layers and len(self.all_layers) > 0:
|
|
377
|
+
logger.info(
|
|
378
|
+
f"Multi-layer generation: "
|
|
379
|
+
f"{len(self.all_layers)} layers"
|
|
380
|
+
)
|
|
381
|
+
self.generated_files = (
|
|
382
|
+
generator.generate_all_layers(
|
|
383
|
+
self.all_layers,
|
|
384
|
+
self.global_defs,
|
|
385
|
+
role_function_library=(
|
|
386
|
+
self.role_function_library),
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
logger.info("Single-layer generation")
|
|
391
|
+
self.generated_files = generator.generate_all(
|
|
392
|
+
self.state_machine,
|
|
393
|
+
self.global_defs,
|
|
394
|
+
role_function_library=(
|
|
395
|
+
self.role_function_library),
|
|
396
|
+
)
|
|
397
|
+
except Exception as e:
|
|
398
|
+
self.generate_btn.setEnabled(True)
|
|
399
|
+
self.progress_bar.setVisible(False)
|
|
400
|
+
QMessageBox.critical(
|
|
401
|
+
self, self.tr("Error"),
|
|
402
|
+
f"Code generation failed:\n{e}")
|
|
403
|
+
return
|
|
404
|
+
finally:
|
|
405
|
+
root_logger.removeHandler(collector)
|
|
406
|
+
|
|
407
|
+
# Preview apply
|
|
408
|
+
self.preview_tabs.clear()
|
|
409
|
+
for filename in self.generated_files.keys():
|
|
410
|
+
self.preview_tabs.addItem(filename)
|
|
411
|
+
self._update_preview()
|
|
412
|
+
|
|
413
|
+
self.generate_btn.setEnabled(True)
|
|
414
|
+
self.save_btn.setEnabled(True)
|
|
415
|
+
self.progress_bar.setVisible(False)
|
|
416
|
+
self._save_settings()
|
|
417
|
+
|
|
418
|
+
# CompletionMessage
|
|
419
|
+
QMessageBox.information(
|
|
420
|
+
self, self.tr("Completion"),
|
|
421
|
+
f"{len(self.generated_files)} files "
|
|
422
|
+
f"generated.\nOutput: {output_dir}")
|
|
423
|
+
|
|
424
|
+
# Show warnings last if any
|
|
425
|
+
self._show_warnings(collector.records)
|
|
426
|
+
|
|
427
|
+
def _update_preview(self):
|
|
428
|
+
"""Update preview"""
|
|
429
|
+
filename = self.preview_tabs.currentText()
|
|
430
|
+
if filename in self.generated_files:
|
|
431
|
+
self.preview_text.setPlainText(
|
|
432
|
+
self.generated_files[filename])
|
|
433
|
+
else:
|
|
434
|
+
self.preview_text.clear()
|
|
435
|
+
|
|
436
|
+
def _save_code(self):
|
|
437
|
+
"""Save generated code"""
|
|
438
|
+
if not self.generated_files:
|
|
439
|
+
QMessageBox.warning(
|
|
440
|
+
self, self.tr("Warning"),
|
|
441
|
+
self.tr("No code was generated."))
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
output_dir = self.output_dir_edit.text().strip()
|
|
445
|
+
if not output_dir:
|
|
446
|
+
QMessageBox.warning(
|
|
447
|
+
self, self.tr("Warning"),
|
|
448
|
+
self.tr("Please set the output directory."))
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
453
|
+
config = self.config_manager.get_config()
|
|
454
|
+
generator = CCodeGenerator(config=config)
|
|
455
|
+
if config.save_with_merge:
|
|
456
|
+
saved_files = \
|
|
457
|
+
generator.save_generated_code_with_merge(
|
|
458
|
+
self.generated_files, output_dir)
|
|
459
|
+
else:
|
|
460
|
+
saved_files = \
|
|
461
|
+
generator.save_generated_code(
|
|
462
|
+
self.generated_files, output_dir)
|
|
463
|
+
self._save_settings()
|
|
464
|
+
QMessageBox.information(
|
|
465
|
+
self, self.tr("Save complete"),
|
|
466
|
+
f"{len(saved_files)} files "
|
|
467
|
+
f"saved.\n\n"
|
|
468
|
+
f"Output: {output_dir}")
|
|
469
|
+
except Exception as e:
|
|
470
|
+
QMessageBox.critical(
|
|
471
|
+
self, self.tr("Error"),
|
|
472
|
+
f"Save failed:\n{e}")
|
|
473
|
+
|
|
474
|
+
def _on_close(self):
|
|
475
|
+
"""Handler on close"""
|
|
476
|
+
self._save_settings()
|
|
477
|
+
self.reject()
|
|
478
|
+
|
|
479
|
+
# ---- getter ----
|
|
480
|
+
def get_generated_files(self) -> Dict[str, str]:
|
|
481
|
+
"""Get generated files"""
|
|
482
|
+
return self.generated_files
|
|
483
|
+
|
|
484
|
+
def get_config(self):
|
|
485
|
+
"""Get current settings"""
|
|
486
|
+
return self.config_manager.get_config()
|