You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
vguz_v2/configure_session_widget.py

427 lines
15 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/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())