# components/uart/uart_ui.py # One-file UART page: dedicated handler + UI (no base_handler, no protocol_ui). from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLineEdit, QSplitter, ) from PyQt6.QtCore import Qt # project-local deps (unchanged) from components.console.console_ui import console_widget from components.console.console_registry import log_main_console import components.items.elements as elements from components.commands.command_table_ui import command_table_widget import config.config as config # UART logic + dialogs + db (unchanged) from components.uart.uart_logic import UART_logic from components.uart.uart_command_editor import UartCommandEditorDialog from components.data import db # ----------------------------- # Internal, dedicated UART handler # ----------------------------- # ----------------------------- # Thin handler that calls back into the UI widget # ----------------------------- class UartHandler: """ Used by command_table_widget. It doesn't own UART logic. It just forwards actions to the parent UartWidget. """ def __init__(self, widget: "UartWidget"): self.w = widget # back-reference to the UI # table may request the list via the handler def get_command_list(self): return self.w.uart_logic.get_predefined_commands() # ----- actions triggered from the command table ----- def send_command(self, command: dict): # command has "hex_string" self.w.send_command(command) # command editor ops (invoked from command table) def add_command(self): dialog = UartCommandEditorDialog() if dialog.exec(): command = dialog.get_data() db.add_uart_command(command) def modify_command(self, command): dialog = UartCommandEditorDialog(command=command) if dialog.exec(): updated_command = dialog.get_data() updated_command["id"] = command["id"] db.modify_uart_command(updated_command) def delete_command(self, command): db.delete_uart_command(command) # ----------------------------- # UART UI (layout preserved) # ----------------------------- class UartWidget(QWidget): """ Drops in where your old ProtocolUIWidget(UART) was used. Same controls, same enable/disable behavior, same splitter/table/console layout. """ def __init__(self, parent=None): super().__init__(parent) self.uart_logic = UART_logic() # all comms live here self.handler = UartHandler(self) # thin handler for table actions self.commands = self.uart_logic.get_predefined_commands() self.comboboxes = {} self.connection_status = False self.init_ui() def init_ui(self): # === Top Control Row === top_controls = QWidget() top_controls_layout = QHBoxLayout(top_controls) top_controls_layout.setContentsMargins(0, 0, 0, 0) top_controls_layout.setSpacing(12) top_controls_layout.setAlignment(Qt.AlignmentFlag.AlignTop) # Dynamically create comboboxes based on handler configuration # Static definition instead of get_combobox_config() self.comboboxes["port"] = QComboBox() self.comboboxes["port"].addItems(self.uart_logic.get_channels()) top_controls_layout.addWidget( elements.label_and_widget("Port", self.comboboxes["port"]) ) # Refresh button self.button_refresh = elements.create_icon_button( config.REFRESH_BUTTON_ICON_LINK, icon_size=30, border_size=4 ) top_controls_layout.addWidget(self.button_refresh) self.comboboxes["baudrate"] = QComboBox() self.comboboxes["baudrate"].addItems(self.uart_logic.get_baud_rates()) top_controls_layout.addWidget( elements.label_and_widget("Baudrate", self.comboboxes["baudrate"]) ) self.comboboxes["data_bits"] = QComboBox() self.comboboxes["data_bits"].addItems(self.uart_logic.get_data_bits()) top_controls_layout.addWidget( elements.label_and_widget("Data Bits", self.comboboxes["data_bits"]) ) self.comboboxes["stop_bits"] = QComboBox() self.comboboxes["stop_bits"].addItems(self.uart_logic.get_stop_bits()) top_controls_layout.addWidget( elements.label_and_widget("Stop Bits", self.comboboxes["stop_bits"]) ) self.comboboxes["parity"] = QComboBox() self.comboboxes["parity"].addItems(self.uart_logic.get_parity()) top_controls_layout.addWidget( elements.label_and_widget("Parity", self.comboboxes["parity"]) ) # Connect / Disconnect self.button_connect = QPushButton("Connect") top_controls_layout.addWidget( elements.label_and_widget("", self.button_connect) ) self.button_disconnect = QPushButton("Disconnect") top_controls_layout.addWidget( elements.label_and_widget("", self.button_disconnect) ) # === Command Table === self.command_table = command_table_widget( commands=self.commands, handler=self.handler, # table uses handler methods for send/CRUD ) col1_widget = QWidget() col1_layout = QVBoxLayout(col1_widget) col1_layout.setContentsMargins(0, 0, 0, 0) col1_layout.setSpacing(4) col1_layout.addWidget(self.command_table) # === Input Field + Send Button === self.input_hex = QLineEdit() self.button_send_raw = QPushButton("Send Raw") input_line_layout = QHBoxLayout() input_line_layout.setContentsMargins(0, 0, 0, 0) input_line_layout.setSpacing(4) input_line_layout.addWidget(self.input_hex) input_line_layout.addWidget(self.button_send_raw) # === Console === self.console = console_widget() self.uart_logic.set_logger(self.console.log) console_stack_widget = QWidget() console_stack_layout = QVBoxLayout(console_stack_widget) console_stack_layout.setContentsMargins(0, 0, 0, 0) console_stack_layout.setSpacing(4) console_stack_layout.addLayout(input_line_layout) console_stack_layout.addWidget(self.console) # === Horizontal Splitter: Table | Console (sizes preserved) === splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(col1_widget) splitter.addWidget(console_stack_widget) splitter.setSizes([740, 1200]) splitter.setStretchFactor(0, 0) splitter.setStretchFactor(1, 1) # === Main Layout === main_layout = QVBoxLayout() main_layout.setContentsMargins(4, 4, 4, 4) main_layout.setSpacing(6) main_layout.addWidget(top_controls) main_layout.addWidget(splitter, stretch=1) self.setLayout(main_layout) # === Signals === self.button_refresh.clicked.connect(self.refresh) self.button_connect.clicked.connect(self.connect) self.button_disconnect.clicked.connect(self.disconnect) self.button_send_raw.clicked.connect(self.send_command_raw) self.disconnected_enable_status() # ---- UI state toggles (unchanged) ---- def disconnected_enable_status(self): for combo in self.comboboxes.values(): elements.set_enabled_state(True, combo, grayOut=False) elements.set_enabled_state(True, self.command_table, grayOut=False) elements.set_enabled_state(False, self.input_hex, grayOut=True) elements.set_enabled_state(False, self.button_send_raw, grayOut=True) elements.set_enabled_state(False, self.button_disconnect, grayOut=True) elements.set_enabled_state(True, self.button_connect, grayOut=False) def connected_enable_status(self): for combo in self.comboboxes.values(): elements.set_enabled_state(False, combo, grayOut=True) elements.set_enabled_state(True, self.command_table, grayOut=False) elements.set_enabled_state(True, self.input_hex, grayOut=False) elements.set_enabled_state(True, self.button_send_raw, grayOut=False) elements.set_enabled_state(True, self.button_disconnect, grayOut=False) elements.set_enabled_state(False, self.button_connect, grayOut=True) # ---- Button handlers (unchanged behavior) ---- def connect(self): log_main_console("info", "🔗 Connecting...") port = self.comboboxes["port"].currentText() baudrate = int(self.comboboxes["baudrate"].currentText()) data_bits = int(self.comboboxes["data_bits"].currentText()) stop_bits = float(self.comboboxes["stop_bits"].currentText()) parity = self.comboboxes["parity"].currentText()[0].upper() success = self.uart_logic.connect( port=port, baudrate=baudrate, data_bits=data_bits, stop_bits=stop_bits, parity=parity, ) if success: self.connected_enable_status() self.command_table.set_connected_state() self.connection_status = True else: elements.flash_button( self.button_connect, flash_style="background-color: red;" ) def disconnect(self): log_main_console("info", "🔌 Disconnecting...") success = self.uart_logic.disconnect() if success: log_main_console("info", "🔌 Disconnecting...") self.disconnected_enable_status() self.command_table.set_disconnected_state() self.connection_status = False self.refresh(silent=True) else: elements.flash_button( self.button_disconnect, flash_style="background-color: red;" ) def refresh(self, silent: bool = False): log_main_console("info", "🔄 Refreshing...") self.comboboxes["port"].clear() port = self.uart_logic.get_channels() if port: self.comboboxes["port"].addItems(port) if not silent: elements.flash_button(self.button_refresh) log_main_console("success", "🔄 Refresehd") else: elements.flash_button( self.button_refresh, flash_style="background-color: red;" ) log_main_console("error", "🔄 Refresehd") def get_current_config(self): return {key: cb.currentText() for key, cb in self.comboboxes.items()} def send_command(self, command): # self.uart_logic.send_command(command["hex_string"]) self.uart_logic.send_pgkomm2(command["hex_string"]) config.CURRENT_COMMAND = command["name"] def send_command_raw(self): hex_str = self.input_hex.text().strip() if not hex_str: self.console.log("warning", "⚠️ No command entered.") else: self.uart_logic.send_command(hex_str) config.CURRENT_COMMAND = hex_str