""" Settings View - Application configuration and preferences Manages all application settings, model configurations, and system preferences. """ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QGroupBox, QGridLayout, QFrame, QScrollArea, QTabWidget, QLineEdit, QSpinBox, QDoubleSpinBox, QComboBox, QCheckBox, QSlider, QTextEdit, QFileDialog, QMessageBox, QProgressBar, QFormLayout, QButtonGroup, QRadioButton ) from PySide6.QtCore import Qt, Signal, Slot, QTimer, QSettings, QThread from PySide6.QtGui import QFont, QPixmap import os import json import sys from pathlib import Path # Import finale components from ..styles import FinaleStyles, MaterialColors from ..icons import FinaleIcons from qt_app_pyside.ui.config_panel import ConfigPanel from qt_app_pyside.utils.helpers import load_configuration, save_configuration from qt_app_pyside.utils.helpers import format_timestamp, format_duration class ModelConfigWidget(QGroupBox): """ Widget for configuring AI models and detection parameters. """ config_changed = Signal(dict) def __init__(self, parent=None): super().__init__("AI Model Configuration", parent) self.setup_ui() def setup_ui(self): """Setup model configuration UI""" layout = QFormLayout(self) # Vehicle detection model self.vehicle_model_edit = QLineEdit() self.vehicle_model_edit.setPlaceholderText("Path to vehicle detection model...") vehicle_browse_btn = QPushButton(FinaleIcons.get_icon("folder"), "") vehicle_browse_btn.setFixedSize(32, 32) vehicle_browse_btn.clicked.connect(lambda: self.browse_model("vehicle")) vehicle_layout = QHBoxLayout() vehicle_layout.addWidget(self.vehicle_model_edit) vehicle_layout.addWidget(vehicle_browse_btn) layout.addRow("Vehicle Model:", vehicle_layout) # Traffic light detection model self.traffic_model_edit = QLineEdit() self.traffic_model_edit.setPlaceholderText("Path to traffic light model...") traffic_browse_btn = QPushButton(FinaleIcons.get_icon("folder"), "") traffic_browse_btn.setFixedSize(32, 32) traffic_browse_btn.clicked.connect(lambda: self.browse_model("traffic")) traffic_layout = QHBoxLayout() traffic_layout.addWidget(self.traffic_model_edit) traffic_layout.addWidget(traffic_browse_btn) layout.addRow("Traffic Light Model:", traffic_layout) # Detection parameters self.confidence_spin = QDoubleSpinBox() self.confidence_spin.setRange(0.1, 1.0) self.confidence_spin.setSingleStep(0.05) self.confidence_spin.setValue(0.3) self.confidence_spin.setSuffix(" (30%)") layout.addRow("Confidence Threshold:", self.confidence_spin) self.nms_spin = QDoubleSpinBox() self.nms_spin.setRange(0.1, 1.0) self.nms_spin.setSingleStep(0.05) self.nms_spin.setValue(0.45) layout.addRow("NMS Threshold:", self.nms_spin) self.max_detections_spin = QSpinBox() self.max_detections_spin.setRange(10, 1000) self.max_detections_spin.setValue(100) layout.addRow("Max Detections:", self.max_detections_spin) # Device selection self.device_combo = QComboBox() self.device_combo.addItems(["CPU", "GPU", "AUTO"]) layout.addRow("Device:", self.device_combo) # Model optimization self.optimize_check = QCheckBox("Enable Model Optimization") self.optimize_check.setChecked(True) layout.addRow(self.optimize_check) # Apply styling self.setStyleSheet(FinaleStyles.get_group_box_style()) @Slot() def browse_model(self, model_type): """Browse for model file""" file_path, _ = QFileDialog.getOpenFileName( self, f"Select {model_type.title()} Model", "", "Model Files (*.xml *.onnx *.pt *.bin);;All Files (*)" ) if file_path: if model_type == "vehicle": self.vehicle_model_edit.setText(file_path) elif model_type == "traffic": self.traffic_model_edit.setText(file_path) def get_config(self): """Get current model configuration""" return { 'vehicle_model': self.vehicle_model_edit.text(), 'traffic_model': self.traffic_model_edit.text(), 'confidence_threshold': self.confidence_spin.value(), 'nms_threshold': self.nms_spin.value(), 'max_detections': self.max_detections_spin.value(), 'device': self.device_combo.currentText(), 'optimize_model': self.optimize_check.isChecked() } def set_config(self, config): """Set model configuration""" self.vehicle_model_edit.setText(config.get('vehicle_model', '')) self.traffic_model_edit.setText(config.get('traffic_model', '')) self.confidence_spin.setValue(config.get('confidence_threshold', 0.3)) self.nms_spin.setValue(config.get('nms_threshold', 0.45)) self.max_detections_spin.setValue(config.get('max_detections', 100)) self.device_combo.setCurrentText(config.get('device', 'CPU')) self.optimize_check.setChecked(config.get('optimize_model', True)) class ViolationConfigWidget(QGroupBox): """ Widget for configuring violation detection parameters. """ def __init__(self, parent=None): super().__init__("Violation Detection", parent) self.setup_ui() def setup_ui(self): """Setup violation configuration UI""" layout = QFormLayout(self) # Red light violation self.red_light_check = QCheckBox("Enable Red Light Detection") self.red_light_check.setChecked(True) layout.addRow(self.red_light_check) self.red_light_sensitivity = QSlider(Qt.Horizontal) self.red_light_sensitivity.setRange(1, 10) self.red_light_sensitivity.setValue(5) layout.addRow("Red Light Sensitivity:", self.red_light_sensitivity) # Speed violation self.speed_check = QCheckBox("Enable Speed Detection") self.speed_check.setChecked(True) layout.addRow(self.speed_check) self.speed_limit_spin = QSpinBox() self.speed_limit_spin.setRange(10, 200) self.speed_limit_spin.setValue(50) self.speed_limit_spin.setSuffix(" km/h") layout.addRow("Speed Limit:", self.speed_limit_spin) self.speed_tolerance_spin = QSpinBox() self.speed_tolerance_spin.setRange(0, 20) self.speed_tolerance_spin.setValue(5) self.speed_tolerance_spin.setSuffix(" km/h") layout.addRow("Speed Tolerance:", self.speed_tolerance_spin) # Wrong lane detection self.wrong_lane_check = QCheckBox("Enable Wrong Lane Detection") self.wrong_lane_check.setChecked(True) layout.addRow(self.wrong_lane_check) # Helmet detection self.helmet_check = QCheckBox("Enable Helmet Detection") self.helmet_check.setChecked(False) layout.addRow(self.helmet_check) # Violation zone setup self.zone_setup_btn = QPushButton(FinaleIcons.get_icon("map"), "Setup Violation Zones") layout.addRow(self.zone_setup_btn) # Apply styling self.setStyleSheet(FinaleStyles.get_group_box_style()) class UIPreferencesWidget(QGroupBox): """ Widget for UI preferences and appearance settings. """ theme_changed = Signal(bool) # dark_mode def __init__(self, parent=None): super().__init__("User Interface", parent) self.setup_ui() def setup_ui(self): """Setup UI preferences""" layout = QFormLayout(self) # Theme selection theme_group = QButtonGroup(self) self.dark_radio = QRadioButton("Dark Theme") self.light_radio = QRadioButton("Light Theme") self.auto_radio = QRadioButton("Auto (System)") self.dark_radio.setChecked(True) # Default to dark theme_group.addButton(self.dark_radio) theme_group.addButton(self.light_radio) theme_group.addButton(self.auto_radio) theme_layout = QVBoxLayout() theme_layout.addWidget(self.dark_radio) theme_layout.addWidget(self.light_radio) theme_layout.addWidget(self.auto_radio) layout.addRow("Theme:", theme_layout) # Language selection self.language_combo = QComboBox() self.language_combo.addItems(["English", "Español", "Français", "Deutsch", "العربية"]) layout.addRow("Language:", self.language_combo) # Font size self.font_size_spin = QSpinBox() self.font_size_spin.setRange(8, 16) self.font_size_spin.setValue(9) layout.addRow("Font Size:", self.font_size_spin) # Animations self.animations_check = QCheckBox("Enable Animations") self.animations_check.setChecked(True) layout.addRow(self.animations_check) # Sound notifications self.sound_check = QCheckBox("Sound Notifications") self.sound_check.setChecked(True) layout.addRow(self.sound_check) # Auto-save self.autosave_check = QCheckBox("Auto-save Configuration") self.autosave_check.setChecked(True) layout.addRow(self.autosave_check) # Update interval self.update_interval_spin = QSpinBox() self.update_interval_spin.setRange(100, 5000) self.update_interval_spin.setValue(1000) self.update_interval_spin.setSuffix(" ms") layout.addRow("Update Interval:", self.update_interval_spin) # Connect theme signals self.dark_radio.toggled.connect(lambda checked: self.theme_changed.emit(True) if checked else None) self.light_radio.toggled.connect(lambda checked: self.theme_changed.emit(False) if checked else None) # Apply styling self.setStyleSheet(FinaleStyles.get_group_box_style()) class PerformanceWidget(QGroupBox): """ Widget for performance and system settings. """ def __init__(self, parent=None): super().__init__("Performance", parent) self.setup_ui() def setup_ui(self): """Setup performance settings""" layout = QFormLayout(self) # Processing threads self.threads_spin = QSpinBox() self.threads_spin.setRange(1, 16) self.threads_spin.setValue(4) layout.addRow("Processing Threads:", self.threads_spin) # Frame buffer size self.buffer_size_spin = QSpinBox() self.buffer_size_spin.setRange(1, 100) self.buffer_size_spin.setValue(10) layout.addRow("Frame Buffer Size:", self.buffer_size_spin) # Memory limit self.memory_limit_spin = QSpinBox() self.memory_limit_spin.setRange(512, 8192) self.memory_limit_spin.setValue(2048) self.memory_limit_spin.setSuffix(" MB") layout.addRow("Memory Limit:", self.memory_limit_spin) # GPU acceleration self.gpu_check = QCheckBox("Enable GPU Acceleration") self.gpu_check.setChecked(False) layout.addRow(self.gpu_check) # Performance mode self.performance_combo = QComboBox() self.performance_combo.addItems(["Balanced", "Performance", "Power Save"]) layout.addRow("Performance Mode:", self.performance_combo) # Logging level self.logging_combo = QComboBox() self.logging_combo.addItems(["DEBUG", "INFO", "WARNING", "ERROR"]) self.logging_combo.setCurrentText("INFO") layout.addRow("Logging Level:", self.logging_combo) # Apply styling self.setStyleSheet(FinaleStyles.get_group_box_style()) class DataManagementWidget(QGroupBox): """ Widget for data storage and export settings. """ def __init__(self, parent=None): super().__init__("Data Management", parent) self.setup_ui() def setup_ui(self): """Setup data management settings""" layout = QFormLayout(self) # Data directory self.data_dir_edit = QLineEdit() self.data_dir_edit.setPlaceholderText("Data storage directory...") data_browse_btn = QPushButton(FinaleIcons.get_icon("folder"), "") data_browse_btn.setFixedSize(32, 32) data_browse_btn.clicked.connect(self.browse_data_directory) data_layout = QHBoxLayout() data_layout.addWidget(self.data_dir_edit) data_layout.addWidget(data_browse_btn) layout.addRow("Data Directory:", data_layout) # Auto-export self.auto_export_check = QCheckBox("Auto-export Violations") layout.addRow(self.auto_export_check) # Export format self.export_format_combo = QComboBox() self.export_format_combo.addItems(["JSON", "CSV", "XML", "PDF"]) layout.addRow("Export Format:", self.export_format_combo) # Data retention self.retention_spin = QSpinBox() self.retention_spin.setRange(1, 365) self.retention_spin.setValue(30) self.retention_spin.setSuffix(" days") layout.addRow("Data Retention:", self.retention_spin) # Backup settings self.backup_check = QCheckBox("Enable Automatic Backup") layout.addRow(self.backup_check) self.backup_interval_combo = QComboBox() self.backup_interval_combo.addItems(["Daily", "Weekly", "Monthly"]) layout.addRow("Backup Interval:", self.backup_interval_combo) # Database cleanup cleanup_btn = QPushButton(FinaleIcons.get_icon("delete"), "Cleanup Old Data") layout.addRow(cleanup_btn) # Apply styling self.setStyleSheet(FinaleStyles.get_group_box_style()) @Slot() def browse_data_directory(self): """Browse for data directory""" directory = QFileDialog.getExistingDirectory( self, "Select Data Directory", self.data_dir_edit.text() ) if directory: self.data_dir_edit.setText(directory) class SettingsView(QWidget): """ Main settings view with tabbed configuration sections. """ settings_changed = Signal(dict) def __init__(self, parent=None): super().__init__(parent) self.config = load_configuration('config.json') # Add configuration panel from original self.config_panel = ConfigPanel() self.settings = QSettings("Finale", "TrafficMonitoring") self.setup_ui() self.load_settings() def setup_ui(self): """Setup the settings view UI""" layout = QVBoxLayout(self) layout.setContentsMargins(16, 16, 16, 16) layout.setSpacing(16) # Header header_layout = QHBoxLayout() title_label = QLabel("Settings") title_label.setFont(QFont("Segoe UI", 18, QFont.Bold)) # Action buttons self.reset_btn = QPushButton(FinaleIcons.get_icon("refresh"), "Reset to Defaults") self.reset_btn.clicked.connect(self.reset_to_defaults) self.export_btn = QPushButton(FinaleIcons.get_icon("export"), "Export Settings") self.export_btn.clicked.connect(self.export_settings) self.import_btn = QPushButton(FinaleIcons.get_icon("import"), "Import Settings") self.import_btn.clicked.connect(self.import_settings) header_layout.addWidget(title_label) header_layout.addStretch() header_layout.addWidget(self.reset_btn) header_layout.addWidget(self.export_btn) header_layout.addWidget(self.import_btn) layout.addLayout(header_layout) # Settings tabs self.tabs = QTabWidget() # Create configuration widgets self.model_config = ModelConfigWidget() self.violation_config = ViolationConfigWidget() self.ui_preferences = UIPreferencesWidget() self.performance_config = PerformanceWidget() self.data_management = DataManagementWidget() # Add tabs self.tabs.addTab(self.model_config, FinaleIcons.get_icon("model"), "AI Models") self.tabs.addTab(self.violation_config, FinaleIcons.get_icon("warning"), "Violations") self.tabs.addTab(self.ui_preferences, FinaleIcons.get_icon("palette"), "Interface") self.tabs.addTab(self.performance_config, FinaleIcons.get_icon("speed"), "Performance") self.tabs.addTab(self.data_management, FinaleIcons.get_icon("database"), "Data") # Style tabs self.tabs.setStyleSheet(FinaleStyles.get_tab_widget_style()) layout.addWidget(self.tabs, 1) # Bottom action bar action_layout = QHBoxLayout() self.apply_btn = QPushButton(FinaleIcons.get_icon("check"), "Apply") self.apply_btn.clicked.connect(self.apply_settings) self.save_btn = QPushButton(FinaleIcons.get_icon("save"), "Save") self.save_btn.clicked.connect(self.save_settings) self.cancel_btn = QPushButton(FinaleIcons.get_icon("close"), "Cancel") self.cancel_btn.clicked.connect(self.cancel_changes) action_layout.addStretch() action_layout.addWidget(self.apply_btn) action_layout.addWidget(self.save_btn) action_layout.addWidget(self.cancel_btn) layout.addLayout(action_layout) # Connect signals self.ui_preferences.theme_changed.connect(self.on_theme_changed) # Apply theme self.apply_theme(True) def load_settings(self): """Load settings from QSettings""" # Load model configuration model_config = { 'vehicle_model': self.settings.value('model/vehicle_model', ''), 'traffic_model': self.settings.value('model/traffic_model', ''), 'confidence_threshold': self.settings.value('model/confidence_threshold', 0.3, float), 'nms_threshold': self.settings.value('model/nms_threshold', 0.45, float), 'max_detections': self.settings.value('model/max_detections', 100, int), 'device': self.settings.value('model/device', 'CPU'), 'optimize_model': self.settings.value('model/optimize_model', True, bool) } self.model_config.set_config(model_config) # Load UI preferences dark_mode = self.settings.value('ui/dark_mode', True, bool) if dark_mode: self.ui_preferences.dark_radio.setChecked(True) else: self.ui_preferences.light_radio.setChecked(True) @Slot() def apply_settings(self): """Apply current settings""" settings_data = self.get_all_settings() self.settings_changed.emit(settings_data) @Slot() def save_settings(self): """Save settings to QSettings""" # Save model configuration model_config = self.model_config.get_config() for key, value in model_config.items(): self.settings.setValue(f'model/{key}', value) # Save UI preferences self.settings.setValue('ui/dark_mode', self.ui_preferences.dark_radio.isChecked()) # Sync settings self.settings.sync() QMessageBox.information(self, "Settings Saved", "Settings have been saved successfully.") save_configuration(settings_data, 'config.json') @Slot() def cancel_changes(self): """Cancel changes and reload settings""" self.load_settings() @Slot() def reset_to_defaults(self): """Reset all settings to defaults""" reply = QMessageBox.question( self, "Reset Settings", "Are you sure you want to reset all settings to defaults?", QMessageBox.Yes | QMessageBox.No ) if reply == QMessageBox.Yes: self.settings.clear() self.load_settings() @Slot() def export_settings(self): """Export settings to file""" file_path, _ = QFileDialog.getSaveFileName( self, "Export Settings", "", "JSON Files (*.json);;All Files (*)" ) if file_path: settings_data = self.get_all_settings() try: with open(file_path, 'w') as f: json.dump(settings_data, f, indent=2) QMessageBox.information(self, "Export Successful", "Settings exported successfully.") except Exception as e: QMessageBox.critical(self, "Export Error", f"Failed to export settings:\n{str(e)}") @Slot() def import_settings(self): """Import settings from file""" file_path, _ = QFileDialog.getOpenFileName( self, "Import Settings", "", "JSON Files (*.json);;All Files (*)" ) if file_path: try: with open(file_path, 'r') as f: settings_data = json.load(f) # Apply imported settings self.apply_imported_settings(settings_data) QMessageBox.information(self, "Import Successful", "Settings imported successfully.") except Exception as e: QMessageBox.critical(self, "Import Error", f"Failed to import settings:\n{str(e)}") def get_all_settings(self): """Get all current settings as dictionary""" return { 'model': self.model_config.get_config(), 'ui': { 'dark_mode': self.ui_preferences.dark_radio.isChecked(), 'language': self.ui_preferences.language_combo.currentText(), 'font_size': self.ui_preferences.font_size_spin.value(), 'animations': self.ui_preferences.animations_check.isChecked(), 'sound': self.ui_preferences.sound_check.isChecked() } } def apply_imported_settings(self, settings_data): """Apply imported settings data""" if 'model' in settings_data: self.model_config.set_config(settings_data['model']) if 'ui' in settings_data: ui_settings = settings_data['ui'] if 'dark_mode' in ui_settings: if ui_settings['dark_mode']: self.ui_preferences.dark_radio.setChecked(True) else: self.ui_preferences.light_radio.setChecked(True) @Slot(bool) def on_theme_changed(self, dark_mode): """Handle theme change""" self.apply_theme(dark_mode) def apply_theme(self, dark_mode=True): """Apply theme to the view""" if dark_mode: self.setStyleSheet(f""" QWidget {{ background-color: {MaterialColors.surface}; color: {MaterialColors.text_primary}; }} QPushButton {{ background-color: {MaterialColors.primary}; color: {MaterialColors.text_on_primary}; border: none; border-radius: 6px; padding: 8px 16px; }} QPushButton:hover {{ background-color: {MaterialColors.primary_variant}; }} """) def display_timestamp(self, ts): return format_timestamp(ts) def display_duration(self, seconds): return format_duration(seconds)