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.
Files changed (107) hide show
  1. digsim/__init__.py +6 -0
  2. digsim/app/__main__.py +12 -0
  3. digsim/app/cli.py +68 -0
  4. digsim/app/gui/__init__.py +6 -0
  5. digsim/app/gui/_circuit_area.py +468 -0
  6. digsim/app/gui/_component_selection.py +154 -0
  7. digsim/app/gui/_main_window.py +163 -0
  8. digsim/app/gui/_top_bar.py +339 -0
  9. digsim/app/gui/_utils.py +26 -0
  10. digsim/app/gui/_warning_dialog.py +46 -0
  11. digsim/app/gui_objects/__init__.py +7 -0
  12. digsim/app/gui_objects/_bus_bit_object.py +94 -0
  13. digsim/app/gui_objects/_buzzer_object.py +97 -0
  14. digsim/app/gui_objects/_component_context_menu.py +79 -0
  15. digsim/app/gui_objects/_component_object.py +374 -0
  16. digsim/app/gui_objects/_component_port_item.py +63 -0
  17. digsim/app/gui_objects/_dip_switch_object.py +104 -0
  18. digsim/app/gui_objects/_gui_note_object.py +80 -0
  19. digsim/app/gui_objects/_gui_object_factory.py +80 -0
  20. digsim/app/gui_objects/_hexdigit_object.py +53 -0
  21. digsim/app/gui_objects/_image_objects.py +239 -0
  22. digsim/app/gui_objects/_label_object.py +97 -0
  23. digsim/app/gui_objects/_logic_analyzer_object.py +86 -0
  24. digsim/app/gui_objects/_seven_segment_object.py +131 -0
  25. digsim/app/gui_objects/_shortcut_objects.py +82 -0
  26. digsim/app/gui_objects/_yosys_object.py +32 -0
  27. digsim/app/gui_objects/images/AND.png +0 -0
  28. digsim/app/gui_objects/images/Analyzer.png +0 -0
  29. digsim/app/gui_objects/images/BUF.png +0 -0
  30. digsim/app/gui_objects/images/Buzzer.png +0 -0
  31. digsim/app/gui_objects/images/Clock.png +0 -0
  32. digsim/app/gui_objects/images/DFF.png +0 -0
  33. digsim/app/gui_objects/images/DIP_SWITCH.png +0 -0
  34. digsim/app/gui_objects/images/FlipFlop.png +0 -0
  35. digsim/app/gui_objects/images/IC.png +0 -0
  36. digsim/app/gui_objects/images/LED_OFF.png +0 -0
  37. digsim/app/gui_objects/images/LED_ON.png +0 -0
  38. digsim/app/gui_objects/images/MUX.png +0 -0
  39. digsim/app/gui_objects/images/NAND.png +0 -0
  40. digsim/app/gui_objects/images/NOR.png +0 -0
  41. digsim/app/gui_objects/images/NOT.png +0 -0
  42. digsim/app/gui_objects/images/ONE.png +0 -0
  43. digsim/app/gui_objects/images/OR.png +0 -0
  44. digsim/app/gui_objects/images/PB.png +0 -0
  45. digsim/app/gui_objects/images/Switch_OFF.png +0 -0
  46. digsim/app/gui_objects/images/Switch_ON.png +0 -0
  47. digsim/app/gui_objects/images/XNOR.png +0 -0
  48. digsim/app/gui_objects/images/XOR.png +0 -0
  49. digsim/app/gui_objects/images/YOSYS.png +0 -0
  50. digsim/app/gui_objects/images/ZERO.png +0 -0
  51. digsim/app/images/app_icon.png +0 -0
  52. digsim/app/model/__init__.py +6 -0
  53. digsim/app/model/_model.py +210 -0
  54. digsim/app/model/_model_components.py +162 -0
  55. digsim/app/model/_model_new_wire.py +57 -0
  56. digsim/app/model/_model_objects.py +155 -0
  57. digsim/app/model/_model_settings.py +35 -0
  58. digsim/app/model/_model_shortcuts.py +72 -0
  59. digsim/app/settings/__init__.py +8 -0
  60. digsim/app/settings/_component_settings.py +415 -0
  61. digsim/app/settings/_gui_settings.py +71 -0
  62. digsim/app/settings/_shortcut_dialog.py +39 -0
  63. digsim/circuit/__init__.py +7 -0
  64. digsim/circuit/_circuit.py +329 -0
  65. digsim/circuit/_waves_writer.py +61 -0
  66. digsim/circuit/components/__init__.py +26 -0
  67. digsim/circuit/components/_bus_bits.py +68 -0
  68. digsim/circuit/components/_button.py +44 -0
  69. digsim/circuit/components/_buzzer.py +45 -0
  70. digsim/circuit/components/_clock.py +54 -0
  71. digsim/circuit/components/_dip_switch.py +73 -0
  72. digsim/circuit/components/_flip_flops.py +99 -0
  73. digsim/circuit/components/_gates.py +246 -0
  74. digsim/circuit/components/_hexdigit.py +82 -0
  75. digsim/circuit/components/_ic.py +36 -0
  76. digsim/circuit/components/_label_wire.py +167 -0
  77. digsim/circuit/components/_led.py +18 -0
  78. digsim/circuit/components/_logic_analyzer.py +60 -0
  79. digsim/circuit/components/_mem64kbyte.py +42 -0
  80. digsim/circuit/components/_memstdout.py +37 -0
  81. digsim/circuit/components/_note.py +25 -0
  82. digsim/circuit/components/_on_off_switch.py +54 -0
  83. digsim/circuit/components/_seven_segment.py +28 -0
  84. digsim/circuit/components/_static_level.py +28 -0
  85. digsim/circuit/components/_static_value.py +44 -0
  86. digsim/circuit/components/_yosys_atoms.py +1353 -0
  87. digsim/circuit/components/_yosys_component.py +232 -0
  88. digsim/circuit/components/atoms/__init__.py +23 -0
  89. digsim/circuit/components/atoms/_component.py +280 -0
  90. digsim/circuit/components/atoms/_digsim_exception.py +8 -0
  91. digsim/circuit/components/atoms/_port.py +398 -0
  92. digsim/circuit/components/ic/74162.json +1331 -0
  93. digsim/circuit/components/ic/7448.json +834 -0
  94. digsim/storage_model/__init__.py +7 -0
  95. digsim/storage_model/_app.py +58 -0
  96. digsim/storage_model/_circuit.py +126 -0
  97. digsim/synth/__init__.py +6 -0
  98. digsim/synth/__main__.py +67 -0
  99. digsim/synth/_synthesis.py +156 -0
  100. digsim/utils/__init__.py +6 -0
  101. digsim/utils/_yosys_netlist.py +134 -0
  102. digsim_logic_simulator-0.22.0.dist-info/METADATA +140 -0
  103. digsim_logic_simulator-0.22.0.dist-info/RECORD +107 -0
  104. digsim_logic_simulator-0.22.0.dist-info/WHEEL +5 -0
  105. digsim_logic_simulator-0.22.0.dist-info/entry_points.txt +2 -0
  106. digsim_logic_simulator-0.22.0.dist-info/licenses/LICENSE.md +32 -0
  107. 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()
@@ -0,0 +1,7 @@
1
+ # Copyright (c) Fredrik Andersson, 2023-2025
2
+ # All rights reserved
3
+
4
+ """All classes within digsim.circuit namespace"""
5
+
6
+ from ._circuit import Circuit # noqa: F401
7
+ from .components import PortConnectionError # noqa: F401