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.
776 lines
27 KiB
776 lines
27 KiB
#!/usr/bin/env python3
|
|
"""
|
|
Database Manager Widget - vzug-e-hinge
|
|
=======================================
|
|
Comprehensive database management interface for inspecting and managing sessions.
|
|
|
|
Features:
|
|
- Session browser with sort/filter/search
|
|
- Session details with editable notes
|
|
- Rename, delete, and export sessions
|
|
- Delete individual runs
|
|
- Session statistics
|
|
|
|
Author: Kynsight
|
|
Version: 1.0.0
|
|
"""
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QSplitter,
|
|
QTableWidget, QTableWidgetItem, QHeaderView,
|
|
QLabel, QPushButton, QLineEdit, QTextEdit,
|
|
QGroupBox, QMessageBox, QInputDialog, QFileDialog,
|
|
QCheckBox, QListWidget, QListWidgetItem
|
|
)
|
|
from PyQt6.QtCore import Qt
|
|
from PyQt6.QtGui import QFont
|
|
from datetime import datetime
|
|
import sqlite3
|
|
|
|
|
|
class DatabaseManagerWidget(QWidget):
|
|
"""
|
|
Database Manager Widget.
|
|
|
|
Provides comprehensive session management:
|
|
- Browse all sessions
|
|
- View session details and runs
|
|
- Rename, delete, export sessions
|
|
- Add notes to sessions
|
|
- Delete individual runs
|
|
"""
|
|
|
|
def __init__(self, db_manager):
|
|
"""
|
|
Initialize Database Manager Widget.
|
|
|
|
Args:
|
|
db_manager: DatabaseManager instance
|
|
"""
|
|
super().__init__()
|
|
self.db_manager = db_manager
|
|
self.selected_session_id = None
|
|
self._ensure_notes_column()
|
|
self._init_ui()
|
|
self._load_sessions()
|
|
|
|
def _ensure_notes_column(self):
|
|
"""Ensure notes column exists in sessions table (migration)."""
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
# Check if notes column exists
|
|
cursor = conn.execute("PRAGMA table_info(sessions)")
|
|
columns = [row[1] for row in cursor.fetchall()]
|
|
|
|
if 'notes' not in columns:
|
|
# Add notes column
|
|
conn.execute("ALTER TABLE sessions ADD COLUMN notes TEXT DEFAULT ''")
|
|
conn.commit()
|
|
print("[INFO] Added 'notes' column to sessions table")
|
|
except Exception as e:
|
|
print(f"[WARN] Failed to add notes column: {e}")
|
|
|
|
def _init_ui(self):
|
|
"""Initialize user interface."""
|
|
layout = QVBoxLayout()
|
|
self.setLayout(layout)
|
|
|
|
# Main splitter (left: session browser, right: details)
|
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
layout.addWidget(splitter)
|
|
|
|
# Left panel: Session Browser
|
|
left_panel = self._create_session_browser()
|
|
splitter.addWidget(left_panel)
|
|
|
|
# Right panel: Session Details
|
|
right_panel = self._create_session_details()
|
|
splitter.addWidget(right_panel)
|
|
|
|
# Set splitter ratio (40% left, 60% right)
|
|
splitter.setStretchFactor(0, 4)
|
|
splitter.setStretchFactor(1, 6)
|
|
|
|
def _create_session_browser(self):
|
|
"""Create session browser panel (left side)."""
|
|
panel = QWidget()
|
|
layout = QVBoxLayout()
|
|
panel.setLayout(layout)
|
|
|
|
# Search and filter
|
|
search_layout = QHBoxLayout()
|
|
search_layout.addWidget(QLabel("Search:"))
|
|
self.search_input = QLineEdit()
|
|
self.search_input.setPlaceholderText("Filter sessions by name...")
|
|
self.search_input.textChanged.connect(self._filter_sessions)
|
|
search_layout.addWidget(self.search_input)
|
|
layout.addLayout(search_layout)
|
|
|
|
# Session table
|
|
self.session_table = QTableWidget()
|
|
self.session_table.setColumnCount(5)
|
|
self.session_table.setHorizontalHeaderLabels([
|
|
"Select", "Session Name", "Date", "Runs", "Size (MB)"
|
|
])
|
|
|
|
# Configure table
|
|
header = self.session_table.horizontalHeader()
|
|
header.setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
|
|
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
|
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
|
header.setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
|
|
header.setSectionResizeMode(4, QHeaderView.ResizeMode.ResizeToContents)
|
|
|
|
self.session_table.setSelectionBehavior(QTableWidget.SelectionBehavior.SelectRows)
|
|
self.session_table.setSelectionMode(QTableWidget.SelectionMode.SingleSelection)
|
|
self.session_table.itemSelectionChanged.connect(self._on_session_selected)
|
|
self.session_table.setSortingEnabled(True)
|
|
|
|
layout.addWidget(self.session_table)
|
|
|
|
# Batch action buttons
|
|
btn_layout = QHBoxLayout()
|
|
|
|
self.btn_select_all = QPushButton("Select All")
|
|
self.btn_select_all.clicked.connect(self._select_all_sessions)
|
|
btn_layout.addWidget(self.btn_select_all)
|
|
|
|
self.btn_clear_selection = QPushButton("Clear")
|
|
self.btn_clear_selection.clicked.connect(self._clear_selection)
|
|
btn_layout.addWidget(self.btn_clear_selection)
|
|
|
|
btn_layout.addStretch()
|
|
|
|
self.btn_delete_selected = QPushButton("Delete Selected")
|
|
self.btn_delete_selected.clicked.connect(self._delete_selected_sessions)
|
|
btn_layout.addWidget(self.btn_delete_selected)
|
|
|
|
self.btn_refresh = QPushButton("Refresh")
|
|
self.btn_refresh.clicked.connect(self._load_sessions)
|
|
btn_layout.addWidget(self.btn_refresh)
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
|
return panel
|
|
|
|
def _create_session_details(self):
|
|
"""Create session details panel (right side)."""
|
|
panel = QWidget()
|
|
layout = QVBoxLayout()
|
|
panel.setLayout(layout)
|
|
|
|
# Session info group
|
|
info_group = QGroupBox("Session Information")
|
|
info_layout = QVBoxLayout()
|
|
info_group.setLayout(info_layout)
|
|
|
|
# Session ID
|
|
self.label_session_id = QLabel("No session selected")
|
|
self.label_session_id.setFont(QFont("Arial", 10, QFont.Weight.Bold))
|
|
info_layout.addWidget(self.label_session_id)
|
|
|
|
# Created date
|
|
self.label_created = QLabel("")
|
|
info_layout.addWidget(self.label_created)
|
|
|
|
# Notes
|
|
notes_layout = QHBoxLayout()
|
|
notes_layout.addWidget(QLabel("Notes:"))
|
|
self.text_notes = QTextEdit()
|
|
self.text_notes.setMaximumHeight(80)
|
|
self.text_notes.setPlaceholderText("Add notes or description for this session...")
|
|
self.text_notes.textChanged.connect(self._on_notes_changed)
|
|
notes_layout.addWidget(self.text_notes)
|
|
info_layout.addLayout(notes_layout)
|
|
|
|
# Session actions
|
|
action_layout = QHBoxLayout()
|
|
|
|
self.btn_rename = QPushButton("Rename Session")
|
|
self.btn_rename.clicked.connect(self._rename_session)
|
|
action_layout.addWidget(self.btn_rename)
|
|
|
|
self.btn_delete = QPushButton("Delete Session")
|
|
self.btn_delete.clicked.connect(self._delete_session)
|
|
action_layout.addWidget(self.btn_delete)
|
|
|
|
self.btn_export = QPushButton("Export CSV")
|
|
self.btn_export.clicked.connect(self._export_session)
|
|
action_layout.addWidget(self.btn_export)
|
|
|
|
action_layout.addStretch()
|
|
info_layout.addLayout(action_layout)
|
|
|
|
layout.addWidget(info_group)
|
|
|
|
# Runs group
|
|
runs_group = QGroupBox("Runs in Session")
|
|
runs_layout = QVBoxLayout()
|
|
runs_group.setLayout(runs_layout)
|
|
|
|
self.runs_list = QListWidget()
|
|
runs_layout.addWidget(self.runs_list)
|
|
|
|
# Run actions
|
|
run_action_layout = QHBoxLayout()
|
|
|
|
self.btn_delete_runs = QPushButton("Delete Selected Runs")
|
|
self.btn_delete_runs.clicked.connect(self._delete_selected_runs)
|
|
run_action_layout.addWidget(self.btn_delete_runs)
|
|
|
|
run_action_layout.addStretch()
|
|
runs_layout.addLayout(run_action_layout)
|
|
|
|
layout.addWidget(runs_group)
|
|
|
|
# Statistics group
|
|
stats_group = QGroupBox("Session Statistics")
|
|
stats_layout = QVBoxLayout()
|
|
stats_group.setLayout(stats_layout)
|
|
|
|
self.label_stats = QLabel("Select a session to view statistics")
|
|
self.label_stats.setFont(QFont("Monospace", 9))
|
|
stats_layout.addWidget(self.label_stats)
|
|
|
|
layout.addWidget(stats_group)
|
|
|
|
# Disable all controls initially
|
|
self._set_details_enabled(False)
|
|
|
|
return panel
|
|
|
|
def _set_details_enabled(self, enabled: bool):
|
|
"""Enable or disable detail panel controls."""
|
|
self.text_notes.setEnabled(enabled)
|
|
self.btn_rename.setEnabled(enabled)
|
|
self.btn_delete.setEnabled(enabled)
|
|
self.btn_export.setEnabled(enabled)
|
|
self.btn_delete_runs.setEnabled(enabled)
|
|
|
|
def _load_sessions(self):
|
|
"""Load all sessions from database."""
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
cursor = conn.execute("""
|
|
SELECT
|
|
session_id,
|
|
session_name,
|
|
created_at,
|
|
total_runs,
|
|
notes,
|
|
(SELECT COUNT(*) FROM telemetry_decoded WHERE telemetry_decoded.session_id = sessions.session_id) as data_points
|
|
FROM sessions
|
|
ORDER BY created_at DESC
|
|
""")
|
|
|
|
sessions = cursor.fetchall()
|
|
|
|
# Store all sessions for filtering
|
|
self.all_sessions = sessions
|
|
|
|
# Display sessions
|
|
self._display_sessions(sessions)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to load sessions:\n{str(e)}")
|
|
|
|
def _display_sessions(self, sessions):
|
|
"""Display sessions in table."""
|
|
self.session_table.setSortingEnabled(False)
|
|
self.session_table.setRowCount(0)
|
|
|
|
for row_idx, session in enumerate(sessions):
|
|
session_id, session_name, created_at, total_runs, notes, data_points = session
|
|
|
|
self.session_table.insertRow(row_idx)
|
|
|
|
# Checkbox
|
|
checkbox = QCheckBox()
|
|
checkbox_widget = QWidget()
|
|
checkbox_layout = QHBoxLayout(checkbox_widget)
|
|
checkbox_layout.addWidget(checkbox)
|
|
checkbox_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
checkbox_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.session_table.setCellWidget(row_idx, 0, checkbox_widget)
|
|
|
|
# Session name
|
|
name_item = QTableWidgetItem(session_name)
|
|
name_item.setData(Qt.ItemDataRole.UserRole, session_id)
|
|
self.session_table.setItem(row_idx, 1, name_item)
|
|
|
|
# Date
|
|
date_item = QTableWidgetItem(created_at)
|
|
self.session_table.setItem(row_idx, 2, date_item)
|
|
|
|
# Runs
|
|
runs_item = QTableWidgetItem(str(total_runs))
|
|
runs_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self.session_table.setItem(row_idx, 3, runs_item)
|
|
|
|
# Size (estimate: ~100 bytes per data point)
|
|
size_mb = (data_points * 100) / (1024 * 1024)
|
|
size_item = QTableWidgetItem(f"{size_mb:.2f}")
|
|
size_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self.session_table.setItem(row_idx, 4, size_item)
|
|
|
|
self.session_table.setSortingEnabled(True)
|
|
|
|
def _filter_sessions(self):
|
|
"""Filter sessions based on search text."""
|
|
search_text = self.search_input.text().lower()
|
|
|
|
if not search_text:
|
|
# Show all sessions
|
|
self._display_sessions(self.all_sessions)
|
|
else:
|
|
# Filter sessions
|
|
filtered = [s for s in self.all_sessions if search_text in s[1].lower()]
|
|
self._display_sessions(filtered)
|
|
|
|
def _on_session_selected(self):
|
|
"""Handle session selection."""
|
|
selected_items = self.session_table.selectedItems()
|
|
|
|
if not selected_items:
|
|
self._set_details_enabled(False)
|
|
self.selected_session_id = None
|
|
self.label_session_id.setText("No session selected")
|
|
self.label_created.setText("")
|
|
self.text_notes.clear()
|
|
self.runs_list.clear()
|
|
self.label_stats.setText("Select a session to view statistics")
|
|
return
|
|
|
|
# Get session ID from selected row
|
|
row = selected_items[0].row()
|
|
session_id = self.session_table.item(row, 1).data(Qt.ItemDataRole.UserRole)
|
|
|
|
self.selected_session_id = session_id
|
|
self._load_session_details(session_id)
|
|
self._set_details_enabled(True)
|
|
|
|
def _load_session_details(self, session_id: str):
|
|
"""Load details for selected session."""
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
# Get session info
|
|
cursor = conn.execute("""
|
|
SELECT session_id, session_name, created_at, total_runs, notes
|
|
FROM sessions
|
|
WHERE session_id = ?
|
|
""", (session_id,))
|
|
|
|
session = cursor.fetchone()
|
|
|
|
if not session:
|
|
return
|
|
|
|
session_id, session_name, created_at, total_runs, notes = session
|
|
|
|
# Update UI
|
|
self.label_session_id.setText(f"Session: {session_name}")
|
|
self.label_created.setText(f"Created: {created_at}")
|
|
|
|
# Block signals while setting notes to avoid triggering save
|
|
self.text_notes.blockSignals(True)
|
|
self.text_notes.setPlainText(notes or "")
|
|
self.text_notes.blockSignals(False)
|
|
|
|
# Load runs
|
|
self._load_runs(session_id)
|
|
|
|
# Load statistics
|
|
self._load_statistics(session_id)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to load session details:\n{str(e)}")
|
|
|
|
def _load_runs(self, session_id: str):
|
|
"""Load runs for selected session."""
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
cursor = conn.execute("""
|
|
SELECT DISTINCT
|
|
t.run_no,
|
|
COALESCE(u.command_name, i.command_name, 'Unknown') as command_name,
|
|
COUNT(*) as sample_count
|
|
FROM telemetry_decoded t
|
|
LEFT JOIN uart_commands u ON t.run_command_id = u.command_id
|
|
LEFT JOIN i2c_commands i ON t.run_command_id = i.command_id
|
|
WHERE t.session_id = ?
|
|
GROUP BY t.run_no
|
|
ORDER BY t.run_no
|
|
""", (session_id,))
|
|
|
|
runs = cursor.fetchall()
|
|
|
|
self.runs_list.clear()
|
|
|
|
for run_no, command_name, sample_count in runs:
|
|
item = QListWidgetItem(f"Run {run_no}: {command_name} ({sample_count:,} samples)")
|
|
item.setData(Qt.ItemDataRole.UserRole, run_no)
|
|
item.setCheckState(Qt.CheckState.Unchecked)
|
|
self.runs_list.addItem(item)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to load runs:\n{str(e)}")
|
|
|
|
def _load_statistics(self, session_id: str):
|
|
"""Load statistics for selected session."""
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
# Get statistics
|
|
cursor = conn.execute("""
|
|
SELECT
|
|
COUNT(DISTINCT run_no) as total_runs,
|
|
COUNT(*) as total_samples,
|
|
MIN(t_ns) as start_time,
|
|
MAX(t_ns) as end_time
|
|
FROM telemetry_decoded
|
|
WHERE session_id = ?
|
|
""", (session_id,))
|
|
|
|
stats = cursor.fetchone()
|
|
|
|
if stats and stats[0] > 0:
|
|
total_runs, total_samples, start_time, end_time = stats
|
|
|
|
# Calculate duration
|
|
duration_ns = end_time - start_time
|
|
duration_s = duration_ns / 1_000_000_000
|
|
|
|
# Format statistics
|
|
stats_text = f"• Total Runs: {total_runs}\n"
|
|
stats_text += f"• Total Samples: {total_samples:,}\n"
|
|
stats_text += f"• Avg Samples/Run: {total_samples // total_runs:,}\n"
|
|
stats_text += f"• Duration: {duration_s:.1f} seconds\n"
|
|
|
|
self.label_stats.setText(stats_text)
|
|
else:
|
|
self.label_stats.setText("No data available for this session")
|
|
|
|
except Exception as e:
|
|
self.label_stats.setText(f"Error loading statistics: {str(e)}")
|
|
|
|
def _on_notes_changed(self):
|
|
"""Handle notes text change - auto-save."""
|
|
if not self.selected_session_id:
|
|
return
|
|
|
|
notes = self.text_notes.toPlainText()
|
|
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
conn.execute("""
|
|
UPDATE sessions
|
|
SET notes = ?
|
|
WHERE session_id = ?
|
|
""", (notes, self.selected_session_id))
|
|
conn.commit()
|
|
except Exception as e:
|
|
print(f"Failed to save notes: {e}")
|
|
|
|
def _rename_session(self):
|
|
"""Rename selected session."""
|
|
if not self.selected_session_id:
|
|
return
|
|
|
|
# Get current name
|
|
current_name = self.label_session_id.text().replace("Session: ", "")
|
|
|
|
# Ask for new name
|
|
new_name, ok = QInputDialog.getText(
|
|
self,
|
|
"Rename Session",
|
|
"Enter new session name:",
|
|
text=current_name
|
|
)
|
|
|
|
if not ok or not new_name.strip():
|
|
return
|
|
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
# Update session name in sessions table
|
|
conn.execute("""
|
|
UPDATE sessions
|
|
SET session_name = ?
|
|
WHERE session_id = ?
|
|
""", (new_name.strip(), self.selected_session_id))
|
|
|
|
# Update session name in telemetry_raw table
|
|
conn.execute("""
|
|
UPDATE telemetry_raw
|
|
SET session_name = ?
|
|
WHERE session_id = ?
|
|
""", (new_name.strip(), self.selected_session_id))
|
|
|
|
# Update session name in telemetry_decoded table
|
|
conn.execute("""
|
|
UPDATE telemetry_decoded
|
|
SET session_name = ?
|
|
WHERE session_id = ?
|
|
""", (new_name.strip(), self.selected_session_id))
|
|
|
|
conn.commit()
|
|
|
|
# Reload sessions
|
|
self._load_sessions()
|
|
|
|
# Reload details
|
|
self._load_session_details(self.selected_session_id)
|
|
|
|
QMessageBox.information(self, "Success", "Session renamed successfully!")
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to rename session:\n{str(e)}")
|
|
|
|
def _delete_session(self):
|
|
"""Delete selected session."""
|
|
if not self.selected_session_id:
|
|
return
|
|
|
|
# Confirm
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"Confirm Delete",
|
|
f"Delete session '{self.selected_session_id}'?\n\n"
|
|
"This will delete all runs and data for this session.\n"
|
|
"This action cannot be undone!",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
return
|
|
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
conn.execute("DELETE FROM telemetry_decoded WHERE session_id = ?", (self.selected_session_id,))
|
|
conn.execute("DELETE FROM telemetry_raw WHERE session_id = ?", (self.selected_session_id,))
|
|
conn.execute("DELETE FROM sessions WHERE session_id = ?", (self.selected_session_id,))
|
|
conn.commit()
|
|
|
|
self.selected_session_id = None
|
|
self._load_sessions()
|
|
self._set_details_enabled(False)
|
|
|
|
QMessageBox.information(self, "Success", "Session deleted successfully!")
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to delete session:\n{str(e)}")
|
|
|
|
def _export_session(self):
|
|
"""Export selected session to CSV."""
|
|
if not self.selected_session_id:
|
|
return
|
|
|
|
# Get save location
|
|
filename = f"{self.selected_session_id}_export.csv"
|
|
filepath, _ = QFileDialog.getSaveFileName(
|
|
self,
|
|
"Export Session",
|
|
filename,
|
|
"CSV Files (*.csv);;All Files (*)"
|
|
)
|
|
|
|
if not filepath:
|
|
return
|
|
|
|
try:
|
|
import csv
|
|
|
|
conn = self.db_manager.get_connection()
|
|
|
|
# Get all data for session
|
|
cursor = conn.execute("""
|
|
SELECT
|
|
session_id, session_name, run_no, t_ns, time_ms,
|
|
motor_current, encoder_value, relative_encoder_value,
|
|
v24_pec_diff, pwm,
|
|
i2c_raw14, i2c_zero_raw14, i2c_delta_raw14,
|
|
i2c_angle_deg, i2c_zero_angle_deg,
|
|
angular_velocity, angular_acceleration
|
|
FROM telemetry_decoded
|
|
WHERE session_id = ?
|
|
ORDER BY run_no, t_ns
|
|
""", (self.selected_session_id,))
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
# Write to CSV
|
|
with open(filepath, 'w', newline='') as f:
|
|
writer = csv.writer(f)
|
|
|
|
# Header
|
|
writer.writerow([
|
|
'session_id', 'session_name', 'run_no', 't_ns', 'time_ms',
|
|
'motor_current', 'encoder_value', 'relative_encoder_value',
|
|
'v24_pec_diff', 'pwm',
|
|
'i2c_raw14', 'i2c_zero_raw14', 'i2c_delta_raw14',
|
|
'i2c_angle_deg', 'i2c_zero_angle_deg',
|
|
'angular_velocity', 'angular_acceleration'
|
|
])
|
|
|
|
# Data
|
|
writer.writerows(rows)
|
|
|
|
QMessageBox.information(
|
|
self,
|
|
"Export Complete",
|
|
f"Session exported successfully!\n\n"
|
|
f"File: {filepath}\n"
|
|
f"Rows: {len(rows):,}"
|
|
)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to export session:\n{str(e)}")
|
|
|
|
def _delete_selected_runs(self):
|
|
"""Delete selected runs from session."""
|
|
if not self.selected_session_id:
|
|
return
|
|
|
|
# Get checked runs
|
|
selected_runs = []
|
|
for i in range(self.runs_list.count()):
|
|
item = self.runs_list.item(i)
|
|
if item.checkState() == Qt.CheckState.Checked:
|
|
run_no = item.data(Qt.ItemDataRole.UserRole)
|
|
selected_runs.append(run_no)
|
|
|
|
if not selected_runs:
|
|
QMessageBox.warning(self, "No Selection", "Please select runs to delete.")
|
|
return
|
|
|
|
# Confirm
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"Confirm Delete",
|
|
f"Delete {len(selected_runs)} run(s) from session?\n\n"
|
|
"This will permanently delete all data for these runs.\n"
|
|
"This action cannot be undone!",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
return
|
|
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
for run_no in selected_runs:
|
|
conn.execute("""
|
|
DELETE FROM telemetry_decoded
|
|
WHERE session_id = ? AND run_no = ?
|
|
""", (self.selected_session_id, run_no))
|
|
|
|
conn.execute("""
|
|
DELETE FROM telemetry_raw
|
|
WHERE session_id = ? AND run_no = ?
|
|
""", (self.selected_session_id, run_no))
|
|
|
|
# Update total_runs in sessions table
|
|
cursor = conn.execute("""
|
|
SELECT COUNT(DISTINCT run_no)
|
|
FROM telemetry_decoded
|
|
WHERE session_id = ?
|
|
""", (self.selected_session_id,))
|
|
|
|
new_run_count = cursor.fetchone()[0]
|
|
|
|
conn.execute("""
|
|
UPDATE sessions
|
|
SET total_runs = ?
|
|
WHERE session_id = ?
|
|
""", (new_run_count, self.selected_session_id))
|
|
|
|
conn.commit()
|
|
|
|
# Reload details
|
|
self._load_session_details(self.selected_session_id)
|
|
self._load_sessions()
|
|
|
|
QMessageBox.information(self, "Success", f"Deleted {len(selected_runs)} run(s) successfully!")
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to delete runs:\n{str(e)}")
|
|
|
|
def _select_all_sessions(self):
|
|
"""Select all checkboxes in session table."""
|
|
for row in range(self.session_table.rowCount()):
|
|
checkbox_widget = self.session_table.cellWidget(row, 0)
|
|
checkbox = checkbox_widget.findChild(QCheckBox)
|
|
if checkbox:
|
|
checkbox.setChecked(True)
|
|
|
|
def _clear_selection(self):
|
|
"""Clear all checkboxes in session table."""
|
|
for row in range(self.session_table.rowCount()):
|
|
checkbox_widget = self.session_table.cellWidget(row, 0)
|
|
checkbox = checkbox_widget.findChild(QCheckBox)
|
|
if checkbox:
|
|
checkbox.setChecked(False)
|
|
|
|
def _delete_selected_sessions(self):
|
|
"""Delete all checked sessions."""
|
|
# Get checked sessions
|
|
selected_sessions = []
|
|
for row in range(self.session_table.rowCount()):
|
|
checkbox_widget = self.session_table.cellWidget(row, 0)
|
|
checkbox = checkbox_widget.findChild(QCheckBox)
|
|
if checkbox and checkbox.isChecked():
|
|
session_id = self.session_table.item(row, 1).data(Qt.ItemDataRole.UserRole)
|
|
session_name = self.session_table.item(row, 1).text()
|
|
selected_sessions.append((session_id, session_name))
|
|
|
|
if not selected_sessions:
|
|
QMessageBox.warning(self, "No Selection", "Please select sessions to delete.")
|
|
return
|
|
|
|
# Confirm
|
|
session_list = "\n".join([f"• {name}" for _, name in selected_sessions])
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"Confirm Delete",
|
|
f"Delete {len(selected_sessions)} session(s)?\n\n{session_list}\n\n"
|
|
"This will permanently delete all data for these sessions.\n"
|
|
"This action cannot be undone!",
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
)
|
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
return
|
|
|
|
try:
|
|
conn = self.db_manager.get_connection()
|
|
|
|
for session_id, _ in selected_sessions:
|
|
conn.execute("DELETE FROM telemetry_decoded WHERE session_id = ?", (session_id,))
|
|
conn.execute("DELETE FROM telemetry_raw WHERE session_id = ?", (session_id,))
|
|
conn.execute("DELETE FROM sessions WHERE session_id = ?", (session_id,))
|
|
|
|
conn.commit()
|
|
|
|
# Vacuum database
|
|
self.db_manager.vacuum()
|
|
|
|
self.selected_session_id = None
|
|
self._load_sessions()
|
|
self._set_details_enabled(False)
|
|
|
|
QMessageBox.information(
|
|
self,
|
|
"Success",
|
|
f"Deleted {len(selected_sessions)} session(s) successfully!\n\n"
|
|
"Database vacuumed."
|
|
)
|
|
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Error", f"Failed to delete sessions:\n{str(e)}")
|