#!/usr/bin/env python3 """ Configure Session Widget - vzug-e-hinge ======================================== Visual command sequence builder. """ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel, QLineEdit, QPushButton, QListWidget, QListWidgetItem, QMessageBox, QSplitter, QFormLayout, QSpinBox, QComboBox, QCheckBox ) from PyQt6.QtCore import Qt, pyqtSignal import json from database.init_database import DatabaseManager class ConfigureSessionWidget(QWidget): """Visual command sequence builder.""" profile_saved = pyqtSignal(int) profile_deleted = pyqtSignal(int) def __init__(self, db_manager: DatabaseManager): super().__init__() self.db_manager = db_manager self.db_conn = db_manager.get_connection() self.current_profile_id = None self.command_sequence = [] # List of {command_id, command_name, delay_ms} self._init_ui() self._load_profiles() self._load_uart_commands() def _init_ui(self): """Initialize UI.""" layout = QVBoxLayout() splitter = QSplitter(Qt.Orientation.Horizontal) splitter.addWidget(self._create_list_panel()) splitter.addWidget(self._create_editor_panel()) splitter.setSizes([300, 700]) layout.addWidget(splitter) self.setLayout(layout) def _create_list_panel(self): """Create profile list panel.""" widget = QWidget() layout = QVBoxLayout() layout.addWidget(QLabel("Session Profiles:")) self.profile_list = QListWidget() self.profile_list.currentItemChanged.connect(self._on_profile_selected) layout.addWidget(self.profile_list) btn_layout = QHBoxLayout() self.btn_add = QPushButton("Add New") self.btn_add.clicked.connect(self._on_add_clicked) btn_layout.addWidget(self.btn_add) self.btn_delete = QPushButton("Delete") self.btn_delete.clicked.connect(self._on_delete_clicked) self.btn_delete.setEnabled(False) btn_layout.addWidget(self.btn_delete) layout.addLayout(btn_layout) widget.setLayout(layout) return widget def _create_editor_panel(self): """Create visual command builder.""" widget = QWidget() layout = QVBoxLayout() # Profile name and description form = QFormLayout() self.name_input = QLineEdit() form.addRow("Profile Name:", self.name_input) self.desc_input = QLineEdit() form.addRow("Description:", self.desc_input) # Print RX checkbox self.print_rx_checkbox = QCheckBox("Print Command RX (disable packet detection & DB saving)") self.print_rx_checkbox.setToolTip( "Enable: Print raw RX buffer to data monitor, skip database saving\n" "Disable: Normal packet detection and database logging" ) form.addRow("", self.print_rx_checkbox) layout.addLayout(form) # Add command section add_group = QGroupBox("Add Command") add_layout = QVBoxLayout() add_form = QFormLayout() # Command type selector self.cmd_type_combo = QComboBox() self.cmd_type_combo.addItem("UART", "uart") self.cmd_type_combo.addItem("I2C", "i2c") self.cmd_type_combo.currentIndexChanged.connect(self._on_command_type_changed) add_form.addRow("Command Type:", self.cmd_type_combo) # Command selector (will be populated based on type) self.cmd_combo = QComboBox() add_form.addRow("Command:", self.cmd_combo) self.delay_spin = QSpinBox() self.delay_spin.setRange(0, 60000) self.delay_spin.setValue(3000) self.delay_spin.setSuffix(" ms") add_form.addRow("Delay After:", self.delay_spin) add_layout.addLayout(add_form) btn_add_cmd = QPushButton("➕ Add to Sequence") btn_add_cmd.clicked.connect(self._on_add_command) add_layout.addWidget(btn_add_cmd) add_group.setLayout(add_layout) layout.addWidget(add_group) # Command sequence list seq_group = QGroupBox("Command Sequence") seq_layout = QVBoxLayout() self.sequence_list = QListWidget() seq_layout.addWidget(self.sequence_list) # Sequence controls seq_btn_layout = QHBoxLayout() self.btn_up = QPushButton("↑ Move Up") self.btn_up.clicked.connect(self._on_move_up) seq_btn_layout.addWidget(self.btn_up) self.btn_down = QPushButton("↓ Move Down") self.btn_down.clicked.connect(self._on_move_down) seq_btn_layout.addWidget(self.btn_down) self.btn_remove = QPushButton("✖ Remove") self.btn_remove.clicked.connect(self._on_remove_command) seq_btn_layout.addWidget(self.btn_remove) seq_layout.addLayout(seq_btn_layout) seq_group.setLayout(seq_layout) layout.addWidget(seq_group) # Save button btn_layout = QHBoxLayout() self.btn_save = QPushButton("Save Profile") self.btn_save.clicked.connect(self._on_save_clicked) self.btn_save.setEnabled(False) btn_layout.addWidget(self.btn_save) btn_layout.addStretch() layout.addLayout(btn_layout) widget.setLayout(layout) return widget def _on_command_type_changed(self): """Load commands when type changes.""" cmd_type = self.cmd_type_combo.currentData() if cmd_type == "uart": self._load_uart_commands() else: self._load_i2c_commands() def _load_uart_commands(self): """Load UART commands into dropdown.""" self.cmd_combo.clear() cursor = self.db_conn.execute( "SELECT command_id, command_name FROM uart_commands WHERE is_active=1 ORDER BY command_name" ) for row in cursor.fetchall(): self.cmd_combo.addItem(row[1], row[0]) def _load_i2c_commands(self): """Load I2C commands into dropdown.""" self.cmd_combo.clear() cursor = self.db_conn.execute( "SELECT command_id, command_name FROM i2c_commands WHERE is_active=1 ORDER BY command_name" ) for row in cursor.fetchall(): self.cmd_combo.addItem(row[1], row[0]) def _load_profiles(self): """Load profiles from database.""" self.profile_list.clear() cursor = self.db_conn.execute( "SELECT profile_id, profile_name FROM session_profiles ORDER BY profile_name" ) for row in cursor.fetchall(): item = QListWidgetItem(row[1]) item.setData(Qt.ItemDataRole.UserRole, row[0]) self.profile_list.addItem(item) def _load_profile_details(self, profile_id: int): """Load profile into editor.""" cursor = self.db_conn.execute( "SELECT profile_name, description, command_sequence, print_command_rx FROM session_profiles WHERE profile_id = ?", (profile_id,) ) row = cursor.fetchone() if row: self.name_input.setText(row[0] or "") self.desc_input.setText(row[1] or "") # Load print_command_rx checkbox print_rx = bool(row[3]) if len(row) > 3 and row[3] is not None else False self.print_rx_checkbox.setChecked(print_rx) # Parse JSON and populate sequence try: json_obj = json.loads(row[2] or "{}") self.command_sequence = [] for cmd in json_obj.get("commands", []): cmd_id = cmd["command_id"] cmd_type = cmd.get("command_type", "uart") # Default to uart for backwards compatibility # Get command name from correct table based on type if cmd_type == "i2c": c = self.db_conn.execute( "SELECT command_name FROM i2c_commands WHERE command_id = ?", (cmd_id,) ) else: c = self.db_conn.execute( "SELECT command_name FROM uart_commands WHERE command_id = ?", (cmd_id,) ) cmd_row = c.fetchone() if cmd_row: self.command_sequence.append({ "command_type": cmd_type, "command_id": cmd_id, "command_name": cmd_row[0], "delay_ms": cmd["delay_ms"] }) self._update_sequence_list() except: self.command_sequence = [] self.current_profile_id = profile_id self.btn_delete.setEnabled(True) self.btn_save.setEnabled(True) # Always allow saving def _update_sequence_list(self): """Update sequence list display.""" self.sequence_list.clear() for i, cmd in enumerate(self.command_sequence, 1): cmd_type = cmd.get('command_type', 'uart') type_label = "UART" if cmd_type == "uart" else "I2C" text = f"{i}. [{type_label}] {cmd['command_name']} ({cmd['delay_ms']}ms)" self.sequence_list.addItem(text) def _on_add_command(self): """Add command to sequence.""" if self.cmd_combo.count() == 0: QMessageBox.warning(self, "Error", "No commands available") return cmd_type = self.cmd_type_combo.currentData() cmd_id = self.cmd_combo.currentData() cmd_name = self.cmd_combo.currentText() delay = self.delay_spin.value() self.command_sequence.append({ "command_type": cmd_type, "command_id": cmd_id, "command_name": cmd_name, "delay_ms": delay }) self._update_sequence_list() self.btn_save.setEnabled(True) def _on_move_up(self): """Move selected command up.""" idx = self.sequence_list.currentRow() if idx > 0: self.command_sequence[idx], self.command_sequence[idx-1] = \ self.command_sequence[idx-1], self.command_sequence[idx] self._update_sequence_list() self.sequence_list.setCurrentRow(idx-1) self.btn_save.setEnabled(True) def _on_move_down(self): """Move selected command down.""" idx = self.sequence_list.currentRow() if 0 <= idx < len(self.command_sequence) - 1: self.command_sequence[idx], self.command_sequence[idx+1] = \ self.command_sequence[idx+1], self.command_sequence[idx] self._update_sequence_list() self.sequence_list.setCurrentRow(idx+1) self.btn_save.setEnabled(True) def _on_remove_command(self): """Remove selected command.""" idx = self.sequence_list.currentRow() if idx >= 0: del self.command_sequence[idx] self._update_sequence_list() self.btn_save.setEnabled(True) def _on_profile_selected(self, current, previous): """Handle profile selection.""" if current: profile_id = current.data(Qt.ItemDataRole.UserRole) self._load_profile_details(profile_id) def _on_add_clicked(self): """Handle add button.""" self.name_input.clear() self.desc_input.clear() self.print_rx_checkbox.setChecked(False) # Default to disabled self.command_sequence = [] self._update_sequence_list() self.profile_list.clearSelection() self.current_profile_id = None self.btn_delete.setEnabled(False) self.btn_save.setEnabled(True) self.name_input.setFocus() def _on_delete_clicked(self): """Handle delete button.""" if not self.current_profile_id: return reply = QMessageBox.question( self, "Confirm Delete", f"Delete profile '{self.name_input.text()}'?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if reply != QMessageBox.StandardButton.Yes: return try: self.db_conn.execute( "DELETE FROM session_profiles WHERE profile_id = ?", (self.current_profile_id,) ) self.db_conn.commit() self.profile_deleted.emit(self.current_profile_id) self._load_profiles() self.name_input.clear() self.desc_input.clear() self.command_sequence = [] self._update_sequence_list() self.current_profile_id = None self.btn_save.setEnabled(False) self.btn_delete.setEnabled(False) QMessageBox.information(self, "Success", "Profile deleted") except Exception as e: QMessageBox.critical(self, "Error", f"Delete failed: {str(e)}") def _on_save_clicked(self): """Handle save button.""" name = self.name_input.text().strip() if not name: QMessageBox.warning(self, "Error", "Profile name required") return if not self.command_sequence: QMessageBox.warning(self, "Error", "Add at least one command") return desc = self.desc_input.text().strip() print_rx = 1 if self.print_rx_checkbox.isChecked() else 0 # Build JSON json_obj = { "commands": [ { "command_id": cmd["command_id"], "command_type": cmd.get("command_type", "uart"), "delay_ms": cmd["delay_ms"] } for cmd in self.command_sequence ] } json_text = json.dumps(json_obj) try: if self.current_profile_id: self.db_conn.execute( "UPDATE session_profiles SET profile_name=?, description=?, " "command_sequence=?, print_command_rx=?, last_modified=datetime('now') WHERE profile_id=?", (name, desc, json_text, print_rx, self.current_profile_id) ) msg = "Profile updated" else: cursor = self.db_conn.execute( "INSERT INTO session_profiles (profile_name, description, command_sequence, print_command_rx) " "VALUES (?, ?, ?, ?)", (name, desc, json_text, print_rx) ) self.current_profile_id = cursor.lastrowid msg = "Profile created" self.db_conn.commit() self.profile_saved.emit(self.current_profile_id) self._load_profiles() QMessageBox.information(self, "Success", msg) self.btn_save.setEnabled(False) except Exception as e: QMessageBox.critical(self, "Error", f"Save failed: {str(e)}") if __name__ == "__main__": import sys from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv) db = DatabaseManager("database/ehinge.db") db.initialize() widget = ConfigureSessionWidget(db) widget.setWindowTitle("Configure Session Profiles") widget.resize(1000, 600) widget.show() sys.exit(app.exec())