import sys, glob, json, os from PySide6 import QtWidgets from PySide6.QtWidgets import ( QApplication, QWidget, QRadioButton, QButtonGroup, QGroupBox, QVBoxLayout, QHBoxLayout, QLabel, QFileDialog, QInputDialog, QMessageBox, ) from PySide6.QtCore import Slot from readdisk import ReadDiskWindow from ui_main_window import Ui_MainWindow from ui_read_disk import Ui_ReadDialog # Inherit only from the Qt base class class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.settings = self.get_defaults() # Create an instance of the UI class self.ui = Ui_MainWindow() # Call its setupUi method, passing in the current window (self) self.ui.setupUi(self) # 1. Create the logical group. Pass `self` for parentage. self.logical_button_group = QButtonGroup(self) # 2. Add all radio buttons from different containers. # Assign a unique integer ID to each one. This is best practice. self.logical_button_group.addButton(self.ui.rb_read, 1) self.logical_button_group.addButton(self.ui.rb_write, 2) self.logical_button_group.addButton(self.ui.rb_clean_heads, 3) self.logical_button_group.addButton(self.ui.rb_erase_disk, 4) self.logical_button_group.addButton(self.ui.rb_convert_files, 5) self.logical_button_group.addButton(self.ui.rb_info, 6) self.logical_button_group.addButton(self.ui.rb_measure, 7) self.logical_button_group.addButton(self.ui.rb_pin_level, 8) self.logical_button_group.addButton(self.ui.rb_reset, 9) self.logical_button_group.addButton(self.ui.rb_rpm, 10) self.logical_button_group.addButton(self.ui.rb_seek, 11) self.logical_button_group.addButton(self.ui.rb_delays, 12) self.logical_button_group.addButton(self.ui.rb_update_firmware, 13) self.profile = "GWUIDefault" self.ui.rb_read.setChecked(True) # Populate combo_port with /dev/ttyACM* devices self.populate_serial_ports() self.profile = "GWUIDefault" # Populate combo_profile with available profiles self.populate_profiles() self.ui.combo_profile.currentTextChanged.connect(self.on_combo_profile_changed) self.load_settings(self.profile) self.ui.combo_port.setCurrentText(self.settings.get("device", "Auto")) self.ui.combo_port.currentTextChanged.connect(self.on_combo_port_text_changed) self.ui.action_quit.triggered.connect(self.close) self.ui.pb_close.clicked.connect(self.close) self.ui.pb_execute.clicked.connect(self.on_execute) self.ui.pb_refresh_ports.clicked.connect(self.populate_serial_ports) self.ui.pb_add_profile.clicked.connect(self.on_pb_add_profile) self.ui.pb_delete_profile.clicked.connect(self.on_pb_delete_profile) def on_combo_profile_changed(self, profile_name: str): if profile_name: self.profile = profile_name self.load_settings(self.profile) self.ui.combo_port.setCurrentText(self.settings.get("device", "Auto")) def on_pb_add_profile(self): text, ok = QInputDialog.getText(self, "Add Profile", "Enter new profile name:") if ok and text: self.profile = text self.settings = self.get_defaults() self.save_settings(self.profile) self.populate_profiles() self.ui.combo_port.setCurrentText(self.settings.get("device", "Auto")) def on_pb_delete_profile(self): """Delete the currently selected profile and load the previous one, or defaults if none remain.""" config_path = self.get_config_path(self.profile) try: if os.path.exists(config_path): os.remove(config_path) except Exception as e: QMessageBox.critical(self, "Error", f"Failed to delete profile: {e}") return # Refresh profile list self.populate_profiles() profiles = [self.ui.combo_profile.itemText(i) for i in range(self.ui.combo_profile.count())] if profiles: # Select the previous profile in the list, or the first if deleted was first idx = profiles.index(self.profile) if self.profile in profiles else 0 if idx > 0: idx -= 1 self.profile = profiles[idx] self.ui.combo_profile.setCurrentText(self.profile) self.load_settings(self.profile) self.ui.combo_port.setCurrentText(self.settings.get("device", "Auto")) else: # No profiles left, create default self.profile = "GWUIDefault" self.settings = self.get_defaults() self.save_settings(self.profile) self.populate_profiles() self.ui.combo_profile.setCurrentText(self.profile) self.ui.combo_port.setCurrentText(self.settings.get("device", "Auto")) def on_combo_port_text_changed(self, text: str) -> None: self.settings["device"] = text self.save_settings(self.profile) def get_defaults(self): return { "fileprefix": "disk-image", "filepath": "", "device": "Auto", "double_step_enabled": False, "double_step_value": "1", "fake_index_enabled": False, "fake_index_value": "300", "fake_index_unit": "", "bitrate_enabled": False, "bitrate_value": "250", "revs_enabled": False, "revs_value": "3", "drive_select_enabled": False, "drive_select_value": "A", "retries_enabled": False, "retriesAttributeError: 'MainWindow' object has no attribute 'profile'_value": "3", "pllspec_enabled": False, "pll_period": "", "pll_phase": "", "pll_lowpass": "", "head_sets_enabled": False, "head_sets_value": "0-1", "head_swap_enabled": False, "ss_legacy_enabled": False, "cylinder_sets_enabled": False, "cylinder_sets_value": "0-34,35-79", "rev_track_data_enabled": False, "hard_sectors_enabled": False, "no_clobber_enabled": False, "raw_enabled": False, "pin2_enabled": False, "pin2_high": False, "pin2_low": False, "flippy_enabled": False, "flippy_panasonic": True, "flippy_teac": False, "adjust_speed_enabled": False, "adjust_speed_value": "300", "adjust_speed_unit": "", "suffix_value": "", "inc_enabled": True, "format_enabled": False, "format_value": "", "disktype_value": "" } def get_config_path(self, profile_name: str) -> str: """Return the full path for a profile config file in ~/.config/gwui/""" config_dir = os.path.expanduser("~/.config/gwui") if not os.path.exists(config_dir): os.makedirs(config_dir, exist_ok=True) return os.path.join(config_dir, profile_name + ".json") @Slot() def on_execute(self): read_disk_window = ReadDiskWindow() # read_disk_window.set_port(self.ui.combo_port.currentText()) read_disk_window.set_settings(self.settings) read_disk_window.exec() self.settings = read_disk_window.get_settings() self.settings["device"] = self.ui.combo_port.currentText() self.save_settings(self.profile) def load_settings(self, profile_name: str): config_path = self.get_config_path(profile_name) if profile_name: if os.path.exists(config_path): with open(config_path, 'r') as f: self.settings = json.load(f) return True return False def save_settings(self, profile_name: str): config_path = self.get_config_path(profile_name) if profile_name: with open(config_path, 'w') as f: json.dump(self.settings, f, indent=2) def populate_serial_ports(self): """Populate the combo_port with all /dev/ttyACM* devices.""" ports = glob.glob('/dev/ttyACM*') self.ui.combo_port.clear() self.ui.combo_port.addItem("Auto") self.ui.combo_port.addItems(ports) def populate_profiles(self): """Populate combo_profile with all config files in ~/.config/gwui/ (minus .json).""" config_dir = os.path.expanduser("~/.config/gwui") if not os.path.exists(config_dir): os.makedirs(config_dir, exist_ok=True) profiles = [] for fname in os.listdir(config_dir): if fname.endswith('.json'): profiles.append(fname[:-5]) self.ui.combo_profile.blockSignals(True) self.ui.combo_profile.clear() self.ui.combo_profile.addItems(sorted(profiles)) # Optionally, set current profile if self.profile in profiles: self.ui.combo_profile.setCurrentText(self.profile) self.ui.combo_profile.blockSignals(False) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec())