""" Analysis Timeline Panel Panel showing history of analysis results and session statistics. """ from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QScrollArea, QFrame) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont from datetime import datetime from ui.widgets.panel_header import PanelHeader from ui.widgets.timeline_entry import TimelineEntry class AnalysisTimelinePanel(QWidget): """ Panel for displaying analysis timeline and statistics. Signals: save_audio_clicked: Emitted when save audio button is clicked save_complete_clicked: Emitted when save complete package is clicked """ save_audio_clicked = pyqtSignal() save_complete_clicked = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self.test_counter = 0 self.init_ui() def init_ui(self): """Initialize the timeline panel UI.""" layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) # Main panel container with card styling self.setStyleSheet(""" QWidget { background-color: white; border: 1px solid #ddd; } """) # Header header = QWidget() header.setFixedHeight(25) header.setStyleSheet("background-color: #34495e;") header_layout = QHBoxLayout(header) header_layout.setContentsMargins(10, 0, 10, 0) header_layout.setSpacing(0) title = QLabel("Analysis Timeline") title.setStyleSheet("color: white; font-weight: bold; font-size: 16px;") header_layout.addWidget(title) # Content area content = QWidget() content.setStyleSheet(""" background-color: white; border: none; """) content_layout = QVBoxLayout(content) content_layout.setSpacing(10) content_layout.setContentsMargins(10, 10, 10, 10) # Timeline entries (scrollable) timeline_scroll = QScrollArea() timeline_scroll.setWidgetResizable(True) timeline_scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) timeline_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) timeline_scroll.setMinimumHeight(220) timeline_scroll.setStyleSheet(""" QScrollArea { border: none; background-color: transparent; } """) timeline_widget = QWidget() self.timeline_layout = QVBoxLayout(timeline_widget) self.timeline_layout.setSpacing(8) self.timeline_layout.setContentsMargins(0, 0, 5, 0) self.timeline_layout.addStretch() timeline_scroll.setWidget(timeline_widget) content_layout.addWidget(timeline_scroll) # Separator separator = QFrame() separator.setFrameShape(QFrame.HLine) separator.setStyleSheet("background-color: #ecf0f1;") content_layout.addWidget(separator) # Snapshot options section snapshot_label = QLabel("Current Snapshot Options:") snapshot_label.setFont(QFont("Arial", 10, QFont.Bold)) snapshot_label.setStyleSheet("color: #2c3e50; margin-top: 8px;") content_layout.addWidget(snapshot_label) # Snapshot buttons (first row) snapshot_row1 = QHBoxLayout() snapshot_row1.setSpacing(8) save_images_btn = self._create_snapshot_btn("Save Images", "#3498db") save_images_btn.setEnabled(False) save_images_btn.setToolTip("Save camera images - Coming soon") snapshot_row1.addWidget(save_images_btn) self.save_audio_btn = self._create_snapshot_btn("Save Audio", "#16a085") self.save_audio_btn.clicked.connect(self.save_audio_clicked.emit) self.save_audio_btn.setEnabled(False) # Enabled after processing snapshot_row1.addWidget(self.save_audio_btn) save_spectral_btn = self._create_snapshot_btn("Save Spectral", "#8e44ad") save_spectral_btn.setEnabled(False) save_spectral_btn.setToolTip("Save spectral data - Coming soon") snapshot_row1.addWidget(save_spectral_btn) content_layout.addLayout(snapshot_row1) # Complete package button (second row) self.save_complete_btn = QPushButton("Save Complete Analysis Package") self.save_complete_btn.setFont(QFont("Arial", 9, QFont.Bold)) self.save_complete_btn.setFixedHeight(28) self.save_complete_btn.setStyleSheet(""" QPushButton { background-color: #27ae60; border: 1px solid #229954; color: white; } QPushButton:hover { background-color: #229954; } QPushButton:disabled { background-color: #95a5a6; border-color: #7f8c8d; } """) self.save_complete_btn.clicked.connect(self.save_complete_clicked.emit) self.save_complete_btn.setEnabled(False) self.save_complete_btn.setToolTip("Save complete analysis - Coming soon") content_layout.addWidget(self.save_complete_btn) # Session statistics stats_label = QLabel("Session Statistics:") stats_label.setFont(QFont("Arial", 10, QFont.Bold)) stats_label.setStyleSheet("color: #2c3e50; margin-top: 10px;") content_layout.addWidget(stats_label) # Statistics labels self.stats_labels = {} stats = [ ("tests", "• Tests Completed: 0"), ("avg_time", "• Average Processing: 0.00s"), ("accuracy", "• Classification Accuracy: --"), ("ripe_count", "• Ripe Fruits Detected: 0 (0.0%)"), ("duration", "• Session Duration: 0h 0m") ] for key, text in stats: label = QLabel(text) label.setFont(QFont("Arial", 10)) label.setStyleSheet("color: #2c3e50; line-height: 1.4;") self.stats_labels[key] = label content_layout.addWidget(label) content_layout.addStretch() layout.addWidget(header) layout.addWidget(content) def _create_snapshot_btn(self, text: str, color: str) -> QPushButton: """Create a snapshot button with specific color.""" btn = QPushButton(text) btn.setFont(QFont("Arial", 8, QFont.Bold)) btn.setFixedHeight(26) btn.setStyleSheet(f""" QPushButton {{ background-color: {color}; border: 1px solid {color}; color: white; }} QPushButton:hover {{ opacity: 0.9; }} QPushButton:disabled {{ background-color: #95a5a6; border-color: #7f8c8d; }} """) return btn def add_test_result(self, classification: str, confidence: float, processing_time: float): """ Add a new test result to the timeline. Args: classification: Classification result confidence: Confidence percentage (0-100) processing_time: Processing time in seconds """ self.test_counter += 1 timestamp = datetime.now().strftime("%H:%M:%S") entry = TimelineEntry( self.test_counter, timestamp, classification, confidence, processing_time ) # Insert at the top (most recent first) self.timeline_layout.insertWidget(0, entry) # Keep only last 10 entries while self.timeline_layout.count() > 11: # 10 + stretch item = self.timeline_layout.takeAt(10) if item.widget(): item.widget().deleteLater() # Enable save audio button self.save_audio_btn.setEnabled(True) def update_statistics(self, total_tests: int, avg_processing: float, ripe_count: int, session_start: datetime = None): """ Update session statistics. Args: total_tests: Total number of tests avg_processing: Average processing time ripe_count: Number of ripe classifications session_start: Session start datetime """ self.stats_labels["tests"].setText(f"• Tests Completed: {total_tests}") self.stats_labels["avg_time"].setText(f"• Average Processing: {avg_processing:.2f}s") ripe_percentage = (ripe_count / total_tests * 100) if total_tests > 0 else 0 self.stats_labels["ripe_count"].setText( f"• Ripe Fruits Detected: {ripe_count} ({ripe_percentage:.1f}%)" ) if session_start: duration = datetime.now() - session_start hours = duration.seconds // 3600 minutes = (duration.seconds % 3600) // 60 self.stats_labels["duration"].setText(f"• Session Duration: {hours}h {minutes}m") def clear_timeline(self): """Clear all timeline entries.""" while self.timeline_layout.count() > 1: # Keep stretch item = self.timeline_layout.takeAt(0) if item.widget(): item.widget().deleteLater() self.test_counter = 0 self.save_audio_btn.setEnabled(False)