digsim-logic-simulator 0.22.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.
- digsim/__init__.py +6 -0
- digsim/app/__main__.py +12 -0
- digsim/app/cli.py +68 -0
- digsim/app/gui/__init__.py +6 -0
- digsim/app/gui/_circuit_area.py +468 -0
- digsim/app/gui/_component_selection.py +154 -0
- digsim/app/gui/_main_window.py +163 -0
- digsim/app/gui/_top_bar.py +339 -0
- digsim/app/gui/_utils.py +26 -0
- digsim/app/gui/_warning_dialog.py +46 -0
- digsim/app/gui_objects/__init__.py +7 -0
- digsim/app/gui_objects/_bus_bit_object.py +94 -0
- digsim/app/gui_objects/_buzzer_object.py +97 -0
- digsim/app/gui_objects/_component_context_menu.py +79 -0
- digsim/app/gui_objects/_component_object.py +374 -0
- digsim/app/gui_objects/_component_port_item.py +63 -0
- digsim/app/gui_objects/_dip_switch_object.py +104 -0
- digsim/app/gui_objects/_gui_note_object.py +80 -0
- digsim/app/gui_objects/_gui_object_factory.py +80 -0
- digsim/app/gui_objects/_hexdigit_object.py +53 -0
- digsim/app/gui_objects/_image_objects.py +239 -0
- digsim/app/gui_objects/_label_object.py +97 -0
- digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
- digsim/app/gui_objects/_seven_segment_object.py +131 -0
- digsim/app/gui_objects/_shortcut_objects.py +82 -0
- digsim/app/gui_objects/_yosys_object.py +32 -0
- digsim/app/gui_objects/images/AND.png +0 -0
- digsim/app/gui_objects/images/Analyzer.png +0 -0
- digsim/app/gui_objects/images/BUF.png +0 -0
- digsim/app/gui_objects/images/Buzzer.png +0 -0
- digsim/app/gui_objects/images/Clock.png +0 -0
- digsim/app/gui_objects/images/DFF.png +0 -0
- digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
- digsim/app/gui_objects/images/FlipFlop.png +0 -0
- digsim/app/gui_objects/images/IC.png +0 -0
- digsim/app/gui_objects/images/LED_OFF.png +0 -0
- digsim/app/gui_objects/images/LED_ON.png +0 -0
- digsim/app/gui_objects/images/MUX.png +0 -0
- digsim/app/gui_objects/images/NAND.png +0 -0
- digsim/app/gui_objects/images/NOR.png +0 -0
- digsim/app/gui_objects/images/NOT.png +0 -0
- digsim/app/gui_objects/images/ONE.png +0 -0
- digsim/app/gui_objects/images/OR.png +0 -0
- digsim/app/gui_objects/images/PB.png +0 -0
- digsim/app/gui_objects/images/Switch_OFF.png +0 -0
- digsim/app/gui_objects/images/Switch_ON.png +0 -0
- digsim/app/gui_objects/images/XNOR.png +0 -0
- digsim/app/gui_objects/images/XOR.png +0 -0
- digsim/app/gui_objects/images/YOSYS.png +0 -0
- digsim/app/gui_objects/images/ZERO.png +0 -0
- digsim/app/images/app_icon.png +0 -0
- digsim/app/model/__init__.py +6 -0
- digsim/app/model/_model.py +210 -0
- digsim/app/model/_model_components.py +162 -0
- digsim/app/model/_model_new_wire.py +57 -0
- digsim/app/model/_model_objects.py +155 -0
- digsim/app/model/_model_settings.py +35 -0
- digsim/app/model/_model_shortcuts.py +72 -0
- digsim/app/settings/__init__.py +8 -0
- digsim/app/settings/_component_settings.py +415 -0
- digsim/app/settings/_gui_settings.py +71 -0
- digsim/app/settings/_shortcut_dialog.py +39 -0
- digsim/circuit/__init__.py +7 -0
- digsim/circuit/_circuit.py +329 -0
- digsim/circuit/_waves_writer.py +61 -0
- digsim/circuit/components/__init__.py +26 -0
- digsim/circuit/components/_bus_bits.py +68 -0
- digsim/circuit/components/_button.py +44 -0
- digsim/circuit/components/_buzzer.py +45 -0
- digsim/circuit/components/_clock.py +54 -0
- digsim/circuit/components/_dip_switch.py +73 -0
- digsim/circuit/components/_flip_flops.py +99 -0
- digsim/circuit/components/_gates.py +246 -0
- digsim/circuit/components/_hexdigit.py +82 -0
- digsim/circuit/components/_ic.py +36 -0
- digsim/circuit/components/_label_wire.py +167 -0
- digsim/circuit/components/_led.py +18 -0
- digsim/circuit/components/_logic_analyzer.py +60 -0
- digsim/circuit/components/_mem64kbyte.py +42 -0
- digsim/circuit/components/_memstdout.py +37 -0
- digsim/circuit/components/_note.py +25 -0
- digsim/circuit/components/_on_off_switch.py +54 -0
- digsim/circuit/components/_seven_segment.py +28 -0
- digsim/circuit/components/_static_level.py +28 -0
- digsim/circuit/components/_static_value.py +44 -0
- digsim/circuit/components/_yosys_atoms.py +1353 -0
- digsim/circuit/components/_yosys_component.py +232 -0
- digsim/circuit/components/atoms/__init__.py +23 -0
- digsim/circuit/components/atoms/_component.py +280 -0
- digsim/circuit/components/atoms/_digsim_exception.py +8 -0
- digsim/circuit/components/atoms/_port.py +398 -0
- digsim/circuit/components/ic/74162.json +1331 -0
- digsim/circuit/components/ic/7448.json +834 -0
- digsim/storage_model/__init__.py +7 -0
- digsim/storage_model/_app.py +58 -0
- digsim/storage_model/_circuit.py +126 -0
- digsim/synth/__init__.py +6 -0
- digsim/synth/__main__.py +67 -0
- digsim/synth/_synthesis.py +156 -0
- digsim/utils/__init__.py +6 -0
- digsim/utils/_yosys_netlist.py +134 -0
- digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
- digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
- digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
- digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
- digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
- digsim_logic_simulator-0.22.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""All classes within digsim.app.settings namespace"""
|
|
5
|
+
|
|
6
|
+
from ._component_settings import ComponentSettingsDialog # noqa: F401
|
|
7
|
+
from ._gui_settings import GuiSettingsDialog # noqa: F401
|
|
8
|
+
from ._shortcut_dialog import ShortcutDialog # noqa: F401
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""The component settings dialog(s)"""
|
|
5
|
+
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
8
|
+
from PySide6.QtCore import Qt, Signal
|
|
9
|
+
from PySide6.QtWidgets import (
|
|
10
|
+
QCheckBox,
|
|
11
|
+
QComboBox,
|
|
12
|
+
QDialog,
|
|
13
|
+
QDialogButtonBox,
|
|
14
|
+
QFileDialog,
|
|
15
|
+
QFrame,
|
|
16
|
+
QGridLayout,
|
|
17
|
+
QHBoxLayout,
|
|
18
|
+
QLabel,
|
|
19
|
+
QLineEdit,
|
|
20
|
+
QPushButton,
|
|
21
|
+
QSlider,
|
|
22
|
+
QTextEdit,
|
|
23
|
+
QVBoxLayout,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from digsim.circuit.components import IntegratedCircuit
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ComponentSettingsException(Exception):
|
|
30
|
+
"""ComponentSettingsException"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ComponentSettingsBase(QFrame):
|
|
34
|
+
"""SettingsBase"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
37
|
+
super().__init__(parent)
|
|
38
|
+
self.setFrameStyle(QFrame.Panel)
|
|
39
|
+
self._parent = parent
|
|
40
|
+
self._parameter = parameter
|
|
41
|
+
self._parameter_dict = parameter_dict
|
|
42
|
+
self._settings = settings
|
|
43
|
+
self._parent.sig_value_updated.connect(self._on_setting_change)
|
|
44
|
+
|
|
45
|
+
def emit_signal(self):
|
|
46
|
+
"""Signal other settings components that a value has changed"""
|
|
47
|
+
self._parent.sig_value_updated.emit(self._parameter)
|
|
48
|
+
|
|
49
|
+
def _on_setting_change(self, parameter):
|
|
50
|
+
if parameter != self._parameter:
|
|
51
|
+
self.on_setting_change(parameter)
|
|
52
|
+
|
|
53
|
+
def on_setting_change(self, parameter):
|
|
54
|
+
"""Called when other setting is changed"""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ComponentSettingText(ComponentSettingsBase):
|
|
58
|
+
"""Text setting"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
61
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
62
|
+
self.setLayout(QVBoxLayout(self))
|
|
63
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]))
|
|
64
|
+
self._text_edit = QTextEdit(self)
|
|
65
|
+
self._text_edit.setText(self._parameter_dict["default"])
|
|
66
|
+
self._text_edit.textChanged.connect(self._update)
|
|
67
|
+
self.layout().addWidget(self._text_edit)
|
|
68
|
+
|
|
69
|
+
def _update(self):
|
|
70
|
+
value = self._text_edit.toPlainText()
|
|
71
|
+
self._settings[self._parameter] = value
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ComponentSettingSingleLineText(ComponentSettingsBase):
|
|
75
|
+
"""Text setting"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
78
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
79
|
+
self._parent = parent
|
|
80
|
+
self.setLayout(QVBoxLayout(self))
|
|
81
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]))
|
|
82
|
+
self._invalid_list = self._parameter_dict.get("invalid_list", [])
|
|
83
|
+
self._line_edit = QLineEdit(self)
|
|
84
|
+
default = self._parameter_dict["default"]
|
|
85
|
+
while default in self._invalid_list:
|
|
86
|
+
default += "x"
|
|
87
|
+
self._line_edit.setText(default)
|
|
88
|
+
self._settings[self._parameter] = self._parameter_dict["default"]
|
|
89
|
+
self._line_edit.textChanged.connect(self._update)
|
|
90
|
+
self.layout().addWidget(self._line_edit)
|
|
91
|
+
self._invalid_text = QLabel()
|
|
92
|
+
if len(self._invalid_list) > 0:
|
|
93
|
+
self.layout().addWidget(self._invalid_text)
|
|
94
|
+
self._update()
|
|
95
|
+
|
|
96
|
+
def _update(self):
|
|
97
|
+
value = self._line_edit.text()
|
|
98
|
+
self._settings[self._parameter] = value
|
|
99
|
+
if value in self._invalid_list:
|
|
100
|
+
self._parent.sig_enable_ok.emit(False)
|
|
101
|
+
self._invalid_text.setText("Invalid string")
|
|
102
|
+
else:
|
|
103
|
+
self._parent.sig_enable_ok.emit(True)
|
|
104
|
+
self._invalid_text.setText("")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ComponentSettingsSlider(ComponentSettingsBase):
|
|
108
|
+
"""SettingsSlider"""
|
|
109
|
+
|
|
110
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
111
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
112
|
+
self.setLayout(QGridLayout(self))
|
|
113
|
+
self._settings_slider = QSlider(Qt.Horizontal, self)
|
|
114
|
+
self._settings_slider.setSingleStep(1)
|
|
115
|
+
self._settings_slider.setPageStep(1)
|
|
116
|
+
self._settings_slider.setTickPosition(QSlider.TicksBothSides)
|
|
117
|
+
self._settings_slider.valueChanged.connect(self._update)
|
|
118
|
+
self._value_label = QLabel("")
|
|
119
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]), 0, 0, 1, 6)
|
|
120
|
+
self._setup()
|
|
121
|
+
self.layout().addWidget(self._value_label, 1, 6, 1, 1)
|
|
122
|
+
self.layout().addWidget(self._settings_slider, 1, 0, 1, 5)
|
|
123
|
+
self._init_slider()
|
|
124
|
+
|
|
125
|
+
def _init_slider(self):
|
|
126
|
+
self._settings_slider.setValue(self._parameter_dict["default"])
|
|
127
|
+
self._update(self._parameter_dict["default"])
|
|
128
|
+
|
|
129
|
+
def _setup(self):
|
|
130
|
+
self._settings_slider.setMaximum(self._parameter_dict["max"])
|
|
131
|
+
self._settings_slider.setMinimum(self._parameter_dict["min"])
|
|
132
|
+
self._settings_slider.setValue(self._parameter_dict["default"])
|
|
133
|
+
self._settings_slider.setTickInterval(
|
|
134
|
+
max(1, (self._parameter_dict["max"] - self._parameter_dict["min"]) / 10)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def _update(self, value):
|
|
138
|
+
self._settings[self._parameter] = value
|
|
139
|
+
self._value_label.setText(f"{value}")
|
|
140
|
+
self.emit_signal()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ComponentSettingsSliderWidthPow2(ComponentSettingsSlider):
|
|
144
|
+
"""SettingsSlider which is dependent on bitwidth"""
|
|
145
|
+
|
|
146
|
+
def _setup(self):
|
|
147
|
+
value_max = 2 ** self._settings.get("width", 1) - 1
|
|
148
|
+
self._settings_slider.setMaximum(value_max)
|
|
149
|
+
self._settings_slider.setMinimum(0)
|
|
150
|
+
self._settings_slider.setTickInterval(max(1, (value_max - 0) / 10))
|
|
151
|
+
|
|
152
|
+
def _update(self, value):
|
|
153
|
+
self._settings[self._parameter] = value
|
|
154
|
+
self._value_label.setText(f"{value}")
|
|
155
|
+
self.emit_signal()
|
|
156
|
+
|
|
157
|
+
def on_setting_change(self, parameter):
|
|
158
|
+
if parameter == "width":
|
|
159
|
+
self._setup()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class ComponentSettingsIntRangeSlider(ComponentSettingsSlider):
|
|
163
|
+
"""SettingsSlider (range)"""
|
|
164
|
+
|
|
165
|
+
def _init_slider(self):
|
|
166
|
+
default_index = self._parameter_dict["default_index"]
|
|
167
|
+
self._settings_slider.setValue(default_index)
|
|
168
|
+
self._update(default_index)
|
|
169
|
+
|
|
170
|
+
def _setup(self):
|
|
171
|
+
self._settings_slider.setMaximum(len(self._parameter_dict["range"]) - 1)
|
|
172
|
+
self._settings_slider.setMinimum(0)
|
|
173
|
+
self._settings_slider.setTickInterval(1)
|
|
174
|
+
|
|
175
|
+
def _update(self, value):
|
|
176
|
+
range_val = self._parameter_dict["range"][value]
|
|
177
|
+
self._settings[self._parameter] = range_val
|
|
178
|
+
self._value_label.setText(f"{range_val}")
|
|
179
|
+
self.emit_signal()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ComponentSettingsCheckBox(ComponentSettingsBase):
|
|
183
|
+
"""SettingsCheckbox"""
|
|
184
|
+
|
|
185
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
186
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
187
|
+
self.setLayout(QHBoxLayout(self))
|
|
188
|
+
self.setFrameStyle(QFrame.Panel)
|
|
189
|
+
self._settings[parameter] = parameter_dict["default"]
|
|
190
|
+
self._settings_checkbox = QCheckBox(parameter_dict["description"], self)
|
|
191
|
+
self._settings_checkbox.setChecked(parameter_dict["default"])
|
|
192
|
+
self.layout().addWidget(self._settings_checkbox)
|
|
193
|
+
self._settings_checkbox.stateChanged.connect(self._update)
|
|
194
|
+
self._settings_checkbox.setFocus()
|
|
195
|
+
|
|
196
|
+
def _update(self, _):
|
|
197
|
+
self._settings[self._parameter] = self._settings_checkbox.isChecked()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class ComponentSettingsIcSelector(ComponentSettingsBase):
|
|
201
|
+
"""IC Selector"""
|
|
202
|
+
|
|
203
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
204
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
205
|
+
self.setLayout(QVBoxLayout(self))
|
|
206
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]))
|
|
207
|
+
ic_folder = IntegratedCircuit.folder()
|
|
208
|
+
ic_files = pathlib.Path(ic_folder).glob("*.json")
|
|
209
|
+
self._ic_selector = QComboBox(parent)
|
|
210
|
+
for ic_file in ic_files:
|
|
211
|
+
self._ic_selector.addItem(ic_file.stem, userData=ic_file.stem)
|
|
212
|
+
self.layout().addWidget(self._ic_selector)
|
|
213
|
+
self._ic_selector.currentIndexChanged.connect(self._update)
|
|
214
|
+
self._update(self._ic_selector.currentIndex())
|
|
215
|
+
|
|
216
|
+
def _update(self, value):
|
|
217
|
+
self._settings[self._parameter] = self._ic_selector.itemData(value)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ComponentSettingsNameSelector(ComponentSettingsBase):
|
|
221
|
+
"""Name Selector"""
|
|
222
|
+
|
|
223
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
224
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
225
|
+
self.setLayout(QVBoxLayout(self))
|
|
226
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]))
|
|
227
|
+
component_dict = self._parameter_dict["component_list"]
|
|
228
|
+
self._component_selector = QComboBox(parent)
|
|
229
|
+
for component_name, component_desc in component_dict.items():
|
|
230
|
+
self._component_selector.addItem(component_desc, userData=component_name)
|
|
231
|
+
self.layout().addWidget(self._component_selector)
|
|
232
|
+
self._component_selector.currentIndexChanged.connect(self._update)
|
|
233
|
+
self._update(self._component_selector.currentIndex())
|
|
234
|
+
|
|
235
|
+
def _update(self, value):
|
|
236
|
+
self._settings[self._parameter] = self._component_selector.itemData(value)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class ComponentSettingsListSelector(ComponentSettingsBase):
|
|
240
|
+
"""List Selector"""
|
|
241
|
+
|
|
242
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
243
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
244
|
+
self.setLayout(QVBoxLayout(self))
|
|
245
|
+
self.layout().addWidget(QLabel(self._parameter_dict["description"]))
|
|
246
|
+
item_list = self._parameter_dict["items"]
|
|
247
|
+
if len(item_list) == 0:
|
|
248
|
+
raise ComponentSettingsException("No available items to choose from")
|
|
249
|
+
|
|
250
|
+
self._item_selector = QComboBox(parent)
|
|
251
|
+
for item in item_list:
|
|
252
|
+
self._item_selector.addItem(item, userData=item)
|
|
253
|
+
self.layout().addWidget(self._item_selector)
|
|
254
|
+
self._item_selector.currentIndexChanged.connect(self._update)
|
|
255
|
+
self._update(self._item_selector.currentIndex())
|
|
256
|
+
|
|
257
|
+
def _update(self, value):
|
|
258
|
+
self._settings[self._parameter] = self._item_selector.itemData(value)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class ComponentSettingsCheckBoxWidthBool(ComponentSettingsBase):
|
|
262
|
+
"""SettingsCheckbox for width number of bits (max32)"""
|
|
263
|
+
|
|
264
|
+
MAX_WIDTH = 32
|
|
265
|
+
|
|
266
|
+
def __init__(self, parent, parameter, parameter_dict, settings):
|
|
267
|
+
super().__init__(parent, parameter, parameter_dict, settings)
|
|
268
|
+
self.setLayout(QGridLayout(self))
|
|
269
|
+
self._bit_checkboxes = []
|
|
270
|
+
for checkbox_id in range(self.MAX_WIDTH):
|
|
271
|
+
checkbox = QCheckBox(f"bit{checkbox_id}")
|
|
272
|
+
checkbox.setChecked(True)
|
|
273
|
+
checkbox.stateChanged.connect(self._update)
|
|
274
|
+
self.layout().addWidget(checkbox, checkbox_id % 8, checkbox_id // 8, 1, 1)
|
|
275
|
+
self._bit_checkboxes.append(checkbox)
|
|
276
|
+
disable_all = QPushButton("Disable All")
|
|
277
|
+
disable_all.clicked.connect(self._disable_all)
|
|
278
|
+
enable_all = QPushButton("Enable All")
|
|
279
|
+
enable_all.clicked.connect(self._enable_all)
|
|
280
|
+
self.layout().addWidget(disable_all, 8, 0, 2, 1)
|
|
281
|
+
self.layout().addWidget(enable_all, 8, 2, 2, 1)
|
|
282
|
+
self._update()
|
|
283
|
+
|
|
284
|
+
def _set_all(self, state):
|
|
285
|
+
for checkbox_id in range(self.MAX_WIDTH):
|
|
286
|
+
checkbox = self._bit_checkboxes[checkbox_id]
|
|
287
|
+
checkbox.setChecked(state)
|
|
288
|
+
|
|
289
|
+
def _enable_all(self):
|
|
290
|
+
self._set_all(True)
|
|
291
|
+
|
|
292
|
+
def _disable_all(self):
|
|
293
|
+
self._set_all(False)
|
|
294
|
+
|
|
295
|
+
def _update(self):
|
|
296
|
+
self._settings[self._parameter] = []
|
|
297
|
+
for checkbox_id in range(32):
|
|
298
|
+
checkbox = self._bit_checkboxes[checkbox_id]
|
|
299
|
+
checkbox.setEnabled(checkbox_id < self._settings["width"])
|
|
300
|
+
if checkbox_id < self._settings["width"] and checkbox.isChecked():
|
|
301
|
+
self._settings[self._parameter].append(checkbox_id)
|
|
302
|
+
|
|
303
|
+
def on_setting_change(self, parameter):
|
|
304
|
+
if parameter == "width":
|
|
305
|
+
self._update()
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class ComponentSettingsDialog(QDialog):
|
|
309
|
+
"""SettingsDialog"""
|
|
310
|
+
|
|
311
|
+
sig_value_updated = Signal(str)
|
|
312
|
+
sig_enable_ok = Signal(bool)
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def _is_file_path(parent, parameters):
|
|
316
|
+
parameters_list = list(parameters.keys())
|
|
317
|
+
first_key = parameters_list[0]
|
|
318
|
+
settings = {}
|
|
319
|
+
is_file_path = len(parameters_list) == 1 and parameters[first_key]["type"] == "path"
|
|
320
|
+
if is_file_path:
|
|
321
|
+
description = parameters[first_key]["description"]
|
|
322
|
+
fileinfo = parameters[first_key]["fileinfo"]
|
|
323
|
+
path = QFileDialog.getOpenFileName(
|
|
324
|
+
parent, description, "", f"{fileinfo};;All Files (*.*)"
|
|
325
|
+
)
|
|
326
|
+
if len(path[0]) > 0:
|
|
327
|
+
settings[first_key] = path[0]
|
|
328
|
+
return is_file_path, settings
|
|
329
|
+
|
|
330
|
+
@classmethod
|
|
331
|
+
def start(cls, parent, app_model, name, parameters):
|
|
332
|
+
"""Start a settings dialog and return the settings"""
|
|
333
|
+
if len(parameters) == 0:
|
|
334
|
+
# No parameters, just place component without settings
|
|
335
|
+
return True, {}
|
|
336
|
+
|
|
337
|
+
is_file_path, settings = cls._is_file_path(parent, parameters)
|
|
338
|
+
if is_file_path:
|
|
339
|
+
return len(settings) > 0, settings
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
dialog = ComponentSettingsDialog(parent, app_model, name, parameters)
|
|
343
|
+
result = dialog.exec_()
|
|
344
|
+
if result == QDialog.DialogCode.Rejected:
|
|
345
|
+
return False, {}
|
|
346
|
+
return True, dialog.get_settings()
|
|
347
|
+
except ComponentSettingsException as exc:
|
|
348
|
+
app_model.sig_error.emit(str(exc))
|
|
349
|
+
return False, {}
|
|
350
|
+
|
|
351
|
+
def _setup_dialog(self, parameters):
|
|
352
|
+
for parameter, parameter_dict in parameters.items():
|
|
353
|
+
if parameter_dict["type"] == "width_pow2":
|
|
354
|
+
widget = ComponentSettingsSliderWidthPow2(
|
|
355
|
+
self, parameter, parameter_dict, self._settings
|
|
356
|
+
)
|
|
357
|
+
elif parameter_dict["type"] == "width_bool":
|
|
358
|
+
widget = ComponentSettingsCheckBoxWidthBool(
|
|
359
|
+
self, parameter, parameter_dict, self._settings
|
|
360
|
+
)
|
|
361
|
+
elif parameter_dict["type"] == "int":
|
|
362
|
+
widget = ComponentSettingsSlider(self, parameter, parameter_dict, self._settings)
|
|
363
|
+
elif parameter_dict["type"] == "str":
|
|
364
|
+
single_line = parameter_dict.get("single_line", False)
|
|
365
|
+
if single_line:
|
|
366
|
+
widget = ComponentSettingSingleLineText(
|
|
367
|
+
self, parameter, parameter_dict, self._settings
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
widget = ComponentSettingText(self, parameter, parameter_dict, self._settings)
|
|
371
|
+
elif parameter_dict["type"] == "bool":
|
|
372
|
+
widget = ComponentSettingsCheckBox(self, parameter, parameter_dict, self._settings)
|
|
373
|
+
elif parameter_dict["type"] == "intrange":
|
|
374
|
+
widget = ComponentSettingsIntRangeSlider(
|
|
375
|
+
self, parameter, parameter_dict, self._settings
|
|
376
|
+
)
|
|
377
|
+
elif parameter_dict["type"] == "ic_name":
|
|
378
|
+
widget = ComponentSettingsIcSelector(
|
|
379
|
+
self, parameter, parameter_dict, self._settings
|
|
380
|
+
)
|
|
381
|
+
elif parameter_dict["type"] == "component_name":
|
|
382
|
+
widget = ComponentSettingsNameSelector(
|
|
383
|
+
self, parameter, parameter_dict, self._settings
|
|
384
|
+
)
|
|
385
|
+
elif parameter_dict["type"] == "list":
|
|
386
|
+
widget = ComponentSettingsListSelector(
|
|
387
|
+
self, parameter, parameter_dict, self._settings
|
|
388
|
+
)
|
|
389
|
+
else:
|
|
390
|
+
self._app_model.sig_error.emit(f"Unknown settings type '{parameter_dict['type']}'")
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
self.layout().addWidget(widget)
|
|
394
|
+
|
|
395
|
+
def __init__(self, parent, app_model, name, parameters):
|
|
396
|
+
super().__init__(parent)
|
|
397
|
+
self._app_model = app_model
|
|
398
|
+
self.setLayout(QVBoxLayout(self))
|
|
399
|
+
self.setWindowTitle(f"Setup {name}")
|
|
400
|
+
self.setFocusPolicy(Qt.StrongFocus)
|
|
401
|
+
self._settings = {}
|
|
402
|
+
self._setup_dialog(parameters)
|
|
403
|
+
QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
|
|
404
|
+
self.buttonBox = QDialogButtonBox(QBtn)
|
|
405
|
+
self.buttonBox.accepted.connect(self.accept)
|
|
406
|
+
self.buttonBox.rejected.connect(self.reject)
|
|
407
|
+
self.layout().addWidget(self.buttonBox)
|
|
408
|
+
self.sig_enable_ok.connect(self._enable_ok)
|
|
409
|
+
|
|
410
|
+
def _enable_ok(self, enable):
|
|
411
|
+
self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable)
|
|
412
|
+
|
|
413
|
+
def get_settings(self):
|
|
414
|
+
"""Get settings from setting dialog"""
|
|
415
|
+
return self._settings
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""The GUI settings dialog"""
|
|
5
|
+
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
|
+
from PySide6.QtWidgets import QCheckBox, QComboBox, QDialog, QDialogButtonBox, QGridLayout, QLabel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GuiSettingsDialog(QDialog):
|
|
11
|
+
"""GUI Settings Dialog"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, parent, app_model):
|
|
14
|
+
super().__init__(parent)
|
|
15
|
+
self._app_model = app_model
|
|
16
|
+
self.setLayout(QGridLayout(self))
|
|
17
|
+
self.setWindowTitle("Application Settings")
|
|
18
|
+
self.setFocusPolicy(Qt.StrongFocus)
|
|
19
|
+
|
|
20
|
+
# GUI Update Frequency
|
|
21
|
+
self._update_frequency = QComboBox(self)
|
|
22
|
+
for frequency in [1, 10, 20, 50, 100]:
|
|
23
|
+
self._update_frequency.addItem(f"{frequency} Hz", userData=frequency)
|
|
24
|
+
|
|
25
|
+
self._slow_to_real_time = QCheckBox("", self)
|
|
26
|
+
self._show_wire_value = QCheckBox("", self)
|
|
27
|
+
|
|
28
|
+
# OK / Cancel
|
|
29
|
+
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
30
|
+
self.buttonBox.accepted.connect(self.accept)
|
|
31
|
+
self.buttonBox.rejected.connect(self.reject)
|
|
32
|
+
|
|
33
|
+
# Layout
|
|
34
|
+
row = 0
|
|
35
|
+
self.layout().addWidget(QLabel("GUI Update Frequency", self), row, 0, 1, 1)
|
|
36
|
+
self.layout().addWidget(self._update_frequency, row, 1, 1, 1)
|
|
37
|
+
row += 1
|
|
38
|
+
self.layout().addWidget(QLabel("Slow down sim to 'Real Time'", self), row, 0, 1, 1)
|
|
39
|
+
self.layout().addWidget(self._slow_to_real_time, row, 1, 1, 1)
|
|
40
|
+
row += 1
|
|
41
|
+
self.layout().addWidget(QLabel("Show Wire Value", self), row, 0, 1, 1)
|
|
42
|
+
self.layout().addWidget(self._show_wire_value, row, 1, 1, 1)
|
|
43
|
+
row += 1
|
|
44
|
+
self.layout().addWidget(self.buttonBox, row, 0, 1, 2, alignment=Qt.AlignCenter)
|
|
45
|
+
|
|
46
|
+
self._settings = self._app_model.settings.get_all()
|
|
47
|
+
self._slow_to_real_time.setChecked(self._settings["real_time"])
|
|
48
|
+
self._show_wire_value.setChecked(self._settings["color_wires"])
|
|
49
|
+
|
|
50
|
+
index = self._update_frequency.findData(self._settings["update_frequency"])
|
|
51
|
+
self._update_frequency.setCurrentIndex(index)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def default_settings(cls):
|
|
55
|
+
"""Default settings"""
|
|
56
|
+
return {
|
|
57
|
+
"real_time": True,
|
|
58
|
+
"color_wires": True,
|
|
59
|
+
"update_frequency": 20,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def start(self):
|
|
63
|
+
"""Start Dialog"""
|
|
64
|
+
result = self.exec_()
|
|
65
|
+
if result == QDialog.DialogCode.Accepted:
|
|
66
|
+
self._settings["real_time"] = self._slow_to_real_time.isChecked()
|
|
67
|
+
self._settings["color_wires"] = self._show_wire_value.isChecked()
|
|
68
|
+
self._settings["update_frequency"] = self._update_frequency.itemData(
|
|
69
|
+
self._update_frequency.currentIndex()
|
|
70
|
+
)
|
|
71
|
+
self._app_model.settings.update(self._settings)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright (c) Fredrik Andersson, 2023-2025
|
|
2
|
+
# All rights reserved
|
|
3
|
+
|
|
4
|
+
"""The shurtcut dialog"""
|
|
5
|
+
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
|
+
from PySide6.QtWidgets import QComboBox, QDialog, QDialogButtonBox, QLabel, QVBoxLayout
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ShortcutDialog(QDialog):
|
|
11
|
+
"""Shortcut Dialog"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, parent, app_model):
|
|
14
|
+
super().__init__(parent)
|
|
15
|
+
self._app_model = app_model
|
|
16
|
+
self.setLayout(QVBoxLayout(self))
|
|
17
|
+
self.setWindowTitle("Add shortcut")
|
|
18
|
+
self.setFocusPolicy(Qt.StrongFocus)
|
|
19
|
+
|
|
20
|
+
self.layout().addWidget(QLabel("Select shortcut key for component"))
|
|
21
|
+
self._key_selector = QComboBox(parent)
|
|
22
|
+
for key in "1234567890":
|
|
23
|
+
shortcut_string = key
|
|
24
|
+
shortcut_component = app_model.shortcuts.get_component(key)
|
|
25
|
+
if shortcut_component is not None:
|
|
26
|
+
shortcut_string += f" - {shortcut_component.name()}"
|
|
27
|
+
self._key_selector.addItem(shortcut_string, userData=key)
|
|
28
|
+
self.layout().addWidget(self._key_selector)
|
|
29
|
+
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
|
30
|
+
self.buttonBox.accepted.connect(self.accept)
|
|
31
|
+
self.buttonBox.rejected.connect(self.reject)
|
|
32
|
+
self.layout().addWidget(self.buttonBox)
|
|
33
|
+
|
|
34
|
+
def start(self):
|
|
35
|
+
"""Start Dialog"""
|
|
36
|
+
result = self.exec_()
|
|
37
|
+
if result == QDialog.DialogCode.Rejected:
|
|
38
|
+
return None
|
|
39
|
+
return self._key_selector.currentData()
|